From 2c4c6044010d5e0a2cc8c0db8f09b65f151207b5 Mon Sep 17 00:00:00 2001 From: moana Date: Fri, 8 Dec 2023 13:32:30 +0100 Subject: [PATCH 1/2] Add `hash_to_scalar` --- CHANGELOG.md | 3 +- src/fr/dusk.rs | 89 ++++++++++++++++++++++++++++---------------------- 2 files changed, 52 insertions(+), 40 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e287d8..73573db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,7 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added -- Add `from_var_bytes` to scalar [#126] +- Add `from_var_bytes` to fr [#126] and refactor and rename to `hash_to_scalar` [#129] ## [0.13.1] - 2023-10-11 @@ -207,6 +207,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Initial fork from [`zkcrypto/jubjub`] +[#129]: https://github.com/dusk-network/jubjub/issues/129 [#127]: https://github.com/dusk-network/jubjub/issues/127 [#126]: https://github.com/dusk-network/jubjub/issues/126 [#115]: https://github.com/dusk-network/jubjub/issues/115 diff --git a/src/fr/dusk.rs b/src/fr/dusk.rs index 6182c41..a94d0a4 100644 --- a/src/fr/dusk.rs +++ b/src/fr/dusk.rs @@ -15,6 +15,47 @@ use super::{Fr, MODULUS, R2}; use crate::util::sbb; impl Fr { + /// Creates a `Fr` from arbitrary bytes by hashing the input with BLAKE2b + /// into a 512-bits number, and then converting the number into its scalar + /// representation by reducing it by the modulo. + /// + /// By treating the output of the BLAKE2b hash as a random oracle, this + /// implementation follows the first conversion of + /// https://hackmd.io/zV6qe1_oSU-kYU6Tt7pO7Q with concrete numbers: + /// ```text + /// p = 0x0e7db4ea6533afa906673b0101343b00a6682093ccc81082d0970e5ed6f72cb7 + /// p = 6554484396890773809930967563523245729705921265872317281365359162392183254199 + /// + /// l = 2 + /// + /// s^l = (2^256)^2 = 2^512 + /// s = 13407807929942597099574024998205846127479365820592393377723561443721764030073546976801874298166903427690031858186486050853753882811946569946433649006084096 + /// + /// r' = 2045593080716281616348203381729468609728209645786990242449482205581148743408809 + /// + /// m' = 2244478849891746936202736009816130624903096691796347063256129649283183245105 + /// ``` + pub fn hash_to_scalar(input: &[u8]) -> Self { + let state = blake2b_simd::Params::new() + .hash_length(64) + .to_state() + .update(input) + .finalize(); + + let bytes = state.as_bytes(); + + Self::from_u512([ + u64::from_le_bytes(<[u8; 8]>::try_from(&bytes[0..8]).unwrap()), + u64::from_le_bytes(<[u8; 8]>::try_from(&bytes[8..16]).unwrap()), + u64::from_le_bytes(<[u8; 8]>::try_from(&bytes[16..24]).unwrap()), + u64::from_le_bytes(<[u8; 8]>::try_from(&bytes[24..32]).unwrap()), + u64::from_le_bytes(<[u8; 8]>::try_from(&bytes[32..40]).unwrap()), + u64::from_le_bytes(<[u8; 8]>::try_from(&bytes[40..48]).unwrap()), + u64::from_le_bytes(<[u8; 8]>::try_from(&bytes[48..56]).unwrap()), + u64::from_le_bytes(<[u8; 8]>::try_from(&bytes[56..64]).unwrap()), + ]) + } + /// SHR impl: shifts bits n times, equivalent to division by 2^n. #[inline] pub fn divn(&mut self, mut n: u32) { @@ -111,37 +152,6 @@ impl Fr { } res } - - /// Creates a `Fr` from arbitrary bytes by hashing the input with BLAKE2b - /// into a 256-bits number, and then converting it into its `Fr` - /// representation. - pub fn from_var_bytes(input: &[u8]) -> Self { - let state = blake2b_simd::Params::new() - .hash_length(32) - .to_state() - .update(input) - .finalize(); - - let h = state.as_bytes(); - let mut r = [0u64; 4]; - - // will be optmized by the compiler, depending on the available target - for i in 0..4 { - r[i] = u64::from_le_bytes([ - h[i * 8], - h[i * 8 + 1], - h[i * 8 + 2], - h[i * 8 + 3], - h[i * 8 + 4], - h[i * 8 + 5], - h[i * 8 + 6], - h[i * 8 + 7], - ]); - } - - // `from_raw` converts from arbitrary to congruent scalar - Self::from_raw(r) - } } // TODO implement From for any integer type smaller than 128-bit @@ -324,21 +334,22 @@ mod fuzz { use crate::fr::{Fr, MODULUS}; use crate::util::sbb; - fn is_fr_in_range(fr: &Fr) -> bool { + fn is_scalar_in_range(scalar: &Fr) -> bool { // subtraction against modulus must underflow - let borrow = - fr.0.iter() - .zip(MODULUS.0.iter()) - .fold(0, |borrow, (&s, &m)| sbb(s, m, borrow).1); + let borrow = scalar + .0 + .iter() + .zip(MODULUS.0.iter()) + .fold(0, |borrow, (&s, &m)| sbb(s, m, borrow).1); borrow == u64::MAX } quickcheck::quickcheck! { - fn prop_fr_from_raw_bytes(bytes: Vec) -> bool { - let fr = Fr::from_var_bytes(&bytes); + fn prop_hash_to_scalar(bytes: Vec) -> bool { + let scalar = Fr::hash_to_scalar(&bytes); - is_fr_in_range(&fr) + is_scalar_in_range(&scalar) } } } From 437e1a08fc99247b19e4efd26413dd3f719370d4 Mon Sep 17 00:00:00 2001 From: moana Date: Fri, 8 Dec 2023 15:08:23 +0100 Subject: [PATCH 2/2] Add `hash_to_point` to `ExtendedPoint` Resolves #129 --- CHANGELOG.md | 1 + src/dusk.rs | 60 +++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 60 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 73573db..c802242 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Add `from_var_bytes` to fr [#126] and refactor and rename to `hash_to_scalar` [#129] +- Add `hash_to_point` to `ExtendedPoint` [#129] ## [0.13.1] - 2023-10-11 diff --git a/src/dusk.rs b/src/dusk.rs index 28a1705..e911f9f 100644 --- a/src/dusk.rs +++ b/src/dusk.rs @@ -213,6 +213,48 @@ impl JubJubExtended { let p = JubJubAffine::from(self); [p.u, p.v] } + + /// Hash an arbitrary slice of bytes to a point on the elliptic curve and + /// in the prime order subgroup. + /// + /// This algorithm uses rejection sampling to hash to a point on the curve: + /// The input together with a counter are hashed into an array of 32 bytes. + /// If the hash is a canonical representation of a point on the curve and + /// a member of the prime-order subgroup, we return it. If not, we increment + /// the counter, hash and try to de-serialize again. + /// This is the same algorithm we used to generate `GENERATOR_NUMS` as + /// outlined [here](https://app.gitbook.com/@dusk-network/s/specs/specifications/poseidon/pedersen-commitment-scheme). + /// + /// **Note:** This implementation of `hash_to_point` is not constant time, + /// in the long run we want to implement an algorithm outlined + /// [here](https://datatracker.ietf.org/doc/html/rfc9380), but we start with + /// this implementation in order to be able to use the API already. + pub fn hash_to_point(input: &[u8]) -> Self { + let mut counter = 0u64; + let mut array = [0u8; 32]; + loop { + let state = blake2b_simd::Params::new() + .hash_length(32) + .to_state() + .update(input) + .update(&counter.to_le_bytes()) + .finalize(); + + array.copy_from_slice(&state.as_bytes()[..32]); + + // check if we hit a point on the curve + if let Ok(point) = + >::from_bytes(&array) + { + // check if this point is part of the correct subgroup and not + // the identity + if point.is_prime_order().into() { + return point.into(); + } + } + counter += 1 + } + } } #[test] @@ -254,7 +296,6 @@ fn test_affine_point_generator_nums_is_not_identity() { ); } -#[ignore] #[test] fn second_gen_nums() { use blake2::{Blake2b, Digest}; @@ -279,7 +320,24 @@ fn second_gen_nums() { == >::from_bytes(&array) .unwrap() ); + break; } counter += 1; } + assert_eq!(counter, 18); +} + +#[cfg(all(test, feature = "alloc"))] +mod fuzz { + use alloc::vec::Vec; + + use crate::ExtendedPoint; + + quickcheck::quickcheck! { + fn prop_hash_to_point(bytes: Vec) -> bool { + let point = ExtendedPoint::hash_to_point(&bytes); + + point.is_on_curve_vartime() && point.is_prime_order().into() + } + } }