Skip to content

Commit

Permalink
ed: Brought back to_scalar_bytes; added regression test
Browse files Browse the repository at this point in the history
  • Loading branch information
rozbb committed Nov 14, 2023
1 parent 6a45ca4 commit d80be5b
Show file tree
Hide file tree
Showing 2 changed files with 38 additions and 48 deletions.
23 changes: 7 additions & 16 deletions ed25519-dalek/src/signing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -498,8 +498,13 @@ impl SigningKey {
/// 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).
pub fn to_scalar(&self) -> Scalar {
ExpandedSecretKey::from(&self.secret_key).scalar
pub fn to_scalar_bytes(&self) -> [u8; 32] {
// The unreduced ed25519 scalar bytes are given by the first 32 bytes of the SHA-512 hash
// of the secret key.
let mut buf = [0u8; 32];
let scalar_and_hash_prefix = Sha512::default().chain_update(self.secret_key).finalize();
buf.copy_from_slice(&scalar_and_hash_prefix[..32]);
buf
}
}

Expand Down Expand Up @@ -876,17 +881,3 @@ impl ExpandedSecretKey {
Ok(InternalSignature { R, s }.into())
}
}

/// Tests the claim made in the comments of SigningKey::to_scalar, i.e., that the resulting scalar
/// is a valid private key for the x25519 pubkey represented by `verifying_key().to_montgomery()`
#[test]
fn privkey_scalar_yields_x25519_pubkey() {
let signing_privkey = SigningKey::generate(&mut rand::thread_rng());
let signing_pubkey = signing_privkey.verifying_key().to_montgomery();
let signing_scalar = signing_privkey.to_scalar();

let x_privkey = x25519_dalek::StaticSecret::from(signing_scalar.to_bytes());
let x_pubkey = x25519_dalek::PublicKey::from(&x_privkey);

assert_eq!(signing_pubkey.to_bytes(), x_pubkey.to_bytes());
}
63 changes: 31 additions & 32 deletions ed25519-dalek/tests/x25519.rs
Original file line number Diff line number Diff line change
@@ -1,66 +1,65 @@
//! Tests for converting Ed25519 keys into X25519 (Montgomery form) keys.

use curve25519_dalek::scalar::{clamp_integer, Scalar};
use ed25519_dalek::SigningKey;
use hex_literal::hex;
use sha2::{Digest, Sha512};

/// Helper function to return the bytes corresponding to the input bytes after being clamped and
/// reduced mod 2^255 - 19
fn clamp_and_reduce(bytes: &[u8]) -> [u8; 32] {
assert_eq!(bytes.len(), 32);
Scalar::from_bytes_mod_order(clamp_integer(bytes.try_into().unwrap())).to_bytes()
}
use x25519_dalek::{PublicKey as XPublicKey, StaticSecret as XStaticSecret};

/// 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 ed_secret_key_a = hex!("9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60");
let ed_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 ed_signing_key_a = SigningKey::from_bytes(&ed_secret_key_a);
let ed_signing_key_b = SigningKey::from_bytes(&ed_secret_key_b);

let scalar_a = ed25519_signing_key_a.to_scalar();
let scalar_b = ed25519_signing_key_b.to_scalar();
// Create an x25519 static secret from the ed25519 signing key
let scalar_bytes_a = ed_signing_key_a.to_scalar_bytes();
let scalar_bytes_b = ed_signing_key_b.to_scalar_bytes();
let x_static_secret_a = XStaticSecret::from(scalar_bytes_a);
let x_static_secret_b = XStaticSecret::from(scalar_bytes_b);

// Compare the scalar bytes to the first 32 bytes of SHA-512(secret_key). We have to clamp and
// reduce the SHA-512 output because that's what the spec does before using the scalars for
// anything.
assert_eq!(scalar_bytes_a, &Sha512::digest(ed_secret_key_a)[..32],);
assert_eq!(scalar_bytes_b, &Sha512::digest(ed_secret_key_b)[..32],);

let x_public_key_a = XPublicKey::from(&x_static_secret_a);
let x_public_key_b = XPublicKey::from(&x_static_secret_b);
assert_eq!(
scalar_a.to_bytes(),
clamp_and_reduce(&Sha512::digest(ed25519_secret_key_a)[..32]),
x_public_key_a.to_bytes(),
hex!("d85e07ec22b0ad881537c2f44d662d1a143cf830c57aca4305d85c7a90f6b62e")
);
assert_eq!(
scalar_b.to_bytes(),
clamp_and_reduce(&Sha512::digest(ed25519_secret_key_b)[..32]),
x_public_key_b.to_bytes(),
hex!("25c704c594b88afc00a76b69d1ed2b984d7e22550f3ed0802d04fbcd07d38d47")
);

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();

// Test the claim made in the comments of SigningKey::to_scalar_bytes, i.e., that the resulting
// scalar is a valid private key for the x25519 pubkey represented by
// `sk.verifying_key().to_montgomery()`
assert_eq!(
x25519_public_key_a.to_bytes(),
hex!("d85e07ec22b0ad881537c2f44d662d1a143cf830c57aca4305d85c7a90f6b62e")
ed_signing_key_a.verifying_key().to_montgomery().as_bytes(),
x_public_key_a.as_bytes()
);
assert_eq!(
x25519_public_key_b.to_bytes(),
hex!("25c704c594b88afc00a76b69d1ed2b984d7e22550f3ed0802d04fbcd07d38d47")
ed_signing_key_b.verifying_key().to_montgomery().as_bytes(),
x_public_key_b.as_bytes()
);

// Check that Diffie-Hellman works
let expected_shared_secret =
hex!("5166f24a6918368e2af831a4affadd97af0ac326bdf143596c045967cc00230e");

assert_eq!(
(x25519_public_key_a * scalar_b).to_bytes(),
expected_shared_secret
x_static_secret_a.diffie_hellman(&x_public_key_b).to_bytes(),
expected_shared_secret,
);
assert_eq!(
(x25519_public_key_b * scalar_a).to_bytes(),
expected_shared_secret
x_static_secret_b.diffie_hellman(&x_public_key_a).to_bytes(),
expected_shared_secret,
);
}

0 comments on commit d80be5b

Please sign in to comment.