Skip to content
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
46 changes: 46 additions & 0 deletions src/arithmetic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,19 @@
//! This module is temporary, and the extension traits defined here are expected to be
//! upstreamed into the `ff` and `group` crates after some refactoring.

use crate::CurveExt;

pub(crate) struct EndoParameters {
pub(crate) gamma1: [u64; 4],
pub(crate) gamma2: [u64; 4],
pub(crate) b1: [u64; 4],
pub(crate) b2: [u64; 4],
}

pub trait CurveEndo: CurveExt {
fn decompose_scalar(e: &Self::ScalarExt) -> (u128, bool, u128, bool);
}

pub trait CurveAffineExt: pasta_curves::arithmetic::CurveAffine {
fn batch_add<const COMPLETE: bool, const LOAD_POINTS: bool>(
points: &mut [Self],
Expand Down Expand Up @@ -42,3 +55,36 @@ pub(crate) const fn mac(a: u64, b: u64, c: u64, carry: u64) -> (u64, u64) {
let ret = (a as u128) + ((b as u128) * (c as u128)) + (carry as u128);
(ret as u64, (ret >> 64) as u64)
}

/// Compute a + (b * c), returning the result and the new carry over.
#[inline(always)]
pub(crate) const fn macx(a: u64, b: u64, c: u64) -> (u64, u64) {
let res = (a as u128) + ((b as u128) * (c as u128));
(res as u64, (res >> 64) as u64)
}

/// Compute a * b, returning the result.
#[inline(always)]
pub(crate) fn mul_512(a: [u64; 4], b: [u64; 4]) -> [u64; 8] {
let (r0, carry) = macx(0, a[0], b[0]);
let (r1, carry) = macx(carry, a[0], b[1]);
let (r2, carry) = macx(carry, a[0], b[2]);
let (r3, carry_out) = macx(carry, a[0], b[3]);

let (r1, carry) = macx(r1, a[1], b[0]);
let (r2, carry) = mac(r2, a[1], b[1], carry);
let (r3, carry) = mac(r3, a[1], b[2], carry);
let (r4, carry_out) = mac(carry_out, a[1], b[3], carry);

let (r2, carry) = macx(r2, a[2], b[0]);
let (r3, carry) = mac(r3, a[2], b[1], carry);
let (r4, carry) = mac(r4, a[2], b[2], carry);
let (r5, carry_out) = mac(carry_out, a[2], b[3], carry);

let (r3, carry) = macx(r3, a[3], b[0]);
let (r4, carry) = mac(r4, a[3], b[1], carry);
let (r5, carry) = mac(r5, a[3], b[2], carry);
let (r6, carry_out) = mac(carry_out, a[3], b[3], carry);

[r0, r1, r2, r3, r4, r5, r6, carry_out]
}
Comment thread
CPerezz marked this conversation as resolved.
47 changes: 45 additions & 2 deletions src/bn256/curve.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
use crate::arithmetic::mul_512;
use crate::arithmetic::sbb;
use crate::arithmetic::CurveEndo;
use crate::arithmetic::EndoParameters;
use crate::bn256::Fq;
use crate::bn256::Fq2;
use crate::bn256::Fr;
use crate::endo;
use crate::ff::WithSmallOrderMulGroup;
use crate::ff::{Field, PrimeField};
use crate::group::Curve;
Expand All @@ -16,6 +21,7 @@ use core::fmt::Debug;
use core::iter::Sum;
use core::ops::{Add, Mul, Neg, Sub};
use rand::RngCore;
use std::convert::TryInto;
use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption};

#[cfg(feature = "derive_serde")]
Expand Down Expand Up @@ -111,6 +117,26 @@ const G2_GENERATOR_Y: Fq2 = Fq2 {
]),
};

// Generated using https://github.com/ConsenSys/gnark-crypto/blob/master/ecc/utils.go
// with `bn256::Fr::ZETA`
// See https://github.com/demining/Endomorphism-Secp256k1/blob/main/README.md
// to have more details about the endomorphism.
const ENDO_PARAMS: EndoParameters = EndoParameters {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these derived directly or taken form some implementation/ article? I think it would be good to add some reference in any case.
I also think it would be good to add a reference to some resource that explains what these are. Maybe something like:

//  https://www.iacr.org/archive/crypto2001/21390189.pdf
//  Vectors v1, v2 described in Section 4.

Without some indication I know future me will forget what these are x)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is resolved with de71b18

// round(b2/n)
gamma1: [
0x7a7bd9d4391eb18du64,
0x4ccef014a773d2cfu64,
0x0000000000000002u64,
0u64,
],
// round(-b1/n)
gamma2: [0xd91d232ec7e0b3d7u64, 0x0000000000000002u64, 0u64, 0u64],
b1: [0x8211bbeb7d4f1128u64, 0x6f4d8248eeb859fcu64, 0u64, 0u64],
b2: [0x89d3256894d213e3u64, 0u64, 0u64, 0u64],
};

endo!(G1, Fr, ENDO_PARAMS);

impl group::cofactor::CofactorGroup for G1 {
type Subgroup = G1;

Expand Down Expand Up @@ -180,9 +206,14 @@ impl CofactorGroup for G2 {

#[cfg(test)]
mod tests {

use crate::arithmetic::CurveEndo;
use crate::bn256::{Fr, G1, G2};
use crate::CurveExt;
use ff::Field;
use ff::PrimeField;
use ff::WithSmallOrderMulGroup;
use rand_core::OsRng;

#[test]
fn test_curve() {
Expand All @@ -191,12 +222,24 @@ mod tests {
}

#[test]
fn test_endo_consistency() {
fn test_endo() {
let g = G1::generator();
assert_eq!(g * Fr::ZETA, g.endo());

let g = G2::generator();
assert_eq!(g * Fr::ZETA, g.endo());
for _ in 0..100000 {
let k = Fr::random(OsRng);
let (k1, k1_neg, k2, k2_neg) = G1::decompose_scalar(&k);
if k1_neg & k2_neg {
assert_eq!(k, -Fr::from_u128(k1) + Fr::ZETA * Fr::from_u128(k2))
} else if k1_neg {
assert_eq!(k, -Fr::from_u128(k1) - Fr::ZETA * Fr::from_u128(k2))
} else if k2_neg {
assert_eq!(k, Fr::from_u128(k1) + Fr::ZETA * Fr::from_u128(k2))
} else {
assert_eq!(k, Fr::from_u128(k1) - Fr::ZETA * Fr::from_u128(k2))
}
}
}

#[test]
Expand Down
51 changes: 51 additions & 0 deletions src/derive/curve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,57 @@ macro_rules! batch_add {
};
}

#[macro_export]
macro_rules! endo {
($name:ident, $field:ident, $params:expr) => {
impl CurveEndo for $name {
fn decompose_scalar(k: &$field) -> (u128, bool, u128, bool) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good! However there are some non-obvious optimizations that make the algorithm quite hard to follow.
I gave some explanation of what I believe is going on in this note
I think some comments should be added so that working in this code is easier in the future.

let to_limbs = |e: &$field| {
let repr = e.to_repr();
let repr = repr.as_ref();
let tmp0 = u64::from_le_bytes(repr[0..8].try_into().unwrap());
let tmp1 = u64::from_le_bytes(repr[8..16].try_into().unwrap());
let tmp2 = u64::from_le_bytes(repr[16..24].try_into().unwrap());
let tmp3 = u64::from_le_bytes(repr[24..32].try_into().unwrap());
[tmp0, tmp1, tmp2, tmp3]
};

let get_lower_128 = |e: &$field| {
let e = to_limbs(e);
u128::from(e[0]) | (u128::from(e[1]) << 64)
};

let is_neg = |e: &$field| {
let e = to_limbs(e);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am unsure of the criteria of this function. Could you give some further explanation please? :)
Do we get is_neg(e) when e[3] >0?
Aren't the 3 first assignments of borrow always 0?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This trick actually reveals whether a element is ~128 bit or ~F::NUM_BITS. if latter we see that element is negative and subtract it from modulus and get the actual ~128 bit element. It was originally like below

let (_, borrow) = sbb(0xffffffffffffffff, e[0], 0);
let (_, borrow) = sbb(0xffffffffffffffff, e[1], borrow);
let (_, borrow) = sbb(0x00, e[2], borrow);
let (_, borrow) = sbb(0x00, e[3], borrow);

However in some fields we hit scalars decomposed into 129 bits so I just also made third control limb sparse�

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, thanks for the explanation!

let (_, borrow) = sbb(0xffffffffffffffff, e[0], 0);
let (_, borrow) = sbb(0xffffffffffffffff, e[1], borrow);
let (_, borrow) = sbb(0xffffffffffffffff, e[2], borrow);
let (_, borrow) = sbb(0x00, e[3], borrow);
borrow & 1 != 0
};

let input = to_limbs(&k);
let c1 = mul_512($params.gamma2, input);
let c2 = mul_512($params.gamma1, input);
let c1 = [c1[4], c1[5], c1[6], c1[7]];
let c2 = [c2[4], c2[5], c2[6], c2[7]];
let q1 = mul_512(c1, $params.b1);
let q2 = mul_512(c2, $params.b2);
let q1 = $field::from_raw([q1[0], q1[1], q1[2], q1[3]]);
let q2 = $field::from_raw([q2[0], q2[1], q2[2], q2[3]]);
let k2 = q2 - q1;
let k1 = k + k2 * $field::ZETA;
let k1_neg = is_neg(&k1);
let k2_neg = is_neg(&k2);
let k1 = if k1_neg { -k1 } else { k1 };
let k2 = if k2_neg { -k2 } else { k2 };
Comment thread
CPerezz marked this conversation as resolved.

(get_lower_128(&k1), k1_neg, get_lower_128(&k2), k2_neg)
}
}
};
}

#[macro_export]
macro_rules! new_curve_impl {
(($($privacy:tt)*),
Expand Down
72 changes: 72 additions & 0 deletions src/pasta/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
use crate::arithmetic::mul_512;
use crate::arithmetic::sbb;
use crate::{
arithmetic::{CurveEndo, EndoParameters},
endo,
};
use ff::PrimeField;
use ff::WithSmallOrderMulGroup;
pub use pasta_curves::{pallas, vesta, Ep, EpAffine, Eq, EqAffine, Fp, Fq};
use std::convert::TryInto;

impl crate::CurveAffineExt for EpAffine {
fn batch_add<const COMPLETE: bool, const LOAD_POINTS: bool>(
Expand All @@ -25,3 +34,66 @@ impl crate::CurveAffineExt for EqAffine {
unimplemented!();
}
}

// Generated using https://github.com/ConsenSys/gnark-crypto/blob/master/ecc/utils.go
// with `pasta_curves::Fp::ZETA`
// See https://github.com/demining/Endomorphism-Secp256k1/blob/main/README.md
// to have more details about the endomorphism.
const ENDO_PARAMS_EQ: EndoParameters = EndoParameters {
// round(b2/n)
gamma1: [0x32c49e4c00000003, 0x279a745902a2654e, 0x1, 0x0],
// round(-b1/n)
gamma2: [0x31f0256800000002, 0x4f34e8b2066389a4, 0x2, 0x0],
b1: [0x8cb1279300000001, 0x49e69d1640a89953, 0x0, 0x0],
b2: [0x0c7c095a00000001, 0x93cd3a2c8198e269, 0x0, 0x0],
};

// Generated using https://github.com/ConsenSys/gnark-crypto/blob/master/ecc/utils.go
// with `pasta_curves::Fq::ZETA`
// See https://github.com/demining/Endomorphism-Secp256k1/blob/main/README.md
// to have more details about the endomorphism.
const ENDO_PARAMS_EP: EndoParameters = EndoParameters {
// round(b2/n)
gamma1: [0x32c49e4bffffffff, 0x279a745902a2654e, 0x1, 0x0],
// round(-b1/n)
gamma2: [0x31f0256800000002, 0x4f34e8b2066389a4, 0x2, 0x0],
b1: [0x8cb1279300000000, 0x49e69d1640a89953, 0x0, 0x0],
b2: [0x0c7c095a00000001, 0x93cd3a2c8198e269, 0x0, 0x0],
};

endo!(Eq, Fp, ENDO_PARAMS_EQ);
endo!(Ep, Fq, ENDO_PARAMS_EP);

#[test]
fn test_endo() {
use ff::Field;
use rand_core::OsRng;

for _ in 0..100000 {
let k = Fp::random(OsRng);
let (k1, k1_neg, k2, k2_neg) = Eq::decompose_scalar(&k);
if k1_neg & k2_neg {
assert_eq!(k, -Fp::from_u128(k1) + Fp::ZETA * Fp::from_u128(k2))
} else if k1_neg {
assert_eq!(k, -Fp::from_u128(k1) - Fp::ZETA * Fp::from_u128(k2))
} else if k2_neg {
assert_eq!(k, Fp::from_u128(k1) + Fp::ZETA * Fp::from_u128(k2))
} else {
assert_eq!(k, Fp::from_u128(k1) - Fp::ZETA * Fp::from_u128(k2))
}
}

for _ in 0..100000 {
let k = Fp::random(OsRng);
let (k1, k1_neg, k2, k2_neg) = Eq::decompose_scalar(&k);
if k1_neg & k2_neg {
assert_eq!(k, -Fp::from_u128(k1) + Fp::ZETA * Fp::from_u128(k2))
} else if k1_neg {
assert_eq!(k, -Fp::from_u128(k1) - Fp::ZETA * Fp::from_u128(k2))
} else if k2_neg {
assert_eq!(k, Fp::from_u128(k1) + Fp::ZETA * Fp::from_u128(k2))
} else {
assert_eq!(k, Fp::from_u128(k1) - Fp::ZETA * Fp::from_u128(k2))
}
}
}