Skip to content

Commit 1d9e99b

Browse files
Merge pull request #128 from HorizenOfficial/endo
Endo
2 parents e10cb02 + 42470dc commit 1d9e99b

File tree

28 files changed

+709
-42
lines changed

28 files changed

+709
-42
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,4 @@ Cargo.lock
77
*.orig
88
coeffs_*
99
msm_bases_*
10+
*.py

algebra/src/curves/bls12_377/g1.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use crate::{
33
biginteger::{BigInteger256, BigInteger384},
44
curves::models::{ModelParameters, SWModelParameters},
55
fields::{
6-
bls12_377::{Fq, Fr},
6+
bls12_377::*,
77
Field,
88
},
99
};

algebra/src/curves/bls12_377/g2.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use crate::{
44
biginteger::{BigInteger256, BigInteger384},
55
curves::models::{ModelParameters, SWModelParameters},
66
fields::{
7-
bls12_377::{Fq, Fq2, Fr},
7+
bls12_377::*,
88
Field,
99
},
1010
};

algebra/src/curves/bls12_381/g1.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use crate::{
77
models::{ModelParameters, SWModelParameters},
88
},
99
fields::{
10-
bls12_381::{Fq, Fr},
10+
bls12_381::*,
1111
Field,
1212
},
1313
};

algebra/src/curves/bls12_381/g2.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use crate::{
77
models::{ModelParameters, SWModelParameters},
88
},
99
fields::{
10-
bls12_381::{Fq, Fq2, Fr},
10+
bls12_381::*,
1111
Field,
1212
},
1313
};

algebra/src/curves/check_curve_parameters.sage

+68-4
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
# The following Sage script check the consistency of the following curves parameters:
22
#
3-
# 1) P=(GENERATOR_X,GENERATOR_Y) must belongs to the curve of equation E: y^2 = x^3 + Ax + B
3+
# 1) P=(GENERATOR_X,GENERATOR_Y) must belongs to the curve of equation E: y^2 = x^3 + Ax + B
44
# 2) P must have order equal to the MODULUS of the scalar field
55
# 3) COFACTOR must be equal to Order(E)/Order(P)
66
# 4) COFACTOR_INV must be the inverse of COFACTOR in the scalar Field
7+
# 5) ENDO_COEFF must be a cube root in the base field.
8+
# 6) ENDO_SCALAR must be a cube root in the scalar field and satisfy ENDO_SCALAR * (X, Y) == (ENDO_COEFF * X, Y)
9+
# 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,
10+
# where A = 2^(LAMBDA/2 + 1) + 2^(LAMBDA/2) + 1.
711
# Open Sage Shell in the corresponding folder and run the command
812
# "sage check_curve_paramaters sage [file_path_curve] [file_path_basefield] [file_path_scalarfield]".
913

@@ -65,10 +69,10 @@ scalar_field_name = re.findall(pattern, readfile)[0]
6569
fn = "(?:" + base_field_name + "|" + scalar_field_name + ")" #fn = field name = "(:?Fr|Fq)". Useful declaration for the pattern
6670

6771
#### Reading the big integers list and extracting names and values
68-
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*\)"
72+
pattern = "const\s+(\w+)[:\w\s]*=\s*field_new!\([\s\w,]*\(\s*\[" + "([0-9a-fA-Fxu\s,]+)\s*" + "\]\s*\)"
6973
big_int_ls = re.findall(pattern,readfile) #####list of couples of the form ('[VARIABLE_NAME]',"[u64],..,[u64]")
7074

71-
big_int_names = [b[0] for b in big_int_ls]
75+
big_int_names = [b[0] for b in big_int_ls]
7276
big_int_values = [BigInteger_to_number(b[1]) for b in big_int_ls]
7377

7478
BigIntegerLen = BigInteger_len(big_int_ls[0][1])
@@ -87,6 +91,10 @@ for s in big_int_names:
8791
pattern = "const\s+COFACTOR:\s*&'static\s*\[u64\]\s*=\s*&\[([0-9a-fA-Fxu\s,]+)\]\s*;"
8892
COFACTOR = BigInteger_to_number(re.findall(pattern,readfile)[0])
8993

94+
####Reading the value of LAMBDA
95+
pattern = "const\s+LAMBDA[:\w\s]*=\s*([\d]+)\s*;"
96+
LAMBDA = int(re.findall(pattern,readfile)[0])
97+
9098
#######################################Reading the values from the file containing the Base Field parameters########################
9199
filename = sys.argv[2]
92100

@@ -163,4 +171,60 @@ else:
163171
if Fr(COFACTOR) * Fr(COFACTOR_INV) == Fr(SCALAR_FIELD_R):
164172
print("Correct. COFACTOR_INV is the inverse of COFACTOR in the the scalar field.")
165173
else:
166-
print("WARNING! COFACTOR_INV IS NOT THE INVERSE OF COFACTOR IN THE SCALAR FIELD!")
174+
print("WARNING! COFACTOR_INV IS NOT THE INVERSE OF COFACTOR IN THE SCALAR FIELD!")
175+
####### Checking the correctness of ENDO_COEFF and ENDO_FACTOR ############
176+
endo_mul_is_used = False
177+
if 'ENDO_COEFF' in locals() and 'ENDO_SCALAR' in locals():
178+
zeta_q = Fq(ENDO_COEFF) * Fq(BASE_FIELD_R)**(-1)
179+
if zeta_q**2 + zeta_q == Fq(-1):
180+
endo_mul_is_used = True
181+
print("Correct. ENDO_COEFF is a primitive cube root of unity.")
182+
else:
183+
print("WARNING! ENDO_COEFF IS NOT A PRIMITIVE CUBE ROOT OF UNITY.")
184+
zeta_r = Fr(ENDO_SCALAR) * Fr(SCALAR_FIELD_R)**(-1)
185+
if zeta_r**2 + zeta_r == Fr(-1):
186+
print("Correct. ENDO_SCALAR is a primitive cube root of unity.")
187+
else:
188+
print("WARNING! ENDO_SCALAR IS NOT A PRIMITIVE CUBE ROOT OF UNITY.")
189+
190+
191+
####### Checking the consistency of ENDO_COEFF and ENDO_SCALAR #############
192+
if endo_mul_is_used:
193+
Q = int(zeta_r) * P
194+
if Q == E([zeta_q * X, Y]):
195+
print("Correct. ENDO_COEFF and ENDO_SCALAR are consistent.")
196+
else:
197+
print("WARNING! ENDO_COEFF AND ENDO_SCALAR ARE NOT CONSISTENT!")
198+
199+
200+
########## Checking that shortest vector in the lattice ([1,zeta_r),[0,r]) is long enough #########
201+
## The Halo paper (https://eprint.iacr.org/2019/1021.pdf) proves the injectivity of the endo_mul map.
202+
## The injectivity of the map (a,b) |-> a\zeta + b for a,b in [0,A] (essential for using add_unsafe)
203+
## is equivalent the lattice condition below.
204+
## a*zeta + b = a'*zeta_r + b' mod r for a,a',b,b' in [0,A]
205+
## is equivalent to the fact that there are non-zero solutions to
206+
## a * zeta_r = b mod r for a,b in [-A,A].
207+
## Then it would exists c such that
208+
## b = a * zeta_r + c * r.
209+
## Any such solution correspond to a point of the lattice spanned by (1, zeta_r) and (0, r).
210+
## (a, b) = (a, c) * (1 zeta_r)
211+
## (0 r )
212+
## The injectivity is equivalent to the fact that the intersection between this lattice and [-A, A]^2
213+
## is trivial. To verify this we first compute a LLL reduced basis {v,w} and
214+
## then check if at least one of v, w, v + w, v - w is belongs to such a square.
215+
## If not, there can't be other lattice points in the square.
216+
if endo_mul_is_used:
217+
A = 2**(LAMBDA//2 + 1) + 2**(LAMBDA//2) + 1
218+
L = Matrix([[1,Integer(zeta_r)],[0,SCALAR_FIELD_MODULUS]])
219+
Lred = L.LLL()
220+
set = [Lred.row(0), Lred.row(1), Lred.row(0) - Lred.row(1), Lred.row(0) + Lred.row(1)]
221+
add_unsafe = True
222+
for v in set:
223+
if abs(v[0]) <= A and abs(v[1]) <= A:
224+
add_unsafe = False
225+
if add_unsafe:
226+
print("We can use add_unsafe for endo_mul.")
227+
else:
228+
print("WARNING! WE CAN'T USE add_unsafe FOR endo_mul!")
229+
else:
230+
print("endo_mul is not used for this curve.")

algebra/src/curves/mnt6/g1.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use crate::{
77
short_weierstrass_projective::{GroupAffine, GroupProjective},
88
AffineCurve,
99
},
10-
fields::mnt6::{Fq, Fq3, Fr},
10+
fields::mnt6::*,
1111
};
1212
use crate::{field_new, FromBytes};
1313
use serde::{Deserialize, Serialize};

algebra/src/curves/mnt6/g2.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use crate::{
77
short_weierstrass_projective::{GroupAffine, GroupProjective},
88
AffineCurve,
99
},
10-
fields::mnt6::{Fq, Fq3, Fr},
10+
fields::mnt6::*,
1111
};
1212
use crate::{field_new, FromBytes};
1313
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};

algebra/src/curves/mod.rs

+15
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,21 @@ pub trait AffineCurve:
321321
fn mul_by_cofactor_inv(&self) -> Self;
322322
}
323323

324+
/// The `EndoMulCurve` trait for curves that have a non-trivial endomorphism
325+
/// `Phi` of the form `Phi(x,y) = (zeta*x,y)`.
326+
pub trait EndoMulCurve: AffineCurve {
327+
/// Apply `Phi`
328+
fn apply_endomorphism(&self) -> Self;
329+
330+
/// Conversion of a bit sequence used in `endo_mul()` into its equivalent
331+
/// scalar
332+
fn endo_rep_to_scalar(bits: Vec<bool>) -> Result<Self::ScalarField, Error>;
333+
334+
/// Endomorphism-based multiplication of `&self` with `bits`, a little-endian
335+
/// endomorphism representation.
336+
fn endo_mul(&self, bits: Vec<bool>) -> Result<Self::Projective, Error>;
337+
}
338+
324339
impl<C: ProjectiveCurve> Group for C {
325340
type ScalarField = C::ScalarField;
326341
#[must_use]

algebra/src/curves/models/mod.rs

+19
Original file line numberDiff line numberDiff line change
@@ -122,3 +122,22 @@ pub trait MontgomeryModelParameters: ModelParameters {
122122

123123
type TEModelParameters: TEModelParameters<BaseField = Self::BaseField>;
124124
}
125+
126+
pub trait EndoMulParameters: SWModelParameters {
127+
/// Parameters for endomorphism-based scalar multiplication [Halo](https://eprint.iacr.org/2019/1021).
128+
/// A non-trivial cubic root of unity `ENDO_COEFF` for a curve endomorphism of the form
129+
/// (x, y) -> (ENDO_COEFF * x, y).
130+
const ENDO_COEFF: Self::BaseField;
131+
132+
/// The scalar representation `zeta_r` of `ENDO_COEFF`.
133+
/// NOTE : If one wants to use the endo mul circuit with `lambda` many bits,
134+
/// then `zeta_r` MUST satisfy the minimal distance property
135+
/// D = min { d(n*zeta_r, m*zeta_r) : n,m in [0, T] } >= R + 1,
136+
/// where `T = 2^{lambda/2 + 1} + 2^{lambda/2} - 1` is the output
137+
/// bound for the coefficients a, b of the equivalent scalar
138+
/// representation `a*zeta_r + b`.
139+
const ENDO_SCALAR: Self::ScalarField;
140+
141+
/// Maximum number of bits for which security of endo mul is proven. MUST be an even number.
142+
const LAMBDA: usize;
143+
}

algebra/src/curves/models/short_weierstrass_jacobian.rs

+97-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
use crate::{
22
bytes::{FromBytes, ToBytes},
3-
curves::{models::SWModelParameters as Parameters, AffineCurve, ProjectiveCurve},
3+
curves::{models::{
4+
SWModelParameters as Parameters,
5+
EndoMulParameters as EndoParameters,
6+
}, AffineCurve, ProjectiveCurve, EndoMulCurve},
47
fields::{BitIterator, Field, PrimeField, SquareRootField},
58
BitSerializationError, CanonicalDeserialize, CanonicalDeserializeWithFlags, CanonicalSerialize,
69
CanonicalSerializeWithFlags, Error, FromBytesChecked, FromCompressedBits, SWFlags,
@@ -295,6 +298,99 @@ impl<P: Parameters> AffineCurve for GroupAffine<P> {
295298
}
296299
}
297300

301+
impl<P: EndoParameters> EndoMulCurve for GroupAffine<P> {
302+
303+
fn apply_endomorphism(&self) -> Self {
304+
let mut self_e = self.clone();
305+
self_e.x.mul_assign(P::ENDO_COEFF);
306+
self_e
307+
}
308+
309+
fn endo_rep_to_scalar(bits: Vec<bool>) -> Result<Self::ScalarField, Error> {
310+
311+
let mut a : P::ScalarField = 2u64.into();
312+
let mut b : P::ScalarField = 2u64.into();
313+
314+
let one = P::ScalarField::one();
315+
let one_neg = one.neg();
316+
317+
let mut bits = bits;
318+
if bits.len() % 2 == 1 {
319+
bits.push(false);
320+
}
321+
322+
if bits.len() > P::LAMBDA {
323+
Err("Endo mul bits length exceeds LAMBDA")?
324+
}
325+
326+
for i in (0..(bits.len() / 2)).rev() {
327+
a.double_in_place();
328+
b.double_in_place();
329+
330+
let s =
331+
if bits[i * 2] {
332+
&one
333+
} else {
334+
&one_neg
335+
};
336+
337+
if bits[i * 2 + 1] {
338+
a.add_assign(s);
339+
} else {
340+
b.add_assign(s);
341+
}
342+
}
343+
344+
Ok(a.mul(P::ENDO_SCALAR) + &b)
345+
}
346+
347+
/// Endomorphism-based multiplication of a curve point
348+
/// with a scalar in little-endian endomorphism representation.
349+
fn endo_mul(&self, bits: Vec<bool>) -> Result<Self::Projective, Error> {
350+
351+
let self_neg = self.neg();
352+
353+
let self_e = self.apply_endomorphism();
354+
let self_e_neg = self_e.neg();
355+
356+
let mut acc = self_e.into_projective();
357+
acc.add_assign_mixed(&self);
358+
acc.double_in_place();
359+
360+
let mut bits = bits;
361+
if bits.len() % 2 == 1 {
362+
bits.push(false);
363+
}
364+
365+
if bits.len() > P::LAMBDA {
366+
Err("Endo mul bits length exceeds LAMBDA")?
367+
}
368+
369+
for i in (0..(bits.len() / 2)).rev() {
370+
371+
let s =
372+
if bits[i * 2 + 1] {
373+
if bits[i * 2] {
374+
&self_e
375+
} else {
376+
&self_e_neg
377+
}
378+
} else {
379+
if bits[i * 2] {
380+
&self
381+
} else {
382+
&self_neg
383+
}
384+
};
385+
386+
acc.double_in_place();
387+
acc.add_assign_mixed(s);
388+
}
389+
390+
Ok(acc)
391+
}
392+
}
393+
298394
impl<P: Parameters> SemanticallyValid for GroupAffine<P> {
299395
fn is_valid(&self) -> bool {
300396
self.x.is_valid() && self.y.is_valid() && self.group_membership_test()

0 commit comments

Comments
 (0)