mirror of
https://github.com/logos-storage/plonky2.git
synced 2026-01-09 09:13:09 +00:00
Merge branch 'main' into ecrecover_kernel
# Conflicts: # evm/src/cpu/kernel/aggregator.rs
This commit is contained in:
commit
cb7215436b
@ -38,12 +38,23 @@ pub(crate) fn combined_kernel() -> Kernel {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::str::FromStr;
|
||||
|
||||
use anyhow::Result;
|
||||
use ethereum_types::U256;
|
||||
use log::debug;
|
||||
use rand::{thread_rng, Rng};
|
||||
|
||||
use crate::cpu::kernel::aggregator::combined_kernel;
|
||||
|
||||
#[test]
|
||||
fn make_kernel() {
|
||||
let _ = env_logger::Builder::from_default_env()
|
||||
.format_timestamp(None)
|
||||
.try_init();
|
||||
|
||||
// Make sure we can parse and assemble the entire kernel.
|
||||
let kernel = combined_kernel();
|
||||
println!("Kernel size: {} bytes", kernel.code.len());
|
||||
debug!("Total kernel size: {} bytes", kernel.code.len());
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@ use std::collections::HashMap;
|
||||
|
||||
use ethereum_types::U256;
|
||||
use itertools::izip;
|
||||
use log::debug;
|
||||
|
||||
use super::ast::PushTarget;
|
||||
use crate::cpu::kernel::ast::Literal;
|
||||
@ -49,7 +50,10 @@ pub(crate) fn assemble(files: Vec<File>, constants: HashMap<String, U256>) -> Ke
|
||||
}
|
||||
let mut code = vec![];
|
||||
for (file, locals) in izip!(expanded_files, local_labels) {
|
||||
let prev_len = code.len();
|
||||
assemble_file(file, &mut code, locals, &global_labels);
|
||||
let file_len = code.len() - prev_len;
|
||||
debug!("Assembled file size: {} bytes", file_len);
|
||||
}
|
||||
assert_eq!(code.len(), offset, "Code length doesn't match offset.");
|
||||
Kernel {
|
||||
|
||||
@ -62,7 +62,7 @@ impl<F: Field> GenerationState<F> {
|
||||
let context = self.current_context;
|
||||
let value = self.memory.contexts[context].segments[segment].get(virt);
|
||||
self.memory.log.push(MemoryOp {
|
||||
channel_index,
|
||||
channel_index: Some(channel_index),
|
||||
timestamp,
|
||||
is_read: true,
|
||||
context,
|
||||
@ -84,7 +84,7 @@ impl<F: Field> GenerationState<F> {
|
||||
let timestamp = self.cpu_rows.len();
|
||||
let context = self.current_context;
|
||||
self.memory.log.push(MemoryOp {
|
||||
channel_index,
|
||||
channel_index: Some(channel_index),
|
||||
timestamp,
|
||||
is_read: false,
|
||||
context,
|
||||
|
||||
@ -1,7 +1,5 @@
|
||||
//! Memory registers.
|
||||
|
||||
use std::ops::Range;
|
||||
|
||||
use crate::memory::{NUM_CHANNELS, VALUE_LIMBS};
|
||||
|
||||
// Columns for memory operations, ordered by (addr, timestamp).
|
||||
@ -41,7 +39,4 @@ pub(crate) const COUNTER: usize = RANGE_CHECK + 1;
|
||||
pub(crate) const RANGE_CHECK_PERMUTED: usize = COUNTER + 1;
|
||||
pub(crate) const COUNTER_PERMUTED: usize = RANGE_CHECK_PERMUTED + 1;
|
||||
|
||||
// Columns to be padded at the top with zeroes, before the permutation argument takes place.
|
||||
pub(crate) const COLUMNS_TO_PAD: Range<usize> = TIMESTAMP..RANGE_CHECK + 1;
|
||||
|
||||
pub(crate) const NUM_COLUMNS: usize = COUNTER_PERMUTED + 1;
|
||||
|
||||
@ -9,20 +9,21 @@ use plonky2::field::types::Field;
|
||||
use plonky2::hash::hash_types::RichField;
|
||||
use plonky2::timed;
|
||||
use plonky2::util::timing::TimingTree;
|
||||
use plonky2::util::transpose;
|
||||
use rand::Rng;
|
||||
use rayon::prelude::*;
|
||||
|
||||
use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer};
|
||||
use crate::cross_table_lookup::Column;
|
||||
use crate::lookup::{eval_lookups, eval_lookups_circuit, permuted_cols};
|
||||
use crate::memory::columns::{
|
||||
is_channel, value_limb, ADDR_CONTEXT, ADDR_SEGMENT, ADDR_VIRTUAL, COLUMNS_TO_PAD,
|
||||
CONTEXT_FIRST_CHANGE, COUNTER, COUNTER_PERMUTED, IS_READ, NUM_COLUMNS, RANGE_CHECK,
|
||||
RANGE_CHECK_PERMUTED, SEGMENT_FIRST_CHANGE, TIMESTAMP, VIRTUAL_FIRST_CHANGE,
|
||||
is_channel, value_limb, ADDR_CONTEXT, ADDR_SEGMENT, ADDR_VIRTUAL, CONTEXT_FIRST_CHANGE,
|
||||
COUNTER, COUNTER_PERMUTED, IS_READ, NUM_COLUMNS, RANGE_CHECK, RANGE_CHECK_PERMUTED,
|
||||
SEGMENT_FIRST_CHANGE, TIMESTAMP, VIRTUAL_FIRST_CHANGE,
|
||||
};
|
||||
use crate::memory::NUM_CHANNELS;
|
||||
use crate::memory::{NUM_CHANNELS, VALUE_LIMBS};
|
||||
use crate::permutation::PermutationPair;
|
||||
use crate::stark::Stark;
|
||||
use crate::util::trace_rows_to_poly_values;
|
||||
use crate::vars::{StarkEvaluationTargets, StarkEvaluationVars};
|
||||
|
||||
pub(crate) const NUM_PUBLIC_INPUTS: usize = 0;
|
||||
@ -44,9 +45,10 @@ pub struct MemoryStark<F, const D: usize> {
|
||||
pub(crate) f: PhantomData<F>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct MemoryOp<F> {
|
||||
pub channel_index: usize,
|
||||
/// The channel this operation came from, or `None` if it's a dummy operation for padding.
|
||||
pub channel_index: Option<usize>,
|
||||
pub timestamp: usize,
|
||||
pub is_read: bool,
|
||||
pub context: usize,
|
||||
@ -55,6 +57,28 @@ pub struct MemoryOp<F> {
|
||||
pub value: [F; 8],
|
||||
}
|
||||
|
||||
impl<F: Field> MemoryOp<F> {
|
||||
/// Generate a row for a given memory operation. Note that this does not generate columns which
|
||||
/// depend on the next operation, such as `CONTEXT_FIRST_CHANGE`; those are generated later.
|
||||
/// It also does not generate columns such as `COUNTER`, which are generated later, after the
|
||||
/// trace has been transposed into column-major form.
|
||||
fn to_row(&self) -> [F; NUM_COLUMNS] {
|
||||
let mut row = [F::ZERO; NUM_COLUMNS];
|
||||
if let Some(channel) = self.channel_index {
|
||||
row[is_channel(channel)] = F::ONE;
|
||||
}
|
||||
row[TIMESTAMP] = F::from_canonical_usize(self.timestamp);
|
||||
row[IS_READ] = F::from_bool(self.is_read);
|
||||
row[ADDR_CONTEXT] = F::from_canonical_usize(self.context);
|
||||
row[ADDR_SEGMENT] = F::from_canonical_usize(self.segment);
|
||||
row[ADDR_VIRTUAL] = F::from_canonical_usize(self.virt);
|
||||
for j in 0..VALUE_LIMBS {
|
||||
row[value_limb(j)] = self.value[j];
|
||||
}
|
||||
row
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_random_memory_ops<F: RichField, R: Rng>(
|
||||
num_ops: usize,
|
||||
rng: &mut R,
|
||||
@ -111,7 +135,7 @@ pub fn generate_random_memory_ops<F: RichField, R: Rng>(
|
||||
|
||||
let timestamp = clock * NUM_CHANNELS + channel_index;
|
||||
memory_ops.push(MemoryOp {
|
||||
channel_index,
|
||||
channel_index: Some(channel_index),
|
||||
timestamp,
|
||||
is_read,
|
||||
context,
|
||||
@ -128,171 +152,139 @@ pub fn generate_random_memory_ops<F: RichField, R: Rng>(
|
||||
memory_ops
|
||||
}
|
||||
|
||||
pub fn generate_first_change_flags<F: RichField>(
|
||||
context: &[F],
|
||||
segment: &[F],
|
||||
virtuals: &[F],
|
||||
) -> (Vec<F>, Vec<F>, Vec<F>) {
|
||||
let num_ops = context.len();
|
||||
let mut context_first_change = Vec::with_capacity(num_ops);
|
||||
let mut segment_first_change = Vec::with_capacity(num_ops);
|
||||
let mut virtual_first_change = Vec::with_capacity(num_ops);
|
||||
for idx in 0..num_ops - 1 {
|
||||
let this_context_first_change = context[idx] != context[idx + 1];
|
||||
let this_segment_first_change =
|
||||
segment[idx] != segment[idx + 1] && !this_context_first_change;
|
||||
let this_virtual_first_change = virtuals[idx] != virtuals[idx + 1]
|
||||
&& !this_segment_first_change
|
||||
&& !this_context_first_change;
|
||||
|
||||
context_first_change.push(F::from_bool(this_context_first_change));
|
||||
segment_first_change.push(F::from_bool(this_segment_first_change));
|
||||
virtual_first_change.push(F::from_bool(this_virtual_first_change));
|
||||
}
|
||||
|
||||
context_first_change.push(F::ZERO);
|
||||
segment_first_change.push(F::ZERO);
|
||||
virtual_first_change.push(F::ZERO);
|
||||
|
||||
(
|
||||
context_first_change,
|
||||
segment_first_change,
|
||||
virtual_first_change,
|
||||
)
|
||||
fn get_max_range_check<F: Field>(memory_ops: &[MemoryOp<F>]) -> usize {
|
||||
memory_ops
|
||||
.iter()
|
||||
.tuple_windows()
|
||||
.map(|(curr, next)| {
|
||||
if curr.context != next.context {
|
||||
next.context - curr.context - 1
|
||||
} else if curr.segment != next.segment {
|
||||
next.segment - curr.segment - 1
|
||||
} else if curr.virt != next.virt {
|
||||
next.virt - curr.virt - 1
|
||||
} else {
|
||||
next.timestamp - curr.timestamp - 1
|
||||
}
|
||||
})
|
||||
.max()
|
||||
.unwrap_or(0)
|
||||
}
|
||||
|
||||
pub fn generate_range_check_value<F: RichField>(
|
||||
context: &[F],
|
||||
segment: &[F],
|
||||
virtuals: &[F],
|
||||
timestamp: &[F],
|
||||
context_first_change: &[F],
|
||||
segment_first_change: &[F],
|
||||
virtual_first_change: &[F],
|
||||
) -> (Vec<F>, usize) {
|
||||
let num_ops = context.len();
|
||||
let mut range_check = Vec::new();
|
||||
|
||||
/// Generates the `_FIRST_CHANGE` columns and the `RANGE_CHECK` column in the trace.
|
||||
pub fn generate_first_change_flags_and_rc<F: RichField>(trace_rows: &mut [[F; NUM_COLUMNS]]) {
|
||||
let num_ops = trace_rows.len();
|
||||
for idx in 0..num_ops - 1 {
|
||||
let this_address_unchanged = F::ONE
|
||||
- context_first_change[idx]
|
||||
- segment_first_change[idx]
|
||||
- virtual_first_change[idx];
|
||||
range_check.push(
|
||||
context_first_change[idx] * (context[idx + 1] - context[idx] - F::ONE)
|
||||
+ segment_first_change[idx] * (segment[idx + 1] - segment[idx] - F::ONE)
|
||||
+ virtual_first_change[idx] * (virtuals[idx + 1] - virtuals[idx] - F::ONE)
|
||||
+ this_address_unchanged * (timestamp[idx + 1] - timestamp[idx] - F::ONE),
|
||||
);
|
||||
let row = trace_rows[idx].as_slice();
|
||||
let next_row = trace_rows[idx + 1].as_slice();
|
||||
|
||||
let context = row[ADDR_CONTEXT];
|
||||
let segment = row[ADDR_SEGMENT];
|
||||
let virt = row[ADDR_VIRTUAL];
|
||||
let timestamp = row[TIMESTAMP];
|
||||
let next_context = next_row[ADDR_CONTEXT];
|
||||
let next_segment = next_row[ADDR_SEGMENT];
|
||||
let next_virt = next_row[ADDR_VIRTUAL];
|
||||
let next_timestamp = next_row[TIMESTAMP];
|
||||
|
||||
let context_changed = context != next_context;
|
||||
let segment_changed = segment != next_segment;
|
||||
let virtual_changed = virt != next_virt;
|
||||
|
||||
let context_first_change = context_changed;
|
||||
let segment_first_change = segment_changed && !context_first_change;
|
||||
let virtual_first_change =
|
||||
virtual_changed && !segment_first_change && !context_first_change;
|
||||
|
||||
let row = trace_rows[idx].as_mut_slice();
|
||||
row[CONTEXT_FIRST_CHANGE] = F::from_bool(context_first_change);
|
||||
row[SEGMENT_FIRST_CHANGE] = F::from_bool(segment_first_change);
|
||||
row[VIRTUAL_FIRST_CHANGE] = F::from_bool(virtual_first_change);
|
||||
|
||||
row[RANGE_CHECK] = if context_first_change {
|
||||
next_context - context - F::ONE
|
||||
} else if segment_first_change {
|
||||
next_segment - segment - F::ONE
|
||||
} else if virtual_first_change {
|
||||
next_virt - virt - F::ONE
|
||||
} else {
|
||||
next_timestamp - timestamp - F::ONE
|
||||
};
|
||||
}
|
||||
range_check.push(F::ZERO);
|
||||
|
||||
let max_diff = range_check.iter().map(F::to_canonical_u64).max().unwrap() as usize;
|
||||
|
||||
(range_check, max_diff)
|
||||
}
|
||||
|
||||
impl<F: RichField + Extendable<D>, const D: usize> MemoryStark<F, D> {
|
||||
pub(crate) fn generate_trace_rows(
|
||||
&self,
|
||||
mut memory_ops: Vec<MemoryOp<F>>,
|
||||
) -> Vec<[F; NUM_COLUMNS]> {
|
||||
/// Generate most of the trace rows. Excludes a few columns like `COUNTER`, which are generated
|
||||
/// later, after transposing to column-major form.
|
||||
fn generate_trace_row_major(&self, mut memory_ops: Vec<MemoryOp<F>>) -> Vec<[F; NUM_COLUMNS]> {
|
||||
memory_ops.sort_by_key(|op| (op.context, op.segment, op.virt, op.timestamp));
|
||||
|
||||
let num_ops = memory_ops.len();
|
||||
Self::pad_memory_ops(&mut memory_ops);
|
||||
|
||||
let mut trace_cols = [(); NUM_COLUMNS].map(|_| vec![F::ZERO; num_ops]);
|
||||
for i in 0..num_ops {
|
||||
let MemoryOp {
|
||||
channel_index,
|
||||
timestamp,
|
||||
is_read,
|
||||
context,
|
||||
segment,
|
||||
virt,
|
||||
value,
|
||||
} = memory_ops[i];
|
||||
trace_cols[is_channel(channel_index)][i] = F::ONE;
|
||||
trace_cols[TIMESTAMP][i] = F::from_canonical_usize(timestamp);
|
||||
trace_cols[IS_READ][i] = F::from_bool(is_read);
|
||||
trace_cols[ADDR_CONTEXT][i] = F::from_canonical_usize(context);
|
||||
trace_cols[ADDR_SEGMENT][i] = F::from_canonical_usize(segment);
|
||||
trace_cols[ADDR_VIRTUAL][i] = F::from_canonical_usize(virt);
|
||||
for j in 0..8 {
|
||||
trace_cols[value_limb(j)][i] = value[j];
|
||||
}
|
||||
}
|
||||
|
||||
self.generate_memory(&mut trace_cols);
|
||||
|
||||
// The number of rows may have changed, if the range check required padding.
|
||||
let num_ops = trace_cols[0].len();
|
||||
|
||||
let mut trace_rows = vec![[F::ZERO; NUM_COLUMNS]; num_ops];
|
||||
for (i, col) in trace_cols.iter().enumerate() {
|
||||
for (j, &val) in col.iter().enumerate() {
|
||||
trace_rows[j][i] = val;
|
||||
}
|
||||
}
|
||||
let mut trace_rows = memory_ops
|
||||
.into_par_iter()
|
||||
.map(|op| op.to_row())
|
||||
.collect::<Vec<_>>();
|
||||
generate_first_change_flags_and_rc(trace_rows.as_mut_slice());
|
||||
trace_rows
|
||||
}
|
||||
|
||||
fn generate_memory(&self, trace_cols: &mut [Vec<F>]) {
|
||||
let num_trace_rows = trace_cols[0].len();
|
||||
|
||||
let timestamp = &trace_cols[TIMESTAMP];
|
||||
let context = &trace_cols[ADDR_CONTEXT];
|
||||
let segment = &trace_cols[ADDR_SEGMENT];
|
||||
let virtuals = &trace_cols[ADDR_VIRTUAL];
|
||||
|
||||
let (context_first_change, segment_first_change, virtual_first_change) =
|
||||
generate_first_change_flags(context, segment, virtuals);
|
||||
|
||||
let (range_check_value, max_diff) = generate_range_check_value(
|
||||
context,
|
||||
segment,
|
||||
virtuals,
|
||||
timestamp,
|
||||
&context_first_change,
|
||||
&segment_first_change,
|
||||
&virtual_first_change,
|
||||
);
|
||||
let to_pad_to = (max_diff + 1).max(num_trace_rows).next_power_of_two();
|
||||
let to_pad = to_pad_to - num_trace_rows;
|
||||
|
||||
trace_cols[CONTEXT_FIRST_CHANGE] = context_first_change;
|
||||
trace_cols[SEGMENT_FIRST_CHANGE] = segment_first_change;
|
||||
trace_cols[VIRTUAL_FIRST_CHANGE] = virtual_first_change;
|
||||
|
||||
trace_cols[RANGE_CHECK] = range_check_value;
|
||||
|
||||
for col in COLUMNS_TO_PAD {
|
||||
trace_cols[col].splice(0..0, vec![F::ZERO; to_pad]);
|
||||
}
|
||||
|
||||
trace_cols[COUNTER] = (0..to_pad_to).map(|i| F::from_canonical_usize(i)).collect();
|
||||
/// Generates the `COUNTER`, `RANGE_CHECK_PERMUTED` and `COUNTER_PERMUTED` columns, given a
|
||||
/// trace in column-major form.
|
||||
fn generate_trace_col_major(trace_col_vecs: &mut [Vec<F>]) {
|
||||
let height = trace_col_vecs[0].len();
|
||||
trace_col_vecs[COUNTER] = (0..height).map(|i| F::from_canonical_usize(i)).collect();
|
||||
|
||||
let (permuted_inputs, permuted_table) =
|
||||
permuted_cols(&trace_cols[RANGE_CHECK], &trace_cols[COUNTER]);
|
||||
trace_cols[RANGE_CHECK_PERMUTED] = permuted_inputs;
|
||||
trace_cols[COUNTER_PERMUTED] = permuted_table;
|
||||
permuted_cols(&trace_col_vecs[RANGE_CHECK], &trace_col_vecs[COUNTER]);
|
||||
trace_col_vecs[RANGE_CHECK_PERMUTED] = permuted_inputs;
|
||||
trace_col_vecs[COUNTER_PERMUTED] = permuted_table;
|
||||
}
|
||||
|
||||
fn pad_memory_ops(memory_ops: &mut Vec<MemoryOp<F>>) {
|
||||
let num_ops = memory_ops.len();
|
||||
let max_range_check = get_max_range_check(memory_ops);
|
||||
let num_ops_padded = num_ops.max(max_range_check + 1).next_power_of_two();
|
||||
let to_pad = num_ops_padded - num_ops;
|
||||
|
||||
let last_op = memory_ops.last().expect("No memory ops?").clone();
|
||||
|
||||
// We essentially repeat the last operation until our operation list has the desired size,
|
||||
// with a few changes:
|
||||
// - We change its channel to `None` to indicate that this is a dummy operation.
|
||||
// - We increment its timestamp in order to pass the ordering check.
|
||||
// - We make sure it's a read, sine dummy operations must be reads.
|
||||
for i in 0..to_pad {
|
||||
memory_ops.push(MemoryOp {
|
||||
channel_index: None,
|
||||
timestamp: last_op.timestamp + i + 1,
|
||||
is_read: true,
|
||||
..last_op
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn generate_trace(&self, memory_ops: Vec<MemoryOp<F>>) -> Vec<PolynomialValues<F>> {
|
||||
let mut timing = TimingTree::new("generate trace", log::Level::Debug);
|
||||
|
||||
// Generate the witness.
|
||||
// Generate most of the trace in row-major form.
|
||||
let trace_rows = timed!(
|
||||
&mut timing,
|
||||
"generate trace rows",
|
||||
self.generate_trace_rows(memory_ops)
|
||||
self.generate_trace_row_major(memory_ops)
|
||||
);
|
||||
let trace_row_vecs: Vec<_> = trace_rows.into_iter().map(|row| row.to_vec()).collect();
|
||||
|
||||
let trace_polys = timed!(
|
||||
&mut timing,
|
||||
"convert to PolynomialValues",
|
||||
trace_rows_to_poly_values(trace_rows)
|
||||
);
|
||||
// Transpose to column-major form.
|
||||
let mut trace_col_vecs = transpose(&trace_row_vecs);
|
||||
|
||||
// A few final generation steps, which work better in column-major form.
|
||||
Self::generate_trace_col_major(&mut trace_col_vecs);
|
||||
|
||||
let trace_polys = trace_col_vecs
|
||||
.into_iter()
|
||||
.map(|column| PolynomialValues::new(column))
|
||||
.collect();
|
||||
|
||||
timing.print();
|
||||
trace_polys
|
||||
@ -326,11 +318,23 @@ impl<F: RichField + Extendable<D>, const D: usize> Stark<F, D> for MemoryStark<F
|
||||
let next_addr_virtual = vars.next_values[ADDR_VIRTUAL];
|
||||
let next_values: Vec<_> = (0..8).map(|i| vars.next_values[value_limb(i)]).collect();
|
||||
|
||||
// Indicator that this is a real row, not a row of padding.
|
||||
// TODO: enforce that all padding is at the beginning.
|
||||
let valid_row: P = (0..NUM_CHANNELS)
|
||||
// Each `is_channel` value must be 0 or 1.
|
||||
for c in 0..NUM_CHANNELS {
|
||||
let is_channel = vars.local_values[is_channel(c)];
|
||||
yield_constr.constraint(is_channel * (is_channel - P::ONES));
|
||||
}
|
||||
|
||||
// The sum of `is_channel` flags, `has_channel`, must also be 0 or 1.
|
||||
let has_channel: P = (0..NUM_CHANNELS)
|
||||
.map(|c| vars.local_values[is_channel(c)])
|
||||
.sum();
|
||||
yield_constr.constraint(has_channel * (has_channel - P::ONES));
|
||||
|
||||
// If this is a dummy row (with no channel), it must be a read. This means the prover can
|
||||
// insert reads which never appear in the CPU trace (which are harmless), but not writes.
|
||||
let is_dummy = P::ONES - has_channel;
|
||||
let is_write = P::ONES - vars.local_values[IS_READ];
|
||||
yield_constr.constraint(is_dummy * is_write);
|
||||
|
||||
let context_first_change = vars.local_values[CONTEXT_FIRST_CHANGE];
|
||||
let segment_first_change = vars.local_values[SEGMENT_FIRST_CHANGE];
|
||||
@ -358,21 +362,15 @@ impl<F: RichField + Extendable<D>, const D: usize> Stark<F, D> for MemoryStark<F
|
||||
.constraint_transition(virtual_first_change * (next_addr_context - addr_context));
|
||||
yield_constr
|
||||
.constraint_transition(virtual_first_change * (next_addr_segment - addr_segment));
|
||||
yield_constr.constraint_transition(
|
||||
valid_row * address_unchanged * (next_addr_context - addr_context),
|
||||
);
|
||||
yield_constr.constraint_transition(
|
||||
valid_row * address_unchanged * (next_addr_segment - addr_segment),
|
||||
);
|
||||
yield_constr.constraint_transition(
|
||||
valid_row * address_unchanged * (next_addr_virtual - addr_virtual),
|
||||
);
|
||||
yield_constr.constraint_transition(address_unchanged * (next_addr_context - addr_context));
|
||||
yield_constr.constraint_transition(address_unchanged * (next_addr_segment - addr_segment));
|
||||
yield_constr.constraint_transition(address_unchanged * (next_addr_virtual - addr_virtual));
|
||||
|
||||
// Third set of ordering constraints: range-check difference in the column that should be increasing.
|
||||
let computed_range_check = context_first_change * (next_addr_context - addr_context - one)
|
||||
+ segment_first_change * (next_addr_segment - addr_segment - one)
|
||||
+ virtual_first_change * (next_addr_virtual - addr_virtual - one)
|
||||
+ valid_row * address_unchanged * (next_timestamp - timestamp - one);
|
||||
+ address_unchanged * (next_timestamp - timestamp - one);
|
||||
yield_constr.constraint_transition(range_check - computed_range_check);
|
||||
|
||||
// Enumerate purportedly-ordered log.
|
||||
@ -405,12 +403,26 @@ impl<F: RichField + Extendable<D>, const D: usize> Stark<F, D> for MemoryStark<F
|
||||
let next_is_read = vars.next_values[IS_READ];
|
||||
let next_timestamp = vars.next_values[TIMESTAMP];
|
||||
|
||||
// Indicator that this is a real row, not a row of padding.
|
||||
let mut valid_row = vars.local_values[is_channel(0)];
|
||||
for c in 1..NUM_CHANNELS {
|
||||
valid_row = builder.add_extension(valid_row, vars.local_values[is_channel(c)]);
|
||||
// Each `is_channel` value must be 0 or 1.
|
||||
for c in 0..NUM_CHANNELS {
|
||||
let is_channel = vars.local_values[is_channel(c)];
|
||||
let constraint = builder.mul_sub_extension(is_channel, is_channel, is_channel);
|
||||
yield_constr.constraint(builder, constraint);
|
||||
}
|
||||
|
||||
// The sum of `is_channel` flags, `has_channel`, must also be 0 or 1.
|
||||
let has_channel =
|
||||
builder.add_many_extension((0..NUM_CHANNELS).map(|c| vars.local_values[is_channel(c)]));
|
||||
let has_channel_bool = builder.mul_sub_extension(has_channel, has_channel, has_channel);
|
||||
yield_constr.constraint(builder, has_channel_bool);
|
||||
|
||||
// If this is a dummy row (with no channel), it must be a read. This means the prover can
|
||||
// insert reads which never appear in the CPU trace (which are harmless), but not writes.
|
||||
let is_dummy = builder.sub_extension(one, has_channel);
|
||||
let is_write = builder.sub_extension(one, vars.local_values[IS_READ]);
|
||||
let is_dummy_write = builder.mul_extension(is_dummy, is_write);
|
||||
yield_constr.constraint(builder, is_dummy_write);
|
||||
|
||||
let context_first_change = vars.local_values[CONTEXT_FIRST_CHANGE];
|
||||
let segment_first_change = vars.local_values[SEGMENT_FIRST_CHANGE];
|
||||
let virtual_first_change = vars.local_values[VIRTUAL_FIRST_CHANGE];
|
||||
@ -455,17 +467,11 @@ impl<F: RichField + Extendable<D>, const D: usize> Stark<F, D> for MemoryStark<F
|
||||
builder.mul_extension(virtual_first_change, addr_segment_diff);
|
||||
yield_constr.constraint_transition(builder, virtual_first_change_check_2);
|
||||
let address_unchanged_check_1 = builder.mul_extension(address_unchanged, addr_context_diff);
|
||||
let address_unchanged_check_1_valid =
|
||||
builder.mul_extension(valid_row, address_unchanged_check_1);
|
||||
yield_constr.constraint_transition(builder, address_unchanged_check_1_valid);
|
||||
yield_constr.constraint_transition(builder, address_unchanged_check_1);
|
||||
let address_unchanged_check_2 = builder.mul_extension(address_unchanged, addr_segment_diff);
|
||||
let address_unchanged_check_2_valid =
|
||||
builder.mul_extension(valid_row, address_unchanged_check_2);
|
||||
yield_constr.constraint_transition(builder, address_unchanged_check_2_valid);
|
||||
yield_constr.constraint_transition(builder, address_unchanged_check_2);
|
||||
let address_unchanged_check_3 = builder.mul_extension(address_unchanged, addr_virtual_diff);
|
||||
let address_unchanged_check_3_valid =
|
||||
builder.mul_extension(valid_row, address_unchanged_check_3);
|
||||
yield_constr.constraint_transition(builder, address_unchanged_check_3_valid);
|
||||
yield_constr.constraint_transition(builder, address_unchanged_check_3);
|
||||
|
||||
// Third set of ordering constraints: range-check difference in the column that should be increasing.
|
||||
let context_diff = {
|
||||
@ -488,7 +494,6 @@ impl<F: RichField + Extendable<D>, const D: usize> Stark<F, D> for MemoryStark<F
|
||||
builder.sub_extension(diff, one)
|
||||
};
|
||||
let timestamp_range_check = builder.mul_extension(address_unchanged, timestamp_diff);
|
||||
let timestamp_range_check = builder.mul_extension(valid_row, timestamp_range_check);
|
||||
|
||||
let computed_range_check = {
|
||||
let mut sum = builder.add_extension(context_range_check, segment_range_check);
|
||||
|
||||
@ -492,14 +492,19 @@ fn check_constraints<'a, F, C, S, const D: usize>(
|
||||
|
||||
let subgroup = F::two_adic_subgroup(degree_bits + rate_bits);
|
||||
|
||||
// Retrieve the polynomials values at index `i`.
|
||||
let get_comm_values = |comm: &PolynomialBatch<F, C, D>, i| -> Vec<F> {
|
||||
comm.polynomials
|
||||
.iter()
|
||||
.map(|poly| poly.eval(subgroup[i])) // O(n^2) FTW
|
||||
.collect()
|
||||
// Get the evaluations of a batch of polynomials over our subgroup.
|
||||
let get_subgroup_evals = |comm: &PolynomialBatch<F, C, D>| -> Vec<Vec<F>> {
|
||||
let values = comm
|
||||
.polynomials
|
||||
.par_iter()
|
||||
.map(|coeffs| coeffs.clone().fft().values)
|
||||
.collect::<Vec<_>>();
|
||||
transpose(&values)
|
||||
};
|
||||
|
||||
let trace_subgroup_evals = get_subgroup_evals(trace_commitment);
|
||||
let permutation_ctl_zs_subgroup_evals = get_subgroup_evals(permutation_ctl_zs_commitment);
|
||||
|
||||
// Last element of the subgroup.
|
||||
let last = F::primitive_root_of_unity(degree_bits).inverse();
|
||||
|
||||
@ -519,19 +524,14 @@ fn check_constraints<'a, F, C, S, const D: usize>(
|
||||
lagrange_basis_last,
|
||||
);
|
||||
let vars = StarkEvaluationVars {
|
||||
local_values: &get_comm_values(trace_commitment, i).try_into().unwrap(),
|
||||
next_values: &get_comm_values(trace_commitment, i_next)
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
local_values: trace_subgroup_evals[i].as_slice().try_into().unwrap(),
|
||||
next_values: trace_subgroup_evals[i_next].as_slice().try_into().unwrap(),
|
||||
public_inputs: &public_inputs,
|
||||
};
|
||||
let permutation_check_vars =
|
||||
permutation_challenges.map(|permutation_challenge_sets| PermutationCheckVars {
|
||||
local_zs: get_comm_values(permutation_ctl_zs_commitment, i)
|
||||
[..num_permutation_zs]
|
||||
.to_vec(),
|
||||
next_zs: get_comm_values(permutation_ctl_zs_commitment, i_next)
|
||||
[..num_permutation_zs]
|
||||
local_zs: permutation_ctl_zs_subgroup_evals[i][..num_permutation_zs].to_vec(),
|
||||
next_zs: permutation_ctl_zs_subgroup_evals[i_next][..num_permutation_zs]
|
||||
.to_vec(),
|
||||
permutation_challenge_sets: permutation_challenge_sets.to_vec(),
|
||||
});
|
||||
@ -542,10 +542,8 @@ fn check_constraints<'a, F, C, S, const D: usize>(
|
||||
.enumerate()
|
||||
.map(
|
||||
|(iii, (_, columns, filter_column))| CtlCheckVars::<F, F, F, 1> {
|
||||
local_z: get_comm_values(permutation_ctl_zs_commitment, i)
|
||||
[num_permutation_zs + iii],
|
||||
next_z: get_comm_values(permutation_ctl_zs_commitment, i_next)
|
||||
[num_permutation_zs + iii],
|
||||
local_z: permutation_ctl_zs_subgroup_evals[i][num_permutation_zs + iii],
|
||||
next_z: permutation_ctl_zs_subgroup_evals[i_next][num_permutation_zs + iii],
|
||||
challenges: ctl_data.challenges.challenges[iii % config.num_challenges],
|
||||
columns,
|
||||
filter_column,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user