Skip to content

Commit

Permalink
Added back SigningKey::to_scalar and updated docs and tests
Browse files Browse the repository at this point in the history
  • Loading branch information
rozbb committed Nov 14, 2023
1 parent d80be5b commit 923b14d
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 7 deletions.
4 changes: 4 additions & 0 deletions ed25519-dalek/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

Entries are listed in reverse chronological order per undeprecated major series.

# Unreleased

* Add `SigningKey::to_scalar_bytes` for getting the unclamped scalar from signing key

# 2.x series

## 2.0.0
Expand Down
37 changes: 32 additions & 5 deletions ed25519-dalek/src/signing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -486,26 +486,53 @@ impl SigningKey {
/// Convert this signing key into a byte representation of a(n) (unreduced) Curve25519 scalar.
///
/// This can be used for performing X25519 Diffie-Hellman using Ed25519 keys. The bytes output
/// by this function are a valid secret key for the X25519 public key given by
/// `self.verifying_key().to_montgomery()`.
/// by this function are a valid corresponding [`StaticSecret`](https://docs.rs/x25519-dalek/2.0.0/x25519_dalek/struct.StaticSecret.html#impl-From%3C%5Bu8;+32%5D%3E-for-StaticSecret)
/// for the X25519 public key given by `self.verifying_key().to_montgomery()`.
///
/// # Note
///
/// We do NOT recommend this usage of a signing/verifying key. Signing keys are usually
/// We do NOT recommend using a signing/verifying key for encryption. Signing keys are usually
/// long-term keys, while keys used for key exchange should rather be ephemeral. If you can
/// help it, use a separate key for encryption.
///
/// 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_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.
// Per the spec, the ed25519 secret key sk is expanded to
// (scalar_bytes, hash_prefix) = SHA-512(sk)
// where the two outputs are both 32 bytes. scalar_bytes is what we return. Its clamped and
// reduced form is what we use for signing (see impl ExpandedSecretKey)
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
}

/// Convert this signing key into a Curve25519 scalar. This is computed by clamping and
/// reducing the output of [`Self::to_scalar_bytes`].
///
/// This can be used anywhere where a Curve25519 scalar is used as a private key, e.g., in
/// [`crypto_box`](https://docs.rs/crypto_box/0.9.1/crypto_box/struct.SecretKey.html#impl-From%3CScalar%3E-for-SecretKey).
///
/// # Note
///
/// We do NOT recommend using a signing/verifying key for encryption. Signing keys are usually
/// long-term keys, while keys used for key exchange should rather be ephemeral. If you can
/// help it, use a separate key for encryption.
///
/// 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 {
// Per the spec, the ed25519 secret key sk is expanded to
// (scalar_bytes, hash_prefix) = SHA-512(sk)
// where the two outputs are both 32 bytes. To use for signing, scalar_bytes must be
// clamped and reduced (see ExpandedSecretKey::from_bytes). We return the clamped and
// reduced form.
let hash = Sha512::default().chain_update(self.secret_key).finalize();
ExpandedSecretKey::from_bytes(hash.as_ref()).scalar
}
}

impl AsRef<VerifyingKey> for SigningKey {
Expand Down
19 changes: 17 additions & 2 deletions ed25519-dalek/tests/x25519.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! 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};
Expand All @@ -22,11 +23,25 @@ fn ed25519_to_x25519_dh() {
let x_static_secret_a = XStaticSecret::from(scalar_bytes_a);
let x_static_secret_b = XStaticSecret::from(scalar_bytes_b);

// Compute the secret scalars too
let scalar_a = ed_signing_key_a.to_scalar();
let scalar_b = ed_signing_key_b.to_scalar();

// 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],);
assert_eq!(scalar_bytes_a, &Sha512::digest(ed_secret_key_a)[..32]);
assert_eq!(scalar_bytes_b, &Sha512::digest(ed_secret_key_b)[..32]);

// Compare the scalar with the clamped and reduced scalar bytes
assert_eq!(
scalar_a,
Scalar::from_bytes_mod_order(clamp_integer(scalar_bytes_a))
);
assert_eq!(
scalar_b,
Scalar::from_bytes_mod_order(clamp_integer(scalar_bytes_b))
);

let x_public_key_a = XPublicKey::from(&x_static_secret_a);
let x_public_key_b = XPublicKey::from(&x_static_secret_b);
Expand Down

0 comments on commit 923b14d

Please sign in to comment.