SHA-256 chip that uses a 2^16 lookup table

Co-authored-by: Jack Grigg <jack@electriccoin.co>
This commit is contained in:
therealyingtong 2021-01-06 09:16:30 +08:00 committed by Jack Grigg
parent e33fe2cb36
commit 570f90e4ee
6 changed files with 1058 additions and 0 deletions

View file

@ -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<Any>,
}

View file

@ -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.

View file

@ -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<u32>,
}
impl BlockWord {
pub fn new(value: u32) -> Self {
BlockWord {
var: (),
value: Some(value),
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct CellValue16 {
var: Cell,
value: Option<u16>,
}
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<u32>,
}
impl CellValue32 {
pub fn new(var: Cell, value: u32) -> Self {
CellValue32 {
var,
value: Some(value),
}
}
}
impl Into<CellValue32> 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<F: FieldExt> {
_marker: PhantomData<F>,
}
impl<F: FieldExt> Table16Chip<F> {
/// Configures this chip for use in a circuit.
pub fn configure(meta: &mut ConstraintSystem<F>) -> 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<F: FieldExt> Chip for Table16Chip<F> {
type Field = F;
type Config = Table16Config;
type Loaded = ();
fn load(layouter: &mut impl Layouter<Self>) -> Result<(), Error> {
let table = layouter.config().lookup_table.clone();
table.load(layouter)
}
}
impl<F: FieldExt> Sha256Instructions for Table16Chip<F> {
type State = State;
type BlockWord = BlockWord;
fn zero() -> Self::BlockWord {
BlockWord::new(0)
}
fn initialization_vector(layouter: &mut impl Layouter<Self>) -> Result<State, Error> {
todo!()
}
fn initialization(
layouter: &mut impl Layouter<Table16Chip<F>>,
init_state: &Self::State,
) -> Result<Self::State, Error> {
todo!()
}
fn compress(
layouter: &mut impl Layouter<Self>,
initialized_state: &Self::State,
input: [Self::BlockWord; super::BLOCK_SIZE],
) -> Result<Self::State, Error> {
let config = layouter.config().clone();
todo!()
}
fn digest(
layouter: &mut impl Layouter<Self>,
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<F: FieldExt> {
// Assign cells for general spread computation used in sigma, ch, ch_neg, maj gates
fn assign_spread_outputs(
&self,
region: &mut Region<'_, Table16Chip<F>>,
lookup: &SpreadInputs,
a_3: Column<Advice>,
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<F>>,
lookup: &SpreadInputs,
a_3: Column<Advice>,
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<A, AR>(
&self,
region: &mut Region<'_, Table16Chip<F>>,
annotation: A,
column: Column<Advice>,
row: usize,
copy: &CellValue32,
perm: &Permutation,
) -> Result<Cell, Error>
where
A: Fn() -> AR,
AR: Into<String>,
{
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)
}
}

View file

@ -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<Self, Error> {
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<Advice>,
dense_row: usize,
spread_col: Column<Advice>,
spread_row: usize,
word: SpreadWord,
) -> Result<Self, Error> {
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<Advice>,
pub(super) dense: Column<Advice>,
pub(super) spread: Column<Advice>,
}
#[derive(Clone, Debug)]
pub(super) struct SpreadTable {
table_tag: Column<Fixed>,
table_dense: Column<Fixed>,
table_spread: Column<Fixed>,
}
impl SpreadTable {
pub(super) fn configure<F: FieldExt>(
meta: &mut ConstraintSystem<F>,
tag: Column<Advice>,
dense: Column<Advice>,
spread: Column<Advice>,
) -> (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<F: FieldExt>() -> impl Iterator<Item = (F, F, F)> {
(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<F: FieldExt>(
&self,
layouter: &mut impl Layouter<Table16Chip<F>>,
) -> 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::<F>();
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<Advice>, usize);
#[derive(Clone, Debug)]
struct MyConfig {
lookup_inputs: SpreadInputs,
sha256: Table16Config,
}
struct MyCircuit {}
struct MyLayouter<'a, F: FieldExt, CS: Assignment<F> + 'a> {
cs: &'a mut CS,
config: MyConfig,
regions: Vec<usize>,
/// Stores the first empty row for each column.
columns: HashMap<Column<Any>, usize>,
_marker: PhantomData<F>,
}
impl<'a, F: FieldExt, CS: Assignment<F> + '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<FF>> MyLayouter<'a, FF, CS> {
fn new(cs: &'a mut CS, config: MyConfig) -> Result<Self, Error> {
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<F> + 'a> Layouter<Table16Chip<F>> for MyLayouter<'a, F, CS> {
type Root = Self;
fn config(&self) -> &Table16Config {
&self.config.sha256
}
fn loaded(&self) -> &() {
&()
}
fn assign_region<A, AR, N, NR>(
&mut self,
name: N,
mut assignment: A,
) -> Result<AR, Error>
where
A: FnMut(Region<'_, Table16Chip<F>>) -> Result<AR, Error>,
N: Fn() -> NR,
NR: Into<String>,
{
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<Table16Chip<F>> = &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<Table16Chip<F>> = &mut region;
assignment(region.into())
}?;
self.cs.exit_region();
Ok(result)
}
fn get_root(&mut self) -> &mut Self::Root {
self
}
fn push_namespace<NR, N>(&mut self, name_fn: N)
where
NR: Into<String>,
N: FnOnce() -> NR,
{
self.cs.push_namespace(name_fn)
}
fn pop_namespace(&mut self, gadget_name: Option<String>) {
self.cs.pop_namespace(gadget_name)
}
}
struct MyRegion<'r, 'a, F: FieldExt, CS: Assignment<F> + 'a> {
layouter: &'r mut MyLayouter<'a, F, CS>,
region_index: RegionIndex,
_marker: PhantomData<F>,
}
impl<'r, 'a, F: FieldExt, CS: Assignment<F> + '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<F> + '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<F> + 'a> layouter::RegionLayouter<Table16Chip<F>>
for MyRegion<'r, 'a, F, CS>
{
fn assign_advice<'v>(
&'v mut self,
annotation: &'v (dyn Fn() -> String + 'v),
column: Column<Advice>,
offset: usize,
to: &'v mut (dyn FnMut() -> Result<F, Error> + 'v),
) -> Result<Cell, Error> {
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<Fixed>,
offset: usize,
to: &'v mut (dyn FnMut() -> Result<F, Error> + 'v),
) -> Result<Cell, Error> {
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<F: FieldExt> Circuit<F> for MyCircuit {
type Config = MyConfig;
fn configure(meta: &mut ConstraintSystem<F>) -> 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<F>,
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::<Fp>::run(16, &circuit, vec![]) {
Ok(prover) => prover,
Err(e) => panic!("{:?}", e),
};
assert_eq!(prover.verify(), Ok(()));
}
}

View file

@ -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<u32> {
assert_eq!(lengths.iter().sum::<u8>(), 32 as u8);
let mut pieces: Vec<u32> = 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<u64> {
assert_eq!(lengths.iter().sum::<u8>(), 64 as u8);
let mut pieces: Vec<u64> = 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)
}

View file

@ -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<Column<Any>>,
}