diff --git a/.env b/.env index 93681e353..29111e064 100644 --- a/.env +++ b/.env @@ -8,3 +8,4 @@ LOGIC_CIRCUIT_SIZE=4..21 MEMORY_CIRCUIT_SIZE=17..24 MEMORY_BEFORE_CIRCUIT_SIZE=16..23 MEMORY_AFTER_CIRCUIT_SIZE=7..23 +POSEIDON_CIRCUIT_SIZE=4..25 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c4d2f7fb1..98051cc55 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -239,5 +239,8 @@ jobs: - name: Run cargo clippy run: cargo clippy --all-targets -- -D warnings -A incomplete-features + - name: Run cargo clippy (with `cdk_erigon` flag) + run: cargo clippy --all-features --all-targets --features cdk_erigon -- -D warnings -A incomplete-features + - name: Rustdoc run: cargo doc --all diff --git a/Cargo.lock b/Cargo.lock index 11feda934..dcd859ede 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -83,6 +83,16 @@ dependencies = [ "strum", ] +[[package]] +name = "alloy-compat" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6216c5ab5502a6824ec87a7bcd5c52ca14137f00b37dea3ab321a1656ff46383" +dependencies = [ + "alloy-primitives", + "ethereum-types", +] + [[package]] name = "alloy-consensus" version = "0.3.0" @@ -735,6 +745,30 @@ dependencies = [ "syn 2.0.76", ] +[[package]] +name = "assert2" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d31fea2b6e18dfe892863c3a0a68f9e005b0195565f3d55b8612946ebca789cc" +dependencies = [ + "assert2-macros", + "diff", + "is-terminal", + "yansi", +] + +[[package]] +name = "assert2-macros" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c1ac052c642f6d94e4be0b33028b346b7ab809ea5432b584eb8859f12f7ad2c" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version 0.4.1", + "syn 2.0.76", +] + [[package]] name = "async-channel" version = "2.3.1" @@ -1737,6 +1771,12 @@ dependencies = [ "cipher", ] +[[package]] +name = "diff" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" + [[package]] name = "digest" version = "0.9.0" @@ -1923,6 +1963,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "escape8259" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5692dd7b5a1978a5aeb0ce83b7655c58ca8efdcb79d21036ea249da95afec2c6" + [[package]] name = "eth_trie" version = "0.4.0" @@ -2024,6 +2070,7 @@ dependencies = [ "serde-big-array", "serde_json", "sha2", + "smt_trie", "starky", "static_assertions", "thiserror", @@ -2249,12 +2296,6 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" -[[package]] -name = "futures-timer" -version = "3.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" - [[package]] name = "futures-util" version = "0.3.30" @@ -2859,6 +2900,18 @@ dependencies = [ "libc", ] +[[package]] +name = "libtest-mimic" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc0bda45ed5b3a2904262c1bb91e526127aa70e7ef3758aba2ef93cf896b9b58" +dependencies = [ + "clap", + "escape8259", + "termcolor", + "threadpool", +] + [[package]] name = "linkme" version = "0.3.28" @@ -4006,12 +4059,6 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" -[[package]] -name = "relative-path" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" - [[package]] name = "reqwest" version = "0.12.7" @@ -4134,36 +4181,6 @@ dependencies = [ "zero_bin_common", ] -[[package]] -name = "rstest" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9afd55a67069d6e434a95161415f5beeada95a01c7b815508a82dcb0e1593682" -dependencies = [ - "futures", - "futures-timer", - "rstest_macros", - "rustc_version 0.4.1", -] - -[[package]] -name = "rstest_macros" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4165dfae59a39dd41d8dec720d3cbfbc71f69744efb480a3920f5d4e0cc6798d" -dependencies = [ - "cfg-if", - "glob", - "proc-macro-crate", - "proc-macro2", - "quote", - "regex", - "relative-path", - "rustc_version 0.4.1", - "syn 2.0.76", - "unicode-ident", -] - [[package]] name = "ruint" version = "1.12.3" @@ -5092,10 +5109,13 @@ name = "trace_decoder" version = "0.6.0" dependencies = [ "alloy", + "alloy-compat", "anyhow", + "assert2", "bitflags 2.6.0", "bitvec", "bytes", + "camino", "ciborium", "ciborium-io", "copyvec", @@ -5104,10 +5124,12 @@ dependencies = [ "enum-as-inner", "ethereum-types", "evm_arithmetization", + "glob", "hex", "hex-literal", "itertools 0.13.0", "keccak-hash 0.10.0", + "libtest-mimic", "log", "mpt_trie", "nunny", @@ -5116,7 +5138,6 @@ dependencies = [ "pretty_env_logger", "prover", "rlp", - "rstest", "serde", "serde_json", "serde_path_to_error", @@ -5776,6 +5797,12 @@ dependencies = [ "time", ] +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + [[package]] name = "zero_bin_common" version = "0.1.0" diff --git a/evm_arithmetization/Cargo.toml b/evm_arithmetization/Cargo.toml index 4fcd03335..a2b38cc7e 100644 --- a/evm_arithmetization/Cargo.toml +++ b/evm_arithmetization/Cargo.toml @@ -48,6 +48,7 @@ serde-big-array = { workspace = true } # Local dependencies mpt_trie = { workspace = true } +smt_trie = { workspace = true, optional = true } zk_evm_proc_macro = { workspace = true } [dev-dependencies] @@ -59,7 +60,7 @@ ripemd = { workspace = true } default = ["eth_mainnet"] asmtools = ["hex"] polygon_pos = [] -cdk_erigon = [] +cdk_erigon = ["smt_trie"] eth_mainnet = [] [[bin]] diff --git a/evm_arithmetization/src/all_stark.rs b/evm_arithmetization/src/all_stark.rs index c3b5733c1..f37021897 100644 --- a/evm_arithmetization/src/all_stark.rs +++ b/evm_arithmetization/src/all_stark.rs @@ -25,6 +25,11 @@ use crate::logic::LogicStark; use crate::memory::memory_stark::MemoryStark; use crate::memory::memory_stark::{self, ctl_context_pruning_looking}; use crate::memory_continuation::memory_continuation_stark::{self, MemoryContinuationStark}; +#[cfg(feature = "cdk_erigon")] +use crate::poseidon::{ + columns::POSEIDON_SPONGE_RATE, + poseidon_stark::{self, PoseidonStark, FELT_MAX_BYTES}, +}; /// Structure containing all STARKs and the cross-table lookups. #[derive(Clone)] @@ -38,6 +43,8 @@ pub struct AllStark, const D: usize> { pub(crate) memory_stark: MemoryStark, pub(crate) mem_before_stark: MemoryContinuationStark, pub(crate) mem_after_stark: MemoryContinuationStark, + #[cfg(feature = "cdk_erigon")] + pub(crate) poseidon_stark: PoseidonStark, pub(crate) cross_table_lookups: Vec>, } @@ -55,6 +62,8 @@ impl, const D: usize> Default for AllStark { memory_stark: MemoryStark::default(), mem_before_stark: MemoryContinuationStark::default(), mem_after_stark: MemoryContinuationStark::default(), + #[cfg(feature = "cdk_erigon")] + poseidon_stark: PoseidonStark::default(), cross_table_lookups: all_cross_table_lookups(), } } @@ -72,6 +81,8 @@ impl, const D: usize> AllStark { self.memory_stark.num_lookup_helper_columns(config), self.mem_before_stark.num_lookup_helper_columns(config), self.mem_after_stark.num_lookup_helper_columns(config), + #[cfg(feature = "cdk_erigon")] + self.poseidon_stark.num_lookup_helper_columns(config), ] } } @@ -90,6 +101,8 @@ pub enum Table { Memory = 6, MemBefore = 7, MemAfter = 8, + #[cfg(feature = "cdk_erigon")] + Poseidon = 9, } impl Deref for Table { @@ -98,12 +111,20 @@ impl Deref for Table { fn deref(&self) -> &Self::Target { // Hacky way to implement `Deref` for `Table` so that we don't have to // call `Table::Foo as usize`, but perhaps too ugly to be worth it. - [&0, &1, &2, &3, &4, &5, &6, &7, &8][*self as TableIdx] + #[cfg(not(feature = "cdk_erigon"))] + return [&0, &1, &2, &3, &4, &5, &6, &7, &8][*self as TableIdx]; + + #[cfg(feature = "cdk_erigon")] + [&0, &1, &2, &3, &4, &5, &6, &7, &8, &9][*self as TableIdx] } } /// Number of STARK tables. -pub(crate) const NUM_TABLES: usize = Table::MemAfter as usize + 1; +pub const NUM_TABLES: usize = if cfg!(feature = "cdk_erigon") { + Table::MemAfter as usize + 2 +} else { + Table::MemAfter as usize + 1 +}; impl Table { /// Returns all STARK table indices. @@ -118,6 +139,8 @@ impl Table { Self::Memory, Self::MemBefore, Self::MemAfter, + #[cfg(feature = "cdk_erigon")] + Self::Poseidon, ] } } @@ -135,6 +158,12 @@ pub(crate) fn all_cross_table_lookups() -> Vec> { ctl_mem_before(), ctl_mem_after(), ctl_context_pruning(), + #[cfg(feature = "cdk_erigon")] + ctl_poseidon_simple(), + #[cfg(feature = "cdk_erigon")] + ctl_poseidon_general_input(), + #[cfg(feature = "cdk_erigon")] + ctl_poseidon_general_output(), ] } @@ -307,6 +336,16 @@ fn ctl_memory() -> CrossTableLookup { memory_continuation_stark::ctl_data_memory(), memory_continuation_stark::ctl_filter(), ); + + #[cfg(feature = "cdk_erigon")] + let poseidon_general_reads = (0..FELT_MAX_BYTES * POSEIDON_SPONGE_RATE).map(|i| { + TableWithColumns::new( + *Table::Poseidon, + poseidon_stark::ctl_looking_memory(i), + poseidon_stark::ctl_looking_memory_filter(), + ) + }); + let all_lookers = vec![ cpu_memory_code_read, cpu_push_write_ops, @@ -317,8 +356,12 @@ fn ctl_memory() -> CrossTableLookup { .chain(cpu_memory_gp_ops) .chain(keccak_sponge_reads) .chain(byte_packing_ops) - .chain(iter::once(mem_before_ops)) - .collect(); + .chain(iter::once(mem_before_ops)); + + #[cfg(feature = "cdk_erigon")] + let all_lookers = all_lookers.chain(poseidon_general_reads); + + let all_lookers = all_lookers.collect(); let memory_looked = TableWithColumns::new( *Table::Memory, memory_stark::ctl_data(), @@ -368,3 +411,27 @@ fn ctl_mem_after() -> CrossTableLookup { ); CrossTableLookup::new(all_lookers, mem_after_looked) } + +#[cfg(feature = "cdk_erigon")] +fn ctl_poseidon_simple() -> CrossTableLookup { + CrossTableLookup::new( + vec![cpu_stark::ctl_poseidon_simple_op()], + poseidon_stark::ctl_looked_simple_op(), + ) +} + +#[cfg(feature = "cdk_erigon")] +fn ctl_poseidon_general_input() -> CrossTableLookup { + CrossTableLookup::new( + vec![cpu_stark::ctl_poseidon_general_input()], + poseidon_stark::ctl_looked_general_input(), + ) +} + +#[cfg(feature = "cdk_erigon")] +fn ctl_poseidon_general_output() -> CrossTableLookup { + CrossTableLookup::new( + vec![cpu_stark::ctl_poseidon_general_output()], + poseidon_stark::ctl_looked_general_output(), + ) +} diff --git a/evm_arithmetization/src/cpu/columns/general.rs b/evm_arithmetization/src/cpu/columns/general.rs index 5ab033b82..97f8b8f49 100644 --- a/evm_arithmetization/src/cpu/columns/general.rs +++ b/evm_arithmetization/src/cpu/columns/general.rs @@ -95,7 +95,7 @@ impl CpuGeneralColumnsView { /// View of the column for context pruning. /// SAFETY: Each view is a valid interpretation of the underlying array. - pub(crate) fn context_pruning(&self) -> &CpuContextPruningView { + pub(crate) const fn context_pruning(&self) -> &CpuContextPruningView { unsafe { &self.context_pruning } } diff --git a/evm_arithmetization/src/cpu/columns/ops.rs b/evm_arithmetization/src/cpu/columns/ops.rs index c3d1281a6..5e91457e9 100644 --- a/evm_arithmetization/src/cpu/columns/ops.rs +++ b/evm_arithmetization/src/cpu/columns/ops.rs @@ -20,6 +20,9 @@ pub(crate) struct OpsColumnsView { pub shift: T, /// Combines JUMPDEST and KECCAK_GENERAL flags. pub jumpdest_keccak_general: T, + /// Combines POSEIDON and POSEIDON_GENERAL flags. + #[cfg(feature = "cdk_erigon")] + pub poseidon: T, /// Combines JUMP and JUMPI flags. pub jumps: T, /// Combines PUSH and PROVER_INPUT flags. diff --git a/evm_arithmetization/src/cpu/contextops.rs b/evm_arithmetization/src/cpu/contextops.rs index 9fdd92f29..11ffe0da3 100644 --- a/evm_arithmetization/src/cpu/contextops.rs +++ b/evm_arithmetization/src/cpu/contextops.rs @@ -23,6 +23,8 @@ const KEEPS_CONTEXT: OpsColumnsView = OpsColumnsView { not_pop: true, shift: true, jumpdest_keccak_general: true, + #[cfg(feature = "cdk_erigon")] + poseidon: true, push_prover_input: true, jumps: true, pc_push0: true, diff --git a/evm_arithmetization/src/cpu/control_flow.rs b/evm_arithmetization/src/cpu/control_flow.rs index b32ee0ae7..0aa1a307e 100644 --- a/evm_arithmetization/src/cpu/control_flow.rs +++ b/evm_arithmetization/src/cpu/control_flow.rs @@ -8,7 +8,9 @@ use starky::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsume use crate::cpu::columns::{CpuColumnsView, COL_MAP}; use crate::cpu::kernel::aggregator::KERNEL; -const NATIVE_INSTRUCTIONS: [usize; 12] = [ +const NATIVE_INST_LEN: usize = if cfg!(feature = "cdk_erigon") { 13 } else { 12 }; + +const NATIVE_INSTRUCTIONS: [usize; NATIVE_INST_LEN] = [ COL_MAP.op.binary_op, COL_MAP.op.ternary_op, COL_MAP.op.fp254_op, @@ -17,6 +19,8 @@ const NATIVE_INSTRUCTIONS: [usize; 12] = [ COL_MAP.op.not_pop, COL_MAP.op.shift, COL_MAP.op.jumpdest_keccak_general, + #[cfg(feature = "cdk_erigon")] + COL_MAP.op.poseidon, // Not PROVER_INPUT: it is dealt with manually below. // not JUMPS (possible need to jump) COL_MAP.op.pc_push0, diff --git a/evm_arithmetization/src/cpu/cpu_stark.rs b/evm_arithmetization/src/cpu/cpu_stark.rs index daafa164d..386f89519 100644 --- a/evm_arithmetization/src/cpu/cpu_stark.rs +++ b/evm_arithmetization/src/cpu/cpu_stark.rs @@ -462,6 +462,87 @@ pub(crate) fn ctl_filter_set_context() -> Filter { ) } +#[cfg(feature = "cdk_erigon")] +/// Returns the `TableWithColumns` for the CPU rows calling POSEIDON. +pub(crate) fn ctl_poseidon_simple_op() -> TableWithColumns { + // When executing POSEIDON, the GP memory channels are used as follows: + // GP channel 0: stack[-1] = x + // GP channel 1: stack[-2] = y + // GP channel 2: stack[-3] = z + // Such that we can compute `POSEIDON(x || y || z)`. + let mut columns = Vec::new(); + for channel in 0..3 { + for i in 0..VALUE_LIMBS / 2 { + columns.push(Column::linear_combination([ + (COL_MAP.mem_channels[channel].value[2 * i], F::ONE), + ( + COL_MAP.mem_channels[channel].value[2 * i + 1], + F::from_canonical_u64(1 << 32), + ), + ])); + } + } + columns.extend(Column::singles_next_row(COL_MAP.mem_channels[0].value)); + TableWithColumns::new(*Table::Cpu, columns, ctl_poseidon_simple_filter()) +} + +#[cfg(feature = "cdk_erigon")] +pub(crate) fn ctl_poseidon_general_input() -> TableWithColumns { + // When executing POSEIDON_GENERAL, the GP memory channels are used as follows: + // GP channel 0: stack[-1] = addr (context, segment, virt) + // GP channel 1: stack[-2] = len + let (context, segment, virt) = get_addr(&COL_MAP, 0); + let context = Column::single(context); + let segment: Column = Column::single(segment); + let virt = Column::single(virt); + let len = Column::single(COL_MAP.mem_channels[1].value[0]); + + let num_channels = F::from_canonical_usize(NUM_CHANNELS); + let timestamp = Column::linear_combination([(COL_MAP.clock, num_channels)]); + + TableWithColumns::new( + *Table::Cpu, + vec![context, segment, virt, len, timestamp], + ctl_poseidon_general_filter(), + ) +} + +#[cfg(feature = "cdk_erigon")] +/// CTL filter for the `POSEIDON` operation. +/// POSEIDON is differentiated from POSEIDON_GENERAL by its first bit set to 0. +pub(crate) fn ctl_poseidon_simple_filter() -> Filter { + Filter::new( + vec![( + Column::single(COL_MAP.op.poseidon), + Column::linear_combination_with_constant([(COL_MAP.opcode_bits[0], -F::ONE)], F::ONE), + )], + vec![], + ) +} + +#[cfg(feature = "cdk_erigon")] +/// CTL filter for the `POSEIDON_GENERAL` operation. +/// POSEIDON_GENERAL is differentiated from POSEIDON by its first bit set to 1. +pub(crate) fn ctl_poseidon_general_filter() -> Filter { + Filter::new( + vec![( + Column::single(COL_MAP.op.poseidon), + Column::single(COL_MAP.opcode_bits[0]), + )], + vec![], + ) +} + +#[cfg(feature = "cdk_erigon")] +/// Returns the `TableWithColumns` for the CPU rows calling POSEIDON_GENERAL. +pub(crate) fn ctl_poseidon_general_output() -> TableWithColumns { + let mut columns = Vec::new(); + columns.extend(Column::singles_next_row(COL_MAP.mem_channels[0].value)); + let num_channels = F::from_canonical_usize(NUM_CHANNELS); + columns.push(Column::linear_combination([(COL_MAP.clock, num_channels)])); + TableWithColumns::new(*Table::Cpu, columns, ctl_poseidon_general_filter()) +} + /// Disable the specified memory channels. /// Since channel 0 contains the top of the stack and is handled specially, /// channels to disable are 1, 2 or both. All cases can be expressed as a vec. diff --git a/evm_arithmetization/src/cpu/decode.rs b/evm_arithmetization/src/cpu/decode.rs index 0dfb65be5..26656528c 100644 --- a/evm_arithmetization/src/cpu/decode.rs +++ b/evm_arithmetization/src/cpu/decode.rs @@ -7,6 +7,8 @@ use starky::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsume use crate::cpu::columns::{CpuColumnsView, COL_MAP}; +const OPCODES_LEN: usize = if cfg!(feature = "cdk_erigon") { 6 } else { 5 }; + /// List of opcode blocks /// Each block corresponds to exactly one flag, and each flag corresponds to /// exactly one block. Each block of opcodes: @@ -29,13 +31,15 @@ use crate::cpu::columns::{CpuColumnsView, COL_MAP}; /// Note: invalid opcodes are not represented here. _Any_ opcode is permitted to /// decode to `is_invalid`. The kernel then verifies that the opcode was /// _actually_ invalid. -const OPCODES: [(u8, usize, bool, usize); 5] = [ +const OPCODES: [(u8, usize, bool, usize); OPCODES_LEN] = [ // (start index of block, number of top bits to check (log2), kernel-only, flag column) // ADD, MUL, SUB, DIV, MOD, LT, GT and BYTE flags are handled partly manually here, and partly // through the Arithmetic table CTL. ADDMOD, MULMOD and SUBMOD flags are handled partly // manually here, and partly through the Arithmetic table CTL. FP254 operation flags are // handled partly manually here, and partly through the Arithmetic table CTL. (0x14, 1, false, COL_MAP.op.eq_iszero), + #[cfg(feature = "cdk_erigon")] + (0x22, 1, true, COL_MAP.op.poseidon), // AND, OR and XOR flags are handled partly manually here, and partly through the Logic table // CTL. NOT and POP are handled manually here. // SHL and SHR flags are handled partly manually here, and partly through the Logic table CTL. diff --git a/evm_arithmetization/src/cpu/gas.rs b/evm_arithmetization/src/cpu/gas.rs index c3ec89089..7c036ae9b 100644 --- a/evm_arithmetization/src/cpu/gas.rs +++ b/evm_arithmetization/src/cpu/gas.rs @@ -27,8 +27,10 @@ const SIMPLE_OPCODES: OpsColumnsView> = OpsColumnsView { not_pop: None, // This is handled manually below shift: G_VERYLOW, jumpdest_keccak_general: None, // This is handled manually below. - push_prover_input: None, // This is handled manually below. - jumps: None, // Combined flag handled separately. + #[cfg(feature = "cdk_erigon")] + poseidon: KERNEL_ONLY_INSTR, + push_prover_input: None, // This is handled manually below. + jumps: None, // Combined flag handled separately. pc_push0: G_BASE, dup_swap: G_VERYLOW, context_op: KERNEL_ONLY_INSTR, diff --git a/evm_arithmetization/src/cpu/kernel/asm/cdk_pre_execution.asm b/evm_arithmetization/src/cpu/kernel/asm/cdk_pre_execution.asm index 90e76cb60..fa8828097 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/cdk_pre_execution.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/cdk_pre_execution.asm @@ -50,6 +50,9 @@ global update_scalable_prev_block_root_hash: %write_scalable_storage // stack: retdest +// Note: We assume that if the l1 info tree has been re-used or the GER does not exist, +// the payload will not contain any root to store, in which case calling `PROVER_INPUT(ger)` +// will return `U256::MAX` causing this to return early. global update_scalable_l1blockhash: // stack: retdest PROVER_INPUT(ger) diff --git a/evm_arithmetization/src/cpu/kernel/asm/mpt/linked_list/linked_list.asm b/evm_arithmetization/src/cpu/kernel/asm/mpt/linked_list/linked_list.asm index e4bff5b12..ef1213aae 100644 --- a/evm_arithmetization/src/cpu/kernel/asm/mpt/linked_list/linked_list.asm +++ b/evm_arithmetization/src/cpu/kernel/asm/mpt/linked_list/linked_list.asm @@ -859,9 +859,10 @@ remove_all_slots_end: %macro read_storage_linked_list // stack: slot %slot_to_storage_key + %stack (storage_key) -> (storage_key, 0, %%after) %address %addr_to_state_key - %stack (addr_key, key) -> (addr_key, key, 0, %%after) + // stack: addr_key, storage_key, 0, %%after %jump(search_slot) %%after: // stack: slot_value @@ -870,9 +871,9 @@ remove_all_slots_end: %macro read_storage_linked_list_w_addr // stack: slot, address %slot_to_storage_key - SWAP1 + %stack (storage_key, address) -> (address, storage_key, 0, %%after) %addr_to_state_key - %stack (addr_key, key) -> (addr_key, key, 0, %%after) + // stack: addr_key, storage_key, 0, %%after %jump(search_slot) %%after: // stack: slot_value @@ -881,8 +882,7 @@ remove_all_slots_end: %macro read_storage_linked_list_w_state_key // stack: slot, state_key %slot_to_storage_key - SWAP1 - %stack (state_key, key) -> (state_key, key, 0, %%after) + %stack (storage_key, state_key) -> (state_key, storage_key, 0, %%after) %jump(search_slot) %%after: // stack: slot_ptr diff --git a/evm_arithmetization/src/cpu/kernel/interpreter.rs b/evm_arithmetization/src/cpu/kernel/interpreter.rs index 65e27fb1c..ec54b308d 100644 --- a/evm_arithmetization/src/cpu/kernel/interpreter.rs +++ b/evm_arithmetization/src/cpu/kernel/interpreter.rs @@ -11,7 +11,7 @@ use anyhow::anyhow; use ethereum_types::{BigEndianHash, U256}; use log::Level; use mpt_trie::partial_trie::PartialTrie; -use plonky2::field::types::Field; +use plonky2::hash::hash_types::RichField; use serde::{Deserialize, Serialize}; use crate::byte_packing::byte_packing_stark::BytePackingOp; @@ -44,7 +44,7 @@ use crate::{arithmetic, keccak, logic}; /// Halt interpreter execution whenever a jump to this offset is done. const DEFAULT_HALT_OFFSET: usize = 0xdeadbeef; -pub(crate) struct Interpreter { +pub(crate) struct Interpreter { /// The interpreter holds a `GenerationState` to keep track of the memory /// and registers. pub(crate) generation_state: GenerationState, @@ -68,7 +68,7 @@ pub(crate) struct Interpreter { /// Simulates the CPU execution from `state` until the program counter reaches /// `final_label` in the current context. -pub(crate) fn simulate_cpu_and_get_user_jumps( +pub(crate) fn simulate_cpu_and_get_user_jumps( final_label: &str, state: &GenerationState, ) -> Option>> { @@ -118,7 +118,7 @@ pub(crate) struct ExtraSegmentData { pub(crate) next_txn_index: usize, } -pub(crate) fn set_registers_and_run( +pub(crate) fn set_registers_and_run( registers: RegistersState, interpreter: &mut Interpreter, ) -> anyhow::Result<(RegistersState, Option)> { @@ -148,7 +148,7 @@ pub(crate) fn set_registers_and_run( interpreter.run() } -impl Interpreter { +impl Interpreter { /// Returns an instance of `Interpreter` given `GenerationInputs`, and /// assuming we are initializing with the `KERNEL` code. pub(crate) fn new_with_generation_inputs( @@ -414,24 +414,11 @@ impl Interpreter { } pub(crate) fn run(&mut self) -> Result<(RegistersState, Option), anyhow::Error> { - let (final_registers, final_mem) = self.run_cpu(self.max_cpu_len_log)?; - - #[cfg(debug_assertions)] - { - println!("Opcode count:"); - for i in 0..0x100 { - if self.opcode_count[i] > 0 { - println!("{}: {}", get_mnemonic(i as u8), self.opcode_count[i]) - } - } - println!("Total: {}", self.opcode_count.into_iter().sum::()); - } - - Ok((final_registers, final_mem)) + self.run_cpu(self.max_cpu_len_log) } /// Returns the max number of CPU cycles. - pub(crate) fn get_max_cpu_len_log(&self) -> Option { + pub(crate) const fn get_max_cpu_len_log(&self) -> Option { self.max_cpu_len_log } @@ -515,7 +502,7 @@ impl Interpreter { } } -impl State for Interpreter { +impl State for Interpreter { /// Returns a `GenerationStateCheckpoint` to save the current registers and /// reset memory operations to the empty vector. fn checkpoint(&mut self) -> GenerationStateCheckpoint { @@ -708,7 +695,7 @@ impl State for Interpreter { } } -impl Transition for Interpreter { +impl Transition for Interpreter { fn generate_jumpdest_analysis(&mut self, dst: usize) -> bool { if self.is_jumpdest_analysis && !self.generation_state.registers.is_kernel { self.add_jumpdest_offset(dst); @@ -750,203 +737,6 @@ impl Transition for Interpreter { } } -#[cfg(debug_assertions)] -fn get_mnemonic(opcode: u8) -> &'static str { - match opcode { - 0x00 => "STOP", - 0x01 => "ADD", - 0x02 => "MUL", - 0x03 => "SUB", - 0x04 => "DIV", - 0x05 => "SDIV", - 0x06 => "MOD", - 0x07 => "SMOD", - 0x08 => "ADDMOD", - 0x09 => "MULMOD", - 0x0a => "EXP", - 0x0b => "SIGNEXTEND", - 0x0c => "ADDFP254", - 0x0d => "MULFP254", - 0x0e => "SUBFP254", - 0x0f => "SUBMOD", - 0x10 => "LT", - 0x11 => "GT", - 0x12 => "SLT", - 0x13 => "SGT", - 0x14 => "EQ", - 0x15 => "ISZERO", - 0x16 => "AND", - 0x17 => "OR", - 0x18 => "XOR", - 0x19 => "NOT", - 0x1a => "BYTE", - 0x1b => "SHL", - 0x1c => "SHR", - 0x1d => "SAR", - 0x20 => "KECCAK256", - 0x21 => "KECCAK_GENERAL", - 0x30 => "ADDRESS", - 0x31 => "BALANCE", - 0x32 => "ORIGIN", - 0x33 => "CALLER", - 0x34 => "CALLVALUE", - 0x35 => "CALLDATALOAD", - 0x36 => "CALLDATASIZE", - 0x37 => "CALLDATACOPY", - 0x38 => "CODESIZE", - 0x39 => "CODECOPY", - 0x3a => "GASPRICE", - 0x3b => "EXTCODESIZE", - 0x3c => "EXTCODECOPY", - 0x3d => "RETURNDATASIZE", - 0x3e => "RETURNDATACOPY", - 0x3f => "EXTCODEHASH", - 0x40 => "BLOCKHASH", - 0x41 => "COINBASE", - 0x42 => "TIMESTAMP", - 0x43 => "NUMBER", - 0x44 => "DIFFICULTY", - 0x45 => "GASLIMIT", - 0x46 => "CHAINID", - 0x48 => "BASEFEE", - 0x4a => "BLOBBASEFEE", - 0x50 => "POP", - 0x51 => "MLOAD", - 0x52 => "MSTORE", - 0x53 => "MSTORE8", - 0x54 => "SLOAD", - 0x55 => "SSTORE", - 0x56 => "JUMP", - 0x57 => "JUMPI", - 0x58 => "GETPC", - 0x59 => "MSIZE", - 0x5a => "GAS", - 0x5b => "JUMPDEST", - 0x5e => "MCOPY", - 0x5f => "PUSH0", - 0x60 => "PUSH1", - 0x61 => "PUSH2", - 0x62 => "PUSH3", - 0x63 => "PUSH4", - 0x64 => "PUSH5", - 0x65 => "PUSH6", - 0x66 => "PUSH7", - 0x67 => "PUSH8", - 0x68 => "PUSH9", - 0x69 => "PUSH10", - 0x6a => "PUSH11", - 0x6b => "PUSH12", - 0x6c => "PUSH13", - 0x6d => "PUSH14", - 0x6e => "PUSH15", - 0x6f => "PUSH16", - 0x70 => "PUSH17", - 0x71 => "PUSH18", - 0x72 => "PUSH19", - 0x73 => "PUSH20", - 0x74 => "PUSH21", - 0x75 => "PUSH22", - 0x76 => "PUSH23", - 0x77 => "PUSH24", - 0x78 => "PUSH25", - 0x79 => "PUSH26", - 0x7a => "PUSH27", - 0x7b => "PUSH28", - 0x7c => "PUSH29", - 0x7d => "PUSH30", - 0x7e => "PUSH31", - 0x7f => "PUSH32", - 0x80 => "DUP1", - 0x81 => "DUP2", - 0x82 => "DUP3", - 0x83 => "DUP4", - 0x84 => "DUP5", - 0x85 => "DUP6", - 0x86 => "DUP7", - 0x87 => "DUP8", - 0x88 => "DUP9", - 0x89 => "DUP10", - 0x8a => "DUP11", - 0x8b => "DUP12", - 0x8c => "DUP13", - 0x8d => "DUP14", - 0x8e => "DUP15", - 0x8f => "DUP16", - 0x90 => "SWAP1", - 0x91 => "SWAP2", - 0x92 => "SWAP3", - 0x93 => "SWAP4", - 0x94 => "SWAP5", - 0x95 => "SWAP6", - 0x96 => "SWAP7", - 0x97 => "SWAP8", - 0x98 => "SWAP9", - 0x99 => "SWAP10", - 0x9a => "SWAP11", - 0x9b => "SWAP12", - 0x9c => "SWAP13", - 0x9d => "SWAP14", - 0x9e => "SWAP15", - 0x9f => "SWAP16", - 0xa0 => "LOG0", - 0xa1 => "LOG1", - 0xa2 => "LOG2", - 0xa3 => "LOG3", - 0xa4 => "LOG4", - 0xa5 => "PANIC", - 0xc0 => "MSTORE_32BYTES_1", - 0xc1 => "MSTORE_32BYTES_2", - 0xc2 => "MSTORE_32BYTES_3", - 0xc3 => "MSTORE_32BYTES_4", - 0xc4 => "MSTORE_32BYTES_5", - 0xc5 => "MSTORE_32BYTES_6", - 0xc6 => "MSTORE_32BYTES_7", - 0xc7 => "MSTORE_32BYTES_8", - 0xc8 => "MSTORE_32BYTES_9", - 0xc9 => "MSTORE_32BYTES_10", - 0xca => "MSTORE_32BYTES_11", - 0xcb => "MSTORE_32BYTES_12", - 0xcc => "MSTORE_32BYTES_13", - 0xcd => "MSTORE_32BYTES_14", - 0xce => "MSTORE_32BYTES_15", - 0xcf => "MSTORE_32BYTES_16", - 0xd0 => "MSTORE_32BYTES_17", - 0xd1 => "MSTORE_32BYTES_18", - 0xd2 => "MSTORE_32BYTES_19", - 0xd3 => "MSTORE_32BYTES_20", - 0xd4 => "MSTORE_32BYTES_21", - 0xd5 => "MSTORE_32BYTES_22", - 0xd6 => "MSTORE_32BYTES_23", - 0xd7 => "MSTORE_32BYTES_24", - 0xd8 => "MSTORE_32BYTES_25", - 0xd9 => "MSTORE_32BYTES_26", - 0xda => "MSTORE_32BYTES_27", - 0xdb => "MSTORE_32BYTES_28", - 0xdc => "MSTORE_32BYTES_29", - 0xdd => "MSTORE_32BYTES_30", - 0xde => "MSTORE_32BYTES_31", - 0xdf => "MSTORE_32BYTES_32", - 0xee => "PROVER_INPUT", - 0xf0 => "CREATE", - 0xf1 => "CALL", - 0xf2 => "CALLCODE", - 0xf3 => "RETURN", - 0xf4 => "DELEGATECALL", - 0xf5 => "CREATE2", - 0xf6 => "GET_CONTEXT", - 0xf7 => "SET_CONTEXT", - 0xf8 => "MLOAD_32BYTES", - 0xf9 => "EXIT_KERNEL", - 0xfa => "STATICCALL", - 0xfb => "MLOAD_GENERAL", - 0xfc => "MSTORE_GENERAL", - 0xfd => "REVERT", - 0xfe => "INVALID", - 0xff => "SELFDESTRUCT", - _ => panic!("Unrecognized opcode {opcode}"), - } -} - #[cfg(test)] mod tests { use ethereum_types::U256; diff --git a/evm_arithmetization/src/cpu/kernel/tests/account_code.rs b/evm_arithmetization/src/cpu/kernel/tests/account_code.rs index 0ca79a368..a69d29c83 100644 --- a/evm_arithmetization/src/cpu/kernel/tests/account_code.rs +++ b/evm_arithmetization/src/cpu/kernel/tests/account_code.rs @@ -7,7 +7,7 @@ use keccak_hash::keccak; use mpt_trie::nibbles::Nibbles; use mpt_trie::partial_trie::{HashedPartialTrie, Node, PartialTrie}; use plonky2::field::goldilocks_field::GoldilocksField as F; -use plonky2::field::types::Field; +use plonky2::hash::hash_types::RichField; use rand::{thread_rng, Rng}; use crate::cpu::kernel::aggregator::KERNEL; @@ -25,7 +25,7 @@ use crate::util::h2u; use crate::witness::memory::MemoryAddress; use crate::witness::operation::CONTEXT_SCALING_FACTOR; -pub(crate) fn initialize_mpts( +pub(crate) fn initialize_mpts( interpreter: &mut Interpreter, trie_inputs: &TrieInputs, ) { @@ -132,7 +132,7 @@ pub(crate) fn initialize_mpts( // Stolen from `tests/mpt/insert.rs` // Prepare the interpreter by inserting the account in the state trie. -pub(crate) fn prepare_interpreter( +pub(crate) fn prepare_interpreter( interpreter: &mut Interpreter, address: Address, account: &AccountRlp, @@ -386,7 +386,7 @@ fn test_extcodecopy() -> Result<()> { /// Prepare the interpreter for storage tests by inserting all necessary /// accounts in the state trie, adding the code we want to context 1 and /// switching the context. -fn prepare_interpreter_all_accounts( +fn prepare_interpreter_all_accounts( interpreter: &mut Interpreter, trie_inputs: TrieInputs, addr: [u8; 20], diff --git a/evm_arithmetization/src/cpu/kernel/tests/add11.rs b/evm_arithmetization/src/cpu/kernel/tests/add11.rs index 6f4ec1ec9..9e44c8bfa 100644 --- a/evm_arithmetization/src/cpu/kernel/tests/add11.rs +++ b/evm_arithmetization/src/cpu/kernel/tests/add11.rs @@ -1,3 +1,5 @@ +#![cfg(not(feature = "cdk_erigon"))] + use std::collections::HashMap; use std::str::FromStr; diff --git a/evm_arithmetization/src/cpu/kernel/tests/mod.rs b/evm_arithmetization/src/cpu/kernel/tests/mod.rs index 01cccde9f..39810148b 100644 --- a/evm_arithmetization/src/cpu/kernel/tests/mod.rs +++ b/evm_arithmetization/src/cpu/kernel/tests/mod.rs @@ -34,7 +34,7 @@ use std::{ use anyhow::Result; use ethereum_types::U256; -use plonky2::field::types::Field; +use plonky2::hash::hash_types::RichField; use super::{ aggregator::KERNEL, @@ -59,7 +59,7 @@ pub(crate) fn u256ify<'a>(hexes: impl IntoIterator) -> Result, _>>()?) } -pub(crate) fn run_interpreter( +pub(crate) fn run_interpreter( initial_offset: usize, initial_stack: Vec, ) -> anyhow::Result> { @@ -76,7 +76,7 @@ pub(crate) struct InterpreterMemoryInitialization { pub memory: Vec<(usize, Vec)>, } -pub(crate) fn run_interpreter_with_memory( +pub(crate) fn run_interpreter_with_memory( memory_init: InterpreterMemoryInitialization, ) -> anyhow::Result> { let label = KERNEL.global_labels[&memory_init.label]; @@ -95,7 +95,7 @@ pub(crate) fn run_interpreter_with_memory( Ok(interpreter) } -impl Interpreter { +impl Interpreter { pub(crate) fn get_txn_field(&self, field: NormalizedTxnField) -> U256 { // These fields are already scaled by their respective segment. self.generation_state.memory.contexts[0].segments[Segment::TxnFields.unscale()] diff --git a/evm_arithmetization/src/cpu/kernel/tests/transaction_parsing/mod.rs b/evm_arithmetization/src/cpu/kernel/tests/transaction_parsing/mod.rs index 58f50e2cd..1f70a3a1e 100644 --- a/evm_arithmetization/src/cpu/kernel/tests/transaction_parsing/mod.rs +++ b/evm_arithmetization/src/cpu/kernel/tests/transaction_parsing/mod.rs @@ -1,5 +1,5 @@ use anyhow::{anyhow, Result}; -use plonky2::field::types::Field; +use plonky2::hash::hash_types::RichField; use crate::{ cpu::kernel::{constants::INITIAL_RLP_ADDR, interpreter::Interpreter}, @@ -12,7 +12,7 @@ mod parse_type_2_txn; #[cfg(feature = "eth_mainnet")] mod parse_type_3_txn; -pub(crate) fn prepare_interpreter_for_txn_parsing( +pub(crate) fn prepare_interpreter_for_txn_parsing( interpreter: &mut Interpreter, entry_point: usize, exit_point: usize, diff --git a/evm_arithmetization/src/cpu/kernel/tests/transient_storage.rs b/evm_arithmetization/src/cpu/kernel/tests/transient_storage.rs index f584b1322..a5ca6fa9e 100644 --- a/evm_arithmetization/src/cpu/kernel/tests/transient_storage.rs +++ b/evm_arithmetization/src/cpu/kernel/tests/transient_storage.rs @@ -2,7 +2,7 @@ use anyhow::Result; use ethereum_types::U256; use once_cell::sync::Lazy; use plonky2::field::goldilocks_field::GoldilocksField as F; -use plonky2::field::types::Field; +use plonky2::hash::hash_types::RichField; use crate::cpu::kernel::aggregator::{ combined_kernel_from_files, KERNEL_FILES, NUMBER_KERNEL_FILES, @@ -16,7 +16,7 @@ use crate::memory::segments::Segment; use crate::witness::memory::MemoryAddress; use crate::GenerationInputs; -fn initialize_interpreter(interpreter: &mut Interpreter, gas_limit: U256) { +fn initialize_interpreter(interpreter: &mut Interpreter, gas_limit: U256) { let gas_limit_address = MemoryAddress { context: 0, segment: Segment::ContextMetadata.unscale(), diff --git a/evm_arithmetization/src/cpu/stack.rs b/evm_arithmetization/src/cpu/stack.rs index cd7ca703d..e5b1682ad 100644 --- a/evm_arithmetization/src/cpu/stack.rs +++ b/evm_arithmetization/src/cpu/stack.rs @@ -29,6 +29,8 @@ pub(crate) const MIGHT_OVERFLOW: OpsColumnsView = OpsColumnsView { not_pop: false, shift: false, jumpdest_keccak_general: false, + #[cfg(feature = "cdk_erigon")] + poseidon: false, push_prover_input: true, // PROVER_INPUT doesn't require the check, but PUSH does. jumps: false, pc_push0: true, @@ -101,6 +103,22 @@ pub(crate) const JUMPDEST_OP: StackBehavior = StackBehavior { disable_other_channels: true, }; +#[cfg(feature = "cdk_erigon")] +/// Stack behavior for POSEIDON. +pub(crate) const POSEIDON_OP: StackBehavior = StackBehavior { + num_pops: 3, + pushes: true, + disable_other_channels: true, +}; + +#[cfg(feature = "cdk_erigon")] +/// Stack behavior for POSEIDON_GENERAL. +pub(crate) const POSEIDON_GENERAL_OP: StackBehavior = StackBehavior { + num_pops: 2, + pushes: true, + disable_other_channels: true, +}; + // AUDITORS: If the value below is `None`, then the operation must be manually // checked to ensure that every general-purpose memory channel is either // disabled or has its read flag and address properly constrained. The same @@ -120,6 +138,8 @@ pub(crate) const STACK_BEHAVIORS: OpsColumnsView> = OpsCol disable_other_channels: false, }), jumpdest_keccak_general: None, + #[cfg(feature = "cdk_erigon")] + poseidon: None, push_prover_input: Some(StackBehavior { num_pops: 0, pushes: true, @@ -330,6 +350,23 @@ pub(crate) fn eval_packed( yield_constr, ); + #[cfg(feature = "cdk_erigon")] + { + // Constrain stack for POSEIDON. + let poseidon_filter = lv.op.poseidon * (P::ONES - lv.opcode_bits[0]); + eval_packed_one(lv, nv, poseidon_filter, POSEIDON_OP, yield_constr); + + // Constrain stack for POSEIDON_GENERAL. + let poseidon_general_filter = lv.op.poseidon * lv.opcode_bits[0]; + eval_packed_one( + lv, + nv, + poseidon_general_filter, + POSEIDON_GENERAL_OP, + yield_constr, + ); + } + // Stack constraints for POP. // The only constraints POP has are stack constraints. // Since POP and NOT are combined into one flag and they have @@ -650,6 +687,25 @@ pub(crate) fn eval_ext_circuit, const D: usize>( yield_constr, ); + #[cfg(feature = "cdk_erigon")] + { + // Constrain stack for POSEIDON. + let mut poseidon_filter = builder.sub_extension(one, lv.opcode_bits[0]); + poseidon_filter = builder.mul_extension(lv.op.poseidon, poseidon_filter); + eval_ext_circuit_one(builder, lv, nv, poseidon_filter, POSEIDON_OP, yield_constr); + + // Constrain stack for POSEIDON_GENERAL. + let poseidon_general_filter = builder.mul_extension(lv.op.poseidon, lv.opcode_bits[0]); + eval_ext_circuit_one( + builder, + lv, + nv, + poseidon_general_filter, + POSEIDON_GENERAL_OP, + yield_constr, + ); + } + // Stack constraints for POP. // The only constraints POP has are stack constraints. // Since POP and NOT are combined into one flag and they have diff --git a/evm_arithmetization/src/fixed_recursive_verifier.rs b/evm_arithmetization/src/fixed_recursive_verifier.rs index 47109f20f..be9a3daeb 100644 --- a/evm_arithmetization/src/fixed_recursive_verifier.rs +++ b/evm_arithmetization/src/fixed_recursive_verifier.rs @@ -749,14 +749,22 @@ where let mem_before = RecursiveCircuitsForTable::new( Table::MemBefore, &all_stark.mem_before_stark, - degree_bits_ranges[Table::MemBefore as usize].clone(), + degree_bits_ranges[*Table::MemBefore].clone(), &all_stark.cross_table_lookups, stark_config, ); let mem_after = RecursiveCircuitsForTable::new( Table::MemAfter, &all_stark.mem_after_stark, - degree_bits_ranges[Table::MemAfter as usize].clone(), + degree_bits_ranges[*Table::MemAfter].clone(), + &all_stark.cross_table_lookups, + stark_config, + ); + #[cfg(feature = "cdk_erigon")] + let poseidon = RecursiveCircuitsForTable::new( + Table::Poseidon, + &all_stark.poseidon_stark, + degree_bits_ranges[*Table::Poseidon].clone(), &all_stark.cross_table_lookups, stark_config, ); @@ -771,6 +779,8 @@ where memory, mem_before, mem_after, + #[cfg(feature = "cdk_erigon")] + poseidon, ]; let root = Self::create_segment_circuit(&by_table, stark_config); let segment_aggregation = Self::create_segment_aggregation_circuit(&root); diff --git a/evm_arithmetization/src/generation/linked_list.rs b/evm_arithmetization/src/generation/linked_list.rs index a89e49657..d8a313c90 100644 --- a/evm_arithmetization/src/generation/linked_list.rs +++ b/evm_arithmetization/src/generation/linked_list.rs @@ -32,14 +32,14 @@ pub(crate) fn empty_list_mem(segment: Segment) -> [Option; } impl<'a, const N: usize> LinkedList<'a, N> { - pub fn from_mem_and_segment( + pub const fn from_mem_and_segment( mem: &'a [Option], segment: Segment, ) -> Result { Self::from_mem_len_and_segment(mem, segment) } - pub fn from_mem_len_and_segment( + pub const fn from_mem_len_and_segment( mem: &'a [Option], segment: Segment, ) -> Result { diff --git a/evm_arithmetization/src/generation/mod.rs b/evm_arithmetization/src/generation/mod.rs index e6f33b4cd..f30e1e5de 100644 --- a/evm_arithmetization/src/generation/mod.rs +++ b/evm_arithmetization/src/generation/mod.rs @@ -8,7 +8,6 @@ use log::log_enabled; use mpt_trie::partial_trie::{HashedPartialTrie, PartialTrie}; use plonky2::field::extension::Extendable; use plonky2::field::polynomial::PolynomialValues; -use plonky2::field::types::Field; use plonky2::hash::hash_types::RichField; use plonky2::timed; use plonky2::util::timing::TimingTree; @@ -99,7 +98,7 @@ pub struct GenerationInputs { /// hashes. pub block_hashes: BlockHashes, - /// The the global exit root along with the l1blockhash to write to the GER + /// The global exit root along with the l1blockhash to write to the GER /// manager. /// /// This is specific to `cdk-erigon`. @@ -556,7 +555,7 @@ pub fn generate_traces, const D: usize>( Ok((tables, public_values)) } -fn simulate_cpu( +fn simulate_cpu( state: &mut GenerationState, max_cpu_len_log: Option, ) -> anyhow::Result<(RegistersState, Option)> { diff --git a/evm_arithmetization/src/generation/mpt.rs b/evm_arithmetization/src/generation/mpt.rs index 36f520d9f..adb89b5a6 100644 --- a/evm_arithmetization/src/generation/mpt.rs +++ b/evm_arithmetization/src/generation/mpt.rs @@ -133,7 +133,7 @@ fn parse_storage_value(value_rlp: &[u8]) -> Result, ProgramError> { Ok(vec![value]) } -fn parse_storage_value_no_return(_value_rlp: &[u8]) -> Result, ProgramError> { +const fn parse_storage_value_no_return(_value_rlp: &[u8]) -> Result, ProgramError> { Ok(vec![]) } diff --git a/evm_arithmetization/src/generation/prover_input.rs b/evm_arithmetization/src/generation/prover_input.rs index 1b22f2026..4a5043d92 100644 --- a/evm_arithmetization/src/generation/prover_input.rs +++ b/evm_arithmetization/src/generation/prover_input.rs @@ -7,7 +7,7 @@ use anyhow::{bail, Error, Result}; use ethereum_types::{BigEndianHash, H256, U256, U512}; use itertools::Itertools; use num_bigint::BigUint; -use plonky2::field::types::Field; +use plonky2::hash::hash_types::RichField; use serde::{Deserialize, Serialize}; use super::linked_list::LinkedList; @@ -52,7 +52,7 @@ impl From> for ProverInputFn { } } -impl GenerationState { +impl GenerationState { pub(crate) fn prover_input(&mut self, input_fn: &ProverInputFn) -> Result { match input_fn.0[0].as_str() { "end_of_txns" => self.run_end_of_txns(), @@ -791,7 +791,7 @@ impl GenerationState { } } -impl GenerationState { +impl GenerationState { /// Simulate the user's code and store all the jump addresses with their /// respective contexts. fn generate_jumpdest_table(&mut self) -> Result<(), ProgramError> { diff --git a/evm_arithmetization/src/generation/segments.rs b/evm_arithmetization/src/generation/segments.rs index 424ace9ad..cc90154dc 100644 --- a/evm_arithmetization/src/generation/segments.rs +++ b/evm_arithmetization/src/generation/segments.rs @@ -33,7 +33,7 @@ pub struct GenerationSegmentData { impl GenerationSegmentData { /// Retrieves the index of this segment. - pub fn segment_index(&self) -> usize { + pub const fn segment_index(&self) -> usize { self.segment_index } } diff --git a/evm_arithmetization/src/generation/state.rs b/evm_arithmetization/src/generation/state.rs index de0031366..9eef07f65 100644 --- a/evm_arithmetization/src/generation/state.rs +++ b/evm_arithmetization/src/generation/state.rs @@ -6,7 +6,7 @@ use ethereum_types::{Address, BigEndianHash, H160, H256, U256}; use itertools::Itertools; use keccak_hash::keccak; use log::Level; -use plonky2::field::types::Field; +use plonky2::hash::hash_types::RichField; use super::mpt::TrieRootPtrs; use super::segments::GenerationSegmentData; @@ -22,6 +22,8 @@ use crate::generation::GenerationInputs; use crate::keccak_sponge::columns::KECCAK_WIDTH_BYTES; use crate::keccak_sponge::keccak_sponge_stark::KeccakSpongeOp; use crate::memory::segments::Segment; +#[cfg(feature = "cdk_erigon")] +use crate::poseidon::poseidon_stark::PoseidonOp; use crate::util::u256_to_usize; use crate::witness::errors::ProgramError; use crate::witness::memory::MemoryChannel::GeneralPurpose; @@ -39,7 +41,7 @@ use crate::{arithmetic, keccak, logic}; /// A State is either an `Interpreter` (used for tests and jumpdest analysis) or /// a `GenerationState`. -pub(crate) trait State { +pub(crate) trait State { /// Returns a `State`'s latest `Checkpoint`. fn checkpoint(&mut self) -> GenerationStateCheckpoint; @@ -148,6 +150,11 @@ pub(crate) trait State { .push(op); } + #[cfg(feature = "cdk_erigon")] + fn push_poseidon(&mut self, op: PoseidonOp) { + self.get_mut_generation_state().traces.poseidon_ops.push(op); + } + /// Returns the content of a the `KernelGeneral` segment of a `State`. fn mem_get_kernel_content(&self) -> Vec>; @@ -326,7 +333,7 @@ pub(crate) trait State { } #[derive(Debug, Default)] -pub struct GenerationState { +pub struct GenerationState { pub(crate) inputs: TrimmedGenerationInputs, pub(crate) registers: RegistersState, pub(crate) memory: MemoryState, @@ -368,7 +375,7 @@ pub struct GenerationState { pub(crate) jumpdest_table: Option>>, } -impl GenerationState { +impl GenerationState { fn preinitialize_linked_lists_and_txn_and_receipt_mpts( &mut self, trie_inputs: &TrieInputs, @@ -559,7 +566,7 @@ impl GenerationState { } } -impl State for GenerationState { +impl State for GenerationState { fn checkpoint(&mut self) -> GenerationStateCheckpoint { GenerationStateCheckpoint { registers: self.registers, @@ -665,7 +672,7 @@ impl State for GenerationState { } } -impl Transition for GenerationState { +impl Transition for GenerationState { fn skip_if_necessary(&mut self, op: Operation) -> Result { Ok(op) } diff --git a/evm_arithmetization/src/lib.rs b/evm_arithmetization/src/lib.rs index ae2aa9317..bdabc1c79 100644 --- a/evm_arithmetization/src/lib.rs +++ b/evm_arithmetization/src/lib.rs @@ -203,6 +203,8 @@ pub mod keccak_sponge; pub mod logic; pub mod memory; pub mod memory_continuation; +#[cfg(feature = "cdk_erigon")] +pub mod poseidon; // Proving system components pub mod all_stark; @@ -233,7 +235,7 @@ pub type Node = mpt_trie::partial_trie::Node; /// A type alias for `u64` of a block height. pub type BlockHeight = u64; -pub use all_stark::AllStark; +pub use all_stark::{AllStark, NUM_TABLES}; pub use fixed_recursive_verifier::AllRecursiveCircuits; pub use generation::segments::{GenerationSegmentData, SegmentDataIterator}; pub use generation::GenerationInputs; diff --git a/evm_arithmetization/src/poseidon/columns.rs b/evm_arithmetization/src/poseidon/columns.rs new file mode 100644 index 000000000..cb3823950 --- /dev/null +++ b/evm_arithmetization/src/poseidon/columns.rs @@ -0,0 +1,138 @@ +use std::mem::{size_of, transmute}; + +use plonky2::hash::poseidon; +use zk_evm_proc_macro::Columns; + +use super::poseidon_stark::FELT_MAX_BYTES; +use crate::util::indices_arr; + +pub(crate) const POSEIDON_SPONGE_WIDTH: usize = poseidon::SPONGE_WIDTH; +pub(crate) const POSEIDON_SPONGE_RATE: usize = poseidon::SPONGE_RATE; +pub(crate) const HALF_N_FULL_ROUNDS: usize = poseidon::HALF_N_FULL_ROUNDS; +pub(crate) const N_PARTIAL_ROUNDS: usize = poseidon::N_PARTIAL_ROUNDS; +pub(crate) const POSEIDON_DIGEST: usize = 4; + +#[repr(C)] +#[derive(Columns, Eq, PartialEq, Debug)] +pub(crate) struct PoseidonColumnsView { + // The base address at which we will read the input block. + pub context: T, + pub segment: T, + pub virt: T, + + /// The timestamp at which Poseidon is called. + pub timestamp: T, + + /// The length of the original input for `PoseidonGeneralOp`. 0 for + /// `PoseidonSimpleOp`. + pub len: T, + /// The number of elements that have already been absorbed prior + /// to this block. + pub already_absorbed_elements: T, + + /// If this row represents a final block row, the `i`th entry should be 1 if + /// the final chunk of input has length `i` (in other words if `len - + /// already_absorbed == i`), otherwise 0. + /// + /// If this row represents a full input block, this should contain all 0s. + pub is_final_input_len: [T; POSEIDON_SPONGE_RATE], + + /// 1 if this row represents a full input block, i.e. one in which each + /// element is an input element, not a padding element; 0 otherwise. + pub is_full_input_block: T, + + /// Registers to hold permutation inputs. + pub input: [T; POSEIDON_SPONGE_WIDTH], + + /// Holds x^3 for all elements in full rounds. + pub cubed_full: [T; 2 * HALF_N_FULL_ROUNDS * POSEIDON_SPONGE_WIDTH], + + /// Holds x^3 for the first element in partial rounds. + pub cubed_partial: [T; N_PARTIAL_ROUNDS], + + /// Holds the input of the `i`-th S-box of the `round`-th round of the first + /// set of full rounds. + pub full_sbox_0: [T; POSEIDON_SPONGE_WIDTH * (HALF_N_FULL_ROUNDS - 1)], + + /// Holds the input of the S-box of the `round`-th round of the partial + /// rounds. + pub partial_sbox: [T; N_PARTIAL_ROUNDS], + + /// Holds the input of the `i`-th S-box of the `round`-th round of the + /// second set of full rounds. + pub full_sbox_1: [T; POSEIDON_SPONGE_WIDTH * HALF_N_FULL_ROUNDS], + + /// The digest, with each element divided into two 32-bit limbs. + pub digest: [T; 2 * POSEIDON_DIGEST], + + /// The output of the hash function with the digest removed. + pub output_partial: [T; POSEIDON_SPONGE_WIDTH - POSEIDON_DIGEST], + + /// Holds the pseudo-inverse of (digest_high_limb_i - 2^32 + 1). + pub pinv: [T; POSEIDON_DIGEST], + + /// Holds the byte decomposition of the input, except for the less + /// significant byte. + pub input_bytes: [[T; FELT_MAX_BYTES - 1]; POSEIDON_SPONGE_RATE], + + /// Indicates if this is a simple operation where inputs are + /// read from the top of the stack. + pub is_simple_op: T, + + /// Indicates if this is the first row of a general operation. + pub is_first_row_general_op: T, + + pub not_padding: T, +} + +/// Returns the index the `i`-th x^3 in the `round`-th round for full rounds. +/// Note: the cubes of the two sets of full rounds are stored one after the +/// other. +pub(crate) fn reg_cubed_full(round: usize, i: usize) -> usize { + debug_assert!(i < POSEIDON_SPONGE_WIDTH); + debug_assert!(round < 2 * HALF_N_FULL_ROUNDS); + POSEIDON_SPONGE_WIDTH * round + i +} + +/// Returns the index of x^3 within for the `round`-th partial round. +pub(crate) fn reg_cubed_partial(round: usize) -> usize { + debug_assert!(round < N_PARTIAL_ROUNDS); + round +} + +/// Returns the index of the `i`-th input in the `round`-th round within +/// `full_sbox_0`. +pub(crate) fn reg_full_sbox_0(round: usize, i: usize) -> usize { + debug_assert!( + round != 0, + "First round S-box inputs are not stored as wires" + ); + debug_assert!(round < HALF_N_FULL_ROUNDS); + debug_assert!(i < POSEIDON_SPONGE_WIDTH); + POSEIDON_SPONGE_WIDTH * (round - 1) + i +} + +/// Returns the index of the input of the S-box of the `round`-th round of the +/// partial rounds. +pub(crate) fn reg_partial_sbox(round: usize) -> usize { + debug_assert!(round < N_PARTIAL_ROUNDS); + round +} + +/// Returns the index of the `i`-th input in the `round`-th round within +/// `full_sbox_1`. +pub(crate) fn reg_full_sbox_1(round: usize, i: usize) -> usize { + debug_assert!(round < HALF_N_FULL_ROUNDS); + debug_assert!(i < POSEIDON_SPONGE_WIDTH); + POSEIDON_SPONGE_WIDTH * round + i +} + +// `u8` is guaranteed to have a `size_of` of 1. +pub(crate) const NUM_COLUMNS: usize = size_of::>(); + +const fn make_col_map() -> PoseidonColumnsView { + let indices_arr = indices_arr::(); + unsafe { transmute::<[usize; NUM_COLUMNS], PoseidonColumnsView>(indices_arr) } +} + +pub(crate) const POSEIDON_COL_MAP: PoseidonColumnsView = make_col_map(); diff --git a/evm_arithmetization/src/poseidon/mod.rs b/evm_arithmetization/src/poseidon/mod.rs new file mode 100644 index 000000000..5ee77f125 --- /dev/null +++ b/evm_arithmetization/src/poseidon/mod.rs @@ -0,0 +1,2 @@ +pub mod columns; +pub mod poseidon_stark; diff --git a/evm_arithmetization/src/poseidon/poseidon_stark.rs b/evm_arithmetization/src/poseidon/poseidon_stark.rs new file mode 100644 index 000000000..0aacbaa50 --- /dev/null +++ b/evm_arithmetization/src/poseidon/poseidon_stark.rs @@ -0,0 +1,1004 @@ +use std::borrow::Borrow; +use std::marker::PhantomData; + +use itertools::Itertools; +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 plonky2::hash::poseidon::Poseidon; +use plonky2::iop::ext_target::ExtensionTarget; +use plonky2::timed; +use plonky2::util::timing::TimingTree; +use starky::constraint_consumer::{ConstraintConsumer, RecursiveConstraintConsumer}; +use starky::cross_table_lookup::TableWithColumns; +use starky::evaluation_frame::StarkEvaluationFrame; +use starky::lookup::{Column, Filter}; +use starky::stark::Stark; +use starky::util::trace_rows_to_poly_values; + +use super::columns::{ + reg_cubed_full, reg_cubed_partial, reg_full_sbox_0, reg_full_sbox_1, reg_partial_sbox, + PoseidonColumnsView, HALF_N_FULL_ROUNDS, NUM_COLUMNS, N_PARTIAL_ROUNDS, POSEIDON_COL_MAP, + POSEIDON_DIGEST, POSEIDON_SPONGE_RATE, POSEIDON_SPONGE_WIDTH, +}; +use crate::all_stark::{EvmStarkFrame, Table}; +use crate::witness::memory::MemoryAddress; + +/// Maximum number of bytes that can be packed into a field element without +/// performing a modular reduction. +// TODO(Alonso): this constant depends on the size of F, which is not bounded. +pub(crate) const FELT_MAX_BYTES: usize = 7; + +pub(crate) fn ctl_looked_simple_op() -> TableWithColumns { + let mut columns = Column::singles(POSEIDON_COL_MAP.input).collect_vec(); + columns.extend(Column::singles(POSEIDON_COL_MAP.digest)); + TableWithColumns::new( + *Table::Poseidon, + columns, + Filter::new_simple(Column::single(POSEIDON_COL_MAP.is_simple_op)), + ) +} + +pub(crate) fn ctl_looked_general_output() -> TableWithColumns { + let mut columns = Column::singles(POSEIDON_COL_MAP.digest).collect_vec(); + columns.push(Column::single(POSEIDON_COL_MAP.timestamp)); + TableWithColumns::new( + *Table::Poseidon, + columns, + Filter::new( + vec![( + Column::sum(POSEIDON_COL_MAP.is_final_input_len), + Column::linear_combination_with_constant( + [(POSEIDON_COL_MAP.is_simple_op, -F::ONE)], + F::ONE, + ), + )], + vec![], + ), + ) +} + +pub(crate) fn ctl_looked_general_input() -> TableWithColumns { + let context = Column::single(POSEIDON_COL_MAP.context); + let segment: Column = Column::single(POSEIDON_COL_MAP.segment); + let virt = Column::single(POSEIDON_COL_MAP.virt); + let len = Column::single(POSEIDON_COL_MAP.len); + + let timestamp = Column::single(POSEIDON_COL_MAP.timestamp); + + TableWithColumns::new( + *Table::Poseidon, + vec![context, segment, virt, len, timestamp], + Filter::new_simple(Column::single(POSEIDON_COL_MAP.is_first_row_general_op)), + ) +} + +pub(crate) fn ctl_looking_memory(i: usize) -> Vec> { + let cols = POSEIDON_COL_MAP; + let mut res = vec![Column::constant(F::ONE)]; // is_read + + res.extend(Column::singles([cols.context, cols.segment])); + + res.push(Column::linear_combination_with_constant( + [ + (cols.virt, F::ONE), + (cols.already_absorbed_elements, F::ONE), + ], + F::from_canonical_usize(i), + )); + + // The first byte of of each field element is + // mem[virt + already_absorbed_elements + i/ FELT_MAX_BYTES] - \sum_j + // input_bytes[i/FELT_MAX_BYTES][j]* 256^j. + // The other bytes are input_bytes[i/FELT_MAX_BYTES][i % FELT_MAX_BYTES - 1] + if i % FELT_MAX_BYTES == 0 { + res.push(Column::linear_combination( + std::iter::once((cols.input[i / FELT_MAX_BYTES], F::ONE)).chain( + (0..FELT_MAX_BYTES - 1).map(|j| { + ( + cols.input_bytes[i / FELT_MAX_BYTES][j], + -F::from_canonical_u64(1 << (8 * (j + 1))), + ) + }), + ), + )); + } else { + res.push(Column::single( + cols.input_bytes[i / FELT_MAX_BYTES][(i % FELT_MAX_BYTES) - 1], + )); + } + res.extend((1..8).map(|_| Column::zero())); + + res.push(Column::single(cols.timestamp)); + + assert_eq!( + res.len(), + crate::memory::memory_stark::ctl_data::().len() + ); + + res +} + +pub(crate) fn ctl_looking_memory_filter() -> Filter { + let cols = POSEIDON_COL_MAP; + Filter::new( + vec![( + Column::single(cols.not_padding), + Column::linear_combination_with_constant([(cols.is_simple_op, -F::ONE)], F::ONE), + )], + vec![], + ) +} + +#[derive(Clone, Debug)] +pub(crate) enum PoseidonOp { + PoseidonSimpleOp(PoseidonSimpleOp), + PoseidonGeneralOp(PoseidonGeneralOp), +} + +#[derive(Copy, Clone, Debug)] +pub(crate) struct PoseidonSimpleOp(pub [F; POSEIDON_SPONGE_WIDTH]); + +#[derive(Clone, Debug)] +pub(crate) struct PoseidonGeneralOp { + /// The base address at which inputs are read. + pub(crate) base_address: MemoryAddress, + + /// The timestamp at which inputs are read. + pub(crate) timestamp: usize, + + /// The input that was read. We assume that it was + /// previously padded. + pub(crate) input: Vec, + + /// Length of the input before paddding. + pub(crate) len: usize, +} + +#[derive(Copy, Clone, Default)] +pub(crate) struct PoseidonStark { + pub(crate) f: PhantomData, +} + +/// Information about a Poseidon operation needed for witness generation. +impl, const D: usize> PoseidonStark { + /// 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, + operations: Vec>, + min_rows: usize, + ) -> Vec<[F; NUM_COLUMNS]> { + let base_len: usize = operations + .iter() + .map(|op| match op { + PoseidonOp::PoseidonSimpleOp(_) => 1, + PoseidonOp::PoseidonGeneralOp(op) => { + debug_assert!(op.input.len() % (FELT_MAX_BYTES * POSEIDON_SPONGE_RATE) == 0); + (op.input.len() + FELT_MAX_BYTES * POSEIDON_SPONGE_RATE - 1) + / (FELT_MAX_BYTES * POSEIDON_SPONGE_RATE) + } + }) + .sum(); + + let num_rows = base_len.max(min_rows).next_power_of_two(); + let mut rows = Vec::with_capacity(base_len.max(min_rows)); + + for op in operations { + match op { + PoseidonOp::PoseidonSimpleOp(op) => rows.push(self.generate_row_for_simple_op(op)), + PoseidonOp::PoseidonGeneralOp(op) => { + rows.extend(self.generate_rows_for_general_op(op)) + } + } + } + + // We generate "actual" rows for padding to avoid having to store + // another power of x, on top of x^3 and x^6. + let padding_row: [F; NUM_COLUMNS] = { + let mut tmp_row = PoseidonColumnsView::default(); + let padding_inp = [F::ZERO; POSEIDON_SPONGE_WIDTH]; + Self::generate_perm(&mut tmp_row, padding_inp); + tmp_row + } + .into(); + while rows.len() < num_rows { + rows.push(padding_row); + } + rows + } + + fn generate_row_for_simple_op(&self, op: PoseidonSimpleOp) -> [F; NUM_COLUMNS] { + let mut row = PoseidonColumnsView::default(); + Self::generate_perm(&mut row, op.0); + row.is_final_input_len[POSEIDON_SPONGE_RATE - 1] = F::ONE; + row.not_padding = F::ONE; + row.is_simple_op = F::ONE; + row.into() + } + + fn generate_rows_for_general_op(&self, op: PoseidonGeneralOp) -> Vec<[F; NUM_COLUMNS]> { + let input_blocks = op.input.chunks_exact(FELT_MAX_BYTES * POSEIDON_SPONGE_RATE); + let mut rows: Vec<[F; NUM_COLUMNS]> = + Vec::with_capacity(op.input.len() / (FELT_MAX_BYTES * POSEIDON_SPONGE_RATE)); + let last_non_padding_elt = op.len % (FELT_MAX_BYTES * POSEIDON_SPONGE_RATE); + let total_length = input_blocks.len(); + let mut already_absorbed_elements = 0; + let mut state = [F::ZERO; POSEIDON_SPONGE_WIDTH]; + for (counter, block) in input_blocks.enumerate() { + state[0..POSEIDON_SPONGE_RATE].copy_from_slice( + &block + .chunks_exact(FELT_MAX_BYTES) + .map(|first_bytes| { + let mut bytes = [0u8; POSEIDON_SPONGE_RATE]; + bytes[..7].copy_from_slice(first_bytes); + F::from_canonical_u64(u64::from_le_bytes(bytes)) + }) + .collect::>(), + ); + let mut row = if counter == total_length - 1 { + let tmp_row = + self.generate_trace_final_row_for_perm(state, &op, already_absorbed_elements); + already_absorbed_elements += last_non_padding_elt; + tmp_row + } else { + let tmp_row = + self.generate_trace_row_for_perm(state, &op, already_absorbed_elements); + already_absorbed_elements += FELT_MAX_BYTES * POSEIDON_SPONGE_RATE; + tmp_row + }; + row.not_padding = F::ONE; + for (input_bytes_chunk, block_chunk) in row + .input_bytes + .iter_mut() + .zip_eq(block.chunks(FELT_MAX_BYTES)) + { + input_bytes_chunk.copy_from_slice( + &block_chunk[1..] + .iter() + .map(|&byte| F::from_canonical_u8(byte)) + .collect::>(), + ); + } + // Set the capacity to the digest + state[POSEIDON_SPONGE_RATE..POSEIDON_SPONGE_WIDTH].copy_from_slice( + &row.digest + .chunks(2) + .map(|x| x[0] + F::from_canonical_u64(1 << 32) * x[1]) + .collect_vec(), + ); + + rows.push(row.into()); + } + if let Some(first_row) = rows.first_mut() { + first_row[POSEIDON_COL_MAP.is_first_row_general_op] = F::ONE; + } + + rows + } + + fn generate_commons( + row: &mut PoseidonColumnsView, + input: [F; POSEIDON_SPONGE_WIDTH], + op: &PoseidonGeneralOp, + already_absorbed_elements: usize, + ) { + row.context = F::from_canonical_usize(op.base_address.context); + row.segment = F::from_canonical_usize(op.base_address.segment); + row.virt = F::from_canonical_usize(op.base_address.virt); + row.timestamp = F::from_canonical_usize(op.timestamp); + row.len = F::from_canonical_usize(op.len); + row.already_absorbed_elements = F::from_canonical_usize(already_absorbed_elements); + + Self::generate_perm(row, input); + } + // One row per permutation. + fn generate_trace_row_for_perm( + &self, + input: [F; POSEIDON_SPONGE_WIDTH], + op: &PoseidonGeneralOp, + already_absorbed_elements: usize, + ) -> PoseidonColumnsView { + let mut row = PoseidonColumnsView::default(); + row.is_full_input_block = F::ONE; + + Self::generate_commons(&mut row, input, op, already_absorbed_elements); + row + } + + fn generate_trace_final_row_for_perm( + &self, + input: [F; POSEIDON_SPONGE_WIDTH], + op: &PoseidonGeneralOp, + already_absorbed_elements: usize, + ) -> PoseidonColumnsView { + let mut row = PoseidonColumnsView::default(); + // TODO(Alonso): I think we're assumming op.len is a multiple FELT_MAX_BYTES * + // POSEIDON_SPONGE_RATE. + row.is_final_input_len[op.len % (FELT_MAX_BYTES * POSEIDON_SPONGE_RATE)] = F::ONE; + + Self::generate_commons(&mut row, input, op, already_absorbed_elements); + + row + } + + fn generate_perm(row: &mut PoseidonColumnsView, input: [F; POSEIDON_SPONGE_WIDTH]) { + // Populate the round input for the first round. + row.input.copy_from_slice(&input); + + let mut state = input; + let mut round_ctr = 0; + + for r in 0..HALF_N_FULL_ROUNDS { + ::constant_layer_field(&mut state, round_ctr); + + for i in 0..POSEIDON_SPONGE_WIDTH { + // We do not need to store the first full_sbox_0 inputs, since they are + // the permutation's inputs. + if r != 0 { + row.full_sbox_0[reg_full_sbox_0(r, i)] = state[i]; + } + // Generate x^3 and x^6 for the SBox layer constraints. + row.cubed_full[reg_cubed_full(r, i)] = state[i].cube(); + + // Apply x^7 to the state. + state[i] *= + row.cubed_full[reg_cubed_full(r, i)] * row.cubed_full[reg_cubed_full(r, i)]; + } + state = ::mds_layer_field(&state); + round_ctr += 1; + } + + ::partial_first_constant_layer(&mut state); + state = ::mds_partial_layer_init(&state); + for r in 0..(N_PARTIAL_ROUNDS - 1) { + row.partial_sbox[reg_partial_sbox(r)] = state[0]; + + // Generate x^3 for the SBox layer constraints. + row.cubed_partial[reg_cubed_partial(r)] = state[0] * state[0] * state[0]; + + state[0] *= + row.cubed_partial[reg_cubed_partial(r)] * row.cubed_partial[reg_cubed_partial(r)]; + state[0] += F::from_canonical_u64(::FAST_PARTIAL_ROUND_CONSTANTS[r]); + state = ::mds_partial_layer_fast_field(&state, r); + } + + row.partial_sbox[reg_partial_sbox(N_PARTIAL_ROUNDS - 1)] = state[0]; + // Generate x^3 and x^6 for the SBox layer constraints. + row.cubed_partial[reg_cubed_partial(N_PARTIAL_ROUNDS - 1)] = state[0].cube(); + + state[0] *= row.cubed_partial[reg_cubed_partial(N_PARTIAL_ROUNDS - 1)] + * row.cubed_partial[reg_cubed_partial(N_PARTIAL_ROUNDS - 1)]; + state = ::mds_partial_layer_fast_field(&state, N_PARTIAL_ROUNDS - 1); + round_ctr += N_PARTIAL_ROUNDS; + + for r in 0..HALF_N_FULL_ROUNDS { + ::constant_layer_field(&mut state, round_ctr); + for i in 0..POSEIDON_SPONGE_WIDTH { + row.full_sbox_1[reg_full_sbox_1(r, i)] = state[i]; + // Generate x^3 and x^6 for the SBox layer constraints. + row.cubed_full[reg_cubed_full(HALF_N_FULL_ROUNDS + r, i)] = state[i].cube(); + + state[i] *= row.cubed_full[reg_cubed_full(HALF_N_FULL_ROUNDS + r, i)] + * row.cubed_full[reg_cubed_full(HALF_N_FULL_ROUNDS + r, i)]; + } + state = ::mds_layer_field(&state); + round_ctr += 1; + } + + for i in 0..POSEIDON_DIGEST { + let state_val = state[i].to_canonical_u64(); + let hi_limb = F::from_canonical_u32((state_val >> 32) as u32); + row.pinv[i] = + if let Some(inv) = (hi_limb - F::from_canonical_u32(u32::MAX)).try_inverse() { + inv + } else { + F::ZERO + }; + row.digest[2 * i] = F::from_canonical_u32(state_val as u32); + row.digest[2 * i + 1] = hi_limb; + } + row.output_partial + .copy_from_slice(&state[POSEIDON_DIGEST..POSEIDON_SPONGE_WIDTH]); + } + + pub(crate) fn generate_trace( + &self, + operations: Vec>, + min_rows: usize, + timing: &mut TimingTree, + ) -> Vec> { + // Generate the witness, except for permuted columns in the lookup argument. + let trace_rows = timed!( + timing, + "generate trace rows", + self.generate_trace_rows(operations, min_rows) + ); + let trace_polys = timed!( + timing, + "convert to PolynomialValues", + trace_rows_to_poly_values(trace_rows) + ); + trace_polys + } +} + +impl, const D: usize> Stark for PoseidonStark { + type EvaluationFrame = EvmStarkFrame + where + FE: FieldExtension, + P: PackedField; + + type EvaluationFrameTarget = EvmStarkFrame, ExtensionTarget, NUM_COLUMNS>; + + fn eval_packed_generic( + &self, + vars: &Self::EvaluationFrame, + yield_constr: &mut ConstraintConsumer

, + ) where + FE: FieldExtension, + P: PackedField, + { + let lv: &[P; NUM_COLUMNS] = vars.get_local_values().try_into().unwrap(); + let lv: &PoseidonColumnsView

= lv.borrow(); + let nv: &[P; NUM_COLUMNS] = vars.get_next_values().try_into().unwrap(); + let nv: &PoseidonColumnsView

= nv.borrow(); + + // Each flag must be boolean. + let is_full_input_block = lv.is_full_input_block; + yield_constr.constraint(is_full_input_block * (is_full_input_block - P::ONES)); + + let is_final_block: P = lv.is_final_input_len.iter().copied().sum(); + yield_constr.constraint(is_final_block * (is_final_block - P::ONES)); + + for &is_final_len in lv.is_final_input_len.iter() { + yield_constr.constraint(is_final_len * (is_final_len - P::ONES)); + } + + let is_first_row_general_op = lv.is_first_row_general_op; + yield_constr.constraint(is_first_row_general_op * (is_first_row_general_op - P::ONES)); + + // Ensure that full-input block and final block flags are not set to 1 at the + // same time. + yield_constr.constraint(is_final_block * is_full_input_block); + + // If this is the first row, the original sponge state should have the input in + // the first `POSEIDON_SPONGE_RATE` elements followed by 0 for the + // capacity elements. The input values are checked with a CTL. + // Also, already_absorbed_elements = 0. + let already_absorbed_elements = lv.already_absorbed_elements; + yield_constr.constraint_first_row(already_absorbed_elements); + + for i in POSEIDON_SPONGE_RATE..POSEIDON_SPONGE_WIDTH { + // If the operation has len > 0 the capacity must be 0 + yield_constr.constraint_first_row(lv.len * lv.input[i]); + } + + // If this is a final row and there is an upcoming operation, then + // we make the previous checks for next row's `already_absorbed_elements` + // and the original sponge state. + yield_constr.constraint_transition(is_final_block * nv.already_absorbed_elements); + + for i in POSEIDON_SPONGE_RATE..POSEIDON_SPONGE_WIDTH { + // If the next block is a general operation (len > 0) and this is a final block, + // the capacity must be 0 for the next row. + yield_constr.constraint_transition(nv.len * is_final_block * nv.input[i]); + } + + // If this is a full-input block, the next row's address, + // time and len must match as well as its timestamp. + yield_constr.constraint_transition(is_full_input_block * (lv.context - nv.context)); + yield_constr.constraint_transition(is_full_input_block * (lv.segment - nv.segment)); + yield_constr.constraint_transition(is_full_input_block * (lv.virt - nv.virt)); + yield_constr.constraint_transition(is_full_input_block * (lv.timestamp - nv.timestamp)); + + // If this is a full-input block, the next row's already_absorbed_elements + // should be ours plus `POSEIDON_SPONGE_RATE`, and the next input's + // capacity is the current digest. + yield_constr.constraint_transition( + is_full_input_block + * (already_absorbed_elements + + P::from(FE::from_canonical_usize( + FELT_MAX_BYTES * POSEIDON_SPONGE_RATE, + )) + - nv.already_absorbed_elements), + ); + + for i in 0..POSEIDON_SPONGE_WIDTH - POSEIDON_SPONGE_RATE { + yield_constr.constraint_transition( + is_full_input_block + * (lv.digest[2 * i] + + lv.digest[2 * i + 1] * P::Scalar::from_canonical_u64(1 << 32) + - nv.input[POSEIDON_SPONGE_RATE + i]), + ); + } + + // A dummy row is always followed by another dummy row, so the prover + // can't put dummy rows "in between" to avoid the above checks. + let is_dummy = P::ONES - is_full_input_block - is_final_block; + let next_is_final_block: P = nv.is_final_input_len.iter().copied().sum(); + yield_constr + .constraint_transition(is_dummy * (nv.is_full_input_block + next_is_final_block)); + + // If len > 0 and this is a final block, is_final_input_len implies `len + // - already_absorbed == i`. + let offset = lv.len - already_absorbed_elements; + for (i, &is_final_len) in lv.is_final_input_len.iter().enumerate() { + let entry_match = offset + - P::from(FE::from_canonical_usize( + FELT_MAX_BYTES * POSEIDON_SPONGE_RATE - i, + )); + yield_constr.constraint(lv.len * is_final_len * entry_match); + } + + // Compute the input layer. We assume that, when necessary, + // input values were previously swapped before being passed + // to Poseidon. + let mut state = lv.input; + + let mut round_ctr = 0; + + // First set of full rounds. + for r in 0..HALF_N_FULL_ROUNDS { + ::constant_layer_packed_field(&mut state, round_ctr); + + for i in 0..POSEIDON_SPONGE_WIDTH { + if r != 0 { + let sbox_in = lv.full_sbox_0[reg_full_sbox_0(r, i)]; + yield_constr.constraint(state[i] - sbox_in); + state[i] = sbox_in; + } + + // Check that the powers were correctly generated. + let cube = state[i] * state[i] * state[i]; + yield_constr.constraint(cube - lv.cubed_full[reg_cubed_full(r, i)]); + + state[i] *= + lv.cubed_full[reg_cubed_full(r, i)] * lv.cubed_full[reg_cubed_full(r, i)]; + } + + state = ::mds_layer_packed_field(&state); + round_ctr += 1; + } + + // Partial rounds. + ::partial_first_constant_layer_packed_field(&mut state); + state = ::mds_partial_layer_init_packed_field(&state); + for r in 0..(N_PARTIAL_ROUNDS - 1) { + let sbox_in = lv.partial_sbox[reg_partial_sbox(r)]; + yield_constr.constraint(state[0] - sbox_in); + state[0] = sbox_in; + + // Check that the powers were generated correctly. + let cube = state[0] * state[0] * state[0]; + yield_constr.constraint(cube - lv.cubed_partial[reg_cubed_partial(r)]); + + state[0] = lv.cubed_partial[reg_cubed_partial(r)] + * lv.cubed_partial[reg_cubed_partial(r)] + * sbox_in; + state[0] += + P::Scalar::from_canonical_u64(::FAST_PARTIAL_ROUND_CONSTANTS[r]); + state = ::mds_partial_layer_fast_packed_field(&state, r); + } + let sbox_in = lv.partial_sbox[reg_partial_sbox(N_PARTIAL_ROUNDS - 1)]; + yield_constr.constraint(state[0] - sbox_in); + state[0] = sbox_in; + + // Check that the powers were generated correctly. + let cube = state[0] * state[0] * state[0]; + yield_constr.constraint(cube - lv.cubed_partial[reg_cubed_partial(N_PARTIAL_ROUNDS - 1)]); + + state[0] = lv.cubed_partial[reg_cubed_partial(N_PARTIAL_ROUNDS - 1)] + * lv.cubed_partial[reg_cubed_partial(N_PARTIAL_ROUNDS - 1)] + * sbox_in; + state = ::mds_partial_layer_fast_packed_field(&state, N_PARTIAL_ROUNDS - 1); + round_ctr += N_PARTIAL_ROUNDS; + + // Second set of full rounds. + for r in 0..HALF_N_FULL_ROUNDS { + ::constant_layer_packed_field(&mut state, round_ctr); + for i in 0..POSEIDON_SPONGE_WIDTH { + let sbox_in = lv.full_sbox_1[reg_full_sbox_1(r, i)]; + yield_constr.constraint(state[i] - sbox_in); + state[i] = sbox_in; + + // Check that the powers were correctly generated. + let cube = state[i] * state[i] * state[i]; + yield_constr + .constraint(cube - lv.cubed_full[reg_cubed_full(HALF_N_FULL_ROUNDS + r, i)]); + + state[i] *= lv.cubed_full[reg_cubed_full(HALF_N_FULL_ROUNDS + r, i)] + * lv.cubed_full[reg_cubed_full(HALF_N_FULL_ROUNDS + r, i)]; + } + state = ::mds_layer_packed_field(&state); + round_ctr += 1; + } + + for i in 0..POSEIDON_DIGEST { + yield_constr.constraint( + state[i] + - (lv.digest[2 * i] + + lv.digest[2 * i + 1] * P::Scalar::from_canonical_u64(1 << 32)), + ); + } + for i in POSEIDON_DIGEST..POSEIDON_SPONGE_WIDTH { + yield_constr.constraint(state[i] - lv.output_partial[i - POSEIDON_DIGEST]) + } + + // Ensure that the output limbs are written in canonical form. + for i in 0..POSEIDON_DIGEST { + let constr = ((lv.digest[2 * i + 1] - P::Scalar::from_canonical_u32(u32::MAX)) + * lv.pinv[i] + - P::ONES) + * lv.digest[2 * i]; + yield_constr.constraint(constr); + } + } + + fn eval_ext_circuit( + &self, + builder: &mut plonky2::plonk::circuit_builder::CircuitBuilder, + vars: &Self::EvaluationFrameTarget, + yield_constr: &mut RecursiveConstraintConsumer, + ) { + let lv: &[ExtensionTarget; NUM_COLUMNS] = vars.get_local_values().try_into().unwrap(); + let lv: &PoseidonColumnsView> = lv.borrow(); + let nv: &[ExtensionTarget; NUM_COLUMNS] = vars.get_next_values().try_into().unwrap(); + let nv: &PoseidonColumnsView> = nv.borrow(); + + //Each flag (full-input block, final block or implied dummy flag) must be + //boolean. + let is_full_input_block = lv.is_full_input_block; + let constr = builder.mul_sub_extension( + is_full_input_block, + is_full_input_block, + is_full_input_block, + ); + yield_constr.constraint(builder, constr); + + let is_final_block = builder.add_many_extension(lv.is_final_input_len); + let constr = builder.mul_sub_extension(is_final_block, is_final_block, is_final_block); + yield_constr.constraint(builder, constr); + + for &is_final_len in lv.is_final_input_len.iter() { + let constr = builder.mul_sub_extension(is_final_len, is_final_len, is_final_len); + yield_constr.constraint(builder, constr); + } + + let one = builder.one_extension(); + let is_first_row_general_op = lv.is_first_row_general_op; + let constr = builder.mul_sub_extension( + is_first_row_general_op, + is_first_row_general_op, + is_first_row_general_op, + ); + yield_constr.constraint(builder, constr); + + // Ensure that full-input block and final block flags are not set to 1 at the + // same time. + let constr = builder.mul_extension(is_final_block, is_full_input_block); + yield_constr.constraint(builder, constr); + + // If this is the first row, the original sponge state should have the input in + // the first `POSEIDON_SPONGE_RATE` elements followed by 0 for the + // capacity elements. Also, already_absorbed_elements = 0. + let already_absorbed_elements = lv.already_absorbed_elements; + yield_constr.constraint_first_row(builder, already_absorbed_elements); + + for i in POSEIDON_SPONGE_RATE..POSEIDON_SPONGE_WIDTH { + let constr = builder.mul_extension(lv.input[i], lv.len); + yield_constr.constraint_first_row(builder, constr); + } + + // If this is a final row and there is an upcoming operation, then + // we make the previous checks for next row's `already_absorbed_elements` + // and the original sponge state. + let constr = builder.mul_extension(is_final_block, nv.already_absorbed_elements); + yield_constr.constraint_transition(builder, constr); + + for i in POSEIDON_SPONGE_RATE..POSEIDON_SPONGE_WIDTH { + let mut constr = builder.mul_extension(is_final_block, nv.input[i]); + constr = builder.mul_extension(constr, nv.len); + yield_constr.constraint_transition(builder, constr); + } + + // If this is a full-input block, the next row's address, + // time and len must match as well as its timestamp. + let mut constr = builder.sub_extension(lv.context, nv.context); + constr = builder.mul_extension(is_full_input_block, constr); + yield_constr.constraint_transition(builder, constr); + let mut constr = builder.sub_extension(lv.segment, nv.segment); + constr = builder.mul_extension(is_full_input_block, constr); + yield_constr.constraint_transition(builder, constr); + let mut constr = builder.sub_extension(lv.virt, nv.virt); + constr = builder.mul_extension(is_full_input_block, constr); + yield_constr.constraint_transition(builder, constr); + let mut constr = builder.sub_extension(lv.timestamp, nv.timestamp); + constr = builder.mul_extension(is_full_input_block, constr); + yield_constr.constraint_transition(builder, constr); + + // If this is a full-input block, the next row's already_absorbed_elements + // should be ours plus `POSEIDON_SPONGE_RATE`, and the next input's + // capacity is the current output's capacity. + let diff = builder.sub_extension(already_absorbed_elements, nv.already_absorbed_elements); + let constr = builder.arithmetic_extension( + F::ONE, + F::from_canonical_usize(FELT_MAX_BYTES * POSEIDON_SPONGE_RATE), + diff, + is_full_input_block, + is_full_input_block, + ); + yield_constr.constraint_transition(builder, constr); + + for i in 0..POSEIDON_SPONGE_WIDTH - POSEIDON_SPONGE_RATE { + let mut constr = builder.mul_const_add_extension( + F::from_canonical_u64(1 << 32), + lv.digest[2 * i + 1], + lv.digest[2 * i], + ); + constr = builder.sub_extension(constr, nv.input[POSEIDON_SPONGE_RATE + i]); + constr = builder.mul_extension(is_full_input_block, constr); + yield_constr.constraint_transition(builder, constr); + } + + // A dummy row is always followed by another dummy row, so the prover can't put + // dummy rows "in between" to avoid the above checks. + let mut is_dummy = builder.add_extension(is_full_input_block, is_final_block); + is_dummy = builder.sub_extension(one, is_dummy); + let next_is_final_block = builder.add_many_extension(nv.is_final_input_len.iter()); + let mut constr = builder.add_extension(nv.is_full_input_block, next_is_final_block); + constr = builder.mul_extension(is_dummy, constr); + yield_constr.constraint_transition(builder, constr); + + // If len > 0 and this is a final block, is_final_input_len implies `len - + // already_absorbed == i` + let offset = builder.sub_extension(lv.len, already_absorbed_elements); + for (i, &is_final_len) in lv.is_final_input_len.iter().enumerate() { + let index = builder.constant_extension( + F::from_canonical_usize(FELT_MAX_BYTES * POSEIDON_SPONGE_RATE - i).into(), + ); + let entry_match = builder.sub_extension(offset, index); + let mut constr = builder.mul_extension(is_final_len, entry_match); + constr = builder.mul_extension(constr, lv.len); + yield_constr.constraint(builder, constr); + } + + // Compute the input layer. We assume that, when necessary, + // input values were previously swapped before being passed + // to Poseidon. + let mut state = lv.input; + + let mut round_ctr = 0; + + // First set of full rounds. + for r in 0..HALF_N_FULL_ROUNDS { + ::constant_layer_circuit(builder, &mut state, round_ctr); + for i in 0..POSEIDON_SPONGE_WIDTH { + if r != 0 { + let sbox_in = lv.full_sbox_0[reg_full_sbox_0(r, i)]; + let constr = builder.sub_extension(state[i], sbox_in); + yield_constr.constraint(builder, constr); + state[i] = sbox_in; + } + + // Check that the powers were correctly generated. + let cube = builder.mul_many_extension([state[i], state[i], state[i]]); + let constr = builder.sub_extension(cube, lv.cubed_full[reg_cubed_full(r, i)]); + yield_constr.constraint(builder, constr); + + // Update the i'th element of the state. + state[i] = builder.mul_many_extension([ + state[i], + lv.cubed_full[reg_cubed_full(r, i)], + lv.cubed_full[reg_cubed_full(r, i)], + ]); + } + + state = ::mds_layer_circuit(builder, &state); + round_ctr += 1; + } + + // Partial rounds. + ::partial_first_constant_layer_circuit(builder, &mut state); + state = ::mds_partial_layer_init_circuit(builder, &state); + for r in 0..(N_PARTIAL_ROUNDS - 1) { + let sbox_in = lv.partial_sbox[reg_partial_sbox(r)]; + let constr = builder.sub_extension(state[0], sbox_in); + yield_constr.constraint(builder, constr); + state[0] = sbox_in; + + // Check that the powers were generated correctly. + let cube = builder.mul_many_extension([state[0], state[0], state[0]]); + let constr = builder.sub_extension(cube, lv.cubed_partial[reg_cubed_partial(r)]); + yield_constr.constraint(builder, constr); + + // Update state[0]. + state[0] = builder.mul_many_extension([ + lv.cubed_partial[reg_cubed_partial(r)], + lv.cubed_partial[reg_cubed_partial(r)], + sbox_in, + ]); + state[0] = builder.add_const_extension( + state[0], + F::from_canonical_u64(::FAST_PARTIAL_ROUND_CONSTANTS[r]), + ); + state = ::mds_partial_layer_fast_circuit(builder, &state, r); + } + let sbox_in = lv.partial_sbox[reg_partial_sbox(N_PARTIAL_ROUNDS - 1)]; + let constr = builder.sub_extension(state[0], sbox_in); + yield_constr.constraint(builder, constr); + state[0] = sbox_in; + + // Check that the powers were generated correctly. + let mut constr = builder.mul_many_extension([state[0], state[0], state[0]]); + constr = builder.sub_extension( + constr, + lv.cubed_partial[reg_cubed_partial(N_PARTIAL_ROUNDS - 1)], + ); + yield_constr.constraint(builder, constr); + + state[0] = builder.mul_many_extension([ + lv.cubed_partial[reg_cubed_partial(N_PARTIAL_ROUNDS - 1)], + lv.cubed_partial[reg_cubed_partial(N_PARTIAL_ROUNDS - 1)], + sbox_in, + ]); + state = + ::mds_partial_layer_fast_circuit(builder, &state, N_PARTIAL_ROUNDS - 1); + round_ctr += N_PARTIAL_ROUNDS; + + // Second set of full rounds. + for r in 0..HALF_N_FULL_ROUNDS { + ::constant_layer_circuit(builder, &mut state, round_ctr); + for i in 0..POSEIDON_SPONGE_WIDTH { + let sbox_in = lv.full_sbox_1[reg_full_sbox_1(r, i)]; + let constr = builder.sub_extension(state[i], sbox_in); + yield_constr.constraint(builder, constr); + state[i] = sbox_in; + + // Check that the powers were correctly generated. + let mut constr = builder.mul_many_extension([state[i], state[i], state[i]]); + constr = builder.sub_extension( + constr, + lv.cubed_full[reg_cubed_full(HALF_N_FULL_ROUNDS + r, i)], + ); + yield_constr.constraint(builder, constr); + + // Update the i'th element of the state. + state[i] = builder.mul_many_extension([ + lv.cubed_full[reg_cubed_full(HALF_N_FULL_ROUNDS + r, i)], + lv.cubed_full[reg_cubed_full(HALF_N_FULL_ROUNDS + r, i)], + state[i], + ]); + } + + state = ::mds_layer_circuit(builder, &state); + round_ctr += 1; + } + + for i in 0..POSEIDON_DIGEST { + let val = builder.mul_const_add_extension( + F::from_canonical_u64(1 << 32), + lv.digest[2 * i + 1], + lv.digest[2 * i], + ); + let constr = builder.sub_extension(state[i], val); + yield_constr.constraint(builder, constr); + } + for i in POSEIDON_DIGEST..POSEIDON_SPONGE_WIDTH { + let constr = builder.sub_extension(state[i], lv.output_partial[i - POSEIDON_DIGEST]); + yield_constr.constraint(builder, constr); + } + + // Ensure that the output limbs are written in canonical form. + for i in 0..POSEIDON_DIGEST { + let mut constr = builder.arithmetic_extension( + F::ONE, + F::NEG_ONE * F::from_canonical_u32(u32::MAX), + lv.digest[2 * i + 1], + lv.pinv[i], + lv.pinv[i], + ); + constr = builder.mul_sub_extension(lv.digest[2 * i], constr, lv.digest[2 * i]); + + yield_constr.constraint(builder, constr); + } + } + + fn constraint_degree(&self) -> usize { + 3 + } + + fn requires_ctls(&self) -> bool { + true + } +} + +#[cfg(test)] +mod tests { + use anyhow::Result; + use plonky2::{ + field::{goldilocks_field::GoldilocksField, types::PrimeField64}, + plonk::config::{GenericConfig, PoseidonGoldilocksConfig}, + }; + use smt_trie::code::poseidon_hash_padded_byte_vec; + use starky::stark_testing::{test_stark_circuit_constraints, test_stark_low_degree}; + + use super::*; + + #[test] + fn test_stark_degree() -> Result<()> { + const D: usize = 2; + type C = PoseidonGoldilocksConfig; + type F = >::F; + type S = PoseidonStark; + + let stark = S { + f: Default::default(), + }; + test_stark_low_degree(stark) + } + + #[test] + fn test_stark_circuit() -> Result<()> { + const D: usize = 2; + type C = PoseidonGoldilocksConfig; + type F = >::F; + type S = PoseidonStark; + + let stark = S { + f: Default::default(), + }; + test_stark_circuit_constraints::(stark) + } + + #[test] + fn poseidon_correctness_test() -> Result<()> { + let input: Vec = (0..POSEIDON_SPONGE_RATE * FELT_MAX_BYTES) + .map(|_| rand::random()) + .collect(); + let int_inputs = PoseidonOp::PoseidonGeneralOp(PoseidonGeneralOp { + base_address: MemoryAddress::new( + 0, + crate::memory::segments::Segment::AccessedAddresses, + 0, + ), + input: input.clone(), + timestamp: 0, + len: POSEIDON_SPONGE_RATE * FELT_MAX_BYTES, + }); + const D: usize = 2; + type F = GoldilocksField; + type S = PoseidonStark; + + let stark = S { + f: Default::default(), + }; + + let rows = stark.generate_trace_rows(vec![int_inputs], 8); + assert_eq!(rows.len(), 8); + let last_row: &PoseidonColumnsView<_> = rows[0].borrow(); + let output: Vec<_> = (0..POSEIDON_DIGEST) + .map(|i| { + last_row.digest[2 * i] + F::from_canonical_u64(1 << 32) * last_row.digest[2 * i + 1] + }) + .collect(); + + let hash = poseidon_hash_padded_byte_vec(input); + + assert_eq!( + output + .iter() + .map(|x| x.to_noncanonical_u64()) + .collect::>(), + hash.elements + .iter() + .map(|x| x.to_noncanonical_u64()) + .collect::>() + ); + + Ok(()) + } +} diff --git a/evm_arithmetization/src/proof.rs b/evm_arithmetization/src/proof.rs index 8d9a00cb0..8c4742a07 100644 --- a/evm_arithmetization/src/proof.rs +++ b/evm_arithmetization/src/proof.rs @@ -925,7 +925,7 @@ pub enum BurnAddrTarget { } impl BurnAddrTarget { - pub fn get_size() -> usize { + pub const fn get_size() -> usize { match cfg!(feature = "cdk_erigon") { true => 8, false => 0, diff --git a/evm_arithmetization/src/prover.rs b/evm_arithmetization/src/prover.rs index 95e0fd780..736c900ef 100644 --- a/evm_arithmetization/src/prover.rs +++ b/evm_arithmetization/src/prover.rs @@ -245,9 +245,9 @@ where prove_single_table( &all_stark.arithmetic_stark, config, - &trace_poly_values[Table::Arithmetic as usize], - &trace_commitments[Table::Arithmetic as usize], - &ctl_data_per_table[Table::Arithmetic as usize], + &trace_poly_values[*Table::Arithmetic], + &trace_commitments[*Table::Arithmetic], + &ctl_data_per_table[*Table::Arithmetic], ctl_challenges, challenger, timing, @@ -260,9 +260,9 @@ where prove_single_table( &all_stark.byte_packing_stark, config, - &trace_poly_values[Table::BytePacking as usize], - &trace_commitments[Table::BytePacking as usize], - &ctl_data_per_table[Table::BytePacking as usize], + &trace_poly_values[*Table::BytePacking], + &trace_commitments[*Table::BytePacking], + &ctl_data_per_table[*Table::BytePacking], ctl_challenges, challenger, timing, @@ -275,9 +275,9 @@ where prove_single_table( &all_stark.cpu_stark, config, - &trace_poly_values[Table::Cpu as usize], - &trace_commitments[Table::Cpu as usize], - &ctl_data_per_table[Table::Cpu as usize], + &trace_poly_values[*Table::Cpu], + &trace_commitments[*Table::Cpu], + &ctl_data_per_table[*Table::Cpu], ctl_challenges, challenger, timing, @@ -290,9 +290,9 @@ where prove_single_table( &all_stark.keccak_stark, config, - &trace_poly_values[Table::Keccak as usize], - &trace_commitments[Table::Keccak as usize], - &ctl_data_per_table[Table::Keccak as usize], + &trace_poly_values[*Table::Keccak], + &trace_commitments[*Table::Keccak], + &ctl_data_per_table[*Table::Keccak], ctl_challenges, challenger, timing, @@ -305,9 +305,9 @@ where prove_single_table( &all_stark.keccak_sponge_stark, config, - &trace_poly_values[Table::KeccakSponge as usize], - &trace_commitments[Table::KeccakSponge as usize], - &ctl_data_per_table[Table::KeccakSponge as usize], + &trace_poly_values[*Table::KeccakSponge], + &trace_commitments[*Table::KeccakSponge], + &ctl_data_per_table[*Table::KeccakSponge], ctl_challenges, challenger, timing, @@ -320,9 +320,9 @@ where prove_single_table( &all_stark.logic_stark, config, - &trace_poly_values[Table::Logic as usize], - &trace_commitments[Table::Logic as usize], - &ctl_data_per_table[Table::Logic as usize], + &trace_poly_values[*Table::Logic], + &trace_commitments[*Table::Logic], + &ctl_data_per_table[*Table::Logic], ctl_challenges, challenger, timing, @@ -335,9 +335,9 @@ where prove_single_table( &all_stark.memory_stark, config, - &trace_poly_values[Table::Memory as usize], - &trace_commitments[Table::Memory as usize], - &ctl_data_per_table[Table::Memory as usize], + &trace_poly_values[*Table::Memory], + &trace_commitments[*Table::Memory], + &ctl_data_per_table[*Table::Memory], ctl_challenges, challenger, timing, @@ -350,9 +350,9 @@ where prove_single_table( &all_stark.mem_before_stark, config, - &trace_poly_values[Table::MemBefore as usize], - &trace_commitments[Table::MemBefore as usize], - &ctl_data_per_table[Table::MemBefore as usize], + &trace_poly_values[*Table::MemBefore], + &trace_commitments[*Table::MemBefore], + &ctl_data_per_table[*Table::MemBefore], ctl_challenges, challenger, timing, @@ -365,9 +365,25 @@ where prove_single_table( &all_stark.mem_after_stark, config, - &trace_poly_values[Table::MemAfter as usize], - &trace_commitments[Table::MemAfter as usize], - &ctl_data_per_table[Table::MemAfter as usize], + &trace_poly_values[*Table::MemAfter], + &trace_commitments[*Table::MemAfter], + &ctl_data_per_table[*Table::MemAfter], + ctl_challenges, + challenger, + timing, + abort_signal.clone(), + )? + ); + #[cfg(feature = "cdk_erigon")] + let (poseidon_proof, _) = timed!( + timing, + "prove poseidon STARK", + prove_single_table( + &all_stark.poseidon_stark, + config, + &trace_poly_values[*Table::Poseidon], + &trace_commitments[*Table::Poseidon], + &ctl_data_per_table[*Table::Poseidon], ctl_challenges, challenger, timing, @@ -386,6 +402,8 @@ where memory_proof, mem_before_proof, mem_after_proof, + #[cfg(feature = "cdk_erigon")] + poseidon_proof, ], mem_before_cap, mem_after_cap, diff --git a/evm_arithmetization/src/verifier.rs b/evm_arithmetization/src/verifier.rs index e62c59cb1..89220b607 100644 --- a/evm_arithmetization/src/verifier.rs +++ b/evm_arithmetization/src/verifier.rs @@ -141,6 +141,8 @@ fn verify_proof, C: GenericConfig, const memory_stark, mem_before_stark, mem_after_stark, + #[cfg(feature = "cdk_erigon")] + poseidon_stark, cross_table_lookups, } = all_stark; @@ -156,74 +158,83 @@ fn verify_proof, C: GenericConfig, const verify_stark_proof_with_challenges( arithmetic_stark, - &stark_proofs[Table::Arithmetic as usize].proof, - &stark_challenges[Table::Arithmetic as usize], - Some(&ctl_vars_per_table[Table::Arithmetic as usize]), + &stark_proofs[*Table::Arithmetic].proof, + &stark_challenges[*Table::Arithmetic], + Some(&ctl_vars_per_table[*Table::Arithmetic]), &[], config, )?; verify_stark_proof_with_challenges( byte_packing_stark, - &stark_proofs[Table::BytePacking as usize].proof, - &stark_challenges[Table::BytePacking as usize], - Some(&ctl_vars_per_table[Table::BytePacking as usize]), + &stark_proofs[*Table::BytePacking].proof, + &stark_challenges[*Table::BytePacking], + Some(&ctl_vars_per_table[*Table::BytePacking]), &[], config, )?; verify_stark_proof_with_challenges( cpu_stark, - &stark_proofs[Table::Cpu as usize].proof, - &stark_challenges[Table::Cpu as usize], - Some(&ctl_vars_per_table[Table::Cpu as usize]), + &stark_proofs[*Table::Cpu].proof, + &stark_challenges[*Table::Cpu], + Some(&ctl_vars_per_table[*Table::Cpu]), &[], config, )?; verify_stark_proof_with_challenges( keccak_stark, - &stark_proofs[Table::Keccak as usize].proof, - &stark_challenges[Table::Keccak as usize], - Some(&ctl_vars_per_table[Table::Keccak as usize]), + &stark_proofs[*Table::Keccak].proof, + &stark_challenges[*Table::Keccak], + Some(&ctl_vars_per_table[*Table::Keccak]), &[], config, )?; verify_stark_proof_with_challenges( keccak_sponge_stark, - &stark_proofs[Table::KeccakSponge as usize].proof, - &stark_challenges[Table::KeccakSponge as usize], - Some(&ctl_vars_per_table[Table::KeccakSponge as usize]), + &stark_proofs[*Table::KeccakSponge].proof, + &stark_challenges[*Table::KeccakSponge], + Some(&ctl_vars_per_table[*Table::KeccakSponge]), &[], config, )?; verify_stark_proof_with_challenges( logic_stark, - &stark_proofs[Table::Logic as usize].proof, - &stark_challenges[Table::Logic as usize], - Some(&ctl_vars_per_table[Table::Logic as usize]), + &stark_proofs[*Table::Logic].proof, + &stark_challenges[*Table::Logic], + Some(&ctl_vars_per_table[*Table::Logic]), &[], config, )?; verify_stark_proof_with_challenges( memory_stark, - &stark_proofs[Table::Memory as usize].proof, - &stark_challenges[Table::Memory as usize], - Some(&ctl_vars_per_table[Table::Memory as usize]), + &stark_proofs[*Table::Memory].proof, + &stark_challenges[*Table::Memory], + Some(&ctl_vars_per_table[*Table::Memory]), &[], config, )?; verify_stark_proof_with_challenges( mem_before_stark, - &stark_proofs[Table::MemBefore as usize].proof, - &stark_challenges[Table::MemBefore as usize], - Some(&ctl_vars_per_table[Table::MemBefore as usize]), + &stark_proofs[*Table::MemBefore].proof, + &stark_challenges[*Table::MemBefore], + Some(&ctl_vars_per_table[*Table::MemBefore]), &[], config, )?; verify_stark_proof_with_challenges( mem_after_stark, - &stark_proofs[Table::MemAfter as usize].proof, - &stark_challenges[Table::MemAfter as usize], - Some(&ctl_vars_per_table[Table::MemAfter as usize]), + &stark_proofs[*Table::MemAfter].proof, + &stark_challenges[*Table::MemAfter], + Some(&ctl_vars_per_table[*Table::MemAfter]), + &[], + config, + )?; + #[cfg(feature = "cdk_erigon")] + verify_stark_proof_with_challenges( + poseidon_stark, + &stark_proofs[*Table::Poseidon].proof, + &stark_challenges[*Table::Poseidon], + Some(&ctl_vars_per_table[*Table::Poseidon]), &[], config, )?; @@ -240,7 +251,7 @@ fn verify_proof, C: GenericConfig, const let mut extra_looking_sums = vec![vec![F::ZERO; config.num_challenges]; NUM_TABLES]; // Memory - extra_looking_sums[Table::Memory as usize] = (0..config.num_challenges) + extra_looking_sums[*Table::Memory] = (0..config.num_challenges) .map(|i| get_memory_extra_looking_sum(&public_values, ctl_challenges.challenges[i])) .collect_vec(); diff --git a/evm_arithmetization/src/witness/gas.rs b/evm_arithmetization/src/witness/gas.rs index 54597a3eb..3a184d3d0 100644 --- a/evm_arithmetization/src/witness/gas.rs +++ b/evm_arithmetization/src/witness/gas.rs @@ -35,6 +35,10 @@ pub(crate) const fn gas_to_charge(op: Operation) -> u64 { TernaryArithmetic(MulMod) => G_MID, TernaryArithmetic(SubMod) => KERNEL_ONLY_INSTR, KeccakGeneral => KERNEL_ONLY_INSTR, + #[cfg(feature = "cdk_erigon")] + Poseidon => KERNEL_ONLY_INSTR, + #[cfg(feature = "cdk_erigon")] + PoseidonGeneral => KERNEL_ONLY_INSTR, ProverInput => KERNEL_ONLY_INSTR, Pop => G_BASE, Jump => G_MID, diff --git a/evm_arithmetization/src/witness/operation.rs b/evm_arithmetization/src/witness/operation.rs index 0076388ce..55f3d3c18 100644 --- a/evm_arithmetization/src/witness/operation.rs +++ b/evm_arithmetization/src/witness/operation.rs @@ -1,7 +1,7 @@ use ethereum_types::{BigEndianHash, U256}; use itertools::Itertools; use keccak_hash::keccak; -use plonky2::field::types::Field; +use plonky2::hash::hash_types::RichField; use super::state::KERNEL_CONTEXT; use super::transition::Transition; @@ -40,6 +40,10 @@ pub(crate) enum Operation { BinaryArithmetic(arithmetic::BinaryOperator), TernaryArithmetic(arithmetic::TernaryOperator), KeccakGeneral, + #[cfg(feature = "cdk_erigon")] + Poseidon, + #[cfg(feature = "cdk_erigon")] + PoseidonGeneral, ProverInput, Pop, Jump, @@ -66,7 +70,7 @@ pub(crate) const CONTEXT_SCALING_FACTOR: usize = 64; /// operation. Generates a new logic operation and adds it to the vector of /// operation in `LogicStark`. Adds three memory read operations to /// `MemoryStark`: for the two inputs and the output. -pub(crate) fn generate_binary_logic_op>( +pub(crate) fn generate_binary_logic_op>( op: logic::Op, state: &mut T, mut row: CpuColumnsView, @@ -84,7 +88,7 @@ pub(crate) fn generate_binary_logic_op>( Ok(()) } -pub(crate) fn generate_binary_arithmetic_op>( +pub(crate) fn generate_binary_arithmetic_op>( operator: arithmetic::BinaryOperator, state: &mut T, mut row: CpuColumnsView, @@ -115,7 +119,7 @@ pub(crate) fn generate_binary_arithmetic_op>( Ok(()) } -pub(crate) fn generate_ternary_arithmetic_op>( +pub(crate) fn generate_ternary_arithmetic_op>( operator: arithmetic::TernaryOperator, state: &mut T, mut row: CpuColumnsView, @@ -134,7 +138,7 @@ pub(crate) fn generate_ternary_arithmetic_op>( Ok(()) } -pub(crate) fn generate_keccak_general>( +pub(crate) fn generate_keccak_general>( state: &mut T, mut row: CpuColumnsView, ) -> Result<(), ProgramError> { @@ -166,7 +170,95 @@ pub(crate) fn generate_keccak_general>( Ok(()) } -pub(crate) fn generate_prover_input>( +#[cfg(feature = "cdk_erigon")] +/// Pops 3 elements `x,y,z` from the stack, and returns `Poseidon(x || y || +/// z)[0..4]`, where values are split into 64-bit limbs, and `z` is used as the +/// capacity. Limbs are range-checked to be in canonical form in the +/// PoseidonStark. +pub(crate) fn generate_poseidon>( + state: &mut T, + mut row: CpuColumnsView, +) -> Result<(), ProgramError> { + use crate::poseidon::poseidon_stark::{PoseidonOp, PoseidonSimpleOp}; + + let generation_state = state.get_mut_generation_state(); + let [(x, _), (y, log_in1), (z, log_in2)] = + stack_pop_with_log_and_fill::<3, _>(generation_state, &mut row)?; + let arr = [ + x.0[0], x.0[1], x.0[2], x.0[3], y.0[0], y.0[1], y.0[2], y.0[3], z.0[0], z.0[1], z.0[2], + z.0[3], + ] + .map(F::from_canonical_u64); + let hash = F::poseidon(arr); + let hash = U256(std::array::from_fn(|i| hash[i].to_canonical_u64())); + log::debug!("Poseidon hashing {:?} -> {}", arr, hash); + push_no_write(generation_state, hash); + + state.push_poseidon(PoseidonOp::PoseidonSimpleOp(PoseidonSimpleOp(arr))); + + state.push_memory(log_in1); + state.push_memory(log_in2); + state.push_cpu(row); + Ok(()) +} + +#[cfg(feature = "cdk_erigon")] +pub(crate) fn generate_poseidon_general>( + state: &mut T, + mut row: CpuColumnsView, +) -> Result<(), ProgramError> { + use smt_trie::{code::poseidon_hash_padded_byte_vec, utils::hashout2u}; + + use crate::{ + cpu::membus::NUM_CHANNELS, + poseidon::poseidon_stark::{PoseidonGeneralOp, PoseidonOp}, + }; + + let clock = state.get_clock(); + let generation_state = state.get_mut_generation_state(); + let [(addr, _), (len, log_in1)] = + stack_pop_with_log_and_fill::<2, _>(generation_state, &mut row)?; + let len = u256_to_usize(len)?; + + let base_address = MemoryAddress::new_bundle(addr)?; + let input = (0..len) + .map(|i| { + let address = MemoryAddress { + virt: base_address.virt.saturating_add(i), + ..base_address + }; + let val = generation_state.memory.get_with_init(address); + generation_state.traces.memory_ops.push(MemoryOp::new( + MemoryChannel::Code, + clock, + address, + MemoryOpKind::Read, + val.0[0].into(), + )); + + val.0[0] as u8 + }) + .collect_vec(); + + let poseidon_op = PoseidonOp::PoseidonGeneralOp(PoseidonGeneralOp { + base_address, + timestamp: clock * NUM_CHANNELS, + input: input.clone(), + len: input.len(), + }); + + let hash = hashout2u(poseidon_hash_padded_byte_vec(input.clone())); + + push_no_write(generation_state, hash); + + state.push_poseidon(poseidon_op); + + state.push_memory(log_in1); + state.push_cpu(row); + Ok(()) +} + +pub(crate) fn generate_prover_input>( state: &mut T, mut row: CpuColumnsView, ) -> Result<(), ProgramError> { @@ -194,7 +286,7 @@ pub(crate) fn generate_prover_input>( Ok(()) } -pub(crate) fn generate_pop>( +pub(crate) fn generate_pop>( state: &mut T, mut row: CpuColumnsView, ) -> Result<(), ProgramError> { @@ -217,7 +309,7 @@ pub(crate) fn generate_pop>( Ok(()) } -pub(crate) fn generate_pc>( +pub(crate) fn generate_pc>( state: &mut T, mut row: CpuColumnsView, ) -> Result<(), ProgramError> { @@ -230,7 +322,7 @@ pub(crate) fn generate_pc>( Ok(()) } -pub(crate) fn generate_jumpdest>( +pub(crate) fn generate_jumpdest>( state: &mut T, row: CpuColumnsView, ) -> Result<(), ProgramError> { @@ -238,7 +330,7 @@ pub(crate) fn generate_jumpdest>( Ok(()) } -pub(crate) fn generate_get_context>( +pub(crate) fn generate_get_context>( state: &mut T, mut row: CpuColumnsView, ) -> Result<(), ProgramError> { @@ -274,7 +366,7 @@ pub(crate) fn generate_get_context>( Ok(()) } -pub(crate) fn generate_set_context>( +pub(crate) fn generate_set_context>( state: &mut T, mut row: CpuColumnsView, ) -> Result<(), ProgramError> { @@ -359,7 +451,7 @@ pub(crate) fn generate_set_context>( Ok(()) } -pub(crate) fn generate_push>( +pub(crate) fn generate_push>( n: u8, state: &mut T, mut row: CpuColumnsView, @@ -410,7 +502,7 @@ pub(crate) fn generate_push>( // - Update `stack_top` with `val` and add 1 to `stack_len` // Since the write must happen before the read, the normal way of assigning // GP channels doesn't work and we must handle them manually. -pub(crate) fn generate_dup>( +pub(crate) fn generate_dup>( n: u8, state: &mut T, mut row: CpuColumnsView, @@ -475,7 +567,7 @@ pub(crate) fn generate_dup>( Ok(()) } -pub(crate) fn generate_swap>( +pub(crate) fn generate_swap>( n: u8, state: &mut T, mut row: CpuColumnsView, @@ -504,7 +596,7 @@ pub(crate) fn generate_swap>( Ok(()) } -pub(crate) fn generate_not>( +pub(crate) fn generate_not>( state: &mut T, mut row: CpuColumnsView, ) -> Result<(), ProgramError> { @@ -528,7 +620,7 @@ pub(crate) fn generate_not>( Ok(()) } -pub(crate) fn generate_iszero>( +pub(crate) fn generate_iszero>( state: &mut T, mut row: CpuColumnsView, ) -> Result<(), ProgramError> { @@ -547,7 +639,7 @@ pub(crate) fn generate_iszero>( Ok(()) } -fn append_shift>( +fn append_shift>( state: &mut T, mut row: CpuColumnsView, is_shl: bool, @@ -594,7 +686,7 @@ fn append_shift>( Ok(()) } -pub(crate) fn generate_shl>( +pub(crate) fn generate_shl>( state: &mut T, mut row: CpuColumnsView, ) -> Result<(), ProgramError> { @@ -610,7 +702,7 @@ pub(crate) fn generate_shl>( append_shift(state, row, true, input0, input1, log_in1, result) } -pub(crate) fn generate_shr>( +pub(crate) fn generate_shr>( state: &mut T, mut row: CpuColumnsView, ) -> Result<(), ProgramError> { @@ -625,7 +717,7 @@ pub(crate) fn generate_shr>( append_shift(state, row, false, input0, input1, log_in1, result) } -pub(crate) fn generate_syscall>( +pub(crate) fn generate_syscall>( opcode: u8, stack_values_read: usize, stack_len_increased: bool, @@ -716,7 +808,7 @@ pub(crate) fn generate_syscall>( Ok(()) } -pub(crate) fn generate_eq>( +pub(crate) fn generate_eq>( state: &mut T, mut row: CpuColumnsView, ) -> Result<(), ProgramError> { @@ -734,7 +826,7 @@ pub(crate) fn generate_eq>( Ok(()) } -pub(crate) fn generate_exit_kernel>( +pub(crate) fn generate_exit_kernel>( state: &mut T, mut row: CpuColumnsView, ) -> Result<(), ProgramError> { @@ -763,7 +855,7 @@ pub(crate) fn generate_exit_kernel>( Ok(()) } -pub(crate) fn generate_mload_general>( +pub(crate) fn generate_mload_general>( state: &mut T, mut row: CpuColumnsView, ) -> Result<(), ProgramError> { @@ -796,7 +888,7 @@ pub(crate) fn generate_mload_general>( Ok(()) } -pub(crate) fn generate_mload_32bytes>( +pub(crate) fn generate_mload_32bytes>( state: &mut T, mut row: CpuColumnsView, ) -> Result<(), ProgramError> { @@ -836,7 +928,7 @@ pub(crate) fn generate_mload_32bytes>( Ok(()) } -pub(crate) fn generate_mstore_general>( +pub(crate) fn generate_mstore_general>( state: &mut T, mut row: CpuColumnsView, ) -> Result<(), ProgramError> { @@ -866,7 +958,7 @@ pub(crate) fn generate_mstore_general>( Ok(()) } -pub(crate) fn generate_mstore_32bytes>( +pub(crate) fn generate_mstore_32bytes>( n: u8, state: &mut T, mut row: CpuColumnsView, @@ -886,7 +978,7 @@ pub(crate) fn generate_mstore_32bytes>( Ok(()) } -pub(crate) fn generate_exception>( +pub(crate) fn generate_exception>( exc_code: u8, state: &mut T, mut row: CpuColumnsView, diff --git a/evm_arithmetization/src/witness/traces.rs b/evm_arithmetization/src/witness/traces.rs index fe59bba59..3ff68d8b6 100644 --- a/evm_arithmetization/src/witness/traces.rs +++ b/evm_arithmetization/src/witness/traces.rs @@ -1,5 +1,6 @@ use plonky2::field::extension::Extendable; use plonky2::field::polynomial::PolynomialValues; +use plonky2::field::types::Field; use plonky2::hash::hash_types::RichField; use plonky2::timed; use plonky2::util::timing::TimingTree; @@ -13,6 +14,8 @@ use crate::cpu::columns::CpuColumnsView; use crate::generation::MemBeforeValues; use crate::keccak_sponge::keccak_sponge_stark::KeccakSpongeOp; use crate::memory_continuation::memory_continuation_stark::mem_before_values_to_rows; +#[cfg(feature = "cdk_erigon")] +use crate::poseidon::poseidon_stark::PoseidonOp; use crate::witness::memory::MemoryOp; use crate::{arithmetic, keccak, keccak_sponge, logic}; @@ -25,10 +28,12 @@ pub(crate) struct TraceCheckpoint { pub(self) keccak_sponge_len: usize, pub(self) logic_len: usize, pub(self) memory_len: usize, + #[cfg(feature = "cdk_erigon")] + pub(self) poseidon_len: usize, } #[derive(Debug)] -pub(crate) struct Traces { +pub(crate) struct Traces { pub(crate) arithmetic_ops: Vec, pub(crate) byte_packing_ops: Vec, pub(crate) cpu: Vec>, @@ -36,9 +41,11 @@ pub(crate) struct Traces { pub(crate) memory_ops: Vec, pub(crate) keccak_inputs: Vec<([u64; keccak::keccak_stark::NUM_INPUTS], usize)>, pub(crate) keccak_sponge_ops: Vec, + #[cfg(feature = "cdk_erigon")] + pub(crate) poseidon_ops: Vec>, } -impl Traces { +impl Traces { pub(crate) const fn new() -> Self { Traces { arithmetic_ops: vec![], @@ -48,6 +55,8 @@ impl Traces { memory_ops: vec![], keccak_inputs: vec![], keccak_sponge_ops: vec![], + #[cfg(feature = "cdk_erigon")] + poseidon_ops: vec![], } } @@ -84,6 +93,8 @@ impl Traces { // This is technically a lower-bound, as we may fill gaps, // but this gives a relatively good estimate. memory_len: self.memory_ops.len(), + #[cfg(feature = "cdk_erigon")] + poseidon_len: self.poseidon_ops.len(), } } @@ -97,6 +108,8 @@ impl Traces { keccak_sponge_len: self.keccak_sponge_ops.len(), logic_len: self.logic_ops.len(), memory_len: self.memory_ops.len(), + #[cfg(feature = "cdk_erigon")] + poseidon_len: self.poseidon_ops.len(), } } @@ -109,6 +122,8 @@ impl Traces { .truncate(checkpoint.keccak_sponge_len); self.logic_ops.truncate(checkpoint.logic_len); self.memory_ops.truncate(checkpoint.memory_len); + #[cfg(feature = "cdk_erigon")] + self.poseidon_ops.truncate(checkpoint.poseidon_len); } pub(crate) fn mem_ops_since(&self, checkpoint: TraceCheckpoint) -> &[MemoryOp] { @@ -140,16 +155,18 @@ impl Traces { memory_ops, keccak_inputs, keccak_sponge_ops, + #[cfg(feature = "cdk_erigon")] + poseidon_ops, } = self; let arithmetic_trace = timed!( timing, - "generate arithmetic trace", + "generate Arithmetic trace", all_stark.arithmetic_stark.generate_trace(arithmetic_ops) ); let byte_packing_trace = timed!( timing, - "generate byte packing trace", + "generate BytePacking trace", all_stark .byte_packing_stark .generate_trace(byte_packing_ops, cap_elements, timing) @@ -165,21 +182,21 @@ impl Traces { ); let keccak_sponge_trace = timed!( timing, - "generate Keccak sponge trace", + "generate KeccakSponge trace", all_stark .keccak_sponge_stark .generate_trace(keccak_sponge_ops, cap_elements, timing) ); let logic_trace = timed!( timing, - "generate logic trace", + "generate Logic trace", all_stark .logic_stark .generate_trace(logic_ops, cap_elements, timing) ); let (memory_trace, final_values, unpadded_memory_length) = timed!( timing, - "generate memory trace", + "generate Memory trace", all_stark.memory_stark.generate_trace( memory_ops, mem_before_values, @@ -187,23 +204,33 @@ impl Traces { timing ) ); + trace_lengths.memory_len = unpadded_memory_length; let mem_before_trace = timed!( timing, - "generate mem_before trace", + "generate MemBefore trace", all_stark .mem_before_stark .generate_trace(mem_before_values_to_rows(mem_before_values)) ); let mem_after_trace = timed!( timing, - "generate mem_after trace", + "generate MemAfter trace", all_stark .mem_after_stark .generate_trace(final_values.clone()) ); + #[cfg(feature = "cdk_erigon")] + let poseidon_trace = timed!( + timing, + "generate Poseidon trace", + all_stark + .poseidon_stark + .generate_trace(poseidon_ops, cap_elements, timing) + ); + log::info!( "Trace lengths (before padding): {:?}, mem_before_len: {}, mem_after_len: {}", trace_lengths, @@ -221,11 +248,13 @@ impl Traces { memory_trace, mem_before_trace, mem_after_trace, + #[cfg(feature = "cdk_erigon")] + poseidon_trace, ] } } -impl Default for Traces { +impl Default for Traces { fn default() -> Self { Self::new() } diff --git a/evm_arithmetization/src/witness/transition.rs b/evm_arithmetization/src/witness/transition.rs index 794dabd85..6263977f4 100644 --- a/evm_arithmetization/src/witness/transition.rs +++ b/evm_arithmetization/src/witness/transition.rs @@ -1,6 +1,7 @@ use ethereum_types::U256; use log::log_enabled; use plonky2::field::types::Field; +use plonky2::hash::hash_types::RichField; use super::util::{mem_read_gp_with_log_and_fill, stack_pop_with_log_and_fill}; use crate::cpu::columns::CpuColumnsView; @@ -22,7 +23,7 @@ use crate::{arithmetic, logic}; pub(crate) const EXC_STOP_CODE: u8 = 6; -pub(crate) fn read_code_memory>( +pub(crate) fn read_code_memory>( state: &mut T, row: &mut CpuColumnsView, ) -> u8 { @@ -90,6 +91,10 @@ pub(crate) fn decode(registers: RegistersState, opcode: u8) -> Result Ok(Operation::Syscall(opcode, 2, false)), // SAR (0x20, _) => Ok(Operation::Syscall(opcode, 2, false)), // KECCAK256 (0x21, true) => Ok(Operation::KeccakGeneral), + #[cfg(feature = "cdk_erigon")] + (0x22, true) => Ok(Operation::Poseidon), + #[cfg(feature = "cdk_erigon")] + (0x23, true) => Ok(Operation::PoseidonGeneral), (0x30, _) => Ok(Operation::Syscall(opcode, 0, true)), // ADDRESS (0x31, _) => Ok(Operation::Syscall(opcode, 1, false)), // BALANCE (0x32, _) => Ok(Operation::Syscall(opcode, 0, true)), // ORIGIN @@ -189,6 +194,8 @@ pub(crate) fn fill_op_flag(op: Operation, row: &mut CpuColumnsView) Operation::BinaryArithmetic(_) => &mut flags.binary_op, Operation::TernaryArithmetic(_) => &mut flags.ternary_op, Operation::KeccakGeneral | Operation::Jumpdest => &mut flags.jumpdest_keccak_general, + #[cfg(feature = "cdk_erigon")] + Operation::Poseidon | Operation::PoseidonGeneral => &mut flags.poseidon, Operation::ProverInput | Operation::Push(1..) => &mut flags.push_prover_input, Operation::Jump | Operation::Jumpi => &mut flags.jumps, Operation::Pc | Operation::Push(0) => &mut flags.pc_push0, @@ -221,6 +228,8 @@ pub(crate) const fn get_op_special_length(op: Operation) -> Option { Operation::BinaryArithmetic(_) => STACK_BEHAVIORS.binary_op, Operation::TernaryArithmetic(_) => STACK_BEHAVIORS.ternary_op, Operation::KeccakGeneral | Operation::Jumpdest => STACK_BEHAVIORS.jumpdest_keccak_general, + #[cfg(feature = "cdk_erigon")] + Operation::Poseidon | Operation::PoseidonGeneral => STACK_BEHAVIORS.poseidon, Operation::Jump => JUMP_OP, Operation::Jumpi => JUMPI_OP, Operation::GetContext | Operation::SetContext => None, @@ -260,6 +269,8 @@ pub(crate) const fn might_overflow_op(op: Operation) -> bool { Operation::BinaryArithmetic(_) => MIGHT_OVERFLOW.binary_op, Operation::TernaryArithmetic(_) => MIGHT_OVERFLOW.ternary_op, Operation::KeccakGeneral | Operation::Jumpdest => MIGHT_OVERFLOW.jumpdest_keccak_general, + #[cfg(feature = "cdk_erigon")] + Operation::Poseidon | Operation::PoseidonGeneral => MIGHT_OVERFLOW.poseidon, Operation::Jump | Operation::Jumpi => MIGHT_OVERFLOW.jumps, Operation::Pc | Operation::Push(0) => MIGHT_OVERFLOW.pc_push0, Operation::GetContext | Operation::SetContext => MIGHT_OVERFLOW.context_op, @@ -269,7 +280,7 @@ pub(crate) const fn might_overflow_op(op: Operation) -> bool { } } -pub(crate) fn log_kernel_instruction>(state: &mut S, op: Operation) { +pub(crate) fn log_kernel_instruction>(state: &mut S, op: Operation) { // The logic below is a bit costly, so skip it if debug logs aren't enabled. if !log_enabled!(log::Level::Debug) { return; @@ -300,7 +311,7 @@ pub(crate) fn log_kernel_instruction>(state: &mut S, op: O assert!(pc < KERNEL.code.len(), "Kernel PC is out of range: {}", pc); } -pub(crate) trait Transition: State +pub(crate) trait Transition: State where Self: Sized, { @@ -506,6 +517,10 @@ where Operation::BinaryArithmetic(op) => generate_binary_arithmetic_op(op, self, row), Operation::TernaryArithmetic(op) => generate_ternary_arithmetic_op(op, self, row), Operation::KeccakGeneral => generate_keccak_general(self, row), + #[cfg(feature = "cdk_erigon")] + Operation::Poseidon => generate_poseidon(self, row), + #[cfg(feature = "cdk_erigon")] + Operation::PoseidonGeneral => generate_poseidon_general(self, row), Operation::ProverInput => generate_prover_input(self, row), Operation::Pop => generate_pop(self, row), Operation::Jump => self.generate_jump(row), diff --git a/evm_arithmetization/src/witness/util.rs b/evm_arithmetization/src/witness/util.rs index bca6f580c..5769f6600 100644 --- a/evm_arithmetization/src/witness/util.rs +++ b/evm_arithmetization/src/witness/util.rs @@ -1,5 +1,6 @@ use ethereum_types::U256; use plonky2::field::types::Field; +use plonky2::hash::hash_types::RichField; use super::memory::DUMMY_MEMOP; use super::transition::Transition; @@ -31,7 +32,7 @@ fn to_bits_le(n: u8) -> [F; 8] { } /// Peek at the stack item `i`th from the top. If `i=0` this gives the tip. -pub(crate) fn stack_peek( +pub(crate) fn stack_peek( state: &GenerationState, i: usize, ) -> Result { @@ -50,7 +51,7 @@ pub(crate) fn stack_peek( } /// Peek at kernel at specified segment and address -pub(crate) fn current_context_peek( +pub(crate) fn current_context_peek( state: &GenerationState, segment: Segment, virt: usize, @@ -72,14 +73,14 @@ pub(crate) fn fill_channel_with_value(row: &mut CpuColumnsView, n: /// Pushes without writing in memory. This happens in opcodes where a push /// immediately follows a pop. -pub(crate) fn push_no_write(state: &mut GenerationState, val: U256) { +pub(crate) fn push_no_write(state: &mut GenerationState, val: U256) { state.registers.stack_top = val; state.registers.stack_len += 1; } /// Pushes and (maybe) writes the previous stack top in memory. This happens in /// opcodes which only push. -pub(crate) fn push_with_write>( +pub(crate) fn push_with_write>( state: &mut T, row: &mut CpuColumnsView, val: U256, @@ -115,7 +116,7 @@ pub(crate) fn push_with_write>( Ok(()) } -pub(crate) fn mem_read_with_log( +pub(crate) fn mem_read_with_log( channel: MemoryChannel, address: MemoryAddress, state: &GenerationState, @@ -131,7 +132,7 @@ pub(crate) fn mem_read_with_log( (val, op) } -pub(crate) fn mem_write_log( +pub(crate) fn mem_write_log( channel: MemoryChannel, address: MemoryAddress, state: &GenerationState, @@ -146,7 +147,7 @@ pub(crate) fn mem_write_log( ) } -pub(crate) fn mem_read_code_with_log_and_fill( +pub(crate) fn mem_read_code_with_log_and_fill( address: MemoryAddress, state: &GenerationState, row: &mut CpuColumnsView, @@ -159,7 +160,7 @@ pub(crate) fn mem_read_code_with_log_and_fill( (val_u8, op) } -pub(crate) fn mem_read_gp_with_log_and_fill( +pub(crate) fn mem_read_gp_with_log_and_fill( n: usize, address: MemoryAddress, state: &GenerationState, @@ -183,7 +184,7 @@ pub(crate) fn mem_read_gp_with_log_and_fill( (val, op) } -pub(crate) fn mem_write_gp_log_and_fill( +pub(crate) fn mem_write_gp_log_and_fill( n: usize, address: MemoryAddress, state: &GenerationState, @@ -208,7 +209,7 @@ pub(crate) fn mem_write_gp_log_and_fill( op } -pub(crate) fn mem_write_partial_log_and_fill( +pub(crate) fn mem_write_partial_log_and_fill( address: MemoryAddress, state: &GenerationState, row: &mut CpuColumnsView, @@ -230,7 +231,7 @@ pub(crate) fn mem_write_partial_log_and_fill( // Channel 0 already contains the top of the stack. You only need to read // from the second popped element. // If the resulting stack isn't empty, update `stack_top`. -pub(crate) fn stack_pop_with_log_and_fill( +pub(crate) fn stack_pop_with_log_and_fill( state: &mut GenerationState, row: &mut CpuColumnsView, ) -> Result<[(U256, MemoryOp); N], ProgramError> { @@ -267,7 +268,7 @@ pub(crate) fn stack_pop_with_log_and_fill( Ok(result) } -fn xor_into_sponge>( +fn xor_into_sponge>( state: &mut T, sponge_state: &mut [u8; KECCAK_WIDTH_BYTES], block: &[u8; KECCAK_RATE_BYTES], @@ -283,7 +284,7 @@ fn xor_into_sponge>( } } -pub(crate) fn keccak_sponge_log>( +pub(crate) fn keccak_sponge_log>( state: &mut T, base_address: MemoryAddress, input: Vec, @@ -339,7 +340,7 @@ pub(crate) fn keccak_sponge_log>( }); } -pub(crate) fn byte_packing_log>( +pub(crate) fn byte_packing_log>( state: &mut T, base_address: MemoryAddress, bytes: Vec, @@ -371,7 +372,7 @@ pub(crate) fn byte_packing_log>( }); } -pub(crate) fn byte_unpacking_log>( +pub(crate) fn byte_unpacking_log>( state: &mut T, base_address: MemoryAddress, val: U256, diff --git a/evm_arithmetization/tests/two_to_one_block.rs b/evm_arithmetization/tests/two_to_one_block.rs index fb734ea00..2474bb82b 100644 --- a/evm_arithmetization/tests/two_to_one_block.rs +++ b/evm_arithmetization/tests/two_to_one_block.rs @@ -172,6 +172,7 @@ fn test_two_to_one_block_aggregation() -> anyhow::Result<()> { let all_stark = AllStark::::default(); let config = StarkConfig::standard_fast_config(); + let all_circuits = AllRecursiveCircuits::::new( &all_stark, &[ diff --git a/proof_gen/Cargo.toml b/proof_gen/Cargo.toml index 87e31d9e8..304862ece 100644 --- a/proof_gen/Cargo.toml +++ b/proof_gen/Cargo.toml @@ -19,5 +19,9 @@ hashbrown = { workspace = true } # Local dependencies evm_arithmetization = { workspace = true } +[features] +default = [] +cdk_erigon = ["evm_arithmetization/cdk_erigon"] + [lints] workspace = true diff --git a/proof_gen/src/constants.rs b/proof_gen/src/constants.rs index e0b84387d..9e00a93eb 100644 --- a/proof_gen/src/constants.rs +++ b/proof_gen/src/constants.rs @@ -22,3 +22,6 @@ pub(crate) const DEFAULT_MEMORY_RANGE: Range = 17..30; pub(crate) const DEFAULT_MEMORY_BEFORE_RANGE: Range = 8..20; /// Default range to be used for the `MemoryAfterStark` table. pub(crate) const DEFAULT_MEMORY_AFTER_RANGE: Range = 16..30; +#[cfg(feature = "cdk_erigon")] +/// Default range to be used for the `PoseidonStark` table. +pub(crate) const DEFAULT_POSEIDON_RANGE: Range = 4..25; diff --git a/proof_gen/src/proof_types.rs b/proof_gen/src/proof_types.rs index ddca989da..20be552d9 100644 --- a/proof_gen/src/proof_types.rs +++ b/proof_gen/src/proof_types.rs @@ -125,7 +125,7 @@ impl BatchAggregatableProof { } } - pub(crate) fn is_agg(&self) -> bool { + pub(crate) const fn is_agg(&self) -> bool { match self { BatchAggregatableProof::Segment(_) => false, BatchAggregatableProof::Txn(_) => false, @@ -133,7 +133,7 @@ impl BatchAggregatableProof { } } - pub(crate) fn intern(&self) -> &PlonkyProofIntern { + pub(crate) const fn intern(&self) -> &PlonkyProofIntern { match self { BatchAggregatableProof::Segment(info) => &info.intern, BatchAggregatableProof::Txn(info) => &info.intern, diff --git a/proof_gen/src/prover_state.rs b/proof_gen/src/prover_state.rs index bb3e5656f..7d8acf901 100644 --- a/proof_gen/src/prover_state.rs +++ b/proof_gen/src/prover_state.rs @@ -31,6 +31,8 @@ pub struct ProverStateBuilder { pub(crate) memory_circuit_size: Range, pub(crate) memory_before_circuit_size: Range, pub(crate) memory_after_circuit_size: Range, + #[cfg(feature = "cdk_erigon")] + pub(crate) poseidon_circuit_size: Range, } impl Default for ProverStateBuilder { @@ -52,6 +54,8 @@ impl Default for ProverStateBuilder { memory_circuit_size: DEFAULT_MEMORY_RANGE, memory_before_circuit_size: DEFAULT_MEMORY_BEFORE_RANGE, memory_after_circuit_size: DEFAULT_MEMORY_AFTER_RANGE, + #[cfg(feature = "cdk_erigon")] + poseidon_circuit_size: DEFAULT_POSEIDON_RANGE, } } } @@ -79,6 +83,8 @@ impl ProverStateBuilder { define_set_circuit_size_method!(memory); define_set_circuit_size_method!(memory_before); define_set_circuit_size_method!(memory_after); + #[cfg(feature = "cdk_erigon")] + define_set_circuit_size_method!(poseidon); // TODO: Consider adding async version? /// Instantiate the prover state from the builder. Note that this is a very @@ -98,6 +104,8 @@ impl ProverStateBuilder { self.memory_circuit_size, self.memory_before_circuit_size, self.memory_after_circuit_size, + #[cfg(feature = "cdk_erigon")] + self.poseidon_circuit_size, ], &StarkConfig::standard_fast_config(), ); diff --git a/trace_decoder/Cargo.toml b/trace_decoder/Cargo.toml index 4308a0ebe..78e0aa3ef 100644 --- a/trace_decoder/Cargo.toml +++ b/trace_decoder/Cargo.toml @@ -41,14 +41,26 @@ zk_evm_common = { workspace = true } [dev-dependencies] alloy = { workspace = true } +alloy-compat = "0.1.0" +assert2 = "0.3.15" +camino = "1.1.9" criterion = { workspace = true } +glob = "0.3.1" +libtest-mimic = "0.7.3" plonky2_maybe_rayon = { workspace = true } pretty_env_logger = { workspace = true } prover = { workspace = true } -rstest = "0.21.0" serde_json = { workspace = true } serde_path_to_error = { workspace = true } [[bench]] name = "block_processing" harness = false + +[[test]] +name = "consistent-with-header" +harness = false + +[[test]] +name = "simulate-execution" +harness = false diff --git a/trace_decoder/tests/data/.gitattributes b/trace_decoder/src/cases/.gitattributes similarity index 100% rename from trace_decoder/tests/data/.gitattributes rename to trace_decoder/src/cases/.gitattributes diff --git a/trace_decoder/src/cases/README.md b/trace_decoder/src/cases/README.md new file mode 100644 index 000000000..a37883839 --- /dev/null +++ b/trace_decoder/src/cases/README.md @@ -0,0 +1 @@ +Test vectors for unit tests in [../wire](../wire.rs). diff --git a/trace_decoder/tests/data/tries/hermez_cdk_erigon.json b/trace_decoder/src/cases/hermez_cdk_erigon.json similarity index 100% rename from trace_decoder/tests/data/tries/hermez_cdk_erigon.json rename to trace_decoder/src/cases/hermez_cdk_erigon.json diff --git a/trace_decoder/tests/data/tries/zero_jerigon.json b/trace_decoder/src/cases/zero_jerigon.json similarity index 100% rename from trace_decoder/tests/data/tries/zero_jerigon.json rename to trace_decoder/src/cases/zero_jerigon.json diff --git a/trace_decoder/src/type1.rs b/trace_decoder/src/type1.rs index c073c2a13..019a75c95 100644 --- a/trace_decoder/src/type1.rs +++ b/trace_decoder/src/type1.rs @@ -380,12 +380,11 @@ fn finish_stack(v: &mut Vec) -> anyhow::Result { #[test] fn test_tries() { - for (ix, case) in serde_json::from_str::>(include_str!( - "../tests/data/tries/zero_jerigon.json" - )) - .unwrap() - .into_iter() - .enumerate() + for (ix, case) in + serde_json::from_str::>(include_str!("cases/zero_jerigon.json")) + .unwrap() + .into_iter() + .enumerate() { println!("case {}", ix); let instructions = crate::wire::parse(&case.bytes).unwrap(); diff --git a/trace_decoder/src/type2.rs b/trace_decoder/src/type2.rs index 2d10edf40..dd3e45c4b 100644 --- a/trace_decoder/src/type2.rs +++ b/trace_decoder/src/type2.rs @@ -226,12 +226,11 @@ fn iter_leaves(node: Node) -> Box>(include_str!( - "../tests/data/tries/hermez_cdk_erigon.json" - )) - .unwrap() - .into_iter() - .enumerate() + for (ix, case) in + serde_json::from_str::>(include_str!("cases/hermez_cdk_erigon.json")) + .unwrap() + .into_iter() + .enumerate() { println!("case {}", ix); let instructions = crate::wire::parse(&case.bytes).unwrap(); diff --git a/trace_decoder/src/typed_mpt.rs b/trace_decoder/src/typed_mpt.rs index 8409e74c0..5a49966a6 100644 --- a/trace_decoder/src/typed_mpt.rs +++ b/trace_decoder/src/typed_mpt.rs @@ -58,7 +58,7 @@ impl TypedMpt { let bytes = self.inner.get(key.into_nibbles())?; Some(rlp::decode(bytes).expect(Self::PANIC_MSG)) } - fn as_hashed_partial_trie(&self) -> &HashedPartialTrie { + const fn as_hashed_partial_trie(&self) -> &HashedPartialTrie { &self.inner } fn as_mut_hashed_partial_trie_unchecked(&mut self) -> &mut HashedPartialTrie { @@ -198,7 +198,7 @@ impl TransactionTrie { pub fn root(&self) -> H256 { self.untyped.hash() } - pub fn as_hashed_partial_trie(&self) -> &mpt_trie::partial_trie::HashedPartialTrie { + pub const fn as_hashed_partial_trie(&self) -> &mpt_trie::partial_trie::HashedPartialTrie { &self.untyped } } @@ -224,7 +224,7 @@ impl ReceiptTrie { pub fn root(&self) -> H256 { self.untyped.hash() } - pub fn as_hashed_partial_trie(&self) -> &mpt_trie::partial_trie::HashedPartialTrie { + pub const fn as_hashed_partial_trie(&self) -> &mpt_trie::partial_trie::HashedPartialTrie { &self.untyped } } @@ -259,7 +259,7 @@ impl StateMpt { .iter() .map(|(key, rlp)| (key.into_hash().expect("key is always H256"), rlp)) } - pub fn as_hashed_partial_trie(&self) -> &mpt_trie::partial_trie::HashedPartialTrie { + pub const fn as_hashed_partial_trie(&self) -> &mpt_trie::partial_trie::HashedPartialTrie { self.typed.as_hashed_partial_trie() } pub fn root(&self) -> H256 { @@ -392,7 +392,7 @@ impl StorageTrie { pub fn root(&self) -> H256 { self.untyped.hash() } - pub fn as_hashed_partial_trie(&self) -> &HashedPartialTrie { + pub const fn as_hashed_partial_trie(&self) -> &HashedPartialTrie { &self.untyped } diff --git a/trace_decoder/tests/cases/.gitattributes b/trace_decoder/tests/cases/.gitattributes new file mode 100644 index 000000000..f53fb93bf --- /dev/null +++ b/trace_decoder/tests/cases/.gitattributes @@ -0,0 +1 @@ +*.json linguist-generated=true diff --git a/trace_decoder/tests/data/witnesses/zero_jerigon/b19807080_main.json b/trace_decoder/tests/cases/b19807080_main.json similarity index 100% rename from trace_decoder/tests/data/witnesses/zero_jerigon/b19807080_main.json rename to trace_decoder/tests/cases/b19807080_main.json diff --git a/trace_decoder/tests/data/witnesses/zero_jerigon/b19807080_main_header.json b/trace_decoder/tests/cases/b19807080_main_header.json similarity index 100% rename from trace_decoder/tests/data/witnesses/zero_jerigon/b19807080_main_header.json rename to trace_decoder/tests/cases/b19807080_main_header.json diff --git a/trace_decoder/tests/data/witnesses/zero_jerigon/b19840104_main.json b/trace_decoder/tests/cases/b19840104_main.json similarity index 100% rename from trace_decoder/tests/data/witnesses/zero_jerigon/b19840104_main.json rename to trace_decoder/tests/cases/b19840104_main.json diff --git a/trace_decoder/tests/data/witnesses/zero_jerigon/b19840104_main_header.json b/trace_decoder/tests/cases/b19840104_main_header.json similarity index 100% rename from trace_decoder/tests/data/witnesses/zero_jerigon/b19840104_main_header.json rename to trace_decoder/tests/cases/b19840104_main_header.json diff --git a/trace_decoder/tests/data/witnesses/zero_jerigon/b20240052_main.json b/trace_decoder/tests/cases/b20240052_main.json similarity index 100% rename from trace_decoder/tests/data/witnesses/zero_jerigon/b20240052_main.json rename to trace_decoder/tests/cases/b20240052_main.json diff --git a/trace_decoder/tests/data/witnesses/zero_jerigon/b20240052_main_header.json b/trace_decoder/tests/cases/b20240052_main_header.json similarity index 100% rename from trace_decoder/tests/data/witnesses/zero_jerigon/b20240052_main_header.json rename to trace_decoder/tests/cases/b20240052_main_header.json diff --git a/trace_decoder/tests/data/witnesses/zero_jerigon/b20240058_main.json b/trace_decoder/tests/cases/b20240058_main.json similarity index 100% rename from trace_decoder/tests/data/witnesses/zero_jerigon/b20240058_main.json rename to trace_decoder/tests/cases/b20240058_main.json diff --git a/trace_decoder/tests/data/witnesses/zero_jerigon/b20240058_main_header.json b/trace_decoder/tests/cases/b20240058_main_header.json similarity index 100% rename from trace_decoder/tests/data/witnesses/zero_jerigon/b20240058_main_header.json rename to trace_decoder/tests/cases/b20240058_main_header.json diff --git a/trace_decoder/tests/data/witnesses/zero_jerigon/b20472570_main.json b/trace_decoder/tests/cases/b20472570_main.json similarity index 100% rename from trace_decoder/tests/data/witnesses/zero_jerigon/b20472570_main.json rename to trace_decoder/tests/cases/b20472570_main.json diff --git a/trace_decoder/tests/data/witnesses/zero_jerigon/b20472570_main_header.json b/trace_decoder/tests/cases/b20472570_main_header.json similarity index 100% rename from trace_decoder/tests/data/witnesses/zero_jerigon/b20472570_main_header.json rename to trace_decoder/tests/cases/b20472570_main_header.json diff --git a/trace_decoder/tests/data/witnesses/zero_jerigon/b28_dev.json b/trace_decoder/tests/cases/b28_dev.json similarity index 100% rename from trace_decoder/tests/data/witnesses/zero_jerigon/b28_dev.json rename to trace_decoder/tests/cases/b28_dev.json diff --git a/trace_decoder/tests/data/witnesses/zero_jerigon/b28_dev_header.json b/trace_decoder/tests/cases/b28_dev_header.json similarity index 100% rename from trace_decoder/tests/data/witnesses/zero_jerigon/b28_dev_header.json rename to trace_decoder/tests/cases/b28_dev_header.json diff --git a/trace_decoder/tests/data/witnesses/zero_jerigon/b4_dev.json b/trace_decoder/tests/cases/b4_dev.json similarity index 100% rename from trace_decoder/tests/data/witnesses/zero_jerigon/b4_dev.json rename to trace_decoder/tests/cases/b4_dev.json diff --git a/trace_decoder/tests/data/witnesses/zero_jerigon/b4_dev_header.json b/trace_decoder/tests/cases/b4_dev_header.json similarity index 100% rename from trace_decoder/tests/data/witnesses/zero_jerigon/b4_dev_header.json rename to trace_decoder/tests/cases/b4_dev_header.json diff --git a/trace_decoder/tests/common/mod.rs b/trace_decoder/tests/common/mod.rs new file mode 100644 index 000000000..51c74f75c --- /dev/null +++ b/trace_decoder/tests/common/mod.rs @@ -0,0 +1,83 @@ +use std::{fs::File, path::Path}; + +use alloy::rpc::types::Header; +use anyhow::{ensure, Context as _}; +use camino::Utf8Path; +use prover::BlockProverInput; +use serde::de::DeserializeOwned; +use trace_decoder::{BlockTrace, OtherBlockData}; + +pub fn cases() -> anyhow::Result> { + print!("loading test vectors..."); + let ret = glob::glob(concat!( + env!("CARGO_MANIFEST_DIR"), + "/tests/cases/*_header.json" + )) + .expect("valid glob pattern") + .map(|res| { + let header_path = res.context("filesystem error discovering test vectors")?; + Case::load(&header_path).context(format!( + "couldn't load case for header {}", + header_path.display() + )) + }) + .collect(); + println!("done"); + ret +} + +/// Test cases consist of [`BlockProverInput`] collected from `zero_bin`'s `rpc` +/// command, and the corresponding block header, fetched directly over RPC. +/// +/// In directory above, the files are stored alongside one another, as, for +/// example: +/// - `b4_dev.json` +/// - `b4_dev_header.json` +pub struct Case { + /// `b4_dev`, in the above example. + /// + /// Used as a test identifier. + pub name: String, + #[allow(unused)] // only used by one of the test binaries + pub header: Header, + pub trace: BlockTrace, + pub other: OtherBlockData, +} + +impl Case { + fn load(header_path: &Path) -> anyhow::Result { + let header_path = Utf8Path::from_path(header_path).context("non-UTF-8 path")?; + let base = Utf8Path::new( + header_path + .as_str() + .strip_suffix("_header.json") + .context("inconsistent header name")?, // sync with glob call + ); + // for some reason these are lists... + let mut headers = json::>(header_path)?; + let mut bpis = json::>(base.with_extension("json"))?; + ensure!(headers.len() == 1, "bad header file"); + ensure!(bpis.len() == 1, "bad bpi file"); + let BlockProverInput { + block_trace, + other_data, + } = bpis.remove(0); + anyhow::Ok(Case { + name: base.file_name().context("inconsistent base name")?.into(), + header: headers.remove(0), + trace: block_trace, + other: other_data, + }) + } +} + +fn json(path: impl AsRef) -> anyhow::Result { + fn _imp(path: impl AsRef) -> anyhow::Result { + let file = File::open(path)?; + Ok(serde_path_to_error::deserialize( + &mut serde_json::Deserializer::from_reader(file), + )?) + } + + _imp(&path).context(format!("couldn't load {}", path.as_ref().display())) +} diff --git a/trace_decoder/tests/consistent-with-header.rs b/trace_decoder/tests/consistent-with-header.rs new file mode 100644 index 000000000..0cd477680 --- /dev/null +++ b/trace_decoder/tests/consistent-with-header.rs @@ -0,0 +1,81 @@ +//! Check that the [`evm_arithmetization::GenerationInputs`] produced by +//! [`trace_decoder`] are consistent between each other, and with the block +//! header obtained over RPC. + +mod common; + +use alloy_compat::Compat as _; +use assert2::check; +use common::{cases, Case}; +use itertools::Itertools; +use libtest_mimic::{Arguments, Trial}; +use mpt_trie::partial_trie::PartialTrie as _; + +fn main() -> anyhow::Result<()> { + let mut trials = vec![]; + + for batch_size in [1, 3] { + for Case { + name, + header, + trace, + other, + } in cases()? + { + trials.push(Trial::test(format!("{name}@{batch_size}"), move || { + let gen_inputs = trace_decoder::entrypoint(trace, other.clone(), batch_size, false) + .map_err(|e| format!("{e:?}"))?; // get the full cause chain + check!(gen_inputs.len() >= 2); + check!( + Some(other.checkpoint_state_trie_root) + == gen_inputs.first().map(|it| it.tries.state_trie.hash()) + ); + let pairs = || gen_inputs.iter().tuple_windows::<(_, _)>(); + check!( + pairs().position(|(before, after)| { + before.trie_roots_after.state_root != after.tries.state_trie.hash() + }) == None + ); + check!( + pairs().position(|(before, after)| { + before.trie_roots_after.receipts_root != after.tries.receipts_trie.hash() + }) == None + ); + check!( + pairs().position(|(before, after)| { + before.trie_roots_after.transactions_root + != after.tries.transactions_trie.hash() + }) == None + ); + check!( + gen_inputs + .last() + .map(|it| it.trie_roots_after.state_root.compat()) + == Some(header.state_root) + ); + check!( + gen_inputs + .iter() + .position(|it| it.block_metadata.block_timestamp != header.timestamp.into()) + == None + ); + check!( + gen_inputs + .last() + .map(|it| it.block_hashes.cur_hash.compat()) + == Some(header.hash) + ); + check!( + gen_inputs.iter().position(|it| it + .block_hashes + .prev_hashes + .last() + .is_some_and(|it| *it != header.parent_hash.compat())) + == None + ); + Ok(()) + })); + } + } + libtest_mimic::run(&Arguments::from_args(), trials).exit() +} diff --git a/trace_decoder/tests/simulate-execution.rs b/trace_decoder/tests/simulate-execution.rs new file mode 100644 index 000000000..080a02c62 --- /dev/null +++ b/trace_decoder/tests/simulate-execution.rs @@ -0,0 +1,40 @@ +//! Check that the [`evm_arithmetization::GenerationInputs`] produced by +//! [`trace_decoder`] are consistent between each other, and with the block +//! header obtained over RPC. + +mod common; + +use anyhow::Context as _; +use common::{cases, Case}; +use libtest_mimic::{Arguments, Trial}; +use plonky2::field::goldilocks_field::GoldilocksField; + +fn main() -> anyhow::Result<()> { + let mut trials = vec![]; + for batch_size in [1, 3] { + for Case { + name, + header: _, + trace, + other, + } in cases()? + { + let gen_inputs = trace_decoder::entrypoint(trace, other, batch_size, false).context( + format!("error in `trace_decoder` for {name} at batch size {batch_size}"), + )?; + for (ix, gi) in gen_inputs.into_iter().enumerate() { + trials.push(Trial::test( + format!("{name}@{batch_size}/{ix}"), + move || { + evm_arithmetization::prover::testing::simulate_execution_all_segments::< + GoldilocksField, + >(gi, 19) + .map_err(|e| format!("{e:?}"))?; // get the full error chain + Ok(()) + }, + )) + } + } + } + libtest_mimic::run(&Arguments::from_args(), trials).exit() +} diff --git a/trace_decoder/tests/trace_decoder_tests.rs b/trace_decoder/tests/trace_decoder_tests.rs deleted file mode 100644 index 6db98749f..000000000 --- a/trace_decoder/tests/trace_decoder_tests.rs +++ /dev/null @@ -1,281 +0,0 @@ -//! Tests to check the parsing/decoding and `GenerationInputs` validity. -//! They rely on the jerigon and cdk erigon witness files as input. - -use std::time::Duration; -use std::{ - fs, - path::{Path, PathBuf}, -}; - -use alloy::rpc::types::eth::Header; -use anyhow::Context as _; -use evm_arithmetization::prover::testing::simulate_execution_all_segments; -use evm_arithmetization::GenerationInputs; -use itertools::Itertools; -use log::info; -use mpt_trie::partial_trie::PartialTrie; -use plonky2::field::goldilocks_field::GoldilocksField; -use plonky2::util::timing::TimingTree; -use plonky2_maybe_rayon::*; -use pretty_env_logger::env_logger::{try_init_from_env, Env, DEFAULT_FILTER_ENV}; -use prover::BlockProverInput; -use rstest::rstest; -use trace_decoder::OtherBlockData; - -type F = GoldilocksField; - -const JERIGON_WITNESS_DIR: &str = "tests/data/witnesses/zero_jerigon"; -///TODO Add CDK Erigon witness test data. -/// Local [cdk erigon](https://github.com/0xPolygonHermez/cdk-erigon?tab=readme-ov-file#running-cdk-erigon) dev network -/// could be used for basic witness generation. -/// Related work for type2 prover is on the [type2_cancun](https://github.com/0xPolygonZero/zk_evm/pull/319) branch at the moment. -/// When the cdk erigon witness data is added, enable test execution for -/// `CDK_ERIGON_WITNESS_DIR` -//const CDK_ERIGON_WITNESS_DIR: &str = -// "tests/data/witnesses/hermez_cdk_erigon"; - -fn init_logger() { - let _ = try_init_from_env(Env::default().filter_or(DEFAULT_FILTER_ENV, "info")); -} - -fn find_witness_data_files(dir: &str) -> anyhow::Result> { - let read_dir = fs::read_dir(dir)?; - read_dir - .into_iter() - .map(|dir_entry| dir_entry.map(|it| it.path())) - .filter_ok(|path| { - !path - .to_str() - .expect("valid str file path") - .contains("header") - }) - .collect::, _>>() - .context(format!("Failed to find witness files in dir {dir}")) -} - -fn read_witness_file(file_path: &Path) -> anyhow::Result> { - let witness = fs::File::open(file_path).context("Unable to read file")?; - let mut reader = std::io::BufReader::new(witness); - let jd = &mut serde_json::Deserializer::from_reader(&mut reader); - serde_path_to_error::deserialize(jd).context(format!( - "Failed to deserialize json file {}", - file_path.display() - )) -} - -fn derive_header_file_path(witness_file_path: &Path) -> Result { - let mut header_file_path = witness_file_path.to_path_buf(); - header_file_path.set_extension(""); - let mut block_header_file_name = header_file_path - .file_name() - .context("Invalid header file name")? - .to_os_string(); - block_header_file_name.push("_header.json"); - header_file_path.set_file_name(block_header_file_name); - Ok(header_file_path) -} - -fn decode_generation_inputs( - block_prover_input: BlockProverInput, - use_burn_addr: bool, -) -> anyhow::Result> { - let block_num = block_prover_input.other_data.b_data.b_meta.block_number; - let trace_decoder_output = trace_decoder::entrypoint( - block_prover_input.block_trace, - block_prover_input.other_data.clone(), - 3, - use_burn_addr, - ) - .context(format!( - "Failed to execute trace decoder on block {}", - block_num - ))? - .into_iter() - .collect::>(); - Ok(trace_decoder_output) -} - -fn verify_generation_inputs( - header: &Header, - other: &OtherBlockData, - generation_inputs: Vec, -) -> anyhow::Result<()> { - assert!(generation_inputs.len() >= 2); - assert_eq!( - other.checkpoint_state_trie_root, - generation_inputs - .first() - .expect("generation inputs should have first element") - .tries - .state_trie - .hash() - ); - assert!(generation_inputs - .windows(2) - .map(|inputs| { - inputs[0].trie_roots_after.state_root == inputs[1].tries.state_trie.hash() - && inputs[0].trie_roots_after.receipts_root == inputs[1].tries.receipts_trie.hash() - && inputs[0].trie_roots_after.transactions_root - == inputs[1].tries.transactions_trie.hash() - }) - .all(|it| it)); - let last_generation_input = generation_inputs - .last() - .expect("generation inputs should have last element"); - assert_eq!( - last_generation_input.trie_roots_after.state_root.0, - header.state_root.0 - ); - // Some block metadata sanity checks - assert_eq!( - last_generation_input - .block_metadata - .block_timestamp - .as_u64(), - header.timestamp - ); - // Block hash check - assert_eq!( - last_generation_input.block_hashes.cur_hash.as_bytes(), - &header.hash.to_vec() - ); - // Previous block hash check - assert_eq!( - last_generation_input - .block_hashes - .prev_hashes - .last() - .expect("Valid last hash") - .as_bytes(), - &header.parent_hash.to_vec() - ); - info!( - "Block {} GenerationInputs valid", - other.b_data.b_meta.block_number - ); - Ok(()) -} - -/// This test aims at ensuring that the decoder can properly parse a block trace -/// received from Jerigon and CDK Erigon into zkEVM `GenerationInputs`, which -/// the prover can then pick to prove each transaction in the block -/// independently. -/// -/// This test only `simulates` the zkEVM CPU, i.e. does not generate STARK -/// traces nor generates proofs, as its purpose is to be runnable easily in the -/// CI even in `debug` mode. -#[rstest] -#[case(JERIGON_WITNESS_DIR)] -//#[case(CDK_ERIGON_WITNESS_DIR)] -fn test_parsing_decoding_proving(#[case] test_witness_directory: &str) { - init_logger(); - - // TODO: https://github.com/0xPolygonZero/zk_evm/issues/565 - // Once CDK_ERIGON_WITNESS_DIR is available, change this so - // `use_burn_addr` is only true in that case. - let use_burn_addr = test_witness_directory != JERIGON_WITNESS_DIR; - let results = find_witness_data_files(test_witness_directory) - .expect("valid json data files found") - .into_iter() - .map(|file_path| { - { - // Read one json witness file for this block and get list of BlockProverInputs - read_witness_file(&file_path) - } - }) - .map_ok(|block_prover_inputs| { - block_prover_inputs.into_iter().map(|block_prover_input| { - // Run trace decoder, create list of generation inputs - let block_generation_inputs = - decode_generation_inputs(block_prover_input, use_burn_addr)?; - block_generation_inputs - .into_par_iter() - .map(|generation_inputs| { - // For every generation input, simulate execution. - // Execution will be simulated in parallel. - // If system runs out of memory, limit the rayon - // with setting env variable RAYON_NUM_THREADS=. - let timing = TimingTree::new( - &format!( - "simulate zkEVM CPU for block {}, txns {:?}..{:?}.", - generation_inputs.block_metadata.block_number, - generation_inputs.txn_number_before, - generation_inputs.txn_number_before - + generation_inputs.signed_txns.len() - ), - log::Level::Info, - ); - simulate_execution_all_segments::(generation_inputs, 19)?; - timing.filter(Duration::from_millis(100)).print(); - Ok::<(), anyhow::Error>(()) - }) - .collect::, anyhow::Error>>() - }) - }) - .flatten_ok() - .map(|it| it?) - .collect::>>(); - - results.iter().for_each(|it| { - if let Err(e) = it { - panic!("Failed to run parsing decoding proving test: {e:?}"); - } - }); -} - -/// This test checks for the parsing and decoding of the block witness -/// received from Jerigon and CDK Erigon into zkEVM `GenerationInputs`, and -/// checks if trace decoder output generation inputs are valid and consistent. -#[rstest] -#[case(JERIGON_WITNESS_DIR)] -//#[case(CDK_ERIGON_WITNESS_DIR)] -fn test_generation_inputs_consistency(#[case] test_witness_directory: &str) { - init_logger(); - - // TODO: https://github.com/0xPolygonZero/zk_evm/issues/565 - // Once CDK_ERIGON_WITNESS_DIR is available, change this so - // `use_burn_addr` is only true in that case. - let use_burn_addr = test_witness_directory != JERIGON_WITNESS_DIR; - let result: Vec> = find_witness_data_files(test_witness_directory) - .expect("valid json data files found") - .into_iter() - .map(|file_path| { - { - // Read json header file of the block. We need it to check tracer output - // consistency - let header_file_path = derive_header_file_path(&file_path)?; - let header_file = fs::File::open(header_file_path.as_path()).context(format!( - "Unable to open header file {}", - header_file_path.display() - ))?; - let mut header_reader = std::io::BufReader::new(header_file); - let block_headers = serde_json::from_reader::<_, Vec

>(&mut header_reader) - .context(format!( - "Failed to deserialize header json file {}", - header_file_path.display() - ))?; - // Read one json witness file and get list of BlockProverInputs - let block_prover_inputs = read_witness_file(&file_path)?; - Ok(block_headers - .into_iter() - .zip(block_prover_inputs.into_iter())) - } - }) - .flatten_ok() - .map_ok(|(block_header, block_prover_input)| { - let other_block_data = block_prover_input.other_data.clone(); - // Run trace decoder, create generation inputs for this block - let block_generation_inputs = - decode_generation_inputs(block_prover_input, use_burn_addr)?; - // Verify generation inputs for this block - verify_generation_inputs(&block_header, &other_block_data, block_generation_inputs) - }) - .map(|it: Result, anyhow::Error>| it?) - .collect(); - - result.iter().for_each(|it| { - if let Err(e) = it { - panic!("Failed to verify generation inputs consistency: {e:?}"); - } - }); -} diff --git a/zero_bin/common/Cargo.toml b/zero_bin/common/Cargo.toml index be68cc779..7171fa2a8 100644 --- a/zero_bin/common/Cargo.toml +++ b/zero_bin/common/Cargo.toml @@ -35,5 +35,12 @@ cargo_metadata = { workspace = true } vergen = { workspace = true } anyhow = { workspace = true } +[features] +default = [] +cdk_erigon = [ + "evm_arithmetization/cdk_erigon", + "proof_gen/cdk_erigon" +] + [lints] workspace = true diff --git a/zero_bin/common/src/prover_state/circuit.rs b/zero_bin/common/src/prover_state/circuit.rs index 94596c8c9..de68b09f8 100644 --- a/zero_bin/common/src/prover_state/circuit.rs +++ b/zero_bin/common/src/prover_state/circuit.rs @@ -5,16 +5,12 @@ use std::{ str::FromStr, }; +pub use evm_arithmetization::NUM_TABLES; use evm_arithmetization::{AllStark, StarkConfig}; use proof_gen::types::AllRecursiveCircuits; use crate::parsing::{parse_range_exclusive, RangeParseError}; -/// Number of tables defined in plonky2. -/// -/// TODO: This should be made public in the evm_arithmetization crate. -pub(crate) const NUM_TABLES: usize = 9; - /// New type wrapper for [`Range`] that implements [`FromStr`] and [`Display`]. /// /// Useful for using in clap arguments. @@ -68,6 +64,8 @@ pub enum Circuit { Memory, MemoryBefore, MemoryAfter, + #[cfg(feature = "cdk_erigon")] + Poseidon, } impl Display for Circuit { @@ -89,6 +87,8 @@ impl Circuit { Circuit::Memory => 17..28, Circuit::MemoryBefore => 7..23, Circuit::MemoryAfter => 7..27, + #[cfg(feature = "cdk_erigon")] + Circuit::Poseidon => 4..22, } } @@ -104,6 +104,8 @@ impl Circuit { Circuit::Memory => "MEMORY_CIRCUIT_SIZE", Circuit::MemoryBefore => "MEMORY_BEFORE_CIRCUIT_SIZE", Circuit::MemoryAfter => "MEMORY_AFTER_CIRCUIT_SIZE", + #[cfg(feature = "cdk_erigon")] + Circuit::Poseidon => "POSEIDON_CIRCUIT_SIZE", } } @@ -119,6 +121,8 @@ impl Circuit { Circuit::Memory => "memory", Circuit::MemoryBefore => "memory before", Circuit::MemoryAfter => "memory after", + #[cfg(feature = "cdk_erigon")] + Circuit::Poseidon => "poseidon", } } @@ -134,6 +138,8 @@ impl Circuit { Circuit::Memory => "m", Circuit::MemoryBefore => "m_b", Circuit::MemoryAfter => "m_a", + #[cfg(feature = "cdk_erigon")] + Circuit::Poseidon => "p", } } } @@ -150,6 +156,8 @@ impl From for Circuit { 6 => Circuit::Memory, 7 => Circuit::MemoryBefore, 8 => Circuit::MemoryAfter, + #[cfg(feature = "cdk_erigon")] + 9 => Circuit::Poseidon, _ => unreachable!(), } } @@ -189,6 +197,8 @@ impl Default for CircuitConfig { Circuit::Memory.default_size(), Circuit::MemoryBefore.default_size(), Circuit::MemoryAfter.default_size(), + #[cfg(feature = "cdk_erigon")] + Circuit::Poseidon.default_size(), ], } } diff --git a/zero_bin/common/src/prover_state/mod.rs b/zero_bin/common/src/prover_state/mod.rs index b1673e5f6..ca049268f 100644 --- a/zero_bin/common/src/prover_state/mod.rs +++ b/zero_bin/common/src/prover_state/mod.rs @@ -185,6 +185,8 @@ impl ProverStateManager { circuit!(6), circuit!(7), circuit!(8), + #[cfg(feature = "cdk_erigon")] + circuit!(9), ]) } diff --git a/zero_bin/leader/Cargo.toml b/zero_bin/leader/Cargo.toml index 1f70f96a2..cad05cd22 100644 --- a/zero_bin/leader/Cargo.toml +++ b/zero_bin/leader/Cargo.toml @@ -35,7 +35,13 @@ zero_bin_common = { workspace = true } [features] default = [] -cdk_erigon = ["prover/cdk_erigon", "evm_arithmetization/cdk_erigon", "rpc/cdk_erigon"] +cdk_erigon = [ + "evm_arithmetization/cdk_erigon", + "proof_gen/cdk_erigon", + "prover/cdk_erigon", + "rpc/cdk_erigon", + "zero_bin_common/cdk_erigon" +] [build-dependencies] cargo_metadata = { workspace = true } diff --git a/zero_bin/prover/src/lib.rs b/zero_bin/prover/src/lib.rs index 21be514ad..8aab306bb 100644 --- a/zero_bin/prover/src/lib.rs +++ b/zero_bin/prover/src/lib.rs @@ -264,9 +264,10 @@ pub async fn prove( // `keep_intermediate_proofs` is set, output all block proofs to disk. let is_block_batch_finished = block_counter % prover_config.block_batch_size as u64 == 0; - if is_last_block - || prover_config.keep_intermediate_proofs - || is_block_batch_finished + if !prover_config.test_only + && (is_last_block + || prover_config.keep_intermediate_proofs + || is_block_batch_finished) { write_proof_to_dir(&prover_config.proof_output_dir, proof.clone()) .await diff --git a/zero_bin/rpc/src/lib.rs b/zero_bin/rpc/src/lib.rs index c27015df6..58df5430a 100644 --- a/zero_bin/rpc/src/lib.rs +++ b/zero_bin/rpc/src/lib.rs @@ -33,14 +33,6 @@ pub enum RpcType { Native, } -#[derive(Clone, Debug, Copy)] -pub struct RpcParams { - pub start_block: u64, - pub end_block: u64, - pub checkpoint_block_number: Option, - pub rpc_type: RpcType, -} - /// Obtain the prover input for one block pub async fn block_prover_input( cached_provider: Arc>, diff --git a/zero_bin/rpc/src/main.rs b/zero_bin/rpc/src/main.rs index 9ec2b9b62..8f860689f 100644 --- a/zero_bin/rpc/src/main.rs +++ b/zero_bin/rpc/src/main.rs @@ -10,7 +10,7 @@ use anyhow::anyhow; use clap::{Args, Parser, Subcommand, ValueHint}; use futures::StreamExt; use prover::BlockProverInput; -use rpc::{retry::build_http_retry_provider, RpcParams, RpcType}; +use rpc::{retry::build_http_retry_provider, RpcType}; use tracing_subscriber::{prelude::*, EnvFilter}; use url::Url; use zero_bin_common::block_interval::BlockIntervalStream; @@ -19,8 +19,16 @@ use zero_bin_common::provider::CachedProvider; use zero_bin_common::version; use zero_bin_common::{block_interval::BlockInterval, prover_state::persistence::CIRCUIT_VERSION}; +#[derive(Clone, Debug, Copy)] +struct FetchParams { + pub start_block: u64, + pub end_block: u64, + pub checkpoint_block_number: Option, + pub rpc_type: RpcType, +} + #[derive(Args, Clone, Debug)] -pub(crate) struct RpcConfig { +struct RpcToolConfig { /// The RPC URL. #[arg(short = 'u', long, value_hint = ValueHint::Url)] rpc_url: Url, @@ -36,7 +44,7 @@ pub(crate) struct RpcConfig { } #[derive(Subcommand)] -pub(crate) enum Command { +enum Command { Fetch { /// Starting block of interval to fetch. #[arg(short, long)] @@ -60,18 +68,18 @@ pub(crate) enum Command { } #[derive(Parser)] -pub(crate) struct Cli { +struct Cli { #[clap(flatten)] - pub(crate) config: RpcConfig, + pub(crate) config: RpcToolConfig, /// Fetch and generate prover input from the RPC endpoint. #[command(subcommand)] pub(crate) command: Command, } -pub(crate) async fn retrieve_block_prover_inputs( +pub(crate) async fn fetch_block_prover_inputs( cached_provider: Arc>, - params: RpcParams, + params: FetchParams, ) -> Result, anyhow::Error> where ProviderT: Provider, @@ -127,7 +135,7 @@ impl Cli { end_block, checkpoint_block_number, } => { - let params = RpcParams { + let params = FetchParams { start_block, end_block, checkpoint_block_number, @@ -135,7 +143,7 @@ impl Cli { }; let block_prover_inputs = - retrieve_block_prover_inputs(cached_provider, params).await?; + fetch_block_prover_inputs(cached_provider, params).await?; serde_json::to_writer_pretty(std::io::stdout(), &block_prover_inputs)?; } Command::Extract { tx, batch_size } => { @@ -153,7 +161,7 @@ impl Cli { "transaction {} does not have block number", tx_hash ))?; - let params = RpcParams { + let params = FetchParams { start_block: block_number, end_block: block_number, checkpoint_block_number: None, @@ -161,7 +169,7 @@ impl Cli { }; let block_prover_inputs = - retrieve_block_prover_inputs(cached_provider, params).await?; + fetch_block_prover_inputs(cached_provider, params).await?; let block_prover_input = block_prover_inputs.into_iter().next().ok_or(anyhow!( diff --git a/zero_bin/tools/prove_stdio.sh b/zero_bin/tools/prove_stdio.sh index d37f84bf9..35e39d400 100755 --- a/zero_bin/tools/prove_stdio.sh +++ b/zero_bin/tools/prove_stdio.sh @@ -61,6 +61,8 @@ if ! [[ $TEST_ONLY == "test_only" ]]; then export MEMORY_CIRCUIT_SIZE="18..22" export MEMORY_BEFORE_CIRCUIT_SIZE="16..20" export MEMORY_AFTER_CIRCUIT_SIZE="7..20" + # TODO(Robin): update Poseidon ranges here and below once Kernel ASM supports Poseidon ops + export POSEIDON_CIRCUIT_SIZE="4..8" elif [[ $INPUT_FILE == *"witness_b3_b6"* ]]; then # These sizes are configured specifically for custom blocks 3 to 6. Don't use this in other scenarios echo "Using specific circuit sizes for witness_b3_b6.json" @@ -73,6 +75,7 @@ if ! [[ $TEST_ONLY == "test_only" ]]; then export MEMORY_CIRCUIT_SIZE="17..22" export MEMORY_BEFORE_CIRCUIT_SIZE="17..18" export MEMORY_AFTER_CIRCUIT_SIZE="7..8" + export POSEIDON_CIRCUIT_SIZE="4..8" else export ARITHMETIC_CIRCUIT_SIZE="16..21" export BYTE_PACKING_CIRCUIT_SIZE="8..21" @@ -83,6 +86,7 @@ if ! [[ $TEST_ONLY == "test_only" ]]; then export MEMORY_CIRCUIT_SIZE="17..24" export MEMORY_BEFORE_CIRCUIT_SIZE="16..23" export MEMORY_AFTER_CIRCUIT_SIZE="7..23" + export POSEIDON_CIRCUIT_SIZE="4..8" fi fi