use std::convert::TryInto; use std::marker::PhantomData; use super::Sha256Instructions; use halo2::{ circuit::{AssignedCell, Chip, Layouter, Region}, pasta::pallas, plonk::{Advice, Any, Assigned, Column, ConstraintSystem, Error}, }; mod compression; mod gates; mod message_schedule; mod spread_table; mod util; use compression::*; use gates::*; use message_schedule::*; use spread_table::*; use util::*; const ROUNDS: usize = 64; const STATE: usize = 8; #[allow(clippy::unreadable_literal)] pub(crate) const ROUND_CONSTANTS: [u32; ROUNDS] = [ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2, ]; const IV: [u32; STATE] = [ 0x6a09_e667, 0xbb67_ae85, 0x3c6e_f372, 0xa54f_f53a, 0x510e_527f, 0x9b05_688c, 0x1f83_d9ab, 0x5be0_cd19, ]; #[derive(Clone, Copy, Debug, Default)] /// A word in a `Table16` message block. pub struct BlockWord(pub(crate) Option); #[derive(Clone, Debug)] /// Little-endian bits (up to 64 bits) pub struct Bits([bool; LEN]); impl Bits { fn spread(&self) -> [bool; SPREAD] { spread_bits(self.0) } } impl std::ops::Deref for Bits { type Target = [bool; LEN]; fn deref(&self) -> &Self::Target { &self.0 } } impl From<[bool; LEN]> for Bits { fn from(bits: [bool; LEN]) -> Self { Self(bits) } } impl From<&Bits> for [bool; LEN] { fn from(bits: &Bits) -> Self { bits.0 } } impl From<&Bits> for Assigned { fn from(bits: &Bits) -> Assigned { assert!(LEN <= 64); pallas::Base::from(lebs2ip(&bits.0)).into() } } impl From<&Bits<16>> for u16 { fn from(bits: &Bits<16>) -> u16 { lebs2ip(&bits.0) as u16 } } impl From for Bits<16> { fn from(int: u16) -> Bits<16> { Bits(i2lebsp::<16>(int.into())) } } impl From<&Bits<32>> for u32 { fn from(bits: &Bits<32>) -> u32 { lebs2ip(&bits.0) as u32 } } impl From for Bits<32> { fn from(int: u32) -> Bits<32> { Bits(i2lebsp::<32>(int.into())) } } #[derive(Clone, Debug)] pub struct AssignedBits(AssignedCell, pallas::Base>); impl std::ops::Deref for AssignedBits { type Target = AssignedCell, pallas::Base>; fn deref(&self) -> &Self::Target { &self.0 } } impl AssignedBits { fn assign_bits + std::fmt::Debug + Clone>( region: &mut Region<'_, pallas::Base>, annotation: A, column: impl Into>, offset: usize, value: Option, ) -> Result where A: Fn() -> AR, AR: Into, >::Error: std::fmt::Debug, { let value: Option<[bool; LEN]> = value.map(|v| v.try_into().unwrap()); let value: Option> = value.map(|v| v.into()); let column: Column = column.into(); match column.column_type() { Any::Advice => { region.assign_advice(annotation, column.try_into().unwrap(), offset, || { value.clone().ok_or(Error::Synthesis) }) } Any::Fixed => { region.assign_fixed(annotation, column.try_into().unwrap(), offset, || { value.clone().ok_or(Error::Synthesis) }) } _ => panic!("Cannot assign to instance column"), } .map(AssignedBits) } } impl AssignedBits<16> { fn value_u16(&self) -> Option { self.value().map(|v| v.into()) } fn assign( region: &mut Region<'_, pallas::Base>, annotation: A, column: impl Into>, offset: usize, value: Option, ) -> Result where A: Fn() -> AR, AR: Into, { let column: Column = column.into(); let value: Option> = value.map(|v| v.into()); match column.column_type() { Any::Advice => { region.assign_advice(annotation, column.try_into().unwrap(), offset, || { value.clone().ok_or(Error::Synthesis) }) } Any::Fixed => { region.assign_fixed(annotation, column.try_into().unwrap(), offset, || { value.clone().ok_or(Error::Synthesis) }) } _ => panic!("Cannot assign to instance column"), } .map(AssignedBits) } } impl AssignedBits<32> { fn value_u32(&self) -> Option { self.value().map(|v| v.into()) } fn assign( region: &mut Region<'_, pallas::Base>, annotation: A, column: impl Into>, offset: usize, value: Option, ) -> Result where A: Fn() -> AR, AR: Into, { let column: Column = column.into(); let value: Option> = value.map(|v| v.into()); match column.column_type() { Any::Advice => { region.assign_advice(annotation, column.try_into().unwrap(), offset, || { value.clone().ok_or(Error::Synthesis) }) } Any::Fixed => { region.assign_fixed(annotation, column.try_into().unwrap(), offset, || { value.clone().ok_or(Error::Synthesis) }) } _ => panic!("Cannot assign to instance column"), } .map(AssignedBits) } } /// Configuration for a [`Table16Chip`]. #[derive(Clone, Debug)] pub struct Table16Config { lookup: SpreadTableConfig, message_schedule: MessageScheduleConfig, compression: CompressionConfig, } /// A chip that implements SHA-256 with a maximum lookup table size of $2^16$. #[derive(Clone, Debug)] pub struct Table16Chip { config: Table16Config, _marker: PhantomData, } impl Chip for Table16Chip { type Config = Table16Config; type Loaded = (); fn config(&self) -> &Self::Config { &self.config } fn loaded(&self) -> &Self::Loaded { &() } } impl Table16Chip { pub fn construct(config: >::Config) -> Self { Self { config, _marker: PhantomData, } } pub fn configure( meta: &mut ConstraintSystem, ) -> >::Config { // Columns required by this chip: let message_schedule = meta.advice_column(); let extras = [ meta.advice_column(), meta.advice_column(), meta.advice_column(), meta.advice_column(), meta.advice_column(), meta.advice_column(), ]; // - Three advice columns to interact with the lookup table. let input_tag = meta.advice_column(); let input_dense = meta.advice_column(); let input_spread = meta.advice_column(); let lookup = SpreadTableChip::configure(meta, input_tag, input_dense, input_spread); let lookup_inputs = lookup.input.clone(); // Rename these here for ease of matching the gates to the specification. let _a_0 = lookup_inputs.tag; let a_1 = lookup_inputs.dense; let a_2 = lookup_inputs.spread; let a_3 = extras[0]; let a_4 = extras[1]; let a_5 = message_schedule; let a_6 = extras[2]; let a_7 = extras[3]; let a_8 = extras[4]; let _a_9 = extras[5]; // Add all advice columns to permutation for column in [a_1, a_2, a_3, a_4, a_5, a_6, a_7, a_8].iter() { meta.enable_equality(*column); } let compression = CompressionConfig::configure(meta, lookup_inputs.clone(), message_schedule, extras); let message_schedule = MessageScheduleConfig::configure(meta, lookup_inputs, message_schedule, extras); Table16Config { lookup, message_schedule, compression, } } pub fn load( config: Table16Config, layouter: &mut impl Layouter, ) -> Result<(), Error> { SpreadTableChip::load(config.lookup, layouter) } } impl Sha256Instructions for Table16Chip { type State = State; type BlockWord = BlockWord; fn initialization_vector( &self, layouter: &mut impl Layouter, ) -> Result { self.config().compression.initialize_with_iv(layouter, IV) } fn initialization( &self, layouter: &mut impl Layouter, init_state: &Self::State, ) -> Result { self.config() .compression .initialize_with_state(layouter, init_state.clone()) } // Given an initialized state and an input message block, compress the // message block and return the final state. fn compress( &self, layouter: &mut impl Layouter, initialized_state: &Self::State, input: [Self::BlockWord; super::BLOCK_SIZE], ) -> Result { let config = self.config(); let (_, w_halves) = config.message_schedule.process(layouter, input)?; config .compression .compress(layouter, initialized_state.clone(), w_halves) } fn digest( &self, layouter: &mut impl Layouter, state: &Self::State, ) -> Result<[Self::BlockWord; super::DIGEST_SIZE], Error> { // Copy the dense forms of the state variable chunks down to this gate. // Reconstruct the 32-bit dense words. self.config().compression.digest(layouter, state.clone()) } } /// Common assignment patterns used by Table16 regions. trait Table16Assignment { /// Assign cells for general spread computation used in sigma, ch, ch_neg, maj gates #[allow(clippy::too_many_arguments)] #[allow(clippy::type_complexity)] fn assign_spread_outputs( &self, region: &mut Region<'_, pallas::Base>, lookup: &SpreadInputs, a_3: Column, row: usize, r_0_even: Option<[bool; 16]>, r_0_odd: Option<[bool; 16]>, r_1_even: Option<[bool; 16]>, r_1_odd: Option<[bool; 16]>, ) -> Result< ( (AssignedBits<16>, AssignedBits<16>), (AssignedBits<16>, AssignedBits<16>), ), Error, > { // Lookup R_0^{even}, R_0^{odd}, R_1^{even}, R_1^{odd} let r_0_even = SpreadVar::with_lookup( region, lookup, row - 1, r_0_even.map(SpreadWord::<16, 32>::new), )?; let r_0_odd = SpreadVar::with_lookup(region, lookup, row, r_0_odd.map(SpreadWord::<16, 32>::new))?; let r_1_even = SpreadVar::with_lookup( region, lookup, row + 1, r_1_even.map(SpreadWord::<16, 32>::new), )?; let r_1_odd = SpreadVar::with_lookup( region, lookup, row + 2, r_1_odd.map(SpreadWord::<16, 32>::new), )?; // Assign and copy R_1^{odd} r_1_odd .spread .copy_advice(|| "Assign and copy R_1^{odd}", region, a_3, row)?; Ok(( (r_0_even.dense, r_1_even.dense), (r_0_odd.dense, r_1_odd.dense), )) } /// Assign outputs of sigma gates #[allow(clippy::too_many_arguments)] fn assign_sigma_outputs( &self, region: &mut Region<'_, pallas::Base>, lookup: &SpreadInputs, a_3: Column, row: usize, r_0_even: Option<[bool; 16]>, r_0_odd: Option<[bool; 16]>, r_1_even: Option<[bool; 16]>, r_1_odd: Option<[bool; 16]>, ) -> Result<(AssignedBits<16>, AssignedBits<16>), Error> { let (even, _odd) = self.assign_spread_outputs( region, lookup, a_3, row, r_0_even, r_0_odd, r_1_even, r_1_odd, )?; Ok(even) } } #[cfg(test)] #[cfg(feature = "dev-graph")] mod tests { use super::super::{Sha256, BLOCK_SIZE}; use super::{message_schedule::msg_schedule_test_input, Table16Chip, Table16Config}; use halo2::{ circuit::{Layouter, SimpleFloorPlanner}, pasta::pallas, plonk::{Circuit, ConstraintSystem, Error}, }; #[test] fn print_sha256_circuit() { use plotters::prelude::*; struct MyCircuit {} impl Circuit for MyCircuit { type Config = Table16Config; type FloorPlanner = SimpleFloorPlanner; fn without_witnesses(&self) -> Self { MyCircuit {} } fn configure(meta: &mut ConstraintSystem) -> Self::Config { Table16Chip::configure(meta) } fn synthesize( &self, config: Self::Config, mut layouter: impl Layouter, ) -> Result<(), Error> { let table16_chip = Table16Chip::construct(config.clone()); Table16Chip::load(config, &mut layouter)?; // Test vector: "abc" let test_input = msg_schedule_test_input(); // Create a message of length 31 blocks let mut input = Vec::with_capacity(31 * BLOCK_SIZE); for _ in 0..31 { input.extend_from_slice(&test_input); } Sha256::digest(table16_chip, layouter.namespace(|| "'abc' * 31"), &input)?; Ok(()) } } let root = BitMapBackend::new("sha-256-table16-chip-layout.png", (1024, 3480)).into_drawing_area(); root.fill(&WHITE).unwrap(); let root = root .titled("16-bit Table SHA-256 Chip", ("sans-serif", 60)) .unwrap(); let circuit = MyCircuit {}; halo2::dev::CircuitLayout::default() .render::(17, &circuit, &root) .unwrap(); } }