From 570f90e4ee6da1b8915846030245c4114c1fc735 Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Wed, 6 Jan 2021 09:16:30 +0800 Subject: [PATCH] SHA-256 chip that uses a 2^16 lookup table Co-authored-by: Jack Grigg --- src/circuit.rs | 2 + src/gadget/sha256.rs | 4 + src/gadget/sha256/table16.rs | 347 ++++++++++++ src/gadget/sha256/table16/spread_table.rs | 608 ++++++++++++++++++++++ src/gadget/sha256/table16/util.rs | 95 ++++ src/plonk/circuit.rs | 2 + 6 files changed, 1058 insertions(+) create mode 100644 src/gadget/sha256/table16.rs create mode 100644 src/gadget/sha256/table16/spread_table.rs create mode 100644 src/gadget/sha256/table16/util.rs diff --git a/src/circuit.rs b/src/circuit.rs index 79078bc..5065833 100644 --- a/src/circuit.rs +++ b/src/circuit.rs @@ -79,7 +79,9 @@ impl std::ops::Deref for RegionStart { pub struct Cell { /// Identifies the region in which this cell resides. region_index: RegionIndex, + /// The relative offset of this cell within its region. row_offset: usize, + /// The column of this cell. column: Column, } diff --git a/src/gadget/sha256.rs b/src/gadget/sha256.rs index 2dc32fd..0b5fd61 100644 --- a/src/gadget/sha256.rs +++ b/src/gadget/sha256.rs @@ -11,6 +11,10 @@ use crate::{ plonk::Error, }; +mod table16; + +pub use table16::Table16Chip; + /// 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. diff --git a/src/gadget/sha256/table16.rs b/src/gadget/sha256/table16.rs new file mode 100644 index 0000000..0bf0d19 --- /dev/null +++ b/src/gadget/sha256/table16.rs @@ -0,0 +1,347 @@ +use std::marker::PhantomData; + +use super::Sha256Instructions; +use crate::{ + arithmetic::FieldExt, + circuit::{Cell, Chip, Layouter, Region}, + plonk::{Advice, Column, ConstraintSystem, Error, Permutation}, +}; + +mod spread_table; +mod util; + +use spread_table::*; + +const ROUNDS: usize = 64; +const STATE: usize = 8; + +#[allow(clippy::unreadable_literal)] +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)] +pub struct BlockWord { + var: (), + value: Option, +} + +impl BlockWord { + pub fn new(value: u32) -> Self { + BlockWord { + var: (), + value: Some(value), + } + } +} + +#[derive(Clone, Copy, Debug)] +pub struct CellValue16 { + var: Cell, + value: Option, +} + +impl CellValue16 { + pub fn new(var: Cell, value: u16) -> Self { + CellValue16 { + var, + value: Some(value), + } + } +} + +#[derive(Clone, Copy, Debug)] +pub struct CellValue32 { + var: Cell, + value: Option, +} + +impl CellValue32 { + pub fn new(var: Cell, value: u32) -> Self { + CellValue32 { + var, + value: Some(value), + } + } +} + +impl Into for CellValue16 { + fn into(self) -> CellValue32 { + CellValue32::new(self.var, self.value.unwrap() as u32) + } +} + +/// A variable that represents the `[A,B,C,D]` words of the SHA-256 internal state. +/// +/// The structure of this variable is influenced by the following factors: +/// - In `Σ_0(A)` we need `A` to be split into pieces `(a,b,c,d)` of lengths `(2,11,9,10)` +/// bits respectively (counting from the little end), as well as their spread forms. +/// - `Maj(A,B,C)` requires having the bits of each input in spread form. For `A` we can +/// reuse the pieces from `Σ_0(A)`. Since `B` and `C` are assigned from `A` and `B` +/// respectively in each round, we therefore also have the same pieces in earlier rows. +/// We align the columns to make it efficient to copy-constrain these forms where they +/// are needed. +#[derive(Copy, Clone, Debug)] +pub struct AbcdVar { + idx: i32, + val: u32, + a: SpreadVar, + b: SpreadVar, + c_lo: SpreadVar, + c_mid: SpreadVar, + c_hi: SpreadVar, + d: SpreadVar, +} + +/// A variable that represents the `[E,F,G,H]` words of the SHA-256 internal state. +/// +/// The structure of this variable is influenced by the following factors: +/// - In `Σ_1(E)` we need `E` to be split into pieces `(a,b,c,d)` of lengths `(6,5,14,7)` +/// bits respectively (counting from the little end), as well as their spread forms. +/// - `Ch(E,F,G)` requires having the bits of each input in spread form. For `E` we can +/// reuse the pieces from `Σ_1(E)`. Since `F` and `G` are assigned from `E` and `F` +/// respectively in each round, we therefore also have the same pieces in earlier rows. +/// We align the columns to make it efficient to copy-constrain these forms where they +/// are needed. +#[derive(Copy, Clone, Debug)] +pub struct EfghVar { + idx: i32, + val: u32, + a_lo: SpreadVar, + a_hi: SpreadVar, + b_lo: SpreadVar, + b_hi: SpreadVar, + c: SpreadVar, + d: SpreadVar, +} + +/// The internal state for SHA-256. +#[derive(Clone, Debug)] +pub struct State { + h_0: AbcdVar, + h_1: AbcdVar, + h_2: AbcdVar, + h_3: AbcdVar, + h_4: EfghVar, + h_5: EfghVar, + h_6: EfghVar, + h_7: EfghVar, +} + +#[derive(Clone, Debug)] +struct HPrime {} + +/// Configuration for a [`Table16Chip`]. +#[derive(Clone, Debug)] +pub struct Table16Config { + lookup_table: SpreadTable, +} + +/// A chip that implements SHA-256 with a maximum lookup table size of $2^16$. +#[derive(Clone, Debug)] +pub struct Table16Chip { + _marker: PhantomData, +} + +impl Table16Chip { + /// Configures this chip for use in a circuit. + pub fn configure(meta: &mut ConstraintSystem) -> Table16Config { + // Columns required by this chip: + // - Three advice columns to interact with the lookup table. + let tag = meta.advice_column(); + let dense = meta.advice_column(); + let spread = meta.advice_column(); + + 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(), + ]; + + let (lookup_inputs, lookup_table) = SpreadTable::configure(meta, tag, dense, spread); + + // 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]; + + let perm = Permutation::new( + meta, + &[ + a_1.into(), + a_2.into(), + a_3.into(), + a_4.into(), + a_5.into(), + a_6.into(), + a_7.into(), + a_8.into(), + ], + ); + + Table16Config { lookup_table } + } +} + +impl Chip for Table16Chip { + type Field = F; + type Config = Table16Config; + type Loaded = (); + + fn load(layouter: &mut impl Layouter) -> Result<(), Error> { + let table = layouter.config().lookup_table.clone(); + table.load(layouter) + } +} + +impl Sha256Instructions for Table16Chip { + type State = State; + type BlockWord = BlockWord; + + fn zero() -> Self::BlockWord { + BlockWord::new(0) + } + + fn initialization_vector(layouter: &mut impl Layouter) -> Result { + todo!() + } + + fn initialization( + layouter: &mut impl Layouter>, + init_state: &Self::State, + ) -> Result { + todo!() + } + + fn compress( + layouter: &mut impl Layouter, + initialized_state: &Self::State, + input: [Self::BlockWord; super::BLOCK_SIZE], + ) -> Result { + let config = layouter.config().clone(); + + todo!() + } + + fn digest( + 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. + todo!() + } +} + +/// Common assignment patterns used by Table16 regions. +trait Table16Assignment { + // Assign cells for general spread computation used in sigma, ch, ch_neg, maj gates + fn assign_spread_outputs( + &self, + region: &mut Region<'_, Table16Chip>, + lookup: &SpreadInputs, + a_3: Column, + perm: &Permutation, + row: usize, + r_0_even: u16, + r_0_odd: u16, + r_1_even: u16, + r_1_odd: u16, + ) -> Result<((CellValue16, CellValue16), (CellValue16, CellValue16)), 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, SpreadWord::new(r_0_even))?; + let r_0_odd = SpreadVar::with_lookup(region, lookup, row, SpreadWord::new(r_0_odd))?; + let r_1_even = SpreadVar::with_lookup(region, lookup, row + 1, SpreadWord::new(r_1_even))?; + let r_1_odd = SpreadVar::with_lookup(region, lookup, row + 2, SpreadWord::new(r_1_odd))?; + + // Assign and copy R_1^{odd} + let r_1_odd_spread = region.assign_advice( + || "Assign and copy R_1^{odd}", + a_3, + row, + || Ok(F::from_u64(r_1_odd.spread.value.unwrap().into())), + )?; + region.constrain_equal(perm, r_1_odd.spread.var, r_1_odd_spread)?; + + Ok(( + ( + CellValue16::new(r_0_even.dense.var, r_0_even.dense.value.unwrap()), + CellValue16::new(r_1_even.dense.var, r_1_even.dense.value.unwrap()), + ), + ( + CellValue16::new(r_0_odd.dense.var, r_0_odd.dense.value.unwrap()), + CellValue16::new(r_1_odd.dense.var, r_1_odd.dense.value.unwrap()), + ), + )) + } + + // Assign outputs of sigma gates + fn assign_sigma_outputs( + &self, + region: &mut Region<'_, Table16Chip>, + lookup: &SpreadInputs, + a_3: Column, + perm: &Permutation, + row: usize, + r_0_even: u16, + r_0_odd: u16, + r_1_even: u16, + r_1_odd: u16, + ) -> Result<(CellValue16, CellValue16), Error> { + let (even, _odd) = self.assign_spread_outputs( + region, lookup, a_3, perm, row, r_0_even, r_0_odd, r_1_even, r_1_odd, + )?; + + Ok(even) + } + + // Assign a cell the same value as another cell and set up a copy constraint between them + fn assign_and_constrain( + &self, + region: &mut Region<'_, Table16Chip>, + annotation: A, + column: Column, + row: usize, + copy: &CellValue32, + perm: &Permutation, + ) -> Result + where + A: Fn() -> AR, + AR: Into, + { + let cell = region.assign_advice(annotation, column, row, || { + Ok(F::from_u64(copy.value.unwrap() as u64)) + })?; + region.constrain_equal(perm, cell, copy.var)?; + Ok(cell) + } +} diff --git a/src/gadget/sha256/table16/spread_table.rs b/src/gadget/sha256/table16/spread_table.rs new file mode 100644 index 0000000..fe5b66c --- /dev/null +++ b/src/gadget/sha256/table16/spread_table.rs @@ -0,0 +1,608 @@ +use super::{util::*, CellValue16, CellValue32, Table16Chip}; +use crate::{ + arithmetic::FieldExt, + circuit::{Chip, Layouter, Region}, + plonk::{Advice, Column, ConstraintSystem, Error, Fixed}, + poly::Rotation, +}; + +/// An input word into a lookup, containing (tag, dense, spread) +#[derive(Copy, Clone, Debug)] +pub(super) struct SpreadWord { + pub tag: u8, + pub dense: u16, + pub spread: u32, +} + +impl SpreadWord { + pub(super) fn new(word: u16) -> Self { + SpreadWord { + tag: get_tag(word), + dense: word, + spread: interleave_u16_with_zeros(word), + } + } +} + +/// A variable stored in advice columns corresponding to a row of [`SpreadTable`]. +#[derive(Copy, Clone, Debug)] +pub(super) struct SpreadVar { + pub tag: u8, + pub dense: CellValue16, + pub spread: CellValue32, +} + +impl SpreadVar { + pub(super) fn with_lookup<'r, C: Chip>( + region: &mut Region<'r, C>, + cols: &SpreadInputs, + row: usize, + word: SpreadWord, + ) -> Result { + let tag = word.tag; + let dense_val = Some(word.dense); + let spread_val = Some(word.spread); + region.assign_advice( + || "tag", + cols.tag, + row, + || Ok(C::Field::from_u64(tag as u64)), + )?; + let dense_var = region.assign_advice( + || "dense", + cols.dense, + row, + || { + dense_val + .map(|v| C::Field::from_u64(v as u64)) + .ok_or(Error::SynthesisError) + }, + )?; + let spread_var = region.assign_advice( + || "spread", + cols.spread, + row, + || { + spread_val + .map(|v| C::Field::from_u64(v as u64)) + .ok_or(Error::SynthesisError) + }, + )?; + + Ok(SpreadVar { + tag, + dense: CellValue16::new(dense_var, dense_val.unwrap()), + spread: CellValue32::new(spread_var, spread_val.unwrap()), + }) + } + + pub(super) fn without_lookup<'r, C: Chip>( + region: &mut Region<'r, C>, + dense_col: Column, + dense_row: usize, + spread_col: Column, + spread_row: usize, + word: SpreadWord, + ) -> Result { + let tag = word.tag; + let dense_val = Some(word.dense); + let spread_val = Some(word.spread); + let dense_var = region.assign_advice( + || "dense", + dense_col, + dense_row, + || { + dense_val + .map(|v| C::Field::from_u64(v as u64)) + .ok_or(Error::SynthesisError) + }, + )?; + let spread_var = region.assign_advice( + || "spread", + spread_col, + spread_row, + || { + spread_val + .map(|v| C::Field::from_u64(v as u64)) + .ok_or(Error::SynthesisError) + }, + )?; + + Ok(SpreadVar { + tag, + dense: CellValue16::new(dense_var, dense_val.unwrap()), + spread: CellValue32::new(spread_var, spread_val.unwrap()), + }) + } +} + +#[derive(Clone, Debug)] +pub(super) struct SpreadInputs { + pub(super) tag: Column, + pub(super) dense: Column, + pub(super) spread: Column, +} + +#[derive(Clone, Debug)] +pub(super) struct SpreadTable { + table_tag: Column, + table_dense: Column, + table_spread: Column, +} + +impl SpreadTable { + pub(super) fn configure( + meta: &mut ConstraintSystem, + tag: Column, + dense: Column, + spread: Column, + ) -> (SpreadInputs, Self) { + let table_tag = meta.fixed_column(); + let table_dense = meta.fixed_column(); + let table_spread = meta.fixed_column(); + + let tag_ = meta.query_any(tag.into(), Rotation::cur()); + let dense_ = meta.query_any(dense.into(), Rotation::cur()); + let spread_ = meta.query_any(spread.into(), Rotation::cur()); + let table_tag_ = meta.query_any(table_tag.into(), Rotation::cur()); + let table_dense_ = meta.query_any(table_dense.into(), Rotation::cur()); + let table_spread_ = meta.query_any(table_spread.into(), Rotation::cur()); + meta.lookup( + &[tag_, dense_, spread_], + &[table_tag_, table_dense_, table_spread_], + ); + + ( + SpreadInputs { tag, dense, spread }, + SpreadTable { + table_tag, + table_dense, + table_spread, + }, + ) + } + + fn generate() -> impl Iterator { + (1..=(1 << 16)).scan( + (F::zero(), F::zero(), F::zero()), + |(tag, dense, spread), i| { + // We computed this table row in the previous iteration. + let res = (*tag, *dense, *spread); + + // i holds the zero-indexed row number for the next table row. + match i { + BITS_7 | BITS_10 | BITS_11 | BITS_13 | BITS_14 => *tag += F::one(), + _ => (), + } + *dense += F::one(); + if i & 1 == 0 { + // On even-numbered rows we recompute the spread. + *spread = F::zero(); + for b in 0..16 { + if (i >> b) & 1 != 0 { + *spread += F::from_u64(1 << (2 * b)); + } + } + } else { + // On odd-numbered rows we add one. + *spread += F::one(); + } + + Some(res) + }, + ) + } + + pub(super) fn load( + &self, + layouter: &mut impl Layouter>, + ) -> Result<(), Error> { + layouter.assign_region( + || "spread table", + |mut gate| { + // We generate the row values lazily (we only need them during keygen). + let mut rows = Self::generate::(); + + for index in 0..(1 << 16) { + let mut row = None; + gate.assign_fixed( + || "tag", + self.table_tag, + index, + || { + row = rows.next(); + row.map(|(tag, _, _)| tag).ok_or(Error::SynthesisError) + }, + )?; + gate.assign_fixed( + || "dense", + self.table_dense, + index, + || row.map(|(_, dense, _)| dense).ok_or(Error::SynthesisError), + )?; + gate.assign_fixed( + || "spread", + self.table_spread, + index, + || { + row.map(|(_, _, spread)| spread) + .ok_or(Error::SynthesisError) + }, + )?; + } + Ok(()) + }, + ) + } +} + +#[cfg(test)] +mod tests { + use rand::Rng; + use std::cmp; + use std::collections::HashMap; + use std::fmt; + use std::marker::PhantomData; + + use super::{ + super::{util::*, Table16Chip, Table16Config}, + SpreadInputs, SpreadTable, + }; + use crate::{ + arithmetic::FieldExt, + circuit::{layouter, Cell, Layouter, Region, RegionIndex}, + dev::MockProver, + pasta::Fp, + plonk::{ + Advice, Any, Assignment, Circuit, Column, ConstraintSystem, Error, Fixed, Permutation, + }, + }; + + #[test] + fn lookup_table() { + /// This represents an advice column at a certain row in the ConstraintSystem + #[derive(Copy, Clone, Debug)] + pub struct Variable(Column, usize); + + #[derive(Clone, Debug)] + struct MyConfig { + lookup_inputs: SpreadInputs, + sha256: Table16Config, + } + + struct MyCircuit {} + + struct MyLayouter<'a, F: FieldExt, CS: Assignment + 'a> { + cs: &'a mut CS, + config: MyConfig, + regions: Vec, + /// Stores the first empty row for each column. + columns: HashMap, usize>, + _marker: PhantomData, + } + + impl<'a, F: FieldExt, CS: Assignment + 'a> fmt::Debug for MyLayouter<'a, F, CS> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("MyLayouter") + .field("config", &self.config) + .field("regions", &self.regions) + .field("columns", &self.columns) + .finish() + } + } + + impl<'a, FF: FieldExt, CS: Assignment> MyLayouter<'a, FF, CS> { + fn new(cs: &'a mut CS, config: MyConfig) -> Result { + let mut res = MyLayouter { + cs, + config, + regions: vec![], + columns: HashMap::default(), + _marker: PhantomData, + }; + + let table = res.config.sha256.lookup_table.clone(); + table.load(&mut res)?; + + Ok(res) + } + } + + impl<'a, F: FieldExt, CS: Assignment + 'a> Layouter> for MyLayouter<'a, F, CS> { + type Root = Self; + + fn config(&self) -> &Table16Config { + &self.config.sha256 + } + + fn loaded(&self) -> &() { + &() + } + + fn assign_region( + &mut self, + name: N, + mut assignment: A, + ) -> Result + where + A: FnMut(Region<'_, Table16Chip>) -> Result, + N: Fn() -> NR, + NR: Into, + { + let region_index = self.regions.len(); + + // Get shape of the region. + let mut shape = layouter::RegionShape::new(region_index.into()); + { + let region: &mut dyn layouter::RegionLayouter> = &mut shape; + assignment(region.into())?; + } + + // Lay out this region. We implement the simplest approach here: position the + // region starting at the earliest row for which none of the columns are in use. + let mut region_start = 0; + for column in shape.columns() { + region_start = + cmp::max(region_start, self.columns.get(column).cloned().unwrap_or(0)); + } + self.regions.push(region_start); + + // Update column usage information. + for column in shape.columns() { + self.columns + .insert(*column, region_start + shape.row_count()); + } + + self.cs.enter_region(name); + let mut region = MyRegion::new(self, region_index.into()); + let result = { + let region: &mut dyn layouter::RegionLayouter> = &mut region; + assignment(region.into()) + }?; + self.cs.exit_region(); + + Ok(result) + } + + fn get_root(&mut self) -> &mut Self::Root { + self + } + + fn push_namespace(&mut self, name_fn: N) + where + NR: Into, + N: FnOnce() -> NR, + { + self.cs.push_namespace(name_fn) + } + + fn pop_namespace(&mut self, gadget_name: Option) { + self.cs.pop_namespace(gadget_name) + } + } + + struct MyRegion<'r, 'a, F: FieldExt, CS: Assignment + 'a> { + layouter: &'r mut MyLayouter<'a, F, CS>, + region_index: RegionIndex, + _marker: PhantomData, + } + + impl<'r, 'a, F: FieldExt, CS: Assignment + 'a> fmt::Debug for MyRegion<'r, 'a, F, CS> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("MyRegion") + .field("layouter", &self.layouter) + .field("region_index", &self.region_index) + .finish() + } + } + + impl<'r, 'a, F: FieldExt, CS: Assignment + 'a> MyRegion<'r, 'a, F, CS> { + fn new(layouter: &'r mut MyLayouter<'a, F, CS>, region_index: RegionIndex) -> Self { + MyRegion { + layouter, + region_index, + _marker: PhantomData::default(), + } + } + } + + impl<'r, 'a, F: FieldExt, CS: Assignment + 'a> layouter::RegionLayouter> + for MyRegion<'r, 'a, F, CS> + { + fn assign_advice<'v>( + &'v mut self, + annotation: &'v (dyn Fn() -> String + 'v), + column: Column, + offset: usize, + to: &'v mut (dyn FnMut() -> Result + 'v), + ) -> Result { + self.layouter.cs.assign_advice( + annotation, + column, + self.layouter.regions[*self.region_index] + offset, + to, + )?; + + Ok(Cell { + region_index: self.region_index, + row_offset: offset, + column: column.into(), + }) + } + + fn assign_fixed<'v>( + &'v mut self, + annotation: &'v (dyn Fn() -> String + 'v), + column: Column, + offset: usize, + to: &'v mut (dyn FnMut() -> Result + 'v), + ) -> Result { + self.layouter.cs.assign_fixed( + annotation, + column, + self.layouter.regions[*self.region_index] + offset, + to, + )?; + Ok(Cell { + region_index: self.region_index, + row_offset: offset, + column: column.into(), + }) + } + + fn constrain_equal( + &mut self, + permutation: &Permutation, + left: Cell, + right: Cell, + ) -> Result<(), Error> { + self.layouter.cs.copy( + permutation, + left.column, + self.layouter.regions[*left.region_index] + left.row_offset, + right.column, + self.layouter.regions[*right.region_index] + right.row_offset, + )?; + + Ok(()) + } + } + + impl Circuit for MyCircuit { + type Config = MyConfig; + + fn configure(meta: &mut ConstraintSystem) -> MyConfig { + let a = meta.advice_column(); + let b = meta.advice_column(); + let c = meta.advice_column(); + + let (lookup_inputs, lookup_table) = SpreadTable::configure(meta, a, b, c); + + 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(), + ]; + + MyConfig { + lookup_inputs, + sha256: Table16Config { lookup_table }, + } + } + + fn synthesize( + &self, + cs: &mut impl Assignment, + config: MyConfig, + ) -> Result<(), Error> { + let lookup = config.lookup_inputs.clone(); + let mut layouter = MyLayouter::new(cs, config)?; + + layouter.assign_region( + || "spread_test", + |mut gate| { + let mut row = 0; + let mut add_row = |tag, dense, spread| { + gate.assign_advice(|| "tag", lookup.tag, row, || Ok(tag))?; + gate.assign_advice(|| "dense", lookup.dense, row, || Ok(dense))?; + gate.assign_advice(|| "spread", lookup.spread, row, || Ok(spread))?; + row += 1; + Ok(()) + }; + + // Test the first few small values. + add_row(F::zero(), F::from_u64(0b000), F::from_u64(0b000000))?; + add_row(F::zero(), F::from_u64(0b001), F::from_u64(0b000001))?; + add_row(F::zero(), F::from_u64(0b010), F::from_u64(0b000100))?; + add_row(F::zero(), F::from_u64(0b011), F::from_u64(0b000101))?; + add_row(F::zero(), F::from_u64(0b100), F::from_u64(0b010000))?; + add_row(F::zero(), F::from_u64(0b101), F::from_u64(0b010001))?; + + // Test the tag boundaries: + // 7-bit + add_row( + F::zero(), + F::from_u64(0b1111111), + F::from_u64(0b01010101010101), + )?; + add_row( + F::one(), + F::from_u64(0b10000000), + F::from_u64(0b0100000000000000), + )?; + // - 10-bit + add_row( + F::one(), + F::from_u64(0b1111111111), + F::from_u64(0b01010101010101010101), + )?; + add_row( + F::from_u64(2), + F::from_u64(0b10000000000), + F::from_u64(0b0100000000000000000000), + )?; + // - 11-bit + add_row( + F::from_u64(2), + F::from_u64(0b11111111111), + F::from_u64(0b0101010101010101010101), + )?; + add_row( + F::from_u64(3), + F::from_u64(0b100000000000), + F::from_u64(0b010000000000000000000000), + )?; + // - 13-bit + add_row( + F::from_u64(3), + F::from_u64(0b1111111111111), + F::from_u64(0b01010101010101010101010101), + )?; + add_row( + F::from_u64(4), + F::from_u64(0b10000000000000), + F::from_u64(0b0100000000000000000000000000), + )?; + // - 14-bit + add_row( + F::from_u64(4), + F::from_u64(0b11111111111111), + F::from_u64(0b0101010101010101010101010101), + )?; + add_row( + F::from_u64(5), + F::from_u64(0b100000000000000), + F::from_u64(0b010000000000000000000000000000), + )?; + + // Test random lookup values + let mut rng = rand::thread_rng(); + + for _ in 0..10 { + let word: u16 = rng.gen(); + add_row( + F::from_u64(get_tag(word).into()), + F::from_u64(word.into()), + F::from_u64(interleave_u16_with_zeros(word).into()), + )?; + } + + Ok(()) + }, + ) + } + } + + let circuit: MyCircuit = MyCircuit {}; + + let prover = match MockProver::::run(16, &circuit, vec![]) { + Ok(prover) => prover, + Err(e) => panic!("{:?}", e), + }; + assert_eq!(prover.verify(), Ok(())); + } +} diff --git a/src/gadget/sha256/table16/util.rs b/src/gadget/sha256/table16/util.rs new file mode 100644 index 0000000..7052a8e --- /dev/null +++ b/src/gadget/sha256/table16/util.rs @@ -0,0 +1,95 @@ +pub const BITS_7: usize = 1 << 7; +pub const BITS_10: usize = 1 << 10; +pub const BITS_11: usize = 1 << 11; +pub const BITS_13: usize = 1 << 13; +pub const BITS_14: usize = 1 << 14; +pub const MASK_EVEN_32: u32 = 0x55555555; +pub const MASK_ODD_32: u32 = 0xAAAAAAAA; + +// Helper function that returns tag of 16-bit input +pub fn get_tag(input: u16) -> u8 { + let input = input as usize; + if input < BITS_7 { + 0 + } else if input < BITS_10 { + 1 + } else if input < BITS_11 { + 2 + } else if input < BITS_13 { + 3 + } else if input < BITS_14 { + 4 + } else { + 5 + } +} + +/// Helper function that returns 32-bit spread version of 16-bit input. +pub fn interleave_u16_with_zeros(word: u16) -> u32 { + let mut word: u32 = word.into(); + word = (word ^ (word << 8)) & 0x00ff00ff; + word = (word ^ (word << 4)) & 0x0f0f0f0f; + word = (word ^ (word << 2)) & 0x33333333; + word = (word ^ (word << 1)) & 0x55555555; + word +} + +// Reverses interleaving function by removing interleaved zeros. +pub fn compress_u32(word: u32) -> u16 { + let mut word = word; + assert_eq!(word & MASK_EVEN_32, word); + word = (word | (word >> 1)) & 0x33333333; + word = (word | (word >> 2)) & 0x0f0f0f0f; + word = (word | (word >> 4)) & 0x00ff00ff; + word = (word | (word >> 8)) & 0x0000ffff; + word as u16 +} + +// Chops a 32-bit word into pieces of given length. The lengths are specified +// starting from the little end. +pub fn chop_u32(word: u32, lengths: &[u8]) -> Vec { + assert_eq!(lengths.iter().sum::(), 32 as u8); + let mut pieces: Vec = Vec::with_capacity(lengths.len()); + for i in 0..lengths.len() { + assert!(lengths[i] > 0); + // lengths[i] bitstring of all 1's + let mask: u32 = (1 << lengths[i]) as u32 - 1; + // Shift mask by bits already shifted + let offset: u8 = lengths[0..i].into_iter().sum(); + let mask: u32 = mask << offset; + pieces.push((word & mask) >> offset as u32); + } + pieces +} + +// Chops a 64-bit word into pieces of given length. The lengths are specified +// starting from the little end. +pub fn chop_u64(word: u64, lengths: &[u8]) -> Vec { + assert_eq!(lengths.iter().sum::(), 64 as u8); + let mut pieces: Vec = Vec::with_capacity(lengths.len()); + for i in 0..lengths.len() { + assert!(lengths[i] > 0); + // lengths[i] bitstring of all 1's + let mask: u64 = ((1 as u64) << lengths[i]) - 1; + // Shift mask by bits already shifted + let offset: u8 = lengths[0..i].into_iter().sum(); + let mask: u64 = mask << offset; + pieces.push((word & mask) >> offset as u64); + } + pieces +} + +// Returns compressed even and odd bits of 32-bit word +pub fn get_even_and_odd_bits_u32(word: u32) -> (u16, u16) { + let even = word & MASK_EVEN_32; + let odd = (word & MASK_ODD_32) >> 1; + (compress_u32(even), compress_u32(odd)) +} + +// Split 4-bit value into 2-bit lo and hi halves +pub fn bisect_four_bit(word: u32) -> (u32, u32) { + assert!(word < 16); // 4-bit range-check + let word_hi = (word & 0b1100) >> 2; + let word_lo = word & 0b0011; + (word_lo, word_hi) +} diff --git a/src/plonk/circuit.rs b/src/plonk/circuit.rs index d7d04c7..918b7c7 100644 --- a/src/plonk/circuit.rs +++ b/src/plonk/circuit.rs @@ -198,7 +198,9 @@ impl Selector { /// A permutation. #[derive(Clone, Debug)] pub struct Permutation { + /// The index of this permutation. index: usize, + /// The mapping between columns involved in this permutation. mapping: Vec>, }