diff --git a/Cargo.toml b/Cargo.toml index e0b9d8ba..f29c917e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -63,3 +63,7 @@ required-features = ["reexport"] [[bench]] name = "group" harness = false + +[[bench]] +name = "hash_to_curve" +harness = false diff --git a/benches/hash_to_curve.rs b/benches/hash_to_curve.rs new file mode 100644 index 00000000..76f6733a --- /dev/null +++ b/benches/hash_to_curve.rs @@ -0,0 +1,59 @@ +use criterion::{black_box, criterion_group, criterion_main, Criterion}; +use pasta_curves::arithmetic::CurveExt; +use rand_core::{OsRng, RngCore}; +use std::iter; + +fn hash_to_secp256k1(c: &mut Criterion) { + hash_to_curve::(c, "Secp256k1"); +} + +fn hash_to_secq256k1(c: &mut Criterion) { + hash_to_curve::(c, "Secq256k1"); +} + +fn hash_to_secp256r1(c: &mut Criterion) { + hash_to_curve::(c, "Secp256r1"); +} + +fn hash_to_pallas(c: &mut Criterion) { + hash_to_curve::(c, "Pallas"); +} + +fn hash_to_vesta(c: &mut Criterion) { + hash_to_curve::(c, "Vesta"); +} + +fn hash_to_bn256(c: &mut Criterion) { + hash_to_curve::(c, "Bn256"); +} + +fn hash_to_grumpkin(c: &mut Criterion) { + hash_to_curve::(c, "Grumpkin"); +} + +fn hash_to_curve(c: &mut Criterion, name: &'static str) { + { + let hasher = G::hash_to_curve("test"); + let mut rng = OsRng; + let message = iter::repeat_with(|| rng.next_u32().to_be_bytes()) + .take(1024) + .flatten() + .collect::>(); + + c.bench_function(&format!("Hash to {}", name), move |b| { + b.iter(|| hasher(black_box(&message))) + }); + } +} + +criterion_group!( + benches, + hash_to_secp256k1, + hash_to_secq256k1, + hash_to_secp256r1, + hash_to_pallas, + hash_to_vesta, + hash_to_bn256, + hash_to_grumpkin, +); +criterion_main!(benches); diff --git a/src/bn256/fq.rs b/src/bn256/fq.rs index 0e2fc10f..0024723a 100644 --- a/src/bn256/fq.rs +++ b/src/bn256/fq.rs @@ -1,11 +1,10 @@ #[cfg(feature = "asm")] use crate::bn256::assembly::field_arithmetic_asm; #[cfg(not(feature = "asm"))] -use crate::{field_arithmetic, field_specific}; +use crate::{arithmetic::macx, field_arithmetic, field_specific}; -use crate::arithmetic::{adc, mac, macx, sbb}; -use crate::bn256::LegendreSymbol; -use crate::ff::{Field, FromUniformBytes, PrimeField, WithSmallOrderMulGroup}; +use crate::arithmetic::{adc, mac, sbb}; +use crate::ff::{FromUniformBytes, PrimeField, WithSmallOrderMulGroup}; use crate::{ field_bits, field_common, impl_add_binop_specify_output, impl_binops_additive, impl_binops_additive_specify_output, impl_binops_multiplicative, @@ -160,27 +159,10 @@ impl Fq { pub const fn size() -> usize { 32 } - - pub fn legendre(&self) -> LegendreSymbol { - // s = self^((modulus - 1) // 2) - // 0x183227397098d014dc2822db40c0ac2ecbc0b548b438e5469e10460b6c3e7ea3 - let s = &[ - 0x9e10460b6c3e7ea3u64, - 0xcbc0b548b438e546u64, - 0xdc2822db40c0ac2eu64, - 0x183227397098d014u64, - ]; - let s = self.pow(s); - if s == Self::zero() { - LegendreSymbol::Zero - } else if s == Self::one() { - LegendreSymbol::QuadraticResidue - } else { - LegendreSymbol::QuadraticNonResidue - } - } } +prime_field_legendre!(Fq); + impl ff::Field for Fq { const ZERO: Self = Self::zero(); const ONE: Self = Self::one(); @@ -310,6 +292,7 @@ impl WithSmallOrderMulGroup<3> for Fq { #[cfg(test)] mod test { use super::*; + use crate::legendre::Legendre; use ff::Field; use rand_core::OsRng; @@ -322,7 +305,7 @@ mod test { let a = Fq::random(OsRng); let mut b = a; b = b.square(); - assert_eq!(b.legendre(), LegendreSymbol::QuadraticResidue); + assert_eq!(b.legendre(), Fq::ONE); let b = b.sqrt().unwrap(); let mut negb = b; @@ -335,7 +318,7 @@ mod test { for _ in 0..10000 { let mut b = c; b = b.square(); - assert_eq!(b.legendre(), LegendreSymbol::QuadraticResidue); + assert_eq!(b.legendre(), Fq::ONE); b = b.sqrt().unwrap(); diff --git a/src/bn256/fq2.rs b/src/bn256/fq2.rs index e5a249ee..468b018c 100644 --- a/src/bn256/fq2.rs +++ b/src/bn256/fq2.rs @@ -1,6 +1,6 @@ use super::fq::{Fq, NEGATIVE_ONE}; -use super::LegendreSymbol; use crate::ff::{Field, FromUniformBytes, PrimeField, WithSmallOrderMulGroup}; +use crate::legendre::Legendre; use core::convert::TryInto; use core::ops::{Add, Mul, Neg, Sub}; use rand::RngCore; @@ -125,6 +125,30 @@ impl_binops_additive!(Fq2, Fq2); impl_binops_multiplicative!(Fq2, Fq2); impl_sum_prod!(Fq2); +impl Legendre for Fq2 { + type BasePrimeField = Fq; + + #[inline] + fn legendre_exp() -> &'static [u64] { + lazy_static::lazy_static! { + // (p-1) / 2 + static ref LEGENDRE_EXP: Vec = + (num_bigint::BigUint::from_bytes_le((-::ONE).to_repr().as_ref())/2usize).to_u64_digits(); + } + &*LEGENDRE_EXP + } + + /// Norm of Fq2 as extension field in i over Fq + #[inline] + fn norm(&self) -> Self::BasePrimeField { + let mut t0 = self.c0; + let mut t1 = self.c1; + t0 = t0.square(); + t1 = t1.square(); + t1 + t0 + } +} + impl Fq2 { #[inline] pub const fn zero() -> Fq2 { @@ -174,10 +198,6 @@ impl Fq2 { res } - pub fn legendre(&self) -> LegendreSymbol { - self.norm().legendre() - } - pub fn mul_assign(&mut self, other: &Self) { let mut t1 = self.c0 * other.c0; let mut t0 = self.c0 + self.c1; @@ -298,15 +318,6 @@ impl Fq2 { self.c1 += &t0; } - /// Norm of Fq2 as extension field in i over Fq - pub fn norm(&self) -> Fq { - let mut t0 = self.c0; - let mut t1 = self.c1; - t0 = t0.square(); - t1 = t1.square(); - t1 + t0 - } - pub fn invert(&self) -> CtOption { let mut t1 = self.c1; t1 = t1.square(); @@ -696,17 +707,6 @@ fn test_fq2_mul_nonresidue() { } } -#[test] -fn test_fq2_legendre() { - assert_eq!(LegendreSymbol::Zero, Fq2::ZERO.legendre()); - // i^2 = -1 - let mut m1 = Fq2::ONE; - m1 = m1.neg(); - assert_eq!(LegendreSymbol::QuadraticResidue, m1.legendre()); - m1.mul_by_nonresidue(); - assert_eq!(LegendreSymbol::QuadraticNonResidue, m1.legendre()); -} - #[test] pub fn test_sqrt() { let mut rng = XorShiftRng::from_seed([ @@ -716,7 +716,7 @@ pub fn test_sqrt() { for _ in 0..10000 { let a = Fq2::random(&mut rng); - if a.legendre() == LegendreSymbol::QuadraticNonResidue { + if a.legendre() == -Fq::ONE { assert!(bool::from(a.sqrt().is_none())); } } @@ -725,7 +725,7 @@ pub fn test_sqrt() { let a = Fq2::random(&mut rng); let mut b = a; b.square_assign(); - assert_eq!(b.legendre(), LegendreSymbol::QuadraticResidue); + assert_eq!(b.legendre(), Fq::ONE); let b = b.sqrt().unwrap(); let mut negb = b; @@ -738,7 +738,7 @@ pub fn test_sqrt() { for _ in 0..10000 { let mut b = c; b.square_assign(); - assert_eq!(b.legendre(), LegendreSymbol::QuadraticResidue); + assert_eq!(b.legendre(), Fq::ONE); b = b.sqrt().unwrap(); diff --git a/src/bn256/fr.rs b/src/bn256/fr.rs index cd422d4b..8a57ff9f 100644 --- a/src/bn256/fr.rs +++ b/src/bn256/fr.rs @@ -1,7 +1,7 @@ #[cfg(feature = "asm")] use crate::bn256::assembly::field_arithmetic_asm; #[cfg(not(feature = "asm"))] -use crate::{field_arithmetic, field_specific}; +use crate::{arithmetic::macx, field_arithmetic, field_specific}; #[cfg(feature = "bn256-table")] #[rustfmt::skip] @@ -18,7 +18,7 @@ pub use table::FR_TABLE; #[cfg(not(feature = "bn256-table"))] use crate::impl_from_u64; -use crate::arithmetic::{adc, mac, macx, sbb}; +use crate::arithmetic::{adc, mac, sbb}; use crate::ff::{FromUniformBytes, PrimeField, WithSmallOrderMulGroup}; use crate::{ field_bits, field_common, impl_add_binop_specify_output, impl_binops_additive, @@ -166,6 +166,7 @@ field_common!( R3 ); impl_sum_prod!(Fr); +prime_field_legendre!(Fr); #[cfg(not(feature = "bn256-table"))] impl_from_u64!(Fr, R2); @@ -470,4 +471,9 @@ mod test { end_timer!(timer); } + + #[test] + fn test_quadratic_residue() { + crate::tests::field::random_quadratic_residue_test::(); + } } diff --git a/src/bn256/mod.rs b/src/bn256/mod.rs index 9cd08946..3530b765 100644 --- a/src/bn256/mod.rs +++ b/src/bn256/mod.rs @@ -16,10 +16,3 @@ pub use fq12::*; pub use fq2::*; pub use fq6::*; pub use fr::*; - -#[derive(Debug, PartialEq, Eq)] -pub enum LegendreSymbol { - Zero = 0, - QuadraticResidue = 1, - QuadraticNonResidue = -1, -} diff --git a/src/hash_to_curve.rs b/src/hash_to_curve.rs index 4cef7095..5d51ec75 100644 --- a/src/hash_to_curve.rs +++ b/src/hash_to_curve.rs @@ -5,6 +5,8 @@ use pasta_curves::arithmetic::CurveExt; use static_assertions::const_assert; use subtle::{ConditionallySelectable, ConstantTimeEq}; +use crate::legendre::Legendre; + /// Hashes over a message and writes the output to all of `buf`. /// Modified from https://github.com/zcash/pasta_curves/blob/7e3fc6a4919f6462a32b79dd226cb2587b7961eb/src/hashtocurve.rs#L11. fn hash_to_field>( @@ -94,6 +96,7 @@ pub(crate) fn svdw_map_to_curve( ) -> C where C: CurveExt, + C::Base: Legendre, { let one = C::Base::ONE; let a = C::a(); @@ -128,7 +131,7 @@ where // 14. gx1 = gx1 + B let gx1 = gx1 + b; // 15. e1 = is_square(gx1) - let e1 = gx1.sqrt().is_some(); + let e1 = !gx1.ct_quadratic_non_residue(); // 16. x2 = c2 + tv4 let x2 = c2 + tv4; // 17. gx2 = x2^2 @@ -140,7 +143,7 @@ where // 20. gx2 = gx2 + B let gx2 = gx2 + b; // 21. e2 = is_square(gx2) AND NOT e1 # Avoid short-circuit logic ops - let e2 = gx2.sqrt().is_some() & (!e1); + let e2 = !gx2.ct_quadratic_non_residue() & (!e1); // 22. x3 = tv2^2 let x3 = tv2.square(); // 23. x3 = x3 * tv3 @@ -182,7 +185,7 @@ pub(crate) fn svdw_hash_to_curve<'a, C>( ) -> Box C + 'a> where C: CurveExt, - C::Base: FromUniformBytes<64>, + C::Base: FromUniformBytes<64> + Legendre, { let [c1, c2, c3, c4] = svdw_precomputed_constants::(z); diff --git a/src/legendre.rs b/src/legendre.rs new file mode 100644 index 00000000..6f6fda17 --- /dev/null +++ b/src/legendre.rs @@ -0,0 +1,50 @@ +use ff::{Field, PrimeField}; +use subtle::{Choice, ConstantTimeEq}; + +pub trait Legendre: Field { + type BasePrimeField: PrimeField; + + // This is (p-1)/2 where p is the modulus of the base prime field + fn legendre_exp() -> &'static [u64]; + + fn norm(&self) -> Self::BasePrimeField; + + #[inline] + fn legendre(&self) -> Self::BasePrimeField { + self.norm().pow(Self::legendre_exp()) + } + + #[inline] + fn ct_quadratic_residue(&self) -> Choice { + self.legendre().ct_eq(&Self::BasePrimeField::ONE) + } + + #[inline] + fn ct_quadratic_non_residue(&self) -> Choice { + self.legendre().ct_eq(&-Self::BasePrimeField::ONE) + } +} + +#[macro_export] +macro_rules! prime_field_legendre { + ($field:ident ) => { + impl crate::legendre::Legendre for $field { + type BasePrimeField = Self; + + #[inline] + fn legendre_exp() -> &'static [u64] { + lazy_static::lazy_static! { + // (p-1) / 2 + static ref LEGENDRE_EXP: Vec = + (num_bigint::BigUint::from_bytes_le((-<$field as ff::Field>::ONE).to_repr().as_ref())/2usize).to_u64_digits(); + } + &*LEGENDRE_EXP + } + + #[inline] + fn norm(&self) -> Self::BasePrimeField { + self.clone() + } + } + }; +} diff --git a/src/lib.rs b/src/lib.rs index b75d7143..3fa8e98f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,7 @@ mod arithmetic; pub mod hash_to_curve; +#[macro_use] +pub mod legendre; pub mod serde; pub mod bn256; diff --git a/src/pasta/mod.rs b/src/pasta/mod.rs index 164697b5..078b663e 100644 --- a/src/pasta/mod.rs +++ b/src/pasta/mod.rs @@ -38,6 +38,9 @@ const ENDO_PARAMS_EP: EndoParameters = EndoParameters { endo!(Eq, Fp, ENDO_PARAMS_EQ); endo!(Ep, Fq, ENDO_PARAMS_EP); +prime_field_legendre!(Fp); +prime_field_legendre!(Fq); + #[test] fn test_endo() { use ff::Field; @@ -71,3 +74,9 @@ fn test_endo() { } } } + +#[test] +fn test_quadratic_residue() { + crate::tests::field::random_quadratic_residue_test::(); + crate::tests::field::random_quadratic_residue_test::(); +} diff --git a/src/secp256k1/fp.rs b/src/secp256k1/fp.rs index 01fecf84..f6a2a54b 100644 --- a/src/secp256k1/fp.rs +++ b/src/secp256k1/fp.rs @@ -295,6 +295,8 @@ impl WithSmallOrderMulGroup<3> for Fp { const ZETA: Self = ZETA; } +prime_field_legendre!(Fp); + #[cfg(test)] mod test { use super::*; @@ -367,4 +369,9 @@ mod test { #[cfg(feature = "derive_serde")] crate::tests::field::random_serde_test::("secp256k1 base".to_string()); } + + #[test] + fn test_quadratic_residue() { + crate::tests::field::random_quadratic_residue_test::(); + } } diff --git a/src/secp256k1/fq.rs b/src/secp256k1/fq.rs index d38dc517..304f5f10 100644 --- a/src/secp256k1/fq.rs +++ b/src/secp256k1/fq.rs @@ -302,6 +302,8 @@ impl WithSmallOrderMulGroup<3> for Fq { const ZETA: Self = ZETA; } +prime_field_legendre!(Fq); + #[cfg(test)] mod test { use super::*; @@ -374,4 +376,8 @@ mod test { #[cfg(feature = "derive_serde")] crate::tests::field::random_serde_test::("secp256k1 scalar".to_string()); } + #[test] + fn test_quadratic_residue() { + crate::tests::field::random_quadratic_residue_test::(); + } } diff --git a/src/secp256r1/fp.rs b/src/secp256r1/fp.rs index e26f19fc..228e4a67 100644 --- a/src/secp256r1/fp.rs +++ b/src/secp256r1/fp.rs @@ -313,6 +313,8 @@ impl WithSmallOrderMulGroup<3> for Fp { const ZETA: Self = ZETA; } +prime_field_legendre!(Fp); + #[cfg(test)] mod test { use super::*; @@ -385,4 +387,9 @@ mod test { #[cfg(feature = "derive_serde")] crate::tests::field::random_serde_test::("secp256r1 base".to_string()); } + + #[test] + fn test_quadratic_residue() { + crate::tests::field::random_quadratic_residue_test::(); + } } diff --git a/src/secp256r1/fq.rs b/src/secp256r1/fq.rs index 05fcf1fa..1b98761c 100644 --- a/src/secp256r1/fq.rs +++ b/src/secp256r1/fq.rs @@ -1,6 +1,5 @@ use crate::arithmetic::{adc, mac, macx, sbb}; use crate::ff::{FromUniformBytes, PrimeField, WithSmallOrderMulGroup}; -use core::convert::TryInto; use core::fmt; use core::ops::{Add, Mul, Neg, Sub}; use rand::RngCore; @@ -298,6 +297,8 @@ impl WithSmallOrderMulGroup<3> for Fq { const ZETA: Self = ZETA; } +prime_field_legendre!(Fq); + #[cfg(test)] mod test { use super::*; @@ -370,4 +371,9 @@ mod test { #[cfg(feature = "derive_serde")] crate::tests::field::random_serde_test::("secp256r1 scalar".to_string()); } + + #[test] + fn test_quadratic_residue() { + crate::tests::field::random_quadratic_residue_test::(); + } } diff --git a/src/tests/curve.rs b/src/tests/curve.rs index 9bb0fe0b..54d23791 100644 --- a/src/tests/curve.rs +++ b/src/tests/curve.rs @@ -2,6 +2,7 @@ use crate::ff::Field; use crate::group::prime::PrimeCurveAffine; +use crate::legendre::Legendre; use crate::tests::fe_from_str; use crate::{group::GroupEncoding, serde::SerdeObject}; use crate::{hash_to_curve, CurveAffine, CurveExt}; @@ -343,7 +344,9 @@ pub fn svdw_map_to_curve_test( z: G::Base, precomputed_constants: [&'static str; 4], test_vector: impl IntoIterator, -) { +) where + ::Base: Legendre, +{ let [c1, c2, c3, c4] = hash_to_curve::svdw_precomputed_constants::(z); assert_eq!([c1, c2, c3, c4], precomputed_constants.map(fe_from_str)); for (u, (x, y)) in test_vector.into_iter() { diff --git a/src/tests/field.rs b/src/tests/field.rs index a064441e..b04f801e 100644 --- a/src/tests/field.rs +++ b/src/tests/field.rs @@ -1,6 +1,7 @@ -use crate::ff::Field; use crate::serde::SerdeObject; +use crate::{ff::Field, legendre::Legendre}; use ark_std::{end_timer, start_timer}; +use ff::PrimeField; use rand::{RngCore, SeedableRng}; use rand_xorshift::XorShiftRng; @@ -287,3 +288,16 @@ where } end_timer!(start); } + +pub fn random_quadratic_residue_test() { + let mut rng = XorShiftRng::from_seed([ + 0x59, 0x62, 0xbe, 0x5d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06, 0xbc, + 0xe5, + ]); + for _ in 0..100000 { + let elem = F::random(&mut rng); + let is_quad_res_or_zero: bool = elem.sqrt().is_some().into(); + let is_quad_non_res: bool = elem.ct_quadratic_non_residue().into(); + assert_eq!(!is_quad_non_res, is_quad_res_or_zero) + } +}