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