From e1336cbb4cd53f6f651be71eff1609460341a1c1 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Mon, 19 Dec 2022 11:31:18 +0000 Subject: [PATCH] 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..26a82c204 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_crate::{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_crate::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 // ------------------------------------------------------------------------