Merge pull request #214 from zcash/sha-256

Example SHA-256 chip
This commit is contained in:
ebfull 2021-02-25 14:05:18 -07:00 committed by GitHub
commit 4e7d4a0f64
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 6474 additions and 7 deletions

View File

@ -1,3 +1,7 @@
# Gadgets
In this section we document the gadgets and chip designs provided by halo2.
In this section we document some example gadgets and chip designs that are suitable for
Halo 2.
> Neither these gadgets, nor their implementations, have been reviewed, and they should
> not be used in production.

163
examples/sha256/benches.rs Normal file
View File

@ -0,0 +1,163 @@
use halo2::{
arithmetic::FieldExt,
circuit::Chip,
circuit::{layouter, Layouter},
pasta::EqAffine,
plonk::{
create_proof, keygen_pk, keygen_vk, verify_proof, Assignment, Circuit, ConstraintSystem,
Error, VerifyingKey,
},
poly::commitment::Params,
transcript::{Blake2bRead, Blake2bWrite},
};
use std::{
fs::File,
io::{prelude::*, BufReader},
path::Path,
};
use criterion::{criterion_group, criterion_main, Criterion};
use crate::{BlockWord, Sha256, Table16Chip, Table16Config, BLOCK_SIZE};
#[allow(dead_code)]
fn bench(name: &str, k: u32, c: &mut Criterion) {
struct MyCircuit {}
impl<F: FieldExt> Circuit<F> for MyCircuit {
type Config = Table16Config;
fn configure(meta: &mut ConstraintSystem<F>) -> Table16Config {
Table16Chip::configure(meta)
}
fn synthesize(
&self,
cs: &mut impl Assignment<F>,
config: Table16Config,
) -> Result<(), Error> {
let mut layouter = layouter::SingleChip::<Table16Chip<F>, _>::new(cs, config)?;
Table16Chip::load(&mut layouter)?;
// Test vector: "abc"
let test_input = [
BlockWord::new(0b01100001011000100110001110000000),
BlockWord::new(0),
BlockWord::new(0),
BlockWord::new(0),
BlockWord::new(0),
BlockWord::new(0),
BlockWord::new(0),
BlockWord::new(0),
BlockWord::new(0),
BlockWord::new(0),
BlockWord::new(0),
BlockWord::new(0),
BlockWord::new(0),
BlockWord::new(0),
BlockWord::new(0),
];
// 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(layouter.namespace(|| "test vector"), &input)?;
Ok(())
}
}
// Initialize the polynomial commitment parameters
let params_path = Path::new("./benches/sha256_assets/sha256_params");
if File::open(&params_path).is_err() {
let params: Params<EqAffine> = Params::new(k);
let mut buf = Vec::new();
params.write(&mut buf).expect("Failed to write params");
let mut file = File::create(&params_path).expect("Failed to create sha256_params");
file.write_all(&buf[..])
.expect("Failed to write params to file");
}
let params_fs = File::open(&params_path).expect("couldn't load sha256_params");
let params: Params<EqAffine> =
Params::read::<_>(&mut BufReader::new(params_fs)).expect("Failed to read params");
let empty_circuit: MyCircuit = MyCircuit {};
// Initialize the proving key
let vk_path = Path::new("./benches/sha256_assets/sha256_vk");
if File::open(&vk_path).is_err() {
let vk = keygen_vk(&params, &empty_circuit).expect("keygen_vk should not fail");
let mut buf = Vec::new();
vk.write(&mut buf).expect("Failed to write vk");
let mut file = File::create(&vk_path).expect("Failed to create sha256_vk");
file.write_all(&buf[..])
.expect("Failed to write vk to file");
}
let vk_fs = File::open(&vk_path).expect("couldn't load sha256_params");
let vk: VerifyingKey<EqAffine> =
VerifyingKey::<EqAffine>::read::<_, MyCircuit>(&mut BufReader::new(vk_fs), &params)
.expect("Failed to read vk");
let pk = keygen_pk(&params, vk, &empty_circuit).expect("keygen_pk should not fail");
let circuit: MyCircuit = MyCircuit {};
// let prover_name = name.to_string() + "-prover";
let verifier_name = name.to_string() + "-verifier";
// /// Benchmark proof creation
// c.bench_function(&prover_name, |b| {
// b.iter(|| {
// let mut transcript = Blake2bWrite::init(Fq::one());
// create_proof(&params, &pk, &circuit, &[], &mut transcript)
// .expect("proof generation should not fail");
// let proof: Vec<u8> = transcript.finalize();
// });
// });
// Create a proof
let proof_path = Path::new("./benches/sha256_assets/sha256_proof");
if File::open(&proof_path).is_err() {
let mut transcript = Blake2bWrite::init(vec![]);
create_proof(&params, &pk, &[circuit], &[], &mut transcript)
.expect("proof generation should not fail");
let proof: Vec<u8> = transcript.finalize();
let mut file = File::create(&proof_path).expect("Failed to create sha256_proof");
file.write_all(&proof[..]).expect("Failed to write proof");
}
let mut proof_fs = File::open(&proof_path).expect("couldn't load sha256_proof");
let mut proof = Vec::<u8>::new();
proof_fs
.read_to_end(&mut proof)
.expect("Couldn't read proof");
c.bench_function(&verifier_name, |b| {
b.iter(|| {
let msm = params.empty_msm();
let mut transcript = Blake2bRead::init(&proof[..]);
let guard = verify_proof(&params, pk.get_vk(), msm, &[], &mut transcript).unwrap();
let msm = guard.clone().use_challenges();
assert!(msm.eval());
});
});
}
#[allow(dead_code)]
fn criterion_benchmark(c: &mut Criterion) {
bench("sha256", 16, c);
// bench("sha256", 20, c);
}
criterion_group!(benches, criterion_benchmark);
criterion_main!(benches);

View File

@ -6,13 +6,18 @@ use std::cmp::min;
use std::convert::TryInto;
use std::fmt;
use crate::{
use halo2::{
circuit::{Chip, Layouter},
plonk::Error,
};
mod benches;
mod table16;
pub use table16::{BlockWord, Table16Chip, Table16Config};
/// The size of a SHA-256 block, in 32-bit words.
const BLOCK_SIZE: usize = 16;
pub const BLOCK_SIZE: usize = 16;
/// The size of a SHA-256 digest, in 32-bit words.
const DIGEST_SIZE: usize = 8;
@ -153,3 +158,5 @@ impl<Sha256Chip: Sha256Instructions> Sha256<Sha256Chip> {
hasher.finalize(layouter.namespace(|| "finalize"))
}
}
fn main() {}

456
examples/sha256/table16.rs Normal file
View File

@ -0,0 +1,456 @@
use std::marker::PhantomData;
use super::Sha256Instructions;
use halo2::{
arithmetic::FieldExt,
circuit::{Cell, Chip, Layouter, Region},
plonk::{Advice, Column, ConstraintSystem, Error, Permutation},
};
mod compression;
mod gates;
mod message_schedule;
mod spread_table;
mod util;
use compression::*;
use gates::*;
use message_schedule::*;
use spread_table::*;
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)]
/// A word in a `Table16` message block.
pub struct BlockWord {
var: (),
value: Option<u32>,
}
impl BlockWord {
/// Create a new `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)
}
}
/// Configuration for a [`Table16Chip`].
#[derive(Clone, Debug)]
pub struct Table16Config {
lookup_table: SpreadTable,
message_schedule: MessageSchedule,
compression: Compression,
}
/// 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(),
],
);
let compression = Compression::configure(
meta,
lookup_inputs.clone(),
message_schedule,
extras,
perm.clone(),
);
let message_schedule =
MessageSchedule::configure(meta, lookup_inputs, message_schedule, extras, perm);
Table16Config {
lookup_table,
message_schedule,
compression,
}
}
}
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> {
let config = layouter.config().clone();
config.compression.initialize_with_iv(layouter, IV)
}
fn initialization(
layouter: &mut impl Layouter<Table16Chip<F>>,
init_state: &Self::State,
) -> Result<Self::State, Error> {
let config = layouter.config().clone();
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(
layouter: &mut impl Layouter<Self>,
initialized_state: &Self::State,
input: [Self::BlockWord; super::BLOCK_SIZE],
) -> Result<Self::State, Error> {
let config = layouter.config().clone();
let (_, w_halves) = config.message_schedule.process(layouter, input)?;
config
.compression
.compress(layouter, initialized_state.clone(), w_halves)
}
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.
let config = layouter.config().clone();
config.compression.digest(layouter, state.clone())
}
}
/// 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
#[allow(clippy::too_many_arguments)]
#[allow(clippy::type_complexity)]
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
#[allow(clippy::too_many_arguments)]
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)
}
}
#[cfg(test)]
mod tests {
#[cfg(feature = "dev-graph")]
use halo2::{
arithmetic::FieldExt,
circuit::Chip,
circuit::{layouter, Layouter},
gadget::sha256::{BlockWord, Sha256, Table16Chip, Table16Config, BLOCK_SIZE},
pasta::Fq,
plonk::{Assignment, Circuit, ConstraintSystem, Error},
};
#[cfg(feature = "dev-graph")]
#[test]
fn print_sha256_circuit() {
struct MyCircuit {}
impl<F: FieldExt> Circuit<F> for MyCircuit {
type Config = Table16Config;
fn configure(meta: &mut ConstraintSystem<F>) -> Table16Config {
Table16Chip::configure(meta)
}
fn synthesize(
&self,
cs: &mut impl Assignment<F>,
config: Table16Config,
) -> Result<(), Error> {
let mut layouter = layouter::SingleChip::<Table16Chip<F>, _>::new(cs, config)?;
Table16Chip::load(&mut layouter)?;
// Test vector: "abc"
let test_input = [
BlockWord::new(0b01100001011000100110001110000000),
BlockWord::new(0),
BlockWord::new(0),
BlockWord::new(0),
BlockWord::new(0),
BlockWord::new(0),
BlockWord::new(0),
BlockWord::new(0),
BlockWord::new(0),
BlockWord::new(0),
BlockWord::new(0),
BlockWord::new(0),
BlockWord::new(0),
BlockWord::new(0),
BlockWord::new(0),
];
// 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(layouter.namespace(|| "'abc' * 31"), &input)?;
Ok(())
}
}
let circuit: MyCircuit = MyCircuit {};
eprintln!("{}", crate::dev::circuit_dot_graph::<Fq, _>(&circuit));
}
#[cfg(feature = "dev-graph")]
#[test]
fn print_table16_chip() {
use plotters::prelude::*;
struct MyCircuit {}
impl<F: FieldExt> Circuit<F> for MyCircuit {
type Config = Table16Config;
fn configure(meta: &mut ConstraintSystem<F>) -> Table16Config {
Table16Chip::configure(meta)
}
fn synthesize(
&self,
cs: &mut impl Assignment<F>,
config: Table16Config,
) -> Result<(), Error> {
let mut layouter = layouter::SingleChip::<Table16Chip<F>, _>::new(cs, config)?;
Table16Chip::load(&mut layouter)?;
// Test vector: "abc"
let test_input = [
BlockWord::new(0b01100001011000100110001110000000),
BlockWord::new(0),
BlockWord::new(0),
BlockWord::new(0),
BlockWord::new(0),
BlockWord::new(0),
BlockWord::new(0),
BlockWord::new(0),
BlockWord::new(0),
BlockWord::new(0),
BlockWord::new(0),
BlockWord::new(0),
BlockWord::new(0),
BlockWord::new(0),
BlockWord::new(0),
];
// Create a message of length 2 blocks
let mut input = Vec::with_capacity(2 * BLOCK_SIZE);
for _ in 0..2 {
input.extend_from_slice(&test_input);
}
Sha256::digest(layouter.namespace(|| "'abc' * 2"), &input)?;
Ok(())
}
}
let root =
SVGBackend::new("sha-256-table16-chip-layout.svg", (1024, 20480)).into_drawing_area();
root.fill(&WHITE).unwrap();
let root = root
.titled("16-bit Table SHA-256 Chip", ("sans-serif", 60))
.unwrap();
let circuit = MyCircuit {};
crate::dev::circuit_layout::<Fq, _, _>(&circuit, &root).unwrap();
}
}

View File

@ -0,0 +1,932 @@
use super::{
super::DIGEST_SIZE, BlockWord, CellValue16, CellValue32, SpreadInputs, SpreadVar,
Table16Assignment, Table16Chip, ROUNDS, STATE,
};
use halo2::{
arithmetic::FieldExt,
circuit::Layouter,
plonk::{Advice, Column, ConstraintSystem, Error, Fixed, Permutation},
poly::Rotation,
};
mod compression_gates;
mod compression_util;
mod subregion_digest;
mod subregion_initial;
mod subregion_main;
use compression_gates::CompressionGate;
/// 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,
}
#[derive(Clone, Debug)]
pub struct RoundWordDense {
dense_halves: (CellValue16, CellValue16),
}
impl RoundWordDense {
pub fn new(dense_halves: (CellValue16, CellValue16)) -> Self {
RoundWordDense { dense_halves }
}
}
#[derive(Clone, Debug)]
pub struct RoundWordSpread {
dense_halves: (CellValue16, CellValue16),
spread_halves: (CellValue32, CellValue32),
}
impl RoundWordSpread {
pub fn new(
dense_halves: (CellValue16, CellValue16),
spread_halves: (CellValue32, CellValue32),
) -> Self {
RoundWordSpread {
dense_halves,
spread_halves,
}
}
}
impl Into<RoundWordDense> for RoundWordSpread {
fn into(self) -> RoundWordDense {
RoundWordDense::new(self.dense_halves)
}
}
#[derive(Clone, Debug)]
pub struct RoundWordA {
pieces: Option<AbcdVar>,
dense_halves: (CellValue16, CellValue16),
spread_halves: Option<(CellValue32, CellValue32)>,
}
impl RoundWordA {
pub fn new(
pieces: AbcdVar,
dense_halves: (CellValue16, CellValue16),
spread_halves: (CellValue32, CellValue32),
) -> Self {
RoundWordA {
pieces: Some(pieces),
dense_halves,
spread_halves: Some(spread_halves),
}
}
pub fn new_dense(dense_halves: (CellValue16, CellValue16)) -> Self {
RoundWordA {
pieces: None,
dense_halves,
spread_halves: None,
}
}
}
impl Into<RoundWordSpread> for RoundWordA {
fn into(self) -> RoundWordSpread {
RoundWordSpread::new(self.dense_halves, self.spread_halves.unwrap())
}
}
#[derive(Clone, Debug)]
pub struct RoundWordE {
pieces: Option<EfghVar>,
dense_halves: (CellValue16, CellValue16),
spread_halves: Option<(CellValue32, CellValue32)>,
}
impl RoundWordE {
pub fn new(
pieces: EfghVar,
dense_halves: (CellValue16, CellValue16),
spread_halves: (CellValue32, CellValue32),
) -> Self {
RoundWordE {
pieces: Some(pieces),
dense_halves,
spread_halves: Some(spread_halves),
}
}
pub fn new_dense(dense_halves: (CellValue16, CellValue16)) -> Self {
RoundWordE {
pieces: None,
dense_halves,
spread_halves: None,
}
}
}
impl Into<RoundWordSpread> for RoundWordE {
fn into(self) -> RoundWordSpread {
RoundWordSpread::new(self.dense_halves, self.spread_halves.unwrap())
}
}
/// The internal state for SHA-256.
#[derive(Clone, Debug)]
pub struct State {
a: Option<StateWord>,
b: Option<StateWord>,
c: Option<StateWord>,
d: Option<StateWord>,
e: Option<StateWord>,
f: Option<StateWord>,
g: Option<StateWord>,
h: Option<StateWord>,
}
impl State {
#[allow(clippy::many_single_char_names)]
#[allow(clippy::too_many_arguments)]
pub fn new(
a: StateWord,
b: StateWord,
c: StateWord,
d: StateWord,
e: StateWord,
f: StateWord,
g: StateWord,
h: StateWord,
) -> Self {
State {
a: Some(a),
b: Some(b),
c: Some(c),
d: Some(d),
e: Some(e),
f: Some(f),
g: Some(g),
h: Some(h),
}
}
pub fn empty_state() -> Self {
State {
a: None,
b: None,
c: None,
d: None,
e: None,
f: None,
g: None,
h: None,
}
}
}
#[derive(Clone, Debug)]
pub enum StateWord {
A(RoundWordA),
B(RoundWordSpread),
C(RoundWordSpread),
D(RoundWordDense),
E(RoundWordE),
F(RoundWordSpread),
G(RoundWordSpread),
H(RoundWordDense),
}
#[derive(Clone, Debug)]
pub(super) struct Compression {
lookup: SpreadInputs,
message_schedule: Column<Advice>,
extras: [Column<Advice>; 6],
s_ch: Column<Fixed>,
s_ch_neg: Column<Fixed>,
s_maj: Column<Fixed>,
s_h_prime: Column<Fixed>,
s_a_new: Column<Fixed>,
s_e_new: Column<Fixed>,
s_upper_sigma_0: Column<Fixed>,
s_upper_sigma_1: Column<Fixed>,
// Decomposition gate for AbcdVar
s_decompose_abcd: Column<Fixed>,
// Decomposition gate for EfghVar
s_decompose_efgh: Column<Fixed>,
s_digest: Column<Fixed>,
perm: Permutation,
}
impl<F: FieldExt> Table16Assignment<F> for Compression {}
impl Compression {
pub(super) fn configure<F: FieldExt>(
meta: &mut ConstraintSystem<F>,
lookup: SpreadInputs,
message_schedule: Column<Advice>,
extras: [Column<Advice>; 6],
perm: Permutation,
) -> Self {
let s_ch = meta.fixed_column();
let s_ch_neg = meta.fixed_column();
let s_maj = meta.fixed_column();
let s_h_prime = meta.fixed_column();
let s_a_new = meta.fixed_column();
let s_e_new = meta.fixed_column();
let s_upper_sigma_0 = meta.fixed_column();
let s_upper_sigma_1 = meta.fixed_column();
// Decomposition gate for AbcdVar
let s_decompose_abcd = meta.fixed_column();
// Decomposition gate for EfghVar
let s_decompose_efgh = meta.fixed_column();
let s_digest = meta.fixed_column();
// Rename these here for ease of matching the gates to the specification.
let a_0 = lookup.tag;
let a_1 = lookup.dense;
let a_2 = lookup.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];
// Decompose `A,B,C,D` words into (2, 11, 9, 10)-bit chunks.
// `c` is split into (3, 3, 3)-bit c_lo, c_mid, c_hi.
meta.create_gate("decompose ABCD", |meta| {
let s_decompose_abcd = meta.query_fixed(s_decompose_abcd, Rotation::cur());
let a = meta.query_advice(a_3, Rotation::next()); // 2-bit chunk
let spread_a = meta.query_advice(a_4, Rotation::next());
let b = meta.query_advice(a_1, Rotation::cur()); // 11-bit chunk
let spread_b = meta.query_advice(a_2, Rotation::cur());
let tag_b = meta.query_advice(a_0, Rotation::cur());
let c_lo = meta.query_advice(a_3, Rotation::cur()); // 3-bit chunk
let spread_c_lo = meta.query_advice(a_4, Rotation::cur());
let c_mid = meta.query_advice(a_5, Rotation::cur()); // 3-bit chunk
let spread_c_mid = meta.query_advice(a_6, Rotation::cur());
let c_hi = meta.query_advice(a_5, Rotation::next()); // 3-bit chunk
let spread_c_hi = meta.query_advice(a_6, Rotation::next());
let d = meta.query_advice(a_1, Rotation::next()); // 7-bit chunk
let spread_d = meta.query_advice(a_2, Rotation::next());
let tag_d = meta.query_advice(a_0, Rotation::next());
let word_lo = meta.query_advice(a_7, Rotation::cur());
let spread_word_lo = meta.query_advice(a_8, Rotation::cur());
let word_hi = meta.query_advice(a_7, Rotation::next());
let spread_word_hi = meta.query_advice(a_8, Rotation::next());
CompressionGate::s_decompose_abcd(
s_decompose_abcd,
a,
spread_a,
b,
spread_b,
tag_b,
c_lo,
spread_c_lo,
c_mid,
spread_c_mid,
c_hi,
spread_c_hi,
d,
spread_d,
tag_d,
word_lo,
spread_word_lo,
word_hi,
spread_word_hi,
)
.0
});
// Decompose `E,F,G,H` words into (6, 5, 14, 7)-bit chunks.
// `a` is split into (3, 3)-bit a_lo, a_hi
// `b` is split into (2, 3)-bit b_lo, b_hi
meta.create_gate("Decompose EFGH", |meta| {
let s_decompose_efgh = meta.query_fixed(s_decompose_efgh, Rotation::cur());
let a_lo = meta.query_advice(a_3, Rotation::next()); // 3-bit chunk
let spread_a_lo = meta.query_advice(a_4, Rotation::next());
let a_hi = meta.query_advice(a_5, Rotation::next()); // 3-bit chunk
let spread_a_hi = meta.query_advice(a_6, Rotation::next());
let b_lo = meta.query_advice(a_3, Rotation::cur()); // 2-bit chunk
let spread_b_lo = meta.query_advice(a_4, Rotation::cur());
let b_hi = meta.query_advice(a_5, Rotation::cur()); // 3-bit chunk
let spread_b_hi = meta.query_advice(a_6, Rotation::cur());
let c = meta.query_advice(a_1, Rotation::next()); // 14-bit chunk
let spread_c = meta.query_advice(a_2, Rotation::next());
let tag_c = meta.query_advice(a_0, Rotation::next());
let d = meta.query_advice(a_1, Rotation::cur()); // 7-bit chunk
let spread_d = meta.query_advice(a_2, Rotation::cur());
let tag_d = meta.query_advice(a_0, Rotation::cur());
let word_lo = meta.query_advice(a_7, Rotation::cur());
let spread_word_lo = meta.query_advice(a_8, Rotation::cur());
let word_hi = meta.query_advice(a_7, Rotation::next());
let spread_word_hi = meta.query_advice(a_8, Rotation::next());
CompressionGate::s_decompose_efgh(
s_decompose_efgh,
a_lo,
spread_a_lo,
a_hi,
spread_a_hi,
b_lo,
spread_b_lo,
b_hi,
spread_b_hi,
c,
spread_c,
tag_c,
d,
spread_d,
tag_d,
word_lo,
spread_word_lo,
word_hi,
spread_word_hi,
)
.0
});
// s_upper_sigma_0 on abcd words
// (2, 11, 9, 10)-bit chunks
meta.create_gate("s_upper_sigma_0", |meta| {
let s_upper_sigma_0 = meta.query_fixed(s_upper_sigma_0, Rotation::cur());
let spread_r0_even = meta.query_advice(a_2, Rotation::prev());
let spread_r0_odd = meta.query_advice(a_2, Rotation::cur());
let spread_r1_even = meta.query_advice(a_2, Rotation::next());
let spread_r1_odd = meta.query_advice(a_3, Rotation::cur());
let spread_a = meta.query_advice(a_3, Rotation::next());
let spread_b = meta.query_advice(a_5, Rotation::cur());
let spread_c_lo = meta.query_advice(a_3, Rotation::prev());
let spread_c_mid = meta.query_advice(a_4, Rotation::prev());
let spread_c_hi = meta.query_advice(a_4, Rotation::next());
let spread_d = meta.query_advice(a_4, Rotation::cur());
CompressionGate::s_upper_sigma_0(
s_upper_sigma_0,
spread_r0_even,
spread_r0_odd,
spread_r1_even,
spread_r1_odd,
spread_a,
spread_b,
spread_c_lo,
spread_c_mid,
spread_c_hi,
spread_d,
)
.0
});
// s_upper_sigma_1 on efgh words
// (6, 5, 14, 7)-bit chunks
meta.create_gate("s_upper_sigma_1", |meta| {
let s_upper_sigma_1 = meta.query_fixed(s_upper_sigma_1, Rotation::cur());
let spread_r0_even = meta.query_advice(a_2, Rotation::prev());
let spread_r0_odd = meta.query_advice(a_2, Rotation::cur());
let spread_r1_even = meta.query_advice(a_2, Rotation::next());
let spread_r1_odd = meta.query_advice(a_3, Rotation::cur());
let spread_a_lo = meta.query_advice(a_3, Rotation::next());
let spread_a_hi = meta.query_advice(a_4, Rotation::next());
let spread_b_lo = meta.query_advice(a_3, Rotation::prev());
let spread_b_hi = meta.query_advice(a_4, Rotation::prev());
let spread_c = meta.query_advice(a_5, Rotation::cur());
let spread_d = meta.query_advice(a_4, Rotation::cur());
CompressionGate::s_upper_sigma_1(
s_upper_sigma_1,
spread_r0_even,
spread_r0_odd,
spread_r1_even,
spread_r1_odd,
spread_a_lo,
spread_a_hi,
spread_b_lo,
spread_b_hi,
spread_c,
spread_d,
)
.0
});
// s_ch on efgh words
// First part of choice gate on (E, F, G), E ∧ F
meta.create_gate("s_ch", |meta| {
let s_ch = meta.query_fixed(s_ch, Rotation::cur());
let spread_p0_even = meta.query_advice(a_2, Rotation::prev());
let spread_p0_odd = meta.query_advice(a_2, Rotation::cur());
let spread_p1_even = meta.query_advice(a_2, Rotation::next());
let spread_p1_odd = meta.query_advice(a_3, Rotation::cur());
let spread_e_lo = meta.query_advice(a_3, Rotation::prev());
let spread_e_hi = meta.query_advice(a_4, Rotation::prev());
let spread_f_lo = meta.query_advice(a_3, Rotation::next());
let spread_f_hi = meta.query_advice(a_4, Rotation::next());
CompressionGate::s_ch(
s_ch,
spread_p0_even,
spread_p0_odd,
spread_p1_even,
spread_p1_odd,
spread_e_lo,
spread_e_hi,
spread_f_lo,
spread_f_hi,
)
.0
});
// s_ch_neg on efgh words
// Second part of Choice gate on (E, F, G), ¬E ∧ G
meta.create_gate("s_ch_neg", |meta| {
let s_ch_neg = meta.query_fixed(s_ch_neg, Rotation::cur());
let spread_q0_even = meta.query_advice(a_2, Rotation::prev());
let spread_q0_odd = meta.query_advice(a_2, Rotation::cur());
let spread_q1_even = meta.query_advice(a_2, Rotation::next());
let spread_q1_odd = meta.query_advice(a_3, Rotation::cur());
let spread_e_lo = meta.query_advice(a_5, Rotation::prev());
let spread_e_hi = meta.query_advice(a_5, Rotation::cur());
let spread_e_neg_lo = meta.query_advice(a_3, Rotation::prev());
let spread_e_neg_hi = meta.query_advice(a_4, Rotation::prev());
let spread_g_lo = meta.query_advice(a_3, Rotation::next());
let spread_g_hi = meta.query_advice(a_4, Rotation::next());
CompressionGate::s_ch_neg(
s_ch_neg,
spread_q0_even,
spread_q0_odd,
spread_q1_even,
spread_q1_odd,
spread_e_lo,
spread_e_hi,
spread_e_neg_lo,
spread_e_neg_hi,
spread_g_lo,
spread_g_hi,
)
.0
});
// s_maj on abcd words
meta.create_gate("s_maj", |meta| {
let s_maj = meta.query_fixed(s_maj, Rotation::cur());
let spread_m0_even = meta.query_advice(a_2, Rotation::prev());
let spread_m0_odd = meta.query_advice(a_2, Rotation::cur());
let spread_m1_even = meta.query_advice(a_2, Rotation::next());
let spread_m1_odd = meta.query_advice(a_3, Rotation::cur());
let spread_a_lo = meta.query_advice(a_4, Rotation::prev());
let spread_a_hi = meta.query_advice(a_5, Rotation::prev());
let spread_b_lo = meta.query_advice(a_4, Rotation::cur());
let spread_b_hi = meta.query_advice(a_5, Rotation::cur());
let spread_c_lo = meta.query_advice(a_4, Rotation::next());
let spread_c_hi = meta.query_advice(a_5, Rotation::next());
CompressionGate::s_maj(
s_maj,
spread_m0_even,
spread_m0_odd,
spread_m1_even,
spread_m1_odd,
spread_a_lo,
spread_a_hi,
spread_b_lo,
spread_b_hi,
spread_c_lo,
spread_c_hi,
)
.0
});
// s_h_prime to compute H' = H + Ch(E, F, G) + s_upper_sigma_1(E) + K + W
meta.create_gate("s_h_prime", |meta| {
let s_h_prime = meta.query_fixed(s_h_prime, Rotation::cur());
let h_prime_lo = meta.query_advice(a_7, Rotation::next());
let h_prime_hi = meta.query_advice(a_8, Rotation::next());
let h_prime_carry = meta.query_advice(a_9, Rotation::next());
let sigma_e_lo = meta.query_advice(a_4, Rotation::cur());
let sigma_e_hi = meta.query_advice(a_5, Rotation::cur());
let ch_lo = meta.query_advice(a_1, Rotation::cur());
let ch_hi = meta.query_advice(a_6, Rotation::next());
let ch_neg_lo = meta.query_advice(a_5, Rotation::prev());
let ch_neg_hi = meta.query_advice(a_5, Rotation::next());
let h_lo = meta.query_advice(a_7, Rotation::prev());
let h_hi = meta.query_advice(a_7, Rotation::cur());
let k_lo = meta.query_advice(a_6, Rotation::prev());
let k_hi = meta.query_advice(a_6, Rotation::cur());
let w_lo = meta.query_advice(a_8, Rotation::prev());
let w_hi = meta.query_advice(a_8, Rotation::cur());
CompressionGate::s_h_prime(
s_h_prime,
h_prime_lo,
h_prime_hi,
h_prime_carry,
sigma_e_lo,
sigma_e_hi,
ch_lo,
ch_hi,
ch_neg_lo,
ch_neg_hi,
h_lo,
h_hi,
k_lo,
k_hi,
w_lo,
w_hi,
)
.0
});
// s_a_new
meta.create_gate("s_a_new", |meta| {
let s_a_new = meta.query_fixed(s_a_new, Rotation::cur());
let a_new_lo = meta.query_advice(a_8, Rotation::cur());
let a_new_hi = meta.query_advice(a_8, Rotation::next());
let a_new_carry = meta.query_advice(a_9, Rotation::cur());
let sigma_a_lo = meta.query_advice(a_6, Rotation::cur());
let sigma_a_hi = meta.query_advice(a_6, Rotation::next());
let maj_abc_lo = meta.query_advice(a_1, Rotation::cur());
let maj_abc_hi = meta.query_advice(a_3, Rotation::prev());
let h_prime_lo = meta.query_advice(a_7, Rotation::prev());
let h_prime_hi = meta.query_advice(a_8, Rotation::prev());
CompressionGate::s_a_new(
s_a_new,
a_new_lo,
a_new_hi,
a_new_carry,
sigma_a_lo,
sigma_a_hi,
maj_abc_lo,
maj_abc_hi,
h_prime_lo,
h_prime_hi,
)
.0
});
// s_e_new
meta.create_gate("s_e_new", |meta| {
let s_e_new = meta.query_fixed(s_e_new, Rotation::cur());
let e_new_lo = meta.query_advice(a_8, Rotation::cur());
let e_new_hi = meta.query_advice(a_8, Rotation::next());
let e_new_carry = meta.query_advice(a_9, Rotation::next());
let d_lo = meta.query_advice(a_7, Rotation::cur());
let d_hi = meta.query_advice(a_7, Rotation::next());
let h_prime_lo = meta.query_advice(a_7, Rotation::prev());
let h_prime_hi = meta.query_advice(a_8, Rotation::prev());
CompressionGate::s_e_new(
s_e_new,
e_new_lo,
e_new_hi,
e_new_carry,
d_lo,
d_hi,
h_prime_lo,
h_prime_hi,
)
.0
});
// s_digest for final round
meta.create_gate("s_digest", |meta| {
let s_digest = meta.query_fixed(s_digest, Rotation::cur());
let lo_0 = meta.query_advice(a_3, Rotation::cur());
let hi_0 = meta.query_advice(a_4, Rotation::cur());
let word_0 = meta.query_advice(a_5, Rotation::cur());
let lo_1 = meta.query_advice(a_6, Rotation::cur());
let hi_1 = meta.query_advice(a_7, Rotation::cur());
let word_1 = meta.query_advice(a_8, Rotation::cur());
let lo_2 = meta.query_advice(a_3, Rotation::next());
let hi_2 = meta.query_advice(a_4, Rotation::next());
let word_2 = meta.query_advice(a_5, Rotation::next());
let lo_3 = meta.query_advice(a_6, Rotation::next());
let hi_3 = meta.query_advice(a_7, Rotation::next());
let word_3 = meta.query_advice(a_8, Rotation::next());
CompressionGate::s_digest(
s_digest, lo_0, hi_0, word_0, lo_1, hi_1, word_1, lo_2, hi_2, word_2, lo_3, hi_3,
word_3,
)
.0
});
Compression {
lookup,
message_schedule,
extras,
s_ch,
s_ch_neg,
s_maj,
s_h_prime,
s_a_new,
s_e_new,
s_upper_sigma_0,
s_upper_sigma_1,
s_decompose_abcd,
s_decompose_efgh,
s_digest,
perm,
}
}
/// Initialize compression with a constant Initialization Vector of 32-byte words.
/// Returns an initialized state.
pub(super) fn initialize_with_iv<F: FieldExt>(
&self,
layouter: &mut impl Layouter<Table16Chip<F>>,
init_state: [u32; STATE],
) -> Result<State, Error> {
let mut new_state = State::empty_state();
layouter.assign_region(
|| "initialize_with_iv",
|mut region| {
new_state = self.initialize_iv(&mut region, init_state)?;
Ok(())
},
)?;
Ok(new_state)
}
/// Initialize compression with some initialized state. This could be a state
/// output from a previous compression round.
pub(super) fn initialize_with_state<F: FieldExt>(
&self,
layouter: &mut impl Layouter<Table16Chip<F>>,
init_state: State,
) -> Result<State, Error> {
let mut new_state = State::empty_state();
layouter.assign_region(
|| "initialize_with_state",
|mut region| {
new_state = self.initialize_state(&mut region, init_state.clone())?;
Ok(())
},
)?;
Ok(new_state)
}
/// Given an initialized state and a message schedule, perform 64 compression rounds.
pub(super) fn compress<F: FieldExt>(
&self,
layouter: &mut impl Layouter<Table16Chip<F>>,
initialized_state: State,
w_halves: [(CellValue16, CellValue16); ROUNDS],
) -> Result<State, Error> {
let mut state = State::empty_state();
layouter.assign_region(
|| "compress",
|mut region| {
state = initialized_state.clone();
for idx in 0..64 {
state =
self.assign_round(&mut region, idx, state.clone(), w_halves[idx as usize])?;
}
Ok(())
},
)?;
Ok(state)
}
/// After the final round, convert the state into the final digest.
pub(super) fn digest<F: FieldExt>(
&self,
layouter: &mut impl Layouter<Table16Chip<F>>,
state: State,
) -> Result<[BlockWord; DIGEST_SIZE], Error> {
let mut digest = [BlockWord::new(0); DIGEST_SIZE];
layouter.assign_region(
|| "digest",
|mut region| {
digest = self.assign_digest(&mut region, state.clone())?;
Ok(())
},
)?;
Ok(digest)
}
#[cfg(test)]
pub(super) fn empty_configure<F: FieldExt>(
meta: &mut ConstraintSystem<F>,
lookup: SpreadInputs,
message_schedule: Column<Advice>,
extras: [Column<Advice>; 6],
perm: Permutation,
) -> Self {
let s_ch = meta.fixed_column();
let s_ch_neg = meta.fixed_column();
let s_maj = meta.fixed_column();
let s_h_prime = meta.fixed_column();
let s_a_new = meta.fixed_column();
let s_e_new = meta.fixed_column();
let s_upper_sigma_0 = meta.fixed_column();
let s_upper_sigma_1 = meta.fixed_column();
// Decomposition gate for AbcdVar
let s_decompose_abcd = meta.fixed_column();
// Decomposition gate for EfghVar
let s_decompose_efgh = meta.fixed_column();
let s_digest = meta.fixed_column();
Compression {
lookup,
message_schedule,
extras,
s_ch,
s_ch_neg,
s_maj,
s_h_prime,
s_a_new,
s_e_new,
s_upper_sigma_0,
s_upper_sigma_1,
s_decompose_abcd,
s_decompose_efgh,
s_digest,
perm,
}
}
}
#[cfg(test)]
mod tests {
use super::super::{
super::BLOCK_SIZE, get_msg_schedule_test_input, BlockWord, MessageSchedule, SpreadTable,
Table16Chip, Table16Config, IV,
};
use super::Compression;
use halo2::{
arithmetic::FieldExt,
circuit::{layouter, Layouter},
dev::MockProver,
pasta::Fp,
plonk::{Assignment, Circuit, ConstraintSystem, Error, Permutation},
};
#[test]
fn compress() {
struct MyCircuit {}
impl<F: FieldExt> Circuit<F> for MyCircuit {
type Config = Table16Config;
fn configure(meta: &mut ConstraintSystem<F>) -> Self::Config {
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(),
];
// 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(),
],
);
let compression = Compression::configure(
meta,
lookup_inputs.clone(),
message_schedule,
extras,
perm.clone(),
);
let message_schedule = MessageSchedule::configure(
meta,
lookup_inputs.clone(),
message_schedule,
extras,
perm.clone(),
);
Table16Config {
lookup_table,
message_schedule,
compression,
}
}
fn synthesize(
&self,
cs: &mut impl Assignment<F>,
config: Self::Config,
) -> Result<(), Error> {
let mut layouter = layouter::SingleChip::<Table16Chip<F>, _>::new(cs, config)?;
// Load table
let table = layouter.config().lookup_table.clone();
table.load(&mut layouter)?;
// Test vector: "abc"
let input: [BlockWord; BLOCK_SIZE] = get_msg_schedule_test_input();
let config = layouter.config().clone();
let (_, w_halves) = config.message_schedule.process(&mut layouter, input)?;
let compression = config.compression.clone();
let initial_state = compression.initialize_with_iv(&mut layouter, IV)?;
let state =
config
.compression
.compress(&mut layouter, initial_state.clone(), w_halves)?;
let digest = config.compression.digest(&mut layouter, state)?;
for (idx, digest_word) in digest.iter().enumerate() {
assert_eq!(
(digest_word.value.unwrap() as u64 + IV[idx] as u64) as u32,
super::compression_util::COMPRESSION_OUTPUT[idx]
);
}
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,422 @@
use super::super::{util::*, Gate};
use halo2::{arithmetic::FieldExt, plonk::Expression};
pub struct CompressionGate<F: FieldExt>(pub Expression<F>);
impl<F: FieldExt> CompressionGate<F> {
fn ones() -> Expression<F> {
Expression::Constant(F::one())
}
// Decompose `A,B,C,D` words
// (2, 11, 9, 10)-bit chunks
#[allow(clippy::too_many_arguments)]
pub fn s_decompose_abcd(
s_decompose_abcd: Expression<F>,
a: Expression<F>,
spread_a: Expression<F>,
b: Expression<F>,
spread_b: Expression<F>,
tag_b: Expression<F>,
c_lo: Expression<F>,
spread_c_lo: Expression<F>,
c_mid: Expression<F>,
spread_c_mid: Expression<F>,
c_hi: Expression<F>,
spread_c_hi: Expression<F>,
d: Expression<F>,
spread_d: Expression<F>,
tag_d: Expression<F>,
word_lo: Expression<F>,
spread_word_lo: Expression<F>,
word_hi: Expression<F>,
spread_word_hi: Expression<F>,
) -> Self {
let check_spread_and_range =
Gate::three_bit_spread_and_range(c_lo.clone(), spread_c_lo.clone())
+ Gate::three_bit_spread_and_range(c_mid.clone(), spread_c_mid.clone())
+ Gate::three_bit_spread_and_range(c_hi.clone(), spread_c_hi.clone())
+ Gate::two_bit_spread_and_range(a.clone(), spread_a.clone());
let range_check_tag_b = Gate::range_check(tag_b, 0, 2);
let range_check_tag_d = Gate::range_check(tag_d, 0, 1);
let dense_check = a
+ b * F::from_u64(1 << 2)
+ c_lo * F::from_u64(1 << 13)
+ c_mid * F::from_u64(1 << 16)
+ c_hi * F::from_u64(1 << 19)
+ d * F::from_u64(1 << 22)
+ word_lo * (-F::one())
+ word_hi * F::from_u64(1 << 16) * (-F::one());
let spread_check = spread_a
+ spread_b * F::from_u64(1 << 4)
+ spread_c_lo * F::from_u64(1 << 26)
+ spread_c_mid * F::from_u64(1 << 32)
+ spread_c_hi * F::from_u64(1 << 38)
+ spread_d * F::from_u64(1 << 44)
+ spread_word_lo * (-F::one())
+ spread_word_hi * F::from_u64(1 << 32) * (-F::one());
CompressionGate(
s_decompose_abcd
* (range_check_tag_b
+ range_check_tag_d
+ dense_check
+ spread_check
+ check_spread_and_range),
)
}
// Decompose `E,F,G,H` words
// (6, 5, 14, 7)-bit chunks
#[allow(clippy::too_many_arguments)]
pub fn s_decompose_efgh(
s_decompose_efgh: Expression<F>,
a_lo: Expression<F>,
spread_a_lo: Expression<F>,
a_hi: Expression<F>,
spread_a_hi: Expression<F>,
b_lo: Expression<F>,
spread_b_lo: Expression<F>,
b_hi: Expression<F>,
spread_b_hi: Expression<F>,
c: Expression<F>,
spread_c: Expression<F>,
tag_c: Expression<F>,
d: Expression<F>,
spread_d: Expression<F>,
tag_d: Expression<F>,
word_lo: Expression<F>,
spread_word_lo: Expression<F>,
word_hi: Expression<F>,
spread_word_hi: Expression<F>,
) -> Self {
let check_spread_and_range =
Gate::three_bit_spread_and_range(a_lo.clone(), spread_a_lo.clone())
+ Gate::three_bit_spread_and_range(a_hi.clone(), spread_a_hi.clone())
+ Gate::three_bit_spread_and_range(b_hi.clone(), spread_b_hi.clone())
+ Gate::two_bit_spread_and_range(b_lo.clone(), spread_b_lo.clone());
let range_check_tag_c = Gate::range_check(tag_c, 0, 4);
let range_check_tag_d = Gate::range_check(tag_d, 0, 0);
let dense_check = a_lo
+ a_hi * F::from_u64(1 << 3)
+ b_lo * F::from_u64(1 << 6)
+ b_hi * F::from_u64(1 << 8)
+ c * F::from_u64(1 << 11)
+ d * F::from_u64(1 << 25)
+ word_lo * (-F::one())
+ word_hi * F::from_u64(1 << 16) * (-F::one());
let spread_check = spread_a_lo
+ spread_a_hi * F::from_u64(1 << 6)
+ spread_b_lo * F::from_u64(1 << 12)
+ spread_b_hi * F::from_u64(1 << 16)
+ spread_c * F::from_u64(1 << 22)
+ spread_d * F::from_u64(1 << 50)
+ spread_word_lo * (-F::one())
+ spread_word_hi * F::from_u64(1 << 32) * (-F::one());
CompressionGate(
s_decompose_efgh
* (range_check_tag_c
+ range_check_tag_d
+ dense_check
+ spread_check
+ check_spread_and_range),
)
}
// s_upper_sigma_0 on abcd words
// (2, 11, 9, 10)-bit chunks
#[allow(clippy::too_many_arguments)]
pub fn s_upper_sigma_0(
s_upper_sigma_0: Expression<F>,
spread_r0_even: Expression<F>,
spread_r0_odd: Expression<F>,
spread_r1_even: Expression<F>,
spread_r1_odd: Expression<F>,
spread_a: Expression<F>,
spread_b: Expression<F>,
spread_c_lo: Expression<F>,
spread_c_mid: Expression<F>,
spread_c_hi: Expression<F>,
spread_d: Expression<F>,
) -> Self {
let spread_witness = spread_r0_even
+ spread_r0_odd * F::from_u64(2)
+ (spread_r1_even + spread_r1_odd * F::from_u64(2)) * F::from_u64(1 << 32);
let xor_0 = spread_b.clone()
+ spread_c_lo.clone() * F::from_u64(1 << 22)
+ spread_c_mid.clone() * F::from_u64(1 << 28)
+ spread_c_hi.clone() * F::from_u64(1 << 34)
+ spread_d.clone() * F::from_u64(1 << 40)
+ spread_a.clone() * F::from_u64(1 << 60);
let xor_1 = spread_c_lo.clone()
+ spread_c_mid.clone() * F::from_u64(1 << 6)
+ spread_c_hi.clone() * F::from_u64(1 << 12)
+ spread_d.clone() * F::from_u64(1 << 18)
+ spread_a.clone() * F::from_u64(1 << 38)
+ spread_b.clone() * F::from_u64(1 << 42);
let xor_2 = spread_d
+ spread_a * F::from_u64(1 << 20)
+ spread_b * F::from_u64(1 << 24)
+ spread_c_lo * F::from_u64(1 << 46)
+ spread_c_mid * F::from_u64(1 << 52)
+ spread_c_hi * F::from_u64(1 << 58);
let xor = xor_0 + xor_1 + xor_2;
CompressionGate(s_upper_sigma_0 * (spread_witness + (xor * -F::one())))
}
// s_upper_sigma_1 on efgh words
// (6, 5, 14, 7)-bit chunks
#[allow(clippy::too_many_arguments)]
pub fn s_upper_sigma_1(
s_upper_sigma_1: Expression<F>,
spread_r0_even: Expression<F>,
spread_r0_odd: Expression<F>,
spread_r1_even: Expression<F>,
spread_r1_odd: Expression<F>,
spread_a_lo: Expression<F>,
spread_a_hi: Expression<F>,
spread_b_lo: Expression<F>,
spread_b_hi: Expression<F>,
spread_c: Expression<F>,
spread_d: Expression<F>,
) -> Self {
let spread_witness = spread_r0_even
+ spread_r0_odd * F::from_u64(2)
+ (spread_r1_even + spread_r1_odd * F::from_u64(2)) * F::from_u64(1 << 32);
let xor_0 = spread_b_lo.clone()
+ spread_b_hi.clone() * F::from_u64(1 << 4)
+ spread_c.clone() * F::from_u64(1 << 10)
+ spread_d.clone() * F::from_u64(1 << 38)
+ spread_a_lo.clone() * F::from_u64(1 << 52)
+ spread_a_hi.clone() * F::from_u64(1 << 58);
let xor_1 = spread_c.clone()
+ spread_d.clone() * F::from_u64(1 << 28)
+ spread_a_lo.clone() * F::from_u64(1 << 42)
+ spread_a_hi.clone() * F::from_u64(1 << 48)
+ spread_b_lo.clone() * F::from_u64(1 << 54)
+ spread_b_hi.clone() * F::from_u64(1 << 58);
let xor_2 = spread_d
+ spread_a_lo * F::from_u64(1 << 14)
+ spread_a_hi * F::from_u64(1 << 20)
+ spread_b_lo * F::from_u64(1 << 26)
+ spread_b_hi * F::from_u64(1 << 30)
+ spread_c * F::from_u64(1 << 36);
let xor = xor_0 + xor_1 + xor_2;
CompressionGate(s_upper_sigma_1 * (spread_witness + (xor * -F::one())))
}
// First part of choice gate on (E, F, G), E ∧ F
#[allow(clippy::too_many_arguments)]
pub fn s_ch(
s_ch: Expression<F>,
spread_p0_even: Expression<F>,
spread_p0_odd: Expression<F>,
spread_p1_even: Expression<F>,
spread_p1_odd: Expression<F>,
spread_e_lo: Expression<F>,
spread_e_hi: Expression<F>,
spread_f_lo: Expression<F>,
spread_f_hi: Expression<F>,
) -> Self {
let lhs_lo = spread_e_lo + spread_f_lo;
let lhs_hi = spread_e_hi + spread_f_hi;
let lhs = lhs_lo + lhs_hi * F::from_u64(1 << 32);
let rhs_even = spread_p0_even + spread_p1_even * F::from_u64(1 << 32);
let rhs_odd = spread_p0_odd + spread_p1_odd * F::from_u64(1 << 32);
let rhs = rhs_even + rhs_odd * F::from_u64(2);
CompressionGate(s_ch * (lhs + rhs * -F::one()))
}
// Second part of Choice gate on (E, F, G), ¬E ∧ G
#[allow(clippy::too_many_arguments)]
pub fn s_ch_neg(
s_ch_neg: Expression<F>,
spread_q0_even: Expression<F>,
spread_q0_odd: Expression<F>,
spread_q1_even: Expression<F>,
spread_q1_odd: Expression<F>,
spread_e_lo: Expression<F>,
spread_e_hi: Expression<F>,
spread_e_neg_lo: Expression<F>,
spread_e_neg_hi: Expression<F>,
spread_g_lo: Expression<F>,
spread_g_hi: Expression<F>,
) -> Self {
let neg_check = Self::neg_check(
spread_e_lo,
spread_e_hi,
spread_e_neg_lo.clone(),
spread_e_neg_hi.clone(),
);
let lhs_lo = spread_e_neg_lo + spread_g_lo;
let lhs_hi = spread_e_neg_hi + spread_g_hi;
let lhs = lhs_lo + lhs_hi * F::from_u64(1 << 32);
let rhs_even = spread_q0_even + spread_q1_even * F::from_u64(1 << 32);
let rhs_odd = spread_q0_odd + spread_q1_odd * F::from_u64(1 << 32);
let rhs = rhs_even + rhs_odd * F::from_u64(2);
CompressionGate(s_ch_neg * (neg_check + lhs + rhs * -F::one()))
}
// Majority gate on (A, B, C)
#[allow(clippy::too_many_arguments)]
pub fn s_maj(
s_maj: Expression<F>,
spread_m_0_even: Expression<F>,
spread_m_0_odd: Expression<F>,
spread_m_1_even: Expression<F>,
spread_m_1_odd: Expression<F>,
spread_a_lo: Expression<F>,
spread_a_hi: Expression<F>,
spread_b_lo: Expression<F>,
spread_b_hi: Expression<F>,
spread_c_lo: Expression<F>,
spread_c_hi: Expression<F>,
) -> Self {
let maj_even = spread_m_0_even + spread_m_1_even * F::from_u64(1 << 32);
let maj_odd = spread_m_0_odd + spread_m_1_odd * F::from_u64(1 << 32);
let maj = maj_even + maj_odd * F::from_u64(2);
let a = spread_a_lo + spread_a_hi * F::from_u64(1 << 32);
let b = spread_b_lo + spread_b_hi * F::from_u64(1 << 32);
let c = spread_c_lo + spread_c_hi * F::from_u64(1 << 32);
let sum = a + b + c;
CompressionGate(s_maj * (sum + maj * -F::one()))
}
// Negation gate, used in second part of Choice gate
fn neg_check(
word_lo: Expression<F>,
word_hi: Expression<F>,
neg_word_lo: Expression<F>,
neg_word_hi: Expression<F>,
) -> Expression<F> {
let evens = Self::ones() * F::from_u64(MASK_EVEN_32 as u64);
// evens - word_lo = neg_word_lo
let lo_check = neg_word_lo + word_lo + (evens.clone() * (-F::one()));
// evens - word_hi = neg_word_hi
let hi_check = neg_word_hi + word_hi + (evens * (-F::one()));
lo_check + hi_check
}
// s_h_prime to get H' = H + Ch(E, F, G) + s_upper_sigma_1(E) + K + W
#[allow(clippy::too_many_arguments)]
pub fn s_h_prime(
s_h_prime: Expression<F>,
h_prime_lo: Expression<F>,
h_prime_hi: Expression<F>,
h_prime_carry: Expression<F>,
sigma_e_lo: Expression<F>,
sigma_e_hi: Expression<F>,
ch_lo: Expression<F>,
ch_hi: Expression<F>,
ch_neg_lo: Expression<F>,
ch_neg_hi: Expression<F>,
h_lo: Expression<F>,
h_hi: Expression<F>,
k_lo: Expression<F>,
k_hi: Expression<F>,
w_lo: Expression<F>,
w_hi: Expression<F>,
) -> Self {
let lo = h_lo + ch_lo + ch_neg_lo + sigma_e_lo + k_lo + w_lo;
let hi = h_hi + ch_hi + ch_neg_hi + sigma_e_hi + k_hi + w_hi;
let sum = lo + hi * F::from_u64(1 << 16);
let h_prime = h_prime_lo + h_prime_hi * F::from_u64(1 << 16);
CompressionGate(
s_h_prime
* (sum
+ h_prime_carry * F::from_u64(1 << 32) * (-F::one())
+ h_prime * (-F::one())),
)
}
// s_a_new to get A_new = H' + Maj(A, B, C) + s_upper_sigma_0(A)
#[allow(clippy::too_many_arguments)]
pub fn s_a_new(
s_a_new: Expression<F>,
a_new_lo: Expression<F>,
a_new_hi: Expression<F>,
a_new_carry: Expression<F>,
sigma_a_lo: Expression<F>,
sigma_a_hi: Expression<F>,
maj_abc_lo: Expression<F>,
maj_abc_hi: Expression<F>,
h_prime_lo: Expression<F>,
h_prime_hi: Expression<F>,
) -> Self {
let lo = sigma_a_lo + maj_abc_lo + h_prime_lo;
let hi = sigma_a_hi + maj_abc_hi + h_prime_hi;
let sum = lo + hi * F::from_u64(1 << 16);
let a_new = a_new_lo + a_new_hi * F::from_u64(1 << 16);
CompressionGate(
s_a_new
* (sum + a_new_carry * F::from_u64(1 << 32) * (-F::one()) + a_new * (-F::one())),
)
}
// s_e_new to get E_new = H' + D
#[allow(clippy::too_many_arguments)]
pub fn s_e_new(
s_e_new: Expression<F>,
e_new_lo: Expression<F>,
e_new_hi: Expression<F>,
e_new_carry: Expression<F>,
d_lo: Expression<F>,
d_hi: Expression<F>,
h_prime_lo: Expression<F>,
h_prime_hi: Expression<F>,
) -> Self {
let lo = h_prime_lo + d_lo;
let hi = h_prime_hi + d_hi;
let sum = lo + hi * F::from_u64(1 << 16);
let e_new = e_new_lo + e_new_hi * F::from_u64(1 << 16);
CompressionGate(
s_e_new
* (sum + e_new_carry * F::from_u64(1 << 32) * (-F::one()) + e_new * (-F::one())),
)
}
fn check_lo_hi(lo: Expression<F>, hi: Expression<F>, word: Expression<F>) -> Expression<F> {
lo + hi * F::from_u64(1 << 16) + (word * (-F::one()))
}
// s_digest on final round
#[allow(clippy::too_many_arguments)]
pub fn s_digest(
s_digest: Expression<F>,
lo_0: Expression<F>,
hi_0: Expression<F>,
word_0: Expression<F>,
lo_1: Expression<F>,
hi_1: Expression<F>,
word_1: Expression<F>,
lo_2: Expression<F>,
hi_2: Expression<F>,
word_2: Expression<F>,
lo_3: Expression<F>,
hi_3: Expression<F>,
word_3: Expression<F>,
) -> Self {
CompressionGate(
s_digest
* (Self::check_lo_hi(lo_0, hi_0, word_0)
+ Self::check_lo_hi(lo_1, hi_1, word_1)
+ Self::check_lo_hi(lo_2, hi_2, word_2)
+ Self::check_lo_hi(lo_3, hi_3, word_3)),
)
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,123 @@
use super::super::{super::DIGEST_SIZE, BlockWord, CellValue16, Table16Assignment, Table16Chip};
use super::{compression_util::*, Compression, State};
use halo2::{
arithmetic::FieldExt,
circuit::Region,
plonk::{Advice, Column, Error},
};
impl Compression {
#[allow(clippy::many_single_char_names)]
pub fn assign_digest<F: FieldExt>(
&self,
region: &mut Region<'_, Table16Chip<F>>,
state: State,
) -> Result<[BlockWord; DIGEST_SIZE], Error> {
let a_3 = self.extras[0];
let a_4 = self.extras[1];
let a_5 = self.message_schedule;
let a_6 = self.extras[2];
let a_7 = self.extras[3];
let a_8 = self.extras[4];
let (a, b, c, d, e, f, g, h) = match_state(state);
let abcd_row = 0;
region.assign_fixed(|| "s_digest", self.s_digest, abcd_row, || Ok(F::one()))?;
let efgh_row = abcd_row + 2;
region.assign_fixed(|| "s_digest", self.s_digest, efgh_row, || Ok(F::one()))?;
// Assign digest for A, B, C, D
self.assign_and_constrain(
region,
|| "a_lo",
a_3,
abcd_row,
&a.dense_halves.0.into(),
&self.perm,
)?;
self.assign_and_constrain(
region,
|| "a_hi",
a_4,
abcd_row,
&a.dense_halves.1.into(),
&self.perm,
)?;
let a = a.dense_halves.0.value.unwrap() as u32
+ (1 << 16) * (a.dense_halves.1.value.unwrap() as u32);
region.assign_advice(|| "a", a_5, abcd_row, || Ok(F::from_u64(a as u64)))?;
let b = self.assign_digest_word(region, abcd_row, a_6, a_7, a_8, b.dense_halves)?;
let c = self.assign_digest_word(region, abcd_row + 1, a_3, a_4, a_5, c.dense_halves)?;
let d = self.assign_digest_word(region, abcd_row + 1, a_6, a_7, a_8, d.dense_halves)?;
// Assign digest for E, F, G, H
self.assign_and_constrain(
region,
|| "e_lo",
a_3,
efgh_row,
&e.dense_halves.0.into(),
&self.perm,
)?;
self.assign_and_constrain(
region,
|| "e_hi",
a_4,
efgh_row,
&e.dense_halves.1.into(),
&self.perm,
)?;
let e = e.dense_halves.0.value.unwrap() as u32
+ (1 << 16) * (e.dense_halves.1.value.unwrap() as u32);
region.assign_advice(|| "e", a_5, efgh_row, || Ok(F::from_u64(e as u64)))?;
let f = self.assign_digest_word(region, efgh_row, a_6, a_7, a_8, f.dense_halves)?;
let g = self.assign_digest_word(region, efgh_row + 1, a_3, a_4, a_5, g.dense_halves)?;
let h = self.assign_digest_word(region, efgh_row + 1, a_6, a_7, a_8, h.dense_halves)?;
Ok([
BlockWord::new(a),
BlockWord::new(b),
BlockWord::new(c),
BlockWord::new(d),
BlockWord::new(e),
BlockWord::new(f),
BlockWord::new(g),
BlockWord::new(h),
])
}
fn assign_digest_word<F: FieldExt>(
&self,
region: &mut Region<'_, Table16Chip<F>>,
row: usize,
lo_col: Column<Advice>,
hi_col: Column<Advice>,
word_col: Column<Advice>,
dense_halves: (CellValue16, CellValue16),
) -> Result<u32, Error> {
self.assign_and_constrain(
region,
|| "lo",
lo_col,
row,
&dense_halves.0.into(),
&self.perm,
)?;
self.assign_and_constrain(
region,
|| "hi",
hi_col,
row,
&dense_halves.1.into(),
&self.perm,
)?;
let val = dense_halves.0.value.unwrap() as u32
+ (1 << 16) * (dense_halves.1.value.unwrap() as u32);
region.assign_advice(|| "word", word_col, row, || Ok(F::from_u64(val as u64)))?;
Ok(val)
}
}

View File

@ -0,0 +1,158 @@
use super::super::{RoundWordDense, RoundWordSpread, StateWord, Table16Chip, STATE};
use super::{compression_util::*, Compression, State};
use halo2::{arithmetic::FieldExt, circuit::Region, plonk::Error};
impl Compression {
#[allow(clippy::many_single_char_names)]
pub fn initialize_iv<F: FieldExt>(
&self,
region: &mut Region<'_, Table16Chip<F>>,
iv: [u32; STATE],
) -> Result<State, Error> {
let a_7 = self.extras[3];
let idx = -1;
// Decompose E into (6, 5, 14, 7)-bit chunks
let e = self.decompose_e(region, idx, iv[4])?;
// Decompose F, G
let f = self.decompose_f(region, idx, iv[5])?;
let g = self.decompose_g(region, idx, iv[6])?;
// Assign H
let h_row = get_h_row(idx);
let h_dense = self.assign_word_halves_dense(region, h_row, a_7, h_row + 1, a_7, iv[7])?;
let h = RoundWordDense::new(h_dense);
// Decompose A into (2, 11, 9, 10)-bit chunks
let a = self.decompose_a(region, idx, iv[0])?;
// Decompose B, C
let b = self.decompose_b(region, idx, iv[1])?;
let c = self.decompose_c(region, idx, iv[2])?;
// Assign D
let d_row = get_d_row(idx);
let d_dense = self.assign_word_halves_dense(region, d_row, a_7, d_row + 1, a_7, iv[3])?;
let d = RoundWordDense::new(d_dense);
Ok(State::new(
StateWord::A(a),
StateWord::B(b),
StateWord::C(c),
StateWord::D(d),
StateWord::E(e),
StateWord::F(f),
StateWord::G(g),
StateWord::H(h),
))
}
#[allow(clippy::many_single_char_names)]
pub fn initialize_state<F: FieldExt>(
&self,
region: &mut Region<'_, Table16Chip<F>>,
state: State,
) -> Result<State, Error> {
let a_7 = self.extras[3];
let (a, b, c, d, e, f, g, h) = match_state(state);
let idx = -1;
// Decompose E into (6, 5, 14, 7)-bit chunks
let e = val_from_dense_halves(e.dense_halves);
let e = self.decompose_e(region, idx, e)?;
// Decompose F, G
let f = val_from_dense_halves(f.dense_halves);
let f = self.decompose_f(region, idx, f)?;
let g = val_from_dense_halves(g.dense_halves);
let g = self.decompose_g(region, idx, g)?;
// Assign H
let h = val_from_dense_halves(h.dense_halves);
let h_row = get_h_row(idx);
let h_dense = self.assign_word_halves_dense(region, h_row, a_7, h_row + 1, a_7, h)?;
let h = RoundWordDense::new(h_dense);
// Decompose A into (2, 11, 9, 10)-bit chunks
let a = val_from_dense_halves(a.dense_halves);
let a = self.decompose_a(region, idx, a)?;
// Decompose B, C
let b = val_from_dense_halves(b.dense_halves);
let b = self.decompose_b(region, idx, b)?;
let c = val_from_dense_halves(c.dense_halves);
let c = self.decompose_c(region, idx, c)?;
// Assign D
let d = val_from_dense_halves(d.dense_halves);
let d_row = get_d_row(idx);
let d_dense = self.assign_word_halves_dense(region, d_row, a_7, d_row + 1, a_7, d)?;
let d = RoundWordDense::new(d_dense);
Ok(State::new(
StateWord::A(a),
StateWord::B(b),
StateWord::C(c),
StateWord::D(d),
StateWord::E(e),
StateWord::F(f),
StateWord::G(g),
StateWord::H(h),
))
}
fn decompose_b<F: FieldExt>(
&self,
region: &mut Region<'_, Table16Chip<F>>,
idx: i32,
b_val: u32,
) -> Result<RoundWordSpread, Error> {
let row = get_decompose_b_row(idx);
let (dense_halves, spread_halves) = self.assign_word_halves(region, row, b_val)?;
self.decompose_abcd(region, row, b_val)?;
Ok(RoundWordSpread::new(dense_halves, spread_halves))
}
fn decompose_c<F: FieldExt>(
&self,
region: &mut Region<'_, Table16Chip<F>>,
idx: i32,
c_val: u32,
) -> Result<RoundWordSpread, Error> {
let row = get_decompose_c_row(idx);
let (dense_halves, spread_halves) = self.assign_word_halves(region, row, c_val)?;
self.decompose_abcd(region, row, c_val)?;
Ok(RoundWordSpread::new(dense_halves, spread_halves))
}
fn decompose_f<F: FieldExt>(
&self,
region: &mut Region<'_, Table16Chip<F>>,
idx: i32,
f_val: u32,
) -> Result<RoundWordSpread, Error> {
let row = get_decompose_f_row(idx);
let (dense_halves, spread_halves) = self.assign_word_halves(region, row, f_val)?;
self.decompose_efgh(region, row, f_val)?;
Ok(RoundWordSpread::new(dense_halves, spread_halves))
}
fn decompose_g<F: FieldExt>(
&self,
region: &mut Region<'_, Table16Chip<F>>,
idx: i32,
g_val: u32,
) -> Result<RoundWordSpread, Error> {
let row = get_decompose_g_row(idx);
let (dense_halves, spread_halves) = self.assign_word_halves(region, row, g_val)?;
self.decompose_efgh(region, row, g_val)?;
Ok(RoundWordSpread::new(dense_halves, spread_halves))
}
}

View File

@ -0,0 +1,138 @@
use super::super::{
CellValue16, RoundWordA, RoundWordE, StateWord, Table16Assignment, Table16Chip, ROUND_CONSTANTS,
};
use super::{compression_util::*, Compression, State};
use halo2::{arithmetic::FieldExt, circuit::Region, plonk::Error};
impl Compression {
#[allow(clippy::many_single_char_names)]
pub fn assign_round<F: FieldExt>(
&self,
region: &mut Region<'_, Table16Chip<F>>,
idx: i32,
state: State,
schedule_word: (CellValue16, CellValue16),
) -> Result<State, Error> {
let a_3 = self.extras[0];
let a_4 = self.extras[1];
let a_7 = self.extras[3];
let (a, b, c, d, e, f, g, h) = match_state(state);
// s_upper_sigma_1(E)
let sigma_1 = self.assign_upper_sigma_1(region, idx, e.pieces.unwrap())?;
// Ch(E, F, G)
let ch = self.assign_ch(region, idx, e.spread_halves.unwrap(), f.spread_halves)?;
let ch_neg = self.assign_ch_neg(region, idx, e.spread_halves.unwrap(), g.spread_halves)?;
// s_upper_sigma_0(A)
let sigma_0 = self.assign_upper_sigma_0(region, idx, a.pieces.unwrap())?;
// Maj(A, B, C)
let maj = self.assign_maj(
region,
idx,
a.spread_halves.unwrap(),
b.spread_halves,
c.spread_halves,
)?;
// H' = H + Ch(E, F, G) + s_upper_sigma_1(E) + K + W
let h_prime = self.assign_h_prime(
region,
idx,
h.dense_halves,
ch,
ch_neg,
sigma_1,
ROUND_CONSTANTS[idx as usize],
schedule_word,
)?;
// E_new = H' + D
let e_new_dense = self.assign_e_new(region, idx, d.dense_halves, h_prime)?;
let e_new_val = val_from_dense_halves(e_new_dense);
// A_new = H' + Maj(A, B, C) + sigma_0(A)
let a_new_dense = self.assign_a_new(region, idx, maj, sigma_0, h_prime)?;
let a_new_val = val_from_dense_halves(a_new_dense);
if idx < 63 {
// Assign and copy A_new
let a_new_row = get_decompose_a_row(idx + 1);
self.assign_and_constrain(
region,
|| "a_new_lo",
a_7,
a_new_row,
&a_new_dense.0.into(),
&self.perm,
)?;
self.assign_and_constrain(
region,
|| "a_new_hi",
a_7,
a_new_row + 1,
&a_new_dense.1.into(),
&self.perm,
)?;
// Assign and copy E_new
let e_new_row = get_decompose_e_row(idx + 1);
self.assign_and_constrain(
region,
|| "e_new_lo",
a_7,
e_new_row,
&e_new_dense.0.into(),
&self.perm,
)?;
self.assign_and_constrain(
region,
|| "e_new_hi",
a_7,
e_new_row + 1,
&e_new_dense.1.into(),
&self.perm,
)?;
// Decompose A into (2, 11, 9, 10)-bit chunks
let a_new = self.decompose_a(region, idx + 1, a_new_val)?;
// Decompose E into (6, 5, 14, 7)-bit chunks
let e_new = self.decompose_e(region, idx + 1, e_new_val)?;
Ok(State::new(
StateWord::A(a_new),
StateWord::B(a.into()),
StateWord::C(b),
StateWord::D(c.into()),
StateWord::E(e_new),
StateWord::F(e.into()),
StateWord::G(f),
StateWord::H(g.into()),
))
} else {
let abcd_row = get_digest_abcd_row();
let efgh_row = get_digest_efgh_row();
let a_final =
self.assign_word_halves_dense(region, abcd_row, a_3, abcd_row, a_4, a_new_val)?;
let e_final =
self.assign_word_halves_dense(region, efgh_row, a_3, efgh_row, a_4, e_new_val)?;
Ok(State::new(
StateWord::A(RoundWordA::new_dense(a_final)),
StateWord::B(a.into()),
StateWord::C(b),
StateWord::D(c.into()),
StateWord::E(RoundWordE::new_dense(e_final)),
StateWord::F(e.into()),
StateWord::G(f),
StateWord::H(g.into()),
))
}
}
}

View File

@ -0,0 +1,117 @@
use halo2::{arithmetic::FieldExt, plonk::Expression};
pub struct Gate<F: FieldExt>(pub Expression<F>);
impl<F: FieldExt> Gate<F> {
fn ones() -> Expression<F> {
Expression::Constant(F::one())
}
// Helper gates
fn lagrange_interpolate(
var: Expression<F>,
points: Vec<u16>,
evals: Vec<u32>,
) -> (F, Expression<F>) {
assert_eq!(points.len(), evals.len());
let deg = points.len();
fn factorial(n: u64) -> u64 {
if n < 2 {
1
} else {
n * factorial(n - 1)
}
}
// Scale the whole expression by factor to avoid divisions
let factor = factorial((deg - 1) as u64);
let numerator = |var: Expression<F>, eval: u32, idx: u64| {
let mut expr = Self::ones();
for i in 0..deg {
let i = i as u64;
if i != idx {
expr = expr * (Self::ones() * (-F::one()) * F::from_u64(i) + var.clone());
}
}
expr * F::from_u64(eval.into())
};
let denominator = |idx: i32| {
let mut denom: i32 = 1;
for i in 0..deg {
let i = i as i32;
if i != idx {
denom *= idx - i
}
}
if denom < 0 {
-F::one() * F::from_u64(factor / (-denom as u64))
} else {
F::from_u64(factor / (denom as u64))
}
};
let mut expr = Self::ones() * F::zero();
for ((idx, _), eval) in points.iter().enumerate().zip(evals.iter()) {
expr = expr + numerator(var.clone(), *eval, idx as u64) * denominator(idx as i32)
}
(F::from_u64(factor), expr)
}
pub fn range_check(value: Expression<F>, lower_range: u64, upper_range: u64) -> Expression<F> {
let mut expr = Self::ones();
for i in lower_range..(upper_range + 1) {
expr = expr * (Self::ones() * (-F::one()) * F::from_u64(i) + value.clone())
}
expr
}
// 2-bit range check
fn two_bit_range_check(value: Expression<F>) -> Expression<F> {
Self::range_check(value, 0, (1 << 2) - 1)
}
// 2-bit spread interpolation
fn two_bit_spread(dense: Expression<F>, spread: Expression<F>) -> Expression<F> {
let (factor, lagrange_poly) = Self::lagrange_interpolate(
dense,
vec![0b00, 0b01, 0b10, 0b11],
vec![0b0000, 0b0001, 0b0100, 0b0101],
);
lagrange_poly + (spread * factor * (-F::one()))
}
// 3-bit range check
fn three_bit_range_check(value: Expression<F>) -> Expression<F> {
Self::range_check(value, 0, (1 << 3) - 1)
}
// 3-bit spread
fn three_bit_spread(dense: Expression<F>, spread: Expression<F>) -> Expression<F> {
let (factor, lagrange_poly) = Self::lagrange_interpolate(
dense,
vec![0b000, 0b001, 0b010, 0b011, 0b100, 0b101, 0b110, 0b111],
vec![
0b000000, 0b000001, 0b000100, 0b000101, 0b010000, 0b010001, 0b010100, 0b010101,
],
);
lagrange_poly + (spread * factor * (-F::one()))
}
/// Spread and range check on 2-bit word
pub fn two_bit_spread_and_range(dense: Expression<F>, spread: Expression<F>) -> Expression<F> {
Self::two_bit_range_check(dense.clone()) + Self::two_bit_spread(dense, spread)
}
/// Spread and range check on 3-bit word
pub fn three_bit_spread_and_range(
dense: Expression<F>,
spread: Expression<F>,
) -> Expression<F> {
Self::three_bit_range_check(dense.clone()) + Self::three_bit_spread(dense, spread)
}
}

View File

@ -0,0 +1,597 @@
use std::convert::TryInto;
use super::{
super::BLOCK_SIZE, BlockWord, CellValue16, SpreadInputs, Table16Assignment, Table16Chip, ROUNDS,
};
use halo2::{
arithmetic::FieldExt,
circuit::{Cell, Layouter},
plonk::{Advice, Column, ConstraintSystem, Error, Fixed, Permutation},
poly::Rotation,
};
mod schedule_gates;
mod schedule_util;
mod subregion1;
mod subregion2;
mod subregion3;
use schedule_gates::ScheduleGate;
use schedule_util::*;
#[cfg(test)]
pub use schedule_util::get_msg_schedule_test_input;
#[derive(Clone, Debug)]
pub(super) struct MessageWord {
var: Cell,
value: Option<u32>,
}
#[derive(Clone, Debug)]
pub(super) struct MessageSchedule {
lookup: SpreadInputs,
message_schedule: Column<Advice>,
extras: [Column<Advice>; 6],
/// Construct a word using reduce_4.
s_word: Column<Fixed>,
/// Decomposition gate for W_0, W_62, W_63.
s_decompose_0: Column<Fixed>,
/// Decomposition gate for W_[1..14]
s_decompose_1: Column<Fixed>,
/// Decomposition gate for W_[14..49]
s_decompose_2: Column<Fixed>,
/// Decomposition gate for W_[49..62]
s_decompose_3: Column<Fixed>,
/// sigma_0 gate for W_[1..14]
s_lower_sigma_0: Column<Fixed>,
/// sigma_1 gate for W_[49..62]
s_lower_sigma_1: Column<Fixed>,
/// sigma_0_v2 gate for W_[14..49]
s_lower_sigma_0_v2: Column<Fixed>,
/// sigma_1_v2 gate for W_[14..49]
s_lower_sigma_1_v2: Column<Fixed>,
perm: Permutation,
}
impl<F: FieldExt> Table16Assignment<F> for MessageSchedule {}
impl MessageSchedule {
/// Configures the message schedule.
///
/// `message_schedule` is the column into which the message schedule will be placed.
/// The caller must create appropriate permutations in order to load schedule words
/// into the compression rounds.
///
/// `extras` contains columns that the message schedule will only use for internal
/// gates, and will not place any constraints on (such as lookup constraints) outside
/// itself.
#[allow(clippy::many_single_char_names)]
pub(super) fn configure<F: FieldExt>(
meta: &mut ConstraintSystem<F>,
lookup: SpreadInputs,
message_schedule: Column<Advice>,
extras: [Column<Advice>; 6],
perm: Permutation,
) -> Self {
// Create fixed columns for the selectors we will require.
let s_word = meta.fixed_column();
let s_decompose_0 = meta.fixed_column();
let s_decompose_1 = meta.fixed_column();
let s_decompose_2 = meta.fixed_column();
let s_decompose_3 = meta.fixed_column();
let s_lower_sigma_0 = meta.fixed_column();
let s_lower_sigma_1 = meta.fixed_column();
let s_lower_sigma_0_v2 = meta.fixed_column();
let s_lower_sigma_1_v2 = meta.fixed_column();
// Rename these here for ease of matching the gates to the specification.
let a_0 = lookup.tag;
let a_1 = lookup.dense;
let a_2 = lookup.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];
// s_word for W_[16..64]
meta.create_gate("s_word for W_[16..64]", |meta| {
let s_word = meta.query_fixed(s_word, Rotation::cur());
let sigma_0_lo = meta.query_advice(a_6, Rotation::prev());
let sigma_0_hi = meta.query_advice(a_6, Rotation::cur());
let sigma_1_lo = meta.query_advice(a_7, Rotation::prev());
let sigma_1_hi = meta.query_advice(a_7, Rotation::cur());
let w_minus_9_lo = meta.query_advice(a_8, Rotation::prev());
let w_minus_9_hi = meta.query_advice(a_8, Rotation::cur());
let w_minus_16_lo = meta.query_advice(a_3, Rotation::prev());
let w_minus_16_hi = meta.query_advice(a_4, Rotation::prev());
let word = meta.query_advice(a_5, Rotation::cur());
let carry = meta.query_advice(a_9, Rotation::cur());
ScheduleGate::s_word(
s_word,
sigma_0_lo,
sigma_0_hi,
sigma_1_lo,
sigma_1_hi,
w_minus_9_lo,
w_minus_9_hi,
w_minus_16_lo,
w_minus_16_hi,
word,
carry,
)
.0
});
// s_decompose_0 for all words
meta.create_gate("s_decompose_0", |meta| {
let s_decompose_0 = meta.query_fixed(s_decompose_0, Rotation::cur());
let lo = meta.query_advice(a_3, Rotation::cur());
let hi = meta.query_advice(a_4, Rotation::cur());
let word = meta.query_advice(a_5, Rotation::cur());
ScheduleGate::s_decompose_0(s_decompose_0, lo, hi, word).0
});
// s_decompose_1 for W_[1..14]
// (3, 4, 11, 14)-bit chunks
meta.create_gate("s_decompose_1", |meta| {
let s_decompose_1 = meta.query_fixed(s_decompose_1, Rotation::cur());
let a = meta.query_advice(a_3, Rotation::next()); // 3-bit chunk
let b = meta.query_advice(a_4, Rotation::next()); // 4-bit chunk
let c = meta.query_advice(a_1, Rotation::next()); // 11-bit chunk
let tag_c = meta.query_advice(a_0, Rotation::next());
let d = meta.query_advice(a_1, Rotation::cur()); // 14-bit chunk
let tag_d = meta.query_advice(a_0, Rotation::cur());
let word = meta.query_advice(a_5, Rotation::cur());
ScheduleGate::s_decompose_1(s_decompose_1, a, b, c, tag_c, d, tag_d, word).0
});
// s_decompose_2 for W_[14..49]
// (3, 4, 3, 7, 1, 1, 13)-bit chunks
meta.create_gate("s_decompose_2", |meta| {
let s_decompose_2 = meta.query_fixed(s_decompose_2, Rotation::cur());
let a = meta.query_advice(a_3, Rotation::prev()); // 3-bit chunk
let b = meta.query_advice(a_1, Rotation::next()); // 4-bit chunk
let c = meta.query_advice(a_4, Rotation::prev()); // 3-bit chunk
let d = meta.query_advice(a_1, Rotation::cur()); // 7-bit chunk
let tag_d = meta.query_advice(a_0, Rotation::cur());
let e = meta.query_advice(a_3, Rotation::next()); // 1-bit chunk
let f = meta.query_advice(a_4, Rotation::next()); // 1-bit chunk
let g = meta.query_advice(a_1, Rotation::prev()); // 13-bit chunk
let tag_g = meta.query_advice(a_0, Rotation::prev());
let word = meta.query_advice(a_5, Rotation::cur());
ScheduleGate::s_decompose_2(s_decompose_2, a, b, c, d, tag_d, e, f, g, tag_g, word).0
});
// s_decompose_3 for W_49 to W_61
// (10, 7, 2, 13)-bit chunks
meta.create_gate("s_decompose_3", |meta| {
let s_decompose_3 = meta.query_fixed(s_decompose_3, Rotation::cur());
let a = meta.query_advice(a_1, Rotation::next()); // 10-bit chunk
let tag_a = meta.query_advice(a_0, Rotation::next());
let b = meta.query_advice(a_4, Rotation::next()); // 7-bit chunk
let c = meta.query_advice(a_3, Rotation::next()); // 2-bit chunk
let d = meta.query_advice(a_1, Rotation::cur()); // 13-bit chunk
let tag_d = meta.query_advice(a_0, Rotation::cur());
let word = meta.query_advice(a_5, Rotation::cur());
ScheduleGate::s_decompose_3(s_decompose_3, a, tag_a, b, c, d, tag_d, word).0
});
// sigma_0 v1 on W_[1..14]
// (3, 4, 11, 14)-bit chunks
meta.create_gate("sigma_0 v1", |meta| {
ScheduleGate::s_lower_sigma_0(
meta.query_fixed(s_lower_sigma_0, Rotation::cur()), // s_lower_sigma_0
meta.query_advice(a_2, Rotation::prev()), // spread_r0_even
meta.query_advice(a_2, Rotation::cur()), // spread_r0_odd
meta.query_advice(a_2, Rotation::next()), // spread_r1_even
meta.query_advice(a_3, Rotation::cur()), // spread_r1_odd
meta.query_advice(a_5, Rotation::next()), // a
meta.query_advice(a_6, Rotation::next()), // spread_a
meta.query_advice(a_6, Rotation::cur()), // b
meta.query_advice(a_3, Rotation::prev()), // b_lo
meta.query_advice(a_4, Rotation::prev()), // spread_b_lo
meta.query_advice(a_5, Rotation::prev()), // b_hi
meta.query_advice(a_6, Rotation::prev()), // spread_b_hi
meta.query_advice(a_4, Rotation::cur()), // spread_c
meta.query_advice(a_5, Rotation::cur()), // spread_d
)
.0
});
// sigma_0 v2 on W_[14..49]
// (3, 4, 3, 7, 1, 1, 13)-bit chunks
meta.create_gate("sigma_0 v2", |meta| {
ScheduleGate::s_lower_sigma_0_v2(
meta.query_fixed(s_lower_sigma_0_v2, Rotation::cur()), // s_lower_sigma_0_v2
meta.query_advice(a_2, Rotation::prev()), // spread_r0_even
meta.query_advice(a_2, Rotation::cur()), // spread_r0_odd
meta.query_advice(a_2, Rotation::next()), // spread_r1_even
meta.query_advice(a_3, Rotation::cur()), // spread_r1_odd
meta.query_advice(a_3, Rotation::next()), // a
meta.query_advice(a_4, Rotation::next()), // spread_a
meta.query_advice(a_6, Rotation::cur()), // b
meta.query_advice(a_3, Rotation::prev()), // b_lo
meta.query_advice(a_4, Rotation::prev()), // spread_b_lo
meta.query_advice(a_5, Rotation::prev()), // b_hi
meta.query_advice(a_6, Rotation::prev()), // spread_b_hi
meta.query_advice(a_5, Rotation::next()), // c
meta.query_advice(a_6, Rotation::next()), // spread_c
meta.query_advice(a_4, Rotation::cur()), // spread_d
meta.query_advice(a_7, Rotation::cur()), // spread_e
meta.query_advice(a_7, Rotation::next()), // spread_f
meta.query_advice(a_5, Rotation::cur()), // spread_g
)
.0
});
// sigma_1 v2 on W_14 to W_48
// (3, 4, 3, 7, 1, 1, 13)-bit chunks
meta.create_gate("sigma_1 v2", |meta| {
ScheduleGate::s_lower_sigma_1_v2(
meta.query_fixed(s_lower_sigma_1_v2, Rotation::cur()), // s_lower_sigma_1_v2
meta.query_advice(a_2, Rotation::prev()), // spread_r0_even
meta.query_advice(a_2, Rotation::cur()), // spread_r0_odd
meta.query_advice(a_2, Rotation::next()), // spread_r1_even
meta.query_advice(a_3, Rotation::cur()), // spread_r1_odd
meta.query_advice(a_3, Rotation::next()), // a
meta.query_advice(a_4, Rotation::next()), // spread_a
meta.query_advice(a_6, Rotation::cur()), // b
meta.query_advice(a_3, Rotation::prev()), // b_lo
meta.query_advice(a_4, Rotation::prev()), // spread_b_lo
meta.query_advice(a_5, Rotation::prev()), // b_hi
meta.query_advice(a_6, Rotation::prev()), // spread_b_hi
meta.query_advice(a_5, Rotation::next()), // c
meta.query_advice(a_6, Rotation::next()), // spread_c
meta.query_advice(a_4, Rotation::cur()), // spread_d
meta.query_advice(a_7, Rotation::cur()), // spread_e
meta.query_advice(a_7, Rotation::next()), // spread_f
meta.query_advice(a_5, Rotation::cur()), // spread_g
)
.0
});
// sigma_1 v1 on W_49 to W_61
// (10, 7, 2, 13)-bit chunks
meta.create_gate("sigma_1 v1", |meta| {
ScheduleGate::s_lower_sigma_1(
meta.query_fixed(s_lower_sigma_1, Rotation::cur()), // s_lower_sigma_1
meta.query_advice(a_2, Rotation::prev()), // spread_r0_even
meta.query_advice(a_2, Rotation::cur()), // spread_r0_odd
meta.query_advice(a_2, Rotation::next()), // spread_r1_even
meta.query_advice(a_3, Rotation::cur()), // spread_r1_odd
meta.query_advice(a_4, Rotation::cur()), // spread_a
meta.query_advice(a_6, Rotation::cur()), // b
meta.query_advice(a_3, Rotation::prev()), // b_lo
meta.query_advice(a_4, Rotation::prev()), // spread_b_lo
meta.query_advice(a_5, Rotation::prev()), // b_mid
meta.query_advice(a_6, Rotation::prev()), // spread_b_mid
meta.query_advice(a_5, Rotation::next()), // b_hi
meta.query_advice(a_6, Rotation::next()), // spread_b_hi
meta.query_advice(a_3, Rotation::next()), // c
meta.query_advice(a_4, Rotation::next()), // spread_c
meta.query_advice(a_5, Rotation::cur()), // spread_d
)
.0
});
MessageSchedule {
lookup,
message_schedule,
extras,
s_word,
s_decompose_0,
s_decompose_1,
s_decompose_2,
s_decompose_3,
s_lower_sigma_0,
s_lower_sigma_1,
s_lower_sigma_0_v2,
s_lower_sigma_1_v2,
perm,
}
}
#[allow(clippy::type_complexity)]
pub(super) fn process<F: FieldExt>(
&self,
layouter: &mut impl Layouter<Table16Chip<F>>,
input: [BlockWord; BLOCK_SIZE],
) -> Result<([MessageWord; ROUNDS], [(CellValue16, CellValue16); ROUNDS]), Error> {
let mut w = Vec::<MessageWord>::with_capacity(ROUNDS);
let mut w_halves = Vec::<(CellValue16, CellValue16)>::with_capacity(ROUNDS);
layouter.assign_region(
|| "process message block",
|mut region| {
w = Vec::<MessageWord>::with_capacity(ROUNDS);
w_halves = Vec::<(CellValue16, CellValue16)>::with_capacity(ROUNDS);
// Assign all fixed columns
for index in 1..14 {
let row = get_word_row(index);
region.assign_fixed(
|| "s_decompose_1",
self.s_decompose_1,
row,
|| Ok(F::one()),
)?;
region.assign_fixed(
|| "s_lower_sigma_0",
self.s_lower_sigma_0,
row + 3,
|| Ok(F::one()),
)?;
}
for index in 14..49 {
let row = get_word_row(index);
region.assign_fixed(
|| "s_decompose_2",
self.s_decompose_2,
row,
|| Ok(F::one()),
)?;
region.assign_fixed(
|| "s_lower_sigma_0_v2",
self.s_lower_sigma_0_v2,
row + 3,
|| Ok(F::one()),
)?;
region.assign_fixed(
|| "s_lower_sigma_1_v2",
self.s_lower_sigma_1_v2,
row + SIGMA_0_V2_ROWS + 3,
|| Ok(F::one()),
)?;
let new_word_idx = index + 2;
region.assign_fixed(
|| "s_word",
self.s_word,
get_word_row(new_word_idx - 16) + 1,
|| Ok(F::one()),
)?;
}
for index in 49..62 {
let row = get_word_row(index);
region.assign_fixed(
|| "s_decompose_3",
self.s_decompose_3,
row,
|| Ok(F::one()),
)?;
region.assign_fixed(
|| "s_lower_sigma_1",
self.s_lower_sigma_1,
row + 3,
|| Ok(F::one()),
)?;
let new_word_idx = index + 2;
region.assign_fixed(
|| "s_word",
self.s_word,
get_word_row(new_word_idx - 16) + 1,
|| Ok(F::one()),
)?;
}
for index in 0..64 {
let row = get_word_row(index);
region.assign_fixed(
|| "s_decompose_0",
self.s_decompose_0,
row,
|| Ok(F::one()),
)?;
}
// Assign W[0..16]
for (i, word) in input.iter().enumerate() {
let (var, halves) =
self.assign_word_and_halves(&mut region, word.value.unwrap(), i)?;
w.push(MessageWord {
var,
value: word.value,
});
w_halves.push(halves);
}
// Returns the output of sigma_0 on W_[1..14]
let lower_sigma_0_output = self.assign_subregion1(&mut region, &input[1..14])?;
// sigma_0_v2 and sigma_1_v2 on W_[14..49]
// Returns the output of sigma_0_v2 on W_[36..49], to be used in subregion3
let lower_sigma_0_v2_output = self.assign_subregion2(
&mut region,
lower_sigma_0_output,
&mut w,
&mut w_halves,
)?;
// sigma_1 v1 on W[49..62]
self.assign_subregion3(
&mut region,
lower_sigma_0_v2_output,
&mut w,
&mut w_halves,
)?;
Ok(())
},
)?;
Ok((w.try_into().unwrap(), w_halves.try_into().unwrap()))
}
/// Empty configuration without gates. Useful for fast testing
#[cfg(test)]
pub(super) fn empty_configure<F: FieldExt>(
meta: &mut ConstraintSystem<F>,
lookup: SpreadInputs,
message_schedule: Column<Advice>,
extras: [Column<Advice>; 6],
perm: Permutation,
) -> Self {
// Create fixed columns for the selectors we will require.
let s_word = meta.fixed_column();
let s_decompose_0 = meta.fixed_column();
let s_decompose_1 = meta.fixed_column();
let s_decompose_2 = meta.fixed_column();
let s_decompose_3 = meta.fixed_column();
let s_lower_sigma_0 = meta.fixed_column();
let s_lower_sigma_1 = meta.fixed_column();
let s_lower_sigma_0_v2 = meta.fixed_column();
let s_lower_sigma_1_v2 = meta.fixed_column();
MessageSchedule {
lookup,
message_schedule,
extras,
s_word,
s_decompose_0,
s_decompose_1,
s_decompose_2,
s_decompose_3,
s_lower_sigma_0,
s_lower_sigma_1,
s_lower_sigma_0_v2,
s_lower_sigma_1_v2,
perm,
}
}
}
#[cfg(test)]
mod tests {
use super::super::{
super::BLOCK_SIZE, BlockWord, Compression, SpreadTable, Table16Chip, Table16Config,
};
use super::{schedule_util::*, MessageSchedule};
use halo2::{
arithmetic::FieldExt,
circuit::{layouter, Layouter},
dev::MockProver,
pasta::Fp,
plonk::{Assignment, Circuit, ConstraintSystem, Error, Permutation},
};
#[test]
fn message_schedule() {
struct MyCircuit {}
impl<F: FieldExt> Circuit<F> for MyCircuit {
type Config = Table16Config;
fn configure(meta: &mut ConstraintSystem<F>) -> Self::Config {
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(),
];
// 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(),
],
);
let compression = Compression::empty_configure(
meta,
lookup_inputs.clone(),
message_schedule,
extras,
perm.clone(),
);
let message_schedule =
MessageSchedule::configure(meta, lookup_inputs, message_schedule, extras, perm);
Table16Config {
lookup_table,
message_schedule,
compression,
}
}
fn synthesize(
&self,
cs: &mut impl Assignment<F>,
config: Self::Config,
) -> Result<(), Error> {
let mut layouter = layouter::SingleChip::<Table16Chip<F>, _>::new(cs, config)?;
// Load table
let table = layouter.config().lookup_table.clone();
table.load(&mut layouter)?;
// Provide input
// Test vector: "abc"
let inputs: [BlockWord; BLOCK_SIZE] = get_msg_schedule_test_input();
// Run message_scheduler to get W_[0..64]
let message_schedule = layouter.config().message_schedule.clone();
let (w, _) = message_schedule.process(&mut layouter, inputs)?;
for (word, test_word) in w.iter().zip(MSG_SCHEDULE_TEST_OUTPUT.iter()) {
let word = word.value.unwrap();
assert_eq!(word, *test_word);
}
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,364 @@
use super::super::Gate;
use halo2::{arithmetic::FieldExt, plonk::Expression};
pub struct ScheduleGate<F: FieldExt>(pub Expression<F>);
impl<F: FieldExt> ScheduleGate<F> {
/// s_word for W_16 to W_63
#[allow(clippy::too_many_arguments)]
pub fn s_word(
s_word: Expression<F>,
sigma_0_lo: Expression<F>,
sigma_0_hi: Expression<F>,
sigma_1_lo: Expression<F>,
sigma_1_hi: Expression<F>,
w_minus_9_lo: Expression<F>,
w_minus_9_hi: Expression<F>,
w_minus_16_lo: Expression<F>,
w_minus_16_hi: Expression<F>,
word: Expression<F>,
carry: Expression<F>,
) -> Self {
let lo = sigma_0_lo + sigma_1_lo + w_minus_9_lo + w_minus_16_lo;
let hi = sigma_0_hi + sigma_1_hi + w_minus_9_hi + w_minus_16_hi;
let word_check = lo
+ hi * F::from_u64(1 << 16)
+ (carry.clone() * F::from_u64(1 << 32) * (-F::one()))
+ (word * (-F::one()));
let carry_check = Gate::range_check(carry, 0, 3);
ScheduleGate(s_word * (word_check + carry_check))
}
/// s_decompose_0 for all words
pub fn s_decompose_0(
s_decompose_0: Expression<F>,
lo: Expression<F>,
hi: Expression<F>,
word: Expression<F>,
) -> Self {
ScheduleGate(s_decompose_0 * (lo + hi * F::from_u64(1 << 16) + word * (-F::one())))
}
/// s_decompose_1 for W_1 to W_13
/// (3, 4, 11, 14)-bit chunks
#[allow(clippy::too_many_arguments)]
pub fn s_decompose_1(
s_decompose_1: Expression<F>,
a: Expression<F>,
b: Expression<F>,
c: Expression<F>,
tag_c: Expression<F>,
d: Expression<F>,
tag_d: Expression<F>,
word: Expression<F>,
) -> Self {
let decompose_check = a
+ b * F::from_u64(1 << 3)
+ c * F::from_u64(1 << 7)
+ d * F::from_u64(1 << 18)
+ word * (-F::one());
let range_check_tag_c = Gate::range_check(tag_c, 0, 2);
let range_check_tag_d = Gate::range_check(tag_d, 0, 4);
ScheduleGate(s_decompose_1 * (decompose_check + range_check_tag_c + range_check_tag_d))
}
/// s_decompose_2 for W_14 to W_48
/// (3, 4, 3, 7, 1, 1, 13)-bit chunks
#[allow(clippy::many_single_char_names)]
#[allow(clippy::too_many_arguments)]
pub fn s_decompose_2(
s_decompose_2: Expression<F>,
a: Expression<F>,
b: Expression<F>,
c: Expression<F>,
d: Expression<F>,
tag_d: Expression<F>,
e: Expression<F>,
f: Expression<F>,
g: Expression<F>,
tag_g: Expression<F>,
word: Expression<F>,
) -> Self {
let decompose_check = a
+ b * F::from_u64(1 << 3)
+ c * F::from_u64(1 << 7)
+ d * F::from_u64(1 << 10)
+ e * F::from_u64(1 << 17)
+ f * F::from_u64(1 << 18)
+ g * F::from_u64(1 << 19)
+ word * (-F::one());
let range_check_tag_d = Gate::range_check(tag_d, 0, 0);
let range_check_tag_g = Gate::range_check(tag_g, 0, 3);
ScheduleGate(s_decompose_2 * (decompose_check + range_check_tag_g + range_check_tag_d))
}
/// s_decompose_3 for W_49 to W_61
/// (10, 7, 2, 13)-bit chunks
#[allow(clippy::too_many_arguments)]
pub fn s_decompose_3(
s_decompose_3: Expression<F>,
a: Expression<F>,
tag_a: Expression<F>,
b: Expression<F>,
c: Expression<F>,
d: Expression<F>,
tag_d: Expression<F>,
word: Expression<F>,
) -> Self {
let decompose_check = a
+ b * F::from_u64(1 << 10)
+ c * F::from_u64(1 << 17)
+ d * F::from_u64(1 << 19)
+ word * (-F::one());
let range_check_tag_a = Gate::range_check(tag_a, 0, 1);
let range_check_tag_d = Gate::range_check(tag_d, 0, 3);
ScheduleGate(s_decompose_3 * (decompose_check + range_check_tag_a + range_check_tag_d))
}
/// b_lo + 2^2 * b_mid = b, on W_[1..49]
fn check_b(b: Expression<F>, b_lo: Expression<F>, b_hi: Expression<F>) -> Expression<F> {
let expected_b = b_lo + b_hi * F::from_u64(1 << 2);
expected_b + (b * -F::one())
}
/// b_lo + 2^2 * b_mid + 2^4 * b_hi = b, on W_[49..62]
fn check_b1(
b: Expression<F>,
b_lo: Expression<F>,
b_mid: Expression<F>,
b_hi: Expression<F>,
) -> Expression<F> {
let expected_b = b_lo + b_mid * F::from_u64(1 << 2) + b_hi * F::from_u64(1 << 4);
expected_b + (b * -F::one())
}
/// sigma_0 v1 on W_1 to W_13
/// (3, 4, 11, 14)-bit chunks
#[allow(clippy::too_many_arguments)]
pub fn s_lower_sigma_0(
s_lower_sigma_0: Expression<F>,
spread_r0_even: Expression<F>,
spread_r0_odd: Expression<F>,
spread_r1_even: Expression<F>,
spread_r1_odd: Expression<F>,
a: Expression<F>,
spread_a: Expression<F>,
b: Expression<F>,
b_lo: Expression<F>,
spread_b_lo: Expression<F>,
b_hi: Expression<F>,
spread_b_hi: Expression<F>,
spread_c: Expression<F>,
spread_d: Expression<F>,
) -> Self {
let check_spread_and_range =
Gate::two_bit_spread_and_range(b_lo.clone(), spread_b_lo.clone())
+ Gate::two_bit_spread_and_range(b_hi.clone(), spread_b_hi.clone())
+ Gate::three_bit_spread_and_range(a, spread_a.clone());
let check_b = Self::check_b(b, b_lo, b_hi);
let spread_witness = spread_r0_even
+ spread_r0_odd * F::from_u64(2)
+ (spread_r1_even + spread_r1_odd * F::from_u64(2)) * F::from_u64(1 << 32);
let xor_0 = spread_b_lo.clone()
+ spread_b_hi.clone() * F::from_u64(1 << 4)
+ spread_c.clone() * F::from_u64(1 << 8)
+ spread_d.clone() * F::from_u64(1 << 30);
let xor_1 = spread_c.clone()
+ spread_d.clone() * F::from_u64(1 << 22)
+ spread_a.clone() * F::from_u64(1 << 50)
+ spread_b_lo.clone() * F::from_u64(1 << 56)
+ spread_b_hi.clone() * F::from_u64(1 << 60);
let xor_2 = spread_d
+ spread_a * F::from_u64(1 << 28)
+ spread_b_lo * F::from_u64(1 << 34)
+ spread_b_hi * F::from_u64(1 << 38)
+ spread_c * F::from_u64(1 << 42);
let xor = xor_0 + xor_1 + xor_2;
ScheduleGate(
s_lower_sigma_0
* (check_spread_and_range + check_b + spread_witness + (xor * -F::one())),
)
}
/// sigma_1 v1 on W_49 to W_61
/// (10, 7, 2, 13)-bit chunks
#[allow(clippy::too_many_arguments)]
pub fn s_lower_sigma_1(
s_lower_sigma_1: Expression<F>,
spread_r0_even: Expression<F>,
spread_r0_odd: Expression<F>,
spread_r1_even: Expression<F>,
spread_r1_odd: Expression<F>,
spread_a: Expression<F>,
b: Expression<F>,
b_lo: Expression<F>,
spread_b_lo: Expression<F>,
b_mid: Expression<F>,
spread_b_mid: Expression<F>,
b_hi: Expression<F>,
spread_b_hi: Expression<F>,
c: Expression<F>,
spread_c: Expression<F>,
spread_d: Expression<F>,
) -> Self {
let check_spread_and_range =
Gate::two_bit_spread_and_range(b_lo.clone(), spread_b_lo.clone())
+ Gate::two_bit_spread_and_range(b_mid.clone(), spread_b_mid.clone())
+ Gate::two_bit_spread_and_range(c, spread_c.clone())
+ Gate::three_bit_spread_and_range(b_hi.clone(), spread_b_hi.clone());
let check_b1 = Self::check_b1(b, b_lo, b_mid, b_hi);
let spread_witness = spread_r0_even
+ spread_r0_odd * F::from_u64(2)
+ (spread_r1_even + spread_r1_odd * F::from_u64(2)) * F::from_u64(1 << 32);
let xor_0 = spread_b_lo.clone()
+ spread_b_mid.clone() * F::from_u64(1 << 4)
+ spread_b_hi.clone() * F::from_u64(1 << 8)
+ spread_c.clone() * F::from_u64(1 << 14)
+ spread_d.clone() * F::from_u64(1 << 18);
let xor_1 = spread_c.clone()
+ spread_d.clone() * F::from_u64(1 << 4)
+ spread_a.clone() * F::from_u64(1 << 30)
+ spread_b_lo.clone() * F::from_u64(1 << 50)
+ spread_b_mid.clone() * F::from_u64(1 << 54)
+ spread_b_hi.clone() * F::from_u64(1 << 58);
let xor_2 = spread_d
+ spread_a * F::from_u64(1 << 26)
+ spread_b_lo * F::from_u64(1 << 46)
+ spread_b_mid * F::from_u64(1 << 50)
+ spread_b_hi * F::from_u64(1 << 54)
+ spread_c * F::from_u64(1 << 60);
let xor = xor_0 + xor_1 + xor_2;
ScheduleGate(
s_lower_sigma_1
* (check_spread_and_range + check_b1 + spread_witness + (xor * -F::one())),
)
}
/// sigma_0 v2 on W_14 to W_48
/// (3, 4, 3, 7, 1, 1, 13)-bit chunks
#[allow(clippy::too_many_arguments)]
pub fn s_lower_sigma_0_v2(
s_lower_sigma_0_v2: Expression<F>,
spread_r0_even: Expression<F>,
spread_r0_odd: Expression<F>,
spread_r1_even: Expression<F>,
spread_r1_odd: Expression<F>,
a: Expression<F>,
spread_a: Expression<F>,
b: Expression<F>,
b_lo: Expression<F>,
spread_b_lo: Expression<F>,
b_hi: Expression<F>,
spread_b_hi: Expression<F>,
c: Expression<F>,
spread_c: Expression<F>,
spread_d: Expression<F>,
spread_e: Expression<F>,
spread_f: Expression<F>,
spread_g: Expression<F>,
) -> Self {
let check_spread_and_range =
Gate::two_bit_spread_and_range(b_lo.clone(), spread_b_lo.clone())
+ Gate::two_bit_spread_and_range(b_hi.clone(), spread_b_hi.clone())
+ Gate::three_bit_spread_and_range(a, spread_a.clone())
+ Gate::three_bit_spread_and_range(c, spread_c.clone());
let check_b = Self::check_b(b, b_lo, b_hi);
let spread_witness = spread_r0_even
+ spread_r0_odd * F::from_u64(2)
+ (spread_r1_even + spread_r1_odd * F::from_u64(2)) * F::from_u64(1 << 32);
let xor_0 = spread_b_lo.clone()
+ spread_b_hi.clone() * F::from_u64(1 << 4)
+ spread_c.clone() * F::from_u64(1 << 8)
+ spread_d.clone() * F::from_u64(1 << 14)
+ spread_e.clone() * F::from_u64(1 << 28)
+ spread_f.clone() * F::from_u64(1 << 30)
+ spread_g.clone() * F::from_u64(1 << 32);
let xor_1 = spread_c.clone()
+ spread_d.clone() * F::from_u64(1 << 6)
+ spread_e.clone() * F::from_u64(1 << 20)
+ spread_f.clone() * F::from_u64(1 << 22)
+ spread_g.clone() * F::from_u64(1 << 24)
+ spread_a.clone() * F::from_u64(1 << 50)
+ spread_b_lo.clone() * F::from_u64(1 << 56)
+ spread_b_hi.clone() * F::from_u64(1 << 60);
let xor_2 = spread_f
+ spread_g * F::from_u64(1 << 2)
+ spread_a * F::from_u64(1 << 28)
+ spread_b_lo * F::from_u64(1 << 34)
+ spread_b_hi * F::from_u64(1 << 38)
+ spread_c * F::from_u64(1 << 42)
+ spread_d * F::from_u64(1 << 48)
+ spread_e * F::from_u64(1 << 62);
let xor = xor_0 + xor_1 + xor_2;
ScheduleGate(
s_lower_sigma_0_v2
* (check_spread_and_range + check_b + spread_witness + (xor * -F::one())),
)
}
/// sigma_1 v2 on W_14 to W_48
/// (3, 4, 3, 7, 1, 1, 13)-bit chunks
#[allow(clippy::too_many_arguments)]
pub fn s_lower_sigma_1_v2(
s_lower_sigma_1_v2: Expression<F>,
spread_r0_even: Expression<F>,
spread_r0_odd: Expression<F>,
spread_r1_even: Expression<F>,
spread_r1_odd: Expression<F>,
a: Expression<F>,
spread_a: Expression<F>,
b: Expression<F>,
b_lo: Expression<F>,
spread_b_lo: Expression<F>,
b_hi: Expression<F>,
spread_b_hi: Expression<F>,
c: Expression<F>,
spread_c: Expression<F>,
spread_d: Expression<F>,
spread_e: Expression<F>,
spread_f: Expression<F>,
spread_g: Expression<F>,
) -> Self {
let check_spread_and_range =
Gate::two_bit_spread_and_range(b_lo.clone(), spread_b_lo.clone())
+ Gate::two_bit_spread_and_range(b_hi.clone(), spread_b_hi.clone())
+ Gate::three_bit_spread_and_range(a, spread_a.clone())
+ Gate::three_bit_spread_and_range(c, spread_c.clone());
let check_b = Self::check_b(b, b_lo, b_hi);
let spread_witness = spread_r0_even
+ spread_r0_odd * F::from_u64(2)
+ (spread_r1_even + spread_r1_odd * F::from_u64(2)) * F::from_u64(1 << 32);
let xor_0 = spread_d.clone()
+ spread_e.clone() * F::from_u64(1 << 14)
+ spread_f.clone() * F::from_u64(1 << 16)
+ spread_g.clone() * F::from_u64(1 << 18);
let xor_1 = spread_e.clone()
+ spread_f.clone() * F::from_u64(1 << 2)
+ spread_g.clone() * F::from_u64(1 << 4)
+ spread_a.clone() * F::from_u64(1 << 30)
+ spread_b_lo.clone() * F::from_u64(1 << 36)
+ spread_b_hi.clone() * F::from_u64(1 << 40)
+ spread_c.clone() * F::from_u64(1 << 44)
+ spread_d.clone() * F::from_u64(1 << 50);
let xor_2 = spread_g
+ spread_a * F::from_u64(1 << 26)
+ spread_b_lo * F::from_u64(1 << 32)
+ spread_b_hi * F::from_u64(1 << 36)
+ spread_c * F::from_u64(1 << 40)
+ spread_d * F::from_u64(1 << 46)
+ spread_e * F::from_u64(1 << 60)
+ spread_f * F::from_u64(1 << 62);
let xor = xor_0 + xor_1 + xor_2;
ScheduleGate(
s_lower_sigma_1_v2
* (check_spread_and_range + check_b + spread_witness + (xor * -F::one())),
)
}
}

View File

@ -0,0 +1,196 @@
use super::super::{CellValue16, Table16Chip};
use super::MessageSchedule;
use halo2::{
arithmetic::FieldExt,
circuit::{Cell, Region},
plonk::Error,
};
#[cfg(test)]
use super::super::{super::BLOCK_SIZE, BlockWord, ROUNDS};
// Rows needed for each gate
pub const DECOMPOSE_0_ROWS: usize = 2;
pub const DECOMPOSE_1_ROWS: usize = 2;
pub const DECOMPOSE_2_ROWS: usize = 3;
pub const DECOMPOSE_3_ROWS: usize = 2;
pub const SIGMA_0_V1_ROWS: usize = 4;
pub const SIGMA_0_V2_ROWS: usize = 4;
pub const SIGMA_1_V1_ROWS: usize = 4;
pub const SIGMA_1_V2_ROWS: usize = 4;
// Rows needed for each subregion
pub const SUBREGION_0_LEN: usize = 1; // W_0
pub const SUBREGION_0_ROWS: usize = SUBREGION_0_LEN * DECOMPOSE_0_ROWS;
pub const SUBREGION_1_WORD: usize = DECOMPOSE_1_ROWS + SIGMA_0_V1_ROWS;
pub const SUBREGION_1_LEN: usize = 13; // W_[1..14]
pub const SUBREGION_1_ROWS: usize = SUBREGION_1_LEN * SUBREGION_1_WORD;
pub const SUBREGION_2_WORD: usize = DECOMPOSE_2_ROWS + SIGMA_0_V2_ROWS + SIGMA_1_V2_ROWS;
pub const SUBREGION_2_LEN: usize = 35; // W_[14..49]
pub const SUBREGION_2_ROWS: usize = SUBREGION_2_LEN * SUBREGION_2_WORD;
pub const SUBREGION_3_WORD: usize = DECOMPOSE_3_ROWS + SIGMA_1_V1_ROWS;
pub const SUBREGION_3_LEN: usize = 13; // W[49..62]
pub const SUBREGION_3_ROWS: usize = SUBREGION_3_LEN * SUBREGION_3_WORD;
// pub const SUBREGION_4_LEN: usize = 2; // W_[62..64]
// pub const SUBREGION_4_ROWS: usize = SUBREGION_4_LEN * DECOMPOSE_0_ROWS;
/// Returns row number of a word
pub fn get_word_row(word_idx: usize) -> usize {
assert!(word_idx <= 63);
if word_idx == 0 {
0
} else if (1..=13).contains(&word_idx) {
SUBREGION_0_ROWS + SUBREGION_1_WORD * (word_idx - 1) as usize
} else if (14..=48).contains(&word_idx) {
SUBREGION_0_ROWS + SUBREGION_1_ROWS + SUBREGION_2_WORD * (word_idx - 14) + 1
} else if (49..=61).contains(&word_idx) {
SUBREGION_0_ROWS
+ SUBREGION_1_ROWS
+ SUBREGION_2_ROWS
+ SUBREGION_3_WORD * (word_idx - 49) as usize
} else {
SUBREGION_0_ROWS
+ SUBREGION_1_ROWS
+ SUBREGION_2_ROWS
+ SUBREGION_3_ROWS
+ DECOMPOSE_0_ROWS * (word_idx - 62) as usize
}
}
/// Test vector: "abc"
#[cfg(test)]
pub fn get_msg_schedule_test_input() -> [BlockWord; BLOCK_SIZE] {
[
BlockWord::new(0b01100001011000100110001110000000),
BlockWord::new(0b00000000000000000000000000000000),
BlockWord::new(0b00000000000000000000000000000000),
BlockWord::new(0b00000000000000000000000000000000),
BlockWord::new(0b00000000000000000000000000000000),
BlockWord::new(0b00000000000000000000000000000000),
BlockWord::new(0b00000000000000000000000000000000),
BlockWord::new(0b00000000000000000000000000000000),
BlockWord::new(0b00000000000000000000000000000000),
BlockWord::new(0b00000000000000000000000000000000),
BlockWord::new(0b00000000000000000000000000000000),
BlockWord::new(0b00000000000000000000000000000000),
BlockWord::new(0b00000000000000000000000000000000),
BlockWord::new(0b00000000000000000000000000000000),
BlockWord::new(0b00000000000000000000000000000000),
BlockWord::new(0b00000000000000000000000000011000),
]
}
#[cfg(test)]
pub const MSG_SCHEDULE_TEST_OUTPUT: [u32; ROUNDS] = [
0b01100001011000100110001110000000,
0b00000000000000000000000000000000,
0b00000000000000000000000000000000,
0b00000000000000000000000000000000,
0b00000000000000000000000000000000,
0b00000000000000000000000000000000,
0b00000000000000000000000000000000,
0b00000000000000000000000000000000,
0b00000000000000000000000000000000,
0b00000000000000000000000000000000,
0b00000000000000000000000000000000,
0b00000000000000000000000000000000,
0b00000000000000000000000000000000,
0b00000000000000000000000000000000,
0b00000000000000000000000000000000,
0b00000000000000000000000000011000,
0b01100001011000100110001110000000,
0b00000000000011110000000000000000,
0b01111101101010000110010000000101,
0b01100000000000000000001111000110,
0b00111110100111010111101101111000,
0b00000001100000111111110000000000,
0b00010010110111001011111111011011,
0b11100010111000101100001110001110,
0b11001000001000010101110000011010,
0b10110111001101100111100110100010,
0b11100101101111000011100100001001,
0b00110010011001100011110001011011,
0b10011101001000001001110101100111,
0b11101100100001110010011011001011,
0b01110000001000010011100010100100,
0b11010011101101111001011100111011,
0b10010011111101011001100101111111,
0b00111011011010001011101001110011,
0b10101111111101001111111111000001,
0b11110001000010100101110001100010,
0b00001010100010110011100110010110,
0b01110010101011111000001100001010,
0b10010100000010011110001100111110,
0b00100100011001000001010100100010,
0b10011111010001111011111110010100,
0b11110000101001100100111101011010,
0b00111110001001000110101001111001,
0b00100111001100110011101110100011,
0b00001100010001110110001111110010,
0b10000100000010101011111100100111,
0b01111010001010010000110101011101,
0b00000110010111000100001111011010,
0b11111011001111101000100111001011,
0b11001100011101100001011111011011,
0b10111001111001100110110000110100,
0b10101001100110010011011001100111,
0b10000100101110101101111011011101,
0b11000010000101000110001010111100,
0b00010100100001110100011100101100,
0b10110010000011110111101010011001,
0b11101111010101111011100111001101,
0b11101011111001101011001000111000,
0b10011111111000110000100101011110,
0b01111000101111001000110101001011,
0b10100100001111111100111100010101,
0b01100110100010110010111111111000,
0b11101110101010111010001011001100,
0b00010010101100011110110111101011,
];
impl MessageSchedule {
// Assign a word and its hi and lo halves
pub fn assign_word_and_halves<F: FieldExt>(
&self,
region: &mut Region<'_, Table16Chip<F>>,
word: u32,
word_idx: usize,
) -> Result<(Cell, (CellValue16, CellValue16)), Error> {
// Rename these here for ease of matching the gates to the specification.
let a_3 = self.extras[0];
let a_4 = self.extras[1];
let row = get_word_row(word_idx);
let var = region.assign_advice(
|| format!("W_{}", word_idx),
self.message_schedule,
row,
|| Ok(F::from_u64(word as u64)),
)?;
let w_lo = word as u16;
let w_hi = (word >> 16) as u16;
let w_lo_cell = region.assign_advice(
|| format!("W_{}_lo", word_idx),
a_3,
row,
|| Ok(F::from_u64(w_lo as u64)),
)?;
let w_hi_cell = region.assign_advice(
|| format!("W_{}_hi", word_idx),
a_4,
row,
|| Ok(F::from_u64(w_hi as u64)),
)?;
Ok((
var,
(
CellValue16::new(w_lo_cell, w_lo),
CellValue16::new(w_hi_cell, w_hi),
),
))
}
}

View File

@ -0,0 +1,183 @@
use super::super::{
util::*, BlockWord, CellValue16, CellValue32, SpreadVar, SpreadWord, Table16Assignment,
Table16Chip,
};
use super::{schedule_util::*, MessageSchedule};
use halo2::{arithmetic::FieldExt, circuit::Region, plonk::Error};
// A word in subregion 1
// (3, 4, 11, 14)-bit chunks
#[derive(Debug)]
pub struct Subregion1Word {
index: usize,
a: CellValue32,
b: CellValue32,
c: CellValue32,
d: CellValue32,
spread_c: CellValue32,
spread_d: CellValue32,
}
impl MessageSchedule {
pub fn assign_subregion1<F: FieldExt>(
&self,
region: &mut Region<'_, Table16Chip<F>>,
input: &[BlockWord],
) -> Result<Vec<(CellValue16, CellValue16)>, Error> {
assert_eq!(input.len(), SUBREGION_1_LEN);
Ok(input
.iter()
.enumerate()
.map(|(idx, word)| {
// s_decompose_1 on W_[1..14]
let subregion1_word = self
.decompose_subregion1_word(region, word.value.unwrap(), idx + 1)
.unwrap();
// lower_sigma_0 on W_[1..14]
self.lower_sigma_0(region, subregion1_word).unwrap()
})
.collect::<Vec<_>>())
}
fn decompose_subregion1_word<F: FieldExt>(
&self,
region: &mut Region<'_, Table16Chip<F>>,
word: u32,
index: usize,
) -> Result<Subregion1Word, Error> {
let row = get_word_row(index);
// Rename these here for ease of matching the gates to the specification.
let a_3 = self.extras[0];
let a_4 = self.extras[1];
let pieces = chop_u32(word, &[3, 4, 11, 14]);
// Assign `a` (3-bit piece)
let a = region.assign_advice(
|| "word_a",
a_3,
row + 1,
|| Ok(F::from_u64(pieces[0] as u64)),
)?;
// Assign `b` (4-bit piece)
let b = region.assign_advice(
|| "word_b",
a_4,
row + 1,
|| Ok(F::from_u64(pieces[1] as u64)),
)?;
// Assign `c` (11-bit piece) lookup
let spread_c = SpreadWord::new(pieces[2] as u16);
let spread_c = SpreadVar::with_lookup(region, &self.lookup, row + 1, spread_c)?;
// Assign `d` (14-bit piece) lookup
let spread_d = SpreadWord::new(pieces[3] as u16);
let spread_d = SpreadVar::with_lookup(region, &self.lookup, row, spread_d)?;
Ok(Subregion1Word {
index,
a: CellValue32::new(a, pieces[0]),
b: CellValue32::new(b, pieces[1]),
c: CellValue32::new(spread_c.dense.var, spread_c.dense.value.unwrap().into()),
d: CellValue32::new(spread_d.dense.var, spread_d.dense.value.unwrap().into()),
spread_c: CellValue32::new(spread_c.spread.var, spread_c.spread.value.unwrap()),
spread_d: CellValue32::new(spread_d.spread.var, spread_d.spread.value.unwrap()),
})
}
// sigma_0 v1 on a word in W_1 to W_13
// (3, 4, 11, 14)-bit chunks
fn lower_sigma_0<F: FieldExt>(
&self,
region: &mut Region<'_, Table16Chip<F>>,
word: Subregion1Word,
) -> Result<(CellValue16, CellValue16), Error> {
let a_3 = self.extras[0];
let a_4 = self.extras[1];
let a_5 = self.message_schedule;
let a_6 = self.extras[2];
let row = get_word_row(word.index) + 3;
// Assign `a` and copy constraint
self.assign_and_constrain(region, || "a", a_5, row + 1, &word.a, &self.perm)?;
// Witness `spread_a`
let spread_a = interleave_u16_with_zeros(word.a.value.unwrap() as u16);
region.assign_advice(
|| "spread_a",
a_6,
row + 1,
|| Ok(F::from_u64(spread_a as u64)),
)?;
// Split `b` (2-bit chunk) into `b_hi` and `b_lo`
let b = word.b.value.unwrap();
let (b_lo, b_hi) = bisect_four_bit(b);
let spread_b_lo = interleave_u16_with_zeros(b_lo as u16);
let spread_b_hi = interleave_u16_with_zeros(b_hi as u16);
// Assign `b_hi`, `spread_b_hi`, `b_lo`, `spread_b_lo`
region.assign_advice(|| "b_lo", a_3, row - 1, || Ok(F::from_u64(b_lo as u64)))?;
region.assign_advice(
|| "spread_b_lo",
a_4,
row - 1,
|| Ok(F::from_u64(spread_b_lo as u64)),
)?;
region.assign_advice(|| "b_hi", a_5, row - 1, || Ok(F::from_u64(b_hi as u64)))?;
region.assign_advice(
|| "spread_b_hi",
a_6,
row - 1,
|| Ok(F::from_u64(spread_b_hi as u64)),
)?;
// Assign `b` and copy constraint
self.assign_and_constrain(region, || "b", a_6, row, &word.b, &self.perm)?;
// Assign `spread_c` and copy constraint
self.assign_and_constrain(region, || "spread_c", a_4, row, &word.spread_c, &self.perm)?;
// Assign `spread_d` and copy constraint
self.assign_and_constrain(region, || "spread_d", a_5, row, &word.spread_d, &self.perm)?;
// Calculate R_0^{even}, R_0^{odd}, R_1^{even}, R_1^{odd}
let spread_a = spread_a as u64;
let spread_b_lo = spread_b_lo as u64;
let spread_b_hi = spread_b_hi as u64;
let spread_c = word.spread_c.value.unwrap() as u64;
let spread_d = word.spread_d.value.unwrap() as u64;
let xor_0: u64 =
spread_b_lo + (1 << 4) * spread_b_hi + (1 << 8) * spread_c + (1 << 30) * spread_d;
let xor_1: u64 = spread_c
+ (1 << 22) * spread_d
+ (1 << 50) * spread_a
+ (1 << 56) * spread_b_lo
+ (1 << 60) * spread_b_hi;
let xor_2: u64 = spread_d
+ (1 << 28) * spread_a
+ (1 << 34) * spread_b_lo
+ (1 << 38) * spread_b_hi
+ (1 << 42) * spread_c;
let r = xor_0 + xor_1 + xor_2;
let r_pieces = chop_u64(r, &[32, 32]); // r_0, r_1
let (r_0_even, r_0_odd) = get_even_and_odd_bits_u32(r_pieces[0] as u32);
let (r_1_even, r_1_odd) = get_even_and_odd_bits_u32(r_pieces[1] as u32);
self.assign_sigma_outputs(
region,
&self.lookup,
a_3,
&self.perm,
row,
r_0_even,
r_0_odd,
r_1_even,
r_1_odd,
)
}
}

View File

@ -0,0 +1,428 @@
use super::super::{
util::*, CellValue16, CellValue32, SpreadVar, SpreadWord, Table16Assignment, Table16Chip,
};
use super::{schedule_util::*, MessageSchedule, MessageWord};
use halo2::{arithmetic::FieldExt, circuit::Region, plonk::Error};
// A word in subregion 2
// (3, 4, 3, 7, 1, 1, 13)-bit chunks
#[derive(Clone, Debug)]
pub struct Subregion2Word {
index: usize,
a: CellValue32,
b: CellValue32,
c: CellValue32,
d: CellValue32,
e: CellValue32,
f: CellValue32,
g: CellValue32,
spread_d: CellValue32,
spread_g: CellValue32,
}
impl MessageSchedule {
// W_[14..49]
pub fn assign_subregion2<F: FieldExt>(
&self,
region: &mut Region<'_, Table16Chip<F>>,
lower_sigma_0_output: Vec<(CellValue16, CellValue16)>,
w: &mut Vec<MessageWord>,
w_halves: &mut Vec<(CellValue16, CellValue16)>,
) -> Result<Vec<(CellValue16, CellValue16)>, Error> {
let a_5 = self.message_schedule;
let a_6 = self.extras[2];
let a_7 = self.extras[3];
let a_8 = self.extras[4];
let a_9 = self.extras[5];
let mut lower_sigma_0_v2_results =
Vec::<(CellValue16, CellValue16)>::with_capacity(SUBREGION_2_LEN);
let mut lower_sigma_1_v2_results =
Vec::<(CellValue16, CellValue16)>::with_capacity(SUBREGION_2_LEN);
// Closure to compose new word
// W_i = sigma_1(W_{i - 2}) + W_{i - 7} + sigma_0(W_{i - 15}) + W_{i - 16}
// e.g. W_16 = sigma_1(W_14) + W_9 + sigma_0(W_1) + W_0
// sigma_0(W_[1..14]) will be used to get the new W_[16..29]
// sigma_0_v2(W_[14..36]) will be used to get the new W_[29..51]
// sigma_1_v2(W_[14..49]) will be used to get the W_[16..51]
// The lowest-index words involved will be W_[0..13]
let mut new_word = |idx: usize,
sigma_0_output: (CellValue16, CellValue16)|
-> Result<Vec<(CellValue16, CellValue16)>, Error> {
// Decompose word into (3, 4, 3, 7, 1, 1, 13)-bit chunks
let subregion2_word =
self.decompose_subregion2_word(region, w[idx].value.unwrap(), idx)?;
// sigma_0 v2 and sigma_1 v2 on subregion2_word
lower_sigma_0_v2_results.push(self.lower_sigma_0_v2(region, subregion2_word.clone())?);
lower_sigma_1_v2_results.push(self.lower_sigma_1_v2(region, subregion2_word)?);
let new_word_idx = idx + 2;
// Copy sigma_0(W_{i - 15}) output from Subregion 1
self.assign_and_constrain(
region,
|| format!("sigma_0(W_{})_lo", new_word_idx - 15),
a_6,
get_word_row(new_word_idx - 16),
&sigma_0_output.0.into(),
&self.perm,
)?;
self.assign_and_constrain(
region,
|| format!("sigma_0(W_{})_hi", new_word_idx - 15),
a_6,
get_word_row(new_word_idx - 16) + 1,
&sigma_0_output.1.into(),
&self.perm,
)?;
// Copy sigma_1(W_{i - 2})
self.assign_and_constrain(
region,
|| format!("sigma_1(W_{})_lo", new_word_idx - 2),
a_7,
get_word_row(new_word_idx - 16),
&lower_sigma_1_v2_results[new_word_idx - 16].0.into(),
&self.perm,
)?;
self.assign_and_constrain(
region,
|| format!("sigma_1(W_{})_hi", new_word_idx - 2),
a_7,
get_word_row(new_word_idx - 16) + 1,
&lower_sigma_1_v2_results[new_word_idx - 16].1.into(),
&self.perm,
)?;
// Copy W_{i - 7}
self.assign_and_constrain(
region,
|| format!("W_{}_lo", new_word_idx - 7),
a_8,
get_word_row(new_word_idx - 16),
&w_halves[new_word_idx - 7].0.into(),
&self.perm,
)?;
self.assign_and_constrain(
region,
|| format!("W_{}_hi", new_word_idx - 7),
a_8,
get_word_row(new_word_idx - 16) + 1,
&w_halves[new_word_idx - 7].1.into(),
&self.perm,
)?;
// Calculate W_i, carry_i
let word_lo: u32 = lower_sigma_1_v2_results[new_word_idx - 16].0.value.unwrap() as u32
+ w_halves[new_word_idx - 7].0.value.unwrap() as u32
+ sigma_0_output.0.value.unwrap() as u32
+ w_halves[new_word_idx - 16].0.value.unwrap() as u32;
let word_hi: u32 = lower_sigma_1_v2_results[new_word_idx - 16].1.value.unwrap() as u32
+ w_halves[new_word_idx - 7].1.value.unwrap() as u32
+ sigma_0_output.1.value.unwrap() as u32
+ w_halves[new_word_idx - 16].1.value.unwrap() as u32;
let word: u64 = word_lo as u64 + (1 << 16) * (word_hi as u64);
let carry = word >> 32;
let word = word as u32;
// Assign W_i, carry_i
region.assign_advice(
|| format!("W_{}", new_word_idx),
a_5,
get_word_row(new_word_idx - 16) + 1,
|| Ok(F::from_u64(word as u64)),
)?;
region.assign_advice(
|| format!("carry_{}", new_word_idx),
a_9,
get_word_row(new_word_idx - 16) + 1,
|| Ok(F::from_u64(carry as u64)),
)?;
let (var, halves) = self.assign_word_and_halves(region, word, new_word_idx)?;
w.push(MessageWord {
var,
value: Some(word),
});
w_halves.push(halves);
Ok(lower_sigma_0_v2_results.clone())
};
let mut tmp_lower_sigma_0_v2_results: Vec<(CellValue16, CellValue16)> =
Vec::with_capacity(SUBREGION_2_LEN);
// Use up all the output from Subregion 1 lower_sigma_0
for i in 14..27 {
tmp_lower_sigma_0_v2_results = new_word(i, lower_sigma_0_output[i - 14])?;
}
for i in 27..49 {
tmp_lower_sigma_0_v2_results =
new_word(i, tmp_lower_sigma_0_v2_results[i + 2 - 15 - 14])?;
}
// Return lower_sigma_0_v2 output for W_[36..49]
Ok(lower_sigma_0_v2_results.split_off(36 - 14))
}
fn decompose_subregion2_word<F: FieldExt>(
&self,
region: &mut Region<'_, Table16Chip<F>>,
word: u32,
index: usize,
) -> Result<Subregion2Word, Error> {
let row = get_word_row(index);
// Rename these here for ease of matching the gates to the specification.
let a_3 = self.extras[0];
let a_4 = self.extras[1];
let pieces = chop_u32(word, &[3, 4, 3, 7, 1, 1, 13]);
// Assign `a` (3-bit piece)
let a = region.assign_advice(|| "a", a_3, row - 1, || Ok(F::from_u64(pieces[0] as u64)))?;
// Assign `b` (4-bit piece) lookup
let spread_b = SpreadWord::new(pieces[1] as u16);
let spread_b = SpreadVar::with_lookup(region, &self.lookup, row + 1, spread_b)?;
// Assign `c` (3-bit piece)
let c = region.assign_advice(|| "c", a_4, row - 1, || Ok(F::from_u64(pieces[2] as u64)))?;
// Assign `d` (7-bit piece) lookup
let spread_d = SpreadWord::new(pieces[3] as u16);
let spread_d = SpreadVar::with_lookup(region, &self.lookup, row, spread_d)?;
// Assign `e` (1-bit piece)
let e = region.assign_advice(|| "e", a_3, row + 1, || Ok(F::from_u64(pieces[4] as u64)))?;
// Assign `f` (1-bit piece)
let f = region.assign_advice(|| "f", a_4, row + 1, || Ok(F::from_u64(pieces[5] as u64)))?;
// Assign `g` (13-bit piece) lookup
let spread_g = SpreadWord::new(pieces[6] as u16);
let spread_g = SpreadVar::with_lookup(region, &self.lookup, row - 1, spread_g)?;
Ok(Subregion2Word {
index,
a: CellValue32::new(a, pieces[0]),
b: CellValue32::new(spread_b.dense.var, spread_b.dense.value.unwrap().into()),
c: CellValue32::new(c, pieces[2]),
d: CellValue32::new(spread_d.dense.var, spread_d.dense.value.unwrap().into()),
e: CellValue32::new(e, pieces[4]),
f: CellValue32::new(f, pieces[5]),
g: CellValue32::new(spread_g.dense.var, spread_g.dense.value.unwrap().into()),
spread_d: CellValue32::new(spread_d.spread.var, spread_d.spread.value.unwrap()),
spread_g: CellValue32::new(spread_g.spread.var, spread_g.spread.value.unwrap()),
})
}
#[allow(clippy::type_complexity)]
fn assign_lower_sigma_v2_pieces<F: FieldExt>(
&self,
region: &mut Region<'_, Table16Chip<F>>,
row: usize,
subregion2_word: Subregion2Word,
) -> Result<(u64, u64, u64, u64, u64, u64, u64, u64), Error> {
let a_3 = self.extras[0];
let a_4 = self.extras[1];
let a_5 = self.message_schedule;
let a_6 = self.extras[2];
let a_7 = self.extras[3];
// Assign `a` and copy constraint
self.assign_and_constrain(region, || "a", a_3, row + 1, &subregion2_word.a, &self.perm)?;
// Witness `spread_a`
let spread_a = interleave_u16_with_zeros(subregion2_word.a.value.unwrap() as u16);
region.assign_advice(
|| "spread_a",
a_4,
row + 1,
|| Ok(F::from_u64(spread_a as u64)),
)?;
// Split `b` (2-bit chunk) into `b_hi` and `b_lo`
let b = subregion2_word.b.value.unwrap();
let (b_lo, b_hi) = bisect_four_bit(b);
let spread_b_lo = interleave_u16_with_zeros(b_lo as u16);
let spread_b_hi = interleave_u16_with_zeros(b_hi as u16);
// Assign `b_hi`, `spread_b_hi`, `b_lo`, `spread_b_lo`
region.assign_advice(|| "b_lo", a_3, row - 1, || Ok(F::from_u64(b_lo as u64)))?;
region.assign_advice(
|| "spread_b_lo",
a_4,
row - 1,
|| Ok(F::from_u64(spread_b_lo as u64)),
)?;
region.assign_advice(|| "b_hi", a_5, row - 1, || Ok(F::from_u64(b_hi as u64)))?;
region.assign_advice(
|| "spread_b_hi",
a_6,
row - 1,
|| Ok(F::from_u64(spread_b_hi as u64)),
)?;
// Assign `b` and copy constraint
self.assign_and_constrain(region, || "b", a_6, row, &subregion2_word.b, &self.perm)?;
// Assign `c` and copy constraint
self.assign_and_constrain(region, || "c", a_5, row + 1, &subregion2_word.c, &self.perm)?;
// Witness `spread_c`
let spread_c = interleave_u16_with_zeros(subregion2_word.c.value.unwrap() as u16);
region.assign_advice(
|| "spread_c",
a_6,
row + 1,
|| Ok(F::from_u64(spread_c as u64)),
)?;
// Assign `spread_d` and copy constraint
self.assign_and_constrain(
region,
|| "spread_d",
a_4,
row,
&subregion2_word.spread_d,
&self.perm,
)?;
// Assign `e` and copy constraint
self.assign_and_constrain(region, || "e", a_7, row, &subregion2_word.e, &self.perm)?;
// Assign `f` and copy constraint
self.assign_and_constrain(region, || "f", a_7, row + 1, &subregion2_word.f, &self.perm)?;
// Assign `spread_g` and copy constraint
self.assign_and_constrain(
region,
|| "spread_g",
a_5,
row,
&subregion2_word.spread_g,
&self.perm,
)?;
Ok((
spread_a as u64,
spread_b_lo as u64,
spread_b_hi as u64,
spread_c as u64,
subregion2_word.spread_d.value.unwrap() as u64,
subregion2_word.e.value.unwrap() as u64,
subregion2_word.f.value.unwrap() as u64,
subregion2_word.spread_g.value.unwrap() as u64,
))
}
fn lower_sigma_0_v2<F: FieldExt>(
&self,
region: &mut Region<'_, Table16Chip<F>>,
subregion2_word: Subregion2Word,
) -> Result<(CellValue16, CellValue16), Error> {
let a_3 = self.extras[0];
let row = get_word_row(subregion2_word.index) + 3;
// Get spread pieces
let (spread_a, spread_b_lo, spread_b_hi, spread_c, spread_d, e, f, spread_g) =
self.assign_lower_sigma_v2_pieces(region, row, subregion2_word)?;
// Calculate R_0^{even}, R_0^{odd}, R_1^{even}, R_1^{odd}
let xor_0 = spread_b_lo
+ (1 << 4) * spread_b_hi
+ (1 << 8) * spread_c
+ (1 << 14) * spread_d
+ (1 << 28) * e
+ (1 << 30) * f
+ (1 << 32) * spread_g;
let xor_1 = spread_c
+ (1 << 6) * spread_d
+ (1 << 20) * e
+ (1 << 22) * f
+ (1 << 24) * spread_g
+ (1 << 50) * spread_a
+ (1 << 56) * spread_b_lo
+ (1 << 60) * spread_b_hi;
let xor_2 = f
+ (1 << 2) * spread_g
+ (1 << 28) * spread_a
+ (1 << 34) * spread_b_lo
+ (1 << 38) * spread_b_hi
+ (1 << 42) * spread_c
+ (1 << 48) * spread_d
+ (1 << 62) * e;
let r = xor_0 + xor_1 + xor_2;
let r_pieces = chop_u64(r, &[32, 32]); // r_0, r_1
let (r_0_even, r_0_odd) = get_even_and_odd_bits_u32(r_pieces[0] as u32);
let (r_1_even, r_1_odd) = get_even_and_odd_bits_u32(r_pieces[1] as u32);
self.assign_sigma_outputs(
region,
&self.lookup,
a_3,
&self.perm,
row,
r_0_even,
r_0_odd,
r_1_even,
r_1_odd,
)
}
fn lower_sigma_1_v2<F: FieldExt>(
&self,
region: &mut Region<'_, Table16Chip<F>>,
subregion2_word: Subregion2Word,
) -> Result<(CellValue16, CellValue16), Error> {
let a_3 = self.extras[0];
let row = get_word_row(subregion2_word.index) + SIGMA_0_V2_ROWS + 3;
let (spread_a, spread_b_lo, spread_b_hi, spread_c, spread_d, e, f, spread_g) =
self.assign_lower_sigma_v2_pieces(region, row, subregion2_word)?;
// (3, 4, 3, 7, 1, 1, 13)
// Calculate R_0^{even}, R_0^{odd}, R_1^{even}, R_1^{odd}
let xor_0 = spread_d + (1 << 14) * e + (1 << 16) * f + (1 << 18) * spread_g;
let xor_1 = e
+ (1 << 2) * f
+ (1 << 4) * spread_g
+ (1 << 30) * spread_a
+ (1 << 36) * spread_b_lo
+ (1 << 40) * spread_b_hi
+ (1 << 44) * spread_c
+ (1 << 50) * spread_d;
let xor_2 = spread_g
+ (1 << 26) * spread_a
+ (1 << 32) * spread_b_lo
+ (1 << 36) * spread_b_hi
+ (1 << 40) * spread_c
+ (1 << 46) * spread_d
+ (1 << 60) * e
+ (1 << 62) * f;
let r = xor_0 + xor_1 + xor_2;
let r_pieces = chop_u64(r, &[32, 32]); // r_0, r_1
let (r_0_even, r_0_odd) = get_even_and_odd_bits_u32(r_pieces[0] as u32);
let (r_1_even, r_1_odd) = get_even_and_odd_bits_u32(r_pieces[1] as u32);
self.assign_sigma_outputs(
region,
&self.lookup,
a_3,
&self.perm,
row,
r_0_even,
r_0_odd,
r_1_even,
r_1_odd,
)
}
}

View File

@ -0,0 +1,296 @@
use super::super::{
util::*, CellValue16, CellValue32, SpreadVar, SpreadWord, Table16Assignment, Table16Chip,
};
use super::{schedule_util::*, MessageSchedule, MessageWord};
use halo2::{arithmetic::FieldExt, circuit::Region, plonk::Error};
// A word in subregion 3
// (10, 7, 2, 13)-bit chunks
pub struct Subregion3Word {
index: usize,
#[allow(dead_code)]
a: CellValue32,
b: CellValue32,
c: CellValue32,
#[allow(dead_code)]
d: CellValue32,
spread_a: CellValue32,
spread_d: CellValue32,
}
impl MessageSchedule {
// W_[49..62]
pub fn assign_subregion3<F: FieldExt>(
&self,
region: &mut Region<'_, Table16Chip<F>>,
lower_sigma_0_v2_output: Vec<(CellValue16, CellValue16)>,
w: &mut Vec<MessageWord>,
w_halves: &mut Vec<(CellValue16, CellValue16)>,
) -> Result<(), Error> {
let a_5 = self.message_schedule;
let a_6 = self.extras[2];
let a_7 = self.extras[3];
let a_8 = self.extras[4];
let a_9 = self.extras[5];
// Closure to compose new word
// W_i = sigma_1(W_{i - 2}) + W_{i - 7} + sigma_0(W_{i - 15}) + W_{i - 16}
// e.g. W_51 = sigma_1(W_49) + W_44 + sigma_0(W_36) + W_35
// sigma_0_v2(W_[36..49]) will be used to get the new W_[51..64]
// sigma_1(W_[49..62]) will also be used to get the W_[51..64]
// The lowest-index words involved will be W_[35..58]
let mut new_word = |idx: usize| -> Result<(), Error> {
// Decompose word into (10, 7, 2, 13)-bit chunks
let subregion3_word =
self.decompose_subregion3_word(region, w[idx].value.unwrap(), idx)?;
// sigma_1 on subregion3_word
let (r_0_even, r_1_even) = self.lower_sigma_1(region, subregion3_word)?;
let new_word_idx = idx + 2;
// Copy sigma_0_v2(W_{i - 15}) output from Subregion 2
self.assign_and_constrain(
region,
|| format!("sigma_0(W_{})_lo", new_word_idx - 15),
a_6,
get_word_row(new_word_idx - 16),
&lower_sigma_0_v2_output[idx - 49].0.into(),
&self.perm,
)?;
self.assign_and_constrain(
region,
|| format!("sigma_0(W_{})_hi", new_word_idx - 15),
a_6,
get_word_row(new_word_idx - 16) + 1,
&lower_sigma_0_v2_output[idx - 49].1.into(),
&self.perm,
)?;
// Copy sigma_1(W_{i - 2})
self.assign_and_constrain(
region,
|| format!("sigma_1(W_{})_lo", new_word_idx - 2),
a_7,
get_word_row(new_word_idx - 16),
&r_0_even.into(),
&self.perm,
)?;
self.assign_and_constrain(
region,
|| format!("sigma_1(W_{})_hi", new_word_idx - 2),
a_7,
get_word_row(new_word_idx - 16) + 1,
&r_1_even.into(),
&self.perm,
)?;
// Copy W_{i - 7}
self.assign_and_constrain(
region,
|| format!("W_{}_lo", new_word_idx - 7),
a_8,
get_word_row(new_word_idx - 16),
&w_halves[new_word_idx - 7].0.into(),
&self.perm,
)?;
self.assign_and_constrain(
region,
|| format!("W_{}_hi", new_word_idx - 7),
a_8,
get_word_row(new_word_idx - 16) + 1,
&w_halves[new_word_idx - 7].1.into(),
&self.perm,
)?;
// Calculate W_i, carry_i
let word_lo: u32 = r_0_even.value.unwrap() as u32
+ w_halves[new_word_idx - 7].0.value.unwrap() as u32
+ lower_sigma_0_v2_output[idx - 49].0.value.unwrap() as u32
+ w_halves[new_word_idx - 16].0.value.unwrap() as u32;
let word_hi: u32 = r_1_even.value.unwrap() as u32
+ w_halves[new_word_idx - 7].1.value.unwrap() as u32
+ lower_sigma_0_v2_output[idx - 49].1.value.unwrap() as u32
+ w_halves[new_word_idx - 16].1.value.unwrap() as u32;
let word: u64 = word_lo as u64 + (1 << 16) * (word_hi as u64);
let carry = word >> 32;
let word = word as u32;
// Assign W_i, carry_i
region.assign_advice(
|| format!("W_{}", new_word_idx),
a_5,
get_word_row(new_word_idx - 16) + 1,
|| Ok(F::from_u64(word as u64)),
)?;
region.assign_advice(
|| format!("carry_{}", new_word_idx),
a_9,
get_word_row(new_word_idx - 16) + 1,
|| Ok(F::from_u64(carry as u64)),
)?;
let (var, halves) = self.assign_word_and_halves(region, word, new_word_idx)?;
w.push(MessageWord {
var,
value: Some(word),
});
w_halves.push(halves);
Ok(())
};
for i in 49..62 {
new_word(i)?;
}
Ok(())
}
fn decompose_subregion3_word<F: FieldExt>(
&self,
region: &mut Region<'_, Table16Chip<F>>,
word: u32,
index: usize,
) -> Result<Subregion3Word, Error> {
let row = get_word_row(index);
// Rename these here for ease of matching the gates to the specification.
let a_3 = self.extras[0];
let a_4 = self.extras[1];
let pieces = chop_u32(word, &[10, 7, 2, 13]);
// Assign `a` (10-bit piece)
let spread_a = SpreadWord::new(pieces[0] as u16);
let spread_a = SpreadVar::with_lookup(region, &self.lookup, row + 1, spread_a)?;
// Assign `b` (7-bit piece)
let b = region.assign_advice(|| "b", a_4, row + 1, || Ok(F::from_u64(pieces[1] as u64)))?;
// Assign `c` (2-bit piece)
let c = region.assign_advice(|| "c", a_3, row + 1, || Ok(F::from_u64(pieces[2] as u64)))?;
// Assign `d` (13-bit piece) lookup
let spread_d = SpreadWord::new(pieces[3] as u16);
let spread_d = SpreadVar::with_lookup(region, &self.lookup, row, spread_d)?;
Ok(Subregion3Word {
index,
a: CellValue32::new(spread_a.dense.var, spread_a.dense.value.unwrap().into()),
b: CellValue32::new(b, pieces[1]),
c: CellValue32::new(c, pieces[2]),
d: CellValue32::new(spread_d.dense.var, spread_d.dense.value.unwrap().into()),
spread_a: CellValue32::new(spread_a.spread.var, spread_a.spread.value.unwrap()),
spread_d: CellValue32::new(spread_d.spread.var, spread_d.spread.value.unwrap()),
})
}
fn lower_sigma_1<F: FieldExt>(
&self,
region: &mut Region<'_, Table16Chip<F>>,
word: Subregion3Word,
) -> Result<(CellValue16, CellValue16), Error> {
let a_3 = self.extras[0];
let a_4 = self.extras[1];
let a_5 = self.message_schedule;
let a_6 = self.extras[2];
let row = get_word_row(word.index) + 3;
// Assign `spread_a` and copy constraint
self.assign_and_constrain(region, || "spread_a", a_4, row, &word.spread_a, &self.perm)?;
// Split `b` (7-bit chunk) into (2,2,3)-bit `b_lo`, `b_mid` and `b_hi`
let b = word.b.value.unwrap();
let b_hi = (b & 0b1110000) >> 4;
let (b_lo, b_mid) = bisect_four_bit(b & 0b1111);
let spread_b_lo = interleave_u16_with_zeros(b_lo as u16);
let spread_b_mid = interleave_u16_with_zeros(b_mid as u16);
let spread_b_hi = interleave_u16_with_zeros(b_hi as u16);
// Assign `b_lo`, `spread_b_lo`, `b_mid`, `spread_b_mid`, `b_hi`, `spread_b_hi`
region.assign_advice(|| "b_lo", a_3, row - 1, || Ok(F::from_u64(b_lo as u64)))?;
region.assign_advice(
|| "spread_b_lo",
a_4,
row - 1,
|| Ok(F::from_u64(spread_b_lo as u64)),
)?;
region.assign_advice(|| "b_mid", a_5, row - 1, || Ok(F::from_u64(b_mid as u64)))?;
region.assign_advice(
|| "spread_b_mid",
a_6,
row - 1,
|| Ok(F::from_u64(spread_b_mid as u64)),
)?;
region.assign_advice(|| "b_hi", a_5, row + 1, || Ok(F::from_u64(b_hi as u64)))?;
region.assign_advice(
|| "spread_b_hi",
a_6,
row + 1,
|| Ok(F::from_u64(spread_b_hi as u64)),
)?;
// Assign `b` and copy constraint
self.assign_and_constrain(region, || "b", a_6, row, &word.b, &self.perm)?;
// Assign `c` and copy constraint
self.assign_and_constrain(region, || "c", a_3, row + 1, &word.c, &self.perm)?;
// Witness `spread_c`
let spread_c = interleave_u16_with_zeros(word.c.value.unwrap() as u16);
region.assign_advice(
|| "spread_c",
a_4,
row + 1,
|| Ok(F::from_u64(spread_c as u64)),
)?;
// Assign `spread_d` and copy constraint
self.assign_and_constrain(region, || "spread_d", a_5, row, &word.spread_d, &self.perm)?;
// (10, 7, 2, 13)
// Calculate R_0^{even}, R_0^{odd}, R_1^{even}, R_1^{odd}
let spread_a = word.spread_a.value.unwrap() as u64;
let spread_b_lo = spread_b_lo as u64;
let spread_b_mid = spread_b_mid as u64;
let spread_b_hi = spread_b_hi as u64;
let spread_c = spread_c as u64;
let spread_d = word.spread_d.value.unwrap() as u64;
let xor_0 = spread_b_lo
+ (1 << 4) * spread_b_mid
+ (1 << 8) * spread_b_hi
+ (1 << 14) * spread_c
+ (1 << 18) * spread_d;
let xor_1 = spread_c
+ (1 << 4) * spread_d
+ (1 << 30) * spread_a
+ (1 << 50) * spread_b_lo
+ (1 << 54) * spread_b_mid
+ (1 << 58) * spread_b_hi;
let xor_2 = spread_d
+ (1 << 26) * spread_a
+ (1 << 46) * spread_b_lo
+ (1 << 50) * spread_b_mid
+ (1 << 54) * spread_b_hi
+ (1 << 60) * spread_c;
let r = xor_0 + xor_1 + xor_2;
let r_pieces = chop_u64(r, &[32, 32]); // r_0, r_1
let (r_0_even, r_0_odd) = get_even_and_odd_bits_u32(r_pieces[0] as u32);
let (r_1_even, r_1_odd) = get_even_and_odd_bits_u32(r_pieces[1] as u32);
self.assign_sigma_outputs(
region,
&self.lookup,
a_3,
&self.perm,
row,
r_0_even,
r_0_odd,
r_1_even,
r_1_odd,
)
}
}

View File

@ -0,0 +1,654 @@
use super::{util::*, CellValue16, CellValue32, Table16Chip};
use halo2::{
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<C: Chip>(
region: &mut Region<'_, 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::*, Compression, MessageSchedule, Table16Chip, Table16Config},
SpreadInputs, SpreadTable,
};
use halo2::{
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(),
];
// 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(),
],
);
let compression = Compression::empty_configure(
meta,
lookup_inputs.clone(),
message_schedule,
extras,
perm.clone(),
);
let message_schedule = MessageSchedule::empty_configure(
meta,
lookup_inputs.clone(),
message_schedule,
extras,
perm.clone(),
);
MyConfig {
lookup_inputs,
sha256: Table16Config {
lookup_table,
message_schedule,
compression,
},
}
}
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>(), 32u8);
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].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>(), 64u8);
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 = (1u64 << lengths[i]) - 1;
// Shift mask by bits already shifted
let offset: u8 = lengths[0..i].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

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

@ -1,3 +0,0 @@
//! Self-contained circuit implementations of various primitives.
pub mod sha256;

View File

@ -19,7 +19,6 @@
pub mod arithmetic;
pub mod circuit;
pub mod gadget;
pub mod pasta;
pub mod plonk;
pub mod poly;

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