mirror of
https://github.com/dalek-cryptography/ed25519-dalek.git
synced 2026-01-09 11:17:53 -05:00
Add Scalar and MontgomeryPoint conversions (#296)
* 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
This commit is contained in:
10
Cargo.lock
generated
10
Cargo.lock
generated
@@ -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"
|
||||
|
||||
@@ -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.
|
||||
@@ -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 {
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
@@ -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<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)
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
54
tests/x25519.rs
Normal file
54
tests/x25519.rs
Normal file
@@ -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
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user