Skip to content

Commit

Permalink
Change finalize to return the hash directly
Browse files Browse the repository at this point in the history
Resolves #259
  • Loading branch information
moCello committed Apr 17, 2024
1 parent e969795 commit 3004d0c
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 51 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Change `const PARTIAL_ROUNDS` to 60 (was 59) [#260]
- Increase the number of constants to 340 (was 335) [#260]
- Change `finalize` to return the hash directly (not wrapped in a `Result`) [#259]

## [0.37.0] - 2024-03-27

Expand Down Expand Up @@ -499,6 +500,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

<!-- ISSUES -->
[#260]: https://github.com/dusk-network/poseidon252/issues/260
[#259]: https://github.com/dusk-network/poseidon252/issues/259
[#251]: https://github.com/dusk-network/poseidon252/issues/251
[#248]: https://github.com/dusk-network/poseidon252/issues/248
[#246]: https://github.com/dusk-network/poseidon252/issues/246
Expand Down
75 changes: 57 additions & 18 deletions src/hash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,15 @@ pub(crate) mod gadget;
/// The Domain Separation for Poseidon
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Domain {
/// Domain to specify hashing of 4-arity merkle tree
/// Domain to specify hashing of 4-arity merkle tree.
/// Note that selecting this domain-separator means that the total hash
/// input must be exactly 4 `BlsScalar` long, and any empty slots of the
/// merkle tree level need to be filled with `BlsScalar::zero()`.
Merkle4,
/// Domain to specify hashing of 2-arity merkle tree
/// Note that selecting this domain-separator means that the total hash
/// input must be exactly 2 `BlsScalar` long, and any empty slots of the
/// merkle tree level need to be filled with `BlsScalar::zero()`.
Merkle2,
/// Domain to specify hash used for encryption
Encryption,
Expand Down Expand Up @@ -49,6 +55,8 @@ impl From<Domain> for u64 {
}
}

// This function, which is called during the finalization step of the hash, will
// always produce a valid io-pattern based on the input.
fn io_pattern<T>(
domain: Domain,
input: &[&[T]],
Expand Down Expand Up @@ -95,9 +103,13 @@ impl<'a> Hash<'a> {
}
}

/// Override the length of the hash output (default value is 1).
/// Override the length of the hash output (default value is 1) when using
/// the hash for anything other than hashing a merkle tree or
/// encryption.
pub fn output_len(&mut self, output_len: usize) {
self.output_len = output_len;
if self.domain == Domain::Other && output_len > 0 {
self.output_len = output_len;
}
}

/// Update the hash input.
Expand All @@ -106,28 +118,48 @@ impl<'a> Hash<'a> {
}

/// Finalize the hash.
pub fn finalize(&self) -> Result<Vec<BlsScalar>, Error> {
///
/// # Panics
/// This function panics when the io-pattern can not be created with the
/// given domain and input, e.g. using [`Domain::Merkle4`] with an input
/// anything other than 4 Scalar.
pub fn finalize(&self) -> Vec<BlsScalar> {
// Generate the hash using the sponge framework:
// initialize the sponge
let mut sponge = Sponge::start(
ScalarPermutation::new(),
io_pattern(self.domain, &self.input, self.output_len)?,
io_pattern(self.domain, &self.input, self.output_len)
.expect("io-pattern should be valid"),
self.domain.into(),
)?;
)
.expect("at this point the io-pattern is valid");

// absorb the input
for input in self.input.iter() {
sponge.absorb(input.len(), input)?;
sponge
.absorb(input.len(), input)
.expect("at this point the io-pattern is valid");
}

// squeeze output_len elements
sponge.squeeze(self.output_len)?;
sponge
.squeeze(self.output_len)
.expect("at this point the io-pattern is valid");

// return the result
Ok(sponge.finish()?)
sponge
.finish()
.expect("at this point the io-pattern is valid")
}

/// Finalize the hash and output the result as a `JubJubScalar` by
/// truncating the `BlsScalar` output to 250 bits.
pub fn finalize_truncated(&self) -> Result<Vec<JubJubScalar>, Error> {
///
/// # Panics
/// This function panics when the io-pattern can not be created with the
/// given domain and input, e.g. using [`Domain::Merkle4`] with an input
/// anything other than 4 Scalar.
pub fn finalize_truncated(&self) -> Vec<JubJubScalar> {
// bit-mask to 'cast' a bls-scalar result to a jubjub-scalar by
// truncating the 6 highest bits
const TRUNCATION_MASK: BlsScalar = BlsScalar::from_raw([
Expand All @@ -138,31 +170,38 @@ impl<'a> Hash<'a> {
]);

// finalize the hash as bls-scalar
let bls_output = self.finalize()?;
let bls_output = self.finalize();

Ok(bls_output
bls_output
.iter()
.map(|bls| {
JubJubScalar::from_raw((bls & &TRUNCATION_MASK).reduce().0)
})
.collect())
.collect()
}

/// Digest an input and calculate the hash immediately
pub fn digest(
domain: Domain,
input: &'a [BlsScalar],
) -> Result<Vec<BlsScalar>, Error> {
///
/// # Panics
/// This function panics when the io-pattern can not be created with the
/// given domain and input, e.g. using [`Domain::Merkle4`] with an input
/// anything other than 4 Scalar.
pub fn digest(domain: Domain, input: &'a [BlsScalar]) -> Vec<BlsScalar> {
let mut hash = Self::new(domain);
hash.update(input);
hash.finalize()
}

/// Digest an input and calculate the hash as jubjub-scalar immediately
///
/// # Panics
/// This function panics when the io-pattern can not be created with the
/// given domain and input, e.g. using [`Domain::Merkle4`] with an input
/// anything other than 4 Scalar.
pub fn digest_truncated(
domain: Domain,
input: &'a [BlsScalar],
) -> Result<Vec<JubJubScalar>, Error> {
) -> Vec<JubJubScalar> {
let mut hash = Self::new(domain);
hash.update(input);
hash.finalize_truncated()
Expand Down
69 changes: 48 additions & 21 deletions src/hash/gadget.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use dusk_plonk::prelude::{Composer, Witness};
use dusk_safe::Sponge;

use crate::hades::GadgetPermutation;
use crate::{Domain, Error};
use crate::Domain;

use super::io_pattern;

Expand All @@ -31,9 +31,12 @@ impl<'a> HashGadget<'a> {
}
}

/// Override the length of the hash output (default value is 1).
/// Override the length of the hash output (default value is 1) when using
/// the hash for anything other than hashing a merkle tree or
/// encryption.
pub fn output_len(&mut self, output_len: usize) {
self.output_len = output_len;
if self.domain == Domain::Other && output_len > 0 {
self.output_len = output_len;
}

/// Update the hash input.
Expand All @@ -42,60 +45,84 @@ impl<'a> HashGadget<'a> {
}

/// Finalize the hash.
pub fn finalize(
&self,
composer: &mut Composer,
) -> Result<Vec<Witness>, Error> {
///
/// # Panics
/// This function panics when the io-pattern can not be created with the
/// given domain and input, e.g. using [`Domain::Merkle4`] with an input
/// anything other than 4 Scalar.
pub fn finalize(&self, composer: &mut Composer) -> Vec<Witness> {
// Generate the hash using the sponge framework:
// initialize the sponge
let mut sponge = Sponge::start(
GadgetPermutation::new(composer),
io_pattern(self.domain, &self.input, self.output_len)?,
io_pattern(self.domain, &self.input, self.output_len)
.expect("io-pattern should be valid"),
self.domain.into(),
)?;
)
.expect("at this point the io-pattern is valid");

// absorb the input
for input in self.input.iter() {
sponge.absorb(input.len(), input)?;
sponge
.absorb(input.len(), input)
.expect("at this point the io-pattern is valid");
}

// squeeze output_len elements
sponge.squeeze(self.output_len)?;
sponge
.squeeze(self.output_len)
.expect("at this point the io-pattern is valid");

// return the result
Ok(sponge.finish()?)
sponge
.finish()
.expect("at this point the io-pattern is valid")
}

/// Finalize the hash and output JubJubScalar.
pub fn finalize_truncated(
&self,
composer: &mut Composer,
) -> Result<Vec<Witness>, Error> {
///
/// # Panics
/// This function panics when the io-pattern can not be created with the
/// given domain and input, e.g. using [`Domain::Merkle4`] with an input
/// anything other than 4 Scalar.
pub fn finalize_truncated(&self, composer: &mut Composer) -> Vec<Witness> {
// finalize the hash as bls-scalar witnesses
let bls_output = self.finalize(composer)?;
let bls_output = self.finalize(composer);

// truncate the bls witnesses to 250 bits
Ok(bls_output
bls_output
.iter()
.map(|bls| composer.append_logic_xor::<125>(*bls, Composer::ZERO))
.collect())
.collect()
}

/// Digest an input and calculate the hash immediately
///
/// # Panics
/// This function panics when the io-pattern can not be created with the
/// given domain and input, e.g. using [`Domain::Merkle4`] with an input
/// anything other than 4 Scalar.
pub fn digest(
domain: Domain,
composer: &mut Composer,
input: &'a [Witness],
) -> Result<Vec<Witness>, Error> {
) -> Vec<Witness> {
let mut hash = Self::new(domain);
hash.update(input);
hash.finalize(composer)
}

/// Digest an input and calculate the hash as jubjub-scalar immediately
///
/// # Panics
/// This function panics when the io-pattern can not be created with the
/// given domain and input, e.g. using [`Domain::Merkle4`] with an input
/// anything other than 4 Scalar.
pub fn digest_truncated(
domain: Domain,
composer: &mut Composer,
input: &'a [Witness],
) -> Result<Vec<Witness>, Error> {
) -> Vec<Witness> {
let mut hash = Self::new(domain);
hash.update(input);
hash.finalize_truncated(composer)
Expand Down
18 changes: 6 additions & 12 deletions tests/hash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,7 @@ impl<const L: usize> TestCircuit<L> {
.for_each(|s| *s = BlsScalar::random(&mut *rng));

// calculate expected hash output
let output = Hash::digest(Domain::Other, &input)
.expect("hash creation should not fail");
let output = Hash::digest(Domain::Other, &input);

Self {
input,
Expand All @@ -94,8 +93,7 @@ impl<const L: usize> Circuit for TestCircuit<L> {

// check that the gadget result is as expected
let gadget_output =
HashGadget::digest(Domain::Other, composer, &input_witnesses)
.expect("hash creation should not fail");
HashGadget::digest(Domain::Other, composer, &input_witnesses);
composer.assert_equal_constant(gadget_output[0], 0, Some(self.output));

Ok(())
Expand Down Expand Up @@ -147,8 +145,7 @@ impl<const L: usize> TestTruncatedCircuit<L> {
.for_each(|s| *s = BlsScalar::random(&mut *rng));

// calculate expected hash output
let output = Hash::digest_truncated(Domain::Other, &input)
.expect("hash creation should not fail");
let output = Hash::digest_truncated(Domain::Other, &input);

Self {
input,
Expand Down Expand Up @@ -179,8 +176,7 @@ impl<const L: usize> Circuit for TestTruncatedCircuit<L> {
Domain::Other,
composer,
&input_witnesses,
)
.expect("hash creation should not fail");
);
composer.assert_equal_constant(
gadget_output[0],
0,
Expand Down Expand Up @@ -239,7 +235,7 @@ impl<const I: usize, const O: usize> MultipleOutputCircuit<I, O> {
let mut hash = Hash::new(Domain::Other);
hash.update(&input);
hash.output_len(O);
let output = hash.finalize().expect("Hash creation should pass");
let output = hash.finalize();

assert_eq!(output.len(), O);

Expand All @@ -266,9 +262,7 @@ impl<const I: usize, const O: usize> Circuit for MultipleOutputCircuit<I, O> {
let mut hash = HashGadget::new(Domain::Other);
hash.output_len(O);
hash.update(&input_witnesses);
let gadget_output = hash
.finalize(composer)
.expect("hash creation should not fail");
let gadget_output = hash.finalize(composer);

assert_eq!(gadget_output.len(), self.output.len());

Expand Down

0 comments on commit 3004d0c

Please sign in to comment.