在curve25519-dalek中X25519转Ed25519签名验证失败问题排查
X25519转Ed25519密钥不匹配问题排查
问题背景
仅持有X25519密钥对,需基于它创建并验证Ed25519签名,因此尝试将X25519公钥转为对应Ed25519验证密钥,同时转换X25519私钥为Ed25519签名密钥。参考公开实现基于curve25519-dalek库编写转换函数后,测试发现转换后的Ed25519验证密钥与私钥派生的验证密钥不匹配,签名验证失败。
原公钥转换函数
pub(crate) fn x25519_pub_to_ed25519_ver( public_key: [u8; 32], ) -> Result<VerifyingKey, SignatureError> { match MontgomeryPoint(public_key).to_edwards(SIGN_BIT) { None => Err(SignatureError::new()), Some(key) => VerifyingKey::from_bytes(key.compress().as_bytes()), } }
原私钥转换函数
pub(crate) fn x25519_priv_to_ed25519_sig(private_key: &StaticSecret) -> SigningKey { let clamped = clamp_integer(private_key.to_bytes()); let signing_key = SigningKey::from_bytes(&clamped); let test_public_key = signing_key.verifying_key().to_bytes(); if (test_public_key[31] & 0x80) >> 7 == SIGN_BIT { signing_key } else { let scalar = Scalar::from_bytes_mod_order(clamped); let neg = scalar.invert(); SigningKey::from_bytes(&neg.to_bytes()) } }
测试代码
const TEST_MESSAGE: &[u8] = b"Das Pferd frisst keinen Gurkensalat."; fn main() { let private_key = StaticSecret::random(); println!("X25519 private key: {:02x?}", private_key.to_bytes()); let public_key = PublicKey::from(&private_key); let signing_key = x25519_priv_to_ed25519_sig(&private_key); println!("Ed25519 signing key: {:02x?}", signing_key.to_bytes()); let signature = signing_key.sign(TEST_MESSAGE); let verifying_key = x25519_pub_to_ed25519_ver(public_key.to_bytes()).unwrap(); println!( "Transformed verifying key: {:02x?}", verifying_key.to_bytes() ); println!( "Real verifying key: {:02x?}", signing_key.verifying_key().to_bytes() ); let result = verifying_key.verify(TEST_MESSAGE, &signature); assert!(result.is_ok()); }
错误原因
私钥转换函数中取反逻辑完全错误:原代码使用scalar.invert()求标量的乘法逆元,但实际需要的是求标量的负元(即-scalar mod l,l为Curve25519的曲线阶)。
X25519私钥转Ed25519时,若派生公钥的符号位与转换公钥时指定的SIGN_BIT不匹配,需将私钥标量取负,这样对应的Edwards公钥也会取负,从而符号位匹配。逆元操作和取负的数学意义完全不同,直接导致私钥转换后对应的公钥与X25519公钥转换来的Edwards公钥不匹配。
额外注意:若自定义的clamp_integer未遵循X25519标准clamp规则(第0字节最低3位置0,第31字节最高位置0、次高位置1),也会引发密钥不匹配问题。
修正后的私钥转换函数
pub(crate) fn x25519_priv_to_ed25519_sig(private_key: &StaticSecret) -> SigningKey { let mut clamped = private_key.to_bytes(); // 执行X25519标准clamp操作 clamped[0] &= 0xF8; clamped[31] &= 0x7F; clamped[31] |= 0x40; let signing_key = SigningKey::from_bytes(&clamped); let test_pub = signing_key.verifying_key().to_bytes(); if (test_pub[31] & 0x80) >> 7 == SIGN_BIT { signing_key } else { // 计算标量负元而非逆元 let scalar = Scalar::from_bytes_mod_order(clamped); let neg_scalar = -scalar; SigningKey::from_bytes(&neg_scalar.to_bytes()) } }
验证逻辑说明
- X25519 Montgomery公钥转Edwards公钥时,
to_edwards(SIGN_BIT)强制设置Edwards点的符号位为指定值,这一步逻辑正确。 - 私钥转换时先做标准clamp,生成Ed25519签名密钥后检查公钥符号位,不匹配则取负私钥标量,确保转换后的公钥与X25519公钥转换得到的Edwards公钥完全一致,从而签名验证可通过。
内容的提问来源于stack exchange,提问作者user3035850
相关产品推荐
相关产品推荐

