From a738da480bd24a8fd26b8e45d4fb64615ea354f3 Mon Sep 17 00:00:00 2001 From: kilic Date: Tue, 26 Mar 2024 09:30:15 +0300 Subject: [PATCH] add hash to curve suite --- Cargo.toml | 3 + src/bn256/curve.rs | 34 ++- src/bn256/fq.rs | 67 +---- src/bn256/fq2.rs | 9 + src/bn256/fr.rs | 68 +---- src/bn256/mod.rs | 50 ---- src/derive/curve.rs | 2 +- src/derive/field.rs | 136 ++++----- src/grumpkin/curve.rs | 11 +- src/hash_to_curve.rs | 514 ++++++++++++++++++++++------------- src/pluto_eris/curve.rs | 56 +++- src/pluto_eris/fields/fp.rs | 109 +------- src/pluto_eris/fields/fq.rs | 110 +------- src/pluto_eris/fields/mod.rs | 26 +- src/secp256k1/curve.rs | 119 +++++++- src/secp256k1/fp.rs | 1 + src/secp256k1/fq.rs | 1 + src/secp256r1/curve.rs | 92 ++++++- src/secp256r1/fp.rs | 1 + src/secp256r1/fq.rs | 1 + src/secq256k1/curve.rs | 11 +- src/tests/curve.rs | 12 +- src/tests/field.rs | 56 ++-- src/tests/mod.rs | 33 +++ 24 files changed, 788 insertions(+), 734 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 22e2d429..18576ead 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,6 +43,9 @@ hex = { version = "0.4", optional = true, default-features = false, features = [ blake2b_simd = "1" rayon = "1.8" unroll = "0.1.5" +blake2 = "0.10.6" +sha2 = "0.10.8" +digest = "0.10.7" [features] default = ["bits"] diff --git a/src/bn256/curve.rs b/src/bn256/curve.rs index 4549183b..5200e30b 100644 --- a/src/bn256/curve.rs +++ b/src/bn256/curve.rs @@ -10,7 +10,6 @@ use crate::ff::WithSmallOrderMulGroup; use crate::ff::{Field, PrimeField}; use crate::group::Curve; use crate::group::{cofactor::CofactorGroup, prime::PrimeCurveAffine, Group, GroupEncoding}; -use crate::hash_to_curve::svdw_hash_to_curve; use crate::{ impl_add_binop_specify_output, impl_binops_additive, impl_binops_additive_specify_output, impl_binops_multiplicative, impl_binops_multiplicative_mixed, impl_sub_binop_specify_output, @@ -39,7 +38,7 @@ new_curve_impl!( G1_A, G1_B, "bn256_g1", - |curve_id, domain_prefix| svdw_hash_to_curve(curve_id, domain_prefix, G1::SVDW_Z), + |domain_prefix| crate::hash_to_curve::hash_to_curve(domain_prefix, G1::default_hash_to_curve_suite()), ); new_curve_impl!( @@ -53,9 +52,18 @@ new_curve_impl!( G2_A, G2_B, "bn256_g2", - |_, _| unimplemented!(), + |domain_prefix| hash_to_curve_g2(domain_prefix), ); +#[allow(clippy::type_complexity)] +pub(crate) fn hash_to_curve_g2<'a>(domain_prefix: &'a str) -> Box G2 + 'a> { + let suite = G2::default_hash_to_curve_suite(); + Box::new(move |message| { + let r0 = suite.hash_to_curve(domain_prefix, message); + r0.clear_cofactor() + }) +} + const G1_GENERATOR_X: Fq = Fq::one(); const G1_GENERATOR_Y: Fq = Fq::from_raw([2, 0, 0, 0]); const G1_A: Fq = Fq::from_raw([0, 0, 0, 0]); @@ -196,6 +204,26 @@ impl CofactorGroup for G2 { impl G1 { const SVDW_Z: Fq = Fq::ONE; + + fn default_hash_to_curve_suite() -> crate::hash_to_curve::Suite { + crate::hash_to_curve::Suite::::new( + b"bn256_g1_XMD:SHA-256_SVDW_RO_", + Self::SVDW_Z, + crate::hash_to_curve::Method::SVDW, + ) + } +} + +impl G2 { + const SVDW_Z: Fq2 = Fq2::ONE; + + fn default_hash_to_curve_suite() -> crate::hash_to_curve::Suite { + crate::hash_to_curve::Suite::::new( + b"bn256_g2_XMD:SHA-256_SVDW_RO_", + Self::SVDW_Z, + crate::hash_to_curve::Method::SVDW, + ) + } } #[cfg(test)] diff --git a/src/bn256/fq.rs b/src/bn256/fq.rs index 8da18bbd..96713400 100644 --- a/src/bn256/fq.rs +++ b/src/bn256/fq.rs @@ -294,70 +294,5 @@ mod test { crate::field_testing_suite!(Fq, "constants", MODULUS_STR); crate::field_testing_suite!(Fq, "sqrt"); crate::field_testing_suite!(Fq, "zeta"); - crate::field_testing_suite!( - Fq, - "from_uniform_bytes", - [ - Fq::from_raw([ - 0xd1f334151139642a, - 0xb0f28bcaa90fdb88, - 0x9a13255d88eca613, - 0x02afef300dd32d9a, - ]), - Fq::from_raw([ - 0x03cd906680808fbe, - 0xc28902db5aef5254, - 0x3dbdb406ae292ddf, - 0x276ec249e6b9e195 - ]), - Fq::from_raw([ - 0xb0e07ded189e91f7, - 0x9e3b0caae2b98899, - 0x49e511f19341fdcf, - 0x1ea71260f64b72da - ]), - Fq::from_raw([ - 0x61132be14bb978d4, - 0xe27e09a20808067b, - 0x3842cc8fd1d8406f, - 0x13163c8a13fd550b - ]), - Fq::from_raw([ - 0x04a6495a33d39ac5, - 0xc918e75bb383fae0, - 0x80068784d577b035, - 0x1dd962b86e44e1be - ]), - Fq::from_raw([ - 0x107ffeecf4cb3348, - 0x53a0adb5491a4944, - 0x50028f636ffcb780, - 0x0af7f3aa38015c1d - ]), - Fq::from_raw([ - 0x22513787342eba07, - 0x4fac22ed88770319, - 0x0b7c31082cc92b13, - 0x250e22a8cac6e790 - ]), - Fq::from_raw([ - 0x5954fd7dda014940, - 0x9df859b2124e66fa, - 0xaab48d94eadd9d14, - 0x2a9a75013e3da632 - ]), - Fq::from_raw([ - 0xedd59c88fee718de, - 0x2b034dcfe6de3844, - 0x76b0e2e360488694, - 0x068998ef20d62df1 - ]), - Fq::from_raw([ - 0xac161667911634a4, - 0x296c2f453152552f, - 0x2653625dfaa1cf74, - 0x171abf201a2587d7 - ]), - ] - ); + crate::field_testing_suite!(Fq, "from_uniform_bytes", 64); } diff --git a/src/bn256/fq2.rs b/src/bn256/fq2.rs index 96e2f000..8095cf2d 100644 --- a/src/bn256/fq2.rs +++ b/src/bn256/fq2.rs @@ -472,6 +472,15 @@ impl FromUniformBytes<64> for Fq2 { Self::new(Fq::from_uniform_bytes(bytes), Fq::zero()) } } + +impl FromUniformBytes<128> for Fq2 { + fn from_uniform_bytes(bytes: &[u8; 128]) -> Self { + let c0 = bytes[..64].try_into().unwrap(); + let c1 = bytes[64..].try_into().unwrap(); + Self::new(Fq::from_uniform_bytes(c0), Fq::from_uniform_bytes(c1)) + } +} + #[derive(Clone, Copy, Debug)] pub struct Fq2Bytes([u8; 64]); diff --git a/src/bn256/fr.rs b/src/bn256/fr.rs index 22029e3a..971b4522 100644 --- a/src/bn256/fr.rs +++ b/src/bn256/fr.rs @@ -332,6 +332,7 @@ impl WithSmallOrderMulGroup<3> for Fr { #[cfg(test)] mod test { + use super::*; crate::field_testing_suite!(Fr, "field_arithmetic"); crate::field_testing_suite!(Fr, "conversion"); @@ -342,72 +343,7 @@ mod test { crate::field_testing_suite!(Fr, "constants", MODULUS_STR); crate::field_testing_suite!(Fr, "sqrt"); crate::field_testing_suite!(Fr, "zeta"); - crate::field_testing_suite!( - Fr, - "from_uniform_bytes", - [ - Fr::from_raw([ - 0x2ca6366467811a07, - 0x22727e3db430ed7e, - 0xbdb79bcb97d9e250, - 0x2cee6d1152d1d7b0 - ]), - Fr::from_raw([ - 0x6ec33f1a3af8cb2d, - 0x2c8f3330e85dab4b, - 0xfeeff4ae1b019172, - 0x095cd2a455dd67b6 - ]), - Fr::from_raw([ - 0x4741eee9c02c9f33, - 0xfc0111dd8aeb7e7a, - 0xb1d79e2a22d4ab08, - 0x0cb7168893a7bbda - ]), - Fr::from_raw([ - 0xc2ff8410555287f8, - 0x0927fbea8c6049c8, - 0xc0edccc8e4d3efe4, - 0x1d724b76911436c4 - ]), - Fr::from_raw([ - 0xdef98bc8d4db6e5b, - 0x42f0ea50590d557e, - 0x1f311a3b8114fd9a, - 0x0487c555645c67b1 - ]), - Fr::from_raw([ - 0x8ad4879b05ceb610, - 0x2e4e9a46537c84b0, - 0x5cfa7c43c9dfcfa1, - 0x0b6b2a4d122d0bb6 - ]), - Fr::from_raw([ - 0xe7f11ee016df7fe7, - 0x6419da89bd8aef3d, - 0x3511f5d293af95c8, - 0x10379c1d4d49593a - ]), - Fr::from_raw([ - 0xd63080c8aa3ecd37, - 0x19c20f30b56fe458, - 0xc9dbbcb3aa780e06, - 0x28a4e2b8273762c6 - ]), - Fr::from_raw([ - 0xecea51b521eac0b8, - 0x65fff58a5881c562, - 0x603ac7d1e06ef3af, - 0x1e0c2c51226eecea - ]), - Fr::from_raw([ - 0xe6ec4779b8bd6516, - 0x0d5411f3cb9504ae, - 0xff706ec73df8e92a, - 0x2c56d60b3e351e56 - ]), - ] - ); + crate::field_testing_suite!(Fr, "from_uniform_bytes", 64); #[test] fn bench_fr_from_u16() { diff --git a/src/bn256/mod.rs b/src/bn256/mod.rs index 53c78273..3530b765 100644 --- a/src/bn256/mod.rs +++ b/src/bn256/mod.rs @@ -16,53 +16,3 @@ pub use fq12::*; pub use fq2::*; pub use fq6::*; pub use fr::*; - -#[cfg(test)] -mod test { - use super::G1 as Bn256Point; - use group::GroupEncoding; - use pasta_curves::arithmetic::CurveExt; - use rand_core::{RngCore, SeedableRng}; - - #[test] - fn test_consistent_hash_to_curve() { - // The goal of this test is to generate test vectors to ensure that the ASM implementation - // matches the rust implementation. - let num_vecs = 10; - - // Test vectors generated with rust implementation. - let expected_results = [ - "e0c5a6834e0329b4f8bdc91144b3e687ac9d810a8e899415267db9cfbf61e91e", - "7052a20bee99cbe054fdd8b2e336db3ed3e9a265229e44ab8197c5eabdef2b0b", - "2f058acc133957074ac79e9b9b1867a0cf3d13df7aa7de7f48e9a6be7d96aa6d", - "b2ff44a25693b811f35e33feb3e99ad9ba0d06425a3ffd5e79cef63d20143314", - "ab2f6d71d2fde51546d8a5782aa9f707e585b84644470f0c876784dbebd30c55", - "6a4e0e30f37a8d1b92b8cf08df3735a36b4937ee455a9dc5f9283a13530db144", - "f1c69be8c5f5f9e28b0e9f76ab77651a7dcaaae371fbba66450cbcee0ed5b16b", - "e86267c2e3355d7a6f664a0ea71374406337d452a3f9a294a0594df53c08df21", - "03cf55ca983ecd8a2e2baae18d979d97d688a978d829701c66a14d7c4da58e62", - "5302c2cfe3c909e9378d08c951bb33d0813818a1baf734379aac8aaa47f38f0d", - ]; - - let mut seeded_rng = rand_chacha::ChaChaRng::seed_from_u64(0u64); - let uniform_bytes = std::iter::from_fn(|| { - let mut bytes = [0u8; 32]; - seeded_rng.fill_bytes(&mut bytes); - Some(bytes) - }) - .take(num_vecs) - .collect::>(); - let hash = Bn256Point::hash_to_curve("from_uniform_bytes"); - for i in 0..num_vecs { - let p = hash(&uniform_bytes[i]); - let expected_result = hex::decode(expected_results[i]).unwrap(); - assert_eq!( - p.to_bytes().as_ref(), - &expected_result[..], - "hash_to_curve_print failed, expected: {}, got: {}", - expected_results[i], - hex::encode(p.to_bytes().as_ref()) - ); - } - } -} diff --git a/src/derive/curve.rs b/src/derive/curve.rs index 6aa3960c..ab541c11 100644 --- a/src/derive/curve.rs +++ b/src/derive/curve.rs @@ -532,7 +532,7 @@ macro_rules! new_curve_impl { #[allow(clippy::redundant_closure_call)] fn hash_to_curve<'a>(domain_prefix: &'a str) -> Box Self + 'a> { - $hash_to_curve($curve_id, domain_prefix) + $hash_to_curve(domain_prefix) } fn is_on_curve(&self) -> Choice { diff --git a/src/derive/field.rs b/src/derive/field.rs index a6518b02..e67b8289 100644 --- a/src/derive/field.rs +++ b/src/derive/field.rs @@ -68,83 +68,67 @@ macro_rules! field_common { // If `val` represents a 256 bit value then `r` should be R^2, // if `val` represents the 256 MSB of a 512 bit value, then `r` should be R^3. - #[cfg(feature = "asm")] - { - let (r0, carry) = mac(0, val[0], r.0[0], 0); - let (r1, carry) = mac(0, val[0], r.0[1], carry); - let (r2, carry) = mac(0, val[0], r.0[2], carry); - let (r3, r4) = mac(0, val[0], r.0[3], carry); - - let (r1, carry) = mac(r1, val[1], r.0[0], 0); - let (r2, carry) = mac(r2, val[1], r.0[1], carry); - let (r3, carry) = mac(r3, val[1], r.0[2], carry); - let (r4, r5) = mac(r4, val[1], r.0[3], carry); - - let (r2, carry) = mac(r2, val[2], r.0[0], 0); - let (r3, carry) = mac(r3, val[2], r.0[1], carry); - let (r4, carry) = mac(r4, val[2], r.0[2], carry); - let (r5, r6) = mac(r5, val[2], r.0[3], carry); - - let (r3, carry) = mac(r3, val[3], r.0[0], 0); - let (r4, carry) = mac(r4, val[3], r.0[1], carry); - let (r5, carry) = mac(r5, val[3], r.0[2], carry); - let (r6, r7) = mac(r6, val[3], r.0[3], carry); - - // Montgomery reduction - let k = r0.wrapping_mul($inv); - let (_, carry) = mac(r0, k, $modulus.0[0], 0); - let (r1, carry) = mac(r1, k, $modulus.0[1], carry); - let (r2, carry) = mac(r2, k, $modulus.0[2], carry); - let (r3, carry) = mac(r3, k, $modulus.0[3], carry); - let (r4, carry2) = adc(r4, 0, carry); - - let k = r1.wrapping_mul($inv); - let (_, carry) = mac(r1, k, $modulus.0[0], 0); - let (r2, carry) = mac(r2, k, $modulus.0[1], carry); - let (r3, carry) = mac(r3, k, $modulus.0[2], carry); - let (r4, carry) = mac(r4, k, $modulus.0[3], carry); - let (r5, carry2) = adc(r5, carry2, carry); - - let k = r2.wrapping_mul($inv); - let (_, carry) = mac(r2, k, $modulus.0[0], 0); - let (r3, carry) = mac(r3, k, $modulus.0[1], carry); - let (r4, carry) = mac(r4, k, $modulus.0[2], carry); - let (r5, carry) = mac(r5, k, $modulus.0[3], carry); - let (r6, carry2) = adc(r6, carry2, carry); - - let k = r3.wrapping_mul($inv); - let (_, carry) = mac(r3, k, $modulus.0[0], 0); - let (r4, carry) = mac(r4, k, $modulus.0[1], carry); - let (r5, carry) = mac(r5, k, $modulus.0[2], carry); - let (r6, carry) = mac(r6, k, $modulus.0[3], carry); - let (r7, carry2) = adc(r7, carry2, carry); - - // Result may be within MODULUS of the correct value - let (d0, borrow) = sbb(r4, $modulus.0[0], 0); - let (d1, borrow) = sbb(r5, $modulus.0[1], borrow); - let (d2, borrow) = sbb(r6, $modulus.0[2], borrow); - let (d3, borrow) = sbb(r7, $modulus.0[3], borrow); - let (_, borrow) = sbb(carry2, 0, borrow); - let (d0, carry) = adc(d0, $modulus.0[0] & borrow, 0); - let (d1, carry) = adc(d1, $modulus.0[1] & borrow, carry); - let (d2, carry) = adc(d2, $modulus.0[2] & borrow, carry); - let (d3, _) = adc(d3, $modulus.0[3] & borrow, carry); - - $field([d0, d1, d2, d3]) - } + let (r0, carry) = mac(0, val[0], r.0[0], 0); + let (r1, carry) = mac(0, val[0], r.0[1], carry); + let (r2, carry) = mac(0, val[0], r.0[2], carry); + let (r3, r4) = mac(0, val[0], r.0[3], carry); + + let (r1, carry) = mac(r1, val[1], r.0[0], 0); + let (r2, carry) = mac(r2, val[1], r.0[1], carry); + let (r3, carry) = mac(r3, val[1], r.0[2], carry); + let (r4, r5) = mac(r4, val[1], r.0[3], carry); + + let (r2, carry) = mac(r2, val[2], r.0[0], 0); + let (r3, carry) = mac(r3, val[2], r.0[1], carry); + let (r4, carry) = mac(r4, val[2], r.0[2], carry); + let (r5, r6) = mac(r5, val[2], r.0[3], carry); + + let (r3, carry) = mac(r3, val[3], r.0[0], 0); + let (r4, carry) = mac(r4, val[3], r.0[1], carry); + let (r5, carry) = mac(r5, val[3], r.0[2], carry); + let (r6, r7) = mac(r6, val[3], r.0[3], carry); + + // Montgomery reduction + let k = r0.wrapping_mul($inv); + let (_, carry) = mac(r0, k, $modulus.0[0], 0); + let (r1, carry) = mac(r1, k, $modulus.0[1], carry); + let (r2, carry) = mac(r2, k, $modulus.0[2], carry); + let (r3, carry) = mac(r3, k, $modulus.0[3], carry); + let (r4, carry2) = adc(r4, 0, carry); - #[cfg(not(feature = "asm"))] - { - let mut val = val; - if bigint_geq(&val, &$modulus.0) { - let mut borrow = 0; - (val[0], borrow) = sbb(val[0], $modulus.0[0], borrow); - (val[1], borrow) = sbb(val[1], $modulus.0[1], borrow); - (val[2], borrow) = sbb(val[2], $modulus.0[2], borrow); - (val[3], _) = sbb(val[3], $modulus.0[3], borrow); - } - $field::mul(&$field(val), &r) - } + let k = r1.wrapping_mul($inv); + let (_, carry) = mac(r1, k, $modulus.0[0], 0); + let (r2, carry) = mac(r2, k, $modulus.0[1], carry); + let (r3, carry) = mac(r3, k, $modulus.0[2], carry); + let (r4, carry) = mac(r4, k, $modulus.0[3], carry); + let (r5, carry2) = adc(r5, carry2, carry); + + let k = r2.wrapping_mul($inv); + let (_, carry) = mac(r2, k, $modulus.0[0], 0); + let (r3, carry) = mac(r3, k, $modulus.0[1], carry); + let (r4, carry) = mac(r4, k, $modulus.0[2], carry); + let (r5, carry) = mac(r5, k, $modulus.0[3], carry); + let (r6, carry2) = adc(r6, carry2, carry); + + let k = r3.wrapping_mul($inv); + let (_, carry) = mac(r3, k, $modulus.0[0], 0); + let (r4, carry) = mac(r4, k, $modulus.0[1], carry); + let (r5, carry) = mac(r5, k, $modulus.0[2], carry); + let (r6, carry) = mac(r6, k, $modulus.0[3], carry); + let (r7, carry2) = adc(r7, carry2, carry); + + // Result may be within MODULUS of the correct value + let (d0, borrow) = sbb(r4, $modulus.0[0], 0); + let (d1, borrow) = sbb(r5, $modulus.0[1], borrow); + let (d2, borrow) = sbb(r6, $modulus.0[2], borrow); + let (d3, borrow) = sbb(r7, $modulus.0[3], borrow); + let (_, borrow) = sbb(carry2, 0, borrow); + let (d0, carry) = adc(d0, $modulus.0[0] & borrow, 0); + let (d1, carry) = adc(d1, $modulus.0[1] & borrow, carry); + let (d2, carry) = adc(d2, $modulus.0[2] & borrow, carry); + let (d3, _) = adc(d3, $modulus.0[3] & borrow, carry); + + $field([d0, d1, d2, d3]) } fn from_u512(limbs: [u64; 8]) -> $field { diff --git a/src/grumpkin/curve.rs b/src/grumpkin/curve.rs index f0ddf86a..842f96b5 100644 --- a/src/grumpkin/curve.rs +++ b/src/grumpkin/curve.rs @@ -8,7 +8,6 @@ use crate::group::Curve; use crate::group::{prime::PrimeCurveAffine, Group, GroupEncoding}; use crate::grumpkin::Fq; use crate::grumpkin::Fr; -use crate::hash_to_curve::svdw_hash_to_curve; use crate::{ endo, impl_add_binop_specify_output, impl_binops_additive, impl_binops_additive_specify_output, impl_binops_multiplicative, impl_binops_multiplicative_mixed, impl_sub_binop_specify_output, @@ -36,7 +35,7 @@ new_curve_impl!( G1_A, G1_B, "grumpkin_g1", - |curve_id, domain_prefix| svdw_hash_to_curve(curve_id, domain_prefix, G1::SVDW_Z), + |domain_prefix| crate::hash_to_curve::hash_to_curve(domain_prefix, G1::default_hash_to_curve_suite()), ); // Parameters in montgomery form taken from @@ -87,6 +86,14 @@ impl group::cofactor::CofactorGroup for G1 { impl G1 { const SVDW_Z: Fq = Fq::ONE; + + fn default_hash_to_curve_suite() -> crate::hash_to_curve::Suite { + crate::hash_to_curve::Suite::::new( + b"grumpkin_g1_XMD:SHA-256_SVDW_RO_", + Self::SVDW_Z, + crate::hash_to_curve::Method::SVDW, + ) + } } #[cfg(test)] diff --git a/src/hash_to_curve.rs b/src/hash_to_curve.rs index b4d19b86..993001de 100644 --- a/src/hash_to_curve.rs +++ b/src/hash_to_curve.rs @@ -1,99 +1,211 @@ #![allow(clippy::op_ref)] +use crate::ff_ext::Legendre; +use digest::{core_api::BlockSizeUser, Digest}; use ff::{Field, FromUniformBytes, PrimeField}; use pasta_curves::arithmetic::CurveExt; -use static_assertions::const_assert; use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}; -use crate::{ - ff_ext::Legendre, - secp256k1::{iso_map_secp256k1, IsoSecp256k1, Secp256k1}, -}; - -/// 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>( - method: &str, - curve_id: &str, - domain_prefix: &str, +pub enum Method { + SSWU, + SVDW, +} + +pub struct Suite { + domain: Vec, + map_to_curve: Box C>, + _marker: std::marker::PhantomData, +} + +pub(crate) fn expand_message( + domain_prefix: &[u8], + domain: &[u8], message: &[u8], - buf: &mut [F; 2], -) { - assert!(domain_prefix.len() < 256); - assert!((18 + method.len() + curve_id.len() + domain_prefix.len()) < 256); - - // Assume that the field size is 32 bytes and k is 256, where k is defined in - // . - const CHUNKLEN: usize = 64; - const_assert!(CHUNKLEN * 2 < 256); - - // Input block size of BLAKE2b. - const R_IN_BYTES: usize = 128; - - let personal = [0u8; 16]; - let empty_hasher = blake2b_simd::Params::new() - .hash_length(CHUNKLEN) - .personal(&personal) - .to_state(); - - let b_0 = empty_hasher - .clone() - .update(&[0; R_IN_BYTES]) - .update(message) - .update(&[0, (CHUNKLEN * 2) as u8, 0]) - .update(domain_prefix.as_bytes()) - .update(b"-") - .update(curve_id.as_bytes()) - .update(b"_XMD:BLAKE2b_") - .update(method.as_bytes()) - .update(b"_RO_") - .update(&[(18 + method.len() + curve_id.len() + domain_prefix.len()) as u8]) - .finalize(); - - let b_1 = empty_hasher - .clone() - .update(b_0.as_array()) - .update(&[1]) - .update(domain_prefix.as_bytes()) - .update(b"-") - .update(curve_id.as_bytes()) - .update(b"_XMD:BLAKE2b_") - .update(method.as_bytes()) - .update(b"_RO_") - .update(&[(18 + method.len() + curve_id.len() + domain_prefix.len()) as u8]) - .finalize(); - - let b_2 = { - let mut empty_hasher = empty_hasher; - for (l, r) in b_0.as_array().iter().zip(b_1.as_array().iter()) { - empty_hasher.update(&[*l ^ *r]); + out_len: usize, +) -> Vec { + assert!( + domain_prefix.len() + domain.len() < 256, + "long dst is not supported yet" + ); + assert!(out_len < 256, "larger output length is not supported yet"); + + let mut h = D::new(); + h.update(vec![0; D::block_size()]); + h.update(message); + h.update([0, out_len as u8, 0]); + h.update(domain_prefix); + h.update(domain); + h.update([(domain.len() + domain_prefix.len()) as u8]); + let b_0 = h.finalize(); + + let mut h = D::new(); + h.update(&b_0); + h.update([1]); + h.update(domain_prefix); + h.update(domain); + h.update([(domain.len() + domain_prefix.len()) as u8]); + let mut b_i = h.finalize(); + + let output_size = ::output_size(); + let ell = (out_len + output_size - 1) / output_size; + let mut out = vec![0u8; out_len]; + + for i in 1..ell { + let mut h = D::new(); + b_0.iter() + .zip(b_i.iter()) + .for_each(|(b_0, b_i)| h.update([*b_0 ^ *b_i])); + h.update([1 + i as u8]); + h.update(domain_prefix); + h.update(domain); + h.update([(domain.len() + domain_prefix.len()) as u8]); + + out.iter_mut() + .skip((i - 1) * output_size) + .zip(b_i.iter()) + .for_each(|(out, b_i)| *out = *b_i); + + b_i = h.finalize(); + } + + out.iter_mut() + .skip((ell - 1) * output_size) + .zip(b_i.iter()) + .for_each(|(out, b_i)| *out = *b_i); + + out +} + +#[allow(clippy::type_complexity)] +pub fn hash_to_curve<'a, C, D: Digest + BlockSizeUser + 'a, const L: usize>( + domain_prefix: &'a str, + suite: Suite, +) -> Box C + 'a> +where + C: CurveExt, + C::Base: Legendre, + C::Base: FromUniformBytes, +{ + Box::new(move |message| suite.hash_to_curve(domain_prefix, message)) +} + +impl Suite +where + C::Base: Legendre + FromUniformBytes, +{ + pub(crate) fn new(domain: &[u8], z: C::Base, method: Method) -> Self { + // check minimum target security + // L = ceil((ceil(log2(p)) + k) / 8) + assert!((C::Base::NUM_BITS as usize + 128) / 8 <= L); + + // let shifter = find_shifter(modulus, byte_size); + let map_to_curve: Box C> = match method { + Method::SSWU => Box::new(move |u| sswu_map_to_curve::(u, z)), + Method::SVDW => { + let [c1, c2, c3, c4] = svdw_precomputed_constants::(z); + Box::new(move |u| svdw_map_to_curve::(u, c1, c2, c3, c4, z)) + } + }; + + Self { + map_to_curve, + domain: domain.to_vec(), + _marker: std::marker::PhantomData, } - empty_hasher - .update(&[2]) - .update(domain_prefix.as_bytes()) - .update(b"-") - .update(curve_id.as_bytes()) - .update(b"_XMD:BLAKE2b_") - .update(method.as_bytes()) - .update(b"_RO_") - .update(&[(18 + method.len() + curve_id.len() + domain_prefix.len()) as u8]) - .finalize() - }; + } + + pub(crate) fn hash_to_field(&self, domain_prefix: &[u8], message: &[u8]) -> (C::Base, C::Base) { + let out = expand_message::(domain_prefix, &self.domain[..], message, L * 2); + + let u0 = { + let mut out = out[0..L].to_vec(); + out.reverse(); + let out: [u8; L] = out.try_into().unwrap(); + C::Base::from_uniform_bytes(&out) + }; + + let u1 = { + let mut out = out[L..L * 2].to_vec(); + out.reverse(); + let out: [u8; L] = out.try_into().unwrap(); + C::Base::from_uniform_bytes(&out) + }; + + (u0, u1) + } - for (big, buf) in [b_1, b_2].iter().zip(buf.iter_mut()) { - let mut little = [0u8; CHUNKLEN]; - little.copy_from_slice(big.as_array()); - little.reverse(); - *buf = F::from_uniform_bytes(&little); + pub fn hash_to_curve(&self, domain_prefix: &str, message: &[u8]) -> C { + let (u0, u1) = self.hash_to_field(domain_prefix.as_bytes(), message); + (self.map_to_curve)(u0) + (self.map_to_curve)(u1) } } +pub(crate) fn svdw_precomputed_constants(z: C::Base) -> [C::Base; 4] { + let a = C::a(); + let b = C::b(); + let one = C::Base::ONE; + let three = one + one + one; + let four = three + one; + let tmp = three * z.square() + four * a; + + // 1. c1 = g(Z) + let c1 = (z.square() + a) * z + b; + // 2. c2 = -Z / 2 + let c2 = -z * C::Base::TWO_INV; + // 3. c3 = sqrt(-g(Z) * (3 * Z^2 + 4 * A)) # sgn0(c3) MUST equal 0 + let c3 = { + let c3 = (-c1 * tmp).sqrt().unwrap(); + C::Base::conditional_select(&c3, &-c3, c3.is_odd()) + }; + // 4. c4 = -4 * g(Z) / (3 * Z^2 + 4 * A) + let c4 = -four * c1 * tmp.invert().unwrap(); + + [c1, c2, c3, c4] +} + // Implementation of #[allow(clippy::too_many_arguments)] pub(crate) fn sswu_map_to_curve(u: C::Base, z: C::Base) -> C where C: CurveExt, { + // Implement https://datatracker.ietf.org/doc/html/rfc9380#name-sqrt_ratio-for-any-field + // Copied from ff sqrt_ratio_generic substituting F::ROOT_OF_UNITY for input Z + fn sqrt_ratio(num: &F, div: &F, z: &F) -> (Choice, F) { + // General implementation: + // + // a = num * inv0(div) + // = { 0 if div is zero + // { num/div otherwise + // + // b = z * a + // = { 0 if div is zero + // { z*num/div otherwise + + // Since z is non-square, a and b are either both zero (and both square), or + // only one of them is square. We can therefore choose the square root to return + // based on whether a is square, but for the boolean output we need to handle the + // num != 0 && div == 0 case specifically. + + let a = div.invert().unwrap_or(F::ZERO) * num; + let b = a * z; + let sqrt_a = a.sqrt(); + let sqrt_b = b.sqrt(); + + let num_is_zero = num.is_zero(); + let div_is_zero = div.is_zero(); + let is_square = sqrt_a.is_some(); + let is_nonsquare = sqrt_b.is_some(); + assert!(bool::from( + num_is_zero | div_is_zero | (is_square ^ is_nonsquare) + )); + + ( + is_square & (num_is_zero | !div_is_zero), + CtOption::conditional_select(&sqrt_b, &sqrt_a, is_square).unwrap(), + ) + } + let zero = C::Base::ZERO; let one = C::Base::ONE; let a = C::a(); @@ -154,45 +266,6 @@ where C::new_jacobian(x, y, one).unwrap() } -// Implementation of -#[allow(clippy::type_complexity)] -pub(crate) fn sswu_hash_to_curve<'a, C>( - curve_id: &'static str, - domain_prefix: &'a str, - z: C::Base, -) -> Box C + 'a> -where - C: CurveExt, - C::Base: FromUniformBytes<64>, -{ - Box::new(move |message| { - let mut us = [C::Base::ZERO; 2]; - hash_to_field("SSWU", curve_id, domain_prefix, message, &mut us); - - let [q0, q1]: [C; 2] = us.map(|u| sswu_map_to_curve::(u, z)); - - let r = q0 + &q1; - debug_assert!(bool::from(r.is_on_curve())); - r - }) -} - -// Implementation of -#[allow(clippy::type_complexity)] -pub(crate) fn sswu_hash_to_curve_secp256k1<'a>( - _curve_id: &'static str, - domain_prefix: &'a str, -) -> Box Secp256k1 + 'a> { - Box::new(move |message| { - let rp = IsoSecp256k1::hash_to_curve(domain_prefix)(message); - - let r = iso_map_secp256k1(rp); - - debug_assert!(bool::from(r.is_on_curve())); - r - }) -} - #[allow(clippy::too_many_arguments)] pub(crate) fn svdw_map_to_curve( u: C::Base, @@ -284,87 +357,136 @@ where C::new_jacobian(x, y, one).unwrap() } -// Implement https://datatracker.ietf.org/doc/html/rfc9380#name-sqrt_ratio-for-any-field -// Copied from ff sqrt_ratio_generic substituting F::ROOT_OF_UNITY for input Z -fn sqrt_ratio(num: &F, div: &F, z: &F) -> (Choice, F) { - // General implementation: - // - // a = num * inv0(div) - // = { 0 if div is zero - // { num/div otherwise - // - // b = z * a - // = { 0 if div is zero - // { z*num/div otherwise - - // Since z is non-square, a and b are either both zero (and both square), or - // only one of them is square. We can therefore choose the square root to return - // based on whether a is square, but for the boolean output we need to handle the - // num != 0 && div == 0 case specifically. - - let a = div.invert().unwrap_or(F::ZERO) * num; - let b = a * z; - let sqrt_a = a.sqrt(); - let sqrt_b = b.sqrt(); - - let num_is_zero = num.is_zero(); - let div_is_zero = div.is_zero(); - let is_square = sqrt_a.is_some(); - let is_nonsquare = sqrt_b.is_some(); - assert!(bool::from( - num_is_zero | div_is_zero | (is_square ^ is_nonsquare) - )); - - ( - is_square & (num_is_zero | !div_is_zero), - CtOption::conditional_select(&sqrt_b, &sqrt_a, is_square).unwrap(), - ) -} - -/// Implementation of https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-10.html#section-6.6.1 -#[allow(clippy::type_complexity)] -pub(crate) fn svdw_hash_to_curve<'a, C>( - curve_id: &'static str, - domain_prefix: &'a str, - z: C::Base, -) -> Box C + 'a> -where - C: CurveExt, - C::Base: FromUniformBytes<64> + Legendre, -{ - let [c1, c2, c3, c4] = svdw_precomputed_constants::(z); - - Box::new(move |message| { - let mut us = [C::Base::ZERO; 2]; - hash_to_field("SVDW", curve_id, domain_prefix, message, &mut us); +#[cfg(test)] +mod test { - let [q0, q1]: [C; 2] = us.map(|u| svdw_map_to_curve(u, c1, c2, c3, c4, z)); + use super::*; + use sha2::Sha256; + use sha2::Sha512; + use std::marker::PhantomData; - let r = q0 + &q1; - debug_assert!(bool::from(r.is_on_curve())); - r - }) -} + #[test] + fn test_expand_message() { + // https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-10.html#name-expand_message_xmdsha-256-2 -pub(crate) fn svdw_precomputed_constants(z: C::Base) -> [C::Base; 4] { - let a = C::a(); - let b = C::b(); - let one = C::Base::ONE; - let three = one + one + one; - let four = three + one; - let tmp = three * z.square() + four * a; - - // 1. c1 = g(Z) - let c1 = (z.square() + a) * z + b; - // 2. c2 = -Z / 2 - let c2 = -z * C::Base::TWO_INV; - // 3. c3 = sqrt(-g(Z) * (3 * Z^2 + 4 * A)) # sgn0(c3) MUST equal 0 - let c3 = { - let c3 = (-c1 * tmp).sqrt().unwrap(); - C::Base::conditional_select(&c3, &-c3, c3.is_odd()) - }; - // 4. c4 = -4 * g(Z) / (3 * Z^2 + 4 * A) - let c4 = -four * c1 * tmp.invert().unwrap(); + struct Test { + msg: &'static [u8], + expect: Vec, + _marker: PhantomData, + } - [c1, c2, c3, c4] + impl Test { + fn new(msg: &'static [u8], expect: &str) -> Self { + Self { + msg, + expect: crate::tests::hex_to_bytes(expect), + _marker: PhantomData, + } + } + + fn run(&self, domain_prefix: &[u8], domain: &[u8]) { + let outlen = self.expect.len(); + let out = expand_message::(domain_prefix, domain, self.msg, outlen); + assert_eq!(out, self.expect); + } + } + [ + // out len 0x20 + Test::::new( + b"", + "68a985b87eb6b46952128911f2a4412bbc302a9d759667f87f7a21d803f07235", + ), + Test::::new( + b"abc", + "d8ccab23b5985ccea865c6c97b6e5b8350e794e603b4b97902f53a8a0d605615", + ), + Test::::new( + b"abcdef0123456789", + "eff31487c770a893cfb36f912fbfcbff40d5661771ca4b2cb4eafe524333f5c1", + ), + Test::::new( + b"q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq", + "b23a1d2b4d97b2ef7785562a7e8bac7eed54ed6e97e29aa51bfe3f12ddad1ff9", + ), + Test::::new( + b"a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "4623227bcc01293b8c130bf771da8c298dede7383243dc0993d2d94823958c4c", + ), + // out len 0x80 + Test::::new( + b"", + "af84c27ccfd45d41914fdff5df25293e221afc53d8ad2ac06d5e3e29485dadbee0d121587713a3e0dd4d5e69e93eb7cd4f5df4cd103e188cf60cb02edc3edf18eda8576c412b18ffb658e3dd6ec849469b979d444cf7b26911a08e63cf31f9dcc541708d3491184472c2c29bb749d4286b004ceb5ee6b9a7fa5b646c993f0ced", + ), + Test::::new( + b"abc", + "abba86a6129e366fc877aab32fc4ffc70120d8996c88aee2fe4b32d6c7b6437a647e6c3163d40b76a73cf6a5674ef1d890f95b664ee0afa5359a5c4e07985635bbecbac65d747d3d2da7ec2b8221b17b0ca9dc8a1ac1c07ea6a1e60583e2cb00058e77b7b72a298425cd1b941ad4ec65e8afc50303a22c0f99b0509b4c895f40", + ), + Test::::new( + b"abcdef0123456789", + "ef904a29bffc4cf9ee82832451c946ac3c8f8058ae97d8d629831a74c6572bd9ebd0df635cd1f208e2038e760c4994984ce73f0d55ea9f22af83ba4734569d4bc95e18350f740c07eef653cbb9f87910d833751825f0ebefa1abe5420bb52be14cf489b37fe1a72f7de2d10be453b2c9d9eb20c7e3f6edc5a60629178d9478df", + ), + Test::::new( + b"q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq", + "80be107d0884f0d881bb460322f0443d38bd222db8bd0b0a5312a6fedb49c1bbd88fd75d8b9a09486c60123dfa1d73c1cc3169761b17476d3c6b7cbbd727acd0e2c942f4dd96ae3da5de368d26b32286e32de7e5a8cb2949f866a0b80c58116b29fa7fabb3ea7d520ee603e0c25bcaf0b9a5e92ec6a1fe4e0391d1cdbce8c68a", + ), + Test::::new( + b"a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "546aff5444b5b79aa6148bd81728704c32decb73a3ba76e9e75885cad9def1d06d6792f8a7d12794e90efed817d96920d728896a4510864370c207f99bd4a608ea121700ef01ed879745ee3e4ceef777eda6d9e5e38b90c86ea6fb0b36504ba4a45d22e86f6db5dd43d98a294bebb9125d5b794e9d2a81181066eb954966a487", + ), + + ] + .iter() + .for_each(|test| { + test.run(b"QUUX-V01-CS02-with-expander-",b"SHA256-128"); + }); + + [ + // out len 0x20 + Test::::new( + b"", + "6b9a7312411d92f921c6f68ca0b6380730a1a4d982c507211a90964c394179ba", + ), + Test::::new( + b"abc", + "0da749f12fbe5483eb066a5f595055679b976e93abe9be6f0f6318bce7aca8dc", + ), + Test::::new( + b"abcdef0123456789", + "087e45a86e2939ee8b91100af1583c4938e0f5fc6c9db4b107b83346bc967f58", + ), + Test::::new( + b"q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq", + "7336234ee9983902440f6bc35b348352013becd88938d2afec44311caf8356b3", + ), + Test::::new( + b"a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "57b5f7e766d5be68a6bfe1768e3c2b7f1228b3e4b3134956dd73a59b954c66f4", + ), + // out len 0x80 + Test::::new( + b"", + "41b037d1734a5f8df225dd8c7de38f851efdb45c372887be655212d07251b921b052b62eaed99b46f72f2ef4cc96bfaf254ebbbec091e1a3b9e4fb5e5b619d2e0c5414800a1d882b62bb5cd1778f098b8eb6cb399d5d9d18f5d5842cf5d13d7eb00a7cff859b605da678b318bd0e65ebff70bec88c753b159a805d2c89c55961", + ), + Test::::new( + b"abc", + "7f1dddd13c08b543f2e2037b14cefb255b44c83cc397c1786d975653e36a6b11bdd7732d8b38adb4a0edc26a0cef4bb45217135456e58fbca1703cd6032cb1347ee720b87972d63fbf232587043ed2901bce7f22610c0419751c065922b488431851041310ad659e4b23520e1772ab29dcdeb2002222a363f0c2b1c972b3efe1", + ), + Test::::new( + b"abcdef0123456789", + "3f721f208e6199fe903545abc26c837ce59ac6fa45733f1baaf0222f8b7acb0424814fcb5eecf6c1d38f06e9d0a6ccfbf85ae612ab8735dfdf9ce84c372a77c8f9e1c1e952c3a61b7567dd0693016af51d2745822663d0c2367e3f4f0bed827feecc2aaf98c949b5ed0d35c3f1023d64ad1407924288d366ea159f46287e61ac", + ), + Test::::new( + b"q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq", + "b799b045a58c8d2b4334cf54b78260b45eec544f9f2fb5bd12fb603eaee70db7317bf807c406e26373922b7b8920fa29142703dd52bdf280084fb7ef69da78afdf80b3586395b433dc66cde048a258e476a561e9deba7060af40adf30c64249ca7ddea79806ee5beb9a1422949471d267b21bc88e688e4014087a0b592b695ed", + ), + Test::::new( + b"a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "05b0bfef265dcee87654372777b7c44177e2ae4c13a27f103340d9cd11c86cb2426ffcad5bd964080c2aee97f03be1ca18e30a1f14e27bc11ebbd650f305269cc9fb1db08bf90bfc79b42a952b46daf810359e7bc36452684784a64952c343c52e5124cd1f71d474d5197fefc571a92929c9084ffe1112cf5eea5192ebff330b", + ), + ] + .iter() + .for_each(|test| { + test.run(b"QUUX-V01-CS02-with-expander-", b"SHA512-256"); + }); + } } diff --git a/src/pluto_eris/curve.rs b/src/pluto_eris/curve.rs index 9b86c69c..7173c3dd 100644 --- a/src/pluto_eris/curve.rs +++ b/src/pluto_eris/curve.rs @@ -2,12 +2,12 @@ use super::fields::{fp::Fp, fp2::Fp2, fq::Fq}; use crate::ff::WithSmallOrderMulGroup; use crate::ff::{Field, PrimeField}; use crate::group::{prime::PrimeCurveAffine, Curve, Group as _, GroupEncoding}; -use crate::hash_to_curve::svdw_hash_to_curve; use crate::{Coordinates, CurveAffine, CurveExt}; use core::cmp; use core::fmt::Debug; use core::iter::Sum; use core::ops::{Add, Mul, Neg, Sub}; +use ff::FromUniformBytes; use group::cofactor::CofactorGroup; use rand::RngCore; use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}; @@ -131,7 +131,7 @@ new_curve_impl!( PLUTO_A, PLUTO_B, "pluto", - |curve_id, domain_prefix| svdw_hash_to_curve(curve_id, domain_prefix, G1::SVDW_Z), + |domain_prefix| crate::hash_to_curve::hash_to_curve(domain_prefix, G1::default_hash_to_curve_suite()), ); impl group::cofactor::CofactorGroup for Eris { @@ -154,6 +154,30 @@ impl G1 { /// Constant Z for the Shallue-van de Woestijne map. /// Computed using https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-10.html#svdw-z-code const SVDW_Z: Fp = Fp::ONE; + + fn default_hash_to_curve_suite() -> crate::hash_to_curve::Suite { + crate::hash_to_curve::Suite::::new( + b"pluto_XMD:SHA-256_SVDW_RO_", + Self::SVDW_Z, + crate::hash_to_curve::Method::SVDW, + ) + } +} + +impl FromUniformBytes<72> for Fp { + fn from_uniform_bytes(bytes: &[u8; 72]) -> Self { + let repr = &mut [0u8; Self::size()]; + + (*repr)[0..36].copy_from_slice(&bytes[..36]); + let e0 = Fp::from_repr((*repr).into()).unwrap(); + (*repr)[0..36].copy_from_slice(&bytes[36..]); + let e1 = Fp::from_repr((*repr).into()).unwrap(); + + // 2^(36*8) + const SHIFTER: Fp = Fp::from_raw([0, 0, 0, 0, 0x100000000, 0, 0]); + + e0 + e1 * SHIFTER + } } new_curve_impl!( @@ -167,7 +191,7 @@ new_curve_impl!( ERIS_A, ERIS_B, "eris", - |curve_id, domain_prefix| svdw_hash_to_curve(curve_id, domain_prefix, Eris::SVDW_Z), + |domain_prefix| crate::hash_to_curve::hash_to_curve(domain_prefix, Eris::default_hash_to_curve_suite()), ); impl CofactorGroup for G2 { @@ -226,6 +250,30 @@ impl Eris { /// Constant Z for the Shallue-van de Woestijne map. /// Computed using https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-10.html#svdw-z-code const SVDW_Z: Fq = Fq::ONE; + + fn default_hash_to_curve_suite() -> crate::hash_to_curve::Suite { + crate::hash_to_curve::Suite::::new( + b"eris_XMD:SHA-256_SVDW_RO_", + Self::SVDW_Z, + crate::hash_to_curve::Method::SVDW, + ) + } +} + +impl FromUniformBytes<72> for Fq { + fn from_uniform_bytes(bytes: &[u8; 72]) -> Self { + let repr = &mut [0u8; Self::size()]; + + (*repr)[0..36].copy_from_slice(&bytes[..36]); + let e0 = Fq::from_repr((*repr).into()).unwrap(); + (*repr)[0..36].copy_from_slice(&bytes[36..]); + let e1 = Fq::from_repr((*repr).into()).unwrap(); + + // 2^(36*8) + const SHIFTER: Fq = Fq::from_raw([0, 0, 0, 0, 0x100000000, 0, 0]); + + e0 + e1 * SHIFTER + } } new_curve_impl!( @@ -239,7 +287,7 @@ new_curve_impl!( TRITON_A, TRITON_B, "triton", - |_, _| unimplemented!(), + |_| unimplemented!(), ); #[cfg(test)] diff --git a/src/pluto_eris/fields/fp.rs b/src/pluto_eris/fields/fp.rs index a7ff3588..94b6c2e0 100644 --- a/src/pluto_eris/fields/fp.rs +++ b/src/pluto_eris/fields/fp.rs @@ -86,18 +86,6 @@ const R2: Fp = Fp([ 0x1a4b16581f66e3cc, ]); -/// `R^3 = 2^1792 mod p` -/// `0x1f51e40a048ddc1789010189f4df0ae1f3bc57efac4b3280b25aa8b46a40b225e5446680e4c4ea0449937d6b40e58f05c67afa3fe916dd69` -const R3: Fp = Fp([ - 0xc67afa3fe916dd69, - 0x49937d6b40e58f05, - 0xe5446680e4c4ea04, - 0xb25aa8b46a40b225, - 0xf3bc57efac4b3280, - 0x89010189f4df0ae1, - 0x1f51e40a048ddc17, -]); - /// `GENERATOR = 10 mod p` is a generator of the `p - 1` order multiplicative /// subgroup, or in other words a primitive root of the field. const GENERATOR: Fp = Fp::from_raw([0x0a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); @@ -411,100 +399,5 @@ mod test { crate::field_testing_suite!(Fp, "constants", MODULUS_STR); crate::field_testing_suite!(Fp, "sqrt"); crate::field_testing_suite!(Fp, "zeta"); - crate::field_testing_suite!( - Fp, - "from_uniform_bytes", - [ - Fp::from_raw([ - 0x93638251ffeffed3, - 0x4d32f4d20020be11, - 0x9c39ee168df390f0, - 0xaeef355d313cce4b, - 0xc97c592ef6030675, - 0xd7bc83d286537318, - 0x01d4a87b24f91154, - ]), - Fp::from_raw([ - 0x63e0a8f1beefc612, - 0xbb28d56dae950a42, - 0x5264111f4a5ea3ad, - 0xbebe71c829f662f7, - 0xa760708568d6060c, - 0x617d8b0cda3f6328, - 0x03096ea964e009c0, - ]), - Fp::from_raw([ - 0xdeaedbda63b3e431, - 0x65892bc45ec174c8, - 0x83ad8d96c18556c7, - 0x3fce5f9d2c537fbe, - 0x001666753a4972d1, - 0x9f7f457a48d6d322, - 0x20b2fadc6bf4004d, - ]), - Fp::from_raw([ - 0x6eea9cbd68b174cf, - 0x63aa4abda18f73e6, - 0x0a6ccc999b1c7864, - 0x0f90b43928625cc2, - 0x55f541b0680af76b, - 0x2045de539849b035, - 0x1d5d7b5f6e8cc333, - ]), - Fp::from_raw([ - 0x673df0f69b71a763, - 0x215a1362cfe53e1e, - 0x7028d2b3766b0f40, - 0x996ac521f57a7f05, - 0x5006663a5c8cea53, - 0xd7ead2b7c71e460d, - 0x0f7c36b781cba9ed, - ]), - Fp::from_raw([ - 0x2eed10e8f00b189d, - 0xe6c79fb4600e94d4, - 0x2a9066b23daac6d4, - 0x476d275780b553fe, - 0xc3f2296317f71051, - 0xb1d2bb5373270c43, - 0x0e18a3597be61302, - ]), - Fp::from_raw([ - 0x7fbbc6b3e494ca68, - 0x2afcc7335152430b, - 0x93d5bd3acbccf3b3, - 0x61a76bb383622b8c, - 0x93efc4d40d7fac4d, - 0x0a791ad7698655a7, - 0x22b10d5c1090eec8, - ]), - Fp::from_raw([ - 0x596eec60211ad67b, - 0xf23f57b9f9db8c07, - 0x33e66f105ffc5e45, - 0xb10ef45226f3ae42, - 0xb98a559ccfc0ba32, - 0x819ba919d0b6e9b5, - 0x20f73876330a90e8, - ]), - Fp::from_raw([ - 0xbade57a48e2d9868, - 0xe61829ffe983fcfc, - 0xd0d080b774c31996, - 0xa1d712ef206b4a2f, - 0x7957f20173071cf6, - 0xf850f49359458652, - 0x17ba9f9aa08b9ee2, - ]), - Fp::from_raw([ - 0xd0239c8282ccc372, - 0xfa20a695ee8f6288, - 0x269f2ef315e029a5, - 0xcc915da35e10b4e6, - 0x8406f6977aadce0f, - 0xd7d5d8bc4497465a, - 0x08ce8bee1323d4f9, - ]), - ] - ); + crate::field_testing_suite!(Fp, "from_uniform_bytes", 64); } diff --git a/src/pluto_eris/fields/fq.rs b/src/pluto_eris/fields/fq.rs index 782ca241..675f46e8 100644 --- a/src/pluto_eris/fields/fq.rs +++ b/src/pluto_eris/fields/fq.rs @@ -84,18 +84,6 @@ const R2: Fq = Fq([ 0x050d7c998f46144e, ]); -/// `R^3 = 2^1792 mod q` -/// `0x2f2c41fb476072baa10b8225e69f7de3b2c1031e6d01279e65191fab1f6ce25295c3c8bd6945406c89b51b218477a6f7252704d7495b38a` -const R3: Fq = Fq([ - 0x7252704d7495b38a, - 0xc89b51b218477a6f, - 0x295c3c8bd6945406, - 0xe65191fab1f6ce25, - 0x3b2c1031e6d01279, - 0xaa10b8225e69f7de, - 0x02f2c41fb476072b, -]); - /// `GENERATOR = 7 mod q` is a generator of the `q - 1` order multiplicative /// subgroup, or in other words a primitive root of the field. const GENERATOR: Fq = Fq::from_raw([0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); @@ -402,100 +390,6 @@ mod test { crate::field_testing_suite!(Fq, "constants", MODULUS_STR); crate::field_testing_suite!(Fq, "sqrt"); crate::field_testing_suite!(Fq, "zeta"); - crate::field_testing_suite!( - Fq, - "from_uniform_bytes", - [ - Fq::from_raw([ - 0x93638251ffeffed3, - 0xb17ab6ae332352b4, - 0xbf2731af91057325, - 0x7b700ef5a22260d0, - 0xc97c59318d325250, - 0xd7bc83d286537318, - 0x01d4a87b24f91154, - ]), - Fq::from_raw([ - 0x63e0a8f1beefc612, - 0x080f69572a9ddaae, - 0xb9ff1cf0e1f7c067, - 0xd8d8bf5b522bc48b, - 0xa7607085c7065359, - 0x617d8b0cda3f6328, - 0x03096ea964e009c0, - ]), - Fq::from_raw([ - 0x5eaedbda63b3e431, - 0x90ebbfa6f11a9266, - 0x4528cf4d506c9f9b, - 0x8c6ac679e9ac3856, - 0x001666755d9c2c57, - 0x9f7f457a48d6d322, - 0x20b2fadc6bf4004d, - ]), - Fq::from_raw([ - 0xeeea9cbd68b174cf, - 0x84af9e4ce5a781a5, - 0x3578772b5b482647, - 0x6b202eb54b7df723, - 0x55f541b1436b7660, - 0x2045de539849b035, - 0x1d5d7b5f6e8cc333, - ]), - Fq::from_raw([ - 0xe73df0f69b71a763, - 0xbccfb84010979d9d, - 0x1ce3c87be8bf3247, - 0x695fde61877cb617, - 0x5006663bd0944209, - 0xd7ead2b7c71e460d, - 0x0f7c36b781cba9ed, - ]), - Fq::from_raw([ - 0xaeed10e8f00b189d, - 0x5190807038915743, - 0x90b840c0a13b0307, - 0x20fa8cc52c3a9a28, - 0xc3f229646be29c1d, - 0xb1d2bb5373270c43, - 0x0e18a3597be61302, - ]), - Fq::from_raw([ - 0xffbbc6b3e494ca68, - 0x30d4a100158c1751, - 0x0328dae560dff403, - 0x1495c3ce50cce340, - 0x93efc4d4d6ea0079, - 0x0a791ad7698655a7, - 0x22b10d5c1090eec8, - ]), - Fq::from_raw([ - 0xd96eec60211ad67b, - 0x4d081a969b3d8488, - 0x57c9b5abbeec4cf0, - 0x13ced15637e4b0eb, - 0xb98a559f49b0071c, - 0x819ba919d0b6e9b5, - 0x20f73876330a90e8, - ]), - Fq::from_raw([ - 0xbade57a48e2d9868, - 0xc688e43e21f9d2fc, - 0x848a82da9e1d75dc, - 0xae5f4536b9d60aa7, - 0x7957f2028c96467b, - 0xf850f49359458652, - 0x17ba9f9aa08b9ee2, - ]), - Fq::from_raw([ - 0xd0239c8282ccc372, - 0x4a777ad0b66181ea, - 0x53737d5f19e61bfc, - 0x5340b579fe7c4c83, - 0x8406f69a0f89f90a, - 0xd7d5d8bc4497465a, - 0x08ce8bee1323d4f9, - ]), - ] - ); + // crate::field_testing_suite!(Fq, "from_uniform_bytes", 64, 72); + crate::field_testing_suite!(Fq, "from_uniform_bytes", 64); } diff --git a/src/pluto_eris/fields/mod.rs b/src/pluto_eris/fields/mod.rs index 2ed86a6b..b30aff24 100644 --- a/src/pluto_eris/fields/mod.rs +++ b/src/pluto_eris/fields/mod.rs @@ -55,25 +55,13 @@ macro_rules! field_common_7_limbs { } fn from_u512(limbs: [u64; 8]) -> $field { - // We reduce an arbitrary 512-bit number by decomposing it into two 256-bit digits - // with the higher bits multiplied by 2^256. Thus, we perform two reductions - // - // 1. the lower bits are multiplied by R^2, as normal - // 2. the upper bits are multiplied by R^2 * 2^256 = R^3 - // - // and computing their sum in the field. It remains to see that arbitrary 256-bit - // numbers can be placed into Montgomery form safely using the reduction. The - // reduction works so long as the product is less than R=2^256 multiplied by - // the modulus. This holds because for any `c` smaller than the modulus, we have - // that (2^256 - 1)*c is an acceptable product for the reduction. Therefore, the - // reduction always works so long as `c` is in the field; in this case it is either the - // constant `R2` or `R3`. - let d0 = $field([ - limbs[0], limbs[1], limbs[2], limbs[3], limbs[4], limbs[5], limbs[6], - ]); - let d1 = $field([limbs[7], 0u64, 0u64, 0u64, 0u64, 0u64, 0u64]); - // Convert to Montgomery form - d0 * $r2 + d1 * $r3 + let d0 = $field([limbs[0], limbs[1], limbs[2], limbs[3], 0, 0, 0]); + let d1 = $field([limbs[4], limbs[5], limbs[6], limbs[7], 0, 0, 0]); + + // 2^256 + let shifter = $field([0, 0, 0, 0, 1, 0, 0]); + + (d0 + d1 * $r2 * shifter) * $r2 } /// Converts from an integer represented in little endian diff --git a/src/secp256k1/curve.rs b/src/secp256k1/curve.rs index a8bf5bca..b2d5566e 100644 --- a/src/secp256k1/curve.rs +++ b/src/secp256k1/curve.rs @@ -1,7 +1,6 @@ use crate::ff::WithSmallOrderMulGroup; use crate::ff::{Field, PrimeField}; use crate::group::{prime::PrimeCurveAffine, Curve, Group as _, GroupEncoding}; -use crate::hash_to_curve::{sswu_hash_to_curve, sswu_hash_to_curve_secp256k1}; use crate::secp256k1::Fp; use crate::secp256k1::Fq; use crate::{Coordinates, CurveAffine, CurveExt}; @@ -9,6 +8,8 @@ use core::cmp; use core::fmt::Debug; use core::iter::Sum; use core::ops::{Add, Mul, Neg, Sub}; +use ff::FromUniformBytes; +use group::cofactor::CofactorGroup; use rand::RngCore; use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}; @@ -65,7 +66,7 @@ new_curve_impl!( SECP_A, SECP_B, "secp256k1", - |curve_id, domain_prefix| sswu_hash_to_curve_secp256k1(curve_id, domain_prefix), + |domain_prefix| hash_to_curve(domain_prefix), ); impl Secp256k1 { @@ -80,6 +81,15 @@ impl Secp256k1 { ]); } +#[allow(clippy::type_complexity)] +pub(crate) fn hash_to_curve<'a>(domain_prefix: &'a str) -> Box Secp256k1 + 'a> { + Box::new(move |message| { + let r0 = IsoSecp256k1::hash_to_curve(domain_prefix)(message); + let r1 = iso_map_secp256k1(r0); + r1.clear_cofactor() + }) +} + // Simplified SWU for AB == 0 // // E': y'^2 = x'^3 + A' * x' + B', where @@ -134,7 +144,7 @@ new_curve_impl!( ISO_SECP_A, ISO_SECP_B, "secp256k1", - |curve_id, domain_prefix| sswu_hash_to_curve(curve_id, domain_prefix, IsoSecp256k1::SSWU_Z), + |domain_prefix| crate::hash_to_curve::hash_to_curve(domain_prefix, IsoSecp256k1::default_hash_to_curve_suite()), ); impl IsoSecp256k1 { @@ -147,6 +157,47 @@ impl IsoSecp256k1 { 0xffffffffffffffff, 0xffffffffffffffff, ]); + + fn default_hash_to_curve_suite() -> crate::hash_to_curve::Suite + { + crate::hash_to_curve::Suite::::new( + b"secp256k1_XMD:SHA-256_SSWU_RO_", + Self::SSWU_Z, + crate::hash_to_curve::Method::SSWU, + ) + } +} + +impl FromUniformBytes<48> for Fp { + fn from_uniform_bytes(bytes: &[u8; 48]) -> Self { + let repr = &mut [0u8; Self::size()]; + + (*repr)[0..24].copy_from_slice(&bytes[..24]); + let e0 = Fp::from_repr(*repr).unwrap(); + (*repr)[0..24].copy_from_slice(&bytes[24..]); + let e1 = Fp::from_repr(*repr).unwrap(); + + // 2^192 + const SHIFTER: Fp = Fp::from_raw([0, 0, 0, 1]); + + e0 + e1 * SHIFTER + } +} + +impl FromUniformBytes<48> for Fq { + fn from_uniform_bytes(bytes: &[u8; 48]) -> Self { + let repr = &mut [0u8; Self::size()]; + + (*repr)[0..24].copy_from_slice(&bytes[..24]); + let e0 = Fq::from_repr(*repr).unwrap(); + (*repr)[0..24].copy_from_slice(&bytes[24..]); + let e1 = Fq::from_repr(*repr).unwrap(); + + // 2^192 + const SHIFTER: Fq = Fq::from_raw([0, 0, 0, 1]); + + e0 + e1 * SHIFTER + } } /// 3-Isogeny Map for Secp256k1 @@ -284,4 +335,66 @@ mod test { SECP_GENERATOR_Y, Fq::MODULUS ); + + #[test] + fn test_hash_to_curve() { + struct Test { + msg: &'static [u8], + expect: C, + } + + impl Test { + fn new(msg: &'static [u8], expect: C) -> Self { + Self { msg, expect } + } + + fn run(&self, domain_prefix: &str) { + // default + let r0 = C::CurveExt::hash_to_curve(domain_prefix)(self.msg); + assert_eq!(r0.to_affine(), self.expect); + } + } + + let tests = [ + Test::::new( + b"", + crate::tests::point_from_hex( + "c1cae290e291aee617ebaef1be6d73861479c48b841eaba9b7b5852ddfeb1346", + "64fa678e07ae116126f08b022a94af6de15985c996c3a91b64c406a960e51067", + ), + ), + Test::::new( + b"abc", + crate::tests::point_from_hex( + "3377e01eab42db296b512293120c6cee72b6ecf9f9205760bd9ff11fb3cb2c4b", + "7f95890f33efebd1044d382a01b1bee0900fb6116f94688d487c6c7b9c8371f6", + ), + ), + Test::::new( + b"abcdef0123456789", + crate::tests::point_from_hex( + "bac54083f293f1fe08e4a70137260aa90783a5cb84d3f35848b324d0674b0e3a", + "4436476085d4c3c4508b60fcf4389c40176adce756b398bdee27bca19758d828", + ), + ), + Test::::new( + b"q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq", + crate::tests::point_from_hex( + "e2167bc785333a37aa562f021f1e881defb853839babf52a7f72b102e41890e9", + "f2401dd95cc35867ffed4f367cd564763719fbc6a53e969fb8496a1e6685d873", + ), + ), // + Test::::new( + b"a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + crate::tests::point_from_hex( + "e3c8d35aaaf0b9b647e88a0a0a7ee5d5bed5ad38238152e4e6fd8c1f8cb7c998", + "8446eeb6181bf12f56a9d24e262221cc2f0c4725c7e3803024b5888ee5823aa6", + ), + ), + ]; + + tests.iter().for_each(|test| { + test.run("QUUX-V01-CS02-with-"); + }); + } } diff --git a/src/secp256k1/fp.rs b/src/secp256k1/fp.rs index 415017fa..9045f0cf 100644 --- a/src/secp256k1/fp.rs +++ b/src/secp256k1/fp.rs @@ -302,4 +302,5 @@ mod test { crate::field_testing_suite!(Fp, "constants", MODULUS_STR); crate::field_testing_suite!(Fp, "sqrt"); crate::field_testing_suite!(Fp, "zeta"); + crate::field_testing_suite!(Fp, "from_uniform_bytes", 48, 64); } diff --git a/src/secp256k1/fq.rs b/src/secp256k1/fq.rs index ba7351ca..c9296639 100644 --- a/src/secp256k1/fq.rs +++ b/src/secp256k1/fq.rs @@ -309,4 +309,5 @@ mod test { crate::field_testing_suite!(Fq, "constants", MODULUS_STR); crate::field_testing_suite!(Fq, "sqrt"); crate::field_testing_suite!(Fq, "zeta"); + crate::field_testing_suite!(Fq, "from_uniform_bytes", 48, 64); } diff --git a/src/secp256r1/curve.rs b/src/secp256r1/curve.rs index fbd1c538..c5a48464 100644 --- a/src/secp256r1/curve.rs +++ b/src/secp256r1/curve.rs @@ -1,7 +1,6 @@ use crate::ff::WithSmallOrderMulGroup; use crate::ff::{Field, PrimeField}; use crate::group::{prime::PrimeCurveAffine, Curve, Group as _, GroupEncoding}; -use crate::hash_to_curve::sswu_hash_to_curve; use crate::secp256r1::Fp; use crate::secp256r1::Fq; use crate::{Coordinates, CurveAffine, CurveExt}; @@ -9,6 +8,7 @@ use core::cmp; use core::fmt::Debug; use core::iter::Sum; use core::ops::{Add, Mul, Neg, Sub}; +use ff::FromUniformBytes; use rand::RngCore; use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}; @@ -76,19 +76,43 @@ new_curve_impl!( SECP_A, SECP_B, "secp256r1", - |curve_id, domain_prefix| sswu_hash_to_curve(curve_id, domain_prefix, Secp256r1::SSVDW_Z), + |domain_prefix| crate::hash_to_curve::hash_to_curve(domain_prefix, Secp256r1::default_hash_to_curve_suite()), ); impl Secp256r1 { // Optimal Z with: // 0xffffffff00000001000000000000000000000000fffffffffffffffffffffff5 // Z = -10 (reference: ) - const SSVDW_Z: Fp = Fp::from_raw([ + const SSWU_Z: Fp = Fp::from_raw([ 0xfffffffffffffff5, 0x00000000ffffffff, 0x0000000000000000, 0xffffffff00000001, ]); + + fn default_hash_to_curve_suite() -> crate::hash_to_curve::Suite { + crate::hash_to_curve::Suite::::new( + b"P256_XMD:SHA-256_SSWU_RO_", + Self::SSWU_Z, + crate::hash_to_curve::Method::SSWU, + ) + } +} + +impl FromUniformBytes<48> for Fp { + fn from_uniform_bytes(bytes: &[u8; 48]) -> Self { + let repr = &mut [0u8; Self::size()]; + + (*repr)[0..24].copy_from_slice(&bytes[..24]); + let e0 = Fp::from_repr(*repr).unwrap(); + (*repr)[0..24].copy_from_slice(&bytes[24..]); + let e1 = Fp::from_repr(*repr).unwrap(); + + // 2^192 + const SHIFTER: Fp = Fp::from_raw([0, 0, 0, 1]); + + e0 + e1 * SHIFTER + } } #[cfg(test)] @@ -106,4 +130,66 @@ mod test { SECP_GENERATOR_Y, Fq::MODULUS ); + + #[test] + fn test_hash_to_curve() { + struct Test { + msg: &'static [u8], + expect: C, + } + + impl Test { + fn new(msg: &'static [u8], expect: C) -> Self { + Self { msg, expect } + } + + fn run(&self, domain_prefix: &str) { + // default + let r0 = C::CurveExt::hash_to_curve(domain_prefix)(self.msg); + assert_eq!(r0.to_affine(), self.expect); + } + } + + let tests = [ + Test::::new( + b"", + crate::tests::point_from_hex( + "2c15230b26dbc6fc9a37051158c95b79656e17a1a920b11394ca91c44247d3e4", + "8a7a74985cc5c776cdfe4b1f19884970453912e9d31528c060be9ab5c43e8415", + ), + ), + Test::::new( + b"abc", + crate::tests::point_from_hex( + "0bb8b87485551aa43ed54f009230450b492fead5f1cc91658775dac4a3388a0f", + "5c41b3d0731a27a7b14bc0bf0ccded2d8751f83493404c84a88e71ffd424212e", + ), + ), + Test::::new( + b"abcdef0123456789", + crate::tests::point_from_hex( + "65038ac8f2b1def042a5df0b33b1f4eca6bff7cb0f9c6c1526811864e544ed80", + "cad44d40a656e7aff4002a8de287abc8ae0482b5ae825822bb870d6df9b56ca3", + ), + ), + Test::::new( + b"q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq", + crate::tests::point_from_hex( + "4be61ee205094282ba8a2042bcb48d88dfbb609301c49aa8b078533dc65a0b5d", + "98f8df449a072c4721d241a3b1236d3caccba603f916ca680f4539d2bfb3c29e", + ), + ), // + Test::::new( + b"a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + crate::tests::point_from_hex( + "457ae2981f70ca85d8e24c308b14db22f3e3862c5ea0f652ca38b5e49cd64bc5", + "ecb9f0eadc9aeed232dabc53235368c1394c78de05dd96893eefa62b0f4757dc", + ), + ), + ]; + + tests.iter().for_each(|test| { + test.run("QUUX-V01-CS02-with-"); + }); + } } diff --git a/src/secp256r1/fp.rs b/src/secp256r1/fp.rs index bce80808..30aa9353 100644 --- a/src/secp256r1/fp.rs +++ b/src/secp256r1/fp.rs @@ -320,4 +320,5 @@ mod test { crate::field_testing_suite!(Fp, "constants", MODULUS_STR); crate::field_testing_suite!(Fp, "sqrt"); crate::field_testing_suite!(Fp, "zeta"); + crate::field_testing_suite!(Fp, "from_uniform_bytes", 48, 64); } diff --git a/src/secp256r1/fq.rs b/src/secp256r1/fq.rs index c6f1dd86..2d965fb1 100644 --- a/src/secp256r1/fq.rs +++ b/src/secp256r1/fq.rs @@ -308,4 +308,5 @@ mod test { crate::field_testing_suite!(Fq, "constants", MODULUS_STR); crate::field_testing_suite!(Fq, "sqrt"); crate::field_testing_suite!(Fq, "zeta"); + crate::field_testing_suite!(Fq, "from_uniform_bytes", 64); } diff --git a/src/secq256k1/curve.rs b/src/secq256k1/curve.rs index 726eae8a..d1d16af9 100644 --- a/src/secq256k1/curve.rs +++ b/src/secq256k1/curve.rs @@ -2,7 +2,6 @@ use crate::ff::WithSmallOrderMulGroup; use crate::ff::{Field, PrimeField}; use crate::group::Curve; use crate::group::{prime::PrimeCurveAffine, Group, GroupEncoding}; -use crate::hash_to_curve::svdw_hash_to_curve; use crate::secp256k1::{Fp, Fq}; use crate::{ impl_add_binop_specify_output, impl_binops_additive, impl_binops_additive_specify_output, @@ -48,7 +47,7 @@ new_curve_impl!( SECQ_A, SECQ_B, "secq256k1", - |curve_id, domain_prefix| svdw_hash_to_curve(curve_id, domain_prefix, Secq256k1::SVDW_Z), + |domain_prefix| crate::hash_to_curve::hash_to_curve(domain_prefix, Secq256k1::default_hash_to_curve_suite()), ); impl group::cofactor::CofactorGroup for Secq256k1 { @@ -69,6 +68,14 @@ impl group::cofactor::CofactorGroup for Secq256k1 { impl Secq256k1 { const SVDW_Z: Fq = Fq::ONE; + + fn default_hash_to_curve_suite() -> crate::hash_to_curve::Suite { + crate::hash_to_curve::Suite::::new( + b"secq256k1_XMD:SHA-256_SWDW_RO_", + Self::SVDW_Z, + crate::hash_to_curve::Method::SVDW, + ) + } } #[cfg(test)] diff --git a/src/tests/curve.rs b/src/tests/curve.rs index 390ea5de..e8620a28 100644 --- a/src/tests/curve.rs +++ b/src/tests/curve.rs @@ -321,10 +321,10 @@ macro_rules! curve_testing_suite { } } - use crate::ff::Field; - use crate::group::prime::PrimeCurveAffine; - use crate::{group::GroupEncoding, serde::SerdeObject}; - use crate::{CurveAffine, CurveExt}; + use $crate::ff::Field; + use $crate::group::prime::PrimeCurveAffine; + use $crate::{group::GroupEncoding, serde::SerdeObject}; + use $crate::{CurveAffine, CurveExt}; use rand_core::OsRng; #[test] @@ -483,8 +483,8 @@ macro_rules! curve_testing_suite { ($curve: ident, "svdw_map_to_curve", ($precomputed_constants: expr, $test_vector: expr)) => { #[test] fn test_map_to_curve() { - use crate::ff_ext::Legendre; - use crate::{hash_to_curve, CurveAffine, CurveExt}; + use $crate::ff_ext::Legendre; + use $crate::{hash_to_curve, CurveAffine, CurveExt}; use ff::PrimeField; use num_bigint::BigUint; use num_traits::Num; diff --git a/src/tests/field.rs b/src/tests/field.rs index 95c91ed2..5a62eb0b 100644 --- a/src/tests/field.rs +++ b/src/tests/field.rs @@ -1,3 +1,6 @@ +use ff::{FromUniformBytes, PrimeField}; +use rand::RngCore; + #[macro_export] macro_rules! field_testing_suite { ($field: ident, "field_arithmetic") => { @@ -280,7 +283,7 @@ macro_rules! field_testing_suite { #[test] fn test_serialization() { - use crate::serde::SerdeObject; + use $crate::serde::SerdeObject; random_serialization_test!($field); #[cfg(feature = "derive_serde")] random_serde_test!($field); @@ -290,7 +293,7 @@ macro_rules! field_testing_suite { ($field: ident, "quadratic_residue") => { #[test] fn test_quadratic_residue() { - use crate::ff_ext::Legendre; + use $crate::ff_ext::Legendre; use ff::Field; use rand_core::SeedableRng; use rand_xorshift::XorShiftRng; @@ -344,7 +347,7 @@ macro_rules! field_testing_suite { #[test] fn test_serialization_check() { - use crate::serde::SerdeObject; + use $crate::serde::SerdeObject; let mut rng = XorShiftRng::from_seed([ 0x59, 0x62, 0xbe, 0x5d, 0x76, 0x3d, 0x31, 0x8d, 0x17, 0xdb, 0x37, 0x32, 0x54, 0x06, 0xbc, 0xe5, @@ -392,7 +395,7 @@ macro_rules! field_testing_suite { ($field: ident, "sqrt") => { #[test] fn test_sqrt() { - use crate::ff_ext::Legendre; + use $crate::ff_ext::Legendre; use rand_core::OsRng; let v = ($field::TWO_INV).square().sqrt().unwrap(); @@ -449,25 +452,13 @@ macro_rules! field_testing_suite { } }; - ($field: ident, "from_uniform_bytes", $test_vectors: expr) => { + ($field: ident, "from_uniform_bytes", $($L:expr),* $(,)?) => { + #[test] fn test_from_uniform_bytes() { - const N_VECS: usize = 10; - assert!($test_vectors.len() == N_VECS); - - let mut seeded_rng = XorShiftRng::seed_from_u64(0u64); - let uniform_bytes = std::iter::from_fn(|| { - let mut bytes = [0u8; 64]; - seeded_rng.fill_bytes(&mut bytes); - Some(bytes) - }) - .take(N_VECS) - .collect::>(); - - for i in 0..N_VECS { - let q = $field::from_uniform_bytes(&uniform_bytes[i]); - assert_eq!($test_vectors[i], q); - } + $( + $crate::tests::field::run_test_from_uniform_bytes::<$field, $L>(); + )* } }; @@ -733,3 +724,26 @@ macro_rules! field_testing_suite { } }; } + +pub(crate) fn run_test_from_uniform_bytes() +where + F: FromUniformBytes, +{ + use num_bigint::BigUint; + use rand_core::OsRng; + + let mut uniform_bytes = [0u8; L]; + OsRng.fill_bytes(&mut uniform_bytes[..]); + + let e0 = { + let e0 = BigUint::from_bytes_le(&uniform_bytes); + let e0 = e0 % crate::tests::modulus::(); + let bytes = e0.to_bytes_le(); + let mut e0 = F::Repr::default(); + e0.as_mut()[..bytes.len()].copy_from_slice(&bytes); + F::from_repr(e0).unwrap() + }; + + let e1 = F::from_uniform_bytes(&uniform_bytes); + assert_eq!(e0, e1); +} diff --git a/src/tests/mod.rs b/src/tests/mod.rs index f773c8d7..e0fff697 100644 --- a/src/tests/mod.rs +++ b/src/tests/mod.rs @@ -1,2 +1,35 @@ +use ff::PrimeField; +use num_bigint::BigUint; +use pasta_curves::arithmetic::CurveAffine; + pub mod curve; pub mod field; + +pub(crate) fn hex_to_bytes(hex: &str) -> Vec { + hex.as_bytes() + .chunks(2) + .map(|chunk| u8::from_str_radix(std::str::from_utf8(chunk).unwrap(), 16).unwrap()) + .collect() +} + +pub(crate) fn hex_to_field(hex: &str) -> F { + let bytes = hex_to_bytes(hex); + let mut repr = F::Repr::default(); + repr.as_mut().copy_from_slice(&bytes); + repr.as_mut().reverse(); + F::from_repr(repr).unwrap() +} + +pub(crate) fn point_from_hex(x: &str, y: &str) -> C { + let x = crate::tests::hex_to_field(x); + let y = crate::tests::hex_to_field(y); + C::from_xy(x, y).unwrap() +} + +pub(crate) fn fe_to_big(fe: &F) -> BigUint { + BigUint::from_bytes_le(fe.to_repr().as_ref()) +} + +pub(crate) fn modulus() -> BigUint { + fe_to_big(&-F::ONE) + 1usize +}