From 3004d0ca1fb26a438d7b68a4ccd6380f736e15f2 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 ++ src/hash.rs | 75 +++++++++++++++++++++++++++++++++++----------- src/hash/gadget.rs | 69 +++++++++++++++++++++++++++++------------- tests/hash.rs | 18 ++++------- 4 files changed, 113 insertions(+), 51 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/src/hash.rs b/src/hash.rs index 9f4e708..fa4be17 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 `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, @@ -49,6 +55,8 @@ 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. fn io_pattern( domain: Domain, input: &[&[T]], @@ -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. @@ -106,28 +118,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 +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, 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..716c116 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,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. @@ -42,60 +45,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());