//! The Poseidon algebraic hash function. use std::convert::TryInto; use std::fmt; use std::marker::PhantomData; use group::ff::{Field, PrimeField}; use halo2_proofs::{ circuit::{AssignedCell, Chip, Layouter}, plonk::Error, }; mod pow5; pub use pow5::{Pow5Chip, Pow5Config, StateWord}; pub mod primitives; use primitives::{Absorbing, ConstantLength, Domain, Spec, SpongeMode, Squeezing, State}; /// A word from the padded input to a Poseidon sponge. #[derive(Clone, Debug)] pub enum PaddedWord { /// A message word provided by the prover. Message(AssignedCell), /// A padding word, that will be fixed in the circuit parameters. Padding(F), } /// The set of circuit instructions required to use the Poseidon permutation. pub trait PoseidonInstructions, const T: usize, const RATE: usize>: Chip { /// Variable representing the word over which the Poseidon permutation operates. type Word: Clone + fmt::Debug + From> + Into>; /// Applies the Poseidon permutation to the given state. fn permute( &self, layouter: &mut impl Layouter, initial_state: &State, ) -> Result, Error>; } /// The set of circuit instructions required to use the [`Sponge`] and [`Hash`] gadgets. /// /// [`Hash`]: self::Hash pub trait PoseidonSpongeInstructions< F: Field, S: Spec, D: Domain, const T: usize, const RATE: usize, >: PoseidonInstructions { /// Returns the initial empty state for the given domain. fn initial_state(&self, layouter: &mut impl Layouter) -> Result, Error>; /// Adds the given input to the state. fn add_input( &self, layouter: &mut impl Layouter, initial_state: &State, input: &Absorbing, RATE>, ) -> Result, Error>; /// Extracts sponge output from the given state. fn get_output(state: &State) -> Squeezing; } /// A word over which the Poseidon permutation operates. #[derive(Debug)] pub struct Word< F: Field, PoseidonChip: PoseidonInstructions, S: Spec, const T: usize, const RATE: usize, > { inner: PoseidonChip::Word, } impl< F: Field, PoseidonChip: PoseidonInstructions, S: Spec, const T: usize, const RATE: usize, > Word { /// The word contained in this gadget. pub fn inner(&self) -> PoseidonChip::Word { self.inner.clone() } /// Construct a [`Word`] gadget from the inner word. pub fn from_inner(inner: PoseidonChip::Word) -> Self { Self { inner } } } fn poseidon_sponge< F: Field, PoseidonChip: PoseidonSpongeInstructions, S: Spec, D: Domain, const T: usize, const RATE: usize, >( chip: &PoseidonChip, mut layouter: impl Layouter, state: &mut State, input: Option<&Absorbing, RATE>>, ) -> Result, Error> { if let Some(input) = input { *state = chip.add_input(&mut layouter, state, input)?; } *state = chip.permute(&mut layouter, state)?; Ok(PoseidonChip::get_output(state)) } /// A Poseidon sponge. #[derive(Debug)] pub struct Sponge< F: Field, PoseidonChip: PoseidonSpongeInstructions, S: Spec, M: SpongeMode, D: Domain, const T: usize, const RATE: usize, > { chip: PoseidonChip, mode: M, state: State, _marker: PhantomData, } impl< F: Field, PoseidonChip: PoseidonSpongeInstructions, S: Spec, D: Domain, const T: usize, const RATE: usize, > Sponge, RATE>, D, T, RATE> { /// Constructs a new duplex sponge for the given Poseidon specification. pub fn new(chip: PoseidonChip, mut layouter: impl Layouter) -> Result { chip.initial_state(&mut layouter).map(|state| Sponge { chip, mode: Absorbing( (0..RATE) .map(|_| None) .collect::>() .try_into() .unwrap(), ), state, _marker: PhantomData::default(), }) } /// Absorbs an element into the sponge. pub fn absorb( &mut self, mut layouter: impl Layouter, value: PaddedWord, ) -> Result<(), Error> { for entry in self.mode.0.iter_mut() { if entry.is_none() { *entry = Some(value); return Ok(()); } } // We've already absorbed as many elements as we can let _ = poseidon_sponge( &self.chip, layouter.namespace(|| "PoseidonSponge"), &mut self.state, Some(&self.mode), )?; self.mode = Absorbing::init_with(value); Ok(()) } /// Transitions the sponge into its squeezing state. #[allow(clippy::type_complexity)] pub fn finish_absorbing( mut self, mut layouter: impl Layouter, ) -> Result, D, T, RATE>, Error> { let mode = poseidon_sponge( &self.chip, layouter.namespace(|| "PoseidonSponge"), &mut self.state, Some(&self.mode), )?; Ok(Sponge { chip: self.chip, mode, state: self.state, _marker: PhantomData::default(), }) } } impl< F: Field, PoseidonChip: PoseidonSpongeInstructions, S: Spec, D: Domain, const T: usize, const RATE: usize, > Sponge, D, T, RATE> { /// Squeezes an element from the sponge. pub fn squeeze(&mut self, mut layouter: impl Layouter) -> Result, Error> { loop { for entry in self.mode.0.iter_mut() { if let Some(inner) = entry.take() { return Ok(inner.into()); } } // We've already squeezed out all available elements self.mode = poseidon_sponge( &self.chip, layouter.namespace(|| "PoseidonSponge"), &mut self.state, None, )?; } } } /// A Poseidon hash function, built around a sponge. #[derive(Debug)] pub struct Hash< F: Field, PoseidonChip: PoseidonSpongeInstructions, S: Spec, D: Domain, const T: usize, const RATE: usize, > { sponge: Sponge, RATE>, D, T, RATE>, } impl< F: Field, PoseidonChip: PoseidonSpongeInstructions, S: Spec, D: Domain, const T: usize, const RATE: usize, > Hash { /// Initializes a new hasher. pub fn init(chip: PoseidonChip, layouter: impl Layouter) -> Result { Sponge::new(chip, layouter).map(|sponge| Hash { sponge }) } } impl< F: PrimeField, PoseidonChip: PoseidonSpongeInstructions, T, RATE>, S: Spec, const T: usize, const RATE: usize, const L: usize, > Hash, T, RATE> { /// Hashes the given input. pub fn hash( mut self, mut layouter: impl Layouter, message: [AssignedCell; L], ) -> Result, Error> { for (i, value) in message .into_iter() .map(PaddedWord::Message) .chain( as Domain>::padding(L).map(PaddedWord::Padding)) .enumerate() { self.sponge .absorb(layouter.namespace(|| format!("absorb_{}", i)), value)?; } self.sponge .finish_absorbing(layouter.namespace(|| "finish absorbing"))? .squeeze(layouter.namespace(|| "squeeze")) } }