From 21def8d900f088b325b61d6d8fe2777975960d28 Mon Sep 17 00:00:00 2001 From: kilic Date: Thu, 8 Jun 2023 11:49:32 +0300 Subject: [PATCH] Add endo scalar decomposition (#24) * feat: add endo scalar decomposition * fix: clippy * docs: add comments to endo parameters * remove example glv mul * cargo fmt --- src/arithmetic.rs | 46 +++++++++++++++++++++++++++++ src/bn256/curve.rs | 47 +++++++++++++++++++++++++++-- src/derive/curve.rs | 51 ++++++++++++++++++++++++++++++++ src/pasta/mod.rs | 72 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 214 insertions(+), 2 deletions(-) diff --git a/src/arithmetic.rs b/src/arithmetic.rs index 42b84772..edbefbd6 100644 --- a/src/arithmetic.rs +++ b/src/arithmetic.rs @@ -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( points: &mut [Self], @@ -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] +} diff --git a/src/bn256/curve.rs b/src/bn256/curve.rs index 4d115008..b1d6abe1 100644 --- a/src/bn256/curve.rs +++ b/src/bn256/curve.rs @@ -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; @@ -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")] @@ -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; @@ -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() { @@ -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] diff --git a/src/derive/curve.rs b/src/derive/curve.rs index 39aac496..dc1a4aa4 100644 --- a/src/derive/curve.rs +++ b/src/derive/curve.rs @@ -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)*), diff --git a/src/pasta/mod.rs b/src/pasta/mod.rs index f6aee547..9cd4592e 100644 --- a/src/pasta/mod.rs +++ b/src/pasta/mod.rs @@ -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( @@ -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)) + } + } +}