Skip to content

Commit

Permalink
Add Scalar and MontgomeryPoint conversions (dalek-cryptography#296)
Browse files Browse the repository at this point in the history
* Add `Scalar` and `MontgomeryPoint` conversions

- Adds `SigningKey::to_scalar` to extract the private scalar
- Adds `VerifyingKey::to_montgomery` to map the verifying key's
  `EdwardsPoint` to a `MontgomeryPoint`
- Also adds corresponding `From<&T>` impls which call the inherent
  methods.

This is useful for systems which are keyed using Ed25519 keys which
would like to use X25519 for D-H. Having inherent methods means it's
possible to call these methods without having to import `Scalar` and
`MontgomeryPoint` from `curve25519-dalek`.

This is of course a bit circuitous: we could just multiply `Scalar` by
`EdwardsPoint` and use the resulting `EdwardsPoint` as the D-H shared
secret, however it seems many protocols have adopted this approach of
mapping to `MontgomeryPoint` and using that for the shared secret, since
X25519 is traditionally used for ECDH with Curve25519.

* Add reference to eprint 2021/509

* Basic X25519 Diffie-Hellman test
  • Loading branch information
tarcieri committed Mar 30, 2023
1 parent 5014c91 commit c8c9f29
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 14 deletions.
10 changes: 5 additions & 5 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 18 additions & 8 deletions src/signing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -305,9 +305,11 @@ impl SigningKey {
where
D: Digest<OutputSize = U64>,
{
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.
Expand Down Expand Up @@ -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<VerifyingKey> for SigningKey {
Expand Down Expand Up @@ -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()
}
}
Expand All @@ -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(),
}
}
Expand All @@ -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()
}
Expand Down Expand Up @@ -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())
}
Expand Down
18 changes: 17 additions & 1 deletion src/verifying.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -88,7 +89,7 @@ impl PartialEq<VerifyingKey> 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)
}
}
Expand Down Expand Up @@ -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<ed25519::Signature> for VerifyingKey {
Expand Down
54 changes: 54 additions & 0 deletions tests/x25519.rs
Original file line number Diff line number Diff line change
@@ -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
);
}

0 comments on commit c8c9f29

Please sign in to comment.