curve25519_dalek/montgomery.rs
1// -*- mode: rust; -*-
2//
3// This file is part of curve25519-dalek.
4// Copyright (c) 2016-2021 isis lovecruft
5// Copyright (c) 2016-2019 Henry de Valence
6// See LICENSE for licensing information.
7//
8// Authors:
9// - isis agora lovecruft <isis@patternsinthevoid.net>
10// - Henry de Valence <hdevalence@hdevalence.ca>
11
12//! Scalar multiplication on the Montgomery form of Curve25519.
13//!
14//! To avoid notational confusion with the Edwards code, we use
15//! variables \\( u, v \\) for the Montgomery curve, so that “Montgomery
16//! \\(u\\)” here corresponds to “Montgomery \\(x\\)” elsewhere.
17//!
18//! Montgomery arithmetic works not on the curve itself, but on the
19//! \\(u\\)-line, which discards sign information and unifies the curve
20//! and its quadratic twist. See [_Montgomery curves and their
21//! arithmetic_][costello-smith] by Costello and Smith for more details.
22//!
23//! The `MontgomeryPoint` struct contains the affine \\(u\\)-coordinate
24//! \\(u\_0(P)\\) of a point \\(P\\) on either the curve or the twist.
25//! Here the map \\(u\_0 : \mathcal M \rightarrow \mathbb F\_p \\) is
26//! defined by \\(u\_0((u,v)) = u\\); \\(u\_0(\mathcal O) = 0\\). See
27//! section 5.4 of Costello-Smith for more details.
28//!
29//! # Scalar Multiplication
30//!
31//! Scalar multiplication on `MontgomeryPoint`s is provided by the `*`
32//! operator, which implements the Montgomery ladder.
33//!
34//! # Edwards Conversion
35//!
36//! The \\(2\\)-to-\\(1\\) map from the Edwards model to the Montgomery
37//! \\(u\\)-line is provided by `EdwardsPoint::to_montgomery()`.
38//!
39//! To lift a `MontgomeryPoint` to an `EdwardsPoint`, use
40//! `MontgomeryPoint::to_edwards()`, which takes a sign parameter.
41//! This function rejects `MontgomeryPoints` which correspond to points
42//! on the twist.
43//!
44//! [costello-smith]: https://eprint.iacr.org/2017/212.pdf
45
46// We allow non snake_case names because coordinates in projective space are
47// traditionally denoted by the capitalisation of their respective
48// counterparts in affine space. Yeah, you heard me, rustc, I'm gonna have my
49// affine and projective cakes and eat both of them too.
50#![allow(non_snake_case)]
51
52use core::{
53 hash::{Hash, Hasher},
54 ops::{Mul, MulAssign},
55};
56
57use crate::constants::{APLUS2_OVER_FOUR, MONTGOMERY_A, MONTGOMERY_A_NEG};
58use crate::edwards::{CompressedEdwardsY, EdwardsPoint};
59use crate::field::FieldElement;
60use crate::scalar::{clamp_integer, Scalar};
61
62use crate::traits::Identity;
63
64use subtle::Choice;
65use subtle::ConstantTimeEq;
66use subtle::{ConditionallyNegatable, ConditionallySelectable};
67
68#[cfg(feature = "zeroize")]
69use zeroize::Zeroize;
70
71/// Holds the \\(u\\)-coordinate of a point on the Montgomery form of
72/// Curve25519 or its twist.
73#[derive(Copy, Clone, Debug, Default)]
74#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
75pub struct MontgomeryPoint(pub [u8; 32]);
76
77/// Equality of `MontgomeryPoint`s is defined mod p.
78impl ConstantTimeEq for MontgomeryPoint {
79 fn ct_eq(&self, other: &MontgomeryPoint) -> Choice {
80 let self_fe = FieldElement::from_bytes(&self.0);
81 let other_fe = FieldElement::from_bytes(&other.0);
82
83 self_fe.ct_eq(&other_fe)
84 }
85}
86
87impl PartialEq for MontgomeryPoint {
88 fn eq(&self, other: &MontgomeryPoint) -> bool {
89 self.ct_eq(other).into()
90 }
91}
92
93impl Eq for MontgomeryPoint {}
94
95// Equal MontgomeryPoints must hash to the same value. So we have to get them into a canonical
96// encoding first
97impl Hash for MontgomeryPoint {
98 fn hash<H: Hasher>(&self, state: &mut H) {
99 // Do a round trip through a `FieldElement`. `as_bytes` is guaranteed to give a canonical
100 // 32-byte encoding
101 let canonical_bytes = FieldElement::from_bytes(&self.0).as_bytes();
102 canonical_bytes.hash(state);
103 }
104}
105
106impl Identity for MontgomeryPoint {
107 /// Return the group identity element, which has order 4.
108 fn identity() -> MontgomeryPoint {
109 MontgomeryPoint([0u8; 32])
110 }
111}
112
113#[cfg(feature = "zeroize")]
114impl Zeroize for MontgomeryPoint {
115 fn zeroize(&mut self) {
116 self.0.zeroize();
117 }
118}
119
120impl MontgomeryPoint {
121 /// Fixed-base scalar multiplication (i.e. multiplication by the base point).
122 pub fn mul_base(scalar: &Scalar) -> Self {
123 EdwardsPoint::mul_base(scalar).to_montgomery()
124 }
125
126 /// Multiply this point by `clamp_integer(bytes)`. For a description of clamping, see
127 /// [`clamp_integer`].
128 pub fn mul_clamped(self, bytes: [u8; 32]) -> Self {
129 // We have to construct a Scalar that is not reduced mod l, which breaks scalar invariant
130 // #2. But #2 is not necessary for correctness of variable-base multiplication. All that
131 // needs to hold is invariant #1, i.e., the scalar is less than 2^255. This is guaranteed
132 // by clamping.
133 // Further, we don't do any reduction or arithmetic with this clamped value, so there's no
134 // issues arising from the fact that the curve point is not necessarily in the prime-order
135 // subgroup.
136 let s = Scalar {
137 bytes: clamp_integer(bytes),
138 };
139 s * self
140 }
141
142 /// Multiply the basepoint by `clamp_integer(bytes)`. For a description of clamping, see
143 /// [`clamp_integer`].
144 pub fn mul_base_clamped(bytes: [u8; 32]) -> Self {
145 // See reasoning in Self::mul_clamped why it is OK to make an unreduced Scalar here. We
146 // note that fixed-base multiplication is also defined for all values of `bytes` less than
147 // 2^255.
148 let s = Scalar {
149 bytes: clamp_integer(bytes),
150 };
151 Self::mul_base(&s)
152 }
153
154 /// Given `self` \\( = u\_0(P) \\), and a big-endian bit representation of an integer
155 /// \\(n\\), return \\( u\_0(\[n\]P) \\). This is constant time in the length of `bits`.
156 ///
157 /// **NOTE:** You probably do not want to use this function. Almost every protocol built on
158 /// Curve25519 uses _clamped multiplication_, explained
159 /// [here](https://neilmadden.blog/2020/05/28/whats-the-curve25519-clamping-all-about/).
160 /// When in doubt, use [`Self::mul_clamped`].
161 pub fn mul_bits_be(&self, bits: impl Iterator<Item = bool>) -> MontgomeryPoint {
162 // Algorithm 8 of Costello-Smith 2017
163 let affine_u = FieldElement::from_bytes(&self.0);
164 let mut x0 = ProjectivePoint::identity();
165 let mut x1 = ProjectivePoint {
166 U: affine_u,
167 W: FieldElement::ONE,
168 };
169
170 // Go through the bits from most to least significant, using a sliding window of 2
171 let mut prev_bit = false;
172 for cur_bit in bits {
173 let choice: u8 = (prev_bit ^ cur_bit) as u8;
174
175 debug_assert!(choice == 0 || choice == 1);
176
177 ProjectivePoint::conditional_swap(&mut x0, &mut x1, choice.into());
178 differential_add_and_double(&mut x0, &mut x1, &affine_u);
179
180 prev_bit = cur_bit;
181 }
182 // The final value of prev_bit above is scalar.bits()[0], i.e., the LSB of scalar
183 ProjectivePoint::conditional_swap(&mut x0, &mut x1, Choice::from(prev_bit as u8));
184 // Don't leave the bit in the stack
185 #[cfg(feature = "zeroize")]
186 prev_bit.zeroize();
187
188 x0.as_affine()
189 }
190
191 /// View this `MontgomeryPoint` as an array of bytes.
192 pub const fn as_bytes(&self) -> &[u8; 32] {
193 &self.0
194 }
195
196 /// Convert this `MontgomeryPoint` to an array of bytes.
197 pub const fn to_bytes(&self) -> [u8; 32] {
198 self.0
199 }
200
201 /// Attempt to convert to an `EdwardsPoint`, using the supplied
202 /// choice of sign for the `EdwardsPoint`.
203 ///
204 /// # Inputs
205 ///
206 /// * `sign`: a `u8` donating the desired sign of the resulting
207 /// `EdwardsPoint`. `0` denotes positive and `1` negative.
208 ///
209 /// # Return
210 ///
211 /// * `Some(EdwardsPoint)` if `self` is the \\(u\\)-coordinate of a
212 /// point on (the Montgomery form of) Curve25519;
213 ///
214 /// * `None` if `self` is the \\(u\\)-coordinate of a point on the
215 /// twist of (the Montgomery form of) Curve25519;
216 ///
217 pub fn to_edwards(&self, sign: u8) -> Option<EdwardsPoint> {
218 // To decompress the Montgomery u coordinate to an
219 // `EdwardsPoint`, we apply the birational map to obtain the
220 // Edwards y coordinate, then do Edwards decompression.
221 //
222 // The birational map is y = (u-1)/(u+1).
223 //
224 // The exceptional points are the zeros of the denominator,
225 // i.e., u = -1.
226 //
227 // But when u = -1, v^2 = u*(u^2+486662*u+1) = 486660.
228 //
229 // Since this is nonsquare mod p, u = -1 corresponds to a point
230 // on the twist, not the curve, so we can reject it early.
231
232 let u = FieldElement::from_bytes(&self.0);
233
234 if u == FieldElement::MINUS_ONE {
235 return None;
236 }
237
238 let one = FieldElement::ONE;
239
240 let y = &(&u - &one) * &(&u + &one).invert();
241
242 let mut y_bytes = y.as_bytes();
243 y_bytes[31] ^= sign << 7;
244
245 CompressedEdwardsY(y_bytes).decompress()
246 }
247}
248
249/// Perform the Elligator2 mapping to a Montgomery point.
250///
251/// See <https://tools.ietf.org/html/draft-irtf-cfrg-hash-to-curve-10#section-6.7.1>
252//
253// TODO Determine how much of the hash-to-group API should be exposed after the CFRG
254// draft gets into a more polished/accepted state.
255#[allow(unused)]
256pub(crate) fn elligator_encode(r_0: &FieldElement) -> MontgomeryPoint {
257 let one = FieldElement::ONE;
258 let d_1 = &one + &r_0.square2(); /* 2r^2 */
259
260 let d = &MONTGOMERY_A_NEG * &(d_1.invert()); /* A/(1+2r^2) */
261
262 let d_sq = &d.square();
263 let au = &MONTGOMERY_A * &d;
264
265 let inner = &(d_sq + &au) + &one;
266 let eps = &d * &inner; /* eps = d^3 + Ad^2 + d */
267
268 let (eps_is_sq, _eps) = FieldElement::sqrt_ratio_i(&eps, &one);
269
270 let zero = FieldElement::ZERO;
271 let Atemp = FieldElement::conditional_select(&MONTGOMERY_A, &zero, eps_is_sq); /* 0, or A if nonsquare*/
272 let mut u = &d + &Atemp; /* d, or d+A if nonsquare */
273 u.conditional_negate(!eps_is_sq); /* d, or -d-A if nonsquare */
274
275 MontgomeryPoint(u.as_bytes())
276}
277
278/// A `ProjectivePoint` holds a point on the projective line
279/// \\( \mathbb P(\mathbb F\_p) \\), which we identify with the Kummer
280/// line of the Montgomery curve.
281#[derive(Copy, Clone, Debug)]
282struct ProjectivePoint {
283 pub U: FieldElement,
284 pub W: FieldElement,
285}
286
287impl Identity for ProjectivePoint {
288 fn identity() -> ProjectivePoint {
289 ProjectivePoint {
290 U: FieldElement::ONE,
291 W: FieldElement::ZERO,
292 }
293 }
294}
295
296impl Default for ProjectivePoint {
297 fn default() -> ProjectivePoint {
298 ProjectivePoint::identity()
299 }
300}
301
302impl ConditionallySelectable for ProjectivePoint {
303 fn conditional_select(
304 a: &ProjectivePoint,
305 b: &ProjectivePoint,
306 choice: Choice,
307 ) -> ProjectivePoint {
308 ProjectivePoint {
309 U: FieldElement::conditional_select(&a.U, &b.U, choice),
310 W: FieldElement::conditional_select(&a.W, &b.W, choice),
311 }
312 }
313}
314
315impl ProjectivePoint {
316 /// Dehomogenize this point to affine coordinates.
317 ///
318 /// # Return
319 ///
320 /// * \\( u = U / W \\) if \\( W \neq 0 \\);
321 /// * \\( 0 \\) if \\( W \eq 0 \\);
322 pub fn as_affine(&self) -> MontgomeryPoint {
323 let u = &self.U * &self.W.invert();
324 MontgomeryPoint(u.as_bytes())
325 }
326}
327
328/// Perform the double-and-add step of the Montgomery ladder.
329///
330/// Given projective points
331/// \\( (U\_P : W\_P) = u(P) \\),
332/// \\( (U\_Q : W\_Q) = u(Q) \\),
333/// and the affine difference
334/// \\( u\_{P-Q} = u(P-Q) \\), set
335/// $$
336/// (U\_P : W\_P) \gets u(\[2\]P)
337/// $$
338/// and
339/// $$
340/// (U\_Q : W\_Q) \gets u(P + Q).
341/// $$
342#[rustfmt::skip] // keep alignment of explanatory comments
343fn differential_add_and_double(
344 P: &mut ProjectivePoint,
345 Q: &mut ProjectivePoint,
346 affine_PmQ: &FieldElement,
347) {
348 let t0 = &P.U + &P.W;
349 let t1 = &P.U - &P.W;
350 let t2 = &Q.U + &Q.W;
351 let t3 = &Q.U - &Q.W;
352
353 let t4 = t0.square(); // (U_P + W_P)^2 = U_P^2 + 2 U_P W_P + W_P^2
354 let t5 = t1.square(); // (U_P - W_P)^2 = U_P^2 - 2 U_P W_P + W_P^2
355
356 let t6 = &t4 - &t5; // 4 U_P W_P
357
358 let t7 = &t0 * &t3; // (U_P + W_P) (U_Q - W_Q) = U_P U_Q + W_P U_Q - U_P W_Q - W_P W_Q
359 let t8 = &t1 * &t2; // (U_P - W_P) (U_Q + W_Q) = U_P U_Q - W_P U_Q + U_P W_Q - W_P W_Q
360
361 let t9 = &t7 + &t8; // 2 (U_P U_Q - W_P W_Q)
362 let t10 = &t7 - &t8; // 2 (W_P U_Q - U_P W_Q)
363
364 let t11 = t9.square(); // 4 (U_P U_Q - W_P W_Q)^2
365 let t12 = t10.square(); // 4 (W_P U_Q - U_P W_Q)^2
366
367 let t13 = &APLUS2_OVER_FOUR * &t6; // (A + 2) U_P U_Q
368
369 let t14 = &t4 * &t5; // ((U_P + W_P)(U_P - W_P))^2 = (U_P^2 - W_P^2)^2
370 let t15 = &t13 + &t5; // (U_P - W_P)^2 + (A + 2) U_P W_P
371
372 let t16 = &t6 * &t15; // 4 (U_P W_P) ((U_P - W_P)^2 + (A + 2) U_P W_P)
373
374 let t17 = affine_PmQ * &t12; // U_D * 4 (W_P U_Q - U_P W_Q)^2
375 let t18 = t11; // W_D * 4 (U_P U_Q - W_P W_Q)^2
376
377 P.U = t14; // U_{P'} = (U_P + W_P)^2 (U_P - W_P)^2
378 P.W = t16; // W_{P'} = (4 U_P W_P) ((U_P - W_P)^2 + ((A + 2)/4) 4 U_P W_P)
379 Q.U = t18; // U_{Q'} = W_D * 4 (U_P U_Q - W_P W_Q)^2
380 Q.W = t17; // W_{Q'} = U_D * 4 (W_P U_Q - U_P W_Q)^2
381}
382
383define_mul_assign_variants!(LHS = MontgomeryPoint, RHS = Scalar);
384
385define_mul_variants!(
386 LHS = MontgomeryPoint,
387 RHS = Scalar,
388 Output = MontgomeryPoint
389);
390define_mul_variants!(
391 LHS = Scalar,
392 RHS = MontgomeryPoint,
393 Output = MontgomeryPoint
394);
395
396/// Multiply this `MontgomeryPoint` by a `Scalar`.
397impl Mul<&Scalar> for &MontgomeryPoint {
398 type Output = MontgomeryPoint;
399
400 /// Given `self` \\( = u\_0(P) \\), and a `Scalar` \\(n\\), return \\( u\_0(\[n\]P) \\)
401 fn mul(self, scalar: &Scalar) -> MontgomeryPoint {
402 // We multiply by the integer representation of the given Scalar. By scalar invariant #1,
403 // the MSB is 0, so we can skip it.
404 self.mul_bits_be(scalar.bits_le().rev().skip(1))
405 }
406}
407
408impl MulAssign<&Scalar> for MontgomeryPoint {
409 fn mul_assign(&mut self, scalar: &Scalar) {
410 *self = (self as &MontgomeryPoint) * scalar;
411 }
412}
413
414impl Mul<&MontgomeryPoint> for &Scalar {
415 type Output = MontgomeryPoint;
416
417 fn mul(self, point: &MontgomeryPoint) -> MontgomeryPoint {
418 point * self
419 }
420}
421
422// ------------------------------------------------------------------------
423// Tests
424// ------------------------------------------------------------------------
425
426#[cfg(test)]
427mod test {
428 use super::*;
429 use crate::constants;
430
431 #[cfg(feature = "alloc")]
432 use alloc::vec::Vec;
433
434 use rand_core::{CryptoRng, RngCore};
435
436 #[test]
437 fn identity_in_different_coordinates() {
438 let id_projective = ProjectivePoint::identity();
439 let id_montgomery = id_projective.as_affine();
440
441 assert!(id_montgomery == MontgomeryPoint::identity());
442 }
443
444 #[test]
445 fn identity_in_different_models() {
446 assert!(EdwardsPoint::identity().to_montgomery() == MontgomeryPoint::identity());
447 }
448
449 #[test]
450 #[cfg(feature = "serde")]
451 fn serde_bincode_basepoint_roundtrip() {
452 use bincode;
453
454 let encoded = bincode::serialize(&constants::X25519_BASEPOINT).unwrap();
455 let decoded: MontgomeryPoint = bincode::deserialize(&encoded).unwrap();
456
457 assert_eq!(encoded.len(), 32);
458 assert_eq!(decoded, constants::X25519_BASEPOINT);
459
460 let raw_bytes = constants::X25519_BASEPOINT.as_bytes();
461 let bp: MontgomeryPoint = bincode::deserialize(raw_bytes).unwrap();
462 assert_eq!(bp, constants::X25519_BASEPOINT);
463 }
464
465 /// Test Montgomery -> Edwards on the X/Ed25519 basepoint
466 #[test]
467 fn basepoint_montgomery_to_edwards() {
468 // sign bit = 0 => basepoint
469 assert_eq!(
470 constants::ED25519_BASEPOINT_POINT,
471 constants::X25519_BASEPOINT.to_edwards(0).unwrap()
472 );
473 // sign bit = 1 => minus basepoint
474 assert_eq!(
475 -constants::ED25519_BASEPOINT_POINT,
476 constants::X25519_BASEPOINT.to_edwards(1).unwrap()
477 );
478 }
479
480 /// Test Edwards -> Montgomery on the X/Ed25519 basepoint
481 #[test]
482 fn basepoint_edwards_to_montgomery() {
483 assert_eq!(
484 constants::ED25519_BASEPOINT_POINT.to_montgomery(),
485 constants::X25519_BASEPOINT
486 );
487 }
488
489 /// Check that Montgomery -> Edwards fails for points on the twist.
490 #[test]
491 fn montgomery_to_edwards_rejects_twist() {
492 let one = FieldElement::ONE;
493
494 // u = 2 corresponds to a point on the twist.
495 let two = MontgomeryPoint((&one + &one).as_bytes());
496
497 assert!(two.to_edwards(0).is_none());
498
499 // u = -1 corresponds to a point on the twist, but should be
500 // checked explicitly because it's an exceptional point for the
501 // birational map. For instance, libsignal will accept it.
502 let minus_one = MontgomeryPoint((-&one).as_bytes());
503
504 assert!(minus_one.to_edwards(0).is_none());
505 }
506
507 #[test]
508 fn eq_defined_mod_p() {
509 let mut u18_bytes = [0u8; 32];
510 u18_bytes[0] = 18;
511 let u18 = MontgomeryPoint(u18_bytes);
512 let u18_unred = MontgomeryPoint([255; 32]);
513
514 assert_eq!(u18, u18_unred);
515 }
516
517 /// Returns a random point on the prime-order subgroup
518 fn rand_prime_order_point(mut rng: impl RngCore + CryptoRng) -> EdwardsPoint {
519 let s: Scalar = Scalar::random(&mut rng);
520 EdwardsPoint::mul_base(&s)
521 }
522
523 /// Given a bytestring that's little-endian at the byte level, return an iterator over all the
524 /// bits, in little-endian order.
525 fn bytestring_bits_le(x: &[u8]) -> impl DoubleEndedIterator<Item = bool> + Clone + '_ {
526 let bitlen = x.len() * 8;
527 (0..bitlen).map(|i| {
528 // As i runs from 0..256, the bottom 3 bits index the bit, while the upper bits index
529 // the byte. Since self.bytes is little-endian at the byte level, this iterator is
530 // little-endian on the bit level
531 ((x[i >> 3] >> (i & 7)) & 1u8) == 1
532 })
533 }
534
535 #[test]
536 fn montgomery_ladder_matches_edwards_scalarmult() {
537 let mut csprng = rand_core::OsRng;
538
539 for _ in 0..100 {
540 let p_edwards = rand_prime_order_point(&mut csprng);
541 let p_montgomery: MontgomeryPoint = p_edwards.to_montgomery();
542
543 let s: Scalar = Scalar::random(&mut csprng);
544 let expected = s * p_edwards;
545 let result = s * p_montgomery;
546
547 assert_eq!(result, expected.to_montgomery())
548 }
549 }
550
551 // Tests that, on the prime-order subgroup, MontgomeryPoint::mul_bits_be is the same as
552 // multiplying by the Scalar representation of the same bits
553 #[test]
554 fn montgomery_mul_bits_be() {
555 let mut csprng = rand_core::OsRng;
556
557 for _ in 0..100 {
558 // Make a random prime-order point P
559 let p_edwards = rand_prime_order_point(&mut csprng);
560 let p_montgomery: MontgomeryPoint = p_edwards.to_montgomery();
561
562 // Make a random integer b
563 let mut bigint = [0u8; 64];
564 csprng.fill_bytes(&mut bigint[..]);
565 let bigint_bits_be = bytestring_bits_le(&bigint).rev();
566
567 // Check that bP is the same whether calculated as scalar-times-edwards or
568 // integer-times-montgomery.
569 let expected = Scalar::from_bytes_mod_order_wide(&bigint) * p_edwards;
570 let result = p_montgomery.mul_bits_be(bigint_bits_be);
571 assert_eq!(result, expected.to_montgomery())
572 }
573 }
574
575 // Tests that MontgomeryPoint::mul_bits_be is consistent on any point, even ones that might be
576 // on the curve's twist. Specifically, this tests that b₁(b₂P) == b₂(b₁P) for random
577 // integers b₁, b₂ and random (curve or twist) point P.
578 #[test]
579 fn montgomery_mul_bits_be_twist() {
580 let mut csprng = rand_core::OsRng;
581
582 for _ in 0..100 {
583 // Make a random point P on the curve or its twist
584 let p_montgomery = {
585 let mut buf = [0u8; 32];
586 csprng.fill_bytes(&mut buf);
587 MontgomeryPoint(buf)
588 };
589
590 // Compute two big integers b₁ and b₂
591 let mut bigint1 = [0u8; 64];
592 let mut bigint2 = [0u8; 64];
593 csprng.fill_bytes(&mut bigint1[..]);
594 csprng.fill_bytes(&mut bigint2[..]);
595
596 // Compute b₁P and b₂P
597 let bigint1_bits_be = bytestring_bits_le(&bigint1).rev();
598 let bigint2_bits_be = bytestring_bits_le(&bigint2).rev();
599 let prod1 = p_montgomery.mul_bits_be(bigint1_bits_be.clone());
600 let prod2 = p_montgomery.mul_bits_be(bigint2_bits_be.clone());
601
602 // Check that b₁(b₂P) == b₂(b₁P)
603 assert_eq!(
604 prod1.mul_bits_be(bigint2_bits_be),
605 prod2.mul_bits_be(bigint1_bits_be)
606 );
607 }
608 }
609
610 /// Check that mul_base_clamped and mul_clamped agree
611 #[test]
612 fn mul_base_clamped() {
613 let mut csprng = rand_core::OsRng;
614
615 // Test agreement on a large integer. Even after clamping, this is not reduced mod l.
616 let a_bytes = [0xff; 32];
617 assert_eq!(
618 MontgomeryPoint::mul_base_clamped(a_bytes),
619 constants::X25519_BASEPOINT.mul_clamped(a_bytes)
620 );
621
622 // Test agreement on random integers
623 for _ in 0..100 {
624 // This will be reduced mod l with probability l / 2^256 ≈ 6.25%
625 let mut a_bytes = [0u8; 32];
626 csprng.fill_bytes(&mut a_bytes);
627
628 assert_eq!(
629 MontgomeryPoint::mul_base_clamped(a_bytes),
630 constants::X25519_BASEPOINT.mul_clamped(a_bytes)
631 );
632 }
633 }
634
635 #[cfg(feature = "alloc")]
636 const ELLIGATOR_CORRECT_OUTPUT: [u8; 32] = [
637 0x5f, 0x35, 0x20, 0x00, 0x1c, 0x6c, 0x99, 0x36, 0xa3, 0x12, 0x06, 0xaf, 0xe7, 0xc7, 0xac,
638 0x22, 0x4e, 0x88, 0x61, 0x61, 0x9b, 0xf9, 0x88, 0x72, 0x44, 0x49, 0x15, 0x89, 0x9d, 0x95,
639 0xf4, 0x6e,
640 ];
641
642 #[test]
643 #[cfg(feature = "alloc")]
644 fn montgomery_elligator_correct() {
645 let bytes: Vec<u8> = (0u8..32u8).collect();
646 let bits_in: [u8; 32] = (&bytes[..]).try_into().expect("Range invariant broken");
647
648 let fe = FieldElement::from_bytes(&bits_in);
649 let eg = elligator_encode(&fe);
650 assert_eq!(eg.to_bytes(), ELLIGATOR_CORRECT_OUTPUT);
651 }
652
653 #[test]
654 fn montgomery_elligator_zero_zero() {
655 let zero = [0u8; 32];
656 let fe = FieldElement::from_bytes(&zero);
657 let eg = elligator_encode(&fe);
658 assert_eq!(eg.to_bytes(), zero);
659 }
660}