diff --git a/Cargo.lock b/Cargo.lock index ddd13c0c..7240a95c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -741,7 +741,7 @@ dependencies = [ "bitflags", "cexpr", "clang-sys", - "itertools 0.12.1", + "itertools 0.13.0", "proc-macro2", "quote", "regex", @@ -1568,7 +1568,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccc2776f0c61eca1ca32528f85548abd1a4be8fb53d1b21c013e4f18da1e7090" dependencies = [ "data-encoding", - "syn 1.0.109", + "syn 2.0.117", ] [[package]] @@ -2124,6 +2124,14 @@ dependencies = [ "tracing", ] +[[package]] +name = "ethlambda-prover-core" +version = "0.1.0" +dependencies = [ + "ethlambda-types", + "thiserror 2.0.18", +] + [[package]] name = "ethlambda-rpc" version = "0.1.0" @@ -4842,7 +4850,7 @@ source = "git+https://github.com/leanEthereum/leanVM.git?rev=e2592df#e2592df4e30 dependencies = [ "itertools 0.14.0", "mt-utils", - "num-bigint 0.3.3", + "num-bigint 0.4.6", "parallel", "paste", "rand 0.10.1", @@ -4858,7 +4866,7 @@ dependencies = [ "itertools 0.14.0", "mt-field", "mt-utils", - "num-bigint 0.3.3", + "num-bigint 0.4.6", "paste", "rand 0.10.1", "serde", @@ -5327,29 +5335,29 @@ dependencies = [ [[package]] name = "p3-bn254-fr" -version = "0.3.3-succinct" +version = "0.4.3-succinct" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "577200e3fa7e49e2b21e940a6dc7399dc63acb8581da088558cdf7c455adafc0" +checksum = "2077757c7cb514202ccb5368f521f23f5709c720599e6545c683c66e0a52d2d8" dependencies = [ "ff", "num-bigint 0.4.6", - "p3-field 0.3.3-succinct", - "p3-poseidon2 0.3.3-succinct", - "p3-symmetric 0.3.3-succinct", + "p3-field 0.4.3-succinct", + "p3-poseidon2 0.4.3-succinct", + "p3-symmetric 0.4.3-succinct", "rand 0.8.6", "serde", ] [[package]] name = "p3-challenger" -version = "0.3.3-succinct" +version = "0.4.3-succinct" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75358edd6e2562752c01f5064a66d88144a3e75ace0407166dbdf8a727597f52" +checksum = "b6a908924d43e4cfb93fb41c8346cac211b70314385a9037e9241f5b7f3eaf77" dependencies = [ - "p3-field 0.3.3-succinct", - "p3-maybe-rayon 0.3.3-succinct", - "p3-symmetric 0.3.3-succinct", - "p3-util 0.3.3-succinct", + "p3-field 0.4.3-succinct", + "p3-maybe-rayon 0.4.3-succinct", + "p3-symmetric 0.4.3-succinct", + "p3-util 0.4.3-succinct", "serde", "tracing", ] @@ -5369,14 +5377,14 @@ dependencies = [ [[package]] name = "p3-dft" -version = "0.3.3-succinct" +version = "0.4.3-succinct" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "761f1e1b014f2b1b69bd0309124e233d64aa3590e6a41ee786000dd849506d51" +checksum = "be6408b10a2c27eb13a7d5580c546c2179a8dc7dbc10a990657311891f9b41c0" dependencies = [ - "p3-field 0.3.3-succinct", - "p3-matrix 0.3.3-succinct", - "p3-maybe-rayon 0.3.3-succinct", - "p3-util 0.3.3-succinct", + "p3-field 0.4.3-succinct", + "p3-matrix 0.4.3-succinct", + "p3-maybe-rayon 0.4.3-succinct", + "p3-util 0.4.3-succinct", "tracing", ] @@ -5396,14 +5404,14 @@ dependencies = [ [[package]] name = "p3-field" -version = "0.3.3-succinct" +version = "0.4.3-succinct" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2df7cebaa4079b24e0dd7e3aad59eebcbb99a67c1271f79ad884a7c032f5f183" +checksum = "3dc75969ca3ac847f43e632ab979d59ff7a68f9eac8dbf8edcbba47fc2e1d3aa" dependencies = [ "itertools 0.12.1", "num-bigint 0.4.6", "num-traits", - "p3-util 0.3.3-succinct", + "p3-util 0.4.3-succinct", "rand 0.8.6", "serde", ] @@ -5425,16 +5433,16 @@ dependencies = [ [[package]] name = "p3-koala-bear" -version = "0.3.3-succinct" +version = "0.4.3-succinct" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cea0ba3389b034b6088d566aea8b57aa29dd2e180966e0c8056f61331c92b4e" +checksum = "3a9683cd0ef68100df7c62490533047bcf19c04c4a0fa1efc9d7c1e03e31f6b3" dependencies = [ "cfg-if 1.0.4", "num-bigint 0.4.6", - "p3-field 0.3.3-succinct", - "p3-mds 0.3.3-succinct", - "p3-poseidon2 0.3.3-succinct", - "p3-symmetric 0.3.3-succinct", + "p3-field 0.4.3-succinct", + "p3-mds 0.4.3-succinct", + "p3-poseidon2 0.4.3-succinct", + "p3-symmetric 0.4.3-succinct", "rand 0.8.6", "rustc_version 0.4.1", "serde", @@ -5457,14 +5465,14 @@ dependencies = [ [[package]] name = "p3-matrix" -version = "0.3.3-succinct" +version = "0.4.3-succinct" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fae5cc6ce726cc265cc687c1214e3f1ac1f5c6e973442286ba00d1e75da1c3cb" +checksum = "75c3f150ceb90e09539413bf481e618d05ee19210b4e467d2902eb82d2e15281" dependencies = [ "itertools 0.12.1", - "p3-field 0.3.3-succinct", - "p3-maybe-rayon 0.3.3-succinct", - "p3-util 0.3.3-succinct", + "p3-field 0.4.3-succinct", + "p3-maybe-rayon 0.4.3-succinct", + "p3-util 0.4.3-succinct", "rand 0.8.6", "serde", "tracing", @@ -5486,9 +5494,9 @@ dependencies = [ [[package]] name = "p3-maybe-rayon" -version = "0.3.3-succinct" +version = "0.4.3-succinct" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55ac1d2f102cf8c71dba1b449575c99697781fcc028831e83d2245787bd7a650" +checksum = "e0641952b42da45e1dfa2d4a2a3163e330f944ad9740942f35026c0a71a605f1" [[package]] name = "p3-maybe-rayon" @@ -5497,16 +5505,16 @@ source = "git+https://github.com/Plonky3/Plonky3.git#3f67d136c71bec40f180c85d0bb [[package]] name = "p3-mds" -version = "0.3.3-succinct" +version = "0.4.3-succinct" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f072643e385d65fb9eb089ee6824b320417f78671a0db748566e057e28b250e" +checksum = "aa4a5f250e174dcfca5cbeac6ad75713924e7e7320e0a335e3c50b8b1f4fe8ec" dependencies = [ "itertools 0.12.1", - "p3-dft 0.3.3-succinct", - "p3-field 0.3.3-succinct", - "p3-matrix 0.3.3-succinct", - "p3-symmetric 0.3.3-succinct", - "p3-util 0.3.3-succinct", + "p3-dft 0.4.3-succinct", + "p3-field 0.4.3-succinct", + "p3-matrix 0.4.3-succinct", + "p3-symmetric 0.4.3-succinct", + "p3-util 0.4.3-succinct", "rand 0.8.6", ] @@ -5557,14 +5565,14 @@ dependencies = [ [[package]] name = "p3-poseidon2" -version = "0.3.3-succinct" +version = "0.4.3-succinct" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00cc4b6e8a439f79541b0910a016da9e6e12a05a24309bbb713e1db0db396952" +checksum = "522986377b2164c5f94f2dae88e0e0a3d169cc6239202ef4aeb4322d60feffd0" dependencies = [ "gcd", - "p3-field 0.3.3-succinct", - "p3-mds 0.3.3-succinct", - "p3-symmetric 0.3.3-succinct", + "p3-field 0.4.3-succinct", + "p3-mds 0.4.3-succinct", + "p3-symmetric 0.4.3-succinct", "rand 0.8.6", "serde", ] @@ -5583,12 +5591,12 @@ dependencies = [ [[package]] name = "p3-symmetric" -version = "0.3.3-succinct" +version = "0.4.3-succinct" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eebff7fea7deb08a57ccf731a0ed39df25cc66a0e0c2d92c4472c4dee02ee21" +checksum = "9047ce85c086a9b3f118e10078f10636f7bfeed5da871a04da0b61400af8793a" dependencies = [ "itertools 0.12.1", - "p3-field 0.3.3-succinct", + "p3-field 0.4.3-succinct", "serde", ] @@ -5605,9 +5613,9 @@ dependencies = [ [[package]] name = "p3-util" -version = "0.3.3-succinct" +version = "0.4.3-succinct" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8164df89bbc92e29938f916cc5f1ccbfe6a36fb5040f21ba93c1f21985b9868" +checksum = "cff962f8eaa5f36e0447cee7c241f6b4b475fadf3ee61f154327a26bb4e009ba" dependencies = [ "serde", ] @@ -6041,7 +6049,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" dependencies = [ "anyhow", - "itertools 0.12.1", + "itertools 0.14.0", "proc-macro2", "quote", "syn 2.0.117", @@ -7105,20 +7113,20 @@ checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "slop-algebra" -version = "6.2.1" +version = "6.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a473c3a06b466dd0708829415a8a9fab451740da066e07862c8c098904aaad6" +checksum = "8c112cafd4c5c374d267a48c8976d12fca3b0f2cc2e44e6fb1343808310b4fc6" dependencies = [ "itertools 0.14.0", - "p3-field 0.3.3-succinct", + "p3-field 0.4.3-succinct", "serde", ] [[package]] name = "slop-bn254" -version = "6.2.1" +version = "6.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7fbae5dd16a3d1e87c9e99cfd557338171710be01458bd5b12dded3878d3fd8" +checksum = "ea991a652ea2e55f0523649f5c9efbfb32cf9fdcc2bf3b3580b4343a94379503" dependencies = [ "ff", "p3-bn254-fr", @@ -7131,12 +7139,12 @@ dependencies = [ [[package]] name = "slop-challenger" -version = "6.2.1" +version = "6.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4e80df718cef7d3100658dc8b46fafcc994b814421ec9a7d0763a6ee1e5070c" +checksum = "33b33f1eaadcd4159157758b0edcf1c14f470915f85c648e660e4740ff83e921" dependencies = [ "futures", - "p3-challenger 0.3.3-succinct", + "p3-challenger 0.4.3-succinct", "serde", "slop-algebra", "slop-symmetric", @@ -7144,12 +7152,12 @@ dependencies = [ [[package]] name = "slop-koala-bear" -version = "6.2.1" +version = "6.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6586b1c0e66c503e4026a8cb007349fa99c2466957c5b09d18fe658d1391ed8" +checksum = "8795ee9a92ecf0a604c0e3ed20e80bbc38772310c0622f94a2e1b66ca2455cb8" dependencies = [ "lazy_static", - "p3-koala-bear 0.3.3-succinct", + "p3-koala-bear 0.4.3-succinct", "serde", "slop-algebra", "slop-challenger", @@ -7159,29 +7167,29 @@ dependencies = [ [[package]] name = "slop-poseidon2" -version = "6.2.1" +version = "6.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c956b11fff1b8a071fa4ba982dc35e458cff1620dc7b33d9cf22d8df30895f79" +checksum = "25561d9ecf4bcf1aa2e800560ac557bff6dad943b9dc5cd0e8427fceca1e8c65" dependencies = [ - "p3-poseidon2 0.3.3-succinct", + "p3-poseidon2 0.4.3-succinct", ] [[package]] name = "slop-primitives" -version = "6.2.1" +version = "6.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de169e0ca381847f9efa0db5a54533371c10558d7aaed4cb3b2a9bae24a0fe83" +checksum = "f6b24ad70b49f40d6acfe7b1ccf042db25820abe305a4c2232f15204f63f0227" dependencies = [ "slop-algebra", ] [[package]] name = "slop-symmetric" -version = "6.2.1" +version = "6.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955145ad6e3a1d083a428f9274071cfbb44c3b29013aae9d6c4c29fb7328cfc0" +checksum = "94e580d03bb5383aca1c12579a8a24b838afb12eb356439e740cba34904b6864" dependencies = [ - "p3-symmetric 0.3.3-succinct", + "p3-symmetric 0.4.3-succinct", ] [[package]] @@ -7250,9 +7258,9 @@ dependencies = [ [[package]] name = "sp1-lib" -version = "6.2.1" +version = "6.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02cd166e010c80e542585bf74585ea80eff117c361656cae43f2968cf0af12d4" +checksum = "629b673d6aa3c666b41e14d699323413fa90195c906365ba8c49d1b8e4531151" dependencies = [ "bincode", "serde", @@ -7261,9 +7269,9 @@ dependencies = [ [[package]] name = "sp1-primitives" -version = "6.2.1" +version = "6.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4df14efe799ebd675cf530c853153a4787327a2385067716dfad4ede79ff31ad" +checksum = "421b4590a89bf7c263f8a64844a4034ee8cb73c351498911ce0acd18da6b5ed2" dependencies = [ "bincode", "blake3", @@ -7564,7 +7572,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.3.4", + "getrandom 0.4.2", "once_cell", "rustix", "windows-sys 0.61.2", diff --git a/Cargo.toml b/Cargo.toml index 100b1a4a..a27e9f72 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,9 +13,19 @@ members = [ "crates/net/api", "crates/net/p2p", "crates/net/rpc", + "crates/prover/core", "crates/storage", ] +# NOTE: zkVM guest programs are their own workspaces: they build for the respective +# target ISA via their respective build commands, not for the host, so `cargo {clippy,test} +# --workspace` must not try to compile them. +# +# `ethlambda-prover-sp1` targets the host, but its build script compiles the +# guest ELF and needs the SP1 (`cargo prove`) toolchain, so it is kept out of +# the default `--workspace` build too. +exclude = ["crates/guest-program/sp1", "crates/prover/sp1"] + [workspace.package] authors = ["LambdaClass"] edition = "2024" @@ -38,6 +48,8 @@ ethlambda-network-api = { path = "crates/net/api" } ethlambda-p2p = { path = "crates/net/p2p" } ethlambda-rpc = { path = "crates/net/rpc" } ethlambda-storage = { path = "crates/storage" } +ethlambda-prover-core = { path = "crates/prover/core" } +ethlambda-prover-sp1 = { path = "crates/prover/sp1" } tracing = "0.1" thiserror = "2.0.9" @@ -72,11 +84,17 @@ rand = "0.10" lru = "0.16" rocksdb = "0.24" libc = "0.2" -reqwest = { version = "0.12", default-features = false, features = ["rustls-tls"] } +reqwest = { version = "0.12", default-features = false, features = [ + "rustls-tls", +] } eyre = "0.6" # Allocator + heap profiling -tikv-jemallocator = { version = "0.6", features = ["stats", "unprefixed_malloc_on_supported_platforms", "profiling"] } +tikv-jemallocator = { version = "0.6", features = [ + "stats", + "unprefixed_malloc_on_supported_platforms", + "profiling", +] } jemalloc_pprof = { version = "0.8", features = ["flamegraph"] } # NOTE: Shadow-simulator builds also replace quinn-udp with the fallback crate diff --git a/crates/blockchain/state_transition/Cargo.toml b/crates/blockchain/state_transition/Cargo.toml index dc26096e..f74efc0f 100644 --- a/crates/blockchain/state_transition/Cargo.toml +++ b/crates/blockchain/state_transition/Cargo.toml @@ -11,9 +11,13 @@ version.workspace = true [dependencies] ethlambda-types.workspace = true -ethlambda-metrics.workspace = true - thiserror.workspace = true + +# Host-only: prometheus (metrics) and tracing have no meaning in the zkVM guest +# and prometheus doesn't build for the target. Included on every target except +# the zkVM guest (`target_os = "zkvm"`), which drops them automatically. +[target.'cfg(not(target_os = "zkvm"))'.dependencies] +ethlambda-metrics.workspace = true tracing.workspace = true [dev-dependencies] diff --git a/crates/blockchain/state_transition/src/lib.rs b/crates/blockchain/state_transition/src/lib.rs index 403b170a..b0fd1faf 100644 --- a/crates/blockchain/state_transition/src/lib.rs +++ b/crates/blockchain/state_transition/src/lib.rs @@ -1,3 +1,7 @@ +// On the zkVM guest (`target_os = "zkvm"`) `info!`/`warn!` become no-ops, so +// imports and bindings that only feed them go unused — silence those there. +#![cfg_attr(target_os = "zkvm", allow(unused_imports, unused_variables))] + use std::collections::HashMap; use ethlambda_types::{ @@ -8,8 +12,21 @@ use ethlambda_types::{ primitives::{H256, HashTreeRoot as _}, state::{HISTORICAL_ROOTS_LIMIT, JustificationValidators, State}, }; +#[cfg(not(target_os = "zkvm"))] use tracing::{info, warn}; + +// for the zkVM guest, tracing is not compiled in, so set the macros to no-ops. +// `info!`/`warn!` call sites stay unchanged. Defined before first use. +#[cfg(target_os = "zkvm")] +macro_rules! info { + ($($t:tt)*) => {{}}; +} +#[cfg(target_os = "zkvm")] +macro_rules! warn { + ($($t:tt)*) => {{}}; +} + pub mod justified_slots_ops; pub mod metrics; diff --git a/crates/blockchain/state_transition/src/metrics.rs b/crates/blockchain/state_transition/src/metrics.rs index 8df30909..4fa22fa0 100644 --- a/crates/blockchain/state_transition/src/metrics.rs +++ b/crates/blockchain/state_transition/src/metrics.rs @@ -1,9 +1,20 @@ //! Prometheus metrics for state transition. +//! +//! Gated on the target: on the zkVM guest (`target_os = "zkvm"`) the statics +//! are dropped and each function's body becomes a no-op, so the call sites in +//! `lib.rs` never change. +#[cfg(not(target_os = "zkvm"))] use std::sync::LazyLock; +#[cfg(not(target_os = "zkvm"))] use ethlambda_metrics::*; +/// No-op version of the struct imported from ethlambda_metrics for the zkVM guest. +#[cfg(target_os = "zkvm")] +pub struct TimingGuard; + +#[cfg(not(target_os = "zkvm"))] static LEAN_STATE_TRANSITION_SLOTS_PROCESSED_TOTAL: LazyLock = LazyLock::new(|| { register_int_counter!( "lean_state_transition_slots_processed_total", @@ -12,6 +23,7 @@ static LEAN_STATE_TRANSITION_SLOTS_PROCESSED_TOTAL: LazyLock = LazyL .unwrap() }); +#[cfg(not(target_os = "zkvm"))] static LEAN_STATE_TRANSITION_ATTESTATIONS_PROCESSED_TOTAL: LazyLock = LazyLock::new(|| { register_int_counter!( @@ -21,6 +33,7 @@ static LEAN_STATE_TRANSITION_ATTESTATIONS_PROCESSED_TOTAL: LazyLock .unwrap() }); +#[cfg(not(target_os = "zkvm"))] static LEAN_FINALIZATIONS_TOTAL: LazyLock = LazyLock::new(|| { register_int_counter_vec!( "lean_finalizations_total", @@ -32,19 +45,29 @@ static LEAN_FINALIZATIONS_TOTAL: LazyLock = LazyLock::new(|| { /// Increment the slots processed counter by the given amount. pub fn inc_slots_processed(count: u64) { + #[cfg(not(target_os = "zkvm"))] LEAN_STATE_TRANSITION_SLOTS_PROCESSED_TOTAL.inc_by(count); + #[cfg(target_os = "zkvm")] + let _ = count; } /// Increment the attestations processed counter by the given amount. pub fn inc_attestations_processed(count: u64) { + #[cfg(not(target_os = "zkvm"))] LEAN_STATE_TRANSITION_ATTESTATIONS_PROCESSED_TOTAL.inc_by(count); + #[cfg(target_os = "zkvm")] + let _ = count; } /// Increment the finalization counter with the given result. pub fn inc_finalizations(result: &str) { + #[cfg(not(target_os = "zkvm"))] LEAN_FINALIZATIONS_TOTAL.with_label_values(&[result]).inc(); + #[cfg(target_os = "zkvm")] + let _ = result; } +#[cfg(not(target_os = "zkvm"))] static LEAN_STATE_TRANSITION_TIME_SECONDS: LazyLock = LazyLock::new(|| { register_histogram!( "lean_state_transition_time_seconds", @@ -54,6 +77,7 @@ static LEAN_STATE_TRANSITION_TIME_SECONDS: LazyLock = LazyLock::new(| .unwrap() }); +#[cfg(not(target_os = "zkvm"))] static LEAN_STATE_TRANSITION_SLOTS_PROCESSING_TIME_SECONDS: LazyLock = LazyLock::new(|| { register_histogram!( @@ -64,6 +88,7 @@ static LEAN_STATE_TRANSITION_SLOTS_PROCESSING_TIME_SECONDS: LazyLock .unwrap() }); +#[cfg(not(target_os = "zkvm"))] static LEAN_STATE_TRANSITION_BLOCK_PROCESSING_TIME_SECONDS: LazyLock = LazyLock::new(|| { register_histogram!( @@ -74,6 +99,7 @@ static LEAN_STATE_TRANSITION_BLOCK_PROCESSING_TIME_SECONDS: LazyLock .unwrap() }); +#[cfg(not(target_os = "zkvm"))] static LEAN_STATE_TRANSITION_ATTESTATIONS_PROCESSING_TIME_SECONDS: LazyLock = LazyLock::new(|| { register_histogram!( @@ -86,20 +112,36 @@ static LEAN_STATE_TRANSITION_ATTESTATIONS_PROCESSING_TIME_SECONDS: LazyLock TimingGuard { - TimingGuard::new(&LEAN_STATE_TRANSITION_TIME_SECONDS) + #[cfg(not(target_os = "zkvm"))] + let guard = TimingGuard::new(&LEAN_STATE_TRANSITION_TIME_SECONDS); + #[cfg(target_os = "zkvm")] + let guard = TimingGuard; + guard } /// Start timing slots processing. Records duration when the guard is dropped. pub fn time_slots_processing() -> TimingGuard { - TimingGuard::new(&LEAN_STATE_TRANSITION_SLOTS_PROCESSING_TIME_SECONDS) + #[cfg(not(target_os = "zkvm"))] + let guard = TimingGuard::new(&LEAN_STATE_TRANSITION_SLOTS_PROCESSING_TIME_SECONDS); + #[cfg(target_os = "zkvm")] + let guard = TimingGuard; + guard } /// Start timing block processing. Records duration when the guard is dropped. pub fn time_block_processing() -> TimingGuard { - TimingGuard::new(&LEAN_STATE_TRANSITION_BLOCK_PROCESSING_TIME_SECONDS) + #[cfg(not(target_os = "zkvm"))] + let guard = TimingGuard::new(&LEAN_STATE_TRANSITION_BLOCK_PROCESSING_TIME_SECONDS); + #[cfg(target_os = "zkvm")] + let guard = TimingGuard; + guard } /// Start timing attestations processing. Records duration when the guard is dropped. pub fn time_attestations_processing() -> TimingGuard { - TimingGuard::new(&LEAN_STATE_TRANSITION_ATTESTATIONS_PROCESSING_TIME_SECONDS) + #[cfg(not(target_os = "zkvm"))] + let guard = TimingGuard::new(&LEAN_STATE_TRANSITION_ATTESTATIONS_PROCESSING_TIME_SECONDS); + #[cfg(target_os = "zkvm")] + let guard = TimingGuard; + guard } diff --git a/crates/common/types/Cargo.toml b/crates/common/types/Cargo.toml index 1de09a67..e5a2fdb5 100644 --- a/crates/common/types/Cargo.toml +++ b/crates/common/types/Cargo.toml @@ -14,13 +14,17 @@ thiserror.workspace = true serde.workspace = true hex.workspace = true -leansig.workspace = true - libssz.workspace = true libssz-derive.workspace = true libssz-merkle.workspace = true libssz-types.workspace = true +# leansig is host only and used for signing/verification, never on +# the STF path and should not be built for the zkVM guest, so the +# guest drops it with no feature flags or `default-features` juggling. +[target.'cfg(not(target_os = "zkvm"))'.dependencies] +leansig.workspace = true + [dev-dependencies] serde_json.workspace = true serde_yaml_ng.workspace = true diff --git a/crates/common/types/src/lib.rs b/crates/common/types/src/lib.rs index 6db9a590..8eff6965 100644 --- a/crates/common/types/src/lib.rs +++ b/crates/common/types/src/lib.rs @@ -7,6 +7,7 @@ pub mod genesis; pub mod primitives; pub mod signature; pub mod state; +pub mod stf; /// Display helper for truncated root hashes (8 hex chars) pub struct ShortRoot<'a>(pub &'a [u8; 32]); diff --git a/crates/common/types/src/signature.rs b/crates/common/types/src/signature.rs index fa9c8da8..c4b71dfd 100644 --- a/crates/common/types/src/signature.rs +++ b/crates/common/types/src/signature.rs @@ -1,45 +1,59 @@ +#[cfg(not(target_os = "zkvm"))] use std::ops::Range; +#[cfg(not(target_os = "zkvm"))] use leansig::{ serialization::Serializable, signature::{SignatureScheme, SignatureSchemeSecretKey as _, SigningError}, }; +#[cfg(not(target_os = "zkvm"))] use crate::primitives::H256; +/// Size of an XMSS signature in bytes. +/// +/// Computed from: path(32*8*4) + rho(7*4) + hashes(46*8*4) + ssz_offsets(3*4) = 2536 +pub const SIGNATURE_SIZE: usize = 2536; + +/// Error returned when parsing signature or key bytes fails. +#[derive(Debug, Clone, thiserror::Error)] +#[error("signature parse error: {0}")] +pub struct SignatureParseError(pub String); + +// All the structs and functions below use `leansig` which is excluded on the +// zkVM guest, so they are excluded from the ethlambda-types build for the guest. +// `SIGNATURE_SIZE` and `SignatureParseError` are independent of leansig and stay available + /// The XMSS signature scheme used for validator signatures. /// /// This is a post-quantum secure signature scheme based on hash functions. /// Uses Poseidon1 hashing with an aborting hypercube message hash, /// 32-bit lifetime (2^32 signatures per key), dimension 46, and base 8. +#[cfg(not(target_os = "zkvm"))] pub type LeanSignatureScheme = leansig::signature::generalized_xmss::instantiations_aborting::lifetime_2_to_the_32::SIGAbortingTargetSumLifetime32Dim46Base8; /// The public key type from the leansig library. +#[cfg(not(target_os = "zkvm"))] pub type LeanSigPublicKey = ::PublicKey; /// The signature type from the leansig library. +#[cfg(not(target_os = "zkvm"))] pub type LeanSigSignature = ::Signature; /// The secret key type from the leansig library. +#[cfg(not(target_os = "zkvm"))] pub type LeanSigSecretKey = ::SecretKey; +#[cfg(not(target_os = "zkvm"))] pub type Signature = LeanSigSignature; -/// Size of an XMSS signature in bytes. -/// -/// Computed from: path(32*8*4) + rho(7*4) + hashes(46*8*4) + ssz_offsets(3*4) = 2536 -pub const SIGNATURE_SIZE: usize = 2536; - -/// Error returned when parsing signature or key bytes fails. -#[derive(Debug, Clone, thiserror::Error)] -#[error("signature parse error: {0}")] -pub struct SignatureParseError(pub String); - +#[cfg(not(target_os = "zkvm"))] #[derive(Clone)] pub struct ValidatorSignature { inner: LeanSigSignature, } +#[cfg(not(target_os = "zkvm"))] impl ValidatorSignature { pub fn from_bytes(bytes: &[u8]) -> Result { let sig = LeanSigSignature::from_bytes(bytes) @@ -60,11 +74,13 @@ impl ValidatorSignature { } } +#[cfg(not(target_os = "zkvm"))] #[derive(Clone)] pub struct ValidatorPublicKey { inner: LeanSigPublicKey, } +#[cfg(not(target_os = "zkvm"))] impl ValidatorPublicKey { pub fn from_bytes(bytes: &[u8]) -> Result { let pk = LeanSigPublicKey::from_bytes(bytes) @@ -82,10 +98,12 @@ impl ValidatorPublicKey { } /// Validator private key for signing attestations and blocks. +#[cfg(not(target_os = "zkvm"))] pub struct ValidatorSecretKey { inner: LeanSigSecretKey, } +#[cfg(not(target_os = "zkvm"))] impl ValidatorSecretKey { pub fn from_bytes(bytes: &[u8]) -> Result { let sk = LeanSigSecretKey::from_bytes(bytes) @@ -125,7 +143,7 @@ impl ValidatorSecretKey { } } -#[cfg(test)] +#[cfg(all(test, not(target_os = "zkvm")))] mod tests { use super::*; use leansig::serialization::Serializable; diff --git a/crates/common/types/src/state.rs b/crates/common/types/src/state.rs index 26ff110d..fbc3a5eb 100644 --- a/crates/common/types/src/state.rs +++ b/crates/common/types/src/state.rs @@ -6,8 +6,10 @@ use crate::{ block::{Block, BlockBody, BlockHeader}, checkpoint::Checkpoint, primitives::{self, H256}, - signature::{SignatureParseError, ValidatorPublicKey}, }; +// Host-only: leansig-backed pubkey parsing, gated so the guest builds without leansig. +#[cfg(not(target_os = "zkvm"))] +use crate::signature::{SignatureParseError, ValidatorPublicKey}; // Convenience trait for calling hash_tree_root() without a hasher argument use primitives::HashTreeRoot as _; @@ -85,6 +87,7 @@ where serializer.serialize_str(&hex::encode(pubkey)) } +#[cfg(not(target_os = "zkvm"))] impl Validator { pub fn get_attestation_pubkey(&self) -> Result { ValidatorPublicKey::from_bytes(&self.attestation_pubkey) diff --git a/crates/common/types/src/stf.rs b/crates/common/types/src/stf.rs new file mode 100644 index 00000000..4244b5e8 --- /dev/null +++ b/crates/common/types/src/stf.rs @@ -0,0 +1,49 @@ +use libssz::{SszDecode, SszEncode}; +use serde::{Deserialize, Serialize}; + +use crate::{block::Block, primitives::H256, state::State}; + +/// Input type for the zkVM's +/// +/// The inputs to the zkVM need to derive serde Serialize/deserialize +/// which is not derived for state (libssz traits) +/// wrapping pre-serialized SSZ bytes sidesteps that part. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct StfInput { + state: Vec, + block: Vec, +} + +/// Public values committed by the STF guest program. +/// +/// These bind a proof to one concrete transition: applying the block with +/// `block_root` to the state with `pre_state_root` yields the state with +/// `post_state_root`. A verifier reads these back from the proof without ever +/// seeing the full pre-state or block, and can chain proofs by matching one +/// transition's `post_state_root` to the next's `pre_state_root`. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct StfPublicValues { + /// `hash_tree_root` of the pre-state. + pub pre_state_root: H256, + /// `hash_tree_root` of the block being applied. + pub block_root: H256, + /// `hash_tree_root` of the post-state (equals `block.state_root`). + pub post_state_root: H256, +} + +impl StfInput { + pub fn new(state: State, block: Block) -> Self { + StfInput { + state: state.to_ssz(), + block: block.to_ssz(), + } + } + + pub fn state(&self) -> State { + State::from_ssz_bytes(&self.state).expect("error decoding State") + } + + pub fn block(&self) -> Block { + Block::from_ssz_bytes(&self.block).expect("error decoding block") + } +} diff --git a/crates/guest-program/sp1/Cargo.toml b/crates/guest-program/sp1/Cargo.toml new file mode 100644 index 00000000..1e2a93c7 --- /dev/null +++ b/crates/guest-program/sp1/Cargo.toml @@ -0,0 +1,15 @@ +# Standalone workspace: this guest builds for a RISC-V zkVM target via +# `cargo prove build`, so it is excluded from the root workspace and cannot +# inherit its `[workspace.package]` / `[workspace.dependencies]` values. +[workspace] + +[package] +name = "zkvm_guest_sp1" +version = "0.1.0" +edition = "2024" +publish = false + +[dependencies] +sp1-zkvm = "6.1.0" +ethlambda-types = { path = "../../common/types" } +ethlambda-state-transition = { path = "../../blockchain/state_transition" } diff --git a/crates/guest-program/sp1/src/main.rs b/crates/guest-program/sp1/src/main.rs new file mode 100644 index 00000000..859fb72a --- /dev/null +++ b/crates/guest-program/sp1/src/main.rs @@ -0,0 +1,29 @@ +#![no_main] +sp1_zkvm::entrypoint!(main); + +use ethlambda_state_transition::state_transition; +use ethlambda_types::{ + primitives::HashTreeRoot, + stf::{StfInput, StfPublicValues}, +}; + +fn main() { + let input: StfInput = sp1_zkvm::io::read(); + let mut state = input.state(); + let block = input.block(); + + // Capture the pre-state and block roots before mutating the state, so the + // committed public values bind the proof to this specific transition. + let pre_state_root = state.hash_tree_root(); + let block_root = block.hash_tree_root(); + + state_transition(&mut state, &block).expect("state transition failed"); + + let public_values = StfPublicValues { + pre_state_root, + block_root, + // state_transition already asserts this equals block.state_root. + post_state_root: state.hash_tree_root(), + }; + sp1_zkvm::io::commit(&public_values); +} diff --git a/crates/prover/core/Cargo.toml b/crates/prover/core/Cargo.toml new file mode 100644 index 00000000..a8f3187d --- /dev/null +++ b/crates/prover/core/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "ethlambda-prover-core" +authors.workspace = true +edition.workspace = true +keywords.workspace = true +license.workspace = true +readme.workspace = true +repository.workspace = true +rust-version.workspace = true +version.workspace = true + +[dependencies] +ethlambda-types.workspace = true +thiserror.workspace = true + diff --git a/crates/prover/core/src/lib.rs b/crates/prover/core/src/lib.rs new file mode 100644 index 00000000..e0a7518c --- /dev/null +++ b/crates/prover/core/src/lib.rs @@ -0,0 +1,55 @@ +pub use ethlambda_types::stf::{StfInput, StfPublicValues}; + +/// A serialized proof of a single state transition. +/// +/// The bytes depend upon the specific zkVM being used, (SP1ProofWithPublicValues) +/// and carry both the proof and the committed public values, so +/// [`StfProver::verify`] can recover the [`StfPublicValues`] without re-running +/// the transition. +#[derive(Debug, Clone)] +pub struct Proof(pub Vec); + +impl Proof { + /// Borrow the raw proof bytes. + pub fn as_bytes(&self) -> &[u8] { + &self.0 + } +} + +impl From> for Proof { + fn from(bytes: Vec) -> Self { + Self(bytes) + } +} + +/// Proves and verifies state-transition executions on a zkVM backend. +/// +/// The methods are `async` because real backends (SP1, RISC0, …) drive an +/// async prover client. This makes the trait non–object-safe, so consumers +/// select a backend by concrete type rather than `Box`. +/// +/// `async_fn_in_trait` is allowed deliberately: we don't constrain the returned +/// futures to `Send`, since backend prover clients don't all guarantee it. +#[allow(async_fn_in_trait)] +pub trait StfProver { + /// Prove that applying the input's block to its pre-state is a valid + /// transition, returning [`Proof`]. + async fn prove(&self, input: &StfInput) -> Result; + + /// Verify a proof and return the public values it commits to. + async fn verify(&self, proof: &Proof) -> Result; +} + +/// Errors raised while proving or verifying a state transition. +#[derive(Debug, thiserror::Error)] +pub enum ProverError { + /// The backend failed to produce a proof. + #[error("proving failed: {0}")] + Prove(String), + /// The proof did not verify, or verification could not run. + #[error("verification failed: {0}")] + Verify(String), + /// A proof or its public values could not be (de)serialized. + #[error("proof (de)serialization failed: {0}")] + Serialization(String), +} diff --git a/crates/prover/sp1/Cargo.toml b/crates/prover/sp1/Cargo.toml new file mode 100644 index 00000000..395223c3 --- /dev/null +++ b/crates/prover/sp1/Cargo.toml @@ -0,0 +1,20 @@ +# Standalone workspace: this crate is excluded from the root workspace because +# its build script needs the SP1 (`cargo prove`) toolchain. +[workspace] + +[package] +name = "ethlambda-prover-sp1" +version = "0.1.0" +edition = "2024" +publish = false + +[dependencies] +sp1-sdk = "6.3.1" +ethlambda-prover-core = { path = "../core" } +ethlambda-types = { path = "../../common/types" } +# Serializes the whole SP1ProofWithPublicValues into `Proof` so verify can +# recover the committed public values. +bincode = "1.3" + +[build-dependencies] +sp1-build = "6.3.1" diff --git a/crates/prover/sp1/build.rs b/crates/prover/sp1/build.rs new file mode 100644 index 00000000..9952bcb9 --- /dev/null +++ b/crates/prover/sp1/build.rs @@ -0,0 +1,5 @@ +fn main() { + // Compile the RISC-V guest program so `include_elf!("zkvm_guest_sp1")` can + // embed its ELF. Path is relative to this crate's manifest directory. + sp1_build::build_program("../../guest-program/sp1"); +} diff --git a/crates/prover/sp1/src/lib.rs b/crates/prover/sp1/src/lib.rs new file mode 100644 index 00000000..cb8cc688 --- /dev/null +++ b/crates/prover/sp1/src/lib.rs @@ -0,0 +1,67 @@ +use ethlambda_prover_core::{Proof, ProverError, StfInput, StfProver, StfPublicValues}; +use sp1_sdk::{ + MockProver, Prover, SP1ProofWithPublicValues, SP1ProvingKey, SP1Stdin, SP1VerifyingKey, + include_elf, +}; + + +const STATE_TRANSITION_ELF: &[u8] = include_elf!("zkvm_guest_sp1"); +const CYCLE_LIMIT: u64 = 10_000_000; + +/// SP1 prover: proves the STF guest and verifies its proofs. +/// +/// The proving/verifying keys are derived once via [`Sp1Prover::new`] because +/// `setup` is expensive and must not run per proof. +/// [TODO!]: check if using Lazylock might be better +pub struct Sp1Prover { + client: MockProver, + pk: SP1ProvingKey, + vk: SP1VerifyingKey, +} + +impl Sp1Prover { + /// Build the prover once, caching the proving/verifying keys and + /// currently uses the MockProver. + pub async fn new() -> Self { + let client = MockProver::new().await; + // let client = ProverClient::builder().cpu().await; + let (pk, vk) = client.setup(STATE_TRANSITION_ELF).await; + Self { client, pk, vk } + } +} + +impl StfProver for Sp1Prover { + async fn prove(&self, input: &StfInput) -> Result { + + let mut stdin = SP1Stdin::new(); + stdin.write(input); + + // use `.groth16()` instead of `.compressed()` for real proving and verification + let proof = self + .client + .prove(&self.pk, stdin) + .compressed() + .cycle_limit(CYCLE_LIMIT) + .await + .map_err(|err| ProverError::Prove(err.to_string()))?; + + // Store the whole proof (including public values) so `verify` can + // recover the committed `StfPublicValues`. + let bytes = + bincode::serialize(&proof).map_err(|err| ProverError::Serialization(err.to_string()))?; + Ok(Proof(bytes)) + } + + async fn verify(&self, proof: &Proof) -> Result { + let mut sp1_proof: SP1ProofWithPublicValues = bincode::deserialize(proof.as_bytes()) + .map_err(|err| ProverError::Serialization(err.to_string()))?; + + self.client + .verify(&sp1_proof, &self.vk) + .await + .map_err(|err| ProverError::Verify(err.to_string()))?; + + // The guest committed `StfPublicValues` via `io::commit`; read it back. + Ok(sp1_proof.public_values.read::()) + } +}