Skip to content

Commit

Permalink
Merge pull request #130 from dusk-network/mocello/129/hash_to_curve
Browse files Browse the repository at this point in the history
Add `hash_to_scalar` and `hash_to_point`
  • Loading branch information
moCello authored Dec 12, 2023
2 parents 1f832dc + 437e1a0 commit 9caa7df
Show file tree
Hide file tree
Showing 3 changed files with 112 additions and 41 deletions.
4 changes: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ 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]
- Add `hash_to_point` to `ExtendedPoint` [#129]

## [0.13.1] - 2023-10-11

Expand Down Expand Up @@ -207,6 +208,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Initial fork from [`zkcrypto/jubjub`]

<!-- ISSUES -->
[#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
Expand Down
60 changes: 59 additions & 1 deletion src/dusk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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) =
<JubJubAffine as Serializable<32>>::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]
Expand Down Expand Up @@ -254,7 +296,6 @@ fn test_affine_point_generator_nums_is_not_identity() {
);
}

#[ignore]
#[test]
fn second_gen_nums() {
use blake2::{Blake2b, Digest};
Expand All @@ -279,7 +320,24 @@ fn second_gen_nums() {
== <JubJubAffine as Serializable<32>>::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<u8>) -> bool {
let point = ExtendedPoint::hash_to_point(&bytes);

point.is_on_curve_vartime() && point.is_prime_order().into()
}
}
}
89 changes: 50 additions & 39 deletions src/fr/dusk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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<T> for any integer type smaller than 128-bit
Expand Down Expand Up @@ -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<u8>) -> bool {
let fr = Fr::from_var_bytes(&bytes);
fn prop_hash_to_scalar(bytes: Vec<u8>) -> bool {
let scalar = Fr::hash_to_scalar(&bytes);

is_fr_in_range(&fr)
is_scalar_in_range(&scalar)
}
}
}

0 comments on commit 9caa7df

Please sign in to comment.