Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support scalar mul of arkworks curve using precomputed table #34

Merged
merged 8 commits into from
Mar 26, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions manta-crypto/src/ecc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,9 @@ pub trait ScalarMul<COM = ()> {
/// Elliptic Curve Pre-processed Scalar Multiplication Operation
pub trait PreprocessedScalarMul<COM, const N: usize>: ScalarMul<COM> + Sized {
tsunrise marked this conversation as resolved.
Show resolved Hide resolved
/// Performs the scalar multiplication against a pre-computed table.
///
/// The pre-computed table is powers of two of `scalar`, such that
/// `table[i] = 2^i * G`.
#[must_use]
fn preprocessed_scalar_mul(
table: &[Self; N],
Expand All @@ -162,7 +165,9 @@ where
/// Elliptic Curve Group
pub trait Group<COM = ()>: PointAdd<COM> + PointDouble<COM> + ScalarMul<COM> {}

/// Pre-processed Scalar Multiplication Table
/// Pre-processed Scalar Multiplication Table.
///
/// This table contains power-of-two multiples of a fixed base group element.
#[cfg_attr(
feature = "serde",
derive(Deserialize, Serialize),
Expand All @@ -180,17 +185,16 @@ pub struct PreprocessedScalarMulTable<G, const N: usize> {
}

impl<G, const N: usize> PreprocessedScalarMulTable<G, N> {
/// Builds a new [`PreprocessedScalarMulTable`] collection from `base`.
/// Builds a new [`PreprocessedScalarMulTable`] collection from `base`, such that `table[i] = 2^i * base`.
#[inline]
pub fn from_base<COM>(mut base: G, compiler: &mut COM) -> Self
where
G: Clone + PointAdd<COM, Output = G> + PointDouble<COM, Output = G>,
{
let mut powers = Vec::with_capacity(N);
let double = base.double(compiler);
for _ in 0..N {
powers.push(base.clone());
base.add_assign(&double, compiler);
base.double_assign(compiler);
}
Self::from_powers_unchecked(
powers
Expand Down
1 change: 1 addition & 0 deletions manta-pay/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -120,3 +120,4 @@ tungstenite = { version = "0.17.2", optional = true, default-features = false, f

[dev-dependencies]
manta-pay = { path = ".", features = ["groth16", "test"] }
manta-crypto = { path = "../manta-crypto", default-features = false, features = ["getrandom"] }
8 changes: 8 additions & 0 deletions manta-pay/src/crypto/constraint/arkworks/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,14 @@ where
cs.set_optimization_goal(ark_r1cs::OptimizationGoal::Constraints);
Self { cs }
}

/// Check if all constraints are satisfied.
#[inline]
pub fn is_satisfied(&self) -> bool {
self.cs
.is_satisfied()
.expect("is_satisfied is not allowed to fail")
}
}

impl<F> ConstraintSystem for R1CS<F>
Expand Down
125 changes: 124 additions & 1 deletion manta-pay/src/crypto/ecc/arkworks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ use ark_serialize::{CanonicalDeserialize, CanonicalSerialize, SerializationError
use core::marker::PhantomData;
use manta_crypto::{
constraint::{Allocator, Constant, Equal, Public, Secret, ValueSource, Variable},
ecc,
ecc::{self, PointAdd, PointDouble},
key::kdf,
rand::{CryptoRng, RngCore, Sample, Standard},
};
Expand All @@ -43,6 +43,9 @@ type ConstraintField<C> = <<C as ProjectiveCurve>::BaseField as Field>::BasePrim
/// Compiler Type
type Compiler<C> = R1CS<ConstraintField<C>>;

/// Curve Scalar Parameter
type ScalarParam<C> = <<C as ProjectiveCurve>::ScalarField as PrimeField>::Params;

/// Scalar Field Element
pub type Scalar<C> = Fp<<C as ProjectiveCurve>::ScalarField>;

Expand Down Expand Up @@ -363,6 +366,8 @@ where
}

/// Elliptic Curve Group Element Variable
#[derive(derivative::Derivative)]
#[derivative(Clone)]
pub struct GroupVar<C, CV>(pub(crate) CV, PhantomData<C>)
where
C: ProjectiveCurve,
Expand Down Expand Up @@ -406,6 +411,45 @@ where
}
}

macro_rules! impl_processed_scalar_mul {
($curve: ty) => {
impl<CV>
ecc::PreprocessedScalarMul<
Compiler<$curve>,
{
<<$curve as ProjectiveCurve>::ScalarField as PrimeField>::Params::MODULUS_BITS
as usize
},
tsunrise marked this conversation as resolved.
Show resolved Hide resolved
> for GroupVar<$curve, CV>
where
CV: CurveVar<$curve, ConstraintField<$curve>>,
{
#[inline]
fn preprocessed_scalar_mul(
tsunrise marked this conversation as resolved.
Show resolved Hide resolved
table: &[Self; ScalarParam::<$curve>::MODULUS_BITS as usize],
scalar: &Self::Scalar,
compiler: &mut Compiler<$curve>,
) -> Self::Output {
let _ = compiler;
let mut result = CV::zero();
let scalar_bits = scalar
.0
.to_bits_le()
.expect("Bit decomposition is not allowed to fail.");
// TODO: Add `+` implementations, `conditional_add` to avoid unnecessary clones.
for (bit, base) in scalar_bits.into_iter().zip(table.iter()) {
result = bit
.select(&(result.clone() + &base.0), &result)
.expect("Conditional select is not allowed to fail. ");
}
Self(result, PhantomData)
}
}
};
}

impl_processed_scalar_mul!(ark_ed_on_bls12_381::EdwardsProjective);

impl<C, CV> Equal<Compiler<C>> for GroupVar<C, CV>
where
C: ProjectiveCurve,
Expand Down Expand Up @@ -495,3 +539,82 @@ where
)
}
}

impl<C, CV> PointAdd<Compiler<C>> for GroupVar<C, CV>
where
C: ProjectiveCurve,
CV: CurveVar<C, ConstraintField<C>>,
{
type Output = Self;

#[inline]
fn add(&self, rhs: &Self, compiler: &mut Compiler<C>) -> Self::Output {
tsunrise marked this conversation as resolved.
Show resolved Hide resolved
let _ = compiler;
let mut result = self.0.clone();
tsunrise marked this conversation as resolved.
Show resolved Hide resolved
result += &rhs.0;
Self::new(result)
}
}

impl<C, CV> PointDouble<Compiler<C>> for GroupVar<C, CV>
where
C: ProjectiveCurve,
CV: CurveVar<C, ConstraintField<C>>,
{
type Output = Self;

#[inline]
fn double(&self, compiler: &mut Compiler<C>) -> Self::Output {
tsunrise marked this conversation as resolved.
Show resolved Hide resolved
let _ = compiler;
Self::new(self.0.double().expect("Doubling is not allowed to fail."))
}
}

#[cfg(test)]
mod test {
use super::*;
use ark_ec::ProjectiveCurve;
use ark_ed_on_bls12_381::{constraints::EdwardsVar, EdwardsProjective};
use ecc::PreprocessedScalarMul;
use manta_crypto::{
constraint::ConstraintSystem,
ecc::{PreprocessedScalarMulTable, ScalarMul},
rand::OsRng,
};

/// Tests preprocessed scalar multiplication on curve `C`.
fn preprocessed_scalar_mul_test_template<C, CV, const N: usize>(rng: &mut OsRng)
tsunrise marked this conversation as resolved.
Show resolved Hide resolved
where
C: ProjectiveCurve,
CV: CurveVar<C, ConstraintField<C>>,
GroupVar<C, CV>: PreprocessedScalarMul<
Compiler<C>,
N,
Scalar = ScalarVar<C, CV>,
Output = GroupVar<C, CV>,
>,
{
const NUM_TRIALS: usize = 5;

let mut cs = R1CS::for_known();
for _ in 0..NUM_TRIALS {
let base = Group::<_>::gen(rng).as_known::<Secret, GroupVar<_, _>>(&mut cs);
let scalar = Scalar::<C>::gen(rng).as_known::<Secret, _>(&mut cs);
let expected = base.scalar_mul(&scalar, &mut cs);
let table = PreprocessedScalarMulTable::<_, N>::from_base(base, &mut cs);
let actual = table.scalar_mul(&scalar, &mut cs);
cs.assert_eq(&expected, &actual);
}
assert!(cs.is_satisfied());
}

/// Tests preprocessed scalar multiplication on different curves.
#[test]
fn preprocessed_scalar_mul() {
preprocessed_scalar_mul_test_template::<
EdwardsProjective,
EdwardsVar,
{ ScalarParam::<EdwardsProjective>::MODULUS_BITS as usize },
>(&mut OsRng);
}
}