mirror of
https://github.com/arnaucube/ark-r1cs-std.git
synced 2026-01-07 14:31:32 +01:00
feat: implement Joye07 scalar mul
This commit is contained in:
@@ -498,6 +498,80 @@ where
|
||||
Ok(Self::new(self.x.clone(), self.y.negate()?, self.z.clone()))
|
||||
}
|
||||
|
||||
/// Computes `bits * self`, where `bits` is a little-endian
|
||||
/// `Boolean` representation of a scalar.
|
||||
///
|
||||
/// [Joye07](<https://www.iacr.org/archive/ches2007/47270135/47270135.pdf>), Alg.1.
|
||||
#[tracing::instrument(target = "r1cs", skip(bits))]
|
||||
fn scalar_mul_joye_le<'a>(
|
||||
&self,
|
||||
bits: impl Iterator<Item = &'a Boolean<<P::BaseField as Field>::BasePrimeField>>,
|
||||
) -> Result<Self, SynthesisError> {
|
||||
if self.is_constant() {
|
||||
if self.value().unwrap().is_zero() {
|
||||
return Ok(self.clone());
|
||||
}
|
||||
}
|
||||
let self_affine = self.to_affine()?;
|
||||
let (x, y, infinity) = (self_affine.x, self_affine.y, self_affine.infinity);
|
||||
// We first handle the non-zero case, and then later will conditionally select
|
||||
// zero if `self` was zero. However, we also want to make sure that generated
|
||||
// constraints are satisfiable in both cases.
|
||||
//
|
||||
// In particular, using non-sensible values for `x` and `y` in zero-case may cause
|
||||
// `unchecked` operations to generate constraints that can never be satisfied, depending
|
||||
// on the curve equation coefficients.
|
||||
//
|
||||
// The safest approach is to use coordinates of some point from the curve, thus not
|
||||
// violating assumptions of `NonZeroAffine`. For instance, generator point.
|
||||
let x = infinity.select(&F::constant(P::GENERATOR.x), &x)?;
|
||||
let y = infinity.select(&F::constant(P::GENERATOR.y), &y)?;
|
||||
let non_zero_self = NonZeroAffineVar::new(x, y);
|
||||
|
||||
let mut bits = bits.collect::<Vec<_>>();
|
||||
if bits.len() == 0 {
|
||||
return Ok(Self::zero());
|
||||
}
|
||||
// Remove unnecessary constant zeros in the most-significant positions.
|
||||
bits = bits
|
||||
.into_iter()
|
||||
// We iterate from the MSB down.
|
||||
.rev()
|
||||
// Skip leading zeros, if they are constants.
|
||||
.skip_while(|b| b.is_constant() && (b.value().unwrap() == false))
|
||||
.collect();
|
||||
// After collecting we are in big-endian form; we have to reverse to get back to
|
||||
// little-endian.
|
||||
bits.reverse();
|
||||
|
||||
// second bit
|
||||
let mut acc = non_zero_self.triple()?;
|
||||
let mut acc0 = bits[1].select(&acc, &non_zero_self)?;
|
||||
let mut acc1 = bits[1].select(&non_zero_self, &acc)?;
|
||||
|
||||
for bit in bits.iter().skip(2).rev().skip(1).rev() {
|
||||
acc = acc0.double_and_select_add_unchecked(bit, &acc1)?;
|
||||
acc0 = bit.select(&acc, &acc0)?;
|
||||
acc1 = bit.select(&acc1, &acc)?;
|
||||
}
|
||||
|
||||
// last bit
|
||||
let n = bits.len() - 1;
|
||||
acc = acc0.double_and_select_add_unchecked(bits[n], &acc1)?;
|
||||
acc0 = bits[n].select(&acc, &acc0)?;
|
||||
|
||||
// first bit
|
||||
let temp = NonZeroAffineVar::new(non_zero_self.x, non_zero_self.y.negate()?);
|
||||
acc1 = acc0.add_unchecked(&temp)?;
|
||||
acc0 = bits[0].select(
|
||||
&acc0,
|
||||
&acc1,
|
||||
)?;
|
||||
|
||||
let mul_result = acc0.into_projective();
|
||||
infinity.select(&Self::zero(), &mul_result)
|
||||
}
|
||||
|
||||
/// Computes `bits * self`, where `bits` is a little-endian
|
||||
/// `Boolean` representation of a scalar.
|
||||
#[tracing::instrument(target = "r1cs", skip(bits))]
|
||||
@@ -978,61 +1052,3 @@ where
|
||||
Ok(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_sw_curve {
|
||||
use crate::{
|
||||
alloc::AllocVar,
|
||||
eq::EqGadget,
|
||||
fields::{fp::FpVar, nonnative::NonNativeFieldVar},
|
||||
groups::{curves::short_weierstrass::ProjectiveVar, CurveVar},
|
||||
ToBitsGadget,
|
||||
};
|
||||
use ark_ec::{
|
||||
short_weierstrass::{Projective, SWCurveConfig},
|
||||
CurveGroup,
|
||||
};
|
||||
use ark_ff::PrimeField;
|
||||
use ark_relations::r1cs::{ConstraintSystem, Result};
|
||||
use ark_std::UniformRand;
|
||||
use num_traits::Zero;
|
||||
|
||||
fn zero_point_scalar_mul_satisfied<G>() -> Result<bool>
|
||||
where
|
||||
G: CurveGroup,
|
||||
G::BaseField: PrimeField,
|
||||
G::Config: SWCurveConfig,
|
||||
{
|
||||
let mut rng = ark_std::test_rng();
|
||||
|
||||
let cs = ConstraintSystem::new_ref();
|
||||
let point_in = Projective::<G::Config>::zero();
|
||||
let point_out = Projective::<G::Config>::zero();
|
||||
let scalar = G::ScalarField::rand(&mut rng);
|
||||
|
||||
let point_in =
|
||||
ProjectiveVar::<G::Config, FpVar<G::BaseField>>::new_witness(cs.clone(), || {
|
||||
Ok(point_in)
|
||||
})?;
|
||||
let point_out =
|
||||
ProjectiveVar::<G::Config, FpVar<G::BaseField>>::new_input(cs.clone(), || {
|
||||
Ok(point_out)
|
||||
})?;
|
||||
let scalar = NonNativeFieldVar::new_input(cs.clone(), || Ok(scalar))?;
|
||||
|
||||
let mul = point_in.scalar_mul_le(scalar.to_bits_le().unwrap().iter())?;
|
||||
|
||||
point_out.enforce_equal(&mul)?;
|
||||
|
||||
cs.is_satisfied()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_zero_point_scalar_mul() {
|
||||
assert!(zero_point_scalar_mul_satisfied::<ark_bls12_381::G1Projective>().unwrap());
|
||||
assert!(zero_point_scalar_mul_satisfied::<ark_pallas::Projective>().unwrap());
|
||||
assert!(zero_point_scalar_mul_satisfied::<ark_mnt4_298::G1Projective>().unwrap());
|
||||
assert!(zero_point_scalar_mul_satisfied::<ark_mnt6_298::G1Projective>().unwrap());
|
||||
assert!(zero_point_scalar_mul_satisfied::<ark_bn254::G1Projective>().unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,6 +130,79 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Conditionally computes `(self + other) + self` or `(self + other) + other`
|
||||
/// depending on the value of `cond`.
|
||||
///
|
||||
/// This follows the formulae from [\[ELM03\]](https://arxiv.org/abs/math/0208038).
|
||||
#[tracing::instrument(target = "r1cs", skip(self))]
|
||||
pub fn double_and_select_add_unchecked(
|
||||
&self,
|
||||
cond: &Boolean<<P::BaseField as Field>::BasePrimeField>,
|
||||
other: &Self,
|
||||
) -> Result<Self, SynthesisError> {
|
||||
if [self].is_constant() || other.is_constant() {
|
||||
// /!\ TODO: correct constant case /!\
|
||||
self.double()?.add_unchecked(other)
|
||||
} else {
|
||||
// It's okay to use `unchecked` the precondition is that `self != ±other` (i.e.
|
||||
// same logic as in `add_unchecked`)
|
||||
let (x1, y1) = (&self.x, &self.y);
|
||||
let (x2, y2) = (&other.x, &other.y);
|
||||
|
||||
// Calculate self + other:
|
||||
// slope lambda := (y2 - y1)/(x2 - x1);
|
||||
// x3 = lambda^2 - x1 - x2;
|
||||
// y3 = lambda * (x1 - x3) - y1
|
||||
let numerator = y2 - y1;
|
||||
let denominator = x2 - x1;
|
||||
let lambda_1 = numerator.mul_by_inverse_unchecked(&denominator)?;
|
||||
|
||||
let x3 = lambda_1.square()? - x1 - x2;
|
||||
|
||||
let x = &F::conditionally_select(&cond, &x1, &x2)?;
|
||||
let y = &F::conditionally_select(&cond, &y1, &y2)?;
|
||||
|
||||
// Calculate final addition slope:
|
||||
let lambda_2 =
|
||||
(lambda_1 + y.double()?.mul_by_inverse_unchecked(&(&x3 - x))?).negate()?;
|
||||
|
||||
// Calculate (self + other) + (self or other):
|
||||
let x4 = lambda_2.square()? - x - x3;
|
||||
let y4 = lambda_2 * &(x - &x4) - y;
|
||||
Ok(Self::new(x4, y4))
|
||||
}
|
||||
}
|
||||
|
||||
/// Triples `self`.
|
||||
///
|
||||
/// This follows the formulae from [\[ELM03\]](https://arxiv.org/abs/math/0208038).
|
||||
#[tracing::instrument(target = "r1cs", skip(self))]
|
||||
pub fn triple(&self) -> Result<Self, SynthesisError> {
|
||||
if [self].is_constant() {
|
||||
self.double()?.add_unchecked(self)
|
||||
} else {
|
||||
let (x1, y1) = (&self.x, &self.y);
|
||||
let x1_sqr = x1.square()?;
|
||||
// tangent lambda_1 := (3 * x1^2 + a) / (2 * y1);
|
||||
// x3 = lambda_1^2 - 2x1
|
||||
// y3 = lambda_1 * (x1 - x3) - y1
|
||||
let numerator = x1_sqr.double()? + &x1_sqr + P::COEFF_A;
|
||||
let denominator = y1.double()?;
|
||||
// It's okay to use `unchecked` here, because the precondition of `double` is
|
||||
// that self != zero.
|
||||
let lambda_1 = numerator.mul_by_inverse_unchecked(&denominator)?;
|
||||
let x3 = lambda_1.square()? - x1.double()?;
|
||||
|
||||
// Calculate final addition slope:
|
||||
let lambda_2 =
|
||||
(lambda_1 + y1.double()?.mul_by_inverse_unchecked(&(&x3 - x1))?).negate()?;
|
||||
|
||||
let x4 = lambda_2.square()? - x1 - x3;
|
||||
let y4 = lambda_2 * &(x1 - &x4) - y1;
|
||||
Ok(Self::new(x4, y4))
|
||||
}
|
||||
}
|
||||
|
||||
/// Doubles `self` in place.
|
||||
#[tracing::instrument(target = "r1cs", skip(self))]
|
||||
pub fn double_in_place(&mut self) -> Result<(), SynthesisError> {
|
||||
@@ -390,4 +463,50 @@ mod test_non_zero_affine {
|
||||
|
||||
assert!(cs.is_satisfied().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn correctness_double_and_add_select() {
|
||||
let cs = ConstraintSystem::<Fq>::new_ref();
|
||||
|
||||
let x = FpVar::Var(
|
||||
AllocatedFp::<Fq>::new_witness(cs.clone(), || Ok(G1Config::GENERATOR.x)).unwrap(),
|
||||
);
|
||||
let y = FpVar::Var(
|
||||
AllocatedFp::<Fq>::new_witness(cs.clone(), || Ok(G1Config::GENERATOR.y)).unwrap(),
|
||||
);
|
||||
|
||||
// The following code tests `double_and_add`.
|
||||
let sum_a = {
|
||||
let a = ProjectiveVar::<G1Config, FpVar<Fq>>::new(
|
||||
x.clone(),
|
||||
y.clone(),
|
||||
FpVar::Constant(Fq::one()),
|
||||
);
|
||||
|
||||
let mut cur = a.clone();
|
||||
cur.double_in_place().unwrap();
|
||||
for _ in 1..10 {
|
||||
cur.double_in_place().unwrap();
|
||||
cur = cur + &a;
|
||||
}
|
||||
|
||||
let sum = cur.value().unwrap().into_affine();
|
||||
(sum.x, sum.y)
|
||||
};
|
||||
|
||||
let sum_b = {
|
||||
let a = NonZeroAffineVar::<G1Config, FpVar<Fq>>::new(x, y);
|
||||
|
||||
let mut cur = a.double().unwrap();
|
||||
for _ in 1..10 {
|
||||
cur = cur.double_and_add_unchecked(&a).unwrap();
|
||||
}
|
||||
|
||||
(cur.x.value().unwrap(), cur.y.value().unwrap())
|
||||
};
|
||||
|
||||
assert!(cs.is_satisfied().unwrap());
|
||||
assert_eq!(sum_a.0, sum_b.0);
|
||||
assert_eq!(sum_a.1, sum_b.1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,6 +107,30 @@ pub trait CurveVar<C: CurveGroup, ConstraintF: Field>:
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Computes `bits * self`, where `bits` is a little-endian
|
||||
/// `Boolean` representation of a scalar.
|
||||
///
|
||||
/// [Joye07](<https://www.iacr.org/archive/ches2007/47270135/47270135.pdf>), Alg.1.
|
||||
#[tracing::instrument(target = "r1cs", skip(bits))]
|
||||
fn scalar_mul_joye_le<'a>(
|
||||
&self,
|
||||
bits: impl Iterator<Item = &'a Boolean<ConstraintF>>,
|
||||
) -> Result<Self, SynthesisError> {
|
||||
// TODO: in the constant case we should call precomputed_scalar_mul_le,
|
||||
// but rn there's a bug when doing this with TE curves.
|
||||
|
||||
// Computes the standard little-endian double-and-add algorithm
|
||||
// (Algorithm 3.26, Guide to Elliptic Curve Cryptography)
|
||||
let mut res = Self::zero();
|
||||
let mut multiple = self.clone();
|
||||
for bit in bits {
|
||||
let tmp = res.clone() + &multiple;
|
||||
res = bit.select(&tmp, &res)?;
|
||||
multiple.double_in_place()?;
|
||||
}
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Computes a `I * self` in place, where `I` is a `Boolean` *little-endian*
|
||||
/// representation of the scalar.
|
||||
///
|
||||
@@ -161,3 +185,62 @@ pub trait CurveVar<C: CurveGroup, ConstraintF: Field>:
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_sw_arithmetic {
|
||||
use crate::{
|
||||
alloc::AllocVar,
|
||||
eq::EqGadget,
|
||||
fields::{fp::FpVar, nonnative::NonNativeFieldVar},
|
||||
groups::{curves::short_weierstrass::ProjectiveVar, CurveVar},
|
||||
ToBitsGadget,
|
||||
};
|
||||
use ark_ec::{
|
||||
short_weierstrass::{Projective, SWCurveConfig},
|
||||
CurveGroup,
|
||||
};
|
||||
use ark_ff::PrimeField;
|
||||
use ark_relations::r1cs::{ConstraintSystem, Result};
|
||||
use ark_std::UniformRand;
|
||||
|
||||
fn point_scalar_mul_joye_satisfied<G>() -> Result<bool>
|
||||
where
|
||||
G: CurveGroup,
|
||||
G::BaseField: PrimeField,
|
||||
G::Config: SWCurveConfig,
|
||||
{
|
||||
let mut rng = ark_std::test_rng();
|
||||
|
||||
let cs = ConstraintSystem::new_ref();
|
||||
let point_in = Projective::<G::Config>::rand(&mut rng);
|
||||
let scalar = G::ScalarField::rand(&mut rng);
|
||||
let point_out = point_in * scalar;
|
||||
|
||||
let point_in =
|
||||
ProjectiveVar::<G::Config, FpVar<G::BaseField>>::new_witness(cs.clone(), || {
|
||||
Ok(point_in)
|
||||
})?;
|
||||
let point_out =
|
||||
ProjectiveVar::<G::Config, FpVar<G::BaseField>>::new_input(cs.clone(), || {
|
||||
Ok(point_out)
|
||||
})?;
|
||||
let scalar = NonNativeFieldVar::new_input(cs.clone(), || Ok(scalar))?;
|
||||
|
||||
let mul = point_in.scalar_mul_joye_le(scalar.to_bits_le().unwrap().iter())?;
|
||||
|
||||
point_out.enforce_equal(&mul)?;
|
||||
|
||||
println!(
|
||||
"#r1cs for scalar_mul_joye_le: {}",
|
||||
cs.num_constraints()
|
||||
);
|
||||
|
||||
|
||||
cs.is_satisfied()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_point_scalar_mul_joye() {
|
||||
assert!(point_scalar_mul_joye_satisfied::<ark_bn254::G1Projective>().unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user