mirror of
https://github.com/logos-storage/plonky2.git
synced 2026-01-07 00:03:10 +00:00
Halo2 style lookup arguments in System Zero (#513)
* Halo2 style lookup arguments in System Zero It's a really nice and simple protocol, particularly for the verifier since the constraints are trivial (aside from the underlying batched permutation checks, which we already support). See the [Halo2 book](https://zcash.github.io/halo2/design/proving-system/lookup.html) and this [talk](https://www.youtube.com/watch?v=YlTt12s7vGE&t=5237s) by @daira. Previously we generated the whole trace in row-wise form, but it's much more efficient to generate these "permuted" columns column-wise. So I changed our STARK framework to accept the trace in column-wise form. STARK impls now have the flexibility to do some generation row-wise and some column-wise (without extra costs; there's a single transpose as before). * sorting * fixes * PR feedback * into_iter * timing
This commit is contained in:
parent
660d785ed1
commit
7d6c0a448d
@ -462,6 +462,11 @@ pub trait PrimeField64: PrimeField + Field64 {
|
|||||||
fn to_canonical_u64(&self) -> u64;
|
fn to_canonical_u64(&self) -> u64;
|
||||||
|
|
||||||
fn to_noncanonical_u64(&self) -> u64;
|
fn to_noncanonical_u64(&self) -> u64;
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn to_canonical(&self) -> Self {
|
||||||
|
Self::from_canonical_u64(self.to_canonical_u64())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An iterator over the powers of a certain base element `b`: `b^0, b^1, b^2, ...`.
|
/// An iterator over the powers of a certain base element `b`: `b^0, b^1, b^2, ...`.
|
||||||
|
|||||||
@ -95,7 +95,7 @@ impl Field for GoldilocksField {
|
|||||||
Self(n.mod_floor(&Self::order()).to_u64_digits()[0])
|
Self(n.mod_floor(&Self::order()).to_u64_digits()[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline(always)]
|
||||||
fn from_canonical_u64(n: u64) -> Self {
|
fn from_canonical_u64(n: u64) -> Self {
|
||||||
debug_assert!(n < Self::ORDER);
|
debug_assert!(n < Self::ORDER);
|
||||||
Self(n)
|
Self(n)
|
||||||
@ -156,6 +156,7 @@ impl PrimeField64 for GoldilocksField {
|
|||||||
c
|
c
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
fn to_noncanonical_u64(&self) -> u64 {
|
fn to_noncanonical_u64(&self) -> u64 {
|
||||||
self.0
|
self.0
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,12 +2,14 @@ use std::marker::PhantomData;
|
|||||||
|
|
||||||
use plonky2::field::extension_field::{Extendable, FieldExtension};
|
use plonky2::field::extension_field::{Extendable, FieldExtension};
|
||||||
use plonky2::field::packed_field::PackedField;
|
use plonky2::field::packed_field::PackedField;
|
||||||
|
use plonky2::field::polynomial::PolynomialValues;
|
||||||
use plonky2::hash::hash_types::RichField;
|
use plonky2::hash::hash_types::RichField;
|
||||||
use plonky2::plonk::circuit_builder::CircuitBuilder;
|
use plonky2::plonk::circuit_builder::CircuitBuilder;
|
||||||
|
|
||||||
use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer};
|
use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer};
|
||||||
use crate::permutation::PermutationPair;
|
use crate::permutation::PermutationPair;
|
||||||
use crate::stark::Stark;
|
use crate::stark::Stark;
|
||||||
|
use crate::util::trace_rows_to_poly_values;
|
||||||
use crate::vars::{StarkEvaluationTargets, StarkEvaluationVars};
|
use crate::vars::{StarkEvaluationTargets, StarkEvaluationVars};
|
||||||
|
|
||||||
/// Toy STARK system used for testing.
|
/// Toy STARK system used for testing.
|
||||||
@ -37,8 +39,8 @@ impl<F: RichField + Extendable<D>, const D: usize> FibonacciStark<F, D> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Generate the trace using `x0, x1, 0, 1` as initial state values.
|
/// Generate the trace using `x0, x1, 0, 1` as initial state values.
|
||||||
fn generate_trace(&self, x0: F, x1: F) -> Vec<[F; Self::COLUMNS]> {
|
fn generate_trace(&self, x0: F, x1: F) -> Vec<PolynomialValues<F>> {
|
||||||
let mut trace = (0..self.num_rows)
|
let mut trace_rows = (0..self.num_rows)
|
||||||
.scan([x0, x1, F::ZERO, F::ONE], |acc, _| {
|
.scan([x0, x1, F::ZERO, F::ONE], |acc, _| {
|
||||||
let tmp = *acc;
|
let tmp = *acc;
|
||||||
acc[0] = tmp[1];
|
acc[0] = tmp[1];
|
||||||
@ -48,8 +50,8 @@ impl<F: RichField + Extendable<D>, const D: usize> FibonacciStark<F, D> {
|
|||||||
Some(tmp)
|
Some(tmp)
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
trace[self.num_rows - 1][3] = F::ZERO; // So that column 2 and 3 are permutation of one another.
|
trace_rows[self.num_rows - 1][3] = F::ZERO; // So that column 2 and 3 are permutation of one another.
|
||||||
trace
|
trace_rows_to_poly_values(trace_rows)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -113,9 +115,7 @@ impl<F: RichField + Extendable<D>, const D: usize> Stark<F, D> for FibonacciStar
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn permutation_pairs(&self) -> Vec<PermutationPair> {
|
fn permutation_pairs(&self) -> Vec<PermutationPair> {
|
||||||
vec![PermutationPair {
|
vec![PermutationPair::singletons(2, 3)]
|
||||||
column_pairs: vec![(2, 3)],
|
|
||||||
}]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -15,6 +15,7 @@ pub mod prover;
|
|||||||
pub mod recursive_verifier;
|
pub mod recursive_verifier;
|
||||||
pub mod stark;
|
pub mod stark;
|
||||||
pub mod stark_testing;
|
pub mod stark_testing;
|
||||||
|
pub mod util;
|
||||||
pub mod vanishing_poly;
|
pub mod vanishing_poly;
|
||||||
pub mod vars;
|
pub mod vars;
|
||||||
pub mod verifier;
|
pub mod verifier;
|
||||||
|
|||||||
@ -30,6 +30,14 @@ pub struct PermutationPair {
|
|||||||
pub column_pairs: Vec<(usize, usize)>,
|
pub column_pairs: Vec<(usize, usize)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl PermutationPair {
|
||||||
|
pub fn singletons(lhs: usize, rhs: usize) -> Self {
|
||||||
|
Self {
|
||||||
|
column_pairs: vec![(lhs, rhs)],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A single instance of a permutation check protocol.
|
/// A single instance of a permutation check protocol.
|
||||||
pub(crate) struct PermutationInstance<'a, T: Copy> {
|
pub(crate) struct PermutationInstance<'a, T: Copy> {
|
||||||
pub(crate) pair: &'a PermutationPair,
|
pub(crate) pair: &'a PermutationPair,
|
||||||
|
|||||||
@ -30,7 +30,7 @@ use crate::vars::StarkEvaluationVars;
|
|||||||
pub fn prove<F, C, S, const D: usize>(
|
pub fn prove<F, C, S, const D: usize>(
|
||||||
stark: S,
|
stark: S,
|
||||||
config: &StarkConfig,
|
config: &StarkConfig,
|
||||||
trace: Vec<[F; S::COLUMNS]>,
|
trace_poly_values: Vec<PolynomialValues<F>>,
|
||||||
public_inputs: [F; S::PUBLIC_INPUTS],
|
public_inputs: [F; S::PUBLIC_INPUTS],
|
||||||
timing: &mut TimingTree,
|
timing: &mut TimingTree,
|
||||||
) -> Result<StarkProofWithPublicInputs<F, C, D>>
|
) -> Result<StarkProofWithPublicInputs<F, C, D>>
|
||||||
@ -42,7 +42,7 @@ where
|
|||||||
[(); S::PUBLIC_INPUTS]:,
|
[(); S::PUBLIC_INPUTS]:,
|
||||||
[(); C::Hasher::HASH_SIZE]:,
|
[(); C::Hasher::HASH_SIZE]:,
|
||||||
{
|
{
|
||||||
let degree = trace.len();
|
let degree = trace_poly_values[0].len();
|
||||||
let degree_bits = log2_strict(degree);
|
let degree_bits = log2_strict(degree);
|
||||||
let fri_params = config.fri_params(degree_bits);
|
let fri_params = config.fri_params(degree_bits);
|
||||||
let rate_bits = config.fri_config.rate_bits;
|
let rate_bits = config.fri_config.rate_bits;
|
||||||
@ -52,18 +52,6 @@ where
|
|||||||
"FRI total reduction arity is too large.",
|
"FRI total reduction arity is too large.",
|
||||||
);
|
);
|
||||||
|
|
||||||
let trace_vecs = trace.iter().map(|row| row.to_vec()).collect_vec();
|
|
||||||
let trace_col_major: Vec<Vec<F>> = transpose(&trace_vecs);
|
|
||||||
|
|
||||||
let trace_poly_values: Vec<PolynomialValues<F>> = timed!(
|
|
||||||
timing,
|
|
||||||
"compute trace polynomials",
|
|
||||||
trace_col_major
|
|
||||||
.par_iter()
|
|
||||||
.map(|column| PolynomialValues::new(column.clone()))
|
|
||||||
.collect()
|
|
||||||
);
|
|
||||||
|
|
||||||
let trace_commitment = timed!(
|
let trace_commitment = timed!(
|
||||||
timing,
|
timing,
|
||||||
"compute trace commitment",
|
"compute trace commitment",
|
||||||
|
|||||||
16
starky/src/util.rs
Normal file
16
starky/src/util.rs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
use itertools::Itertools;
|
||||||
|
use plonky2::field::field_types::Field;
|
||||||
|
use plonky2::field::polynomial::PolynomialValues;
|
||||||
|
use plonky2::util::transpose;
|
||||||
|
|
||||||
|
/// A helper function to transpose a row-wise trace and put it in the format that `prove` expects.
|
||||||
|
pub fn trace_rows_to_poly_values<F: Field, const COLUMNS: usize>(
|
||||||
|
trace_rows: Vec<[F; COLUMNS]>,
|
||||||
|
) -> Vec<PolynomialValues<F>> {
|
||||||
|
let trace_row_vecs = trace_rows.into_iter().map(|row| row.to_vec()).collect_vec();
|
||||||
|
let trace_col_vecs: Vec<Vec<F>> = transpose(&trace_row_vecs);
|
||||||
|
trace_col_vecs
|
||||||
|
.into_iter()
|
||||||
|
.map(|column| PolynomialValues::new(column))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
@ -118,7 +118,10 @@ where
|
|||||||
.chunks(stark.quotient_degree_factor())
|
.chunks(stark.quotient_degree_factor())
|
||||||
.enumerate()
|
.enumerate()
|
||||||
{
|
{
|
||||||
ensure!(vanishing_polys_zeta[i] == z_h_zeta * reduce_with_powers(chunk, zeta_pow_deg));
|
ensure!(
|
||||||
|
vanishing_polys_zeta[i] == z_h_zeta * reduce_with_powers(chunk, zeta_pow_deg),
|
||||||
|
"Mismatch between evaluation and opening of quotient polynomial"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let merkle_caps = once(proof.trace_cap)
|
let merkle_caps = once(proof.trace_cap)
|
||||||
|
|||||||
@ -10,6 +10,14 @@ plonky2_util = { path = "../util" }
|
|||||||
starky = { path = "../starky" }
|
starky = { path = "../starky" }
|
||||||
anyhow = "1.0.40"
|
anyhow = "1.0.40"
|
||||||
env_logger = "0.9.0"
|
env_logger = "0.9.0"
|
||||||
|
itertools = "0.10.0"
|
||||||
log = "0.4.14"
|
log = "0.4.14"
|
||||||
rand = "0.8.4"
|
rand = "0.8.4"
|
||||||
rand_chacha = "0.3.1"
|
rand_chacha = "0.3.1"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
criterion = "0.3.5"
|
||||||
|
|
||||||
|
[[bench]]
|
||||||
|
name = "lookup_permuted_cols"
|
||||||
|
harness = false
|
||||||
|
|||||||
30
system_zero/benches/lookup_permuted_cols.rs
Normal file
30
system_zero/benches/lookup_permuted_cols.rs
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
|
||||||
|
use itertools::Itertools;
|
||||||
|
use plonky2::field::field_types::Field;
|
||||||
|
use plonky2::field::goldilocks_field::GoldilocksField;
|
||||||
|
use rand::{thread_rng, Rng};
|
||||||
|
use system_zero::lookup::permuted_cols;
|
||||||
|
|
||||||
|
type F = GoldilocksField;
|
||||||
|
|
||||||
|
fn criterion_benchmark(c: &mut Criterion) {
|
||||||
|
let mut group = c.benchmark_group("lookup-permuted-cols");
|
||||||
|
|
||||||
|
for size_log in [16, 17, 18] {
|
||||||
|
let size = 1 << size_log;
|
||||||
|
group.bench_with_input(BenchmarkId::from_parameter(size), &size, |b, _| {
|
||||||
|
// We could benchmark a table of random values with
|
||||||
|
// let table = F::rand_vec(size);
|
||||||
|
// But in practice we currently use tables that are pre-sorted, which makes
|
||||||
|
// permuted_cols cheaper since it will sort the table.
|
||||||
|
let table = (0..size).map(F::from_canonical_usize).collect_vec();
|
||||||
|
let input = (0..size)
|
||||||
|
.map(|_| table[thread_rng().gen_range(0..size)])
|
||||||
|
.collect_vec();
|
||||||
|
b.iter(|| permuted_cols(&input, &table));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
criterion_group!(benches, criterion_benchmark);
|
||||||
|
criterion_main!(benches);
|
||||||
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
mod alu;
|
mod alu;
|
||||||
mod core_registers;
|
mod core_registers;
|
||||||
|
pub mod lookup;
|
||||||
mod memory;
|
mod memory;
|
||||||
mod permutation_unit;
|
mod permutation_unit;
|
||||||
mod public_input_layout;
|
mod public_input_layout;
|
||||||
|
|||||||
147
system_zero/src/lookup.rs
Normal file
147
system_zero/src/lookup.rs
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
//! Implementation of the Halo2 lookup argument.
|
||||||
|
//!
|
||||||
|
//! References:
|
||||||
|
//! - https://zcash.github.io/halo2/design/proving-system/lookup.html
|
||||||
|
//! - https://www.youtube.com/watch?v=YlTt12s7vGE&t=5237s
|
||||||
|
|
||||||
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
|
use itertools::Itertools;
|
||||||
|
use plonky2::field::extension_field::Extendable;
|
||||||
|
use plonky2::field::field_types::{Field, PrimeField64};
|
||||||
|
use plonky2::field::packed_field::PackedField;
|
||||||
|
use plonky2::hash::hash_types::RichField;
|
||||||
|
use plonky2::plonk::circuit_builder::CircuitBuilder;
|
||||||
|
use starky::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer};
|
||||||
|
use starky::vars::StarkEvaluationTargets;
|
||||||
|
use starky::vars::StarkEvaluationVars;
|
||||||
|
|
||||||
|
use crate::public_input_layout::NUM_PUBLIC_INPUTS;
|
||||||
|
use crate::registers::lookup::*;
|
||||||
|
use crate::registers::NUM_COLUMNS;
|
||||||
|
|
||||||
|
pub(crate) fn generate_lookups<F: PrimeField64>(trace_cols: &mut [Vec<F>]) {
|
||||||
|
for i in 0..NUM_LOOKUPS {
|
||||||
|
let inputs = &trace_cols[col_input(i)];
|
||||||
|
let table = &trace_cols[col_table(i)];
|
||||||
|
let (permuted_inputs, permuted_table) = permuted_cols(inputs, table);
|
||||||
|
trace_cols[col_permuted_input(i)] = permuted_inputs;
|
||||||
|
trace_cols[col_permuted_table(i)] = permuted_table;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Given an input column and a table column, generate the permuted input and permuted table columns
|
||||||
|
/// used in the Halo2 permutation argument.
|
||||||
|
pub fn permuted_cols<F: PrimeField64>(inputs: &[F], table: &[F]) -> (Vec<F>, Vec<F>) {
|
||||||
|
let n = inputs.len();
|
||||||
|
|
||||||
|
// The permuted inputs do not have to be ordered, but we found that sorting was faster than
|
||||||
|
// hash-based grouping. We also sort the table, as this helps us identify "unused" table
|
||||||
|
// elements efficiently.
|
||||||
|
|
||||||
|
// To compare elements, e.g. for sorting, we first need them in canonical form. It would be
|
||||||
|
// wasteful to canonicalize in each comparison, as a single element may be involved in many
|
||||||
|
// comparisons. So we will canonicalize once upfront, then use `to_noncanonical_u64` when
|
||||||
|
// comparing elements.
|
||||||
|
|
||||||
|
let sorted_inputs = inputs
|
||||||
|
.iter()
|
||||||
|
.map(|x| x.to_canonical())
|
||||||
|
.sorted_unstable_by_key(|x| x.to_noncanonical_u64())
|
||||||
|
.collect_vec();
|
||||||
|
let sorted_table = table
|
||||||
|
.iter()
|
||||||
|
.map(|x| x.to_canonical())
|
||||||
|
.sorted_unstable_by_key(|x| x.to_noncanonical_u64())
|
||||||
|
.collect_vec();
|
||||||
|
|
||||||
|
let mut unused_table_inds = Vec::with_capacity(n);
|
||||||
|
let mut unused_table_vals = Vec::with_capacity(n);
|
||||||
|
let mut permuted_table = vec![F::ZERO; n];
|
||||||
|
let mut i = 0;
|
||||||
|
let mut j = 0;
|
||||||
|
while (j < n) && (i < n) {
|
||||||
|
let input_val = sorted_inputs[i].to_noncanonical_u64();
|
||||||
|
let table_val = sorted_table[j].to_noncanonical_u64();
|
||||||
|
match input_val.cmp(&table_val) {
|
||||||
|
Ordering::Greater => {
|
||||||
|
unused_table_vals.push(sorted_table[j]);
|
||||||
|
j += 1;
|
||||||
|
}
|
||||||
|
Ordering::Less => {
|
||||||
|
if let Some(x) = unused_table_vals.pop() {
|
||||||
|
permuted_table[i] = x;
|
||||||
|
} else {
|
||||||
|
unused_table_inds.push(i);
|
||||||
|
}
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
Ordering::Equal => {
|
||||||
|
permuted_table[i] = sorted_table[j];
|
||||||
|
i += 1;
|
||||||
|
j += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::needless_range_loop)] // indexing is just more natural here
|
||||||
|
for jj in j..n {
|
||||||
|
unused_table_vals.push(sorted_table[jj]);
|
||||||
|
}
|
||||||
|
for ii in i..n {
|
||||||
|
unused_table_inds.push(ii);
|
||||||
|
}
|
||||||
|
for (ind, val) in unused_table_inds.into_iter().zip_eq(unused_table_vals) {
|
||||||
|
permuted_table[ind] = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
(sorted_inputs, permuted_table)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn eval_lookups<F: Field, P: PackedField<Scalar = F>>(
|
||||||
|
vars: StarkEvaluationVars<F, P, NUM_COLUMNS, NUM_PUBLIC_INPUTS>,
|
||||||
|
yield_constr: &mut ConstraintConsumer<P>,
|
||||||
|
) {
|
||||||
|
for i in 0..NUM_LOOKUPS {
|
||||||
|
let local_perm_input = vars.local_values[col_permuted_input(i)];
|
||||||
|
let next_perm_table = vars.next_values[col_permuted_table(i)];
|
||||||
|
let next_perm_input = vars.next_values[col_permuted_input(i)];
|
||||||
|
|
||||||
|
// A "vertical" diff between the local and next permuted inputs.
|
||||||
|
let diff_input_prev = next_perm_input - local_perm_input;
|
||||||
|
// A "horizontal" diff between the next permuted input and permuted table value.
|
||||||
|
let diff_input_table = next_perm_input - next_perm_table;
|
||||||
|
|
||||||
|
yield_constr.constraint(diff_input_prev * diff_input_table);
|
||||||
|
|
||||||
|
// This is actually constraining the first row, as per the spec, since `diff_input_table`
|
||||||
|
// is a diff of the next row's values. In the context of `constraint_last_row`, the next
|
||||||
|
// row is the first row.
|
||||||
|
yield_constr.constraint_last_row(diff_input_table);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn eval_lookups_recursively<F: RichField + Extendable<D>, const D: usize>(
|
||||||
|
builder: &mut CircuitBuilder<F, D>,
|
||||||
|
vars: StarkEvaluationTargets<D, NUM_COLUMNS, NUM_PUBLIC_INPUTS>,
|
||||||
|
yield_constr: &mut RecursiveConstraintConsumer<F, D>,
|
||||||
|
) {
|
||||||
|
for i in 0..NUM_LOOKUPS {
|
||||||
|
let local_perm_input = vars.local_values[col_permuted_input(i)];
|
||||||
|
let next_perm_table = vars.next_values[col_permuted_table(i)];
|
||||||
|
let next_perm_input = vars.next_values[col_permuted_input(i)];
|
||||||
|
|
||||||
|
// A "vertical" diff between the local and next permuted inputs.
|
||||||
|
let diff_input_prev = builder.sub_extension(next_perm_input, local_perm_input);
|
||||||
|
// A "horizontal" diff between the next permuted input and permuted table value.
|
||||||
|
let diff_input_table = builder.sub_extension(next_perm_input, next_perm_table);
|
||||||
|
|
||||||
|
let diff_product = builder.mul_extension(diff_input_prev, diff_input_table);
|
||||||
|
yield_constr.constraint(builder, diff_product);
|
||||||
|
|
||||||
|
// This is actually constraining the first row, as per the spec, since `diff_input_table`
|
||||||
|
// is a diff of the next row's values. In the context of `constraint_last_row`, the next
|
||||||
|
// row is the first row.
|
||||||
|
yield_constr.constraint_last_row(builder, diff_input_table);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -3,19 +3,35 @@
|
|||||||
|
|
||||||
const START_UNIT: usize = super::START_LOOKUP;
|
const START_UNIT: usize = super::START_LOOKUP;
|
||||||
|
|
||||||
const NUM_LOOKUPS: usize =
|
pub(crate) const NUM_LOOKUPS: usize =
|
||||||
super::range_check_16::NUM_RANGE_CHECKS + super::range_check_degree::NUM_RANGE_CHECKS;
|
super::range_check_16::NUM_RANGE_CHECKS + super::range_check_degree::NUM_RANGE_CHECKS;
|
||||||
|
|
||||||
|
pub(crate) const fn col_input(i: usize) -> usize {
|
||||||
|
if i < super::range_check_16::NUM_RANGE_CHECKS {
|
||||||
|
super::range_check_16::col_rc_16_input(i)
|
||||||
|
} else {
|
||||||
|
super::range_check_degree::col_rc_degree_input(i - super::range_check_16::NUM_RANGE_CHECKS)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// This column contains a permutation of the input values.
|
/// This column contains a permutation of the input values.
|
||||||
const fn col_permuted_input(i: usize) -> usize {
|
pub(crate) const fn col_permuted_input(i: usize) -> usize {
|
||||||
debug_assert!(i < NUM_LOOKUPS);
|
debug_assert!(i < NUM_LOOKUPS);
|
||||||
START_UNIT + 2 * i
|
START_UNIT + 2 * i
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) const fn col_table(i: usize) -> usize {
|
||||||
|
if i < super::range_check_16::NUM_RANGE_CHECKS {
|
||||||
|
super::core::COL_RANGE_16
|
||||||
|
} else {
|
||||||
|
super::core::COL_CLOCK
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// This column contains a permutation of the table values.
|
/// This column contains a permutation of the table values.
|
||||||
const fn col_permuted_table(i: usize) -> usize {
|
pub(crate) const fn col_permuted_table(i: usize) -> usize {
|
||||||
debug_assert!(i < NUM_LOOKUPS);
|
debug_assert!(i < NUM_LOOKUPS);
|
||||||
START_UNIT + 2 * i + 1
|
START_UNIT + 2 * i + 1
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) const END: usize = START_UNIT + NUM_LOOKUPS;
|
pub(super) const END: usize = START_UNIT + NUM_LOOKUPS * 2;
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
//! Range check unit which checks that values are in `[0, 2^16)`.
|
//! Range check unit which checks that values are in `[0, 2^16)`.
|
||||||
|
|
||||||
pub(super) const NUM_RANGE_CHECKS: usize = 5;
|
pub(crate) const NUM_RANGE_CHECKS: usize = 5;
|
||||||
|
|
||||||
/// The input of the `i`th range check, i.e. the value being range checked.
|
/// The input of the `i`th range check, i.e. the value being range checked.
|
||||||
pub(crate) const fn col_rc_16_input(i: usize) -> usize {
|
pub(crate) const fn col_rc_16_input(i: usize) -> usize {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
//! Range check unit which checks that values are in `[0, degree)`.
|
//! Range check unit which checks that values are in `[0, degree)`.
|
||||||
|
|
||||||
pub(super) const NUM_RANGE_CHECKS: usize = 5;
|
pub(crate) const NUM_RANGE_CHECKS: usize = 5;
|
||||||
|
|
||||||
/// The input of the `i`th range check, i.e. the value being range checked.
|
/// The input of the `i`th range check, i.e. the value being range checked.
|
||||||
pub(crate) const fn col_rc_degree_input(i: usize) -> usize {
|
pub(crate) const fn col_rc_degree_input(i: usize) -> usize {
|
||||||
|
|||||||
@ -2,8 +2,12 @@ use std::marker::PhantomData;
|
|||||||
|
|
||||||
use plonky2::field::extension_field::{Extendable, FieldExtension};
|
use plonky2::field::extension_field::{Extendable, FieldExtension};
|
||||||
use plonky2::field::packed_field::PackedField;
|
use plonky2::field::packed_field::PackedField;
|
||||||
|
use plonky2::field::polynomial::PolynomialValues;
|
||||||
use plonky2::hash::hash_types::RichField;
|
use plonky2::hash::hash_types::RichField;
|
||||||
use plonky2::plonk::circuit_builder::CircuitBuilder;
|
use plonky2::plonk::circuit_builder::CircuitBuilder;
|
||||||
|
use plonky2::timed;
|
||||||
|
use plonky2::util::timing::TimingTree;
|
||||||
|
use plonky2::util::transpose;
|
||||||
use starky::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer};
|
use starky::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer};
|
||||||
use starky::permutation::PermutationPair;
|
use starky::permutation::PermutationPair;
|
||||||
use starky::stark::Stark;
|
use starky::stark::Stark;
|
||||||
@ -15,12 +19,13 @@ use crate::core_registers::{
|
|||||||
eval_core_registers, eval_core_registers_recursively, generate_first_row_core_registers,
|
eval_core_registers, eval_core_registers_recursively, generate_first_row_core_registers,
|
||||||
generate_next_row_core_registers,
|
generate_next_row_core_registers,
|
||||||
};
|
};
|
||||||
|
use crate::lookup::{eval_lookups, eval_lookups_recursively, generate_lookups};
|
||||||
use crate::memory::TransactionMemory;
|
use crate::memory::TransactionMemory;
|
||||||
use crate::permutation_unit::{
|
use crate::permutation_unit::{
|
||||||
eval_permutation_unit, eval_permutation_unit_recursively, generate_permutation_unit,
|
eval_permutation_unit, eval_permutation_unit_recursively, generate_permutation_unit,
|
||||||
};
|
};
|
||||||
use crate::public_input_layout::NUM_PUBLIC_INPUTS;
|
use crate::public_input_layout::NUM_PUBLIC_INPUTS;
|
||||||
use crate::registers::NUM_COLUMNS;
|
use crate::registers::{lookup, NUM_COLUMNS};
|
||||||
|
|
||||||
/// We require at least 2^16 rows as it helps support efficient 16-bit range checks.
|
/// We require at least 2^16 rows as it helps support efficient 16-bit range checks.
|
||||||
const MIN_TRACE_ROWS: usize = 1 << 16;
|
const MIN_TRACE_ROWS: usize = 1 << 16;
|
||||||
@ -31,7 +36,9 @@ pub struct SystemZero<F: RichField + Extendable<D>, const D: usize> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<F: RichField + Extendable<D>, const D: usize> SystemZero<F, D> {
|
impl<F: RichField + Extendable<D>, const D: usize> SystemZero<F, D> {
|
||||||
fn generate_trace(&self) -> Vec<[F; NUM_COLUMNS]> {
|
/// Generate the rows of the trace. Note that this does not generate the permuted columns used
|
||||||
|
/// in our lookup arguments, as those are computed after transposing to column-wise form.
|
||||||
|
fn generate_trace_rows(&self) -> Vec<[F; NUM_COLUMNS]> {
|
||||||
let memory = TransactionMemory::default();
|
let memory = TransactionMemory::default();
|
||||||
|
|
||||||
let mut row = [F::ZERO; NUM_COLUMNS];
|
let mut row = [F::ZERO; NUM_COLUMNS];
|
||||||
@ -59,6 +66,45 @@ impl<F: RichField + Extendable<D>, const D: usize> SystemZero<F, D> {
|
|||||||
trace.push(row);
|
trace.push(row);
|
||||||
trace
|
trace
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn generate_trace(&self) -> Vec<PolynomialValues<F>> {
|
||||||
|
let mut timing = TimingTree::new("generate trace", log::Level::Debug);
|
||||||
|
|
||||||
|
// Generate the witness, except for permuted columns in the lookup argument.
|
||||||
|
let trace_rows = timed!(
|
||||||
|
&mut timing,
|
||||||
|
"generate trace rows",
|
||||||
|
self.generate_trace_rows()
|
||||||
|
);
|
||||||
|
|
||||||
|
// Transpose from row-wise to column-wise.
|
||||||
|
let trace_row_vecs: Vec<_> = timed!(
|
||||||
|
&mut timing,
|
||||||
|
"convert to Vecs",
|
||||||
|
trace_rows.into_iter().map(|row| row.to_vec()).collect()
|
||||||
|
);
|
||||||
|
let mut trace_col_vecs: Vec<Vec<F>> =
|
||||||
|
timed!(&mut timing, "transpose", transpose(&trace_row_vecs));
|
||||||
|
|
||||||
|
// Generate permuted columns in the lookup argument.
|
||||||
|
timed!(
|
||||||
|
&mut timing,
|
||||||
|
"generate lookup columns",
|
||||||
|
generate_lookups(&mut trace_col_vecs)
|
||||||
|
);
|
||||||
|
|
||||||
|
let trace_polys = timed!(
|
||||||
|
&mut timing,
|
||||||
|
"convert to PolynomialValues",
|
||||||
|
trace_col_vecs
|
||||||
|
.into_iter()
|
||||||
|
.map(|column| PolynomialValues::new(column))
|
||||||
|
.collect()
|
||||||
|
);
|
||||||
|
|
||||||
|
timing.print();
|
||||||
|
trace_polys
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<F: RichField + Extendable<D>, const D: usize> Default for SystemZero<F, D> {
|
impl<F: RichField + Extendable<D>, const D: usize> Default for SystemZero<F, D> {
|
||||||
@ -84,6 +130,7 @@ impl<F: RichField + Extendable<D>, const D: usize> Stark<F, D> for SystemZero<F,
|
|||||||
eval_core_registers(vars, yield_constr);
|
eval_core_registers(vars, yield_constr);
|
||||||
eval_alu(vars, yield_constr);
|
eval_alu(vars, yield_constr);
|
||||||
eval_permutation_unit::<F, FE, P, D2>(vars, yield_constr);
|
eval_permutation_unit::<F, FE, P, D2>(vars, yield_constr);
|
||||||
|
eval_lookups(vars, yield_constr);
|
||||||
// TODO: Other units
|
// TODO: Other units
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,6 +143,7 @@ impl<F: RichField + Extendable<D>, const D: usize> Stark<F, D> for SystemZero<F,
|
|||||||
eval_core_registers_recursively(builder, vars, yield_constr);
|
eval_core_registers_recursively(builder, vars, yield_constr);
|
||||||
eval_alu_recursively(builder, vars, yield_constr);
|
eval_alu_recursively(builder, vars, yield_constr);
|
||||||
eval_permutation_unit_recursively(builder, vars, yield_constr);
|
eval_permutation_unit_recursively(builder, vars, yield_constr);
|
||||||
|
eval_lookups_recursively(builder, vars, yield_constr);
|
||||||
// TODO: Other units
|
// TODO: Other units
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,9 +152,22 @@ impl<F: RichField + Extendable<D>, const D: usize> Stark<F, D> for SystemZero<F,
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn permutation_pairs(&self) -> Vec<PermutationPair> {
|
fn permutation_pairs(&self) -> Vec<PermutationPair> {
|
||||||
|
let mut pairs = Vec::new();
|
||||||
|
|
||||||
|
for i in 0..lookup::NUM_LOOKUPS {
|
||||||
|
pairs.push(PermutationPair::singletons(
|
||||||
|
lookup::col_input(i),
|
||||||
|
lookup::col_permuted_input(i),
|
||||||
|
));
|
||||||
|
pairs.push(PermutationPair::singletons(
|
||||||
|
lookup::col_table(i),
|
||||||
|
lookup::col_permuted_table(i),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Add permutation pairs for memory.
|
// TODO: Add permutation pairs for memory.
|
||||||
// TODO: Add permutation pairs for range checks.
|
|
||||||
vec![]
|
pairs
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -127,8 +188,9 @@ mod tests {
|
|||||||
use crate::system_zero::SystemZero;
|
use crate::system_zero::SystemZero;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[ignore] // A bit slow.
|
|
||||||
fn run() -> Result<()> {
|
fn run() -> Result<()> {
|
||||||
|
init_logger();
|
||||||
|
|
||||||
type F = GoldilocksField;
|
type F = GoldilocksField;
|
||||||
type C = PoseidonGoldilocksConfig;
|
type C = PoseidonGoldilocksConfig;
|
||||||
const D: usize = 2;
|
const D: usize = 2;
|
||||||
@ -154,4 +216,8 @@ mod tests {
|
|||||||
let system = S::default();
|
let system = S::default();
|
||||||
test_stark_low_degree(system)
|
test_stark_low_degree(system)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn init_logger() {
|
||||||
|
let _ = env_logger::builder().format_timestamp(None).try_init();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user