mirror of
https://github.com/saymrwulf/pasta_curves-source.git
synced 2026-09-04 20:03:39 +00:00
SHA-256 chip that uses a 2^16 lookup table
Co-authored-by: Jack Grigg <jack@electriccoin.co>
This commit is contained in:
parent
e33fe2cb36
commit
570f90e4ee
6 changed files with 1058 additions and 0 deletions
|
|
@ -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>,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
347
src/gadget/sha256/table16.rs
Normal file
347
src/gadget/sha256/table16.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
608
src/gadget/sha256/table16/spread_table.rs
Normal file
608
src/gadget/sha256/table16/spread_table.rs
Normal 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(()));
|
||||
}
|
||||
}
|
||||
95
src/gadget/sha256/table16/util.rs
Normal file
95
src/gadget/sha256/table16/util.rs
Normal 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)
|
||||
}
|
||||
|
|
@ -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>>,
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue