Publicize non-zero weierstrass affine variables (#84) Co-authored-by: Marcin <marcin.gorny.94@protonmail.com> Co-authored-by: Pratyush Mishra <pratyushmishra@berkeley.edu>
Alex Ozdemir aozdemir@hmc.edu
Tue, 08 Mar 2022 00:43:32 -0800
3 files changed,
114 insertions(+),
7 deletions(-)
M
CHANGELOG.md
→
CHANGELOG.md
@@ -8,6 +8,8 @@ - [\#86](https://github.com/arkworks-rs/r1cs-std/pull/86) Change the API for domains for coset.
### Features +- [\#84](https://github.com/arkworks-rs/r1cs-std/pull/84) Expose `short_weierstrass::non_zero_affine` module + and implement `EqGadget` for `NonZeroAffineVar`. - [\#79](https://github.com/arkworks-rs/r1cs-std/pull/79) Move `NonNativeFieldVar` from `ark-nonnative` to `ark-r1cs-std`. - [\#76](https://github.com/arkworks-rs/r1cs-std/pull/76) Implement `ToBytesGadget` for `Vec<UInt8>`. - [nonnative/\#45](https://github.com/arkworks-rs/nonnative/pull/45) Add `new_witness_with_le_bits` which returns the bits used during variable allocation.
M
src/groups/curves/short_weierstrass/mod.rs
→
src/groups/curves/short_weierstrass/mod.rs
@@ -22,7 +22,13 @@ /// the [\[MNT6]\](<https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.20.8113&rep=rep1&type=pdf>)
/// family of bilinear groups. pub mod mnt6; -mod non_zero_affine; +/// This module provides a generic implementation of elliptic curve operations for points on +/// short-weierstrass curves in affine coordinates that **are not** equal to zero. +/// +/// Note: this module is **unsafe** in general: it can synthesize unsatisfiable or +/// underconstrained constraint systems when a represented point _is_ equal to zero. +/// The [ProjectiveVar] gadget is the recommended way of working with elliptic curve points. +pub mod non_zero_affine; /// An implementation of arithmetic for Short Weierstrass curves that relies on /// the complete formulae derived in the paper of /// [[Renes, Costello, Batina 2015]](<https://eprint.iacr.org/2015/1060>).
M
src/groups/curves/short_weierstrass/non_zero_affine.rs
→
src/groups/curves/short_weierstrass/non_zero_affine.rs
@@ -25,7 +25,7 @@ P: SWModelParameters,
F: FieldVar<P::BaseField, <P::BaseField as Field>::BasePrimeField>, for<'a> &'a F: FieldOpsBounds<'a, P::BaseField, F>, { - pub(crate) fn new(x: F, y: F) -> Self { + pub fn new(x: F, y: F) -> Self { Self { x, y,@@ -35,13 +35,13 @@ }
/// Converts self into a non-zero projective point. #[tracing::instrument(target = "r1cs", skip(self))] - pub(crate) fn into_projective(&self) -> ProjectiveVar<P, F> { + pub fn into_projective(&self) -> ProjectiveVar<P, F> { ProjectiveVar::new(self.x.clone(), self.y.clone(), F::one()) } /// Performs an addition without checking that other != ±self. #[tracing::instrument(target = "r1cs", skip(self, other))] - pub(crate) fn add_unchecked(&self, other: &Self) -> Result<Self, SynthesisError> { + pub fn add_unchecked(&self, other: &Self) -> Result<Self, SynthesisError> { if [self, other].is_constant() { let result = (self.value()?.into_projective() + other.value()?.into_projective()).into_affine();@@ -67,7 +67,7 @@
/// Doubles `self`. As this is a prime order curve point, /// the output is guaranteed to not be the point at infinity. #[tracing::instrument(target = "r1cs", skip(self))] - pub(crate) fn double(&self) -> Result<Self, SynthesisError> { + pub fn double(&self) -> Result<Self, SynthesisError> { if [self].is_constant() { let result = self.value()?.into_projective().double().into_affine(); // Panic if the result is zero.@@ -96,7 +96,7 @@ /// less than the 7 required when computing via `self.double() + other`.
/// /// This follows the formulae from [\[ELM03\]](https://arxiv.org/abs/math/0208038). #[tracing::instrument(target = "r1cs", skip(self))] - pub(crate) fn double_and_add_unchecked(&self, other: &Self) -> Result<Self, SynthesisError> { + pub fn double_and_add_unchecked(&self, other: &Self) -> Result<Self, SynthesisError> { if [self].is_constant() || other.is_constant() { self.double()?.add_unchecked(other) } else {@@ -126,7 +126,7 @@ }
/// Doubles `self` in place. #[tracing::instrument(target = "r1cs", skip(self))] - pub(crate) fn double_in_place(&mut self) -> Result<(), SynthesisError> { + pub fn double_in_place(&mut self) -> Result<(), SynthesisError> { *self = self.double()?; Ok(()) }@@ -169,9 +169,62 @@ Ok(Self::new(x, y))
} } +impl<P, F> EqGadget<<P::BaseField as Field>::BasePrimeField> for NonZeroAffineVar<P, F> +where + P: SWModelParameters, + F: FieldVar<P::BaseField, <P::BaseField as Field>::BasePrimeField>, + for<'a> &'a F: FieldOpsBounds<'a, P::BaseField, F>, +{ + #[tracing::instrument(target = "r1cs")] + fn is_eq( + &self, + other: &Self, + ) -> Result<Boolean<<P::BaseField as Field>::BasePrimeField>, SynthesisError> { + let x_equal = self.x.is_eq(&other.x)?; + let y_equal = self.y.is_eq(&other.y)?; + x_equal.and(&y_equal) + } + + #[inline] + #[tracing::instrument(target = "r1cs")] + fn conditional_enforce_equal( + &self, + other: &Self, + condition: &Boolean<<P::BaseField as Field>::BasePrimeField>, + ) -> Result<(), SynthesisError> { + let x_equal = self.x.is_eq(&other.x)?; + let y_equal = self.y.is_eq(&other.y)?; + let coordinates_equal = x_equal.and(&y_equal)?; + coordinates_equal.conditional_enforce_equal(&Boolean::Constant(true), condition)?; + Ok(()) + } + + #[inline] + #[tracing::instrument(target = "r1cs")] + fn enforce_equal(&self, other: &Self) -> Result<(), SynthesisError> { + self.x.enforce_equal(&other.x)?; + self.y.enforce_equal(&other.y)?; + Ok(()) + } + + #[inline] + #[tracing::instrument(target = "r1cs")] + fn conditional_enforce_not_equal( + &self, + other: &Self, + condition: &Boolean<<P::BaseField as Field>::BasePrimeField>, + ) -> Result<(), SynthesisError> { + let is_equal = self.is_eq(other)?; + is_equal + .and(condition)? + .enforce_equal(&Boolean::Constant(false)) + } +} + #[cfg(test)] mod test_non_zero_affine { use crate::alloc::AllocVar; + use crate::eq::EqGadget; use crate::fields::fp::{AllocatedFp, FpVar}; use crate::groups::curves::short_weierstrass::non_zero_affine::NonZeroAffineVar; use crate::groups::curves::short_weierstrass::ProjectiveVar;@@ -299,5 +352,51 @@
assert!(cs.is_satisfied().unwrap()); assert_eq!(sum_a.0, sum_b.0); assert_eq!(sum_a.1, sum_b.1); + } + + #[test] + fn correctness_test_eq() { + let cs = ConstraintSystem::<Fq>::new_ref(); + + let x = FpVar::Var( + AllocatedFp::<Fq>::new_witness(cs.clone(), || { + Ok(G1Parameters::AFFINE_GENERATOR_COEFFS.0) + }) + .unwrap(), + ); + let y = FpVar::Var( + AllocatedFp::<Fq>::new_witness(cs.clone(), || { + Ok(G1Parameters::AFFINE_GENERATOR_COEFFS.1) + }) + .unwrap(), + ); + + let a = NonZeroAffineVar::<G1Parameters, FpVar<Fq>>::new(x, y); + + let n = 10; + + let a_multiples: Vec<NonZeroAffineVar<G1Parameters, FpVar<Fq>>> = + std::iter::successors(Some(a.clone()), |acc| Some(acc.add_unchecked(&a).unwrap())) + .take(n) + .collect(); + + let all_equal: Vec<NonZeroAffineVar<G1Parameters, FpVar<Fq>>> = (0..n / 2) + .map(|i| { + a_multiples[i] + .add_unchecked(&a_multiples[n - i - 1]) + .unwrap() + }) + .collect(); + + for i in 0..n - 1 { + a_multiples[i] + .enforce_not_equal(&a_multiples[i + 1]) + .unwrap(); + } + for i in 0..all_equal.len() - 1 { + all_equal[i].enforce_equal(&all_equal[i + 1]).unwrap(); + } + + assert!(cs.is_satisfied().unwrap()); } }