diff --git a/.gitignore b/.gitignore index 4f0275a10..2fb043a76 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ Cargo.lock *.orig coeffs_* msm_bases_* +*.py diff --git a/algebra/src/curves/bls12_377/g1.rs b/algebra/src/curves/bls12_377/g1.rs index 2f3de6e36..ec872fe64 100644 --- a/algebra/src/curves/bls12_377/g1.rs +++ b/algebra/src/curves/bls12_377/g1.rs @@ -3,7 +3,7 @@ use crate::{ biginteger::{BigInteger256, BigInteger384}, curves::models::{ModelParameters, SWModelParameters}, fields::{ - bls12_377::{Fq, Fr}, + bls12_377::*, Field, }, }; diff --git a/algebra/src/curves/bls12_377/g2.rs b/algebra/src/curves/bls12_377/g2.rs index 4993e8ad3..388d23aac 100644 --- a/algebra/src/curves/bls12_377/g2.rs +++ b/algebra/src/curves/bls12_377/g2.rs @@ -4,7 +4,7 @@ use crate::{ biginteger::{BigInteger256, BigInteger384}, curves::models::{ModelParameters, SWModelParameters}, fields::{ - bls12_377::{Fq, Fq2, Fr}, + bls12_377::*, Field, }, }; diff --git a/algebra/src/curves/bls12_381/g1.rs b/algebra/src/curves/bls12_381/g1.rs index b731b5156..3de22365b 100644 --- a/algebra/src/curves/bls12_381/g1.rs +++ b/algebra/src/curves/bls12_381/g1.rs @@ -7,7 +7,7 @@ use crate::{ models::{ModelParameters, SWModelParameters}, }, fields::{ - bls12_381::{Fq, Fr}, + bls12_381::*, Field, }, }; diff --git a/algebra/src/curves/bls12_381/g2.rs b/algebra/src/curves/bls12_381/g2.rs index 61e4ea9ed..0ca461c1e 100644 --- a/algebra/src/curves/bls12_381/g2.rs +++ b/algebra/src/curves/bls12_381/g2.rs @@ -7,7 +7,7 @@ use crate::{ models::{ModelParameters, SWModelParameters}, }, fields::{ - bls12_381::{Fq, Fq2, Fr}, + bls12_381::*, Field, }, }; diff --git a/algebra/src/curves/check_curve_parameters.sage b/algebra/src/curves/check_curve_parameters.sage index 39e7c150e..8a5984f1c 100644 --- a/algebra/src/curves/check_curve_parameters.sage +++ b/algebra/src/curves/check_curve_parameters.sage @@ -1,9 +1,13 @@ # The following Sage script check the consistency of the following curves parameters: # -# 1) P=(GENERATOR_X,GENERATOR_Y) must belongs to the curve of equation E: y^2 = x^3 + Ax + B +# 1) P=(GENERATOR_X,GENERATOR_Y) must belongs to the curve of equation E: y^2 = x^3 + Ax + B # 2) P must have order equal to the MODULUS of the scalar field # 3) COFACTOR must be equal to Order(E)/Order(P) # 4) COFACTOR_INV must be the inverse of COFACTOR in the scalar Field +# 5) ENDO_COEFF must be a cube root in the base field. +# 6) ENDO_SCALAR must be a cube root in the scalar field and satisfy ENDO_SCALAR * (X, Y) == (ENDO_COEFF * X, Y) +# 7) The intersection of the plane lattice spanned by {(1, ENDO_SCALAR), (0, SCALAR_FIELD_MODULUS)} with the square [-A,A]^2 must be empty, +# where A = 2^(LAMBDA/2 + 1) + 2^(LAMBDA/2) + 1. # Open Sage Shell in the corresponding folder and run the command # "sage check_curve_paramaters sage [file_path_curve] [file_path_basefield] [file_path_scalarfield]". @@ -65,10 +69,10 @@ scalar_field_name = re.findall(pattern, readfile)[0] fn = "(?:" + base_field_name + "|" + scalar_field_name + ")" #fn = field name = "(:?Fr|Fq)". Useful declaration for the pattern #### Reading the big integers list and extracting names and values -pattern = "const\s+(\w+):\s*" + fn + "\s*=\s*field_new!\(\s*" + fn + "\s*,\s*BigInteger\d*\s*\(\s*\[" + "([0-9a-fA-Fxu\s,]+)\s*" + "\]\s*\)" +pattern = "const\s+(\w+)[:\w\s]*=\s*field_new!\([\s\w,]*\(\s*\[" + "([0-9a-fA-Fxu\s,]+)\s*" + "\]\s*\)" big_int_ls = re.findall(pattern,readfile) #####list of couples of the form ('[VARIABLE_NAME]',"[u64],..,[u64]") -big_int_names = [b[0] for b in big_int_ls] +big_int_names = [b[0] for b in big_int_ls] big_int_values = [BigInteger_to_number(b[1]) for b in big_int_ls] BigIntegerLen = BigInteger_len(big_int_ls[0][1]) @@ -87,6 +91,10 @@ for s in big_int_names: pattern = "const\s+COFACTOR:\s*&'static\s*\[u64\]\s*=\s*&\[([0-9a-fA-Fxu\s,]+)\]\s*;" COFACTOR = BigInteger_to_number(re.findall(pattern,readfile)[0]) +####Reading the value of LAMBDA +pattern = "const\s+LAMBDA[:\w\s]*=\s*([\d]+)\s*;" +LAMBDA = int(re.findall(pattern,readfile)[0]) + #######################################Reading the values from the file containing the Base Field parameters######################## filename = sys.argv[2] @@ -163,4 +171,60 @@ else: if Fr(COFACTOR) * Fr(COFACTOR_INV) == Fr(SCALAR_FIELD_R): print("Correct. COFACTOR_INV is the inverse of COFACTOR in the the scalar field.") else: - print("WARNING! COFACTOR_INV IS NOT THE INVERSE OF COFACTOR IN THE SCALAR FIELD!") \ No newline at end of file + print("WARNING! COFACTOR_INV IS NOT THE INVERSE OF COFACTOR IN THE SCALAR FIELD!") +####### Checking the correctness of ENDO_COEFF and ENDO_FACTOR ############ +endo_mul_is_used = False +if 'ENDO_COEFF' in locals() and 'ENDO_SCALAR' in locals(): + zeta_q = Fq(ENDO_COEFF) * Fq(BASE_FIELD_R)**(-1) + if zeta_q**2 + zeta_q == Fq(-1): + endo_mul_is_used = True + print("Correct. ENDO_COEFF is a primitive cube root of unity.") + else: + print("WARNING! ENDO_COEFF IS NOT A PRIMITIVE CUBE ROOT OF UNITY.") + zeta_r = Fr(ENDO_SCALAR) * Fr(SCALAR_FIELD_R)**(-1) + if zeta_r**2 + zeta_r == Fr(-1): + print("Correct. ENDO_SCALAR is a primitive cube root of unity.") + else: + print("WARNING! ENDO_SCALAR IS NOT A PRIMITIVE CUBE ROOT OF UNITY.") + + +####### Checking the consistency of ENDO_COEFF and ENDO_SCALAR ############# +if endo_mul_is_used: + Q = int(zeta_r) * P + if Q == E([zeta_q * X, Y]): + print("Correct. ENDO_COEFF and ENDO_SCALAR are consistent.") + else: + print("WARNING! ENDO_COEFF AND ENDO_SCALAR ARE NOT CONSISTENT!") + + +########## Checking that shortest vector in the lattice ([1,zeta_r),[0,r]) is long enough ######### +## The Halo paper (https://eprint.iacr.org/2019/1021.pdf) proves the injectivity of the endo_mul map. +## The injectivity of the map (a,b) |-> a\zeta + b for a,b in [0,A] (essential for using add_unsafe) +## is equivalent the lattice condition below. +## a*zeta + b = a'*zeta_r + b' mod r for a,a',b,b' in [0,A] +## is equivalent to the fact that there are non-zero solutions to +## a * zeta_r = b mod r for a,b in [-A,A]. +## Then it would exists c such that +## b = a * zeta_r + c * r. +## Any such solution correspond to a point of the lattice spanned by (1, zeta_r) and (0, r). +## (a, b) = (a, c) * (1 zeta_r) +## (0 r ) +## The injectivity is equivalent to the fact that the intersection between this lattice and [-A, A]^2 +## is trivial. To verify this we first compute a LLL reduced basis {v,w} and +## then check if at least one of v, w, v + w, v - w is belongs to such a square. +## If not, there can't be other lattice points in the square. +if endo_mul_is_used: + A = 2**(LAMBDA//2 + 1) + 2**(LAMBDA//2) + 1 + L = Matrix([[1,Integer(zeta_r)],[0,SCALAR_FIELD_MODULUS]]) + Lred = L.LLL() + set = [Lred.row(0), Lred.row(1), Lred.row(0) - Lred.row(1), Lred.row(0) + Lred.row(1)] + add_unsafe = True + for v in set: + if abs(v[0]) <= A and abs(v[1]) <= A: + add_unsafe = False + if add_unsafe: + print("We can use add_unsafe for endo_mul.") + else: + print("WARNING! WE CAN'T USE add_unsafe FOR endo_mul!") +else: + print("endo_mul is not used for this curve.") diff --git a/algebra/src/curves/mnt6/g1.rs b/algebra/src/curves/mnt6/g1.rs index 985d8a2b3..f693ec989 100644 --- a/algebra/src/curves/mnt6/g1.rs +++ b/algebra/src/curves/mnt6/g1.rs @@ -7,7 +7,7 @@ use crate::{ short_weierstrass_projective::{GroupAffine, GroupProjective}, AffineCurve, }, - fields::mnt6::{Fq, Fq3, Fr}, + fields::mnt6::*, }; use crate::{field_new, FromBytes}; use serde::{Deserialize, Serialize}; diff --git a/algebra/src/curves/mnt6/g2.rs b/algebra/src/curves/mnt6/g2.rs index 7d0fe059f..2df06b0d1 100644 --- a/algebra/src/curves/mnt6/g2.rs +++ b/algebra/src/curves/mnt6/g2.rs @@ -7,7 +7,7 @@ use crate::{ short_weierstrass_projective::{GroupAffine, GroupProjective}, AffineCurve, }, - fields::mnt6::{Fq, Fq3, Fr}, + fields::mnt6::*, }; use crate::{field_new, FromBytes}; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; diff --git a/algebra/src/curves/mod.rs b/algebra/src/curves/mod.rs index 3a31404d8..fbf9c2f31 100644 --- a/algebra/src/curves/mod.rs +++ b/algebra/src/curves/mod.rs @@ -321,6 +321,21 @@ pub trait AffineCurve: fn mul_by_cofactor_inv(&self) -> Self; } +/// The `EndoMulCurve` trait for curves that have a non-trivial endomorphism +/// `Phi` of the form `Phi(x,y) = (zeta*x,y)`. +pub trait EndoMulCurve: AffineCurve { + /// Apply `Phi` + fn apply_endomorphism(&self) -> Self; + + /// Conversion of a bit sequence used in `endo_mul()` into its equivalent + /// scalar + fn endo_rep_to_scalar(bits: Vec) -> Result; + + /// Endomorphism-based multiplication of `&self` with `bits`, a little-endian + /// endomorphism representation. + fn endo_mul(&self, bits: Vec) -> Result; +} + impl Group for C { type ScalarField = C::ScalarField; #[must_use] diff --git a/algebra/src/curves/models/mod.rs b/algebra/src/curves/models/mod.rs index 641477bf8..63bc3e22d 100644 --- a/algebra/src/curves/models/mod.rs +++ b/algebra/src/curves/models/mod.rs @@ -122,3 +122,22 @@ pub trait MontgomeryModelParameters: ModelParameters { type TEModelParameters: TEModelParameters; } + +pub trait EndoMulParameters: SWModelParameters { + /// Parameters for endomorphism-based scalar multiplication [Halo](https://eprint.iacr.org/2019/1021). + /// A non-trivial cubic root of unity `ENDO_COEFF` for a curve endomorphism of the form + /// (x, y) -> (ENDO_COEFF * x, y). + const ENDO_COEFF: Self::BaseField; + + /// The scalar representation `zeta_r` of `ENDO_COEFF`. + /// NOTE : If one wants to use the endo mul circuit with `lambda` many bits, + /// then `zeta_r` MUST satisfy the minimal distance property + /// D = min { d(n*zeta_r, m*zeta_r) : n,m in [0, T] } >= R + 1, + /// where `T = 2^{lambda/2 + 1} + 2^{lambda/2} - 1` is the output + /// bound for the coefficients a, b of the equivalent scalar + /// representation `a*zeta_r + b`. + const ENDO_SCALAR: Self::ScalarField; + + /// Maximum number of bits for which security of endo mul is proven. MUST be an even number. + const LAMBDA: usize; +} diff --git a/algebra/src/curves/models/short_weierstrass_jacobian.rs b/algebra/src/curves/models/short_weierstrass_jacobian.rs index 1485a0330..2de661fe7 100644 --- a/algebra/src/curves/models/short_weierstrass_jacobian.rs +++ b/algebra/src/curves/models/short_weierstrass_jacobian.rs @@ -1,6 +1,9 @@ use crate::{ bytes::{FromBytes, ToBytes}, - curves::{models::SWModelParameters as Parameters, AffineCurve, ProjectiveCurve}, + curves::{models::{ + SWModelParameters as Parameters, + EndoMulParameters as EndoParameters, + }, AffineCurve, ProjectiveCurve, EndoMulCurve}, fields::{BitIterator, Field, PrimeField, SquareRootField}, BitSerializationError, CanonicalDeserialize, CanonicalDeserializeWithFlags, CanonicalSerialize, CanonicalSerializeWithFlags, Error, FromBytesChecked, FromCompressedBits, SWFlags, @@ -295,6 +298,99 @@ impl AffineCurve for GroupAffine

{ } } +impl EndoMulCurve for GroupAffine

{ + + fn apply_endomorphism(&self) -> Self { + let mut self_e = self.clone(); + self_e.x.mul_assign(P::ENDO_COEFF); + self_e + } + + fn endo_rep_to_scalar(bits: Vec) -> Result { + + let mut a : P::ScalarField = 2u64.into(); + let mut b : P::ScalarField = 2u64.into(); + + let one = P::ScalarField::one(); + let one_neg = one.neg(); + + let mut bits = bits; + if bits.len() % 2 == 1 { + bits.push(false); + } + + if bits.len() > P::LAMBDA { + Err("Endo mul bits length exceeds LAMBDA")? + } + + for i in (0..(bits.len() / 2)).rev() { + a.double_in_place(); + b.double_in_place(); + + let s = + if bits[i * 2] { + &one + } else { + &one_neg + }; + + if bits[i * 2 + 1] { + a.add_assign(s); + } else { + b.add_assign(s); + } + } + + Ok(a.mul(P::ENDO_SCALAR) + &b) + } + + /// Endomorphism-based multiplication of a curve point + /// with a scalar in little-endian endomorphism representation. + fn endo_mul(&self, bits: Vec) -> Result { + + let self_neg = self.neg(); + + let self_e = self.apply_endomorphism(); + let self_e_neg = self_e.neg(); + + let mut acc = self_e.into_projective(); + acc.add_assign_mixed(&self); + acc.double_in_place(); + + let mut bits = bits; + if bits.len() % 2 == 1 { + bits.push(false); + } + + if bits.len() > P::LAMBDA { + Err("Endo mul bits length exceeds LAMBDA")? + } + + for i in (0..(bits.len() / 2)).rev() { + + let s = + if bits[i * 2 + 1] { + if bits[i * 2] { + &self_e + } else { + &self_e_neg + } + } else { + if bits[i * 2] { + &self + } else { + &self_neg + } + }; + + acc.double_in_place(); + acc.add_assign_mixed(s); + } + + Ok(acc) + } +} + impl SemanticallyValid for GroupAffine

{ fn is_valid(&self) -> bool { self.x.is_valid() && self.y.is_valid() && self.group_membership_test() diff --git a/algebra/src/curves/models/short_weierstrass_projective.rs b/algebra/src/curves/models/short_weierstrass_projective.rs index 6ba478271..a5301d0d3 100644 --- a/algebra/src/curves/models/short_weierstrass_projective.rs +++ b/algebra/src/curves/models/short_weierstrass_projective.rs @@ -1,6 +1,9 @@ use crate::{ bytes::{FromBytes, ToBytes}, - curves::{models::SWModelParameters as Parameters, AffineCurve, ProjectiveCurve}, + curves::{models::{ + SWModelParameters as Parameters, + EndoMulParameters as EndoParameters, + }, AffineCurve, ProjectiveCurve, EndoMulCurve}, fields::{BitIterator, Field, PrimeField, SquareRootField}, BitSerializationError, CanonicalDeserialize, CanonicalDeserializeWithFlags, CanonicalSerialize, CanonicalSerializeWithFlags, Error, FromBytesChecked, FromCompressedBits, SWFlags, @@ -300,6 +303,98 @@ impl AffineCurve for GroupAffine

{ } } +impl EndoMulCurve for GroupAffine

{ + + fn apply_endomorphism(&self) -> Self { + let mut self_e = self.clone(); + self_e.x.mul_assign(P::ENDO_COEFF); + self_e + } + + fn endo_rep_to_scalar(bits: Vec) -> Result { + + let mut a : P::ScalarField = 2u64.into(); + let mut b : P::ScalarField = 2u64.into(); + + let one = P::ScalarField::one(); + let one_neg = one.neg(); + + let mut bits = bits; + if bits.len() % 2 == 1 { + bits.push(false); + } + + if bits.len() > P::LAMBDA { + Err("Endo mul bits length exceeds LAMBDA")? + } + + for i in (0..(bits.len() / 2)).rev() { + a.double_in_place(); + b.double_in_place(); + + let s = + if bits[i * 2] { + &one + } else { + &one_neg + }; + + if bits[i * 2 + 1] { + a.add_assign(s); + } else { + b.add_assign(s); + } + } + + Ok(a.mul(P::ENDO_SCALAR) + &b) + } + + /// Performs scalar multiplication of this element with mixed addition. + fn endo_mul(&self, bits: Vec) -> Result { + + let self_neg = self.neg(); + + let self_e = self.apply_endomorphism(); + let self_e_neg = self_e.neg(); + + let mut acc = self_e.into_projective(); + acc.add_assign_mixed(&self); + acc.double_in_place(); + + let mut bits = bits; + if bits.len() % 2 == 1 { + bits.push(false); + } + + if bits.len() > P::LAMBDA { + Err("Endo mul bits length exceeds LAMBDA")? + } + + for i in (0..(bits.len() / 2)).rev() { + + let s = + if bits[i * 2 + 1] { + if bits[i * 2] { + &self_e + } else { + &self_e_neg + } + } else { + if bits[i * 2] { + &self + } else { + &self_neg + } + }; + + acc.double_in_place(); + acc.add_assign_mixed(s); + } + + Ok(acc) + } +} + impl SemanticallyValid for GroupAffine

{ fn is_valid(&self) -> bool { self.x.is_valid() && self.y.is_valid() && self.group_membership_test() diff --git a/algebra/src/curves/sw6/g1.rs b/algebra/src/curves/sw6/g1.rs index a8a2157d0..319ce785f 100644 --- a/algebra/src/curves/sw6/g1.rs +++ b/algebra/src/curves/sw6/g1.rs @@ -5,7 +5,7 @@ use crate::{ models::{ModelParameters, SWModelParameters}, short_weierstrass_jacobian::{GroupAffine, GroupProjective}, }, - fields::sw6::{Fq, Fr}, + fields::sw6::*, }; pub type G1Affine = GroupAffine; diff --git a/algebra/src/curves/sw6/g2.rs b/algebra/src/curves/sw6/g2.rs index e33e5b8a6..6c55972fa 100644 --- a/algebra/src/curves/sw6/g2.rs +++ b/algebra/src/curves/sw6/g2.rs @@ -6,7 +6,7 @@ use crate::{ models::{ModelParameters, SWModelParameters}, short_weierstrass_jacobian::{GroupAffine, GroupProjective}, }, - fields::sw6::{Fq, Fq3, Fr}, + fields::sw6::*, }; pub type G2Affine = GroupAffine; diff --git a/algebra/src/curves/tweedle/dee.rs b/algebra/src/curves/tweedle/dee.rs index dc4190a70..a72e01d39 100644 --- a/algebra/src/curves/tweedle/dee.rs +++ b/algebra/src/curves/tweedle/dee.rs @@ -3,7 +3,7 @@ use crate::{ biginteger::BigInteger256, curves::{ models::short_weierstrass_jacobian::{GroupAffine, GroupProjective}, - ModelParameters, SWModelParameters, + ModelParameters, SWModelParameters, EndoMulParameters }, fields::tweedle::*, Field, @@ -59,6 +59,31 @@ impl SWModelParameters for TweedledeeParameters { } } +impl EndoMulParameters for TweedledeeParameters { + + const ENDO_COEFF: Self::BaseField = field_new!( + Fq, + BigInteger256([ + 0x140bcab6b02dbd7e, + 0x2dd04fb5f1e1e149, + 0x0101a1617f7ccf41, + 0x09d5c457f968aea9, + ]) + ); + + const ENDO_SCALAR: Self::ScalarField = field_new!( + Fr, + BigInteger256([ + 0xbcb40a68df900505, + 0xe63d49524c1c22e0, + 0xd21459c9bc9b5382, + 0x1a64663c51995775, + ]) + ); + + const LAMBDA: usize = 128; +} + /// G_GENERATOR_X = /// 1 pub const G_GENERATOR_X: Fq = field_new!( diff --git a/algebra/src/curves/tweedle/dum.rs b/algebra/src/curves/tweedle/dum.rs index 26ce8b858..223de17f9 100644 --- a/algebra/src/curves/tweedle/dum.rs +++ b/algebra/src/curves/tweedle/dum.rs @@ -2,7 +2,7 @@ use crate::{ biginteger::BigInteger256, curves::{ models::short_weierstrass_jacobian::{GroupAffine, GroupProjective}, - ModelParameters, SWModelParameters, + ModelParameters, SWModelParameters, EndoMulParameters }, field_new, fields::tweedle::*, @@ -59,6 +59,31 @@ impl SWModelParameters for TweedledumParameters { } } +impl EndoMulParameters for TweedledumParameters { + + const ENDO_COEFF: Self::BaseField = field_new!( + Fr, + BigInteger256([ + 0xbcb40a68df900505, + 0xe63d49524c1c22e0, + 0xd21459c9bc9b5382, + 0x1a64663c51995775, + ]) + ); + + const ENDO_SCALAR: Self::ScalarField = field_new!( + Fq, + BigInteger256([ + 0x140bcab6b02dbd7e, + 0x2dd04fb5f1e1e149, + 0x0101a1617f7ccf41, + 0x09d5c457f968aea9, + ]) + ); + + const LAMBDA: usize = 128; +} + /// G_GENERATOR_X = /// 1 pub const G_GENERATOR_X: Fr = field_new!( diff --git a/algebra/src/curves/tweedle/tests.rs b/algebra/src/curves/tweedle/tests.rs index df3bfaefa..69d63adb3 100644 --- a/algebra/src/curves/tweedle/tests.rs +++ b/algebra/src/curves/tweedle/tests.rs @@ -1,8 +1,9 @@ use crate::{ + biginteger::BigInteger, curves::{ - models::SWModelParameters, tests::curve_tests, tweedle::*, AffineCurve, ProjectiveCurve, + models::SWModelParameters, tests::curve_tests, tweedle::*, AffineCurve, ProjectiveCurve, EndoMulCurve, }, - fields::{tweedle::*, Field, SquareRootField}, + fields::{tweedle::*, Field, PrimeField, SquareRootField}, groups::tests::group_test, }; use std::ops::{AddAssign, MulAssign}; @@ -11,7 +12,8 @@ use std::str::FromStr; use crate::curves::tests::sw_jacobian_tests; use crate::curves::tweedle::dee::TweedledeeParameters; use crate::curves::tweedle::dum::TweedledumParameters; -use rand::{Rng, SeedableRng}; +use crate::UniformRand; +use rand::{Rng, SeedableRng, thread_rng}; use rand_xorshift::XorShiftRng; #[test] @@ -211,3 +213,39 @@ fn test_dum_addition_correctness() { ) ); } + +#[test] +fn test_dee_endo_mul() { + + for _ in 0..100 { + + let p = dee::Projective::rand(&mut thread_rng()).into_affine(); + + let scalar: Fq = u128::rand(&mut thread_rng()).into(); + let bits = scalar.into_repr().to_bits().as_slice()[0..128].to_vec(); + + let p_mul = p.mul(dee::Affine::endo_rep_to_scalar(bits.clone()).unwrap()); + let pe_mul = p.endo_mul(bits.clone()).unwrap(); + + assert_eq!(p_mul, pe_mul); + } +} + +#[test] +fn test_dum_endo_mul() { + + for _ in 0..100 { + + let p = dum::Projective::rand(&mut thread_rng()).into_affine(); + + let scalar: Fq = u128::rand(&mut thread_rng()).into(); + let bits = scalar.into_repr().to_bits().as_slice()[0..128].to_vec(); + + println!("{}", bits.len()); + + let p_mul = p.mul(dum::Affine::endo_rep_to_scalar(bits.clone()).unwrap()); + let pe_mul = p.endo_mul(bits.clone()).unwrap(); + + assert_eq!(p_mul, pe_mul); + } +} diff --git a/algebra/src/fields/arithmetic.rs b/algebra/src/fields/arithmetic.rs index 4e76fb8e9..b48b2811b 100644 --- a/algebra/src/fields/arithmetic.rs +++ b/algebra/src/fields/arithmetic.rs @@ -89,6 +89,10 @@ macro_rules! impl_field_mul_short_assign { ($limbs: expr) => { #[inline] #[unroll_for_loops] + // Partial, or short, Montgomery multiplication. Computes the product + // R*xy mod p = (R*x mod p)*(R_s * y mod p) + // for y having a 1-limb sized representation (R_s*y mod p) w.r.t. the + // "short" Montgomery constant R_s = 2^64. //TODO: Can we write the assembly equivalent of this ? Is it worth ? //TODO: Probably there's a more compact way to write this fn mul_short_assign(&mut self, other: &Self) { diff --git a/primitives/Cargo.toml b/primitives/Cargo.toml index 72e5b4bc5..b720be82d 100644 --- a/primitives/Cargo.toml +++ b/primitives/Cargo.toml @@ -23,7 +23,7 @@ edition = "2018" ################################# Dependencies ################################ [dependencies] -algebra = { git = "https://github.com/HorizenOfficial/ginger-lib", branch = "development", features = ["parallel"] } +algebra = { git = "https://github.com/HorizenOfficial/ginger-lib", branch = "endo", features = ["parallel"] } bench-utils = { path = "../bench-utils" } digest = { version = "0.8", optional = true } @@ -53,7 +53,7 @@ bn_382 = ["algebra/bn_382"] tweedle = ["algebra/tweedle"] [dev-dependencies] -algebra = { git = "https://github.com/HorizenOfficial/ginger-lib", branch = "development", features = ["edwards_sw6", "jubjub", "sw6", "bls12_377"] } +algebra = { git = "https://github.com/HorizenOfficial/ginger-lib", branch = "endo", features = ["edwards_sw6", "jubjub", "sw6", "bls12_377"] } primitives = { path = "../primitives", features = ["mnt4_753", "mnt6_753", "bn_382", "tweedle"] } criterion = "0.3.2" diff --git a/proof-systems/Cargo.toml b/proof-systems/Cargo.toml index aeb20e18f..d3b0a94fa 100644 --- a/proof-systems/Cargo.toml +++ b/proof-systems/Cargo.toml @@ -23,12 +23,12 @@ edition = "2018" ################################# Dependencies ################################ [dependencies] -algebra = { git = "https://github.com/HorizenOfficial/ginger-lib", branch = "development", features = [ "parallel", "fft"] } -r1cs-core = { git = "https://github.com/HorizenOfficial/ginger-lib", branch = "development" } +algebra = { git = "https://github.com/HorizenOfficial/ginger-lib", branch = "endo", features = [ "parallel", "fft"] } +r1cs-core = { git = "https://github.com/HorizenOfficial/ginger-lib", branch = "endo" } bench-utils = { path = "../bench-utils" } -marlin = { git = "https://github.com/HorizenLabs/marlin", branch = "dev", optional = true } -poly-commit = { git = "https://github.com/HorizenLabs/poly-commit", branch = "dev", optional = true } +marlin = { git = "https://github.com/HorizenLabs/marlin", branch = "endo", optional = true } +poly-commit = { git = "https://github.com/HorizenLabs/poly-commit", branch = "endo", optional = true } r1cs-std = { path = "../r1cs/gadgets/std", optional = true } @@ -46,7 +46,7 @@ criterion = "0.3" rand_xorshift = { version = "0.3.0" } blake2 = { version = "0.8.1", default-features = false } -algebra = { git = "https://github.com/HorizenOfficial/ginger-lib", branch = "development", features = ["full", "parallel", "fft"] } +algebra = { git = "https://github.com/HorizenOfficial/ginger-lib", branch = "endo", features = ["full", "parallel", "fft"] } r1cs-crypto = { path = "../r1cs/gadgets/crypto", features = ["nizk"] } [features] diff --git a/r1cs/core/Cargo.toml b/r1cs/core/Cargo.toml index 94fd06202..d981142de 100644 --- a/r1cs/core/Cargo.toml +++ b/r1cs/core/Cargo.toml @@ -21,5 +21,5 @@ include = ["Cargo.toml", "src", "README.md", "LICENSE-APACHE", "LICENSE-MIT"] license = "MIT/Apache-2.0" [dependencies] -algebra = { git = "https://github.com/HorizenOfficial/ginger-lib", branch = "development" } +algebra = { git = "https://github.com/HorizenOfficial/ginger-lib", branch = "endo" } smallvec = { version = "1.6.1" } \ No newline at end of file diff --git a/r1cs/gadgets/crypto/Cargo.toml b/r1cs/gadgets/crypto/Cargo.toml index 375787472..64e08b3f9 100644 --- a/r1cs/gadgets/crypto/Cargo.toml +++ b/r1cs/gadgets/crypto/Cargo.toml @@ -23,9 +23,9 @@ edition = "2018" ################################# Dependencies ################################ [dependencies] -algebra = { git = "https://github.com/HorizenOfficial/ginger-lib", branch = "development", features = [ "parallel" ] } +algebra = { git = "https://github.com/HorizenOfficial/ginger-lib", branch = "endo", features = [ "parallel" ] } primitives = {path = "../../../primitives"} -r1cs-core = { git = "https://github.com/HorizenOfficial/ginger-lib", branch = "development" } +r1cs-core = { git = "https://github.com/HorizenOfficial/ginger-lib", branch = "endo" } r1cs-std = { path = "../std"} proof-systems = { path = "../../../proof-systems", features = ["groth16", "gm17"], optional = true } bench-utils = { path = "../../../bench-utils" } @@ -58,6 +58,6 @@ llvm_asm = ["algebra/llvm_asm"] [dev-dependencies] rand_xorshift = { version = "0.3.0" } -algebra = { git = "https://github.com/HorizenOfficial/ginger-lib", branch = "development", features = ["bls12_377", "bls12_381", "sw6", "bn_382"] } +algebra = { git = "https://github.com/HorizenOfficial/ginger-lib", branch = "endo", features = ["bls12_377", "bls12_381", "sw6", "bn_382"] } r1cs-std = { path = "../std", features = ["jubjub", "edwards_sw6", "bls12_377", "mnt4_753", "mnt6_753", "bn_382", "tweedle"] } r1cs-crypto = { path = "../crypto", features = ["mnt4_753", "mnt6_753", "bn_382", "tweedle"] } \ No newline at end of file diff --git a/r1cs/gadgets/std/Cargo.toml b/r1cs/gadgets/std/Cargo.toml index 22d1c794a..cefde5e8a 100644 --- a/r1cs/gadgets/std/Cargo.toml +++ b/r1cs/gadgets/std/Cargo.toml @@ -23,8 +23,8 @@ license = "MIT/Apache-2.0" ################################# Dependencies ################################ [dependencies] -algebra = { git = "https://github.com/HorizenOfficial/ginger-lib", branch = "development" } -r1cs-core = { git = "https://github.com/HorizenOfficial/ginger-lib", branch = "development" } +algebra = { git = "https://github.com/HorizenOfficial/ginger-lib", branch = "endo" } +r1cs-core = { git = "https://github.com/HorizenOfficial/ginger-lib", branch = "endo" } derivative = "2.2.0" num-traits = { version = "0.2", default-features = false, optional = true } diff --git a/r1cs/gadgets/std/src/groups/curves/short_weierstrass/short_weierstrass_jacobian.rs b/r1cs/gadgets/std/src/groups/curves/short_weierstrass/short_weierstrass_jacobian.rs index f4cbfcba6..e851a82d7 100644 --- a/r1cs/gadgets/std/src/groups/curves/short_weierstrass/short_weierstrass_jacobian.rs +++ b/r1cs/gadgets/std/src/groups/curves/short_weierstrass/short_weierstrass_jacobian.rs @@ -2,7 +2,7 @@ use algebra::{ curves::short_weierstrass_jacobian::{ GroupAffine as SWAffine, GroupProjective as SWProjective, }, - AffineCurve, BitIterator, Field, PrimeField, ProjectiveCurve, SWModelParameters, + AffineCurve, BitIterator, Field, PrimeField, ProjectiveCurve, SWModelParameters, EndoMulParameters }; use r1cs_core::{ConstraintSystem, SynthesisError}; use std::ops::{Add, Mul}; @@ -918,6 +918,85 @@ where } } +impl EndoMulCurveGadget, ConstraintF> for AffineGadget + where + P: EndoMulParameters, + ConstraintF: PrimeField, + F: FieldGadget, +{ + /// Given an arbitrary curve element `&self`, applies the endomorphism + /// defined by `ENDO_COEFF`. + fn apply_endomorphism>( + &self, + mut cs: CS, + ) -> Result { + Ok(Self::new( + self.x.mul_by_constant(cs.ns(|| "endo x"), &P::ENDO_COEFF)?, + self.y.clone(), + self.infinity + )) + } + + /// The endomorphism-based scalar multiplication circuit from [Halo],taking only + /// 3.5 constraints per "scalar" bit. Assumes that `ENDO_SCALAR` satisfies the minimal + /// distance property as mentioned in `SWModelParameters`. + /// Given any non-trivial point `P= &self` of the prime order r subgroup, and a slice + /// of an even number of at most `lambda` Booleans `bits`, enforces that the result equals + /// phi(bits) * P, + /// where `phi(bits)` is the equivalent scalar representation of `bits`. + /// + /// [Halo]: https://eprint.iacr.org/2019/1021 + fn endo_mul>( + &self, + mut cs: CS, + bits: &[Boolean], + ) -> Result { + + let mut bits = bits.to_vec(); + if bits.len() % 2 == 1 { + bits.push(Boolean::constant(false)); + } + + if bits.len() > P::LAMBDA { + Err(SynthesisError::Other("Endo mul bits length exceeds LAMBDA".to_owned()))? + } + + let endo_self = self.apply_endomorphism(cs.ns(|| "endo self"))?; + let self_y_neg = self.y.negate(cs.ns(|| "self y negate"))?; + + let mut acc = endo_self.clone(); + acc = acc.add(cs.ns(|| "add"), &self)?; + acc.double_in_place(cs.ns(|| "double"))?; + + for i in (0..(bits.len() / 2)).rev() { + + // Conditional select between (-1)^b_0 * Phi^{b_1}(&self), according + // to [b_1,b_0] = bits[2i+1, 2i]. + // Takes 2 constraints. + let add = Self::new( + F::conditionally_select( + cs.ns(|| format!("conditional bit1 select endo {}", i)), + &bits[i * 2 + 1], + &endo_self.x, + &self.x, + )?, + F::conditionally_select( + cs.ns(|| format!("conditional bit0 select negate {}", i)), + &bits[i * 2], + &self.y, + &self_y_neg, + )?, + self.infinity + ); + + // The unsafe double and add, takes 5 constraints. + acc = acc.double_and_add_unsafe(cs.ns(|| format!("double_and_add {}", i)), &add)?; + } + + Ok(acc) + } +} + impl CondSelectGadget for AffineGadget where P: SWModelParameters, @@ -1348,7 +1427,9 @@ where } use crate::fields::fp::FpGadget; +use crate::groups::EndoMulCurveGadget; use crate::ToCompressedBitsGadget; + impl ToCompressedBitsGadget for CompressAffinePointGadget where ConstraintF: PrimeField, diff --git a/r1cs/gadgets/std/src/groups/curves/short_weierstrass/short_weierstrass_projective.rs b/r1cs/gadgets/std/src/groups/curves/short_weierstrass/short_weierstrass_projective.rs index b84d90959..32409a39a 100644 --- a/r1cs/gadgets/std/src/groups/curves/short_weierstrass/short_weierstrass_projective.rs +++ b/r1cs/gadgets/std/src/groups/curves/short_weierstrass/short_weierstrass_projective.rs @@ -2,14 +2,14 @@ use algebra::{ curves::short_weierstrass_projective::{ GroupAffine as SWAffine, GroupProjective as SWProjective, }, - AffineCurve, BitIterator, Field, PrimeField, ProjectiveCurve, SWModelParameters, + AffineCurve, BitIterator, Field, PrimeField, ProjectiveCurve, SWModelParameters, EndoMulParameters, }; use r1cs_core::{ConstraintSystem, SynthesisError}; use std::ops::{Add, Mul}; use std::{borrow::Borrow, marker::PhantomData, ops::Neg}; use crate::{ - groups::{check_mul_bits_fixed_base_inputs, check_mul_bits_inputs}, + groups::{check_mul_bits_fixed_base_inputs, check_mul_bits_inputs, EndoMulCurveGadget}, prelude::*, Assignment, }; @@ -918,6 +918,85 @@ where } } +impl EndoMulCurveGadget, ConstraintF> for AffineGadget + where + P: EndoMulParameters, + ConstraintF: PrimeField, + F: FieldGadget, +{ + /// Given an arbitrary curve element `&self`, applies the endomorphism + /// defined by `ENDO_COEFF`. + fn apply_endomorphism>( + &self, + mut cs: CS, + ) -> Result { + Ok(Self::new( + self.x.mul_by_constant(cs.ns(|| "endo x"), &P::ENDO_COEFF)?, + self.y.clone(), + self.infinity + )) + } + + /// The endomorphism-based scalar multiplication circuit from [Halo],taking only + /// 3.5 constraints per "scalar" bit. Assumes that `ENDO_SCALAR` satisfies the minimal + /// distance property as mentioned in `SWModelParameters`. + /// Given any non-trivial point `P= &self` of the prime order r subgroup, and a slice + /// of an even number of at most `lambda` Booleans `bits`, enforces that the result equals + /// phi(bits) * P, + /// where `phi(bits)` is the equivalent scalar representation of `bits`. + /// + /// [Halo]: https://eprint.iacr.org/2019/1021 + fn endo_mul>( + &self, + mut cs: CS, + bits: &[Boolean], + ) -> Result { + + let mut bits = bits.to_vec(); + if bits.len() % 2 == 1 { + bits.push(Boolean::constant(false)); + } + + if bits.len() > P::LAMBDA { + Err(SynthesisError::Other("Endo mul bits length exceeds LAMBDA".to_owned()))? + } + + let endo_self = self.apply_endomorphism(cs.ns(|| "endo self"))?; + let self_y_neg = self.y.negate(cs.ns(|| "self y negate"))?; + + let mut acc = endo_self.clone(); + acc = acc.add(cs.ns(|| "add"), &self)?; + acc.double_in_place(cs.ns(|| "double"))?; + + for i in (0..(bits.len() / 2)).rev() { + + // Conditional select between (-1)^b_0 * Phi^{b_1}(&self), according + // to [b_1,b_0] = bits[2i+1, 2i]. + // Takes 2 constraints. + let add = Self::new( + F::conditionally_select( + cs.ns(|| format!("conditional bit1 select endo {}", i)), + &bits[i * 2 + 1], + &endo_self.x, + &self.x, + )?, + F::conditionally_select( + cs.ns(|| format!("conditional bit0 select negate {}", i)), + &bits[i * 2], + &self.y, + &self_y_neg, + )?, + self.infinity + ); + + // The unsafe double and add, takes 5 constraints. + acc = acc.double_and_add_unsafe(cs.ns(|| format!("double_and_add {}", i)), &add)?; + } + + Ok(acc) + } +} + impl CondSelectGadget for AffineGadget where P: SWModelParameters, diff --git a/r1cs/gadgets/std/src/groups/mod.rs b/r1cs/gadgets/std/src/groups/mod.rs index 7058f794c..801fc4e72 100644 --- a/r1cs/gadgets/std/src/groups/mod.rs +++ b/r1cs/gadgets/std/src/groups/mod.rs @@ -177,6 +177,20 @@ pub trait GroupGadget: fn cost_of_double() -> usize; } +pub trait EndoMulCurveGadget: GroupGadget { + + fn apply_endomorphism>( + &self, + cs: CS, + ) -> Result; + + fn endo_mul>( + &self, + cs: CS, + bits: &[Boolean], + ) -> Result; +} + /// Pre-checks for vbSM with incomplete arithmetic using Hopwood algorithm (https://github.com/zcash/zcash/issues/3924) : /// - 'self' must be non-trivial and in the prime order subgroup /// - 'bits', in little endian, must be of length <= than the scalar field modulus. @@ -454,11 +468,12 @@ pub(crate) fn scalar_bits_to_constant_length< #[cfg(test)] pub(crate) mod test { - use algebra::{BigInteger, Field, FpParameters, Group, PrimeField, ToBits, UniformRand}; + use algebra::{Field, PrimeField, FpParameters, BigInteger, Group, UniformRand, ToBits, EndoMulCurve, ProjectiveCurve}; use r1cs_core::ConstraintSystem; use crate::{prelude::*, test_constraint_system::TestConstraintSystem}; use rand::thread_rng; + use crate::groups::EndoMulCurveGadget; #[allow(dead_code)] pub(crate) fn group_test< @@ -526,11 +541,11 @@ pub(crate) mod test { >() { let mut cs = TestConstraintSystem::::new(); - let a: G = UniformRand::rand(&mut thread_rng()); - let b: G = UniformRand::rand(&mut thread_rng()); + let a_native: G = UniformRand::rand(&mut thread_rng()); + let b_native: G = UniformRand::rand(&mut thread_rng()); - let a = GG::alloc(&mut cs.ns(|| "generate_a"), || Ok(a)).unwrap(); - let b = GG::alloc(&mut cs.ns(|| "generate_b"), || Ok(b)).unwrap(); + let a = GG::alloc(&mut cs.ns(|| "generate_a"), || Ok(a_native)).unwrap(); + let b = GG::alloc(&mut cs.ns(|| "generate_b"), || Ok(b_native)).unwrap(); let _zero = GG::zero(cs.ns(|| "Zero")).unwrap(); @@ -774,4 +789,33 @@ pub(crate) mod test { mul_bits_additivity_test::(); } } + + #[allow(dead_code)] + pub(crate) fn endo_mul_test< + ConstraintF: Field, + G: ProjectiveCurve, + GG: EndoMulCurveGadget, + >() + where + ::Affine: EndoMulCurve + { + let mut cs = TestConstraintSystem::::new(); + + let a_native_proj = G::rand(&mut thread_rng()); + let a_native = a_native_proj.into_affine(); + let a = GG::alloc(&mut cs.ns(|| "generate_a"), || Ok(a_native_proj)).unwrap(); + + let scalar: G::ScalarField = u128::rand(&mut thread_rng()).into(); + + let b_native = scalar.into_repr().to_bits().as_slice()[0..128].to_vec(); + let b = b_native + .iter() + .map(|&bit| Boolean::constant(bit)) + .collect::>(); + + let r_native = a_native.endo_mul(b_native).unwrap(); + let r = a.endo_mul(cs.ns(|| "endo mul"), &b).unwrap().get_value().unwrap(); + + assert_eq!(r_native, r); + } } diff --git a/r1cs/gadgets/std/src/groups/nonnative/short_weierstrass/short_weierstrass_jacobian.rs b/r1cs/gadgets/std/src/groups/nonnative/short_weierstrass/short_weierstrass_jacobian.rs index a778ce80f..78dc470a9 100644 --- a/r1cs/gadgets/std/src/groups/nonnative/short_weierstrass/short_weierstrass_jacobian.rs +++ b/r1cs/gadgets/std/src/groups/nonnative/short_weierstrass/short_weierstrass_jacobian.rs @@ -7,7 +7,7 @@ use algebra::{ curves::short_weierstrass_jacobian::{ GroupAffine as SWAffine, GroupProjective as SWProjective, }, - AffineCurve, BitIterator, Field, PrimeField, ProjectiveCurve, SWModelParameters, + AffineCurve, BitIterator, Field, PrimeField, ProjectiveCurve, SWModelParameters, EndoMulParameters, SquareRootField, }; @@ -17,7 +17,7 @@ use crate::{ alloc::{AllocGadget, ConstantGadget}, boolean::Boolean, fields::{nonnative::nonnative_field_gadget::NonNativeFieldGadget, FieldGadget}, - groups::{check_mul_bits_fixed_base_inputs, check_mul_bits_inputs, GroupGadget}, + groups::{check_mul_bits_fixed_base_inputs, check_mul_bits_inputs, GroupGadget, EndoMulCurveGadget}, prelude::EqGadget, select::{CondSelectGadget, TwoBitLookupGadget}, uint8::UInt8, @@ -487,6 +487,85 @@ where } } +impl EndoMulCurveGadget, ConstraintF> for GroupAffineNonNativeGadget + where + P: EndoMulParameters, + ConstraintF: PrimeField, + SimulationF: PrimeField + SquareRootField, +{ + /// Given an arbitrary curve element `&self`, applies the endomorphism + /// defined by `ENDO_COEFF`. + fn apply_endomorphism>( + &self, + mut cs: CS, + ) -> Result { + Ok(Self::new( + self.x.mul_by_constant(cs.ns(|| "endo x"), &P::ENDO_COEFF)?, + self.y.clone(), + self.infinity + )) + } + + /// The endomorphism-based scalar multiplication circuit from [Halo] in non-native + /// arithmetics. Assumes that `ENDO_SCALAR` satisfies the minimal distance property as + /// mentioned in `SWModelParameters`. + /// Given any non-trivial point `P= &self` of the prime order r subgroup, and a slice + /// of an even number of at most `lambda` Booleans `bits`, enforces that the result equals + /// `phi(bits) * P`, + /// where `phi(bits)` is the equivalent scalar representation of `bits`. + /// + /// [Halo]: https://eprint.iacr.org/2019/1021 + fn endo_mul>( + &self, + mut cs: CS, + bits: &[Boolean], + ) -> Result { + + let mut bits = bits.to_vec(); + if bits.len() % 2 == 1 { + bits.push(Boolean::constant(false)); + } + + if bits.len() > P::LAMBDA { + Err(SynthesisError::Other("Endo mul bits length exceeds LAMBDA".to_owned()))? + } + + let endo_self = self.apply_endomorphism(cs.ns(|| "endo self"))?; + let self_y_neg = self.y.negate(cs.ns(|| "self y negate"))?; + + let mut acc = endo_self.clone(); + acc = acc.add(cs.ns(|| "add"), &self)?; + acc.double_in_place(cs.ns(|| "double"))?; + + for i in (0..(bits.len() / 2)).rev() { + + // Conditional select between (-1)^b_0 * Phi^{b_1}(&self), according + // to [b_1,b_0] = bits[2i+1, 2i]. + // Takes 2 constraints. + let add = Self::new( + NonNativeFieldGadget::conditionally_select( + cs.ns(|| format!("conditional bit1 select endo {}", i)), + &bits[i * 2 + 1], + &endo_self.x, + &self.x, + )?, + NonNativeFieldGadget::conditionally_select( + cs.ns(|| format!("conditional bit0 select negate {}", i)), + &bits[i * 2], + &self.y, + &self_y_neg, + )?, + self.infinity + ); + + // The unsafe double and add, takes 5 constraints. + acc = acc.double_and_add_unsafe(cs.ns(|| format!("double_and_add {}", i)), &add)?; + } + + Ok(acc) + } +} + impl PartialEq for GroupAffineNonNativeGadget where diff --git a/r1cs/gadgets/std/src/instantiated/tweedle/curves.rs b/r1cs/gadgets/std/src/instantiated/tweedle/curves.rs index 82421357f..b04711797 100644 --- a/r1cs/gadgets/std/src/instantiated/tweedle/curves.rs +++ b/r1cs/gadgets/std/src/instantiated/tweedle/curves.rs @@ -14,10 +14,12 @@ pub type TweedleDumGadget = AffineGadget; fn test_dee() { crate::groups::test::group_test_with_incomplete_add::<_, _, TweedleDeeGadget>(); crate::groups::test::mul_bits_test::<_, _, TweedleDeeGadget>(); + crate::groups::test::endo_mul_test::<_, _, TweedleDeeGadget>(); } #[test] fn test_dum() { crate::groups::test::group_test_with_incomplete_add::<_, _, TweedleDumGadget>(); crate::groups::test::mul_bits_test::<_, _, TweedleDumGadget>(); + crate::groups::test::endo_mul_test::<_, _, TweedleDumGadget>(); }