//! Concrete instantiation of a hash function. use crate::field::extension_field::Extendable; use crate::field::field_types::RichField; use crate::gates::poseidon::PoseidonGate; use crate::hash::hash_types::{HashOut, HashOutTarget}; use crate::iop::target::Target; use crate::plonk::circuit_builder::CircuitBuilder; pub(crate) const SPONGE_RATE: usize = 8; pub(crate) const SPONGE_CAPACITY: usize = 4; pub(crate) const SPONGE_WIDTH: usize = SPONGE_RATE + SPONGE_CAPACITY; pub(crate) const HASH_FAMILY: HashFamily = HashFamily::Poseidon; pub(crate) enum HashFamily { GMiMC, Poseidon, } /// Hash the vector if necessary to reduce its length to ~256 bits. If it already fits, this is a /// no-op. pub fn hash_or_noop(inputs: Vec) -> HashOut { if inputs.len() <= 4 { HashOut::from_partial(inputs) } else { hash_n_to_hash(inputs, false) } } impl, const D: usize> CircuitBuilder { pub fn hash_or_noop(&mut self, inputs: Vec) -> HashOutTarget { let zero = self.zero(); if inputs.len() <= 4 { HashOutTarget::from_partial(inputs, zero) } else { self.hash_n_to_hash(inputs, false) } } pub fn hash_n_to_hash(&mut self, inputs: Vec, pad: bool) -> HashOutTarget { HashOutTarget::from_vec(self.hash_n_to_m(inputs, 4, pad)) } pub fn hash_n_to_m( &mut self, mut 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]; // Absorb all input chunks. for input_chunk in inputs.chunks(SPONGE_RATE) { // Overwrite the first r elements with the inputs. This differs from a standard sponge, // where we would xor or add in the inputs. This is a well-known variant, though, // sometimes called "overwrite mode". state[..input_chunk.len()].copy_from_slice(input_chunk); state = self.permute(state); } // Squeeze until we have the desired number of outputs. let mut outputs = Vec::new(); loop { for i in 0..SPONGE_RATE { outputs.push(state[i]); if outputs.len() == num_outputs { return outputs; } } state = self.permute(state); } } } /// A one-way compression function which takes two ~256 bit inputs and returns a ~256 bit output. pub fn compress(x: HashOut, y: HashOut) -> HashOut { let mut inputs = Vec::with_capacity(8); inputs.extend(&x.elements); inputs.extend(&y.elements); hash_n_to_hash(inputs, false) } /// 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(mut inputs: Vec, num_outputs: usize, pad: bool) -> Vec { if pad { inputs.push(F::ZERO); while (inputs.len() + 1) % SPONGE_WIDTH != 0 { inputs.push(F::ONE); } inputs.push(F::ZERO); } let mut state = [F::ZERO; SPONGE_WIDTH]; // Absorb all input chunks. for input_chunk in inputs.chunks(SPONGE_RATE) { for i in 0..input_chunk.len() { state[i] = input_chunk[i]; } state = permute(state); } // Squeeze until we have the desired number of outputs. let mut outputs = Vec::new(); loop { for &item in state.iter().take(SPONGE_RATE) { outputs.push(item); if outputs.len() == num_outputs { return outputs; } } state = permute(state); } } pub fn hash_n_to_hash(inputs: Vec, pad: bool) -> HashOut { HashOut::from_vec(hash_n_to_m(inputs, 4, pad)) } pub fn hash_n_to_1(inputs: Vec, pad: bool) -> F { hash_n_to_m(inputs, 1, pad)[0] } pub(crate) fn permute(inputs: [F; SPONGE_WIDTH]) -> [F; SPONGE_WIDTH] { match HASH_FAMILY { HashFamily::GMiMC => F::gmimc_permute(inputs), HashFamily::Poseidon => F::poseidon(inputs), } }