diff --git a/src/gadget.rs b/src/gadget.rs index a876ca79..ccf85126 100644 --- a/src/gadget.rs +++ b/src/gadget.rs @@ -1 +1,3 @@ //! Self-contained circuit implementations of various primitives. + +pub mod sha256; diff --git a/src/gadget/sha256.rs b/src/gadget/sha256.rs new file mode 100644 index 00000000..3f4f1e62 --- /dev/null +++ b/src/gadget/sha256.rs @@ -0,0 +1,155 @@ +//! Gadget and chips for the [SHA-256] hash function. +//! +//! [SHA-256]: https://tools.ietf.org/html/rfc6234 + +use std::cmp::min; +use std::convert::TryInto; +use std::fmt; + +use crate::{ + circuit::{Chip, Layouter}, + plonk::Error, +}; + +/// The size of a SHA-256 block, in 32-bit words. +const BLOCK_SIZE: usize = 16; +/// The size of a SHA-256 digest, in 32-bit words. +const DIGEST_SIZE: usize = 8; + +/// The set of circuit instructions required to use the [`Sha256`] gadget. +pub trait Sha256Instructions: Chip { + /// Variable representing the SHA-256 internal state. + type State: Clone + fmt::Debug; + /// Variable representing a 32-bit word of the input block to the SHA-256 compression + /// function. + type BlockWord: Copy + fmt::Debug; + + /// The zero BlockWord + fn zero() -> Self::BlockWord; + + /// Places the SHA-256 IV in the circuit, returning the initial state variable. + fn initialization_vector(layouter: &mut impl Layouter) -> Result; + + /// Creates an initial state from the output state of a previous block + fn initialization( + layouter: &mut impl Layouter, + init_state: &Self::State, + ) -> Result; + + /// Starting from the given initialized state, processes a block of input and returns the + /// final state. + fn compress( + layouter: &mut impl Layouter, + initialized_state: &Self::State, + input: [Self::BlockWord; BLOCK_SIZE], + ) -> Result; + + /// Converts the given state into a message digest. + fn digest( + layouter: &mut impl Layouter, + state: &Self::State, + ) -> Result<[Self::BlockWord; DIGEST_SIZE], Error>; +} + +/// The output of a SHA-256 circuit invocation. +#[derive(Debug)] +pub struct Sha256Digest([BlockWord; DIGEST_SIZE]); + +/// A gadget that constrains a SHA-256 invocation. It supports input at a granularity of +/// 32 bits. +#[derive(Debug)] +pub struct Sha256 { + state: CS::State, + cur_block: Vec, + length: usize, +} + +impl Sha256 { + /// Create a new hasher instance. + pub fn new(layouter: &mut impl Layouter) -> Result { + Ok(Sha256 { + state: Sha256Chip::initialization_vector(layouter)?, + cur_block: Vec::with_capacity(BLOCK_SIZE), + length: 0, + }) + } + + /// Digest data, updating the internal state. + pub fn update( + &mut self, + layouter: &mut impl Layouter, + mut data: &[Sha256Chip::BlockWord], + ) -> Result<(), Error> { + self.length += data.len() * 32; + + // Fill the current block, if possible. + let remaining = BLOCK_SIZE - self.cur_block.len(); + let (l, r) = data.split_at(min(remaining, data.len())); + self.cur_block.extend_from_slice(l); + data = r; + + // If we still don't have a full block, we are done. + if self.cur_block.len() < BLOCK_SIZE { + return Ok(()); + } + + // Process the now-full current block. + self.state = Sha256Chip::compress( + layouter, + &self.state, + self.cur_block[..] + .try_into() + .expect("cur_block.len() == BLOCK_SIZE"), + )?; + self.cur_block.clear(); + + // Process any additional full blocks. + let mut chunks_iter = data.chunks_exact(BLOCK_SIZE); + for chunk in &mut chunks_iter { + self.state = Sha256Chip::initialization(layouter, &self.state)?; + self.state = Sha256Chip::compress( + layouter, + &self.state, + chunk.try_into().expect("chunk.len() == BLOCK_SIZE"), + )?; + } + + // Cache the remaining partial block, if any. + let rem = chunks_iter.remainder(); + self.cur_block.extend_from_slice(rem); + + Ok(()) + } + + /// Retrieve result and consume hasher instance. + pub fn finalize( + mut self, + layouter: &mut impl Layouter, + ) -> Result, Error> { + // Pad the remaining block + if !self.cur_block.is_empty() { + let padding = vec![Sha256Chip::zero(); BLOCK_SIZE - self.cur_block.len()]; + self.cur_block.extend_from_slice(&padding); + self.state = Sha256Chip::initialization(layouter, &self.state)?; + self.state = Sha256Chip::compress( + layouter, + &self.state, + self.cur_block[..] + .try_into() + .expect("cur_block.len() == BLOCK_SIZE"), + )?; + } + Sha256Chip::digest(layouter, &self.state).map(Sha256Digest) + } + + /// Convenience function to compute hash of the data. It will handle hasher creation, + /// data feeding and finalization. + pub fn digest( + layouter: &mut impl Layouter, + data: &[Sha256Chip::BlockWord], + ) -> Result, Error> { + let mut hasher = Self::new(layouter)?; + hasher.update(layouter, data)?; + hasher.finalize(layouter) + } +}