diff --git a/Cargo.lock b/Cargo.lock index 6c11e18c9..1eb6a42c4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -239,9 +239,9 @@ dependencies = [ [[package]] name = "curve25519-dalek" -version = "4.0.0-rc.1" +version = "4.0.0-rc.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d4ba9852b42210c7538b75484f9daa0655e9a3ac04f693747bb0f02cf3cfe16" +checksum = "03d928d978dbec61a1167414f5ec534f24bea0d7a0d24dd9b6233d3d8223e585" dependencies = [ "cfg-if", "digest", @@ -287,7 +287,7 @@ dependencies = [ [[package]] name = "ed25519-dalek" -version = "2.0.0-pre.0" +version = "2.0.0-rc.2" dependencies = [ "bincode", "criterion", @@ -314,9 +314,9 @@ checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" [[package]] name = "fiat-crypto" -version = "0.1.17" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a214f5bb88731d436478f3ae1f8a277b62124089ba9fb67f4f93fb100ef73c90" +checksum = "93ace6ec7cc19c8ed33a32eaa9ea692d7faea05006b5356b9e2b668ec4bc3955" [[package]] name = "generic-array" diff --git a/src/signing.rs b/src/signing.rs index fd59debe7..93084b886 100644 --- a/src/signing.rs +++ b/src/signing.rs @@ -305,9 +305,11 @@ impl SigningKey { where D: Digest, { - let expanded: ExpandedSecretKey = (&self.secret_key).into(); // xxx thanks i hate this - - expanded.sign_prehashed(prehashed_message, &self.verifying_key, context) + ExpandedSecretKey::from(&self.secret_key).sign_prehashed( + prehashed_message, + &self.verifying_key, + context, + ) } /// Verify a signature on a message with this signing key's public key. @@ -459,6 +461,14 @@ impl SigningKey { ) -> Result<(), SignatureError> { self.verifying_key.verify_strict(message, signature) } + + /// Convert this signing key into a Curve25519 scalar. + /// + /// This is useful for e.g. performing X25519 Diffie-Hellman using + /// Ed25519 keys. + pub fn to_scalar(&self) -> Scalar { + ExpandedSecretKey::from(&self.secret_key).scalar + } } impl AsRef for SigningKey { @@ -726,14 +736,14 @@ impl<'d> Deserialize<'d> for SigningKey { // better-designed, Schnorr-based signature scheme, see Trevor Perrin's work on // "generalised EdDSA" and "VXEdDSA". pub(crate) struct ExpandedSecretKey { - pub(crate) key: Scalar, + pub(crate) scalar: Scalar, pub(crate) nonce: [u8; 32], } #[cfg(feature = "zeroize")] impl Drop for ExpandedSecretKey { fn drop(&mut self) { - self.key.zeroize(); + self.scalar.zeroize(); self.nonce.zeroize() } } @@ -747,7 +757,7 @@ impl From<&SecretKey> for ExpandedSecretKey { // The try_into here converts to fixed-size array ExpandedSecretKey { - key: Scalar::from_bits_clamped(lower.try_into().unwrap()), + scalar: Scalar::from_bits_clamped(lower.try_into().unwrap()), nonce: upper.try_into().unwrap(), } } @@ -771,7 +781,7 @@ impl ExpandedSecretKey { h.update(message); let k = Scalar::from_hash(h); - let s: Scalar = (k * self.key) + r; + let s: Scalar = (k * self.scalar) + r; InternalSignature { R, s }.into() } @@ -854,7 +864,7 @@ impl ExpandedSecretKey { .chain_update(&prehash[..]); let k = Scalar::from_hash(h); - let s: Scalar = (k * self.key) + r; + let s: Scalar = (k * self.scalar) + r; Ok(InternalSignature { R, s }.into()) } diff --git a/src/verifying.rs b/src/verifying.rs index 6b0ad4988..7de4fd13d 100644 --- a/src/verifying.rs +++ b/src/verifying.rs @@ -18,6 +18,7 @@ use curve25519_dalek::digest::generic_array::typenum::U64; use curve25519_dalek::digest::Digest; use curve25519_dalek::edwards::CompressedEdwardsY; use curve25519_dalek::edwards::EdwardsPoint; +use curve25519_dalek::montgomery::MontgomeryPoint; use curve25519_dalek::scalar::Scalar; use ed25519::signature::Verifier; @@ -88,7 +89,7 @@ impl PartialEq for VerifyingKey { impl From<&ExpandedSecretKey> for VerifyingKey { /// Derive this public key from its corresponding `ExpandedSecretKey`. fn from(expanded_secret_key: &ExpandedSecretKey) -> VerifyingKey { - let bits: [u8; 32] = expanded_secret_key.key.to_bytes(); + let bits: [u8; 32] = expanded_secret_key.scalar.to_bytes(); VerifyingKey::clamp_and_mul_base(bits) } } @@ -418,6 +419,21 @@ impl VerifyingKey { Err(InternalError::Verify.into()) } } + + /// Convert this verifying key into Montgomery form. + /// + /// This is useful for systems which perform X25519 Diffie-Hellman using + /// Ed25519 keys. + /// + /// When possible, it's recommended to use separate keys for signing and + /// Diffie-Hellman. + /// + /// For more information on the security of systems which use the same keys + /// for both signing and Diffie-Hellman, see the paper + /// [On using the same key pair for Ed25519 and an X25519 based KEM](https://eprint.iacr.org/2021/509.pdf). + pub fn to_montgomery(&self) -> MontgomeryPoint { + self.point.to_montgomery() + } } impl Verifier for VerifyingKey { diff --git a/tests/x25519.rs b/tests/x25519.rs new file mode 100644 index 000000000..bb588f76c --- /dev/null +++ b/tests/x25519.rs @@ -0,0 +1,54 @@ +//! Tests for converting Ed25519 keys into X25519 (Montgomery form) keys. + +use ed25519_dalek::SigningKey; +use hex_literal::hex; + +/// Tests that X25519 Diffie-Hellman works when using keys converted from Ed25519. +// TODO: generate test vectors using another implementation of Ed25519->X25519 +#[test] +fn ed25519_to_x25519_dh() { + // Keys from RFC8032 test vectors (from section 7.1) + let ed25519_secret_key_a = + hex!("9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60"); + let ed25519_secret_key_b = + hex!("4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb"); + + let ed25519_signing_key_a = SigningKey::from_bytes(&ed25519_secret_key_a); + let ed25519_signing_key_b = SigningKey::from_bytes(&ed25519_secret_key_b); + + let scalar_a = ed25519_signing_key_a.to_scalar(); + let scalar_b = ed25519_signing_key_b.to_scalar(); + + assert_eq!( + scalar_a.to_bytes(), + hex!("307c83864f2833cb427a2ef1c00a013cfdff2768d980c0a3a520f006904de94f") + ); + assert_eq!( + scalar_b.to_bytes(), + hex!("68bd9ed75882d52815a97585caf4790a7f6c6b3b7f821c5e259a24b02e502e51") + ); + + let x25519_public_key_a = ed25519_signing_key_a.verifying_key().to_montgomery(); + let x25519_public_key_b = ed25519_signing_key_b.verifying_key().to_montgomery(); + + assert_eq!( + x25519_public_key_a.to_bytes(), + hex!("d85e07ec22b0ad881537c2f44d662d1a143cf830c57aca4305d85c7a90f6b62e") + ); + assert_eq!( + x25519_public_key_b.to_bytes(), + hex!("25c704c594b88afc00a76b69d1ed2b984d7e22550f3ed0802d04fbcd07d38d47") + ); + + let expected_shared_secret = + hex!("5166f24a6918368e2af831a4affadd97af0ac326bdf143596c045967cc00230e"); + + assert_eq!( + (x25519_public_key_a * scalar_b).to_bytes(), + expected_shared_secret + ); + assert_eq!( + (x25519_public_key_b * scalar_a).to_bytes(), + expected_shared_secret + ); +}