Merge pull request #591 from mir-protocol/evm_generation

Begin work on witness generation and kernel bootstrapping
This commit is contained in:
Daniel Lubarov 2022-07-12 14:34:18 -07:00 committed by GitHub
commit 2507985da7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 525 additions and 133 deletions

View File

@ -18,8 +18,12 @@ pest_derive = "2.1.0"
rayon = "1.5.1"
rand = "0.8.5"
rand_chacha = "0.3.1"
rlp = "0.5.1"
keccak-rust = { git = "https://github.com/npwardberkeley/keccak-rust" }
[dev-dependencies]
hex-literal = "0.3.4"
[features]
asmtools = ["hex"]

View File

@ -23,6 +23,18 @@ pub struct AllStark<F: RichField + Extendable<D>, const D: usize> {
pub cross_table_lookups: Vec<CrossTableLookup<F>>,
}
impl<F: RichField + Extendable<D>, const D: usize> Default for AllStark<F, D> {
fn default() -> Self {
Self {
cpu_stark: CpuStark::default(),
keccak_stark: KeccakStark::default(),
logic_stark: LogicStark::default(),
memory_stark: MemoryStark::default(),
cross_table_lookups: all_cross_table_lookups(),
}
}
}
impl<F: RichField + Extendable<D>, const D: usize> AllStark<F, D> {
pub(crate) fn nums_permutation_zs(&self, config: &StarkConfig) -> Vec<usize> {
let ans = vec![
@ -117,6 +129,7 @@ mod tests {
use std::borrow::BorrowMut;
use anyhow::Result;
use ethereum_types::U256;
use itertools::{izip, Itertools};
use plonky2::field::polynomial::PolynomialValues;
use plonky2::field::types::{Field, PrimeField64};
@ -127,12 +140,12 @@ mod tests {
use plonky2::util::timing::TimingTree;
use rand::{thread_rng, Rng};
use crate::all_stark::{all_cross_table_lookups, AllStark};
use crate::all_stark::AllStark;
use crate::config::StarkConfig;
use crate::cpu::cpu_stark::CpuStark;
use crate::cross_table_lookup::testutils::check_ctls;
use crate::keccak::keccak_stark::{KeccakStark, NUM_INPUTS, NUM_ROUNDS};
use crate::logic::{self, LogicStark};
use crate::logic::{self, LogicStark, Operation};
use crate::memory::memory_stark::{generate_random_memory_ops, MemoryStark};
use crate::memory::NUM_CHANNELS;
use crate::proof::AllProof;
@ -165,32 +178,16 @@ mod tests {
logic_stark: &LogicStark<F, D>,
rng: &mut R,
) -> Vec<PolynomialValues<F>> {
let mut trace_rows = vec![];
for _ in 0..num_rows {
let mut row = [F::ZERO; logic::columns::NUM_COLUMNS];
assert_eq!(logic::PACKED_LIMB_BITS, 16);
for col in logic::columns::INPUT0 {
row[col] = F::from_bool(rng.gen());
}
for col in logic::columns::INPUT1 {
row[col] = F::from_bool(rng.gen());
}
let op: usize = rng.gen_range(0..3);
let op_col = [
logic::columns::IS_AND,
logic::columns::IS_OR,
logic::columns::IS_XOR,
][op];
row[op_col] = F::ONE;
logic_stark.generate(&mut row);
trace_rows.push(row);
}
for _ in num_rows..num_rows.next_power_of_two() {
trace_rows.push([F::ZERO; logic::columns::NUM_COLUMNS])
}
trace_rows_to_poly_values(trace_rows)
let all_ops = [logic::Op::And, logic::Op::Or, logic::Op::Xor];
let ops = (0..num_rows)
.map(|_| {
let op = all_ops[rng.gen_range(0..all_ops.len())];
let input0 = U256(rng.gen());
let input1 = U256(rng.gen());
Operation::new(op, input0, input1)
})
.collect();
logic_stark.generate_trace(ops)
}
fn make_memory_trace<R: Rng>(
@ -239,6 +236,11 @@ mod tests {
.collect();
let mut cpu_trace_rows: Vec<[F; CpuStark::<F, D>::COLUMNS]> = vec![];
let mut bootstrap_row: cpu::columns::CpuColumnsView<F> =
[F::ZERO; CpuStark::<F, D>::COLUMNS].into();
bootstrap_row.is_bootstrap_kernel = F::ONE;
cpu_trace_rows.push(bootstrap_row.into());
for i in 0..num_keccak_perms {
let mut row: cpu::columns::CpuColumnsView<F> =
[F::ZERO; CpuStark::<F, D>::COLUMNS].into();
@ -315,54 +317,38 @@ mod tests {
}
}
}
// Pad to a power of two.
for _ in cpu_trace_rows.len()..cpu_trace_rows.len().next_power_of_two() {
cpu_trace_rows.push([F::ZERO; CpuStark::<F, D>::COLUMNS]);
}
trace_rows_to_poly_values(cpu_trace_rows)
}
fn get_proof(config: &StarkConfig) -> Result<(AllStark<F, D>, AllProof<F, C, D>)> {
let cpu_stark = CpuStark::<F, D> {
f: Default::default(),
};
let all_stark = AllStark::default();
let keccak_stark = KeccakStark::<F, D> {
f: Default::default(),
};
let logic_stark = LogicStark::<F, D> {
f: Default::default(),
};
let num_logic_rows = 62;
let memory_stark = MemoryStark::<F, D> {
f: Default::default(),
};
let num_memory_ops = 1 << 5;
let mut rng = thread_rng();
let num_keccak_perms = 2;
let keccak_trace = make_keccak_trace(num_keccak_perms, &keccak_stark, &mut rng);
let logic_trace = make_logic_trace(num_logic_rows, &logic_stark, &mut rng);
let mem_trace = make_memory_trace(num_memory_ops, &memory_stark, &mut rng);
let keccak_trace = make_keccak_trace(num_keccak_perms, &all_stark.keccak_stark, &mut rng);
let logic_trace = make_logic_trace(num_logic_rows, &all_stark.logic_stark, &mut rng);
let mem_trace = make_memory_trace(num_memory_ops, &all_stark.memory_stark, &mut rng);
let mut memory_trace = mem_trace.0;
let num_memory_ops = mem_trace.1;
let cpu_trace = make_cpu_trace(
num_keccak_perms,
num_logic_rows,
num_memory_ops,
&cpu_stark,
&all_stark.cpu_stark,
&keccak_trace,
&logic_trace,
&mut memory_trace,
);
let all_stark = AllStark {
cpu_stark,
keccak_stark,
logic_stark,
memory_stark,
cross_table_lookups: all_cross_table_lookups(),
};
let traces = vec![cpu_trace, keccak_trace, logic_trace, memory_trace];
check_ctls(&traces, &all_stark.cross_table_lookups);

View File

@ -0,0 +1,76 @@
//! The initial phase of execution, where the kernel code is hashed while being written to memory.
//! The hash is then checked against a precomputed kernel hash.
use itertools::Itertools;
use plonky2::field::extension::Extendable;
use plonky2::field::packed::PackedField;
use plonky2::field::types::Field;
use plonky2::hash::hash_types::RichField;
use plonky2::plonk::circuit_builder::CircuitBuilder;
use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer};
use crate::cpu::columns::{COL_MAP, NUM_CPU_COLUMNS};
use crate::cpu::public_inputs::NUM_PUBLIC_INPUTS;
use crate::generation::state::GenerationState;
use crate::memory;
use crate::memory::segments;
use crate::vars::{StarkEvaluationTargets, StarkEvaluationVars};
pub(crate) fn generate_bootstrap_kernel<F: Field>(state: &mut GenerationState<F>) {
for chunk in &state.kernel.code.clone().into_iter().enumerate().chunks(4) {
for (addr, byte) in chunk {
let mut value = [F::ZERO; memory::VALUE_LIMBS];
value[0] = F::from_canonical_u8(byte);
let channel = addr % memory::NUM_CHANNELS;
state.set_mem_current(channel, segments::CODE, addr, value);
// TODO: Set other registers.
state.commit_cpu_row();
}
}
}
pub(crate) fn eval_bootstrap_kernel<F: Field, P: PackedField<Scalar = F>>(
vars: StarkEvaluationVars<F, P, NUM_CPU_COLUMNS, NUM_PUBLIC_INPUTS>,
yield_constr: &mut ConstraintConsumer<P>,
) {
// IS_BOOTSTRAP_KERNEL must have an init value of 1, a final value of 0, and a delta in {0, -1}.
let local_is_bootstrap = vars.local_values[COL_MAP.is_bootstrap_kernel];
let next_is_bootstrap = vars.next_values[COL_MAP.is_bootstrap_kernel];
yield_constr.constraint_first_row(local_is_bootstrap - P::ONES);
yield_constr.constraint_last_row(local_is_bootstrap);
let delta_is_bootstrap = next_is_bootstrap - local_is_bootstrap;
yield_constr.constraint_transition(delta_is_bootstrap * (delta_is_bootstrap + P::ONES));
// If IS_BOOTSTRAP_KERNEL changed (from 1 to 0), check that the current kernel hash matches a
// precomputed one.
let hash_diff = F::ZERO; // TODO
yield_constr.constraint_transition(delta_is_bootstrap * hash_diff)
}
pub(crate) fn eval_bootstrap_kernel_circuit<F: RichField + Extendable<D>, const D: usize>(
builder: &mut CircuitBuilder<F, D>,
vars: StarkEvaluationTargets<D, NUM_CPU_COLUMNS, NUM_PUBLIC_INPUTS>,
yield_constr: &mut RecursiveConstraintConsumer<F, D>,
) {
let one = builder.one_extension();
// IS_BOOTSTRAP_KERNEL must have an init value of 1, a final value of 0, and a delta in {0, -1}.
let local_is_bootstrap = vars.local_values[COL_MAP.is_bootstrap_kernel];
let next_is_bootstrap = vars.next_values[COL_MAP.is_bootstrap_kernel];
let constraint = builder.sub_extension(local_is_bootstrap, one);
yield_constr.constraint_first_row(builder, constraint);
yield_constr.constraint_last_row(builder, local_is_bootstrap);
let delta_is_bootstrap = builder.sub_extension(next_is_bootstrap, local_is_bootstrap);
let constraint =
builder.mul_add_extension(delta_is_bootstrap, delta_is_bootstrap, delta_is_bootstrap);
yield_constr.constraint_transition(builder, constraint);
// If IS_BOOTSTRAP_KERNEL changed (from 1 to 0), check that the current kernel hash matches a
// precomputed one.
let hash_diff = builder.zero_extension(); // TODO
let constraint = builder.mul_extension(delta_is_bootstrap, hash_diff);
yield_constr.constraint_transition(builder, constraint)
}

View File

@ -9,7 +9,7 @@ use plonky2::hash::hash_types::RichField;
use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer};
use crate::cpu::columns::{CpuColumnsView, COL_MAP, NUM_CPU_COLUMNS};
use crate::cpu::{decode, simple_logic};
use crate::cpu::{bootstrap_kernel, decode, simple_logic};
use crate::cross_table_lookup::Column;
use crate::memory::NUM_CHANNELS;
use crate::stark::Stark;
@ -62,7 +62,7 @@ pub fn ctl_filter_memory<F: Field>(channel: usize) -> Column<F> {
Column::single(COL_MAP.mem_channel_used[channel])
}
#[derive(Copy, Clone)]
#[derive(Copy, Clone, Default)]
pub struct CpuStark<F, const D: usize> {
pub f: PhantomData<F>,
}
@ -88,6 +88,7 @@ impl<F: RichField + Extendable<D>, const D: usize> Stark<F, D> for CpuStark<F, D
P: PackedField<Scalar = FE>,
{
let local_values = vars.local_values.borrow();
bootstrap_kernel::eval_bootstrap_kernel(vars, yield_constr);
decode::eval_packed_generic(local_values, yield_constr);
simple_logic::eval_packed(local_values, yield_constr);
}
@ -99,6 +100,7 @@ impl<F: RichField + Extendable<D>, const D: usize> Stark<F, D> for CpuStark<F, D
yield_constr: &mut RecursiveConstraintConsumer<F, D>,
) {
let local_values = vars.local_values.borrow();
bootstrap_kernel::eval_bootstrap_kernel_circuit(builder, vars, yield_constr);
decode::eval_ext_circuit(builder, local_values, yield_constr);
simple_logic::eval_ext_circuit(builder, local_values, yield_constr);
}

View File

@ -17,7 +17,7 @@ const BYTES_PER_OFFSET: u8 = 3;
#[derive(PartialEq, Eq, Debug)]
pub struct Kernel {
pub code: Vec<u8>,
pub(crate) code: Vec<u8>,
pub(crate) global_labels: HashMap<String, usize>,
}

View File

@ -1,5 +1,5 @@
pub mod aggregator;
mod assembler;
pub mod assembler;
mod ast;
mod opcodes;
mod parser;

View File

@ -1,5 +1,7 @@
pub(crate) mod bootstrap_kernel;
pub(crate) mod columns;
pub mod cpu_stark;
pub(crate) mod decode;
pub mod kernel;
pub mod public_inputs;
mod simple_logic;

View File

@ -0,0 +1 @@
pub const NUM_PUBLIC_INPUTS: usize = 0; // PIs will be added later.

View File

@ -0,0 +1,52 @@
use plonky2::field::types::Field;
use crate::memory::memory_stark::MemoryOp;
use crate::memory::segments::NUM_SEGMENTS;
use crate::memory::VALUE_LIMBS;
#[allow(unused)] // TODO: Should be used soon.
#[derive(Debug)]
pub(crate) struct MemoryState<F: Field> {
/// A log of each memory operation, in the order that it occurred.
pub log: Vec<MemoryOp<F>>,
pub contexts: Vec<MemoryContextState<F>>,
}
impl<F: Field> Default for MemoryState<F> {
fn default() -> Self {
Self {
log: vec![],
// We start with an initial context for the kernel.
contexts: vec![MemoryContextState::default()],
}
}
}
#[derive(Default, Debug)]
pub(crate) struct MemoryContextState<F: Field> {
/// The content of each memory segment.
pub segments: [MemorySegmentState<F>; NUM_SEGMENTS],
}
#[derive(Default, Debug)]
pub(crate) struct MemorySegmentState<F: Field> {
pub content: Vec<[F; VALUE_LIMBS]>,
}
impl<F: Field> MemorySegmentState<F> {
pub(super) fn get(&self, virtual_addr: usize) -> [F; VALUE_LIMBS] {
self.content
.get(virtual_addr)
.copied()
.unwrap_or([F::ZERO; VALUE_LIMBS])
}
pub(super) fn set(&mut self, virtual_addr: usize, value: [F; VALUE_LIMBS]) {
if virtual_addr + 1 > self.content.len() {
self.content
.resize(virtual_addr + 1, [F::ZERO; VALUE_LIMBS]);
}
self.content[virtual_addr] = value;
}
}

60
evm/src/generation/mod.rs Normal file
View File

@ -0,0 +1,60 @@
use plonky2::field::extension::Extendable;
use plonky2::field::polynomial::PolynomialValues;
use plonky2::field::types::Field;
use plonky2::hash::hash_types::RichField;
use crate::all_stark::AllStark;
use crate::cpu::bootstrap_kernel::generate_bootstrap_kernel;
use crate::cpu::columns::NUM_CPU_COLUMNS;
use crate::generation::state::GenerationState;
use crate::util::trace_rows_to_poly_values;
mod memory;
pub(crate) mod state;
pub type RlpBlob = Vec<u8>;
/// Merkle proofs are encoded using an RLP blob for each node in the path.
pub type RlpMerkleProof = Vec<RlpBlob>;
#[allow(unused)] // TODO: Should be used soon.
pub struct TransactionData {
pub signed_txn: Vec<u8>,
/// A Merkle proof for each interaction with the state trie, ordered chronologically.
pub trie_proofs: Vec<RlpMerkleProof>,
}
#[allow(unused)] // TODO: Should be used soon.
pub fn generate_traces<F: RichField + Extendable<D>, const D: usize>(
all_stark: &AllStark<F, D>,
txns: &[TransactionData],
) -> Vec<Vec<PolynomialValues<F>>> {
let mut state = GenerationState::<F>::default();
generate_bootstrap_kernel::<F>(&mut state);
for txn in txns {
generate_txn(&mut state, txn);
}
let GenerationState {
cpu_rows,
current_cpu_row,
memory,
keccak_inputs,
logic_ops: logic_inputs,
..
} = state;
assert_eq!(current_cpu_row, [F::ZERO; NUM_CPU_COLUMNS]);
let cpu_trace = trace_rows_to_poly_values(cpu_rows);
let keccak_trace = all_stark.keccak_stark.generate_trace(keccak_inputs);
let logic_trace = all_stark.logic_stark.generate_trace(logic_inputs);
let memory_trace = all_stark.memory_stark.generate_trace(memory.log);
vec![cpu_trace, keccak_trace, logic_trace, memory_trace]
}
fn generate_txn<F: Field>(_state: &mut GenerationState<F>, _txn: &TransactionData) {
todo!()
}

118
evm/src/generation/state.rs Normal file
View File

@ -0,0 +1,118 @@
use ethereum_types::U256;
use plonky2::field::types::Field;
use crate::cpu::columns::NUM_CPU_COLUMNS;
use crate::cpu::kernel::aggregator::combined_kernel;
use crate::cpu::kernel::assembler::Kernel;
use crate::generation::memory::MemoryState;
use crate::logic::{Op, Operation};
use crate::memory::memory_stark::MemoryOp;
use crate::{keccak, logic};
#[derive(Debug)]
pub(crate) struct GenerationState<F: Field> {
pub(crate) kernel: Kernel,
pub(crate) cpu_rows: Vec<[F; NUM_CPU_COLUMNS]>,
pub(crate) current_cpu_row: [F; NUM_CPU_COLUMNS],
pub(crate) current_context: usize,
pub(crate) memory: MemoryState<F>,
pub(crate) keccak_inputs: Vec<[u64; keccak::keccak_stark::NUM_INPUTS]>,
pub(crate) logic_ops: Vec<logic::Operation>,
}
impl<F: Field> GenerationState<F> {
/// Compute logical AND, and record the operation to be added in the logic table later.
#[allow(unused)] // TODO: Should be used soon.
pub(crate) fn and(&mut self, input0: U256, input1: U256) -> U256 {
self.logic_op(Op::And, input0, input1)
}
/// Compute logical OR, and record the operation to be added in the logic table later.
#[allow(unused)] // TODO: Should be used soon.
pub(crate) fn or(&mut self, input0: U256, input1: U256) -> U256 {
self.logic_op(Op::Or, input0, input1)
}
/// Compute logical XOR, and record the operation to be added in the logic table later.
#[allow(unused)] // TODO: Should be used soon.
pub(crate) fn xor(&mut self, input0: U256, input1: U256) -> U256 {
self.logic_op(Op::Xor, input0, input1)
}
/// Compute logical AND, and record the operation to be added in the logic table later.
pub(crate) fn logic_op(&mut self, op: Op, input0: U256, input1: U256) -> U256 {
let operation = Operation::new(op, input0, input1);
let result = operation.result;
self.logic_ops.push(operation);
result
}
/// Read some memory within the current execution context, and log the operation.
#[allow(unused)] // TODO: Should be used soon.
pub(crate) fn get_mem_current(
&mut self,
channel_index: usize,
segment: usize,
virt: usize,
) -> [F; crate::memory::VALUE_LIMBS] {
let timestamp = self.cpu_rows.len();
let context = self.current_context;
let value = self.memory.contexts[context].segments[segment].get(virt);
self.memory.log.push(MemoryOp {
channel_index,
timestamp,
is_read: true,
context,
segment,
virt,
value,
});
value
}
/// Write some memory within the current execution context, and log the operation.
pub(crate) fn set_mem_current(
&mut self,
channel_index: usize,
segment: usize,
virt: usize,
value: [F; crate::memory::VALUE_LIMBS],
) {
let timestamp = self.cpu_rows.len();
let context = self.current_context;
self.memory.log.push(MemoryOp {
channel_index,
timestamp,
is_read: false,
context,
segment,
virt,
value,
});
self.memory.contexts[context].segments[segment].set(virt, value)
}
pub(crate) fn commit_cpu_row(&mut self) {
self.cpu_rows.push(self.current_cpu_row);
self.current_cpu_row = [F::ZERO; NUM_CPU_COLUMNS];
}
}
// `GenerationState` can't `derive(Default)` because `Default` is only implemented for arrays up to
// length 32 :-\.
impl<F: Field> Default for GenerationState<F> {
fn default() -> Self {
Self {
kernel: combined_kernel(),
cpu_rows: vec![],
current_cpu_row: [F::ZERO; NUM_CPU_COLUMNS],
current_context: 0,
memory: MemoryState::default(),
keccak_inputs: vec![],
logic_ops: vec![],
}
}
}

View File

@ -44,7 +44,7 @@ pub fn ctl_filter<F: Field>() -> Column<F> {
Column::single(reg_step(NUM_ROUNDS - 1))
}
#[derive(Copy, Clone)]
#[derive(Copy, Clone, Default)]
pub struct KeccakStark<F, const D: usize> {
pub(crate) f: PhantomData<F>,
}

View File

@ -10,6 +10,7 @@ pub mod config;
pub mod constraint_consumer;
pub mod cpu;
pub mod cross_table_lookup;
pub mod generation;
mod get_challenges;
pub mod keccak;
pub mod logic;

View File

@ -1,15 +1,17 @@
use std::marker::PhantomData;
use ethereum_types::U256;
use itertools::izip;
use plonky2::field::extension::{Extendable, FieldExtension};
use plonky2::field::packed::PackedField;
use plonky2::field::polynomial::PolynomialValues;
use plonky2::field::types::Field;
use plonky2::hash::hash_types::RichField;
use crate::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer};
use crate::cross_table_lookup::Column;
use crate::stark::Stark;
use crate::util::{limb_from_bits_le, limb_from_bits_le_recursive};
use crate::util::{limb_from_bits_le, limb_from_bits_le_recursive, trace_rows_to_poly_values};
use crate::vars::{StarkEvaluationTargets, StarkEvaluationVars};
// Total number of bits per input/output.
@ -61,71 +63,78 @@ pub fn ctl_filter<F: Field>() -> Column<F> {
Column::sum([columns::IS_AND, columns::IS_OR, columns::IS_XOR])
}
#[derive(Copy, Clone)]
#[derive(Copy, Clone, Default)]
pub struct LogicStark<F, const D: usize> {
pub f: PhantomData<F>,
}
enum Op {
// The `Zero` op is just for convenience. The all-zero row already satisfies the constraints;
// `Zero` lets us call `generate` on it without crashing.
Zero,
#[derive(Copy, Clone, Debug)]
pub(crate) enum Op {
And,
Or,
Xor,
}
fn check_op_flags<F: RichField>(lv: &[F; columns::NUM_COLUMNS]) -> Op {
let is_and = lv[columns::IS_AND].to_canonical_u64();
assert!(is_and <= 1);
let is_or = lv[columns::IS_OR].to_canonical_u64();
assert!(is_or <= 1);
let is_xor = lv[columns::IS_XOR].to_canonical_u64();
assert!(is_xor <= 1);
assert!(is_and + is_or + is_xor <= 1);
if is_and == 1 {
Op::And
} else if is_or == 1 {
Op::Or
} else if is_xor == 1 {
Op::Xor
} else {
Op::Zero
}
#[derive(Debug)]
pub(crate) struct Operation {
operator: Op,
input0: U256,
input1: U256,
pub(crate) result: U256,
}
fn check_bits<F: RichField>(lv: &[F; columns::NUM_COLUMNS]) {
for bit_cols in [columns::INPUT0, columns::INPUT1] {
for bit_col in bit_cols {
let bit = lv[bit_col].to_canonical_u64();
assert!(bit <= 1);
impl Operation {
pub(crate) fn new(operator: Op, input0: U256, input1: U256) -> Self {
let result = match operator {
Op::And => input0 & input1,
Op::Or => input0 | input1,
Op::Xor => input0 ^ input1,
};
Operation {
operator,
input0,
input1,
result,
}
}
}
fn make_result<F: RichField>(lv: &mut [F; columns::NUM_COLUMNS], op: Op) {
for (res_col, limb_in0_cols, limb_in1_cols) in izip!(
columns::RESULT,
columns::limb_bit_cols_for_input(columns::INPUT0),
columns::limb_bit_cols_for_input(columns::INPUT1),
) {
let limb_in0: u64 = limb_from_bits_le(limb_in0_cols.map(|col| lv[col])).to_canonical_u64();
let limb_in1: u64 = limb_from_bits_le(limb_in1_cols.map(|col| lv[col])).to_canonical_u64();
let res = match op {
Op::Zero => 0,
Op::And => limb_in0 & limb_in1,
Op::Or => limb_in0 | limb_in1,
Op::Xor => limb_in0 ^ limb_in1,
};
lv[res_col] = F::from_canonical_u64(res);
}
}
impl<F: RichField, const D: usize> LogicStark<F, D> {
pub fn generate(&self, lv: &mut [F; columns::NUM_COLUMNS]) {
let op = check_op_flags(lv);
check_bits(lv);
make_result(lv, op);
pub(crate) fn generate_trace(&self, operations: Vec<Operation>) -> Vec<PolynomialValues<F>> {
let len = operations.len();
let padded_len = len.next_power_of_two();
let mut rows = Vec::with_capacity(padded_len);
for op in operations {
rows.push(Self::generate_row(op));
}
// Pad to a power of two.
for _ in len..padded_len {
rows.push([F::ZERO; columns::NUM_COLUMNS]);
}
trace_rows_to_poly_values(rows)
}
fn generate_row(operation: Operation) -> [F; columns::NUM_COLUMNS] {
let mut row = [F::ZERO; columns::NUM_COLUMNS];
match operation.operator {
Op::And => row[columns::IS_AND] = F::ONE,
Op::Or => row[columns::IS_OR] = F::ONE,
Op::Xor => row[columns::IS_XOR] = F::ONE,
}
for (i, col) in columns::INPUT0.enumerate() {
row[col] = F::from_bool(operation.input0.bit(i));
}
for (i, col) in columns::INPUT1.enumerate() {
row[col] = F::from_bool(operation.input1.bit(i));
}
for (i, col) in columns::RESULT.enumerate() {
let bit_range = i * PACKED_LIMB_BITS..(i + 1) * PACKED_LIMB_BITS;
row[col] = limb_from_bits_le(bit_range.map(|j| F::from_bool(operation.result.bit(j))));
}
row
}
}

View File

@ -41,19 +41,20 @@ pub fn ctl_filter<F: Field>(channel: usize) -> Column<F> {
Column::single(is_channel(channel))
}
#[derive(Copy, Clone)]
#[derive(Copy, Clone, Default)]
pub struct MemoryStark<F, const D: usize> {
pub(crate) f: PhantomData<F>,
}
#[derive(Debug)]
pub struct MemoryOp<F> {
channel_index: usize,
timestamp: F,
is_read: F,
context: F,
segment: F,
virt: F,
value: [F; 8],
pub channel_index: usize,
pub timestamp: usize,
pub is_read: bool,
pub context: usize,
pub segment: usize,
pub virt: usize,
pub value: [F; 8],
}
pub fn generate_random_memory_ops<F: RichField, R: Rng>(
@ -62,7 +63,7 @@ pub fn generate_random_memory_ops<F: RichField, R: Rng>(
) -> Vec<MemoryOp<F>> {
let mut memory_ops = Vec::new();
let mut current_memory_values: HashMap<(F, F, F), [F; 8]> = HashMap::new();
let mut current_memory_values: HashMap<(usize, usize, usize), [F; 8]> = HashMap::new();
let num_cycles = num_ops / 2;
for clock in 0..num_cycles {
let mut used_indices = HashSet::new();
@ -81,7 +82,6 @@ pub fn generate_random_memory_ops<F: RichField, R: Rng>(
!has_read && rng.gen()
};
has_read = has_read || is_read;
let is_read_field = F::from_bool(is_read);
let (context, segment, virt, vals) = if is_read {
let written: Vec<_> = current_memory_values.keys().collect();
@ -94,13 +94,13 @@ pub fn generate_random_memory_ops<F: RichField, R: Rng>(
} else {
// TODO: with taller memory table or more padding (to enable range-checking bigger diffs),
// test larger address values.
let mut context = F::from_canonical_usize(rng.gen_range(0..40));
let mut segment = F::from_canonical_usize(rng.gen_range(0..8));
let mut virt = F::from_canonical_usize(rng.gen_range(0..20));
let mut context = rng.gen_range(0..40);
let mut segment = rng.gen_range(0..8);
let mut virt = rng.gen_range(0..20);
while new_writes_this_cycle.contains_key(&(context, segment, virt)) {
context = F::from_canonical_usize(rng.gen_range(0..40));
segment = F::from_canonical_usize(rng.gen_range(0..8));
virt = F::from_canonical_usize(rng.gen_range(0..20));
context = rng.gen_range(0..40);
segment = rng.gen_range(0..8);
virt = rng.gen_range(0..20);
}
let val: [u32; 8] = rng.gen();
@ -111,11 +111,11 @@ pub fn generate_random_memory_ops<F: RichField, R: Rng>(
(context, segment, virt, vals)
};
let timestamp = F::from_canonical_usize(clock * NUM_CHANNELS + channel_index);
let timestamp = clock * NUM_CHANNELS + channel_index;
memory_ops.push(MemoryOp {
channel_index,
timestamp,
is_read: is_read_field,
is_read,
context,
segment,
virt,
@ -243,11 +243,11 @@ impl<F: RichField + Extendable<D>, const D: usize> MemoryStark<F, D> {
value,
} = memory_ops[i];
trace_cols[is_channel(channel_index)][i] = F::ONE;
trace_cols[TIMESTAMP][i] = timestamp;
trace_cols[IS_READ][i] = is_read;
trace_cols[ADDR_CONTEXT][i] = context;
trace_cols[ADDR_SEGMENT][i] = segment;
trace_cols[ADDR_VIRTUAL][i] = virt;
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];
}

View File

@ -1,5 +1,6 @@
pub mod columns;
pub mod memory_stark;
pub mod segments;
pub(crate) const NUM_CHANNELS: usize = 4;
pub(crate) const VALUE_LIMBS: usize = 8;

View File

@ -0,0 +1,22 @@
/// Contains EVM bytecode.
pub const CODE: usize = 0;
pub const STACK: usize = 1;
/// Main memory, owned by the contract code.
pub const MAIN_MEM: usize = 2;
/// Memory owned by the kernel.
pub const KERNEL_MEM: usize = 3;
/// Data passed to the current context by its caller.
pub const CALLDATA: usize = 4;
/// Data returned to the current context by its latest callee.
pub const RETURNDATA: usize = 5;
/// A segment which contains a few fixed-size metadata fields, such as the caller's context, or the
/// size of `CALLDATA` and `RETURNDATA`.
pub const METADATA: usize = 6;
pub const NUM_SEGMENTS: usize = 7;

View File

@ -1,3 +1,5 @@
use std::any::type_name;
use anyhow::{ensure, Result};
use plonky2::field::extension::Extendable;
use plonky2::field::packable::Packable;
@ -563,6 +565,10 @@ fn check_constraints<'a, F, C, S, const D: usize>(
.collect::<Vec<_>>();
for v in constraint_values {
assert!(v.iter().all(|x| x.is_zero()));
assert!(
v.iter().all(|x| x.is_zero()),
"Constraint failed in {}",
type_name::<S>()
);
}
}

View File

@ -0,0 +1,46 @@
use hex_literal::hex;
use plonky2::field::goldilocks_field::GoldilocksField;
use plonky2::plonk::config::PoseidonGoldilocksConfig;
use plonky2::util::timing::TimingTree;
use plonky2_evm::all_stark::AllStark;
use plonky2_evm::config::StarkConfig;
use plonky2_evm::generation::{generate_traces, TransactionData};
use plonky2_evm::prover::prove;
use plonky2_evm::verifier::verify_proof;
type F = GoldilocksField;
const D: usize = 2;
type C = PoseidonGoldilocksConfig;
/// Test a simple token transfer to a new address.
#[test]
#[ignore] // TODO: Won't work until txn parsing, storage, etc. are implemented.
fn test_simple_transfer() -> anyhow::Result<()> {
let all_stark = AllStark::<F, D>::default();
let txn = TransactionData {
signed_txn: hex!("f85f050a82520894000000000000000000000000000000000000000064801ca0fa56df5d988638fad8798e5ef75a1e1125dc7fb55d2ac4bce25776a63f0c2967a02cb47a5579eb5f83a1cabe4662501c0059f1b58e60ef839a1b0da67af6b9fb38").to_vec(),
trie_proofs: vec![
vec![
hex!("f874a1202f93d0dfb1562c03c825a33eec4438e468c17fff649ae844c004065985ae2945b850f84e058a152d02c7e14af6800000a056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470").to_vec(),
],
vec![
hex!("f8518080a0d36b8b6b60021940d5553689fb33e5d45e649dd8f4f211d26566238a83169da58080a0c62aa627943b70321f89a8b2fea274ecd47116e62042077dcdc0bdca7c1f66738080808080808080808080").to_vec(),
hex!("f873a03f93d0dfb1562c03c825a33eec4438e468c17fff649ae844c004065985ae2945b850f84e068a152d02c7e14af67ccb4ca056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470").to_vec(),
],
]
};
let traces = generate_traces(&all_stark, &[txn]);
let config = StarkConfig::standard_fast_config();
let proof = prove::<F, C, D>(
&all_stark,
&config,
traces,
vec![vec![]; 4],
&mut TimingTree::default(),
)?;
verify_proof(all_stark, proof, &config)
}

View File

@ -284,6 +284,12 @@ pub trait Field:
Self::from_canonical_u64(n as u64)
}
/// Returns `n`. Assumes that `n` is already in canonical form, i.e. `n < Self::order()`.
// TODO: Should probably be unsafe.
fn from_canonical_u8(n: u8) -> Self {
Self::from_canonical_u64(n as u64)
}
/// Returns `n`. Assumes that `n` is already in canonical form, i.e. `n < Self::order()`.
// TODO: Should probably be unsafe.
fn from_canonical_usize(n: usize) -> Self {