From ebb207e27dcf3b5372cf91f7073ca3e322384909 Mon Sep 17 00:00:00 2001 From: moana Date: Wed, 17 Apr 2024 14:18:52 +0100 Subject: [PATCH] Change finalize to return the hash directly Resolves #259 --- CHANGELOG.md | 2 ++ benches/hash.rs | 6 ++-- src/hash.rs | 77 +++++++++++++++++++++++++++++++++++----------- src/hash/gadget.rs | 70 ++++++++++++++++++++++++++++------------- tests/hash.rs | 18 ++++------- 5 files changed, 118 insertions(+), 55 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f1bb75f..67ef193 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 @@ -499,6 +500,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [#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 diff --git a/benches/hash.rs b/benches/hash.rs index 93f3179..ff0dfb4 100644 --- a/benches/hash.rs +++ b/benches/hash.rs @@ -39,8 +39,7 @@ impl Circuit for SpongeCircuit { }); let output_witness = - HashGadget::digest(Domain::Merkle4, composer, &w_message) - .expect("creating the hash should not fail"); + HashGadget::digest(Domain::Merkle4, composer, &w_message); composer.assert_equal_constant(output_witness[0], 0, Some(self.output)); Ok(()) @@ -62,8 +61,7 @@ fn bench_sponge(c: &mut Criterion) { BlsScalar::random(&mut rng), BlsScalar::random(&mut rng), ]; - let public_inputs = Hash::digest(Domain::Merkle4, &message) - .expect("creating the hash should not fail"); + let public_inputs = Hash::digest(Domain::Merkle4, &message); let circuit = SpongeCircuit::new(message, public_inputs[0]); // Benchmark sponge native diff --git a/src/hash.rs b/src/hash.rs index 9f4e708..3662c5b 100644 --- a/src/hash.rs +++ b/src/hash.rs @@ -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 the zero element. 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 the zero element. Merkle2, /// Domain to specify hash used for encryption Encryption, @@ -49,6 +55,10 @@ impl From 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. +// The function will return an error if a merkle domain is selected but the +// given input elements don't add up to the specified arity. fn io_pattern( domain: Domain, input: &[&[T]], @@ -95,9 +105,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. @@ -106,28 +120,48 @@ impl<'a> Hash<'a> { } /// Finalize the hash. - pub fn finalize(&self) -> Result, 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 { // 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, 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 { // 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([ @@ -138,31 +172,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, 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 { 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, Error> { + ) -> Vec { let mut hash = Self::new(domain); hash.update(input); hash.finalize_truncated() diff --git a/src/hash/gadget.rs b/src/hash/gadget.rs index 09c9dd4..988b51a 100644 --- a/src/hash/gadget.rs +++ b/src/hash/gadget.rs @@ -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; @@ -31,9 +31,13 @@ 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. @@ -42,60 +46,84 @@ impl<'a> HashGadget<'a> { } /// Finalize the hash. - pub fn finalize( - &self, - composer: &mut Composer, - ) -> Result, 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 { // 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, 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 { // 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, Error> { + ) -> Vec { 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, Error> { + ) -> Vec { let mut hash = Self::new(domain); hash.update(input); hash.finalize_truncated(composer) diff --git a/tests/hash.rs b/tests/hash.rs index fd63ef9..229e94d 100644 --- a/tests/hash.rs +++ b/tests/hash.rs @@ -67,8 +67,7 @@ impl TestCircuit { .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, @@ -94,8 +93,7 @@ impl Circuit for TestCircuit { // 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(()) @@ -147,8 +145,7 @@ impl TestTruncatedCircuit { .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, @@ -179,8 +176,7 @@ impl Circuit for TestTruncatedCircuit { Domain::Other, composer, &input_witnesses, - ) - .expect("hash creation should not fail"); + ); composer.assert_equal_constant( gadget_output[0], 0, @@ -239,7 +235,7 @@ impl MultipleOutputCircuit { 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); @@ -266,9 +262,7 @@ impl Circuit for MultipleOutputCircuit { 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());