From 61d9e1a5c58f5ca1d129c63b36d235d23c0442ce Mon Sep 17 00:00:00 2001 From: Michael Rosenberg Date: Sun, 30 Jul 2023 10:28:55 -0400 Subject: [PATCH 01/10] Defined MontgomeryPoint::mul_bits_be --- curve25519-dalek/src/montgomery.rs | 99 ++++++++++++++++++++++-------- 1 file changed, 74 insertions(+), 25 deletions(-) diff --git a/curve25519-dalek/src/montgomery.rs b/curve25519-dalek/src/montgomery.rs index a42218c3e..f4364f16e 100644 --- a/curve25519-dalek/src/montgomery.rs +++ b/curve25519-dalek/src/montgomery.rs @@ -117,6 +117,18 @@ impl Zeroize for MontgomeryPoint { } } +/// Given a bytestring that's little-endian at the byte level, return an iterator over all the +/// bits, in little-endian order. +pub(crate) fn bytestring_bits_le(x: &[u8]) -> impl DoubleEndedIterator + '_ { + let bitlen = x.len() * 8; + (0..bitlen).map(|i| { + // As i runs from 0..256, the bottom 3 bits index the bit, while the upper bits index + // the byte. Since self.bytes is little-endian at the byte level, this iterator is + // little-endian on the bit level + ((x[i >> 3] >> (i & 7)) & 1u8) == 1 + }) +} + impl MontgomeryPoint { /// Fixed-base scalar multiplication (i.e. multiplication by the base point). pub fn mul_base(scalar: &Scalar) -> Self { @@ -126,17 +138,12 @@ impl MontgomeryPoint { /// Multiply this point by `clamp_integer(bytes)`. For a description of clamping, see /// [`clamp_integer`]. pub fn mul_clamped(self, bytes: [u8; 32]) -> Self { - // We have to construct a Scalar that is not reduced mod l, which breaks scalar invariant - // #2. But #2 is not necessary for correctness of variable-base multiplication. All that - // needs to hold is invariant #1, i.e., the scalar is less than 2^255. This is guaranteed - // by clamping. - // Further, we don't do any reduction or arithmetic with this clamped value, so there's no - // issues arising from the fact that the curve point is not necessarily in the prime-order - // subgroup. - let s = Scalar { - bytes: clamp_integer(bytes), - }; - s * self + // Clamp the integer, convert to bits, and multiply + let clamped = clamp_integer(bytes); + // Clamping sets the the MSB 0, so we skip it + let le_bits = bytestring_bits_le(&clamped).rev().skip(1); + + self._mul_bits_be(le_bits) } /// Multiply the basepoint by `clamp_integer(bytes)`. For a description of clamping, see @@ -356,12 +363,13 @@ define_mul_variants!( Output = MontgomeryPoint ); -/// Multiply this `MontgomeryPoint` by a `Scalar`. -impl<'a, 'b> Mul<&'b Scalar> for &'a MontgomeryPoint { - type Output = MontgomeryPoint; - - /// Given `self` \\( = u\_0(P) \\), and a `Scalar` \\(n\\), return \\( u\_0(\[n\]P) \\). - fn mul(self, scalar: &'b Scalar) -> MontgomeryPoint { +impl MontgomeryPoint { + /// Given `self` \\( = u\_0(P) \\), and a big-endian bit representation of an integer + /// \\(n\\), return \\( u\_0(\[n\]P) \\). + /// + /// This is a helper function that's hidden by default. Below is a wrapper that's made public + /// when the `hazmat` feature is set. + fn _mul_bits_be(self, bits: impl Iterator) -> MontgomeryPoint { // Algorithm 8 of Costello-Smith 2017 let affine_u = FieldElement::from_bytes(&self.0); let mut x0 = ProjectivePoint::identity(); @@ -370,12 +378,8 @@ impl<'a, 'b> Mul<&'b Scalar> for &'a MontgomeryPoint { W: FieldElement::ONE, }; - // NOTE: The below swap-double-add routine skips the first iteration, i.e., it assumes the - // MSB of `scalar` is 0. This is allowed, since it follows from Scalar invariant #1. - // Go through the bits from most to least significant, using a sliding window of 2 - let mut bits = scalar.bits_le().rev(); - let mut prev_bit = bits.next().unwrap(); + let mut prev_bit = false; for cur_bit in bits { let choice: u8 = (prev_bit ^ cur_bit) as u8; @@ -394,6 +398,25 @@ impl<'a, 'b> Mul<&'b Scalar> for &'a MontgomeryPoint { x0.as_affine() } + + /// Given `self` \\( = u\_0(P) \\), and a big-endian bit representation of an integer + /// \\(n\\), return \\( u\_0(\[n\]P) \\). + #[cfg(feature = "hazmat")] + fn mul_bits_be(self, bits: impl Iterator) -> MontgomeryPoint { + self.mul_bits_be(bits) + } +} + +/// Multiply this `MontgomeryPoint` by a `Scalar`. +impl<'a, 'b> Mul<&'b Scalar> for &'a MontgomeryPoint { + type Output = MontgomeryPoint; + + /// Given `self` \\( = u\_0(P) \\), and a `Scalar` \\(n\\), return \\( u\_0(\[n\]P) \\). + fn mul(self, scalar: &'b Scalar) -> MontgomeryPoint { + // We multiply by the integer representation of the given Scalar. By scalar invariant #1, + // the MSB is 0, so we can skip it. + self._mul_bits_be(scalar.bits_le().rev().skip(1)) + } } impl<'b> MulAssign<&'b Scalar> for MontgomeryPoint { @@ -422,7 +445,7 @@ mod test { #[cfg(feature = "alloc")] use alloc::vec::Vec; - use rand_core::RngCore; + use rand_core::{CryptoRng, RngCore}; #[test] fn identity_in_different_coordinates() { @@ -505,15 +528,20 @@ mod test { assert_eq!(u18, u18_unred); } + fn rand_point(mut rng: impl RngCore + CryptoRng) -> EdwardsPoint { + let s: Scalar = Scalar::random(&mut rng); + EdwardsPoint::mul_base(&s) + } + #[test] fn montgomery_ladder_matches_edwards_scalarmult() { let mut csprng = rand_core::OsRng; for _ in 0..100 { - let s: Scalar = Scalar::random(&mut csprng); - let p_edwards = EdwardsPoint::mul_base(&s); + let p_edwards = rand_point(&mut csprng); let p_montgomery: MontgomeryPoint = p_edwards.to_montgomery(); + let s: Scalar = Scalar::random(&mut csprng); let expected = s * p_edwards; let result = s * p_montgomery; @@ -521,6 +549,27 @@ mod test { } } + // Tests that point._mul_bits_be(bits) is the same as multiplying by the Scalar representation + // of the bits. + #[test] + fn montgomery_mul_bits_be() { + let mut csprng = rand_core::OsRng; + + for _ in 0..100 { + let p_edwards = rand_point(&mut csprng); + let p_montgomery: MontgomeryPoint = p_edwards.to_montgomery(); + + let mut bigint = [0u8; 64]; + csprng.fill_bytes(&mut bigint[..]); + let bigint_bits_be = bytestring_bits_le(&bigint).rev(); + + let expected = Scalar::from_bytes_mod_order_wide(&bigint) * p_edwards; + let result = p_montgomery._mul_bits_be(bigint_bits_be); + + assert_eq!(result, expected.to_montgomery()) + } + } + /// Check that mul_base_clamped and mul_clamped agree #[test] fn mul_base_clamped() { From b06ae947f59da04aa64c09f47b0b57780039512e Mon Sep 17 00:00:00 2001 From: Michael Rosenberg Date: Sun, 30 Jul 2023 10:31:12 -0400 Subject: [PATCH 02/10] Removed bytestring_bits_le helper function --- curve25519-dalek/src/montgomery.rs | 41 +++++++++++++++++------------- 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/curve25519-dalek/src/montgomery.rs b/curve25519-dalek/src/montgomery.rs index f4364f16e..20571f4ae 100644 --- a/curve25519-dalek/src/montgomery.rs +++ b/curve25519-dalek/src/montgomery.rs @@ -117,18 +117,6 @@ impl Zeroize for MontgomeryPoint { } } -/// Given a bytestring that's little-endian at the byte level, return an iterator over all the -/// bits, in little-endian order. -pub(crate) fn bytestring_bits_le(x: &[u8]) -> impl DoubleEndedIterator + '_ { - let bitlen = x.len() * 8; - (0..bitlen).map(|i| { - // As i runs from 0..256, the bottom 3 bits index the bit, while the upper bits index - // the byte. Since self.bytes is little-endian at the byte level, this iterator is - // little-endian on the bit level - ((x[i >> 3] >> (i & 7)) & 1u8) == 1 - }) -} - impl MontgomeryPoint { /// Fixed-base scalar multiplication (i.e. multiplication by the base point). pub fn mul_base(scalar: &Scalar) -> Self { @@ -138,12 +126,17 @@ impl MontgomeryPoint { /// Multiply this point by `clamp_integer(bytes)`. For a description of clamping, see /// [`clamp_integer`]. pub fn mul_clamped(self, bytes: [u8; 32]) -> Self { - // Clamp the integer, convert to bits, and multiply - let clamped = clamp_integer(bytes); - // Clamping sets the the MSB 0, so we skip it - let le_bits = bytestring_bits_le(&clamped).rev().skip(1); - - self._mul_bits_be(le_bits) + // We have to construct a Scalar that is not reduced mod l, which breaks scalar invariant + // #2. But #2 is not necessary for correctness of variable-base multiplication. All that + // needs to hold is invariant #1, i.e., the scalar is less than 2^255. This is guaranteed + // by clamping. + // Further, we don't do any reduction or arithmetic with this clamped value, so there's no + // issues arising from the fact that the curve point is not necessarily in the prime-order + // subgroup. + let s = Scalar { + bytes: clamp_integer(bytes), + }; + s * self } /// Multiply the basepoint by `clamp_integer(bytes)`. For a description of clamping, see @@ -533,6 +526,18 @@ mod test { EdwardsPoint::mul_base(&s) } + /// Given a bytestring that's little-endian at the byte level, return an iterator over all the + /// bits, in little-endian order. + fn bytestring_bits_le(x: &[u8]) -> impl DoubleEndedIterator + '_ { + let bitlen = x.len() * 8; + (0..bitlen).map(|i| { + // As i runs from 0..256, the bottom 3 bits index the bit, while the upper bits index + // the byte. Since self.bytes is little-endian at the byte level, this iterator is + // little-endian on the bit level + ((x[i >> 3] >> (i & 7)) & 1u8) == 1 + }) + } + #[test] fn montgomery_ladder_matches_edwards_scalarmult() { let mut csprng = rand_core::OsRng; From ed53307c165d6fa5acb105829a5b253717362da1 Mon Sep 17 00:00:00 2001 From: Michael Rosenberg Date: Sun, 30 Jul 2023 10:42:44 -0400 Subject: [PATCH 03/10] Made clippy happy --- curve25519-dalek-derive/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/curve25519-dalek-derive/src/lib.rs b/curve25519-dalek-derive/src/lib.rs index 53877493e..6e5920bb5 100644 --- a/curve25519-dalek-derive/src/lib.rs +++ b/curve25519-dalek-derive/src/lib.rs @@ -110,8 +110,8 @@ pub fn unsafe_target_feature_specialize( let features: Vec<_> = attributes .lit() .value() - .split(",") - .map(|feature| feature.replace(" ", "")) + .split(',') + .map(|feature| feature.replace(' ', "")) .collect(); let name = format!("{}_{}", item_mod.ident, features.join("_")); let ident = syn::Ident::new(&name, item_mod.ident.span()); From 4827cd7882cad0fe21e49a60763129b0ca3f550f Mon Sep 17 00:00:00 2001 From: Michael Rosenberg Date: Wed, 2 Aug 2023 00:24:36 -0400 Subject: [PATCH 04/10] Added another consistency test to MontgomeryPoint::_mul_bits_be --- curve25519-dalek/src/montgomery.rs | 49 ++++++++++++++++++++++++++---- 1 file changed, 43 insertions(+), 6 deletions(-) diff --git a/curve25519-dalek/src/montgomery.rs b/curve25519-dalek/src/montgomery.rs index 20571f4ae..e61939673 100644 --- a/curve25519-dalek/src/montgomery.rs +++ b/curve25519-dalek/src/montgomery.rs @@ -521,7 +521,8 @@ mod test { assert_eq!(u18, u18_unred); } - fn rand_point(mut rng: impl RngCore + CryptoRng) -> EdwardsPoint { + /// Returns a random point on the prime-order subgroup + fn rand_prime_order_point(mut rng: impl RngCore + CryptoRng) -> EdwardsPoint { let s: Scalar = Scalar::random(&mut rng); EdwardsPoint::mul_base(&s) } @@ -543,7 +544,7 @@ mod test { let mut csprng = rand_core::OsRng; for _ in 0..100 { - let p_edwards = rand_point(&mut csprng); + let p_edwards = rand_prime_order_point(&mut csprng); let p_montgomery: MontgomeryPoint = p_edwards.to_montgomery(); let s: Scalar = Scalar::random(&mut csprng); @@ -554,27 +555,63 @@ mod test { } } - // Tests that point._mul_bits_be(bits) is the same as multiplying by the Scalar representation - // of the bits. + // Tests that, on the prime-order subgroup, MontgomeryPoint::_mul_bits_be is the same as + // multiplying by the Scalar representation of the same bits #[test] fn montgomery_mul_bits_be() { let mut csprng = rand_core::OsRng; for _ in 0..100 { - let p_edwards = rand_point(&mut csprng); + // Make a random prime-order point P + let p_edwards = rand_prime_order_point(&mut csprng); let p_montgomery: MontgomeryPoint = p_edwards.to_montgomery(); + // Make a random integer b let mut bigint = [0u8; 64]; csprng.fill_bytes(&mut bigint[..]); let bigint_bits_be = bytestring_bits_le(&bigint).rev(); + // Check that bP is the same whether calculated as scalar-times-edwards or + // integer-times-montgomery. let expected = Scalar::from_bytes_mod_order_wide(&bigint) * p_edwards; let result = p_montgomery._mul_bits_be(bigint_bits_be); - assert_eq!(result, expected.to_montgomery()) } } + // Tests that MontgomeryPoint::_mul_bits_be is consistent on any point, even ones that might be + // on the curve's twist. Specifically, this tests that b₁(b₂P) == b₂(b₁P) for random + // integers b₁, b₂ and random (curve or twist) point P. + #[test] + fn montgomery_mul_bits_be_twist() { + let mut csprng = rand_core::OsRng; + + for _ in 0..100 { + // Make a random point P on the curve or its twist + let p_montgomery = { + let mut buf = [0u8; 32]; + csprng.fill_bytes(&mut buf); + MontgomeryPoint(buf) + }; + + // Compute two big integers b₁ and b₂ + let mut bigint1 = [0u8; 64]; + let mut bigint2 = [0u8; 64]; + csprng.fill_bytes(&mut bigint1[..]); + csprng.fill_bytes(&mut bigint2[..]); + + // Compute b₁P and b₂P + let prod1 = p_montgomery._mul_bits_be(bytestring_bits_le(&bigint1).rev()); + let prod2 = p_montgomery._mul_bits_be(bytestring_bits_le(&bigint2).rev()); + + // Check that b₁(b₂P) == b₂(b₁P) + assert_eq!( + prod1._mul_bits_be(bytestring_bits_le(&bigint2).rev()), + prod2._mul_bits_be(bytestring_bits_le(&bigint1).rev()) + ); + } + } + /// Check that mul_base_clamped and mul_clamped agree #[test] fn mul_base_clamped() { From 14788f830c018a7a135cc0f705a9c169f81d7d25 Mon Sep 17 00:00:00 2001 From: Michael Rosenberg Date: Wed, 2 Aug 2023 00:26:11 -0400 Subject: [PATCH 05/10] Tried again to make clippy happy --- curve25519-dalek/src/ristretto.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/curve25519-dalek/src/ristretto.rs b/curve25519-dalek/src/ristretto.rs index 03fa343f9..ac6060a1f 100644 --- a/curve25519-dalek/src/ristretto.rs +++ b/curve25519-dalek/src/ristretto.rs @@ -970,7 +970,7 @@ impl VartimeMultiscalarMul for RistrettoPoint { I::Item: Borrow, J: IntoIterator>, { - let extended_points = points.into_iter().map(|opt_P| opt_P.map(|P| P.borrow().0)); + let extended_points = points.into_iter().map(|opt_P| opt_P.map(|P| P.0)); EdwardsPoint::optional_multiscalar_mul(scalars, extended_points).map(RistrettoPoint) } From 228612a0cfecdb522e2c4c2aef1dcb45538ff901 Mon Sep 17 00:00:00 2001 From: Michael Rosenberg Date: Wed, 2 Aug 2023 00:29:45 -0400 Subject: [PATCH 06/10] Testing cleanup --- curve25519-dalek/src/montgomery.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/curve25519-dalek/src/montgomery.rs b/curve25519-dalek/src/montgomery.rs index e61939673..61d7b923c 100644 --- a/curve25519-dalek/src/montgomery.rs +++ b/curve25519-dalek/src/montgomery.rs @@ -529,7 +529,7 @@ mod test { /// Given a bytestring that's little-endian at the byte level, return an iterator over all the /// bits, in little-endian order. - fn bytestring_bits_le(x: &[u8]) -> impl DoubleEndedIterator + '_ { + fn bytestring_bits_le(x: &[u8]) -> impl DoubleEndedIterator + Clone + '_ { let bitlen = x.len() * 8; (0..bitlen).map(|i| { // As i runs from 0..256, the bottom 3 bits index the bit, while the upper bits index @@ -601,13 +601,15 @@ mod test { csprng.fill_bytes(&mut bigint2[..]); // Compute b₁P and b₂P - let prod1 = p_montgomery._mul_bits_be(bytestring_bits_le(&bigint1).rev()); - let prod2 = p_montgomery._mul_bits_be(bytestring_bits_le(&bigint2).rev()); + let bigint1_bits_be = bytestring_bits_le(&bigint1).rev(); + let bigint2_bits_be = bytestring_bits_le(&bigint2).rev(); + let prod1 = p_montgomery._mul_bits_be(bigint1_bits_be.clone()); + let prod2 = p_montgomery._mul_bits_be(bigint2_bits_be.clone()); // Check that b₁(b₂P) == b₂(b₁P) assert_eq!( - prod1._mul_bits_be(bytestring_bits_le(&bigint2).rev()), - prod2._mul_bits_be(bytestring_bits_le(&bigint1).rev()) + prod1._mul_bits_be(bigint2_bits_be), + prod2._mul_bits_be(bigint1_bits_be) ); } } From 2d59dadf91da660fd110bc3570092e5923a7f09d Mon Sep 17 00:00:00 2001 From: Michael Rosenberg Date: Mon, 7 Aug 2023 23:29:43 -0400 Subject: [PATCH 07/10] Removed unnecessary lifetimes from montgomery.src --- curve25519-dalek/src/montgomery.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/curve25519-dalek/src/montgomery.rs b/curve25519-dalek/src/montgomery.rs index 61d7b923c..c3b7f2e59 100644 --- a/curve25519-dalek/src/montgomery.rs +++ b/curve25519-dalek/src/montgomery.rs @@ -401,27 +401,27 @@ impl MontgomeryPoint { } /// Multiply this `MontgomeryPoint` by a `Scalar`. -impl<'a, 'b> Mul<&'b Scalar> for &'a MontgomeryPoint { +impl Mul<&Scalar> for &MontgomeryPoint { type Output = MontgomeryPoint; /// Given `self` \\( = u\_0(P) \\), and a `Scalar` \\(n\\), return \\( u\_0(\[n\]P) \\). - fn mul(self, scalar: &'b Scalar) -> MontgomeryPoint { + fn mul(self, scalar: &Scalar) -> MontgomeryPoint { // We multiply by the integer representation of the given Scalar. By scalar invariant #1, // the MSB is 0, so we can skip it. self._mul_bits_be(scalar.bits_le().rev().skip(1)) } } -impl<'b> MulAssign<&'b Scalar> for MontgomeryPoint { - fn mul_assign(&mut self, scalar: &'b Scalar) { +impl MulAssign<&Scalar> for MontgomeryPoint { + fn mul_assign(&mut self, scalar: &Scalar) { *self = (self as &MontgomeryPoint) * scalar; } } -impl<'a, 'b> Mul<&'b MontgomeryPoint> for &'a Scalar { +impl Mul<&MontgomeryPoint> for &Scalar { type Output = MontgomeryPoint; - fn mul(self, point: &'b MontgomeryPoint) -> MontgomeryPoint { + fn mul(self, point: &MontgomeryPoint) -> MontgomeryPoint { point * self } } From 2a74ba0082921902e525bae59414e6dcc14e5331 Mon Sep 17 00:00:00 2001 From: Michael Rosenberg Date: Tue, 8 Aug 2023 00:01:09 -0400 Subject: [PATCH 08/10] Made mul_bits_be public --- curve25519-dalek/src/montgomery.rs | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/curve25519-dalek/src/montgomery.rs b/curve25519-dalek/src/montgomery.rs index c3b7f2e59..4311576e6 100644 --- a/curve25519-dalek/src/montgomery.rs +++ b/curve25519-dalek/src/montgomery.rs @@ -358,11 +358,13 @@ define_mul_variants!( impl MontgomeryPoint { /// Given `self` \\( = u\_0(P) \\), and a big-endian bit representation of an integer - /// \\(n\\), return \\( u\_0(\[n\]P) \\). + /// \\(n\\), return \\( u\_0(\[n\]P) \\). This is constant time in the length of `bits`. /// - /// This is a helper function that's hidden by default. Below is a wrapper that's made public - /// when the `hazmat` feature is set. - fn _mul_bits_be(self, bits: impl Iterator) -> MontgomeryPoint { + /// **NOTE:** You probably do not want to use this function. Almost every protocol built on + /// Curve25519 uses _clamped multiplication_, explained + /// [here](https://neilmadden.blog/2020/05/28/whats-the-curve25519-clamping-all-about/). + /// When in doubt, use [`Self::mul_clamped`]. + pub fn mul_bits_be(self, bits: impl Iterator) -> MontgomeryPoint { // Algorithm 8 of Costello-Smith 2017 let affine_u = FieldElement::from_bytes(&self.0); let mut x0 = ProjectivePoint::identity(); @@ -391,24 +393,17 @@ impl MontgomeryPoint { x0.as_affine() } - - /// Given `self` \\( = u\_0(P) \\), and a big-endian bit representation of an integer - /// \\(n\\), return \\( u\_0(\[n\]P) \\). - #[cfg(feature = "hazmat")] - fn mul_bits_be(self, bits: impl Iterator) -> MontgomeryPoint { - self.mul_bits_be(bits) - } } /// Multiply this `MontgomeryPoint` by a `Scalar`. impl Mul<&Scalar> for &MontgomeryPoint { type Output = MontgomeryPoint; - /// Given `self` \\( = u\_0(P) \\), and a `Scalar` \\(n\\), return \\( u\_0(\[n\]P) \\). + /// Given `self` \\( = u\_0(P) \\), and a `Scalar` \\(n\\), return \\( u\_0(\[n\]P) \\) fn mul(self, scalar: &Scalar) -> MontgomeryPoint { // We multiply by the integer representation of the given Scalar. By scalar invariant #1, // the MSB is 0, so we can skip it. - self._mul_bits_be(scalar.bits_le().rev().skip(1)) + self.mul_bits_be(scalar.bits_le().rev().skip(1)) } } From c9e2b08fc5dcec6564ff292a092047767a2067a2 Mon Sep 17 00:00:00 2001 From: Michael Rosenberg Date: Tue, 8 Aug 2023 00:02:38 -0400 Subject: [PATCH 09/10] Moved MontgomeryPoint::mul_bits_be --- curve25519-dalek/src/montgomery.rs | 90 +++++++++++++++--------------- 1 file changed, 44 insertions(+), 46 deletions(-) diff --git a/curve25519-dalek/src/montgomery.rs b/curve25519-dalek/src/montgomery.rs index 4311576e6..f7ec17781 100644 --- a/curve25519-dalek/src/montgomery.rs +++ b/curve25519-dalek/src/montgomery.rs @@ -151,6 +151,43 @@ impl MontgomeryPoint { Self::mul_base(&s) } + /// Given `self` \\( = u\_0(P) \\), and a big-endian bit representation of an integer + /// \\(n\\), return \\( u\_0(\[n\]P) \\). This is constant time in the length of `bits`. + /// + /// **NOTE:** You probably do not want to use this function. Almost every protocol built on + /// Curve25519 uses _clamped multiplication_, explained + /// [here](https://neilmadden.blog/2020/05/28/whats-the-curve25519-clamping-all-about/). + /// When in doubt, use [`Self::mul_clamped`]. + pub fn mul_bits_be(self, bits: impl Iterator) -> MontgomeryPoint { + // Algorithm 8 of Costello-Smith 2017 + let affine_u = FieldElement::from_bytes(&self.0); + let mut x0 = ProjectivePoint::identity(); + let mut x1 = ProjectivePoint { + U: affine_u, + W: FieldElement::ONE, + }; + + // Go through the bits from most to least significant, using a sliding window of 2 + let mut prev_bit = false; + for cur_bit in bits { + let choice: u8 = (prev_bit ^ cur_bit) as u8; + + debug_assert!(choice == 0 || choice == 1); + + ProjectivePoint::conditional_swap(&mut x0, &mut x1, choice.into()); + differential_add_and_double(&mut x0, &mut x1, &affine_u); + + prev_bit = cur_bit; + } + // The final value of prev_bit above is scalar.bits()[0], i.e., the LSB of scalar + ProjectivePoint::conditional_swap(&mut x0, &mut x1, Choice::from(prev_bit as u8)); + // Don't leave the bit in the stack + #[cfg(feature = "zeroize")] + prev_bit.zeroize(); + + x0.as_affine() + } + /// View this `MontgomeryPoint` as an array of bytes. pub const fn as_bytes(&self) -> &[u8; 32] { &self.0 @@ -356,45 +393,6 @@ define_mul_variants!( Output = MontgomeryPoint ); -impl MontgomeryPoint { - /// Given `self` \\( = u\_0(P) \\), and a big-endian bit representation of an integer - /// \\(n\\), return \\( u\_0(\[n\]P) \\). This is constant time in the length of `bits`. - /// - /// **NOTE:** You probably do not want to use this function. Almost every protocol built on - /// Curve25519 uses _clamped multiplication_, explained - /// [here](https://neilmadden.blog/2020/05/28/whats-the-curve25519-clamping-all-about/). - /// When in doubt, use [`Self::mul_clamped`]. - pub fn mul_bits_be(self, bits: impl Iterator) -> MontgomeryPoint { - // Algorithm 8 of Costello-Smith 2017 - let affine_u = FieldElement::from_bytes(&self.0); - let mut x0 = ProjectivePoint::identity(); - let mut x1 = ProjectivePoint { - U: affine_u, - W: FieldElement::ONE, - }; - - // Go through the bits from most to least significant, using a sliding window of 2 - let mut prev_bit = false; - for cur_bit in bits { - let choice: u8 = (prev_bit ^ cur_bit) as u8; - - debug_assert!(choice == 0 || choice == 1); - - ProjectivePoint::conditional_swap(&mut x0, &mut x1, choice.into()); - differential_add_and_double(&mut x0, &mut x1, &affine_u); - - prev_bit = cur_bit; - } - // The final value of prev_bit above is scalar.bits()[0], i.e., the LSB of scalar - ProjectivePoint::conditional_swap(&mut x0, &mut x1, Choice::from(prev_bit as u8)); - // Don't leave the bit in the stack - #[cfg(feature = "zeroize")] - prev_bit.zeroize(); - - x0.as_affine() - } -} - /// Multiply this `MontgomeryPoint` by a `Scalar`. impl Mul<&Scalar> for &MontgomeryPoint { type Output = MontgomeryPoint; @@ -550,7 +548,7 @@ mod test { } } - // Tests that, on the prime-order subgroup, MontgomeryPoint::_mul_bits_be is the same as + // Tests that, on the prime-order subgroup, MontgomeryPoint::mul_bits_be is the same as // multiplying by the Scalar representation of the same bits #[test] fn montgomery_mul_bits_be() { @@ -569,12 +567,12 @@ mod test { // Check that bP is the same whether calculated as scalar-times-edwards or // integer-times-montgomery. let expected = Scalar::from_bytes_mod_order_wide(&bigint) * p_edwards; - let result = p_montgomery._mul_bits_be(bigint_bits_be); + let result = p_montgomery.mul_bits_be(bigint_bits_be); assert_eq!(result, expected.to_montgomery()) } } - // Tests that MontgomeryPoint::_mul_bits_be is consistent on any point, even ones that might be + // Tests that MontgomeryPoint::mul_bits_be is consistent on any point, even ones that might be // on the curve's twist. Specifically, this tests that b₁(b₂P) == b₂(b₁P) for random // integers b₁, b₂ and random (curve or twist) point P. #[test] @@ -598,13 +596,13 @@ mod test { // Compute b₁P and b₂P let bigint1_bits_be = bytestring_bits_le(&bigint1).rev(); let bigint2_bits_be = bytestring_bits_le(&bigint2).rev(); - let prod1 = p_montgomery._mul_bits_be(bigint1_bits_be.clone()); - let prod2 = p_montgomery._mul_bits_be(bigint2_bits_be.clone()); + let prod1 = p_montgomery.mul_bits_be(bigint1_bits_be.clone()); + let prod2 = p_montgomery.mul_bits_be(bigint2_bits_be.clone()); // Check that b₁(b₂P) == b₂(b₁P) assert_eq!( - prod1._mul_bits_be(bigint2_bits_be), - prod2._mul_bits_be(bigint1_bits_be) + prod1.mul_bits_be(bigint2_bits_be), + prod2.mul_bits_be(bigint1_bits_be) ); } } From 82c5fa31339468248d24c085aff52002ab8521c1 Mon Sep 17 00:00:00 2001 From: Michael Rosenberg Date: Mon, 28 Aug 2023 01:43:24 -0400 Subject: [PATCH 10/10] Made mul_bits_be take a ref to self --- curve25519-dalek/src/montgomery.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/curve25519-dalek/src/montgomery.rs b/curve25519-dalek/src/montgomery.rs index f7ec17781..2be35cdc7 100644 --- a/curve25519-dalek/src/montgomery.rs +++ b/curve25519-dalek/src/montgomery.rs @@ -158,7 +158,7 @@ impl MontgomeryPoint { /// Curve25519 uses _clamped multiplication_, explained /// [here](https://neilmadden.blog/2020/05/28/whats-the-curve25519-clamping-all-about/). /// When in doubt, use [`Self::mul_clamped`]. - pub fn mul_bits_be(self, bits: impl Iterator) -> MontgomeryPoint { + pub fn mul_bits_be(&self, bits: impl Iterator) -> MontgomeryPoint { // Algorithm 8 of Costello-Smith 2017 let affine_u = FieldElement::from_bytes(&self.0); let mut x0 = ProjectivePoint::identity();