From b8981a40f5de45ed8782b36d8f5a23e28d9099eb Mon Sep 17 00:00:00 2001 From: Arthur Gautier Date: Mon, 2 Jun 2025 21:43:25 -0700 Subject: [PATCH 1/2] ed448-goldilocks: reuse types from `ed448` --- Cargo.lock | 22 +++ ed448-goldilocks/Cargo.toml | 10 +- .../src/curve/edwards/extended.rs | 26 +-- ed448-goldilocks/src/sign/signature.rs | 173 ++---------------- ed448-goldilocks/src/sign/verifying_key.rs | 63 ++----- 5 files changed, 60 insertions(+), 234 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 39f29d385..3f775976c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -377,10 +377,23 @@ dependencies = [ "zeroize", ] +[[package]] +name = "ed448" +version = "0.5.0-pre.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24f3621b15eb4095d0e7c2502abd44291413b8a638f88dc08f1188993143332b" +dependencies = [ + "pkcs8", + "serde", + "serde_bytes", + "signature", +] + [[package]] name = "ed448-goldilocks" version = "0.14.0-pre.0" dependencies = [ + "ed448", "elliptic-curve", "hex", "hex-literal", @@ -1127,6 +1140,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_bytes" +version = "0.11.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8437fd221bde2d4ca316d61b90e337e9e702b3820b87d63caa9ba6c02bd06d96" +dependencies = [ + "serde", +] + [[package]] name = "serde_derive" version = "1.0.219" diff --git a/ed448-goldilocks/Cargo.toml b/ed448-goldilocks/Cargo.toml index fc1731927..ee1583e3b 100644 --- a/ed448-goldilocks/Cargo.toml +++ b/ed448-goldilocks/Cargo.toml @@ -18,6 +18,7 @@ This crate also includes signing and verifying of Ed448 signatures. [dependencies] crypto_signature = { version = "3.0.0-rc.0", default-features = false, features = ["digest", "rand_core"], optional = true, package = "signature" } elliptic-curve = { version = "0.14.0-rc.5", features = ["arithmetic", "hash2curve", "pkcs8"] } +ed448 = { version = "=0.5.0-pre.0", default-features = false, optional = true } rand_core = { version = "0.9", default-features = false } serdect = { version = "0.3.0", optional = true } sha3 = { version = "0.11.0-rc.0", default-features = false } @@ -25,13 +26,12 @@ subtle = { version = "2.6", default-features = false } [features] default = ["std", "signing", "pkcs8"] -alloc = ["crypto_signature/alloc", "elliptic-curve/alloc", "serdect/alloc"] +alloc = ["crypto_signature/alloc", "ed448?/alloc", "elliptic-curve/alloc", "serdect/alloc"] std = ["alloc"] - bits = ["elliptic-curve/bits"] -pkcs8 = ["elliptic-curve/pkcs8"] -signing = ["dep:crypto_signature"] -serde = ["dep:serdect"] +pkcs8 = ["ed448?/pkcs8", "elliptic-curve/pkcs8"] +signing = ["dep:crypto_signature", "ed448"] +serde = ["dep:serdect", "ed448?/serde_bytes"] [dev-dependencies] hex-literal = "1" diff --git a/ed448-goldilocks/src/curve/edwards/extended.rs b/ed448-goldilocks/src/curve/edwards/extended.rs index 6e32b6f5a..f9c708d11 100644 --- a/ed448-goldilocks/src/curve/edwards/extended.rs +++ b/ed448-goldilocks/src/curve/edwards/extended.rs @@ -148,7 +148,7 @@ impl TryFrom<&[u8]> for CompressedEdwardsY { fn try_from(value: &[u8]) -> Result { let bytes = ::try_from(value).map_err(|_| "Invalid length")?; - Self::try_from(&bytes) + Ok(CompressedEdwardsY(bytes)) } } @@ -173,24 +173,6 @@ impl From<&CompressedEdwardsY> for PointBytes { } } -impl TryFrom for CompressedEdwardsY { - type Error = &'static str; - - fn try_from(value: PointBytes) -> Result { - let pt = CompressedEdwardsY(value); - let _ = Option::::from(pt.decompress()).ok_or("Invalid point")?; - Ok(pt) - } -} - -impl TryFrom<&PointBytes> for CompressedEdwardsY { - type Error = &'static str; - - fn try_from(value: &PointBytes) -> Result { - Self::try_from(*value) - } -} - #[cfg(feature = "serde")] impl serdect::serde::Serialize for CompressedEdwardsY { fn serialize(&self, s: S) -> Result { @@ -210,6 +192,12 @@ impl<'de> serdect::serde::Deserialize<'de> for CompressedEdwardsY { } } +impl From for CompressedEdwardsY { + fn from(point: PointBytes) -> Self { + Self(point) + } +} + impl CompressedEdwardsY { /// The compressed generator point pub const GENERATOR: Self = Self([ diff --git a/ed448-goldilocks/src/sign/signature.rs b/ed448-goldilocks/src/sign/signature.rs index 5fafbd1ae..c57bacf17 100644 --- a/ed448-goldilocks/src/sign/signature.rs +++ b/ed448-goldilocks/src/sign/signature.rs @@ -1,160 +1,24 @@ use crate::*; -use elliptic_curve::Group; +use elliptic_curve::array::Array; -/// Ed448 signature as defined in [RFC8032 ยง 5.2.5] -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub struct Signature { - pub(crate) r: CompressedEdwardsY, - pub(crate) s: [u8; 57], -} - -impl Default for Signature { - fn default() -> Self { - Self { - r: CompressedEdwardsY::default(), - s: [0u8; 57], - } - } -} - -#[cfg(feature = "alloc")] -impl TryFrom> for Signature { - type Error = SigningError; - - fn try_from(value: Vec) -> Result { - Self::try_from(value.as_slice()) - } -} - -#[cfg(feature = "alloc")] -impl TryFrom<&Vec> for Signature { - type Error = SigningError; - - fn try_from(value: &Vec) -> Result { - Self::try_from(value.as_slice()) - } -} - -impl TryFrom<&[u8]> for Signature { - type Error = SigningError; - - fn try_from(value: &[u8]) -> Result { - if value.len() != SIGNATURE_LENGTH { - return Err(SigningError::InvalidSignatureLength); - } - - let mut bytes = [0u8; SIGNATURE_LENGTH]; - bytes.copy_from_slice(value); - Self::from_bytes(&bytes) - } -} - -#[cfg(feature = "alloc")] -impl TryFrom> for Signature { - type Error = SigningError; - - fn try_from(value: Box<[u8]>) -> Result { - Self::try_from(value.as_ref()) - } -} - -#[cfg(feature = "serde")] -impl serdect::serde::Serialize for Signature { - fn serialize(&self, s: S) -> Result - where - S: serdect::serde::Serializer, - { - serdect::array::serialize_hex_lower_or_bin(&self.to_bytes(), s) - } -} - -#[cfg(feature = "serde")] -impl<'de> serdect::serde::Deserialize<'de> for Signature { - fn deserialize(d: D) -> Result - where - D: serdect::serde::Deserializer<'de>, - { - let mut bytes = [0u8; SIGNATURE_LENGTH]; - serdect::array::deserialize_hex_or_bin(&mut bytes, d)?; - Signature::from_bytes(&bytes).map_err(serdect::serde::de::Error::custom) - } -} - -impl Signature { - /// Converts [`Signature`] to a byte array. - pub fn to_bytes(&self) -> [u8; SIGNATURE_LENGTH] { - let mut bytes = [0u8; SIGNATURE_LENGTH]; - bytes[..57].copy_from_slice(self.r.as_bytes()); - bytes[57..].copy_from_slice(&self.s); - bytes - } - - /// Converts a byte array to a [`Signature`]. - pub fn from_bytes(bytes: &[u8; SIGNATURE_LENGTH]) -> Result { - let mut r = [0u8; SECRET_KEY_LENGTH]; - r.copy_from_slice(&bytes[..SECRET_KEY_LENGTH]); - let mut s = [0u8; SECRET_KEY_LENGTH]; - s.copy_from_slice(&bytes[SECRET_KEY_LENGTH..]); - - let r = CompressedEdwardsY(r); - - let big_r = r.decompress(); - if big_r.is_none().into() { - return Err(SigningError::InvalidSignatureRComponent); - } - - let big_r = big_r.expect("big_r is not none"); - if big_r.is_identity().into() { - return Err(SigningError::InvalidSignatureRComponent); - } - - if s[56] != 0x00 { - return Err(SigningError::InvalidSignatureSComponent); - } - let s_bytes = ScalarBytes::from(s); - let ss = Scalar::from_canonical_bytes(&s_bytes); - - if ss.is_none().into() { - return Err(SigningError::InvalidSignatureSComponent); - } - let sc = ss.expect("ss is not none"); - if sc.is_zero().into() { - return Err(SigningError::InvalidSignatureSComponent); - } - - Ok(Self { r, s }) - } - - /// The `r` value of the signature. - pub fn r(&self) -> CompressedEdwardsY { - self.r - } - - /// The `s` value of the signature. - pub fn s(&self) -> &[u8; SECRET_KEY_LENGTH] { - &self.s - } -} +pub use ed448::Signature; impl From for Signature { fn from(inner: InnerSignature) -> Self { let mut s = [0u8; SECRET_KEY_LENGTH]; s.copy_from_slice(&inner.s.to_bytes_rfc_8032()); - Self { - r: inner.r.compress(), - s, - } + Self::from_components(inner.r.compress(), s) } } -impl TryFrom for InnerSignature { +impl TryFrom<&Signature> for InnerSignature { type Error = SigningError; - fn try_from(signature: Signature) -> Result { - let s_bytes = ScalarBytes::try_from(&signature.s[..]).expect("invalid length"); - let s = Option::from(Scalar::from_canonical_bytes(&s_bytes)) + fn try_from(signature: &Signature) -> Result { + let s_bytes: &Array = (signature.s_bytes()).into(); + let s = Option::from(Scalar::from_canonical_bytes(s_bytes)) .ok_or(SigningError::InvalidSignatureSComponent)?; - let r = Option::from(signature.r.decompress()) + let r = Option::from(CompressedEdwardsY::from(*signature.r_bytes()).decompress()) .ok_or(SigningError::InvalidSignatureRComponent)?; Ok(Self { r, s }) } @@ -165,21 +29,10 @@ pub(crate) struct InnerSignature { pub(crate) s: Scalar, } -#[cfg(feature = "serde")] -#[test] -fn serialization() { - use rand_chacha::ChaCha8Rng; - use rand_core::SeedableRng; - - let mut rng = ChaCha8Rng::from_seed([0u8; 32]); - let signing_key = super::SigningKey::generate(&mut rng); - let signature = signing_key.sign_raw(b"Hello, World!"); - - let bytes = serde_bare::to_vec(&signature).unwrap(); - let signature2: Signature = serde_bare::from_slice(&bytes).unwrap(); - assert_eq!(signature, signature2); +impl TryFrom for InnerSignature { + type Error = SigningError; - let string = serde_json::to_string(&signature).unwrap(); - let signature3: Signature = serde_json::from_str(&string).unwrap(); - assert_eq!(signature, signature3); + fn try_from(signature: Signature) -> Result { + Self::try_from(&signature) + } } diff --git a/ed448-goldilocks/src/sign/verifying_key.rs b/ed448-goldilocks/src/sign/verifying_key.rs index ce42317eb..961d26b4f 100644 --- a/ed448-goldilocks/src/sign/verifying_key.rs +++ b/ed448-goldilocks/src/sign/verifying_key.rs @@ -2,14 +2,15 @@ //! and adapted to mirror `ed25519-dalek`'s API. use crate::curve::edwards::extended::PointBytes; -use crate::sign::HASH_HEAD; +use crate::sign::{HASH_HEAD, InnerSignature}; use crate::{ - CompressedEdwardsY, Context, EdwardsPoint, PreHash, Scalar, ScalarBytes, Signature, - SigningError, WideScalarBytes, + CompressedEdwardsY, Context, EdwardsPoint, PreHash, Scalar, Signature, SigningError, + WideScalarBytes, }; -#[cfg(any(feature = "pkcs8", feature = "serde"))] +#[cfg(feature = "serde")] use crate::PUBLIC_KEY_LENGTH; + #[cfg(feature = "pkcs8")] use elliptic_curve::pkcs8; @@ -91,9 +92,7 @@ where } #[cfg(feature = "pkcs8")] -/// This type is primarily useful for decoding/encoding SPKI public key files (either DER or PEM) -#[derive(Debug, Clone, Copy, Eq, PartialEq)] -pub struct PublicKeyBytes(pub [u8; PUBLIC_KEY_LENGTH]); +pub use ed448::pkcs8::PublicKeyBytes; #[cfg(feature = "pkcs8")] impl TryFrom for VerifyingKey { @@ -120,37 +119,6 @@ impl From for PublicKeyBytes { } } -#[cfg(all(feature = "pkcs8", feature = "alloc"))] -impl pkcs8::EncodePublicKey for PublicKeyBytes { - fn to_public_key_der(&self) -> pkcs8::spki::Result { - pkcs8::SubjectPublicKeyInfoRef { - algorithm: super::ALGORITHM_ID, - subject_public_key: pkcs8::der::asn1::BitStringRef::new(0, &self.0)?, - } - .try_into() - } -} - -#[cfg(feature = "pkcs8")] -impl TryFrom> for PublicKeyBytes { - type Error = pkcs8::spki::Error; - - fn try_from(value: pkcs8::spki::SubjectPublicKeyInfoRef<'_>) -> Result { - value.algorithm.assert_algorithm_oid(super::ALGORITHM_OID)?; - - if value.algorithm.parameters.is_some() { - return Err(pkcs8::spki::Error::KeyMalformed); - } - - value - .subject_public_key - .as_bytes() - .ok_or(pkcs8::spki::Error::KeyMalformed)? - .try_into() - .map(Self) - .map_err(|_| pkcs8::spki::Error::KeyMalformed) - } -} #[cfg(all(feature = "alloc", feature = "pkcs8"))] impl pkcs8::EncodePublicKey for VerifyingKey { @@ -293,24 +261,19 @@ impl VerifyingKey { // `signature` should already be valid but check to make sure // Note that the scalar itself uses only 56 bytes; the extra // 57th byte must be 0x00. - if signature.s[56] != 0x00 { + if signature.s_bytes()[56] != 0x00 { return Err(SigningError::InvalidSignatureSComponent.into()); } if self.point.is_identity().into() { return Err(SigningError::InvalidPublicKeyBytes.into()); } - let r = Option::::from(signature.r.decompress()) - .ok_or(SigningError::InvalidSignatureRComponent)?; - if r.is_identity().into() { + let inner_signature = InnerSignature::try_from(signature)?; + if inner_signature.r.is_identity().into() { return Err(SigningError::InvalidSignatureRComponent.into()); } - let s_bytes = ScalarBytes::try_from(&signature.s[..]).expect("signature.s is 57 bytes"); - let s = Option::::from(Scalar::from_canonical_bytes(&s_bytes)) - .ok_or(SigningError::InvalidSignatureSComponent)?; - - if s.is_zero().into() { + if inner_signature.s.is_zero().into() { return Err(SigningError::InvalidSignatureSComponent.into()); } @@ -322,15 +285,15 @@ impl VerifyingKey { .chain([phflag]) .chain([clen]) .chain(ctx) - .chain(signature.r.as_bytes()) + .chain(signature.r_bytes()) .chain(self.compressed.as_bytes()) .chain(m) .finalize_xof(); reader.read(&mut bytes); let k = Scalar::from_bytes_mod_order_wide(&bytes); // Check the verification equation [S]B = R + [k]A. - let lhs = EdwardsPoint::GENERATOR * s; - let rhs = r + (self.point * k); + let lhs = EdwardsPoint::GENERATOR * inner_signature.s; + let rhs = inner_signature.r + (self.point * k); if lhs == rhs { Ok(()) } else { From 56a7603c209e0dc89b8145caf219156ceee623b4 Mon Sep 17 00:00:00 2001 From: Arthur Gautier Date: Wed, 28 May 2025 22:48:29 -0700 Subject: [PATCH 2/2] x448 --- ed448-goldilocks/src/lib.rs | 2 + ed448-goldilocks/src/x448.rs | 424 +++++++++++++++++++++++++++++++++++ 2 files changed, 426 insertions(+) create mode 100644 ed448-goldilocks/src/x448.rs diff --git a/ed448-goldilocks/src/lib.rs b/ed448-goldilocks/src/lib.rs index e1bf3f5e8..930c31e5b 100644 --- a/ed448-goldilocks/src/lib.rs +++ b/ed448-goldilocks/src/lib.rs @@ -48,6 +48,8 @@ pub(crate) mod ristretto; #[cfg(feature = "signing")] pub(crate) mod sign; +pub mod x448; + pub(crate) use field::{GOLDILOCKS_BASE_POINT, TWISTED_EDWARDS_BASE_POINT}; pub use curve::{ diff --git a/ed448-goldilocks/src/x448.rs b/ed448-goldilocks/src/x448.rs new file mode 100644 index 000000000..9cee1dde3 --- /dev/null +++ b/ed448-goldilocks/src/x448.rs @@ -0,0 +1,424 @@ +use crate::Scalar; +use crate::curve::MontgomeryPoint; +use rand_core::CryptoRng; + +/// Computes a Scalar according to RFC7748 +/// given a byte array of length 56 +impl From<[u8; 56]> for Secret { + fn from(arr: [u8; 56]) -> Secret { + let mut secret = Secret(arr); + secret.clamp(); + secret + } +} + +/// Given a Secret Key, compute the corresponding public key +/// using the generator specified in RFC7748 +/// XXX: Waiting for upstream PR to use pre-computation +impl From<&Secret> for PublicKey { + fn from(secret: &Secret) -> PublicKey { + let point = &MontgomeryPoint::GENERATOR * &Scalar::from_bytes(&secret.0); + PublicKey(point) + } +} + +/// A PublicKey is a point on Curve448. +#[derive(Debug, PartialEq, Eq, Copy, Clone)] +pub struct PublicKey(MontgomeryPoint); + +/// A Secret is a Scalar on Curve448. +#[derive(Copy, Clone)] +pub struct Secret([u8; 56]); + +#[cfg(feature = "zeroize")] +impl zeroize::Zeroize for Secret { + fn zeroize(&mut self) { + self.0.zeroize(); + } +} + +/// A SharedSecret is a point on Curve448. +/// This point is the result of a Diffie-Hellman key exchange. +pub struct SharedSecret(MontgomeryPoint); + +#[cfg(feature = "zeroize")] +impl zeroize::Zeroize for SharedSecret { + fn zeroize(&mut self) { + self.0.zeroize(); + } +} + +impl PublicKey { + /// Converts a bytes slice into a Public key + /// Returns None if: + /// - The length of the slice is not 56 + /// - The point is a low order point + pub fn from_bytes(bytes: &[u8]) -> Option { + let public_key = PublicKey::from_bytes_unchecked(bytes)?; + if public_key.0.is_low_order() { + return None; + } + Some(public_key) + } + /// Converts a bytes slice into a Public key + /// Returns None if: + /// - The length of the slice is not 56 + pub fn from_bytes_unchecked(bytes: &[u8]) -> Option { + // First check if we have 56 bytes + if bytes.len() != 56 { + return None; + } + + // Check if the point has low order + let arr = slice_to_array(bytes); + let point = MontgomeryPoint(arr); + + Some(PublicKey(point)) + } + + /// Converts a public key into a byte slice + pub fn as_bytes(&self) -> &[u8; 56] { + self.0.as_bytes() + } +} + +impl SharedSecret { + /// Converts a shared secret into a byte slice + pub fn as_bytes(&self) -> &[u8; 56] { + self.0.as_bytes() + } +} + +impl Secret { + /// Generate a x448 `Secret` key. + // Taken from dalek-x25519 + pub fn new(csprng: &mut T) -> Self + where + T: CryptoRng + ?Sized, + { + let mut bytes = [0u8; 56]; + + csprng.fill_bytes(&mut bytes); + + Secret::from(bytes) + } + + /// Clamps the secret key according to RFC7748 + fn clamp(&mut self) { + self.0[0] &= 252; + self.0[55] |= 128; + } + + /// Views a Secret as a Scalar + fn as_scalar(&self) -> Scalar { + Scalar::from_bytes(&self.0) + } + + /// Performs a Diffie-hellman key exchange between the secret key and an external public key + pub fn as_diffie_hellman(&self, public_key: &PublicKey) -> Option { + // Check if the point is one of the low order points + if public_key.0.is_low_order() { + return None; + } + let shared_key = &public_key.0 * &self.as_scalar(); + Some(SharedSecret(shared_key)) + } + + /// Performs a Diffie-hellman key exchange once between the secret key and an external public key + pub fn to_diffie_hellman(self, public_key: &PublicKey) -> Option { + self.as_diffie_hellman(public_key) + } + + /// Converts a byte slice into a secret and clamp + pub fn from_bytes(bytes: &[u8]) -> Option { + // First check if we have 56 bytes + if bytes.len() != 56 { + return None; + } + + let secret = Secret::from(slice_to_array(bytes)); + Some(secret) + } + + /// Converts a secret into a byte array + pub fn as_bytes(&self) -> &[u8; 56] { + &self.0 + } +} + +fn slice_to_array(bytes: &[u8]) -> [u8; 56] { + let mut array: [u8; 56] = [0; 56]; + array.copy_from_slice(bytes); + array +} + +/// A safe version of the x448 function defined in RFC448. +/// Currently, the only reason I can think of for using the raw function is FFI. +/// Option is FFI safe[1]. So we can still maintain that the invariant that +/// we do not return a low order point. +/// +/// [1]: +pub fn x448(scalar_bytes: [u8; 56], point_bytes: [u8; 56]) -> Option<[u8; 56]> { + let point = PublicKey::from_bytes(&point_bytes)?; + let scalar = Secret::from(scalar_bytes).as_scalar(); + Some((&point.0 * &scalar).0) +} +/// An unchecked version of the x448 function defined in RFC448 +/// No checks are made on the points. +pub fn x448_unchecked(scalar_bytes: [u8; 56], point_bytes: [u8; 56]) -> [u8; 56] { + let point = MontgomeryPoint(point_bytes); + let scalar = Secret::from(scalar_bytes).as_scalar(); + (&point * &scalar).0 +} + +pub const X448_BASEPOINT_BYTES: [u8; 56] = MontgomeryPoint::GENERATOR.0; + +#[cfg(test)] +mod test { + use super::*; + use rand_core::{OsRng, TryRngCore}; + #[test] + fn test_low_order() { + // These are also in ed448-goldilocks. We could export them, but I cannot see any use except for this test. + const LOW_A: MontgomeryPoint = MontgomeryPoint([ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ]); + const LOW_B: MontgomeryPoint = MontgomeryPoint([ + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ]); + const LOW_C: MontgomeryPoint = MontgomeryPoint([ + 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xfe, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + ]); + + // Notice, that this is the only way to add low order points into the system + // and this is not exposed to the user. The user will use `from_bytes` which will check for low order points. + let bad_key_a = PublicKey(LOW_A); + let checked_bad_key_a = PublicKey::from_bytes(&LOW_A.0); + assert!(checked_bad_key_a.is_none()); + + let bad_key_b = PublicKey(LOW_B); + let checked_bad_key_b = PublicKey::from_bytes(&LOW_B.0); + assert!(checked_bad_key_b.is_none()); + + let bad_key_c = PublicKey(LOW_C); + let checked_bad_key_c = PublicKey::from_bytes(&LOW_C.0); + assert!(checked_bad_key_c.is_none()); + + let bob_priv = Secret::new(&mut OsRng.unwrap_err()); + + // If for some reason, these low order points are added to the system + // The Diffie-Hellman key exchange for the honest party will return None. + let shared_bob = bob_priv.as_diffie_hellman(&bad_key_a); + assert!(shared_bob.is_none()); + + let shared_bob = bob_priv.as_diffie_hellman(&bad_key_b); + assert!(shared_bob.is_none()); + + let shared_bob = bob_priv.as_diffie_hellman(&bad_key_c); + assert!(shared_bob.is_none()); + } + + #[test] + fn test_random_dh() { + let alice_priv = Secret::new(&mut OsRng.unwrap_err()); + let alice_pub = PublicKey::from(&alice_priv); + + let bob_priv = Secret::new(&mut OsRng.unwrap_err()); + let bob_pub = PublicKey::from(&bob_priv); + + // Since Alice and Bob are both using the API correctly + // If by chance, a low order point is generated, the clamping function will + // remove it. + let low_order = alice_pub.0.is_low_order() || bob_pub.0.is_low_order(); + assert!(!low_order); + + // Both Alice and Bob perform the DH key exchange. + // As mentioned above, we unwrap because both Parties are using the API correctly. + let shared_alice = alice_priv.as_diffie_hellman(&bob_pub).unwrap(); + let shared_bob = bob_priv.as_diffie_hellman(&alice_pub).unwrap(); + + assert_eq!(shared_alice.as_bytes()[..], shared_bob.as_bytes()[..]); + } + + #[test] + fn test_rfc_test_vectors_alice_bob() { + let alice_priv = Secret::from_bytes(&[ + 0x9a, 0x8f, 0x49, 0x25, 0xd1, 0x51, 0x9f, 0x57, 0x75, 0xcf, 0x46, 0xb0, 0x4b, 0x58, + 0x0, 0xd4, 0xee, 0x9e, 0xe8, 0xba, 0xe8, 0xbc, 0x55, 0x65, 0xd4, 0x98, 0xc2, 0x8d, + 0xd9, 0xc9, 0xba, 0xf5, 0x74, 0xa9, 0x41, 0x97, 0x44, 0x89, 0x73, 0x91, 0x0, 0x63, + 0x82, 0xa6, 0xf1, 0x27, 0xab, 0x1d, 0x9a, 0xc2, 0xd8, 0xc0, 0xa5, 0x98, 0x72, 0x6b, + ]) + .unwrap(); + let got_alice_pub = PublicKey::from(&alice_priv); + + let expected_alice_pub = [ + 0x9b, 0x8, 0xf7, 0xcc, 0x31, 0xb7, 0xe3, 0xe6, 0x7d, 0x22, 0xd5, 0xae, 0xa1, 0x21, 0x7, + 0x4a, 0x27, 0x3b, 0xd2, 0xb8, 0x3d, 0xe0, 0x9c, 0x63, 0xfa, 0xa7, 0x3d, 0x2c, 0x22, + 0xc5, 0xd9, 0xbb, 0xc8, 0x36, 0x64, 0x72, 0x41, 0xd9, 0x53, 0xd4, 0xc, 0x5b, 0x12, + 0xda, 0x88, 0x12, 0xd, 0x53, 0x17, 0x7f, 0x80, 0xe5, 0x32, 0xc4, 0x1f, 0xa0, + ]; + assert_eq!(got_alice_pub.as_bytes()[..], expected_alice_pub[..]); + + let bob_priv = Secret::from_bytes(&[ + 0x1c, 0x30, 0x6a, 0x7a, 0xc2, 0xa0, 0xe2, 0xe0, 0x99, 0xb, 0x29, 0x44, 0x70, 0xcb, + 0xa3, 0x39, 0xe6, 0x45, 0x37, 0x72, 0xb0, 0x75, 0x81, 0x1d, 0x8f, 0xad, 0xd, 0x1d, + 0x69, 0x27, 0xc1, 0x20, 0xbb, 0x5e, 0xe8, 0x97, 0x2b, 0xd, 0x3e, 0x21, 0x37, 0x4c, + 0x9c, 0x92, 0x1b, 0x9, 0xd1, 0xb0, 0x36, 0x6f, 0x10, 0xb6, 0x51, 0x73, 0x99, 0x2d, + ]) + .unwrap(); + let got_bob_pub = PublicKey::from(&bob_priv); + + let expected_bob_pub = [ + 0x3e, 0xb7, 0xa8, 0x29, 0xb0, 0xcd, 0x20, 0xf5, 0xbc, 0xfc, 0xb, 0x59, 0x9b, 0x6f, + 0xec, 0xcf, 0x6d, 0xa4, 0x62, 0x71, 0x7, 0xbd, 0xb0, 0xd4, 0xf3, 0x45, 0xb4, 0x30, + 0x27, 0xd8, 0xb9, 0x72, 0xfc, 0x3e, 0x34, 0xfb, 0x42, 0x32, 0xa1, 0x3c, 0xa7, 0x6, + 0xdc, 0xb5, 0x7a, 0xec, 0x3d, 0xae, 0x7, 0xbd, 0xc1, 0xc6, 0x7b, 0xf3, 0x36, 0x9, + ]; + assert_eq!(got_bob_pub.as_bytes()[..], expected_bob_pub[..]); + + let bob_shared = bob_priv.to_diffie_hellman(&got_alice_pub).unwrap(); + let alice_shared = alice_priv.to_diffie_hellman(&got_bob_pub).unwrap(); + assert_eq!(bob_shared.as_bytes()[..], alice_shared.as_bytes()[..]); + + let expected_shared = [ + 0x7, 0xff, 0xf4, 0x18, 0x1a, 0xc6, 0xcc, 0x95, 0xec, 0x1c, 0x16, 0xa9, 0x4a, 0xf, 0x74, + 0xd1, 0x2d, 0xa2, 0x32, 0xce, 0x40, 0xa7, 0x75, 0x52, 0x28, 0x1d, 0x28, 0x2b, 0xb6, + 0xc, 0xb, 0x56, 0xfd, 0x24, 0x64, 0xc3, 0x35, 0x54, 0x39, 0x36, 0x52, 0x1c, 0x24, 0x40, + 0x30, 0x85, 0xd5, 0x9a, 0x44, 0x9a, 0x50, 0x37, 0x51, 0x4a, 0x87, 0x9d, + ]; + + assert_eq!(bob_shared.as_bytes()[..], expected_shared[..]); + } + + #[test] + fn test_rfc_test_vectors_fixed() { + struct Test { + secret: [u8; 56], + point: [u8; 56], + expected: [u8; 56], + } + + let test_vectors = [ + Test { + secret: [ + 0x3d, 0x26, 0x2f, 0xdd, 0xf9, 0xec, 0x8e, 0x88, 0x49, 0x52, 0x66, 0xfe, 0xa1, + 0x9a, 0x34, 0xd2, 0x88, 0x82, 0xac, 0xef, 0x4, 0x51, 0x4, 0xd0, 0xd1, 0xaa, + 0xe1, 0x21, 0x70, 0xa, 0x77, 0x9c, 0x98, 0x4c, 0x24, 0xf8, 0xcd, 0xd7, 0x8f, + 0xbf, 0xf4, 0x49, 0x43, 0xeb, 0xa3, 0x68, 0xf5, 0x4b, 0x29, 0x25, 0x9a, 0x4f, + 0x1c, 0x60, 0xa, 0xd3, + ], + point: [ + 0x6, 0xfc, 0xe6, 0x40, 0xfa, 0x34, 0x87, 0xbf, 0xda, 0x5f, 0x6c, 0xf2, 0xd5, + 0x26, 0x3f, 0x8a, 0xad, 0x88, 0x33, 0x4c, 0xbd, 0x7, 0x43, 0x7f, 0x2, 0xf, 0x8, + 0xf9, 0x81, 0x4d, 0xc0, 0x31, 0xdd, 0xbd, 0xc3, 0x8c, 0x19, 0xc6, 0xda, 0x25, + 0x83, 0xfa, 0x54, 0x29, 0xdb, 0x94, 0xad, 0xa1, 0x8a, 0xa7, 0xa7, 0xfb, 0x4e, + 0xf8, 0xa0, 0x86, + ], + expected: [ + 0xce, 0x3e, 0x4f, 0xf9, 0x5a, 0x60, 0xdc, 0x66, 0x97, 0xda, 0x1d, 0xb1, 0xd8, + 0x5e, 0x6a, 0xfb, 0xdf, 0x79, 0xb5, 0xa, 0x24, 0x12, 0xd7, 0x54, 0x6d, 0x5f, + 0x23, 0x9f, 0xe1, 0x4f, 0xba, 0xad, 0xeb, 0x44, 0x5f, 0xc6, 0x6a, 0x1, 0xb0, + 0x77, 0x9d, 0x98, 0x22, 0x39, 0x61, 0x11, 0x1e, 0x21, 0x76, 0x62, 0x82, 0xf7, + 0x3d, 0xd9, 0x6b, 0x6f, + ], + }, + Test { + secret: [ + 0x20, 0x3d, 0x49, 0x44, 0x28, 0xb8, 0x39, 0x93, 0x52, 0x66, 0x5d, 0xdc, 0xa4, + 0x2f, 0x9d, 0xe8, 0xfe, 0xf6, 0x0, 0x90, 0x8e, 0xd, 0x46, 0x1c, 0xb0, 0x21, + 0xf8, 0xc5, 0x38, 0x34, 0x5d, 0xd7, 0x7c, 0x3e, 0x48, 0x6, 0xe2, 0x5f, 0x46, + 0xd3, 0x31, 0x5c, 0x44, 0xe0, 0xa5, 0xb4, 0x37, 0x12, 0x82, 0xdd, 0x2c, 0x8d, + 0x5b, 0xe3, 0x9, 0x5f, + ], + point: [ + 0xf, 0xbc, 0xc2, 0xf9, 0x93, 0xcd, 0x56, 0xd3, 0x30, 0x5b, 0xb, 0x7d, 0x9e, + 0x55, 0xd4, 0xc1, 0xa8, 0xfb, 0x5d, 0xbb, 0x52, 0xf8, 0xe9, 0xa1, 0xe9, 0xb6, + 0x20, 0x1b, 0x16, 0x5d, 0x1, 0x58, 0x94, 0xe5, 0x6c, 0x4d, 0x35, 0x70, 0xbe, + 0xe5, 0x2f, 0xe2, 0x5, 0xe2, 0x8a, 0x78, 0xb9, 0x1c, 0xdf, 0xbd, 0xe7, 0x1c, + 0xe8, 0xd1, 0x57, 0xdb, + ], + expected: [ + 0x88, 0x4a, 0x2, 0x57, 0x62, 0x39, 0xff, 0x7a, 0x2f, 0x2f, 0x63, 0xb2, 0xdb, + 0x6a, 0x9f, 0xf3, 0x70, 0x47, 0xac, 0x13, 0x56, 0x8e, 0x1e, 0x30, 0xfe, 0x63, + 0xc4, 0xa7, 0xad, 0x1b, 0x3e, 0xe3, 0xa5, 0x70, 0xd, 0xf3, 0x43, 0x21, 0xd6, + 0x20, 0x77, 0xe6, 0x36, 0x33, 0xc5, 0x75, 0xc1, 0xc9, 0x54, 0x51, 0x4e, 0x99, + 0xda, 0x7c, 0x17, 0x9d, + ], + }, + ]; + + for vector in test_vectors { + let public_key = PublicKey::from_bytes(&vector.point).unwrap(); + let secret = Secret::from_bytes(&vector.secret).unwrap(); + + let got = secret.as_diffie_hellman(&public_key).unwrap(); + + assert_eq!(got.as_bytes()[..], vector.expected[..]) + } + } + + // This function is needed for the second set of test vectors in RFC7748 + fn swap(secret: &mut [u8; 56], public_key: &mut [u8; 56], result: &[u8; 56]) { + // set point to be the secret + *public_key = *secret; + // set the secret to be the result + *secret = *result; + } + + #[test] + #[ignore] + fn test_rfc_test_vectors_iteration() { + let one_iter = [ + 0x3f, 0x48, 0x2c, 0x8a, 0x9f, 0x19, 0xb0, 0x1e, 0x6c, 0x46, 0xee, 0x97, 0x11, 0xd9, + 0xdc, 0x14, 0xfd, 0x4b, 0xf6, 0x7a, 0xf3, 0x7, 0x65, 0xc2, 0xae, 0x2b, 0x84, 0x6a, + 0x4d, 0x23, 0xa8, 0xcd, 0xd, 0xb8, 0x97, 0x8, 0x62, 0x39, 0x49, 0x2c, 0xaf, 0x35, 0xb, + 0x51, 0xf8, 0x33, 0x86, 0x8b, 0x9b, 0xc2, 0xb3, 0xbc, 0xa9, 0xcf, 0x41, 0x13, + ]; + let one_k_iter = [ + 0xaa, 0x3b, 0x47, 0x49, 0xd5, 0x5b, 0x9d, 0xaf, 0x1e, 0x5b, 0x0, 0x28, 0x88, 0x26, + 0xc4, 0x67, 0x27, 0x4c, 0xe3, 0xeb, 0xbd, 0xd5, 0xc1, 0x7b, 0x97, 0x5e, 0x9, 0xd4, + 0xaf, 0x6c, 0x67, 0xcf, 0x10, 0xd0, 0x87, 0x20, 0x2d, 0xb8, 0x82, 0x86, 0xe2, 0xb7, + 0x9f, 0xce, 0xea, 0x3e, 0xc3, 0x53, 0xef, 0x54, 0xfa, 0xa2, 0x6e, 0x21, 0x9f, 0x38, + ]; + let one_mil_iter = [ + 0x7, 0x7f, 0x45, 0x36, 0x81, 0xca, 0xca, 0x36, 0x93, 0x19, 0x84, 0x20, 0xbb, 0xe5, + 0x15, 0xca, 0xe0, 0x0, 0x24, 0x72, 0x51, 0x9b, 0x3e, 0x67, 0x66, 0x1a, 0x7e, 0x89, + 0xca, 0xb9, 0x46, 0x95, 0xc8, 0xf4, 0xbc, 0xd6, 0x6e, 0x61, 0xb9, 0xb9, 0xc9, 0x46, + 0xda, 0x8d, 0x52, 0x4d, 0xe3, 0xd6, 0x9b, 0xd9, 0xd9, 0xd6, 0x6b, 0x99, 0x7e, 0x37, + ]; + + let mut point = MontgomeryPoint::GENERATOR.0; + let mut scalar = MontgomeryPoint::GENERATOR.0; + let mut result = [0u8; 56]; + + // Iterate 1 time then check value on 1st iteration + for _ in 1..=1 { + result = x448(scalar, point).unwrap(); + swap(&mut scalar, &mut point, &result); + } + assert_eq!(&result[..], &one_iter[..]); + + // Iterate 999 times then check value on 1_000th iteration + for _ in 1..=999 { + result = x448(scalar, point).unwrap(); + swap(&mut scalar, &mut point, &result); + } + assert_eq!(&result[..], &one_k_iter[..]); + + // Iterate 999_000 times then check value on 1_000_000th iteration + for _ in 1..=999_000 { + result = x448(scalar, point).unwrap(); + swap(&mut scalar, &mut point, &result); + } + assert_eq!(&result[..], &one_mil_iter[..]); + } +}