From b6a60e721d1c01d172285b1927b4d06c75a66bdb Mon Sep 17 00:00:00 2001 From: Daniel Lubarov Date: Fri, 4 Feb 2022 13:08:57 -0800 Subject: [PATCH] Separate methods for hashing with or without padding (#458) * Separate methods for hashing with or without padding This should be a tad better for for performance, and lets us do padding in a generic way, rather than each hash reimplementing it. This also disables padding for public inputs. It seems unnecessary since the number of public inputs is fixed for any given instance. * PR feedback * update --- plonky2/src/fri/prover.rs | 3 +- plonky2/src/fri/recursive_verifier.rs | 2 +- plonky2/src/hash/hashing.rs | 48 ++++++------------------- plonky2/src/hash/keccak.rs | 2 +- plonky2/src/hash/merkle_proofs.rs | 2 +- plonky2/src/hash/merkle_tree.rs | 4 +-- plonky2/src/hash/path_compression.rs | 2 +- plonky2/src/hash/poseidon.rs | 6 ++-- plonky2/src/iop/challenger.rs | 3 +- plonky2/src/plonk/circuit_builder.rs | 4 +-- plonky2/src/plonk/config.rs | 16 ++++++++- plonky2/src/plonk/proof.rs | 4 +-- plonky2/src/plonk/prover.rs | 2 +- plonky2/src/plonk/recursive_verifier.rs | 2 +- 14 files changed, 43 insertions(+), 57 deletions(-) diff --git a/plonky2/src/fri/prover.rs b/plonky2/src/fri/prover.rs index e814beae..5cd5fdf1 100644 --- a/plonky2/src/fri/prover.rs +++ b/plonky2/src/fri/prover.rs @@ -115,14 +115,13 @@ fn fri_proof_of_work, C: GenericConfig, c (0..=F::NEG_ONE.to_canonical_u64()) .into_par_iter() .find_any(|&i| { - C::InnerHasher::hash( + C::InnerHasher::hash_no_pad( ¤t_hash .elements .iter() .copied() .chain(Some(F::from_canonical_u64(i))) .collect_vec(), - false, ) .elements[0] .to_canonical_u64() diff --git a/plonky2/src/fri/recursive_verifier.rs b/plonky2/src/fri/recursive_verifier.rs index 8dbb2038..65e6e024 100644 --- a/plonky2/src/fri/recursive_verifier.rs +++ b/plonky2/src/fri/recursive_verifier.rs @@ -114,7 +114,7 @@ impl, const D: usize> CircuitBuilder { let mut inputs = challenger.get_hash(self).elements.to_vec(); inputs.push(proof.pow_witness); - let hash = self.hash_n_to_m::(inputs, 1, false)[0]; + let hash = self.hash_n_to_m_no_pad::(inputs, 1)[0]; self.assert_leading_zeros( hash, config.proof_of_work_bits + (64 - F::order().bits()) as u32, diff --git a/plonky2/src/hash/hashing.rs b/plonky2/src/hash/hashing.rs index 997a6b12..ea205654 100644 --- a/plonky2/src/hash/hashing.rs +++ b/plonky2/src/hash/hashing.rs @@ -18,7 +18,7 @@ pub fn hash_or_noop>(inputs: Vec) -> Ha if inputs.len() <= 4 { HashOut::from_partial(inputs) } else { - hash_n_to_hash::(&inputs, false) + hash_n_to_hash_no_pad::(&inputs) } } @@ -28,34 +28,23 @@ impl, const D: usize> CircuitBuilder { if inputs.len() <= 4 { HashOutTarget::from_partial(inputs, zero) } else { - self.hash_n_to_hash::(inputs, false) + self.hash_n_to_hash_no_pad::(inputs) } } - pub fn hash_n_to_hash>( + pub fn hash_n_to_hash_no_pad>( &mut self, inputs: Vec, - pad: bool, ) -> HashOutTarget { - HashOutTarget::from_vec(self.hash_n_to_m::(inputs, 4, pad)) + HashOutTarget::from_vec(self.hash_n_to_m_no_pad::(inputs, 4)) } - pub fn hash_n_to_m>( + pub fn hash_n_to_m_no_pad>( &mut self, - mut inputs: Vec, + inputs: Vec, num_outputs: usize, - pad: bool, ) -> Vec { let zero = self.zero(); - let one = self.one(); - - if pad { - inputs.push(zero); - while (inputs.len() + 1) % SPONGE_WIDTH != 0 { - inputs.push(one); - } - inputs.push(zero); - } let mut state = [zero; SPONGE_WIDTH]; @@ -97,24 +86,12 @@ pub trait PlonkyPermutation { fn permute(input: [F; SPONGE_WIDTH]) -> [F; SPONGE_WIDTH]; } -/// If `pad` is enabled, the message is padded using the pad10*1 rule. In general this is required -/// for the hash to be secure, but it can safely be disabled in certain cases, like if the input -/// length is fixed. -pub fn hash_n_to_m>( +/// Hash a message without any padding step. Note that this can enable length-extension attacks. +/// However, it is still collision-resistant in cases where the input has a fixed length. +pub fn hash_n_to_m_no_pad>( inputs: &[F], num_outputs: usize, - pad: bool, ) -> Vec { - if pad { - let mut padded_inputs = inputs.to_vec(); - padded_inputs.push(F::ZERO); - while (padded_inputs.len() + 1) % SPONGE_WIDTH != 0 { - padded_inputs.push(F::ONE); - } - padded_inputs.push(F::ZERO); - return hash_n_to_m::(&padded_inputs, num_outputs, false); - } - let mut state = [F::ZERO; SPONGE_WIDTH]; // Absorb all input chunks. @@ -136,9 +113,6 @@ pub fn hash_n_to_m>( } } -pub fn hash_n_to_hash>( - inputs: &[F], - pad: bool, -) -> HashOut { - HashOut::from_vec(hash_n_to_m::(inputs, 4, pad)) +pub fn hash_n_to_hash_no_pad>(inputs: &[F]) -> HashOut { + HashOut::from_vec(hash_n_to_m_no_pad::(inputs, 4)) } diff --git a/plonky2/src/hash/keccak.rs b/plonky2/src/hash/keccak.rs index a537f5e3..9a061d82 100644 --- a/plonky2/src/hash/keccak.rs +++ b/plonky2/src/hash/keccak.rs @@ -56,7 +56,7 @@ impl Hasher for KeccakHash { type Hash = BytesHash; type Permutation = KeccakPermutation; - fn hash(input: &[F], _pad: bool) -> Self::Hash { + fn hash_no_pad(input: &[F]) -> Self::Hash { let mut buffer = Buffer::new(Vec::new()); buffer.write_field_vec(input).unwrap(); let mut arr = [0; N]; diff --git a/plonky2/src/hash/merkle_proofs.rs b/plonky2/src/hash/merkle_proofs.rs index c2f3655d..f90f0657 100644 --- a/plonky2/src/hash/merkle_proofs.rs +++ b/plonky2/src/hash/merkle_proofs.rs @@ -32,7 +32,7 @@ pub(crate) fn verify_merkle_proof>( proof: &MerkleProof, ) -> Result<()> { let mut index = leaf_index; - let mut current_digest = H::hash(&leaf_data, false); + let mut current_digest = H::hash_no_pad(&leaf_data); for &sibling_digest in proof.siblings.iter() { let bit = index & 1; index >>= 1; diff --git a/plonky2/src/hash/merkle_tree.rs b/plonky2/src/hash/merkle_tree.rs index 54c62eeb..e9460c14 100644 --- a/plonky2/src/hash/merkle_tree.rs +++ b/plonky2/src/hash/merkle_tree.rs @@ -63,7 +63,7 @@ fn fill_subtree>( ) -> H::Hash { assert_eq!(leaves.len(), digests_buf.len() / 2 + 1); if digests_buf.is_empty() { - H::hash(&leaves[0], false) + H::hash_no_pad(&leaves[0]) } else { // Layout is: left recursive output || left child digest // || right child digest || right recursive output. @@ -99,7 +99,7 @@ fn fill_digests_buf>( .par_iter_mut() .zip(leaves) .for_each(|(cap_buf, leaf)| { - cap_buf.write(H::hash(leaf, false)); + cap_buf.write(H::hash_no_pad(leaf)); }); return; } diff --git a/plonky2/src/hash/path_compression.rs b/plonky2/src/hash/path_compression.rs index c5c3f36e..56c355fd 100644 --- a/plonky2/src/hash/path_compression.rs +++ b/plonky2/src/hash/path_compression.rs @@ -66,7 +66,7 @@ pub(crate) fn decompress_merkle_proofs>( for (&i, v) in leaves_indices.iter().zip(leaves_data) { // Observe the leaves. - seen.insert(i + num_leaves, H::hash(v, false)); + seen.insert(i + num_leaves, H::hash_no_pad(v)); } // Iterators over the siblings. diff --git a/plonky2/src/hash/poseidon.rs b/plonky2/src/hash/poseidon.rs index d2b47932..08c2851a 100644 --- a/plonky2/src/hash/poseidon.rs +++ b/plonky2/src/hash/poseidon.rs @@ -9,7 +9,7 @@ use crate::gates::gate::Gate; use crate::gates::poseidon::PoseidonGate; use crate::gates::poseidon_mds::PoseidonMdsGate; use crate::hash::hash_types::{HashOut, RichField}; -use crate::hash::hashing::{compress, hash_n_to_hash, PlonkyPermutation, SPONGE_WIDTH}; +use crate::hash::hashing::{compress, hash_n_to_hash_no_pad, PlonkyPermutation, SPONGE_WIDTH}; use crate::iop::ext_target::ExtensionTarget; use crate::iop::target::{BoolTarget, Target}; use crate::plonk::circuit_builder::CircuitBuilder; @@ -633,8 +633,8 @@ impl Hasher for PoseidonHash { type Hash = HashOut; type Permutation = PoseidonPermutation; - fn hash(input: &[F], pad: bool) -> Self::Hash { - hash_n_to_hash::(input, pad) + fn hash_no_pad(input: &[F]) -> Self::Hash { + hash_n_to_hash_no_pad::(input) } fn two_to_one(left: Self::Hash, right: Self::Hash) -> Self::Hash { diff --git a/plonky2/src/iop/challenger.rs b/plonky2/src/iop/challenger.rs index b1a4c12b..c85f30d1 100644 --- a/plonky2/src/iop/challenger.rs +++ b/plonky2/src/iop/challenger.rs @@ -165,7 +165,7 @@ impl> Challenger { self.observe_extension_elements(&final_poly.coeffs); - let fri_pow_response = C::InnerHasher::hash( + let fri_pow_response = C::InnerHasher::hash_no_pad( &self .get_hash() .elements @@ -173,7 +173,6 @@ impl> Challenger { .copied() .chain(Some(pow_witness)) .collect::>(), - false, ) .elements[0]; diff --git a/plonky2/src/plonk/circuit_builder.rs b/plonky2/src/plonk/circuit_builder.rs index 67538d00..cf89bf1a 100644 --- a/plonky2/src/plonk/circuit_builder.rs +++ b/plonky2/src/plonk/circuit_builder.rs @@ -621,7 +621,7 @@ impl, const D: usize> CircuitBuilder { // those hash wires match the claimed public inputs. let num_public_inputs = self.public_inputs.len(); let public_inputs_hash = - self.hash_n_to_hash::(self.public_inputs.clone(), true); + self.hash_n_to_hash_no_pad::(self.public_inputs.clone()); let pi_gate = self.add_gate(PublicInputGate, vec![]); for (&hash_part, wire) in public_inputs_hash .elements @@ -749,7 +749,7 @@ impl, const D: usize> CircuitBuilder { constants_sigmas_cap.flatten(), vec![/* Add other circuit data here */], ]; - let circuit_digest = C::Hasher::hash(&circuit_digest_parts.concat(), false); + let circuit_digest = C::Hasher::hash_no_pad(&circuit_digest_parts.concat()); let common = CommonCircuitData { config: self.config, diff --git a/plonky2/src/plonk/config.rs b/plonky2/src/plonk/config.rs index 281d0025..fdca7037 100644 --- a/plonky2/src/plonk/config.rs +++ b/plonky2/src/plonk/config.rs @@ -31,7 +31,21 @@ pub trait Hasher: Sized + Clone + Debug + Eq + PartialEq { /// Permutation used in the sponge construction. type Permutation: PlonkyPermutation; - fn hash(input: &[F], pad: bool) -> Self::Hash; + /// Hash a message without any padding step. Note that this can enable length-extension attacks. + /// However, it is still collision-resistant in cases where the input has a fixed length. + fn hash_no_pad(input: &[F]) -> Self::Hash; + + /// Pad the message using the `pad10*1` rule, then hash it. + fn hash_pad(input: &[F]) -> Self::Hash { + let mut padded_input = input.to_vec(); + padded_input.push(F::ONE); + while (padded_input.len() + 1) % SPONGE_WIDTH != 0 { + padded_input.push(F::ZERO); + } + padded_input.push(F::ONE); + Self::hash_no_pad(&padded_input) + } + fn two_to_one(left: Self::Hash, right: Self::Hash) -> Self::Hash; } diff --git a/plonky2/src/plonk/proof.rs b/plonky2/src/plonk/proof.rs index 803e64d4..2cf0a885 100644 --- a/plonky2/src/plonk/proof.rs +++ b/plonky2/src/plonk/proof.rs @@ -91,7 +91,7 @@ impl, C: GenericConfig, const D: usize> pub(crate) fn get_public_inputs_hash( &self, ) -> <>::InnerHasher as Hasher>::Hash { - C::InnerHasher::hash(&self.public_inputs, true) + C::InnerHasher::hash_no_pad(&self.public_inputs) } pub fn to_bytes(&self) -> anyhow::Result> { @@ -207,7 +207,7 @@ impl, C: GenericConfig, const D: usize> pub(crate) fn get_public_inputs_hash( &self, ) -> <>::InnerHasher as Hasher>::Hash { - C::InnerHasher::hash(&self.public_inputs, true) + C::InnerHasher::hash_no_pad(&self.public_inputs) } pub fn to_bytes(&self) -> anyhow::Result> { diff --git a/plonky2/src/plonk/prover.rs b/plonky2/src/plonk/prover.rs index 5bc89d25..7a172aff 100644 --- a/plonky2/src/plonk/prover.rs +++ b/plonky2/src/plonk/prover.rs @@ -44,7 +44,7 @@ pub(crate) fn prove, C: GenericConfig, co ); let public_inputs = partition_witness.get_targets(&prover_data.public_inputs); - let public_inputs_hash = C::InnerHasher::hash(&public_inputs, true); + let public_inputs_hash = C::InnerHasher::hash_no_pad(&public_inputs); if cfg!(debug_assertions) { // Display the marked targets for debugging purposes. diff --git a/plonky2/src/plonk/recursive_verifier.rs b/plonky2/src/plonk/recursive_verifier.rs index 5d898d0d..cb2bc1e0 100644 --- a/plonky2/src/plonk/recursive_verifier.rs +++ b/plonky2/src/plonk/recursive_verifier.rs @@ -27,7 +27,7 @@ impl, const D: usize> CircuitBuilder { } = proof_with_pis; assert_eq!(public_inputs.len(), inner_common_data.num_public_inputs); - let public_inputs_hash = self.hash_n_to_hash::(public_inputs, true); + let public_inputs_hash = self.hash_n_to_hash_no_pad::(public_inputs); self.verify_proof( proof,