plonky2/evm/src/cpu/cpu_stark.rs

319 lines
12 KiB
Rust
Raw Normal View History

use std::borrow::{Borrow, BorrowMut};
use std::iter::repeat;
2022-05-18 09:22:58 +02:00
use std::marker::PhantomData;
2022-06-23 14:36:14 -07:00
use itertools::Itertools;
use plonky2::field::extension::{Extendable, FieldExtension};
use plonky2::field::packed::PackedField;
use plonky2::field::types::Field;
2022-05-18 09:22:58 +02:00
use plonky2::hash::hash_types::RichField;
use crate::all_stark::Table;
2022-05-18 09:22:58 +02:00
use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer};
use crate::cpu::columns::{CpuColumnsView, COL_MAP, NUM_CPU_COLUMNS};
use crate::cpu::membus::NUM_GP_CHANNELS;
use crate::cpu::{
bootstrap_kernel, contextops, control_flow, decode, dup_swap, gas, jumps, membus, memio,
modfp254, pc, push0, shift, simple_logic, stack, stack_bounds, syscalls_exceptions,
};
use crate::cross_table_lookup::{Column, TableWithColumns};
use crate::memory::segments::Segment;
use crate::memory::{NUM_CHANNELS, VALUE_LIMBS};
2022-05-18 09:22:58 +02:00
use crate::stark::Stark;
use crate::vars::{StarkEvaluationTargets, StarkEvaluationVars};
pub fn ctl_data_keccak_sponge<F: Field>() -> Vec<Column<F>> {
// When executing KECCAK_GENERAL, the GP memory channels are used as follows:
// GP channel 0: stack[-1] = context
// GP channel 1: stack[-2] = segment
// GP channel 2: stack[-3] = virt
// GP channel 3: stack[-4] = len
// GP channel 4: pushed = outputs
let context = Column::single(COL_MAP.mem_channels[0].value[0]);
let segment = Column::single(COL_MAP.mem_channels[1].value[0]);
let virt = Column::single(COL_MAP.mem_channels[2].value[0]);
let len = Column::single(COL_MAP.mem_channels[3].value[0]);
2022-08-14 16:36:07 -07:00
let num_channels = F::from_canonical_usize(NUM_CHANNELS);
let timestamp = Column::linear_combination([(COL_MAP.clock, num_channels)]);
2022-08-14 16:36:07 -07:00
let mut cols = vec![context, segment, virt, len, timestamp];
cols.extend(COL_MAP.mem_channels[4].value.map(Column::single));
cols
}
pub fn ctl_filter_keccak_sponge<F: Field>() -> Column<F> {
Column::single(COL_MAP.is_keccak_sponge)
2022-08-14 16:36:07 -07:00
}
/// Create the vector of Columns corresponding to the two inputs and
/// one output of a binary operation.
fn ctl_data_binops<F: Field>(ops: &[usize]) -> Vec<Column<F>> {
let mut res = Column::singles(ops).collect_vec();
res.extend(Column::singles(COL_MAP.mem_channels[0].value));
res.extend(Column::singles(COL_MAP.mem_channels[1].value));
res.extend(Column::singles(
COL_MAP.mem_channels[NUM_GP_CHANNELS - 1].value,
));
res
}
/// Create the vector of Columns corresponding to the three inputs and
/// one output of a ternary operation. By default, ternary operations use
/// the first three memory channels, and the last one for the result (binary
/// operations do not use the third inputs).
///
/// Shift operations are different, as they are simulated with `MUL` or `DIV`
/// on the arithmetic side. We first convert the shift into the multiplicand
/// (in case of `SHL`) or the divisor (in case of `SHR`), making the first memory
/// channel not directly usable. We overcome this by adding an offset of 1 in
/// case of shift operations, which will skip the first memory channel and use the
/// next three as ternary inputs. Because both `MUL` and `DIV` are binary operations,
/// the last memory channel used for the inputs will be safely ignored.
fn ctl_data_ternops<F: Field>(ops: &[usize], is_shift: bool) -> Vec<Column<F>> {
let offset = is_shift as usize;
let mut res = Column::singles(ops).collect_vec();
res.extend(Column::singles(COL_MAP.mem_channels[offset].value));
res.extend(Column::singles(COL_MAP.mem_channels[offset + 1].value));
res.extend(Column::singles(COL_MAP.mem_channels[offset + 2].value));
res.extend(Column::singles(
COL_MAP.mem_channels[NUM_GP_CHANNELS - 1].value,
));
res
}
pub fn ctl_data_logic<F: Field>() -> Vec<Column<F>> {
2023-08-07 15:04:03 -04:00
ctl_data_binops(&[COL_MAP.op.and_or, COL_MAP.op.xor])
}
pub fn ctl_filter_logic<F: Field>() -> Column<F> {
2023-08-07 15:04:03 -04:00
Column::sum([COL_MAP.op.and_or, COL_MAP.op.xor])
}
pub fn ctl_arithmetic_base_rows<F: Field>() -> TableWithColumns<F> {
const OPS: [usize; 14] = [
COL_MAP.op.add,
COL_MAP.op.sub,
COL_MAP.op.mul,
COL_MAP.op.lt,
COL_MAP.op.gt,
COL_MAP.op.addfp254,
COL_MAP.op.mulfp254,
COL_MAP.op.subfp254,
COL_MAP.op.addmod,
COL_MAP.op.mulmod,
COL_MAP.op.submod,
COL_MAP.op.div,
COL_MAP.op.mod_,
COL_MAP.op.byte,
];
// Create the CPU Table whose columns are those with the three
// inputs and one output of the ternary operations listed in `ops`
// (also `ops` is used as the operation filter). The list of
// operations includes binary operations which will simply ignore
// the third input.
TableWithColumns::new(
Table::Cpu,
ctl_data_ternops(&OPS, false),
Some(Column::sum(OPS)),
)
}
pub fn ctl_arithmetic_shift_rows<F: Field>() -> TableWithColumns<F> {
const OPS: [usize; 14] = [
COL_MAP.op.add,
COL_MAP.op.sub,
// SHL is interpreted as MUL on the arithmetic side
COL_MAP.op.shl,
COL_MAP.op.lt,
COL_MAP.op.gt,
COL_MAP.op.addfp254,
COL_MAP.op.mulfp254,
COL_MAP.op.subfp254,
COL_MAP.op.addmod,
COL_MAP.op.mulmod,
COL_MAP.op.submod,
// SHR is interpreted as DIV on the arithmetic side
COL_MAP.op.shr,
COL_MAP.op.mod_,
COL_MAP.op.byte,
];
// Create the CPU Table whose columns are those with the three
// inputs and one output of the ternary operations listed in `ops`
// (also `ops` is used as the operation filter). The list of
// operations includes binary operations which will simply ignore
// the third input.
TableWithColumns::new(
Table::Cpu,
ctl_data_ternops(&OPS, true),
Some(Column::sum([COL_MAP.op.shl, COL_MAP.op.shr])),
)
}
pub const MEM_CODE_CHANNEL_IDX: usize = 0;
pub const MEM_GP_CHANNELS_IDX_START: usize = MEM_CODE_CHANNEL_IDX + 1;
/// Make the time/channel column for memory lookups.
fn mem_time_and_channel<F: Field>(channel: usize) -> Column<F> {
let scalar = F::from_canonical_usize(NUM_CHANNELS);
let addend = F::from_canonical_usize(channel);
Column::linear_combination_with_constant([(COL_MAP.clock, scalar)], addend)
}
pub fn ctl_data_code_memory<F: Field>() -> Vec<Column<F>> {
let mut cols = vec![
Column::constant(F::ONE), // is_read
Column::single(COL_MAP.code_context), // addr_context
Column::constant(F::from_canonical_u64(Segment::Code as u64)), // addr_segment
Column::single(COL_MAP.program_counter), // addr_virtual
];
// Low limb of the value matches the opcode bits
cols.push(Column::le_bits(COL_MAP.opcode_bits));
// High limbs of the value are all zero.
cols.extend(repeat(Column::constant(F::ZERO)).take(VALUE_LIMBS - 1));
cols.push(mem_time_and_channel(MEM_CODE_CHANNEL_IDX));
cols
}
pub fn ctl_data_gp_memory<F: Field>(channel: usize) -> Vec<Column<F>> {
let channel_map = COL_MAP.mem_channels[channel];
let mut cols: Vec<_> = Column::singles([
channel_map.is_read,
channel_map.addr_context,
channel_map.addr_segment,
channel_map.addr_virtual,
2022-06-23 14:36:14 -07:00
])
.collect();
cols.extend(Column::singles(channel_map.value));
2022-07-07 09:27:50 -07:00
cols.push(mem_time_and_channel(MEM_GP_CHANNELS_IDX_START + channel));
2022-07-07 09:52:38 -07:00
2022-06-23 14:36:14 -07:00
cols
}
pub fn ctl_filter_code_memory<F: Field>() -> Column<F> {
Column::single(COL_MAP.is_cpu_cycle)
}
pub fn ctl_filter_gp_memory<F: Field>(channel: usize) -> Column<F> {
Column::single(COL_MAP.mem_channels[channel].used)
2022-06-23 14:36:14 -07:00
}
#[derive(Copy, Clone, Default)]
2022-05-18 09:22:58 +02:00
pub struct CpuStark<F, const D: usize> {
pub f: PhantomData<F>,
}
impl<F: RichField, const D: usize> CpuStark<F, D> {
2022-12-03 12:02:51 -08:00
// TODO: Remove?
pub fn generate(&self, local_values: &mut [F; NUM_CPU_COLUMNS]) {
let local_values: &mut CpuColumnsView<_> = local_values.borrow_mut();
decode::generate(local_values);
membus::generate(local_values);
}
}
2022-05-18 09:22:58 +02:00
impl<F: RichField + Extendable<D>, const D: usize> Stark<F, D> for CpuStark<F, D> {
const COLUMNS: usize = NUM_CPU_COLUMNS;
2022-05-18 09:22:58 +02:00
fn eval_packed_generic<FE, P, const D2: usize>(
&self,
2022-08-25 12:24:22 -07:00
vars: StarkEvaluationVars<FE, P, { Self::COLUMNS }>,
2022-12-02 22:47:07 -08:00
yield_constr: &mut ConstraintConsumer<P>,
2022-05-18 09:22:58 +02:00
) where
FE: FieldExtension<D2, BaseField = F>,
P: PackedField<Scalar = FE>,
{
2022-12-02 14:49:32 -08:00
let local_values = vars.local_values.borrow();
let next_values = vars.next_values.borrow();
2022-12-02 22:47:07 -08:00
bootstrap_kernel::eval_bootstrap_kernel(vars, yield_constr);
2022-12-11 10:59:14 -08:00
contextops::eval_packed(local_values, next_values, yield_constr);
2022-12-02 22:47:07 -08:00
control_flow::eval_packed_generic(local_values, next_values, yield_constr);
2023-04-07 17:06:57 -04:00
decode::eval_packed_generic(local_values, yield_constr);
2022-12-02 22:47:07 -08:00
dup_swap::eval_packed(local_values, yield_constr);
gas::eval_packed(local_values, next_values, yield_constr);
jumps::eval_packed(local_values, next_values, yield_constr);
2022-12-02 22:47:07 -08:00
membus::eval_packed(local_values, yield_constr);
2022-12-09 10:35:00 -08:00
memio::eval_packed(local_values, yield_constr);
2022-12-02 22:47:07 -08:00
modfp254::eval_packed(local_values, yield_constr);
pc::eval_packed(local_values, yield_constr);
2023-06-13 13:29:30 -07:00
push0::eval_packed(local_values, yield_constr);
2022-12-02 22:47:07 -08:00
shift::eval_packed(local_values, yield_constr);
simple_logic::eval_packed(local_values, yield_constr);
stack::eval_packed(local_values, yield_constr);
2023-04-11 15:13:55 -04:00
stack_bounds::eval_packed(local_values, yield_constr);
syscalls_exceptions::eval_packed(local_values, next_values, yield_constr);
2022-05-18 09:22:58 +02:00
}
fn eval_ext_circuit(
&self,
builder: &mut plonky2::plonk::circuit_builder::CircuitBuilder<F, D>,
2022-08-25 12:24:22 -07:00
vars: StarkEvaluationTargets<D, { Self::COLUMNS }>,
2022-12-02 22:47:07 -08:00
yield_constr: &mut RecursiveConstraintConsumer<F, D>,
2022-05-18 09:22:58 +02:00
) {
let local_values = vars.local_values.borrow();
let next_values = vars.next_values.borrow();
2022-12-02 22:47:07 -08:00
bootstrap_kernel::eval_bootstrap_kernel_circuit(builder, vars, yield_constr);
2022-12-11 10:59:14 -08:00
contextops::eval_ext_circuit(builder, local_values, next_values, yield_constr);
2022-12-02 22:47:07 -08:00
control_flow::eval_ext_circuit(builder, local_values, next_values, yield_constr);
2023-04-07 17:06:57 -04:00
decode::eval_ext_circuit(builder, local_values, yield_constr);
2022-12-02 22:47:07 -08:00
dup_swap::eval_ext_circuit(builder, local_values, yield_constr);
gas::eval_ext_circuit(builder, local_values, next_values, yield_constr);
jumps::eval_ext_circuit(builder, local_values, next_values, yield_constr);
2022-12-02 22:47:07 -08:00
membus::eval_ext_circuit(builder, local_values, yield_constr);
2022-12-09 10:35:00 -08:00
memio::eval_ext_circuit(builder, local_values, yield_constr);
2022-12-02 22:47:07 -08:00
modfp254::eval_ext_circuit(builder, local_values, yield_constr);
pc::eval_ext_circuit(builder, local_values, yield_constr);
2023-06-13 13:29:30 -07:00
push0::eval_ext_circuit(builder, local_values, yield_constr);
2022-12-02 22:47:07 -08:00
shift::eval_ext_circuit(builder, local_values, yield_constr);
simple_logic::eval_ext_circuit(builder, local_values, yield_constr);
stack::eval_ext_circuit(builder, local_values, yield_constr);
2023-04-11 15:13:55 -04:00
stack_bounds::eval_ext_circuit(builder, local_values, yield_constr);
syscalls_exceptions::eval_ext_circuit(builder, local_values, next_values, yield_constr);
2022-05-18 09:22:58 +02:00
}
fn constraint_degree(&self) -> usize {
3
}
}
2022-05-19 11:10:10 +02:00
#[cfg(test)]
mod tests {
use anyhow::Result;
use plonky2::plonk::config::{GenericConfig, PoseidonGoldilocksConfig};
use crate::cpu::cpu_stark::CpuStark;
use crate::stark_testing::{test_stark_circuit_constraints, test_stark_low_degree};
#[test]
2022-05-20 08:34:25 +02:00
fn test_stark_degree() -> Result<()> {
2022-05-19 11:10:10 +02:00
const D: usize = 2;
type C = PoseidonGoldilocksConfig;
type F = <C as GenericConfig<D>>::F;
type S = CpuStark<F, D>;
let stark = S {
f: Default::default(),
};
2022-05-20 08:34:25 +02:00
test_stark_low_degree(stark)
2022-05-19 11:10:10 +02:00
}
#[test]
fn test_stark_circuit() -> Result<()> {
const D: usize = 2;
type C = PoseidonGoldilocksConfig;
type F = <C as GenericConfig<D>>::F;
type S = CpuStark<F, D>;
let stark = S {
f: Default::default(),
};
test_stark_circuit_constraints::<F, C, S, D>(stark)
}
}