Skip to content

Commit

Permalink
Add endo scalar decomposition (#24)
Browse files Browse the repository at this point in the history
* feat: add endo scalar decomposition

* fix: clippy

* docs: add comments to endo parameters

* remove example glv mul

* cargo fmt
  • Loading branch information
kilic authored Jun 8, 2023
1 parent e97adcb commit 21def8d
Show file tree
Hide file tree
Showing 4 changed files with 214 additions and 2 deletions.
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]
}
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 {
// 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) {
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);
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 };

(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))
}
}
}

0 comments on commit 21def8d

Please sign in to comment.