Skip to main content

x448/
lib.rs

1#![no_std]
2#![cfg_attr(docsrs, feature(doc_cfg))]
3#![doc = include_str!("../README.md")]
4#![doc(
5    html_logo_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg",
6    html_favicon_url = "https://raw.githubusercontent.com/RustCrypto/meta/master/logo.svg"
7)]
8
9use ed448_goldilocks::{
10    MontgomeryPoint,
11    elliptic_curve::{
12        Generate,
13        array::{Array, typenum::U56},
14        bigint::U448,
15        rand_core::{CryptoRng, TryCryptoRng},
16        scalar::FromUintUnchecked,
17        zeroize::Zeroize,
18    },
19};
20
21type MontgomeryScalar = ed448_goldilocks::Scalar<ed448_goldilocks::Ed448>;
22
23/// Given an [`EphemeralSecret`] Key, compute the corresponding public key
24/// using the generator specified in RFC7748
25impl From<&EphemeralSecret> for PublicKey {
26    fn from(secret: &EphemeralSecret) -> PublicKey {
27        let secret = secret.as_scalar();
28        let point = &MontgomeryPoint::GENERATOR * &secret;
29        PublicKey(point)
30    }
31}
32
33/// A PublicKey is a point on Curve448.
34#[derive(Debug, PartialEq, Eq, Copy, Clone)]
35pub struct PublicKey(MontgomeryPoint);
36
37impl PublicKey {
38    /// Converts a bytes slice into a Public key
39    /// Returns None if:
40    /// -  The length of the slice is not 56
41    /// -  The point is a low order point
42    pub fn from_bytes(bytes: &[u8]) -> Option<PublicKey> {
43        let public_key = PublicKey::from_bytes_unchecked(bytes)?;
44        if public_key.0.is_low_order() {
45            return None;
46        }
47        Some(public_key)
48    }
49
50    /// Converts a bytes slice into a Public key
51    /// Returns None if:
52    /// -  The length of the slice is not 56
53    pub fn from_bytes_unchecked(bytes: &[u8]) -> Option<PublicKey> {
54        // First check if we have 56 bytes
55        if bytes.len() != 56 {
56            return None;
57        }
58
59        // Check if the point has low order
60        let arr = slice_to_array(bytes);
61        let point = MontgomeryPoint(arr);
62
63        Some(PublicKey(point))
64    }
65
66    /// Converts a public key into a byte slice
67    pub fn as_bytes(&self) -> &[u8; 56] {
68        self.0.as_bytes()
69    }
70}
71
72/// A SharedSecret is a point on Curve448.
73/// This point is the result of a Diffie-Hellman key exchange.
74#[derive(Zeroize)]
75#[zeroize(drop)]
76pub struct SharedSecret(MontgomeryPoint);
77
78impl SharedSecret {
79    /// Converts a shared secret into a byte slice
80    pub fn as_bytes(&self) -> &[u8; 56] {
81        self.0.as_bytes()
82    }
83}
84
85/// An [`EphemeralSecret`] is a Scalar on Curve448.
86#[derive(Clone, Zeroize)]
87#[zeroize(drop)]
88pub struct EphemeralSecret(EphemeralSecretBytes);
89
90type EphemeralSecretBytes = Array<u8, U56>;
91
92impl EphemeralSecret {
93    /// DEPRECATED: Generate a new ephemeral secret from the given RNG.
94    #[deprecated(since = "0.14.0", note = "use the `Generate` trait instead")]
95    pub fn new<R>(csprng: &mut R) -> Self
96    where
97        R: CryptoRng + ?Sized,
98    {
99        Self::generate_from_rng(csprng)
100    }
101
102    /// Views an Secret as a Scalar
103    fn as_scalar(&self) -> MontgomeryScalar {
104        let secret = U448::from_le_slice(&self.0);
105        MontgomeryScalar::from_uint_unchecked(secret)
106    }
107
108    /// Performs a Diffie-hellman key exchange between the secret key and an external public key
109    pub fn diffie_hellman(&self, public_key: &PublicKey) -> SharedSecret {
110        // NOTE(security): it is assumed PublicKey is not a low_order. It should be checked when
111        // created.
112        let shared_key = &public_key.0 * &self.as_scalar();
113        SharedSecret(shared_key)
114    }
115
116    /// Converts a secret into a byte array
117    pub fn as_bytes(&self) -> &[u8; 56] {
118        self.0.as_ref()
119    }
120
121    /// Construct an [`EphemeralSecret`] by the secret key according to RFC7748.
122    fn clamp(mut bytes: EphemeralSecretBytes) -> Self {
123        bytes[0] &= 252;
124        bytes[55] |= 128;
125        Self(bytes)
126    }
127}
128
129impl Generate for EphemeralSecret {
130    fn try_generate_from_rng<R>(csprng: &mut R) -> Result<Self, R::Error>
131    where
132        R: TryCryptoRng + ?Sized,
133    {
134        let mut bytes = Array::default();
135        csprng.try_fill_bytes(bytes.as_mut_slice())?;
136        Ok(EphemeralSecret::clamp(bytes))
137    }
138}
139
140fn slice_to_array(bytes: &[u8]) -> [u8; 56] {
141    let mut array: [u8; 56] = [0; 56];
142    array.copy_from_slice(bytes);
143    array
144}
145
146/// A safe version of the x448 function defined in RFC448.
147/// Currently, the only reason I can think of for using the raw function is FFI.
148/// Option is FFI safe[1]. So we can still maintain that the invariant that
149/// we do not return a low order point.
150///
151/// [1]: https://github.com/rust-lang/nomicon/issues/59
152pub fn x448(scalar_bytes: [u8; 56], point_bytes: [u8; 56]) -> Option<[u8; 56]> {
153    let point = PublicKey::from_bytes(&point_bytes)?;
154    let scalar = EphemeralSecret::clamp(scalar_bytes.into()).as_scalar();
155    Some((&point.0 * &scalar).0)
156}
157/// An unchecked version of the x448 function defined in RFC448
158/// No checks are made on the points.
159pub fn x448_unchecked(scalar_bytes: [u8; 56], point_bytes: [u8; 56]) -> [u8; 56] {
160    let point = MontgomeryPoint(point_bytes);
161    let scalar = EphemeralSecret::clamp(scalar_bytes.into()).as_scalar();
162    (&point * &scalar).0
163}
164
165pub const X448_BASEPOINT_BYTES: [u8; 56] = MontgomeryPoint::GENERATOR.0;
166
167/// A Diffie-Hellman secret key that can be used to compute multiple [`SharedSecret`]s.
168///
169/// This type is identical to the [`EphemeralSecret`] type, except that the
170/// [`StaticSecret::diffie_hellman`] method does not consume the secret key, and the type provides
171/// serialization methods to save and load key material.  This means that the secret may be used
172/// multiple times (but does not *have to be*).
173///
174/// # Warning
175///
176/// If you're uncertain about whether you should use this, then you likely
177/// should not be using this.  Our strongly recommended advice is to use
178/// [`EphemeralSecret`] at all times, as that type enforces at compile-time that
179/// secret keys are never reused, which can have very serious security
180/// implications for many protocols.
181#[cfg(feature = "static_secrets")]
182#[derive(Clone, Zeroize)]
183#[zeroize(drop)]
184pub struct StaticSecret(Array<u8, U56>);
185
186#[cfg(feature = "static_secrets")]
187impl StaticSecret {
188    fn new(value: Array<u8, U56>) -> Self {
189        let mut out = Self(value);
190        out.clamp();
191        out
192    }
193
194    /// Generate a new [`StaticSecret`] with the supplied RNG.
195    pub fn random_from_rng<R: CryptoRng + ?Sized>(csprng: &mut R) -> Self {
196        let mut bytes = Array::default();
197        csprng.fill_bytes(bytes.as_mut_slice());
198        Self::new(bytes)
199    }
200
201    /// Clamps the secret key according to RFC7748
202    fn clamp(&mut self) {
203        self.0[0] &= 252;
204        self.0[55] |= 128;
205    }
206
207    /// Views an Secret as a Scalar
208    fn as_scalar(&self) -> MontgomeryScalar {
209        let secret = U448::from_le_slice(&self.0);
210        MontgomeryScalar::from_uint_unchecked(secret)
211    }
212
213    /// Perform a Diffie-Hellman key agreement between `self` and
214    /// `their_public` key to produce a `SharedSecret`.
215    pub fn diffie_hellman(&self, their_public: &PublicKey) -> SharedSecret {
216        // NOTE(security): it is assumed PublicKey is not a low_order. It should be checked when
217        // created.
218        let shared_key = &their_public.0 * &self.as_scalar();
219        SharedSecret(shared_key)
220    }
221
222    /// View this key as a byte array.
223    #[inline]
224    pub fn as_bytes(&self) -> &[u8; 56] {
225        self.0.as_ref()
226    }
227}
228
229#[cfg(feature = "static_secrets")]
230impl From<[u8; 56]> for StaticSecret {
231    /// Load a secret key from a byte array.
232    fn from(bytes: [u8; 56]) -> StaticSecret {
233        StaticSecret::new(bytes.into())
234    }
235}
236
237#[cfg(feature = "static_secrets")]
238impl From<&StaticSecret> for PublicKey {
239    /// Given an x448 [`StaticSecret`] key, compute its corresponding [`PublicKey`].
240    fn from(secret: &StaticSecret) -> PublicKey {
241        let secret = secret.as_scalar();
242        let point = &MontgomeryPoint::GENERATOR * &secret;
243        PublicKey(point)
244    }
245}
246
247#[cfg(feature = "static_secrets")]
248impl AsRef<[u8]> for StaticSecret {
249    /// View this key as a byte array.
250    #[inline]
251    fn as_ref(&self) -> &[u8] {
252        self.as_bytes()
253    }
254}
255
256#[cfg(all(feature = "static_secrets", feature = "serde"))]
257impl serdect::serde::Serialize for StaticSecret {
258    fn serialize<S>(&self, s: S) -> Result<S::Ok, S::Error>
259    where
260        S: serdect::serde::Serializer,
261    {
262        serdect::array::serialize_hex_lower_or_bin(&self.0, s)
263    }
264}
265
266#[cfg(all(feature = "static_secrets", feature = "serde"))]
267impl<'de> serdect::serde::Deserialize<'de> for StaticSecret {
268    fn deserialize<D>(d: D) -> Result<Self, D::Error>
269    where
270        D: serdect::serde::Deserializer<'de>,
271    {
272        let mut bytes = Array::default();
273        serdect::array::deserialize_hex_or_bin(&mut bytes, d)?;
274        Ok(StaticSecret::new(bytes))
275    }
276}
277
278#[cfg(test)]
279mod test {
280    extern crate alloc;
281
282    use super::*;
283    use alloc::vec;
284    use core::array::TryFromSliceError;
285
286    /// Helpers to test properties of EphemeralSecret. Those `From` and `TryFrom` are not meant to
287    /// be exposed outside tests.
288    impl From<[u8; 56]> for EphemeralSecret {
289        fn from(arr: [u8; 56]) -> Self {
290            Self::clamp(arr.into())
291        }
292    }
293
294    impl TryFrom<&[u8]> for EphemeralSecret {
295        type Error = TryFromSliceError;
296
297        fn try_from(bytes: &[u8]) -> Result<Self, TryFromSliceError> {
298            Ok(Self::clamp(Array::try_from(bytes)?))
299        }
300    }
301
302    #[test]
303    #[cfg(feature = "getrandom")]
304    fn test_random_dh() {
305        let alice_priv = EphemeralSecret::generate();
306        let alice_pub = PublicKey::from(&alice_priv);
307
308        let bob_priv = EphemeralSecret::generate();
309        let bob_pub = PublicKey::from(&bob_priv);
310
311        // Since Alice and Bob are both using the API correctly
312        // If by chance, a low order point is generated, the clamping function will
313        // remove it.
314        let low_order = alice_pub.0.is_low_order() || bob_pub.0.is_low_order();
315        assert!(low_order == false);
316
317        // Both Alice and Bob perform the DH key exchange.
318        // As mentioned above, we unwrap because both Parties are using the API correctly.
319        let shared_alice = alice_priv.diffie_hellman(&bob_pub);
320        let shared_bob = bob_priv.diffie_hellman(&alice_pub);
321
322        assert_eq!(shared_alice.as_bytes()[..], shared_bob.as_bytes()[..]);
323    }
324
325    #[test]
326    fn test_rfc_test_vectors_alice_bob() {
327        let alice_priv = EphemeralSecret::from([
328            0x9a, 0x8f, 0x49, 0x25, 0xd1, 0x51, 0x9f, 0x57, 0x75, 0xcf, 0x46, 0xb0, 0x4b, 0x58,
329            0x0, 0xd4, 0xee, 0x9e, 0xe8, 0xba, 0xe8, 0xbc, 0x55, 0x65, 0xd4, 0x98, 0xc2, 0x8d,
330            0xd9, 0xc9, 0xba, 0xf5, 0x74, 0xa9, 0x41, 0x97, 0x44, 0x89, 0x73, 0x91, 0x0, 0x63,
331            0x82, 0xa6, 0xf1, 0x27, 0xab, 0x1d, 0x9a, 0xc2, 0xd8, 0xc0, 0xa5, 0x98, 0x72, 0x6b,
332        ]);
333        let got_alice_pub = PublicKey::from(&alice_priv);
334
335        let expected_alice_pub = [
336            0x9b, 0x8, 0xf7, 0xcc, 0x31, 0xb7, 0xe3, 0xe6, 0x7d, 0x22, 0xd5, 0xae, 0xa1, 0x21, 0x7,
337            0x4a, 0x27, 0x3b, 0xd2, 0xb8, 0x3d, 0xe0, 0x9c, 0x63, 0xfa, 0xa7, 0x3d, 0x2c, 0x22,
338            0xc5, 0xd9, 0xbb, 0xc8, 0x36, 0x64, 0x72, 0x41, 0xd9, 0x53, 0xd4, 0xc, 0x5b, 0x12,
339            0xda, 0x88, 0x12, 0xd, 0x53, 0x17, 0x7f, 0x80, 0xe5, 0x32, 0xc4, 0x1f, 0xa0,
340        ];
341        assert_eq!(got_alice_pub.0.as_bytes()[..], expected_alice_pub[..]);
342
343        let bob_priv = EphemeralSecret::from([
344            0x1c, 0x30, 0x6a, 0x7a, 0xc2, 0xa0, 0xe2, 0xe0, 0x99, 0xb, 0x29, 0x44, 0x70, 0xcb,
345            0xa3, 0x39, 0xe6, 0x45, 0x37, 0x72, 0xb0, 0x75, 0x81, 0x1d, 0x8f, 0xad, 0xd, 0x1d,
346            0x69, 0x27, 0xc1, 0x20, 0xbb, 0x5e, 0xe8, 0x97, 0x2b, 0xd, 0x3e, 0x21, 0x37, 0x4c,
347            0x9c, 0x92, 0x1b, 0x9, 0xd1, 0xb0, 0x36, 0x6f, 0x10, 0xb6, 0x51, 0x73, 0x99, 0x2d,
348        ]);
349        let got_bob_pub = PublicKey::from(&bob_priv);
350
351        let expected_bob_pub = [
352            0x3e, 0xb7, 0xa8, 0x29, 0xb0, 0xcd, 0x20, 0xf5, 0xbc, 0xfc, 0xb, 0x59, 0x9b, 0x6f,
353            0xec, 0xcf, 0x6d, 0xa4, 0x62, 0x71, 0x7, 0xbd, 0xb0, 0xd4, 0xf3, 0x45, 0xb4, 0x30,
354            0x27, 0xd8, 0xb9, 0x72, 0xfc, 0x3e, 0x34, 0xfb, 0x42, 0x32, 0xa1, 0x3c, 0xa7, 0x6,
355            0xdc, 0xb5, 0x7a, 0xec, 0x3d, 0xae, 0x7, 0xbd, 0xc1, 0xc6, 0x7b, 0xf3, 0x36, 0x9,
356        ];
357        assert_eq!(got_bob_pub.0.as_bytes()[..], expected_bob_pub[..]);
358
359        let bob_shared = bob_priv.diffie_hellman(&got_alice_pub);
360        let alice_shared = alice_priv.diffie_hellman(&got_bob_pub);
361        assert_eq!(bob_shared.as_bytes()[..], alice_shared.as_bytes()[..]);
362
363        let expected_shared = [
364            0x7, 0xff, 0xf4, 0x18, 0x1a, 0xc6, 0xcc, 0x95, 0xec, 0x1c, 0x16, 0xa9, 0x4a, 0xf, 0x74,
365            0xd1, 0x2d, 0xa2, 0x32, 0xce, 0x40, 0xa7, 0x75, 0x52, 0x28, 0x1d, 0x28, 0x2b, 0xb6,
366            0xc, 0xb, 0x56, 0xfd, 0x24, 0x64, 0xc3, 0x35, 0x54, 0x39, 0x36, 0x52, 0x1c, 0x24, 0x40,
367            0x30, 0x85, 0xd5, 0x9a, 0x44, 0x9a, 0x50, 0x37, 0x51, 0x4a, 0x87, 0x9d,
368        ];
369
370        assert_eq!(bob_shared.as_bytes()[..], expected_shared[..]);
371    }
372
373    #[test]
374    fn test_rfc_test_vectors_fixed() {
375        struct Test {
376            secret: [u8; 56],
377            point: [u8; 56],
378            expected: [u8; 56],
379        }
380
381        let test_vectors = vec![
382            Test {
383                secret: [
384                    0x3d, 0x26, 0x2f, 0xdd, 0xf9, 0xec, 0x8e, 0x88, 0x49, 0x52, 0x66, 0xfe, 0xa1,
385                    0x9a, 0x34, 0xd2, 0x88, 0x82, 0xac, 0xef, 0x4, 0x51, 0x4, 0xd0, 0xd1, 0xaa,
386                    0xe1, 0x21, 0x70, 0xa, 0x77, 0x9c, 0x98, 0x4c, 0x24, 0xf8, 0xcd, 0xd7, 0x8f,
387                    0xbf, 0xf4, 0x49, 0x43, 0xeb, 0xa3, 0x68, 0xf5, 0x4b, 0x29, 0x25, 0x9a, 0x4f,
388                    0x1c, 0x60, 0xa, 0xd3,
389                ],
390                point: [
391                    0x6, 0xfc, 0xe6, 0x40, 0xfa, 0x34, 0x87, 0xbf, 0xda, 0x5f, 0x6c, 0xf2, 0xd5,
392                    0x26, 0x3f, 0x8a, 0xad, 0x88, 0x33, 0x4c, 0xbd, 0x7, 0x43, 0x7f, 0x2, 0xf, 0x8,
393                    0xf9, 0x81, 0x4d, 0xc0, 0x31, 0xdd, 0xbd, 0xc3, 0x8c, 0x19, 0xc6, 0xda, 0x25,
394                    0x83, 0xfa, 0x54, 0x29, 0xdb, 0x94, 0xad, 0xa1, 0x8a, 0xa7, 0xa7, 0xfb, 0x4e,
395                    0xf8, 0xa0, 0x86,
396                ],
397                expected: [
398                    0xce, 0x3e, 0x4f, 0xf9, 0x5a, 0x60, 0xdc, 0x66, 0x97, 0xda, 0x1d, 0xb1, 0xd8,
399                    0x5e, 0x6a, 0xfb, 0xdf, 0x79, 0xb5, 0xa, 0x24, 0x12, 0xd7, 0x54, 0x6d, 0x5f,
400                    0x23, 0x9f, 0xe1, 0x4f, 0xba, 0xad, 0xeb, 0x44, 0x5f, 0xc6, 0x6a, 0x1, 0xb0,
401                    0x77, 0x9d, 0x98, 0x22, 0x39, 0x61, 0x11, 0x1e, 0x21, 0x76, 0x62, 0x82, 0xf7,
402                    0x3d, 0xd9, 0x6b, 0x6f,
403                ],
404            },
405            Test {
406                secret: [
407                    0x20, 0x3d, 0x49, 0x44, 0x28, 0xb8, 0x39, 0x93, 0x52, 0x66, 0x5d, 0xdc, 0xa4,
408                    0x2f, 0x9d, 0xe8, 0xfe, 0xf6, 0x0, 0x90, 0x8e, 0xd, 0x46, 0x1c, 0xb0, 0x21,
409                    0xf8, 0xc5, 0x38, 0x34, 0x5d, 0xd7, 0x7c, 0x3e, 0x48, 0x6, 0xe2, 0x5f, 0x46,
410                    0xd3, 0x31, 0x5c, 0x44, 0xe0, 0xa5, 0xb4, 0x37, 0x12, 0x82, 0xdd, 0x2c, 0x8d,
411                    0x5b, 0xe3, 0x9, 0x5f,
412                ],
413                point: [
414                    0xf, 0xbc, 0xc2, 0xf9, 0x93, 0xcd, 0x56, 0xd3, 0x30, 0x5b, 0xb, 0x7d, 0x9e,
415                    0x55, 0xd4, 0xc1, 0xa8, 0xfb, 0x5d, 0xbb, 0x52, 0xf8, 0xe9, 0xa1, 0xe9, 0xb6,
416                    0x20, 0x1b, 0x16, 0x5d, 0x1, 0x58, 0x94, 0xe5, 0x6c, 0x4d, 0x35, 0x70, 0xbe,
417                    0xe5, 0x2f, 0xe2, 0x5, 0xe2, 0x8a, 0x78, 0xb9, 0x1c, 0xdf, 0xbd, 0xe7, 0x1c,
418                    0xe8, 0xd1, 0x57, 0xdb,
419                ],
420                expected: [
421                    0x88, 0x4a, 0x2, 0x57, 0x62, 0x39, 0xff, 0x7a, 0x2f, 0x2f, 0x63, 0xb2, 0xdb,
422                    0x6a, 0x9f, 0xf3, 0x70, 0x47, 0xac, 0x13, 0x56, 0x8e, 0x1e, 0x30, 0xfe, 0x63,
423                    0xc4, 0xa7, 0xad, 0x1b, 0x3e, 0xe3, 0xa5, 0x70, 0xd, 0xf3, 0x43, 0x21, 0xd6,
424                    0x20, 0x77, 0xe6, 0x36, 0x33, 0xc5, 0x75, 0xc1, 0xc9, 0x54, 0x51, 0x4e, 0x99,
425                    0xda, 0x7c, 0x17, 0x9d,
426                ],
427            },
428        ];
429
430        for vector in test_vectors {
431            let public_key = PublicKey::from_bytes(&vector.point).unwrap();
432            let secret = EphemeralSecret::try_from(&vector.secret[..]).unwrap();
433
434            let got = secret.diffie_hellman(&public_key);
435
436            assert_eq!(got.0.as_bytes()[..], vector.expected[..])
437        }
438    }
439
440    // This function is needed for the second set of test vectors in RFC7748
441    fn swap(secret: &mut [u8; 56], public_key: &mut [u8; 56], result: &[u8; 56]) {
442        // set point to be the secret
443        *public_key = *secret;
444        // set the secret to be the result
445        *secret = *result;
446    }
447
448    #[test]
449    #[ignore]
450    fn test_rfc_test_vectors_iteration() {
451        let one_iter = [
452            0x3f, 0x48, 0x2c, 0x8a, 0x9f, 0x19, 0xb0, 0x1e, 0x6c, 0x46, 0xee, 0x97, 0x11, 0xd9,
453            0xdc, 0x14, 0xfd, 0x4b, 0xf6, 0x7a, 0xf3, 0x7, 0x65, 0xc2, 0xae, 0x2b, 0x84, 0x6a,
454            0x4d, 0x23, 0xa8, 0xcd, 0xd, 0xb8, 0x97, 0x8, 0x62, 0x39, 0x49, 0x2c, 0xaf, 0x35, 0xb,
455            0x51, 0xf8, 0x33, 0x86, 0x8b, 0x9b, 0xc2, 0xb3, 0xbc, 0xa9, 0xcf, 0x41, 0x13,
456        ];
457        let one_k_iter = [
458            0xaa, 0x3b, 0x47, 0x49, 0xd5, 0x5b, 0x9d, 0xaf, 0x1e, 0x5b, 0x0, 0x28, 0x88, 0x26,
459            0xc4, 0x67, 0x27, 0x4c, 0xe3, 0xeb, 0xbd, 0xd5, 0xc1, 0x7b, 0x97, 0x5e, 0x9, 0xd4,
460            0xaf, 0x6c, 0x67, 0xcf, 0x10, 0xd0, 0x87, 0x20, 0x2d, 0xb8, 0x82, 0x86, 0xe2, 0xb7,
461            0x9f, 0xce, 0xea, 0x3e, 0xc3, 0x53, 0xef, 0x54, 0xfa, 0xa2, 0x6e, 0x21, 0x9f, 0x38,
462        ];
463        let one_mil_iter = [
464            0x7, 0x7f, 0x45, 0x36, 0x81, 0xca, 0xca, 0x36, 0x93, 0x19, 0x84, 0x20, 0xbb, 0xe5,
465            0x15, 0xca, 0xe0, 0x0, 0x24, 0x72, 0x51, 0x9b, 0x3e, 0x67, 0x66, 0x1a, 0x7e, 0x89,
466            0xca, 0xb9, 0x46, 0x95, 0xc8, 0xf4, 0xbc, 0xd6, 0x6e, 0x61, 0xb9, 0xb9, 0xc9, 0x46,
467            0xda, 0x8d, 0x52, 0x4d, 0xe3, 0xd6, 0x9b, 0xd9, 0xd9, 0xd6, 0x6b, 0x99, 0x7e, 0x37,
468        ];
469
470        let mut point = MontgomeryPoint::GENERATOR.0;
471        let mut scalar = MontgomeryPoint::GENERATOR.0;
472        let mut result = [0u8; 56];
473
474        // Iterate 1 time then check value on 1st iteration
475        for _ in 1..=1 {
476            result = x448(scalar, point).unwrap();
477            swap(&mut scalar, &mut point, &result);
478        }
479        assert_eq!(&result[..], &one_iter[..]);
480
481        // Iterate 999 times then check value on 1_000th iteration
482        for _ in 1..=999 {
483            result = x448(scalar, point).unwrap();
484            swap(&mut scalar, &mut point, &result);
485        }
486        assert_eq!(&result[..], &one_k_iter[..]);
487
488        // Iterate 999_000 times then check value on 1_000_000th iteration
489        for _ in 1..=999_000 {
490            result = x448(scalar, point).unwrap();
491            swap(&mut scalar, &mut point, &result);
492        }
493        assert_eq!(&result[..], &one_mil_iter[..]);
494    }
495}