From a39024ba9176c5ca8b21fe3f2bbfddeea763a719 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Thu, 13 Feb 2025 16:31:41 +0400 Subject: [PATCH 01/24] ++ --- lib/crypto/src/curve/mod.rs | 32 + .../src/curve/short_weierstrass/affine.rs | 390 +++++++++++ .../src/curve/short_weierstrass/group.rs | 658 ++++++++++++++++++ lib/crypto/src/curve/short_weierstrass/mod.rs | 207 ++++++ .../short_weierstrass/serialization_flags.rs | 79 +++ .../src/curve/twisted_edwards/affine.rs | 359 ++++++++++ lib/crypto/src/curve/twisted_edwards/group.rs | 496 +++++++++++++ lib/crypto/src/curve/twisted_edwards/mod.rs | 183 +++++ .../twisted_edwards/serialization_flags.rs | 54 ++ lib/crypto/src/lib.rs | 1 + 10 files changed, 2459 insertions(+) create mode 100644 lib/crypto/src/curve/mod.rs create mode 100644 lib/crypto/src/curve/short_weierstrass/affine.rs create mode 100644 lib/crypto/src/curve/short_weierstrass/group.rs create mode 100644 lib/crypto/src/curve/short_weierstrass/mod.rs create mode 100644 lib/crypto/src/curve/short_weierstrass/serialization_flags.rs create mode 100644 lib/crypto/src/curve/twisted_edwards/affine.rs create mode 100644 lib/crypto/src/curve/twisted_edwards/group.rs create mode 100644 lib/crypto/src/curve/twisted_edwards/mod.rs create mode 100644 lib/crypto/src/curve/twisted_edwards/serialization_flags.rs diff --git a/lib/crypto/src/curve/mod.rs b/lib/crypto/src/curve/mod.rs new file mode 100644 index 000000000..fe93affd7 --- /dev/null +++ b/lib/crypto/src/curve/mod.rs @@ -0,0 +1,32 @@ +use num_traits::Zero; + +use crate::field::{prime::PrimeField, Field}; + +pub mod short_weierstrass; +pub mod twisted_edwards; + +/// Elliptic curves can be represented via different "models" with varying +/// efficiency properties. +/// +/// `CurveConfig` bundles together the types that are common +/// to all models of the given curve, namely the `BaseField` over which the +/// curve is defined, and the `ScalarField` defined by the appropriate +/// prime-order subgroup of the curve. +pub trait CurveConfig: Send + Sync + Sized + 'static { + /// Base field that the curve is defined over. + type BaseField: Field; + /// Finite prime field corresponding to an appropriate prime-order subgroup + /// of the curve group. + type ScalarField: PrimeField + + Into<::BigInt>; + + /// The cofactor of this curve, represented as a sequence of little-endian + /// limbs. + const COFACTOR: &'static [u64]; + const COFACTOR_INV: Self::ScalarField; + + fn cofactor_is_one() -> bool { + Self::COFACTOR[0] == 1 + && Self::COFACTOR.iter().skip(1).all(Zero::is_zero) + } +} diff --git a/lib/crypto/src/curve/short_weierstrass/affine.rs b/lib/crypto/src/curve/short_weierstrass/affine.rs new file mode 100644 index 000000000..304937424 --- /dev/null +++ b/lib/crypto/src/curve/short_weierstrass/affine.rs @@ -0,0 +1,390 @@ +use educe::Educe; +use zeroize::Zeroize; + +use super::{Projective, SWCurveConfig, SWFlags}; +use crate::AffineRepr; + +/// Affine coordinates for a point on an elliptic curve in short Weierstrass +/// form, over the base field `P::BaseField`. +#[derive(Educe)] +#[educe(Copy, Clone, PartialEq, Eq, Hash)] +#[must_use] +pub struct Affine { + #[doc(hidden)] + pub x: P::BaseField, + #[doc(hidden)] + pub y: P::BaseField, + #[doc(hidden)] + pub infinity: bool, +} + +impl PartialEq> for Affine

{ + fn eq(&self, other: &Projective

) -> bool { + self.into_group() == *other + } +} + +impl Display for Affine

{ + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + match self.infinity { + true => write!(f, "infinity"), + false => write!(f, "({}, {})", self.x, self.y), + } + } +} + +impl Debug for Affine

{ + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + match self.infinity { + true => write!(f, "infinity"), + false => write!(f, "({}, {})", self.x, self.y), + } + } +} + +impl Affine

{ + /// Constructs a group element from x and y coordinates. + /// Performs checks to ensure that the point is on the curve and is in the + /// right subgroup. + pub fn new(x: P::BaseField, y: P::BaseField) -> Self { + let point = Self { x, y, infinity: false }; + assert!(point.is_on_curve()); + assert!(point.is_in_correct_subgroup_assuming_on_curve()); + point + } + + /// Constructs a group element from x and y coordinates. + /// + /// # Warning + /// + /// Does *not* perform any checks to ensure the point is in the curve or + /// is in the right subgroup. + pub const fn new_unchecked(x: P::BaseField, y: P::BaseField) -> Self { + Self { x, y, infinity: false } + } + + pub const fn identity() -> Self { + Self { x: P::BaseField::ZERO, y: P::BaseField::ZERO, infinity: true } + } + + /// Attempts to construct an affine point given an x-coordinate. The + /// point is not guaranteed to be in the prime order subgroup. + /// + /// If and only if `greatest` is set will the lexicographically + /// largest y-coordinate be selected. + #[allow(dead_code)] + pub fn get_point_from_x_unchecked( + x: P::BaseField, + greatest: bool, + ) -> Option { + Self::get_ys_from_x_unchecked(x).map(|(smaller, larger)| { + if greatest { + Self::new_unchecked(x, larger) + } else { + Self::new_unchecked(x, smaller) + } + }) + } + + /// Returns the two possible y-coordinates corresponding to the given + /// x-coordinate. The corresponding points are not guaranteed to be in + /// the prime-order subgroup, but are guaranteed to be on the curve. + /// That is, this method returns `None` if the x-coordinate corresponds + /// to a non-curve point. + /// + /// The results are sorted by lexicographical order. + /// This means that, if `P::BaseField: PrimeField`, the results are sorted + /// as integers. + pub fn get_ys_from_x_unchecked( + x: P::BaseField, + ) -> Option<(P::BaseField, P::BaseField)> { + // Compute the curve equation x^3 + Ax + B. + // Since Rust does not optimise away additions with zero, we explicitly + // check for that case here, and avoid multiplication by `a` if + // possible. + let mut x3_plus_ax_plus_b = P::add_b(x.square() * x); + if !P::COEFF_A.is_zero() { + x3_plus_ax_plus_b += P::mul_by_a(x) + }; + let y = x3_plus_ax_plus_b.sqrt()?; + let neg_y = -y; + match y < neg_y { + true => Some((y, neg_y)), + false => Some((neg_y, y)), + } + } + + /// Checks if `self` is a valid point on the curve. + pub fn is_on_curve(&self) -> bool { + if !self.infinity { + // Rust does not optimise away addition with zero + let mut x3b = P::add_b(self.x.square() * self.x); + if !P::COEFF_A.is_zero() { + x3b += P::mul_by_a(self.x); + }; + self.y.square() == x3b + } else { + true + } + } + + pub fn to_flags(&self) -> SWFlags { + if self.infinity { + SWFlags::PointAtInfinity + } else if self.y <= -self.y { + SWFlags::YIsPositive + } else { + SWFlags::YIsNegative + } + } +} + +impl Affine

{ + /// Checks if `self` is in the subgroup having order that equaling that of + /// `P::ScalarField`. + // DISCUSS Maybe these function names are too verbose? + pub fn is_in_correct_subgroup_assuming_on_curve(&self) -> bool { + P::is_in_correct_subgroup_assuming_on_curve(self) + } +} + +impl Zeroize for Affine

{ + // The phantom data does not contain element-specific data + // and thus does not need to be zeroized. + fn zeroize(&mut self) { + self.x.zeroize(); + self.y.zeroize(); + self.infinity.zeroize(); + } +} + +impl Distribution> for Standard { + /// Generates a uniformly random instance of the curve. + #[inline] + fn sample(&self, rng: &mut R) -> Affine

{ + loop { + let x = P::BaseField::rand(rng); + let greatest = rng.gen(); + + if let Some(p) = Affine::get_point_from_x_unchecked(x, greatest) { + return p.mul_by_cofactor(); + } + } + } +} + +impl AffineRepr for Affine

{ + type BaseField = P::BaseField; + type Config = P; + type Group = Projective

; + type ScalarField = P::ScalarField; + + fn xy(&self) -> Option<(Self::BaseField, Self::BaseField)> { + (!self.infinity).then_some((self.x, self.y)) + } + + #[inline] + fn generator() -> Self { + P::GENERATOR + } + + fn zero() -> Self { + Self { x: P::BaseField::ZERO, y: P::BaseField::ZERO, infinity: true } + } + + fn from_random_bytes(bytes: &[u8]) -> Option { + P::BaseField::from_random_bytes_with_flags::(bytes).and_then( + |(x, flags)| { + // if x is valid and is zero and only the infinity flag is set, + // then parse this point as infinity. For all + // other choices, get the original point. + if x.is_zero() && flags.is_infinity() { + Some(Self::identity()) + } else if let Some(y_is_positive) = flags.is_positive() { + Self::get_point_from_x_unchecked(x, y_is_positive) + // Unwrap is safe because it's not zero. + } else { + None + } + }, + ) + } + + fn mul_bigint(&self, by: impl AsRef<[u64]>) -> Self::Group { + P::mul_affine(self, by.as_ref()) + } + + /// Multiplies this element by the cofactor and output the + /// resulting projective element. + #[must_use] + fn mul_by_cofactor_to_group(&self) -> Self::Group { + P::mul_affine(self, Self::Config::COFACTOR) + } + + /// Performs cofactor clearing. + /// The default method is simply to multiply by the cofactor. + /// Some curves can implement a more efficient algorithm. + fn clear_cofactor(&self) -> Self { + P::clear_cofactor(self) + } +} + +impl Neg for Affine

{ + type Output = Self; + + /// If `self.is_zero()`, returns `self` (`== Self::zero()`). + /// Else, returns `(x, -y)`, where `self = (x, y)`. + #[inline] + fn neg(mut self) -> Self { + self.y.neg_in_place(); + self + } +} + +impl> Add for Affine

{ + type Output = Projective

; + + fn add(self, other: T) -> Projective

{ + // TODO implement more efficient formulae when z1 = z2 = 1. + let mut copy = self.into_group(); + copy += other.borrow(); + copy + } +} + +impl Add> for Affine

{ + type Output = Projective

; + + fn add(self, other: Projective

) -> Projective

{ + other + self + } +} + +impl<'a, P: SWCurveConfig> Add<&'a Projective

> for Affine

{ + type Output = Projective

; + + fn add(self, other: &'a Projective

) -> Projective

{ + *other + self + } +} + +impl> Sub for Affine

{ + type Output = Projective

; + + fn sub(self, other: T) -> Projective

{ + let mut copy = self.into_group(); + copy -= other.borrow(); + copy + } +} + +impl Sub> for Affine

{ + type Output = Projective

; + + fn sub(self, other: Projective

) -> Projective

{ + self + (-other) + } +} + +impl<'a, P: SWCurveConfig> Sub<&'a Projective

> for Affine

{ + type Output = Projective

; + + fn sub(self, other: &'a Projective

) -> Projective

{ + self + (-*other) + } +} + +impl Default for Affine

{ + #[inline] + fn default() -> Self { + Self::identity() + } +} + +impl> Mul for Affine

{ + type Output = Projective

; + + #[inline] + fn mul(self, other: T) -> Self::Output { + self.mul_bigint(other.borrow().into_bigint()) + } +} + +// The projective point X, Y, Z is represented in the affine +// coordinates as X/Z^2, Y/Z^3. +impl From> for Affine

{ + #[inline] + fn from(p: Projective

) -> Affine

{ + if p.is_zero() { + Affine::identity() + } else if p.z.is_one() { + // If Z is one, the point is already normalized. + Affine::new_unchecked(p.x, p.y) + } else { + // Z is nonzero, so it must have an inverse in a field. + let zinv = p.z.inverse().unwrap(); + let zinv_squared = zinv.square(); + + // X/Z^2 + let x = p.x * &zinv_squared; + + // Y/Z^3 + let y = p.y * &(zinv_squared * &zinv); + + Affine::new_unchecked(x, y) + } + } +} + +impl CanonicalSerialize for Affine

{ + #[inline] + fn serialize_with_mode( + &self, + writer: W, + compress: ark_serialize::Compress, + ) -> Result<(), SerializationError> { + P::serialize_with_mode(self, writer, compress) + } + + #[inline] + fn serialized_size(&self, compress: Compress) -> usize { + P::serialized_size(compress) + } +} + +impl Valid for Affine

{ + fn check(&self) -> Result<(), SerializationError> { + if self.is_on_curve() && self.is_in_correct_subgroup_assuming_on_curve() + { + Ok(()) + } else { + Err(SerializationError::InvalidData) + } + } +} + +impl CanonicalDeserialize for Affine

{ + fn deserialize_with_mode( + reader: R, + compress: Compress, + validate: Validate, + ) -> Result { + P::deserialize_with_mode(reader, compress, validate) + } +} + +impl ToConstraintField + for Affine +where + M::BaseField: ToConstraintField, +{ + #[inline] + fn to_field_elements(&self) -> Option> { + let mut x = self.x.to_field_elements()?; + let y = self.y.to_field_elements()?; + let infinity = self.infinity.to_field_elements()?; + x.extend_from_slice(&y); + x.extend_from_slice(&infinity); + Some(x) + } +} diff --git a/lib/crypto/src/curve/short_weierstrass/group.rs b/lib/crypto/src/curve/short_weierstrass/group.rs new file mode 100644 index 000000000..27d3d2e88 --- /dev/null +++ b/lib/crypto/src/curve/short_weierstrass/group.rs @@ -0,0 +1,658 @@ +use educe::Educe; +use zeroize::Zeroize; + +use super::{Affine, SWCurveConfig}; + +/// Jacobian coordinates for a point on an elliptic curve in short Weierstrass +/// form, over the base field `P::BaseField`. This struct implements arithmetic +/// via the Jacobian formulae +#[derive(Educe)] +#[educe(Copy, Clone)] +#[must_use] +pub struct Projective { + /// `X / Z` projection of the affine `X` + pub x: P::BaseField, + /// `Y / Z` projection of the affine `Y` + pub y: P::BaseField, + /// Projective multiplicative inverse. Will be `0` only at infinity. + pub z: P::BaseField, +} + +impl Display for Projective

{ + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + write!(f, "{}", Affine::from(*self)) + } +} + +impl Debug for Projective

{ + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + match self.is_zero() { + true => write!(f, "infinity"), + false => write!(f, "({}, {}, {})", self.x, self.y, self.z), + } + } +} + +impl Eq for Projective

{} +impl PartialEq for Projective

{ + fn eq(&self, other: &Self) -> bool { + if self.is_zero() { + return other.is_zero(); + } + + if other.is_zero() { + return false; + } + + // The points (X, Y, Z) and (X', Y', Z') + // are equal when (X * Z^2) = (X' * Z'^2) + // and (Y * Z^3) = (Y' * Z'^3). + let z1z1 = self.z.square(); + let z2z2 = other.z.square(); + + if self.x * &z2z2 != other.x * &z1z1 { + false + } else { + self.y * &(z2z2 * &other.z) == other.y * &(z1z1 * &self.z) + } + } +} + +impl PartialEq> for Projective

{ + fn eq(&self, other: &Affine

) -> bool { + *self == other.into_group() + } +} + +impl Hash for Projective

{ + fn hash(&self, state: &mut H) { + self.into_affine().hash(state) + } +} + +impl Distribution> for Standard { + /// Generates a uniformly random instance of the curve. + #[inline] + fn sample(&self, rng: &mut R) -> Projective

{ + loop { + let x = P::BaseField::rand(rng); + let greatest = rng.gen(); + + if let Some(p) = Affine::get_point_from_x_unchecked(x, greatest) { + return p.mul_by_cofactor_to_group(); + } + } + } +} + +impl Default for Projective

{ + #[inline] + fn default() -> Self { + Self::zero() + } +} + +impl Projective

{ + /// Constructs a new group element without checking whether the coordinates + /// specify a point in the subgroup. + pub const fn new_unchecked( + x: P::BaseField, + y: P::BaseField, + z: P::BaseField, + ) -> Self { + Self { x, y, z } + } + + /// Constructs a new group element in a way while enforcing that points are + /// in the prime-order subgroup. + pub fn new(x: P::BaseField, y: P::BaseField, z: P::BaseField) -> Self { + let p = Self::new_unchecked(x, y, z).into_affine(); + assert!(p.is_on_curve()); + assert!(p.is_in_correct_subgroup_assuming_on_curve()); + p.into() + } +} + +impl Zeroize for Projective

{ + fn zeroize(&mut self) { + self.x.zeroize(); + self.y.zeroize(); + self.z.zeroize(); + } +} + +impl Zero for Projective

{ + /// Returns the point at infinity, which always has Z = 0. + #[inline] + fn zero() -> Self { + Self::new_unchecked( + P::BaseField::one(), + P::BaseField::one(), + P::BaseField::zero(), + ) + } + + /// Checks whether `self.z.is_zero()`. + #[inline] + fn is_zero(&self) -> bool { + self.z == P::BaseField::ZERO + } +} + +impl AdditiveGroup for Projective

{ + type Scalar = P::ScalarField; + + const ZERO: Self = Self::new_unchecked( + P::BaseField::ONE, + P::BaseField::ONE, + P::BaseField::ZERO, + ); + + /// Sets `self = 2 * self`. Note that Jacobian formulae are incomplete, and + /// so doubling cannot be computed as `self + self`. Instead, this + /// implementation uses the following specialized doubling formulae: + /// * [`P::A` is zero](http://www.hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#doubling-dbl-2009-l) + /// * [`P::A` is not zero](https://www.hyperelliptic.org/EFD/g1p/auto-shortw-jacobian.html#doubling-dbl-2007-bl) + fn double_in_place(&mut self) -> &mut Self { + if self.is_zero() { + return self; + } + + if P::COEFF_A == P::BaseField::ZERO { + // A = X1^2 + let mut a = self.x; + a.square_in_place(); + + // B = Y1^2 + let mut b = self.y; + b.square_in_place(); + + // C = B^2 + let mut c = b; + c.square_in_place(); + + // D = 2*((X1+B)^2-A-C) + // = 2 * (X1 + Y1^2)^2 - A - C + // = 2 * 2 * X1 * Y1^2 + let d = if [1, 2].contains(&P::BaseField::extension_degree()) { + let mut d = self.x; + d *= &b; + d.double_in_place().double_in_place(); + d + } else { + let mut d = self.x; + d += &b; + d.square_in_place(); + d -= a; + d -= c; + d.double_in_place(); + d + }; + + // E = 3*A + let e = a + &*a.double_in_place(); + + // Z3 = 2*Y1*Z1 + self.z *= &self.y; + self.z.double_in_place(); + + // F = E^2 + // X3 = F-2*D + self.x = e; + self.x.square_in_place(); + self.x -= &d.double(); + + // Y3 = E*(D-X3)-8*C + self.y = d; + self.y -= &self.x; + self.y *= &e; + self.y -= c.double_in_place().double_in_place().double_in_place(); + self + } else { + // http://www.hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#doubling-dbl-2009-l + // XX = X1^2 + let xx = self.x.square(); + + // YY = Y1^2 + let yy = self.y.square(); + + // YYYY = YY^2 + let mut yyyy = yy; + yyyy.square_in_place(); + + // ZZ = Z1^2 + let mut zz = self.z; + zz.square_in_place(); + + // S = 2*((X1+YY)^2-XX-YYYY) + let s = ((self.x + &yy).square() - &xx - &yyyy).double(); + + // M = 3*XX+a*ZZ^2 + let mut m = xx; + m.double_in_place(); + m += &xx; + m += &P::mul_by_a(zz.square()); + + // T = M^2-2*S + // X3 = T + self.x = m; + self.x.square_in_place(); + self.x -= s.double(); + + // Z3 = (Y1+Z1)^2-YY-ZZ + // Can be calculated as Z3 = 2*Y1*Z1, and this is faster. + self.z *= self.y; + self.z.double_in_place(); + + // Y3 = M*(S-X3)-8*YYYY + self.y = s; + self.y -= &self.x; + self.y *= &m; + self.y -= + yyyy.double_in_place().double_in_place().double_in_place(); + + self + } + } +} + +impl PrimeGroup for Projective

{ + type ScalarField = P::ScalarField; + + #[inline] + fn generator() -> Self { + Affine::generator().into() + } + + #[inline] + fn mul_bigint(&self, other: impl AsRef<[u64]>) -> Self { + P::mul_projective(self, other.as_ref()) + } +} + +impl CurveGroup for Projective

{ + type Affine = Affine

; + type BaseField = P::BaseField; + type Config = P; + type FullGroup = Affine

; + + /// Normalizes a slice of projective elements so that + /// conversion to affine is cheap. + /// + /// In more detail, this method converts a curve point in Jacobian + /// coordinates (x, y, z) into an equivalent representation (x/z^2, + /// y/z^3, 1). + /// + /// For `N = v.len()`, this costs 1 inversion + 6N field multiplications + N + /// field squarings. + /// + /// (Where batch inversion comprises 3N field multiplications + 1 inversion + /// of these operations) + #[inline] + fn normalize_batch(v: &[Self]) -> Vec { + let mut z_s = v.iter().map(|g| g.z).collect::>(); + ark_ff::batch_inversion(&mut z_s); + + // Perform affine transformations + ark_std::cfg_iter!(v) + .zip(z_s) + .map(|(g, z)| match g.is_zero() { + true => Affine::identity(), + false => { + let z2 = z.square(); + let x = g.x * z2; + let y = g.y * z2 * z; + Affine::new_unchecked(x, y) + } + }) + .collect() + } +} + +impl Neg for Projective

{ + type Output = Self; + + #[inline] + fn neg(mut self) -> Self { + self.y = -self.y; + self + } +} + +impl>> AddAssign for Projective

{ + /// Using + fn add_assign(&mut self, other: T) { + let other = other.borrow(); + if let Some((other_x, other_y)) = other.xy() { + if self.is_zero() { + self.x = other_x; + self.y = other_y; + self.z = P::BaseField::one(); + return; + } + + // Z1Z1 = Z1^2 + let mut z1z1 = self.z; + z1z1.square_in_place(); + + // U2 = X2*Z1Z1 + let mut u2 = other_x; + u2 *= &z1z1; + + // S2 = Y2*Z1*Z1Z1 + let mut s2 = self.z; + s2 *= &other_y; + s2 *= &z1z1; + + if self.x == u2 { + if self.y == s2 { + // The two points are equal, so we double. + self.double_in_place(); + } else { + // a + (-a) = 0 + *self = Self::zero() + } + } else { + // H = U2-X1 + let mut h = u2; + h -= &self.x; + + // HH = H^2 + let mut hh = h; + hh.square_in_place(); + + // I = 4*HH + let mut i = hh; + i.double_in_place().double_in_place(); + + // J = -H*I + let mut j = h; + j.neg_in_place(); + j *= &i; + + // r = 2*(S2-Y1) + let mut r = s2; + r -= &self.y; + r.double_in_place(); + + // V = X1*I + let mut v = self.x; + v *= &i; + + // X3 = r^2 + J - 2*V + self.x = r.square(); + self.x += &j; + self.x -= &v.double(); + + // Y3 = r*(V-X3) + 2*Y1*J + v -= &self.x; + self.y.double_in_place(); + self.y = P::BaseField::sum_of_products(&[r, self.y], &[v, j]); + + // Z3 = 2 * Z1 * H; + // Can alternatively be computed as (Z1+H)^2-Z1Z1-HH, but the + // latter is slower. + self.z *= &h; + self.z.double_in_place(); + } + } + } +} + +impl>> Add for Projective

{ + type Output = Self; + + fn add(mut self, other: T) -> Self { + let other = other.borrow(); + self += other; + self + } +} + +impl>> SubAssign for Projective

{ + fn sub_assign(&mut self, other: T) { + *self += -(*other.borrow()); + } +} + +impl>> Sub for Projective

{ + type Output = Self; + + fn sub(mut self, other: T) -> Self { + self -= other.borrow(); + self + } +} + +ark_ff::impl_additive_ops_from_ref!(Projective, SWCurveConfig); + +impl<'a, P: SWCurveConfig> Add<&'a Self> for Projective

{ + type Output = Self; + + #[inline] + fn add(mut self, other: &'a Self) -> Self { + self += other; + self + } +} + +impl<'a, P: SWCurveConfig> AddAssign<&'a Self> for Projective

{ + fn add_assign(&mut self, other: &'a Self) { + if self.is_zero() { + *self = *other; + return; + } + + if other.is_zero() { + return; + } + + // http://www.hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#addition-add-2007-bl + // Works for all curves. + + // Z1Z1 = Z1^2 + let z1z1 = self.z.square(); + + // Z2Z2 = Z2^2 + let z2z2 = other.z.square(); + + // U1 = X1*Z2Z2 + let mut u1 = self.x; + u1 *= &z2z2; + + // U2 = X2*Z1Z1 + let mut u2 = other.x; + u2 *= &z1z1; + + // S1 = Y1*Z2*Z2Z2 + let mut s1 = self.y; + s1 *= &other.z; + s1 *= &z2z2; + + // S2 = Y2*Z1*Z1Z1 + let mut s2 = other.y; + s2 *= &self.z; + s2 *= &z1z1; + + if u1 == u2 { + if s1 == s2 { + // The two points are equal, so we double. + self.double_in_place(); + } else { + // a + (-a) = 0 + *self = Self::zero(); + } + } else { + // H = U2-U1 + let mut h = u2; + h -= &u1; + + // I = (2*H)^2 + let mut i = h; + i.double_in_place().square_in_place(); + + // J = -H*I + let mut j = h; + j.neg_in_place(); + j *= &i; + + // r = 2*(S2-S1) + let mut r = s2; + r -= &s1; + r.double_in_place(); + + // V = U1*I + let mut v = u1; + v *= &i; + + // X3 = r^2 + J - 2*V + self.x = r; + self.x.square_in_place(); + self.x += &j; + self.x -= &(v.double()); + + // Y3 = r*(V - X3) + 2*S1*J + v -= &self.x; + self.y = s1; + self.y.double_in_place(); + self.y = P::BaseField::sum_of_products(&[r, self.y], &[v, j]); + + // Z3 = ((Z1+Z2)^2 - Z1Z1 - Z2Z2)*H + // This is equal to Z3 = 2 * Z1 * Z2 * H, and computing it this way + // is faster. + self.z *= other.z; + self.z.double_in_place(); + self.z *= &h; + } + } +} + +impl<'a, P: SWCurveConfig> Sub<&'a Self> for Projective

{ + type Output = Self; + + #[inline] + fn sub(mut self, other: &'a Self) -> Self { + self -= other; + self + } +} + +impl<'a, P: SWCurveConfig> SubAssign<&'a Self> for Projective

{ + fn sub_assign(&mut self, other: &'a Self) { + *self += &(-(*other)); + } +} + +impl> MulAssign + for Projective

+{ + fn mul_assign(&mut self, other: T) { + *self = self.mul_bigint(other.borrow().into_bigint()) + } +} + +impl> Mul for Projective

{ + type Output = Self; + + #[inline] + fn mul(mut self, other: T) -> Self { + self *= other; + self + } +} + +// The affine point X, Y is represented in the Jacobian +// coordinates with Z = 1. +impl From> for Projective

{ + #[inline] + fn from(p: Affine

) -> Projective

{ + p.xy().map_or(Projective::zero(), |(x, y)| Self { + x, + y, + z: P::BaseField::one(), + }) + } +} + +impl CanonicalSerialize for Projective

{ + #[inline] + fn serialize_with_mode( + &self, + writer: W, + compress: Compress, + ) -> Result<(), SerializationError> { + let aff = Affine::from(*self); + P::serialize_with_mode(&aff, writer, compress) + } + + #[inline] + fn serialized_size(&self, compress: Compress) -> usize { + P::serialized_size(compress) + } +} + +impl Valid for Projective

{ + fn check(&self) -> Result<(), SerializationError> { + self.into_affine().check() + } + + fn batch_check<'a>( + batch: impl Iterator + Send, + ) -> Result<(), SerializationError> + where + Self: 'a, + { + let batch = batch.copied().collect::>(); + let batch = Self::normalize_batch(&batch); + Affine::batch_check(batch.iter()) + } +} + +impl CanonicalDeserialize for Projective

{ + fn deserialize_with_mode( + reader: R, + compress: Compress, + validate: Validate, + ) -> Result { + let aff = P::deserialize_with_mode(reader, compress, validate)?; + Ok(aff.into()) + } +} + +impl ToConstraintField + for Projective +where + M::BaseField: ToConstraintField, +{ + #[inline] + fn to_field_elements(&self) -> Option> { + Affine::from(*self).to_field_elements() + } +} + +impl ScalarMul for Projective

{ + type MulBase = Affine

; + + const NEGATION_IS_CHEAP: bool = true; + + fn batch_convert_to_mul_base(bases: &[Self]) -> Vec { + Self::normalize_batch(bases) + } +} + +impl VariableBaseMSM for Projective

{ + fn msm( + bases: &[Self::MulBase], + bigints: &[Self::ScalarField], + ) -> Result { + P::msm(bases, bigints) + } +} + +impl>> core::iter::Sum + for Projective

+{ + fn sum>(iter: I) -> Self { + iter.fold(Projective::zero(), |sum, x| sum + x.borrow()) + } +} diff --git a/lib/crypto/src/curve/short_weierstrass/mod.rs b/lib/crypto/src/curve/short_weierstrass/mod.rs new file mode 100644 index 000000000..5f4833df1 --- /dev/null +++ b/lib/crypto/src/curve/short_weierstrass/mod.rs @@ -0,0 +1,207 @@ +use num_traits::Zero; + +use crate::{ + scalar_mul::{ + sw_double_and_add_affine, sw_double_and_add_projective, + variable_base::VariableBaseMSM, + }, + AffineRepr, +}; + +mod affine; +pub use affine::*; + +mod group; +pub use group::*; + +mod serialization_flags; +pub use serialization_flags::*; + +/// Constants and convenience functions that collectively define the [Short Weierstrass model](https://www.hyperelliptic.org/EFD/g1p/auto-shortw.html) +/// of the curve. +/// +/// In this model, the curve equation is `y² = x³ + a * x + b`, for constants +/// `a` and `b`. +pub trait SWCurveConfig: super::CurveConfig { + /// Coefficient `a` of the curve equation. + const COEFF_A: Self::BaseField; + /// Coefficient `b` of the curve equation. + const COEFF_B: Self::BaseField; + /// Generator of the prime-order subgroup. + const GENERATOR: Affine; + + /// Helper method for computing `elem * Self::COEFF_A`. + /// + /// The default implementation should be overridden only if + /// the product can be computed faster than standard field multiplication + /// (eg: via doubling if `COEFF_A == 2`, or if `COEFF_A.is_zero()`). + #[inline(always)] + fn mul_by_a(elem: Self::BaseField) -> Self::BaseField { + if Self::COEFF_A.is_zero() { + Self::BaseField::ZERO + } else { + elem * Self::COEFF_A + } + } + + /// Helper method for computing `elem + Self::COEFF_B`. + /// + /// The default implementation should be overridden only if + /// the sum can be computed faster than standard field addition (eg: via + /// doubling). + #[inline(always)] + fn add_b(elem: Self::BaseField) -> Self::BaseField { + if Self::COEFF_B.is_zero() { + elem + } else { + elem + &Self::COEFF_B + } + } + + /// Check if the provided curve point is in the prime-order subgroup. + /// + /// The default implementation multiplies `item` by the order `r` of the + /// prime-order subgroup, and checks if the result is zero. If the + /// curve's cofactor is one, this check automatically returns true. + /// Implementors can choose to override this default impl + /// if the given curve has faster methods + /// for performing this check (for example, via leveraging curve + /// isomorphisms). + fn is_in_correct_subgroup_assuming_on_curve(item: &Affine) -> bool { + if Self::cofactor_is_one() { + true + } else { + Self::mul_affine(item, Self::ScalarField::characteristic()) + .is_zero() + } + } + + /// Performs cofactor clearing. + /// The default method is simply to multiply by the cofactor. + /// Some curves can implement a more efficient algorithm. + fn clear_cofactor(item: &Affine) -> Affine { + item.mul_by_cofactor() + } + + /// Default implementation of group multiplication for projective + /// coordinates + fn mul_projective( + base: &Projective, + scalar: &[u64], + ) -> Projective { + sw_double_and_add_projective(base, scalar) + } + + /// Default implementation of group multiplication for affine + /// coordinates. + fn mul_affine(base: &Affine, scalar: &[u64]) -> Projective { + sw_double_and_add_affine(base, scalar) + } + + /// Default implementation for multi scalar multiplication + fn msm( + bases: &[Affine], + scalars: &[Self::ScalarField], + ) -> Result, usize> { + (bases.len() == scalars.len()) + .then(|| VariableBaseMSM::msm_unchecked(bases, scalars)) + .ok_or(bases.len().min(scalars.len())) + } + + /// If uncompressed, serializes both x and y coordinates as well as a bit + /// for whether it is infinity. If compressed, serializes x coordinate + /// with two bits to encode whether y is positive, negative, or + /// infinity. + #[inline] + fn serialize_with_mode( + item: &Affine, + mut writer: W, + compress: ark_serialize::Compress, + ) -> Result<(), SerializationError> { + let (x, y, flags) = match item.infinity { + true => ( + Self::BaseField::zero(), + Self::BaseField::zero(), + SWFlags::infinity(), + ), + false => (item.x, item.y, item.to_flags()), + }; + + match compress { + Compress::Yes => x.serialize_with_flags(writer, flags), + Compress::No => { + x.serialize_with_mode(&mut writer, compress)?; + y.serialize_with_flags(&mut writer, flags) + } + } + } + + /// If `validate` is `Yes`, calls `check()` to make sure the element is + /// valid. + fn deserialize_with_mode( + mut reader: R, + compress: Compress, + validate: Validate, + ) -> Result, SerializationError> { + let (x, y, flags) = match compress { + Compress::Yes => { + let (x, flags): (_, SWFlags) = + CanonicalDeserializeWithFlags::deserialize_with_flags( + reader, + )?; + match flags { + SWFlags::PointAtInfinity => ( + Affine::::identity().x, + Affine::::identity().y, + flags, + ), + _ => { + let is_positive = flags.is_positive().unwrap(); + let (y, neg_y) = + Affine::::get_ys_from_x_unchecked(x) + .ok_or(SerializationError::InvalidData)?; + if is_positive { + (x, y, flags) + } else { + (x, neg_y, flags) + } + } + } + } + Compress::No => { + let x: Self::BaseField = + CanonicalDeserialize::deserialize_with_mode( + &mut reader, + compress, + validate, + )?; + let (y, flags): (_, SWFlags) = + CanonicalDeserializeWithFlags::deserialize_with_flags( + &mut reader, + )?; + (x, y, flags) + } + }; + if flags.is_infinity() { + Ok(Affine::identity()) + } else { + let point = Affine::new_unchecked(x, y); + if let Validate::Yes = validate { + point.check()?; + } + Ok(point) + } + } + + #[inline] + fn serialized_size(compress: Compress) -> usize { + let zero = Self::BaseField::zero(); + match compress { + Compress::Yes => zero.serialized_size_with_flags::(), + Compress::No => { + zero.compressed_size() + + zero.serialized_size_with_flags::() + } + } + } +} diff --git a/lib/crypto/src/curve/short_weierstrass/serialization_flags.rs b/lib/crypto/src/curve/short_weierstrass/serialization_flags.rs new file mode 100644 index 000000000..b9fb00eb7 --- /dev/null +++ b/lib/crypto/src/curve/short_weierstrass/serialization_flags.rs @@ -0,0 +1,79 @@ +/// Flags to be encoded into the serialization. +/// The default flags (empty) should not change the binary representation. +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum SWFlags { + /// Represents a point with positive y-coordinate by setting all bits to 0. + YIsPositive = 0, + /// Represents the point at infinity by setting the setting the + /// last-but-one bit to 1. + PointAtInfinity = 1 << 6, + /// Represents a point with negative y-coordinate by setting the MSB to 1. + YIsNegative = 1 << 7, +} + +impl SWFlags { + #[inline] + pub fn infinity() -> Self { + SWFlags::PointAtInfinity + } + + #[inline] + pub fn from_y_coordinate(y: impl Field) -> Self { + if y <= -y { + Self::YIsPositive + } else { + Self::YIsNegative + } + } + + #[inline] + pub fn is_infinity(&self) -> bool { + matches!(self, SWFlags::PointAtInfinity) + } + + #[inline] + pub fn is_positive(&self) -> Option { + match self { + SWFlags::PointAtInfinity => None, + SWFlags::YIsPositive => Some(true), + SWFlags::YIsNegative => Some(false), + } + } +} + +impl Default for SWFlags { + #[inline] + fn default() -> Self { + // YIsNegative doesn't change the serialization + SWFlags::YIsNegative + } +} + +impl Flags for SWFlags { + const BIT_SIZE: usize = 2; + + #[inline] + fn u8_bitmask(&self) -> u8 { + let mut mask = 0; + match self { + SWFlags::PointAtInfinity => mask |= 1 << 6, + SWFlags::YIsNegative => mask |= 1 << 7, + _ => (), + } + mask + } + + #[inline] + fn from_u8(value: u8) -> Option { + let is_negative = (value >> 7) & 1 == 1; + let is_infinity = (value >> 6) & 1 == 1; + match (is_negative, is_infinity) { + // This is invalid because we only want *one* way to serialize + // the point at infinity. + (true, true) => None, + (false, true) => Some(SWFlags::PointAtInfinity), + (true, false) => Some(SWFlags::YIsNegative), + (false, false) => Some(SWFlags::YIsPositive), + } + } +} diff --git a/lib/crypto/src/curve/twisted_edwards/affine.rs b/lib/crypto/src/curve/twisted_edwards/affine.rs new file mode 100644 index 000000000..f7cb23e98 --- /dev/null +++ b/lib/crypto/src/curve/twisted_edwards/affine.rs @@ -0,0 +1,359 @@ +use educe::Educe; +use num_traits::{One, Zero}; +use zeroize::Zeroize; + +use super::{Projective, TECurveConfig, TEFlags}; +use crate::AffineRepr; + +/// Affine coordinates for a point on a twisted Edwards curve, over the +/// base field `P::BaseField`. +#[derive(Educe)] +#[educe(Copy, Clone, PartialEq, Eq, Hash)] +#[must_use] +pub struct Affine { + /// X coordinate of the point represented as a field element + pub x: P::BaseField, + /// Y coordinate of the point represented as a field element + pub y: P::BaseField, +} + +impl Display for Affine

{ + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + match self.is_zero() { + true => write!(f, "infinity"), + false => write!(f, "({}, {})", self.x, self.y), + } + } +} + +impl Debug for Affine

{ + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + match self.is_zero() { + true => write!(f, "infinity"), + false => write!(f, "({}, {})", self.x, self.y), + } + } +} + +impl PartialEq> for Affine

{ + fn eq(&self, other: &Projective

) -> bool { + self.into_group() == *other + } +} + +impl Affine

{ + /// Construct a new group element without checking whether the coordinates + /// specify a point in the subgroup. + pub const fn new_unchecked(x: P::BaseField, y: P::BaseField) -> Self { + Self { x, y } + } + + /// Construct a new group element in a way while enforcing that points are + /// in the prime-order subgroup. + pub fn new(x: P::BaseField, y: P::BaseField) -> Self { + let p = Self::new_unchecked(x, y); + assert!(p.is_on_curve()); + assert!(p.is_in_correct_subgroup_assuming_on_curve()); + p + } + + /// Construct the identity of the group + pub const fn zero() -> Self { + Self::new_unchecked(P::BaseField::ZERO, P::BaseField::ONE) + } + + /// Is this point the identity? + pub fn is_zero(&self) -> bool { + self.x.is_zero() && self.y.is_one() + } + + /// Attempts to construct an affine point given an y-coordinate. The + /// point is not guaranteed to be in the prime order subgroup. + /// + /// If and only if `greatest` is set will the lexicographically + /// largest x-coordinate be selected. + /// + /// a * X^2 + Y^2 = 1 + d * X^2 * Y^2 + /// a * X^2 - d * X^2 * Y^2 = 1 - Y^2 + /// X^2 * (a - d * Y^2) = 1 - Y^2 + /// X^2 = (1 - Y^2) / (a - d * Y^2) + #[allow(dead_code)] + pub fn get_point_from_y_unchecked( + y: P::BaseField, + greatest: bool, + ) -> Option { + Self::get_xs_from_y_unchecked(y).map(|(x, neg_x)| { + if greatest { + Self::new_unchecked(neg_x, y) + } else { + Self::new_unchecked(x, y) + } + }) + } + + /// Attempts to recover the x-coordinate given an y-coordinate. The + /// resulting point is not guaranteed to be in the prime order subgroup. + /// + /// If and only if `greatest` is set will the lexicographically + /// largest x-coordinate be selected. + /// + /// a * X^2 + Y^2 = 1 + d * X^2 * Y^2 + /// a * X^2 - d * X^2 * Y^2 = 1 - Y^2 + /// X^2 * (a - d * Y^2) = 1 - Y^2 + /// X^2 = (1 - Y^2) / (a - d * Y^2) + #[allow(dead_code)] + pub fn get_xs_from_y_unchecked( + y: P::BaseField, + ) -> Option<(P::BaseField, P::BaseField)> { + let y2 = y.square(); + + let numerator = P::BaseField::one() - y2; + let denominator = P::COEFF_A - (y2 * P::COEFF_D); + + denominator + .inverse() + .map(|denom| denom * &numerator) + .and_then(|x2| x2.sqrt()) + .map(|x| { + let neg_x = -x; + if x <= neg_x { + (x, neg_x) + } else { + (neg_x, x) + } + }) + } + + /// Checks that the current point is on the elliptic curve. + pub fn is_on_curve(&self) -> bool { + let x2 = self.x.square(); + let y2 = self.y.square(); + + let lhs = y2 + P::mul_by_a(x2); + let rhs = P::BaseField::one() + &(P::COEFF_D * &(x2 * &y2)); + + lhs == rhs + } +} + +impl Affine

{ + /// Checks if `self` is in the subgroup having order equaling that of + /// `P::ScalarField` given it is on the curve. + pub fn is_in_correct_subgroup_assuming_on_curve(&self) -> bool { + P::is_in_correct_subgroup_assuming_on_curve(self) + } +} + +impl AffineRepr for Affine

{ + type BaseField = P::BaseField; + type Config = P; + type Group = Projective

; + type ScalarField = P::ScalarField; + + fn xy(&self) -> Option<(Self::BaseField, Self::BaseField)> { + (!self.is_zero()).then_some((self.x, self.y)) + } + + fn generator() -> Self { + P::GENERATOR + } + + fn zero() -> Self { + Self::new_unchecked(P::BaseField::ZERO, P::BaseField::ONE) + } + + fn from_random_bytes(bytes: &[u8]) -> Option { + P::BaseField::from_random_bytes_with_flags::(bytes).and_then( + |(y, flags)| { + Self::get_point_from_y_unchecked(y, flags.is_negative()) + }, + ) + } + + fn mul_bigint(&self, by: impl AsRef<[u64]>) -> Self::Group { + P::mul_affine(self, by.as_ref()) + } + + /// Multiplies this element by the cofactor and output the + /// resulting projective element. + #[must_use] + fn mul_by_cofactor_to_group(&self) -> Self::Group { + P::mul_affine(self, Self::Config::COFACTOR) + } + + /// Performs cofactor clearing. + /// The default method is simply to multiply by the cofactor. + /// Some curves can implement a more efficient algorithm. + fn clear_cofactor(&self) -> Self { + P::clear_cofactor(self) + } +} + +impl Zeroize for Affine

{ + // The phantom data does not contain element-specific data + // and thus does not need to be zeroized. + fn zeroize(&mut self) { + self.x.zeroize(); + self.y.zeroize(); + } +} + +impl Neg for Affine

{ + type Output = Self; + + fn neg(self) -> Self { + Self::new_unchecked(-self.x, self.y) + } +} + +impl> Add for Affine

{ + type Output = Projective

; + + fn add(self, other: T) -> Self::Output { + let mut copy = self.into_group(); + copy += other.borrow(); + copy + } +} + +impl Add> for Affine

{ + type Output = Projective

; + + fn add(self, other: Projective

) -> Projective

{ + other + self + } +} + +impl<'a, P: TECurveConfig> Add<&'a Projective

> for Affine

{ + type Output = Projective

; + + fn add(self, other: &'a Projective

) -> Projective

{ + *other + self + } +} + +impl> Sub for Affine

{ + type Output = Projective

; + + fn sub(self, other: T) -> Self::Output { + let mut copy = self.into_group(); + copy -= other.borrow(); + copy + } +} + +impl Sub> for Affine

{ + type Output = Projective

; + + fn sub(self, other: Projective

) -> Projective

{ + self + (-other) + } +} + +impl<'a, P: TECurveConfig> Sub<&'a Projective

> for Affine

{ + type Output = Projective

; + + fn sub(self, other: &'a Projective

) -> Projective

{ + self + (-*other) + } +} + +impl Default for Affine

{ + #[inline] + fn default() -> Self { + Self::zero() + } +} + +impl Distribution> for Standard { + /// Generates a uniformly random instance of the curve. + #[inline] + fn sample(&self, rng: &mut R) -> Affine

{ + loop { + let y = P::BaseField::rand(rng); + let greatest = rng.gen(); + + if let Some(p) = Affine::get_point_from_y_unchecked(y, greatest) { + return p.mul_by_cofactor(); + } + } + } +} + +impl> Mul for Affine

{ + type Output = Projective

; + + #[inline] + fn mul(self, other: T) -> Self::Output { + self.mul_bigint(other.borrow().into_bigint()) + } +} + +// The projective point X, Y, T, Z is represented in the affine +// coordinates as X/Z, Y/Z. +impl From> for Affine

{ + fn from(p: Projective

) -> Affine

{ + if p.is_zero() { + Affine::zero() + } else if p.z.is_one() { + // If Z is one, the point is already normalized. + Affine::new_unchecked(p.x, p.y) + } else { + // Z is nonzero, so it must have an inverse in a field. + let z_inv = p.z.inverse().unwrap(); + let x = p.x * &z_inv; + let y = p.y * &z_inv; + Affine::new_unchecked(x, y) + } + } +} +impl CanonicalSerialize for Affine

{ + #[inline] + fn serialize_with_mode( + &self, + writer: W, + compress: ark_serialize::Compress, + ) -> Result<(), SerializationError> { + P::serialize_with_mode(self, writer, compress) + } + + #[inline] + fn serialized_size(&self, compress: Compress) -> usize { + P::serialized_size(compress) + } +} + +impl Valid for Affine

{ + fn check(&self) -> Result<(), SerializationError> { + if self.is_on_curve() && self.is_in_correct_subgroup_assuming_on_curve() + { + Ok(()) + } else { + Err(SerializationError::InvalidData) + } + } +} + +impl CanonicalDeserialize for Affine

{ + fn deserialize_with_mode( + reader: R, + compress: Compress, + validate: Validate, + ) -> Result { + P::deserialize_with_mode(reader, compress, validate) + } +} + +impl ToConstraintField + for Affine +where + M::BaseField: ToConstraintField, +{ + #[inline] + fn to_field_elements(&self) -> Option> { + let mut x_fe = self.x.to_field_elements()?; + let y_fe = self.y.to_field_elements()?; + x_fe.extend_from_slice(&y_fe); + Some(x_fe) + } +} diff --git a/lib/crypto/src/curve/twisted_edwards/group.rs b/lib/crypto/src/curve/twisted_edwards/group.rs new file mode 100644 index 000000000..0383c6d7d --- /dev/null +++ b/lib/crypto/src/curve/twisted_edwards/group.rs @@ -0,0 +1,496 @@ +use educe::Educe; +use zeroize::Zeroize; + +use super::{Affine, MontCurveConfig, TECurveConfig}; +use crate::{ + scalar_mul::{variable_base::VariableBaseMSM, ScalarMul}, + AffineRepr, CurveGroup, PrimeGroup, +}; + +/// `Projective` implements Extended Twisted Edwards Coordinates +/// as described in [\[HKCD08\]](https://eprint.iacr.org/2008/522.pdf). +/// +/// This implementation uses the unified addition formulae from that paper (see +/// Section 3.1). +#[derive(Educe)] +#[educe(Copy, Clone, Eq(bound(P: TECurveConfig)), Debug)] +#[must_use] +pub struct Projective { + pub x: P::BaseField, + pub y: P::BaseField, + pub t: P::BaseField, + pub z: P::BaseField, +} + +impl PartialEq> for Projective

{ + fn eq(&self, other: &Affine

) -> bool { + *self == other.into_group() + } +} + +impl Display for Projective

{ + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + write!(f, "{}", Affine::from(*self)) + } +} + +impl PartialEq for Projective

{ + fn eq(&self, other: &Self) -> bool { + if self.is_zero() { + return other.is_zero(); + } + + if other.is_zero() { + return false; + } + + // x1/z1 == x2/z2 <==> x1 * z2 == x2 * z1 + (self.x * &other.z) == (other.x * &self.z) + && (self.y * &other.z) == (other.y * &self.z) + } +} + +impl Hash for Projective

{ + fn hash(&self, state: &mut H) { + self.into_affine().hash(state) + } +} + +impl Distribution> for Standard { + /// Generates a uniformly random instance of the curve. + #[inline] + fn sample(&self, rng: &mut R) -> Projective

{ + loop { + let y = P::BaseField::rand(rng); + let greatest = rng.gen(); + + if let Some(p) = Affine::get_point_from_y_unchecked(y, greatest) { + return p.mul_by_cofactor_to_group(); + } + } + } +} + +impl Default for Projective

{ + #[inline] + fn default() -> Self { + Self::zero() + } +} + +impl Projective

{ + /// Construct a new group element without checking whether the coordinates + /// specify a point in the subgroup. + pub const fn new_unchecked( + x: P::BaseField, + y: P::BaseField, + t: P::BaseField, + z: P::BaseField, + ) -> Self { + Self { x, y, t, z } + } + + /// Construct a new group element in a way while enforcing that points are + /// in the prime-order subgroup. + pub fn new( + x: P::BaseField, + y: P::BaseField, + t: P::BaseField, + z: P::BaseField, + ) -> Self { + let p = Self::new_unchecked(x, y, t, z).into_affine(); + assert!(p.is_on_curve()); + assert!(p.is_in_correct_subgroup_assuming_on_curve()); + p.into() + } +} +impl Zeroize for Projective

{ + // The phantom data does not contain element-specific data + // and thus does not need to be zeroized. + fn zeroize(&mut self) { + self.x.zeroize(); + self.y.zeroize(); + self.t.zeroize(); + self.z.zeroize(); + } +} + +impl Zero for Projective

{ + fn zero() -> Self { + Self::new_unchecked( + P::BaseField::zero(), + P::BaseField::one(), + P::BaseField::zero(), + P::BaseField::one(), + ) + } + + fn is_zero(&self) -> bool { + self.x.is_zero() + && self.y == self.z + && !self.y.is_zero() + && self.t.is_zero() + } +} + +impl AdditiveGroup for Projective

{ + type Scalar = P::ScalarField; + + const ZERO: Self = Self::new_unchecked( + P::BaseField::ZERO, + P::BaseField::ONE, + P::BaseField::ZERO, + P::BaseField::ONE, + ); + + fn double_in_place(&mut self) -> &mut Self { + // See "Twisted Edwards Curves Revisited" + // Huseyin Hisil, Kenneth Koon-Ho Wong, Gary Carter, and Ed Dawson + // 3.3 Doubling in E^e + // Source: https://www.hyperelliptic.org/EFD/g1p/data/twisted/extended/doubling/dbl-2008-hwcd + + // A = X1^2 + let a = self.x.square(); + // B = Y1^2 + let b = self.y.square(); + // C = 2 * Z1^2 + let c = self.z.square().double(); + // D = a * A + let d = P::mul_by_a(a); + // E = (X1 + Y1)^2 - A - B + let e = (self.x + &self.y).square() - &a - &b; + // G = D + B + let g = d + &b; + // F = G - C + let f = g - &c; + // H = D - B + let h = d - &b; + // X3 = E * F + self.x = e * &f; + // Y3 = G * H + self.y = g * &h; + // T3 = E * H + self.t = e * &h; + // Z3 = F * G + self.z = f * &g; + + self + } +} + +impl PrimeGroup for Projective

{ + type ScalarField = P::ScalarField; + + fn generator() -> Self { + Affine::generator().into() + } + + #[inline] + fn mul_bigint(&self, other: impl AsRef<[u64]>) -> Self { + P::mul_projective(self, other.as_ref()) + } +} + +impl CurveGroup for Projective

{ + type Affine = Affine

; + type BaseField = P::BaseField; + type Config = P; + type FullGroup = Affine

; + + fn normalize_batch(v: &[Self]) -> Vec { + // A projective curve element (x, y, t, z) is normalized + // to its affine representation, by the conversion + // (x, y, t, z) -> (x/z, y/z, t/z, 1) + // Batch normalizing N twisted edwards curve elements costs: + // 1 inversion + 6N field multiplications + // (batch inversion requires 3N multiplications + 1 inversion) + let mut z_s = v.iter().map(|g| g.z).collect::>(); + ark_ff::batch_inversion(&mut z_s); + + // Perform affine transformations + ark_std::cfg_iter!(v) + .zip(z_s) + .map(|(g, z)| match g.is_zero() { + true => Affine::zero(), + false => { + let x = g.x * &z; + let y = g.y * &z; + Affine::new_unchecked(x, y) + } + }) + .collect() + } +} + +impl Neg for Projective

{ + type Output = Self; + + fn neg(mut self) -> Self { + self.x = -self.x; + self.t = -self.t; + self + } +} + +impl>> AddAssign for Projective

{ + fn add_assign(&mut self, other: T) { + let other = other.borrow(); + // See "Twisted Edwards Curves Revisited" + // Huseyin Hisil, Kenneth Koon-Ho Wong, Gary Carter, and Ed Dawson + // 3.1 Unified Addition in E^e + // Source: https://www.hyperelliptic.org/EFD/g1p/data/twisted/extended/addition/madd-2008-hwcd + + // A = X1*X2 + let a = self.x * &other.x; + // B = Y1*Y2 + let b = self.y * &other.y; + // C = T1*d*T2 + let c = P::COEFF_D * &self.t * &other.x * &other.y; + + // D = Z1 + let d = self.z; + // E = (X1+Y1)*(X2+Y2)-A-B + let e = (self.x + &self.y) * &(other.x + &other.y) - &a - &b; + // F = D-C + let f = d - &c; + // G = D+C + let g = d + &c; + // H = B-a*A + let h = b - &P::mul_by_a(a); + // X3 = E*F + self.x = e * &f; + // Y3 = G*H + self.y = g * &h; + // T3 = E*H + self.t = e * &h; + // Z3 = F*G + self.z = f * &g; + } +} + +impl>> Add for Projective

{ + type Output = Self; + + fn add(mut self, other: T) -> Self { + let other = other.borrow(); + self += other; + self + } +} + +impl>> SubAssign for Projective

{ + fn sub_assign(&mut self, other: T) { + *self += -(*other.borrow()); + } +} + +impl>> Sub for Projective

{ + type Output = Self; + + fn sub(mut self, other: T) -> Self { + self -= other.borrow(); + self + } +} +ark_ff::impl_additive_ops_from_ref!(Projective, TECurveConfig); + +impl<'a, P: TECurveConfig> Add<&'a Self> for Projective

{ + type Output = Self; + + fn add(mut self, other: &'a Self) -> Self { + self += other; + self + } +} + +impl<'a, P: TECurveConfig> Sub<&'a Self> for Projective

{ + type Output = Self; + + fn sub(mut self, other: &'a Self) -> Self { + self -= other; + self + } +} + +impl<'a, P: TECurveConfig> AddAssign<&'a Self> for Projective

{ + fn add_assign(&mut self, other: &'a Self) { + // See "Twisted Edwards Curves Revisited" (https://eprint.iacr.org/2008/522.pdf) + // by Huseyin Hisil, Kenneth Koon-Ho Wong, Gary Carter, and Ed Dawson + // 3.1 Unified Addition in E^e + + // A = x1 * x2 + let a = self.x * &other.x; + + // B = y1 * y2 + let b = self.y * &other.y; + + // C = d * t1 * t2 + let c = P::COEFF_D * &self.t * &other.t; + + // D = z1 * z2 + let d = self.z * &other.z; + + // H = B - aA + let h = b - &P::mul_by_a(a); + + // E = (x1 + y1) * (x2 + y2) - A - B + let e = (self.x + &self.y) * &(other.x + &other.y) - &a - &b; + + // F = D - C + let f = d - &c; + + // G = D + C + let g = d + &c; + + // x3 = E * F + self.x = e * &f; + + // y3 = G * H + self.y = g * &h; + + // t3 = E * H + self.t = e * &h; + + // z3 = F * G + self.z = f * &g; + } +} + +impl<'a, P: TECurveConfig> SubAssign<&'a Self> for Projective

{ + fn sub_assign(&mut self, other: &'a Self) { + *self += -(*other); + } +} + +impl> MulAssign + for Projective

+{ + fn mul_assign(&mut self, other: T) { + *self = self.mul_bigint(other.borrow().into_bigint()) + } +} + +impl> Mul for Projective

{ + type Output = Self; + + #[inline] + fn mul(mut self, other: T) -> Self { + self *= other; + self + } +} + +impl>> ark_std::iter::Sum + for Projective

+{ + fn sum(iter: I) -> Self + where + I: Iterator, + { + iter.fold(Self::zero(), |acc, x| acc + x.borrow()) + } +} + +// The affine point (X, Y) is represented in the Extended Projective coordinates +// with Z = 1. +impl From> for Projective

{ + fn from(p: Affine

) -> Projective

{ + Self::new_unchecked(p.x, p.y, p.x * &p.y, P::BaseField::one()) + } +} + +#[derive(Educe)] +#[educe(Copy, Clone, PartialEq, Eq, Debug, Hash)] +pub struct MontgomeryAffine { + pub x: P::BaseField, + pub y: P::BaseField, +} + +impl Display for MontgomeryAffine

{ + fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + write!(f, "MontgomeryAffine(x={}, y={})", self.x, self.y) + } +} + +impl MontgomeryAffine

{ + pub fn new(x: P::BaseField, y: P::BaseField) -> Self { + Self { x, y } + } +} + +impl CanonicalSerialize for Projective

{ + #[allow(unused_qualifications)] + #[inline] + fn serialize_with_mode( + &self, + writer: W, + compress: Compress, + ) -> Result<(), SerializationError> { + let aff = Affine::from(*self); + P::serialize_with_mode(&aff, writer, compress) + } + + #[inline] + fn serialized_size(&self, compress: Compress) -> usize { + P::serialized_size(compress) + } +} + +impl Valid for Projective

{ + fn check(&self) -> Result<(), SerializationError> { + self.into_affine().check() + } + + fn batch_check<'a>( + batch: impl Iterator + Send, + ) -> Result<(), SerializationError> + where + Self: 'a, + { + let batch = batch.copied().collect::>(); + let batch = Self::normalize_batch(&batch); + Affine::batch_check(batch.iter()) + } +} + +impl CanonicalDeserialize for Projective

{ + #[allow(unused_qualifications)] + fn deserialize_with_mode( + reader: R, + compress: Compress, + validate: Validate, + ) -> Result { + let aff = P::deserialize_with_mode(reader, compress, validate)?; + Ok(aff.into()) + } +} + +impl ToConstraintField + for Projective +where + M::BaseField: ToConstraintField, +{ + #[inline] + fn to_field_elements(&self) -> Option> { + Affine::from(*self).to_field_elements() + } +} + +impl ScalarMul for Projective

{ + type MulBase = Affine

; + + const NEGATION_IS_CHEAP: bool = true; + + fn batch_convert_to_mul_base(bases: &[Self]) -> Vec { + Self::normalize_batch(bases) + } +} + +impl VariableBaseMSM for Projective

{ + fn msm( + bases: &[Self::MulBase], + bigints: &[Self::ScalarField], + ) -> Result { + P::msm(bases, bigints) + } +} diff --git a/lib/crypto/src/curve/twisted_edwards/mod.rs b/lib/crypto/src/curve/twisted_edwards/mod.rs new file mode 100644 index 000000000..f34109b41 --- /dev/null +++ b/lib/crypto/src/curve/twisted_edwards/mod.rs @@ -0,0 +1,183 @@ +use num_traits::Zero; + +use crate::{scalar_mul::variable_base::VariableBaseMSM, AffineRepr}; + +mod affine; +pub use affine::*; + +mod group; +pub use group::*; + +mod serialization_flags; +pub use serialization_flags::*; + +/// Constants and convenience functions that collectively define the [Twisted Edwards model](https://www.hyperelliptic.org/EFD/g1p/auto-twisted.html) +/// of the curve. +/// +/// In this model, the curve equation is `a * x² + y² = 1 + d * x² * y²`, for +/// constants `a` and `d`. +pub trait TECurveConfig: super::CurveConfig { + /// Coefficient `a` of the curve equation. + const COEFF_A: Self::BaseField; + /// Coefficient `d` of the curve equation. + const COEFF_D: Self::BaseField; + /// Generator of the prime-order subgroup. + const GENERATOR: Affine; + + /// Model parameters for the Montgomery curve that is birationally + /// equivalent to this curve. + type MontCurveConfig: MontCurveConfig; + + /// Helper method for computing `elem * Self::COEFF_A`. + /// + /// The default implementation should be overridden only if + /// the product can be computed faster than standard field multiplication + /// (eg: via doubling if `COEFF_A == 2`, or if `COEFF_A.is_zero()`). + #[inline(always)] + fn mul_by_a(elem: Self::BaseField) -> Self::BaseField { + elem * Self::COEFF_A + } + + /// Checks that the current point is in the prime order subgroup given + /// the point on the curve. + fn is_in_correct_subgroup_assuming_on_curve(item: &Affine) -> bool { + Self::mul_affine(item, Self::ScalarField::characteristic()).is_zero() + } + + /// Performs cofactor clearing. + /// The default method is simply to multiply by the cofactor. + /// For some curve families though, it is sufficient to multiply + /// by a smaller scalar. + fn clear_cofactor(item: &Affine) -> Affine { + item.mul_by_cofactor() + } + + /// Default implementation of group multiplication for projective + /// coordinates + fn mul_projective( + base: &Projective, + scalar: &[u64], + ) -> Projective { + let mut res = Projective::zero(); + for b in ark_ff::BitIteratorBE::without_leading_zeros(scalar) { + res.double_in_place(); + if b { + res += base; + } + } + + res + } + + /// Default implementation of group multiplication for affine + /// coordinates + fn mul_affine(base: &Affine, scalar: &[u64]) -> Projective { + let mut res = Projective::zero(); + for b in ark_ff::BitIteratorBE::without_leading_zeros(scalar) { + res.double_in_place(); + if b { + res += base + } + } + + res + } + + /// Default implementation for multi scalar multiplication + fn msm( + bases: &[Affine], + scalars: &[Self::ScalarField], + ) -> Result, usize> { + (bases.len() == scalars.len()) + .then(|| VariableBaseMSM::msm_unchecked(bases, scalars)) + .ok_or(bases.len().min(scalars.len())) + } + + /// If uncompressed, serializes both x and y coordinates. + /// If compressed, serializes y coordinate with a bit to encode whether x is + /// positive. + #[inline] + fn serialize_with_mode( + item: &Affine, + mut writer: W, + compress: ark_serialize::Compress, + ) -> Result<(), SerializationError> { + let flags = TEFlags::from_x_coordinate(item.x); + match compress { + Compress::Yes => item.y.serialize_with_flags(writer, flags), + Compress::No => { + item.x.serialize_uncompressed(&mut writer)?; + item.y.serialize_uncompressed(&mut writer) + } + } + } + + /// If `validate` is `Yes`, calls `check()` to make sure the element is + /// valid. + /// + /// Uses `Affine::get_xs_from_y_unchecked()` for the compressed version. + fn deserialize_with_mode( + mut reader: R, + compress: Compress, + validate: Validate, + ) -> Result, SerializationError> { + let (x, y) = match compress { + Compress::Yes => { + let (y, flags): (_, TEFlags) = + CanonicalDeserializeWithFlags::deserialize_with_flags( + reader, + )?; + let (x, neg_x) = Affine::::get_xs_from_y_unchecked(y) + .ok_or(SerializationError::InvalidData)?; + if flags.is_negative() { + (neg_x, y) + } else { + (x, y) + } + } + Compress::No => { + let x: Self::BaseField = + CanonicalDeserialize::deserialize_uncompressed( + &mut reader, + )?; + let y: Self::BaseField = + CanonicalDeserialize::deserialize_uncompressed( + &mut reader, + )?; + (x, y) + } + }; + let point = Affine::new_unchecked(x, y); + if let Validate::Yes = validate { + point.check()?; + } + Ok(point) + } + + #[inline] + fn serialized_size(compress: Compress) -> usize { + let zero = Self::BaseField::zero(); + match compress { + Compress::Yes => zero.serialized_size_with_flags::(), + Compress::No => zero.uncompressed_size() + zero.uncompressed_size(), + } + } +} + +/// Constants and convenience functions that collectively define the [Montgomery model](https://www.hyperelliptic.org/EFD/g1p/auto-montgom.html) +/// of the curve. +/// +/// In this model, the curve equation is `b * y² = x³ + a * x² + x`, for +/// constants `a` and `b`. +pub trait MontCurveConfig: super::CurveConfig { + /// Coefficient `a` of the curve equation. + const COEFF_A: Self::BaseField; + /// Coefficient `b` of the curve equation. + const COEFF_B: Self::BaseField; + + /// Model parameters for the Twisted Edwards curve that is birationally + /// equivalent to this curve. + type TECurveConfig: TECurveConfig; +} + +////////////////////////////////////////////////////////////////////////////// diff --git a/lib/crypto/src/curve/twisted_edwards/serialization_flags.rs b/lib/crypto/src/curve/twisted_edwards/serialization_flags.rs new file mode 100644 index 000000000..2da62b570 --- /dev/null +++ b/lib/crypto/src/curve/twisted_edwards/serialization_flags.rs @@ -0,0 +1,54 @@ +/// Flags to be encoded into the serialization. +/// The default flags (empty) should not change the binary representation. +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum TEFlags { + XIsPositive = 0, + XIsNegative = 1, +} + +impl TEFlags { + #[inline] + pub fn from_x_coordinate(x: impl Field) -> Self { + if x <= -x { + TEFlags::XIsPositive + } else { + TEFlags::XIsNegative + } + } + + #[inline] + pub fn is_negative(&self) -> bool { + matches!(*self, TEFlags::XIsNegative) + } +} + +impl Default for TEFlags { + #[inline] + fn default() -> Self { + // XIsPositive doesn't change the serialization + TEFlags::XIsPositive + } +} + +impl Flags for TEFlags { + const BIT_SIZE: usize = 1; + + #[inline] + fn u8_bitmask(&self) -> u8 { + let mut mask = 0; + if let Self::XIsNegative = self { + mask |= 1 << 7; + } + mask + } + + #[inline] + fn from_u8(value: u8) -> Option { + let x_sign = (value >> 7) & 1 == 1; + if x_sign { + Some(Self::XIsNegative) + } else { + Some(Self::XIsPositive) + } + } +} diff --git a/lib/crypto/src/lib.rs b/lib/crypto/src/lib.rs index 5474da175..e22b1b372 100644 --- a/lib/crypto/src/lib.rs +++ b/lib/crypto/src/lib.rs @@ -37,5 +37,6 @@ pub mod poseidon2; pub use keccak::KeccakBuilder; +pub mod curve; #[cfg(all(test, feature = "std"))] mod test_helpers; From 925c94decc77165f101630d835e1ab967175a6b7 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Thu, 13 Feb 2025 17:22:54 +0400 Subject: [PATCH 02/24] ++ --- lib/crypto/src/curve/mod.rs | 199 ++++++++++- lib/crypto/src/curve/scalar_mul/glv.rs | 171 ++++++++++ lib/crypto/src/curve/scalar_mul/mod.rs | 257 ++++++++++++++ .../src/curve/scalar_mul/variable_base/mod.rs | 315 ++++++++++++++++++ .../variable_base/stream_pippenger.rs | 132 ++++++++ lib/crypto/src/curve/scalar_mul/wnaf.rs | 85 +++++ .../src/curve/short_weierstrass/affine.rs | 84 +---- .../src/curve/short_weierstrass/group.rs | 96 ++---- lib/crypto/src/curve/short_weierstrass/mod.rs | 117 +------ .../short_weierstrass/serialization_flags.rs | 79 ----- .../src/curve/twisted_edwards/affine.rs | 80 +---- lib/crypto/src/curve/twisted_edwards/group.rs | 77 +---- lib/crypto/src/curve/twisted_edwards/mod.rs | 78 +---- .../twisted_edwards/serialization_flags.rs | 54 --- lib/crypto/src/lib.rs | 5 + 15 files changed, 1227 insertions(+), 602 deletions(-) create mode 100644 lib/crypto/src/curve/scalar_mul/glv.rs create mode 100644 lib/crypto/src/curve/scalar_mul/mod.rs create mode 100644 lib/crypto/src/curve/scalar_mul/variable_base/mod.rs create mode 100644 lib/crypto/src/curve/scalar_mul/variable_base/stream_pippenger.rs create mode 100644 lib/crypto/src/curve/scalar_mul/wnaf.rs delete mode 100644 lib/crypto/src/curve/short_weierstrass/serialization_flags.rs delete mode 100644 lib/crypto/src/curve/twisted_edwards/serialization_flags.rs diff --git a/lib/crypto/src/curve/mod.rs b/lib/crypto/src/curve/mod.rs index fe93affd7..07666e6e9 100644 --- a/lib/crypto/src/curve/mod.rs +++ b/lib/crypto/src/curve/mod.rs @@ -1,7 +1,16 @@ +use core::fmt::{Debug, Display}; +use std::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign}; + use num_traits::Zero; +use zeroize::Zeroize; -use crate::field::{prime::PrimeField, Field}; +use crate::{ + curve::scalar_mul::{variable_base::VariableBaseMSM, ScalarMul}, + field::{group::AdditiveGroup, prime::PrimeField, Field}, + hash::Hash, +}; +pub mod scalar_mul; pub mod short_weierstrass; pub mod twisted_edwards; @@ -30,3 +39,191 @@ pub trait CurveConfig: Send + Sync + Sized + 'static { && Self::COFACTOR.iter().skip(1).all(Zero::is_zero) } } + +/// Represents (elements of) a group of prime order `r`. +pub trait PrimeGroup: AdditiveGroup { + /// The scalar field `F_r`, where `r` is the order of this group. + type ScalarField: PrimeField; + + /// Returns a fixed generator of this group. + #[must_use] + fn generator() -> Self; + + /// Performs scalar multiplication of this element. + fn mul_bigint(&self, other: impl AsRef<[u64]>) -> Self; + + /// Computes `other * self`, where `other` is a *big-endian* + /// bit representation of some integer. + fn mul_bits_be(&self, other: impl Iterator) -> Self { + let mut res = Self::zero(); + for b in other.skip_while(|b| !b) { + // skip leading zeros + res.double_in_place(); + if b { + res += self; + } + } + res + } +} + +/// An opaque representation of an elliptic curve group element that is suitable +/// for efficient group arithmetic. +/// +/// The point is guaranteed to be in the correct prime order subgroup. +pub trait CurveGroup: + PrimeGroup + + Add + + AddAssign + + Sub + + SubAssign + + VariableBaseMSM + + ScalarMul + + From + + Into + + core::iter::Sum + + for<'a> core::iter::Sum<&'a Self::Affine> +{ + type Config: CurveConfig< + ScalarField = Self::ScalarField, + BaseField = Self::BaseField, + >; + /// The field over which this curve is defined. + type BaseField: Field; + /// The affine representation of this element. + type Affine: AffineRepr< + Config = Self::Config, + Group = Self, + ScalarField = Self::ScalarField, + BaseField = Self::BaseField, + > + From + + Into; + + /// Type representing an element of the full elliptic curve group, not just + /// the prime order subgroup. + type FullGroup; + + /// Normalizes a slice of group elements into affine. + #[must_use] + fn normalize_batch(v: &[Self]) -> Vec; + + /// Converts `self` into the affine representation. + fn into_affine(self) -> Self::Affine { + self.into() + } +} + +/// The canonical representation of an elliptic curve group element. +/// This should represent the affine coordinates of the point corresponding +/// to this group element. +/// +/// The point is guaranteed to be in the correct prime order subgroup. +pub trait AffineRepr: + Eq + + 'static + + Sized + + Copy + + Clone + + Default + + Send + + Sync + + Hash + + Debug + + Display + + Zeroize + + Neg + + From<::Group> + + Into<::Group> + + Add + + for<'a> Add<&'a Self, Output = Self::Group> + + Add + + for<'a> Add<&'a Self::Group, Output = Self::Group> + + Sub + + for<'a> Sub<&'a Self, Output = Self::Group> + + Sub + + for<'a> Sub<&'a Self::Group, Output = Self::Group> + + Mul + + for<'a> Mul<&'a Self::ScalarField, Output = Self::Group> +{ + type Config: CurveConfig< + ScalarField = Self::ScalarField, + BaseField = Self::BaseField, + >; + type ScalarField: PrimeField + + Into<::BigInt>; + /// The finite field over which this curve is defined. + type BaseField: Field; + + /// The projective representation of points on this curve. + type Group: CurveGroup< + Config = Self::Config, + Affine = Self, + ScalarField = Self::ScalarField, + BaseField = Self::BaseField, + > + From + + Into + + MulAssign; // needed due to https://github.com/rust-lang/rust/issues/69640 + + /// Returns the x and y coordinates of this affine point. + fn xy(&self) -> Option<(Self::BaseField, Self::BaseField)>; + + /// Returns the x coordinate of this affine point. + fn x(&self) -> Option { + self.xy().map(|(x, _)| x) + } + + /// Returns the y coordinate of this affine point. + fn y(&self) -> Option { + self.xy().map(|(_, y)| y) + } + + /// Returns the point at infinity. + fn zero() -> Self; + + /// Is `self` the point at infinity? + fn is_zero(&self) -> bool { + self.xy().is_none() + } + + /// Returns a fixed generator of unknown exponent. + #[must_use] + fn generator() -> Self; + + /// Converts self into the projective representation. + fn into_group(self) -> Self::Group { + self.into() + } + + /// Returns a group element if the set of bytes forms a valid group element, + /// otherwise returns None. This function is primarily intended for sampling + /// random group elements from a hash-function or RNG output. + fn from_random_bytes(bytes: &[u8]) -> Option; + + /// Performs scalar multiplication of this element with mixed addition. + #[must_use] + fn mul_bigint(&self, by: impl AsRef<[u64]>) -> Self::Group; + + /// Performs cofactor clearing. + /// The default method is simply to multiply by the cofactor. + /// For some curve families more efficient methods exist. + #[must_use] + fn clear_cofactor(&self) -> Self; + + /// Multiplies this element by the cofactor and output the + /// resulting projective element. + #[must_use] + fn mul_by_cofactor_to_group(&self) -> Self::Group; + + /// Multiplies this element by the cofactor. + #[must_use] + fn mul_by_cofactor(&self) -> Self { + self.mul_by_cofactor_to_group().into() + } + + /// Multiplies this element by the inverse of the cofactor in + /// `Self::ScalarField`. + #[must_use] + fn mul_by_cofactor_inv(&self) -> Self { + self.mul_bigint(Self::Config::COFACTOR_INV.into_bigint()).into() + } +} diff --git a/lib/crypto/src/curve/scalar_mul/glv.rs b/lib/crypto/src/curve/scalar_mul/glv.rs new file mode 100644 index 000000000..b1a4d7f1a --- /dev/null +++ b/lib/crypto/src/curve/scalar_mul/glv.rs @@ -0,0 +1,171 @@ +use ark_ff::{PrimeField, Zero}; +use ark_std::ops::Neg; +use num_bigint::{BigInt, BigUint, Sign}; +use num_integer::Integer; +use num_traits::{One, Signed}; + +use crate::{ + short_weierstrass::{Affine, Projective, SWCurveConfig}, + AdditiveGroup, CurveGroup, +}; + +/// The GLV parameters for computing the endomorphism and scalar decomposition. +pub trait GLVConfig: Send + Sync + 'static + SWCurveConfig { + /// Constant used to calculate `phi(G) := lambda*G`. + /// + /// The coefficients of the endomorphism + const ENDO_COEFFS: &'static [Self::BaseField]; + + /// Constant used to calculate `phi(G) := lambda*G`. + /// + /// The eigenvalue corresponding to the endomorphism. + const LAMBDA: Self::ScalarField; + + /// A 4-element vector representing a 2x2 matrix of coefficients the for + /// scalar decomposition, s.t. k-th entry in the vector is at col i, row j + /// in the matrix, with ij = BE binary decomposition of k. The entries + /// are the LLL-reduced bases. The determinant of this matrix must equal + /// `ScalarField::characteristic()`. + const SCALAR_DECOMP_COEFFS: [( + bool, + ::BigInt, + ); 4]; + + /// Decomposes a scalar s into k1, k2, s.t. s = k1 + lambda k2, + fn scalar_decomposition( + k: Self::ScalarField, + ) -> ((bool, Self::ScalarField), (bool, Self::ScalarField)) { + let scalar: BigInt = k.into_bigint().into().into(); + + let [n11, n12, n21, n22] = Self::SCALAR_DECOMP_COEFFS.map(|x| { + let sign = if x.0 { Sign::Plus } else { Sign::Minus }; + BigInt::from_biguint(sign, x.1.into()) + }); + + let r = BigInt::from(Self::ScalarField::MODULUS.into()); + + // beta = vector([k,0]) * self.curve.N_inv + // The inverse of N is 1/r * Matrix([[n22, -n12], [-n21, n11]]). + // so β = (k*n22, -k*n12)/r + + let beta_1 = { + let (mut div, rem) = (&scalar * &n22).div_rem(&r); + if (&rem + &rem) > r { + div += BigInt::one(); + } + div + }; + let beta_2 = { + let (mut div, rem) = (&scalar * &n12.clone().neg()).div_rem(&r); + if (&rem + &rem) > r { + div += BigInt::one(); + } + div + }; + + // b = vector([int(beta[0]), int(beta[1])]) * self.curve.N + // b = (β1N11 + β2N21, β1N12 + β2N22) with the signs! + // = (b11 + b12 , b21 + b22) with the signs! + + // b1 + let b11 = &beta_1 * &n11; + let b12 = &beta_2 * &n21; + let b1 = b11 + b12; + + // b2 + let b21 = &beta_1 * &n12; + let b22 = &beta_2 * &n22; + let b2 = b21 + b22; + + let k1 = &scalar - b1; + let k1_abs = BigUint::try_from(k1.abs()).unwrap(); + + // k2 + let k2 = -b2; + let k2_abs = BigUint::try_from(k2.abs()).unwrap(); + + ( + (k1.sign() == Sign::Plus, k1_abs.into()), + (k2.sign() == Sign::Plus, k2_abs.into()), + ) + } + + fn endomorphism(p: &Projective) -> Projective; + + fn endomorphism_affine(p: &Affine) -> Affine; + + fn glv_mul_projective( + p: Projective, + k: Self::ScalarField, + ) -> Projective { + let ((sgn_k1, k1), (sgn_k2, k2)) = Self::scalar_decomposition(k); + + let mut b1 = p; + let mut b2 = Self::endomorphism(&p); + + if !sgn_k1 { + b1 = -b1; + } + if !sgn_k2 { + b2 = -b2; + } + + let b1b2 = b1 + b2; + + let iter_k1 = ark_ff::BitIteratorBE::new(k1.into_bigint()); + let iter_k2 = ark_ff::BitIteratorBE::new(k2.into_bigint()); + + let mut res = Projective::zero(); + let mut skip_zeros = true; + for pair in iter_k1.zip(iter_k2) { + if skip_zeros && pair == (false, false) { + skip_zeros = false; + continue; + } + res.double_in_place(); + match pair { + (true, false) => res += b1, + (false, true) => res += b2, + (true, true) => res += b1b2, + (false, false) => {} + } + } + res + } + + fn glv_mul_affine(p: Affine, k: Self::ScalarField) -> Affine { + let ((sgn_k1, k1), (sgn_k2, k2)) = Self::scalar_decomposition(k); + + let mut b1 = p; + let mut b2 = Self::endomorphism_affine(&p); + + if !sgn_k1 { + b1 = -b1; + } + if !sgn_k2 { + b2 = -b2; + } + + let b1b2 = b1 + b2; + + let iter_k1 = ark_ff::BitIteratorBE::new(k1.into_bigint()); + let iter_k2 = ark_ff::BitIteratorBE::new(k2.into_bigint()); + + let mut res = Projective::zero(); + let mut skip_zeros = true; + for pair in iter_k1.zip(iter_k2) { + if skip_zeros && pair == (false, false) { + skip_zeros = false; + continue; + } + res.double_in_place(); + match pair { + (true, false) => res += b1, + (false, true) => res += b2, + (true, true) => res += b1b2, + (false, false) => {} + } + } + res.into_affine() + } +} diff --git a/lib/crypto/src/curve/scalar_mul/mod.rs b/lib/crypto/src/curve/scalar_mul/mod.rs new file mode 100644 index 000000000..95546fa19 --- /dev/null +++ b/lib/crypto/src/curve/scalar_mul/mod.rs @@ -0,0 +1,257 @@ +pub mod glv; +pub mod wnaf; + +pub mod variable_base; + +use ark_ff::{AdditiveGroup, BigInteger, PrimeField, Zero}; +use ark_std::{ + cfg_iter, cfg_iter_mut, + ops::{Add, AddAssign, Mul, Neg, Sub, SubAssign}, + vec::*, +}; +#[cfg(feature = "parallel")] +use rayon::prelude::*; + +use crate::{ + short_weierstrass::{Affine, Projective, SWCurveConfig}, + PrimeGroup, +}; + +/// The result of this function is only approximately `ln(a)` +/// [`Explanation of usage`] +/// +/// [`Explanation of usage`]: https://github.com/scipr-lab/zexe/issues/79#issue-556220473 +fn ln_without_floats(a: usize) -> usize { + // log2(a) * ln(2) + (ark_std::log2(a) * 69 / 100) as usize +} + +/// Standard double-and-add method for multiplication by a scalar. +#[inline(always)] +pub fn sw_double_and_add_affine( + base: &Affine

, + scalar: impl AsRef<[u64]>, +) -> Projective

{ + let mut res = Projective::zero(); + for b in ark_ff::BitIteratorBE::without_leading_zeros(scalar) { + res.double_in_place(); + if b { + res += base + } + } + + res +} + +/// Standard double-and-add method for multiplication by a scalar. +#[inline(always)] +pub fn sw_double_and_add_projective( + base: &Projective

, + scalar: impl AsRef<[u64]>, +) -> Projective

{ + let mut res = Projective::zero(); + for b in ark_ff::BitIteratorBE::without_leading_zeros(scalar) { + res.double_in_place(); + if b { + res += base + } + } + + res +} + +pub trait ScalarMul: + PrimeGroup + + Add + + AddAssign + + for<'a> Add<&'a Self::MulBase, Output = Self> + + for<'a> AddAssign<&'a Self::MulBase> + + Sub + + SubAssign + + for<'a> Sub<&'a Self::MulBase, Output = Self> + + for<'a> SubAssign<&'a Self::MulBase> + + From +{ + type MulBase: Send + + Sync + + Copy + + Eq + + core::hash::Hash + + Mul + + for<'a> Mul<&'a Self::ScalarField, Output = Self> + + Neg + + From; + + const NEGATION_IS_CHEAP: bool; + + fn batch_convert_to_mul_base(bases: &[Self]) -> Vec; + + /// Compute the vector v[0].G, v[1].G, ..., v[n-1].G, given: + /// - an element `g` + /// - a list `v` of n scalars + /// + /// # Example + /// ``` + /// use ark_std::{One, UniformRand}; + /// use ark_ec::pairing::Pairing; + /// use ark_test_curves::bls12_381::G1Projective as G; + /// use ark_test_curves::bls12_381::Fr; + /// use ark_ec::scalar_mul::ScalarMul; + /// + /// // Compute G, s.G, s^2.G, ..., s^9.G + /// let mut rng = ark_std::test_rng(); + /// let max_degree = 10; + /// let s = Fr::rand(&mut rng); + /// let g = G::rand(&mut rng); + /// let mut powers_of_s = vec![Fr::one()]; + /// let mut cur = s; + /// for _ in 0..max_degree { + /// powers_of_s.push(cur); + /// cur *= &s; + /// } + /// let powers_of_g = g.batch_mul(&powers_of_s); + /// let naive_powers_of_g: Vec = powers_of_s.iter().map(|e| g * e).collect(); + /// assert_eq!(powers_of_g, naive_powers_of_g); + /// ``` + fn batch_mul(self, v: &[Self::ScalarField]) -> Vec { + let table = BatchMulPreprocessing::new(self, v.len()); + Self::batch_mul_with_preprocessing(&table, v) + } + + /// Compute the vector v[0].G, v[1].G, ..., v[n-1].G, given: + /// - an element `g` + /// - a list `v` of n scalars + /// + /// This method allows the user to provide a precomputed table of multiples + /// of `g`. A more ergonomic way to call this would be to use + /// [`BatchMulPreprocessing::batch_mul`]. + /// + /// # Example + /// ``` + /// use ark_std::{One, UniformRand}; + /// use ark_ec::pairing::Pairing; + /// use ark_test_curves::bls12_381::G1Projective as G; + /// use ark_test_curves::bls12_381::Fr; + /// use ark_ec::scalar_mul::*; + /// + /// // Compute G, s.G, s^2.G, ..., s^9.G + /// let mut rng = ark_std::test_rng(); + /// let max_degree = 10; + /// let s = Fr::rand(&mut rng); + /// let g = G::rand(&mut rng); + /// let mut powers_of_s = vec![Fr::one()]; + /// let mut cur = s; + /// for _ in 0..max_degree { + /// powers_of_s.push(cur); + /// cur *= &s; + /// } + /// let table = BatchMulPreprocessing::new(g, powers_of_s.len()); + /// let powers_of_g = G::batch_mul_with_preprocessing(&table, &powers_of_s); + /// let powers_of_g_2 = table.batch_mul(&powers_of_s); + /// let naive_powers_of_g: Vec = powers_of_s.iter().map(|e| g * e).collect(); + /// assert_eq!(powers_of_g, naive_powers_of_g); + /// assert_eq!(powers_of_g_2, naive_powers_of_g); + /// ``` + fn batch_mul_with_preprocessing( + table: &BatchMulPreprocessing, + v: &[Self::ScalarField], + ) -> Vec { + table.batch_mul(v) + } +} + +/// Preprocessing used internally for batch scalar multiplication via +/// [`ScalarMul::batch_mul`]. +/// - `window` is the window size used for the precomputation +/// - `max_scalar_size` is the maximum size of the scalars that will be +/// multiplied +/// - `table` is the precomputed table of multiples of `base` +pub struct BatchMulPreprocessing { + pub window: usize, + pub max_scalar_size: usize, + pub table: Vec>, +} + +impl BatchMulPreprocessing { + pub fn new(base: T, num_scalars: usize) -> Self { + let scalar_size = T::ScalarField::MODULUS_BIT_SIZE as usize; + Self::with_num_scalars_and_scalar_size(base, num_scalars, scalar_size) + } + + pub fn with_num_scalars_and_scalar_size( + base: T, + num_scalars: usize, + max_scalar_size: usize, + ) -> Self { + let window = Self::compute_window_size(num_scalars); + let in_window = 1 << window; + let outerc = max_scalar_size.div_ceil(window); + let last_in_window = 1 << (max_scalar_size - (outerc - 1) * window); + + let mut multiples_of_g = vec![vec![T::zero(); in_window]; outerc]; + + let mut g_outer = base; + let mut g_outers = Vec::with_capacity(outerc); + for _ in 0..outerc { + g_outers.push(g_outer); + for _ in 0..window { + g_outer.double_in_place(); + } + } + cfg_iter_mut!(multiples_of_g) + .enumerate() + .take(outerc) + .zip(g_outers) + .for_each(|((outer, multiples_of_g), g_outer)| { + let cur_in_window = if outer == outerc - 1 { + last_in_window + } else { + in_window + }; + + let mut g_inner = T::zero(); + for inner in multiples_of_g.iter_mut().take(cur_in_window) { + *inner = g_inner; + g_inner += &g_outer; + } + }); + let table = cfg_iter!(multiples_of_g) + .map(|s| T::batch_convert_to_mul_base(s)) + .collect(); + Self { window, max_scalar_size, table } + } + + pub fn compute_window_size(num_scalars: usize) -> usize { + if num_scalars < 32 { + 3 + } else { + ln_without_floats(num_scalars) + } + } + + pub fn batch_mul(&self, v: &[T::ScalarField]) -> Vec { + let result: Vec<_> = + cfg_iter!(v).map(|e| self.windowed_mul(e)).collect(); + T::batch_convert_to_mul_base(&result) + } + + fn windowed_mul(&self, scalar: &T::ScalarField) -> T { + let outerc = self.max_scalar_size.div_ceil(self.window); + let modulus_size = T::ScalarField::MODULUS_BIT_SIZE as usize; + let scalar_val = scalar.into_bigint().to_bits_le(); + + let mut res = T::from(self.table[0][0]); + for outer in 0..outerc { + let mut inner = 0usize; + for i in 0..self.window { + if outer * self.window + i < modulus_size + && scalar_val[outer * self.window + i] + { + inner |= 1 << i; + } + } + res += &self.table[outer][inner]; + } + res + } +} diff --git a/lib/crypto/src/curve/scalar_mul/variable_base/mod.rs b/lib/crypto/src/curve/scalar_mul/variable_base/mod.rs new file mode 100644 index 000000000..5f22e8806 --- /dev/null +++ b/lib/crypto/src/curve/scalar_mul/variable_base/mod.rs @@ -0,0 +1,315 @@ +use ark_ff::prelude::*; +use ark_std::{borrow::Borrow, cfg_into_iter, iterable::Iterable, vec::*}; +#[cfg(feature = "parallel")] +use rayon::prelude::*; + +pub mod stream_pippenger; +pub use stream_pippenger::*; + +use super::ScalarMul; + +#[cfg(all( + target_has_atomic = "8", + target_has_atomic = "16", + target_has_atomic = "32", + target_has_atomic = "64", + target_has_atomic = "ptr" +))] +type DefaultHasher = ahash::AHasher; + +#[cfg(not(all( + target_has_atomic = "8", + target_has_atomic = "16", + target_has_atomic = "32", + target_has_atomic = "64", + target_has_atomic = "ptr" +)))] +type DefaultHasher = fnv::FnvHasher; + +pub trait VariableBaseMSM: ScalarMul { + /// Computes an inner product between the [`PrimeField`] elements in + /// `scalars` and the corresponding group elements in `bases`. + /// + /// If the elements have different length, it will chop the slices to the + /// shortest length between `scalars.len()` and `bases.len()`. + /// + /// Reference: [`VariableBaseMSM::msm`] + fn msm_unchecked( + bases: &[Self::MulBase], + scalars: &[Self::ScalarField], + ) -> Self { + let bigints = cfg_into_iter!(scalars) + .map(|s| s.into_bigint()) + .collect::>(); + Self::msm_bigint(bases, &bigints) + } + + /// Performs multi-scalar multiplication. + /// + /// # Warning + /// + /// This method checks that `bases` and `scalars` have the same length. + /// If they are unequal, it returns an error containing + /// the shortest length over which the MSM can be performed. + fn msm( + bases: &[Self::MulBase], + scalars: &[Self::ScalarField], + ) -> Result { + (bases.len() == scalars.len()) + .then(|| Self::msm_unchecked(bases, scalars)) + .ok_or(bases.len().min(scalars.len())) + } + + /// Optimized implementation of multi-scalar multiplication. + fn msm_bigint( + bases: &[Self::MulBase], + bigints: &[::BigInt], + ) -> Self { + if Self::NEGATION_IS_CHEAP { + msm_bigint_wnaf(bases, bigints) + } else { + msm_bigint(bases, bigints) + } + } + + /// Streaming multi-scalar multiplication algorithm with hard-coded chunk + /// size. + fn msm_chunks(bases_stream: &J, scalars_stream: &I) -> Self + where + I: Iterable + ?Sized, + I::Item: Borrow, + J: Iterable, + J::Item: Borrow, + { + assert!(scalars_stream.len() <= bases_stream.len()); + + // remove offset + let bases_init = bases_stream.iter(); + let mut scalars = scalars_stream.iter(); + + // align the streams + // TODO: change `skip` to `advance_by` once rust-lang/rust#7774 is + // fixed. See + let mut bases = + bases_init.skip(bases_stream.len() - scalars_stream.len()); + let step: usize = 1 << 20; + let mut result = Self::zero(); + for _ in 0..scalars_stream.len().div_ceil(step) { + let bases_step = (&mut bases) + .take(step) + .map(|b| *b.borrow()) + .collect::>(); + let scalars_step = (&mut scalars) + .take(step) + .map(|s| s.borrow().into_bigint()) + .collect::>(); + result += Self::msm_bigint( + bases_step.as_slice(), + scalars_step.as_slice(), + ); + } + result + } +} + +// Compute msm using windowed non-adjacent form +fn msm_bigint_wnaf( + bases: &[V::MulBase], + bigints: &[::BigInt], +) -> V { + let size = ark_std::cmp::min(bases.len(), bigints.len()); + let scalars = &bigints[..size]; + let bases = &bases[..size]; + + let c = if size < 32 { 3 } else { super::ln_without_floats(size) + 2 }; + + let num_bits = V::ScalarField::MODULUS_BIT_SIZE as usize; + let digits_count = num_bits.div_ceil(c); + #[cfg(feature = "parallel")] + let scalar_digits = scalars + .into_par_iter() + .flat_map_iter(|s| make_digits(s, c, num_bits)) + .collect::>(); + #[cfg(not(feature = "parallel"))] + let scalar_digits = scalars + .iter() + .flat_map(|s| make_digits(s, c, num_bits)) + .collect::>(); + let zero = V::zero(); + let window_sums: Vec<_> = ark_std::cfg_into_iter!(0..digits_count) + .map(|i| { + let mut buckets = vec![zero; 1 << c]; + for (digits, base) in scalar_digits.chunks(digits_count).zip(bases) + { + use ark_std::cmp::Ordering; + // digits is the digits thing of the first scalar? + let scalar = digits[i]; + match 0.cmp(&scalar) { + Ordering::Less => buckets[(scalar - 1) as usize] += base, + Ordering::Greater => { + buckets[(-scalar - 1) as usize] -= base + } + Ordering::Equal => (), + } + } + + let mut running_sum = V::zero(); + let mut res = V::zero(); + buckets.into_iter().rev().for_each(|b| { + running_sum += &b; + res += &running_sum; + }); + res + }) + .collect(); + + // We store the sum for the lowest window. + let lowest = *window_sums.first().unwrap(); + + // We're traversing windows from high to low. + lowest + + &window_sums[1..].iter().rev().fold(zero, |mut total, sum_i| { + total += sum_i; + for _ in 0..c { + total.double_in_place(); + } + total + }) +} + +/// Optimized implementation of multi-scalar multiplication. +fn msm_bigint( + bases: &[V::MulBase], + bigints: &[::BigInt], +) -> V { + let size = ark_std::cmp::min(bases.len(), bigints.len()); + let scalars = &bigints[..size]; + let bases = &bases[..size]; + let scalars_and_bases_iter = + scalars.iter().zip(bases).filter(|(s, _)| !s.is_zero()); + + let c = if size < 32 { 3 } else { super::ln_without_floats(size) + 2 }; + + let num_bits = V::ScalarField::MODULUS_BIT_SIZE as usize; + let one = V::ScalarField::one().into_bigint(); + + let zero = V::zero(); + let window_starts: Vec<_> = (0..num_bits).step_by(c).collect(); + + // Each window is of size `c`. + // We divide up the bits 0..num_bits into windows of size `c`, and + // in parallel process each such window. + let window_sums: Vec<_> = ark_std::cfg_into_iter!(window_starts) + .map(|w_start| { + let mut res = zero; + // We don't need the "zero" bucket, so we only have 2^c - 1 buckets. + let mut buckets = vec![zero; (1 << c) - 1]; + // This clone is cheap, because the iterator contains just a + // pointer and an index into the original vectors. + scalars_and_bases_iter.clone().for_each(|(&scalar, base)| { + if scalar == one { + // We only process unit scalars once in the first window. + if w_start == 0 { + res += base; + } + } else { + let mut scalar = scalar; + + // We right-shift by w_start, thus getting rid of the + // lower bits. + scalar >>= w_start as u32; + + // We mod the remaining bits by 2^{window size}, thus taking + // `c` bits. + let scalar = scalar.as_ref()[0] % (1 << c); + + // If the scalar is non-zero, we update the corresponding + // bucket. + // (Recall that `buckets` doesn't have a zero bucket.) + if scalar != 0 { + buckets[(scalar - 1) as usize] += base; + } + } + }); + + // Compute sum_{i in 0..num_buckets} (sum_{j in i..num_buckets} + // bucket[j]) This is computed below for b buckets, + // using 2b curve additions. + // + // We could first normalize `buckets` and then use mixed-addition + // here, but that's slower for the kinds of groups we care about + // (Short Weierstrass curves and Twisted Edwards curves). + // In the case of Short Weierstrass curves, + // mixed addition saves ~4 field multiplications per addition. + // However normalization (with the inversion batched) takes ~6 + // field multiplications per element, + // hence batch normalization is a slowdown. + + // `running_sum` = sum_{j in i..num_buckets} bucket[j], + // where we iterate backward from i = num_buckets to 0. + let mut running_sum = V::zero(); + buckets.into_iter().rev().for_each(|b| { + running_sum += &b; + res += &running_sum; + }); + res + }) + .collect(); + + // We store the sum for the lowest window. + let lowest = *window_sums.first().unwrap(); + + // We're traversing windows from high to low. + lowest + + &window_sums[1..].iter().rev().fold(zero, |mut total, sum_i| { + total += sum_i; + for _ in 0..c { + total.double_in_place(); + } + total + }) +} + +// From: https://github.com/arkworks-rs/gemini/blob/main/src/kzg/msm/variable_base.rs#L20 +fn make_digits( + a: &impl BigInteger, + w: usize, + num_bits: usize, +) -> impl Iterator + '_ { + let scalar = a.as_ref(); + let radix: u64 = 1 << w; + let window_mask: u64 = radix - 1; + + let mut carry = 0u64; + let num_bits = if num_bits == 0 { a.num_bits() as usize } else { num_bits }; + let digits_count = num_bits.div_ceil(w); + + (0..digits_count).map(move |i| { + // Construct a buffer of bits of the scalar, starting at `bit_offset`. + let bit_offset = i * w; + let u64_idx = bit_offset / 64; + let bit_idx = bit_offset % 64; + // Read the bits from the scalar + let bit_buf = if bit_idx < 64 - w || u64_idx == scalar.len() - 1 { + // This window's bits are contained in a single u64, + // or it's the last u64 anyway. + scalar[u64_idx] >> bit_idx + } else { + // Combine the current u64's bits with the bits from the next u64 + (scalar[u64_idx] >> bit_idx) + | (scalar[1 + u64_idx] << (64 - bit_idx)) + }; + + // Read the actual coefficient value from the window + let coef = carry + (bit_buf & window_mask); // coef = [0, 2^r) + + // Recenter coefficients from [0,2^w) to [-2^w/2, 2^w/2) + carry = (coef + radix / 2) >> w; + let mut digit = (coef as i64) - (carry << w) as i64; + + if i == digits_count - 1 { + digit += (carry << w) as i64; + } + digit + }) +} diff --git a/lib/crypto/src/curve/scalar_mul/variable_base/stream_pippenger.rs b/lib/crypto/src/curve/scalar_mul/variable_base/stream_pippenger.rs new file mode 100644 index 000000000..f1d833713 --- /dev/null +++ b/lib/crypto/src/curve/scalar_mul/variable_base/stream_pippenger.rs @@ -0,0 +1,132 @@ +//! A space-efficient implementation of Pippenger's algorithm. +use ark_ff::{PrimeField, Zero}; +use ark_std::{borrow::Borrow, vec::*}; +use hashbrown::HashMap; + +use super::{DefaultHasher, VariableBaseMSM}; + +/// Struct for the chunked Pippenger algorithm. +pub struct ChunkedPippenger { + scalars_buffer: Vec<::BigInt>, + bases_buffer: Vec, + result: G, + buf_size: usize, +} + +impl ChunkedPippenger { + /// Initialize a chunked Pippenger instance with default parameters. + pub fn new(max_msm_buffer: usize) -> Self { + Self { + scalars_buffer: Vec::with_capacity(max_msm_buffer), + bases_buffer: Vec::with_capacity(max_msm_buffer), + result: G::zero(), + buf_size: max_msm_buffer, + } + } + + /// Initialize a chunked Pippenger instance with the given buffer size. + pub fn with_size(buf_size: usize) -> Self { + Self { + scalars_buffer: Vec::with_capacity(buf_size), + bases_buffer: Vec::with_capacity(buf_size), + result: G::zero(), + buf_size, + } + } + + /// Add a new (base, scalar) pair into the instance. + #[inline(always)] + pub fn add(&mut self, base: B, scalar: S) + where + B: Borrow, + S: Borrow<::BigInt>, + { + self.scalars_buffer.push(*scalar.borrow()); + self.bases_buffer.push(*base.borrow()); + if self.scalars_buffer.len() == self.buf_size { + self.result.add_assign(G::msm_bigint( + self.bases_buffer.as_slice(), + self.scalars_buffer.as_slice(), + )); + self.scalars_buffer.clear(); + self.bases_buffer.clear(); + } + } + + /// Output the final Pippenger algorithm result. + #[inline(always)] + pub fn finalize(mut self) -> G { + if !self.scalars_buffer.is_empty() { + self.result += G::msm_bigint( + self.bases_buffer.as_slice(), + self.scalars_buffer.as_slice(), + ); + } + self.result + } +} + +/// Hash map struct for Pippenger algorithm. +pub struct HashMapPippenger { + buffer: HashMap< + G::MulBase, + G::ScalarField, + core::hash::BuildHasherDefault, + >, + result: G, + buf_size: usize, +} + +impl HashMapPippenger { + /// Produce a new hash map with the maximum msm buffer size. + pub fn new(max_msm_buffer: usize) -> Self { + Self { + buffer: HashMap::with_capacity_and_hasher( + max_msm_buffer, + core::hash::BuildHasherDefault::default(), + ), + result: G::zero(), + buf_size: max_msm_buffer, + } + } + + /// Add a new (base, scalar) pair into the hash map. + #[inline(always)] + pub fn add(&mut self, base: B, scalar: S) + where + B: Borrow, + S: Borrow, + { + // update the entry, guarding the possibility that it has been already + // set. + let entry = + self.buffer.entry(*base.borrow()).or_insert(G::ScalarField::zero()); + *entry += *scalar.borrow(); + if self.buffer.len() == self.buf_size { + let bases = self.buffer.keys().cloned().collect::>(); + let scalars = self + .buffer + .values() + .map(|s| s.into_bigint()) + .collect::>(); + self.result += G::msm_bigint(&bases, &scalars); + self.buffer.clear(); + } + } + + /// Update the final result with (base, scalar) pairs in the hash map. + #[inline(always)] + pub fn finalize(mut self) -> G { + if !self.buffer.is_empty() { + let bases = self.buffer.keys().cloned().collect::>(); + let scalars = self + .buffer + .values() + .map(|s| s.into_bigint()) + .collect::>(); + + self.result += G::msm_bigint(&bases, &scalars); + } + self.result + } +} diff --git a/lib/crypto/src/curve/scalar_mul/wnaf.rs b/lib/crypto/src/curve/scalar_mul/wnaf.rs new file mode 100644 index 000000000..d93c100a8 --- /dev/null +++ b/lib/crypto/src/curve/scalar_mul/wnaf.rs @@ -0,0 +1,85 @@ +use ark_ff::{BigInteger, PrimeField}; +use ark_std::vec::*; + +use crate::PrimeGroup; + +/// A helper type that contains all the context required for computing +/// a window NAF multiplication of a group element by a scalar. +pub struct WnafContext { + pub window_size: usize, +} + +impl WnafContext { + /// Constructs a new context for a window of size `window_size`. + /// + /// # Panics + /// + /// This function will panic if not `2 <= window_size < 64` + pub fn new(window_size: usize) -> Self { + assert!(window_size >= 2); + assert!(window_size < 64); + Self { window_size } + } + + pub fn table(&self, mut base: G) -> Vec { + let mut table = Vec::with_capacity(1 << (self.window_size - 1)); + let dbl = base.double(); + + for _ in 0..(1 << (self.window_size - 1)) { + table.push(base); + base += &dbl; + } + table + } + + /// Computes scalar multiplication of a group element `g` by `scalar`. + /// + /// This method uses the wNAF algorithm to perform the scalar + /// multiplication; first, it uses `Self::table` to calculate an + /// appropriate table of multiples of `g`, and then uses the wNAF + /// algorithm to compute the scalar multiple. + pub fn mul(&self, g: G, scalar: &G::ScalarField) -> G { + let table = self.table(g); + self.mul_with_table(&table, scalar).unwrap() + } + + /// Computes scalar multiplication of a group element by `scalar`. + /// `base_table` holds precomputed multiples of the group element; it can be + /// generated using `Self::table`. `scalar` is an element of + /// `G::ScalarField`. + /// + /// Returns `None` if the table is too small. + pub fn mul_with_table( + &self, + base_table: &[G], + scalar: &G::ScalarField, + ) -> Option { + if 1 << (self.window_size - 1) > base_table.len() { + return None; + } + let scalar_wnaf = + scalar.into_bigint().find_wnaf(self.window_size).unwrap(); + + let mut result = G::zero(); + + let mut found_non_zero = false; + + for n in scalar_wnaf.iter().rev() { + if found_non_zero { + result.double_in_place(); + } + + if *n != 0 { + found_non_zero = true; + + if *n > 0 { + result += &base_table[(n / 2) as usize]; + } else { + result -= &base_table[((-n) / 2) as usize]; + } + } + } + + Some(result) + } +} diff --git a/lib/crypto/src/curve/short_weierstrass/affine.rs b/lib/crypto/src/curve/short_weierstrass/affine.rs index 304937424..a8efe8c91 100644 --- a/lib/crypto/src/curve/short_weierstrass/affine.rs +++ b/lib/crypto/src/curve/short_weierstrass/affine.rs @@ -1,8 +1,18 @@ +use core::{ + borrow::Borrow, + fmt::{Debug, Display, Formatter}, + ops::{Add, Mul, Neg, Sub}, +}; + use educe::Educe; +use num_traits::{real::Real, One, Zero}; use zeroize::Zeroize; use super::{Projective, SWCurveConfig, SWFlags}; -use crate::AffineRepr; +use crate::{ + curve::AffineRepr, + field::{group::AdditiveGroup, prime::PrimeField, Field}, +}; /// Affine coordinates for a point on an elliptic curve in short Weierstrass /// form, over the base field `P::BaseField`. @@ -25,7 +35,7 @@ impl PartialEq> for Affine

{ } impl Display for Affine

{ - fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { match self.infinity { true => write!(f, "infinity"), false => write!(f, "({}, {})", self.x, self.y), @@ -34,7 +44,7 @@ impl Display for Affine

{ } impl Debug for Affine

{ - fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { match self.infinity { true => write!(f, "infinity"), false => write!(f, "({}, {})", self.x, self.y), @@ -158,21 +168,6 @@ impl Zeroize for Affine

{ } } -impl Distribution> for Standard { - /// Generates a uniformly random instance of the curve. - #[inline] - fn sample(&self, rng: &mut R) -> Affine

{ - loop { - let x = P::BaseField::rand(rng); - let greatest = rng.gen(); - - if let Some(p) = Affine::get_point_from_x_unchecked(x, greatest) { - return p.mul_by_cofactor(); - } - } - } -} - impl AffineRepr for Affine

{ type BaseField = P::BaseField; type Config = P; @@ -335,56 +330,3 @@ impl From> for Affine

{ } } } - -impl CanonicalSerialize for Affine

{ - #[inline] - fn serialize_with_mode( - &self, - writer: W, - compress: ark_serialize::Compress, - ) -> Result<(), SerializationError> { - P::serialize_with_mode(self, writer, compress) - } - - #[inline] - fn serialized_size(&self, compress: Compress) -> usize { - P::serialized_size(compress) - } -} - -impl Valid for Affine

{ - fn check(&self) -> Result<(), SerializationError> { - if self.is_on_curve() && self.is_in_correct_subgroup_assuming_on_curve() - { - Ok(()) - } else { - Err(SerializationError::InvalidData) - } - } -} - -impl CanonicalDeserialize for Affine

{ - fn deserialize_with_mode( - reader: R, - compress: Compress, - validate: Validate, - ) -> Result { - P::deserialize_with_mode(reader, compress, validate) - } -} - -impl ToConstraintField - for Affine -where - M::BaseField: ToConstraintField, -{ - #[inline] - fn to_field_elements(&self) -> Option> { - let mut x = self.x.to_field_elements()?; - let y = self.y.to_field_elements()?; - let infinity = self.infinity.to_field_elements()?; - x.extend_from_slice(&y); - x.extend_from_slice(&infinity); - Some(x) - } -} diff --git a/lib/crypto/src/curve/short_weierstrass/group.rs b/lib/crypto/src/curve/short_weierstrass/group.rs index 27d3d2e88..46c524349 100644 --- a/lib/crypto/src/curve/short_weierstrass/group.rs +++ b/lib/crypto/src/curve/short_weierstrass/group.rs @@ -1,7 +1,22 @@ +use core::{ + borrow::Borrow, + fmt::{Debug, Display, Formatter}, + hash::{Hash, Hasher}, + ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign}, +}; + use educe::Educe; +use num_traits::{One, Zero}; use zeroize::Zeroize; use super::{Affine, SWCurveConfig}; +use crate::{ + curve::{ + scalar_mul::{variable_base::VariableBaseMSM, ScalarMul}, + AffineRepr, CurveGroup, PrimeGroup, + }, + field::{group::AdditiveGroup, prime::PrimeField, Field}, +}; /// Jacobian coordinates for a point on an elliptic curve in short Weierstrass /// form, over the base field `P::BaseField`. This struct implements arithmetic @@ -19,13 +34,13 @@ pub struct Projective { } impl Display for Projective

{ - fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { write!(f, "{}", Affine::from(*self)) } } impl Debug for Projective

{ - fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { match self.is_zero() { true => write!(f, "infinity"), false => write!(f, "({}, {}, {})", self.x, self.y, self.z), @@ -70,21 +85,6 @@ impl Hash for Projective

{ } } -impl Distribution> for Standard { - /// Generates a uniformly random instance of the curve. - #[inline] - fn sample(&self, rng: &mut R) -> Projective

{ - loop { - let x = P::BaseField::rand(rng); - let greatest = rng.gen(); - - if let Some(p) = Affine::get_point_from_x_unchecked(x, greatest) { - return p.mul_by_cofactor_to_group(); - } - } - } -} - impl Default for Projective

{ #[inline] fn default() -> Self { @@ -174,6 +174,7 @@ impl AdditiveGroup for Projective

{ // D = 2*((X1+B)^2-A-C) // = 2 * (X1 + Y1^2)^2 - A - C // = 2 * 2 * X1 * Y1^2 + // TODO#q: probably use different trait for extension_degree let d = if [1, 2].contains(&P::BaseField::extension_degree()) { let mut d = self.x; d *= &b; @@ -291,6 +292,8 @@ impl CurveGroup for Projective

{ #[inline] fn normalize_batch(v: &[Self]) -> Vec { let mut z_s = v.iter().map(|g| g.z).collect::>(); + // TODO#q: we don't need to do it in parallel onchain + ark_ff::batch_inversion(&mut z_s); // Perform affine transformations @@ -387,6 +390,7 @@ impl>> AddAssign for Projective

{ // Y3 = r*(V-X3) + 2*Y1*J v -= &self.x; self.y.double_in_place(); + // TODO#q: we need sum of products for add assign self.y = P::BaseField::sum_of_products(&[r, self.y], &[v, j]); // Z3 = 2 * Z1 * H; @@ -424,6 +428,8 @@ impl>> Sub for Projective

{ } } +// TODO#q: Add auto derive of multiplication operations +// Implements AddAssign on Self by deferring to an implementation on &Self ark_ff::impl_additive_ops_from_ref!(Projective, SWCurveConfig); impl<'a, P: SWCurveConfig> Add<&'a Self> for Projective

{ @@ -574,62 +580,6 @@ impl From> for Projective

{ } } -impl CanonicalSerialize for Projective

{ - #[inline] - fn serialize_with_mode( - &self, - writer: W, - compress: Compress, - ) -> Result<(), SerializationError> { - let aff = Affine::from(*self); - P::serialize_with_mode(&aff, writer, compress) - } - - #[inline] - fn serialized_size(&self, compress: Compress) -> usize { - P::serialized_size(compress) - } -} - -impl Valid for Projective

{ - fn check(&self) -> Result<(), SerializationError> { - self.into_affine().check() - } - - fn batch_check<'a>( - batch: impl Iterator + Send, - ) -> Result<(), SerializationError> - where - Self: 'a, - { - let batch = batch.copied().collect::>(); - let batch = Self::normalize_batch(&batch); - Affine::batch_check(batch.iter()) - } -} - -impl CanonicalDeserialize for Projective

{ - fn deserialize_with_mode( - reader: R, - compress: Compress, - validate: Validate, - ) -> Result { - let aff = P::deserialize_with_mode(reader, compress, validate)?; - Ok(aff.into()) - } -} - -impl ToConstraintField - for Projective -where - M::BaseField: ToConstraintField, -{ - #[inline] - fn to_field_elements(&self) -> Option> { - Affine::from(*self).to_field_elements() - } -} - impl ScalarMul for Projective

{ type MulBase = Affine

; diff --git a/lib/crypto/src/curve/short_weierstrass/mod.rs b/lib/crypto/src/curve/short_weierstrass/mod.rs index 5f4833df1..9476c6b53 100644 --- a/lib/crypto/src/curve/short_weierstrass/mod.rs +++ b/lib/crypto/src/curve/short_weierstrass/mod.rs @@ -1,21 +1,21 @@ use num_traits::Zero; -use crate::{ - scalar_mul::{ - sw_double_and_add_affine, sw_double_and_add_projective, - variable_base::VariableBaseMSM, - }, - AffineRepr, -}; - mod affine; pub use affine::*; mod group; pub use group::*; -mod serialization_flags; -pub use serialization_flags::*; +use crate::{ + curve::{ + scalar_mul::{ + sw_double_and_add_affine, sw_double_and_add_projective, + variable_base::VariableBaseMSM, + }, + AffineRepr, + }, + field::{group::AdditiveGroup, prime::PrimeField}, +}; /// Constants and convenience functions that collectively define the [Short Weierstrass model](https://www.hyperelliptic.org/EFD/g1p/auto-shortw.html) /// of the curve. @@ -107,101 +107,4 @@ pub trait SWCurveConfig: super::CurveConfig { .then(|| VariableBaseMSM::msm_unchecked(bases, scalars)) .ok_or(bases.len().min(scalars.len())) } - - /// If uncompressed, serializes both x and y coordinates as well as a bit - /// for whether it is infinity. If compressed, serializes x coordinate - /// with two bits to encode whether y is positive, negative, or - /// infinity. - #[inline] - fn serialize_with_mode( - item: &Affine, - mut writer: W, - compress: ark_serialize::Compress, - ) -> Result<(), SerializationError> { - let (x, y, flags) = match item.infinity { - true => ( - Self::BaseField::zero(), - Self::BaseField::zero(), - SWFlags::infinity(), - ), - false => (item.x, item.y, item.to_flags()), - }; - - match compress { - Compress::Yes => x.serialize_with_flags(writer, flags), - Compress::No => { - x.serialize_with_mode(&mut writer, compress)?; - y.serialize_with_flags(&mut writer, flags) - } - } - } - - /// If `validate` is `Yes`, calls `check()` to make sure the element is - /// valid. - fn deserialize_with_mode( - mut reader: R, - compress: Compress, - validate: Validate, - ) -> Result, SerializationError> { - let (x, y, flags) = match compress { - Compress::Yes => { - let (x, flags): (_, SWFlags) = - CanonicalDeserializeWithFlags::deserialize_with_flags( - reader, - )?; - match flags { - SWFlags::PointAtInfinity => ( - Affine::::identity().x, - Affine::::identity().y, - flags, - ), - _ => { - let is_positive = flags.is_positive().unwrap(); - let (y, neg_y) = - Affine::::get_ys_from_x_unchecked(x) - .ok_or(SerializationError::InvalidData)?; - if is_positive { - (x, y, flags) - } else { - (x, neg_y, flags) - } - } - } - } - Compress::No => { - let x: Self::BaseField = - CanonicalDeserialize::deserialize_with_mode( - &mut reader, - compress, - validate, - )?; - let (y, flags): (_, SWFlags) = - CanonicalDeserializeWithFlags::deserialize_with_flags( - &mut reader, - )?; - (x, y, flags) - } - }; - if flags.is_infinity() { - Ok(Affine::identity()) - } else { - let point = Affine::new_unchecked(x, y); - if let Validate::Yes = validate { - point.check()?; - } - Ok(point) - } - } - - #[inline] - fn serialized_size(compress: Compress) -> usize { - let zero = Self::BaseField::zero(); - match compress { - Compress::Yes => zero.serialized_size_with_flags::(), - Compress::No => { - zero.compressed_size() - + zero.serialized_size_with_flags::() - } - } - } } diff --git a/lib/crypto/src/curve/short_weierstrass/serialization_flags.rs b/lib/crypto/src/curve/short_weierstrass/serialization_flags.rs deleted file mode 100644 index b9fb00eb7..000000000 --- a/lib/crypto/src/curve/short_weierstrass/serialization_flags.rs +++ /dev/null @@ -1,79 +0,0 @@ -/// Flags to be encoded into the serialization. -/// The default flags (empty) should not change the binary representation. -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub enum SWFlags { - /// Represents a point with positive y-coordinate by setting all bits to 0. - YIsPositive = 0, - /// Represents the point at infinity by setting the setting the - /// last-but-one bit to 1. - PointAtInfinity = 1 << 6, - /// Represents a point with negative y-coordinate by setting the MSB to 1. - YIsNegative = 1 << 7, -} - -impl SWFlags { - #[inline] - pub fn infinity() -> Self { - SWFlags::PointAtInfinity - } - - #[inline] - pub fn from_y_coordinate(y: impl Field) -> Self { - if y <= -y { - Self::YIsPositive - } else { - Self::YIsNegative - } - } - - #[inline] - pub fn is_infinity(&self) -> bool { - matches!(self, SWFlags::PointAtInfinity) - } - - #[inline] - pub fn is_positive(&self) -> Option { - match self { - SWFlags::PointAtInfinity => None, - SWFlags::YIsPositive => Some(true), - SWFlags::YIsNegative => Some(false), - } - } -} - -impl Default for SWFlags { - #[inline] - fn default() -> Self { - // YIsNegative doesn't change the serialization - SWFlags::YIsNegative - } -} - -impl Flags for SWFlags { - const BIT_SIZE: usize = 2; - - #[inline] - fn u8_bitmask(&self) -> u8 { - let mut mask = 0; - match self { - SWFlags::PointAtInfinity => mask |= 1 << 6, - SWFlags::YIsNegative => mask |= 1 << 7, - _ => (), - } - mask - } - - #[inline] - fn from_u8(value: u8) -> Option { - let is_negative = (value >> 7) & 1 == 1; - let is_infinity = (value >> 6) & 1 == 1; - match (is_negative, is_infinity) { - // This is invalid because we only want *one* way to serialize - // the point at infinity. - (true, true) => None, - (false, true) => Some(SWFlags::PointAtInfinity), - (true, false) => Some(SWFlags::YIsNegative), - (false, false) => Some(SWFlags::YIsPositive), - } - } -} diff --git a/lib/crypto/src/curve/twisted_edwards/affine.rs b/lib/crypto/src/curve/twisted_edwards/affine.rs index f7cb23e98..12360e3e8 100644 --- a/lib/crypto/src/curve/twisted_edwards/affine.rs +++ b/lib/crypto/src/curve/twisted_edwards/affine.rs @@ -1,9 +1,18 @@ +use core::{ + borrow::Borrow, + fmt::{Debug, Display, Formatter}, +}; +use std::ops::{Add, Mul, Neg, Sub}; + use educe::Educe; use num_traits::{One, Zero}; use zeroize::Zeroize; use super::{Projective, TECurveConfig, TEFlags}; -use crate::AffineRepr; +use crate::{ + curve::AffineRepr, + field::{group::AdditiveGroup, Field}, +}; /// Affine coordinates for a point on a twisted Edwards curve, over the /// base field `P::BaseField`. @@ -18,7 +27,7 @@ pub struct Affine { } impl Display for Affine

{ - fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { match self.is_zero() { true => write!(f, "infinity"), false => write!(f, "({}, {})", self.x, self.y), @@ -27,7 +36,7 @@ impl Display for Affine

{ } impl Debug for Affine

{ - fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { match self.is_zero() { true => write!(f, "infinity"), false => write!(f, "({}, {})", self.x, self.y), @@ -265,21 +274,6 @@ impl Default for Affine

{ } } -impl Distribution> for Standard { - /// Generates a uniformly random instance of the curve. - #[inline] - fn sample(&self, rng: &mut R) -> Affine

{ - loop { - let y = P::BaseField::rand(rng); - let greatest = rng.gen(); - - if let Some(p) = Affine::get_point_from_y_unchecked(y, greatest) { - return p.mul_by_cofactor(); - } - } - } -} - impl> Mul for Affine

{ type Output = Projective

; @@ -307,53 +301,3 @@ impl From> for Affine

{ } } } -impl CanonicalSerialize for Affine

{ - #[inline] - fn serialize_with_mode( - &self, - writer: W, - compress: ark_serialize::Compress, - ) -> Result<(), SerializationError> { - P::serialize_with_mode(self, writer, compress) - } - - #[inline] - fn serialized_size(&self, compress: Compress) -> usize { - P::serialized_size(compress) - } -} - -impl Valid for Affine

{ - fn check(&self) -> Result<(), SerializationError> { - if self.is_on_curve() && self.is_in_correct_subgroup_assuming_on_curve() - { - Ok(()) - } else { - Err(SerializationError::InvalidData) - } - } -} - -impl CanonicalDeserialize for Affine

{ - fn deserialize_with_mode( - reader: R, - compress: Compress, - validate: Validate, - ) -> Result { - P::deserialize_with_mode(reader, compress, validate) - } -} - -impl ToConstraintField - for Affine -where - M::BaseField: ToConstraintField, -{ - #[inline] - fn to_field_elements(&self) -> Option> { - let mut x_fe = self.x.to_field_elements()?; - let y_fe = self.y.to_field_elements()?; - x_fe.extend_from_slice(&y_fe); - Some(x_fe) - } -} diff --git a/lib/crypto/src/curve/twisted_edwards/group.rs b/lib/crypto/src/curve/twisted_edwards/group.rs index 0383c6d7d..bb21b50ae 100644 --- a/lib/crypto/src/curve/twisted_edwards/group.rs +++ b/lib/crypto/src/curve/twisted_edwards/group.rs @@ -29,7 +29,7 @@ impl PartialEq> for Projective

{ } impl Display for Projective

{ - fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { write!(f, "{}", Affine::from(*self)) } } @@ -56,21 +56,6 @@ impl Hash for Projective

{ } } -impl Distribution> for Standard { - /// Generates a uniformly random instance of the curve. - #[inline] - fn sample(&self, rng: &mut R) -> Projective

{ - loop { - let y = P::BaseField::rand(rng); - let greatest = rng.gen(); - - if let Some(p) = Affine::get_point_from_y_unchecked(y, greatest) { - return p.mul_by_cofactor_to_group(); - } - } - } -} - impl Default for Projective

{ #[inline] fn default() -> Self { @@ -407,7 +392,7 @@ pub struct MontgomeryAffine { } impl Display for MontgomeryAffine

{ - fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { write!(f, "MontgomeryAffine(x={}, y={})", self.x, self.y) } } @@ -418,64 +403,6 @@ impl MontgomeryAffine

{ } } -impl CanonicalSerialize for Projective

{ - #[allow(unused_qualifications)] - #[inline] - fn serialize_with_mode( - &self, - writer: W, - compress: Compress, - ) -> Result<(), SerializationError> { - let aff = Affine::from(*self); - P::serialize_with_mode(&aff, writer, compress) - } - - #[inline] - fn serialized_size(&self, compress: Compress) -> usize { - P::serialized_size(compress) - } -} - -impl Valid for Projective

{ - fn check(&self) -> Result<(), SerializationError> { - self.into_affine().check() - } - - fn batch_check<'a>( - batch: impl Iterator + Send, - ) -> Result<(), SerializationError> - where - Self: 'a, - { - let batch = batch.copied().collect::>(); - let batch = Self::normalize_batch(&batch); - Affine::batch_check(batch.iter()) - } -} - -impl CanonicalDeserialize for Projective

{ - #[allow(unused_qualifications)] - fn deserialize_with_mode( - reader: R, - compress: Compress, - validate: Validate, - ) -> Result { - let aff = P::deserialize_with_mode(reader, compress, validate)?; - Ok(aff.into()) - } -} - -impl ToConstraintField - for Projective -where - M::BaseField: ToConstraintField, -{ - #[inline] - fn to_field_elements(&self) -> Option> { - Affine::from(*self).to_field_elements() - } -} - impl ScalarMul for Projective

{ type MulBase = Affine

; diff --git a/lib/crypto/src/curve/twisted_edwards/mod.rs b/lib/crypto/src/curve/twisted_edwards/mod.rs index f34109b41..0198a5706 100644 --- a/lib/crypto/src/curve/twisted_edwards/mod.rs +++ b/lib/crypto/src/curve/twisted_edwards/mod.rs @@ -1,15 +1,15 @@ use num_traits::Zero; -use crate::{scalar_mul::variable_base::VariableBaseMSM, AffineRepr}; - mod affine; pub use affine::*; mod group; pub use group::*; -mod serialization_flags; -pub use serialization_flags::*; +use crate::{ + curve::{scalar_mul::variable_base::VariableBaseMSM, AffineRepr}, + field::prime::PrimeField, +}; /// Constants and convenience functions that collectively define the [Twisted Edwards model](https://www.hyperelliptic.org/EFD/g1p/auto-twisted.html) /// of the curve. @@ -92,76 +92,6 @@ pub trait TECurveConfig: super::CurveConfig { .then(|| VariableBaseMSM::msm_unchecked(bases, scalars)) .ok_or(bases.len().min(scalars.len())) } - - /// If uncompressed, serializes both x and y coordinates. - /// If compressed, serializes y coordinate with a bit to encode whether x is - /// positive. - #[inline] - fn serialize_with_mode( - item: &Affine, - mut writer: W, - compress: ark_serialize::Compress, - ) -> Result<(), SerializationError> { - let flags = TEFlags::from_x_coordinate(item.x); - match compress { - Compress::Yes => item.y.serialize_with_flags(writer, flags), - Compress::No => { - item.x.serialize_uncompressed(&mut writer)?; - item.y.serialize_uncompressed(&mut writer) - } - } - } - - /// If `validate` is `Yes`, calls `check()` to make sure the element is - /// valid. - /// - /// Uses `Affine::get_xs_from_y_unchecked()` for the compressed version. - fn deserialize_with_mode( - mut reader: R, - compress: Compress, - validate: Validate, - ) -> Result, SerializationError> { - let (x, y) = match compress { - Compress::Yes => { - let (y, flags): (_, TEFlags) = - CanonicalDeserializeWithFlags::deserialize_with_flags( - reader, - )?; - let (x, neg_x) = Affine::::get_xs_from_y_unchecked(y) - .ok_or(SerializationError::InvalidData)?; - if flags.is_negative() { - (neg_x, y) - } else { - (x, y) - } - } - Compress::No => { - let x: Self::BaseField = - CanonicalDeserialize::deserialize_uncompressed( - &mut reader, - )?; - let y: Self::BaseField = - CanonicalDeserialize::deserialize_uncompressed( - &mut reader, - )?; - (x, y) - } - }; - let point = Affine::new_unchecked(x, y); - if let Validate::Yes = validate { - point.check()?; - } - Ok(point) - } - - #[inline] - fn serialized_size(compress: Compress) -> usize { - let zero = Self::BaseField::zero(); - match compress { - Compress::Yes => zero.serialized_size_with_flags::(), - Compress::No => zero.uncompressed_size() + zero.uncompressed_size(), - } - } } /// Constants and convenience functions that collectively define the [Montgomery model](https://www.hyperelliptic.org/EFD/g1p/auto-montgom.html) diff --git a/lib/crypto/src/curve/twisted_edwards/serialization_flags.rs b/lib/crypto/src/curve/twisted_edwards/serialization_flags.rs deleted file mode 100644 index 2da62b570..000000000 --- a/lib/crypto/src/curve/twisted_edwards/serialization_flags.rs +++ /dev/null @@ -1,54 +0,0 @@ -/// Flags to be encoded into the serialization. -/// The default flags (empty) should not change the binary representation. -#[derive(Clone, Copy, PartialEq, Eq, Debug)] -pub enum TEFlags { - XIsPositive = 0, - XIsNegative = 1, -} - -impl TEFlags { - #[inline] - pub fn from_x_coordinate(x: impl Field) -> Self { - if x <= -x { - TEFlags::XIsPositive - } else { - TEFlags::XIsNegative - } - } - - #[inline] - pub fn is_negative(&self) -> bool { - matches!(*self, TEFlags::XIsNegative) - } -} - -impl Default for TEFlags { - #[inline] - fn default() -> Self { - // XIsPositive doesn't change the serialization - TEFlags::XIsPositive - } -} - -impl Flags for TEFlags { - const BIT_SIZE: usize = 1; - - #[inline] - fn u8_bitmask(&self) -> u8 { - let mut mask = 0; - if let Self::XIsNegative = self { - mask |= 1 << 7; - } - mask - } - - #[inline] - fn from_u8(value: u8) -> Option { - let x_sign = (value >> 7) & 1 == 1; - if x_sign { - Some(Self::XIsNegative) - } else { - Some(Self::XIsPositive) - } - } -} diff --git a/lib/crypto/src/lib.rs b/lib/crypto/src/lib.rs index e22b1b372..18f46328c 100644 --- a/lib/crypto/src/lib.rs +++ b/lib/crypto/src/lib.rs @@ -24,6 +24,11 @@ Common cryptographic procedures for a blockchain environment. #![cfg_attr(not(feature = "std"), no_std, no_main)] extern crate alloc; extern crate core; +extern crate core; +extern crate core; +extern crate core; +extern crate core; +extern crate core; pub mod arithmetic; pub mod bits; From 6edd263b55c5ed74672087b8ce0e17dbb36c5705 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Thu, 13 Feb 2025 18:23:43 +0400 Subject: [PATCH 03/24] ++ --- lib/crypto/src/curve/scalar_mul/glv.rs | 171 ------------------------ lib/crypto/src/curve/scalar_mul/mod.rs | 3 - lib/crypto/src/curve/scalar_mul/wnaf.rs | 85 ------------ 3 files changed, 259 deletions(-) delete mode 100644 lib/crypto/src/curve/scalar_mul/glv.rs delete mode 100644 lib/crypto/src/curve/scalar_mul/wnaf.rs diff --git a/lib/crypto/src/curve/scalar_mul/glv.rs b/lib/crypto/src/curve/scalar_mul/glv.rs deleted file mode 100644 index b1a4d7f1a..000000000 --- a/lib/crypto/src/curve/scalar_mul/glv.rs +++ /dev/null @@ -1,171 +0,0 @@ -use ark_ff::{PrimeField, Zero}; -use ark_std::ops::Neg; -use num_bigint::{BigInt, BigUint, Sign}; -use num_integer::Integer; -use num_traits::{One, Signed}; - -use crate::{ - short_weierstrass::{Affine, Projective, SWCurveConfig}, - AdditiveGroup, CurveGroup, -}; - -/// The GLV parameters for computing the endomorphism and scalar decomposition. -pub trait GLVConfig: Send + Sync + 'static + SWCurveConfig { - /// Constant used to calculate `phi(G) := lambda*G`. - /// - /// The coefficients of the endomorphism - const ENDO_COEFFS: &'static [Self::BaseField]; - - /// Constant used to calculate `phi(G) := lambda*G`. - /// - /// The eigenvalue corresponding to the endomorphism. - const LAMBDA: Self::ScalarField; - - /// A 4-element vector representing a 2x2 matrix of coefficients the for - /// scalar decomposition, s.t. k-th entry in the vector is at col i, row j - /// in the matrix, with ij = BE binary decomposition of k. The entries - /// are the LLL-reduced bases. The determinant of this matrix must equal - /// `ScalarField::characteristic()`. - const SCALAR_DECOMP_COEFFS: [( - bool, - ::BigInt, - ); 4]; - - /// Decomposes a scalar s into k1, k2, s.t. s = k1 + lambda k2, - fn scalar_decomposition( - k: Self::ScalarField, - ) -> ((bool, Self::ScalarField), (bool, Self::ScalarField)) { - let scalar: BigInt = k.into_bigint().into().into(); - - let [n11, n12, n21, n22] = Self::SCALAR_DECOMP_COEFFS.map(|x| { - let sign = if x.0 { Sign::Plus } else { Sign::Minus }; - BigInt::from_biguint(sign, x.1.into()) - }); - - let r = BigInt::from(Self::ScalarField::MODULUS.into()); - - // beta = vector([k,0]) * self.curve.N_inv - // The inverse of N is 1/r * Matrix([[n22, -n12], [-n21, n11]]). - // so β = (k*n22, -k*n12)/r - - let beta_1 = { - let (mut div, rem) = (&scalar * &n22).div_rem(&r); - if (&rem + &rem) > r { - div += BigInt::one(); - } - div - }; - let beta_2 = { - let (mut div, rem) = (&scalar * &n12.clone().neg()).div_rem(&r); - if (&rem + &rem) > r { - div += BigInt::one(); - } - div - }; - - // b = vector([int(beta[0]), int(beta[1])]) * self.curve.N - // b = (β1N11 + β2N21, β1N12 + β2N22) with the signs! - // = (b11 + b12 , b21 + b22) with the signs! - - // b1 - let b11 = &beta_1 * &n11; - let b12 = &beta_2 * &n21; - let b1 = b11 + b12; - - // b2 - let b21 = &beta_1 * &n12; - let b22 = &beta_2 * &n22; - let b2 = b21 + b22; - - let k1 = &scalar - b1; - let k1_abs = BigUint::try_from(k1.abs()).unwrap(); - - // k2 - let k2 = -b2; - let k2_abs = BigUint::try_from(k2.abs()).unwrap(); - - ( - (k1.sign() == Sign::Plus, k1_abs.into()), - (k2.sign() == Sign::Plus, k2_abs.into()), - ) - } - - fn endomorphism(p: &Projective) -> Projective; - - fn endomorphism_affine(p: &Affine) -> Affine; - - fn glv_mul_projective( - p: Projective, - k: Self::ScalarField, - ) -> Projective { - let ((sgn_k1, k1), (sgn_k2, k2)) = Self::scalar_decomposition(k); - - let mut b1 = p; - let mut b2 = Self::endomorphism(&p); - - if !sgn_k1 { - b1 = -b1; - } - if !sgn_k2 { - b2 = -b2; - } - - let b1b2 = b1 + b2; - - let iter_k1 = ark_ff::BitIteratorBE::new(k1.into_bigint()); - let iter_k2 = ark_ff::BitIteratorBE::new(k2.into_bigint()); - - let mut res = Projective::zero(); - let mut skip_zeros = true; - for pair in iter_k1.zip(iter_k2) { - if skip_zeros && pair == (false, false) { - skip_zeros = false; - continue; - } - res.double_in_place(); - match pair { - (true, false) => res += b1, - (false, true) => res += b2, - (true, true) => res += b1b2, - (false, false) => {} - } - } - res - } - - fn glv_mul_affine(p: Affine, k: Self::ScalarField) -> Affine { - let ((sgn_k1, k1), (sgn_k2, k2)) = Self::scalar_decomposition(k); - - let mut b1 = p; - let mut b2 = Self::endomorphism_affine(&p); - - if !sgn_k1 { - b1 = -b1; - } - if !sgn_k2 { - b2 = -b2; - } - - let b1b2 = b1 + b2; - - let iter_k1 = ark_ff::BitIteratorBE::new(k1.into_bigint()); - let iter_k2 = ark_ff::BitIteratorBE::new(k2.into_bigint()); - - let mut res = Projective::zero(); - let mut skip_zeros = true; - for pair in iter_k1.zip(iter_k2) { - if skip_zeros && pair == (false, false) { - skip_zeros = false; - continue; - } - res.double_in_place(); - match pair { - (true, false) => res += b1, - (false, true) => res += b2, - (true, true) => res += b1b2, - (false, false) => {} - } - } - res.into_affine() - } -} diff --git a/lib/crypto/src/curve/scalar_mul/mod.rs b/lib/crypto/src/curve/scalar_mul/mod.rs index 95546fa19..c32014f06 100644 --- a/lib/crypto/src/curve/scalar_mul/mod.rs +++ b/lib/crypto/src/curve/scalar_mul/mod.rs @@ -1,6 +1,3 @@ -pub mod glv; -pub mod wnaf; - pub mod variable_base; use ark_ff::{AdditiveGroup, BigInteger, PrimeField, Zero}; diff --git a/lib/crypto/src/curve/scalar_mul/wnaf.rs b/lib/crypto/src/curve/scalar_mul/wnaf.rs deleted file mode 100644 index d93c100a8..000000000 --- a/lib/crypto/src/curve/scalar_mul/wnaf.rs +++ /dev/null @@ -1,85 +0,0 @@ -use ark_ff::{BigInteger, PrimeField}; -use ark_std::vec::*; - -use crate::PrimeGroup; - -/// A helper type that contains all the context required for computing -/// a window NAF multiplication of a group element by a scalar. -pub struct WnafContext { - pub window_size: usize, -} - -impl WnafContext { - /// Constructs a new context for a window of size `window_size`. - /// - /// # Panics - /// - /// This function will panic if not `2 <= window_size < 64` - pub fn new(window_size: usize) -> Self { - assert!(window_size >= 2); - assert!(window_size < 64); - Self { window_size } - } - - pub fn table(&self, mut base: G) -> Vec { - let mut table = Vec::with_capacity(1 << (self.window_size - 1)); - let dbl = base.double(); - - for _ in 0..(1 << (self.window_size - 1)) { - table.push(base); - base += &dbl; - } - table - } - - /// Computes scalar multiplication of a group element `g` by `scalar`. - /// - /// This method uses the wNAF algorithm to perform the scalar - /// multiplication; first, it uses `Self::table` to calculate an - /// appropriate table of multiples of `g`, and then uses the wNAF - /// algorithm to compute the scalar multiple. - pub fn mul(&self, g: G, scalar: &G::ScalarField) -> G { - let table = self.table(g); - self.mul_with_table(&table, scalar).unwrap() - } - - /// Computes scalar multiplication of a group element by `scalar`. - /// `base_table` holds precomputed multiples of the group element; it can be - /// generated using `Self::table`. `scalar` is an element of - /// `G::ScalarField`. - /// - /// Returns `None` if the table is too small. - pub fn mul_with_table( - &self, - base_table: &[G], - scalar: &G::ScalarField, - ) -> Option { - if 1 << (self.window_size - 1) > base_table.len() { - return None; - } - let scalar_wnaf = - scalar.into_bigint().find_wnaf(self.window_size).unwrap(); - - let mut result = G::zero(); - - let mut found_non_zero = false; - - for n in scalar_wnaf.iter().rev() { - if found_non_zero { - result.double_in_place(); - } - - if *n != 0 { - found_non_zero = true; - - if *n > 0 { - result += &base_table[(n / 2) as usize]; - } else { - result -= &base_table[((-n) / 2) as usize]; - } - } - } - - Some(result) - } -} From 18ef0f881d0a0c86d7b8136d589c9fdab5a12de5 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Thu, 13 Feb 2025 20:49:38 +0400 Subject: [PATCH 04/24] refactor: reorganize imports and remove unused scalar multiplication implementations --- .../src/curve/short_weierstrass/affine.rs | 2 +- .../src/curve/short_weierstrass/group.rs | 24 +----------- lib/crypto/src/curve/twisted_edwards/group.rs | 37 ++++++++----------- lib/crypto/src/curve/twisted_edwards/mod.rs | 2 +- lib/crypto/src/lib.rs | 5 --- 5 files changed, 18 insertions(+), 52 deletions(-) diff --git a/lib/crypto/src/curve/short_weierstrass/affine.rs b/lib/crypto/src/curve/short_weierstrass/affine.rs index a8efe8c91..39662c121 100644 --- a/lib/crypto/src/curve/short_weierstrass/affine.rs +++ b/lib/crypto/src/curve/short_weierstrass/affine.rs @@ -240,7 +240,7 @@ impl> Add for Affine

{ type Output = Projective

; fn add(self, other: T) -> Projective

{ - // TODO implement more efficient formulae when z1 = z2 = 1. + // TODO#q: implement more efficient formulae when z1 = z2 = 1. let mut copy = self.into_group(); copy += other.borrow(); copy diff --git a/lib/crypto/src/curve/short_weierstrass/group.rs b/lib/crypto/src/curve/short_weierstrass/group.rs index 46c524349..8075ed17d 100644 --- a/lib/crypto/src/curve/short_weierstrass/group.rs +++ b/lib/crypto/src/curve/short_weierstrass/group.rs @@ -11,10 +11,7 @@ use zeroize::Zeroize; use super::{Affine, SWCurveConfig}; use crate::{ - curve::{ - scalar_mul::{variable_base::VariableBaseMSM, ScalarMul}, - AffineRepr, CurveGroup, PrimeGroup, - }, + curve::{AffineRepr, CurveGroup, PrimeGroup}, field::{group::AdditiveGroup, prime::PrimeField, Field}, }; @@ -580,25 +577,6 @@ impl From> for Projective

{ } } -impl ScalarMul for Projective

{ - type MulBase = Affine

; - - const NEGATION_IS_CHEAP: bool = true; - - fn batch_convert_to_mul_base(bases: &[Self]) -> Vec { - Self::normalize_batch(bases) - } -} - -impl VariableBaseMSM for Projective

{ - fn msm( - bases: &[Self::MulBase], - bigints: &[Self::ScalarField], - ) -> Result { - P::msm(bases, bigints) - } -} - impl>> core::iter::Sum for Projective

{ diff --git a/lib/crypto/src/curve/twisted_edwards/group.rs b/lib/crypto/src/curve/twisted_edwards/group.rs index bb21b50ae..b03f2f688 100644 --- a/lib/crypto/src/curve/twisted_edwards/group.rs +++ b/lib/crypto/src/curve/twisted_edwards/group.rs @@ -1,10 +1,20 @@ +use core::{ + borrow::Borrow, + fmt::{Display, Formatter}, +}; +use std::{ + hash::{Hash, Hasher}, + ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign}, +}; + use educe::Educe; +use num_traits::{One, Zero}; use zeroize::Zeroize; use super::{Affine, MontCurveConfig, TECurveConfig}; use crate::{ - scalar_mul::{variable_base::VariableBaseMSM, ScalarMul}, - AffineRepr, CurveGroup, PrimeGroup, + curve::{AffineRepr, CurveGroup, PrimeGroup}, + field::{group::AdditiveGroup, prime::PrimeField, Field}, }; /// `Projective` implements Extended Twisted Edwards Coordinates @@ -190,6 +200,8 @@ impl CurveGroup for Projective

{ // 1 inversion + 6N field multiplications // (batch inversion requires 3N multiplications + 1 inversion) let mut z_s = v.iter().map(|g| g.z).collect::>(); + + // TODO#q: we don't need parallel here ark_ff::batch_inversion(&mut z_s); // Perform affine transformations @@ -365,7 +377,7 @@ impl> Mul for Projective

{ } } -impl>> ark_std::iter::Sum +impl>> core::iter::Sum for Projective

{ fn sum(iter: I) -> Self @@ -402,22 +414,3 @@ impl MontgomeryAffine

{ Self { x, y } } } - -impl ScalarMul for Projective

{ - type MulBase = Affine

; - - const NEGATION_IS_CHEAP: bool = true; - - fn batch_convert_to_mul_base(bases: &[Self]) -> Vec { - Self::normalize_batch(bases) - } -} - -impl VariableBaseMSM for Projective

{ - fn msm( - bases: &[Self::MulBase], - bigints: &[Self::ScalarField], - ) -> Result { - P::msm(bases, bigints) - } -} diff --git a/lib/crypto/src/curve/twisted_edwards/mod.rs b/lib/crypto/src/curve/twisted_edwards/mod.rs index 0198a5706..8a769ab6d 100644 --- a/lib/crypto/src/curve/twisted_edwards/mod.rs +++ b/lib/crypto/src/curve/twisted_edwards/mod.rs @@ -8,7 +8,7 @@ pub use group::*; use crate::{ curve::{scalar_mul::variable_base::VariableBaseMSM, AffineRepr}, - field::prime::PrimeField, + field::{group::AdditiveGroup, prime::PrimeField}, }; /// Constants and convenience functions that collectively define the [Twisted Edwards model](https://www.hyperelliptic.org/EFD/g1p/auto-twisted.html) diff --git a/lib/crypto/src/lib.rs b/lib/crypto/src/lib.rs index 18f46328c..e22b1b372 100644 --- a/lib/crypto/src/lib.rs +++ b/lib/crypto/src/lib.rs @@ -24,11 +24,6 @@ Common cryptographic procedures for a blockchain environment. #![cfg_attr(not(feature = "std"), no_std, no_main)] extern crate alloc; extern crate core; -extern crate core; -extern crate core; -extern crate core; -extern crate core; -extern crate core; pub mod arithmetic; pub mod bits; From be5dee098f59aec7e764022a831870cc98a6726e Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Thu, 13 Feb 2025 21:48:24 +0400 Subject: [PATCH 05/24] ++ --- lib/crypto/src/curve/mod.rs | 6 +- lib/crypto/src/curve/scalar_mul/mod.rs | 220 +----------- .../src/curve/scalar_mul/variable_base/mod.rs | 315 ------------------ .../variable_base/stream_pippenger.rs | 132 -------- lib/crypto/src/curve/short_weierstrass/mod.rs | 7 +- lib/crypto/src/curve/twisted_edwards/mod.rs | 2 + 6 files changed, 11 insertions(+), 671 deletions(-) delete mode 100644 lib/crypto/src/curve/scalar_mul/variable_base/mod.rs delete mode 100644 lib/crypto/src/curve/scalar_mul/variable_base/stream_pippenger.rs diff --git a/lib/crypto/src/curve/mod.rs b/lib/crypto/src/curve/mod.rs index 07666e6e9..6122cdb9e 100644 --- a/lib/crypto/src/curve/mod.rs +++ b/lib/crypto/src/curve/mod.rs @@ -5,7 +5,6 @@ use num_traits::Zero; use zeroize::Zeroize; use crate::{ - curve::scalar_mul::{variable_base::VariableBaseMSM, ScalarMul}, field::{group::AdditiveGroup, prime::PrimeField, Field}, hash::Hash, }; @@ -77,8 +76,9 @@ pub trait CurveGroup: + AddAssign + Sub + SubAssign - + VariableBaseMSM - + ScalarMul +// TODO#q: think to replace VariableBaseMSM and ScalarMul + // + VariableBaseMSM + // + ScalarMul + From + Into + core::iter::Sum diff --git a/lib/crypto/src/curve/scalar_mul/mod.rs b/lib/crypto/src/curve/scalar_mul/mod.rs index c32014f06..44039b710 100644 --- a/lib/crypto/src/curve/scalar_mul/mod.rs +++ b/lib/crypto/src/curve/scalar_mul/mod.rs @@ -1,28 +1,10 @@ -pub mod variable_base; - -use ark_ff::{AdditiveGroup, BigInteger, PrimeField, Zero}; -use ark_std::{ - cfg_iter, cfg_iter_mut, - ops::{Add, AddAssign, Mul, Neg, Sub, SubAssign}, - vec::*, -}; -#[cfg(feature = "parallel")] -use rayon::prelude::*; +use num_traits::Zero; use crate::{ - short_weierstrass::{Affine, Projective, SWCurveConfig}, - PrimeGroup, + curve::short_weierstrass::{Affine, Projective, SWCurveConfig}, + field::group::AdditiveGroup, }; -/// The result of this function is only approximately `ln(a)` -/// [`Explanation of usage`] -/// -/// [`Explanation of usage`]: https://github.com/scipr-lab/zexe/issues/79#issue-556220473 -fn ln_without_floats(a: usize) -> usize { - // log2(a) * ln(2) - (ark_std::log2(a) * 69 / 100) as usize -} - /// Standard double-and-add method for multiplication by a scalar. #[inline(always)] pub fn sw_double_and_add_affine( @@ -56,199 +38,3 @@ pub fn sw_double_and_add_projective( res } - -pub trait ScalarMul: - PrimeGroup - + Add - + AddAssign - + for<'a> Add<&'a Self::MulBase, Output = Self> - + for<'a> AddAssign<&'a Self::MulBase> - + Sub - + SubAssign - + for<'a> Sub<&'a Self::MulBase, Output = Self> - + for<'a> SubAssign<&'a Self::MulBase> - + From -{ - type MulBase: Send - + Sync - + Copy - + Eq - + core::hash::Hash - + Mul - + for<'a> Mul<&'a Self::ScalarField, Output = Self> - + Neg - + From; - - const NEGATION_IS_CHEAP: bool; - - fn batch_convert_to_mul_base(bases: &[Self]) -> Vec; - - /// Compute the vector v[0].G, v[1].G, ..., v[n-1].G, given: - /// - an element `g` - /// - a list `v` of n scalars - /// - /// # Example - /// ``` - /// use ark_std::{One, UniformRand}; - /// use ark_ec::pairing::Pairing; - /// use ark_test_curves::bls12_381::G1Projective as G; - /// use ark_test_curves::bls12_381::Fr; - /// use ark_ec::scalar_mul::ScalarMul; - /// - /// // Compute G, s.G, s^2.G, ..., s^9.G - /// let mut rng = ark_std::test_rng(); - /// let max_degree = 10; - /// let s = Fr::rand(&mut rng); - /// let g = G::rand(&mut rng); - /// let mut powers_of_s = vec![Fr::one()]; - /// let mut cur = s; - /// for _ in 0..max_degree { - /// powers_of_s.push(cur); - /// cur *= &s; - /// } - /// let powers_of_g = g.batch_mul(&powers_of_s); - /// let naive_powers_of_g: Vec = powers_of_s.iter().map(|e| g * e).collect(); - /// assert_eq!(powers_of_g, naive_powers_of_g); - /// ``` - fn batch_mul(self, v: &[Self::ScalarField]) -> Vec { - let table = BatchMulPreprocessing::new(self, v.len()); - Self::batch_mul_with_preprocessing(&table, v) - } - - /// Compute the vector v[0].G, v[1].G, ..., v[n-1].G, given: - /// - an element `g` - /// - a list `v` of n scalars - /// - /// This method allows the user to provide a precomputed table of multiples - /// of `g`. A more ergonomic way to call this would be to use - /// [`BatchMulPreprocessing::batch_mul`]. - /// - /// # Example - /// ``` - /// use ark_std::{One, UniformRand}; - /// use ark_ec::pairing::Pairing; - /// use ark_test_curves::bls12_381::G1Projective as G; - /// use ark_test_curves::bls12_381::Fr; - /// use ark_ec::scalar_mul::*; - /// - /// // Compute G, s.G, s^2.G, ..., s^9.G - /// let mut rng = ark_std::test_rng(); - /// let max_degree = 10; - /// let s = Fr::rand(&mut rng); - /// let g = G::rand(&mut rng); - /// let mut powers_of_s = vec![Fr::one()]; - /// let mut cur = s; - /// for _ in 0..max_degree { - /// powers_of_s.push(cur); - /// cur *= &s; - /// } - /// let table = BatchMulPreprocessing::new(g, powers_of_s.len()); - /// let powers_of_g = G::batch_mul_with_preprocessing(&table, &powers_of_s); - /// let powers_of_g_2 = table.batch_mul(&powers_of_s); - /// let naive_powers_of_g: Vec = powers_of_s.iter().map(|e| g * e).collect(); - /// assert_eq!(powers_of_g, naive_powers_of_g); - /// assert_eq!(powers_of_g_2, naive_powers_of_g); - /// ``` - fn batch_mul_with_preprocessing( - table: &BatchMulPreprocessing, - v: &[Self::ScalarField], - ) -> Vec { - table.batch_mul(v) - } -} - -/// Preprocessing used internally for batch scalar multiplication via -/// [`ScalarMul::batch_mul`]. -/// - `window` is the window size used for the precomputation -/// - `max_scalar_size` is the maximum size of the scalars that will be -/// multiplied -/// - `table` is the precomputed table of multiples of `base` -pub struct BatchMulPreprocessing { - pub window: usize, - pub max_scalar_size: usize, - pub table: Vec>, -} - -impl BatchMulPreprocessing { - pub fn new(base: T, num_scalars: usize) -> Self { - let scalar_size = T::ScalarField::MODULUS_BIT_SIZE as usize; - Self::with_num_scalars_and_scalar_size(base, num_scalars, scalar_size) - } - - pub fn with_num_scalars_and_scalar_size( - base: T, - num_scalars: usize, - max_scalar_size: usize, - ) -> Self { - let window = Self::compute_window_size(num_scalars); - let in_window = 1 << window; - let outerc = max_scalar_size.div_ceil(window); - let last_in_window = 1 << (max_scalar_size - (outerc - 1) * window); - - let mut multiples_of_g = vec![vec![T::zero(); in_window]; outerc]; - - let mut g_outer = base; - let mut g_outers = Vec::with_capacity(outerc); - for _ in 0..outerc { - g_outers.push(g_outer); - for _ in 0..window { - g_outer.double_in_place(); - } - } - cfg_iter_mut!(multiples_of_g) - .enumerate() - .take(outerc) - .zip(g_outers) - .for_each(|((outer, multiples_of_g), g_outer)| { - let cur_in_window = if outer == outerc - 1 { - last_in_window - } else { - in_window - }; - - let mut g_inner = T::zero(); - for inner in multiples_of_g.iter_mut().take(cur_in_window) { - *inner = g_inner; - g_inner += &g_outer; - } - }); - let table = cfg_iter!(multiples_of_g) - .map(|s| T::batch_convert_to_mul_base(s)) - .collect(); - Self { window, max_scalar_size, table } - } - - pub fn compute_window_size(num_scalars: usize) -> usize { - if num_scalars < 32 { - 3 - } else { - ln_without_floats(num_scalars) - } - } - - pub fn batch_mul(&self, v: &[T::ScalarField]) -> Vec { - let result: Vec<_> = - cfg_iter!(v).map(|e| self.windowed_mul(e)).collect(); - T::batch_convert_to_mul_base(&result) - } - - fn windowed_mul(&self, scalar: &T::ScalarField) -> T { - let outerc = self.max_scalar_size.div_ceil(self.window); - let modulus_size = T::ScalarField::MODULUS_BIT_SIZE as usize; - let scalar_val = scalar.into_bigint().to_bits_le(); - - let mut res = T::from(self.table[0][0]); - for outer in 0..outerc { - let mut inner = 0usize; - for i in 0..self.window { - if outer * self.window + i < modulus_size - && scalar_val[outer * self.window + i] - { - inner |= 1 << i; - } - } - res += &self.table[outer][inner]; - } - res - } -} diff --git a/lib/crypto/src/curve/scalar_mul/variable_base/mod.rs b/lib/crypto/src/curve/scalar_mul/variable_base/mod.rs deleted file mode 100644 index 5f22e8806..000000000 --- a/lib/crypto/src/curve/scalar_mul/variable_base/mod.rs +++ /dev/null @@ -1,315 +0,0 @@ -use ark_ff::prelude::*; -use ark_std::{borrow::Borrow, cfg_into_iter, iterable::Iterable, vec::*}; -#[cfg(feature = "parallel")] -use rayon::prelude::*; - -pub mod stream_pippenger; -pub use stream_pippenger::*; - -use super::ScalarMul; - -#[cfg(all( - target_has_atomic = "8", - target_has_atomic = "16", - target_has_atomic = "32", - target_has_atomic = "64", - target_has_atomic = "ptr" -))] -type DefaultHasher = ahash::AHasher; - -#[cfg(not(all( - target_has_atomic = "8", - target_has_atomic = "16", - target_has_atomic = "32", - target_has_atomic = "64", - target_has_atomic = "ptr" -)))] -type DefaultHasher = fnv::FnvHasher; - -pub trait VariableBaseMSM: ScalarMul { - /// Computes an inner product between the [`PrimeField`] elements in - /// `scalars` and the corresponding group elements in `bases`. - /// - /// If the elements have different length, it will chop the slices to the - /// shortest length between `scalars.len()` and `bases.len()`. - /// - /// Reference: [`VariableBaseMSM::msm`] - fn msm_unchecked( - bases: &[Self::MulBase], - scalars: &[Self::ScalarField], - ) -> Self { - let bigints = cfg_into_iter!(scalars) - .map(|s| s.into_bigint()) - .collect::>(); - Self::msm_bigint(bases, &bigints) - } - - /// Performs multi-scalar multiplication. - /// - /// # Warning - /// - /// This method checks that `bases` and `scalars` have the same length. - /// If they are unequal, it returns an error containing - /// the shortest length over which the MSM can be performed. - fn msm( - bases: &[Self::MulBase], - scalars: &[Self::ScalarField], - ) -> Result { - (bases.len() == scalars.len()) - .then(|| Self::msm_unchecked(bases, scalars)) - .ok_or(bases.len().min(scalars.len())) - } - - /// Optimized implementation of multi-scalar multiplication. - fn msm_bigint( - bases: &[Self::MulBase], - bigints: &[::BigInt], - ) -> Self { - if Self::NEGATION_IS_CHEAP { - msm_bigint_wnaf(bases, bigints) - } else { - msm_bigint(bases, bigints) - } - } - - /// Streaming multi-scalar multiplication algorithm with hard-coded chunk - /// size. - fn msm_chunks(bases_stream: &J, scalars_stream: &I) -> Self - where - I: Iterable + ?Sized, - I::Item: Borrow, - J: Iterable, - J::Item: Borrow, - { - assert!(scalars_stream.len() <= bases_stream.len()); - - // remove offset - let bases_init = bases_stream.iter(); - let mut scalars = scalars_stream.iter(); - - // align the streams - // TODO: change `skip` to `advance_by` once rust-lang/rust#7774 is - // fixed. See - let mut bases = - bases_init.skip(bases_stream.len() - scalars_stream.len()); - let step: usize = 1 << 20; - let mut result = Self::zero(); - for _ in 0..scalars_stream.len().div_ceil(step) { - let bases_step = (&mut bases) - .take(step) - .map(|b| *b.borrow()) - .collect::>(); - let scalars_step = (&mut scalars) - .take(step) - .map(|s| s.borrow().into_bigint()) - .collect::>(); - result += Self::msm_bigint( - bases_step.as_slice(), - scalars_step.as_slice(), - ); - } - result - } -} - -// Compute msm using windowed non-adjacent form -fn msm_bigint_wnaf( - bases: &[V::MulBase], - bigints: &[::BigInt], -) -> V { - let size = ark_std::cmp::min(bases.len(), bigints.len()); - let scalars = &bigints[..size]; - let bases = &bases[..size]; - - let c = if size < 32 { 3 } else { super::ln_without_floats(size) + 2 }; - - let num_bits = V::ScalarField::MODULUS_BIT_SIZE as usize; - let digits_count = num_bits.div_ceil(c); - #[cfg(feature = "parallel")] - let scalar_digits = scalars - .into_par_iter() - .flat_map_iter(|s| make_digits(s, c, num_bits)) - .collect::>(); - #[cfg(not(feature = "parallel"))] - let scalar_digits = scalars - .iter() - .flat_map(|s| make_digits(s, c, num_bits)) - .collect::>(); - let zero = V::zero(); - let window_sums: Vec<_> = ark_std::cfg_into_iter!(0..digits_count) - .map(|i| { - let mut buckets = vec![zero; 1 << c]; - for (digits, base) in scalar_digits.chunks(digits_count).zip(bases) - { - use ark_std::cmp::Ordering; - // digits is the digits thing of the first scalar? - let scalar = digits[i]; - match 0.cmp(&scalar) { - Ordering::Less => buckets[(scalar - 1) as usize] += base, - Ordering::Greater => { - buckets[(-scalar - 1) as usize] -= base - } - Ordering::Equal => (), - } - } - - let mut running_sum = V::zero(); - let mut res = V::zero(); - buckets.into_iter().rev().for_each(|b| { - running_sum += &b; - res += &running_sum; - }); - res - }) - .collect(); - - // We store the sum for the lowest window. - let lowest = *window_sums.first().unwrap(); - - // We're traversing windows from high to low. - lowest - + &window_sums[1..].iter().rev().fold(zero, |mut total, sum_i| { - total += sum_i; - for _ in 0..c { - total.double_in_place(); - } - total - }) -} - -/// Optimized implementation of multi-scalar multiplication. -fn msm_bigint( - bases: &[V::MulBase], - bigints: &[::BigInt], -) -> V { - let size = ark_std::cmp::min(bases.len(), bigints.len()); - let scalars = &bigints[..size]; - let bases = &bases[..size]; - let scalars_and_bases_iter = - scalars.iter().zip(bases).filter(|(s, _)| !s.is_zero()); - - let c = if size < 32 { 3 } else { super::ln_without_floats(size) + 2 }; - - let num_bits = V::ScalarField::MODULUS_BIT_SIZE as usize; - let one = V::ScalarField::one().into_bigint(); - - let zero = V::zero(); - let window_starts: Vec<_> = (0..num_bits).step_by(c).collect(); - - // Each window is of size `c`. - // We divide up the bits 0..num_bits into windows of size `c`, and - // in parallel process each such window. - let window_sums: Vec<_> = ark_std::cfg_into_iter!(window_starts) - .map(|w_start| { - let mut res = zero; - // We don't need the "zero" bucket, so we only have 2^c - 1 buckets. - let mut buckets = vec![zero; (1 << c) - 1]; - // This clone is cheap, because the iterator contains just a - // pointer and an index into the original vectors. - scalars_and_bases_iter.clone().for_each(|(&scalar, base)| { - if scalar == one { - // We only process unit scalars once in the first window. - if w_start == 0 { - res += base; - } - } else { - let mut scalar = scalar; - - // We right-shift by w_start, thus getting rid of the - // lower bits. - scalar >>= w_start as u32; - - // We mod the remaining bits by 2^{window size}, thus taking - // `c` bits. - let scalar = scalar.as_ref()[0] % (1 << c); - - // If the scalar is non-zero, we update the corresponding - // bucket. - // (Recall that `buckets` doesn't have a zero bucket.) - if scalar != 0 { - buckets[(scalar - 1) as usize] += base; - } - } - }); - - // Compute sum_{i in 0..num_buckets} (sum_{j in i..num_buckets} - // bucket[j]) This is computed below for b buckets, - // using 2b curve additions. - // - // We could first normalize `buckets` and then use mixed-addition - // here, but that's slower for the kinds of groups we care about - // (Short Weierstrass curves and Twisted Edwards curves). - // In the case of Short Weierstrass curves, - // mixed addition saves ~4 field multiplications per addition. - // However normalization (with the inversion batched) takes ~6 - // field multiplications per element, - // hence batch normalization is a slowdown. - - // `running_sum` = sum_{j in i..num_buckets} bucket[j], - // where we iterate backward from i = num_buckets to 0. - let mut running_sum = V::zero(); - buckets.into_iter().rev().for_each(|b| { - running_sum += &b; - res += &running_sum; - }); - res - }) - .collect(); - - // We store the sum for the lowest window. - let lowest = *window_sums.first().unwrap(); - - // We're traversing windows from high to low. - lowest - + &window_sums[1..].iter().rev().fold(zero, |mut total, sum_i| { - total += sum_i; - for _ in 0..c { - total.double_in_place(); - } - total - }) -} - -// From: https://github.com/arkworks-rs/gemini/blob/main/src/kzg/msm/variable_base.rs#L20 -fn make_digits( - a: &impl BigInteger, - w: usize, - num_bits: usize, -) -> impl Iterator + '_ { - let scalar = a.as_ref(); - let radix: u64 = 1 << w; - let window_mask: u64 = radix - 1; - - let mut carry = 0u64; - let num_bits = if num_bits == 0 { a.num_bits() as usize } else { num_bits }; - let digits_count = num_bits.div_ceil(w); - - (0..digits_count).map(move |i| { - // Construct a buffer of bits of the scalar, starting at `bit_offset`. - let bit_offset = i * w; - let u64_idx = bit_offset / 64; - let bit_idx = bit_offset % 64; - // Read the bits from the scalar - let bit_buf = if bit_idx < 64 - w || u64_idx == scalar.len() - 1 { - // This window's bits are contained in a single u64, - // or it's the last u64 anyway. - scalar[u64_idx] >> bit_idx - } else { - // Combine the current u64's bits with the bits from the next u64 - (scalar[u64_idx] >> bit_idx) - | (scalar[1 + u64_idx] << (64 - bit_idx)) - }; - - // Read the actual coefficient value from the window - let coef = carry + (bit_buf & window_mask); // coef = [0, 2^r) - - // Recenter coefficients from [0,2^w) to [-2^w/2, 2^w/2) - carry = (coef + radix / 2) >> w; - let mut digit = (coef as i64) - (carry << w) as i64; - - if i == digits_count - 1 { - digit += (carry << w) as i64; - } - digit - }) -} diff --git a/lib/crypto/src/curve/scalar_mul/variable_base/stream_pippenger.rs b/lib/crypto/src/curve/scalar_mul/variable_base/stream_pippenger.rs deleted file mode 100644 index f1d833713..000000000 --- a/lib/crypto/src/curve/scalar_mul/variable_base/stream_pippenger.rs +++ /dev/null @@ -1,132 +0,0 @@ -//! A space-efficient implementation of Pippenger's algorithm. -use ark_ff::{PrimeField, Zero}; -use ark_std::{borrow::Borrow, vec::*}; -use hashbrown::HashMap; - -use super::{DefaultHasher, VariableBaseMSM}; - -/// Struct for the chunked Pippenger algorithm. -pub struct ChunkedPippenger { - scalars_buffer: Vec<::BigInt>, - bases_buffer: Vec, - result: G, - buf_size: usize, -} - -impl ChunkedPippenger { - /// Initialize a chunked Pippenger instance with default parameters. - pub fn new(max_msm_buffer: usize) -> Self { - Self { - scalars_buffer: Vec::with_capacity(max_msm_buffer), - bases_buffer: Vec::with_capacity(max_msm_buffer), - result: G::zero(), - buf_size: max_msm_buffer, - } - } - - /// Initialize a chunked Pippenger instance with the given buffer size. - pub fn with_size(buf_size: usize) -> Self { - Self { - scalars_buffer: Vec::with_capacity(buf_size), - bases_buffer: Vec::with_capacity(buf_size), - result: G::zero(), - buf_size, - } - } - - /// Add a new (base, scalar) pair into the instance. - #[inline(always)] - pub fn add(&mut self, base: B, scalar: S) - where - B: Borrow, - S: Borrow<::BigInt>, - { - self.scalars_buffer.push(*scalar.borrow()); - self.bases_buffer.push(*base.borrow()); - if self.scalars_buffer.len() == self.buf_size { - self.result.add_assign(G::msm_bigint( - self.bases_buffer.as_slice(), - self.scalars_buffer.as_slice(), - )); - self.scalars_buffer.clear(); - self.bases_buffer.clear(); - } - } - - /// Output the final Pippenger algorithm result. - #[inline(always)] - pub fn finalize(mut self) -> G { - if !self.scalars_buffer.is_empty() { - self.result += G::msm_bigint( - self.bases_buffer.as_slice(), - self.scalars_buffer.as_slice(), - ); - } - self.result - } -} - -/// Hash map struct for Pippenger algorithm. -pub struct HashMapPippenger { - buffer: HashMap< - G::MulBase, - G::ScalarField, - core::hash::BuildHasherDefault, - >, - result: G, - buf_size: usize, -} - -impl HashMapPippenger { - /// Produce a new hash map with the maximum msm buffer size. - pub fn new(max_msm_buffer: usize) -> Self { - Self { - buffer: HashMap::with_capacity_and_hasher( - max_msm_buffer, - core::hash::BuildHasherDefault::default(), - ), - result: G::zero(), - buf_size: max_msm_buffer, - } - } - - /// Add a new (base, scalar) pair into the hash map. - #[inline(always)] - pub fn add(&mut self, base: B, scalar: S) - where - B: Borrow, - S: Borrow, - { - // update the entry, guarding the possibility that it has been already - // set. - let entry = - self.buffer.entry(*base.borrow()).or_insert(G::ScalarField::zero()); - *entry += *scalar.borrow(); - if self.buffer.len() == self.buf_size { - let bases = self.buffer.keys().cloned().collect::>(); - let scalars = self - .buffer - .values() - .map(|s| s.into_bigint()) - .collect::>(); - self.result += G::msm_bigint(&bases, &scalars); - self.buffer.clear(); - } - } - - /// Update the final result with (base, scalar) pairs in the hash map. - #[inline(always)] - pub fn finalize(mut self) -> G { - if !self.buffer.is_empty() { - let bases = self.buffer.keys().cloned().collect::>(); - let scalars = self - .buffer - .values() - .map(|s| s.into_bigint()) - .collect::>(); - - self.result += G::msm_bigint(&bases, &scalars); - } - self.result - } -} diff --git a/lib/crypto/src/curve/short_weierstrass/mod.rs b/lib/crypto/src/curve/short_weierstrass/mod.rs index 9476c6b53..9aa5f3b1b 100644 --- a/lib/crypto/src/curve/short_weierstrass/mod.rs +++ b/lib/crypto/src/curve/short_weierstrass/mod.rs @@ -8,10 +8,7 @@ pub use group::*; use crate::{ curve::{ - scalar_mul::{ - sw_double_and_add_affine, sw_double_and_add_projective, - variable_base::VariableBaseMSM, - }, + scalar_mul::{sw_double_and_add_affine, sw_double_and_add_projective}, AffineRepr, }, field::{group::AdditiveGroup, prime::PrimeField}, @@ -98,6 +95,7 @@ pub trait SWCurveConfig: super::CurveConfig { sw_double_and_add_affine(base, scalar) } + /* TODO#q: implement msm for short weierstrass curves /// Default implementation for multi scalar multiplication fn msm( bases: &[Affine], @@ -107,4 +105,5 @@ pub trait SWCurveConfig: super::CurveConfig { .then(|| VariableBaseMSM::msm_unchecked(bases, scalars)) .ok_or(bases.len().min(scalars.len())) } + */ } diff --git a/lib/crypto/src/curve/twisted_edwards/mod.rs b/lib/crypto/src/curve/twisted_edwards/mod.rs index 8a769ab6d..e021be51e 100644 --- a/lib/crypto/src/curve/twisted_edwards/mod.rs +++ b/lib/crypto/src/curve/twisted_edwards/mod.rs @@ -83,6 +83,7 @@ pub trait TECurveConfig: super::CurveConfig { res } + /* TODO#q: implement msm for twisted edwards curves /// Default implementation for multi scalar multiplication fn msm( bases: &[Affine], @@ -92,6 +93,7 @@ pub trait TECurveConfig: super::CurveConfig { .then(|| VariableBaseMSM::msm_unchecked(bases, scalars)) .ok_or(bases.len().min(scalars.len())) } + */ } /// Constants and convenience functions that collectively define the [Montgomery model](https://www.hyperelliptic.org/EFD/g1p/auto-montgom.html) From 7046684a1eef9dd81f840048dae4d29037fc95f9 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Thu, 13 Feb 2025 21:53:41 +0400 Subject: [PATCH 06/24] ++ --- lib/crypto/src/curve/short_weierstrass/affine.rs | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/lib/crypto/src/curve/short_weierstrass/affine.rs b/lib/crypto/src/curve/short_weierstrass/affine.rs index 39662c121..1900b6857 100644 --- a/lib/crypto/src/curve/short_weierstrass/affine.rs +++ b/lib/crypto/src/curve/short_weierstrass/affine.rs @@ -8,7 +8,7 @@ use educe::Educe; use num_traits::{real::Real, One, Zero}; use zeroize::Zeroize; -use super::{Projective, SWCurveConfig, SWFlags}; +use super::{Projective, SWCurveConfig}; use crate::{ curve::AffineRepr, field::{group::AdditiveGroup, prime::PrimeField, Field}, @@ -137,16 +137,6 @@ impl Affine

{ true } } - - pub fn to_flags(&self) -> SWFlags { - if self.infinity { - SWFlags::PointAtInfinity - } else if self.y <= -self.y { - SWFlags::YIsPositive - } else { - SWFlags::YIsNegative - } - } } impl Affine

{ From 3b9119f7c24611c5566fec3c9e72d9476a15750a Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Thu, 13 Feb 2025 22:02:16 +0400 Subject: [PATCH 07/24] ++ --- lib/crypto/src/curve/twisted_edwards/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/crypto/src/curve/twisted_edwards/mod.rs b/lib/crypto/src/curve/twisted_edwards/mod.rs index e021be51e..d08e7fb84 100644 --- a/lib/crypto/src/curve/twisted_edwards/mod.rs +++ b/lib/crypto/src/curve/twisted_edwards/mod.rs @@ -7,7 +7,7 @@ mod group; pub use group::*; use crate::{ - curve::{scalar_mul::variable_base::VariableBaseMSM, AffineRepr}, + curve::AffineRepr, field::{group::AdditiveGroup, prime::PrimeField}, }; From d4601240bd0a77b473dba8d94e833157341caf0f Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Thu, 13 Feb 2025 22:31:54 +0400 Subject: [PATCH 08/24] ++ --- lib/crypto/src/curve/mod.rs | 5 ----- .../src/curve/short_weierstrass/affine.rs | 18 ------------------ lib/crypto/src/curve/twisted_edwards/affine.rs | 12 ++---------- 3 files changed, 2 insertions(+), 33 deletions(-) diff --git a/lib/crypto/src/curve/mod.rs b/lib/crypto/src/curve/mod.rs index 6122cdb9e..a7cc76716 100644 --- a/lib/crypto/src/curve/mod.rs +++ b/lib/crypto/src/curve/mod.rs @@ -194,11 +194,6 @@ pub trait AffineRepr: self.into() } - /// Returns a group element if the set of bytes forms a valid group element, - /// otherwise returns None. This function is primarily intended for sampling - /// random group elements from a hash-function or RNG output. - fn from_random_bytes(bytes: &[u8]) -> Option; - /// Performs scalar multiplication of this element with mixed addition. #[must_use] fn mul_bigint(&self, by: impl AsRef<[u64]>) -> Self::Group; diff --git a/lib/crypto/src/curve/short_weierstrass/affine.rs b/lib/crypto/src/curve/short_weierstrass/affine.rs index 1900b6857..cb20f9376 100644 --- a/lib/crypto/src/curve/short_weierstrass/affine.rs +++ b/lib/crypto/src/curve/short_weierstrass/affine.rs @@ -177,24 +177,6 @@ impl AffineRepr for Affine

{ Self { x: P::BaseField::ZERO, y: P::BaseField::ZERO, infinity: true } } - fn from_random_bytes(bytes: &[u8]) -> Option { - P::BaseField::from_random_bytes_with_flags::(bytes).and_then( - |(x, flags)| { - // if x is valid and is zero and only the infinity flag is set, - // then parse this point as infinity. For all - // other choices, get the original point. - if x.is_zero() && flags.is_infinity() { - Some(Self::identity()) - } else if let Some(y_is_positive) = flags.is_positive() { - Self::get_point_from_x_unchecked(x, y_is_positive) - // Unwrap is safe because it's not zero. - } else { - None - } - }, - ) - } - fn mul_bigint(&self, by: impl AsRef<[u64]>) -> Self::Group { P::mul_affine(self, by.as_ref()) } diff --git a/lib/crypto/src/curve/twisted_edwards/affine.rs b/lib/crypto/src/curve/twisted_edwards/affine.rs index 12360e3e8..3d239b5c9 100644 --- a/lib/crypto/src/curve/twisted_edwards/affine.rs +++ b/lib/crypto/src/curve/twisted_edwards/affine.rs @@ -8,10 +8,10 @@ use educe::Educe; use num_traits::{One, Zero}; use zeroize::Zeroize; -use super::{Projective, TECurveConfig, TEFlags}; +use super::{Projective, TECurveConfig}; use crate::{ curve::AffineRepr, - field::{group::AdditiveGroup, Field}, + field::{group::AdditiveGroup, prime::PrimeField, Field}, }; /// Affine coordinates for a point on a twisted Edwards curve, over the @@ -171,14 +171,6 @@ impl AffineRepr for Affine

{ Self::new_unchecked(P::BaseField::ZERO, P::BaseField::ONE) } - fn from_random_bytes(bytes: &[u8]) -> Option { - P::BaseField::from_random_bytes_with_flags::(bytes).and_then( - |(y, flags)| { - Self::get_point_from_y_unchecked(y, flags.is_negative()) - }, - ) - } - fn mul_bigint(&self, by: impl AsRef<[u64]>) -> Self::Group { P::mul_affine(self, by.as_ref()) } From cb8c3650a6af09daf7c560e649e6f93c6a198eff Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Thu, 13 Feb 2025 22:35:13 +0400 Subject: [PATCH 09/24] ++ --- lib/crypto/src/curve/mod.rs | 12 ++++++------ lib/crypto/src/curve/twisted_edwards/affine.rs | 2 +- lib/crypto/src/curve/twisted_edwards/group.rs | 2 -- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/lib/crypto/src/curve/mod.rs b/lib/crypto/src/curve/mod.rs index a7cc76716..4b47029c8 100644 --- a/lib/crypto/src/curve/mod.rs +++ b/lib/crypto/src/curve/mod.rs @@ -1,13 +1,13 @@ -use core::fmt::{Debug, Display}; -use std::ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign}; +use core::{ + fmt::{Debug, Display}, + hash::Hash, + ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign}, +}; use num_traits::Zero; use zeroize::Zeroize; -use crate::{ - field::{group::AdditiveGroup, prime::PrimeField, Field}, - hash::Hash, -}; +use crate::field::{group::AdditiveGroup, prime::PrimeField, Field}; pub mod scalar_mul; pub mod short_weierstrass; diff --git a/lib/crypto/src/curve/twisted_edwards/affine.rs b/lib/crypto/src/curve/twisted_edwards/affine.rs index 3d239b5c9..4f68613bf 100644 --- a/lib/crypto/src/curve/twisted_edwards/affine.rs +++ b/lib/crypto/src/curve/twisted_edwards/affine.rs @@ -1,8 +1,8 @@ use core::{ borrow::Borrow, fmt::{Debug, Display, Formatter}, + ops::{Add, Mul, Neg, Sub}, }; -use std::ops::{Add, Mul, Neg, Sub}; use educe::Educe; use num_traits::{One, Zero}; diff --git a/lib/crypto/src/curve/twisted_edwards/group.rs b/lib/crypto/src/curve/twisted_edwards/group.rs index b03f2f688..488964f42 100644 --- a/lib/crypto/src/curve/twisted_edwards/group.rs +++ b/lib/crypto/src/curve/twisted_edwards/group.rs @@ -1,8 +1,6 @@ use core::{ borrow::Borrow, fmt::{Display, Formatter}, -}; -use std::{ hash::{Hash, Hasher}, ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign}, }; From 0b16b1cb9f0d68511befe9123d651d90e3d36ed0 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Thu, 13 Feb 2025 22:45:52 +0400 Subject: [PATCH 10/24] ++ --- lib/crypto/src/curve/helpers.rs | 168 ++++++++++++++++++ lib/crypto/src/curve/mod.rs | 1 + .../src/curve/short_weierstrass/group.rs | 3 +- lib/crypto/src/curve/twisted_edwards/group.rs | 4 +- 4 files changed, 174 insertions(+), 2 deletions(-) create mode 100644 lib/crypto/src/curve/helpers.rs diff --git a/lib/crypto/src/curve/helpers.rs b/lib/crypto/src/curve/helpers.rs new file mode 100644 index 000000000..f242a58d1 --- /dev/null +++ b/lib/crypto/src/curve/helpers.rs @@ -0,0 +1,168 @@ +/// Implements AddAssign on Self by deferring to an implementation on &Self +#[macro_export] +macro_rules! impl_additive_ops_from_ref { + ($type:ident, $params:ident) => { + #[allow(unused_qualifications)] + impl core::ops::Add for $type

{ + type Output = Self; + + #[inline] + fn add(self, other: Self) -> Self { + let mut result = self; + result += &other; + result + } + } + + #[allow(unused_qualifications)] + impl<'a, P: $params> core::ops::Add<&'a mut Self> for $type

{ + type Output = Self; + + #[inline] + fn add(self, other: &'a mut Self) -> Self { + let mut result = self; + result += &*other; + result + } + } + + impl<'b, P: $params> core::ops::Add<$type

> for &'b $type

{ + type Output = $type

; + + #[inline] + fn add(self, mut other: $type

) -> $type

{ + other += self; + other + } + } + + #[allow(unused_qualifications)] + impl<'a, 'b, P: $params> core::ops::Add<&'a $type

> for &'b $type

{ + type Output = $type

; + + #[inline] + fn add(self, other: &'a $type

) -> $type

{ + let mut result = *self; + result += &*other; + result + } + } + + #[allow(unused_qualifications)] + impl<'a, 'b, P: $params> core::ops::Add<&'a mut $type

> + for &'b $type

+ { + type Output = $type

; + + #[inline] + fn add(self, other: &'a mut $type

) -> $type

{ + let mut result = *self; + result += &*other; + result + } + } + + impl<'b, P: $params> core::ops::Sub<$type

> for &'b $type

{ + type Output = $type

; + + #[inline] + fn sub(self, other: $type

) -> $type

{ + let mut result = *self; + result -= &other; + result + } + } + + #[allow(unused_qualifications)] + impl<'a, 'b, P: $params> core::ops::Sub<&'a $type

> for &'b $type

{ + type Output = $type

; + + #[inline] + fn sub(self, other: &'a $type

) -> $type

{ + let mut result = *self; + result -= &*other; + result + } + } + + #[allow(unused_qualifications)] + impl<'a, 'b, P: $params> core::ops::Sub<&'a mut $type

> + for &'b $type

+ { + type Output = $type

; + + #[inline] + fn sub(self, other: &'a mut $type

) -> $type

{ + let mut result = *self; + result -= &*other; + result + } + } + + #[allow(unused_qualifications)] + impl core::ops::Sub for $type

{ + type Output = Self; + + #[inline] + fn sub(self, other: Self) -> Self { + let mut result = self; + result -= &other; + result + } + } + + #[allow(unused_qualifications)] + impl<'a, P: $params> core::ops::Sub<&'a mut Self> for $type

{ + type Output = Self; + + #[inline] + fn sub(self, other: &'a mut Self) -> Self { + let mut result = self; + result -= &*other; + result + } + } + + #[allow(unused_qualifications)] + impl core::iter::Sum for $type

{ + fn sum>(iter: I) -> Self { + iter.fold(Self::zero(), core::ops::Add::add) + } + } + + #[allow(unused_qualifications)] + impl<'a, P: $params> core::iter::Sum<&'a Self> for $type

{ + fn sum>(iter: I) -> Self { + iter.fold(Self::zero(), core::ops::Add::add) + } + } + + #[allow(unused_qualifications)] + impl core::ops::AddAssign for $type

{ + fn add_assign(&mut self, other: Self) { + *self += &other + } + } + + #[allow(unused_qualifications)] + impl core::ops::SubAssign for $type

{ + fn sub_assign(&mut self, other: Self) { + *self -= &other + } + } + + #[allow(unused_qualifications)] + impl<'a, P: $params> core::ops::AddAssign<&'a mut Self> for $type

{ + fn add_assign(&mut self, other: &'a mut Self) { + *self += &*other + } + } + + #[allow(unused_qualifications)] + impl<'a, P: $params> core::ops::SubAssign<&'a mut Self> for $type

{ + fn sub_assign(&mut self, other: &'a mut Self) { + *self -= &*other + } + } + }; +} diff --git a/lib/crypto/src/curve/mod.rs b/lib/crypto/src/curve/mod.rs index 4b47029c8..89232041a 100644 --- a/lib/crypto/src/curve/mod.rs +++ b/lib/crypto/src/curve/mod.rs @@ -9,6 +9,7 @@ use zeroize::Zeroize; use crate::field::{group::AdditiveGroup, prime::PrimeField, Field}; +mod helpers; pub mod scalar_mul; pub mod short_weierstrass; pub mod twisted_edwards; diff --git a/lib/crypto/src/curve/short_weierstrass/group.rs b/lib/crypto/src/curve/short_weierstrass/group.rs index 8075ed17d..c69591bdc 100644 --- a/lib/crypto/src/curve/short_weierstrass/group.rs +++ b/lib/crypto/src/curve/short_weierstrass/group.rs @@ -13,6 +13,7 @@ use super::{Affine, SWCurveConfig}; use crate::{ curve::{AffineRepr, CurveGroup, PrimeGroup}, field::{group::AdditiveGroup, prime::PrimeField, Field}, + impl_additive_ops_from_ref, }; /// Jacobian coordinates for a point on an elliptic curve in short Weierstrass @@ -427,7 +428,7 @@ impl>> Sub for Projective

{ // TODO#q: Add auto derive of multiplication operations // Implements AddAssign on Self by deferring to an implementation on &Self -ark_ff::impl_additive_ops_from_ref!(Projective, SWCurveConfig); +impl_additive_ops_from_ref!(Projective, SWCurveConfig); impl<'a, P: SWCurveConfig> Add<&'a Self> for Projective

{ type Output = Self; diff --git a/lib/crypto/src/curve/twisted_edwards/group.rs b/lib/crypto/src/curve/twisted_edwards/group.rs index 488964f42..f13a2a890 100644 --- a/lib/crypto/src/curve/twisted_edwards/group.rs +++ b/lib/crypto/src/curve/twisted_edwards/group.rs @@ -13,6 +13,7 @@ use super::{Affine, MontCurveConfig, TECurveConfig}; use crate::{ curve::{AffineRepr, CurveGroup, PrimeGroup}, field::{group::AdditiveGroup, prime::PrimeField, Field}, + impl_additive_ops_from_ref, }; /// `Projective` implements Extended Twisted Edwards Coordinates @@ -287,7 +288,8 @@ impl>> Sub for Projective

{ self } } -ark_ff::impl_additive_ops_from_ref!(Projective, TECurveConfig); + +impl_additive_ops_from_ref!(Projective, TECurveConfig); impl<'a, P: TECurveConfig> Add<&'a Self> for Projective

{ type Output = Self; From 80fbc2413787c624ebf8002e5f595e72e2420721 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Fri, 14 Feb 2025 16:19:30 +0400 Subject: [PATCH 11/24] ++ --- lib/crypto/src/curve/mod.rs | 44 ++++++++++++++++++++----------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/lib/crypto/src/curve/mod.rs b/lib/crypto/src/curve/mod.rs index 89232041a..a674ec8b6 100644 --- a/lib/crypto/src/curve/mod.rs +++ b/lib/crypto/src/curve/mod.rs @@ -27,7 +27,7 @@ pub trait CurveConfig: Send + Sync + Sized + 'static { /// Finite prime field corresponding to an appropriate prime-order subgroup /// of the curve group. type ScalarField: PrimeField - + Into<::BigInt>; + + Into<::BigInt>; // TODO#q: we don't need this additional generic restriction Into /// The cofactor of this curve, represented as a sequence of little-endian /// limbs. @@ -72,33 +72,35 @@ pub trait PrimeGroup: AdditiveGroup { /// /// The point is guaranteed to be in the correct prime order subgroup. pub trait CurveGroup: - PrimeGroup - + Add - + AddAssign - + Sub - + SubAssign +PrimeGroup ++ Add ++ AddAssign ++ Sub ++ SubAssign // TODO#q: think to replace VariableBaseMSM and ScalarMul - // + VariableBaseMSM - // + ScalarMul - + From - + Into - + core::iter::Sum - + for<'a> core::iter::Sum<&'a Self::Affine> +// + VariableBaseMSM +// + ScalarMul ++ From ++ Into ++ core::iter::Sum ++ for<'a> core::iter::Sum<&'a Self::Affine> { type Config: CurveConfig< - ScalarField = Self::ScalarField, - BaseField = Self::BaseField, + ScalarField=Self::ScalarField, + BaseField=Self::BaseField, >; + /// The field over which this curve is defined. type BaseField: Field; + /// The affine representation of this element. type Affine: AffineRepr< - Config = Self::Config, - Group = Self, - ScalarField = Self::ScalarField, - BaseField = Self::BaseField, - > + From - + Into; + Config=Self::Config, + Group=Self, + ScalarField=Self::ScalarField, + BaseField=Self::BaseField, + > + From + + Into; /// Type representing an element of the full elliptic curve group, not just /// the prime order subgroup. @@ -150,8 +152,10 @@ pub trait AffineRepr: ScalarField = Self::ScalarField, BaseField = Self::BaseField, >; + type ScalarField: PrimeField + Into<::BigInt>; + /// The finite field over which this curve is defined. type BaseField: Field; From 50e45317156fdfe04eeccc8229b2dbcbc943ac20 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Mon, 17 Feb 2025 23:11:49 +0400 Subject: [PATCH 12/24] ++ --- lib/crypto/src/field/mod.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/lib/crypto/src/field/mod.rs b/lib/crypto/src/field/mod.rs index 4b64eaa5b..ca10e2222 100644 --- a/lib/crypto/src/field/mod.rs +++ b/lib/crypto/src/field/mod.rs @@ -151,4 +151,17 @@ pub trait Field: // If res is empty, return one. res.unwrap_or(Self::ONE) } + + // TODO#q: add sum of products + /* + /// Returns `sum([a_i * b_i])`. + #[inline] + fn sum_of_products(a: &[Self; T], b: &[Self; T]) -> Self { + let mut sum = Self::zero(); + for i in 0..a.len() { + sum += a[i] * b[i]; + } + sum + } + */ } From 1e7cc0b674abe68491829034baa4abf5cc684767 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Tue, 18 Feb 2025 00:51:27 +0400 Subject: [PATCH 13/24] ++ --- lib/crypto/src/curve/short_weierstrass/affine.rs | 2 +- lib/crypto/src/curve/twisted_edwards/affine.rs | 2 +- lib/crypto/src/field/mod.rs | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/crypto/src/curve/short_weierstrass/affine.rs b/lib/crypto/src/curve/short_weierstrass/affine.rs index cb20f9376..e00078c04 100644 --- a/lib/crypto/src/curve/short_weierstrass/affine.rs +++ b/lib/crypto/src/curve/short_weierstrass/affine.rs @@ -116,7 +116,7 @@ impl Affine

{ if !P::COEFF_A.is_zero() { x3_plus_ax_plus_b += P::mul_by_a(x) }; - let y = x3_plus_ax_plus_b.sqrt()?; + let y = x3_plus_ax_plus_b.sqrt()?; // TODO#q: add sqrt or remove it let neg_y = -y; match y < neg_y { true => Some((y, neg_y)), diff --git a/lib/crypto/src/curve/twisted_edwards/affine.rs b/lib/crypto/src/curve/twisted_edwards/affine.rs index 4f68613bf..100e40895 100644 --- a/lib/crypto/src/curve/twisted_edwards/affine.rs +++ b/lib/crypto/src/curve/twisted_edwards/affine.rs @@ -122,7 +122,7 @@ impl Affine

{ denominator .inverse() .map(|denom| denom * &numerator) - .and_then(|x2| x2.sqrt()) + .and_then(|x2| x2.sqrt()) // TODO#q: add sqrt or remove it .map(|x| { let neg_x = -x; if x <= neg_x { diff --git a/lib/crypto/src/field/mod.rs b/lib/crypto/src/field/mod.rs index ca10e2222..34d31f795 100644 --- a/lib/crypto/src/field/mod.rs +++ b/lib/crypto/src/field/mod.rs @@ -164,4 +164,6 @@ pub trait Field: sum } */ + + // TODO#q: add sqrt? } From 9acaeb414cb528ed4cf008f4a8468720770f345e Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Tue, 18 Feb 2025 14:39:09 +0400 Subject: [PATCH 14/24] ++ --- lib/crypto/src/curve/helpers.rs | 1 + lib/crypto/src/curve/scalar_mul/mod.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/lib/crypto/src/curve/helpers.rs b/lib/crypto/src/curve/helpers.rs index f242a58d1..d032b61a3 100644 --- a/lib/crypto/src/curve/helpers.rs +++ b/lib/crypto/src/curve/helpers.rs @@ -1,3 +1,4 @@ +// TODO#q: probably rename `impl_additive_ops_from_ref` and add same for field /// Implements AddAssign on Self by deferring to an implementation on &Self #[macro_export] macro_rules! impl_additive_ops_from_ref { diff --git a/lib/crypto/src/curve/scalar_mul/mod.rs b/lib/crypto/src/curve/scalar_mul/mod.rs index 44039b710..27eadec12 100644 --- a/lib/crypto/src/curve/scalar_mul/mod.rs +++ b/lib/crypto/src/curve/scalar_mul/mod.rs @@ -4,6 +4,7 @@ use crate::{ curve::short_weierstrass::{Affine, Projective, SWCurveConfig}, field::group::AdditiveGroup, }; +// TODO#q: move scalar_mul to curve module /// Standard double-and-add method for multiplication by a scalar. #[inline(always)] From 620fa07ee2dc209be62fbccadf79a07d9a814293 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Wed, 19 Feb 2025 15:11:48 +0400 Subject: [PATCH 15/24] remove get_point_from_x_unchecked from affine repr --- .../src/curve/short_weierstrass/affine.rs | 47 --------------- .../src/curve/twisted_edwards/affine.rs | 57 ------------------- 2 files changed, 104 deletions(-) diff --git a/lib/crypto/src/curve/short_weierstrass/affine.rs b/lib/crypto/src/curve/short_weierstrass/affine.rs index e00078c04..6e071f1eb 100644 --- a/lib/crypto/src/curve/short_weierstrass/affine.rs +++ b/lib/crypto/src/curve/short_weierstrass/affine.rs @@ -77,53 +77,6 @@ impl Affine

{ Self { x: P::BaseField::ZERO, y: P::BaseField::ZERO, infinity: true } } - /// Attempts to construct an affine point given an x-coordinate. The - /// point is not guaranteed to be in the prime order subgroup. - /// - /// If and only if `greatest` is set will the lexicographically - /// largest y-coordinate be selected. - #[allow(dead_code)] - pub fn get_point_from_x_unchecked( - x: P::BaseField, - greatest: bool, - ) -> Option { - Self::get_ys_from_x_unchecked(x).map(|(smaller, larger)| { - if greatest { - Self::new_unchecked(x, larger) - } else { - Self::new_unchecked(x, smaller) - } - }) - } - - /// Returns the two possible y-coordinates corresponding to the given - /// x-coordinate. The corresponding points are not guaranteed to be in - /// the prime-order subgroup, but are guaranteed to be on the curve. - /// That is, this method returns `None` if the x-coordinate corresponds - /// to a non-curve point. - /// - /// The results are sorted by lexicographical order. - /// This means that, if `P::BaseField: PrimeField`, the results are sorted - /// as integers. - pub fn get_ys_from_x_unchecked( - x: P::BaseField, - ) -> Option<(P::BaseField, P::BaseField)> { - // Compute the curve equation x^3 + Ax + B. - // Since Rust does not optimise away additions with zero, we explicitly - // check for that case here, and avoid multiplication by `a` if - // possible. - let mut x3_plus_ax_plus_b = P::add_b(x.square() * x); - if !P::COEFF_A.is_zero() { - x3_plus_ax_plus_b += P::mul_by_a(x) - }; - let y = x3_plus_ax_plus_b.sqrt()?; // TODO#q: add sqrt or remove it - let neg_y = -y; - match y < neg_y { - true => Some((y, neg_y)), - false => Some((neg_y, y)), - } - } - /// Checks if `self` is a valid point on the curve. pub fn is_on_curve(&self) -> bool { if !self.infinity { diff --git a/lib/crypto/src/curve/twisted_edwards/affine.rs b/lib/crypto/src/curve/twisted_edwards/affine.rs index 100e40895..2d5600337 100644 --- a/lib/crypto/src/curve/twisted_edwards/affine.rs +++ b/lib/crypto/src/curve/twisted_edwards/affine.rs @@ -76,63 +76,6 @@ impl Affine

{ self.x.is_zero() && self.y.is_one() } - /// Attempts to construct an affine point given an y-coordinate. The - /// point is not guaranteed to be in the prime order subgroup. - /// - /// If and only if `greatest` is set will the lexicographically - /// largest x-coordinate be selected. - /// - /// a * X^2 + Y^2 = 1 + d * X^2 * Y^2 - /// a * X^2 - d * X^2 * Y^2 = 1 - Y^2 - /// X^2 * (a - d * Y^2) = 1 - Y^2 - /// X^2 = (1 - Y^2) / (a - d * Y^2) - #[allow(dead_code)] - pub fn get_point_from_y_unchecked( - y: P::BaseField, - greatest: bool, - ) -> Option { - Self::get_xs_from_y_unchecked(y).map(|(x, neg_x)| { - if greatest { - Self::new_unchecked(neg_x, y) - } else { - Self::new_unchecked(x, y) - } - }) - } - - /// Attempts to recover the x-coordinate given an y-coordinate. The - /// resulting point is not guaranteed to be in the prime order subgroup. - /// - /// If and only if `greatest` is set will the lexicographically - /// largest x-coordinate be selected. - /// - /// a * X^2 + Y^2 = 1 + d * X^2 * Y^2 - /// a * X^2 - d * X^2 * Y^2 = 1 - Y^2 - /// X^2 * (a - d * Y^2) = 1 - Y^2 - /// X^2 = (1 - Y^2) / (a - d * Y^2) - #[allow(dead_code)] - pub fn get_xs_from_y_unchecked( - y: P::BaseField, - ) -> Option<(P::BaseField, P::BaseField)> { - let y2 = y.square(); - - let numerator = P::BaseField::one() - y2; - let denominator = P::COEFF_A - (y2 * P::COEFF_D); - - denominator - .inverse() - .map(|denom| denom * &numerator) - .and_then(|x2| x2.sqrt()) // TODO#q: add sqrt or remove it - .map(|x| { - let neg_x = -x; - if x <= neg_x { - (x, neg_x) - } else { - (neg_x, x) - } - }) - } - /// Checks that the current point is on the elliptic curve. pub fn is_on_curve(&self) -> bool { let x2 = self.x.square(); From 036e48ea9854a8724177f49e7a9513ebea9853c2 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Wed, 19 Feb 2025 15:17:21 +0400 Subject: [PATCH 16/24] move sw_double_and_add_affine and sw_double_and_add_projective to consumers --- lib/crypto/src/curve/mod.rs | 1 - lib/crypto/src/curve/scalar_mul/mod.rs | 41 ------------------- lib/crypto/src/curve/short_weierstrass/mod.rs | 39 ++++++++++++++++-- 3 files changed, 35 insertions(+), 46 deletions(-) delete mode 100644 lib/crypto/src/curve/scalar_mul/mod.rs diff --git a/lib/crypto/src/curve/mod.rs b/lib/crypto/src/curve/mod.rs index a674ec8b6..71c1d2ab3 100644 --- a/lib/crypto/src/curve/mod.rs +++ b/lib/crypto/src/curve/mod.rs @@ -10,7 +10,6 @@ use zeroize::Zeroize; use crate::field::{group::AdditiveGroup, prime::PrimeField, Field}; mod helpers; -pub mod scalar_mul; pub mod short_weierstrass; pub mod twisted_edwards; diff --git a/lib/crypto/src/curve/scalar_mul/mod.rs b/lib/crypto/src/curve/scalar_mul/mod.rs deleted file mode 100644 index 27eadec12..000000000 --- a/lib/crypto/src/curve/scalar_mul/mod.rs +++ /dev/null @@ -1,41 +0,0 @@ -use num_traits::Zero; - -use crate::{ - curve::short_weierstrass::{Affine, Projective, SWCurveConfig}, - field::group::AdditiveGroup, -}; -// TODO#q: move scalar_mul to curve module - -/// Standard double-and-add method for multiplication by a scalar. -#[inline(always)] -pub fn sw_double_and_add_affine( - base: &Affine

, - scalar: impl AsRef<[u64]>, -) -> Projective

{ - let mut res = Projective::zero(); - for b in ark_ff::BitIteratorBE::without_leading_zeros(scalar) { - res.double_in_place(); - if b { - res += base - } - } - - res -} - -/// Standard double-and-add method for multiplication by a scalar. -#[inline(always)] -pub fn sw_double_and_add_projective( - base: &Projective

, - scalar: impl AsRef<[u64]>, -) -> Projective

{ - let mut res = Projective::zero(); - for b in ark_ff::BitIteratorBE::without_leading_zeros(scalar) { - res.double_in_place(); - if b { - res += base - } - } - - res -} diff --git a/lib/crypto/src/curve/short_weierstrass/mod.rs b/lib/crypto/src/curve/short_weierstrass/mod.rs index 9aa5f3b1b..49a05a244 100644 --- a/lib/crypto/src/curve/short_weierstrass/mod.rs +++ b/lib/crypto/src/curve/short_weierstrass/mod.rs @@ -7,10 +7,7 @@ mod group; pub use group::*; use crate::{ - curve::{ - scalar_mul::{sw_double_and_add_affine, sw_double_and_add_projective}, - AffineRepr, - }, + curve::AffineRepr, field::{group::AdditiveGroup, prime::PrimeField}, }; @@ -107,3 +104,37 @@ pub trait SWCurveConfig: super::CurveConfig { } */ } + +/// Standard double-and-add method for multiplication by a scalar. +#[inline(always)] +pub fn sw_double_and_add_affine( + base: &Affine

, + scalar: impl AsRef<[u64]>, +) -> Projective

{ + let mut res = Projective::zero(); + for b in ark_ff::BitIteratorBE::without_leading_zeros(scalar) { + res.double_in_place(); + if b { + res += base + } + } + + res +} + +/// Standard double-and-add method for multiplication by a scalar. +#[inline(always)] +pub fn sw_double_and_add_projective( + base: &Projective

, + scalar: impl AsRef<[u64]>, +) -> Projective

{ + let mut res = Projective::zero(); + for b in ark_ff::BitIteratorBE::without_leading_zeros(scalar) { + res.double_in_place(); + if b { + res += base + } + } + + res +} From 608ed0cca822a84379e38d9af1dc256330f7356e Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Wed, 19 Feb 2025 16:03:57 +0400 Subject: [PATCH 17/24] use BitIteratorBE --- lib/crypto/src/arithmetic/mod.rs | 3 +++ lib/crypto/src/curve/mod.rs | 9 ++++++--- lib/crypto/src/curve/short_weierstrass/affine.rs | 5 +++-- lib/crypto/src/curve/short_weierstrass/group.rs | 7 ++++--- lib/crypto/src/curve/short_weierstrass/mod.rs | 16 ++++++++++------ lib/crypto/src/curve/twisted_edwards/affine.rs | 5 +++-- lib/crypto/src/curve/twisted_edwards/group.rs | 5 +++-- lib/crypto/src/curve/twisted_edwards/mod.rs | 12 ++++++++---- 8 files changed, 40 insertions(+), 22 deletions(-) diff --git a/lib/crypto/src/arithmetic/mod.rs b/lib/crypto/src/arithmetic/mod.rs index 635b424cb..1ed2c56b2 100644 --- a/lib/crypto/src/arithmetic/mod.rs +++ b/lib/crypto/src/arithmetic/mod.rs @@ -12,6 +12,8 @@ use core::{ use limb::Limb; use zeroize::Zeroize; +use crate::bits::BitIteratorBE; + /// Defines a big integer with a constant length. pub trait BigInteger: 'static @@ -43,6 +45,7 @@ pub trait BigInteger: + for<'a> BitOrAssign<&'a Self> + BitOr + for<'a> BitOr<&'a Self, Output = Self> + + BitIteratorBE { /// Number of `usize` limbs representing `Self`. const NUM_LIMBS: usize; diff --git a/lib/crypto/src/curve/mod.rs b/lib/crypto/src/curve/mod.rs index 71c1d2ab3..0f4cf3d22 100644 --- a/lib/crypto/src/curve/mod.rs +++ b/lib/crypto/src/curve/mod.rs @@ -7,7 +7,10 @@ use core::{ use num_traits::Zero; use zeroize::Zeroize; -use crate::field::{group::AdditiveGroup, prime::PrimeField, Field}; +use crate::{ + bits::BitIteratorBE, + field::{group::AdditiveGroup, prime::PrimeField, Field}, +}; mod helpers; pub mod short_weierstrass; @@ -49,7 +52,7 @@ pub trait PrimeGroup: AdditiveGroup { fn generator() -> Self; /// Performs scalar multiplication of this element. - fn mul_bigint(&self, other: impl AsRef<[u64]>) -> Self; + fn mul_bigint(&self, other: impl BitIteratorBE) -> Self; /// Computes `other * self`, where `other` is a *big-endian* /// bit representation of some integer. @@ -200,7 +203,7 @@ pub trait AffineRepr: /// Performs scalar multiplication of this element with mixed addition. #[must_use] - fn mul_bigint(&self, by: impl AsRef<[u64]>) -> Self::Group; + fn mul_bigint(&self, by: impl BitIteratorBE) -> Self::Group; /// Performs cofactor clearing. /// The default method is simply to multiply by the cofactor. diff --git a/lib/crypto/src/curve/short_weierstrass/affine.rs b/lib/crypto/src/curve/short_weierstrass/affine.rs index 6e071f1eb..0a4654664 100644 --- a/lib/crypto/src/curve/short_weierstrass/affine.rs +++ b/lib/crypto/src/curve/short_weierstrass/affine.rs @@ -10,6 +10,7 @@ use zeroize::Zeroize; use super::{Projective, SWCurveConfig}; use crate::{ + bits::BitIteratorBE, curve::AffineRepr, field::{group::AdditiveGroup, prime::PrimeField, Field}, }; @@ -130,8 +131,8 @@ impl AffineRepr for Affine

{ Self { x: P::BaseField::ZERO, y: P::BaseField::ZERO, infinity: true } } - fn mul_bigint(&self, by: impl AsRef<[u64]>) -> Self::Group { - P::mul_affine(self, by.as_ref()) + fn mul_bigint(&self, by: impl BitIteratorBE) -> Self::Group { + P::mul_affine(self, by) } /// Multiplies this element by the cofactor and output the diff --git a/lib/crypto/src/curve/short_weierstrass/group.rs b/lib/crypto/src/curve/short_weierstrass/group.rs index c69591bdc..94870b4d1 100644 --- a/lib/crypto/src/curve/short_weierstrass/group.rs +++ b/lib/crypto/src/curve/short_weierstrass/group.rs @@ -11,6 +11,7 @@ use zeroize::Zeroize; use super::{Affine, SWCurveConfig}; use crate::{ + bits::BitIteratorBE, curve::{AffineRepr, CurveGroup, PrimeGroup}, field::{group::AdditiveGroup, prime::PrimeField, Field}, impl_additive_ops_from_ref, @@ -172,7 +173,7 @@ impl AdditiveGroup for Projective

{ // D = 2*((X1+B)^2-A-C) // = 2 * (X1 + Y1^2)^2 - A - C // = 2 * 2 * X1 * Y1^2 - // TODO#q: probably use different trait for extension_degree + // TODO#q: use different trait for extension_degree let d = if [1, 2].contains(&P::BaseField::extension_degree()) { let mut d = self.x; d *= &b; @@ -264,8 +265,8 @@ impl PrimeGroup for Projective

{ } #[inline] - fn mul_bigint(&self, other: impl AsRef<[u64]>) -> Self { - P::mul_projective(self, other.as_ref()) + fn mul_bigint(&self, other: impl BitIteratorBE) -> Self { + P::mul_projective(self, other) } } diff --git a/lib/crypto/src/curve/short_weierstrass/mod.rs b/lib/crypto/src/curve/short_weierstrass/mod.rs index 49a05a244..3585921d7 100644 --- a/lib/crypto/src/curve/short_weierstrass/mod.rs +++ b/lib/crypto/src/curve/short_weierstrass/mod.rs @@ -7,6 +7,7 @@ mod group; pub use group::*; use crate::{ + bits::BitIteratorBE, curve::AffineRepr, field::{group::AdditiveGroup, prime::PrimeField}, }; @@ -81,14 +82,17 @@ pub trait SWCurveConfig: super::CurveConfig { /// coordinates fn mul_projective( base: &Projective, - scalar: &[u64], + scalar: impl BitIteratorBE, ) -> Projective { sw_double_and_add_projective(base, scalar) } /// Default implementation of group multiplication for affine /// coordinates. - fn mul_affine(base: &Affine, scalar: &[u64]) -> Projective { + fn mul_affine( + base: &Affine, + scalar: impl BitIteratorBE, + ) -> Projective { sw_double_and_add_affine(base, scalar) } @@ -109,10 +113,10 @@ pub trait SWCurveConfig: super::CurveConfig { #[inline(always)] pub fn sw_double_and_add_affine( base: &Affine

, - scalar: impl AsRef<[u64]>, + scalar: impl BitIteratorBE, ) -> Projective

{ let mut res = Projective::zero(); - for b in ark_ff::BitIteratorBE::without_leading_zeros(scalar) { + for b in scalar.bit_be_trimmed_iter() { res.double_in_place(); if b { res += base @@ -126,10 +130,10 @@ pub fn sw_double_and_add_affine( #[inline(always)] pub fn sw_double_and_add_projective( base: &Projective

, - scalar: impl AsRef<[u64]>, + scalar: impl BitIteratorBE, ) -> Projective

{ let mut res = Projective::zero(); - for b in ark_ff::BitIteratorBE::without_leading_zeros(scalar) { + for b in scalar.bit_be_trimmed_iter() { res.double_in_place(); if b { res += base diff --git a/lib/crypto/src/curve/twisted_edwards/affine.rs b/lib/crypto/src/curve/twisted_edwards/affine.rs index 2d5600337..dd1507004 100644 --- a/lib/crypto/src/curve/twisted_edwards/affine.rs +++ b/lib/crypto/src/curve/twisted_edwards/affine.rs @@ -10,6 +10,7 @@ use zeroize::Zeroize; use super::{Projective, TECurveConfig}; use crate::{ + bits::BitIteratorBE, curve::AffineRepr, field::{group::AdditiveGroup, prime::PrimeField, Field}, }; @@ -114,8 +115,8 @@ impl AffineRepr for Affine

{ Self::new_unchecked(P::BaseField::ZERO, P::BaseField::ONE) } - fn mul_bigint(&self, by: impl AsRef<[u64]>) -> Self::Group { - P::mul_affine(self, by.as_ref()) + fn mul_bigint(&self, by: impl BitIteratorBE) -> Self::Group { + P::mul_affine(self, by) } /// Multiplies this element by the cofactor and output the diff --git a/lib/crypto/src/curve/twisted_edwards/group.rs b/lib/crypto/src/curve/twisted_edwards/group.rs index f13a2a890..77525a8a0 100644 --- a/lib/crypto/src/curve/twisted_edwards/group.rs +++ b/lib/crypto/src/curve/twisted_edwards/group.rs @@ -11,6 +11,7 @@ use zeroize::Zeroize; use super::{Affine, MontCurveConfig, TECurveConfig}; use crate::{ + bits::BitIteratorBE, curve::{AffineRepr, CurveGroup, PrimeGroup}, field::{group::AdditiveGroup, prime::PrimeField, Field}, impl_additive_ops_from_ref, @@ -180,8 +181,8 @@ impl PrimeGroup for Projective

{ } #[inline] - fn mul_bigint(&self, other: impl AsRef<[u64]>) -> Self { - P::mul_projective(self, other.as_ref()) + fn mul_bigint(&self, other: impl BitIteratorBE) -> Self { + P::mul_projective(self, other) } } diff --git a/lib/crypto/src/curve/twisted_edwards/mod.rs b/lib/crypto/src/curve/twisted_edwards/mod.rs index d08e7fb84..adf655044 100644 --- a/lib/crypto/src/curve/twisted_edwards/mod.rs +++ b/lib/crypto/src/curve/twisted_edwards/mod.rs @@ -7,6 +7,7 @@ mod group; pub use group::*; use crate::{ + bits::BitIteratorBE, curve::AffineRepr, field::{group::AdditiveGroup, prime::PrimeField}, }; @@ -56,10 +57,10 @@ pub trait TECurveConfig: super::CurveConfig { /// coordinates fn mul_projective( base: &Projective, - scalar: &[u64], + scalar: impl BitIteratorBE, ) -> Projective { let mut res = Projective::zero(); - for b in ark_ff::BitIteratorBE::without_leading_zeros(scalar) { + for b in scalar.bit_be_trimmed_iter() { res.double_in_place(); if b { res += base; @@ -71,9 +72,12 @@ pub trait TECurveConfig: super::CurveConfig { /// Default implementation of group multiplication for affine /// coordinates - fn mul_affine(base: &Affine, scalar: &[u64]) -> Projective { + fn mul_affine( + base: &Affine, + scalar: impl BitIteratorBE, + ) -> Projective { let mut res = Projective::zero(); - for b in ark_ff::BitIteratorBE::without_leading_zeros(scalar) { + for b in scalar.bit_be_trimmed_iter() { res.double_in_place(); if b { res += base From 0a94af1567a48bf023db1e911e95feb5f2df3445 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Wed, 19 Feb 2025 16:08:05 +0400 Subject: [PATCH 18/24] ++ --- lib/crypto/src/curve/short_weierstrass/group.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/crypto/src/curve/short_weierstrass/group.rs b/lib/crypto/src/curve/short_weierstrass/group.rs index 94870b4d1..be31ae351 100644 --- a/lib/crypto/src/curve/short_weierstrass/group.rs +++ b/lib/crypto/src/curve/short_weierstrass/group.rs @@ -1,3 +1,4 @@ +use alloc::vec::Vec; use core::{ borrow::Borrow, fmt::{Debug, Display, Formatter}, From f8b1d0c0c7ba4ea082d6bfbc7f4f8d10786ff7c9 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Wed, 19 Feb 2025 16:12:30 +0400 Subject: [PATCH 19/24] ++ --- lib/crypto/src/curve/mod.rs | 1 + lib/crypto/src/field/mod.rs | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/crypto/src/curve/mod.rs b/lib/crypto/src/curve/mod.rs index 0f4cf3d22..f3f05b320 100644 --- a/lib/crypto/src/curve/mod.rs +++ b/lib/crypto/src/curve/mod.rs @@ -31,6 +31,7 @@ pub trait CurveConfig: Send + Sync + Sized + 'static { type ScalarField: PrimeField + Into<::BigInt>; // TODO#q: we don't need this additional generic restriction Into + // TODO#q: cofactor should be represented as a big integer /// The cofactor of this curve, represented as a sequence of little-endian /// limbs. const COFACTOR: &'static [u64]; diff --git a/lib/crypto/src/field/mod.rs b/lib/crypto/src/field/mod.rs index 34d31f795..ca10e2222 100644 --- a/lib/crypto/src/field/mod.rs +++ b/lib/crypto/src/field/mod.rs @@ -164,6 +164,4 @@ pub trait Field: sum } */ - - // TODO#q: add sqrt? } From 7802b0fdc5fe3608a818d176947da03b408729a9 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Wed, 19 Feb 2025 16:19:36 +0400 Subject: [PATCH 20/24] ++ --- lib/crypto/src/curve/mod.rs | 45 +++++++++++++++++++ .../src/curve/short_weierstrass/group.rs | 7 ++- lib/crypto/src/curve/twisted_edwards/group.rs | 7 ++- 3 files changed, 51 insertions(+), 8 deletions(-) diff --git a/lib/crypto/src/curve/mod.rs b/lib/crypto/src/curve/mod.rs index f3f05b320..f7dbb5e39 100644 --- a/lib/crypto/src/curve/mod.rs +++ b/lib/crypto/src/curve/mod.rs @@ -230,3 +230,48 @@ pub trait AffineRepr: self.mul_bigint(Self::Config::COFACTOR_INV.into_bigint()).into() } } + +// Given a vector of field elements {v_i}, compute the vector {v_i^(-1)} +pub fn batch_inversion(v: &mut [F]) { + serial_batch_inversion_and_mul(v, &F::one()); +} + +/// Given a vector of field elements {v_i}, compute the vector {coeff * +/// v_i^(-1)}. This method is explicitly single-threaded. +fn serial_batch_inversion_and_mul(v: &mut [F], coeff: &F) { + // Montgomery’s Trick and Fast Implementation of Masked AES + // Genelle, Prouff and Quisquater + // Section 3.2 + // but with an optimization to multiply every element in the returned vector + // by coeff + + // First pass: compute [a, ab, abc, ...] + let mut prod = Vec::with_capacity(v.len()); + let mut tmp = F::one(); + for f in v.iter().filter(|f| !f.is_zero()) { + tmp *= f; + prod.push(tmp); + } + + // Invert `tmp`. + tmp = tmp.inverse().unwrap(); // Guaranteed to be nonzero. + + // Multiply product by coeff, so all inverses will be scaled by coeff + tmp *= coeff; + + // Second pass: iterate backwards to compute inverses + for (f, s) in v + .iter_mut() + // Backwards + .rev() + // Ignore normalized elements + .filter(|f| !f.is_zero()) + // Backwards, skip last element, fill in one for last term. + .zip(prod.into_iter().rev().skip(1).chain(Some(F::one()))) + { + // tmp := tmp * f; f := tmp * s = 1/f + let new_tmp = tmp * *f; + *f = tmp * &s; + tmp = new_tmp; + } +} diff --git a/lib/crypto/src/curve/short_weierstrass/group.rs b/lib/crypto/src/curve/short_weierstrass/group.rs index be31ae351..555f95505 100644 --- a/lib/crypto/src/curve/short_weierstrass/group.rs +++ b/lib/crypto/src/curve/short_weierstrass/group.rs @@ -13,7 +13,7 @@ use zeroize::Zeroize; use super::{Affine, SWCurveConfig}; use crate::{ bits::BitIteratorBE, - curve::{AffineRepr, CurveGroup, PrimeGroup}, + curve::{batch_inversion, AffineRepr, CurveGroup, PrimeGroup}, field::{group::AdditiveGroup, prime::PrimeField, Field}, impl_additive_ops_from_ref, }; @@ -292,12 +292,11 @@ impl CurveGroup for Projective

{ #[inline] fn normalize_batch(v: &[Self]) -> Vec { let mut z_s = v.iter().map(|g| g.z).collect::>(); - // TODO#q: we don't need to do it in parallel onchain - ark_ff::batch_inversion(&mut z_s); + batch_inversion(&mut z_s); // Perform affine transformations - ark_std::cfg_iter!(v) + v.iter() .zip(z_s) .map(|(g, z)| match g.is_zero() { true => Affine::identity(), diff --git a/lib/crypto/src/curve/twisted_edwards/group.rs b/lib/crypto/src/curve/twisted_edwards/group.rs index 77525a8a0..334e6e512 100644 --- a/lib/crypto/src/curve/twisted_edwards/group.rs +++ b/lib/crypto/src/curve/twisted_edwards/group.rs @@ -12,7 +12,7 @@ use zeroize::Zeroize; use super::{Affine, MontCurveConfig, TECurveConfig}; use crate::{ bits::BitIteratorBE, - curve::{AffineRepr, CurveGroup, PrimeGroup}, + curve::{batch_inversion, AffineRepr, CurveGroup, PrimeGroup}, field::{group::AdditiveGroup, prime::PrimeField, Field}, impl_additive_ops_from_ref, }; @@ -201,11 +201,10 @@ impl CurveGroup for Projective

{ // (batch inversion requires 3N multiplications + 1 inversion) let mut z_s = v.iter().map(|g| g.z).collect::>(); - // TODO#q: we don't need parallel here - ark_ff::batch_inversion(&mut z_s); + batch_inversion(&mut z_s); // Perform affine transformations - ark_std::cfg_iter!(v) + v.iter() .zip(z_s) .map(|(g, z)| match g.is_zero() { true => Affine::zero(), From 7059243b4f279b34523fea5aa1fbd73fb5894359 Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Wed, 19 Feb 2025 16:28:00 +0400 Subject: [PATCH 21/24] ++ --- lib/crypto/src/curve/twisted_edwards/affine.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/crypto/src/curve/twisted_edwards/affine.rs b/lib/crypto/src/curve/twisted_edwards/affine.rs index dd1507004..8472012f5 100644 --- a/lib/crypto/src/curve/twisted_edwards/affine.rs +++ b/lib/crypto/src/curve/twisted_edwards/affine.rs @@ -121,7 +121,6 @@ impl AffineRepr for Affine

{ /// Multiplies this element by the cofactor and output the /// resulting projective element. - #[must_use] fn mul_by_cofactor_to_group(&self) -> Self::Group { P::mul_affine(self, Self::Config::COFACTOR) } From 061e878c81eaf7185d4f1d0bf482ad7b96a2fe4a Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Wed, 19 Feb 2025 16:52:05 +0400 Subject: [PATCH 22/24] ++ --- lib/crypto/src/curve/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/crypto/src/curve/mod.rs b/lib/crypto/src/curve/mod.rs index f7dbb5e39..77855a366 100644 --- a/lib/crypto/src/curve/mod.rs +++ b/lib/crypto/src/curve/mod.rs @@ -80,7 +80,7 @@ PrimeGroup + AddAssign + Sub + SubAssign -// TODO#q: think to replace VariableBaseMSM and ScalarMul +// TODO#q: replace VariableBaseMSM and ScalarMul restrictions // + VariableBaseMSM // + ScalarMul + From From c8c483c06c36a8374a74c429c32514eea625f3af Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Thu, 6 Mar 2025 14:11:22 +0400 Subject: [PATCH 23/24] ++ --- lib/crypto/src/curve/short_weierstrass/group.rs | 1 - lib/crypto/src/field/mod.rs | 3 --- 2 files changed, 4 deletions(-) diff --git a/lib/crypto/src/curve/short_weierstrass/group.rs b/lib/crypto/src/curve/short_weierstrass/group.rs index 555f95505..326649cc5 100644 --- a/lib/crypto/src/curve/short_weierstrass/group.rs +++ b/lib/crypto/src/curve/short_weierstrass/group.rs @@ -389,7 +389,6 @@ impl>> AddAssign for Projective

{ // Y3 = r*(V-X3) + 2*Y1*J v -= &self.x; self.y.double_in_place(); - // TODO#q: we need sum of products for add assign self.y = P::BaseField::sum_of_products(&[r, self.y], &[v, j]); // Z3 = 2 * Z1 * H; diff --git a/lib/crypto/src/field/mod.rs b/lib/crypto/src/field/mod.rs index ca10e2222..ab589be3b 100644 --- a/lib/crypto/src/field/mod.rs +++ b/lib/crypto/src/field/mod.rs @@ -152,8 +152,6 @@ pub trait Field: res.unwrap_or(Self::ONE) } - // TODO#q: add sum of products - /* /// Returns `sum([a_i * b_i])`. #[inline] fn sum_of_products(a: &[Self; T], b: &[Self; T]) -> Self { @@ -163,5 +161,4 @@ pub trait Field: } sum } - */ } From e2fcd0679e59c197fe0ffd9b9389ea069822169b Mon Sep 17 00:00:00 2001 From: Alisander Qoshqosh Date: Wed, 19 Mar 2025 10:45:27 +0400 Subject: [PATCH 24/24] refactor curves implementation --- fuzz/Cargo.lock | 8 +- lib/crypto/src/arithmetic/uint.rs | 6 ++ lib/crypto/src/curve/helpers.rs | 5 +- lib/crypto/src/curve/mod.rs | 78 ++++++++------- .../curve/{short_weierstrass => sw}/affine.rs | 32 +++++-- .../curve/{short_weierstrass => sw}/mod.rs | 33 +++---- .../group.rs => sw/projective.rs} | 42 +++++---- .../curve/{twisted_edwards => te}/affine.rs | 31 ++++-- .../src/curve/{twisted_edwards => te}/mod.rs | 31 +++--- .../group.rs => te/projective.rs} | 94 +++++++++++-------- lib/crypto/src/field/fp.rs | 4 + lib/crypto/src/field/mod.rs | 4 + lib/crypto/src/field/prime.rs | 7 -- 13 files changed, 211 insertions(+), 164 deletions(-) rename lib/crypto/src/curve/{short_weierstrass => sw}/affine.rs (90%) rename lib/crypto/src/curve/{short_weierstrass => sw}/mod.rs (85%) rename lib/crypto/src/curve/{short_weierstrass/group.rs => sw/projective.rs} (94%) rename lib/crypto/src/curve/{twisted_edwards => te}/affine.rs (89%) rename lib/crypto/src/curve/{twisted_edwards => te}/mod.rs (81%) rename lib/crypto/src/curve/{twisted_edwards/group.rs => te/projective.rs} (84%) diff --git a/fuzz/Cargo.lock b/fuzz/Cargo.lock index 10aa5d745..47644e6dc 100644 --- a/fuzz/Cargo.lock +++ b/fuzz/Cargo.lock @@ -1840,9 +1840,9 @@ dependencies = [ [[package]] name = "motsu" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "468405089ee996017d397619c4a1b00ee91bb257ee0f45adad51a8cb95f639ef" +checksum = "f11816ec018094aa612081ec5053c4f41ad8c64adb44666ad5f69ce3e8a1901b" dependencies = [ "alloy-primitives", "alloy-sol-types", @@ -1856,9 +1856,9 @@ dependencies = [ [[package]] name = "motsu-proc" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d89892fb2937177554806b600de553eb21f6e4568515777084c58918541f891" +checksum = "23d4d8171f070b58febd3eae8bd29c91ff993862fa0e01fc604d23321583a18a" dependencies = [ "proc-macro2", "quote", diff --git a/lib/crypto/src/arithmetic/uint.rs b/lib/crypto/src/arithmetic/uint.rs index 29bd73fc3..4205cf330 100644 --- a/lib/crypto/src/arithmetic/uint.rs +++ b/lib/crypto/src/arithmetic/uint.rs @@ -681,6 +681,12 @@ impl BitIteratorBE for Uint { } } +impl BitIteratorBE for &[Limb] { + fn bit_be_iter(&self) -> impl Iterator { + self.iter().rev().flat_map(Limb::bit_be_iter) + } +} + /// Parse a number from a string in a given radix. /// /// This implementation can be slow on big numbers and possibly fail constant diff --git a/lib/crypto/src/curve/helpers.rs b/lib/crypto/src/curve/helpers.rs index d032b61a3..63e985336 100644 --- a/lib/crypto/src/curve/helpers.rs +++ b/lib/crypto/src/curve/helpers.rs @@ -1,5 +1,6 @@ -// TODO#q: probably rename `impl_additive_ops_from_ref` and add same for field -/// Implements AddAssign on Self by deferring to an implementation on &Self +//! Helper macros for implementing common traits for curve types. + +/// Implements additive operations by deferring to an implementation on &Self. #[macro_export] macro_rules! impl_additive_ops_from_ref { ($type:ident, $params:ident) => { diff --git a/lib/crypto/src/curve/mod.rs b/lib/crypto/src/curve/mod.rs index 77855a366..c008d4ee6 100644 --- a/lib/crypto/src/curve/mod.rs +++ b/lib/crypto/src/curve/mod.rs @@ -1,3 +1,6 @@ +//! This module provides common operations to work with elliptic curves. + +use alloc::vec::Vec; use core::{ fmt::{Debug, Display}, hash::Hash, @@ -13,8 +16,8 @@ use crate::{ }; mod helpers; -pub mod short_weierstrass; -pub mod twisted_edwards; +pub mod sw; +pub mod te; /// Elliptic curves can be represented via different "models" with varying /// efficiency properties. @@ -28,15 +31,16 @@ pub trait CurveConfig: Send + Sync + Sized + 'static { type BaseField: Field; /// Finite prime field corresponding to an appropriate prime-order subgroup /// of the curve group. - type ScalarField: PrimeField - + Into<::BigInt>; // TODO#q: we don't need this additional generic restriction Into + type ScalarField: PrimeField; - // TODO#q: cofactor should be represented as a big integer /// The cofactor of this curve, represented as a sequence of little-endian /// limbs. const COFACTOR: &'static [u64]; + + /// The inverse of the cofactor. const COFACTOR_INV: Self::ScalarField; + /// Returns `true` if the cofactor is one. fn cofactor_is_one() -> bool { Self::COFACTOR[0] == 1 && Self::COFACTOR.iter().skip(1).all(Zero::is_zero) @@ -53,10 +57,12 @@ pub trait PrimeGroup: AdditiveGroup { fn generator() -> Self; /// Performs scalar multiplication of this element. + #[must_use] fn mul_bigint(&self, other: impl BitIteratorBE) -> Self; /// Computes `other * self`, where `other` is a *big-endian* /// bit representation of some integer. + #[must_use] fn mul_bits_be(&self, other: impl Iterator) -> Self { let mut res = Self::zero(); for b in other.skip_while(|b| !b) { @@ -75,22 +81,20 @@ pub trait PrimeGroup: AdditiveGroup { /// /// The point is guaranteed to be in the correct prime order subgroup. pub trait CurveGroup: -PrimeGroup -+ Add -+ AddAssign -+ Sub -+ SubAssign -// TODO#q: replace VariableBaseMSM and ScalarMul restrictions -// + VariableBaseMSM -// + ScalarMul -+ From -+ Into -+ core::iter::Sum -+ for<'a> core::iter::Sum<&'a Self::Affine> + PrimeGroup + + Add + + AddAssign + + Sub + + SubAssign + + From + + Into + + core::iter::Sum + + for<'a> core::iter::Sum<&'a Self::Affine> { + /// Associated configuration for this curve. type Config: CurveConfig< - ScalarField=Self::ScalarField, - BaseField=Self::BaseField, + ScalarField = Self::ScalarField, + BaseField = Self::BaseField, >; /// The field over which this curve is defined. @@ -98,12 +102,12 @@ PrimeGroup /// The affine representation of this element. type Affine: AffineRepr< - Config=Self::Config, - Group=Self, - ScalarField=Self::ScalarField, - BaseField=Self::BaseField, - > + From - + Into; + Config = Self::Config, + Group = Self, + ScalarField = Self::ScalarField, + BaseField = Self::BaseField, + > + From + + Into; /// Type representing an element of the full elliptic curve group, not just /// the prime order subgroup. @@ -151,15 +155,17 @@ pub trait AffineRepr: + Mul + for<'a> Mul<&'a Self::ScalarField, Output = Self::Group> { + /// Associated configuration for this curve. type Config: CurveConfig< ScalarField = Self::ScalarField, BaseField = Self::BaseField, >; - type ScalarField: PrimeField - + Into<::BigInt>; + /// Finite prime field corresponding to an appropriate prime-order subgroup + /// of the curve group. + type ScalarField: PrimeField; - /// The finite field over which this curve is defined. + /// Base field that the curve is defined over. type BaseField: Field; /// The projective representation of points on this curve. @@ -231,19 +237,19 @@ pub trait AffineRepr: } } -// Given a vector of field elements {v_i}, compute the vector {v_i^(-1)} +/// Given a vector of field elements `v_i`, compute the vector `v_i^(-1)` pub fn batch_inversion(v: &mut [F]) { - serial_batch_inversion_and_mul(v, &F::one()); + batch_inversion_and_mul(v, &F::one()); } -/// Given a vector of field elements {v_i}, compute the vector {coeff * -/// v_i^(-1)}. This method is explicitly single-threaded. -fn serial_batch_inversion_and_mul(v: &mut [F], coeff: &F) { +/// Given a vector of field elements `v_i`, compute the vector `coeff * +/// v_i^(-1)`. +fn batch_inversion_and_mul(v: &mut [F], coeff: &F) { // Montgomery’s Trick and Fast Implementation of Masked AES // Genelle, Prouff and Quisquater // Section 3.2 // but with an optimization to multiply every element in the returned vector - // by coeff + // by coeff. // First pass: compute [a, ab, abc, ...] let mut prod = Vec::with_capacity(v.len()); @@ -256,7 +262,7 @@ fn serial_batch_inversion_and_mul(v: &mut [F], coeff: &F) { // Invert `tmp`. tmp = tmp.inverse().unwrap(); // Guaranteed to be nonzero. - // Multiply product by coeff, so all inverses will be scaled by coeff + // Multiply product by coeff, so all inverses will be scaled by coeff. tmp *= coeff; // Second pass: iterate backwards to compute inverses @@ -271,7 +277,7 @@ fn serial_batch_inversion_and_mul(v: &mut [F], coeff: &F) { { // tmp := tmp * f; f := tmp * s = 1/f let new_tmp = tmp * *f; - *f = tmp * &s; + *f = tmp * s; tmp = new_tmp; } } diff --git a/lib/crypto/src/curve/short_weierstrass/affine.rs b/lib/crypto/src/curve/sw/affine.rs similarity index 90% rename from lib/crypto/src/curve/short_weierstrass/affine.rs rename to lib/crypto/src/curve/sw/affine.rs index 0a4654664..146b68118 100644 --- a/lib/crypto/src/curve/short_weierstrass/affine.rs +++ b/lib/crypto/src/curve/sw/affine.rs @@ -1,3 +1,8 @@ +//! Affine coordinates for a point on a Short Weierstrass curve +//! ([Affine Space]). +//! +//! [Affine Space]: https://en.wikipedia.org/wiki/Affine_space + use core::{ borrow::Borrow, fmt::{Debug, Display, Formatter}, @@ -5,7 +10,7 @@ use core::{ }; use educe::Educe; -use num_traits::{real::Real, One, Zero}; +use num_traits::{One, Zero}; use zeroize::Zeroize; use super::{Projective, SWCurveConfig}; @@ -37,18 +42,20 @@ impl PartialEq> for Affine

{ impl Display for Affine

{ fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { - match self.infinity { - true => write!(f, "infinity"), - false => write!(f, "({}, {})", self.x, self.y), + if self.infinity { + write!(f, "infinity") + } else { + write!(f, "({}, {})", self.x, self.y) } } } impl Debug for Affine

{ fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { - match self.infinity { - true => write!(f, "infinity"), - false => write!(f, "({}, {})", self.x, self.y), + if self.infinity { + write!(f, "infinity") + } else { + write!(f, "({}, {})", self.x, self.y) } } } @@ -57,6 +64,11 @@ impl Affine

{ /// Constructs a group element from x and y coordinates. /// Performs checks to ensure that the point is on the curve and is in the /// right subgroup. + /// + /// # Panics + /// + /// * If point is not on curve. + /// * If point is not in the prime-order subgroup. pub fn new(x: P::BaseField, y: P::BaseField) -> Self { let point = Self { x, y, infinity: false }; assert!(point.is_on_curve()); @@ -74,6 +86,7 @@ impl Affine

{ Self { x, y, infinity: false } } + /// Additive identity element of the curve group. pub const fn identity() -> Self { Self { x: P::BaseField::ZERO, y: P::BaseField::ZERO, infinity: true } } @@ -166,7 +179,6 @@ impl> Add for Affine

{ type Output = Projective

; fn add(self, other: T) -> Projective

{ - // TODO#q: implement more efficient formulae when z1 = z2 = 1. let mut copy = self.into_group(); copy += other.borrow(); copy @@ -247,10 +259,10 @@ impl From> for Affine

{ let zinv_squared = zinv.square(); // X/Z^2 - let x = p.x * &zinv_squared; + let x = p.x * zinv_squared; // Y/Z^3 - let y = p.y * &(zinv_squared * &zinv); + let y = p.y * (zinv_squared * zinv); Affine::new_unchecked(x, y) } diff --git a/lib/crypto/src/curve/short_weierstrass/mod.rs b/lib/crypto/src/curve/sw/mod.rs similarity index 85% rename from lib/crypto/src/curve/short_weierstrass/mod.rs rename to lib/crypto/src/curve/sw/mod.rs index 3585921d7..43f6f4e43 100644 --- a/lib/crypto/src/curve/short_weierstrass/mod.rs +++ b/lib/crypto/src/curve/sw/mod.rs @@ -1,10 +1,15 @@ +//! This module contains definitions for the [Short Weierstrass model] of the +//! curve. +//! +//! [Short Weierstrass model]: https://www.hyperelliptic.org/EFD/g1p/auto-shortw.html + use num_traits::Zero; mod affine; pub use affine::*; -mod group; -pub use group::*; +mod projective; +pub use projective::*; use crate::{ bits::BitIteratorBE, @@ -12,11 +17,13 @@ use crate::{ field::{group::AdditiveGroup, prime::PrimeField}, }; -/// Constants and convenience functions that collectively define the [Short Weierstrass model](https://www.hyperelliptic.org/EFD/g1p/auto-shortw.html) -/// of the curve. +/// Constants and convenience functions that collectively define the +/// [Short Weierstrass model] of the curve. /// /// In this model, the curve equation is `y² = x³ + a * x + b`, for constants /// `a` and `b`. +/// +/// [Short Weierstrass model]: https://www.hyperelliptic.org/EFD/g1p/auto-shortw.html pub trait SWCurveConfig: super::CurveConfig { /// Coefficient `a` of the curve equation. const COEFF_A: Self::BaseField; @@ -49,7 +56,7 @@ pub trait SWCurveConfig: super::CurveConfig { if Self::COEFF_B.is_zero() { elem } else { - elem + &Self::COEFF_B + elem + Self::COEFF_B } } @@ -95,18 +102,6 @@ pub trait SWCurveConfig: super::CurveConfig { ) -> Projective { sw_double_and_add_affine(base, scalar) } - - /* TODO#q: implement msm for short weierstrass curves - /// Default implementation for multi scalar multiplication - fn msm( - bases: &[Affine], - scalars: &[Self::ScalarField], - ) -> Result, usize> { - (bases.len() == scalars.len()) - .then(|| VariableBaseMSM::msm_unchecked(bases, scalars)) - .ok_or(bases.len().min(scalars.len())) - } - */ } /// Standard double-and-add method for multiplication by a scalar. @@ -119,7 +114,7 @@ pub fn sw_double_and_add_affine( for b in scalar.bit_be_trimmed_iter() { res.double_in_place(); if b { - res += base + res += base; } } @@ -136,7 +131,7 @@ pub fn sw_double_and_add_projective( for b in scalar.bit_be_trimmed_iter() { res.double_in_place(); if b { - res += base + res += base; } } diff --git a/lib/crypto/src/curve/short_weierstrass/group.rs b/lib/crypto/src/curve/sw/projective.rs similarity index 94% rename from lib/crypto/src/curve/short_weierstrass/group.rs rename to lib/crypto/src/curve/sw/projective.rs index 326649cc5..7cefd5375 100644 --- a/lib/crypto/src/curve/short_weierstrass/group.rs +++ b/lib/crypto/src/curve/sw/projective.rs @@ -1,3 +1,8 @@ +//! Projective coordinates for a point on a Short Weierstrass curve +//! ([Homogeneous coordinates]). +//! +//! [Homogeneous coordinates]: https://en.wikipedia.org/wiki/Homogeneous_coordinates + use alloc::vec::Vec; use core::{ borrow::Borrow, @@ -41,9 +46,10 @@ impl Display for Projective

{ impl Debug for Projective

{ fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { - match self.is_zero() { - true => write!(f, "infinity"), - false => write!(f, "({}, {}, {})", self.x, self.y, self.z), + if self.is_zero() { + write!(f, "infinity") + } else { + write!(f, "({}, {}, {})", self.x, self.y, self.z) } } } @@ -65,10 +71,10 @@ impl PartialEq for Projective

{ let z1z1 = self.z.square(); let z2z2 = other.z.square(); - if self.x * &z2z2 != other.x * &z1z1 { - false + if self.x * z2z2 == other.x * z1z1 { + self.y * (z2z2 * other.z) == other.y * (z1z1 * self.z) } else { - self.y * &(z2z2 * &other.z) == other.y * &(z1z1 * &self.z) + false } } } @@ -81,7 +87,7 @@ impl PartialEq> for Projective

{ impl Hash for Projective

{ fn hash(&self, state: &mut H) { - self.into_affine().hash(state) + self.into_affine().hash(state); } } @@ -105,6 +111,11 @@ impl Projective

{ /// Constructs a new group element in a way while enforcing that points are /// in the prime-order subgroup. + /// + /// # Panics + /// + /// * If point is not on curve. + /// * If point is not in the prime-order subgroup. pub fn new(x: P::BaseField, y: P::BaseField, z: P::BaseField) -> Self { let p = Self::new_unchecked(x, y, z).into_affine(); assert!(p.is_on_curve()); @@ -151,6 +162,7 @@ impl AdditiveGroup for Projective

{ /// Sets `self = 2 * self`. Note that Jacobian formulae are incomplete, and /// so doubling cannot be computed as `self + self`. Instead, this /// implementation uses the following specialized doubling formulae: + /// /// * [`P::A` is zero](http://www.hyperelliptic.org/EFD/g1p/auto-shortw-jacobian-0.html#doubling-dbl-2009-l) /// * [`P::A` is not zero](https://www.hyperelliptic.org/EFD/g1p/auto-shortw-jacobian.html#doubling-dbl-2007-bl) fn double_in_place(&mut self) -> &mut Self { @@ -174,7 +186,6 @@ impl AdditiveGroup for Projective

{ // D = 2*((X1+B)^2-A-C) // = 2 * (X1 + Y1^2)^2 - A - C // = 2 * 2 * X1 * Y1^2 - // TODO#q: use different trait for extension_degree let d = if [1, 2].contains(&P::BaseField::extension_degree()) { let mut d = self.x; d *= &b; @@ -226,7 +237,7 @@ impl AdditiveGroup for Projective

{ zz.square_in_place(); // S = 2*((X1+YY)^2-XX-YYYY) - let s = ((self.x + &yy).square() - &xx - &yyyy).double(); + let s = ((self.x + yy).square() - xx - yyyy).double(); // M = 3*XX+a*ZZ^2 let mut m = xx; @@ -298,9 +309,10 @@ impl CurveGroup for Projective

{ // Perform affine transformations v.iter() .zip(z_s) - .map(|(g, z)| match g.is_zero() { - true => Affine::identity(), - false => { + .map(|(g, z)| { + if g.is_zero() { + Affine::identity() + } else { let z2 = z.square(); let x = g.x * z2; let y = g.y * z2 * z; @@ -352,7 +364,7 @@ impl>> AddAssign for Projective

{ self.double_in_place(); } else { // a + (-a) = 0 - *self = Self::zero() + *self = Self::zero(); } } else { // H = U2-X1 @@ -426,8 +438,6 @@ impl>> Sub for Projective

{ } } -// TODO#q: Add auto derive of multiplication operations -// Implements AddAssign on Self by deferring to an implementation on &Self impl_additive_ops_from_ref!(Projective, SWCurveConfig); impl<'a, P: SWCurveConfig> Add<&'a Self> for Projective

{ @@ -551,7 +561,7 @@ impl> MulAssign for Projective

{ fn mul_assign(&mut self, other: T) { - *self = self.mul_bigint(other.borrow().into_bigint()) + *self = self.mul_bigint(other.borrow().into_bigint()); } } diff --git a/lib/crypto/src/curve/twisted_edwards/affine.rs b/lib/crypto/src/curve/te/affine.rs similarity index 89% rename from lib/crypto/src/curve/twisted_edwards/affine.rs rename to lib/crypto/src/curve/te/affine.rs index 8472012f5..93945222c 100644 --- a/lib/crypto/src/curve/twisted_edwards/affine.rs +++ b/lib/crypto/src/curve/te/affine.rs @@ -1,3 +1,7 @@ +//! Affine coordinates for a point on a Twisted Edwards curve ([Affine Space]). +//! +//! [Affine Space]: https://en.wikipedia.org/wiki/Affine_space + use core::{ borrow::Borrow, fmt::{Debug, Display, Formatter}, @@ -29,18 +33,20 @@ pub struct Affine { impl Display for Affine

{ fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { - match self.is_zero() { - true => write!(f, "infinity"), - false => write!(f, "({}, {})", self.x, self.y), + if self.is_zero() { + write!(f, "infinity") + } else { + write!(f, "({}, {})", self.x, self.y) } } } impl Debug for Affine

{ fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { - match self.is_zero() { - true => write!(f, "infinity"), - false => write!(f, "({}, {})", self.x, self.y), + if self.is_zero() { + write!(f, "infinity") + } else { + write!(f, "({}, {})", self.x, self.y) } } } @@ -60,6 +66,11 @@ impl Affine

{ /// Construct a new group element in a way while enforcing that points are /// in the prime-order subgroup. + /// + /// # Panics + /// + /// * If point is not on curve. + /// * If point is not in the prime-order subgroup. pub fn new(x: P::BaseField, y: P::BaseField) -> Self { let p = Self::new_unchecked(x, y); assert!(p.is_on_curve()); @@ -83,7 +94,7 @@ impl Affine

{ let y2 = self.y.square(); let lhs = y2 + P::mul_by_a(x2); - let rhs = P::BaseField::one() + &(P::COEFF_D * &(x2 * &y2)); + let rhs = P::BaseField::one() + (P::COEFF_D * (x2 * y2)); lhs == rhs } @@ -228,10 +239,10 @@ impl From> for Affine

{ // If Z is one, the point is already normalized. Affine::new_unchecked(p.x, p.y) } else { - // Z is nonzero, so it must have an inverse in a field. + // Z is nonzero, so it must have inverse in a field. let z_inv = p.z.inverse().unwrap(); - let x = p.x * &z_inv; - let y = p.y * &z_inv; + let x = p.x * z_inv; + let y = p.y * z_inv; Affine::new_unchecked(x, y) } } diff --git a/lib/crypto/src/curve/twisted_edwards/mod.rs b/lib/crypto/src/curve/te/mod.rs similarity index 81% rename from lib/crypto/src/curve/twisted_edwards/mod.rs rename to lib/crypto/src/curve/te/mod.rs index adf655044..3cf35b38e 100644 --- a/lib/crypto/src/curve/twisted_edwards/mod.rs +++ b/lib/crypto/src/curve/te/mod.rs @@ -1,10 +1,15 @@ +//! This module contains definitions for the [Twisted Edwards model] of the +//! curve. +//! +//! [Twisted Edwards model]: https://www.hyperelliptic.org/EFD/g1p/auto-twisted.html + use num_traits::Zero; mod affine; pub use affine::*; -mod group; -pub use group::*; +mod projective; +pub use projective::*; use crate::{ bits::BitIteratorBE, @@ -12,11 +17,13 @@ use crate::{ field::{group::AdditiveGroup, prime::PrimeField}, }; -/// Constants and convenience functions that collectively define the [Twisted Edwards model](https://www.hyperelliptic.org/EFD/g1p/auto-twisted.html) -/// of the curve. +/// Constants and convenience functions +/// that define the [Twisted Edwards model] of the curve. /// /// In this model, the curve equation is `a * x² + y² = 1 + d * x² * y²`, for /// constants `a` and `d`. +/// +/// [Twisted Edwards model]: https://www.hyperelliptic.org/EFD/g1p/auto-twisted.html pub trait TECurveConfig: super::CurveConfig { /// Coefficient `a` of the curve equation. const COEFF_A: Self::BaseField; @@ -80,24 +87,12 @@ pub trait TECurveConfig: super::CurveConfig { for b in scalar.bit_be_trimmed_iter() { res.double_in_place(); if b { - res += base + res += base; } } res } - - /* TODO#q: implement msm for twisted edwards curves - /// Default implementation for multi scalar multiplication - fn msm( - bases: &[Affine], - scalars: &[Self::ScalarField], - ) -> Result, usize> { - (bases.len() == scalars.len()) - .then(|| VariableBaseMSM::msm_unchecked(bases, scalars)) - .ok_or(bases.len().min(scalars.len())) - } - */ } /// Constants and convenience functions that collectively define the [Montgomery model](https://www.hyperelliptic.org/EFD/g1p/auto-montgom.html) @@ -115,5 +110,3 @@ pub trait MontCurveConfig: super::CurveConfig { /// equivalent to this curve. type TECurveConfig: TECurveConfig; } - -////////////////////////////////////////////////////////////////////////////// diff --git a/lib/crypto/src/curve/twisted_edwards/group.rs b/lib/crypto/src/curve/te/projective.rs similarity index 84% rename from lib/crypto/src/curve/twisted_edwards/group.rs rename to lib/crypto/src/curve/te/projective.rs index 334e6e512..592ec2ad7 100644 --- a/lib/crypto/src/curve/twisted_edwards/group.rs +++ b/lib/crypto/src/curve/te/projective.rs @@ -1,3 +1,9 @@ +//! Projective coordinates for a point on a Twisted Edwards curve +//! ([Homogeneous coordinates]). +//! +//! [Homogeneous coordinates]: https://en.wikipedia.org/wiki/Homogeneous_coordinates + +use alloc::vec::Vec; use core::{ borrow::Borrow, fmt::{Display, Formatter}, @@ -55,14 +61,14 @@ impl PartialEq for Projective

{ } // x1/z1 == x2/z2 <==> x1 * z2 == x2 * z1 - (self.x * &other.z) == (other.x * &self.z) - && (self.y * &other.z) == (other.y * &self.z) + (self.x * other.z) == (other.x * self.z) + && (self.y * other.z) == (other.y * self.z) } } impl Hash for Projective

{ fn hash(&self, state: &mut H) { - self.into_affine().hash(state) + self.into_affine().hash(state); } } @@ -87,6 +93,11 @@ impl Projective

{ /// Construct a new group element in a way while enforcing that points are /// in the prime-order subgroup. + /// + /// # Panics + /// + /// * If point is not on curve. + /// * If point is not in the prime-order subgroup. pub fn new( x: P::BaseField, y: P::BaseField, @@ -153,21 +164,21 @@ impl AdditiveGroup for Projective

{ // D = a * A let d = P::mul_by_a(a); // E = (X1 + Y1)^2 - A - B - let e = (self.x + &self.y).square() - &a - &b; + let e = (self.x + self.y).square() - a - b; // G = D + B - let g = d + &b; + let g = d + b; // F = G - C - let f = g - &c; + let f = g - c; // H = D - B - let h = d - &b; + let h = d - b; // X3 = E * F - self.x = e * &f; + self.x = e * f; // Y3 = G * H - self.y = g * &h; + self.y = g * h; // T3 = E * H - self.t = e * &h; + self.t = e * h; // Z3 = F * G - self.z = f * &g; + self.z = f * g; self } @@ -206,11 +217,12 @@ impl CurveGroup for Projective

{ // Perform affine transformations v.iter() .zip(z_s) - .map(|(g, z)| match g.is_zero() { - true => Affine::zero(), - false => { - let x = g.x * &z; - let y = g.y * &z; + .map(|(g, z)| { + if g.is_zero() { + Affine::zero() + } else { + let x = g.x * z; + let y = g.y * z; Affine::new_unchecked(x, y) } }) @@ -237,30 +249,30 @@ impl>> AddAssign for Projective

{ // Source: https://www.hyperelliptic.org/EFD/g1p/data/twisted/extended/addition/madd-2008-hwcd // A = X1*X2 - let a = self.x * &other.x; + let a = self.x * other.x; // B = Y1*Y2 - let b = self.y * &other.y; + let b = self.y * other.y; // C = T1*d*T2 - let c = P::COEFF_D * &self.t * &other.x * &other.y; + let c = P::COEFF_D * self.t * other.x * other.y; // D = Z1 let d = self.z; // E = (X1+Y1)*(X2+Y2)-A-B - let e = (self.x + &self.y) * &(other.x + &other.y) - &a - &b; + let e = (self.x + self.y) * (other.x + other.y) - a - b; // F = D-C - let f = d - &c; + let f = d - c; // G = D+C - let g = d + &c; + let g = d + c; // H = B-a*A - let h = b - &P::mul_by_a(a); + let h = b - P::mul_by_a(a); // X3 = E*F - self.x = e * &f; + self.x = e * f; // Y3 = G*H - self.y = g * &h; + self.y = g * h; // T3 = E*H - self.t = e * &h; + self.t = e * h; // Z3 = F*G - self.z = f * &g; + self.z = f * g; } } @@ -316,40 +328,40 @@ impl<'a, P: TECurveConfig> AddAssign<&'a Self> for Projective

{ // 3.1 Unified Addition in E^e // A = x1 * x2 - let a = self.x * &other.x; + let a = self.x * other.x; // B = y1 * y2 - let b = self.y * &other.y; + let b = self.y * other.y; // C = d * t1 * t2 - let c = P::COEFF_D * &self.t * &other.t; + let c = P::COEFF_D * self.t * other.t; // D = z1 * z2 - let d = self.z * &other.z; + let d = self.z * other.z; // H = B - aA - let h = b - &P::mul_by_a(a); + let h = b - P::mul_by_a(a); // E = (x1 + y1) * (x2 + y2) - A - B - let e = (self.x + &self.y) * &(other.x + &other.y) - &a - &b; + let e = (self.x + self.y) * (other.x + other.y) - a - b; // F = D - C - let f = d - &c; + let f = d - c; // G = D + C - let g = d + &c; + let g = d + c; // x3 = E * F - self.x = e * &f; + self.x = e * f; // y3 = G * H - self.y = g * &h; + self.y = g * h; // t3 = E * H - self.t = e * &h; + self.t = e * h; // z3 = F * G - self.z = f * &g; + self.z = f * g; } } @@ -363,7 +375,7 @@ impl> MulAssign for Projective

{ fn mul_assign(&mut self, other: T) { - *self = self.mul_bigint(other.borrow().into_bigint()) + *self = self.mul_bigint(other.borrow().into_bigint()); } } @@ -392,7 +404,7 @@ impl>> core::iter::Sum // with Z = 1. impl From> for Projective

{ fn from(p: Affine

) -> Projective

{ - Self::new_unchecked(p.x, p.y, p.x * &p.y, P::BaseField::one()) + Self::new_unchecked(p.x, p.y, p.x * p.y, P::BaseField::one()) } } diff --git a/lib/crypto/src/field/fp.rs b/lib/crypto/src/field/fp.rs index 541745213..1332a9d91 100644 --- a/lib/crypto/src/field/fp.rs +++ b/lib/crypto/src/field/fp.rs @@ -519,6 +519,10 @@ impl, const N: usize> AdditiveGroup for Fp { impl, const N: usize> Field for Fp { const ONE: Self = Fp::new_unchecked(P::R); + fn extension_degree() -> usize { + 1 + } + #[inline] fn square(&self) -> Self { let mut temp = *self; diff --git a/lib/crypto/src/field/mod.rs b/lib/crypto/src/field/mod.rs index ab589be3b..5149c9ce2 100644 --- a/lib/crypto/src/field/mod.rs +++ b/lib/crypto/src/field/mod.rs @@ -101,6 +101,10 @@ pub trait Field: /// The multiplicative identity of the field. const ONE: Self; + /// Returns the extension degree of this field. + #[must_use] + fn extension_degree() -> usize; + /// Returns `self * self`. #[must_use] fn square(&self) -> Self; diff --git a/lib/crypto/src/field/prime.rs b/lib/crypto/src/field/prime.rs index d920386e1..737772356 100644 --- a/lib/crypto/src/field/prime.rs +++ b/lib/crypto/src/field/prime.rs @@ -23,13 +23,6 @@ pub trait PrimeField: Self::MODULUS } - /// Returns the extension degree of this field with respect - /// to `Self::BasePrimeField`. - #[must_use] - fn extension_degree() -> usize { - 1 - } - /// Construct a prime field element from a big integer. fn from_bigint(repr: Self::BigInt) -> Self;