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