From 89835540d74bfb1a619a7fd3084edebab679f37c Mon Sep 17 00:00:00 2001 From: Tristan Britt <53545754+trbritt@users.noreply.github.com> Date: Mon, 19 Aug 2024 14:32:44 -0400 Subject: [PATCH] Revert "Tristan/war 306 optimal ate pairing (#20)" This reverts commit f1579d2a4b83cf33bd486b0fc48837ff2df11a76. --- Cargo.toml | 8 +- README.md | 29 +- src/fields/extensions.rs | 53 +--- src/fields/fp.rs | 225 ++++++++------- src/fields/fp12.rs | 204 ++++--------- src/fields/fp2.rs | 183 ++++++------ src/fields/fp6.rs | 152 +++++++--- src/fields/mod.rs | 10 +- src/fields/utils.rs | 18 +- src/groups/g1.rs | 65 ++--- src/groups/g2.rs | 94 +++--- src/groups/group.rs | 78 ++--- src/groups/gt.rs | 237 --------------- src/groups/mod.rs | 39 ++- src/hasher.rs | 40 +-- src/lib.rs | 63 +--- src/pairing.rs | 608 --------------------------------------- src/svdw.rs | 82 +++--- 18 files changed, 588 insertions(+), 1600 deletions(-) delete mode 100644 src/groups/gt.rs delete mode 100644 src/pairing.rs diff --git a/Cargo.toml b/Cargo.toml index 567e39c..27e19e1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,12 +2,12 @@ authors = ["Tristan Britt ", "0xAlcibiades "] categories = ["cryptography", "mathematics"] description = "Implementation of the BLS signature scheme using the alt-bn128 curve." -homepage = "https://github.com/warlock-labs/sylow" +homepage = "https://github.com/warlock-labs/alt-bn128-bls" keywords = ["alt-bn128", "bls", "cryptography", "elliptic-curve", "pairing"] license = "MIT" readme = "README.md" -repository = "https://github.com/warlock-labs/sylow.git" -name = "sylow" +repository = "https://github.com/warlock-labs/alt-bn128-bls.git" +name = "alt-bn128-bls" version = "0.0.1" edition = "2021" @@ -20,6 +20,8 @@ subtle = "2.6.1" crypto-bigint = "0.6.0-rc.2" sha3 = "0.11.0-pre.4" +[lib] +proc-macro = true [dev-dependencies] serde = { version = "1.0.204", features = ["derive"] } diff --git a/README.md b/README.md index 6ed8e45..10b0078 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ -# sylow +# alt-bn128-bls -[![License](https://img.shields.io/crates/l/sylow)](https://choosealicense.com/licenses/mit/) -[![Crates.io](https://img.shields.io/crates/v/sylow)](https://crates.io/crates/sylow) -[![Docs](https://img.shields.io/crates/v/sylow?color=blue&label=docs)](https://docs.rs/sylow/) -![CI](https://github.com/warlock-labs/sylow/actions/workflows/CI.yml/badge.svg) +[![License](https://img.shields.io/crates/l/alt-bn128-bls)](https://choosealicense.com/licenses/mit/) +[![Crates.io](https://img.shields.io/crates/v/alt-bn128-bls)](https://crates.io/crates/alt-bn128-bls) +[![Docs](https://img.shields.io/crates/v/alt-bn128-bls?color=blue&label=docs)](https://docs.rs/alt-bn128-bls/) +![CI](https://github.com/warlock-labs/alt-bn128-bls/actions/workflows/CI.yml/badge.svg) -sylow is a Rust library implementing the BLS (Boneh-Lynn-Shacham) signature scheme using the alt-bn128 (BN254) elliptic curve. It provides threshold signing capabilities and associated utilities, initially developed for use in the Warlock Chaos product. +alt-bn128-bls is a Rust library implementing the BLS (Boneh-Lynn-Shacham) signature scheme using the alt-bn128 (BN254) elliptic curve. It provides threshold signing capabilities and associated utilities, initially developed for use in the Warlock Chaos product. ## Features @@ -21,27 +21,24 @@ Add this to your `Cargo.toml`: ```toml [dependencies] -sylow = "0.0.1" +alt-bn128-bls = "0.0.1" ``` Here's a basic example of generating a key pair, signing a message, and verifying the signature: ```rust -use sylow::{KeyPair, sign, verify}; +use alt_bn128_bls::{KeyPair, sign, verify}; fn main() { let key_pair = KeyPair::generate(); let message = b"Hello, World!"; - if let Ok(signature) = sign(&key_pair.secret_key, message){ - if let Ok(verify) = verify(&key_pair.public_key, message, &signature){ - assert!(verify, "Signature verification failed"); - } - } + let signature = sign(&key_pair.secret_key, message); + assert!(verify(&key_pair.public_key, message, &signature)); } ``` -For more examples and usage details, see the [API documentation](https://docs.rs/sylow). +For more examples and usage details, see the [API documentation](https://docs.rs/alt-bn128-bls). ## Core Concepts @@ -67,7 +64,7 @@ The following features and improvements are planned for future releases: ## Contributing -Contributions are welcome! Please feel free to submit issues, feature requests, or pull requests on the [GitHub repository](https://github.com/warlock-labs/sylow). +Contributions are welcome! Please feel free to submit issues, feature requests, or pull requests on the [GitHub repository](https://github.com/warlock-labs/alt-bn128-bls). ## License @@ -77,4 +74,4 @@ This project is licensed under the [MIT License](https://choosealicense.com/lice Warlock Labs - [https://github.com/warlock-labs](https://github.com/warlock-labs) -Project Link: [https://github.com/warlock-labs/sylow](https://github.com/warlock-labs/sylow) \ No newline at end of file +Project Link: [https://github.com/warlock-labs/alt-bn128-bls](https://github.com/warlock-labs/alt-bn128-bls) \ No newline at end of file diff --git a/src/fields/extensions.rs b/src/fields/extensions.rs index 3fe0049..08790d9 100644 --- a/src/fields/extensions.rs +++ b/src/fields/extensions.rs @@ -17,7 +17,9 @@ use std::ops::{Add, AddAssign, Neg, Sub, SubAssign}; // since the underlying Mul, Add, etc., are not, and const traits are in the works // https://github.com/rust-lang/rust/issues/67792 #[derive(Copy, Clone, Debug)] -pub struct FieldExtension>(pub [F; N]); +pub(crate) struct FieldExtension>( + pub(crate) [F; N], +); impl> From for FieldExtension @@ -28,17 +30,12 @@ impl> From Self::new(&retval) } } +#[allow(dead_code)] impl> FieldExtension { - /// This is a const constructor that takes a slice of field elements and returns a field extension - /// The usage of the generics means that it is possible to instantiate any representation of - /// an extension need. - pub const fn new(c: &[F; N]) -> Self { + pub(crate) const fn new(c: &[F; N]) -> Self { Self(*c) } - /// There is eventually a need to be able to perform multiplication across different field - /// extensions, and more or less this corresponds to a basic scaling, see - /// - pub fn scale(&self, factor: F) -> Self { + pub(crate) fn scale(&self, factor: F) -> Self { let mut i = 0; let mut retval = [F::zero(); N]; while i < N { @@ -62,27 +59,16 @@ impl> ConstantTimeE retval } } -impl<'a, 'b, const D: usize, const N: usize, F: FieldExtensionTrait> - Add<&'b FieldExtension> for &'a FieldExtension -{ - type Output = FieldExtension; - - fn add(self, other: &'b FieldExtension) -> Self::Output { +impl> Add for FieldExtension { + type Output = Self; + fn add(self, other: Self) -> Self { let mut i = 0; let mut retval = [F::zero(); N]; while i < N { retval[i] = self.0[i] + other.0[i]; i += 1; } - Self::Output::new(&retval) - } -} -impl> Add> - for FieldExtension -{ - type Output = Self; - fn add(self, other: FieldExtension) -> Self::Output { - &self + &other + Self::new(&retval) } } impl> AddAssign @@ -92,27 +78,16 @@ impl> AddAssign *self = *self + other; } } -impl<'a, 'b, const D: usize, const N: usize, F: FieldExtensionTrait> - Sub<&'b FieldExtension> for &'a FieldExtension -{ - type Output = FieldExtension; - - fn sub(self, other: &'b FieldExtension) -> Self::Output { +impl> Sub for FieldExtension { + type Output = Self; + fn sub(self, other: Self) -> Self { let mut i = 0; let mut retval = [F::zero(); N]; while i < N { retval[i] = self.0[i] - other.0[i]; i += 1; } - Self::Output::new(&retval) - } -} -impl> Sub> - for FieldExtension -{ - type Output = Self; - fn sub(self, other: FieldExtension) -> Self::Output { - &self - &other + Self::new(&retval) } } impl> SubAssign diff --git a/src/fields/fp.rs b/src/fields/fp.rs index 450b690..ddee918 100644 --- a/src/fields/fp.rs +++ b/src/fields/fp.rs @@ -43,23 +43,17 @@ use num_traits::{Euclid, Inv, One, Pow, Zero}; use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Rem, Sub, SubAssign}; use subtle::CtOption; -pub const BN254_FP_MODULUS: Fp = Fp::new(U256::from_words([ - 0x3C208C16D87CFD47, - 0x97816A916871CA8D, - 0xB85045B68181585D, - 0x30644E72E131A029, +const FP_QUADRATIC_NON_RESIDUE: Fp = Fp::new(U256::from_words([ + 4332616871279656262, + 10917124144477883021, + 13281191951274694749, + 3486998266802970665, ])); /// This defines the key properties of a field extension. Now, mathematically, /// a finite field satisfies many rigorous mathematical properties. The /// (non-exhaustive) list below simply suffices to illustrate those properties /// that are purely relevant to the task at hand here. -/// -/// There are two generic elements that describe the particular field extension one generates: -/// (i) the degree of the extension (what is the highest degree of an element in the ring that is -/// used to generate the quotient field F(x)/f(x)), D, and (ii) the number of elements -/// required for a unique representation of an element in the extension, N. An extension can have -/// many different representations, so it is key to allow this flexibility. -pub trait FieldExtensionTrait: +pub(crate) trait FieldExtensionTrait: Sized + Copy + Clone @@ -85,12 +79,25 @@ pub trait FieldExtensionTrait: // multiplication in a field extension is dictated // heavily such a value below fn quadratic_non_residue() -> Self; - + // this endomorphism is key for twist operations + #[allow(dead_code)] + fn frobenius(&self, exponent: usize) -> Self; + // specialized algorithms exist in each extension + // for sqrt and square, simply helper functions really + #[allow(dead_code)] + fn sqrt(&self) -> CtOption; + fn square(&self) -> Self; + + #[allow(dead_code)] fn rand(rng: &mut R) -> Self; + fn is_square(&self) -> Choice; + + fn sgn0(&self) -> Choice; + fn curve_constant() -> Self; } -pub trait FinitePrimeField: +pub(crate) trait FinitePrimeField: FieldExtensionTrait + Rem + Euclid + Pow + From where UintType: ConcatMixed>, @@ -105,25 +112,24 @@ where #[allow(unused_macros)] macro_rules! define_finite_prime_field { - ($wrapper_name:ident, $mod_struct:ident, $output:ident, $uint_type:ty, $limbs:expr, - $modulus:expr, - $degree:expr, - $nreps:expr) => { - impl_modulus!($mod_struct, $uint_type, $modulus); + ($wrapper_name:ident, $uint_type:ty, $limbs:expr, $modulus:expr, $degree:expr, $nreps:expr) => { + impl_modulus!(ModulusStruct, $uint_type, $modulus); //special struct for const-time arithmetic on montgomery form integers mod p - type $output = crypto_bigint::modular::ConstMontyForm<$mod_struct, { $mod_struct::LIMBS }>; + type Output = + crypto_bigint::modular::ConstMontyForm; #[derive(Clone, Debug, Copy)] //to be used in const contexts - pub struct $wrapper_name($mod_struct, $output); - + pub(crate) struct $wrapper_name(ModulusStruct, Output); + #[allow(dead_code)] impl FinitePrimeField<$limbs, $uint_type, $degree, $nreps> for $wrapper_name {} - impl $wrapper_name { // builder structure to create elements in the base field of a given value - pub const fn new(value: $uint_type) -> Self { - Self($mod_struct, $output::new(&value)) + pub(crate) const fn new(value: $uint_type) -> Self { + Self(ModulusStruct, Output::new(&value)) } - pub fn new_from_str(value: &str) -> Option { + #[allow(dead_code)] // this is indeed used in the test cases, which are ignored by + // the linter + pub(crate) fn new_from_str(value: &str) -> Option { let ints: Vec<_> = { let mut acc = Self::zero(); (0..11) @@ -147,11 +153,11 @@ macro_rules! define_finite_prime_field { Some(res) } // take the element and convert it to "normal" form from montgomery form - pub const fn value(&self) -> $uint_type { + pub(crate) const fn value(&self) -> $uint_type { self.1.retrieve() } - pub fn characteristic() -> $uint_type { - <$uint_type>::from($mod_struct::MODULUS.as_nz_ref().get()) + pub(crate) fn characteristic() -> $uint_type { + <$uint_type>::from(ModulusStruct::MODULUS.as_nz_ref().get()) } pub const ZERO: Self = Self::new(<$uint_type>::from_words([0x0; 4])); pub const ONE: Self = Self::new(<$uint_type>::from_words([0x1, 0x0, 0x0, 0x0])); @@ -167,14 +173,51 @@ macro_rules! define_finite_prime_field { fn quadratic_non_residue() -> Self { //this is p - 1 mod p = -1 mod p = 0 - 1 mod p // = -1 - Self::new((-Self::ONE).1.retrieve()) + FP_QUADRATIC_NON_RESIDUE + } + fn frobenius(&self, _exponent: usize) -> Self { + Self::zero() + } + fn sqrt(&self) -> CtOption { + // This is an instantiation of Shank's algorithm, which solves congruences of + // the form $r^2\equiv n \mod p$, namely the sqrt of n. It does not work for + // composite moduli (aka nonprime p), since that is the integer factorization + // problem. The full algorithm is not necessary here, and has the additional + // simpication that we can exploit in our case. Namely, the BN254 curve has a + // prime that is congruent to 3 mod 4. In this case, the sqrt only has the + // possible solution of $\pm pow(n, \frac{p+1}{4})$, which is where this magic + // number below comes from ;) + let arg = + ((Self::new(Self::characteristic()) + Self::one()) / Self::from(4)).value(); + let sqrt = self.pow(arg); + CtOption::new( + sqrt, + >::square(&sqrt).ct_eq(self), + ) + } + fn square(&self) -> Self { + (*self) * (*self) } fn rand(rng: &mut R) -> Self { Self::new(<$uint_type>::random_mod( rng, - $mod_struct::MODULUS.as_nz_ref(), + ModulusStruct::MODULUS.as_nz_ref(), )) } + fn is_square(&self) -> Choice { + let p_minus_1_div_2 = + ((Self::new(Self::characteristic()) - Self::from(1)) / Self::from(2)).value(); + let retval = self.pow(p_minus_1_div_2); + Choice::from((retval == Self::zero() || retval == Self::one()) as u8) + } + fn sgn0(&self) -> Choice { + let a = *self % Self::from(2u64); + if a.is_zero() { + Choice::from(0u8) + } else { + Choice::from(1u8) + } + } /// this is the constant of the j-invariant curve defined over this base field. /// Namely, the short Weierstrass curve is of the form $y^2 = x^3 + b$, and the below /// is the constant `b`. For BN254, this is 3. @@ -184,7 +227,7 @@ macro_rules! define_finite_prime_field { } impl From for $wrapper_name { fn from(value: u64) -> Self { - Self($mod_struct, $output::new(&<$uint_type>::from_u64(value))) + Self(ModulusStruct, Output::new(&<$uint_type>::from_u64(value))) } } /// We now implement binary operations on the base field. This more or less @@ -364,79 +407,7 @@ macro_rules! define_finite_prime_field { } const BN254_MOD_STRING: &str = "30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47"; -const BN254_SUBGROUP_MOD_STRING: &str = - "30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001"; -define_finite_prime_field!( - Fp, - FpModStruct, - FpOutputType, - U256, - 8, - BN254_MOD_STRING, - 1, - 1 -); -define_finite_prime_field!( - Fr, - FrModStruct, - FrOutputType, - U256, - 8, - BN254_SUBGROUP_MOD_STRING, - 1, - 1 -); -impl<'a> From<&'a Fr> for Fp { - fn from(value: &'a Fr) -> Self { - Fp::new(value.value()) - } -} -impl From for Fp { - fn from(value: Fr) -> Self { - Fp::from(&value) - } -} -impl Fp { - pub fn frobenius(&self, exponent: usize) -> Self { - // this function is inherently expensive, and we never call it on the base field, but if - // we did, it's only defined for p=1. Specialized versions exist for all extensions which - // will require the frobenius transformation - match exponent { - 1 => self.pow(BN254_FP_MODULUS.value()), - _ => *self, - } - } - pub fn sqrt(&self) -> CtOption { - // This is an instantiation of Shank's algorithm, which solves congruences of - // the form $r^2\equiv n \mod p$, namely the sqrt of n. It does not work for - // composite moduli (aka nonprime p), since that is the integer factorization - // problem. The full algorithm is not necessary here, and has the additional - // simpication that we can exploit in our case. Namely, the BN254 curve has a - // prime that is congruent to 3 mod 4. In this case, the sqrt only has the - // possible solution of $\pm pow(n, \frac{p+1}{4})$, which is where this magic - // number below comes from ;) - let arg = ((Self::new(Self::characteristic()) + Self::one()) / Self::from(4)).value(); - let sqrt = self.pow(arg); - CtOption::new(sqrt, sqrt.square().ct_eq(self)) - } - pub fn square(&self) -> Self { - (*self) * (*self) - } - pub fn is_square(&self) -> Choice { - let p_minus_1_div_2 = - ((Self::new(Self::characteristic()) - Self::from(1)) / Self::from(2)).value(); - let retval = self.pow(p_minus_1_div_2); - Choice::from((retval == Self::zero() || retval == Self::one()) as u8) - } - pub fn sgn0(&self) -> Choice { - let a = *self % Self::from(2u64); - if a.is_zero() { - Choice::from(0u8) - } else { - Choice::from(1u8) - } - } -} +define_finite_prime_field!(Fp, U256, 8, BN254_MOD_STRING, 1, 1); /// the code below makes the base field "visible" to higher /// order extensions. The issue is really the fact that generic /// traits cannot enforce arithmetic relations, such as the @@ -450,9 +421,25 @@ impl FieldExtensionTrait<2, 2> for Fp { fn quadratic_non_residue() -> Self { >::quadratic_non_residue() } + fn frobenius(&self, exponent: usize) -> Self { + >::frobenius(self, exponent) + } + fn sqrt(&self) -> CtOption { + >::sqrt(self) + } + fn square(&self) -> Self { + >::square(self) + } fn rand(rng: &mut R) -> Self { >::rand(rng) } + + fn is_square(&self) -> Choice { + >::is_square(self) + } + fn sgn0(&self) -> Choice { + >::sgn0(self) + } fn curve_constant() -> Self { >::curve_constant() } @@ -463,10 +450,25 @@ impl FieldExtensionTrait<2, 2> for Fp { #[cfg(test)] mod tests { use super::*; + const MODULUS: [u64; 4] = [ + 0x3C208C16D87CFD47, + 0x97816A916871CA8D, + 0xB85045B68181585D, + 0x30644E72E131A029, + ]; fn create_field(value: [u64; 4]) -> Fp { Fp::new(U256::from_words(value)) } + mod test_modulus_conversion { + use super::*; + #[test] + fn test_modulus() { + for i in U256::from_be_hex(BN254_MOD_STRING).as_limbs() { + println!("{:X}", i.0); + } + } + } mod addition_tests { use super::*; @@ -510,7 +512,7 @@ mod tests { ); // Addition that wraps around the modulus - let e = BN254_FP_MODULUS; + let e = create_field(MODULUS); let f = create_field([1, 0, 0, 0]); assert_eq!( (e + f).value(), @@ -598,7 +600,7 @@ mod tests { ); // Subtraction resulting in zero - let g = BN254_FP_MODULUS; + let g = create_field(MODULUS); assert_eq!( (g - g).value(), U256::from_words([0, 0, 0, 0]), @@ -914,15 +916,18 @@ mod tests { fn test_square() { for _ in 0..100 { let a = >::rand(&mut OsRng); - let b = a.square(); - assert!(bool::from(b.is_square()), "Is square failed"); + let b = >::square(&a); + assert!( + bool::from(>::is_square(&b)), + "Is square failed" + ); } } #[test] fn test_sqrt() { for i in 0..100 { let a = create_field([i, 2 * i, 3 * i, 4 * i]); - let b = a.sqrt(); + let b = >::sqrt(&a); match b.into_option() { Some(d) => { assert_eq!(d * d, a, "Sqrt failed") diff --git a/src/fields/fp12.rs b/src/fields/fp12.rs index 6ab8730..a95d9b8 100644 --- a/src/fields/fp12.rs +++ b/src/fields/fp12.rs @@ -14,10 +14,11 @@ use crate::fields::extensions::FieldExtension; use crate::fields::fp::{FieldExtensionTrait, Fp}; use crate::fields::fp2::Fp2; use crate::fields::fp6::Fp6; -use crypto_bigint::{rand_core::CryptoRngCore, subtle::ConditionallySelectable, U256}; +use crate::fields::utils::u256_to_u4096; +use crypto_bigint::{rand_core::CryptoRngCore, subtle::ConditionallySelectable, U256, U4096}; use num_traits::{Inv, One, Zero}; use std::ops::{Div, DivAssign, Mul, MulAssign}; -use subtle::Choice; +use subtle::{Choice, CtOption}; const FROBENIUS_COEFF_FP12_C1: &[Fp2; 12] = &[ // Fp2::quadratic_non_residue().pow( ( p^0 - 1) / 6) Fp2::new(&[Fp::ONE, Fp::ZERO]), @@ -162,25 +163,43 @@ const FROBENIUS_COEFF_FP12_C1: &[Fp2; 12] = &[ ])), ]), ]; -const FP12_QUADRATIC_NON_RESIDUE: Fp12 = Fp12::new(&[ - Fp6::new(&[ - Fp2::new(&[Fp::ZERO, Fp::ZERO]), - Fp2::new(&[Fp::ZERO, Fp::ZERO]), - Fp2::new(&[Fp::ZERO, Fp::ZERO]), - ]), - Fp6::new(&[ - Fp2::new(&[Fp::ONE, Fp::ZERO]), - Fp2::new(&[Fp::ZERO, Fp::ZERO]), - Fp2::new(&[Fp::ZERO, Fp::ZERO]), - ]), -]); +pub(crate) type Fp12 = FieldExtension<12, 2, Fp6>; -pub type Fp12 = FieldExtension<12, 2, Fp6>; +impl Fp12 { + // we have no need to define a residue multiplication since this + // is the top of our tower extension + #[allow(dead_code)] + fn characteristic() -> U4096 { + let wide_p = u256_to_u4096(&Fp::characteristic()); + let wide_p2 = wide_p * wide_p; + let wide_p6 = wide_p2 * wide_p2 * wide_p2; + wide_p6 * wide_p6 + } +} impl FieldExtensionTrait<12, 2> for Fp12 { fn quadratic_non_residue() -> Self { - // Self::new(&[Fp6::zero(), Fp6::one()]) - FP12_QUADRATIC_NON_RESIDUE + Self::new(&[Fp6::zero(), Fp6::one()]) + } + fn frobenius(&self, exponent: usize) -> Self { + // TODO: integrate generic D into struct to not hardcode degrees + Self::new(&[ + >::frobenius(&self.0[0], exponent), + >::frobenius(&self.0[1], exponent) + .scale(FROBENIUS_COEFF_FP12_C1[exponent % 12]), + ]) + } + fn sqrt(&self) -> CtOption { + unimplemented!() + } + fn square(&self) -> Self { + let tmp = self.0[0] * self.0[1]; + Self::new(&[ + (self.0[1].residue_mul() + self.0[0]) * (self.0[0] + self.0[1]) + - tmp + - tmp.residue_mul(), + tmp + tmp, + ]) } fn rand(rng: &mut R) -> Self { Self([ @@ -188,31 +207,31 @@ impl FieldExtensionTrait<12, 2> for Fp12 { >::rand(rng), ]) } + fn is_square(&self) -> Choice { + unimplemented!() + } + fn sgn0(&self) -> Choice { + unimplemented!() + } fn curve_constant() -> Self { unimplemented!() } } -impl<'a, 'b> Mul<&'b Fp12> for &'a Fp12 { - type Output = Fp12; - fn mul(self, other: &'b Fp12) -> Self::Output { +impl Mul for Fp12 { + type Output = Self; + fn mul(self, other: Self) -> Self::Output { // this is again simple Karatsuba multiplication // see comments in Fp2 impl of `Mul` trait let t0 = self.0[0] * other.0[0]; let t1 = self.0[1] * other.0[1]; - Self::Output::new(&[ + Self([ t1.residue_mul() + t0, (self.0[0] + self.0[1]) * (other.0[0] + other.0[1]) - t0 - t1, ]) } } -impl Mul for Fp12 { - type Output = Self; - fn mul(self, other: Self) -> Self::Output { - (&self).mul(&other) - } -} impl MulAssign for Fp12 { fn mul_assign(&mut self, other: Self) { *self = *self * other; @@ -221,7 +240,9 @@ impl MulAssign for Fp12 { impl Inv for Fp12 { type Output = Self; fn inv(self) -> Self::Output { - let tmp = (self.0[0].square() - (self.0[1].square().residue_mul())).inv(); + let tmp = (>::square(&self.0[0]) + - (>::square(&self.0[1]).residue_mul())) + .inv(); Self([self.0[0] * tmp, -(self.0[1] * tmp)]) } } @@ -257,133 +278,6 @@ impl ConditionallySelectable for Fp12 { ]) } } -/// Below are additional functions needed on Fp12 for the pairing operations -impl Fp12 { - pub fn unitary_inverse(&self) -> Self { - Self::new(&[self.0[0], -self.0[1]]) - } - pub fn pow(&self, arg: &[u64; 4]) -> Self { - let mut res = Self::one(); - for e in arg.iter().rev() { - for i in (0..64).rev() { - res = res.square(); - if ((*e >> i) & 1) == 1 { - res *= *self; - } - } - } - res - } - /// Due to the efficiency considerations of storing only the nonzero entries in the sparse - /// Fp12, there is a need to implement sparse multiplication on Fp12, which is what the - /// madness below is. It is an amalgamation of Algs 21-25 of - /// and is really just un-sparsing the value, and doing the multiplication manually. In order - /// to get around all the zeros that would arise if we just instantiated the full Fp12, - /// we have to manually implement all the required multiplication as far down the tower as - /// we can go. - /// - /// The following code relies on a separate representation of an element in Fp12. - /// Namely, hereunto we have defined Fp12 as a pair of Fp6 elements. However, it is just as - /// valid to define Fp12 as a pair of Fp2 elements. For f\in Fp12, f = g+hw, where g, h \in Fp6, - /// with g = g_0 + g_1v + g_2v^2, and h = h_0 + h_1v + h_2v^2, we can then write: - /// - /// f = g_0 + h_0w + g_1w^2 + h_1w^3 + g_2w^4 + h_2w^5 - /// - /// where the representation of Fp12 is not Fp12 = Fp2(w)/(w^6-(9+u)) - /// - /// This is a massive headache to get correct, and relied on existing implementations tbh. - /// Unfortunately for me, the performance boost is noticeable by early estimates (100s us). - /// Therefore, worth it. - /// - /// The function below is called by `zcash`, `bn`, and `arkworks` as `mul_by_024`, referring to - /// the indices of the non-zero elements in the 6x Fp2 representation above for the - /// multiplication. - pub fn sparse_mul(&self, ell_0: Fp2, ell_vw: Fp2, ell_vv: Fp2) -> Fp12 { - let z0 = self.0[0].0[0]; - let z1 = self.0[0].0[1]; - let z2 = self.0[0].0[2]; - let z3 = self.0[1].0[0]; - let z4 = self.0[1].0[1]; - let z5 = self.0[1].0[2]; - - let x0 = ell_0; - let x2 = ell_vv; - let x4 = ell_vw; - - let d0 = z0 * x0; - let d2 = z2 * x2; - let d4 = z4 * x4; - let t2 = z0 + z4; - let t1 = z0 + z2; - let s0 = z1 + z3 + z5; - - let s1 = z1 * x2; - let t3 = s1 + d4; - let t4 = t3.residue_mul() + d0; - let z0 = t4; - - let t3 = z5 * x4; - let s1 = s1 + t3; - let t3 = t3 + d2; - let t4 = t3.residue_mul(); - let t3 = z1 * x0; - let s1 = s1 + t3; - let t4 = t4 + t3; - let z1 = t4; - - let t0 = x0 + x2; - let t3 = t1 * t0 - d0 - d2; - let t4 = z3 * x4; - let s1 = s1 + t4; - let t3 = t3 + t4; - - let t0 = z2 + z4; - let z2 = t3; - - let t1 = x2 + x4; - let t3 = t0 * t1 - d2 - d4; - let t4 = t3.residue_mul(); - let t3 = z3 * x0; - let s1 = s1 + t3; - let t4 = t4 + t3; - let z3 = t4; - - let t3 = z5 * x2; - let s1 = s1 + t3; - let t4 = t3.residue_mul(); - let t0 = x0 + x4; - let t3 = t2 * t0 - d0 - d4; - let t4 = t4 + t3; - let z4 = t4; - - let t0 = x0 + x2 + x4; - let t3 = s0 * t0 - s1; - let z5 = t3; - - Fp12::new(&[Fp6::new(&[z0, z1, z2]), Fp6::new(&[z3, z4, z5])]) - } - pub fn frobenius(&self, exponent: usize) -> Self { - Self::new(&[ - self.0[0].frobenius(exponent), - self.0[1] - .frobenius(exponent) - .scale(FROBENIUS_COEFF_FP12_C1[exponent % 12]), - ]) - } - pub fn square(&self) -> Self { - // For F_{p^{12}} = F_{p^6}(w)/(w^2-\gamma), and A=a_0 + a_1*w \in F_{p^{12}}, - // we determine C=c_0+c_1*w = A^2\in F_{p^{12}} - // Alg 22 from - let c0 = self.0[0] - self.0[1]; - let c3 = self.0[0] - self.0[1].residue_mul(); - let c2 = self.0[0] * self.0[1]; - let c0 = c0 * c3 + c2; - let c1 = c2 + c2; - let c2 = c2.residue_mul(); - let c0 = c0 + c2; - Self::new(&[c0, c1]) - } -} #[cfg(test)] mod tests { use super::*; diff --git a/src/fields/fp2.rs b/src/fields/fp2.rs index fb23e2c..4dc10a3 100644 --- a/src/fields/fp2.rs +++ b/src/fields/fp2.rs @@ -3,58 +3,32 @@ //! that elements of this field are represented as a_0 + a_1*X. This implements //! the specific behaviour for this extension, such as multiplication. use crate::fields::extensions::FieldExtension; -use crate::fields::fp::{FieldExtensionTrait, Fp, BN254_FP_MODULUS}; -use crypto_bigint::{rand_core::CryptoRngCore, subtle::ConditionallySelectable, U256}; +use crate::fields::fp::{FieldExtensionTrait, Fp}; +use crate::fields::utils::u256_to_u512; +use crypto_bigint::{rand_core::CryptoRngCore, subtle::ConditionallySelectable, U512}; use num_traits::{Inv, One, Pow, Zero}; use std::ops::{Div, DivAssign, Mul, MulAssign}; use subtle::{Choice, ConstantTimeEq, CtOption}; const FP2_QUADRATIC_NON_RESIDUE: Fp2 = Fp2::new(&[Fp::NINE, Fp::ONE]); -pub const TWO_INV: Fp = Fp::new(U256::from_words([ - 11389680472494603940, - 14681934109093717318, - 15863968012492123182, - 1743499133401485332, -])); -// (BN254_FP_MODULUS - Fp::THREE)/Fp::FOUR -const P_MINUS_3_OVER_4: Fp = Fp::new(U256::from_words([ - 5694840236247301969, - 7340967054546858659, - 7931984006246061591, - 871749566700742666, -])); -// (BN254_FP_MODULUS - Fp::ONE)/Fp::TWO -const P_MINUS_1_OVER_2: Fp = Fp::new(U256::from_words([ - 11389680472494603939, - 14681934109093717318, - 15863968012492123182, - 1743499133401485332, -])); -const FP2_TWIST_CURVE_CONSTANT: Fp2 = Fp2::new(&[ - Fp::new(U256::from_words([ - 3632125457679333605, - 13093307605518643107, - 9348936922344483523, - 3104278944836790958, - ])), - Fp::new(U256::from_words([ - 16474938222586303954, - 12056031220135172178, - 14784384838321896948, - 42524369107353300, - ])), -]); -pub type Fp2 = FieldExtension<2, 2, Fp>; +pub(crate) type Fp2 = FieldExtension<2, 2, Fp>; // there are some specific things that must be defined as // helper functions for us on this specific extension, but // don't generalize to any extension. impl Fp2 { - pub fn pow(&self, by: &Fp) -> Self { - let bits = by.value().to_words(); + // variable runtime with respect to the input argument, + // aka the size of the argument to the exponentiation. + // the naming convention makes it explicit to us that + // this should be used only in scenarios where we know + // precisely what we're doing to not expose vectors + // for side channel attacks in our api. + // the below is not exposed publicly + #[allow(dead_code)] + pub(crate) fn pow_vartime(&self, by: &[u64]) -> Self { let mut res = Self::one(); - for e in bits.iter().rev() { + for e in by.iter().rev() { for i in (0..64).rev() { res = res * res; if ((*e >> i) & 1) == 1 { @@ -64,11 +38,22 @@ impl Fp2 { } res } + // type casting must be done on case-by-case basis + #[allow(dead_code)] + fn characteristic() -> U512 { + let wide_p = u256_to_u512(&Fp::characteristic()); + wide_p * wide_p + } - pub fn residue_mul(&self) -> Self { - self * &FP2_QUADRATIC_NON_RESIDUE + pub(crate) fn residue_mul(&self) -> Self { + *self * FP2_QUADRATIC_NON_RESIDUE } - pub fn frobenius(&self, exponent: usize) -> Self { +} +impl FieldExtensionTrait<2, 2> for Fp2 { + fn quadratic_non_residue() -> Self { + FP2_QUADRATIC_NON_RESIDUE + } + fn frobenius(&self, exponent: usize) -> Self { let frobenius_coeff_fp2: &[Fp; 2] = &[ // Fp::quadratic_non_residue()**(((p^0) - 1) / 2) Fp::ONE, @@ -83,11 +68,14 @@ impl Fp2 { ]), } } - pub fn sqrt(&self) -> CtOption { - let a1 = self.pow(&P_MINUS_3_OVER_4); + fn sqrt(&self) -> CtOption { + let p_minus_3_over_4 = ((Fp::new(Fp::characteristic()) - Fp::THREE) / Fp::FOUR).value(); + let p_minus_1_over_2 = ((Fp::new(Fp::characteristic()) - Fp::ONE) / Fp::TWO).value(); + let p = Fp::characteristic(); + let a1 = self.pow_vartime(&p_minus_3_over_4.to_words()); let alpha = a1 * a1 * (*self); - let a0 = alpha.pow(&BN254_FP_MODULUS); + let a0 = alpha.pow_vartime(&p.to_words()); if a0 == -Fp2::one() { return CtOption::new(Fp2::zero(), Choice::from(0u8)); } @@ -95,14 +83,20 @@ impl Fp2 { if alpha == -Fp2::one() { let i = Fp2::new(&[Fp::ZERO, Fp::ONE]); let sqrt = i * a1 * (*self); - CtOption::new(sqrt, sqrt.square().ct_eq(self)) + CtOption::new( + sqrt, + >::square(&sqrt).ct_eq(self), + ) } else { - let b = (alpha + Fp2::one()).pow(&P_MINUS_1_OVER_2); + let b = (alpha + Fp2::one()).pow_vartime(&p_minus_1_over_2.to_words()); let sqrt = b * a1 * (*self); - CtOption::new(sqrt, sqrt.square().ct_eq(self)) + CtOption::new( + sqrt, + >::square(&sqrt).ct_eq(self), + ) } } - pub fn square(&self) -> Self { + fn square(&self) -> Self { let t0 = self.0[0] * self.0[1]; Self([ (self.0[1] * >::quadratic_non_residue() + self.0[0]) @@ -112,9 +106,16 @@ impl Fp2 { t0 + t0, ]) } - pub fn is_square(&self) -> Choice { + fn rand(rng: &mut R) -> Self { + Self([ + >::rand(rng), + >::rand(rng), + ]) + } + fn is_square(&self) -> Choice { let legendre = |x: &Fp| -> i32 { - let res = x.pow(P_MINUS_1_OVER_2.value()); + let exp = ((Fp::new(Fp::characteristic()) - Fp::ONE) / Fp::from(2)).value(); + let res = x.pow(exp); if res.is_one() { 1 @@ -124,58 +125,42 @@ impl Fp2 { -1 } }; - let sum = self.0[0].square() - + >::quadratic_non_residue() * (-self.0[0]).square(); + let sum = >::square(&self.0[0]) + + >::quadratic_non_residue() + * >::square(&(-self.0[0])); Choice::from((legendre(&sum) != -1) as u8) } - pub fn sgn0(&self) -> Choice { - let sign_0 = self.0[0].sgn0(); + fn sgn0(&self) -> Choice { + let sign_0 = >::sgn0(&self.0[0]); let zero_0 = Choice::from(self.0[0].is_zero() as u8); - let sign_1 = self.0[1].sgn0(); + let sign_1 = >::sgn0(&self.0[1]); sign_0 | (zero_0 & sign_1) } -} -impl FieldExtensionTrait<2, 2> for Fp2 { - fn quadratic_non_residue() -> Self { - FP2_QUADRATIC_NON_RESIDUE - } - fn rand(rng: &mut R) -> Self { - Self([ - >::rand(rng), - >::rand(rng), - ]) - } fn curve_constant() -> Self { // this is the curve constant for the twist curve in Fp2. In short Weierstrass form the // curve over the twist is $y'^2 = x'^3 + b$, where $b=3/(9+u)$, which is the below. - FP2_TWIST_CURVE_CONSTANT + Self::from(3) / FP2_QUADRATIC_NON_RESIDUE } } -impl<'a, 'b> Mul<&'b Fp2> for &'a Fp2 { - type Output = Fp2; - fn mul(self, other: &'b Fp2) -> Self::Output { + +impl Mul for Fp2 { + type Output = Self; + fn mul(self, other: Self) -> Self::Output { // This requires a bit more consideration. In Fp2, // in order to multiply, we must implement complex Karatsuba // multiplication. // See https://eprint.iacr.org/2006/471.pdf, Sec 3 // We create the addition chain from Algo 1 of https://eprint.iacr.org/2022/367.pdf + // TODO: Implement optimized squaring algorithm in base field? let t0 = self.0[0] * other.0[0]; let t1 = self.0[1] * other.0[1]; - Self::Output::new(&[ + Self([ t1 * >::quadratic_non_residue() + t0, (self.0[0] + self.0[1]) * (other.0[0] + other.0[1]) - t0 - t1, ]) } } -impl Mul for Fp2 { - type Output = Self; - fn mul(self, other: Fp2) -> Self::Output { - // TODO linter complains about this being a needless reference if I do &a * &b, so this - // gets around it - (&self).mul(&other) - } -} impl MulAssign for Fp2 { fn mul_assign(&mut self, other: Self) { *self = *self * other; @@ -185,8 +170,8 @@ impl MulAssign for Fp2 { impl Inv for Fp2 { type Output = Self; fn inv(self) -> Self { - let c0_squared = self.0[0].square(); - let c1_squared = self.0[1].square(); + let c0_squared = >::square(&self.0[0]); + let c1_squared = >::square(&self.0[1]); let tmp = (c0_squared - (c1_squared * >::quadratic_non_residue())) .inv(); @@ -235,9 +220,24 @@ impl FieldExtensionTrait<6, 3> for Fp2 { fn quadratic_non_residue() -> Self { FP2_QUADRATIC_NON_RESIDUE } + fn frobenius(&self, exponent: usize) -> Self { + >::frobenius(self, exponent) + } + fn sqrt(&self) -> CtOption { + >::sqrt(self) + } + fn square(&self) -> Self { + >::square(self) + } fn rand(rng: &mut R) -> Self { >::rand(rng) } + fn is_square(&self) -> Choice { + >::is_square(self) + } + fn sgn0(&self) -> Choice { + >::sgn0(self) + } fn curve_constant() -> Self { >::curve_constant() } @@ -379,7 +379,7 @@ mod tests { [0xffffffffffffffff, 0xffffffffffffffff, 0x0, 0x0], ); for i in [a, b, c, d, e, f] { - let tmp = i.sqrt(); + let tmp = >::sqrt(&i); match tmp.into_option() { Some(d) => { assert_eq!(d * d, i, "Sqrt failed"); @@ -407,7 +407,11 @@ mod tests { ], ); for i in [a, b, c] { - assert_eq!(i.square(), i * i, "Squaring failed"); + assert_eq!( + >::square(&i), + i * i, + "Squaring failed" + ); } } #[test] @@ -415,7 +419,7 @@ mod tests { let q = FP2_QUADRATIC_NON_RESIDUE; let a1 = (Fp::new(Fp::characteristic()) - Fp::from(1)) / Fp::from(3); - let c1_1 = q.pow(&a1); + let c1_1 = q.pow_vartime(&a1.value().to_words()); let c1_1_real = create_field_extension( [ 0x99e39557176f553d, @@ -498,8 +502,11 @@ mod tests { for _ in 0..100 { let a = >::rand(&mut OsRng); - let b = a.square(); - assert!(bool::from(b.is_square()), "Is square failed"); + let b = >::square(&a); + assert!( + bool::from(>::is_square(&b)), + "Is square failed" + ); } } } diff --git a/src/fields/fp6.rs b/src/fields/fp6.rs index 2b6f663..7dd7f3c 100644 --- a/src/fields/fp6.rs +++ b/src/fields/fp6.rs @@ -5,7 +5,8 @@ use crate::fields::extensions::FieldExtension; use crate::fields::fp::{FieldExtensionTrait, Fp}; use crate::fields::fp2::Fp2; -use crypto_bigint::{rand_core::CryptoRngCore, subtle::ConditionallySelectable, U256}; +use crate::fields::utils::u256_to_u2048; +use crypto_bigint::{rand_core::CryptoRngCore, subtle::ConditionallySelectable, U2048, U256}; use num_traits::{Inv, One, Zero}; use std::ops::{Div, DivAssign, Mul, MulAssign}; use subtle::{Choice, CtOption}; @@ -165,27 +166,36 @@ const FROBENIUS_COEFF_FP6_C2: &[Fp2; 6] = &[ ])), ]), ]; -const FP6_QUADRATIC_NON_RESIDUE: Fp6 = Fp6::new(&[ - Fp2::new(&[Fp::ZERO, Fp::ZERO]), - Fp2::new(&[Fp::ONE, Fp::ZERO]), - Fp2::new(&[Fp::ZERO, Fp::ZERO]), -]); - -pub type Fp6 = FieldExtension<6, 3, Fp2>; +pub(crate) type Fp6 = FieldExtension<6, 3, Fp2>; impl Fp6 { - pub fn residue_mul(&self) -> Self { + #[allow(dead_code)] + pub(crate) fn residue_mul(&self) -> Self { Self([self.0[2].residue_mul(), self.0[0], self.0[1]]) } - pub fn frobenius(&self, exponent: usize) -> Self { + // mainly for debug formatting + #[allow(dead_code)] + fn characteristic() -> U2048 { + let wide_p = u256_to_u2048(&Fp::characteristic()); + let wide_p2 = wide_p * wide_p; + wide_p2 * wide_p2 * wide_p2 + } +} +impl FieldExtensionTrait<6, 3> for Fp6 { + fn quadratic_non_residue() -> Self { + Self::new(&[Fp2::zero(), Fp2::one(), Fp2::zero()]) + } + fn frobenius(&self, exponent: usize) -> Self { Self::new(&[ - self.0[0].frobenius(exponent), - self.0[1].frobenius(exponent) * FROBENIUS_COEFF_FP6_C1[exponent % 6], - self.0[2].frobenius(exponent) * FROBENIUS_COEFF_FP6_C2[exponent % 6], + >::frobenius(&self.0[0], exponent), + >::frobenius(&self.0[1], exponent) + * FROBENIUS_COEFF_FP6_C1[exponent % 6], + >::frobenius(&self.0[2], exponent) + * FROBENIUS_COEFF_FP6_C2[exponent % 6], ]) } - pub fn sqrt(&self) -> CtOption { + fn sqrt(&self) -> CtOption { unimplemented!() } @@ -193,16 +203,16 @@ impl Fp6 { // however, there are some simple algebraic reductions // you can do with squaring. this just implements that, // but functionally it is the same as the `Mul` trait below - pub fn square(&self) -> Self { - let t0 = self.0[0].square(); + fn square(&self) -> Self { + let t0 = >::square(&self.0[0]); let cross = self.0[0] * self.0[1]; let t1 = cross + cross; let mut t2 = self.0[0] - self.0[1] + self.0[2]; - t2 = t2.square(); + t2 = >::square(&t2); let bc = self.0[1] * self.0[2]; let s3 = bc + bc; let mut s4 = self.0[2]; - s4 = s4.square(); + s4 = >::square(&s4); Self([ t0 + s3.residue_mul(), @@ -210,12 +220,6 @@ impl Fp6 { t1 + t2 + s3 - t0 - s4, ]) } -} -impl FieldExtensionTrait<6, 3> for Fp6 { - fn quadratic_non_residue() -> Self { - FP6_QUADRATIC_NON_RESIDUE - } - fn rand(rng: &mut R) -> Self { Self([ >::rand(rng), @@ -223,32 +227,32 @@ impl FieldExtensionTrait<6, 3> for Fp6 { >::rand(rng), ]) } + fn is_square(&self) -> Choice { + unimplemented!() + } + fn sgn0(&self) -> Choice { + unimplemented!() + } fn curve_constant() -> Self { - Self::from(3) + unimplemented!() } } -impl<'a, 'b> Mul<&'b Fp6> for &'a Fp6 { - type Output = Fp6; - fn mul(self, other: &'b Fp6) -> Self::Output { +impl Mul for Fp6 { + type Output = Self; + fn mul(self, other: Self) -> Self::Output { // This is the exact same strategy as multiplication in Fp2 // see the doc string there for more details let t0 = self.0[0] * other.0[0]; let t1 = self.0[1] * other.0[1]; let t2 = self.0[2] * other.0[2]; - Self::Output::new(&[ + Self([ ((self.0[1] + self.0[2]) * (other.0[1] + other.0[2]) - t1 - t2).residue_mul() + t0, (self.0[0] + self.0[1]) * (other.0[0] + other.0[1]) - t0 - t1 + t2.residue_mul(), (self.0[0] + self.0[2]) * (other.0[0] + other.0[2]) - t0 + t1 - t2, ]) } } -impl Mul for Fp6 { - type Output = Self; - fn mul(self, other: Self) -> Self::Output { - (&self).mul(&other) - } -} impl MulAssign for Fp6 { fn mul_assign(&mut self, other: Self) { *self = *self * other; @@ -258,9 +262,11 @@ impl MulAssign for Fp6 { impl Inv for Fp6 { type Output = Self; fn inv(self) -> Self::Output { - let t0 = self.0[0].square() - self.0[1] * self.0[2].residue_mul(); - let t1 = self.0[2].square().residue_mul() - self.0[0] * self.0[1]; - let t2 = self.0[1].square() - self.0[0] * self.0[2]; + let t0 = >::square(&self.0[0]) + - self.0[1] * self.0[2].residue_mul(); + let t1 = >::square(&self.0[2]).residue_mul() + - self.0[0] * self.0[1]; + let t2 = >::square(&self.0[1]) - self.0[0] * self.0[2]; let inverse = ((self.0[2] * t1 + self.0[1] * t2).residue_mul() + self.0[0] * t0).inv(); Self([inverse * t0, inverse * t1, inverse * t2]) @@ -305,9 +311,24 @@ impl FieldExtensionTrait<12, 2> for Fp6 { fn quadratic_non_residue() -> Self { >::quadratic_non_residue() } + fn frobenius(&self, exponent: usize) -> Self { + >::frobenius(self, exponent) + } + fn sqrt(&self) -> CtOption { + >::sqrt(self) + } + fn square(&self) -> Self { + >::square(self) + } fn rand(rng: &mut R) -> Self { >::rand(rng) } + fn is_square(&self) -> Choice { + >::is_square(self) + } + fn sgn0(&self) -> Choice { + >::sgn0(self) + } fn curve_constant() -> Self { >::curve_constant() } @@ -335,6 +356,26 @@ mod tests { Fp2::new(&[create_field(v5), create_field(v6)]), ]) } + + mod residue_tests { + use super::*; + #[test] + fn test_residue() { + let q1 = >::quadratic_non_residue(); + let q2 = >::quadratic_non_residue(); + let q3 = >::quadratic_non_residue(); + println!("{:?}\n", q1.value()); + for i in q2.0 { + println!("{:?}", i.value()); + } + println!("\n"); + for j in q3.0 { + for k in j.0 { + println!("{:?} ", k.value()); + } + } + } + } mod addition_tests { use super::*; @@ -556,22 +597,41 @@ mod tests { assert_eq!( a, - a.frobenius(2).frobenius(2).frobenius(2), + >::frobenius( + &>::frobenius( + &>::frobenius(&a, 2), + 2 + ), + 2 + ), "Frobenius failed at cycle order 3" ); assert_eq!( a, - a.frobenius(3).frobenius(3), + >::frobenius( + &>::frobenius(&a, 3), + 3 + ), "Frobenius failed at cycle order 3" ); assert_eq!( a, - a.frobenius(1) - .frobenius(1) - .frobenius(1) - .frobenius(1) - .frobenius(1) - .frobenius(1), + >::frobenius( + &>::frobenius( + &>::frobenius( + &>::frobenius( + &>::frobenius( + &>::frobenius(&a, 1), + 1 + ), + 1 + ), + 1 + ), + 1 + ), + 1 + ), "Frobenius failed at cycle order 6" ); } diff --git a/src/fields/mod.rs b/src/fields/mod.rs index 191494e..d98bb5e 100644 --- a/src/fields/mod.rs +++ b/src/fields/mod.rs @@ -1,6 +1,6 @@ mod extensions; -pub mod fp; -pub mod fp12; -pub mod fp2; -pub mod fp6; -pub mod utils; +pub(crate) mod fp; +mod fp12; +pub(crate) mod fp2; +mod fp6; +pub(crate) mod utils; diff --git a/src/fields/utils.rs b/src/fields/utils.rs index b4443d5..d5fca9c 100644 --- a/src/fields/utils.rs +++ b/src/fields/utils.rs @@ -1,6 +1,6 @@ -use crypto_bigint::{Encoding, U256, U512}; +use crypto_bigint::{Encoding, U1024, U2048, U256, U4096, U512}; -pub fn to_larger_uint(smaller_bytes: &[u8; N]) -> [u8; M] { +pub(crate) fn to_larger_uint(smaller_bytes: &[u8; N]) -> [u8; M] { assert!(M > N, "Target size must be larger than source size"); let mut larger_bytes = [0u8; M]; larger_bytes[M - N..].copy_from_slice(smaller_bytes); @@ -8,6 +8,18 @@ pub fn to_larger_uint(smaller_bytes: &[u8; N]) - } // Specific conversion functions -pub fn u256_to_u512(u256: &U256) -> U512 { +pub(crate) fn u256_to_u512(u256: &U256) -> U512 { U512::from_be_bytes(to_larger_uint::<32, 64>(&u256.to_be_bytes())) } +#[allow(dead_code)] +pub(crate) fn u256_to_u1024(u256: &U256) -> U1024 { + U1024::from_be_bytes(to_larger_uint::<32, 128>(&u256.to_be_bytes())) +} +#[allow(dead_code)] +pub(crate) fn u256_to_u2048(u256: &U256) -> U2048 { + U2048::from_be_bytes(to_larger_uint::<32, 256>(&u256.to_be_bytes())) +} +#[allow(dead_code)] +pub(crate) fn u256_to_u4096(u256: &U256) -> U4096 { + U4096::from_be_bytes(to_larger_uint::<32, 512>(&u256.to_be_bytes())) +} diff --git a/src/groups/g1.rs b/src/groups/g1.rs index d5377de..4aff9ef 100644 --- a/src/groups/g1.rs +++ b/src/groups/g1.rs @@ -18,9 +18,10 @@ use crypto_bigint::rand_core::CryptoRngCore; use num_traits::Zero; use subtle::{Choice, ConstantTimeEq}; -pub type G1Affine = GroupAffine<1, 1, Fp>; - -pub type G1Projective = GroupProjective<1, 1, Fp>; +#[allow(dead_code)] +pub(crate) type G1Affine = GroupAffine<1, 1, Fp>; +#[allow(dead_code)] +pub(crate) type G1Projective = GroupProjective<1, 1, Fp>; impl GroupTrait<1, 1, Fp> for G1Affine { fn generator() -> Self { @@ -51,32 +52,18 @@ impl GroupTrait<1, 1, Fp> for G1Affine { Err(e) => Err(e), } } - /// NOTA BENE: the frobenius map does NOT in general map points from the curve back to the curve - /// It is an endomorphism of the algebraic closure of the base field, but NOT of the curve - /// Therefore, these points must bypass curve membership and torsion checks, and therefore - /// directly be instantiated as a struct - fn frobenius(&self, exponent: usize) -> Self { - let vec: Vec = [self.x, self.y] - .iter() - .map(|x| x.frobenius(exponent)) - .collect(); - Self { - x: vec[0], - y: vec[1], - infinity: self.infinity, - } - } } - +#[allow(dead_code)] impl G1Affine { /// this needs to be defined in order to have user interaction, but currently /// is only visible in tests, and therefore is seen by the linter as unused - pub fn new(v: [Fp; 2]) -> Result { + pub(crate) fn new(v: [Fp; 2]) -> Result { let _g1affine_is_on_curve = |x: &Fp, y: &Fp, z: &Choice| -> Choice { - let y2 = y.square(); - let x2 = x.square(); + let y2 = >::square(y); + let x2 = >::square(x); let lhs = y2 - (x2 * (*x)); let rhs = >::curve_constant(); + // println!("{:?}, {:?}", lhs, rhs); lhs.ct_eq(&rhs) | *z }; @@ -87,6 +74,7 @@ impl G1Affine { let is_on_curve: Choice = _g1affine_is_on_curve(&v[0], &v[1], &Choice::from(0u8)); match bool::from(is_on_curve) { true => { + // println!("Is on curve!"); let is_in_torsion: Choice = _g1affine_is_torsion_free(&v[0], &v[1], &Choice::from(0u8)); match bool::from(is_in_torsion) { @@ -110,7 +98,10 @@ impl GroupTrait<1, 1, Fp> for G1Projective { Self::generator() } fn rand(rng: &mut R) -> Self { - Self::generator() * >::rand(rng) + &Self::generator() + * &>::rand(rng) + .value() + .to_le_bytes() } fn hash_to_curve(exp: &E, msg: &[u8]) -> Result { const COUNT: usize = 2; @@ -118,7 +109,7 @@ impl GroupTrait<1, 1, Fp> for G1Projective { let scalars = exp .hash_to_field(msg, COUNT, L) .expect("Hashing to base field failed"); - match SvdW::precompute_constants(Fp::from(0), Fp::from(3)) { + match SvdW::<1, 1, Fp>::precompute_constants(Fp::from(0), Fp::from(3)) { Ok(bn254_g1_svdw) => { let a = G1Projective::from( bn254_g1_svdw @@ -130,37 +121,28 @@ impl GroupTrait<1, 1, Fp> for G1Projective { .unchecked_map_to_point(scalars[1]) .expect("Failed to hash"), ); - Ok(a + b) + Ok(&a + &b) } _ => Err(GroupError::CannotHashToGroup), } } fn sign_message(exp: &E, msg: &[u8], private_key: Fp) -> Result { if let Ok(d) = Self::hash_to_curve(exp, msg) { - return Ok(d * private_key); + return Ok(&d * &private_key.value().to_le_bytes()); } Err(GroupError::CannotHashToGroup) } - fn frobenius(&self, exponent: usize) -> Self { - let vec: Vec = [self.x, self.y, self.z] - .iter() - .map(|x| x.frobenius(exponent)) - .collect(); - Self { - x: vec[0], - y: vec[1], - z: vec[2], - } - } } impl G1Projective { - pub fn new(v: [Fp; 3]) -> Result { + #[allow(dead_code)] + pub(crate) fn new(v: [Fp; 3]) -> Result { let _g1projective_is_on_curve = |x: &Fp, y: &Fp, z: &Fp| -> Choice { - let y2 = y.square(); - let x2 = x.square(); - let z2 = z.square(); + let y2 = >::square(y); + let x2 = >::square(x); + let z2 = >::square(z); let lhs = y2 * (*z); let rhs = x2 * (*x) + z2 * (*z) * >::curve_constant(); + // println!("{:?}, {:?}", lhs.value(), rhs.value()); lhs.ct_eq(&rhs) | Choice::from(z.is_zero() as u8) }; let _g1projective_is_torsion_free = @@ -168,6 +150,7 @@ impl G1Projective { let is_on_curve: Choice = _g1projective_is_on_curve(&v[0], &v[1], &v[2]); match bool::from(is_on_curve) { true => { + // println!("Is on curve!"); let is_in_torsion: Choice = _g1projective_is_torsion_free(&v[0], &v[1], &v[2]); match bool::from(is_in_torsion) { true => Ok(Self { diff --git a/src/groups/g2.rs b/src/groups/g2.rs index 373d1d1..538055c 100644 --- a/src/groups/g2.rs +++ b/src/groups/g2.rs @@ -14,7 +14,7 @@ //! All public facing methods here implement the subgroup check to ensure that the user cannot //! input a value in $E^\prime(F_{p^2})$ that is not in the r-torsion. -use crate::fields::fp::{FieldExtensionTrait, Fp, Fr}; +use crate::fields::fp::{FieldExtensionTrait, Fp}; use crate::fields::fp2::Fp2; use crate::groups::group::{GroupAffine, GroupError, GroupProjective, GroupTrait}; use crate::hasher::Expander; @@ -23,8 +23,6 @@ use crypto_bigint::U256; use num_traits::{One, Zero}; use subtle::{Choice, ConstantTimeEq}; -/// This is the X coordinate of the generator for the r-torsion of the twist curve, generated -/// directly from sage const G2_X: Fp2 = Fp2::new(&[ Fp::new(U256::from_words([ 5106727233969649389, @@ -40,23 +38,22 @@ const G2_X: Fp2 = Fp2::new(&[ ])), ]); -/// Likewise, this is the y coordinate of the generator for the r-torsion on the twist curve const G2_Y: Fp2 = Fp2::new(&[ Fp::new(U256::from_words([ - 5541340697920699818, - 16416156555105522555, - 5380518976772849807, - 1353435754470862315, + 17238020247068508061, + 12947711663081912081, + 7900672974501844941, + 2133562512332108350, ])), Fp::new(U256::from_words([ - 6173549831154472795, - 13567992399387660019, - 17050234209342075797, - 650358724130500725, + 16605811113834735084, + 15795875818799774617, + 14677701815642170567, + 2836639542672469939, ])), ]); // the first constant of the endomorphism, $\xi^((p-1)/3)$, see below -pub const EPS_EXP0: Fp2 = Fp2::new(&[ +const EPS_EXP0: Fp2 = Fp2::new(&[ Fp::new(U256::from_words([ 11088870908804158781, 13226160682434769676, @@ -71,7 +68,7 @@ pub const EPS_EXP0: Fp2 = Fp2::new(&[ ])), ]); // the second constant of the endomorphism, $\xi^((p-1)/2)$, see below -pub const EPS_EXP1: Fp2 = Fp2::new(&[ +const EPS_EXP1: Fp2 = Fp2::new(&[ Fp::new(U256::from_words([ 15876315988453495642, 15828711151707445656, @@ -93,12 +90,11 @@ const C2: Fp = Fp::new(U256::from_words([ 0, 0, ])); -// the parameter that generates this member of the BN family -pub const BLS_X: Fp = Fp::new(U256::from_words([4965661367192848881, 0, 0, 0])); -pub type G2Affine = GroupAffine<2, 2, Fp2>; - -pub type G2Projective = GroupProjective<2, 2, Fp2>; +#[allow(dead_code)] +pub(crate) type G2Affine = GroupAffine<2, 2, Fp2>; +#[allow(dead_code)] +pub(crate) type G2Projective = GroupProjective<2, 2, Fp2>; impl GroupTrait<2, 2, Fp2> for G2Affine { // This is the generator of $E^\prime(F_{p^2})$, and NOT of the r-torsion. This is because we @@ -139,8 +135,8 @@ impl GroupTrait<2, 2, Fp2> for G2Affine { if self.is_zero() { return *self; } - let x_frob = self.x.frobenius(1); - let y_frob = self.y.frobenius(1); + let x_frob = >::frobenius(&self.x, 1); + let y_frob = >::frobenius(&self.y, 1); let x_endo = EPS_EXP0 * x_frob; let y_endo = EPS_EXP1 * y_frob; @@ -163,17 +159,6 @@ impl GroupTrait<2, 2, Fp2> for G2Affine { ) -> Result { unimplemented!() } - fn frobenius(&self, exponent: usize) -> Self { - let vec: Vec = [self.x, self.y] - .iter() - .map(|x| x.frobenius(exponent)) - .collect(); - Self { - x: vec[0], - y: vec[1], - infinity: self.infinity, - } - } } impl GroupTrait<2, 2, Fp2> for G2Projective { fn generator() -> Self { @@ -193,13 +178,15 @@ impl GroupTrait<2, 2, Fp2> for G2Projective { /// through the `new` constructor to ensure that the random value does indeed pass the curve /// and subgroup checks fn rand(rng: &mut R) -> Self { - let rando = Fp::new(Fr::rand(rng).value()); - let mut tmp = Self::generator() * rando; + let rando = >::rand(rng) + .value() + .to_le_bytes(); + let mut tmp = &Self::generator() * &rando; // multiplying an element of the larger base field by the cofactor of a prime-ordered // subgroup will return an element in the prime-order subgroup, see // for a nice little explainer - tmp = tmp * C2; //this is cofactor clearing + tmp = &tmp * &C2.value().to_le_bytes(); //this is cofactor clearing Self::new([tmp.x, tmp.y, tmp.z]).expect("Generator failed to make new value in torsion") } fn hash_to_curve(_exp: &E, _msg: &[u8]) -> Result { @@ -212,21 +199,6 @@ impl GroupTrait<2, 2, Fp2> for G2Projective { ) -> Result { unimplemented!() } - /// NOTA BENE: the frobenius map does NOT in general map points from the curve back to the curve - /// It is an endomorphism of the algebraic closure of the base field, but NOT of the curve - /// Therefore, these points must bypass curve membership and torsion checks, and therefore - /// directly be instantiated as a struct - fn frobenius(&self, exponent: usize) -> Self { - let vec: Vec = [self.x, self.y, self.z] - .iter() - .map(|x| x.frobenius(exponent)) - .collect(); - Self { - x: vec[0], - y: vec[1], - z: vec[2], - } - } } impl G2Affine { /// This method is used internally for rapid, low overhead, conversion of types when there @@ -237,8 +209,8 @@ impl G2Affine { /// DON'T USE THIS METHOD UNLESS YOU KNOW WHAT YOU'RE DOING fn new_unchecked(v: [Fp2; 2]) -> Result { let _g2affine_is_on_curve = |x: &Fp2, y: &Fp2, z: &Choice| -> Choice { - let y2 = y.square(); - let x2 = x.square(); + let y2 = >::square(y); + let x2 = >::square(x); let lhs = y2 - (x2 * (*x)); let rhs = >::curve_constant(); lhs.ct_eq(&rhs) | *z @@ -259,13 +231,14 @@ impl G2Projective { /// The public entrypoint to making a value in $\mathbb{G}_2$. This takes the (x,y,z) values /// from the user, and passes them through a subgroup and curve check to ensure validity. /// Values returned from this function are guaranteed to be on the curve and in the r-torsion. - pub fn new(v: [Fp2; 3]) -> Result { + pub(crate) fn new(v: [Fp2; 3]) -> Result { let _g2projective_is_on_curve = |x: &Fp2, y: &Fp2, z: &Fp2| -> Choice { - let y2 = y.square(); - let x2 = x.square(); - let z2 = z.square(); + let y2 = >::square(y); + let x2 = >::square(x); + let z2 = >::square(z); let lhs = y2 * (*z); let rhs = x2 * (*x) + z2 * (*z) * >::curve_constant(); + // println!("{:?}, {:?}", lhs.value(), rhs.value()); lhs.ct_eq(&rhs) | Choice::from(z.is_zero() as u8) }; // This method is where the magic happens. In a naïve approach, in order to check for @@ -297,12 +270,13 @@ impl G2Projective { y: *y, z: *z, }; - let mut a = tmp * BLS_X; // xQ + let x = Fp::from(4965661367192848881); + let mut a = &tmp * &x.value().to_le_bytes(); // xQ let b = a.endomorphism(); // ψ(xQ) - a = a + tmp; // (x+1)Q + a = &a + &tmp; // (x+1)Q let mut rhs = b.endomorphism(); // ψ^2(xQ) - let lhs = rhs + b + a; // ψ^2(xQ) + ψ(xQ) + (x+1)Q - rhs = rhs.endomorphism().double() - lhs; // ψ^3(2xQ) - (ψ^2(xQ) + ψ(xQ) + (x+1)Q) + let lhs = &rhs + &(&b + &a); // ψ^2(xQ) + ψ(xQ) + (x+1)Q + rhs = &rhs.endomorphism().double() - &lhs; // ψ^3(2xQ) - (ψ^2(xQ) + ψ(xQ) + (x+1)Q) // we do two checks: one is to verify that the result is indeed a point at infinity, // but we need a second check to verify that it is OUR point at infinity, namely for diff --git a/src/groups/group.rs b/src/groups/group.rs index 9fd77b7..0847ea7 100644 --- a/src/groups/group.rs +++ b/src/groups/group.rs @@ -20,14 +20,14 @@ //! 2. . //! 3. -use crate::fields::fp::{FieldExtensionTrait, Fp}; +use crate::fields::fp::FieldExtensionTrait; use crate::hasher::Expander; use crypto_bigint::rand_core::CryptoRngCore; use crypto_bigint::subtle::{Choice, ConditionallySelectable, ConstantTimeEq}; use std::ops::{Add, Mul, Neg, Sub}; -#[derive(Debug, Copy, Clone)] -pub enum GroupError { +#[derive(Debug)] +pub(crate) enum GroupError { /// This is a simple error struct that specifies the three errors /// that are expected for the generation of a point on the curve. /// Either, the coordinates given are not even on the curve, @@ -42,8 +42,8 @@ pub enum GroupError { /// without addition, which is very specific to the choice of affine, // projective, or mixed addition, and therefore cannot be defined for all instances satisfying // a group trait - -pub trait GroupTrait>: +#[allow(dead_code)] +pub(crate) trait GroupTrait>: Sized + Copy + Clone + std::fmt::Debug + Neg + ConstantTimeEq + ConditionallySelectable + PartialEq { /// this is how we'll make more elements of the field from a scalar value @@ -54,23 +54,18 @@ pub trait GroupTrait(rng: &mut R) -> Self; fn hash_to_curve(exp: &E, msg: &[u8]) -> Result; fn sign_message(exp: &E, msg: &[u8], private_key: F) -> Result; - /// NOTA BENE: the frobenius map does NOT in general map points from the curve back to the curve - /// It is an endomorphism of the algebraic closure of the base field, but NOT of the curve - /// Therefore, these points must bypass curve membership and torsion checks, and therefore - /// directly be instantiated as a struct - fn frobenius(&self, exponent: usize) -> Self; } #[derive(Copy, Clone, Debug)] -pub struct GroupAffine> { +pub(crate) struct GroupAffine> { /// this is the implementation of a point on the curve in affine coordinates. It is not possible /// to directly input a pair of x, y such that infinity is True, since the point at infinity /// has no unique representation in this form. Generation of the point at infinity is accomplished /// either by calling the `zero` method, or by applying binary operations to 'normal' points to /// reach the point at infinity with arithmetic. - pub x: F, - pub y: F, - pub infinity: Choice, + pub(crate) x: F, + pub(crate) y: F, + pub(crate) infinity: Choice, } /// this is the beginning of Rust lifetime magic. The issue is that when we implement /// the arithmetic, we need to explicitly state the lifetime of each operand @@ -141,20 +136,20 @@ impl> PartialEq } } impl> GroupAffine { - pub fn zero() -> Self { + pub(crate) fn zero() -> Self { Self { x: F::zero(), y: F::one(), infinity: Choice::from(1u8), } } - - pub fn is_zero(&self) -> bool { + #[allow(dead_code)] + pub(crate) fn is_zero(&self) -> bool { bool::from(self.infinity) } } #[derive(Copy, Clone, Debug)] -pub struct GroupProjective> { +pub(crate) struct GroupProjective> { /// We now define the projective representation of a point on the curve. Here, the point(s) at /// infinity is (are) indicated by `z=0`. This allows the user to therefore directly generate /// such a point with the associated `new` method. Affine coordinates are those indicated by `z=1`. @@ -165,24 +160,24 @@ pub struct GroupProjective> GroupProjective { /// This is the point at infinity! This object really is the additive identity of the group, /// when the group law is addition, which it is here. It satisfies the properties that /// $zero+a=a$ for some $a$ in the group, as well as $a+(-a)=zero$, which means that the /// convention zero makes the most sense to me here. - pub fn zero() -> Self { + pub(crate) fn zero() -> Self { Self { x: F::zero(), y: F::one(), z: F::zero(), } } - - pub fn is_zero(&self) -> bool { + #[allow(dead_code)] + pub(crate) fn is_zero(&self) -> bool { self.z.is_zero() } @@ -192,7 +187,7 @@ impl> GroupProjecti /// Complexity: /// `6M`(ultiplications) + `2S`(quarings) /// + `1m`(ultiplication by scalar) + `9A`(dditions) - pub fn double(&self) -> Self { + pub(crate) fn double(&self) -> Self { let t0 = self.y * self.y; let z3 = t0 + t0; let z3 = z3 + z3; @@ -402,15 +397,6 @@ impl<'a, 'b, const D: usize, const N: usize, F: FieldExtensionTrait> } } } -impl> Add> - for GroupProjective -{ - type Output = Self; - fn add(self, rhs: GroupProjective) -> Self::Output { - &self + &rhs - } -} - #[allow(clippy::suspicious_arithmetic_impl)] impl<'a, 'b, const D: usize, const N: usize, F: FieldExtensionTrait> Sub<&'b GroupProjective> for &'a GroupProjective @@ -420,17 +406,9 @@ impl<'a, 'b, const D: usize, const N: usize, F: FieldExtensionTrait> self + &(-other) } } -impl> Sub> - for GroupProjective -{ - type Output = Self; - fn sub(self, rhs: GroupProjective) -> Self::Output { - &self - &rhs - } -} #[allow(clippy::suspicious_arithmetic_impl)] -impl<'a, 'b, const D: usize, const N: usize, F: FieldExtensionTrait> Mul<&'b Fp> +impl<'a, 'b, const D: usize, const N: usize, F: FieldExtensionTrait> Mul<&'b [u8; 32]> for &'a GroupProjective { /// This is simply the `double-and-add` algorithm for multiplication, which is the ECC @@ -438,10 +416,9 @@ impl<'a, 'b, const D: usize, const N: usize, F: FieldExtensionTrait> Mul<& /// /// type Output = GroupProjective; - fn mul(self, other: &'b Fp) -> Self::Output { - let bits = other.value().to_le_bytes(); + fn mul(self, other: &'b [u8; 32]) -> Self::Output { let mut res = Self::Output::zero(); - for bit in bits.iter().rev() { + for bit in other.iter().rev() { for i in (0..8).rev() { res = res.double(); if (bit & (1 << i)) != 0 { @@ -452,12 +429,3 @@ impl<'a, 'b, const D: usize, const N: usize, F: FieldExtensionTrait> Mul<& res } } - -impl> Mul - for GroupProjective -{ - type Output = Self; - fn mul(self, rhs: Fp) -> Self::Output { - &self * &rhs - } -} diff --git a/src/groups/gt.rs b/src/groups/gt.rs deleted file mode 100644 index cb14b8c..0000000 --- a/src/groups/gt.rs +++ /dev/null @@ -1,237 +0,0 @@ -use crate::fields::fp::{FieldExtensionTrait, Fp, Fr}; -use crate::fields::fp12::Fp12; -use crate::fields::fp2::Fp2; -use crate::fields::fp6::Fp6; -use crate::groups::group::{GroupError, GroupTrait}; -use crate::hasher::Expander; -use crate::pairing::MillerLoopResult; -use crypto_bigint::rand_core::CryptoRngCore; -use crypto_bigint::U256; -use num_traits::{One, Zero}; -use std::ops::{Add, Mul, Neg, Sub}; -use subtle::{Choice, ConditionallySelectable, ConstantTimeEq}; - -/// Do you have vertigo? Then you may want to close your eyes when you scroll by this massive -/// wall of text ... -/// this magic number is `pairing(&G1Affine::generator(), &G2Affine::generator())` -const GT: Fp12 = Fp12::new(&[ - Fp6::new(&[ - Fp2::new(&[ - Fp::new(U256::from_words([ - 6782248912058519189, - 17905854633700849845, - 981815359735217878, - 2750332953940282622, - ])), - Fp::new(U256::from_words([ - 13014616448268208714, - 4142271424844328294, - 728210408904174525, - 207215253209326080, - ])), - ]), - Fp2::new(&[ - Fp::new(U256::from_words([ - 5625932731339578848, - 6904745502146605564, - 11939514597710067603, - 1416930562523468429, - ])), - Fp::new(U256::from_words([ - 12767899052203382315, - 14173989925134591536, - 5418279272259683929, - 291513493445614172, - ])), - ]), - Fp2::new(&[ - Fp::new(U256::from_words([ - 17718267794268532699, - 5156438002697560843, - 13706034212316115026, - 791559585771054991, - ])), - Fp::new(U256::from_words([ - 18000284984309305840, - 15972481252625908291, - 13674726003407472074, - 2041438157648203876, - ])), - ]), - ]), - Fp6::new(&[ - Fp2::new(&[ - Fp::new(U256::from_words([ - 6633055433011767806, - 1993283657624419055, - 2556155685443179097, - 674431358778088128, - ])), - Fp::new(U256::from_words([ - 1117479660932770634, - 16838289109298230438, - 11753762874743346121, - 1500779265843046736, - ])), - ]), - Fp2::new(&[ - Fp::new(U256::from_words([ - 16118194329268941865, - 6475079101949171807, - 9933850523273906263, - 2143968216258907750, - ])), - Fp::new(U256::from_words([ - 8841354688241740695, - 6537271047255149595, - 11000136646916559527, - 816050994711660747, - ])), - ]), - Fp2::new(&[ - Fp::new(U256::from_words([ - 11229723312742192931, - 1787374600849103887, - 7823112569575231955, - 1416575403721338444, - ])), - Fp::new(U256::from_words([ - 4490955817540267159, - 6696537855995677752, - 13115031265298021014, - 70222861876806950, - ])), - ]), - ]), -]); - -#[derive(Copy, Clone, Debug)] -pub struct Gt(pub Fp12); - -impl<'a> Neg for &'a Gt { - type Output = Gt; - - #[inline] - fn neg(self) -> Gt { - // The element is unitary, so we just conjugate. - Gt(self.0.unitary_inverse()) - } -} -impl Neg for Gt { - type Output = Gt; - - #[inline] - fn neg(self) -> Gt { - -&self - } -} - -impl ConstantTimeEq for Gt { - fn ct_eq(&self, other: &Self) -> Choice { - self.0.ct_eq(&other.0) - } -} - -impl ConditionallySelectable for Gt { - fn conditional_select(a: &Self, b: &Self, choice: Choice) -> Self { - Gt(Fp12::conditional_select(&a.0, &b.0, choice)) - } -} - -impl PartialEq for Gt { - fn eq(&self, other: &Self) -> bool { - bool::from(self.ct_eq(other)) - } -} -impl Eq for Gt {} -#[allow(clippy::suspicious_arithmetic_impl)] -impl<'a, 'b> Add<&'b Gt> for &'a Gt { - type Output = Gt; - - #[inline] - fn add(self, rhs: &'b Gt) -> Gt { - Gt(self.0 * rhs.0) - } -} - -impl<'a, 'b> Sub<&'b Gt> for &'a Gt { - type Output = Gt; - - #[inline] - fn sub(self, rhs: &'b Gt) -> Gt { - self + &(-rhs) - } -} -#[allow(clippy::suspicious_arithmetic_impl)] -impl<'a, 'b> Mul<&'b Fr> for &'a Gt { - /// This is simply the `double-and-add` algorithm for multiplication, which is the ECC - /// equivalent of the `square-and-multiply` algorithm used in modular exponentiation. - /// - /// - type Output = Gt; - fn mul(self, other: &'b Fr) -> Self::Output { - let bits = other.value().to_le_bytes(); - let mut res = Self::Output::identity(); - for bit in bits.iter().rev() { - for i in (0..8).rev() { - res = res.double(); - if (bit & (1 << i)) != 0 { - res = &res + self; - } - } - } - res - } -} - -impl Mul for Gt { - type Output = Self; - fn mul(self, rhs: Fr) -> Self::Output { - &self * &rhs - } -} -impl GroupTrait<12, 2, Fp12> for Gt { - fn generator() -> Self { - Self(GT) - } - - fn endomorphism(&self) -> Self { - unimplemented!() - } - - fn rand(rng: &mut R) -> Self { - loop { - let inner = Fp12::rand(rng); - if !inner.is_zero() { - return MillerLoopResult(inner).final_exponentiation(); - } - } - } - - fn hash_to_curve(_exp: &E, _msg: &[u8]) -> Result { - unimplemented!() - } - - fn sign_message( - _exp: &E, - _msg: &[u8], - _private_key: Fp12, - ) -> Result { - unimplemented!() - } - - fn frobenius(&self, _exponent: usize) -> Self { - unimplemented!() - } -} -impl Gt { - /// Returns the group identity, which is $1$. - pub fn identity() -> Gt { - Gt(Fp12::one()) - } - - /// Doubles this group element. - pub fn double(&self) -> Gt { - Gt(self.0.square()) - } -} diff --git a/src/groups/mod.rs b/src/groups/mod.rs index 05aa285..f4db83a 100644 --- a/src/groups/mod.rs +++ b/src/groups/mod.rs @@ -1,7 +1,6 @@ -pub mod g1; -pub mod g2; -pub mod group; -pub mod gt; +mod g1; +mod g2; +pub(crate) mod group; /// This test suite takes time, the biggest culprit of which is the multiplication. Really the /// biggest bottleneck is assuredly the loading of the reference data from disk. The @@ -291,6 +290,7 @@ mod tests { } } mod special_point_tests { + use crate::fields::fp::{FieldExtensionTrait, Fp}; use crate::groups::g1::G1Projective; use crate::groups::group::GroupTrait; @@ -312,9 +312,11 @@ mod tests { let mut d = G1Projective::generator(); for _ in 0..5 { - d = d + G1Projective::generator(); + d = &d + &G1Projective::generator(); } assert_eq!(j, d, "Generator multiplication not valid"); + let c2 = >::quadratic_non_residue(); + println!("{:?}", c2.value().to_words()); } } mod addition_tests { @@ -395,7 +397,11 @@ mod tests { load_g1_reference_data!(g1_points); let three = Fp::from(3); for i in &g1_points.a { - assert_eq!(i + &(i + i), i * &three, "Multiplication failed"); + assert_eq!( + i + &(i + i), + i * &three.value().to_le_bytes(), + "Multiplication failed" + ); } } #[test] @@ -411,7 +417,7 @@ mod tests { load_g1_reference_data!(g1_points); let expected = g1_points.mul; for (i, (a, r)) in g1_points.a.iter().zip(&g1_points.r).enumerate() { - let result = a * r; + let result = a * &r.value().to_le_bytes(); assert_eq!(result, expected[i], "Simple multiplication failed"); } let expected = g1_points.dbl; @@ -434,7 +440,9 @@ mod tests { #[test] fn test_closure() { let expander = XMDExpander::::new(DST, K); - if let Ok(_d) = G1Projective::hash_to_curve(&expander, MSG) {} + if let Ok(d) = G1Projective::hash_to_curve(&expander, MSG) { + println!("{:?}", d) + } } #[test] @@ -462,10 +470,7 @@ mod tests { fn test_svdw() { load_g1_reference_data!(g1_points); - if let Ok(d) = SvdW::precompute_constants( - Fp::ZERO, - >::curve_constant(), - ) { + if let Ok(d) = SvdW::<1, 1, Fp>::precompute_constants(Fp::from(0), Fp::from(3)) { for s in g1_points.svdw.iter() { let r = s.i; let p = s.p; @@ -537,7 +542,7 @@ mod tests { let mut d = G2Projective::generator(); for _ in 0..5 { - d = d + G2Projective::generator(); + d = &d + &G2Projective::generator(); } assert_eq!(j, d, "Generator multiplication not valid"); } @@ -614,7 +619,11 @@ mod tests { load_g2_reference_data!(g2_points, _g2_invalids); let three = Fp::from(3); for i in &g2_points.a { - assert_eq!(i + &(i + i), i * &three, "Multiplication failed"); + assert_eq!( + i + &(i + i), + i * &three.value().to_le_bytes(), + "Multiplication failed" + ); } } #[test] @@ -622,7 +631,7 @@ mod tests { load_g2_reference_data!(g2_points, _g2_invalids); let expected = g2_points.mul; for (i, (a, r)) in g2_points.a.iter().zip(&g2_points.r).enumerate() { - let result = a * r; + let result = a * &r.value().to_le_bytes(); assert_eq!(result, expected[i], "Simple multiplication failed"); } let expected = g2_points.dbl; diff --git a/src/hasher.rs b/src/hasher.rs index 662808e..1d07184 100644 --- a/src/hasher.rs +++ b/src/hasher.rs @@ -9,11 +9,21 @@ use sha3::digest::crypto_common::BlockSizeUser; use sha3::digest::{ExtendableOutput, FixedOutput}; use std::array::TryFromSliceError; #[derive(Debug)] -pub enum HashError { +pub(crate) enum HashError { CastToField, ExpandMessage, ConvertInt, } +#[allow(dead_code)] +fn to_hex(bytes: &[u8]) -> String { + // A simple utility function to convert a byte array into a big endian hex string + bytes + .iter() + .fold(String::with_capacity(bytes.len() * 2), |mut acc, &b| { + acc.push_str(&format!("{:02x}", b)); + acc + }) +} fn i2osp(val: u64, length: usize) -> Result, HashError> { // this is an integer to octet representation of the given length @@ -22,7 +32,7 @@ fn i2osp(val: u64, length: usize) -> Result, HashError> { } Ok(val.to_be_bytes()[8 - length..].to_vec()) } -pub trait Expander { +pub(crate) trait Expander { const OVERSIZE_DST_PREFIX: &'static [u8] = b"H2C-OVERSIZE-DST-"; fn expand_message(&self, msg: &[u8], len_in_bytes: usize) -> Result, HashError>; @@ -54,7 +64,7 @@ pub trait Expander { } } -pub struct XMDExpander { +pub(crate) struct XMDExpander { /// This implements the XMD function, which produces a uniformly random /// byte string using a hash function that outputs b bits. /// Usage of this function is recommended only with Sha2 and Sha3 hashes. @@ -65,7 +75,8 @@ pub struct XMDExpander { } impl XMDExpander { - pub fn new(dst: &[u8], security_param: u64) -> Self { + #[allow(dead_code)] + pub(crate) fn new(dst: &[u8], security_param: u64) -> Self { let dst_prime = if dst.len() > 255 { let mut hasher = D::default(); hasher.update(Self::OVERSIZE_DST_PREFIX); @@ -142,8 +153,8 @@ struct XOFExpander { hash_fn: std::marker::PhantomData, } -#[allow(dead_code)] impl XOFExpander { + #[allow(dead_code)] fn new(dst: &[u8], security_param: u64) -> Self { let dst_prime = if dst.len() > 255 { let mut hasher = D::default(); @@ -181,15 +192,6 @@ mod tests { use super::*; use std::collections::HashMap; use std::sync::OnceLock; - fn to_hex(bytes: &[u8]) -> String { - // A simple utility function to convert a byte array into a big endian hex string - bytes - .iter() - .fold(String::with_capacity(bytes.len() * 2), |mut acc, &b| { - acc.push_str(&format!("{:02x}", b)); - acc - }) - } fn short_xof_hashmap() -> &'static HashMap<&'static str, &'static str> { static HASHMAP: OnceLock> = OnceLock::new(); HASHMAP.get_or_init(|| { @@ -295,9 +297,13 @@ mod tests { *expected_expanded_msg, "Conversion for short XMD failed" ); - let _res = expander - .hash_to_field(msg.as_bytes(), 2, 48) - .expect("Short XMD failed to cast to field"); + let res = expander.hash_to_field(msg.as_bytes(), 2, 48).expect( + "Short XMD failed to \ + cast to field", + ); + for r in res { + println!("{:?}", r); + } } } #[test] diff --git a/src/lib.rs b/src/lib.rs index 59c9f4d..c1d15da 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,11 +1,9 @@ #![cfg_attr(docsrs, feature(doc_auto_cfg))] -#![doc = include_str!("../README.md")] +// #![doc = include_str!("../README.md")] #![deny(unsafe_code)] -#![deny(dead_code)] -#![allow(clippy::needless_doctest_main)] #![warn( clippy::unwrap_used, - missing_docs, + // missing_docs, missing_debug_implementations, missing_copy_implementations, rust_2021_compatibility, @@ -17,61 +15,4 @@ mod fields; mod groups; mod hasher; -mod pairing; mod svdw; - -use crate::fields::fp::{FieldExtensionTrait, Fp, Fr}; -use crate::groups::g1::G1Projective; -use crate::groups::g2::G2Projective; -use crate::groups::group::{GroupError, GroupTrait}; -use crate::hasher::XMDExpander; -use crate::pairing::pairing; -use crypto_bigint::rand_core::OsRng; -use sha3::Keccak256; -use subtle::ConstantTimeEq; - -const DST: &[u8; 30] = b"WARLOCK-CHAOS-V01-CS01-SHA-256"; -const SECURITY_BITS: u64 = 128; - -/// This is a basic struct that simply contains a pair of (private, pub) keys for later use in -/// the signature and verification -#[derive(Debug, Copy, Clone)] -pub struct KeyPair { - /// a scalar in the base field - pub secret_key: Fp, - /// scalar*g2generator - pub public_key: G2Projective, -} - -impl KeyPair { - /// This instantiates a random pair from the cryptographic RNG - pub fn generate() -> KeyPair { - let secret_key = Fp::new(Fr::rand(&mut OsRng).value()); - let public_key = G2Projective::generator() * secret_key; - KeyPair { - secret_key, - public_key, - } - } -} -/// This takes a message, hashes it, and returns the signature based on the key pair struct -pub fn sign(k: &Fp, msg: &[u8]) -> Result { - let expander = XMDExpander::::new(DST, SECURITY_BITS); - match G1Projective::hash_to_curve(&expander, msg) { - Ok(hashed_message) => Ok(hashed_message * *k), - _ => Err(GroupError::CannotHashToGroup), - } -} - -/// This takes a public key, message, and a signature, and verifies that the signature is valid -pub fn verify(pub_key: &G2Projective, msg: &[u8], sig: &G1Projective) -> Result { - let expander = XMDExpander::::new(DST, SECURITY_BITS); - match G1Projective::hash_to_curve(&expander, msg) { - Ok(hashed_message) => { - let lhs = pairing(sig, &G2Projective::generator()); - let rhs = pairing(&hashed_message, pub_key); - Ok(lhs.ct_eq(&rhs).into()) - } - _ => Err(GroupError::CannotHashToGroup), - } -} diff --git a/src/pairing.rs b/src/pairing.rs deleted file mode 100644 index 15944da..0000000 --- a/src/pairing.rs +++ /dev/null @@ -1,608 +0,0 @@ -use crate::fields::fp::{FieldExtensionTrait, Fp}; -use crate::fields::fp12::Fp12; -use crate::fields::fp2::{Fp2, TWO_INV}; -use crate::fields::fp6::Fp6; -use crate::groups::g1::{G1Affine, G1Projective}; -use crate::groups::g2::{G2Affine, G2Projective, BLS_X}; -use crate::groups::group::GroupTrait; -use crate::groups::gt::Gt; -use num_traits::{Inv, One}; -use std::ops::{Mul, MulAssign, Neg}; -use subtle::{Choice, ConditionallySelectable}; - -/// This is the value 6*BLS_X+2, which is the bound of iterations on the Miller loops. Why weird? -/// Well, great question. This is the (windowed) non-adjacent form of the number 65, meaning that -/// no nonzero digits are adjacent in this form. The benefit is that during the double and add -/// algorithm of multiplication, the number of operations needed to iterate is directly related -/// to the Hamming weight (number of zeros in a binary representation) of a number. In binary -/// base 2, on average half of the digits will be zero, whereas in the trinary base 3 of the NAF, -/// this moves down to 1/3 on average, improving the loop speed. -const ATE_LOOP_COUNT_NAF: [i8; 64] = [ - 1, 0, 1, 0, 0, 0, -1, 0, -1, 0, 0, 0, -1, 0, 1, 0, -1, 0, 0, -1, 0, 0, 0, 0, 0, 1, 0, 0, -1, 0, - 1, 0, 0, -1, 0, 0, 0, 0, -1, 0, 1, 0, 0, 0, -1, 0, -1, 0, 0, 1, 0, 0, 0, -1, 0, 0, -1, 0, 1, 0, - 1, 0, 0, 0, -]; -#[derive(Copy, Clone, Debug)] -pub struct MillerLoopResult(pub Fp12); -impl Default for MillerLoopResult { - fn default() -> Self { - MillerLoopResult(Fp12::one()) - } -} -impl<'a, 'b> Mul<&'b MillerLoopResult> for &'a MillerLoopResult { - type Output = MillerLoopResult; - - #[inline] - fn mul(self, rhs: &'b MillerLoopResult) -> MillerLoopResult { - MillerLoopResult(self.0 * rhs.0) - } -} -impl Mul for MillerLoopResult { - type Output = MillerLoopResult; - - #[inline] - fn mul(self, rhs: MillerLoopResult) -> MillerLoopResult { - &self * &rhs - } -} - -impl MulAssign for MillerLoopResult { - #[inline] - fn mul_assign(&mut self, rhs: MillerLoopResult) { - *self = *self * rhs; - } -} - -impl<'b> MulAssign<&'b MillerLoopResult> for MillerLoopResult { - #[inline] - fn mul_assign(&mut self, rhs: &'b MillerLoopResult) { - *self = *self * *rhs; - } -} - -/// There are many evaluations in Fp12 throughout this. As you can see directly from Algs. 27 and 28 -/// in , for example, regarding the double and addition -/// formulae: -/// // let l0 = Fp6::new(&[t10, Fp2::zero(), Fp2::zero()]); -/// // let l1 = Fp6::new(&[t1, t9, Fp2::zero()]); -/// // return Fp12::new(&[l0, l1]) -/// -/// which is very, very sparse, resulting in many unnecessary multiplications and additions by -/// zero, which is not ideal. We therefore only keep the 3 nonzero coefficients returned by these -/// evaluations. These nonzero coeffs are stored in the struct below. -#[derive(PartialEq, Default, Clone, Copy)] -pub struct Ell(Fp2, Fp2, Fp2); - -impl MillerLoopResult { - pub fn final_exponentiation(&self) -> Gt { - /// As part of the cyclotomic acceleration of the final exponentiation step, there is a - /// shortcut to take when using multiplication in Fp4. We built the tower of extensions using - /// degrees 2, 6, and 12, but there is an additional way to write Fp12: - /// Fp4 = Fp2[w^3]/((w^3)^2-(9+u)) - /// Fp12 = Fp4[w]/(w^3-w^3) - /// - /// This lets us do magic on points in the twist curve with cheaper operations :) - /// This implements algorithm 9 from https://eprint.iacr.org/2010/354.pdf, with the notable - /// difference that instead of passing an element of Fp4 (which I did not implement), we pass - /// in only the two components from Fp2 that comprise the Fp4 element. - fn fp4_square(a: Fp2, b: Fp2) -> (Fp2, Fp2) { - // Line 1 - let t0 = a.square(); - // Line 2 - let t1 = b.square(); - // Line 3 - let c0 = t1.residue_mul(); - // Line 4 - let c0 = c0 + t0; - // Line 5 - let c1 = a + b; - // Line 6 - let c1 = c1.square() - t0 - t1; - (c0, c1) - } - /// This implements efficient squaring of an element of Fp12 in the cyclotomic subgroup - /// C_{\phi^6}. It is what's called "Granger-Scott" squaring, and is an implementation of - /// algorithm 5.5.4 (listing 21) from https://www.math.u-bordeaux.fr/~damienrobert/csi/book/book.pdf - fn cyclotomic_squared(f: Fp12) -> Fp12 { - // Lines 3-8 - let mut z0 = f.0[0].0[0]; - let mut z4 = f.0[0].0[1]; - let mut z3 = f.0[0].0[2]; - let mut z2 = f.0[1].0[0]; - let mut z1 = f.0[1].0[1]; - let mut z5 = f.0[1].0[2]; - // Line 9 - let (t0, t1) = fp4_square(z0, z1); - // Line 13-22 for A - z0 = t0 - z0; - z0 = z0 + z0 + t0; - - z1 = t1 + z1; - z1 = z1 + z1 + t1; - - let (mut t0, t1) = fp4_square(z2, z3); - let (t2, t3) = fp4_square(z4, z5); - - // Lines 25-31, for C - z4 = t0 - z4; - z4 = z4 + z4 + t0; - - z5 = t1 + z5; - z5 = z5 + z5 + t1; - - // Lines 34-41, for B - t0 = t3.residue_mul(); - z2 = t0 + z2; - z2 = z2 + z2 + t0; - - z3 = t2 - z3; - z3 = z3 + z3 + t2; - Fp12::new(&[Fp6::new(&[z0, z4, z3]), Fp6::new(&[z2, z1, z5])]) - } - /// This is a simple double and add algorithm for exponentiation. You can get more - /// complicated algorithms if you go to a compressed representation, such as Algorithm - /// 5.5.4, listing 27 - pub fn cyclotomic_exp(f: Fp12, exponent: &Fp) -> Fp12 { - let bits = exponent.value().to_words(); - let mut res = Fp12::one(); - for e in bits.iter().rev() { - for i in (0..64).rev() { - res = cyclotomic_squared(res); - if ((*e >> i) & 1) == 1 { - res *= f; - } - } - } - res - } - /// This is a helper function to determine f^z, where $z$ is the generator of this - /// particular member of the BN family - pub fn exp_by_neg_z(f: Fp12) -> Fp12 { - cyclotomic_exp(f, &BLS_X).unitary_inverse() - } - /// The below is the easy part of the final exponentiation step, corresponding to Lines - /// 1-4 of Alg 31 from https://eprint.iacr.org/2010/354.pdf. - fn easy_part(f: Fp12) -> Fp12 { - let f1 = f.unitary_inverse(); - let f2 = f.inv(); - let f = f1 * f2; - f.frobenius(2) * f - } - - /// I was originally going to implement lines 5-28 of Alg 31 from , - /// which is allegedly fast, but there is another algorithm that came out more recently - /// that avoids expensive frobenius operations, which is what Arkworks does. - /// - /// This is the hard part, and follows Laura Fuentes-Castaneda et al. "Faster hashing to - /// G2" - /// see https://github.com/arkworks-rs/algebra/blob/273bf2130420904cab815544664a539f049d0494/ec/src/models/bn/mod.rs#L141 - fn hard_part(input: Fp12) -> Fp12 { - let a = exp_by_neg_z(input); - let b = cyclotomic_squared(a); - let c = cyclotomic_squared(b); - let d = c * b; - - let e = exp_by_neg_z(d); - let f = cyclotomic_squared(e); - let g = exp_by_neg_z(f); - let h = d.unitary_inverse(); - let i = g.unitary_inverse(); - - let j = i * e; - let k = j * h; - let l = k * b; - let m = k * e; - let n = input * m; - - let o = l.frobenius(1); - let p = o * n; - - let q = k.frobenius(2); - let r = q * p; - - let s = input.unitary_inverse(); - let t = s * l; - let u = t.frobenius(3); - u * r - } - - Gt(hard_part(easy_part(self.0))) - } -} -/// This is a nice little trick we can use. The Miller loops require the evaluation of an affine -/// point along a line betwixt two projective coordinates, with these two points either being R, -/// and R (therefore leading to the doubling step), or R and Q (leading to the addition step). -/// Think of this as determining the discretization of a parametrized function, but notice that -/// for the entire loop, this discretization does not change, only the point at which we evaluate -/// this function! Therefore, we simply precompute the values on the line, and then use a cheap -/// evaluation in each iteration of the Miller loop to avoid recomputing these "constants" each -/// time. Again, because of the sparse nature of the returned Fp12 from the doubling and addition -/// steps, we store only the 3 non-zero coefficients in an arr of EllCoeffs -/// -/// There's two components to this struct. First, is the original value at which we are -/// computing the line. Second, is an array of the coefficients we determine by the generation -/// of the line, stored as an array. But an array of 87 elements is very specific, no? -/// There's 64 total iterations through the NAF representation, each one incurring a -/// doubling step. Further, there are 9 `1` digits (each with an addition step), and 12 `3` -/// digits, each also with an addition step. After the loop, there are 2 more addition steps, so -/// the total number of coefficients we need to store is 64+9+12+2 = 87. -#[derive(PartialEq)] -pub struct G2PreComputed { - pub q: G2Affine, - pub coeffs: [Ell; 87], -} -impl G2PreComputed { - pub fn miller_loop(&self, g1: &G1Affine) -> MillerLoopResult { - let mut f = Fp12::one(); - - let mut idx = 0; - - for i in ATE_LOOP_COUNT_NAF.iter() { - let c = &self.coeffs[idx]; - idx += 1; - f = f.square().sparse_mul(c.0, c.1.scale(g1.y), c.2.scale(g1.x)); - - if *i != 0 { - let c = &self.coeffs[idx]; - idx += 1; - f = f.sparse_mul(c.0, c.1.scale(g1.y), c.2.scale(g1.x)); - } - } - - let c = &self.coeffs[idx]; - idx += 1; - f = f.sparse_mul(c.0, c.1.scale(g1.y), c.2.scale(g1.x)); - - let c = &self.coeffs[idx]; - f = f.sparse_mul(c.0, c.1.scale(g1.y), c.2.scale(g1.x)); - - MillerLoopResult(f) - } -} -impl G2Affine { - fn precompute(&self) -> G2PreComputed { - let mut r = G2Projective::from(self); - - let mut coeffs = [Ell::default(); 87]; - - let q_neg = -self; - // in order to get rid of all the idx's all over the place, you COULD do use a mut - // iterator, but then you'll have coeffs.iter_mut().next().unwrap().expect("") all over, - // which is worse ... - let mut idx: usize = 0; - for i in ATE_LOOP_COUNT_NAF.iter() { - coeffs[idx] = r.doubling_step(); - idx += 1; - match *i { - 1 => { - coeffs[idx] = r.addition_step(self); - idx += 1; - } - -1 => { - coeffs[idx] = r.addition_step(&q_neg); - idx += 1; - } - _ => {} - } - } - let q1 = self.endomorphism(); - let q2 = -(q1.endomorphism()); - - coeffs[idx] = r.addition_step(&q1); - idx += 1; - coeffs[idx] = r.addition_step(&q2); - G2PreComputed { q: *self, coeffs } - } -} -/// The below implements the doubling and addition steps for the Miller loop algorithm. You'll -/// notice that it doesn't return a Fp12, which it should! See notes on efficiency above as to -/// why this returns EllCoeffs instead. -/// -/// What zkcrypto does is implement Alg 26 and 27 from , -/// but there was a more memory sensitive algorithm that came out the same year for the same -/// speed so for use as a pre-compile, we stick with that version that's used by zcash / bn. -/// -/// It implements the addition step from page 234, and the doubling step from 235 of -/// . -impl G2Projective { - fn addition_step(&mut self, base: &G2Affine) -> Ell { - let d = self.x - self.z * base.x; - let e = self.y - self.z * base.y; - let f = d.square(); - let g = e.square(); - let h = d * f; - let i = self.x * f; - let j = self.z * g + h - (i + i); - - self.x = d * j; - self.y = e * (i - j) - h * self.y; - self.z *= h; - - Ell( - >::quadratic_non_residue() * (e * base.x - d * base.y), - d, - e.neg(), - ) - } - fn doubling_step(&mut self) -> Ell { - let a = (self.x * self.y).scale(TWO_INV); - let b = self.y.square(); - let c = self.z.square(); - let d = c + c + c; - let e = >::curve_constant() * d; - let f = e + e + e; - let g = (b + f).scale(TWO_INV); - let h = (self.y + self.z).square() - (b + c); - let i = e - b; - let j = self.x.square(); - let e_sq = e.square(); - - self.x = a * (b - f); - self.y = g.square() - (e_sq + e_sq + e_sq); - self.z = b * h; - - Ell( - >::quadratic_non_residue() * i, - h.neg(), - j + j + j, - ) - } -} -pub fn pairing(p: &G1Projective, q: &G2Projective) -> Gt { - let p = &G1Affine::from(p); - let q = &G2Affine::from(q); - let either_zero = Choice::from((p.is_zero() | q.is_zero()) as u8); - let p = G1Affine::conditional_select(p, &G1Affine::generator(), either_zero); - let q = G2Affine::conditional_select(q, &G2Affine::generator(), either_zero); - let tmp = q.precompute().miller_loop(&p).0; - let tmp = MillerLoopResult(Fp12::conditional_select(&tmp, &Fp12::one(), either_zero)); - tmp.final_exponentiation() -} - -pub fn glued_miller_loop(g2_precomps: &[G2PreComputed], g1s: &[G1Affine]) -> MillerLoopResult { - let mut f = Fp12::one(); - let mut idx = 0; - for i in ATE_LOOP_COUNT_NAF.iter() { - f = f.square(); - for (g2_precomp, g1) in g2_precomps.iter().zip(g1s.iter()) { - let c = &g2_precomp.coeffs[idx]; - f = f.sparse_mul(c.0, c.1.scale(g1.y), c.2.scale(g1.x)); - } - idx += 1; - if *i != 0 { - for (g2_precomp, g1) in g2_precomps.iter().zip(g1s.iter()) { - let c = &g2_precomp.coeffs[idx]; - f = f.sparse_mul(c.0, c.1.scale(g1.y), c.2.scale(g1.x)); - } - idx += 1; - } - } - - for (g2_precompute, g1) in g2_precomps.iter().zip(g1s.iter()) { - let c = &g2_precompute.coeffs[idx]; - f = f.sparse_mul(c.0, c.1.scale(g1.y), c.2.scale(g1.x)); - } - idx += 1; - for (g2_precompute, g1) in g2_precomps.iter().zip(g1s.iter()) { - let c = &g2_precompute.coeffs[idx]; - f = f.sparse_mul(c.0, c.1.scale(g1.y), c.2.scale(g1.x)); - } - MillerLoopResult(f) -} -#[allow(dead_code)] -pub fn glued_pairing(g1s: &[G1Projective], g2s: &[G2Projective]) -> Gt { - let g1s = g1s.iter().map(G1Affine::from).collect::>(); - let g2s = g2s.iter().map(G2Affine::from).collect::>(); - let g2_precomps = g2s - .iter() - .map(|g2| g2.precompute()) - .collect::>(); - glued_miller_loop(&g2_precomps, &g1s).final_exponentiation() -} -#[cfg(test)] -mod tests { - use super::*; - use crate::fields::fp::Fr; - mod pairing_tests { - use crypto_bigint::rand_core::OsRng; - - use super::*; - use crate::groups::g1::G1Projective; - const DST: &[u8; 30] = b"WARLOCK-CHAOS-V01-CS01-SHA-256"; - const MSG: &[u8; 4] = &20_i32.to_be_bytes(); - const K: u64 = 128; - - #[test] - fn test_gt_generator() { - assert_eq!( - pairing(&G1Projective::generator(), &G2Projective::generator()), - Gt::generator() - ); - } - #[test] - fn test_signatures() { - use crate::hasher::XMDExpander; - use sha3::Keccak256; - let expander = XMDExpander::::new(DST, K); - let private_key = Fp::new(Fr::rand(&mut OsRng).value()); - if let Ok(hashed_message) = G1Projective::hash_to_curve(&expander, MSG) { - let signature = hashed_message * private_key; - let public_key = G2Projective::generator() * private_key; - - let lhs = pairing(&signature, &G2Projective::generator()); - let rhs = pairing(&hashed_message, &public_key); - assert_eq!(lhs, rhs); - } - } - #[test] - fn test_shared_secret() { - fn generate_private_key() -> Fr { - >::rand(&mut OsRng) - } - let alice_sk = generate_private_key(); - let bob_sk = generate_private_key(); - let carol_sk = generate_private_key(); - - let (alice_pk1, alice_pk2) = ( - G1Projective::generator() * alice_sk.into(), - G2Projective::generator() * alice_sk.into(), - ); - let (bob_pk1, bob_pk2) = ( - G1Projective::generator() * bob_sk.into(), - G2Projective::generator() * bob_sk.into(), - ); - let (carol_pk1, carol_pk2) = ( - G1Projective::generator() * carol_sk.into(), - G2Projective::generator() * carol_sk.into(), - ); - - let alice_ss = pairing(&bob_pk1, &carol_pk2) * alice_sk; - let bob_ss = pairing(&carol_pk1, &alice_pk2) * bob_sk; - let carol_ss = pairing(&alice_pk1, &bob_pk2) * carol_sk; - assert!(alice_ss == bob_ss && bob_ss == carol_ss); - } - #[test] - fn test_identities() { - let g1 = G1Projective::zero(); - let g2 = G2Projective::generator(); - let gt = pairing(&g1, &g2); - assert_eq!(gt, Gt::identity()); - - let g1 = G1Projective::generator(); - let g2 = G2Projective::zero(); - let gt = pairing(&g1, &g2); - assert_eq!(gt, Gt::identity()); - - let g = G1Projective::generator(); - let h = G2Projective::generator(); - let p = -pairing(&g, &h); - let q = pairing(&g, &-h); - let r = pairing(&-g, &h); - - assert_eq!(p, q); - assert_eq!(q, r); - } - #[test] - fn test_cases() { - let g1 = G1Projective::generator() - * Fp::new(Fr::new_from_str( - "18097487326282793650237947474982649264364522469319914492172746413872781676", - ).expect("").value()); - let g2 = G2Projective::generator() - * Fp::new(Fr::new_from_str( - "20390255904278144451778773028944684152769293537511418234311120800877067946", - ).expect("").value()); - - let gt = pairing(&g1, &g2); - - let expected = Gt(Fp12::new(&[ - Fp6::new(&[ - Fp2::new(&[ - Fp::new_from_str( - "7520311483001723614143802378045727372643587653754534704390832890681688842501", - ).expect(""), - Fp::new_from_str( - "20265650864814324826731498061022229653175757397078253377158157137251452249882", - ).expect(""), - ]), - Fp2::new(&[ - Fp::new_from_str( - "11942254371042183455193243679791334797733902728447312943687767053513298221130", - ).expect(""), - Fp::new_from_str( - "759657045325139626991751731924144629256296901790485373000297868065176843620", - ).expect(""), - ]), - Fp2::new(&[ - Fp::new_from_str( - "16045761475400271697821392803010234478356356448940805056528536884493606035236", - ).expect(""), - Fp::new_from_str( - "4715626119252431692316067698189337228571577552724976915822652894333558784086", - ).expect(""), - ]), - ]), - Fp6::new(&[ - Fp2::new(&[ - Fp::new_from_str( - "14901948363362882981706797068611719724999331551064314004234728272909570402962", - ).expect(""), - Fp::new_from_str( - "11093203747077241090565767003969726435272313921345853819385060670210834379103", - ).expect(""), - ]), - Fp2::new(&[ - Fp::new_from_str( - "17897835398184801202802503586172351707502775171934235751219763553166796820753", - ).expect(""), - Fp::new_from_str( - "1344517825169318161285758374052722008806261739116142912817807653057880346554", - ).expect(""), - ]), - Fp2::new(&[ - Fp::new_from_str( - "11123896897251094532909582772961906225000817992624500900708432321664085800838", - ).expect(""), - Fp::new_from_str( - "17453370448280081813275586256976217762629631160552329276585874071364454854650", - ).expect(""), - ]), - ]), ] - )); - assert_eq!(gt, expected); - } - - #[test] - fn test_bilinearity() { - use crypto_bigint::rand_core::OsRng; - - for _ in 0..10 { - let p = G1Projective::rand(&mut OsRng); - let q = G2Projective::rand(&mut OsRng); - let s = Fr::rand(&mut OsRng); - let sp = G1Projective::from(p) * s.into(); - let sq = G2Projective::from(q) * s.into(); - - let a = pairing(&p, &q) * s; - let b = pairing(&sp, &q); - let c = pairing(&p, &sq); - - assert_eq!(a, b); - assert_eq!(a, c); - - let t = -Fr::ONE; - assert_ne!(a, Gt::identity()); - assert_eq!(&(a * t) + &a, Gt::identity()); - } - } - - #[test] - fn test_batches() { - use crypto_bigint::rand_core::OsRng; - let r = glued_pairing(&[], &[]); - assert_eq!(r, Gt::identity()); - - const RANGE: usize = 50; - - let mut p_arr = [G1Projective::zero(); RANGE]; - let mut q_arr = [G2Projective::zero(); RANGE]; - let mut sp_arr = [G1Projective::zero(); RANGE]; - let mut sq_arr = [G2Projective::zero(); RANGE]; - - for i in 0..RANGE { - let p = G1Projective::rand(&mut OsRng); - let q = G2Projective::rand(&mut OsRng); - let s = Fr::rand(&mut OsRng); - let sp = p * s.into(); - let sq = q * s.into(); - sp_arr[i] = sp; - q_arr[i] = q; - sq_arr[i] = sq; - p_arr[i] = p; - } - let b_batch = glued_pairing(&sp_arr, &q_arr); - let c_batch = glued_pairing(&p_arr, &sq_arr); - assert_eq!(b_batch, c_batch); - } - } -} diff --git a/src/svdw.rs b/src/svdw.rs index 97d5065..72116dc 100644 --- a/src/svdw.rs +++ b/src/svdw.rs @@ -12,37 +12,37 @@ //! References //! ---------- //! 1. - -use crate::fields::fp::Fp; -use num_traits::{Inv, Zero}; +use crate::fields::fp::FieldExtensionTrait; use subtle::Choice; #[derive(Debug)] -pub enum MapError { +pub(crate) enum MapError { SvdWError, } #[derive(Debug)] -pub struct SvdW { - a: Fp, - b: Fp, - c1: Fp, - c2: Fp, - c3: Fp, - c4: Fp, - z: Fp, +pub(crate) struct SvdW> { + a: F, + b: F, + c1: F, + c2: F, + c3: F, + c4: F, + z: F, } - -pub trait SvdWTrait: Sized { +#[allow(dead_code)] +pub(crate) trait SvdWTrait>: + Sized +{ /// This is the actual struct containing the relevant information. There are a few input /// constants, namely the coefficients A and B that define the curve in its short Weierstrass /// representation. The constants c1-c4 and Z are determined by the algorithm. - - fn find_z_svdw(a: Fp, b: Fp) -> Fp { - let g = |x: &Fp| -> Fp { (*x) * (*x) * (*x) + a * (*x) + b }; - let h = |x: &Fp| -> Fp { -(Fp::THREE * (*x) * (*x) + Fp::FOUR * a) / (Fp::FOUR * g(x)) }; + #[allow(dead_code)] + fn find_z_svdw(a: F, b: F) -> F { + let g = |x: &F| -> F { (*x) * (*x) * (*x) + a * (*x) + b }; + let h = |x: &F| -> F { -(F::from(3) * (*x) * (*x) + F::from(4) * a) / (F::from(4) * g(x)) }; let mut ctr = 1; loop { - for z_cand in [Fp::from(ctr), -Fp::from(ctr)] { + for z_cand in [F::from(ctr), -F::from(ctr)] { if g(&z_cand).is_zero() { continue; } @@ -53,7 +53,7 @@ pub trait SvdWTrait: Sized { continue; } if bool::from(g(&z_cand).is_square()) - | bool::from(g(&(-z_cand / Fp::from(2))).is_square()) + | bool::from(g(&(-z_cand / F::from(2))).is_square()) { return z_cand; } @@ -61,14 +61,14 @@ pub trait SvdWTrait: Sized { ctr += 1; } } - - fn precompute_constants(a: Fp, b: Fp) -> Result { - let g = |x: &Fp| -> Fp { (*x) * (*x) * (*x) + a * (*x) + b }; + #[allow(dead_code)] + fn precompute_constants(a: F, b: F) -> Result, MapError> { + let g = |x: &F| -> F { (*x) * (*x) * (*x) + a * (*x) + b }; let z = Self::find_z_svdw(a, b); let mgz = -g(&z); let c1 = g(&z); - let c2 = -z / Fp::TWO; - let mut c3 = match (-g(&z) * (Fp::THREE * z.square() + Fp::FOUR * a)) + let c2 = -z / F::from(2); + let mut c3 = match (-g(&z) * (F::from(3) * z.square() + F::from(4) * a)) .sqrt() .into_option() { @@ -81,7 +81,7 @@ pub trait SvdWTrait: Sized { if c3.sgn0().unwrap_u8() != 0u8 { return Err(MapError::SvdWError); } - let c4 = Fp::FOUR * mgz / (Fp::THREE * z * z + Fp::FOUR * a); + let c4 = F::from(4) * mgz / (F::from(3) * z * z + F::from(4) * a); Ok(SvdW { a, @@ -93,18 +93,21 @@ pub trait SvdWTrait: Sized { z, }) } - fn unchecked_map_to_point(&self, u: Fp) -> Result<[Fp; 2], MapError>; + fn unchecked_map_to_point(&self, u: F) -> Result<[F; 2], MapError>; } -impl SvdWTrait for SvdW { - fn unchecked_map_to_point(&self, u: Fp) -> Result<[Fp; 2], MapError> { +impl> SvdWTrait + for SvdW +{ + #[allow(dead_code)] + fn unchecked_map_to_point(&self, u: F) -> Result<[F; 2], MapError> { // Implements the SvdW algorithm for a single scalar point - let cmov = |x: &Fp, y: &Fp, b: &Choice| -> Fp { - Fp::from(!bool::from(*b) as u64) * (*x) + Fp::from(bool::from(*b) as u64) * (*y) + let cmov = |x: &F, y: &F, b: &Choice| -> F { + F::from(!bool::from(*b) as u64) * (*x) + F::from(bool::from(*b) as u64) * (*y) }; let tv1 = u * u; let tv1 = tv1 * self.c1; - let tv2 = Fp::from(1) + tv1; - let tv1 = Fp::from(1) - tv1; + let tv2 = F::from(1) + tv1; + let tv1 = F::from(1) - tv1; let tv3 = tv1 * tv2; let tv3 = tv3.inv(); let tv4 = u * tv1; @@ -148,7 +151,7 @@ mod tests { use super::*; mod map_tests { use super::*; - use crate::fields::fp::{FieldExtensionTrait, Fp}; + use crate::fields::fp::Fp; use crate::hasher::Expander; use crypto_bigint::U256; use num_traits::One; @@ -156,10 +159,7 @@ mod tests { #[test] fn test_z_svdw() { - let z = SvdW::find_z_svdw( - Fp::ZERO, - >::curve_constant(), - ); + let z = SvdW::<1, 1, Fp>::find_z_svdw(Fp::from(0), Fp::from(3)); assert_eq!(z, Fp::ONE, "Finding Z failed for BN254"); } #[test] @@ -175,7 +175,7 @@ mod tests { let c4 = U256::from_be_hex( "10216f7ba065e00de81ac1e7808072c9dd2b2385cd7b438469602eb24829a9bd", ); - let res = match SvdW::precompute_constants(Fp::from(0), Fp::from(3)) { + let res = match SvdW::<1, 1, Fp>::precompute_constants(Fp::from(0), Fp::from(3)) { Ok(bn254_svdw) => { println!("{:?}", bn254_svdw.a.value()); println!("{:?}", bn254_svdw.b.value()); @@ -206,7 +206,7 @@ mod tests { .hash_to_field(msg, 2, 48) .expect("Conversion failed"); - let res = match SvdW::precompute_constants(Fp::from(0), Fp::from(3)) { + let res = match SvdW::<1, 1, Fp>::precompute_constants(Fp::from(0), Fp::from(3)) { Ok(bn254_svdw) => { let _d = scalars .iter() @@ -217,7 +217,7 @@ mod tests { .expect("SVDW failed to map to point"), ) }) - .fold(GroupProjective::<1, 1, Fp>::zero(), |acc, x| acc + x); + .fold(GroupProjective::<1, 1, Fp>::zero(), |acc, x| &acc + &x); let d = GroupProjective::<1, 1, Fp>::new([_d.x, _d.y, _d.z]).expect("Map failed"); println!("{:?}, {:?}, {:?}", d.x.value(), d.y.value(), d.z.value());