// MIT License // Copyright (c) 2021-2023 The orion Developers // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. //! # Parameters: //! - `private_key`: The private key used in key agreement. //! - `public_key`: The public key used in key agreement. //! //! # Errors: //! An error will be returned if: //! - The `key_agreement()` operation results in an all-zero output. //! //! # Security: //! - Multiple different `private_key`/`public_key` pairs can produce the same shared key. Therefore, //! using the resulting `SharedKey`, directly from `key_agreement()`, is not recommended. This is handled //! automatically in [`orion::kex`]. //! - To securely generate a strong key, use [`PrivateKey::generate()`]. //! //! # Recommendation: //! - It is recommended to use [`orion::kex`] when possible. //! //! # Example: //! ```rust //! # #[cfg(feature = "safe_api")] { //! use orion::hazardous::ecc::x25519::{PrivateKey, PublicKey, SharedKey, key_agreement}; //! use core::convert::TryFrom; //! //! // Alice generates a private key and computes the corresponding public key //! let alice_sk = PrivateKey::generate(); //! let alice_pk = PublicKey::try_from(&alice_sk)?; //! //! // Bob does the same //! let bob_sk = PrivateKey::generate(); //! let bob_pk = PublicKey::try_from(&bob_sk)?; //! //! // They both compute a shared key using the others public key //! let alice_shared = key_agreement(&alice_sk, &bob_pk)?; //! let bob_shared = key_agreement(&bob_sk, &alice_pk)?; //! //! assert_eq!(alice_shared, bob_shared); //! # } //! # Ok::<(), orion::errors::UnknownCryptoError>(()) //! ``` //! [`PrivateKey::generate()`]: crate::hazardous::ecc::x25519::PrivateKey::generate //! [`orion::kex`]: crate::kex use crate::errors::UnknownCryptoError; use crate::util::secure_cmp; use core::ops::{Add, Mul, Sub}; /// Formally verified Curve25519 field arithmetic from: . use fiat_crypto::curve25519_64 as fiat_curve25519_u64; use fiat_curve25519_u64::{ fiat_25519_add, fiat_25519_carry, fiat_25519_carry_mul, fiat_25519_carry_scmul_121666, fiat_25519_carry_square, fiat_25519_loose_field_element, fiat_25519_relax, fiat_25519_sub, fiat_25519_tight_field_element, }; /// The size of a public key used in X25519. pub const PUBLIC_KEY_SIZE: usize = 32; /// The size of a private key used in X25519. pub const PRIVATE_KEY_SIZE: usize = 32; /// The size of a shared key used in X25519. pub const SHARED_KEY_SIZE: usize = 32; /// u-coordinate of the base point. const BASEPOINT: [u8; 32] = [ 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ]; /// The result of computing a shared secret with a low order point. const LOW_ORDER_POINT_RESULT: [u8; 32] = [0u8; 32]; #[derive(Clone, Copy)] /// Represent an element in the curve field. struct FieldElement(fiat_25519_tight_field_element); impl Eq for FieldElement {} impl PartialEq for FieldElement { fn eq(&self, other: &Self) -> bool { use subtle::ConstantTimeEq; self.as_bytes().ct_eq(&other.as_bytes()).into() } } impl core::fmt::Debug for FieldElement { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!(f, "FieldElement({:?})", &self.0 .0) } } /// The function fiat_25519_carry_mul multiplies two field elements and reduces the result. impl Mul for FieldElement { type Output = Self; fn mul(self, rhs: Self) -> Self::Output { let mut ret = fiat_25519_tight_field_element([0u64; 5]); let mut self_relaxed = fiat_25519_loose_field_element([0u64; 5]); let mut rhs_relaxed = fiat_25519_loose_field_element([0u64; 5]); fiat_25519_relax(&mut self_relaxed, &self.0); fiat_25519_relax(&mut rhs_relaxed, &rhs.0); fiat_25519_carry_mul(&mut ret, &self_relaxed, &rhs_relaxed); Self(ret) } } /// The function fiat_25519_add adds two field elements. impl Add for FieldElement { type Output = Self; fn add(self, rhs: Self) -> Self::Output { let mut ret = fiat_25519_tight_field_element([0u64; 5]); let mut ret_add = fiat_25519_loose_field_element([0u64; 5]); fiat_25519_add(&mut ret_add, &self.0, &rhs.0); fiat_25519_carry(&mut ret, &ret_add); Self(ret) } } /// The function fiat_25519_sub subtracts two field elements. impl Sub for FieldElement { type Output = Self; fn sub(self, rhs: Self) -> Self::Output { let mut ret = fiat_25519_tight_field_element([0u64; 5]); let mut ret_sub = fiat_25519_loose_field_element([0u64; 5]); fiat_25519_sub(&mut ret_sub, &self.0, &rhs.0); fiat_25519_carry(&mut ret, &ret_sub); Self(ret) } } impl FieldElement { /// Create a `FieldElement` that is `0`. fn zero() -> Self { Self(fiat_25519_tight_field_element([ 0u64, 0u64, 0u64, 0u64, 0u64, ])) } /// Create a `FieldElement` that is `1`. fn one() -> Self { Self(fiat_25519_tight_field_element([ 1u64, 0u64, 0u64, 0u64, 0u64, ])) } /// Serialize the `FieldElement` as a byte-array. fn as_bytes(&self) -> [u8; 32] { // The function fiat_25519_to_bytes serializes a field element to bytes in little-endian order. use fiat_curve25519_u64::fiat_25519_to_bytes; let mut ret = [0u8; 32]; fiat_25519_to_bytes(&mut ret, &self.0); ret } /// Deserialize the `FieldElement` from a byte-array in little-endian. /// /// Masks the MSB in the final byte of the input bytes. fn from_bytes(bytes: &[u8; 32]) -> Self { // The function fiat_25519_from_bytes deserializes a field element from bytes in little-endian order use fiat_curve25519_u64::fiat_25519_from_bytes; let mut temp = [0u8; 32]; temp.copy_from_slice(bytes); temp[31] &= 127u8; // See RFC: "When receiving such an array, implementations of X25519 // (but not X448) MUST mask the most significant bit in the final byte." let mut ret = fiat_25519_tight_field_element([0u64; 5]); fiat_25519_from_bytes(&mut ret, &temp); Self(ret) } /// A conditional-swap operation. fn conditional_swap(swap: u8, a: &mut Self, b: &mut Self) { // The function fiat_25519_selectznz is a multi-limb conditional select. use fiat_curve25519_u64::fiat_25519_selectznz; // SAFETY: This is a part of fiat input bounds. debug_assert!(swap == 1 || swap == 0); let tmp_a = *a; let tmp_b = *b; fiat_25519_selectznz(&mut a.0 .0, swap, &tmp_a.0 .0, &tmp_b.0 .0); fiat_25519_selectznz(&mut b.0 .0, swap, &tmp_b.0 .0, &tmp_a.0 .0); } /// Square the `FieldElement` and reduce the result. fn square(&self) -> Self { let mut self_relaxed = fiat_25519_loose_field_element([0u64; 5]); let mut ret = fiat_25519_tight_field_element([0u64; 5]); fiat_25519_relax(&mut self_relaxed, &self.0); fiat_25519_carry_square(&mut ret, &self_relaxed); Self(ret) } /// Multiply the `FieldElement` by 121666 and reduce the result. fn mul_121666(&self) -> Self { let mut self_relaxed = fiat_25519_loose_field_element([0u64; 5]); let mut ret = fiat_25519_tight_field_element([0u64; 5]); fiat_25519_relax(&mut self_relaxed, &self.0); fiat_25519_carry_scmul_121666(&mut ret, &self_relaxed); Self(ret) } /// Compute the multiplicative inverse of the `FieldElement`. /// /// Ref: https://github.com/golang/crypto/blob/0c34fe9e7dc2486962ef9867e3edb3503537209f/curve25519/curve25519_generic.go#L718 fn invert(&mut self) { let mut t0 = self.square(); let mut t1 = t0.square(); t1 = t1.square(); t1 = *self * t1; t0 = t0 * t1; let mut t2 = t0.square(); t1 = t1 * t2; t2 = t1.square(); for _ in 1..5 { t2 = t2.square(); } t1 = t2 * t1; t2 = t1.square(); for _ in 1..10 { t2 = t2.square(); } t2 = t2 * t1; let mut t3 = t2.square(); for _ in 1..20 { t3 = t3.square(); } t2 = t3 * t2; t2 = t2.square(); for _ in 1..10 { t2 = t2.square(); } t1 = t2 * t1; t2 = t1.square(); for _ in 1..50 { t2 = t2.square(); } t2 = t2 * t1; t3 = t2.square(); for _ in 1..100 { t3 = t3.square(); } t2 = t3 * t2; t2 = t2.square(); for _ in 1..50 { t2 = t2.square(); } t1 = t2 * t1; t1 = t1.square(); for _ in 1..5 { t1 = t1.square(); } *self = t1 * t0; } } #[derive(Clone)] /// Represents a Scalar decoded from a byte array. struct Scalar([u8; PRIVATE_KEY_SIZE]); impl Drop for Scalar { fn drop(&mut self) { use zeroize::Zeroize; self.0.iter_mut().zeroize(); } } impl PartialEq for Scalar { fn eq(&self, other: &Self) -> bool { use subtle::ConstantTimeEq; self.0.ct_eq(&other.0).into() } } impl Eq for Scalar {} impl Scalar { /// Create a scalar from some byte-array. /// The scalar is clamped according to the RFC. /// /// Ref: https://www.ietf.org/rfc/rfc7748.html#section-5 fn from_slice(slice: &[u8]) -> Result { if slice.len() != PRIVATE_KEY_SIZE { return Err(UnknownCryptoError); } let mut ret = [0u8; PRIVATE_KEY_SIZE]; ret.copy_from_slice(slice); // Clamp ret[0] &= 248; ret[31] &= 127; ret[31] |= 64; Ok(Self(ret)) } } /// Scalar multiplication using the Montgomery Ladder (a.k.a "scalarmult") /// /// Refs: /// - https://eprint.iacr.org/2020/956.pdf /// - https://eprint.iacr.org/2017/212.pdf /// - https://github.com/golang/crypto/blob/0c34fe9e7dc2486962ef9867e3edb3503537209f/curve25519/curve25519_generic.go#L779 fn mont_ladder(scalar: &Scalar, point: FieldElement) -> FieldElement { let x1 = point; let mut x2 = FieldElement::one(); let mut x3 = x1; let mut z3 = FieldElement::one(); let mut z2 = FieldElement::zero(); let mut tmp0: FieldElement; let mut tmp1: FieldElement; let mut swap: u8 = 0; for idx in (0..=254).rev() { let bit = (scalar.0[idx >> 3] >> (idx & 7)) & 1; swap ^= bit; FieldElement::conditional_swap(swap, &mut x2, &mut x3); FieldElement::conditional_swap(swap, &mut z2, &mut z3); swap = bit; tmp0 = x3 - z3; tmp1 = x2 - z2; x2 = x2 + z2; z2 = x3 + z3; z3 = tmp0 * x2; z2 = z2 * tmp1; tmp0 = tmp1.square(); tmp1 = x2.square(); x3 = z3 + z2; z2 = z3 - z2; x2 = tmp1 * tmp0; tmp1 = tmp1 - tmp0; z2 = z2.square(); z3 = tmp1.mul_121666(); x3 = x3.square(); tmp0 = tmp0 + z3; z3 = x1 * z2; z2 = tmp1 * tmp0; } FieldElement::conditional_swap(swap, &mut x2, &mut x3); FieldElement::conditional_swap(swap, &mut z2, &mut z3); z2.invert(); x2 = x2 * z2; x2 } #[allow(clippy::derive_partial_eq_without_eq)] // NOTE: FieldElement contains a constant-time PartialEq impl. /// A type that represents a `PublicKey` that X25519 uses. /// /// This type holds a field element and is used internally as the u-coordinate. /// As the RFC mandates, the most significant bit of the last byte is masked. /// /// # Errors: /// An error will be returned if: /// - `slice` is not 32 bytes. #[derive(PartialEq, Debug, Clone)] pub struct PublicKey { fe: FieldElement, } impl PartialEq<&[u8]> for PublicKey { fn eq(&self, other: &&[u8]) -> bool { if other.len() != PUBLIC_KEY_SIZE { return false; } let other: [u8; 32] = (*other).try_into().unwrap(); self.fe == FieldElement::from_bytes(&other) } } impl From<[u8; PUBLIC_KEY_SIZE]> for PublicKey { #[inline] fn from(bytes: [u8; PUBLIC_KEY_SIZE]) -> Self { Self { fe: FieldElement::from_bytes(&bytes), } } } impl_try_from_trait!(PublicKey); #[cfg(feature = "serde")] impl_serde_traits!(PublicKey, to_bytes); impl TryFrom<&PrivateKey> for PublicKey { type Error = UnknownCryptoError; fn try_from(private_key: &PrivateKey) -> Result { // NOTE: This implementation should be identical to key_agreement() except // for the check of a resulting low order point result. let scalar = Scalar::from_slice(private_key.unprotected_as_bytes())?; Ok(PublicKey::from( mont_ladder(&scalar, FieldElement::from_bytes(&BASEPOINT)).as_bytes(), )) } } impl PublicKey { #[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."] /// Construct from a given byte slice. pub fn from_slice(slice: &[u8]) -> Result { let slice_len = slice.len(); if slice_len != PUBLIC_KEY_SIZE { return Err(UnknownCryptoError); } Ok(Self { fe: FieldElement::from_bytes(slice.try_into().unwrap()), }) } #[inline] /// Return the length of the object. pub fn len(&self) -> usize { PUBLIC_KEY_SIZE } #[inline] /// Return `true` if this object does not hold any data, `false` otherwise. /// /// __NOTE__: This method should always return `false`, since there shouldn't be a way /// to create an empty instance of this object. pub fn is_empty(&self) -> bool { PUBLIC_KEY_SIZE == 0 } #[inline] /// Convert this PublicKey to its byte-representation. pub fn to_bytes(&self) -> [u8; 32] { self.fe.as_bytes() } } #[allow(clippy::derive_partial_eq_without_eq)] // NOTE: Scalar contains a constant-time PartialEq impl. // NOTE: All newtypes impl Drop by default and Scalar has zeroizing Drop /// A type to represent the `PrivateKey` that X25519 uses. /// /// This type holds a scalar and is used internally as such. The scalar held is decoded /// (a.k.a "clamped") as mandated in the [RFC](https://datatracker.ietf.org/doc/html/rfc7748#section-5). /// /// # Errors: /// An error will be returned if: /// - `slice` is not 32 bytes. /// /// # Panics: /// A panic will occur if: /// - Failure to generate random bytes securely. /// /// /// # Security: /// - __**Avoid using**__ `unprotected_as_bytes()` whenever possible, as it breaks all protections /// that the type implements. /// /// - The trait `PartialEq<&'_ [u8]>` is implemented for this type so that users are not tempted /// to call `unprotected_as_bytes` to compare this sensitive value to a byte slice. The trait /// is implemented in such a way that the comparison happens in constant time. Thus, users should /// prefer `SecretType == &[u8]` over `SecretType.unprotected_as_bytes() == &[u8]`. /// Examples are shown below. The examples apply to any type that implements `PartialEq<&'_ [u8]>`. /// ```rust /// # #[cfg(feature = "safe_api")] { /// use orion::hazardous::ecc::x25519::PrivateKey; /// /// // Initialize a secret key with random bytes. /// let secret_key = PrivateKey::generate(); /// /// // Secure, constant-time comparison with a byte slice /// assert_ne!(secret_key, &[0; 32][..]); /// /// // Secure, constant-time comparison with another SecretKey /// assert_ne!(secret_key, PrivateKey::generate()); /// # } /// # Ok::<(), orion::errors::UnknownCryptoError>(()) /// ``` #[derive(PartialEq)] pub struct PrivateKey { scalar: Scalar, } impl PartialEq<&[u8]> for PrivateKey { fn eq(&self, other: &&[u8]) -> bool { match Scalar::from_slice(other) { Ok(other_scalar) => self.scalar == other_scalar, Err(_) => false, } } } impl core::fmt::Debug for PrivateKey { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { write!(f, "{} {{***OMITTED***}}", stringify!(PrivateKey)) } } impl From<[u8; PRIVATE_KEY_SIZE]> for PrivateKey { #[inline] fn from(bytes: [u8; PRIVATE_KEY_SIZE]) -> Self { PrivateKey { // unwrap OK due to valid len scalar: Scalar::from_slice(bytes.as_ref()).unwrap(), } } } impl PrivateKey { #[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."] /// Construct from a given byte slice. pub fn from_slice(slice: &[u8]) -> Result { Ok(Self { scalar: Scalar::from_slice(slice)?, }) } #[inline] /// Return the length of the object. pub fn len(&self) -> usize { PRIVATE_KEY_SIZE } #[inline] /// Return `true` if this object does not hold any data, `false` otherwise. /// /// __NOTE__: This method should always return `false`, since there shouldn't be a way /// to create an empty instance of this object. pub fn is_empty(&self) -> bool { PRIVATE_KEY_SIZE == 0 } #[inline] /// Return the object as byte slice. __**Warning**__: Should not be used unless strictly /// needed. This __**breaks protections**__ that the type implements. pub fn unprotected_as_bytes(&self) -> &[u8] { self.scalar.0.as_ref() } #[cfg(feature = "safe_api")] /// Randomly generate using a CSPRNG. Not available in `no_std` context. pub fn generate() -> PrivateKey { let mut value = [0u8; PRIVATE_KEY_SIZE]; crate::util::secure_rand_bytes(&mut value).unwrap(); Self { // unwrap OK due to valid len scalar: Scalar::from_slice(&value).unwrap(), } } } construct_secret_key! { /// A type to represent the `SharedKey` that X25519 produces. /// /// This type simply holds bytes. Creating an instance from slices or similar, /// performs no checks whatsoever. /// /// # Errors: /// An error will be returned if: /// - `slice` is not 32 bytes. (SharedKey, test_shared_key, SHARED_KEY_SIZE, SHARED_KEY_SIZE) } impl_from_trait!(SharedKey, SHARED_KEY_SIZE); /// X25519 (Diffie-Hellman with Montgomery form of Curve25519). pub fn key_agreement( private_key: &PrivateKey, public_key: &PublicKey, ) -> Result { let u_coord = public_key.fe; let field_element = mont_ladder(&private_key.scalar, u_coord).as_bytes(); // High bit should be zero. debug_assert_eq!(field_element[31] & 0b1000_0000u8, 0u8); if secure_cmp(&field_element, &LOW_ORDER_POINT_RESULT).is_ok() { return Err(UnknownCryptoError); } Ok(SharedKey::from(field_element)) } #[cfg(test)] mod public { use crate::hazardous::ecc::x25519::{ key_agreement, PrivateKey, PublicKey, SharedKey, BASEPOINT, }; #[test] fn test_public_key_ignores_highbit() { let u = [0u8; 32]; let mut msb_zero = u; msb_zero[31] &= 127u8; let mut msb_one = u; msb_one[31] |= 128u8; // These should equal each-other. The high bits differ, but should be ignored. assert_eq!(PublicKey::from(msb_zero), msb_one.as_ref()); assert_eq!(PublicKey::from(msb_zero), PublicKey::from(msb_one)); } #[test] #[cfg(feature = "safe_api")] fn test_highbit_ignored() { // RFC 7748 dictates that the MSB of final byte must be masked when receiving a field element, // used for agreement (public key). We check that modifying it does not impact the result of // the agreement. let k = PrivateKey::generate(); let mut u = [0u8; 32]; crate::util::secure_rand_bytes(&mut u).unwrap(); debug_assert_ne!(u[31] & 127u8, (u[31] & 127u8) | 128u8); let mut u_msb_zero = u; u_msb_zero[31] &= 127u8; let mut u_msb_one = u; u_msb_one[31] |= 128u8; // Mask bit to 0 as we do in `FieldElement::from_bytes()`. let msb_zero = key_agreement(&k, &PublicKey::from(u_msb_zero)).unwrap(); let msb_one = key_agreement(&k, &PublicKey::from(u_msb_one)).unwrap(); assert_eq!(msb_zero, msb_one); } #[test] /// Ref: https://www.ietf.org/rfc/rfc7748.html#section-5.2 fn test_rfc_section_5() { let mut scalar = [0u8; 32]; let mut point = [0u8; 32]; let mut expected = SharedKey::from([0u8; 32]); hex::decode_to_slice( "a546e36bf0527c9d3b16154b82465edd62144c0ac1fc5a18506a2244ba449ac4", &mut scalar, ) .unwrap(); hex::decode_to_slice( "e6db6867583030db3594c1a424b15f7c726624ec26b3353b10a903a6d0ab1c4c", &mut point, ) .unwrap(); hex::decode_to_slice( "c3da55379de9c6908e94ea4df28d084f32eccf03491c71f754b4075577a28552", &mut expected.value, ) .unwrap(); let actual = key_agreement(&PrivateKey::from(scalar), &PublicKey::from(point)).unwrap(); assert_eq!(actual, expected); hex::decode_to_slice( "4b66e9d4d1b4673c5ad22691957d6af5c11b6421e0ea01d42ca4169e7918ba0d", &mut scalar, ) .unwrap(); hex::decode_to_slice( "e5210f12786811d3f4b7959d0538ae2c31dbe7106fc03c3efc4cd549c715a493", &mut point, ) .unwrap(); hex::decode_to_slice( "95cbde9476e8907d7aade45cb4b873f88b595a68799fa152e6f8f7647aac7957", &mut expected.value, ) .unwrap(); let actual = key_agreement(&PrivateKey::from(scalar), &PublicKey::from(point)).unwrap(); assert_eq!(actual, expected); } #[test] /// Ref: https://www.ietf.org/rfc/rfc7748.html#section-5.2 fn test_rfc_section_5_iter() { let mut k = BASEPOINT; let mut u = BASEPOINT; // 1 iter let ret = key_agreement(&PrivateKey::from(k), &PublicKey::from(u)).unwrap(); u = k; k = ret.value; let mut expected = SharedKey::from([0u8; 32]); hex::decode_to_slice( "422c8e7a6227d7bca1350b3e2bb7279f7897b87bb6854b783c60e80311ae3079", &mut expected.value, ) .unwrap(); assert_eq!(k, expected.value, "Failed after 1 iter"); for _ in 0..999 { let ret = key_agreement(&PrivateKey::from(k), &PublicKey::from(u)).unwrap(); u = k; k = ret.value; } hex::decode_to_slice( "684cf59ba83309552800ef566f2f4d3c1c3887c49360e3875f2eb94d99532c51", &mut expected.value, ) .unwrap(); assert_eq!(k, expected.value, "Failed after 1.000 iter"); /* Taking a decade... for num in 0..999000 { let ret = key_agreement(&PrivateKey::from(k), &PublicKey::from(u)).unwrap(); u = k; k = ret.value; } hex::decode_to_slice( "7c3911e0ab2586fd864497297e575e6f3bc601c0883c30df5f4dd2d24f665424", &mut expected.value, ) .unwrap(); assert_eq!(k, expected.value, "Failed after 1.000.000 iter"); */ } #[test] /// Ref: https://www.ietf.org/rfc/rfc7748.html#section-6.1 fn test_rfc_section_6_pub_priv_basepoint() { let mut alice_pub = [0u8; 32]; let mut alice_priv = [0u8; 32]; let mut bob_pub = [0u8; 32]; let mut bob_priv = [0u8; 32]; let mut shared = SharedKey::from([0u8; 32]); hex::decode_to_slice( "77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a", &mut alice_priv, ) .unwrap(); hex::decode_to_slice( "8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a", &mut alice_pub, ) .unwrap(); assert_eq!( key_agreement(&PrivateKey::from(alice_priv), &PublicKey::from(BASEPOINT)).unwrap(), PublicKey::from(alice_pub).to_bytes().as_ref() ); hex::decode_to_slice( "5dab087e624a8a4b79e17f8b83800ee66f3bb1292618b6fd1c2f8b27ff88e0eb", &mut bob_priv, ) .unwrap(); hex::decode_to_slice( "de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f", &mut bob_pub, ) .unwrap(); assert_eq!( key_agreement(&PrivateKey::from(bob_priv), &PublicKey::from(BASEPOINT)).unwrap(), PublicKey::from(bob_pub).to_bytes().as_ref() ); hex::decode_to_slice( "4a5d9d5ba4ce2de1728e3bf480350f25e07e21c947d19e3376f09b3c1e161742", &mut shared.value, ) .unwrap(); assert_eq!( key_agreement(&PrivateKey::from(alice_priv), &PublicKey::from(bob_pub)).unwrap(), shared.value.as_ref() ); assert_eq!( key_agreement(&PrivateKey::from(bob_priv), &PublicKey::from(alice_pub)).unwrap(), shared.value.as_ref() ); } }