From 2f4b5c8a7a22cd6d9a746bd76b7678a57ed0bb79 Mon Sep 17 00:00:00 2001 From: Nicole Graus Date: Wed, 10 Jun 2026 17:27:07 -0300 Subject: [PATCH 1/3] perf: page preprocessed commitment (#645) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add warn, rename function, add tets * zero init page commitments * change the preprocessed-commitment mismatch check from debug_assert_eq! to a real error * simplify VmAirs page-AIR construction into two branches Zero-init and ELF-data pages took separate arms that ended identically and re-checked init_values.is_none(), which preprocessed_commitment already does. Merge them: non-private pages now prefer a caller-supplied page_commitments entry and otherwise fall back to preprocessed_commitment (static const for zero-init, recompute for ELF data). Behavior-preserving; the lookup stays a linear scan (no hashing, which is costly in the recursion guest). * hoist shared zero-init page commitment; assert uniform page size All zero-init pages share one preprocessed commitment (page-relative OFFSET, all-zero INIT) fixed by (page_size, blowup, coset). Compute it once before the page loop instead of once per zero-init page — this also collapses the 'not static' warning from per-page to once. Assert every page is DEFAULT_PAGE_SIZE so the shared value is valid for all of them; page_size is verifier-derived (never from the proof), so the assert can't be attacker-tripped. A follow-up will remove the per-page page_size field, making uniform size structural and the assert unnecessary. * cleanup: split page preprocessed-commitment wrapper, fix generator output (#656) * refactor: split page preprocessed-commitment wrapper; fix generator output - Replace the dual-duty page::preprocessed_commitment(config, options) with a config-free zero_init_preprocessed_commitment(options); ELF data pages call compute_precomputed_commitment directly. The is-zero-init branch lived inside the wrapper but both call sites already knew the answer, so it was dead weight; each function now has one observable behavior (zero-init: static const or warn+recompute; ELF data: always recompute, never warns). Behavior-identical. - Drop the #[doc(hidden)] herding on compute_precomputed_commitment — calling it directly is now the production path for ELF data pages. - Fix compute_static_commitments: the zero_page arm printed '(DEFAULT_PAGE_SIZE, blowup) => ...', a tuple pattern that does not compile when pasted into 'match blowup_factor' (leftover from the pre-#653 per-page page_size keying). Print 'blowup => ...' like the bitwise/keccak arms. - Drift test now asserts static_zero_page_commitment(blowup).is_some() so a deleted match arm can't pass trivially via the fallback path (the doc comment previously overclaimed this). * test: pin static zero-page bytes directly against recompute; fix stale docs Review feedback on #656: - The drift test compared the static bytes to the recompute only through the wrapper, so a broken dispatch gate (e.g. a coset-gate typo) would silently turn the comparison into recompute-vs-recompute and stop guarding the constants. Now the bytes are read from static_zero_page_commitment directly and compared to the recompute unconditionally; the wrapper gets its own separate equality check. The is_some assert is subsumed by the direct read (panics if the arm is missing) and is removed. - Module doc still described the suite as covering only bitwise/keccak_rc and referenced the old page wrapper name; updated for page and zero_init_preprocessed_commitment. - The drift test's doc no longer overclaims what the (removed) is_some assert guaranteed; the coset-gate coverage note points at the ignored page_non_three_coset test. --------- Co-authored-by: MauroFab Co-authored-by: Mauro Toscano <12560266+MauroToscano@users.noreply.github.com> --- bin/cli/src/main.rs | 2 +- crypto/stark/src/prover.rs | 14 ++- prover/src/bin/compute_static_commitments.rs | 30 +++-- prover/src/lib.rs | 75 +++++++++---- prover/src/tables/page.rs | 80 ++++++++++++++ prover/src/tests/decode_tests.rs | 11 +- prover/src/tests/disk_spill_tests.rs | 8 +- prover/src/tests/page_tests.rs | 109 +++++++++++++++++++ prover/src/tests/prove_elfs_tests.rs | 34 ++++-- prover/src/tests/static_commitments_tests.rs | 109 +++++++++++++++++-- 10 files changed, 410 insertions(+), 62 deletions(-) diff --git a/bin/cli/src/main.rs b/bin/cli/src/main.rs index 66ee8325a..7067eda68 100644 --- a/bin/cli/src/main.rs +++ b/bin/cli/src/main.rs @@ -500,7 +500,7 @@ fn cmd_verify(proof_path: PathBuf, elf_path: PathBuf, blowup: Option, time: return ExitCode::FAILURE; } }; - prover::verify_with_options(&proof, &elf_data, &opts, None) + prover::verify_with_options(&proof, &elf_data, &opts, None, None) } None => prover::verify(&proof, &elf_data), }; diff --git a/crypto/stark/src/prover.rs b/crypto/stark/src/prover.rs index 67bc1c320..601195ffb 100644 --- a/crypto/stark/src/prover.rs +++ b/crypto/stark/src/prover.rs @@ -74,6 +74,13 @@ where pub enum ProvingError { WrongParameter(String), EmptyCommitment, + /// The prover's recomputed preprocessed Merkle root did not match the + /// commitment the AIR was constructed with (e.g. a stale static constant + /// in a table module, or a wrong caller-supplied entry such as + /// `page_commitments` / `decode_commitment`). Continuing would yield a + /// proof an honest verifier always rejects — fail fast on the prover side + /// with a localized error instead. + PrecomputedCommitmentMismatch, /// I/O failure while spilling prover state (traces, LDE, Merkle trees) to disk: /// out of disk space, fd exhaustion, or mmap failure. #[cfg(feature = "disk-spill")] @@ -739,10 +746,9 @@ pub trait IsStarkProver< let (mut mult_tree, mult_root) = Self::commit_columns_bit_reversed(&columns[num_cols..]) .ok_or(ProvingError::EmptyCommitment)?; - debug_assert_eq!( - precomputed_root, expected_precomputed_root, - "Prover's precomputed commitment doesn't match hardcoded AIR commitment" - ); + if precomputed_root != expected_precomputed_root { + return Err(ProvingError::PrecomputedCommitmentMismatch); + } #[cfg(feature = "disk-spill")] if storage_mode == StorageMode::Disk { precomputed_tree.spill_nodes_to_disk().map_err(|e| { diff --git a/prover/src/bin/compute_static_commitments.rs b/prover/src/bin/compute_static_commitments.rs index 54276edaf..045e15a4c 100644 --- a/prover/src/bin/compute_static_commitments.rs +++ b/prover/src/bin/compute_static_commitments.rs @@ -1,18 +1,19 @@ -//! Prints static `(bitwise, keccak_rc)` preprocessed-table commitments for -//! a fixed set of `blowup_factor` values. The output is pasted into the -//! `static_commitment` match bodies in -//! `prover/src/tables/{bitwise,keccak_rc}.rs`. The -//! `static_commitments_tests` test suite pins the values so any drift in +//! Prints static `(bitwise, keccak_rc, zero_page)` preprocessed-table commitments +//! for a fixed set of `blowup_factor` values. The output is pasted into the +//! `static_commitment` match bodies in `prover/src/tables/{bitwise,keccak_rc}.rs` +//! and the `static_zero_page_commitment` match body in `prover/src/tables/page.rs`. +//! The `static_commitments_tests` test suite pins the values so any drift in //! the AIR or FFT pipeline is caught at test time. //! //! Run with: //! cargo run --bin compute_static_commitments --release //! //! ⚠️ Do not run this just to silence a failing drift test — see the -//! "Regenerating" section on `static_commitment` in `bitwise.rs` and -//! `keccak_rc.rs` for when it's actually appropriate to bless new bytes. +//! "Regenerating" section on `static_commitment` in `bitwise.rs` / +//! `keccak_rc.rs` and `static_zero_page_commitment` in `page.rs` for when +//! it's actually appropriate to bless new bytes. -use lambda_vm_prover::tables::{STATIC_BLOWUP_FACTORS, bitwise, keccak_rc}; +use lambda_vm_prover::tables::{STATIC_BLOWUP_FACTORS, bitwise, keccak_rc, page}; use stark::config::Commitment; use stark::proof::options::GoldilocksCubicProofOptions; @@ -34,10 +35,13 @@ fn format_commitment(commitment: &Commitment) -> String { fn main() { println!( - "// Paste these match arms into the `static_commitment` match body\n\ - // in `prover/src/tables/{{bitwise,keccak_rc}}.rs`.\n" + "// Paste these match arms into the `static_commitment` match bodies\n\ + // in `prover/src/tables/{{bitwise,keccak_rc}}.rs` and the\n\ + // `static_zero_page_commitment` match body in `prover/src/tables/page.rs`.\n" ); + let zero_page_config = page::PageConfig::zero_init(0); + for &blowup in STATIC_BLOWUP_FACTORS { let options = match GoldilocksCubicProofOptions::with_blowup(blowup) { Ok(o) => o, @@ -49,15 +53,19 @@ fn main() { let bitwise = bitwise::compute_preprocessed_commitment(&options); let keccak_rc = keccak_rc::compute_preprocessed_commitment(&options); + let zero_page = page::compute_precomputed_commitment(&zero_page_config, &options); println!( "// blowup_factor = {blowup}\n\ // ---- bitwise:\n \ {blowup} => Some({bitwise_fmt}),\n\ // ---- keccak_rc:\n \ - {blowup} => Some({keccak_fmt}),\n", + {blowup} => Some({keccak_fmt}),\n\ + // ---- zero_page:\n \ + {blowup} => Some({zero_page_fmt}),\n", bitwise_fmt = format_commitment(&bitwise), keccak_fmt = format_commitment(&keccak_rc), + zero_page_fmt = format_commitment(&zero_page), ); } } diff --git a/prover/src/lib.rs b/prover/src/lib.rs index 645fe979a..b6f65c80d 100644 --- a/prover/src/lib.rs +++ b/prover/src/lib.rs @@ -337,11 +337,22 @@ impl VmAirs { /// constant (e.g. the recursion guest, where the in-VM recompute is too /// expensive). When `None`, the commitment is computed from the ELF. /// - /// The trust anchor for `decode_commitment` is the caller's compiled - /// binary — never accept prover-supplied bytes here. A wrong value is - /// rejected, never silently accepted: it either mismatches the prover's - /// committed precomputed root (an explicit verifier check) or yields - /// diverging Fiat-Shamir challenges. + /// `page_commitments` is an optional list of precomputed ELF-data-page + /// preprocessed commitments, keyed by `page_base`. For each ELF data page + /// the verifier constructs, if a matching `(page_base, commitment)` pair + /// is supplied, it is used directly and that page's FFT + Merkle build is + /// skipped. Pages not in the list — including all zero-init pages and + /// pages without a match — take the normal compute path (zero-init pages + /// hit a compile-time constant via + /// `page::zero_init_preprocessed_commitment`; ELF data pages recompute + /// from the ELF). When `None`, every ELF data page recomputes from + /// scratch. + /// + /// The trust anchor for both `decode_commitment` and `page_commitments` + /// is the caller's compiled binary — never accept prover-supplied bytes + /// here. A wrong value is rejected, never silently accepted: it either + /// mismatches the prover's committed precomputed root (an explicit + /// verifier check) or yields diverging Fiat-Shamir challenges. pub fn new( elf: &Elf, proof_options: &ProofOptions, @@ -349,6 +360,7 @@ impl VmAirs { page_configs: &[crate::tables::page::PageConfig], table_counts: &TableCounts, decode_commitment: Option, + page_commitments: Option<&[(u64, Commitment)]>, ) -> Self { let cpus: Vec<_> = (0..table_counts.cpu) .map(|i| create_cpu_air(proof_options).with_name(&format!("CPU[{}]", i))) @@ -404,31 +416,38 @@ impl VmAirs { register::NUM_PREPROCESSED_COLS, ); // Every zero-init page shares one preprocessed commitment: OFFSET is - // page-relative and INIT is all-zero, so it depends only on the proof - // options. Compute it once here rather than once per zero-init page. - // Every program has at least one zero-init page (the stack is - // zero-initialized), so this commitment is always used. - let zero_init_commitment = - page::compute_precomputed_commitment(&page::PageConfig::zero_init(0), proof_options); + // page-relative and INIT is all-zero, so it depends only on + // (blowup, coset) — all fixed here. Compute it once (static const + // when shipped, else a single recompute) rather than per page. Every + // program has at least one zero-init page (the stack is zero- + // initialized), so this commitment is always used. + let zero_init_commitment = page::zero_init_preprocessed_commitment(proof_options); let pages: Vec<_> = page_configs .iter() .map(|config| { + let air = create_page_air(proof_options, config.page_base); if config.is_private_input { // Private-input pages: all columns are main trace (not preprocessed). // The verifier doesn't see the init values; correctness is enforced // by the memory bus constraints. - create_page_air(proof_options, config.page_base) + air } else if config.init_values.is_none() { // Zero-init pages: the shared commitment computed once above. - create_page_air(proof_options, config.page_base) - .with_preprocessed(zero_init_commitment, page::NUM_PREPROCESSED_COLS) + air.with_preprocessed(zero_init_commitment, page::NUM_PREPROCESSED_COLS) } else { - // ELF data pages: INIT is program-specific, so recompute per page. - create_page_air(proof_options, config.page_base).with_preprocessed( - page::compute_precomputed_commitment(config, proof_options), - page::NUM_PREPROCESSED_COLS, - ) + // ELF data pages: INIT is program-specific, so the commitment is + // per-page. Prefer a caller-supplied `(page_base, commitment)` + // (recursion guest); otherwise recompute from the ELF. + let commitment = page_commitments + .unwrap_or(&[]) + .iter() + .find(|(pb, _)| *pb == config.page_base) + .map(|(_, c)| *c) + .unwrap_or_else(|| { + page::compute_precomputed_commitment(config, proof_options) + }); + air.with_preprocessed(commitment, page::NUM_PREPROCESSED_COLS) } }) .collect(); @@ -674,6 +693,7 @@ pub fn prove_with_options_and_inputs( &traces.page_configs, &table_counts, None, + None, ); #[cfg(feature = "instruments")] @@ -746,6 +766,7 @@ pub fn verify(vm_proof: &VmProof, elf_bytes: &[u8]) -> Result { elf_bytes, &GoldilocksCubicProofOptions::with_blowup(2).expect("blowup=2 is always valid"), None, + None, ) } @@ -762,8 +783,18 @@ pub fn verify(vm_proof: &VmProof, elf_bytes: &[u8]) -> Result { /// commitment as a compile-time constant to avoid the in-VM recompute /// cost. When `None`, the verifier computes the commitment from the ELF. /// -/// Trust model: `decode_commitment`, when supplied, must come from the -/// caller's compiled binary (e.g. a `const [u8; 32]`), never from prover- +/// `page_commitments` is an optional list of precomputed ELF-data-page +/// preprocessed commitments, keyed by `page_base`. For each ELF data page +/// the verifier constructs, if a matching `(page_base, commitment)` pair is +/// supplied, the FFT + Merkle build for that page is skipped. Pages without +/// a match — including all zero-init pages — take the normal compute path +/// (zero-init pages hit a compile-time constant via +/// `page::zero_init_preprocessed_commitment`; ELF data pages recompute +/// from the ELF). When `None`, every ELF data page recomputes from scratch. +/// +/// Trust model: both `decode_commitment` and `page_commitments`, when +/// supplied, must come from the caller's compiled binary (e.g. a +/// `const [u8; 32]` and a `const [(u64, [u8; 32])]`), never from prover- /// supplied bytes. A wrong value is rejected, never silently accepted: it /// either mismatches the prover's committed precomputed root (an explicit /// verifier check) or yields diverging Fiat-Shamir challenges. @@ -772,6 +803,7 @@ pub fn verify_with_options( elf_bytes: &[u8], proof_options: &ProofOptions, decode_commitment: Option, + page_commitments: Option<&[(u64, Commitment)]>, ) -> Result { // Validate table_counts before constructing AIRs. // A malicious prover could set counts to 0, removing entire constraint sets. @@ -818,6 +850,7 @@ pub fn verify_with_options( &page_configs, &vm_proof.table_counts, decode_commitment, + page_commitments, ); // Recompute the COMMIT output bus offset from VmProof.public_output. diff --git a/prover/src/tables/page.rs b/prover/src/tables/page.rs index c97873d15..3997e8c22 100644 --- a/prover/src/tables/page.rs +++ b/prover/src/tables/page.rs @@ -214,10 +214,65 @@ pub fn generate_page_trace( // Preprocessed commitment // ========================================================================= +/// Returns the static zero-init PAGE preprocessed commitment for +/// `blowup_factor`, or `None` if no value is shipped for it. Values were +/// generated by the `compute_static_commitments` binary at the project's +/// standard `coset_offset = 3` (the value every in-tree `ProofOptions` +/// constructor pins) and pinned by +/// `zero_page_static_matches_recompute_for_all_blowups` so any drift in the +/// AIR or FFT pipeline is caught at test time. The verifier reads these +/// from its compiled binary — no input data is trusted. +/// +/// Because OFFSET is page-relative (`0..DEFAULT_PAGE_SIZE-1`) and INIT is +/// uniformly zero for zero-init pages, the commitment depends only on the +/// blowup factor — not on `page_base` or the program being verified. A +/// single entry covers every zero-init page in the system. +/// +/// # Regenerating +/// +/// Only regenerate these match arms after a *deliberate, reviewed* change +/// to the PAGE table layout, the AIR's preprocessed column count, or the +/// FFT / LDE / Merkle pipeline. Run: +/// +/// ```text +/// cargo run --bin compute_static_commitments --release +/// ``` +/// +/// and paste the printed match arms over the ones below. +/// +/// **If a drift test failed, do not regenerate first.** The drift tests +/// exist to force a human to ask "why did this change?" before the new +/// bytes get blessed. Re-pasting on a drift failure silently launders an +/// unintended table change into the verifier's compiled-in trust anchor. +pub(crate) fn static_zero_page_commitment(blowup_factor: u8) -> Option { + match blowup_factor { + 2 => Some([ + 0xf9, 0x80, 0x0e, 0x45, 0x72, 0x5a, 0x8e, 0x8e, 0x5e, 0xd7, 0x5b, 0x60, 0xce, 0xd0, + 0x8e, 0xa3, 0x27, 0x3b, 0x8a, 0xb5, 0x98, 0xc0, 0xe3, 0x16, 0xf6, 0x86, 0x75, 0x39, + 0x4c, 0xe5, 0x88, 0x5e, + ]), + 4 => Some([ + 0x0f, 0xb5, 0x0c, 0xa8, 0x3b, 0x69, 0x4f, 0x91, 0x60, 0xbf, 0x0d, 0x0d, 0xd3, 0x33, + 0x25, 0x38, 0x11, 0xbb, 0xf8, 0xfd, 0x54, 0xbd, 0x06, 0x7d, 0xd1, 0xeb, 0xa3, 0x58, + 0xe8, 0x37, 0x45, 0x56, + ]), + 8 => Some([ + 0x4a, 0xfb, 0xc9, 0x6d, 0x46, 0x29, 0xa3, 0xc2, 0x36, 0x14, 0xd8, 0x24, 0x3e, 0xef, + 0x97, 0x3f, 0xe1, 0xda, 0x2b, 0xf7, 0x87, 0xb6, 0x54, 0xe1, 0xc6, 0x46, 0xc0, 0x85, + 0x96, 0x7f, 0x7f, 0x48, + ]), + _ => None, + } +} + /// Computes the Merkle root commitment over the LDE of PAGE precomputed columns. /// /// The commitment covers OFFSET (0..page_size-1) and INIT (from config). /// Each page may have different INIT data, producing a different commitment. +/// +/// For zero-init pages, prefer [`zero_init_preprocessed_commitment`], which +/// returns a compile-time constant for the standard proof options instead +/// of rebuilding the FFT + Merkle tree. pub fn compute_precomputed_commitment(config: &PageConfig, options: &ProofOptions) -> Commitment { let page_size = DEFAULT_PAGE_SIZE; let num_rows = page_size; @@ -275,6 +330,31 @@ pub fn compute_precomputed_commitment(config: &PageConfig, options: &ProofOption tree.root } +/// Returns the zero-init PAGE preprocessed commitment. +/// +/// Looks up `blowup_factor` in [`static_zero_page_commitment`] when +/// `coset_offset == 3` (the value the static bytes were generated for); on +/// miss — either a non-3 coset or a `blowup_factor` outside the shipped +/// match arms — logs a warning and recomputes from scratch. ELF data pages +/// have program-dependent INIT columns and no static entry; compute their +/// commitments with [`compute_precomputed_commitment`] directly. +pub fn zero_init_preprocessed_commitment(options: &ProofOptions) -> Commitment { + if options.coset_offset == 3 + && let Some(commitment) = static_zero_page_commitment(options.blowup_factor) + { + return commitment; + } + log::warn!( + "zero-init page preprocessed commitment not static for \ + (blowup={}, coset={}); falling back to recompute. Add a match \ + arm to `static_zero_page_commitment` by running \ + `cargo run --bin compute_static_commitments --release`.", + options.blowup_factor, + options.coset_offset, + ); + compute_precomputed_commitment(&PageConfig::zero_init(0), options) +} + // ========================================================================= // Bus interactions // ========================================================================= diff --git a/prover/src/tests/decode_tests.rs b/prover/src/tests/decode_tests.rs index 84ae8ff3a..0a1f323de 100644 --- a/prover/src/tests/decode_tests.rs +++ b/prover/src/tests/decode_tests.rs @@ -1051,6 +1051,7 @@ fn test_decode_soundness_same_elf_accepted() { &traces.page_configs, &table_counts, None, + None, ); let proof = multi_prove_ram( @@ -1068,6 +1069,7 @@ fn test_decode_soundness_same_elf_accepted() { &traces.page_configs, &table_counts, None, + None, ); let verifier_air_refs = verifier_airs.air_refs(); let mut replay_transcript = DefaultTranscript::::new(&[]); @@ -1175,9 +1177,9 @@ fn decode_commitment_some_matches_default_path() { let decode_c = commitment_from_elf(&elf, &options).expect("decode commitment"); - let default_ok = verify_with_options(&vm_proof, &elf_bytes, &options, None) + let default_ok = verify_with_options(&vm_proof, &elf_bytes, &options, None, None) .expect("verify with None should not error"); - let explicit_ok = verify_with_options(&vm_proof, &elf_bytes, &options, Some(decode_c)) + let explicit_ok = verify_with_options(&vm_proof, &elf_bytes, &options, Some(decode_c), None) .expect("verify with Some(correct) should not error"); assert!(default_ok, "default path must accept the proof"); @@ -1198,7 +1200,7 @@ fn decode_commitment_wrong_value_rejects() { let mut wrong = commitment_from_elf(&elf, &options).expect("decode commitment"); wrong[0] ^= 0xFF; - let result = verify_with_options(&vm_proof, &elf_bytes, &options, Some(wrong)) + let result = verify_with_options(&vm_proof, &elf_bytes, &options, Some(wrong), None) .expect("verify must not return Err — Fiat-Shamir mismatch is Ok(false)"); assert!( !result, @@ -1214,7 +1216,7 @@ fn decode_commitment_zero_bytes_rejects() { // [0u8; 32] is the most plausible accidental default — passing it must // not pass verification. - let result = verify_with_options(&vm_proof, &elf_bytes, &options, Some([0u8; 32])) + let result = verify_with_options(&vm_proof, &elf_bytes, &options, Some([0u8; 32]), None) .expect("verify must not return Err — Fiat-Shamir mismatch is Ok(false)"); assert!( !result, @@ -1245,6 +1247,7 @@ fn decode_commitment_compile_time_const_accepts() { &elf_bytes, &options, Some(SUB_DECODE_COMMITMENT_BLOWUP_2), + None, ) .expect("verify must not return Err"); assert!( diff --git a/prover/src/tests/disk_spill_tests.rs b/prover/src/tests/disk_spill_tests.rs index 2d55a35b9..a03575ba7 100644 --- a/prover/src/tests/disk_spill_tests.rs +++ b/prover/src/tests/disk_spill_tests.rs @@ -25,14 +25,14 @@ fn test_disk_spill_prove_verify_and_roundtrip_small() { let proof = crate::prove_with_options(&elf_bytes, &opts, &MaxRowsConfig::default()) .expect("prove failed"); assert!( - crate::verify_with_options(&proof, &elf_bytes, &opts, None).expect("verify failed"), + crate::verify_with_options(&proof, &elf_bytes, &opts, None, None).expect("verify failed"), "verification returned false" ); let bytes = bincode::serialize(&proof).expect("serialize failed"); let proof2: VmProof = bincode::deserialize(&bytes).expect("deserialize failed"); assert!( - crate::verify_with_options(&proof2, &elf_bytes, &opts, None).expect("verify failed"), + crate::verify_with_options(&proof2, &elf_bytes, &opts, None, None).expect("verify failed"), "verification failed after serialization roundtrip" ); } @@ -45,14 +45,14 @@ fn test_disk_spill_prove_verify_and_roundtrip_chunked() { let proof = crate::prove_with_options(&elf_bytes, &opts, &MaxRowsConfig::small()) .expect("prove failed"); assert!( - crate::verify_with_options(&proof, &elf_bytes, &opts, None).expect("verify failed"), + crate::verify_with_options(&proof, &elf_bytes, &opts, None, None).expect("verify failed"), "verification returned false" ); let bytes = bincode::serialize(&proof).expect("serialize failed"); let proof2: VmProof = bincode::deserialize(&bytes).expect("deserialize failed"); assert!( - crate::verify_with_options(&proof2, &elf_bytes, &opts, None).expect("verify failed"), + crate::verify_with_options(&proof2, &elf_bytes, &opts, None, None).expect("verify failed"), "verification failed after serialization roundtrip (chunked)" ); } diff --git a/prover/src/tests/page_tests.rs b/prover/src/tests/page_tests.rs index d2b3f8479..f1bcc6933 100644 --- a/prover/src/tests/page_tests.rs +++ b/prover/src/tests/page_tests.rs @@ -1,7 +1,13 @@ //! Tests for the PAGE table. +use executor::elf::Elf; +use stark::proof::options::GoldilocksCubicProofOptions; + use crate::tables::page::*; +use crate::tables::trace_builder::Traces; use crate::tables::types::*; +use crate::test_utils::asm_elf_bytes; +use crate::{prove, verify_with_options}; #[test] fn test_page_base_for_address() { @@ -140,3 +146,106 @@ fn test_bus_interactions_high_address() { let interactions = bus_interactions(stack_page); assert_eq!(interactions.len(), 3); } + +// ========================================================================= +// verify_with_options: optional page_commitments parameter +// ========================================================================= + +/// Compute the correct ELF-data-page commitments for the given ELF + options. +/// Returns `(page_base, commitment)` pairs for every non-private, non-zero-init +/// page in the verifier's reconstructed page set. +fn elf_data_page_commitments( + elf_bytes: &[u8], + vm_proof: &crate::VmProof, + options: &stark::proof::options::ProofOptions, +) -> Vec<(u64, stark::config::Commitment)> { + let elf = Elf::load(elf_bytes).expect("ELF load"); + let page_configs = Traces::page_configs_from_elf_and_runtime( + &elf, + &vm_proof.runtime_page_ranges, + vm_proof.num_private_input_pages, + ); + page_configs + .iter() + .filter(|c| !c.is_private_input && c.init_values.is_some()) + .map(|c| (c.page_base, compute_precomputed_commitment(c, options))) + .collect() +} + +#[test] +fn page_commitments_some_matches_default_path() { + let elf_bytes = asm_elf_bytes("sub"); + let vm_proof = prove(&elf_bytes).expect("prove failed"); + let options = GoldilocksCubicProofOptions::with_blowup(2).expect("blowup=2 valid"); + + let list = elf_data_page_commitments(&elf_bytes, &vm_proof, &options); + assert!( + !list.is_empty(), + "test ELF must have at least one ELF data page for this test to be meaningful", + ); + + let default_ok = verify_with_options(&vm_proof, &elf_bytes, &options, None, None) + .expect("verify with None should not error"); + let explicit_ok = verify_with_options(&vm_proof, &elf_bytes, &options, None, Some(&list)) + .expect("verify with Some(correct) should not error"); + + assert!(default_ok, "default path must accept the proof"); + assert!( + explicit_ok, + "Some(correct_page_commitments) must accept the proof" + ); +} + +#[test] +fn page_commitments_wrong_value_rejects() { + let elf_bytes = asm_elf_bytes("sub"); + let vm_proof = prove(&elf_bytes).expect("prove failed"); + let options = GoldilocksCubicProofOptions::with_blowup(2).expect("blowup=2 valid"); + + let mut list = elf_data_page_commitments(&elf_bytes, &vm_proof, &options); + assert!(!list.is_empty(), "test ELF must have ≥ 1 ELF data page"); + // Flip a byte in the first page's commitment so the Fiat-Shamir transcripts diverge. + list[0].1[0] ^= 0xFF; + + let result = verify_with_options(&vm_proof, &elf_bytes, &options, None, Some(&list)) + .expect("verify must not return Err — Fiat-Shamir mismatch is Ok(false)"); + assert!( + !result, + "tampered page commitment must cause Fiat-Shamir rejection", + ); +} + +#[test] +fn page_commitments_zero_bytes_rejects() { + let elf_bytes = asm_elf_bytes("sub"); + let vm_proof = prove(&elf_bytes).expect("prove failed"); + let options = GoldilocksCubicProofOptions::with_blowup(2).expect("blowup=2 valid"); + + let mut list = elf_data_page_commitments(&elf_bytes, &vm_proof, &options); + assert!(!list.is_empty(), "test ELF must have ≥ 1 ELF data page"); + // [0u8; 32] is the most plausible accidental default — passing it must + // not pass verification. + list[0].1 = [0u8; 32]; + + let result = verify_with_options(&vm_proof, &elf_bytes, &options, None, Some(&list)) + .expect("verify must not return Err — Fiat-Shamir mismatch is Ok(false)"); + assert!( + !result, + "all-zero page commitment must cause Fiat-Shamir rejection", + ); +} + +#[test] +fn page_commitments_empty_list_matches_none() { + let elf_bytes = asm_elf_bytes("sub"); + let vm_proof = prove(&elf_bytes).expect("prove failed"); + let options = GoldilocksCubicProofOptions::with_blowup(2).expect("blowup=2 valid"); + + let empty: [(u64, stark::config::Commitment); 0] = []; + let result = verify_with_options(&vm_proof, &elf_bytes, &options, None, Some(&empty)) + .expect("verify with empty list should not error"); + assert!( + result, + "empty page_commitments slice must behave like None — every page falls through to recompute", + ); +} diff --git a/prover/src/tests/prove_elfs_tests.rs b/prover/src/tests/prove_elfs_tests.rs index b908df286..faceec480 100644 --- a/prover/src/tests/prove_elfs_tests.rs +++ b/prover/src/tests/prove_elfs_tests.rs @@ -59,6 +59,7 @@ fn prove_and_verify_vm_minimal(elf: &Elf, traces: &mut Traces) -> bool { &traces.page_configs, &table_counts, None, + None, ); // Build air_trace_pairs for all tables @@ -109,6 +110,7 @@ fn prove_vm_minimal(elf_bytes: &[u8], private_inputs: &[u8], max_rows: &MaxRowsC &traces.page_configs, &table_counts, None, + None, ); let runtime_page_ranges = traces.runtime_page_ranges(); let proof = multi_prove_ram( @@ -148,6 +150,7 @@ fn verify_vm_minimal(vm_proof: &VmProof, elf_bytes: &[u8]) -> bool { &page_configs, &vm_proof.table_counts, None, + None, ); let air_refs = airs.air_refs(); let mut replay_transcript = DefaultTranscript::::new(&[]); @@ -1164,6 +1167,7 @@ fn test_prove_elfs_test_commit_4_wrong_pages_rejected() { &traces.page_configs, &table_counts, None, + None, ); let proof = multi_prove_ram( prover_airs.air_trace_pairs(&mut traces), @@ -1180,6 +1184,7 @@ fn test_prove_elfs_test_commit_4_wrong_pages_rejected() { &wrong_configs, &table_counts, None, + None, ); let verifier_air_refs = verifier_airs.air_refs(); let mut replay_transcript = DefaultTranscript::::new(&[]); @@ -1210,7 +1215,7 @@ fn test_verify_rejects_tampered_public_output() { let vm_proof = crate::prove_with_options(&elf_bytes, &proof_options, &Default::default()) .expect("Prover should succeed for test_commit_4"); assert!( - crate::verify_with_options(&vm_proof, &elf_bytes, &proof_options, None) + crate::verify_with_options(&vm_proof, &elf_bytes, &proof_options, None, None) .expect("Valid commit proof should verify"), "Baseline proof should verify before tampering" ); @@ -1222,8 +1227,9 @@ fn test_verify_rejects_tampered_public_output() { ..vm_proof }; - let verified = crate::verify_with_options(&tampered_proof, &elf_bytes, &proof_options, None) - .expect("Verifier should not error on tampered public output"); + let verified = + crate::verify_with_options(&tampered_proof, &elf_bytes, &proof_options, None, None) + .expect("Verifier should not error on tampered public output"); assert!( !verified, "Verifier should reject proof when VmProof.public_output is tampered" @@ -1910,6 +1916,7 @@ fn test_deep_stack_runtime_pages_roundtrip() { &traces.page_configs, &table_counts, None, + None, ); let proof = multi_prove_ram( prover_airs.air_trace_pairs(&mut traces), @@ -1925,6 +1932,7 @@ fn test_deep_stack_runtime_pages_roundtrip() { &verifier_configs, &table_counts, None, + None, ); let verifier_air_refs = verifier_airs.air_refs(); let mut replay_transcript = DefaultTranscript::::new(&[]); @@ -1974,6 +1982,7 @@ fn test_deep_stack_missing_pages_rejected() { &traces.page_configs, &table_counts, None, + None, ); let proof = multi_prove_ram( prover_airs.air_trace_pairs(&mut traces), @@ -1989,6 +1998,7 @@ fn test_deep_stack_missing_pages_rejected() { &wrong_configs, &table_counts, None, + None, ); let verifier_air_refs = verifier_airs.air_refs(); let mut replay_transcript = DefaultTranscript::::new(&[]); @@ -2073,6 +2083,7 @@ fn test_heap_alloc_runtime_pages_roundtrip() { &traces.page_configs, &table_counts, None, + None, ); let proof = multi_prove_ram( prover_airs.air_trace_pairs(&mut traces), @@ -2088,6 +2099,7 @@ fn test_heap_alloc_runtime_pages_roundtrip() { &verifier_configs, &table_counts, None, + None, ); let verifier_air_refs = verifier_airs.air_refs(); let mut replay_transcript = DefaultTranscript::::new(&[]); @@ -2148,7 +2160,7 @@ fn test_verify_rejects_zero_table_counts() { .expect("Prover should succeed on valid program"); assert!( - crate::verify_with_options(&vm_proof, &elf_bytes, &proof_options, None) + crate::verify_with_options(&vm_proof, &elf_bytes, &proof_options, None, None) .expect("Verification should not error on valid proof"), "Valid proof should verify" ); @@ -2169,7 +2181,8 @@ fn test_verify_rejects_zero_table_counts() { ..vm_proof }; - let result = crate::verify_with_options(&tampered_proof, &elf_bytes, &proof_options, None); + let result = + crate::verify_with_options(&tampered_proof, &elf_bytes, &proof_options, None, None); assert!(result.is_err(), "Got {:?}", result); } @@ -2190,7 +2203,8 @@ fn test_verify_rejects_zero_cpu_count() { ..vm_proof }; - let result = crate::verify_with_options(&tampered_proof, &elf_bytes, &proof_options, None); + let result = + crate::verify_with_options(&tampered_proof, &elf_bytes, &proof_options, None, None); assert!(result.is_err(), "Got {:?}", result); } @@ -2211,7 +2225,8 @@ fn test_verify_rejects_zero_memw_count() { ..vm_proof }; - let result = crate::verify_with_options(&tampered_proof, &elf_bytes, &proof_options, None); + let result = + crate::verify_with_options(&tampered_proof, &elf_bytes, &proof_options, None, None); assert!(result.is_err(), "Got {:?}", result); } @@ -2234,7 +2249,7 @@ fn test_crafted_zero_count_proof_must_not_verify() { branch: 0, memw_register: 0, }; - let airs = VmAirs::new(&elf, &proof_options, true, &[], &zero_counts, None); + let airs = VmAirs::new(&elf, &proof_options, true, &[], &zero_counts, None, None); let verifier_air_refs = airs.air_refs(); assert_eq!(verifier_air_refs.len(), 8); @@ -2338,7 +2353,8 @@ fn test_verify_rejects_inflated_table_counts() { ..vm_proof }; - let result = crate::verify_with_options(&tampered_proof, &elf_bytes, &proof_options, None); + let result = + crate::verify_with_options(&tampered_proof, &elf_bytes, &proof_options, None, None); assert!( result.is_err(), "Inflated table_counts should be rejected, got {:?}", diff --git a/prover/src/tests/static_commitments_tests.rs b/prover/src/tests/static_commitments_tests.rs index c67e9baeb..01d9817e8 100644 --- a/prover/src/tests/static_commitments_tests.rs +++ b/prover/src/tests/static_commitments_tests.rs @@ -1,25 +1,28 @@ //! Drift-detection and lookup-dispatch tests for the static preprocessed-table -//! commitments shipped in `bitwise` and `keccak_rc`. +//! commitments shipped in `bitwise`, `keccak_rc`, and `page` (the shared +//! zero-init page commitment). //! //! - The drift tests recompute the commitment for every blowup in //! `STATIC_BLOWUP_FACTORS` (the list shared with the generator binary) and -//! compare against the value the table-module's `preprocessed_commitment` -//! wrapper returns. This catches AIR or FFT-pipeline drift AND confirms the -//! wrapper dispatches through `static_commitment` for the static blowups. -//! - The non-static-blowup fallback test picks a blowup not in -//! `STATIC_BLOWUP_FACTORS` and asserts the wrapper still returns the correct +//! compare against the value the table-module's wrapper returns +//! (`preprocessed_commitment` for `bitwise`/`keccak_rc`, +//! `zero_init_preprocessed_commitment` for `page`). This catches AIR or +//! FFT-pipeline drift; the page test additionally pins the static bytes +//! against the recompute directly. +//! - The non-static-blowup fallback tests pick a blowup not in +//! `STATIC_BLOWUP_FACTORS` and assert the wrapper still returns the correct //! value via recompute. //! - The coset-mismatch tests use `coset_offset != 3` and assert the wrapper //! takes the recompute path (rather than silently returning the coset-3 //! static bytes); they're the regression test for the -//! `options.coset_offset == 3` gate in `preprocessed_commitment`. +//! `options.coset_offset == 3` gate in the wrappers. //! //! If a drift test fails, regenerate the constants via //! `cargo run --bin compute_static_commitments --release`. use stark::proof::options::GoldilocksCubicProofOptions; -use crate::tables::{STATIC_BLOWUP_FACTORS, bitwise, keccak_rc}; +use crate::tables::{STATIC_BLOWUP_FACTORS, bitwise, keccak_rc, page}; fn options_for(blowup: u8) -> stark::proof::options::ProofOptions { GoldilocksCubicProofOptions::with_blowup(blowup).expect("blowup must be a valid power of 2") @@ -73,6 +76,96 @@ fn keccak_rc_static_matches_recompute_for_all_blowups() { } } +/// Drift / dispatch test for the zero-init PAGE static commitments. For every +/// blowup in `STATIC_BLOWUP_FACTORS`, builds a synthetic zero-init page at +/// `DEFAULT_PAGE_SIZE` (page_base = 0 — the value doesn't affect the +/// commitment since OFFSET is page-relative and INIT is uniformly zero), +/// recomputes the commitment from scratch, and checks two things: +/// +/// - the static bytes equal the recompute, read directly from +/// `static_zero_page_commitment` (panicking if the match arm is missing) — +/// so byte drift can't be masked by a broken dispatch gate; +/// - `zero_init_preprocessed_commitment` equals the recompute — the wrapper +/// returns a correct value whichever path it takes. (The coset-3 gate +/// itself is covered by the ignored +/// `page_non_three_coset_recomputes_and_differs_from_static` test.) +#[test] +fn zero_page_static_matches_recompute_for_all_blowups() { + let zero_page_config = page::PageConfig::zero_init(0); + for &blowup in STATIC_BLOWUP_FACTORS { + let options = options_for(blowup); + let recomputed = page::compute_precomputed_commitment(&zero_page_config, &options); + let Some(static_bytes) = page::static_zero_page_commitment(blowup) else { + panic!("no static zero-page match arm shipped for blowup={blowup}"); + }; + assert_eq!( + static_bytes, recomputed, + "static zero-init page commitment drifted for blowup={blowup}; \ + regenerate constants via \ + `cargo run --bin compute_static_commitments --release`", + ); + let from_wrapper = page::zero_init_preprocessed_commitment(&options); + assert_eq!( + from_wrapper, recomputed, + "zero_init_preprocessed_commitment returned a wrong value for blowup={blowup}", + ); + } +} + +/// Asserts the page wrapper's fallback path (no static entry for this +/// blowup) recomputes a commitment that matches the direct compute call. +/// Ignored by default: at NON_STATIC_BLOWUP=16, the page LDE is 2^22 rows × +/// 2 cols plus the FFT/Merkle build, which takes minutes per run. Run +/// explicitly when validating the wrapper's fallback for page. +#[test] +#[ignore = "heavy: page LDE at NON_STATIC_BLOWUP=16 is 2^22 rows × 2 cols; minutes per run"] +fn page_non_static_blowup_recomputes_via_fallback() { + assert!( + !STATIC_BLOWUP_FACTORS.contains(&NON_STATIC_BLOWUP), + "test relies on NON_STATIC_BLOWUP not being in STATIC_BLOWUP_FACTORS", + ); + let zero_page_config = page::PageConfig::zero_init(0); + let options = options_for(NON_STATIC_BLOWUP); + let from_wrapper = page::zero_init_preprocessed_commitment(&options); + let recomputed = page::compute_precomputed_commitment(&zero_page_config, &options); + assert_eq!( + from_wrapper, recomputed, + "page fallback returned a value that doesn't match direct compute at blowup={NON_STATIC_BLOWUP}", + ); +} + +/// Regression test for the `options.coset_offset == 3` gate in +/// `page::zero_init_preprocessed_commitment`. With a non-3 coset offset, the +/// wrapper must NOT return a static value — it must recompute (matching direct +/// compute) and must NOT equal the coset-3 static commitment. Ignored by +/// default: each blowup at DEFAULT_PAGE_SIZE builds a 2^19-row × 2-col page +/// LDE, multiple seconds per blowup. Run explicitly when validating the +/// coset-3 gate for page. +#[test] +#[ignore = "heavy: 2^19-row page LDE per blowup; tens of seconds total"] +fn page_non_three_coset_recomputes_and_differs_from_static() { + let zero_page_config = page::PageConfig::zero_init(0); + for &blowup in STATIC_BLOWUP_FACTORS { + let opts_coset3 = options_with_coset(blowup, STANDARD_COSET); + let opts_coset7 = options_with_coset(blowup, NON_STANDARD_COSET); + + let from_wrapper_7 = page::zero_init_preprocessed_commitment(&opts_coset7); + let recomputed_7 = page::compute_precomputed_commitment(&zero_page_config, &opts_coset7); + let from_wrapper_3 = page::zero_init_preprocessed_commitment(&opts_coset3); + + assert_eq!( + from_wrapper_7, recomputed_7, + "page wrapper at coset {NON_STANDARD_COSET} must take the recompute path \ + (blowup={blowup})", + ); + assert_ne!( + from_wrapper_7, from_wrapper_3, + "page commitment at coset {NON_STANDARD_COSET} must differ from coset \ + {STANDARD_COSET} static value (blowup={blowup})", + ); + } +} + /// Asserts the wrapper's fallback path (no static entry for this blowup) /// recomputes a commitment that matches the direct compute call. Uses /// keccak_rc because its table is only 32 rows, making the recompute cheap From ec5a7273473cd4b128346a12999351198af1aaf6 Mon Sep 17 00:00:00 2001 From: Nicole Graus Date: Wed, 10 Jun 2026 18:30:41 -0300 Subject: [PATCH 2/3] Add cycles count flag to cli execute (#660) * Add a --cycles flag to cli execute that prints a program's dynamic instruction count * Update the execute loop comment to reflect that it always counts cycles, not only when generating a flamegraph * Fix docs --------- Co-authored-by: MauroFab Co-authored-by: Mauro Toscano <12560266+MauroToscano@users.noreply.github.com> --- bin/cli/README.md | 3 ++- bin/cli/src/main.rs | 16 ++++++++++++++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/bin/cli/README.md b/bin/cli/README.md index da82620c0..c784ff6c7 100644 --- a/bin/cli/README.md +++ b/bin/cli/README.md @@ -34,13 +34,14 @@ See the root [`README.md`](../../README.md) for the toolchain setup. Run a RISC-V ELF program without generating a proof. Useful for testing and debugging. ```sh -cargo run -p cli --release -- execute [--private-input ] [--flamegraph ] +cargo run -p cli --release -- execute [--private-input ] [--flamegraph ] [--cycles] ``` | Flag | Description | |---|---| | `--private-input ` | Pass private input bytes to the guest (read via `get_private_input()`). | | `--flamegraph ` | Generate folded-stack flamegraph output. See [Guest Program Flamegraphs](#guest-program-flamegraphs). | +| `--cycles` | Count instructions during execution and print the dynamic instruction count. | ### Prove diff --git a/bin/cli/src/main.rs b/bin/cli/src/main.rs index 7067eda68..5c9719650 100644 --- a/bin/cli/src/main.rs +++ b/bin/cli/src/main.rs @@ -108,6 +108,10 @@ enum Commands { /// Generate flamegraph folded stacks to file #[arg(long, value_hint = ValueHint::FilePath)] flamegraph: Option, + + /// Print the dynamic instruction (cycle) count + #[arg(long)] + cycles: bool, }, /// Generate a proof for an ELF program @@ -182,7 +186,8 @@ fn main() -> ExitCode { elf, private_input, flamegraph, - } => cmd_execute(elf, private_input, flamegraph), + cycles, + } => cmd_execute(elf, private_input, flamegraph, cycles), Commands::Prove { elf, output, @@ -216,6 +221,7 @@ fn cmd_execute( elf_path: PathBuf, private_input_path: Option, flamegraph_path: Option, + cycles: bool, ) -> ExitCode { let elf_data = match std::fs::read(&elf_path) { Ok(data) => data, @@ -255,7 +261,8 @@ fn cmd_execute( FlamegraphGenerator::new(symbols, program.entry_point) }); - // Execute in chunks, processing logs only if generating flamegraph + // Execute in chunks, counting cycles and (if requested) feeding the flamegraph. + let mut cycle_count: u64 = 0; loop { let logs = match executor.resume() { Ok(logs) => logs, @@ -266,6 +273,7 @@ fn cmd_execute( }; match logs { Some(logs) => { + cycle_count += logs.len() as u64; if let Some(ref mut fg) = generator { let logs: Vec<_> = logs.to_vec(); if let Err(e) = fg.process_logs(&logs, &executor.instructions) { @@ -305,6 +313,10 @@ fn cmd_execute( ); } + if cycles { + println!("Cycles: {}", cycle_count); + } + ExitCode::SUCCESS } From 5cfac85c67a1c1c8df7562a13c7677381ed96c61 Mon Sep 17 00:00:00 2001 From: Joaquin Carletti <56092489+ColoCarletti@users.noreply.github.com> Date: Thu, 11 Jun 2026 11:14:12 -0300 Subject: [PATCH 3/3] Patch keccak and add ethrex bench (#659) * simple ethrex tx * add full test * fix test * fix print ecall * add ethrex with tx bench * add keccak patch * Fix ethrex fixture reproducibility (#661) * Fix ethrex fixture reproducibility * Tighten ethrex benchmark rebuild logging --------- Co-authored-by: Mauro Toscano <12560266+MauroToscano@users.noreply.github.com> --- .github/scripts/publish_bench_vs.sh | 25 +- .github/workflows/bench-vs-nightly.yml | 11 +- Cargo.lock | 149 ++- bench_vs/run_ethrex.sh | 162 ++-- executor/Cargo.toml | 2 +- executor/programs/rust/ethrex/Cargo.lock | 22 +- executor/programs/rust/ethrex/Cargo.toml | 8 +- .../ethrex/patches/ethrex-crypto/Cargo.toml | 31 + .../patches/ethrex-crypto/blake2f/aarch64.rs | 296 ++++++ .../patches/ethrex-crypto/blake2f/mod.rs | 29 + .../patches/ethrex-crypto/blake2f/portable.rs | 99 ++ .../patches/ethrex-crypto/blake2f/x86_64.rs | 14 + .../patches/ethrex-crypto/blake2f/x86_64.s | 539 +++++++++++ .../patches/ethrex-crypto/keccak/README.md | 79 ++ .../keccak/keccak1600-armv8-elf.s | 855 ++++++++++++++++++ .../keccak/keccak1600-armv8-macho.s | 855 ++++++++++++++++++ .../ethrex-crypto/keccak/keccak1600-x86_64.s | 536 +++++++++++ .../patches/ethrex-crypto/keccak/mod.rs | 216 +++++ .../rust/ethrex/patches/ethrex-crypto/kzg.rs | 283 ++++++ .../rust/ethrex/patches/ethrex-crypto/lib.rs | 3 + executor/tests/README.md | 29 + executor/tests/ethrex_simple_tx.bin | Bin 0 -> 2832 bytes executor/tests/rust.rs | 18 + syscalls/src/keccak.rs | 107 +++ syscalls/src/lib.rs | 1 + syscalls/src/syscalls.rs | 24 +- 26 files changed, 4209 insertions(+), 184 deletions(-) create mode 100644 executor/programs/rust/ethrex/patches/ethrex-crypto/Cargo.toml create mode 100644 executor/programs/rust/ethrex/patches/ethrex-crypto/blake2f/aarch64.rs create mode 100644 executor/programs/rust/ethrex/patches/ethrex-crypto/blake2f/mod.rs create mode 100644 executor/programs/rust/ethrex/patches/ethrex-crypto/blake2f/portable.rs create mode 100644 executor/programs/rust/ethrex/patches/ethrex-crypto/blake2f/x86_64.rs create mode 100644 executor/programs/rust/ethrex/patches/ethrex-crypto/blake2f/x86_64.s create mode 100644 executor/programs/rust/ethrex/patches/ethrex-crypto/keccak/README.md create mode 100644 executor/programs/rust/ethrex/patches/ethrex-crypto/keccak/keccak1600-armv8-elf.s create mode 100644 executor/programs/rust/ethrex/patches/ethrex-crypto/keccak/keccak1600-armv8-macho.s create mode 100644 executor/programs/rust/ethrex/patches/ethrex-crypto/keccak/keccak1600-x86_64.s create mode 100644 executor/programs/rust/ethrex/patches/ethrex-crypto/keccak/mod.rs create mode 100644 executor/programs/rust/ethrex/patches/ethrex-crypto/kzg.rs create mode 100644 executor/programs/rust/ethrex/patches/ethrex-crypto/lib.rs create mode 100644 executor/tests/README.md create mode 100644 executor/tests/ethrex_simple_tx.bin create mode 100644 syscalls/src/keccak.rs diff --git a/.github/scripts/publish_bench_vs.sh b/.github/scripts/publish_bench_vs.sh index de79ce64b..679dec756 100644 --- a/.github/scripts/publish_bench_vs.sh +++ b/.github/scripts/publish_bench_vs.sh @@ -82,14 +82,25 @@ fi ETHREX_METRICS_FILE="bench_vs_artifacts/ethrex_metrics.txt" ETHREX_SECTION="" if [ -f "$ETHREX_METRICS_FILE" ]; then - ETHREX_TIME=$(grep '^ethrex_empty_block_time_s=' "$ETHREX_METRICS_FILE" | cut -d= -f2-) - ETHREX_CYCLES=$(grep '^ethrex_empty_block_cycles=' "$ETHREX_METRICS_FILE" | cut -d= -f2-) - if [ -n "$ETHREX_TIME" ]; then - ETHREX_MRKDWN="*Empty block:* ${ETHREX_TIME}s" - if [ -n "$ETHREX_CYCLES" ] && [ "$ETHREX_CYCLES" != "n/a" ]; then - ETHREX_MRKDWN="${ETHREX_MRKDWN} (${ETHREX_CYCLES} cycles)" + # Render one "*