diff --git a/Cargo.toml b/Cargo.toml index 22e2d429..34882b1d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,9 +40,11 @@ paste = "1.0.11" serde = { version = "1.0", default-features = false, optional = true } serde_arrays = { version = "0.1.0", optional = true } hex = { version = "0.4", optional = true, default-features = false, features = ["alloc", "serde"] } -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 5989c9f6..3367fb14 100644 --- a/src/bn256/curve.rs +++ b/src/bn256/curve.rs @@ -11,7 +11,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!( @@ -52,9 +51,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]); @@ -146,24 +154,65 @@ impl CofactorGroup for G2 { type Subgroup = G2; fn clear_cofactor(&self) -> Self { - // "0x30644e72e131a029b85045b68181585e06ceecda572a2489345f2299c0f9fa8d" - let e: [u8; 32] = [ - 0x30, 0x64, 0x4e, 0x72, 0xe1, 0x31, 0xa0, 0x29, 0xb8, 0x50, 0x45, 0xb6, 0x81, 0x81, - 0x58, 0x5e, 0x06, 0xce, 0xec, 0xda, 0x57, 0x2a, 0x24, 0x89, 0x34, 0x5f, 0x22, 0x99, - 0xc0, 0xf9, 0xfa, 0x8d, - ]; + fn exp_by_x(g2: &G2) -> G2 { + let x = super::BN_X; + let mut res = G2::identity(); + for i in (0..64).rev() { + res = res.double(); + if ((x >> i) & 1) == 1 { + res += g2; + } + } + res + } - // self * COFACTOR_G2 - let mut acc = G2::identity(); - for bit in e - .iter() - .flat_map(|byte| (0..8).rev().map(move |i| Choice::from((byte >> i) & 1u8))) - .skip(1) - { - acc = acc.double(); - acc = G2::conditional_select(&acc, &(acc + self), bit); + fn psi(mut g2: G2) -> G2 { + const U0: Fq = Fq::from_raw([ + 0x99e39557176f553d, + 0xb78cc310c2c3330c, + 0x4c0bec3cf559b143, + 0x2fb347984f7911f7, + ]); + + const U1: Fq = Fq::from_raw([ + 0x1665d51c640fcba2, + 0x32ae2a1d0b7c9dce, + 0x4ba4cc8bd75a0794, + 0x16c9e55061ebae20, + ]); + let u = Fq2::new(U0, U1); + + const V0: Fq = Fq::from_raw([ + 0xdc54014671a0135a, + 0xdbaae0eda9c95998, + 0xdc5ec698b6e2f9b9, + 0x063cf305489af5dc, + ]); + + const V1: Fq = Fq::from_raw([ + 0x82d37f632623b0e3, + 0x21807dc98fa25bd2, + 0x0704b5a7ec796f2b, + 0x07c03cbcac41049a, + ]); + let v = Fq2::new(V0, V1); + + g2.x.conjugate(); + g2.y.conjugate(); + g2.z.conjugate(); + + g2.x *= u; + g2.y *= v; + + g2 } - acc + + let u0 = exp_by_x(self); + let u1 = psi(u0.double() + u0); + let u2 = psi(psi(u0)); + let u3 = psi(psi(psi(*self))); + + u0 + u1 + u2 + u3 } fn into_subgroup(self) -> CtOption { @@ -179,7 +228,6 @@ impl CofactorGroup for G2 { ]; // self * GROUP_ORDER; - let mut acc = G2::identity(); for bit in e .iter() @@ -195,14 +243,35 @@ 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"BN254G1_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"BN254G2_XMD:SHA-256_SVDW_RO_", + Self::SVDW_Z, + crate::hash_to_curve::Method::SVDW, + ) + } } #[cfg(test)] mod test { + use crate::tests::curve::TestH2C; + use super::*; use group::UncompressedEncoding; crate::curve_testing_suite!(G1, G2); - crate::curve_testing_suite!(G1, "hash_to_curve"); crate::curve_testing_suite!(G1, "endo_consistency"); crate::curve_testing_suite!( G1, @@ -216,57 +285,7 @@ mod test { 0x00, ] ); - crate::curve_testing_suite!( - G1, - "svdw_map_to_curve", - ( - // Precomputed constants taken from https://github.com/ConsenSys/gnark-crypto/blob/441dc0ffe639294b8d09e394f24ba7575577229c/internal/generator/config/bn254.go#L26-L32. - [ - "4", - "10944121435919637611123202872628637544348155578648911831344518947322613104291", - "8815841940592487685674414971303048083897117035520822607866", - "7296080957279758407415468581752425029565437052432607887563012631548408736189", - ], - // List of (u, (Q.x, Q.y)) taken from https://github.com/ConsenSys/gnark-crypto/blob/441dc0ffe639294b8d09e394f24ba7575577229c/ecc/bn254/hash_vectors_test.go#L4-L28 - [ - ( - "0xcb81538a98a2e3580076eed495256611813f6dae9e16d3d4f8de7af0e9833e1", - ( - "0x1bb8810e2ceaf04786d4efd216fc2820ddd9363712efc736ada11049d8af5925", - "0x1efbf8d54c60d865cce08437668ea30f5bf90d287dbd9b5af31da852915e8f11", - ), - ), - ( - "0xba35e127276e9000b33011860904ddee28f1d48ddd3577e2a797ef4a5e62319", - ( - "0xda4a96147df1f35b0f820bd35c6fac3b80e8e320de7c536b1e054667b22c332", - "0x189bd3fbffe4c8740d6543754d95c790e44cd2d162858e3b733d2b8387983bb7", - ), - ), - ( - "0x11852286660cd970e9d7f46f99c7cca2b75554245e91b9b19d537aa6147c28fc", - ( - "0x2ff727cfaaadb3acab713fa22d91f5fddab3ed77948f3ef6233d7ea9b03f4da1", - "0x304080768fd2f87a852155b727f97db84b191e41970506f0326ed4046d1141aa", - ), - ), - ( - "0x174d1c85d8a690a876cc1deba0166d30569fafdb49cb3ed28405bd1c5357a1cc", - ( - "0x11a2eaa8e3e89de056d1b3a288a7f733c8a1282efa41d28e71af065ab245df9b", - "0x60f37c447ac29fd97b9bb83be98ddccf15e34831a9cdf5493b7fede0777ae06", - ), - ), - ( - "0x73b81432b4cf3a8a9076201500d1b94159539f052a6e0928db7f2df74bff672", - ( - "0x27409dccc6ee4ce90e24744fda8d72c0bc64e79766f778da0c1c0ef1c186ea84", - "0x1ac201a542feca15e77f30370da183514dc99d8a0b2c136d64ede35cd0b51dc0", - ), - ), - ] - ) - ); + crate::curve_testing_suite!( G1, "constants", @@ -277,6 +296,7 @@ mod test { G1_GENERATOR_Y, Fr::MODULUS ); + crate::curve_testing_suite!( G2, "constants", @@ -287,4 +307,112 @@ mod test { G2_GENERATOR_Y, Fr::MODULUS ); + + #[test] + fn test_hash_to_curve_g1() { + // Test vectors are taken from gnark-crypto/ecc/bn254/hash_vectors_test.go + [ + TestH2C::::new( + b"", + crate::tests::point_from_hex( + "0a976ab906170db1f9638d376514dbf8c42aef256a54bbd48521f20749e59e86", + "02925ead66b9e68bfc309b014398640ab55f6619ab59bc1fab2210ad4c4d53d5", + ), + ), + TestH2C::::new( + b"abc", + crate::tests::point_from_hex( + "23f717bee89b1003957139f193e6be7da1df5f1374b26a4643b0378b5baf53d1", + "04142f826b71ee574452dbc47e05bc3e1a647478403a7ba38b7b93948f4e151d", + ), + ), + TestH2C::::new( + b"abcdef0123456789", + crate::tests::point_from_hex( + "187dbf1c3c89aceceef254d6548d7163fdfa43084145f92c4c91c85c21442d4a", + "0abd99d5b0000910b56058f9cc3b0ab0a22d47cf27615f588924fac1e5c63b4d", + ), + ), + TestH2C::::new( + b"q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq", + crate::tests::point_from_hex( + "00fe2b0743575324fc452d590d217390ad48e5a16cf051bee5c40a2eba233f5c", + "0794211e0cc72d3cbbdf8e4e5cd6e7d7e78d101ff94862caae8acbe63e9fdc78", + ), + ), + TestH2C::::new( + b"a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + crate::tests::point_from_hex( + "01b05dc540bd79fd0fea4fbb07de08e94fc2e7bd171fe025c479dc212a2173ce", + "1bf028afc00c0f843d113758968f580640541728cfc6d32ced9779aa613cd9b0", + ), + ), + ].iter().for_each(|test| { + test.run("QUUX-V01-CS02-with-"); + }); + } + + #[test] + fn test_hash_to_curve_g2() { + pub(crate) fn point_from_hex(x0: &str, x1: &str, y0: &str, y1: &str) -> G2Affine { + let x0: Fq = crate::tests::hex_to_field(x0); + let x1: Fq = crate::tests::hex_to_field(x1); + let x = Fq2 { c0: x0, c1: x1 }; + let y0: Fq = crate::tests::hex_to_field(y0); + let y1: Fq = crate::tests::hex_to_field(y1); + let y = Fq2 { c0: y0, c1: y1 }; + G2Affine::from_xy(x, y).unwrap() + } + + // Test vectors are taken from gnark-crypto/ecc/bn254/hash_vectors_test.go + [ + TestH2C::::new( + b"", + point_from_hex( + "1192005a0f121921a6d5629946199e4b27ff8ee4d6dd4f9581dc550ade851300", + "1747d950a6f23c16156e2171bce95d1189b04148ad12628869ed21c96a8c9335", + "0498f6bb5ac309a07d9a8b88e6ff4b8de0d5f27a075830e1eb0e68ea318201d8", + "2c9755350ca363ef2cf541005437221c5740086c2e909b71d075152484e845f4", + ), + ), + TestH2C::::new( + b"abc", + point_from_hex( + "16c88b54eec9af86a41569608cd0f60aab43464e52ce7e6e298bf584b94fccd2", + "0b5db3ca7e8ef5edf3a33dfc3242357fbccead98099c3eb564b3d9d13cba4efd", + "1c42ba524cb74db8e2c680449746c028f7bea923f245e69f89256af2d6c5f3ac", + "22d02d2da7f288545ff8789e789902245ab08c6b1d253561eec789ec2c1bd630", + ), + ), + TestH2C::::new( + b"abcdef0123456789", + point_from_hex( + "1435fd84aa43c699230e371f6fea3545ce7e053cbbb06a320296a2b81efddc70", + "2a8a360585b6b05996ef69c3c09b2c6fb17afe2b1e944f07559c53178eabf171", + "2820188dcdc13ffdca31694942418afa1d6dfaaf259d012fab4da52b0f592e38", + "142f08e2441ec431defc24621b73cfe0252d19b243cb55b84bdeb85de039207a", + ), + ), + TestH2C::::new( + b"q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq", + point_from_hex( + "2cffc213fb63d00d923cb22cda5a2904837bb93a2fe6e875c532c51744388341", + "2718ef38d1bc4347f0266c774c8ef4ee5fa7056cc27a4bd7ecf7a888efb95b26", + "232553f728341afa64ce66d00535764557a052e38657594e10074ad28728c584", + "2206ec0a9288f31ed78531c37295df3b56c42a1284443ee9893adb1521779001", + ), + ), + TestH2C::::new( + b"a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + point_from_hex( + "242a0a159f36f87065e7c5170426012087023165ce47a486e53d6e2845ca625a", + "17f9f6292998cf18ccc155903c1fe6b6465d40c794a3e1ed644a4182ad639f4a", + "2dc5b7b65c9c79e6ef4afab8fbe3083c66d4ce31c78f6621ece17ecc892cf4b3", + "18ef4886c818f01fdf309bc9a46dd904273917f85e74ecd0de62460a68122037", + ), + ), + ].iter().for_each(|test| { + test.run("QUUX-V01-CS02-with-"); + }); + } } diff --git a/src/bn256/fq.rs b/src/bn256/fq.rs index 8da18bbd..0bfc37a5 100644 --- a/src/bn256/fq.rs +++ b/src/bn256/fq.rs @@ -278,6 +278,14 @@ impl FromUniformBytes<64> for Fq { } } +impl FromUniformBytes<48> for Fq { + fn from_uniform_bytes(bytes: &[u8; 48]) -> Self { + let repr = &mut [0u8; 64]; + (*repr)[0..48].copy_from_slice(&bytes[..48]); + Fq::from_uniform_bytes(repr) + } +} + impl WithSmallOrderMulGroup<3> for Fq { const ZETA: Self = ZETA; } @@ -294,70 +302,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..68924210 100644 --- a/src/bn256/fq2.rs +++ b/src/bn256/fq2.rs @@ -463,15 +463,18 @@ impl PrimeField for Fq2 { } fn is_odd(&self) -> Choice { - Choice::from(self.to_repr().as_ref()[0] & 1) + self.c0.is_odd() | (self.c0.is_zero() & self.c1.is_odd()) } } -impl FromUniformBytes<64> for Fq2 { - fn from_uniform_bytes(bytes: &[u8; 64]) -> Self { - Self::new(Fq::from_uniform_bytes(bytes), Fq::zero()) +impl FromUniformBytes<96> for Fq2 { + fn from_uniform_bytes(bytes: &[u8; 96]) -> Self { + let c0: [u8; 48] = bytes[..48].try_into().unwrap(); + let c1: [u8; 48] = bytes[48..].try_into().unwrap(); + Self::new(Fq::from_uniform_bytes(&c1), Fq::from_uniform_bytes(&c0)) } } + #[derive(Clone, Copy, Debug)] pub struct Fq2Bytes([u8; 64]); diff --git a/src/bn256/fr.rs b/src/bn256/fr.rs index 22029e3a..a51c82c4 100644 --- a/src/bn256/fr.rs +++ b/src/bn256/fr.rs @@ -326,12 +326,21 @@ impl FromUniformBytes<64> for Fr { } } +impl FromUniformBytes<48> for Fr { + fn from_uniform_bytes(bytes: &[u8; 48]) -> Self { + let repr = &mut [0u8; 64]; + (*repr)[0..48].copy_from_slice(&bytes[..48]); + Fr::from_uniform_bytes(repr) + } +} + impl WithSmallOrderMulGroup<3> for Fr { const ZETA: Self = ZETA; } #[cfg(test)] mod test { + use super::*; crate::field_testing_suite!(Fr, "field_arithmetic"); crate::field_testing_suite!(Fr, "conversion"); @@ -342,72 +351,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 58186c15..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", - "2f058acc133957074ac79e9b9b1867a0cf3d13df7aa7de7f48e9a6be7d96aaad", - "b2ff44a25693b811f35e33feb3e99ad9ba0d06425a3ffd5e79cef63d20143314", - "ab2f6d71d2fde51546d8a5782aa9f707e585b84644470f0c876784dbebd30c95", - "6a4e0e30f37a8d1b92b8cf08df3735a36b4937ee455a9dc5f9283a13530db184", - "f1c69be8c5f5f9e28b0e9f76ab77651a7dcaaae371fbba66450cbcee0ed5b1ab", - "e86267c2e3355d7a6f664a0ea71374406337d452a3f9a294a0594df53c08df21", - "03cf55ca983ecd8a2e2baae18d979d97d688a978d829701c66a14d7c4da58ea2", - "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 2449752a..7621d9db 100644 --- a/src/derive/curve.rs +++ b/src/derive/curve.rs @@ -724,7 +724,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 dad570da..9a22024b 100644 --- a/src/grumpkin/curve.rs +++ b/src/grumpkin/curve.rs @@ -9,7 +9,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_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..b82d01c0 100644 --- a/src/hash_to_curve.rs +++ b/src/hash_to_curve.rs @@ -1,99 +1,210 @@ #![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" + ); + + let mut h = D::new(); + h.update(vec![0; D::block_size()]); + h.update(message); + h.update([(out_len >> 8) as u8, 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 for the target bits of security `k`. Currently, the target security is 128 bits. + // See: + // L = ceil((ceil(log2(p)) + k) / 8) + assert!((C::Base::NUM_BITS as usize + 128) / 8 <= L); + + 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 +265,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 +356,137 @@ 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() { + // Test vectors are taken from: + // https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-16.html#name-expand_message_xmdsha-256 -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 7cd68205..dbc5f0ee 100644 --- a/src/pluto_eris/curve.rs +++ b/src/pluto_eris/curve.rs @@ -3,12 +3,12 @@ use crate::derive::curve::{IDENTITY_MASK, IDENTITY_SHIFT, SIGN_MASK, SIGN_SHIFT} 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!( @@ -166,7 +190,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 { @@ -225,6 +249,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!( @@ -237,7 +285,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..646ef7e8 100644 --- a/src/pluto_eris/fields/fp.rs +++ b/src/pluto_eris/fields/fp.rs @@ -411,100 +411,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/fp2.rs b/src/pluto_eris/fields/fp2.rs index 9bb68782..1246b654 100644 --- a/src/pluto_eris/fields/fp2.rs +++ b/src/pluto_eris/fields/fp2.rs @@ -484,7 +484,7 @@ impl PrimeField for Fp2 { } fn is_odd(&self) -> Choice { - Choice::from(self.to_repr().as_ref()[0] & 1) + self.c0.is_odd() | (self.c0.is_zero() & self.c1.is_odd()) } } diff --git a/src/pluto_eris/fields/fq.rs b/src/pluto_eris/fields/fq.rs index 782ca241..45d0f542 100644 --- a/src/pluto_eris/fields/fq.rs +++ b/src/pluto_eris/fields/fq.rs @@ -402,100 +402,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([ - 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); } diff --git a/src/pluto_eris/fields/mod.rs b/src/pluto_eris/fields/mod.rs index 2ed86a6b..307f92d3 100644 --- a/src/pluto_eris/fields/mod.rs +++ b/src/pluto_eris/fields/mod.rs @@ -55,24 +55,11 @@ 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 + let d1 = $field([limbs[7], 0, 0, 0, 0, 0, 0]); + d0 * $r2 + d1 * $r3 } diff --git a/src/secp256k1/curve.rs b/src/secp256k1/curve.rs index d4cdd659..76f22eb6 100644 --- a/src/secp256k1/curve.rs +++ b/src/secp256k1/curve.rs @@ -2,7 +2,6 @@ use crate::derive::curve::{IDENTITY_MASK, IDENTITY_SHIFT, SIGN_MASK, SIGN_SHIFT} 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}; @@ -10,6 +9,7 @@ use core::cmp; use core::fmt::Debug; use core::iter::Sum; use core::ops::{Add, Mul, Neg, Sub}; +use group::cofactor::CofactorGroup; use rand::RngCore; use subtle::{Choice, ConditionallySelectable, ConstantTimeEq, CtOption}; @@ -65,7 +65,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 +80,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 @@ -133,7 +142,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 { @@ -146,6 +155,15 @@ 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, + ) + } } /// 3-Isogeny Map for Secp256k1 @@ -269,6 +287,8 @@ pub(crate) fn iso_map_secp256k1(rp: IsoSecp256k1) -> Secp256k1 { #[cfg(test)] mod test { + use crate::tests::curve::TestH2C; + use super::*; use group::UncompressedEncoding; crate::curve_testing_suite!(Secp256k1); @@ -284,4 +304,49 @@ mod test { SECP_GENERATOR_Y, Fq::MODULUS ); + + #[test] + fn test_hash_to_curve() { + // Test vectors are taken from + // https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-16.html#name-expand_message_xmdsha-256 + [ + TestH2C::::new( + b"", + crate::tests::point_from_hex( + "c1cae290e291aee617ebaef1be6d73861479c48b841eaba9b7b5852ddfeb1346", + "64fa678e07ae116126f08b022a94af6de15985c996c3a91b64c406a960e51067", + ), + ), + TestH2C::::new( + b"abc", + crate::tests::point_from_hex( + "3377e01eab42db296b512293120c6cee72b6ecf9f9205760bd9ff11fb3cb2c4b", + "7f95890f33efebd1044d382a01b1bee0900fb6116f94688d487c6c7b9c8371f6", + ), + ), + TestH2C::::new( + b"abcdef0123456789", + crate::tests::point_from_hex( + "bac54083f293f1fe08e4a70137260aa90783a5cb84d3f35848b324d0674b0e3a", + "4436476085d4c3c4508b60fcf4389c40176adce756b398bdee27bca19758d828", + ), + ), + TestH2C::::new( + b"q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq", + crate::tests::point_from_hex( + "e2167bc785333a37aa562f021f1e881defb853839babf52a7f72b102e41890e9", + "f2401dd95cc35867ffed4f367cd564763719fbc6a53e969fb8496a1e6685d873", + ), + ), // + TestH2C::::new( + b"a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + crate::tests::point_from_hex( + "e3c8d35aaaf0b9b647e88a0a0a7ee5d5bed5ad38238152e4e6fd8c1f8cb7c998", + "8446eeb6181bf12f56a9d24e262221cc2f0c4725c7e3803024b5888ee5823aa6", + ), + ), + ].iter().for_each(|test| { + test.run("QUUX-V01-CS02-with-"); + }); + } } diff --git a/src/secp256k1/fp.rs b/src/secp256k1/fp.rs index 415017fa..34fd41d9 100644 --- a/src/secp256k1/fp.rs +++ b/src/secp256k1/fp.rs @@ -284,6 +284,14 @@ impl FromUniformBytes<64> for Fp { } } +impl FromUniformBytes<48> for Fp { + fn from_uniform_bytes(bytes: &[u8; 48]) -> Self { + let repr = &mut [0u8; 64]; + (*repr)[0..48].copy_from_slice(&bytes[..48]); + Fp::from_uniform_bytes(repr) + } +} + impl WithSmallOrderMulGroup<3> for Fp { const ZETA: Self = ZETA; } @@ -302,4 +310,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..ca559623 100644 --- a/src/secp256k1/fq.rs +++ b/src/secp256k1/fq.rs @@ -291,6 +291,14 @@ impl FromUniformBytes<64> for Fq { } } +impl FromUniformBytes<48> for Fq { + fn from_uniform_bytes(bytes: &[u8; 48]) -> Self { + let repr = &mut [0u8; 64]; + (*repr)[0..48].copy_from_slice(&bytes[..48]); + Fq::from_uniform_bytes(repr) + } +} + impl WithSmallOrderMulGroup<3> for Fq { const ZETA: Self = ZETA; } @@ -309,4 +317,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 c1e2fb0f..5d3198aa 100644 --- a/src/secp256r1/curve.rs +++ b/src/secp256r1/curve.rs @@ -2,7 +2,6 @@ use crate::derive::curve::{IDENTITY_MASK, IDENTITY_SHIFT, SIGN_MASK, SIGN_SHIFT} 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}; @@ -76,23 +75,33 @@ 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, + ) + } } #[cfg(test)] mod test { + use crate::tests::curve::TestH2C; + use super::*; use group::UncompressedEncoding; crate::curve_testing_suite!(Secp256r1); @@ -107,4 +116,49 @@ mod test { SECP_GENERATOR_Y, Fq::MODULUS ); + + #[test] + fn test_hash_to_curve() { + // Test vectors are taken from + // https://www.ietf.org/archive/id/draft-irtf-cfrg-hash-to-curve-16.html#name-p256_xmdsha-256_sswu_ro_ + [ + TestH2C::::new( + b"", + crate::tests::point_from_hex( + "2c15230b26dbc6fc9a37051158c95b79656e17a1a920b11394ca91c44247d3e4", + "8a7a74985cc5c776cdfe4b1f19884970453912e9d31528c060be9ab5c43e8415", + ), + ), + TestH2C::::new( + b"abc", + crate::tests::point_from_hex( + "0bb8b87485551aa43ed54f009230450b492fead5f1cc91658775dac4a3388a0f", + "5c41b3d0731a27a7b14bc0bf0ccded2d8751f83493404c84a88e71ffd424212e", + ), + ), + TestH2C::::new( + b"abcdef0123456789", + crate::tests::point_from_hex( + "65038ac8f2b1def042a5df0b33b1f4eca6bff7cb0f9c6c1526811864e544ed80", + "cad44d40a656e7aff4002a8de287abc8ae0482b5ae825822bb870d6df9b56ca3", + ), + ), + TestH2C::::new( + b"q128_qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq", + crate::tests::point_from_hex( + "4be61ee205094282ba8a2042bcb48d88dfbb609301c49aa8b078533dc65a0b5d", + "98f8df449a072c4721d241a3b1236d3caccba603f916ca680f4539d2bfb3c29e", + ), + ), // + TestH2C::::new( + b"a512_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + crate::tests::point_from_hex( + "457ae2981f70ca85d8e24c308b14db22f3e3862c5ea0f652ca38b5e49cd64bc5", + "ecb9f0eadc9aeed232dabc53235368c1394c78de05dd96893eefa62b0f4757dc", + ), + ), + ].iter().for_each(|test| { + test.run("QUUX-V01-CS02-with-"); + }); + } } diff --git a/src/secp256r1/fp.rs b/src/secp256r1/fp.rs index bce80808..9e9805d7 100644 --- a/src/secp256r1/fp.rs +++ b/src/secp256r1/fp.rs @@ -302,6 +302,14 @@ impl FromUniformBytes<64> for Fp { } } +impl FromUniformBytes<48> for Fp { + fn from_uniform_bytes(bytes: &[u8; 48]) -> Self { + let repr = &mut [0u8; 64]; + (*repr)[0..48].copy_from_slice(&bytes[..48]); + Fp::from_uniform_bytes(repr) + } +} + impl WithSmallOrderMulGroup<3> for Fp { const ZETA: Self = ZETA; } @@ -320,4 +328,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 6a7b0f33..52a8d42e 100644 --- a/src/secq256k1/curve.rs +++ b/src/secq256k1/curve.rs @@ -3,7 +3,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_SVDW_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 b89863cf..3583132b 100644 --- a/src/tests/curve.rs +++ b/src/tests/curve.rs @@ -343,10 +343,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; @@ -503,47 +503,6 @@ 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 ff::PrimeField; - use num_bigint::BigUint; - use num_traits::Num; - use std::borrow::Cow; - - fn fe_from_str(string: impl AsRef) -> F { - let string = string.as_ref(); - let oct = if let Some(hex) = string.strip_prefix("0x") { - Cow::Owned(BigUint::from_str_radix(hex, 16).unwrap().to_string()) - } else { - Cow::Borrowed(string) - }; - F::from_str_vartime(&oct).unwrap() - } - - 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() { - let u = fe_from_str(u); - let expected = G::AffineExt::from_xy(fe_from_str(x), fe_from_str(y)).unwrap(); - let output = hash_to_curve::svdw_map_to_curve::(u, c1, c2, c3, c4, z).to_affine(); - assert_eq!(output, expected); - } - } - - svdw_map_to_curve_test::<$curve>($curve::SVDW_Z, $precomputed_constants, $test_vector); - } - }; - ($curve: ident, "constants", $p: expr, $a: expr, $b: expr, $gen_x: expr, $gen_y: expr, $order: expr) => { #[test] #[allow(non_snake_case)] @@ -564,3 +523,22 @@ macro_rules! curve_testing_suite { } }; } + +use crate::{CurveAffine, CurveExt}; +use group::Curve; + +pub(crate) struct TestH2C { + msg: &'static [u8], + expect: C, +} + +impl TestH2C { + pub(crate) fn new(msg: &'static [u8], expect: C) -> Self { + Self { msg, expect } + } + + pub(crate) fn run(&self, domain_prefix: &str) { + let r0 = C::CurveExt::hash_to_curve(domain_prefix)(self.msg); + assert_eq!(r0.to_affine(), self.expect); + } +} 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 +}