mirror of
https://github.com/arnaucube/ark-r1cs-std.git
synced 2026-01-10 16:01:28 +01:00
More efficient scalar multiplication for Short Weierstrass curves (#33)
* When a group element is a constant, precompute multiples of powers of two, and perform simple conditional additions (no doubling!). * For short weierstrass curves, addition with a constant now uses mixed addition, which results in lower constraint weight. * For short weierstrass curves, scalar multiplication now uses mixed addition, saving 1 constraint per bit of the scalar, along with lower constraint weight (at the cost of a small constant number of constraints to check for edge cases)
This commit is contained in:
@@ -221,6 +221,46 @@ where
|
|||||||
|
|
||||||
Ok(Self::new(x, y, z))
|
Ok(Self::new(x, y, z))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Mixed addition, which is useful when `other = (x2, y2)` is known to have z = 1.
|
||||||
|
#[tracing::instrument(target = "r1cs", skip(self, x2, y2))]
|
||||||
|
pub(crate) fn add_mixed(&self, (x2, y2): (&F, &F)) -> Result<Self, SynthesisError> {
|
||||||
|
// Complete mixed addition formula from Renes-Costello-Batina 2015
|
||||||
|
// Algorithm 2
|
||||||
|
// (https://eprint.iacr.org/2015/1060).
|
||||||
|
// Below, comments at the end of a line denote the corresponding
|
||||||
|
// step(s) of the algorithm
|
||||||
|
//
|
||||||
|
// Adapted from code in
|
||||||
|
// https://github.com/RustCrypto/elliptic-curves/blob/master/p256/src/arithmetic/projective.rs
|
||||||
|
let three_b = P::COEFF_B.double() + &P::COEFF_B;
|
||||||
|
let (x1, y1, z1) = (&self.x, &self.y, &self.z);
|
||||||
|
|
||||||
|
let xx = x1 * x2; // 1
|
||||||
|
let yy = y1 * y2; // 2
|
||||||
|
let xy_pairs = ((x1 + y1) * &(x2 + y2)) - (&xx + &yy); // 4, 5, 6, 7, 8
|
||||||
|
let xz_pairs = (x2 * z1) + x1; // 8, 9
|
||||||
|
let yz_pairs = (y2 * z1) + y1; // 10, 11
|
||||||
|
|
||||||
|
let axz = mul_by_coeff_a::<P, F>(&xz_pairs); // 12
|
||||||
|
|
||||||
|
let bz3_part = &axz + z1 * three_b; // 13, 14
|
||||||
|
|
||||||
|
let yy_m_bz3 = &yy - &bz3_part; // 15
|
||||||
|
let yy_p_bz3 = &yy + &bz3_part; // 16
|
||||||
|
|
||||||
|
let azz = mul_by_coeff_a::<P, F>(z1); // 20
|
||||||
|
let xx3_p_azz = xx.double().unwrap() + &xx + &azz; // 18, 19, 22
|
||||||
|
|
||||||
|
let bxz3 = &xz_pairs * three_b; // 21
|
||||||
|
let b3_xz_pairs = mul_by_coeff_a::<P, F>(&(&xx - &azz)) + &bxz3; // 23, 24, 25
|
||||||
|
|
||||||
|
let x = (&yy_m_bz3 * &xy_pairs) - &yz_pairs * &b3_xz_pairs; // 28,29, 30
|
||||||
|
let y = (&yy_p_bz3 * &yy_m_bz3) + &xx3_p_azz * b3_xz_pairs; // 17, 26, 27
|
||||||
|
let z = (&yy_p_bz3 * &yz_pairs) + xy_pairs * xx3_p_azz; // 31, 32, 33
|
||||||
|
|
||||||
|
Ok(ProjectiveVar::new(x, y, z))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P, F> CurveVar<SWProjective<P>, <P::BaseField as Field>::BasePrimeField>
|
impl<P, F> CurveVar<SWProjective<P>, <P::BaseField as Field>::BasePrimeField>
|
||||||
@@ -288,18 +328,19 @@ where
|
|||||||
// formulae are incomplete for even-order points.
|
// formulae are incomplete for even-order points.
|
||||||
#[tracing::instrument(target = "r1cs")]
|
#[tracing::instrument(target = "r1cs")]
|
||||||
fn enforce_prime_order(&self) -> Result<(), SynthesisError> {
|
fn enforce_prime_order(&self) -> Result<(), SynthesisError> {
|
||||||
let r_minus_1 = (-P::ScalarField::one()).into_repr();
|
unimplemented!("cannot enforce prime order");
|
||||||
|
// let r_minus_1 = (-P::ScalarField::one()).into_repr();
|
||||||
|
|
||||||
let mut result = Self::zero();
|
// let mut result = Self::zero();
|
||||||
for b in BitIteratorBE::without_leading_zeros(r_minus_1) {
|
// for b in BitIteratorBE::without_leading_zeros(r_minus_1) {
|
||||||
result.double_in_place()?;
|
// result.double_in_place()?;
|
||||||
|
|
||||||
if b {
|
// if b {
|
||||||
result += self;
|
// result += self;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
self.negate()?.enforce_equal(&result)?;
|
// self.negate()?.enforce_equal(&result)?;
|
||||||
Ok(())
|
// Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
@@ -308,9 +349,11 @@ where
|
|||||||
// Complete doubling formula from Renes-Costello-Batina 2015
|
// Complete doubling formula from Renes-Costello-Batina 2015
|
||||||
// Algorithm 3
|
// Algorithm 3
|
||||||
// (https://eprint.iacr.org/2015/1060).
|
// (https://eprint.iacr.org/2015/1060).
|
||||||
|
// Below, comments at the end of a line denote the corresponding
|
||||||
|
// step(s) of the algorithm
|
||||||
//
|
//
|
||||||
// Adapted from code in
|
// Adapted from code in
|
||||||
// https://github.com/RustCrypto/elliptic-curves/blob/master/p256/src/arithmetic.rs
|
// https://github.com/RustCrypto/elliptic-curves/blob/master/p256/src/arithmetic/projective.rs
|
||||||
let three_b = P::COEFF_B.double() + &P::COEFF_B;
|
let three_b = P::COEFF_B.double() + &P::COEFF_B;
|
||||||
|
|
||||||
let xx = self.x.square()?; // 1
|
let xx = self.x.square()?; // 1
|
||||||
@@ -346,6 +389,57 @@ where
|
|||||||
fn negate(&self) -> Result<Self, SynthesisError> {
|
fn negate(&self) -> Result<Self, SynthesisError> {
|
||||||
Ok(Self::new(self.x.clone(), self.y.negate()?, self.z.clone()))
|
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.
|
||||||
|
#[tracing::instrument(target = "r1cs", skip(bits))]
|
||||||
|
fn scalar_mul_le<'a>(
|
||||||
|
&self,
|
||||||
|
bits: impl Iterator<Item = &'a Boolean<<P::BaseField as Field>::BasePrimeField>>,
|
||||||
|
) -> Result<Self, SynthesisError> {
|
||||||
|
if self.is_constant() {
|
||||||
|
// Compute 2^i * self for i in 0..bits.len()
|
||||||
|
// (these will be used by`precomputed_base_scalar_mul_le`, to perform
|
||||||
|
// a conditional addition.).
|
||||||
|
//
|
||||||
|
// TODO: if `bits.len()` is small n, it might be cheaper to
|
||||||
|
// conditionally select between 2^n options.
|
||||||
|
let mut value = self.value()?;
|
||||||
|
let bits_and_multiples = bits
|
||||||
|
.map(|b| {
|
||||||
|
let multiple = value;
|
||||||
|
value.double_in_place();
|
||||||
|
(b, multiple)
|
||||||
|
})
|
||||||
|
.collect::<ark_std::vec::Vec<_>>();
|
||||||
|
let mut result = self.clone();
|
||||||
|
result.precomputed_base_scalar_mul_le(
|
||||||
|
bits_and_multiples.iter().map(|&(ref b, ref c)| (*b, c)),
|
||||||
|
)?;
|
||||||
|
Ok(result)
|
||||||
|
} else {
|
||||||
|
// Computes the standard big-endian double-and-add algorithm
|
||||||
|
// (Algorithm 3.27, Guide to Elliptic Curve Cryptography)
|
||||||
|
// the input is little-endian, so we need to reverse it to get it in
|
||||||
|
// big-endian.
|
||||||
|
let mut bits = bits.collect::<Vec<_>>();
|
||||||
|
bits.reverse();
|
||||||
|
|
||||||
|
let mut res = Self::zero();
|
||||||
|
let self_affine = self.to_affine()?;
|
||||||
|
for bit in bits {
|
||||||
|
res.double_in_place()?;
|
||||||
|
let tmp = res.add_mixed((&self_affine.x, &self_affine.y))?;
|
||||||
|
res = bit.select(&tmp, &res)?;
|
||||||
|
}
|
||||||
|
// The foregoing algorithm relies on mixed addition, and so does not
|
||||||
|
// work when the input (`self`) is zero. We hence have to perform
|
||||||
|
// a check to ensure that if the input is zero, then so is the output.
|
||||||
|
// The cost of this check should be less than the benefit of using
|
||||||
|
// mixed addition in almost all cases.
|
||||||
|
self_affine.infinity.select(&Self::zero(), &res)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P, F> ToConstraintFieldGadget<<P::BaseField as Field>::BasePrimeField> for ProjectiveVar<P, F>
|
impl<P, F> ToConstraintFieldGadget<<P::BaseField as Field>::BasePrimeField> for ProjectiveVar<P, F>
|
||||||
@@ -385,22 +479,48 @@ impl_bounded_ops!(
|
|||||||
add,
|
add,
|
||||||
AddAssign,
|
AddAssign,
|
||||||
add_assign,
|
add_assign,
|
||||||
|this: &'a ProjectiveVar<P, F>, other: &'a ProjectiveVar<P, F>| {
|
|mut this: &'a ProjectiveVar<P, F>, mut other: &'a ProjectiveVar<P, F>| {
|
||||||
|
// Implement complete addition for Short Weierstrass curves, following
|
||||||
|
// the complete addition formula from Renes-Costello-Batina 2015
|
||||||
|
// (https://eprint.iacr.org/2015/1060).
|
||||||
|
//
|
||||||
|
// We special case handling of constants to get better constraint weight.
|
||||||
|
if this.is_constant() {
|
||||||
|
// we'll just act like `other` is constant.
|
||||||
|
core::mem::swap(&mut this, &mut other);
|
||||||
|
}
|
||||||
|
|
||||||
|
if other.is_constant() {
|
||||||
|
// The value should exist because `other` is a constant.
|
||||||
|
let other = other.value().unwrap();
|
||||||
|
if other.is_zero() {
|
||||||
|
// this + 0 = this
|
||||||
|
this.clone()
|
||||||
|
} else {
|
||||||
|
// We'll use mixed addition to add non-zero constants.
|
||||||
|
let x = F::constant(other.x);
|
||||||
|
let y = F::constant(other.y);
|
||||||
|
this.add_mixed((&x, &y)).unwrap()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
// Complete addition formula from Renes-Costello-Batina 2015
|
// Complete addition formula from Renes-Costello-Batina 2015
|
||||||
// Algorithm 1
|
// Algorithm 1
|
||||||
// (https://eprint.iacr.org/2015/1060).
|
// (https://eprint.iacr.org/2015/1060).
|
||||||
|
// Below, comments at the end of a line denote the corresponding
|
||||||
|
// step(s) of the algorithm
|
||||||
//
|
//
|
||||||
// Adapted from code in
|
// Adapted from code in
|
||||||
// https://github.com/RustCrypto/elliptic-curves/blob/master/p256/src/arithmetic.rs
|
// https://github.com/RustCrypto/elliptic-curves/blob/master/p256/src/arithmetic/projective.rs
|
||||||
|
|
||||||
let three_b = P::COEFF_B.double() + &P::COEFF_B;
|
let three_b = P::COEFF_B.double() + &P::COEFF_B;
|
||||||
|
let (x1, y1, z1) = (&this.x, &this.y, &this.z);
|
||||||
|
let (x2, y2, z2) = (&other.x, &other.y, &other.z);
|
||||||
|
|
||||||
let xx = &this.x * &other.x; // 1
|
let xx = x1 * x2; // 1
|
||||||
let yy = &this.y * &other.y; // 2
|
let yy = y1 * y2; // 2
|
||||||
let zz = &this.z * &other.z; // 3
|
let zz = z1 * z2; // 3
|
||||||
let xy_pairs = ((&this.x + &this.y) * &(&other.x + &other.y)) - (&xx + &yy); // 4, 5, 6, 7, 8
|
let xy_pairs = ((x1 + y1) * &(x2 + y2)) - (&xx + &yy); // 4, 5, 6, 7, 8
|
||||||
let xz_pairs = ((&this.x + &this.z) * &(&other.x + &other.z)) - (&xx + &zz); // 9, 10, 11, 12, 13
|
let xz_pairs = ((x1 + z1) * &(x2 + z2)) - (&xx + &zz); // 9, 10, 11, 12, 13
|
||||||
let yz_pairs = ((&this.y + &this.z) * &(&other.y + &other.z)) - (&yy + &zz); // 14, 15, 16, 17, 18
|
let yz_pairs = ((y1 + z1) * &(y2 + z2)) - (&yy + &zz); // 14, 15, 16, 17, 18
|
||||||
|
|
||||||
let axz = mul_by_coeff_a::<P, F>(&xz_pairs); // 19
|
let axz = mul_by_coeff_a::<P, F>(&xz_pairs); // 19
|
||||||
|
|
||||||
@@ -420,6 +540,8 @@ impl_bounded_ops!(
|
|||||||
let z = (&yy_p_bzz3 * &yz_pairs) + xy_pairs * xx3_p_azz; // 41, 42, 43
|
let z = (&yy_p_bzz3 * &yz_pairs) + xy_pairs * xx3_p_azz; // 41, 42, 43
|
||||||
|
|
||||||
ProjectiveVar::new(x, y, z)
|
ProjectiveVar::new(x, y, z)
|
||||||
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
|this: &'a ProjectiveVar<P, F>, other: SWProjective<P>| {
|
|this: &'a ProjectiveVar<P, F>, other: SWProjective<P>| {
|
||||||
this + ProjectiveVar::constant(other)
|
this + ProjectiveVar::constant(other)
|
||||||
|
|||||||
@@ -92,6 +92,29 @@ pub trait CurveVar<C: ProjectiveCurve, ConstraintF: Field>:
|
|||||||
&self,
|
&self,
|
||||||
bits: impl Iterator<Item = &'a Boolean<ConstraintF>>,
|
bits: impl Iterator<Item = &'a Boolean<ConstraintF>>,
|
||||||
) -> Result<Self, SynthesisError> {
|
) -> Result<Self, SynthesisError> {
|
||||||
|
if self.is_constant() {
|
||||||
|
// Compute 2^i * self for i in 0..bits.len()
|
||||||
|
// (these will be used by`precomputed_base_scalar_mul_le`, to perform
|
||||||
|
// a conditional addition.).
|
||||||
|
//
|
||||||
|
// TODO: if `bits.len()` is small n, it might be cheaper to
|
||||||
|
// conditinally select between 2^n options.
|
||||||
|
let mut value = self.value().unwrap();
|
||||||
|
let bits_and_multiples = bits
|
||||||
|
.map(|b| {
|
||||||
|
let multiple = value;
|
||||||
|
value.double_in_place();
|
||||||
|
(b, multiple)
|
||||||
|
})
|
||||||
|
.collect::<ark_std::vec::Vec<_>>();
|
||||||
|
let mut result = self.clone();
|
||||||
|
result.precomputed_base_scalar_mul_le(
|
||||||
|
bits_and_multiples.iter().map(|&(ref b, ref c)| (*b, c)),
|
||||||
|
)?;
|
||||||
|
Ok(result)
|
||||||
|
} else {
|
||||||
|
// Computes the standard little-endian double-and-add algorithm
|
||||||
|
// (Algorithm 3.27, Guide to Elliptic Curve Cryptography)
|
||||||
let mut res = Self::zero();
|
let mut res = Self::zero();
|
||||||
let mut multiple = self.clone();
|
let mut multiple = self.clone();
|
||||||
for bit in bits {
|
for bit in bits {
|
||||||
@@ -101,30 +124,41 @@ pub trait CurveVar<C: ProjectiveCurve, ConstraintF: Field>:
|
|||||||
}
|
}
|
||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Computes a `I * self` in place, where `I` is a `Boolean` *little-endian*
|
/// Computes a `I * self` in place, where `I` is a `Boolean` *little-endian*
|
||||||
/// representation of the scalar.
|
/// representation of the scalar.
|
||||||
///
|
///
|
||||||
/// The base powers are precomputed power-of-two multiples of a single
|
/// The bases are precomputed power-of-two multiples of a single
|
||||||
/// base.
|
/// base.
|
||||||
#[tracing::instrument(target = "r1cs", skip(scalar_bits_with_base_powers))]
|
#[tracing::instrument(target = "r1cs", skip(scalar_bits_with_bases))]
|
||||||
fn precomputed_base_scalar_mul_le<'a, I, B>(
|
fn precomputed_base_scalar_mul_le<'a, I, B>(
|
||||||
&mut self,
|
&mut self,
|
||||||
scalar_bits_with_base_powers: I,
|
scalar_bits_with_bases: I,
|
||||||
) -> Result<(), SynthesisError>
|
) -> Result<(), SynthesisError>
|
||||||
where
|
where
|
||||||
I: Iterator<Item = (B, &'a C)>,
|
I: Iterator<Item = (B, &'a C)>,
|
||||||
B: Borrow<Boolean<ConstraintF>>,
|
B: Borrow<Boolean<ConstraintF>>,
|
||||||
C: 'a,
|
C: 'a,
|
||||||
{
|
{
|
||||||
for (bit, base_power) in scalar_bits_with_base_powers {
|
// Computes the standard little-endian double-and-add algorithm
|
||||||
let new_encoded = self.clone() + *base_power;
|
// (Algorithm 3.26, Guide to Elliptic Curve Cryptography)
|
||||||
*self = bit.borrow().select(&new_encoded, self)?;
|
|
||||||
|
// Let `original` be the initial value of `self`.
|
||||||
|
let mut result = Self::zero();
|
||||||
|
for (bit, base) in scalar_bits_with_bases {
|
||||||
|
// Compute `self + 2^i * original`
|
||||||
|
let self_plus_base = result.clone() + *base;
|
||||||
|
// If `bit == 1`, set self = self + 2^i * original;
|
||||||
|
// else, set self = self;
|
||||||
|
result = bit.borrow().select(&self_plus_base, &result)?;
|
||||||
}
|
}
|
||||||
|
*self = result;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Computes a `\sum_j I_j * B_j`, where `I_j` is a `Boolean`
|
/// Computes `Σⱼ(scalarⱼ * baseⱼ)` for all j,
|
||||||
|
/// where `scalarⱼ` is a `Boolean` *little-endian*
|
||||||
/// representation of the j-th scalar.
|
/// representation of the j-th scalar.
|
||||||
#[tracing::instrument(target = "r1cs", skip(bases, scalars))]
|
#[tracing::instrument(target = "r1cs", skip(bases, scalars))]
|
||||||
fn precomputed_base_multiscalar_mul_le<'a, T, I, B>(
|
fn precomputed_base_multiscalar_mul_le<'a, T, I, B>(
|
||||||
@@ -137,11 +171,11 @@ pub trait CurveVar<C: ProjectiveCurve, ConstraintF: Field>:
|
|||||||
B: Borrow<[C]>,
|
B: Borrow<[C]>,
|
||||||
{
|
{
|
||||||
let mut result = Self::zero();
|
let mut result = Self::zero();
|
||||||
// Compute ∏(h_i^{m_i}) for all i.
|
// Compute Σᵢ(bitᵢ * baseᵢ) for all i.
|
||||||
for (bits, base_powers) in scalars.zip(bases) {
|
for (bits, bases) in scalars.zip(bases) {
|
||||||
let base_powers = base_powers.borrow();
|
let bases = bases.borrow();
|
||||||
let bits = bits.to_bits_le()?;
|
let bits = bits.to_bits_le()?;
|
||||||
result.precomputed_base_scalar_mul_le(bits.iter().zip(base_powers))?;
|
result.precomputed_base_scalar_mul_le(bits.iter().zip(bases))?;
|
||||||
}
|
}
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user