From f95de3a01166c354296d4e44c540f2b7d08303c8 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Mon, 12 Dec 2022 14:54:29 +1300 Subject: [PATCH 01/16] Implement `ff` traits for `Scalar` These are placed behind the new `ff` feature flag. --- .github/workflows/rust.yml | 1 + CHANGELOG.md | 1 + Cargo.toml | 2 + src/scalar.rs | 176 +++++++++++++++++++++++++++++++++++++ 4 files changed, 180 insertions(+) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 48896192a..2eb912d50 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -29,6 +29,7 @@ jobs: - run: ${{ matrix.deps }} - run: cargo test --target ${{ matrix.target }} --no-default-features - run: cargo test --target ${{ matrix.target }} + - run: cargo test --target ${{ matrix.target }} --features ff - run: cargo test --target ${{ matrix.target }} --features serde - env: RUSTFLAGS: '--cfg curve25519_dalek_backend="fiat"' diff --git a/CHANGELOG.md b/CHANGELOG.md index a05e4f99c..d17769303 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,7 @@ major series. version. * Relax the `zeroize` dependency to `^1` * Update the edition from 2015 to 2021 +* Add implementations of the `ff` traits, behind the `ff` feature flag. ## 3.x series diff --git a/Cargo.toml b/Cargo.toml index b1b49ff04..4527868b0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,6 +48,7 @@ required-features = ["rand_core"] [dependencies] cfg-if = "1" +ff_crate = { version = "0.13", default-features = false, optional = true, package = "ff" } rand_core = { version = "0.6.4", default-features = false, optional = true } digest = { version = "0.10", default-features = false, optional = true } subtle = { version = "2.3.0", default-features = false } @@ -65,6 +66,7 @@ packed_simd = { version = "0.3.4", package = "packed_simd_2", features = ["into_ [features] default = ["alloc"] alloc = ["zeroize/alloc"] +ff = ["ff_crate", "rand_core"] [profile.dev] opt-level = 2 diff --git a/src/scalar.rs b/src/scalar.rs index d14a79627..1167eb9ea 100644 --- a/src/scalar.rs +++ b/src/scalar.rs @@ -152,6 +152,12 @@ use core::ops::{Sub, SubAssign}; use cfg_if::cfg_if; +#[cfg(feature = "ff")] +use { + ff_crate::{Field, FromUniformBytes, PrimeField}, + rand_core::RngCore, +}; + #[cfg(any(test, feature = "rand_core"))] use rand_core::CryptoRngCore; @@ -1214,6 +1220,126 @@ impl UnpackedScalar { } } +#[cfg(feature = "ff")] +impl Field for Scalar { + const ZERO: Self = Self::ZERO; + const ONE: Self = Self::ONE; + + fn random(mut rng: impl RngCore) -> Self { + // NOTE: this is duplicated due to different `rng` bounds + let mut scalar_bytes = [0u8; 64]; + rng.fill_bytes(&mut scalar_bytes); + Self::from_bytes_mod_order_wide(&scalar_bytes) + } + + fn square(&self) -> Self { + self * self + } + + fn double(&self) -> Self { + self + self + } + + fn invert(&self) -> CtOption { + CtOption::new(self.invert(), !self.is_zero()) + } + + fn sqrt_ratio(num: &Self, div: &Self) -> (Choice, Self) { + ff_crate::helpers::sqrt_ratio_generic(num, div) + } + + fn sqrt(&self) -> CtOption { + ff_crate::helpers::sqrt_tonelli_shanks( + self, + [ + 0xcb02_4c63_4b9e_ba7d, + 0x029b_df3b_d45e_f39a, + 0x0000_0000_0000_0000, + 0x0200_0000_0000_0000, + ], + ) + } +} + +#[cfg(feature = "ff")] +impl PrimeField for Scalar { + type Repr = [u8; 32]; + + fn from_repr(repr: Self::Repr) -> CtOption { + Self::from_canonical_bytes(repr) + } + + fn from_repr_vartime(repr: Self::Repr) -> Option { + // Check that the high bit is not set + if (repr[31] >> 7) != 0u8 { + return None; + } + + let candidate = Scalar::from_bits(repr); + + if candidate == candidate.reduce() { + Some(candidate) + } else { + None + } + } + + fn to_repr(&self) -> Self::Repr { + self.to_bytes() + } + + fn is_odd(&self) -> Choice { + Choice::from(self.as_bytes()[0] & 1) + } + + const MODULUS: &'static str = + "0x1000000000000000000000000000000014def9dea2f79cd65812631a5cf5d3ed"; + const NUM_BITS: u32 = 253; + const CAPACITY: u32 = 252; + + const TWO_INV: Self = Self { + bytes: [ + 0xf7, 0xe9, 0x7a, 0x2e, 0x8d, 0x31, 0x09, 0x2c, 0x6b, 0xce, 0x7b, 0x51, 0xef, 0x7c, + 0x6f, 0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x08, + ], + }; + const MULTIPLICATIVE_GENERATOR: Self = Self { + bytes: [ + 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, + ], + }; + const S: u32 = 2; + const ROOT_OF_UNITY: Self = Self { + bytes: [ + 0xd4, 0x07, 0xbe, 0xeb, 0xdf, 0x75, 0x87, 0xbe, 0xfe, 0x83, 0xce, 0x42, 0x53, 0x56, + 0xf0, 0x0e, 0x7a, 0xc2, 0xc1, 0xab, 0x60, 0x6d, 0x3d, 0x7d, 0xe7, 0x81, 0x79, 0xe0, + 0x10, 0x73, 0x4a, 0x09, + ], + }; + const ROOT_OF_UNITY_INV: Self = Self { + bytes: [ + 0x19, 0xcc, 0x37, 0x71, 0x3a, 0xed, 0x8a, 0x99, 0xd7, 0x18, 0x29, 0x60, 0x8b, 0xa3, + 0xee, 0x05, 0x86, 0x3d, 0x3e, 0x54, 0x9f, 0x92, 0xc2, 0x82, 0x18, 0x7e, 0x86, 0x1f, + 0xef, 0x8c, 0xb5, 0x06, + ], + }; + const DELTA: Self = Self { + bytes: [ + 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, + ], + }; +} + +#[cfg(feature = "ff")] +impl FromUniformBytes<64> for Scalar { + fn from_uniform_bytes(bytes: &[u8; 64]) -> Self { + Scalar::from_bytes_mod_order_wide(bytes) + } +} + /// Read one or more u64s stored as little endian bytes. /// /// ## Panics @@ -1857,6 +1983,56 @@ mod test { assert_eq!(sx + s1, Scalar::from(x + 1)); } + #[cfg(feature = "ff")] + #[test] + fn ff_constants() { + assert_eq!(Scalar::from(2u64) * Scalar::TWO_INV, Scalar::ONE); + + assert_eq!( + Scalar::ROOT_OF_UNITY * Scalar::ROOT_OF_UNITY_INV, + Scalar::ONE, + ); + + // ROOT_OF_UNITY^{2^s} mod m == 1 + assert_eq!( + Scalar::ROOT_OF_UNITY.pow(&[1u64 << Scalar::S, 0, 0, 0]), + Scalar::ONE, + ); + + // DELTA^{t} mod m == 1 + assert_eq!( + Scalar::DELTA.pow(&[ + 0x9604_98c6_973d_74fb, + 0x0537_be77_a8bd_e735, + 0x0000_0000_0000_0000, + 0x0400_0000_0000_0000, + ]), + Scalar::ONE, + ); + } + + #[cfg(feature = "ff")] + #[test] + fn ff_impls() { + assert!(bool::from(Scalar::ZERO.is_even())); + assert!(bool::from(Scalar::ONE.is_odd())); + assert!(bool::from(Scalar::from(2u64).is_even())); + assert!(bool::from(Scalar::DELTA.is_even())); + + assert!(bool::from(Field::invert(&Scalar::ZERO).is_none())); + assert_eq!(Field::invert(&X).unwrap(), XINV); + + let x_sq = X.square(); + // We should get back either the positive or negative root. + assert!([X, -X].contains(&x_sq.sqrt().unwrap())); + + assert_eq!(Scalar::from_repr_vartime(X.to_repr()), Some(X)); + assert_eq!(Scalar::from_repr_vartime([0xff; 32]), None); + + assert_eq!(Scalar::from_repr(X.to_repr()).unwrap(), X); + assert!(bool::from(Scalar::from_repr([0xff; 32]).is_none())); + } + #[test] #[should_panic] fn test_read_le_u64_into_should_panic_on_bad_input() { From 1e25bfc2bef977a3117f5059b1aa9a58dbf51dea Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Mon, 19 Dec 2022 11:02:36 +0000 Subject: [PATCH 02/16] Move `ff` traits behind `group` feature flag --- .github/workflows/rust.yml | 2 +- CHANGELOG.md | 2 +- Cargo.toml | 4 ++-- src/lib.rs | 4 ++++ src/scalar.rs | 18 +++++++++--------- 5 files changed, 17 insertions(+), 13 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 2eb912d50..30115521b 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -29,7 +29,7 @@ jobs: - run: ${{ matrix.deps }} - run: cargo test --target ${{ matrix.target }} --no-default-features - run: cargo test --target ${{ matrix.target }} - - run: cargo test --target ${{ matrix.target }} --features ff + - run: cargo test --target ${{ matrix.target }} --features group - run: cargo test --target ${{ matrix.target }} --features serde - env: RUSTFLAGS: '--cfg curve25519_dalek_backend="fiat"' diff --git a/CHANGELOG.md b/CHANGELOG.md index d17769303..fd257409c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,7 +37,7 @@ major series. version. * Relax the `zeroize` dependency to `^1` * Update the edition from 2015 to 2021 -* Add implementations of the `ff` traits, behind the `ff` feature flag. +* Add implementations of the `ff` traits, behind the `group` feature flag. ## 3.x series diff --git a/Cargo.toml b/Cargo.toml index 4527868b0..fd2b0599a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -48,7 +48,7 @@ required-features = ["rand_core"] [dependencies] cfg-if = "1" -ff_crate = { version = "0.13", default-features = false, optional = true, package = "ff" } +group_crate = { version = "0.13", default-features = false, optional = true, package = "group" } rand_core = { version = "0.6.4", default-features = false, optional = true } digest = { version = "0.10", default-features = false, optional = true } subtle = { version = "2.3.0", default-features = false } @@ -66,7 +66,7 @@ packed_simd = { version = "0.3.4", package = "packed_simd_2", features = ["into_ [features] default = ["alloc"] alloc = ["zeroize/alloc"] -ff = ["ff_crate", "rand_core"] +group = ["group_crate", "rand_core"] [profile.dev] opt-level = 2 diff --git a/src/lib.rs b/src/lib.rs index 56ca69e24..11f8cf351 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -40,6 +40,10 @@ extern crate std; #[cfg(feature = "digest")] pub use digest; +// TODO: Remove once our MSRV supports namespaced features. +#[cfg(feature = "group")] +extern crate group_crate as group; + // Internal macros. Must come first! #[macro_use] pub(crate) mod macros; diff --git a/src/scalar.rs b/src/scalar.rs index 1167eb9ea..c58b65e8f 100644 --- a/src/scalar.rs +++ b/src/scalar.rs @@ -152,9 +152,9 @@ use core::ops::{Sub, SubAssign}; use cfg_if::cfg_if; -#[cfg(feature = "ff")] +#[cfg(feature = "group")] use { - ff_crate::{Field, FromUniformBytes, PrimeField}, + group::ff::{Field, FromUniformBytes, PrimeField}, rand_core::RngCore, }; @@ -1220,7 +1220,7 @@ impl UnpackedScalar { } } -#[cfg(feature = "ff")] +#[cfg(feature = "group")] impl Field for Scalar { const ZERO: Self = Self::ZERO; const ONE: Self = Self::ONE; @@ -1245,11 +1245,11 @@ impl Field for Scalar { } fn sqrt_ratio(num: &Self, div: &Self) -> (Choice, Self) { - ff_crate::helpers::sqrt_ratio_generic(num, div) + group::ff::helpers::sqrt_ratio_generic(num, div) } fn sqrt(&self) -> CtOption { - ff_crate::helpers::sqrt_tonelli_shanks( + group::ff::helpers::sqrt_tonelli_shanks( self, [ 0xcb02_4c63_4b9e_ba7d, @@ -1261,7 +1261,7 @@ impl Field for Scalar { } } -#[cfg(feature = "ff")] +#[cfg(feature = "group")] impl PrimeField for Scalar { type Repr = [u8; 32]; @@ -1333,7 +1333,7 @@ impl PrimeField for Scalar { }; } -#[cfg(feature = "ff")] +#[cfg(feature = "group")] impl FromUniformBytes<64> for Scalar { fn from_uniform_bytes(bytes: &[u8; 64]) -> Self { Scalar::from_bytes_mod_order_wide(bytes) @@ -1983,7 +1983,7 @@ mod test { assert_eq!(sx + s1, Scalar::from(x + 1)); } - #[cfg(feature = "ff")] + #[cfg(feature = "group")] #[test] fn ff_constants() { assert_eq!(Scalar::from(2u64) * Scalar::TWO_INV, Scalar::ONE); @@ -2011,7 +2011,7 @@ mod test { ); } - #[cfg(feature = "ff")] + #[cfg(feature = "group")] #[test] fn ff_impls() { assert!(bool::from(Scalar::ZERO.is_even())); From dccdb92ca14ddf41c11eabf493417a4a31017e30 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Mon, 19 Dec 2022 11:31:18 +0000 Subject: [PATCH 03/16] Implement `group` traits for `RistrettoPoint` --- CHANGELOG.md | 2 +- src/ristretto.rs | 134 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 135 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fd257409c..2d2e853b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,7 +37,7 @@ major series. version. * Relax the `zeroize` dependency to `^1` * Update the edition from 2015 to 2021 -* Add implementations of the `ff` traits, behind the `group` feature flag. +* Add implementations of the `ff` and `group` traits, behind the `group` feature flag. ## 3.x series diff --git a/src/ristretto.rs b/src/ristretto.rs index 5d78c9a1d..c722f7d64 100644 --- a/src/ristretto.rs +++ b/src/ristretto.rs @@ -182,6 +182,13 @@ use crate::field::FieldElement; #[cfg(feature = "alloc")] use cfg_if::cfg_if; +#[cfg(feature = "group")] +use { + group::{cofactor::CofactorGroup, prime::PrimeGroup, GroupEncoding}, + rand_core::RngCore, + subtle::CtOption, +}; + use subtle::Choice; use subtle::ConditionallyNegatable; use subtle::ConditionallySelectable; @@ -1129,6 +1136,133 @@ impl Debug for RistrettoPoint { } } +// ------------------------------------------------------------------------ +// group traits +// ------------------------------------------------------------------------ + +// Use the full trait path to avoid Group::identity overlapping Identity::identity in the +// rest of the module (e.g. tests). +#[cfg(feature = "group")] +impl group::Group for RistrettoPoint { + type Scalar = Scalar; + + fn random(mut rng: impl RngCore) -> Self { + // NOTE: this is duplicated due to different `rng` bounds + let mut uniform_bytes = [0u8; 64]; + rng.fill_bytes(&mut uniform_bytes); + RistrettoPoint::from_uniform_bytes(&uniform_bytes) + } + + fn identity() -> Self { + Identity::identity() + } + + fn generator() -> Self { + constants::RISTRETTO_BASEPOINT_POINT + } + + fn is_identity(&self) -> Choice { + self.ct_eq(&Identity::identity()) + } + + fn double(&self) -> Self { + self + self + } +} + +#[cfg(feature = "group")] +impl GroupEncoding for RistrettoPoint { + type Repr = [u8; 32]; + + fn from_bytes(bytes: &Self::Repr) -> CtOption { + // NOTE: this is duplicated from CompressedRistretto::decompress due to the + // constant time requirement. + + // Step 1. Check s for validity: + // 1.a) s must be 32 bytes (we get this from the type system) + // 1.b) s < p + // 1.c) s is nonnegative + // + // Our decoding routine ignores the high bit, so the only + // possible failure for 1.b) is if someone encodes s in 0..18 + // as s+p in 2^255-19..2^255-1. We can check this by + // converting back to bytes, and checking that we get the + // original input, since our encoding routine is canonical. + + let s = FieldElement::from_bytes(bytes); + let s_encoding_is_canonical = s.as_bytes().ct_eq(bytes); + let s_is_negative = s.is_negative(); + + let s_is_valid = s_encoding_is_canonical & !s_is_negative; + + // Step 2. Compute (X:Y:Z:T). + let one = FieldElement::ONE; + let ss = s.square(); + let u1 = &one - &ss; // 1 + as² + let u2 = &one + &ss; // 1 - as² where a=-1 + let u2_sqr = u2.square(); // (1 - as²)² + + // v == ad(1+as²)² - (1-as²)² where d=-121665/121666 + let v = &(&(-&constants::EDWARDS_D) * &u1.square()) - &u2_sqr; + + let (ok, I) = (&v * &u2_sqr).invsqrt(); // 1/sqrt(v*u_2²) + + let Dx = &I * &u2; // 1/sqrt(v) + let Dy = &I * &(&Dx * &v); // 1/u2 + + // x == | 2s/sqrt(v) | == + sqrt(4s²/(ad(1+as²)² - (1-as²)²)) + let mut x = &(&s + &s) * &Dx; + let x_neg = x.is_negative(); + x.conditional_negate(x_neg); + + // y == (1-as²)/(1+as²) + let y = &u1 * &Dy; + + // t == ((1+as²) sqrt(4s²/(ad(1+as²)² - (1-as²)²)))/(1-as²) + let t = &x * &y; + + CtOption::new( + RistrettoPoint(EdwardsPoint { + X: x, + Y: y, + Z: one, + T: t, + }), + s_is_valid & ok & !t.is_negative() & !y.is_zero(), + ) + } + + fn from_bytes_unchecked(bytes: &Self::Repr) -> CtOption { + // Just use the checked API; the checks we could skip aren't expensive. + Self::from_bytes(bytes) + } + + fn to_bytes(&self) -> Self::Repr { + self.compress().to_bytes() + } +} + +#[cfg(feature = "group")] +impl PrimeGroup for RistrettoPoint {} + +/// Ristretto has a cofactor of 1. +#[cfg(feature = "group")] +impl CofactorGroup for RistrettoPoint { + type Subgroup = Self; + + fn clear_cofactor(&self) -> Self::Subgroup { + *self + } + + fn into_subgroup(self) -> CtOption { + CtOption::new(self, Choice::from(1)) + } + + fn is_torsion_free(&self) -> Choice { + Choice::from(1) + } +} + // ------------------------------------------------------------------------ // Zeroize traits // ------------------------------------------------------------------------ From 715e438e622c97d423ba78d8b1c04d4f2d9bd252 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Mon, 19 Dec 2022 12:23:51 +0000 Subject: [PATCH 04/16] Implement `group` traits for `EdwardsPoint` --- src/edwards.rs | 340 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 340 insertions(+) diff --git a/src/edwards.rs b/src/edwards.rs index edb229ff9..730d5674e 100644 --- a/src/edwards.rs +++ b/src/edwards.rs @@ -104,6 +104,13 @@ use core::ops::{Mul, MulAssign}; #[cfg(feature = "digest")] use digest::{generic_array::typenum::U64, Digest}; +#[cfg(feature = "group")] +use { + group::{cofactor::CofactorGroup, prime::PrimeGroup, GroupEncoding}, + rand_core::RngCore, + subtle::CtOption, +}; + use subtle::Choice; use subtle::ConditionallyNegatable; use subtle::ConditionallySelectable; @@ -1107,6 +1114,339 @@ impl Debug for EdwardsPoint { } } +// ------------------------------------------------------------------------ +// group traits +// ------------------------------------------------------------------------ + +// Use the full trait path to avoid Group::identity overlapping Identity::identity in the +// rest of the module (e.g. tests). +#[cfg(feature = "group")] +impl group::Group for EdwardsPoint { + type Scalar = Scalar; + + fn random(mut rng: impl RngCore) -> Self { + let mut repr = CompressedEdwardsY([0u8; 32]); + loop { + rng.fill_bytes(&mut repr.0); + if let Some(p) = repr.decompress() { + if !IsIdentity::is_identity(&p) { + break p; + } + } + } + } + + fn identity() -> Self { + Identity::identity() + } + + fn generator() -> Self { + constants::ED25519_BASEPOINT_POINT + } + + fn is_identity(&self) -> Choice { + self.ct_eq(&Identity::identity()) + } + + fn double(&self) -> Self { + self.double() + } +} + +#[cfg(feature = "group")] +impl GroupEncoding for EdwardsPoint { + type Repr = [u8; 32]; + + fn from_bytes(bytes: &Self::Repr) -> CtOption { + // NOTE: this is duplicated from CompressedEdwardsY::decompress due to the + // constant time requirement. + + let Y = FieldElement::from_bytes(bytes); + let Z = FieldElement::ONE; + let YY = Y.square(); + let u = &YY - &Z; // u = y²-1 + let v = &(&YY * &constants::EDWARDS_D) + &Z; // v = dy²+1 + let (is_valid_y_coord, mut X) = FieldElement::sqrt_ratio_i(&u, &v); + + // FieldElement::sqrt_ratio_i always returns the nonnegative square root, + // so we negate according to the supplied sign bit. + let compressed_sign_bit = Choice::from(bytes[31] >> 7); + X.conditional_negate(compressed_sign_bit); + + CtOption::new( + EdwardsPoint { + X, + Y, + Z, + T: &X * &Y, + }, + is_valid_y_coord, + ) + } + + fn from_bytes_unchecked(bytes: &Self::Repr) -> CtOption { + // Just use the checked API; there are no checks we can skip. + Self::from_bytes(bytes) + } + + fn to_bytes(&self) -> Self::Repr { + self.compress().to_bytes() + } +} + +/// A `SubgroupPoint` represents a point on the Edwards form of Curve25519, that is +/// guaranteed to be in the prime-order subgroup. +#[cfg(feature = "group")] +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct SubgroupPoint(EdwardsPoint); + +#[cfg(feature = "group")] +impl From for EdwardsPoint { + fn from(p: SubgroupPoint) -> Self { + p.0 + } +} + +#[cfg(feature = "group")] +impl Neg for SubgroupPoint { + type Output = Self; + + fn neg(self) -> Self::Output { + SubgroupPoint(-self.0) + } +} + +#[cfg(feature = "group")] +impl<'a, 'b> Add<&'b SubgroupPoint> for &'a SubgroupPoint { + type Output = SubgroupPoint; + fn add(self, other: &'b SubgroupPoint) -> SubgroupPoint { + SubgroupPoint(self.0 + other.0) + } +} + +#[cfg(feature = "group")] +define_add_variants!( + LHS = SubgroupPoint, + RHS = SubgroupPoint, + Output = SubgroupPoint +); + +#[cfg(feature = "group")] +impl<'a, 'b> Add<&'b SubgroupPoint> for &'a EdwardsPoint { + type Output = EdwardsPoint; + fn add(self, other: &'b SubgroupPoint) -> EdwardsPoint { + self + other.0 + } +} + +#[cfg(feature = "group")] +define_add_variants!( + LHS = EdwardsPoint, + RHS = SubgroupPoint, + Output = EdwardsPoint +); + +#[cfg(feature = "group")] +impl AddAssign<&SubgroupPoint> for SubgroupPoint { + fn add_assign(&mut self, rhs: &SubgroupPoint) { + self.0 += rhs.0 + } +} + +#[cfg(feature = "group")] +define_add_assign_variants!(LHS = SubgroupPoint, RHS = SubgroupPoint); + +#[cfg(feature = "group")] +impl AddAssign<&SubgroupPoint> for EdwardsPoint { + fn add_assign(&mut self, rhs: &SubgroupPoint) { + *self += rhs.0 + } +} + +#[cfg(feature = "group")] +define_add_assign_variants!(LHS = EdwardsPoint, RHS = SubgroupPoint); + +#[cfg(feature = "group")] +impl<'a, 'b> Sub<&'b SubgroupPoint> for &'a SubgroupPoint { + type Output = SubgroupPoint; + fn sub(self, other: &'b SubgroupPoint) -> SubgroupPoint { + SubgroupPoint(self.0 - other.0) + } +} + +#[cfg(feature = "group")] +define_sub_variants!( + LHS = SubgroupPoint, + RHS = SubgroupPoint, + Output = SubgroupPoint +); + +#[cfg(feature = "group")] +impl<'a, 'b> Sub<&'b SubgroupPoint> for &'a EdwardsPoint { + type Output = EdwardsPoint; + fn sub(self, other: &'b SubgroupPoint) -> EdwardsPoint { + self - other.0 + } +} + +#[cfg(feature = "group")] +define_sub_variants!( + LHS = EdwardsPoint, + RHS = SubgroupPoint, + Output = EdwardsPoint +); + +#[cfg(feature = "group")] +impl SubAssign<&SubgroupPoint> for SubgroupPoint { + fn sub_assign(&mut self, rhs: &SubgroupPoint) { + self.0 -= rhs.0; + } +} + +#[cfg(feature = "group")] +define_sub_assign_variants!(LHS = SubgroupPoint, RHS = SubgroupPoint); + +#[cfg(feature = "group")] +impl SubAssign<&SubgroupPoint> for EdwardsPoint { + fn sub_assign(&mut self, rhs: &SubgroupPoint) { + *self -= rhs.0; + } +} + +#[cfg(feature = "group")] +define_sub_assign_variants!(LHS = EdwardsPoint, RHS = SubgroupPoint); + +#[cfg(feature = "group")] +impl Sum for SubgroupPoint +where + T: Borrow, +{ + fn sum(iter: I) -> Self + where + I: Iterator, + { + use group::Group; + iter.fold(SubgroupPoint::identity(), |acc, item| acc + item.borrow()) + } +} + +#[cfg(feature = "group")] +impl<'a, 'b> Mul<&'b Scalar> for &'a SubgroupPoint { + type Output = SubgroupPoint; + + /// Scalar multiplication: compute `scalar * self`. + /// + /// For scalar multiplication of a basepoint, + /// `EdwardsBasepointTable` is approximately 4x faster. + fn mul(self, scalar: &'b Scalar) -> SubgroupPoint { + SubgroupPoint(self.0 * scalar) + } +} + +#[cfg(feature = "group")] +define_mul_variants!(LHS = Scalar, RHS = SubgroupPoint, Output = SubgroupPoint); + +#[cfg(feature = "group")] +impl<'a, 'b> Mul<&'b SubgroupPoint> for &'a Scalar { + type Output = SubgroupPoint; + + /// Scalar multiplication: compute `scalar * self`. + /// + /// For scalar multiplication of a basepoint, + /// `EdwardsBasepointTable` is approximately 4x faster. + fn mul(self, point: &'b SubgroupPoint) -> SubgroupPoint { + point * self + } +} + +#[cfg(feature = "group")] +define_mul_variants!(LHS = SubgroupPoint, RHS = Scalar, Output = SubgroupPoint); + +#[cfg(feature = "group")] +impl MulAssign<&Scalar> for SubgroupPoint { + fn mul_assign(&mut self, scalar: &Scalar) { + self.0 *= scalar; + } +} + +#[cfg(feature = "group")] +define_mul_assign_variants!(LHS = SubgroupPoint, RHS = Scalar); + +#[cfg(feature = "group")] +impl group::Group for SubgroupPoint { + type Scalar = Scalar; + + fn random(mut rng: impl RngCore) -> Self { + use group::ff::Field; + + // This will almost never loop, but `Group::random` is documented as returning a + // non-identity element. + let s = loop { + let s: Scalar = Field::random(&mut rng); + if !s.is_zero_vartime() { + break s; + } + }; + + // This gives an element of the prime-order subgroup. + Self::generator() * s + } + + fn identity() -> Self { + SubgroupPoint(Identity::identity()) + } + + fn generator() -> Self { + SubgroupPoint(EdwardsPoint::generator()) + } + + fn is_identity(&self) -> Choice { + self.0.ct_eq(&Identity::identity()) + } + + fn double(&self) -> Self { + SubgroupPoint(self.0.double()) + } +} + +#[cfg(feature = "group")] +impl GroupEncoding for SubgroupPoint { + type Repr = ::Repr; + + fn from_bytes(bytes: &Self::Repr) -> CtOption { + EdwardsPoint::from_bytes(bytes).and_then(|p| p.into_subgroup()) + } + + fn from_bytes_unchecked(bytes: &Self::Repr) -> CtOption { + EdwardsPoint::from_bytes_unchecked(bytes).and_then(|p| p.into_subgroup()) + } + + fn to_bytes(&self) -> Self::Repr { + self.0.compress().to_bytes() + } +} + +#[cfg(feature = "group")] +impl PrimeGroup for SubgroupPoint {} + +/// Ristretto has a cofactor of 1. +#[cfg(feature = "group")] +impl CofactorGroup for EdwardsPoint { + type Subgroup = SubgroupPoint; + + fn clear_cofactor(&self) -> Self::Subgroup { + SubgroupPoint(self.mul_by_cofactor()) + } + + fn into_subgroup(self) -> CtOption { + CtOption::new(SubgroupPoint(self), CofactorGroup::is_torsion_free(&self)) + } + + fn is_torsion_free(&self) -> Choice { + (self * constants::BASEPOINT_ORDER).ct_eq(&Self::identity()) + } +} + // ------------------------------------------------------------------------ // Tests // ------------------------------------------------------------------------ From 926b33ed0e636be79715d001fa6223a1c9669b46 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Tue, 20 Dec 2022 22:17:35 +0000 Subject: [PATCH 05/16] Deduplicate constant-time and non-constant-time decompression code --- src/edwards.rs | 66 ++++++++++++++++-------------- src/ristretto.rs | 102 +++++++++++++++++------------------------------ 2 files changed, 73 insertions(+), 95 deletions(-) diff --git a/src/edwards.rs b/src/edwards.rs index 730d5674e..66bba83c2 100644 --- a/src/edwards.rs +++ b/src/edwards.rs @@ -195,23 +195,50 @@ impl CompressedEdwardsY { /// /// Returns `None` if the input is not the \\(y\\)-coordinate of a /// curve point. - #[rustfmt::skip] // keep alignment of explanatory comments pub fn decompress(&self) -> Option { - let Y = FieldElement::from_bytes(self.as_bytes()); + let (is_valid_y_coord, X, Y, Z) = decompress::step_1(self); + if is_valid_y_coord.unwrap_u8() != 1u8 { + return None; + } + Some(decompress::step_2(self, X, Y, Z)) + } +} + +mod decompress { + use super::*; + + #[rustfmt::skip] // keep alignment of explanatory comments + pub(super) fn step_1( + repr: &CompressedEdwardsY, + ) -> (Choice, FieldElement, FieldElement, FieldElement) { + let Y = FieldElement::from_bytes(repr.as_bytes()); let Z = FieldElement::ONE; let YY = Y.square(); let u = &YY - &Z; // u = y²-1 let v = &(&YY * &constants::EDWARDS_D) + &Z; // v = dy²+1 - let (is_valid_y_coord, mut X) = FieldElement::sqrt_ratio_i(&u, &v); + let (is_valid_y_coord, X) = FieldElement::sqrt_ratio_i(&u, &v); - if is_valid_y_coord.unwrap_u8() != 1u8 { return None; } + (is_valid_y_coord, X, Y, Z) + } + #[rustfmt::skip] + pub(super) fn step_2( + repr: &CompressedEdwardsY, + mut X: FieldElement, + Y: FieldElement, + Z: FieldElement, + ) -> EdwardsPoint { // FieldElement::sqrt_ratio_i always returns the nonnegative square root, // so we negate according to the supplied sign bit. - let compressed_sign_bit = Choice::from(self.as_bytes()[31] >> 7); + let compressed_sign_bit = Choice::from(repr.as_bytes()[31] >> 7); X.conditional_negate(compressed_sign_bit); - Some(EdwardsPoint{ X, Y, Z, T: &X * &Y }) + EdwardsPoint { + X, + Y, + Z, + T: &X * &Y, + } } } @@ -1158,30 +1185,9 @@ impl GroupEncoding for EdwardsPoint { type Repr = [u8; 32]; fn from_bytes(bytes: &Self::Repr) -> CtOption { - // NOTE: this is duplicated from CompressedEdwardsY::decompress due to the - // constant time requirement. - - let Y = FieldElement::from_bytes(bytes); - let Z = FieldElement::ONE; - let YY = Y.square(); - let u = &YY - &Z; // u = y²-1 - let v = &(&YY * &constants::EDWARDS_D) + &Z; // v = dy²+1 - let (is_valid_y_coord, mut X) = FieldElement::sqrt_ratio_i(&u, &v); - - // FieldElement::sqrt_ratio_i always returns the nonnegative square root, - // so we negate according to the supplied sign bit. - let compressed_sign_bit = Choice::from(bytes[31] >> 7); - X.conditional_negate(compressed_sign_bit); - - CtOption::new( - EdwardsPoint { - X, - Y, - Z, - T: &X * &Y, - }, - is_valid_y_coord, - ) + let repr = CompressedEdwardsY(*bytes); + let (is_valid_y_coord, X, Y, Z) = decompress::step_1(&repr); + CtOption::new(decompress::step_2(&repr, X, Y, Z), is_valid_y_coord) } fn from_bytes_unchecked(bytes: &Self::Repr) -> CtOption { diff --git a/src/ristretto.rs b/src/ristretto.rs index c722f7d64..78cf027f3 100644 --- a/src/ristretto.rs +++ b/src/ristretto.rs @@ -267,6 +267,27 @@ impl CompressedRistretto { /// /// - `None` if `self` was not the canonical encoding of a point. pub fn decompress(&self) -> Option { + let (s_encoding_is_canonical, s_is_negative, s) = decompress::step_1(self); + + if s_encoding_is_canonical.unwrap_u8() == 0u8 || s_is_negative.unwrap_u8() == 1u8 { + return None; + } + + let (ok, t_is_negative, y_is_zero, res) = decompress::step_2(s); + + if ok.unwrap_u8() == 0u8 || t_is_negative.unwrap_u8() == 1u8 || y_is_zero.unwrap_u8() == 1u8 + { + None + } else { + Some(res) + } + } +} + +mod decompress { + use super::*; + + pub(super) fn step_1(repr: &CompressedRistretto) -> (Choice, Choice, FieldElement) { // Step 1. Check s for validity: // 1.a) s must be 32 bytes (we get this from the type system) // 1.b) s < p @@ -278,15 +299,15 @@ impl CompressedRistretto { // converting back to bytes, and checking that we get the // original input, since our encoding routine is canonical. - let s = FieldElement::from_bytes(self.as_bytes()); + let s = FieldElement::from_bytes(repr.as_bytes()); let s_bytes_check = s.as_bytes(); - let s_encoding_is_canonical = &s_bytes_check[..].ct_eq(self.as_bytes()); + let s_encoding_is_canonical = s_bytes_check[..].ct_eq(repr.as_bytes()); let s_is_negative = s.is_negative(); - if s_encoding_is_canonical.unwrap_u8() == 0u8 || s_is_negative.unwrap_u8() == 1u8 { - return None; - } + (s_encoding_is_canonical, s_is_negative, s) + } + pub(super) fn step_2(s: FieldElement) -> (Choice, Choice, Choice, RistrettoPoint) { // Step 2. Compute (X:Y:Z:T). let one = FieldElement::ONE; let ss = s.square(); @@ -313,19 +334,17 @@ impl CompressedRistretto { // t == ((1+as²) sqrt(4s²/(ad(1+as²)² - (1-as²)²)))/(1-as²) let t = &x * &y; - if ok.unwrap_u8() == 0u8 - || t.is_negative().unwrap_u8() == 1u8 - || y.is_zero().unwrap_u8() == 1u8 - { - None - } else { - Some(RistrettoPoint(EdwardsPoint { + ( + ok, + t.is_negative(), + y.is_zero(), + RistrettoPoint(EdwardsPoint { X: x, Y: y, Z: one, T: t, - })) - } + }), + ) } } @@ -1175,61 +1194,14 @@ impl GroupEncoding for RistrettoPoint { type Repr = [u8; 32]; fn from_bytes(bytes: &Self::Repr) -> CtOption { - // NOTE: this is duplicated from CompressedRistretto::decompress due to the - // constant time requirement. - - // Step 1. Check s for validity: - // 1.a) s must be 32 bytes (we get this from the type system) - // 1.b) s < p - // 1.c) s is nonnegative - // - // Our decoding routine ignores the high bit, so the only - // possible failure for 1.b) is if someone encodes s in 0..18 - // as s+p in 2^255-19..2^255-1. We can check this by - // converting back to bytes, and checking that we get the - // original input, since our encoding routine is canonical. - - let s = FieldElement::from_bytes(bytes); - let s_encoding_is_canonical = s.as_bytes().ct_eq(bytes); - let s_is_negative = s.is_negative(); + let (s_encoding_is_canonical, s_is_negative, s) = + decompress::step_1(&CompressedRistretto(*bytes)); let s_is_valid = s_encoding_is_canonical & !s_is_negative; - // Step 2. Compute (X:Y:Z:T). - let one = FieldElement::ONE; - let ss = s.square(); - let u1 = &one - &ss; // 1 + as² - let u2 = &one + &ss; // 1 - as² where a=-1 - let u2_sqr = u2.square(); // (1 - as²)² - - // v == ad(1+as²)² - (1-as²)² where d=-121665/121666 - let v = &(&(-&constants::EDWARDS_D) * &u1.square()) - &u2_sqr; - - let (ok, I) = (&v * &u2_sqr).invsqrt(); // 1/sqrt(v*u_2²) + let (ok, t_is_negative, y_is_zero, res) = decompress::step_2(s); - let Dx = &I * &u2; // 1/sqrt(v) - let Dy = &I * &(&Dx * &v); // 1/u2 - - // x == | 2s/sqrt(v) | == + sqrt(4s²/(ad(1+as²)² - (1-as²)²)) - let mut x = &(&s + &s) * &Dx; - let x_neg = x.is_negative(); - x.conditional_negate(x_neg); - - // y == (1-as²)/(1+as²) - let y = &u1 * &Dy; - - // t == ((1+as²) sqrt(4s²/(ad(1+as²)² - (1-as²)²)))/(1-as²) - let t = &x * &y; - - CtOption::new( - RistrettoPoint(EdwardsPoint { - X: x, - Y: y, - Z: one, - T: t, - }), - s_is_valid & ok & !t.is_negative() & !y.is_zero(), - ) + CtOption::new(res, s_is_valid & ok & !t_is_negative & !y_is_zero) } fn from_bytes_unchecked(bytes: &Self::Repr) -> CtOption { From e7ba11901d6b2b863959e7fadb6501bda6027a63 Mon Sep 17 00:00:00 2001 From: pinkforest <36498018+pinkforest@users.noreply.github.com> Date: Wed, 16 Aug 2023 02:24:35 +1000 Subject: [PATCH 06/16] Pre-Merge Rebase glue --- curve25519-dalek/CHANGELOG.md => CHANGELOG.md | 0 Cargo.toml | 76 ++++++++++++++++--- curve25519-dalek/Cargo.toml | 70 ----------------- {curve25519-dalek/src => src}/edwards.rs | 0 {curve25519-dalek/src => src}/lib.rs | 0 {curve25519-dalek/src => src}/ristretto.rs | 0 {curve25519-dalek/src => src}/scalar.rs | 0 tmp/Cargo.toml | 12 +++ 8 files changed, 79 insertions(+), 79 deletions(-) rename curve25519-dalek/CHANGELOG.md => CHANGELOG.md (100%) delete mode 100644 curve25519-dalek/Cargo.toml rename {curve25519-dalek/src => src}/edwards.rs (100%) rename {curve25519-dalek/src => src}/lib.rs (100%) rename {curve25519-dalek/src => src}/ristretto.rs (100%) rename {curve25519-dalek/src => src}/scalar.rs (100%) create mode 100644 tmp/Cargo.toml diff --git a/curve25519-dalek/CHANGELOG.md b/CHANGELOG.md similarity index 100% rename from curve25519-dalek/CHANGELOG.md rename to CHANGELOG.md diff --git a/Cargo.toml b/Cargo.toml index a891c6705..2da4d3b33 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,12 +1,70 @@ -[workspace] -members = [ - "curve25519-dalek", - "curve25519-dalek-derive", - "ed25519-dalek", - "x25519-dalek" +[package] +name = "curve25519-dalek" +# Before incrementing: +# - update CHANGELOG +# - update README if required by semver +# - if README was updated, also update module documentation in src/lib.rs +version = "4.0.0" +edition = "2021" +rust-version = "1.60.0" +authors = ["Isis Lovecruft ", + "Henry de Valence "] +readme = "README.md" +license = "BSD-3-Clause" +repository = "https://github.com/dalek-cryptography/curve25519-dalek" +homepage = "https://github.com/dalek-cryptography/curve25519-dalek" +documentation = "https://docs.rs/curve25519-dalek" +categories = ["cryptography", "no-std"] +keywords = ["cryptography", "crypto", "ristretto", "curve25519", "ristretto255"] +description = "A pure-Rust implementation of group operations on ristretto255 and Curve25519" +exclude = [ + "**/.gitignore", + ".gitignore", ] -resolver = "2" -[profile.dev] -opt-level = 2 +[package.metadata.docs.rs] +rustdoc-args = [ + "--html-in-header", "docs/assets/rustdoc-include-katex-header.html", + "--cfg", "docsrs", +] +features = ["serde", "rand_core", "digest", "legacy_compatibility"] + +[dev-dependencies] +sha2 = { version = "0.10", default-features = false } +bincode = "1" +criterion = { version = "0.4.0", features = ["html_reports"] } +hex = "0.4.2" +rand = "0.8" +rand_core = { version = "0.6", default-features = false, features = ["getrandom"] } + +[build-dependencies] +platforms = "3.0.2" +rustc_version = "0.4.0" + +[[bench]] +name = "dalek_benchmarks" +harness = false +required-features = ["alloc", "rand_core"] + +[dependencies] +cfg-if = "1" +rand_core = { version = "0.6.4", default-features = false, optional = true } +digest = { version = "0.10", default-features = false, optional = true } +subtle = { version = "2.3.0", default-features = false } +serde = { version = "1.0", default-features = false, optional = true, features = ["derive"] } +zeroize = { version = "1", default-features = false, optional = true } + +[target.'cfg(target_arch = "x86_64")'.dependencies] +cpufeatures = "0.2.6" + +[target.'cfg(curve25519_dalek_backend = "fiat")'.dependencies] +fiat-crypto = "0.1.19" + +[features] +default = ["alloc", "precomputed-tables", "zeroize"] +alloc = ["zeroize?/alloc"] +precomputed-tables = [] +legacy_compatibility = [] +[target.'cfg(all(not(curve25519_dalek_backend = "fiat"), not(curve25519_dalek_backend = "serial"), target_arch = "x86_64"))'.dependencies] +curve25519-dalek-derive = { version = "0.1", path = "../curve25519-dalek-derive" } diff --git a/curve25519-dalek/Cargo.toml b/curve25519-dalek/Cargo.toml deleted file mode 100644 index 2da4d3b33..000000000 --- a/curve25519-dalek/Cargo.toml +++ /dev/null @@ -1,70 +0,0 @@ -[package] -name = "curve25519-dalek" -# Before incrementing: -# - update CHANGELOG -# - update README if required by semver -# - if README was updated, also update module documentation in src/lib.rs -version = "4.0.0" -edition = "2021" -rust-version = "1.60.0" -authors = ["Isis Lovecruft ", - "Henry de Valence "] -readme = "README.md" -license = "BSD-3-Clause" -repository = "https://github.com/dalek-cryptography/curve25519-dalek" -homepage = "https://github.com/dalek-cryptography/curve25519-dalek" -documentation = "https://docs.rs/curve25519-dalek" -categories = ["cryptography", "no-std"] -keywords = ["cryptography", "crypto", "ristretto", "curve25519", "ristretto255"] -description = "A pure-Rust implementation of group operations on ristretto255 and Curve25519" -exclude = [ - "**/.gitignore", - ".gitignore", -] - -[package.metadata.docs.rs] -rustdoc-args = [ - "--html-in-header", "docs/assets/rustdoc-include-katex-header.html", - "--cfg", "docsrs", -] -features = ["serde", "rand_core", "digest", "legacy_compatibility"] - -[dev-dependencies] -sha2 = { version = "0.10", default-features = false } -bincode = "1" -criterion = { version = "0.4.0", features = ["html_reports"] } -hex = "0.4.2" -rand = "0.8" -rand_core = { version = "0.6", default-features = false, features = ["getrandom"] } - -[build-dependencies] -platforms = "3.0.2" -rustc_version = "0.4.0" - -[[bench]] -name = "dalek_benchmarks" -harness = false -required-features = ["alloc", "rand_core"] - -[dependencies] -cfg-if = "1" -rand_core = { version = "0.6.4", default-features = false, optional = true } -digest = { version = "0.10", default-features = false, optional = true } -subtle = { version = "2.3.0", default-features = false } -serde = { version = "1.0", default-features = false, optional = true, features = ["derive"] } -zeroize = { version = "1", default-features = false, optional = true } - -[target.'cfg(target_arch = "x86_64")'.dependencies] -cpufeatures = "0.2.6" - -[target.'cfg(curve25519_dalek_backend = "fiat")'.dependencies] -fiat-crypto = "0.1.19" - -[features] -default = ["alloc", "precomputed-tables", "zeroize"] -alloc = ["zeroize?/alloc"] -precomputed-tables = [] -legacy_compatibility = [] - -[target.'cfg(all(not(curve25519_dalek_backend = "fiat"), not(curve25519_dalek_backend = "serial"), target_arch = "x86_64"))'.dependencies] -curve25519-dalek-derive = { version = "0.1", path = "../curve25519-dalek-derive" } diff --git a/curve25519-dalek/src/edwards.rs b/src/edwards.rs similarity index 100% rename from curve25519-dalek/src/edwards.rs rename to src/edwards.rs diff --git a/curve25519-dalek/src/lib.rs b/src/lib.rs similarity index 100% rename from curve25519-dalek/src/lib.rs rename to src/lib.rs diff --git a/curve25519-dalek/src/ristretto.rs b/src/ristretto.rs similarity index 100% rename from curve25519-dalek/src/ristretto.rs rename to src/ristretto.rs diff --git a/curve25519-dalek/src/scalar.rs b/src/scalar.rs similarity index 100% rename from curve25519-dalek/src/scalar.rs rename to src/scalar.rs diff --git a/tmp/Cargo.toml b/tmp/Cargo.toml new file mode 100644 index 000000000..a891c6705 --- /dev/null +++ b/tmp/Cargo.toml @@ -0,0 +1,12 @@ +[workspace] +members = [ + "curve25519-dalek", + "curve25519-dalek-derive", + "ed25519-dalek", + "x25519-dalek" +] +resolver = "2" + +[profile.dev] +opt-level = 2 + From 893260b7dc8f63bee911d68902aa8712399d3da4 Mon Sep 17 00:00:00 2001 From: pinkforest <36498018+pinkforest@users.noreply.github.com> Date: Wed, 16 Aug 2023 03:04:35 +1000 Subject: [PATCH 07/16] Remove useless cfg_if --- curve25519-dalek/src/ristretto.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/curve25519-dalek/src/ristretto.rs b/curve25519-dalek/src/ristretto.rs index 96e6b8f11..ef3e81694 100644 --- a/curve25519-dalek/src/ristretto.rs +++ b/curve25519-dalek/src/ristretto.rs @@ -180,9 +180,6 @@ use digest::Digest; use crate::constants; use crate::field::FieldElement; -#[cfg(feature = "alloc")] -use cfg_if::cfg_if; - #[cfg(feature = "group")] use { group::{cofactor::CofactorGroup, prime::PrimeGroup, GroupEncoding}, From 7b77230786684b7804d9764c2543998d3b636027 Mon Sep 17 00:00:00 2001 From: "pinkforest(she/her)" <36498018+pinkforest@users.noreply.github.com> Date: Tue, 15 Aug 2023 17:36:41 +0000 Subject: [PATCH 08/16] Update curve25519-dalek/Cargo.toml Co-authored-by: Tony Arcieri --- curve25519-dalek/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/curve25519-dalek/Cargo.toml b/curve25519-dalek/Cargo.toml index da77c364d..6febe2401 100644 --- a/curve25519-dalek/Cargo.toml +++ b/curve25519-dalek/Cargo.toml @@ -66,7 +66,7 @@ default = ["alloc", "precomputed-tables", "zeroize"] alloc = ["zeroize?/alloc"] precomputed-tables = [] legacy_compatibility = [] -group = ["group_crate", "rand_core"] +group = ["dep:group", "rand_core"] [target.'cfg(all(not(curve25519_dalek_backend = "fiat"), not(curve25519_dalek_backend = "serial"), target_arch = "x86_64"))'.dependencies] curve25519-dalek-derive = { version = "0.1", path = "../curve25519-dalek-derive" } From a1b252a7934982adb31767e3a325b042e8a99278 Mon Sep 17 00:00:00 2001 From: pinkforest <36498018+pinkforest@users.noreply.github.com> Date: Wed, 16 Aug 2023 03:38:50 +1000 Subject: [PATCH 09/16] Use group crate name --- curve25519-dalek/Cargo.toml | 2 +- curve25519-dalek/src/lib.rs | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/curve25519-dalek/Cargo.toml b/curve25519-dalek/Cargo.toml index 6febe2401..39ca97df9 100644 --- a/curve25519-dalek/Cargo.toml +++ b/curve25519-dalek/Cargo.toml @@ -48,7 +48,7 @@ required-features = ["alloc", "rand_core"] [dependencies] cfg-if = "1" -group_crate = { version = "0.13", default-features = false, optional = true, package = "group" } +group = { version = "0.13", default-features = false, optional = true } rand_core = { version = "0.6.4", default-features = false, optional = true } digest = { version = "0.10", default-features = false, optional = true } subtle = { version = "2.3.0", default-features = false } diff --git a/curve25519-dalek/src/lib.rs b/curve25519-dalek/src/lib.rs index 1db8e390b..b4479e022 100644 --- a/curve25519-dalek/src/lib.rs +++ b/curve25519-dalek/src/lib.rs @@ -44,9 +44,8 @@ extern crate std; #[cfg(feature = "digest")] pub use digest; -// TODO: Remove once our MSRV supports namespaced features. #[cfg(feature = "group")] -extern crate group_crate as group; +extern crate group; // Internal macros. Must come first! #[macro_use] From a1f9ecb2eb9973ec425420f10bdcf6f447f3bd59 Mon Sep 17 00:00:00 2001 From: pinkforest <36498018+pinkforest@users.noreply.github.com> Date: Wed, 16 Aug 2023 03:50:56 +1000 Subject: [PATCH 10/16] Gate vartime behind legacy features with clamp_bits --- curve25519-dalek/src/scalar.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/curve25519-dalek/src/scalar.rs b/curve25519-dalek/src/scalar.rs index af233e9c0..c42f964e4 100644 --- a/curve25519-dalek/src/scalar.rs +++ b/curve25519-dalek/src/scalar.rs @@ -1256,12 +1256,14 @@ impl PrimeField for Scalar { Self::from_canonical_bytes(repr) } + #[cfg(feature = "legacy_compatibility")] fn from_repr_vartime(repr: Self::Repr) -> Option { // Check that the high bit is not set if (repr[31] >> 7) != 0u8 { return None; } + #[allow(deprecated)] let candidate = Scalar::from_bits(repr); if candidate == candidate.reduce() { From fdd6989b9b8508d5543a652416bf758369afb0db Mon Sep 17 00:00:00 2001 From: pinkforest <36498018+pinkforest@users.noreply.github.com> Date: Wed, 16 Aug 2023 04:02:36 +1000 Subject: [PATCH 11/16] Remove idle legacy_compatibility gate Co-authored-by: Tony Arcieri --- curve25519-dalek/src/scalar.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/curve25519-dalek/src/scalar.rs b/curve25519-dalek/src/scalar.rs index c42f964e4..c16c2e887 100644 --- a/curve25519-dalek/src/scalar.rs +++ b/curve25519-dalek/src/scalar.rs @@ -1256,15 +1256,13 @@ impl PrimeField for Scalar { Self::from_canonical_bytes(repr) } - #[cfg(feature = "legacy_compatibility")] fn from_repr_vartime(repr: Self::Repr) -> Option { // Check that the high bit is not set if (repr[31] >> 7) != 0u8 { return None; } - #[allow(deprecated)] - let candidate = Scalar::from_bits(repr); + let candidate = Scalar { bytes: repr }; if candidate == candidate.reduce() { Some(candidate) From 1d6fe91eb7cb4793ba084e8dafe9dd68b257e8e7 Mon Sep 17 00:00:00 2001 From: pinkforest <36498018+pinkforest@users.noreply.github.com> Date: Wed, 16 Aug 2023 04:22:54 +1000 Subject: [PATCH 12/16] Correct CHANGELOG entry --- curve25519-dalek/CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/curve25519-dalek/CHANGELOG.md b/curve25519-dalek/CHANGELOG.md index e8f2b6891..50d040648 100644 --- a/curve25519-dalek/CHANGELOG.md +++ b/curve25519-dalek/CHANGELOG.md @@ -5,6 +5,10 @@ major series. ## 4.x series +### Unreleased + +* Add implementations of the `ff` and `group` traits, behind the `group` feature flag. + ### 4.0.0 #### Breaking changes @@ -43,7 +47,6 @@ major series. version. * Relax the `zeroize` dependency to `^1` * Update the edition from 2015 to 2021 -* Add implementations of the `ff` and `group` traits, behind the `group` feature flag. ## 3.x series From 75d8e6927d192abaf394c418e7eed05960223bfd Mon Sep 17 00:00:00 2001 From: pinkforest <36498018+pinkforest@users.noreply.github.com> Date: Wed, 16 Aug 2023 04:48:10 +1000 Subject: [PATCH 13/16] Switch unwrap_u8s into impl Co-authored-by: Tony Arcieri --- curve25519-dalek/src/edwards.rs | 2 +- curve25519-dalek/src/ristretto.rs | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/curve25519-dalek/src/edwards.rs b/curve25519-dalek/src/edwards.rs index 188c0c44b..221b27b25 100644 --- a/curve25519-dalek/src/edwards.rs +++ b/curve25519-dalek/src/edwards.rs @@ -192,7 +192,7 @@ impl CompressedEdwardsY { /// curve point. pub fn decompress(&self) -> Option { let (is_valid_y_coord, X, Y, Z) = decompress::step_1(self); - if is_valid_y_coord.unwrap_u8() != 1u8 { + if (!is_valid_y_coord).into() { return None; } Some(decompress::step_2(self, X, Y, Z)) diff --git a/curve25519-dalek/src/ristretto.rs b/curve25519-dalek/src/ristretto.rs index ef3e81694..0fbef8200 100644 --- a/curve25519-dalek/src/ristretto.rs +++ b/curve25519-dalek/src/ristretto.rs @@ -255,14 +255,13 @@ impl CompressedRistretto { pub fn decompress(&self) -> Option { let (s_encoding_is_canonical, s_is_negative, s) = decompress::step_1(self); - if s_encoding_is_canonical.unwrap_u8() == 0u8 || s_is_negative.unwrap_u8() == 1u8 { + if (!s_encoding_is_canonical | s_is_negative).into() { return None; } let (ok, t_is_negative, y_is_zero, res) = decompress::step_2(s); - if ok.unwrap_u8() == 0u8 || t_is_negative.unwrap_u8() == 1u8 || y_is_zero.unwrap_u8() == 1u8 - { + if (!ok | t_is_negative | y_is_zero).into() { None } else { Some(res) From a26a1ad6bab83860415f4597dc048c54420f0049 Mon Sep 17 00:00:00 2001 From: pinkforest <36498018+pinkforest@users.noreply.github.com> Date: Wed, 16 Aug 2023 04:57:28 +1000 Subject: [PATCH 14/16] Make if clearer --- curve25519-dalek/src/edwards.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/curve25519-dalek/src/edwards.rs b/curve25519-dalek/src/edwards.rs index 221b27b25..29b589494 100644 --- a/curve25519-dalek/src/edwards.rs +++ b/curve25519-dalek/src/edwards.rs @@ -192,10 +192,12 @@ impl CompressedEdwardsY { /// curve point. pub fn decompress(&self) -> Option { let (is_valid_y_coord, X, Y, Z) = decompress::step_1(self); - if (!is_valid_y_coord).into() { - return None; + + if is_valid_y_coord.into() { + Some(decompress::step_2(self, X, Y, Z)) + } else { + None } - Some(decompress::step_2(self, X, Y, Z)) } } From 9ce5de2d374b7f6fdb995722aa73b43fa21c2e0e Mon Sep 17 00:00:00 2001 From: pinkforest <36498018+pinkforest@users.noreply.github.com> Date: Sat, 19 Aug 2023 00:45:18 +1000 Subject: [PATCH 15/16] Use lifetime elision in Group impls Co-authored-by: Tony Arcieri --- curve25519-dalek/src/edwards.rs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/curve25519-dalek/src/edwards.rs b/curve25519-dalek/src/edwards.rs index 29b589494..beeac17ec 100644 --- a/curve25519-dalek/src/edwards.rs +++ b/curve25519-dalek/src/edwards.rs @@ -1352,9 +1352,9 @@ impl Neg for SubgroupPoint { } #[cfg(feature = "group")] -impl<'a, 'b> Add<&'b SubgroupPoint> for &'a SubgroupPoint { +impl Add<&SubgroupPoint> for &SubgroupPoint { type Output = SubgroupPoint; - fn add(self, other: &'b SubgroupPoint) -> SubgroupPoint { + fn add(self, other: &SubgroupPoint) -> SubgroupPoint { SubgroupPoint(self.0 + other.0) } } @@ -1367,9 +1367,9 @@ define_add_variants!( ); #[cfg(feature = "group")] -impl<'a, 'b> Add<&'b SubgroupPoint> for &'a EdwardsPoint { +impl Add<&SubgroupPoint> for &EdwardsPoint { type Output = EdwardsPoint; - fn add(self, other: &'b SubgroupPoint) -> EdwardsPoint { + fn add(self, other: &SubgroupPoint) -> EdwardsPoint { self + other.0 } } @@ -1402,9 +1402,9 @@ impl AddAssign<&SubgroupPoint> for EdwardsPoint { define_add_assign_variants!(LHS = EdwardsPoint, RHS = SubgroupPoint); #[cfg(feature = "group")] -impl<'a, 'b> Sub<&'b SubgroupPoint> for &'a SubgroupPoint { +impl Sub<&SubgroupPoint> for &SubgroupPoint { type Output = SubgroupPoint; - fn sub(self, other: &'b SubgroupPoint) -> SubgroupPoint { + fn sub(self, other: &SubgroupPoint) -> SubgroupPoint { SubgroupPoint(self.0 - other.0) } } @@ -1417,9 +1417,9 @@ define_sub_variants!( ); #[cfg(feature = "group")] -impl<'a, 'b> Sub<&'b SubgroupPoint> for &'a EdwardsPoint { +impl Sub<&SubgroupPoint> for &EdwardsPoint { type Output = EdwardsPoint; - fn sub(self, other: &'b SubgroupPoint) -> EdwardsPoint { + fn sub(self, other: &SubgroupPoint) -> EdwardsPoint { self - other.0 } } @@ -1466,14 +1466,14 @@ where } #[cfg(feature = "group")] -impl<'a, 'b> Mul<&'b Scalar> for &'a SubgroupPoint { +impl Mul<&Scalar> for &SubgroupPoint { type Output = SubgroupPoint; /// Scalar multiplication: compute `scalar * self`. /// /// For scalar multiplication of a basepoint, /// `EdwardsBasepointTable` is approximately 4x faster. - fn mul(self, scalar: &'b Scalar) -> SubgroupPoint { + fn mul(self, scalar: &Scalar) -> SubgroupPoint { SubgroupPoint(self.0 * scalar) } } @@ -1482,14 +1482,14 @@ impl<'a, 'b> Mul<&'b Scalar> for &'a SubgroupPoint { define_mul_variants!(LHS = Scalar, RHS = SubgroupPoint, Output = SubgroupPoint); #[cfg(feature = "group")] -impl<'a, 'b> Mul<&'b SubgroupPoint> for &'a Scalar { +impl Mul<&SubgroupPoint> for &Scalar { type Output = SubgroupPoint; /// Scalar multiplication: compute `scalar * self`. /// /// For scalar multiplication of a basepoint, /// `EdwardsBasepointTable` is approximately 4x faster. - fn mul(self, point: &'b SubgroupPoint) -> SubgroupPoint { + fn mul(self, point: &SubgroupPoint) -> SubgroupPoint { point * self } } From 26bcaae0b4db5dc5aca06dd1acf8f184037fde9a Mon Sep 17 00:00:00 2001 From: pinkforest <36498018+pinkforest@users.noreply.github.com> Date: Mon, 21 Aug 2023 02:29:21 +1000 Subject: [PATCH 16/16] Add SemVer exempted `group` to README --- curve25519-dalek/README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/curve25519-dalek/README.md b/curve25519-dalek/README.md index db23bdcb4..ebba9cb02 100644 --- a/curve25519-dalek/README.md +++ b/curve25519-dalek/README.md @@ -49,6 +49,7 @@ curve25519-dalek = "4" | `digest` | | Enables `RistrettoPoint::{from_hash, hash_from_bytes}` and `Scalar::{from_hash, hash_from_bytes}`. This is an optional dependency whose version is not subject to SemVer. See [below](#public-api-semver-exemptions) for more details. | | `serde` | | Enables `serde` serialization/deserialization for all the point and scalar types. | | `legacy_compatibility`| | Enables `Scalar::from_bits`, which allows the user to build unreduced scalars whose arithmetic is broken. Do not use this unless you know what you're doing. | +| `group` | | Enables external `group` and `ff` crate traits | To disable the default features when using `curve25519-dalek` as a dependency, add `default-features = false` to the dependency in your `Cargo.toml`. To @@ -190,9 +191,9 @@ From 4.x and on, MSRV changes will be accompanied by a minor version bump. Breaking changes to SemVer exempted components affecting the public API will be accompanied by _some_ version bump. Below are the specific policies: -| Releases | Public API Component(s) | Policy | -| :--- | :--- | :--- | -| 4.x | Dependencies `digest` and `rand_core` | Minor SemVer bump | +| Releases | Public API Component(s) | Policy | +| :--- | :--- | :--- | +| 4.x | Dependencies `group`, `digest` and `rand_core` | Minor SemVer bump | # Safety