diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f0666ac04f..489cc91052 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,17 +1,17 @@ name: Main on: - pull_request: - branches: - - main - paths: - - "cli/**" - - "core/**" - - "prover/**" - - "recursion/**" - - "derive/**" - - "sdk/**" - - ".github/workflows/**" + pull_request: + branches: + - main + paths: + - "cli/**" + - "core/**" + - "prover/**" + - "recursion/**" + - "derive/**" + - "sdk/**" + - ".github/workflows/**" concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index bb521b4997..3056dae375 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -2,7 +2,7 @@ name: PR on: push: - branches: [main] + branches: [main, dev] pull_request: branches: - "**" @@ -132,7 +132,7 @@ jobs: - name: Install SP1 CLI run: | cd cli - cargo install --locked --path . + cargo install --force --locked --path . cd ~ - name: Run cargo check @@ -161,7 +161,7 @@ jobs: - name: Install SP1 CLI run: | cd cli - cargo install --locked --path . + cargo install --force --locked --path . cd ~ - name: Run cargo prove new diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 51700e94f4..943a7d2743 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,7 +14,7 @@ env: jobs: prepare: name: Prepare release - runs-on: runner=64cpu-linux-arm64 + runs-on: runs-on,runner=8cpu-linux-x64 timeout-minutes: 30 outputs: tag_name: ${{ steps.release_info.outputs.tag_name }} @@ -30,16 +30,6 @@ jobs: with: pull_token: ${{ secrets.PULL_TOKEN }} - - name: Run cargo test - uses: actions-rs/cargo@v1 - with: - command: test - toolchain: nightly-2024-04-17 - args: -p sp1-sdk --release -- --nocapture - env: - RUSTFLAGS: -Copt-level=3 -Cdebug-assertions -Coverflow-checks=y -Cdebuginfo=0 -C target-cpu=native - RUST_BACKTRACE: 1 - - name: Compute release name and tag id: release_info run: | diff --git a/Cargo.lock b/Cargo.lock index de99543221..55de795a21 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -725,9 +725,9 @@ checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "axum" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1236b4b292f6c4d6dc34604bb5120d85c3fe1d1aa596bd5cc52ca054d13e7b9e" +checksum = "3a6c9af12842a67734c9a2e355436e5d03b22383ed60cf13cd0c18fbfe3dcbcf" dependencies = [ "async-trait", "axum-core", @@ -749,7 +749,7 @@ dependencies = [ "serde_json", "serde_path_to_error", "serde_urlencoded", - "sync_wrapper", + "sync_wrapper 1.0.1", "tokio", "tower", "tower-layer", @@ -772,7 +772,7 @@ dependencies = [ "mime", "pin-project-lite", "rustversion", - "sync_wrapper", + "sync_wrapper 0.1.2", "tower-layer", "tower-service", "tracing", @@ -827,6 +827,29 @@ dependencies = [ "serde", ] +[[package]] +name = "bindgen" +version = "0.69.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0" +dependencies = [ + "bitflags 2.5.0", + "cexpr", + "clang-sys", + "itertools 0.12.1", + "lazy_static", + "lazycell", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 2.0.61", + "which", +] + [[package]] name = "bit-set" version = "0.5.3" @@ -1029,6 +1052,15 @@ dependencies = [ "once_cell", ] +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -1075,6 +1107,17 @@ dependencies = [ "half", ] +[[package]] +name = "clang-sys" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67523a3b4be3ce1989d607a828d036249522dd9c1c8de7f4dd2dae43a37369d1" +dependencies = [ + "glob", + "libc", + "libloading", +] + [[package]] name = "clap" version = "4.5.4" @@ -1493,33 +1536,13 @@ dependencies = [ "subtle", ] -[[package]] -name = "dirs" -version = "4.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059" -dependencies = [ - "dirs-sys 0.3.7", -] - [[package]] name = "dirs" version = "5.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" dependencies = [ - "dirs-sys 0.4.1", -] - -[[package]] -name = "dirs-sys" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" -dependencies = [ - "libc", - "redox_users", - "winapi", + "dirs-sys", ] [[package]] @@ -2085,6 +2108,15 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "http" version = "0.2.12" @@ -2489,6 +2521,12 @@ dependencies = [ "spin 0.5.2", ] +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + [[package]] name = "libc" version = "0.2.154" @@ -2507,6 +2545,16 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "libloading" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" +dependencies = [ + "cfg-if", + "windows-targets 0.52.5", +] + [[package]] name = "libm" version = "0.2.8" @@ -2599,6 +2647,12 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.7.2" @@ -2643,6 +2697,16 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -3326,6 +3390,16 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "prettyplease" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" +dependencies = [ + "proc-macro2", + "syn 2.0.61", +] + [[package]] name = "primitive-types" version = "0.12.2" @@ -3631,7 +3705,7 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", - "sync_wrapper", + "sync_wrapper 0.1.2", "system-configuration", "tokio", "tokio-rustls 0.24.1", @@ -3678,7 +3752,7 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", - "sync_wrapper", + "sync_wrapper 0.1.2", "system-configuration", "tokio", "tokio-native-tls", @@ -3790,6 +3864,12 @@ version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustc-hex" version = "2.1.0" @@ -4214,6 +4294,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde" +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signal-hook-registry" version = "1.4.2" @@ -4283,7 +4369,7 @@ dependencies = [ "cargo_metadata", "clap", "dialoguer", - "dirs 4.0.0", + "dirs", "downloader", "flate2", "futures-util", @@ -4325,6 +4411,7 @@ dependencies = [ "log", "nohash-hasher", "num", + "num-bigint 0.4.5", "num_cpus", "p3-air", "p3-baby-bear", @@ -4357,6 +4444,7 @@ dependencies = [ "strum", "strum_macros", "tempfile", + "thiserror", "tiny-keccak", "tracing", "tracing-forest", @@ -4429,11 +4517,12 @@ dependencies = [ "backtrace", "bincode", "clap", - "dirs 5.0.1", + "dirs", "futures", "hex", "indicatif", "itertools 0.12.1", + "num-bigint 0.4.5", "p3-baby-bear", "p3-bn254-fr", "p3-challenger", @@ -4455,6 +4544,7 @@ dependencies = [ "sp1-recursion-program", "subtle-encoding", "tempfile", + "thiserror", "tokio", "tracing", "tracing-appender", @@ -4567,8 +4657,12 @@ dependencies = [ name = "sp1-recursion-gnark-ffi" version = "0.1.0" dependencies = [ + "bindgen", + "cc", "crossbeam", "log", + "num-bigint 0.4.5", + "p3-baby-bear", "p3-field", "rand", "reqwest 0.12.4", @@ -4615,12 +4709,13 @@ dependencies = [ "async-trait", "axum", "bincode", - "dirs 5.0.1", + "dirs", "dotenv", "futures", "hex", "indicatif", "log", + "num-bigint 0.4.5", "p3-commit", "p3-field", "p3-matrix", @@ -4773,6 +4868,12 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" +[[package]] +name = "sync_wrapper" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" + [[package]] name = "system-configuration" version = "0.5.1" @@ -5451,6 +5552,18 @@ dependencies = [ "rustls-pki-types", ] +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix", +] + [[package]] name = "winapi" version = "0.3.9" diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 9d70f760d4..05ccf8cd93 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -29,7 +29,7 @@ indicatif = "0.17.8" tokio = { version = "1", features = ["full"] } tar = "0.4" flate2 = "1.0" -dirs = "4.0" +dirs = "5.0" serde = { version = "1", features = ["derive"] } rand = "0.8" downloader = { version = "0.2", default-features = false, features = [ diff --git a/cli/src/assets/program/Cargo.toml b/cli/src/assets/program/Cargo.toml index 4662efd449..793baed10a 100644 --- a/cli/src/assets/program/Cargo.toml +++ b/cli/src/assets/program/Cargo.toml @@ -5,4 +5,4 @@ name = "unnamed-program" edition = "2021" [dependencies] -sp1-zkvm = { git = "https://github.com/succinctlabs/sp1.git", branch = "main" } \ No newline at end of file +sp1-zkvm = { git = "https://github.com/succinctlabs/sp1.git", branch = "main" } diff --git a/cli/src/assets/script/Cargo.toml b/cli/src/assets/script/Cargo.toml index 283777968f..8efd51b9ab 100644 --- a/cli/src/assets/script/Cargo.toml +++ b/cli/src/assets/script/Cargo.toml @@ -8,4 +8,4 @@ edition = "2021" sp1-sdk = { git = "https://github.com/succinctlabs/sp1.git", branch = "main" } [build-dependencies] -sp1-helper = { git = "https://github.com/succinctlabs/sp1.git", branch = "main" } \ No newline at end of file +sp1-helper = { git = "https://github.com/succinctlabs/sp1.git", branch = "main" } diff --git a/core/Cargo.toml b/core/Cargo.toml index e477ec2639..60f98dc7cb 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -58,6 +58,8 @@ strum = "0.26" web-time = "1.1.0" rayon-scan = "0.1.1" serial_test = "3.1.1" +thiserror = "1.0.60" +num-bigint = { version = "0.4.3", default-features = false } [dev-dependencies] tiny-keccak = { version = "2.0.2", features = ["keccak"] } diff --git a/core/benches/main.rs b/core/benches/main.rs index 86e6083448..ca63a750fc 100644 --- a/core/benches/main.rs +++ b/core/benches/main.rs @@ -1,7 +1,7 @@ use criterion::{black_box, criterion_group, criterion_main, Criterion}; use sp1_core::io::SP1Stdin; use sp1_core::runtime::{Program, Runtime}; -use sp1_core::utils::{run_and_prove, BabyBearPoseidon2}; +use sp1_core::utils::{prove, BabyBearPoseidon2}; #[allow(unreachable_code)] pub fn criterion_benchmark(c: &mut Criterion) { @@ -13,14 +13,14 @@ pub fn criterion_benchmark(c: &mut Criterion) { let program = Program::from_elf(&elf_path); let cycles = { let mut runtime = Runtime::new(program.clone()); - runtime.run(); + runtime.run().unwrap(); runtime.state.global_clk }; group.bench_function( format!("main:{}:{}", p.split('/').last().unwrap(), cycles), |b| { b.iter(|| { - run_and_prove( + prove( black_box(program.clone()), &SP1Stdin::new(), BabyBearPoseidon2::new(), diff --git a/core/src/cpu/trace.rs b/core/src/cpu/trace.rs index e5d5b8320d..7000fe0624 100644 --- a/core/src/cpu/trace.rs +++ b/core/src/cpu/trace.rs @@ -662,7 +662,7 @@ mod tests { fn generate_trace_simple_program() { let program = simple_program(); let mut runtime = Runtime::new(program); - runtime.run(); + runtime.run().unwrap(); let chip = CpuChip::default(); let trace: RowMajorMatrix = chip.generate_trace(&runtime.record, &mut ExecutionRecord::default()); diff --git a/core/src/io.rs b/core/src/io.rs index 28698d1e69..cfc461e724 100644 --- a/core/src/io.rs +++ b/core/src/io.rs @@ -2,6 +2,8 @@ use crate::{ stark::{ShardProof, StarkVerifyingKey}, utils::{BabyBearPoseidon2, Buffer}, }; +use k256::sha2::{Digest, Sha256}; +use num_bigint::BigUint; use serde::{de::DeserializeOwned, Deserialize, Serialize}; /// Standard input for the prover. @@ -127,6 +129,26 @@ impl SP1PublicValues { pub fn write_slice(&mut self, slice: &[u8]) { self.buffer.write_slice(slice); } + + /// Hash the public values, mask the top 3 bits and return a BigUint. Matches the implementation + /// of `hashPublicValues` in the Solidity verifier. + /// + /// ```solidity + /// sha256(publicValues) & bytes32(uint256((1 << 253) - 1)); + /// ``` + pub fn hash(&self) -> BigUint { + // Hash the public values. + let mut hasher = Sha256::new(); + hasher.update(self.buffer.data.as_slice()); + let hash_result = hasher.finalize(); + let mut hash = hash_result.to_vec(); + + // Mask the top 3 bits. + hash[0] &= 0b00011111; + + // Return the masked hash as a BigUint. + BigUint::from_bytes_be(&hash) + } } impl AsRef<[u8]> for SP1PublicValues { @@ -172,3 +194,23 @@ pub mod proof_serde { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_hash_public_values() { + let test_hex = "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"; + let test_bytes = hex::decode(test_hex).unwrap(); + + let mut public_values = SP1PublicValues::new(); + public_values.write_slice(&test_bytes); + let hash = public_values.hash(); + + let expected_hash = "1ce987d0a7fcc2636fe87e69295ba12b1cc46c256b369ae7401c51b805ee91bd"; + let expected_hash_biguint = BigUint::from_bytes_be(&hex::decode(expected_hash).unwrap()); + + assert_eq!(hash, expected_hash_biguint); + } +} diff --git a/core/src/lookup/debug.rs b/core/src/lookup/debug.rs index d9d1dd241f..c58d882b7e 100644 --- a/core/src/lookup/debug.rs +++ b/core/src/lookup/debug.rs @@ -223,7 +223,7 @@ mod test { let machine = RiscvAir::machine(config); let (pk, _) = machine.setup(&program); let mut runtime = Runtime::new(program); - runtime.run(); + runtime.run().unwrap(); let shards = machine.shard(runtime.record, &ShardingConfig::default()); let ok = debug_interactions_with_all_chips(&machine, &pk, &shards, InteractionKind::all_kinds()); diff --git a/core/src/memory/global.rs b/core/src/memory/global.rs index e5fa81d378..5c9974e09f 100644 --- a/core/src/memory/global.rs +++ b/core/src/memory/global.rs @@ -185,7 +185,7 @@ mod tests { fn test_memory_generate_trace() { let program = simple_program(); let mut runtime = Runtime::new(program); - runtime.run(); + runtime.run().unwrap(); let shard = runtime.record.clone(); let chip: MemoryChip = MemoryChip::new(MemoryChipType::Initialize); @@ -211,7 +211,7 @@ mod tests { let program = simple_program(); let mut runtime = Runtime::new(program); - runtime.run(); + runtime.run().unwrap(); let chip = MemoryChip::new(MemoryChipType::Initialize); @@ -229,7 +229,7 @@ mod tests { let program = sha_extend_program(); let program_clone = program.clone(); let mut runtime = Runtime::new(program); - runtime.run(); + runtime.run().unwrap(); let machine: crate::stark::StarkMachine> = RiscvAir::machine(BabyBearPoseidon2::new()); let (pkey, _) = machine.setup(&program_clone); @@ -252,7 +252,7 @@ mod tests { let program = sha_extend_program(); let program_clone = program.clone(); let mut runtime = Runtime::new(program); - runtime.run(); + runtime.run().unwrap(); let machine = RiscvAir::machine(BabyBearPoseidon2::new()); let (pkey, _) = machine.setup(&program_clone); let shards = machine.shard( diff --git a/core/src/runtime/io.rs b/core/src/runtime/io.rs index 14ba344501..0be9da0e14 100644 --- a/core/src/runtime/io.rs +++ b/core/src/runtime/io.rs @@ -60,7 +60,7 @@ pub mod tests { use super::*; use crate::runtime::Program; use crate::utils::tests::IO_ELF; - use crate::utils::{self, prove_core, BabyBearBlake3}; + use crate::utils::{self, prove_simple, BabyBearBlake3}; use serde::Deserialize; #[derive(Serialize, Deserialize, Debug, PartialEq)] @@ -93,7 +93,7 @@ pub mod tests { let points = points(); runtime.write_stdin(&points.0); runtime.write_stdin(&points.1); - runtime.run(); + runtime.run().unwrap(); let added_point = runtime.read_public_values::(); assert_eq!( added_point, @@ -113,8 +113,8 @@ pub mod tests { let points = points(); runtime.write_stdin(&points.0); runtime.write_stdin(&points.1); - runtime.run(); + runtime.run().unwrap(); let config = BabyBearBlake3::new(); - prove_core(config, runtime); + prove_simple(config, runtime).unwrap(); } } diff --git a/core/src/runtime/mod.rs b/core/src/runtime/mod.rs index d5e9363285..09b21a88aa 100644 --- a/core/src/runtime/mod.rs +++ b/core/src/runtime/mod.rs @@ -25,16 +25,15 @@ use std::collections::HashMap; use std::fs::File; use std::io::BufWriter; use std::io::Write; -use std::rc::Rc; use std::sync::Arc; +use thiserror::Error; + use crate::memory::MemoryInitializeFinalizeEvent; use crate::utils::env; use crate::{alu::AluEvent, cpu::CpuEvent}; -pub const MAX_SHARD_CLK: usize = (1 << 24) - 1; - -/// An implementation of a runtime for the SP1 VM. +/// An implementation of a runtime for the SP1 RISC-V zkVM. /// /// The runtime is responsible for executing a user program and tracing important events which occur /// during execution (i.e., memory reads, alu operations, etc). @@ -68,23 +67,35 @@ pub struct Runtime { /// A buffer for writing trace events to a file. pub trace_buf: Option>, - /// Whether the runtime should fail on panic or not. - pub fail_on_panic: bool, - /// Whether the runtime is in constrained mode or not. + /// /// In unconstrained mode, any events, clock, register, or memory changes are reset after leaving /// the unconstrained block. The only thing preserved is writes to the input stream. pub unconstrained: bool, pub(crate) unconstrained_state: ForkState, - pub syscall_map: HashMap>, + pub syscall_map: HashMap>, pub max_syscall_cycles: u32, pub emit_events: bool, } +#[derive(Error, Debug)] +pub enum ExecutionError { + #[error("execution failed with exit code {0}")] + HaltWithNonZeroExitCode(u32), + #[error("invalid memory access for opcode {0} and address {1}")] + InvalidMemoryAccess(Opcode, u32), + #[error("unimplemented syscall {0}")] + UnsupportedSyscall(u32), + #[error("breakpoint encountered")] + Breakpoint(), + #[error("got unimplemented as opcode")] + Unimplemented(), +} + impl Runtime { // Create a new runtime from a program. pub fn new(program: Program) -> Self { @@ -105,8 +116,8 @@ impl Runtime { None }; - let syscall_map = default_syscall_map(); // Determine the maximum number of cycles for any syscall. + let syscall_map = default_syscall_map(); let max_syscall_cycles = syscall_map .values() .map(|syscall| syscall.num_extra_cycles()) @@ -114,18 +125,16 @@ impl Runtime { .unwrap_or(0); let shard_size = env::shard_size() as u32; - Self { record, state: ExecutionState::new(program.pc_start), program, memory_accesses: MemoryAccessRecord::default(), shard_size: shard_size * 4, - shard_batch_size: env::shard_batch_size() as u32 * shard_size, + shard_batch_size: env::shard_batch_size() as u32, cycle_tracker: HashMap::new(), io_buf: HashMap::new(), trace_buf, - fail_on_panic: true, unconstrained: false, unconstrained_state: ForkState::default(), syscall_map, @@ -215,6 +224,7 @@ impl Runtime { Entry::Vacant(entry) => { // If addr has a specific value to be initialized with, use that, otherwise 0. let value = self.state.uninitialized_memory.remove(&addr).unwrap_or(0); + // Do not emit memory initialize events for address 0 as that is done in initialize. if addr != 0 { self.record @@ -262,6 +272,7 @@ impl Runtime { Entry::Vacant(entry) => { // If addr has a specific value to be initialized with, use that, otherwise 0. let value = self.state.uninitialized_memory.remove(&addr).unwrap_or(0); + // Do not emit memory initialize events for address 0 as that is done in initialize. if addr != 0 { self.record @@ -502,7 +513,7 @@ impl Runtime { } /// Execute the given instruction over the current state of the runtime. - fn execute_instruction(&mut self, instruction: Instruction) { + fn execute_instruction(&mut self, instruction: Instruction) -> Result<(), ExecutionError> { let mut pc = self.state.pc; let mut clk = self.state.clk; let mut exit_code = 0u32; @@ -578,7 +589,9 @@ impl Runtime { } Opcode::LH => { (rd, b, c, addr, memory_read_value) = self.load_rr(instruction); - assert_eq!(addr % 2, 0, "addr is not aligned"); + if addr % 2 != 0 { + return Err(ExecutionError::InvalidMemoryAccess(Opcode::LH, addr)); + } let value = match (addr >> 1) % 2 { 0 => memory_read_value & 0x0000FFFF, 1 => (memory_read_value & 0xFFFF0000) >> 16, @@ -590,7 +603,9 @@ impl Runtime { } Opcode::LW => { (rd, b, c, addr, memory_read_value) = self.load_rr(instruction); - assert_eq!(addr % 4, 0, "addr is not aligned"); + if addr % 4 != 0 { + return Err(ExecutionError::InvalidMemoryAccess(Opcode::LW, addr)); + } a = memory_read_value; memory_store_value = Some(memory_read_value); self.rw(rd, a); @@ -604,7 +619,9 @@ impl Runtime { } Opcode::LHU => { (rd, b, c, addr, memory_read_value) = self.load_rr(instruction); - assert_eq!(addr % 2, 0, "addr is not aligned"); + if addr % 2 != 0 { + return Err(ExecutionError::InvalidMemoryAccess(Opcode::LHU, addr)); + } let value = match (addr >> 1) % 2 { 0 => memory_read_value & 0x0000FFFF, 1 => (memory_read_value & 0xFFFF0000) >> 16, @@ -630,7 +647,9 @@ impl Runtime { } Opcode::SH => { (a, b, c, addr, memory_read_value) = self.store_rr(instruction); - assert_eq!(addr % 2, 0, "addr is not aligned"); + if addr % 2 != 0 { + return Err(ExecutionError::InvalidMemoryAccess(Opcode::SH, addr)); + } let value = match (addr >> 1) % 2 { 0 => (a & 0x0000FFFF) + (memory_read_value & 0xFFFF0000), 1 => ((a & 0x0000FFFF) << 16) + (memory_read_value & 0x0000FFFF), @@ -641,7 +660,9 @@ impl Runtime { } Opcode::SW => { (a, b, c, addr, _) = self.store_rr(instruction); - assert_eq!(addr % 4, 0, "addr is not aligned"); + if addr % 4 != 0 { + return Err(ExecutionError::InvalidMemoryAccess(Opcode::SW, addr)); + } let value = a; memory_store_value = Some(value); self.mw_cpu(align(addr), value, MemoryAccessPosition::Memory); @@ -711,9 +732,9 @@ impl Runtime { // System instructions. Opcode::ECALL => { + // We peek at register x5 to get the syscall id. The reason we don't `self.rr` this + // register is that we write to it later. let t0 = Register::X5; - // We peek at register x5 to get the syscall id. The reason we don't `self.rr` this register - // is that we write to it later. let syscall_id = self.register(t0); c = self.rr(Register::X11, MemoryAccessPosition::C); b = self.rr(Register::X10, MemoryAccessPosition::B); @@ -721,7 +742,6 @@ impl Runtime { let syscall_impl = self.get_syscall(syscall).cloned(); let mut precompile_rt = SyscallContext::new(self); - let (precompile_next_pc, precompile_cycles, returned_exit_code) = if let Some(syscall_impl) = syscall_impl { // Executing a syscall optionally returns a value to write to the t0 register. @@ -730,16 +750,23 @@ impl Runtime { if let Some(val) = res { a = val; } else { - // Default to syscall_id if no value is returned from syscall execution. a = syscall_id; } + + // If the syscall is `HALT` and the exit code is non-zero, return an error. + if syscall == SyscallCode::HALT && precompile_rt.exit_code != 0 { + return Err(ExecutionError::HaltWithNonZeroExitCode( + precompile_rt.exit_code, + )); + } + ( precompile_rt.next_pc, syscall_impl.num_extra_cycles(), precompile_rt.exit_code, ) } else { - panic!("Unsupported syscall: {:?}", syscall); + return Err(ExecutionError::UnsupportedSyscall(syscall_id)); }; // Allow the syscall impl to modify state.clk/pc (exit unconstrained does this) @@ -751,9 +778,8 @@ impl Runtime { self.state.clk += precompile_cycles; exit_code = returned_exit_code; } - Opcode::EBREAK => { - todo!() + return Err(ExecutionError::Breakpoint()); } // Multiply instructions. @@ -814,14 +840,15 @@ impl Runtime { self.alu_rw(instruction, rd, a, b, c); } + // See https://github.com/riscv-non-isa/riscv-asm-manual/blob/master/riscv-asm.md#instruction-aliases Opcode::UNIMP => { - // See https://github.com/riscv-non-isa/riscv-asm-manual/blob/master/riscv-asm.md#instruction-aliases - panic!("UNIMP encountered, we should never get here."); + return Err(ExecutionError::Unimplemented()); } } // Update the program counter. self.state.pc = next_pc; + // Update the clk to the next cycle. self.state.clk += 4; @@ -840,12 +867,14 @@ impl Runtime { self.memory_accesses, exit_code, ); - } + }; + + Ok(()) } /// Executes one cycle of the program, returning whether the program has finished. #[inline] - fn execute_cycle(&mut self) -> bool { + fn execute_cycle(&mut self) -> Result { // Fetch the instruction at the current program counter. let instruction = self.fetch(); @@ -853,7 +882,7 @@ impl Runtime { self.log(&instruction); // Execute the instruction. - self.execute_instruction(instruction); + self.execute_instruction(instruction)?; // Increment the clock. self.state.global_clk += 1; @@ -865,23 +894,23 @@ impl Runtime { self.state.clk = 0; } - self.state.pc.wrapping_sub(self.program.pc_base) - >= (self.program.instructions.len() * 4) as u32 + Ok(self.state.pc.wrapping_sub(self.program.pc_base) + >= (self.program.instructions.len() * 4) as u32) } /// Execute up to `self.shard_batch_size` cycles, returning the events emitted and whether the program ended. - pub fn execute_record(&mut self) -> (ExecutionRecord, bool) { + pub fn execute_record(&mut self) -> Result<(ExecutionRecord, bool), ExecutionError> { self.emit_events = true; - let done = self.execute(); - (std::mem::take(&mut self.record), done) + let done = self.execute()?; + Ok((std::mem::take(&mut self.record), done)) } /// Execute up to `self.shard_batch_size` cycles, returning a copy of the prestate and whether the program ended. - pub fn execute_state(&mut self) -> (ExecutionState, bool) { + pub fn execute_state(&mut self) -> Result<(ExecutionState, bool), ExecutionError> { self.emit_events = false; let state = self.state.clone(); - let done = self.execute(); - (state, done) + let done = self.execute()?; + Ok((state, done)) } fn initialize(&mut self) { @@ -907,13 +936,25 @@ impl Runtime { tracing::info!("starting execution"); } - pub fn run(&mut self) { + pub fn run_untraced(&mut self) -> Result<(), ExecutionError> { + self.emit_events = false; + while !self.execute()? {} + Ok(()) + } + + pub fn run(&mut self) -> Result<(), ExecutionError> { self.emit_events = true; - while !self.execute() {} + while !self.execute()? {} + Ok(()) + } + + pub fn dry_run(&mut self) { + self.emit_events = false; + while !self.execute().unwrap() {} } /// Executes up to `self.shard_batch_size` cycles of the program, returning whether the program has finished. - fn execute(&mut self) -> bool { + fn execute(&mut self) -> Result { // If it's the first cycle, initialize the program. if self.state.global_clk == 0 { self.initialize(); @@ -924,15 +965,15 @@ impl Runtime { let mut current_shard = self.state.current_shard; let mut num_shards_executed = 0; loop { - if self.execute_cycle() { + if self.execute_cycle()? { done = true; break; } - if env::shard_batch_size() > 0 && current_shard != self.state.current_shard { + if self.shard_batch_size > 0 && current_shard != self.state.current_shard { num_shards_executed += 1; current_shard = self.state.current_shard; - if num_shards_executed == env::shard_batch_size() { + if num_shards_executed == self.shard_batch_size { break; } } @@ -942,7 +983,7 @@ impl Runtime { self.postprocess(); } - done + Ok(done) } fn postprocess(&mut self) { @@ -1004,7 +1045,7 @@ impl Runtime { } } - fn get_syscall(&mut self, code: SyscallCode) -> Option<&Rc> { + fn get_syscall(&mut self, code: SyscallCode) -> Option<&Arc> { self.syscall_map.get(&code) } } @@ -1014,7 +1055,7 @@ pub mod tests { use crate::{ runtime::Register, - utils::tests::{FIBONACCI_ELF, SSZ_WITHDRAWALS_ELF}, + utils::tests::{FIBONACCI_ELF, PANIC_ELF, SSZ_WITHDRAWALS_ELF}, }; use super::{Instruction, Opcode, Program, Runtime}; @@ -1036,14 +1077,26 @@ pub mod tests { Program::from(SSZ_WITHDRAWALS_ELF) } + pub fn panic_program() -> Program { + Program::from(PANIC_ELF) + } + #[test] fn test_simple_program_run() { let program = simple_program(); let mut runtime = Runtime::new(program); - runtime.run(); + runtime.run().unwrap(); assert_eq!(runtime.register(Register::X31), 42); } + #[test] + #[should_panic] + fn test_panic() { + let program = panic_program(); + let mut runtime = Runtime::new(program); + runtime.run().unwrap(); + } + #[test] fn test_add() { // main: @@ -1057,7 +1110,7 @@ pub mod tests { ]; let program = Program::new(instructions, 0, 0); let mut runtime = Runtime::new(program); - runtime.run(); + runtime.run().unwrap(); assert_eq!(runtime.register(Register::X31), 42); } @@ -1074,7 +1127,7 @@ pub mod tests { let program = Program::new(instructions, 0, 0); let mut runtime = Runtime::new(program); - runtime.run(); + runtime.run().unwrap(); assert_eq!(runtime.register(Register::X31), 32); } @@ -1091,7 +1144,7 @@ pub mod tests { let program = Program::new(instructions, 0, 0); let mut runtime = Runtime::new(program); - runtime.run(); + runtime.run().unwrap(); assert_eq!(runtime.register(Register::X31), 32); } @@ -1109,7 +1162,7 @@ pub mod tests { let mut runtime = Runtime::new(program); - runtime.run(); + runtime.run().unwrap(); assert_eq!(runtime.register(Register::X31), 37); } @@ -1126,7 +1179,7 @@ pub mod tests { let program = Program::new(instructions, 0, 0); let mut runtime = Runtime::new(program); - runtime.run(); + runtime.run().unwrap(); assert_eq!(runtime.register(Register::X31), 5); } @@ -1143,7 +1196,7 @@ pub mod tests { let program = Program::new(instructions, 0, 0); let mut runtime = Runtime::new(program); - runtime.run(); + runtime.run().unwrap(); assert_eq!(runtime.register(Register::X31), 1184); } @@ -1160,7 +1213,7 @@ pub mod tests { let program = Program::new(instructions, 0, 0); let mut runtime = Runtime::new(program); - runtime.run(); + runtime.run().unwrap(); assert_eq!(runtime.register(Register::X31), 1); } @@ -1177,7 +1230,7 @@ pub mod tests { let program = Program::new(instructions, 0, 0); let mut runtime = Runtime::new(program); - runtime.run(); + runtime.run().unwrap(); assert_eq!(runtime.register(Register::X31), 1); } @@ -1194,7 +1247,7 @@ pub mod tests { let program = Program::new(instructions, 0, 0); let mut runtime = Runtime::new(program); - runtime.run(); + runtime.run().unwrap(); assert_eq!(runtime.register(Register::X31), 0); } @@ -1211,7 +1264,7 @@ pub mod tests { let program = Program::new(instructions, 0, 0); let mut runtime = Runtime::new(program); - runtime.run(); + runtime.run().unwrap(); assert_eq!(runtime.register(Register::X31), 0); } @@ -1228,7 +1281,7 @@ pub mod tests { let program = Program::new(instructions, 0, 0); let mut runtime = Runtime::new(program); - runtime.run(); + runtime.run().unwrap(); assert_eq!(runtime.register(Register::X31), 84); } @@ -1244,7 +1297,7 @@ pub mod tests { ]; let program = Program::new(instructions, 0, 0); let mut runtime = Runtime::new(program); - runtime.run(); + runtime.run().unwrap(); assert_eq!(runtime.register(Register::X31), 5 - 1 + 4); } @@ -1260,7 +1313,7 @@ pub mod tests { ]; let program = Program::new(instructions, 0, 0); let mut runtime = Runtime::new(program); - runtime.run(); + runtime.run().unwrap(); assert_eq!(runtime.register(Register::X31), 10); } @@ -1276,7 +1329,7 @@ pub mod tests { ]; let program = Program::new(instructions, 0, 0); let mut runtime = Runtime::new(program); - runtime.run(); + runtime.run().unwrap(); assert_eq!(runtime.register(Register::X31), 47); } @@ -1292,7 +1345,7 @@ pub mod tests { ]; let program = Program::new(instructions, 0, 0); let mut runtime = Runtime::new(program); - runtime.run(); + runtime.run().unwrap(); assert_eq!(runtime.register(Register::X31), 0); } @@ -1306,7 +1359,7 @@ pub mod tests { ]; let program = Program::new(instructions, 0, 0); let mut runtime = Runtime::new(program); - runtime.run(); + runtime.run().unwrap(); assert_eq!(runtime.register(Register::X31), 80); } @@ -1320,7 +1373,7 @@ pub mod tests { ]; let program = Program::new(instructions, 0, 0); let mut runtime = Runtime::new(program); - runtime.run(); + runtime.run().unwrap(); assert_eq!(runtime.register(Register::X31), 2); } @@ -1334,7 +1387,7 @@ pub mod tests { ]; let program = Program::new(instructions, 0, 0); let mut runtime = Runtime::new(program); - runtime.run(); + runtime.run().unwrap(); assert_eq!(runtime.register(Register::X31), 2); } @@ -1348,7 +1401,7 @@ pub mod tests { ]; let program = Program::new(instructions, 0, 0); let mut runtime = Runtime::new(program); - runtime.run(); + runtime.run().unwrap(); assert_eq!(runtime.register(Register::X31), 0); } @@ -1362,7 +1415,7 @@ pub mod tests { ]; let program = Program::new(instructions, 0, 0); let mut runtime = Runtime::new(program); - runtime.run(); + runtime.run().unwrap(); assert_eq!(runtime.register(Register::X31), 0); } @@ -1381,7 +1434,7 @@ pub mod tests { ]; let program = Program::new(instructions, 0, 0); let mut runtime = Runtime::new(program); - runtime.run(); + runtime.run().unwrap(); assert_eq!(runtime.registers()[Register::X5 as usize], 8); assert_eq!(runtime.registers()[Register::X11 as usize], 100); assert_eq!(runtime.state.pc, 108); @@ -1395,7 +1448,7 @@ pub mod tests { ]; let program = Program::new(instructions, 0, 0); let mut runtime = Runtime::new(program); - runtime.run(); + runtime.run().unwrap(); assert_eq!(runtime.registers()[Register::X12 as usize], expected); } @@ -1613,7 +1666,7 @@ pub mod tests { fn test_simple_memory_program_run() { let program = simple_memory_program(); let mut runtime = Runtime::new(program); - runtime.run(); + runtime.run().unwrap(); // Assert SW & LW case assert_eq!(runtime.register(Register::X28), 0x12348765); diff --git a/core/src/runtime/syscall.rs b/core/src/runtime/syscall.rs index 4e6304b429..6f89b374e8 100644 --- a/core/src/runtime/syscall.rs +++ b/core/src/runtime/syscall.rs @@ -1,5 +1,5 @@ use std::collections::HashMap; -use std::rc::Rc; +use std::sync::Arc; use strum_macros::EnumIter; @@ -149,7 +149,7 @@ impl SyscallCode { } } -pub trait Syscall { +pub trait Syscall: Send + Sync { /// Execute the syscall and return the resulting value of register a0. `arg1` and `arg2` are the /// values in registers X10 and X11, respectively. While not a hard requirement, the convention /// is that the return value is only for system calls such as `HALT`. Most precompiles use `arg1` @@ -256,86 +256,85 @@ impl<'a> SyscallContext<'a> { } } -pub fn default_syscall_map() -> HashMap> { - let mut syscall_map = HashMap::>::default(); - syscall_map.insert(SyscallCode::HALT, Rc::new(SyscallHalt {})); - syscall_map.insert(SyscallCode::SHA_EXTEND, Rc::new(ShaExtendChip::new())); - syscall_map.insert(SyscallCode::SHA_COMPRESS, Rc::new(ShaCompressChip::new())); +pub fn default_syscall_map() -> HashMap> { + let mut syscall_map = HashMap::>::default(); + syscall_map.insert(SyscallCode::HALT, Arc::new(SyscallHalt {})); + syscall_map.insert(SyscallCode::SHA_EXTEND, Arc::new(ShaExtendChip::new())); + syscall_map.insert(SyscallCode::SHA_COMPRESS, Arc::new(ShaCompressChip::new())); syscall_map.insert( SyscallCode::ED_ADD, - Rc::new(EdAddAssignChip::::new()), + Arc::new(EdAddAssignChip::::new()), ); syscall_map.insert( SyscallCode::ED_DECOMPRESS, - Rc::new(EdDecompressChip::::new()), + Arc::new(EdDecompressChip::::new()), ); syscall_map.insert( SyscallCode::KECCAK_PERMUTE, - Rc::new(KeccakPermuteChip::new()), + Arc::new(KeccakPermuteChip::new()), ); syscall_map.insert( SyscallCode::SECP256K1_ADD, - Rc::new(WeierstrassAddAssignChip::::new()), + Arc::new(WeierstrassAddAssignChip::::new()), ); syscall_map.insert( SyscallCode::SECP256K1_DOUBLE, - Rc::new(WeierstrassDoubleAssignChip::::new()), + Arc::new(WeierstrassDoubleAssignChip::::new()), ); - syscall_map.insert(SyscallCode::SHA_COMPRESS, Rc::new(ShaCompressChip::new())); syscall_map.insert( SyscallCode::SECP256K1_DECOMPRESS, - Rc::new(WeierstrassDecompressChip::::new()), + Arc::new(WeierstrassDecompressChip::::new()), ); syscall_map.insert( SyscallCode::BN254_ADD, - Rc::new(WeierstrassAddAssignChip::::new()), + Arc::new(WeierstrassAddAssignChip::::new()), ); syscall_map.insert( SyscallCode::BN254_DOUBLE, - Rc::new(WeierstrassDoubleAssignChip::::new()), + Arc::new(WeierstrassDoubleAssignChip::::new()), ); syscall_map.insert( SyscallCode::BLAKE3_COMPRESS_INNER, - Rc::new(Blake3CompressInnerChip::new()), + Arc::new(Blake3CompressInnerChip::new()), ); syscall_map.insert( SyscallCode::BLS12381_ADD, - Rc::new(WeierstrassAddAssignChip::::new()), + Arc::new(WeierstrassAddAssignChip::::new()), ); syscall_map.insert( SyscallCode::BLS12381_DOUBLE, - Rc::new(WeierstrassDoubleAssignChip::::new()), + Arc::new(WeierstrassDoubleAssignChip::::new()), ); syscall_map.insert( SyscallCode::BLAKE3_COMPRESS_INNER, - Rc::new(Blake3CompressInnerChip::new()), + Arc::new(Blake3CompressInnerChip::new()), ); - syscall_map.insert(SyscallCode::UINT256_MUL, Rc::new(Uint256MulChip::new())); + syscall_map.insert(SyscallCode::UINT256_MUL, Arc::new(Uint256MulChip::new())); syscall_map.insert( SyscallCode::ENTER_UNCONSTRAINED, - Rc::new(SyscallEnterUnconstrained::new()), + Arc::new(SyscallEnterUnconstrained::new()), ); syscall_map.insert( SyscallCode::EXIT_UNCONSTRAINED, - Rc::new(SyscallExitUnconstrained::new()), + Arc::new(SyscallExitUnconstrained::new()), ); - syscall_map.insert(SyscallCode::WRITE, Rc::new(SyscallWrite::new())); - syscall_map.insert(SyscallCode::COMMIT, Rc::new(SyscallCommit::new())); + syscall_map.insert(SyscallCode::WRITE, Arc::new(SyscallWrite::new())); + syscall_map.insert(SyscallCode::COMMIT, Arc::new(SyscallCommit::new())); syscall_map.insert( SyscallCode::COMMIT_DEFERRED_PROOFS, - Rc::new(SyscallCommitDeferred::new()), + Arc::new(SyscallCommitDeferred::new()), ); syscall_map.insert( SyscallCode::VERIFY_SP1_PROOF, - Rc::new(SyscallVerifySP1Proof::new()), + Arc::new(SyscallVerifySP1Proof::new()), ); - syscall_map.insert(SyscallCode::HINT_LEN, Rc::new(SyscallHintLen::new())); - syscall_map.insert(SyscallCode::HINT_READ, Rc::new(SyscallHintRead::new())); + syscall_map.insert(SyscallCode::HINT_LEN, Arc::new(SyscallHintLen::new())); + syscall_map.insert(SyscallCode::HINT_READ, Arc::new(SyscallHintRead::new())); syscall_map.insert( SyscallCode::BLS12381_DECOMPRESS, - Rc::new(WeierstrassDecompressChip::::new()), + Arc::new(WeierstrassDecompressChip::::new()), ); - syscall_map.insert(SyscallCode::UINT256_MUL, Rc::new(Uint256MulChip::new())); + syscall_map.insert(SyscallCode::UINT256_MUL, Arc::new(Uint256MulChip::new())); syscall_map } diff --git a/core/src/stark/machine.rs b/core/src/stark/machine.rs index 89638765f3..948ceca9ab 100644 --- a/core/src/stark/machine.rs +++ b/core/src/stark/machine.rs @@ -254,9 +254,7 @@ impl>> StarkMachine { // Display some statistics about the workload. let stats = record.stats(); - for (k, v) in stats { - log::info!("{} = {}", k, v); - } + log::info!("Shard: {:?}", stats); // For each chip, shard the events into segments. record.shard(config) @@ -520,7 +518,7 @@ pub mod tests { use crate::runtime::Opcode; use crate::runtime::Program; use crate::utils; - use crate::utils::run_and_prove; + use crate::utils::prove; use crate::utils::run_test; use crate::utils::setup_logger; use crate::utils::BabyBearPoseidon2; @@ -671,7 +669,7 @@ pub mod tests { setup_logger(); let program = fibonacci_program(); let stdin = SP1Stdin::new(); - run_and_prove(program, &stdin, BabyBearPoseidon2::new()); + prove(program, &stdin, BabyBearPoseidon2::new()).unwrap(); } #[test] diff --git a/core/src/stark/types.rs b/core/src/stark/types.rs index b1743d83db..d11e462b64 100644 --- a/core/src/stark/types.rs +++ b/core/src/stark/types.rs @@ -124,7 +124,7 @@ pub struct ShardOpenedValues { /// The maximum number of elements that can be stored in the public values vec. Both SP1 and recursive /// proofs need to pad their public_values vec to this length. This is required since the recursion /// verification program expects the public values vec to be fixed length. -pub const PROOF_MAX_NUM_PVS: usize = 232; +pub const PROOF_MAX_NUM_PVS: usize = 240; #[derive(Serialize, Deserialize, Clone)] #[serde(bound = "")] diff --git a/core/src/syscall/halt.rs b/core/src/syscall/halt.rs index 68e3a39f4d..9572beecb8 100644 --- a/core/src/syscall/halt.rs +++ b/core/src/syscall/halt.rs @@ -10,14 +10,6 @@ impl SyscallHalt { impl Syscall for SyscallHalt { fn execute(&self, ctx: &mut SyscallContext, exit_code: u32, _: u32) -> Option { - let rt = &mut ctx.rt; - - if rt.fail_on_panic && exit_code != 0 { - panic!( - "RISC-V runtime halted during program execution with non-zero exit code {}. This likely means your program panicked during execution.", - exit_code - ); - } ctx.set_next_pc(0); ctx.set_exit_code(exit_code); None diff --git a/core/src/syscall/hint.rs b/core/src/syscall/hint.rs index d11b86dbca..e79fedfec1 100644 --- a/core/src/syscall/hint.rs +++ b/core/src/syscall/hint.rs @@ -75,7 +75,7 @@ mod tests { use crate::{ io::SP1Stdin, runtime::Program, - utils::{run_and_prove, setup_logger, BabyBearPoseidon2}, + utils::{prove, setup_logger, BabyBearPoseidon2}, }; const HINT_IO_ELF: &[u8] = @@ -96,6 +96,6 @@ mod tests { let program = Program::from(HINT_IO_ELF); let config = BabyBearPoseidon2::new(); - run_and_prove(program, &stdin, config); + prove(program, &stdin, config).unwrap(); } } diff --git a/core/src/syscall/precompiles/keccak256/air.rs b/core/src/syscall/precompiles/keccak256/air.rs index 36360af218..ceda18305c 100644 --- a/core/src/syscall/precompiles/keccak256/air.rs +++ b/core/src/syscall/precompiles/keccak256/air.rs @@ -134,7 +134,7 @@ mod test { use crate::io::{SP1PublicValues, SP1Stdin}; use crate::runtime::Program; use crate::stark::{RiscvAir, StarkGenericConfig}; - use crate::utils::{run_and_prove, setup_logger, tests::KECCAK256_ELF, BabyBearPoseidon2}; + use crate::utils::{prove, setup_logger, tests::KECCAK256_ELF, BabyBearPoseidon2}; use rand::Rng; use rand::SeedableRng; @@ -169,7 +169,7 @@ mod test { let config = BabyBearPoseidon2::new(); let program = Program::from(KECCAK256_ELF); - let (proof, public_values) = run_and_prove(program, &stdin, config); + let (proof, public_values) = prove(program, &stdin, config).unwrap(); let mut public_values = SP1PublicValues::from(&public_values); let config = BabyBearPoseidon2::new(); diff --git a/core/src/syscall/precompiles/keccak256/mod.rs b/core/src/syscall/precompiles/keccak256/mod.rs index f0b0a71144..b44ce53f05 100644 --- a/core/src/syscall/precompiles/keccak256/mod.rs +++ b/core/src/syscall/precompiles/keccak256/mod.rs @@ -75,7 +75,7 @@ pub mod permute_tests { utils::setup_logger(); let program = keccak_permute_program(); let mut runtime = Runtime::new(program); - runtime.run() + runtime.run().unwrap(); } #[test] diff --git a/core/src/utils/env.rs b/core/src/utils/env.rs index 371bdc561b..2ee11fef53 100644 --- a/core/src/utils/env.rs +++ b/core/src/utils/env.rs @@ -1,6 +1,7 @@ -use crate::runtime::MAX_SHARD_CLK; use crate::utils::log2_strict_usize; +pub const MAX_SHARD_CLK: usize = (1 << 24) - 1; + /// Gets the number of rows which by default should be used for each chip to maximize padding. pub fn shard_size() -> usize { let value = match std::env::var("SHARD_SIZE") { diff --git a/core/src/utils/programs.rs b/core/src/utils/programs.rs index 839a55b37a..58af5a08c4 100644 --- a/core/src/utils/programs.rs +++ b/core/src/utils/programs.rs @@ -105,4 +105,7 @@ pub mod tests { pub const VERIFY_PROOF_ELF: &[u8] = include_bytes!("../../../tests/verify-proof/elf/riscv32im-succinct-zkvm-elf"); + + pub const PANIC_ELF: &[u8] = + include_bytes!("../../../tests/panic/elf/riscv32im-succinct-zkvm-elf"); } diff --git a/core/src/utils/prove.rs b/core/src/utils/prove.rs index 515672a646..47a96d1c19 100644 --- a/core/src/utils/prove.rs +++ b/core/src/utils/prove.rs @@ -1,26 +1,30 @@ use std::fs::File; +use std::io; use std::io::{Seek, Write}; use web_time::Instant; -use crate::air::MachineAir; -use crate::io::{SP1PublicValues, SP1Stdin}; pub use baby_bear_blake3::BabyBearBlake3; use p3_challenger::CanObserve; use p3_field::PrimeField32; use serde::de::DeserializeOwned; use serde::Serialize; use size::Size; +use thiserror::Error; +use crate::air::MachineAir; +use crate::io::{SP1PublicValues, SP1Stdin}; use crate::lookup::InteractionBuilder; +use crate::runtime::ExecutionError; use crate::runtime::{ExecutionRecord, ShardingConfig}; use crate::stark::DebugConstraintBuilder; +use crate::stark::MachineProof; use crate::stark::ProverConstraintFolder; use crate::stark::StarkVerifyingKey; use crate::stark::Val; use crate::stark::VerifierConstraintFolder; use crate::stark::{Com, PcsProverData, RiscvAir, ShardProof, StarkProvingKey, UniConfig}; use crate::stark::{MachineRecord, StarkMachine}; -use crate::utils::env::shard_batch_size; +use crate::utils::env; use crate::{ runtime::{Program, Runtime}, stark::StarkGenericConfig, @@ -29,119 +33,56 @@ use crate::{ const LOG_DEGREE_BOUND: usize = 31; -/// Runs a program and returns the public values stream. -pub fn run_test_io( - program: Program, - inputs: SP1Stdin, -) -> Result> { - let runtime = tracing::info_span!("runtime.run(...)").in_scope(|| { - let mut runtime = Runtime::new(program); - runtime.write_vecs(&inputs.buffer); - runtime.run(); - runtime - }); - let public_values = SP1PublicValues::from(&runtime.state.public_values_stream); - let _ = run_test_core(runtime)?; - Ok(public_values) -} - -pub fn run_test( - program: Program, -) -> Result< - crate::stark::MachineProof, - crate::stark::MachineVerificationError, -> { - let runtime = tracing::info_span!("runtime.run(...)").in_scope(|| { - let mut runtime = Runtime::new(program); - runtime.run(); - runtime - }); - run_test_core(runtime) +#[derive(Error, Debug)] +pub enum SP1CoreProverError { + #[error("failed to execute program: {0}")] + ExecutionError(ExecutionError), + #[error("io error: {0}")] + IoError(io::Error), + #[error("serialization error: {0}")] + SerializationError(bincode::Error), } -#[allow(unused_variables)] -pub fn run_test_core( +pub fn prove_simple( + config: SC, runtime: Runtime, -) -> Result< - crate::stark::MachineProof, - crate::stark::MachineVerificationError, -> { - let config = BabyBearPoseidon2::new(); - let machine = RiscvAir::machine(config); - let (pk, vk) = machine.setup(runtime.program.as_ref()); - - let record = runtime.record; - run_test_machine(record, machine, pk, vk) -} - -#[allow(unused_variables)] -pub fn run_test_machine( - record: A::Record, - machine: StarkMachine, - pk: StarkProvingKey, - vk: StarkVerifyingKey, -) -> Result, crate::stark::MachineVerificationError> +) -> Result, SP1CoreProverError> where - A: MachineAir - + for<'a> Air> - + Air>> - + for<'a> Air> - + for<'a> Air, SC::Challenge>>, - SC: StarkGenericConfig, - SC::Val: p3_field::PrimeField32, SC::Challenger: Clone, + OpeningProof: Send + Sync, Com: Send + Sync, PcsProverData: Send + Sync, - OpeningProof: Send + Sync, ShardMainData: Serialize + DeserializeOwned, + ::Val: PrimeField32, { - #[cfg(feature = "debug")] - { - let mut challenger_clone = machine.config().challenger(); - let record_clone = record.clone(); - machine.debug_constraints(&pk, record_clone, &mut challenger_clone); - } - let stats = record.stats().clone(); - let cycles = stats.get("cpu_events").unwrap(); + // Setup the machine. + let machine = RiscvAir::machine(config); + let (pk, _) = machine.setup(runtime.program.as_ref()); - let start = Instant::now(); + // Prove the program. let mut challenger = machine.config().challenger(); - let proof = machine.prove::>(&pk, record, &mut challenger); - let time = start.elapsed().as_millis(); + let proving_start = Instant::now(); + let proof = machine.prove::>(&pk, runtime.record, &mut challenger); + let proving_duration = proving_start.elapsed().as_millis(); let nb_bytes = bincode::serialize(&proof).unwrap().len(); - let mut challenger = machine.config().challenger(); - machine.verify(&vk, &proof, &mut challenger)?; - + // Print the summary. tracing::info!( "summary: cycles={}, e2e={}, khz={:.2}, proofSize={}", - cycles, - time, - (*cycles as f64 / time as f64), + runtime.state.global_clk, + proving_duration, + (runtime.state.global_clk as f64 / proving_duration as f64), Size::from_bytes(nb_bytes), ); Ok(proof) } -fn trace_checkpoint(program: Program, file: &File) -> ExecutionRecord { - let mut reader = std::io::BufReader::new(file); - let state = bincode::deserialize_from(&mut reader).expect("failed to deserialize state"); - let mut runtime = Runtime::recover(program.clone(), state); - let (events, _) = tracing::debug_span!("runtime.trace").in_scope(|| runtime.execute_record()); - events -} - -fn reset_seek(file: &mut File) { - file.seek(std::io::SeekFrom::Start(0)) - .expect("failed to seek to start of tempfile"); -} - -pub fn run_and_prove( +pub fn prove( program: Program, stdin: &SP1Stdin, config: SC, -) -> (crate::stark::MachineProof, Vec) +) -> Result<(MachineProof, Vec), SP1CoreProverError> where SC::Challenger: Clone, OpeningProof: Send + Sync, @@ -150,83 +91,87 @@ where ShardMainData: Serialize + DeserializeOwned, ::Val: PrimeField32, { - let mut challenger = config.challenger(); + let proving_start = Instant::now(); - let machine = RiscvAir::machine(config); + // Execute the program. let mut runtime = Runtime::new(program.clone()); runtime.write_vecs(&stdin.buffer); for proof in stdin.proofs.iter() { runtime.write_proof(proof.0.clone(), proof.1.clone()); } + + // Setup the machine. + let machine = RiscvAir::machine(config); let (pk, vk) = machine.setup(runtime.program.as_ref()); - let should_batch = shard_batch_size() > 0; // If we don't need to batch, we can just run the program normally and prove it. - if !should_batch { - runtime.run(); + if env::shard_batch_size() == 0 { + // Execute the runtime and collect all the events.. + runtime.run().map_err(SP1CoreProverError::ExecutionError)?; + + // If debugging is enabled, we will also debug the constraints. #[cfg(feature = "debug")] { - let record_clone = runtime.record.clone(); - machine.debug_constraints(&pk, record_clone, &mut challenger); + let mut challenger = machine.config().challenger(); + machine.debug_constraints(&pk, runtime.record.clone(), &mut challenger); } + + // Generate the proof and return the proof and public values. let public_values = std::mem::take(&mut runtime.state.public_values_stream); - let proof = prove_core(machine.config().clone(), runtime); - return (proof, public_values); + let proof = prove_simple(machine.config().clone(), runtime)?; + return Ok((proof, public_values)); } // Execute the program, saving checkpoints at the start of every `shard_batch_size` cycle range. - let mut cycles = 0; - let mut prove_time = 0; let mut checkpoints = Vec::new(); - let (public_values_stream, public_values) = - tracing::info_span!("runtime.state").in_scope(|| loop { - // Get checkpoint + move to next checkpoint, then save checkpoint to temp file - let (state, done) = runtime.execute_state(); - let mut tempfile = tempfile::tempfile().expect("failed to create tempfile"); - let mut writer = std::io::BufWriter::new(&mut tempfile); - bincode::serialize_into(&mut writer, &state).expect("failed to serialize state"); - writer.flush().expect("failed to flush writer"); - drop(writer); - tempfile - .seek(std::io::SeekFrom::Start(0)) - .expect("failed to seek to start of tempfile"); - checkpoints.push(tempfile); - if done { - return ( - std::mem::take(&mut runtime.state.public_values_stream), - runtime.record.public_values, - ); - } - }); + let (public_values_stream, public_values) = loop { + // Execute the runtime until we reach a checkpoint. + let (checkpoint, done) = runtime + .execute_state() + .map_err(SP1CoreProverError::ExecutionError)?; + + // Save the checkpoint to a temp file. + let mut tempfile = tempfile::tempfile().map_err(SP1CoreProverError::IoError)?; + let mut writer = std::io::BufWriter::new(&mut tempfile); + bincode::serialize_into(&mut writer, &checkpoint) + .map_err(SP1CoreProverError::SerializationError)?; + writer.flush().map_err(SP1CoreProverError::IoError)?; + drop(writer); + tempfile + .seek(std::io::SeekFrom::Start(0)) + .map_err(SP1CoreProverError::IoError)?; + checkpoints.push(tempfile); + + // If we've reached the final checkpoint, break out of the loop. + if done { + break ( + std::mem::take(&mut runtime.state.public_values_stream), + runtime.record.public_values, + ); + } + }; // For each checkpoint, generate events, shard them, commit shards, and observe in challenger. let sharding_config = ShardingConfig::default(); let mut shard_main_datas = Vec::new(); + let mut challenger = machine.config().challenger(); + vk.observe_into(&mut challenger); + for checkpoint_file in checkpoints.iter_mut() { + let mut record = trace_checkpoint(program.clone(), checkpoint_file); + record.public_values = public_values; + reset_seek(&mut *checkpoint_file); - // If there's only one batch, it already must fit in memory so reuse it later in open multi - // rather than running the runtime again. - let reuse_shards = checkpoints.len() == 1; - let mut all_shards = None; + // Shard the record into shards. + let checkpoint_shards = + tracing::info_span!("shard").in_scope(|| machine.shard(record, &sharding_config)); - vk.observe_into(&mut challenger); - for file in checkpoints.iter_mut() { - let mut events = trace_checkpoint(program.clone(), file); - events.public_values = public_values; - - reset_seek(&mut *file); - cycles += events.cpu_events.len(); - let shards = - tracing::debug_span!("shard").in_scope(|| machine.shard(events, &sharding_config)); + // Commit to each shard. let (commitments, commit_data) = tracing::info_span!("commit") - .in_scope(|| LocalProver::commit_shards(&machine, &shards)); - + .in_scope(|| LocalProver::commit_shards(&machine, &checkpoint_shards)); shard_main_datas.push(commit_data); - if reuse_shards { - all_shards = Some(shards.clone()); - } - - for (commitment, shard) in commitments.into_iter().zip(shards.iter()) { + // Observe the commitments. + for (commitment, shard) in commitments.into_iter().zip(checkpoint_shards.iter()) { challenger.observe(commitment); challenger.observe_slice(&shard.public_values::()[0..machine.num_pv_elts()]); } @@ -234,17 +179,14 @@ where // For each checkpoint, generate events and shard again, then prove the shards. let mut shard_proofs = Vec::>::new(); - for mut file in checkpoints.into_iter() { - let shards = if reuse_shards { - Option::take(&mut all_shards).unwrap() - } else { - let mut events = trace_checkpoint(program.clone(), &file); + for mut checkpoint_file in checkpoints.into_iter() { + let checkpoint_shards = { + let mut events = trace_checkpoint(program.clone(), &checkpoint_file); events.public_values = public_values; - reset_seek(&mut file); + reset_seek(&mut checkpoint_file); tracing::debug_span!("shard").in_scope(|| machine.shard(events, &sharding_config)) }; - let start = Instant::now(); - let mut new_proofs = shards + let mut checkpoint_proofs = checkpoint_shards .into_iter() .map(|shard| { let config = machine.config(); @@ -265,59 +207,130 @@ where ) }) .collect::>(); - prove_time += start.elapsed().as_millis(); - shard_proofs.append(&mut new_proofs); + shard_proofs.append(&mut checkpoint_proofs); } + let proof = MachineProof:: { shard_proofs }; - let proof = crate::stark::MachineProof:: { shard_proofs }; - - // Prove the program. - let nb_bytes = bincode::serialize(&proof).unwrap().len(); - + // Print the summary. + let proving_time = proving_start.elapsed().as_secs_f64(); tracing::info!( "summary: cycles={}, e2e={}, khz={:.2}, proofSize={}", - cycles, - prove_time, - (cycles as f64 / prove_time as f64), - Size::from_bytes(nb_bytes), + runtime.state.global_clk, + proving_time, + (runtime.state.global_clk as f64 / proving_time as f64), + bincode::serialize(&proof).unwrap().len(), ); - (proof, public_values_stream) + Ok((proof, public_values_stream)) } -pub fn prove_core( - config: SC, +/// Runs a program and returns the public values stream. +pub fn run_test_io( + program: Program, + inputs: SP1Stdin, +) -> Result> { + let runtime = tracing::info_span!("runtime.run(...)").in_scope(|| { + let mut runtime = Runtime::new(program); + runtime.write_vecs(&inputs.buffer); + runtime.run().unwrap(); + runtime + }); + let public_values = SP1PublicValues::from(&runtime.state.public_values_stream); + let _ = run_test_core(runtime)?; + Ok(public_values) +} + +pub fn run_test( + program: Program, +) -> Result< + crate::stark::MachineProof, + crate::stark::MachineVerificationError, +> { + let runtime = tracing::info_span!("runtime.run(...)").in_scope(|| { + let mut runtime = Runtime::new(program); + runtime.run().unwrap(); + runtime + }); + run_test_core(runtime) +} + +#[allow(unused_variables)] +pub fn run_test_core( runtime: Runtime, -) -> crate::stark::MachineProof +) -> Result< + crate::stark::MachineProof, + crate::stark::MachineVerificationError, +> { + let config = BabyBearPoseidon2::new(); + let machine = RiscvAir::machine(config); + let (pk, vk) = machine.setup(runtime.program.as_ref()); + + let record = runtime.record; + run_test_machine(record, machine, pk, vk) +} + +#[allow(unused_variables)] +pub fn run_test_machine( + record: A::Record, + machine: StarkMachine, + pk: StarkProvingKey, + vk: StarkVerifyingKey, +) -> Result, crate::stark::MachineVerificationError> where + A: MachineAir + + for<'a> Air> + + Air>> + + for<'a> Air> + + for<'a> Air, SC::Challenge>>, + SC: StarkGenericConfig, + SC::Val: p3_field::PrimeField32, SC::Challenger: Clone, - OpeningProof: Send + Sync, Com: Send + Sync, PcsProverData: Send + Sync, + OpeningProof: Send + Sync, ShardMainData: Serialize + DeserializeOwned, - ::Val: PrimeField32, { - let mut challenger = config.challenger(); - - let machine = RiscvAir::machine(config); - let (pk, _) = machine.setup(runtime.program.as_ref()); + #[cfg(feature = "debug")] + { + let mut challenger_clone = machine.config().challenger(); + let record_clone = record.clone(); + machine.debug_constraints(&pk, record_clone, &mut challenger_clone); + } + let stats = record.stats().clone(); + let cycles = stats.get("cpu_events").unwrap(); - // Prove the program. let start = Instant::now(); - let cycles = runtime.state.global_clk; - let proof = machine.prove::>(&pk, runtime.record, &mut challenger); + let mut challenger = machine.config().challenger(); + let proof = machine.prove::>(&pk, record, &mut challenger); let time = start.elapsed().as_millis(); let nb_bytes = bincode::serialize(&proof).unwrap().len(); + let mut challenger = machine.config().challenger(); + machine.verify(&vk, &proof, &mut challenger)?; + tracing::info!( "summary: cycles={}, e2e={}, khz={:.2}, proofSize={}", cycles, time, - (cycles as f64 / time as f64), + (*cycles as f64 / time as f64), Size::from_bytes(nb_bytes), ); - proof + Ok(proof) +} + +fn trace_checkpoint(program: Program, file: &File) -> ExecutionRecord { + let mut reader = std::io::BufReader::new(file); + let state = bincode::deserialize_from(&mut reader).expect("failed to deserialize state"); + let mut runtime = Runtime::recover(program.clone(), state); + let (events, _) = + tracing::debug_span!("runtime.trace").in_scope(|| runtime.execute_record().unwrap()); + events +} + +fn reset_seek(file: &mut File) { + file.seek(std::io::SeekFrom::Start(0)) + .expect("failed to seek to start of tempfile"); } #[cfg(debug_assertions)] diff --git a/eval/src/main.rs b/eval/src/main.rs index ee637ba69f..c11e4413fd 100644 --- a/eval/src/main.rs +++ b/eval/src/main.rs @@ -5,7 +5,7 @@ use clap::{command, Parser}; use csv::WriterBuilder; use serde::Serialize; use sp1_core::runtime::{Program, Runtime}; -use sp1_core::utils::{prove_core, BabyBearBlake3, BabyBearKeccak, BabyBearPoseidon2}; +use sp1_core::utils::{prove_simple, BabyBearBlake3, BabyBearKeccak, BabyBearPoseidon2}; use sp1_prover::utils::get_cycles; use sp1_prover::SP1Stdin; use std::fmt; @@ -139,12 +139,12 @@ fn run_evaluation(hashfn: &HashFnId, program: &Program, _elf: &[u8]) -> (f64, f6 HashFnId::Blake3 => { let mut runtime = Runtime::new(program.clone()); let execution_start = Instant::now(); - runtime.run(); + runtime.run().unwrap(); let execution_duration = execution_start.elapsed().as_secs_f64(); let config = BabyBearBlake3::new(); let prove_start = Instant::now(); - let _proof = prove_core(config.clone(), runtime); + let _proof = prove_simple(config.clone(), runtime); let prove_duration = prove_start.elapsed().as_secs_f64(); let verify_start = Instant::now(); @@ -156,12 +156,12 @@ fn run_evaluation(hashfn: &HashFnId, program: &Program, _elf: &[u8]) -> (f64, f6 HashFnId::Poseidon => { let mut runtime = Runtime::new(program.clone()); let execution_start = Instant::now(); - runtime.run(); + runtime.run().unwrap(); let execution_duration = execution_start.elapsed().as_secs_f64(); let config = BabyBearPoseidon2::new(); let prove_start = Instant::now(); - let _proof = prove_core(config.clone(), runtime); + let _proof = prove_simple(config.clone(), runtime); let prove_duration = prove_start.elapsed().as_secs_f64(); let verify_start = Instant::now(); @@ -173,12 +173,12 @@ fn run_evaluation(hashfn: &HashFnId, program: &Program, _elf: &[u8]) -> (f64, f6 HashFnId::Keccak256 => { let mut runtime = Runtime::new(program.clone()); let execution_start = Instant::now(); - runtime.run(); + runtime.run().unwrap(); let execution_duration = execution_start.elapsed().as_secs_f64(); let config = BabyBearKeccak::new(); let prove_start = Instant::now(); - let _proof = prove_core(config.clone(), runtime); + let _proof = prove_simple(config.clone(), runtime); let prove_duration = prove_start.elapsed().as_secs_f64(); let verify_start = Instant::now(); diff --git a/examples/Cargo.lock b/examples/Cargo.lock index 09818bd07c..ce377df651 100644 --- a/examples/Cargo.lock +++ b/examples/Cargo.lock @@ -716,9 +716,9 @@ checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "axum" -version = "0.7.4" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1236b4b292f6c4d6dc34604bb5120d85c3fe1d1aa596bd5cc52ca054d13e7b9e" +checksum = "3a6c9af12842a67734c9a2e355436e5d03b22383ed60cf13cd0c18fbfe3dcbcf" dependencies = [ "async-trait", "axum-core", @@ -740,7 +740,7 @@ dependencies = [ "serde_json", "serde_path_to_error", "serde_urlencoded", - "sync_wrapper", + "sync_wrapper 1.0.1", "tokio", "tower", "tower-layer", @@ -763,7 +763,7 @@ dependencies = [ "mime", "pin-project-lite", "rustversion", - "sync_wrapper", + "sync_wrapper 0.1.2", "tower-layer", "tower-service", "tracing", @@ -818,6 +818,29 @@ dependencies = [ "serde", ] +[[package]] +name = "bindgen" +version = "0.69.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0" +dependencies = [ + "bitflags 2.5.0", + "cexpr", + "clang-sys", + "itertools 0.12.1", + "lazy_static", + "lazycell", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 2.0.61", + "which", +] + [[package]] name = "bit-set" version = "0.5.3" @@ -1023,6 +1046,15 @@ dependencies = [ "once_cell", ] +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -1050,6 +1082,17 @@ dependencies = [ "windows-targets 0.52.5", ] +[[package]] +name = "clang-sys" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67523a3b4be3ce1989d607a828d036249522dd9c1c8de7f4dd2dae43a37369d1" +dependencies = [ + "glob", + "libc", + "libloading", +] + [[package]] name = "clap" version = "4.5.4" @@ -2011,6 +2054,15 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "http" version = "0.2.12" @@ -2430,6 +2482,12 @@ dependencies = [ "spin 0.5.2", ] +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + [[package]] name = "lib" version = "0.1.0" @@ -2455,6 +2513,16 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "libloading" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" +dependencies = [ + "cfg-if", + "windows-targets 0.52.5", +] + [[package]] name = "libm" version = "0.2.8" @@ -2547,6 +2615,12 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.7.2" @@ -2591,6 +2665,16 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -3303,6 +3387,16 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "prettyplease" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" +dependencies = [ + "proc-macro2", + "syn 2.0.61", +] + [[package]] name = "primitive-types" version = "0.12.2" @@ -3607,7 +3701,7 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", - "sync_wrapper", + "sync_wrapper 0.1.2", "system-configuration", "tokio", "tokio-native-tls", @@ -3653,7 +3747,7 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", - "sync_wrapper", + "sync_wrapper 0.1.2", "system-configuration", "tokio", "tokio-native-tls", @@ -3794,6 +3888,12 @@ version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustc-hex" version = "2.1.0" @@ -4214,6 +4314,12 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signal-hook-registry" version = "1.4.2" @@ -4304,6 +4410,7 @@ dependencies = [ "log", "nohash-hasher", "num", + "num-bigint 0.4.5", "num_cpus", "p3-air", "p3-baby-bear", @@ -4334,6 +4441,7 @@ dependencies = [ "strum", "strum_macros", "tempfile", + "thiserror", "tracing", "tracing-forest", "tracing-subscriber", @@ -4404,6 +4512,7 @@ dependencies = [ "sp1-recursion-program", "subtle-encoding", "tempfile", + "thiserror", "tokio", "tracing", "tracing-appender", @@ -4502,8 +4611,11 @@ dependencies = [ name = "sp1-recursion-gnark-ffi" version = "0.1.0" dependencies = [ + "bindgen", + "cc", "crossbeam", "log", + "p3-baby-bear", "p3-field", "rand", "reqwest 0.12.4", @@ -4713,6 +4825,12 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" +[[package]] +name = "sync_wrapper" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" + [[package]] name = "system-configuration" version = "0.5.1" @@ -5417,6 +5535,18 @@ dependencies = [ "rustls-pki-types", ] +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix", +] + [[package]] name = "winapi" version = "0.3.9" diff --git a/prover/Cargo.toml b/prover/Cargo.toml index 4b3d0885fe..9c2e8b03a2 100644 --- a/prover/Cargo.toml +++ b/prover/Cargo.toml @@ -35,27 +35,25 @@ size = "0.4.1" dirs = "5.0.1" tempfile = "3.10.1" tokio = { version = "1.37.0", features = ["full"] } -reqwest = { version = "0.12.4", features = ["rustls-tls", "trust-dns", "stream"] } +reqwest = { version = "0.12.4", features = [ + "rustls-tls", + "trust-dns", + "stream", +] } indicatif = "0.17.8" futures = "0.3.30" subtle-encoding = "0.5.1" serial_test = "3.1.1" - -[[bin]] -name = "fibonacci_sweep" -path = "scripts/fibonacci_sweep.rs" - -[[bin]] -name = "tendermint_sweep" -path = "scripts/tendermint_sweep.rs" - -[[bin]] -name = "fibonacci_groth16" -path = "scripts/fibonacci_groth16.rs" +num-bigint = "0.4.5" +thiserror = "1.0.60" [[bin]] name = "build_groth16" path = "scripts/build_groth16.rs" +[[bin]] +name = "e2e" +path = "scripts/e2e.rs" + [features] neon = ["sp1-core/neon"] diff --git a/prover/scripts/e2e.rs b/prover/scripts/e2e.rs index 911bf4f113..dfae892795 100644 --- a/prover/scripts/e2e.rs +++ b/prover/scripts/e2e.rs @@ -2,9 +2,11 @@ #![allow(incomplete_features)] use std::borrow::Borrow; +use std::path::PathBuf; use clap::Parser; use p3_baby_bear::BabyBear; +use p3_field::PrimeField; use sp1_core::io::SP1Stdin; use sp1_prover::utils::{babybear_bytes_to_bn254, babybears_to_bn254, words_to_bytes}; use sp1_prover::SP1Prover; @@ -12,7 +14,7 @@ use sp1_recursion_circuit::stark::build_wrap_circuit; use sp1_recursion_circuit::witness::Witnessable; use sp1_recursion_compiler::ir::Witness; use sp1_recursion_core::air::RecursionPublicValues; -use sp1_recursion_gnark_ffi::{convert, verify, Groth16Prover}; +use sp1_recursion_gnark_ffi::Groth16Prover; use subtle_encoding::hex; #[derive(Parser, Debug)] @@ -27,6 +29,7 @@ pub fn main() { std::env::set_var("RECONSTRUCT_COMMITMENTS", "false"); let args = Args::parse(); + let build_dir: PathBuf = args.build_dir.into(); let elf = include_bytes!("../../tests/fibonacci/elf/riscv32im-succinct-zkvm-elf"); @@ -38,23 +41,23 @@ pub fn main() { tracing::info!("prove core"); let stdin = SP1Stdin::new(); - let core_proof = prover.prove_core(&pk, &stdin); + let core_proof = prover.prove_core(&pk, &stdin).unwrap(); tracing::info!("Compress"); - let reduced_proof = prover.compress(&vk, core_proof, vec![]); + let reduced_proof = prover.compress(&vk, core_proof, vec![]).unwrap(); tracing::info!("Shrink"); - let compressed_proof = prover.shrink(reduced_proof); + let compressed_proof = prover.shrink(reduced_proof).unwrap(); tracing::info!("wrap"); - let wrapped_proof = prover.wrap_bn254(compressed_proof); + let wrapped_proof = prover.wrap_bn254(compressed_proof).unwrap(); tracing::info!("building verifier constraints"); let constraints = tracing::info_span!("wrap circuit") - .in_scope(|| build_wrap_circuit(&prover.wrap_vk, wrapped_proof.clone())); + .in_scope(|| build_wrap_circuit(&prover.wrap_vk, wrapped_proof.proof.clone())); tracing::info!("building template witness"); - let pv: &RecursionPublicValues<_> = wrapped_proof.public_values.as_slice().borrow(); + let pv: &RecursionPublicValues<_> = wrapped_proof.proof.public_values.as_slice().borrow(); let vkey_hash = babybears_to_bn254(&pv.sp1_vk_digest); let committed_values_digest_bytes: [BabyBear; 32] = words_to_bytes(&pv.committed_value_digest) .try_into() @@ -62,7 +65,7 @@ pub fn main() { let committed_values_digest = babybear_bytes_to_bn254(&committed_values_digest_bytes); let mut witness = Witness::default(); - wrapped_proof.write(&mut witness); + wrapped_proof.proof.write(&mut witness); witness.write_commited_values_digest(committed_values_digest); witness.write_vkey_hash(vkey_hash); @@ -70,38 +73,24 @@ pub fn main() { Groth16Prover::test(constraints.clone(), witness.clone()); tracing::info!("sanity check gnark build"); - Groth16Prover::build( - constraints.clone(), - witness.clone(), - args.build_dir.clone().into(), - ); + Groth16Prover::build(constraints.clone(), witness.clone(), build_dir.clone()); tracing::info!("sanity check gnark prove"); - let groth16_prover = Groth16Prover::new(args.build_dir.clone().into()); + let groth16_prover = Groth16Prover::new(); tracing::info!("gnark prove"); - let proof = groth16_prover.prove(witness.clone()); + let proof = groth16_prover.prove(witness.clone(), build_dir.clone()); tracing::info!("verify gnark proof"); - let verified = verify(proof.clone(), &args.build_dir.clone().into()); - assert!(verified); - - tracing::info!("convert gnark proof"); - let solidity_proof = convert(proof.clone(), &args.build_dir.clone().into()); - - // tracing::info!("sanity check plonk bn254 build"); - // PlonkBn254Prover::build( - // constraints.clone(), - // witness.clone(), - // args.build_dir.clone().into(), - // ); - - // tracing::info!("sanity check plonk bn254 prove"); - // let proof = PlonkBn254Prover::prove(witness.clone(), args.build_dir.clone().into()); + groth16_prover.verify( + &proof, + &vkey_hash.as_canonical_biguint(), + &committed_values_digest.as_canonical_biguint(), + &build_dir, + ); println!( "{:?}", String::from_utf8(hex::encode(proof.encoded_proof)).unwrap() ); - println!("solidity proof: {:?}", solidity_proof); } diff --git a/prover/scripts/fibonacci_groth16.rs b/prover/scripts/fibonacci_groth16.rs index 480ac2af6c..2a5fc88d8a 100644 --- a/prover/scripts/fibonacci_groth16.rs +++ b/prover/scripts/fibonacci_groth16.rs @@ -62,7 +62,7 @@ fn main() { proofs: vec![], }; let leaf_proving_start = Instant::now(); - let proof = prover.prove_core(&pk, &stdin); + let proof = prover.prove_core(&pk, &stdin).unwrap(); let leaf_proving_duration = leaf_proving_start.elapsed().as_secs_f64(); tracing::info!("leaf_proving_duration={}", leaf_proving_duration); diff --git a/prover/scripts/fibonacci_sweep.rs b/prover/scripts/fibonacci_sweep.rs index c93f7950b2..14f3536628 100644 --- a/prover/scripts/fibonacci_sweep.rs +++ b/prover/scripts/fibonacci_sweep.rs @@ -65,7 +65,7 @@ fn main() { proofs: vec![], }; let leaf_proving_start = Instant::now(); - let proof = prover.prove_core(&pk, &stdin); + let proof = prover.prove_core(&pk, &stdin).unwrap(); let leaf_proving_duration = leaf_proving_start.elapsed().as_secs_f64(); let recursion_proving_start = Instant::now(); diff --git a/prover/scripts/tendermint_sweep.rs b/prover/scripts/tendermint_sweep.rs index bff6050725..37209895f4 100644 --- a/prover/scripts/tendermint_sweep.rs +++ b/prover/scripts/tendermint_sweep.rs @@ -65,7 +65,7 @@ fn main() { proofs: vec![], }; let leaf_proving_start = Instant::now(); - let proof = prover.prove_core(&pk, &stdin); + let proof = prover.prove_core(&pk, &stdin).unwrap(); let leaf_proving_duration = leaf_proving_start.elapsed().as_secs_f64(); let recursion_proving_start = Instant::now(); diff --git a/prover/src/build.rs b/prover/src/build.rs index 0c3325e43c..8aad92a8d7 100644 --- a/prover/src/build.rs +++ b/prover/src/build.rs @@ -9,13 +9,64 @@ pub use sp1_recursion_circuit::witness::Witnessable; pub use sp1_recursion_compiler::ir::Witness; use sp1_recursion_compiler::{config::OuterConfig, constraints::Constraint}; use sp1_recursion_core::air::RecursionPublicValues; -use sp1_recursion_core::stark::utils::sp1_dev_mode; +pub use sp1_recursion_core::stark::utils::sp1_dev_mode; use sp1_recursion_gnark_ffi::Groth16Prover; use crate::install::{install_groth16_artifacts, GROTH16_ARTIFACTS_COMMIT}; use crate::utils::{babybear_bytes_to_bn254, babybears_to_bn254, words_to_bytes}; use crate::{OuterSC, SP1Prover}; +/// Tries to install the Groth16 artifacts if they are not already installed. +pub fn try_install_groth16_artifacts() -> PathBuf { + let build_dir = groth16_artifacts_dir(); + + if build_dir.exists() { + println!("[sp1] groth16 artifacts already seem to exist at {}. if you want to re-download them, delete the directory", build_dir.display()); + } else { + println!( + "[sp1] groth16 artifacts for commit {} do not exist at {}. downloading...", + GROTH16_ARTIFACTS_COMMIT, + build_dir.display() + ); + install_groth16_artifacts(build_dir.clone()); + } + build_dir +} + +/// Tries to build the Groth16 artifacts inside the development directory. +/// +/// TODO: Maybe add some additional logic here to handle rebuilding the artifacts if they are +/// already built. +pub fn try_build_groth16_artifacts_dev( + template_vk: &StarkVerifyingKey, + template_proof: &ShardProof, +) -> PathBuf { + let build_dir = groth16_artifacts_dev_dir(); + println!("[sp1] building groth16 artifacts in development mode"); + build_groth16_artifacts(template_vk, template_proof, &build_dir); + build_dir +} + +/// Gets the directory where the Groth16 artifacts are installed. +pub fn groth16_artifacts_dir() -> PathBuf { + dirs::home_dir() + .unwrap() + .join(".sp1") + .join("circuits") + .join("groth16") + .join(GROTH16_ARTIFACTS_COMMIT) +} + +/// Gets the directory where the Groth16 artifacts are installed in development mode. +pub fn groth16_artifacts_dev_dir() -> PathBuf { + dirs::home_dir() + .unwrap() + .join(".sp1") + .join("circuits") + .join("groth16") + .join("dev") +} + /// Build the groth16 artifacts to the given directory for the given verification key and template /// proof. pub fn build_groth16_artifacts( @@ -23,8 +74,10 @@ pub fn build_groth16_artifacts( template_proof: &ShardProof, build_dir: impl Into, ) { + let build_dir = build_dir.into(); + std::fs::create_dir_all(&build_dir).expect("failed to create build directory"); let (constraints, witness) = build_constraints_and_witness(template_vk, template_proof); - Groth16Prover::build(constraints, witness, build_dir.into()); + Groth16Prover::build(constraints, witness, build_dir); } /// Builds the groth16 artifacts to the given directory. @@ -75,50 +128,16 @@ pub fn dummy_proof() -> (StarkVerifyingKey, ShardProof) { tracing::info!("prove core"); let mut stdin = SP1Stdin::new(); stdin.write(&500u32); - let core_proof = prover.prove_core(&pk, &stdin); + let core_proof = prover.prove_core(&pk, &stdin).unwrap(); tracing::info!("compress"); - let compressed_proof = prover.compress(&vk, core_proof, vec![]); + let compressed_proof = prover.compress(&vk, core_proof, vec![]).unwrap(); tracing::info!("shrink"); - let shrink_proof = prover.shrink(compressed_proof); + let shrink_proof = prover.shrink(compressed_proof).unwrap(); tracing::info!("wrap"); - let wrapped_proof = prover.wrap_bn254(shrink_proof); + let wrapped_proof = prover.wrap_bn254(shrink_proof).unwrap(); (prover.wrap_vk, wrapped_proof.proof) } - -/// Gets the artifacts directory for Groth16 based on the current environment variables. -/// -/// - If `SP1_DEV` is enabled, we will use a smaller version of the final -/// circuit and rebuild it for every proof. This is useful for development and testing purposes, as -/// it allows us to test the end-to-end proving without having to wait for the circuit to compile or -/// download. -/// -/// - Otherwise, assume this is an official release and download the artifacts from the official -/// download url. -pub fn get_groth16_artifacts_dir() -> PathBuf { - if sp1_dev_mode() { - let build_dir = dirs::home_dir() - .unwrap() - .join(".sp1") - .join("circuits") - .join("dev"); - if let Err(err) = std::fs::create_dir_all(&build_dir) { - panic!( - "failed to create build directory for groth16 artifacts: {}", - err - ); - } - build_dir - } else { - let build_dir = dirs::home_dir() - .unwrap() - .join(".sp1") - .join("circuits") - .join(GROTH16_ARTIFACTS_COMMIT); - install_groth16_artifacts(build_dir.clone()); - build_dir - } -} diff --git a/prover/src/install.rs b/prover/src/install.rs index 642c3f1848..bf271ce4ae 100644 --- a/prover/src/install.rs +++ b/prover/src/install.rs @@ -4,29 +4,19 @@ use futures::StreamExt; use indicatif::{ProgressBar, ProgressStyle}; use reqwest::Client; +use crate::utils::block_on; + /// The base URL for the S3 bucket containing the groth16 artifacts. pub const GROTH16_ARTIFACTS_URL_BASE: &str = "https://sp1-circuits.s3-us-east-2.amazonaws.com"; /// The current version of the groth16 artifacts. -pub const GROTH16_ARTIFACTS_COMMIT: &str = "1eee43b6"; +pub const GROTH16_ARTIFACTS_COMMIT: &str = "9f43e920"; /// Install the latest groth16 artifacts. /// /// This function will download the latest groth16 artifacts from the S3 bucket and extract them to /// the directory specified by [groth16_artifacts_dir()]. pub fn install_groth16_artifacts(build_dir: PathBuf) { - // If build directory already exists, skip the download. - if build_dir.exists() { - println!("[sp1] groth16 artifacts already seem to exist at {}. if you want to re-download them, delete the directory", build_dir.display()); - return; - } else { - println!( - "[sp1] groth16 artifacts for commit {} do not exist at {}. downloading...", - GROTH16_ARTIFACTS_COMMIT, - build_dir.display() - ); - } - // Create the build directory. std::fs::create_dir_all(&build_dir).expect("failed to create build directory"); @@ -37,11 +27,10 @@ pub fn install_groth16_artifacts(build_dir: PathBuf) { ); let mut artifacts_tar_gz_file = tempfile::NamedTempFile::new().expect("failed to create tempfile"); - let rt = tokio::runtime::Runtime::new().expect("failed to create tokio runtime"); let client = Client::builder() .build() .expect("failed to create reqwest client"); - rt.block_on(download_file( + block_on(download_file( &client, &download_url, &mut artifacts_tar_gz_file, diff --git a/prover/src/lib.rs b/prover/src/lib.rs index 5281728cb9..eabd8270ef 100644 --- a/prover/src/lib.rs +++ b/prover/src/lib.rs @@ -1,4 +1,4 @@ -//! An end-to-end-prover implementation for SP1. +//! An end-to-end-prover implementation for the SP1 RISC-V zkVM. //! //! Seperates the proof generation process into multiple stages: //! @@ -14,54 +14,54 @@ pub mod build; pub mod install; -mod types; +pub mod types; pub mod utils; -mod verify; +pub mod verify; use std::borrow::Borrow; +use std::env; +use std::path::{Path, PathBuf}; use crate::utils::RECONSTRUCT_COMMITMENTS_ENV_VAR; use p3_baby_bear::BabyBear; use p3_challenger::CanObserve; -use p3_field::AbstractField; +use p3_field::{AbstractField, PrimeField}; use rayon::iter::{IntoParallelIterator, ParallelIterator}; use rayon::prelude::*; -use sp1_core::air::PublicValues; +use sp1_core::air::{PublicValues, Word}; pub use sp1_core::io::{SP1PublicValues, SP1Stdin}; -use sp1_core::runtime::Runtime; -use sp1_core::stark::MachineVerificationError; +use sp1_core::runtime::{ExecutionError, Runtime}; use sp1_core::stark::{Challenge, StarkProvingKey}; +use sp1_core::stark::{Challenger, MachineVerificationError}; use sp1_core::utils::DIGEST_SIZE; use sp1_core::{ runtime::Program, stark::{ LocalProver, RiscvAir, ShardProof, StarkGenericConfig, StarkMachine, StarkVerifyingKey, Val, }, - utils::{run_and_prove, BabyBearPoseidon2}, + utils::{BabyBearPoseidon2, SP1CoreProverError}, }; use sp1_primitives::hash_deferred_proof; use sp1_recursion_circuit::witness::Witnessable; use sp1_recursion_compiler::config::InnerConfig; -use sp1_recursion_compiler::config::OuterConfig; use sp1_recursion_compiler::ir::Witness; -use sp1_recursion_core::runtime::RecursionProgram; -use sp1_recursion_core::stark::RecursionAir; use sp1_recursion_core::{ - air::RecursionPublicValues, runtime::Runtime as RecursionRuntime, - stark::config::BabyBearPoseidon2Outer, + air::RecursionPublicValues, + runtime::{RecursionProgram, Runtime as RecursionRuntime}, + stark::{config::BabyBearPoseidon2Outer, RecursionAir}, }; pub use sp1_recursion_gnark_ffi::plonk_bn254::PlonkBn254Proof; use sp1_recursion_gnark_ffi::plonk_bn254::PlonkBn254Prover; pub use sp1_recursion_gnark_ffi::Groth16Proof; use sp1_recursion_gnark_ffi::Groth16Prover; use sp1_recursion_program::hints::Hintable; +pub use sp1_recursion_program::machine::ReduceProgramType; use sp1_recursion_program::machine::{ - ReduceProgramType, SP1CompressVerifier, SP1DeferredMemoryLayout, SP1DeferredVerifier, - SP1RecursionMemoryLayout, SP1RecursiveVerifier, SP1ReduceMemoryLayout, SP1RootMemoryLayout, - SP1RootVerifier, + SP1CompressVerifier, SP1DeferredVerifier, SP1RecursiveVerifier, SP1RootVerifier, +}; +pub use sp1_recursion_program::machine::{ + SP1DeferredMemoryLayout, SP1RecursionMemoryLayout, SP1ReduceMemoryLayout, SP1RootMemoryLayout, }; -use std::env; -use std::path::PathBuf; use tracing::instrument; pub use types::*; use utils::words_to_bytes; @@ -83,7 +83,7 @@ pub type ReduceAir = RecursionAir; pub type CompressAir = RecursionAir; pub type WrapAir = RecursionAir; -/// A end-to-end prover implementation for SP1. +/// A end-to-end prover implementation for the SP1 RISC-V zkVM. pub struct SP1Prover { /// The program that can recursively verify a set of proofs into a single proof. pub recursion_program: RecursionProgram, @@ -217,74 +217,57 @@ impl SP1Prover { /// Generate a proof of an SP1 program with the specified inputs. #[instrument(name = "execute", level = "info", skip_all)] - pub fn execute(elf: &[u8], stdin: &SP1Stdin) -> SP1PublicValues { + pub fn execute(elf: &[u8], stdin: &SP1Stdin) -> Result { let program = Program::from(elf); let mut runtime = Runtime::new(program); runtime.write_vecs(&stdin.buffer); for (proof, vkey) in stdin.proofs.iter() { runtime.write_proof(proof.clone(), vkey.clone()); } - runtime.run(); - SP1PublicValues::from(&runtime.state.public_values_stream) + runtime.run_untraced()?; + Ok(SP1PublicValues::from(&runtime.state.public_values_stream)) } /// Generate shard proofs which split up and prove the valid execution of a RISC-V program with /// the core prover. #[instrument(name = "prove_core", level = "info", skip_all)] - pub fn prove_core(&self, pk: &SP1ProvingKey, stdin: &SP1Stdin) -> SP1CoreProof { + pub fn prove_core( + &self, + pk: &SP1ProvingKey, + stdin: &SP1Stdin, + ) -> Result { let config = CoreSC::default(); let program = Program::from(&pk.elf); - let (proof, public_values_stream) = run_and_prove(program, stdin, config); + let (proof, public_values_stream) = sp1_core::utils::prove(program, stdin, config)?; let public_values = SP1PublicValues::from(&public_values_stream); - SP1CoreProof { + Ok(SP1CoreProof { proof: SP1CoreProofData(proof.shard_proofs), stdin: stdin.clone(), public_values, - } + }) } - /// Reduce shards proofs to a single shard proof using the recursion prover. - #[instrument(name = "compress", level = "info", skip_all)] - pub fn compress( - &self, - vk: &SP1VerifyingKey, - proof: SP1CoreProof, - deferred_proofs: Vec>, - ) -> SP1ReduceProof { - // Set the batch size for the reduction tree. - let batch_size = 2; - - let shard_proofs = &proof.proof.0; - - // Setup the reconstruct commitments flags to false and save its state. - let rc = env::var(RECONSTRUCT_COMMITMENTS_ENV_VAR).unwrap_or_default(); - env::set_var(RECONSTRUCT_COMMITMENTS_ENV_VAR, "false"); - - // Get the leaf challenger. - let mut leaf_challenger = self.core_machine.config().challenger(); - vk.vk.observe_into(&mut leaf_challenger); - shard_proofs.iter().for_each(|proof| { - leaf_challenger.observe(proof.commitment.main_commit); - leaf_challenger.observe_slice(&proof.public_values[0..self.core_machine.num_pv_elts()]); - }); - // Make sure leaf challenger is not mutable anymore. - let leaf_challenger = leaf_challenger; - + pub fn get_recursion_core_inputs<'a>( + &'a self, + vk: &'a StarkVerifyingKey, + leaf_challenger: &'a Challenger, + shard_proofs: &[ShardProof], + batch_size: usize, + is_complete: bool, + ) -> Vec>> { let mut core_inputs = Vec::new(); - let mut reconstruct_challenger = self.core_machine.config().challenger(); - vk.vk.observe_into(&mut reconstruct_challenger); + vk.observe_into(&mut reconstruct_challenger); // Prepare the inputs for the recursion programs. - let is_complete = shard_proofs.len() == 1 && deferred_proofs.is_empty(); for batch in shard_proofs.chunks(batch_size) { let proofs = batch.to_vec(); core_inputs.push(SP1RecursionMemoryLayout { - vk: &vk.vk, + vk, machine: &self.core_machine, shard_proofs: proofs, - leaf_challenger: &leaf_challenger, + leaf_challenger, initial_reconstruct_challenger: reconstruct_challenger.clone(), is_complete, }); @@ -296,9 +279,6 @@ impl SP1Prover { } } - let last_proof_input = - PublicValues::from_vec(shard_proofs.last().unwrap().public_values.clone()); - // Check that the leaf challenger is the same as the reconstruct challenger. assert_eq!( reconstruct_challenger.sponge_state, @@ -312,13 +292,21 @@ impl SP1Prover { reconstruct_challenger.output_buffer, leaf_challenger.output_buffer ); + core_inputs + } + pub fn get_recursion_deferred_inputs<'a>( + &'a self, + vk: &'a StarkVerifyingKey, + leaf_challenger: &'a Challenger, + last_proof_pv: &PublicValues, BabyBear>, + deferred_proofs: &[ShardProof], + batch_size: usize, + ) -> Vec>> { // Prepare the inputs for the deferred proofs recursive verification. let mut deferred_digest = [Val::::zero(); DIGEST_SIZE]; let mut deferred_inputs = Vec::new(); - let is_deferred_complete = shard_proofs.is_empty() && deferred_proofs.len() == 1; - for batch in deferred_proofs.chunks(batch_size) { let proofs = batch.to_vec(); @@ -327,24 +315,91 @@ impl SP1Prover { machine: &self.compress_machine, proofs, start_reconstruct_deferred_digest: deferred_digest.to_vec(), - is_complete: is_deferred_complete, - sp1_vk: &vk.vk, + is_complete: false, + sp1_vk: vk, sp1_machine: &self.core_machine, end_pc: Val::::zero(), - end_shard: Val::::from_canonical_usize(shard_proofs.len()), + end_shard: last_proof_pv.shard, leaf_challenger: leaf_challenger.clone(), - committed_value_digest: last_proof_input.committed_value_digest.to_vec(), - deferred_proofs_digest: last_proof_input.deferred_proofs_digest.to_vec(), + committed_value_digest: last_proof_pv.committed_value_digest.to_vec(), + deferred_proofs_digest: last_proof_pv.deferred_proofs_digest.to_vec(), }); deferred_digest = Self::hash_deferred_proofs(deferred_digest, batch); } + deferred_inputs + } + + /// Generate the inputs for the first layer of recursive proofs. + #[allow(clippy::type_complexity)] + pub fn get_first_layer_inputs<'a>( + &'a self, + vk: &'a SP1VerifyingKey, + leaf_challenger: &'a Challenger, + shard_proofs: &[ShardProof], + deferred_proofs: &[ShardProof], + batch_size: usize, + ) -> ( + Vec>>, + Vec>>, + ) { + let is_complete = shard_proofs.len() == 1 && deferred_proofs.is_empty(); + let core_inputs = self.get_recursion_core_inputs( + &vk.vk, + leaf_challenger, + shard_proofs, + batch_size, + is_complete, + ); + let last_proof_pv = + PublicValues::from_vec(shard_proofs.last().unwrap().public_values.clone()); + let deferred_inputs = self.get_recursion_deferred_inputs( + &vk.vk, + leaf_challenger, + &last_proof_pv, + deferred_proofs, + batch_size, + ); + (core_inputs, deferred_inputs) + } + + /// Reduce shards proofs to a single shard proof using the recursion prover. + #[instrument(name = "compress", level = "info", skip_all)] + pub fn compress( + &self, + vk: &SP1VerifyingKey, + proof: SP1CoreProof, + deferred_proofs: Vec>, + ) -> Result, SP1RecursionProverError> { + // Set the batch size for the reduction tree. + let batch_size = 2; + + let shard_proofs = &proof.proof.0; + // Get the leaf challenger. + let mut leaf_challenger = self.core_machine.config().challenger(); + vk.vk.observe_into(&mut leaf_challenger); + shard_proofs.iter().for_each(|proof| { + leaf_challenger.observe(proof.commitment.main_commit); + leaf_challenger.observe_slice(&proof.public_values[0..self.core_machine.num_pv_elts()]); + }); + + // Setup the reconstruct commitments flags to false and save its state. + let rc = env::var(RECONSTRUCT_COMMITMENTS_ENV_VAR).unwrap_or_default(); + env::set_var(RECONSTRUCT_COMMITMENTS_ENV_VAR, "false"); // Run the recursion and reduce programs. // Run the recursion programs. let mut records = Vec::new(); + let (core_inputs, deferred_inputs) = self.get_first_layer_inputs( + vk, + &leaf_challenger, + shard_proofs, + &deferred_proofs, + batch_size, + ); + for input in core_inputs { let mut runtime = RecursionRuntime::, Challenge, _>::new( &self.recursion_program, @@ -426,27 +481,12 @@ impl SP1Prover { is_complete, }; - let mut runtime = RecursionRuntime::, Challenge, _>::new( + let proof = self.compress_machine_proof( + input, &self.compress_program, - self.compress_machine.config().perm.clone(), - ); - - let mut witness_stream = Vec::new(); - witness_stream.extend(input.write()); - - runtime.witness_stream = witness_stream.into(); - runtime.run(); - runtime.print_stats(); - - let mut recursive_challenger = self.compress_machine.config().challenger(); - let mut proof = self.compress_machine.prove::>( &self.compress_pk, - runtime.record, - &mut recursive_challenger, ); - - debug_assert_eq!(proof.shard_proofs.len(), 1); - (proof.shard_proofs.pop().unwrap(), ReduceProgramType::Reduce) + (proof, ReduceProgramType::Reduce) }) .collect(); @@ -460,14 +500,43 @@ impl SP1Prover { // Restore the prover parameters. env::set_var(RECONSTRUCT_COMMITMENTS_ENV_VAR, rc); - SP1ReduceProof { + Ok(SP1ReduceProof { proof: reduce_proof.0, - } + }) + } + + pub fn compress_machine_proof( + &self, + input: impl Hintable, + program: &RecursionProgram, + pk: &StarkProvingKey, + ) -> ShardProof { + let mut runtime = RecursionRuntime::, Challenge, _>::new( + program, + self.compress_machine.config().perm.clone(), + ); + + let mut witness_stream = Vec::new(); + witness_stream.extend(input.write()); + + runtime.witness_stream = witness_stream.into(); + runtime.run(); + runtime.print_stats(); + + let mut recursive_challenger = self.compress_machine.config().challenger(); + self.compress_machine + .prove::>(pk, runtime.record, &mut recursive_challenger) + .shard_proofs + .pop() + .unwrap() } /// Wrap a reduce proof into a STARK proven over a SNARK-friendly field. #[instrument(name = "shrink", level = "info", skip_all)] - pub fn shrink(&self, reduced_proof: SP1ReduceProof) -> SP1ReduceProof { + pub fn shrink( + &self, + reduced_proof: SP1ReduceProof, + ) -> Result, SP1RecursionProverError> { // Setup the prover parameters. let rc = env::var(RECONSTRUCT_COMMITMENTS_ENV_VAR).unwrap_or_default(); env::set_var(RECONSTRUCT_COMMITMENTS_ENV_VAR, "false"); @@ -504,14 +573,17 @@ impl SP1Prover { // Restore the prover parameters. env::set_var(RECONSTRUCT_COMMITMENTS_ENV_VAR, rc); - SP1ReduceProof { + Ok(SP1ReduceProof { proof: compress_proof.shard_proofs.pop().unwrap(), - } + }) } /// Wrap a reduce proof into a STARK proven over a SNARK-friendly field. #[instrument(name = "wrap_bn254", level = "info", skip_all)] - pub fn wrap_bn254(&self, compressed_proof: SP1ReduceProof) -> SP1ReduceProof { + pub fn wrap_bn254( + &self, + compressed_proof: SP1ReduceProof, + ) -> Result, SP1RecursionProverError> { // Setup the prover parameters. let rc = env::var(RECONSTRUCT_COMMITMENTS_ENV_VAR).unwrap_or_default(); env::set_var(RECONSTRUCT_COMMITMENTS_ENV_VAR, "false"); @@ -562,14 +634,14 @@ impl SP1Prover { // Restore the prover parameters. env::set_var(RECONSTRUCT_COMMITMENTS_ENV_VAR, rc); - SP1ReduceProof { + Ok(SP1ReduceProof { proof: wrap_proof.shard_proofs.pop().unwrap(), - } + }) } /// Wrap the STARK proven over a SNARK-friendly field into a Groth16 proof. #[instrument(name = "wrap_groth16", level = "info", skip_all)] - pub fn wrap_groth16(&self, proof: SP1ReduceProof, build_dir: PathBuf) -> Groth16Proof { + pub fn wrap_groth16(&self, proof: SP1ReduceProof, build_dir: &Path) -> Groth16Proof { let vkey_digest = proof.sp1_vkey_digest_bn254(); let commited_values_digest = proof.sp1_commited_values_digest_bn254(); @@ -579,13 +651,13 @@ impl SP1Prover { witness.write_vkey_hash(vkey_digest); let prover = Groth16Prover::new(); - let proof = prover.prove(witness, build_dir.clone()); + let proof = prover.prove(witness, build_dir.to_path_buf()); // Verify the proof. - prover.verify::( - proof.clone(), - vkey_digest, - commited_values_digest, + prover.verify( + &proof, + &vkey_digest.as_canonical_biguint(), + &commited_values_digest.as_canonical_biguint(), build_dir, ); @@ -600,7 +672,7 @@ impl SP1Prover { } /// Accumulate deferred proofs into a single digest. - fn hash_deferred_proofs( + pub fn hash_deferred_proofs( prev_digest: [Val; DIGEST_SIZE], deferred_proofs: &[ShardProof], ) -> [Val; 8] { @@ -624,9 +696,10 @@ mod tests { use std::fs::File; use std::io::{Read, Write}; - use crate::build::{build_groth16_artifacts, get_groth16_artifacts_dir}; - + use self::build::try_build_groth16_artifacts_dev; use super::*; + + use anyhow::Result; use p3_field::PrimeField32; use serial_test::serial; use sp1_core::io::SP1Stdin; @@ -634,9 +707,13 @@ mod tests { /// Tests an end-to-end workflow of proving a program across the entire proof generation /// pipeline. + /// + /// Add `FRI_QUERIES`=1 to your environment for faster execution. Should only take a few minutes + /// on a Mac M2. Note: This test always re-builds the groth16 artifacts, so setting SP1_DEV is + /// not needed. #[test] #[serial] - fn test_e2e() { + fn test_e2e() -> Result<()> { setup_logger(); let elf = include_bytes!("../../tests/fibonacci/elf/riscv32im-succinct-zkvm-elf"); @@ -648,25 +725,26 @@ mod tests { tracing::info!("prove core"); let stdin = SP1Stdin::new(); - let core_proof = prover.prove_core(&pk, &stdin); + let core_proof = prover.prove_core(&pk, &stdin)?; + let public_values = core_proof.public_values.clone(); tracing::info!("verify core"); - prover.verify(&core_proof.proof, &vk).unwrap(); + prover.verify(&core_proof.proof, &vk)?; tracing::info!("compress"); - let compressed_proof = prover.compress(&vk, core_proof, vec![]); + let compressed_proof = prover.compress(&vk, core_proof, vec![])?; tracing::info!("verify compressed"); - prover.verify_compressed(&compressed_proof, &vk).unwrap(); + prover.verify_compressed(&compressed_proof, &vk)?; tracing::info!("shrink"); - let shrink_proof = prover.shrink(compressed_proof); + let shrink_proof = prover.shrink(compressed_proof)?; tracing::info!("verify shrink"); - prover.verify_shrink(&shrink_proof, &vk).unwrap(); + prover.verify_shrink(&shrink_proof, &vk)?; tracing::info!("wrap bn254"); - let wrapped_bn254_proof = prover.wrap_bn254(shrink_proof); + let wrapped_bn254_proof = prover.wrap_bn254(shrink_proof)?; let bytes = bincode::serialize(&wrapped_bn254_proof).unwrap(); // Save the proof. @@ -692,17 +770,21 @@ mod tests { assert_eq!(vk_digest_bn254, vk.hash_bn254()); tracing::info!("generate groth16 proof"); - let artifacts_dir = get_groth16_artifacts_dir(); - build_groth16_artifacts(&prover.wrap_vk, &wrapped_bn254_proof.proof, &artifacts_dir); - let groth16_proof = prover.wrap_groth16(wrapped_bn254_proof, artifacts_dir); + let artifacts_dir = + try_build_groth16_artifacts_dev(&prover.wrap_vk, &wrapped_bn254_proof.proof); + let groth16_proof = prover.wrap_groth16(wrapped_bn254_proof, &artifacts_dir); println!("{:?}", groth16_proof); + + prover.verify_groth16(&groth16_proof, &vk, &public_values, &artifacts_dir)?; + + Ok(()) } /// Tests an end-to-end workflow of proving a program across the entire proof generation /// pipeline in addition to verifying deferred proofs. #[test] #[serial] - fn test_e2e_with_deferred_proofs() { + fn test_e2e_with_deferred_proofs() -> Result<()> { setup_logger(); // Test program which proves the Keccak-256 hash of various inputs. @@ -724,7 +806,7 @@ mod tests { let mut stdin = SP1Stdin::new(); stdin.write(&1usize); stdin.write(&vec![0u8, 0, 0]); - let deferred_proof_1 = prover.prove_core(&keccak_pk, &stdin); + let deferred_proof_1 = prover.prove_core(&keccak_pk, &stdin)?; let pv_1 = deferred_proof_1.public_values.as_slice().to_vec().clone(); // Generate a second proof of keccak of various inputs. @@ -734,16 +816,16 @@ mod tests { stdin.write(&vec![0u8, 1, 2]); stdin.write(&vec![2, 3, 4]); stdin.write(&vec![5, 6, 7]); - let deferred_proof_2 = prover.prove_core(&keccak_pk, &stdin); + let deferred_proof_2 = prover.prove_core(&keccak_pk, &stdin)?; let pv_2 = deferred_proof_2.public_values.as_slice().to_vec().clone(); // Generate recursive proof of first subproof. tracing::info!("compress subproof 1"); - let deferred_reduce_1 = prover.compress(&keccak_vk, deferred_proof_1, vec![]); + let deferred_reduce_1 = prover.compress(&keccak_vk, deferred_proof_1, vec![])?; // Generate recursive proof of second subproof. tracing::info!("compress subproof 2"); - let deferred_reduce_2 = prover.compress(&keccak_vk, deferred_proof_2, vec![]); + let deferred_reduce_2 = prover.compress(&keccak_vk, deferred_proof_2, vec![])?; // Run verify program with keccak vkey, subproofs, and their committed values. let mut stdin = SP1Stdin::new(); @@ -761,7 +843,7 @@ mod tests { stdin.write_proof(deferred_reduce_2.proof.clone(), keccak_vk.vk.clone()); tracing::info!("proving verify program (core)"); - let verify_proof = prover.prove_core(&verify_pk, &stdin); + let verify_proof = prover.prove_core(&verify_pk, &stdin)?; // Generate recursive proof of verify program tracing::info!("compress verify program"); @@ -773,15 +855,15 @@ mod tests { deferred_reduce_2.proof.clone(), deferred_reduce_2.proof, ], - ); + )?; let reduce_pv: &RecursionPublicValues<_> = verify_reduce.proof.public_values.as_slice().borrow(); println!("deferred_hash: {:?}", reduce_pv.deferred_proofs_digest); println!("complete: {:?}", reduce_pv.is_complete); tracing::info!("verify verify program"); - prover - .verify_compressed(&verify_reduce, &verify_vk) - .unwrap(); + prover.verify_compressed(&verify_reduce, &verify_vk)?; + + Ok(()) } } diff --git a/prover/src/types.rs b/prover/src/types.rs index 547eac7707..e3aa597bd9 100644 --- a/prover/src/types.rs +++ b/prover/src/types.rs @@ -16,6 +16,7 @@ use sp1_core::{ use sp1_primitives::poseidon2_hash; use sp1_recursion_core::{air::RecursionPublicValues, stark::config::BabyBearPoseidon2Outer}; use sp1_recursion_gnark_ffi::{plonk_bn254::PlonkBn254Proof, Groth16Proof}; +use thiserror::Error; use crate::utils::words_to_bytes_be; use crate::{utils::babybear_bytes_to_bn254, words_to_bytes}; @@ -30,7 +31,7 @@ pub struct SP1ProvingKey { } /// The information necessary to verify a proof for a given RISC-V program. -#[derive(Clone)] +#[derive(Clone, Serialize, Deserialize)] pub struct SP1VerifyingKey { pub vk: StarkVerifyingKey, } @@ -159,7 +160,7 @@ pub struct SP1Groth16ProofData(pub Groth16Proof); pub struct SP1PlonkProofData(pub PlonkBn254Proof); /// An intermediate proof which proves the execution over a range of shards. -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Clone)] #[serde(bound(serialize = "ShardProof: Serialize"))] #[serde(bound(deserialize = "ShardProof: Deserialize<'de>"))] pub struct SP1ReduceProof { @@ -189,8 +190,11 @@ impl SP1ReduceProof { } /// A proof that can be reduced along with other proofs into one proof. -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Clone)] pub enum SP1ReduceProofWrapper { Core(SP1ReduceProof), Recursive(SP1ReduceProof), } + +#[derive(Error, Debug)] +pub enum SP1RecursionProverError {} diff --git a/prover/src/utils.rs b/prover/src/utils.rs index 8983efdefe..785b376079 100644 --- a/prover/src/utils.rs +++ b/prover/src/utils.rs @@ -3,6 +3,7 @@ use std::{ io::Read, }; +use futures::Future; use p3_baby_bear::BabyBear; use p3_bn254_fr::Bn254Fr; use p3_field::AbstractField; @@ -12,6 +13,7 @@ use sp1_core::{ io::SP1Stdin, runtime::{Program, Runtime}, }; +use tokio::{runtime, task::block_in_place}; use crate::SP1CoreProofData; @@ -30,7 +32,7 @@ pub fn get_cycles(elf: &[u8], stdin: &SP1Stdin) -> u64 { let program = Program::from(elf); let mut runtime = Runtime::new(program); runtime.write_vecs(&stdin.buffer); - runtime.run(); + runtime.dry_run(); runtime.state.global_clk } @@ -84,3 +86,16 @@ pub fn words_to_bytes_be(words: &[u32; 8]) -> [u8; 32] { } bytes } + +/// Utility method for blocking on an async function. If we're already in a tokio runtime, we'll +/// block in place. Otherwise, we'll create a new runtime. +pub fn block_on(fut: impl Future) -> T { + // Handle case if we're already in an tokio runtime. + if let Ok(handle) = runtime::Handle::try_current() { + block_in_place(|| handle.block_on(fut)) + } else { + // Otherwise create a new runtime. + let rt = runtime::Runtime::new().expect("Failed to create a new runtime"); + rt.block_on(fut) + } +} diff --git a/prover/src/verify.rs b/prover/src/verify.rs index 9d66971e2a..62f12c7e9b 100644 --- a/prover/src/verify.rs +++ b/prover/src/verify.rs @@ -1,19 +1,31 @@ -use std::borrow::Borrow; +use std::{borrow::Borrow, path::Path, str::FromStr}; use anyhow::Result; +use num_bigint::BigUint; use p3_baby_bear::BabyBear; -use p3_field::AbstractField; +use p3_field::{AbstractField, PrimeField}; use sp1_core::{ air::PublicValues, + io::SP1PublicValues, stark::{MachineProof, MachineVerificationError, StarkGenericConfig}, utils::BabyBearPoseidon2, }; use sp1_recursion_core::{air::RecursionPublicValues, stark::config::BabyBearPoseidon2Outer}; +use sp1_recursion_gnark_ffi::{Groth16Proof, Groth16Prover}; +use thiserror::Error; use crate::{ CoreSC, HashableKey, OuterSC, SP1CoreProofData, SP1Prover, SP1ReduceProof, SP1VerifyingKey, }; +#[derive(Error, Debug)] +pub enum Groth16VerificationError { + #[error("the verifying key does not match the inner groth16 proof's committed verifying key")] + InvalidVerificationKey, + #[error("the public values in the sp1 proof do not match the public values in the inner groth16 proof")] + InvalidPublicValues, +} + impl SP1Prover { /// Verify a core proof by verifying the shards, verifying lookup bus, verifying that the /// shards are contiguous and complete. @@ -199,4 +211,47 @@ impl SP1Prover { Ok(()) } + + /// Verifies a Groth16 proof using the circuit artifacts in the build directory. + pub fn verify_groth16( + &self, + proof: &Groth16Proof, + vk: &SP1VerifyingKey, + public_values: &SP1PublicValues, + build_dir: &Path, + ) -> Result<()> { + let prover = Groth16Prover::new(); + + let vkey_hash = BigUint::from_str(&proof.public_inputs[0])?; + let committed_values_digest = BigUint::from_str(&proof.public_inputs[1])?; + + // Verify the proof with the corresponding public inputs. + prover.verify(proof, &vkey_hash, &committed_values_digest, build_dir); + + verify_groth16_public_inputs(vk, public_values, &proof.public_inputs)?; + + Ok(()) + } +} + +/// Verify the vk_hash and public_values_hash in the public inputs of the Groth16Proof match the expected values. +pub fn verify_groth16_public_inputs( + vk: &SP1VerifyingKey, + public_values: &SP1PublicValues, + groth16_public_inputs: &[String], +) -> Result<()> { + let expected_vk_hash = BigUint::from_str(&groth16_public_inputs[0])?; + let expected_public_values_hash = BigUint::from_str(&groth16_public_inputs[1])?; + + let vk_hash = vk.hash_bn254().as_canonical_biguint(); + if vk_hash != expected_vk_hash { + return Err(Groth16VerificationError::InvalidVerificationKey.into()); + } + + let public_values_hash = public_values.hash(); + if public_values_hash != expected_public_values_hash { + return Err(Groth16VerificationError::InvalidPublicValues.into()); + } + + Ok(()) } diff --git a/recursion/compiler/src/asm/compiler.rs b/recursion/compiler/src/asm/compiler.rs index eb1ab9820d..da94fd9d92 100644 --- a/recursion/compiler/src/asm/compiler.rs +++ b/recursion/compiler/src/asm/compiler.rs @@ -529,6 +529,9 @@ impl + TwoAdicField> AsmCo DslIr::Commit(val, index) => { self.push(AsmInstruction::Commit(val.fp(), index.fp()), trace); } + DslIr::RegisterPublicValue(val) => { + self.push(AsmInstruction::RegisterPublicValue(val.fp()), trace); + } DslIr::LessThan(dst, left, right) => { self.push( AsmInstruction::LessThan(dst.fp(), left.fp(), right.fp()), diff --git a/recursion/compiler/src/asm/instruction.rs b/recursion/compiler/src/asm/instruction.rs index ab89fd4a78..c7083c81c9 100644 --- a/recursion/compiler/src/asm/instruction.rs +++ b/recursion/compiler/src/asm/instruction.rs @@ -174,6 +174,9 @@ pub enum AsmInstruction { // Commit(val, index). Commit(i32, i32), + // RegisterPublicValue(val). + RegisterPublicValue(i32), + LessThan(i32, i32, i32), CycleTracker(String), @@ -849,6 +852,17 @@ impl> AsmInstruction { true, "".to_string(), ), + AsmInstruction::RegisterPublicValue(val) => Instruction::new( + Opcode::RegisterPublicValue, + i32_f(val), + f_u32(F::zero()), + f_u32(F::zero()), + F::zero(), + F::zero(), + false, + true, + "".to_string(), + ), } } @@ -1115,6 +1129,9 @@ impl> AsmInstruction { AsmInstruction::Commit(val, index) => { write!(f, "commit ({})fp ({})fp", val, index) } + AsmInstruction::RegisterPublicValue(val) => { + write!(f, "register_public_value ({})fp", val) + } AsmInstruction::CycleTracker(name) => { write!(f, "cycle-tracker {}", name) } diff --git a/recursion/compiler/src/ir/builder.rs b/recursion/compiler/src/ir/builder.rs index 4a06dca938..f3bc11b386 100644 --- a/recursion/compiler/src/ir/builder.rs +++ b/recursion/compiler/src/ir/builder.rs @@ -424,7 +424,12 @@ impl Builder { } } - /// Commits a felt in public values. + /// Register a felt as public value. This is append to the proof's public values buffer. + pub fn register_public_value(&mut self, val: Felt) { + self.operations.push(DslIr::RegisterPublicValue(val)); + } + + /// Register and commits a felt as public value. This value will be constrained when verified. pub fn commit_public_value(&mut self, val: Felt) { if self.nb_public_values.is_none() { self.nb_public_values = Some(self.eval(C::N::zero())); diff --git a/recursion/compiler/src/ir/instructions.rs b/recursion/compiler/src/ir/instructions.rs index 1e79528029..ee717b1f13 100644 --- a/recursion/compiler/src/ir/instructions.rs +++ b/recursion/compiler/src/ir/instructions.rs @@ -155,6 +155,7 @@ pub enum DslIr { WitnessFelt(Felt, u32), WitnessExt(Ext, u32), Commit(Felt, Var), + RegisterPublicValue(Felt), Halt, // Public inputs for circuits. diff --git a/recursion/core/src/air/block.rs b/recursion/core/src/air/block.rs index 7987c4efc7..5cc89add8c 100644 --- a/recursion/core/src/air/block.rs +++ b/recursion/core/src/air/block.rs @@ -2,7 +2,6 @@ use p3_air::AirBuilder; use p3_field::AbstractField; use p3_field::ExtensionField; use p3_field::Field; -use p3_field::PrimeField32; use serde::{Deserialize, Serialize}; use sp1_core::air::ExtensionAirBuilder; use sp1_core::air::{BinomialExtension, SP1AirBuilder}; @@ -74,9 +73,9 @@ impl From<[T; D]> for Block { } } -impl From for Block { - fn from(value: F) -> Self { - Self([value, F::zero(), F::zero(), F::zero()]) +impl From for Block { + fn from(value: T) -> Self { + Self([value, T::zero(), T::zero(), T::zero()]) } } diff --git a/recursion/core/src/air/public_values.rs b/recursion/core/src/air/public_values.rs index d84af5e905..97bcf0b940 100644 --- a/recursion/core/src/air/public_values.rs +++ b/recursion/core/src/air/public_values.rs @@ -8,10 +8,11 @@ use serde::{Deserialize, Serialize}; use sp1_core::{ air::{Word, POSEIDON_NUM_WORDS}, stark::PROOF_MAX_NUM_PVS, + utils::indices_arr, }; use sp1_derive::AlignedBorrow; use static_assertions::const_assert_eq; -use std::mem::size_of; +use std::mem::{size_of, transmute}; pub const PV_DIGEST_NUM_WORDS: usize = 8; @@ -19,6 +20,18 @@ pub const CHALLENGER_STATE_NUM_ELTS: usize = 50; pub const RECURSIVE_PROOF_NUM_PV_ELTS: usize = size_of::>(); +const fn make_col_map() -> RecursionPublicValues { + let indices_arr = indices_arr::(); + unsafe { + transmute::<[usize; RECURSIVE_PROOF_NUM_PV_ELTS], RecursionPublicValues>(indices_arr) + } +} + +pub const RECURSION_PUBLIC_VALUES_COL_MAP: RecursionPublicValues = make_col_map(); + +// All the fields before `digest` are hashed to produce the digest. +pub const NUM_PV_ELMS_TO_HASH: usize = RECURSION_PUBLIC_VALUES_COL_MAP.digest[0]; + // Recursive proof has more public values than core proof, so the max number constant defined in // sp1_core should be set to `RECURSIVE_PROOF_NUM_PV_ELTS`. const_assert_eq!(RECURSIVE_PROOF_NUM_PV_ELTS, PROOF_MAX_NUM_PVS); @@ -99,4 +112,7 @@ pub struct RecursionPublicValues { /// Whether the proof completely proves the program execution. pub is_complete: T, + + /// The digest of all the public values elements. + pub digest: [T; DIGEST_SIZE], } diff --git a/recursion/core/src/cpu/air/mod.rs b/recursion/core/src/cpu/air/mod.rs index 719d0031da..132c7e03cb 100644 --- a/recursion/core/src/cpu/air/mod.rs +++ b/recursion/core/src/cpu/air/mod.rs @@ -3,6 +3,7 @@ mod branch; mod jump; mod memory; mod operands; +mod public_values; mod system; use std::borrow::Borrow; @@ -13,7 +14,7 @@ use p3_matrix::Matrix; use sp1_core::air::BaseAirBuilder; use crate::{ - air::SP1RecursionAirBuilder, + air::{RecursionPublicValues, SP1RecursionAirBuilder, RECURSIVE_PROOF_NUM_PV_ELTS}, cpu::{CpuChip, CpuCols}, memory::MemoryCols, }; @@ -27,6 +28,11 @@ where let (local, next) = (main.row_slice(0), main.row_slice(1)); let local: &CpuCols = (*local).borrow(); let next: &CpuCols = (*next).borrow(); + let pv = builder.public_values(); + let pv_elms: [AB::Expr; RECURSIVE_PROOF_NUM_PV_ELTS] = + core::array::from_fn(|i| pv[i].into()); + let public_values: &RecursionPublicValues = pv_elms.as_slice().borrow(); + let zero = AB::Expr::zero(); let one = AB::Expr::one(); @@ -74,6 +80,9 @@ where ]; builder.send_table(local.instruction.opcode, &operands, send_syscall); + // Constrain the public values digest. + self.eval_commit(builder, local, public_values.digest.clone()); + // Constrain the clk. self.eval_clk(builder, local, next); @@ -175,4 +184,12 @@ impl CpuChip { + local.selectors.is_poseidon + local.selectors.is_store } + + /// Expr to check for instructions that are commit instructions. + pub fn is_commit_instruction(&self, local: &CpuCols) -> AB::Expr + where + AB: SP1RecursionAirBuilder, + { + local.selectors.is_commit.into() + } } diff --git a/recursion/core/src/cpu/air/public_values.rs b/recursion/core/src/cpu/air/public_values.rs new file mode 100644 index 0000000000..4f1a9c924c --- /dev/null +++ b/recursion/core/src/cpu/air/public_values.rs @@ -0,0 +1,61 @@ +use p3_air::AirBuilder; +use p3_field::{AbstractField, Field}; + +use crate::{ + air::{BlockBuilder, SP1RecursionAirBuilder}, + cpu::{CpuChip, CpuCols}, + memory::MemoryCols, + runtime::DIGEST_SIZE, +}; + +impl CpuChip { + /// Eval the COMMIT instructions. + /// + /// This method will verify the committed public value. + pub fn eval_commit( + &self, + builder: &mut AB, + local: &CpuCols, + commit_digest: [AB::Expr; DIGEST_SIZE], + ) where + AB: SP1RecursionAirBuilder, + { + let public_values_cols = local.opcode_specific.public_values(); + let is_commit_instruction = self.is_commit_instruction::(local); + + // Verify all elements in the index bitmap are bools. + let mut bitmap_sum = AB::Expr::zero(); + for bit in public_values_cols.idx_bitmap.iter() { + builder + .when(is_commit_instruction.clone()) + .assert_bool(*bit); + bitmap_sum += (*bit).into(); + } + // When the instruction is COMMIT there should be exactly one set bit. + builder + .when(is_commit_instruction.clone()) + .assert_one(bitmap_sum.clone()); + + // Verify that idx passed in the b operand corresponds to the set bit in index bitmap. + for (i, bit) in public_values_cols.idx_bitmap.iter().enumerate() { + builder + .when(*bit * is_commit_instruction.clone()) + .assert_block_eq( + *local.b.prev_value(), + AB::Expr::from_canonical_u32(i as u32).into(), + ); + } + + // Calculated the expected public value. + let expected_pv_digest_element = + builder.index_array(&commit_digest, &public_values_cols.idx_bitmap); + + // Get the committed public value in the program from operand a. + let digest_element = local.a.prev_value(); + + // Verify the public value element. + builder + .when(is_commit_instruction.clone()) + .assert_block_eq(expected_pv_digest_element.into(), *digest_element); + } +} diff --git a/recursion/core/src/cpu/columns/mod.rs b/recursion/core/src/cpu/columns/mod.rs index 6449c176e8..c7eae3cbea 100644 --- a/recursion/core/src/cpu/columns/mod.rs +++ b/recursion/core/src/cpu/columns/mod.rs @@ -10,6 +10,7 @@ mod instruction; mod memory; mod opcode; mod opcode_specific; +mod public_values; pub use instruction::*; pub use opcode::*; diff --git a/recursion/core/src/cpu/columns/opcode.rs b/recursion/core/src/cpu/columns/opcode.rs index faf78d58ab..fcea11ce71 100644 --- a/recursion/core/src/cpu/columns/opcode.rs +++ b/recursion/core/src/cpu/columns/opcode.rs @@ -71,6 +71,7 @@ impl OpcodeSelectorCols { Opcode::PrintF => self.is_noop = F::one(), Opcode::PrintE => self.is_noop = F::one(), Opcode::Commit => self.is_commit = F::one(), + Opcode::RegisterPublicValue => self.is_noop = F::one(), _ => {} } diff --git a/recursion/core/src/cpu/columns/opcode_specific.rs b/recursion/core/src/cpu/columns/opcode_specific.rs index 14124f5da7..1b577cde2d 100644 --- a/recursion/core/src/cpu/columns/opcode_specific.rs +++ b/recursion/core/src/cpu/columns/opcode_specific.rs @@ -3,6 +3,7 @@ use std::mem::{size_of, transmute}; use super::branch::BranchCols; use super::memory::MemoryCols; +use super::public_values::PublicValuesCols; pub const NUM_OPCODE_SPECIFIC_COLS: usize = size_of::>(); @@ -12,6 +13,7 @@ pub const NUM_OPCODE_SPECIFIC_COLS: usize = size_of::>(); pub union OpcodeSpecificCols { branch: BranchCols, memory: MemoryCols, + public_values: PublicValuesCols, } impl Default for OpcodeSpecificCols { @@ -46,4 +48,12 @@ impl OpcodeSpecificCols { pub fn memory_mut(&mut self) -> &mut MemoryCols { unsafe { &mut self.memory } } + + pub fn public_values(&self) -> &PublicValuesCols { + unsafe { &self.public_values } + } + + pub fn public_values_mut(&mut self) -> &mut PublicValuesCols { + unsafe { &mut self.public_values } + } } diff --git a/recursion/core/src/cpu/columns/public_values.rs b/recursion/core/src/cpu/columns/public_values.rs new file mode 100644 index 0000000000..a7cc631044 --- /dev/null +++ b/recursion/core/src/cpu/columns/public_values.rs @@ -0,0 +1,13 @@ +use sp1_derive::AlignedBorrow; +use std::mem::size_of; + +use crate::runtime::DIGEST_SIZE; + +#[allow(dead_code)] +pub const NUM_PUBLIC_VALUES_COLS: usize = size_of::>(); + +#[derive(AlignedBorrow, Default, Debug, Clone, Copy)] +#[repr(C)] +pub struct PublicValuesCols { + pub(crate) idx_bitmap: [T; DIGEST_SIZE], +} diff --git a/recursion/core/src/cpu/trace.rs b/recursion/core/src/cpu/trace.rs index fbab723230..318d291943 100644 --- a/recursion/core/src/cpu/trace.rs +++ b/recursion/core/src/cpu/trace.rs @@ -98,6 +98,13 @@ impl> MachineAir for CpuChip { }; } + // Populate the public values columns. + if event.instruction.opcode == Opcode::Commit { + let public_values_cols = cols.opcode_specific.public_values_mut(); + let idx = cols.b.prev_value()[0].as_canonical_u32() as usize; + public_values_cols.idx_bitmap[idx] = F::one(); + } + cols.is_real = F::one(); row }) diff --git a/recursion/core/src/poseidon2/external.rs b/recursion/core/src/poseidon2/external.rs index a6124d798d..72e0cd51f0 100644 --- a/recursion/core/src/poseidon2/external.rs +++ b/recursion/core/src/poseidon2/external.rs @@ -42,24 +42,26 @@ impl Poseidon2Chip { receive_table: AB::Var, memory_access: AB::Expr, ) { - let rounds_f = 8; - let rounds_p = 13; - let rounds_p_beginning = 2 + rounds_f / 2; - let rounds_p_end = rounds_p_beginning + rounds_p; + const NUM_ROUNDS_F: usize = 8; + const NUM_ROUNDS_P: usize = 13; + const ROUNDS_F_1_BEGINNING: usize = 2; // Previous rounds are memory read and initial. + const ROUNDS_P_BEGINNING: usize = ROUNDS_F_1_BEGINNING + NUM_ROUNDS_F / 2; + const ROUNDS_P_END: usize = ROUNDS_P_BEGINNING + NUM_ROUNDS_P; + const ROUND_F_2_END: usize = ROUNDS_P_END + NUM_ROUNDS_F / 2; let is_memory_read = local.rounds[0]; let is_initial = local.rounds[1]; // First half of the external rounds. - let mut is_external_layer = (2..rounds_p_beginning) + let mut is_external_layer = (ROUNDS_F_1_BEGINNING..ROUNDS_P_BEGINNING) .map(|i| local.rounds[i].into()) .sum::(); // Second half of the external rounds. - is_external_layer += (rounds_p_end..rounds_p + rounds_f) + is_external_layer += (ROUNDS_P_END..ROUND_F_2_END) .map(|i| local.rounds[i].into()) .sum::(); - let is_internal_layer = (rounds_p_beginning..rounds_p_end) + let is_internal_layer = (ROUNDS_P_BEGINNING..ROUNDS_P_END) .map(|i| local.rounds[i].into()) .sum::(); let is_memory_write = local.rounds[local.rounds.len() - 1]; @@ -80,7 +82,7 @@ impl Poseidon2Chip { is_initial.into(), is_external_layer.clone(), is_internal_layer.clone(), - rounds_f + rounds_p + 1, + NUM_ROUNDS_F + NUM_ROUNDS_P + 1, ); self.eval_syscall(builder, local, receive_table); @@ -413,39 +415,17 @@ mod tests { } } - #[test] - fn prove_babybear() { - let config = BabyBearPoseidon2::compressed(); - let mut challenger = config.challenger(); - - let chip = Poseidon2Chip { - fixed_log2_rows: None, - }; - let rng = &mut rand::thread_rng(); - - let test_inputs: Vec<[BabyBear; 16]> = (0..16) - .map(|_| core::array::from_fn(|_| BabyBear::rand(rng))) - .collect_vec(); - - let gt: Poseidon2< - BabyBear, - Poseidon2ExternalMatrixGeneral, - DiffusionMatrixBabyBear, - 16, - 7, - > = inner_perm(); - - let expected_outputs = test_inputs - .iter() - .map(|input| gt.permute(*input)) - .collect::>(); - + fn prove_babybear(inputs: Vec<[BabyBear; 16]>, outputs: Vec<[BabyBear; 16]>) { let mut input_exec = ExecutionRecord::::default(); - for (input, output) in test_inputs.into_iter().zip_eq(expected_outputs) { + for (input, output) in inputs.into_iter().zip_eq(outputs) { input_exec .poseidon2_events .push(Poseidon2Event::dummy_from_input(input, output)); } + + let chip = Poseidon2Chip { + fixed_log2_rows: None, + }; let trace: RowMajorMatrix = chip.generate_trace(&input_exec, &mut ExecutionRecord::::default()); println!( @@ -455,6 +435,8 @@ mod tests { ); let start = Instant::now(); + let config = BabyBearPoseidon2::compressed(); + let mut challenger = config.challenger(); let proof = uni_stark_prove(&config, &chip, &mut challenger, trace); let duration = start.elapsed().as_secs_f64(); println!("proof duration = {:?}", duration); @@ -471,4 +453,43 @@ mod tests { let duration = start.elapsed().as_secs_f64(); println!("verify duration = {:?}", duration); } + + #[test] + fn prove_babybear_success() { + let rng = &mut rand::thread_rng(); + + let test_inputs: Vec<[BabyBear; 16]> = (0..16) + .map(|_| core::array::from_fn(|_| BabyBear::rand(rng))) + .collect_vec(); + + let gt: Poseidon2< + BabyBear, + Poseidon2ExternalMatrixGeneral, + DiffusionMatrixBabyBear, + 16, + 7, + > = inner_perm(); + + let expected_outputs = test_inputs + .iter() + .map(|input| gt.permute(*input)) + .collect::>(); + + prove_babybear(test_inputs, expected_outputs) + } + + #[test] + #[should_panic] + fn prove_babybear_failure() { + let rng = &mut rand::thread_rng(); + let test_inputs: Vec<[BabyBear; 16]> = (0..16) + .map(|_| core::array::from_fn(|_| BabyBear::rand(rng))) + .collect_vec(); + + let bad_outputs: Vec<[BabyBear; 16]> = (0..16) + .map(|_| core::array::from_fn(|_| BabyBear::rand(rng))) + .collect_vec(); + + prove_babybear(test_inputs, bad_outputs) + } } diff --git a/recursion/core/src/poseidon2_wide/external.rs b/recursion/core/src/poseidon2_wide/external.rs index 4525f23048..5438e81be7 100644 --- a/recursion/core/src/poseidon2_wide/external.rs +++ b/recursion/core/src/poseidon2_wide/external.rs @@ -454,12 +454,12 @@ mod tests { use p3_baby_bear::{BabyBear, DiffusionMatrixBabyBear}; use p3_field::AbstractField; use p3_matrix::dense::RowMajorMatrix; - use p3_matrix::Matrix; use p3_poseidon2::{Poseidon2, Poseidon2ExternalMatrixGeneral}; use p3_symmetric::Permutation; use sp1_core::air::MachineAir; use sp1_core::stark::StarkGenericConfig; use sp1_core::utils::{inner_perm, uni_stark_prove, uni_stark_verify, BabyBearPoseidon2}; + use zkhash::ark_ff::UniformRand; fn generate_trace_degree() { let chip = Poseidon2WideChip:: { @@ -504,46 +504,24 @@ mod tests { generate_trace_degree::<7>(); } - fn poseidon2_wide_prove_babybear_degree() { - const DEGREE: usize = 7; - - let config = BabyBearPoseidon2::compressed(); - let mut challenger = config.challenger(); - + fn poseidon2_wide_prove_babybear_degree( + inputs: Vec<[BabyBear; 16]>, + outputs: Vec<[BabyBear; 16]>, + ) { let chip = Poseidon2WideChip:: { fixed_log2_rows: None, }; - - let test_inputs = (0..1000) - .map(|i| [BabyBear::from_canonical_u32(i); WIDTH]) - .collect_vec(); - - let gt: Poseidon2< - BabyBear, - Poseidon2ExternalMatrixGeneral, - DiffusionMatrixBabyBear, - 16, - 7, - > = inner_perm(); - - let expected_outputs = test_inputs - .iter() - .map(|input| gt.permute(*input)) - .collect::>(); - let mut input_exec = ExecutionRecord::::default(); - for (input, output) in test_inputs.into_iter().zip_eq(expected_outputs) { + for (input, output) in inputs.into_iter().zip_eq(outputs) { input_exec .poseidon2_events .push(Poseidon2Event::dummy_from_input(input, output)); } let trace: RowMajorMatrix = chip.generate_trace(&input_exec, &mut ExecutionRecord::::default()); - println!( - "trace dims is width: {:?}, height: {:?}", - trace.width(), - trace.height() - ); + + let config = BabyBearPoseidon2::compressed(); + let mut challenger = config.challenger(); let start = Instant::now(); let proof = uni_stark_prove(&config, &chip, &mut challenger, trace); @@ -560,8 +538,44 @@ mod tests { } #[test] - fn poseidon2_wide_prove_babybear() { - poseidon2_wide_prove_babybear_degree::<3>(); - poseidon2_wide_prove_babybear_degree::<7>(); + fn poseidon2_wide_prove_babybear_success() { + let rng = &mut rand::thread_rng(); + + let test_inputs: Vec<[BabyBear; 16]> = (0..1000) + .map(|_| core::array::from_fn(|_| BabyBear::rand(rng))) + .collect_vec(); + + let gt: Poseidon2< + BabyBear, + Poseidon2ExternalMatrixGeneral, + DiffusionMatrixBabyBear, + 16, + 7, + > = inner_perm(); + + let expected_outputs = test_inputs + .iter() + .map(|input| gt.permute(*input)) + .collect::>(); + + poseidon2_wide_prove_babybear_degree::<3>(test_inputs.clone(), expected_outputs.clone()); + poseidon2_wide_prove_babybear_degree::<7>(test_inputs, expected_outputs); + } + + #[test] + #[should_panic] + fn poseidon2_wide_prove_babybear_failure() { + let rng = &mut rand::thread_rng(); + + let test_inputs = (0..1000) + .map(|i| [BabyBear::from_canonical_u32(i); WIDTH]) + .collect_vec(); + + let bad_outputs: Vec<[BabyBear; 16]> = (0..1000) + .map(|_| core::array::from_fn(|_| BabyBear::rand(rng))) + .collect_vec(); + + poseidon2_wide_prove_babybear_degree::<3>(test_inputs.clone(), bad_outputs.clone()); + poseidon2_wide_prove_babybear_degree::<7>(test_inputs, bad_outputs); } } diff --git a/recursion/core/src/runtime/mod.rs b/recursion/core/src/runtime/mod.rs index 3d8b95c3a0..1fd9ca6a67 100644 --- a/recursion/core/src/runtime/mod.rs +++ b/recursion/core/src/runtime/mod.rs @@ -795,13 +795,9 @@ where next_clk = timestamp; (a, b, c) = (a_val, b_val, c_val); } - Opcode::Commit => { + // For both the Commit and RegisterPublicValue opcodes, we record the public value + Opcode::Commit | Opcode::RegisterPublicValue => { let (a_val, b_val, c_val) = self.all_rr(&instruction); - - // Ensure that writes are in order (index should == public_values.len) - let index = b_val[0].as_canonical_u32() as usize; - debug_assert_eq!(index, self.record.public_values.len()); - self.record.public_values.push(a_val[0]); (a, b, c) = (a_val, b_val, c_val); diff --git a/recursion/core/src/runtime/opcode.rs b/recursion/core/src/runtime/opcode.rs index c12ce58864..fb471e443d 100644 --- a/recursion/core/src/runtime/opcode.rs +++ b/recursion/core/src/runtime/opcode.rs @@ -47,8 +47,9 @@ pub enum Opcode { Hint = 38, BNEINC = 40, Commit = 41, - LessThanF = 42, - CycleTracker = 43, + RegisterPublicValue = 42, + LessThanF = 43, + CycleTracker = 44, } impl Opcode { diff --git a/recursion/gnark-ffi/Cargo.toml b/recursion/gnark-ffi/Cargo.toml index 737d8ca447..0c2d5d6c2c 100644 --- a/recursion/gnark-ffi/Cargo.toml +++ b/recursion/gnark-ffi/Cargo.toml @@ -5,6 +5,7 @@ edition = "2021" [dependencies] p3-field = { workspace = true } +p3-baby-bear = { workspace = true } sp1-recursion-compiler = { path = "../compiler" } serde = "1.0.201" serde_json = "1.0.117" @@ -14,3 +15,8 @@ rand = "0.8" crossbeam = "0.8" subtle-encoding = "0.5.1" log = "0.4.21" +num-bigint = "0.4.5" + +[build-dependencies] +bindgen = "0.69.4" +cc = "1.0" diff --git a/recursion/gnark-ffi/build.rs b/recursion/gnark-ffi/build.rs new file mode 100644 index 0000000000..0d91e1b970 --- /dev/null +++ b/recursion/gnark-ffi/build.rs @@ -0,0 +1,58 @@ +use std::env; +use std::path::PathBuf; +use std::process::Command; + +#[allow(deprecated)] +use bindgen::CargoCallbacks; + +/// Build the go library, generate Rust bindings for the exposed functions, and link the library. +fn main() { + println!("cargo:rerun-if-changed=go"); + // Define the output directory + let out_dir = env::var("OUT_DIR").unwrap(); + let dest_path = PathBuf::from(&out_dir); + let lib_name = "sp1gnark"; + let dest = dest_path.join(format!("lib{}.a", lib_name)); + + println!("Building Go library at {}", dest.display()); + + // Run the go build command + let status = Command::new("go") + .current_dir("go") + .env("CGO_ENABLED", "1") + .args([ + "build", + "-o", + dest.to_str().unwrap(), + "-buildmode=c-archive", + ".", + ]) + .status() + .expect("Failed to build Go library"); + if !status.success() { + panic!("Go build failed"); + } + + // Copy go/babybear.h to OUT_DIR/babybear.h + let header_src = PathBuf::from("go/babybear.h"); + let header_dest = dest_path.join("babybear.h"); + std::fs::copy(header_src, header_dest).unwrap(); + + // Generate bindings using bindgen + let header_path = dest_path.join(format!("lib{}.h", lib_name)); + let bindings = bindgen::Builder::default() + .header(header_path.to_str().unwrap()) + .parse_callbacks(Box::new(CargoCallbacks::new())) + .generate() + .expect("Unable to generate bindings"); + + bindings + .write_to_file(dest_path.join("bindings.rs")) + .expect("Couldn't write bindings!"); + + println!("Go library built"); + + // Link the Go library + println!("cargo:rustc-link-search=native={}", dest_path.display()); + println!("cargo:rustc-link-lib=static={}", lib_name); +} diff --git a/recursion/gnark/.gitignore b/recursion/gnark-ffi/go/.gitignore similarity index 100% rename from recursion/gnark/.gitignore rename to recursion/gnark-ffi/go/.gitignore diff --git a/recursion/gnark/lib/babybear.h b/recursion/gnark-ffi/go/babybear.h similarity index 100% rename from recursion/gnark/lib/babybear.h rename to recursion/gnark-ffi/go/babybear.h diff --git a/recursion/gnark/go.mod b/recursion/gnark-ffi/go/go.mod similarity index 90% rename from recursion/gnark/go.mod rename to recursion/gnark-ffi/go/go.mod index e33a31701f..8cfcfd6d7e 100644 --- a/recursion/gnark/go.mod +++ b/recursion/gnark-ffi/go/go.mod @@ -5,7 +5,7 @@ go 1.22 require ( github.com/consensys/gnark v0.10.1-0.20240504023521-d9bfacd7cb60 github.com/consensys/gnark-crypto v0.12.2-0.20240504013751-564b6f724c3b - github.com/pkg/errors v0.9.1 + github.com/spf13/cobra v1.8.0 ) require ( @@ -24,12 +24,10 @@ require ( github.com/pmezard/go-difflib v1.0.0 // indirect github.com/ronanh/intcomp v1.1.0 // indirect github.com/rs/zerolog v1.30.0 // indirect - github.com/spf13/cobra v1.8.0 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/stretchr/testify v1.8.4 // indirect github.com/x448/float16 v0.8.4 // indirect golang.org/x/crypto v0.17.0 // indirect - golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 // indirect golang.org/x/sync v0.3.0 // indirect golang.org/x/sys v0.15.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/recursion/gnark/go.sum b/recursion/gnark-ffi/go/go.sum similarity index 91% rename from recursion/gnark/go.sum rename to recursion/gnark-ffi/go/go.sum index 38e50473a0..a2d96911cf 100644 --- a/recursion/gnark/go.sum +++ b/recursion/gnark-ffi/go/go.sum @@ -4,12 +4,8 @@ github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= github.com/consensys/bavard v0.1.13 h1:oLhMLOFGTLdlda/kma4VOJazblc7IM5y5QPd2A/YjhQ= github.com/consensys/bavard v0.1.13/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI= -github.com/consensys/gnark v0.10.0 h1:yhi6ThoeFP7WrH8zQDaO56WVXe9iJEBSkfrZ9PZxabw= -github.com/consensys/gnark v0.10.0/go.mod h1:VJU5JrrhZorbfDH+EUjcuFWr2c5z19tHPh8D6KVQksU= github.com/consensys/gnark v0.10.1-0.20240504023521-d9bfacd7cb60 h1:+m2KO2BeqBkH1zfCy88z93144AnnD8boClw6d6sD2Ko= github.com/consensys/gnark v0.10.1-0.20240504023521-d9bfacd7cb60/go.mod h1:DU7zXvIuOqheiS3EgVdD7ydbXDiLh71FkaArWPxwJqY= -github.com/consensys/gnark-crypto v0.12.2-0.20240215234832-d72fcb379d3e h1:MKdOuCiy2DAX1tMp2YsmtNDaqdigpY6B5cZQDJ9BvEo= -github.com/consensys/gnark-crypto v0.12.2-0.20240215234832-d72fcb379d3e/go.mod h1:wKqwsieaKPThcFkHe0d0zMsbHEUWFmZcG7KBCse210o= github.com/consensys/gnark-crypto v0.12.2-0.20240504013751-564b6f724c3b h1:tu0NaVr64o6vXzy9rYSK/LCZXmS+u/k9eP1F8OtRUWQ= github.com/consensys/gnark-crypto v0.12.2-0.20240504013751-564b6f724c3b/go.mod h1:wKqwsieaKPThcFkHe0d0zMsbHEUWFmZcG7KBCse210o= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= @@ -46,7 +42,6 @@ github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY= github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU= github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -68,8 +63,6 @@ github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= -golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 h1:m64FZMko/V45gv0bNmrNYoDEq8U5YUhetc9cBWKS1TQ= -golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63/go.mod h1:0v4NqG35kSWCMzLaMeX+IQrlSnVE/bqGSyC2cz/9Le8= golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/recursion/gnark-ffi/go/main.go b/recursion/gnark-ffi/go/main.go new file mode 100644 index 0000000000..7e842d3d75 --- /dev/null +++ b/recursion/gnark-ffi/go/main.go @@ -0,0 +1,138 @@ +package main + +/* +#include "./babybear.h" + +typedef struct { + char *PublicInputs[2]; + char *EncodedProof; + char *RawProof; +} C_Groth16Proof; +*/ +import "C" +import ( + "encoding/json" + "fmt" + "os" + "sync" + + "github.com/consensys/gnark-crypto/ecc" + "github.com/consensys/gnark/backend/groth16" + "github.com/consensys/gnark/frontend" + "github.com/consensys/gnark/frontend/cs/r1cs" + "github.com/succinctlabs/sp1-recursion-gnark/sp1" +) + +func main() {} + +//export ProveGroth16 +func ProveGroth16(dataDir *C.char, witnessPath *C.char) *C.C_Groth16Proof { + dataDirString := C.GoString(dataDir) + witnessPathString := C.GoString(witnessPath) + + sp1Groth16Proof := sp1.ProveGroth16(dataDirString, witnessPathString) + + ms := C.malloc(C.sizeof_C_Groth16Proof) + if ms == nil { + return nil + } + + structPtr := (*C.C_Groth16Proof)(ms) + structPtr.PublicInputs[0] = C.CString(sp1Groth16Proof.PublicInputs[0]) + structPtr.PublicInputs[1] = C.CString(sp1Groth16Proof.PublicInputs[1]) + structPtr.EncodedProof = C.CString(sp1Groth16Proof.EncodedProof) + structPtr.RawProof = C.CString(sp1Groth16Proof.RawProof) + return structPtr +} + +//export BuildGroth16 +func BuildGroth16(dataDir *C.char) { + // Sanity check the required arguments have been provided. + dataDirString := C.GoString(dataDir) + + sp1.BuildGroth16(dataDirString) +} + +//export VerifyGroth16 +func VerifyGroth16(dataDir *C.char, proof *C.char, vkeyHash *C.char, commitedValuesDigest *C.char) *C.char { + dataDirString := C.GoString(dataDir) + proofString := C.GoString(proof) + vkeyHashString := C.GoString(vkeyHash) + commitedValuesDigestString := C.GoString(commitedValuesDigest) + + err := sp1.VerifyGroth16(dataDirString, proofString, vkeyHashString, commitedValuesDigestString) + if err != nil { + return C.CString(err.Error()) + } + return nil +} + +var testMutex = &sync.Mutex{} + +//export TestGroth16 +func TestGroth16(witnessPath *C.char, constraintsJson *C.char) *C.char { + // Because of the global env variables used here, we need to lock this function + testMutex.Lock() + witnessPathString := C.GoString(witnessPath) + constraintsJsonString := C.GoString(constraintsJson) + os.Setenv("WITNESS_JSON", witnessPathString) + os.Setenv("CONSTRAINTS_JSON", constraintsJsonString) + err := TestMain() + testMutex.Unlock() + if err != nil { + return C.CString(err.Error()) + } + return nil +} + +func TestMain() error { + // Get the file name from an environment variable. + fileName := os.Getenv("WITNESS_JSON") + if fileName == "" { + fileName = "witness.json" + } + + // Read the file. + data, err := os.ReadFile(fileName) + if err != nil { + return err + } + + // Deserialize the JSON data into a slice of Instruction structs + var inputs sp1.WitnessInput + err = json.Unmarshal(data, &inputs) + if err != nil { + return err + } + + // Compile the circuit. + circuit := sp1.NewCircuit(inputs) + builder := r1cs.NewBuilder + r1cs, err := frontend.Compile(ecc.BN254.ScalarField(), builder, &circuit) + if err != nil { + return err + } + fmt.Println("[sp1] groth16 verifier constraints:", r1cs.GetNbConstraints()) + + // Run the dummy setup. + var pk groth16.ProvingKey + pk, err = groth16.DummySetup(r1cs) + if err != nil { + return err + } + + // Generate witness. + assignment := sp1.NewCircuit(inputs) + witness, err := frontend.NewWitness(&assignment, ecc.BN254.ScalarField()) + if err != nil { + return err + } + + // Generate the proof. + _, err = groth16.Prove(r1cs, pk, witness) + if err != nil { + return err + } + + return nil +} diff --git a/recursion/gnark-ffi/go/main_test.go b/recursion/gnark-ffi/go/main_test.go new file mode 100644 index 0000000000..0a3ef8682e --- /dev/null +++ b/recursion/gnark-ffi/go/main_test.go @@ -0,0 +1,9 @@ +package main + +import ( + "testing" +) + +func TestCircuit(t *testing.T) { + TestMain() +} diff --git a/recursion/gnark/sp1/babybear/babybear.go b/recursion/gnark-ffi/go/sp1/babybear/babybear.go similarity index 99% rename from recursion/gnark/sp1/babybear/babybear.go rename to recursion/gnark-ffi/go/sp1/babybear/babybear.go index 0b5d539f2c..07ac880298 100644 --- a/recursion/gnark/sp1/babybear/babybear.go +++ b/recursion/gnark-ffi/go/sp1/babybear/babybear.go @@ -1,7 +1,7 @@ package babybear /* -#include "../../lib/babybear.h" +#include "../../babybear.h" */ import "C" diff --git a/recursion/gnark-ffi/go/sp1/build.go b/recursion/gnark-ffi/go/sp1/build.go new file mode 100644 index 0000000000..ccbfb5449d --- /dev/null +++ b/recursion/gnark-ffi/go/sp1/build.go @@ -0,0 +1,112 @@ +package sp1 + +import ( + "crypto/sha256" + "encoding/json" + "os" + + "github.com/consensys/gnark-crypto/ecc" + "github.com/consensys/gnark/backend" + "github.com/consensys/gnark/backend/groth16" + "github.com/consensys/gnark/frontend" + "github.com/consensys/gnark/frontend/cs/r1cs" +) + +func BuildGroth16(dataDir string) { + + // Set the enviroment variable for the constraints file. + // + // TODO: There might be some non-determinism if a single processe is running this command + // multiple times. + os.Setenv("CONSTRAINTS_JSON", dataDir+"/"+CONSTRAINTS_JSON_FILE) + + // Read the file. + witnessInputPath := dataDir + "/witness_groth16.json" + data, err := os.ReadFile(witnessInputPath) + if err != nil { + panic(err) + } + + // Deserialize the JSON data into a slice of Instruction structs + var witnessInput WitnessInput + err = json.Unmarshal(data, &witnessInput) + if err != nil { + panic(err) + } + + // Initialize the circuit. + circuit := NewCircuit(witnessInput) + + // Compile the circuit. + r1cs, err := frontend.Compile(ecc.BN254.ScalarField(), r1cs.NewBuilder, &circuit) + if err != nil { + panic(err) + } + + // Perform the trusted setup. + pk, vk, err := groth16.Setup(r1cs) + if err != nil { + panic(err) + } + + // Generate proof. + assignment := NewCircuit(witnessInput) + witness, err := frontend.NewWitness(&assignment, ecc.BN254.ScalarField()) + if err != nil { + panic(err) + } + proof, err := groth16.Prove(r1cs, pk, witness, backend.WithProverHashToFieldFunction(sha256.New())) + if err != nil { + panic(err) + } + + // Verify proof. + publicWitness, err := witness.Public() + if err != nil { + panic(err) + } + err = groth16.Verify(proof, vk, publicWitness, backend.WithVerifierHashToFieldFunction(sha256.New())) + if err != nil { + panic(err) + } + + // Create the build directory. + os.MkdirAll(dataDir, 0755) + + // Write the solidity verifier. + solidityVerifierFile, err := os.Create(dataDir + "/" + VERIFIER_CONTRACT_PATH) + if err != nil { + panic(err) + } + vk.ExportSolidity(solidityVerifierFile) + + // Write the R1CS. + r1csFile, err := os.Create(dataDir + "/" + CIRCUIT_PATH) + if err != nil { + panic(err) + } + defer r1csFile.Close() + _, err = r1cs.WriteTo(r1csFile) + if err != nil { + panic(err) + } + + // Write the verifier key. + vkFile, err := os.Create(dataDir + "/" + VK_PATH) + if err != nil { + panic(err) + } + defer vkFile.Close() + _, err = vk.WriteTo(vkFile) + if err != nil { + panic(err) + } + + // Write the proving key. + pkFile, err := os.Create(dataDir + "/" + PK_PATH) + if err != nil { + panic(err) + } + defer pkFile.Close() + pk.WriteDump(pkFile) +} diff --git a/recursion/gnark/sp1/poseidon2/constants.go b/recursion/gnark-ffi/go/sp1/poseidon2/constants.go similarity index 100% rename from recursion/gnark/sp1/poseidon2/constants.go rename to recursion/gnark-ffi/go/sp1/poseidon2/constants.go diff --git a/recursion/gnark/sp1/poseidon2/poseidon2.go b/recursion/gnark-ffi/go/sp1/poseidon2/poseidon2.go similarity index 100% rename from recursion/gnark/sp1/poseidon2/poseidon2.go rename to recursion/gnark-ffi/go/sp1/poseidon2/poseidon2.go diff --git a/recursion/gnark/sp1/poseidon2/poseidon2_test.go b/recursion/gnark-ffi/go/sp1/poseidon2/poseidon2_test.go similarity index 100% rename from recursion/gnark/sp1/poseidon2/poseidon2_test.go rename to recursion/gnark-ffi/go/sp1/poseidon2/poseidon2_test.go diff --git a/recursion/gnark/sp1/poseidon2/utils.go b/recursion/gnark-ffi/go/sp1/poseidon2/utils.go similarity index 100% rename from recursion/gnark/sp1/poseidon2/utils.go rename to recursion/gnark-ffi/go/sp1/poseidon2/utils.go diff --git a/recursion/gnark-ffi/go/sp1/prove.go b/recursion/gnark-ffi/go/sp1/prove.go new file mode 100644 index 0000000000..9db841feed --- /dev/null +++ b/recursion/gnark-ffi/go/sp1/prove.go @@ -0,0 +1,82 @@ +package sp1 + +import ( + "crypto/sha256" + "encoding/json" + "os" + + "github.com/consensys/gnark-crypto/ecc" + "github.com/consensys/gnark/backend" + "github.com/consensys/gnark/backend/groth16" + "github.com/consensys/gnark/frontend" +) + +func ProveGroth16(dataDir string, witnessPath string) Groth16Proof { + // Sanity check the required arguments have been provided. + if dataDir == "" { + panic("dataDirStr is required") + } + os.Setenv("CONSTRAINTS_JSON", dataDir+"/"+CONSTRAINTS_JSON_FILE) + + // Read the R1CS. + r1csFile, err := os.Open(dataDir + "/" + CIRCUIT_PATH) + if err != nil { + panic(err) + } + r1cs := groth16.NewCS(ecc.BN254) + r1cs.ReadFrom(r1csFile) + + // Read the proving key. + pkFile, err := os.Open(dataDir + "/" + PK_PATH) + if err != nil { + panic(err) + } + pk := groth16.NewProvingKey(ecc.BN254) + pk.ReadDump(pkFile) + + // Read the verifier key. + vkFile, err := os.Open(dataDir + "/" + VK_PATH) + if err != nil { + panic(err) + } + vk := groth16.NewVerifyingKey(ecc.BN254) + vk.ReadFrom(vkFile) + + // Read the file. + data, err := os.ReadFile(witnessPath) + if err != nil { + panic(err) + } + + // Deserialize the JSON data into a slice of Instruction structs + var witnessInput WitnessInput + err = json.Unmarshal(data, &witnessInput) + if err != nil { + panic(err) + } + + // Generate the witness. + assignment := NewCircuit(witnessInput) + witness, err := frontend.NewWitness(&assignment, ecc.BN254.ScalarField()) + if err != nil { + panic(err) + } + publicWitness, err := witness.Public() + if err != nil { + panic(err) + } + + // Generate the proof. + proof, err := groth16.Prove(r1cs, pk, witness, backend.WithProverHashToFieldFunction(sha256.New())) + if err != nil { + panic(err) + } + + // Verify proof. + err = groth16.Verify(proof, vk, publicWitness, backend.WithVerifierHashToFieldFunction(sha256.New())) + if err != nil { + panic(err) + } + + return NewSP1Groth16Proof(&proof, witnessInput) +} diff --git a/recursion/gnark/sp1/sp1.go b/recursion/gnark-ffi/go/sp1/sp1.go similarity index 95% rename from recursion/gnark/sp1/sp1.go rename to recursion/gnark-ffi/go/sp1/sp1.go index 045453abc1..a2e20a1138 100644 --- a/recursion/gnark/sp1/sp1.go +++ b/recursion/gnark-ffi/go/sp1/sp1.go @@ -11,6 +11,13 @@ import ( "github.com/succinctlabs/sp1-recursion-gnark/sp1/poseidon2" ) +var CONSTRAINTS_JSON_FILE string = "constraints_groth16.json" +var WITNESS_JSON_FILE string = "witness_groth16.json" +var VERIFIER_CONTRACT_PATH string = "SP1Verifier.sol" +var CIRCUIT_PATH string = "circuit_groth16.bin" +var VK_PATH string = "vk_groth16.bin" +var PK_PATH string = "pk_groth16.bin" + type Circuit struct { VkeyHash frontend.Variable `gnark:",public"` CommitedValuesDigest frontend.Variable `gnark:",public"` diff --git a/recursion/gnark/sp1/utils.go b/recursion/gnark-ffi/go/sp1/utils.go similarity index 100% rename from recursion/gnark/sp1/utils.go rename to recursion/gnark-ffi/go/sp1/utils.go diff --git a/recursion/gnark-ffi/go/sp1/verify.go b/recursion/gnark-ffi/go/sp1/verify.go new file mode 100644 index 0000000000..c5ed991ff6 --- /dev/null +++ b/recursion/gnark-ffi/go/sp1/verify.go @@ -0,0 +1,60 @@ +package sp1 + +import ( + "bytes" + "crypto/sha256" + "encoding/hex" + "os" + + "github.com/consensys/gnark-crypto/ecc" + "github.com/consensys/gnark/backend" + "github.com/consensys/gnark/backend/groth16" + "github.com/consensys/gnark/frontend" + "github.com/succinctlabs/sp1-recursion-gnark/sp1/babybear" +) + +func VerifyGroth16(verifyCmdDataDir string, verifyCmdProof string, verifyCmdVkeyHash string, verifyCmdCommitedValuesDigest string) error { + // Sanity check the required arguments have been provided. + if verifyCmdDataDir == "" { + panic("--data is required") + } + + // Decode the proof. + proofDecodedBytes, err := hex.DecodeString(verifyCmdProof) + if err != nil { + panic(err) + } + proof := groth16.NewProof(ecc.BN254) + if _, err := proof.ReadFrom(bytes.NewReader(proofDecodedBytes)); err != nil { + panic(err) + } + + // Read the verifier key. + vkFile, err := os.Open(verifyCmdDataDir + "/" + VK_PATH) + if err != nil { + panic(err) + } + vk := groth16.NewVerifyingKey(ecc.BN254) + vk.ReadFrom(vkFile) + + // Compute the public witness. + circuit := Circuit{ + Vars: []frontend.Variable{}, + Felts: []babybear.Variable{}, + Exts: []babybear.ExtensionVariable{}, + VkeyHash: verifyCmdVkeyHash, + CommitedValuesDigest: verifyCmdCommitedValuesDigest, + } + witness, err := frontend.NewWitness(&circuit, ecc.BN254.ScalarField()) + if err != nil { + panic(err) + } + publicWitness, err := witness.Public() + if err != nil { + panic(err) + } + + // Verify proof. + err = groth16.Verify(proof, vk, publicWitness, backend.WithVerifierHashToFieldFunction(sha256.New())) + return err +} diff --git a/recursion/gnark/lib/babybear/src/lib.rs b/recursion/gnark-ffi/src/babybear.rs similarity index 91% rename from recursion/gnark/lib/babybear/src/lib.rs rename to recursion/gnark-ffi/src/babybear.rs index 7effbb64a4..b0defcc5f8 100644 --- a/recursion/gnark/lib/babybear/src/lib.rs +++ b/recursion/gnark-ffi/src/babybear.rs @@ -1,6 +1,3 @@ -extern crate p3_baby_bear; -extern crate p3_field; - use p3_baby_bear::BabyBear; use p3_field::PrimeField32; use p3_field::{extension::BinomialExtensionField, AbstractExtensionField, AbstractField, Field}; @@ -24,7 +21,7 @@ pub extern "C" fn babybearinv(a: u32) -> u32 { #[cfg(test)] pub mod test { - use crate::babybearextinv; + use super::babybearextinv; #[test] fn test_babybearextinv() { diff --git a/recursion/gnark-ffi/src/ffi.rs b/recursion/gnark-ffi/src/ffi.rs new file mode 100644 index 0000000000..9edc9d11a7 --- /dev/null +++ b/recursion/gnark-ffi/src/ffi.rs @@ -0,0 +1,112 @@ +//! FFI bindings for the Go code. The functions exported in this module are safe to call from Rust. +//! All C strings and other C memory should be freed in Rust, including C Strings returned by Go. +//! Although we cast to *mut c_char because the Go signatures can't be immutable, the Go functions +//! should not modify the strings. + +use crate::Groth16Proof; +use std::ffi::{c_char, CString}; + +#[allow(warnings, clippy::all)] +mod bind { + include!(concat!(env!("OUT_DIR"), "/bindings.rs")); +} +use bind::*; + +pub fn prove_groth16(data_dir: &str, witness_path: &str) -> Groth16Proof { + let data_dir = CString::new(data_dir).expect("CString::new failed"); + let witness_path = CString::new(witness_path).expect("CString::new failed"); + + let proof = unsafe { + let proof = bind::ProveGroth16( + data_dir.as_ptr() as *mut c_char, + witness_path.as_ptr() as *mut c_char, + ); + // Safety: The pointer is returned from the go code and is guaranteed to be valid. + *proof + }; + + proof.into_rust() +} + +pub fn build_groth16(data_dir: &str) { + let data_dir = CString::new(data_dir).expect("CString::new failed"); + + unsafe { + bind::BuildGroth16(data_dir.as_ptr() as *mut c_char); + } +} + +pub fn verify_groth16( + data_dir: &str, + proof: &str, + vkey_hash: &str, + committed_values_digest: &str, +) -> Result<(), String> { + let data_dir = CString::new(data_dir).expect("CString::new failed"); + let proof = CString::new(proof).expect("CString::new failed"); + let vkey_hash = CString::new(vkey_hash).expect("CString::new failed"); + let committed_values_digest = + CString::new(committed_values_digest).expect("CString::new failed"); + + let err_ptr = unsafe { + bind::VerifyGroth16( + data_dir.as_ptr() as *mut c_char, + proof.as_ptr() as *mut c_char, + vkey_hash.as_ptr() as *mut c_char, + committed_values_digest.as_ptr() as *mut c_char, + ) + }; + if err_ptr.is_null() { + Ok(()) + } else { + // Safety: The error message is returned from the go code and is guaranteed to be valid. + let err = unsafe { CString::from_raw(err_ptr) }; + Err(err.into_string().unwrap()) + } +} + +pub fn test_groth16(witness_json: &str, constraints_json: &str) { + unsafe { + let witness_json = CString::new(witness_json).expect("CString::new failed"); + let build_dir = CString::new(constraints_json).expect("CString::new failed"); + let err_ptr = bind::TestGroth16( + witness_json.as_ptr() as *mut c_char, + build_dir.as_ptr() as *mut c_char, + ); + if !err_ptr.is_null() { + // Safety: The error message is returned from the go code and is guaranteed to be valid. + let err = CString::from_raw(err_ptr); + panic!("TestGroth16 failed: {}", err.into_string().unwrap()); + } + } +} + +/// Converts a C string into a Rust String. +/// +/// # Safety +/// This function frees the string memory, so the caller must ensure that the pointer is not used +/// after this function is called. +unsafe fn c_char_ptr_to_string(input: *mut c_char) -> String { + unsafe { + CString::from_raw(input) // Converts a pointer that C uses into a CString + .into_string() + .expect("CString::into_string failed") + } +} + +impl C_Groth16Proof { + /// Converts a C Groth16Proof into a Rust Groth16Proof, freeing the C strings. + fn into_rust(self) -> Groth16Proof { + // Safety: The raw pointers are not used anymore after converted into Rust strings. + unsafe { + Groth16Proof { + public_inputs: [ + c_char_ptr_to_string(self.PublicInputs[0]), + c_char_ptr_to_string(self.PublicInputs[1]), + ], + encoded_proof: c_char_ptr_to_string(self.EncodedProof), + raw_proof: c_char_ptr_to_string(self.RawProof), + } + } + } +} diff --git a/recursion/gnark-ffi/src/groth16.rs b/recursion/gnark-ffi/src/groth16.rs index 7d11d5ba5a..48b3509ee9 100644 --- a/recursion/gnark-ffi/src/groth16.rs +++ b/recursion/gnark-ffi/src/groth16.rs @@ -1,15 +1,15 @@ use std::{ - env, fs::{File, OpenOptions}, - io::{Read, Write}, - panic, - path::PathBuf, - process::{Command, Stdio}, + io::Write, + path::{Path, PathBuf}, }; -use crate::witness::GnarkWitness; +use crate::{ + ffi::{build_groth16, prove_groth16, test_groth16, verify_groth16}, + witness::GnarkWitness, +}; -use p3_field::PrimeField; +use num_bigint::BigUint; use serde::{Deserialize, Serialize}; use sp1_recursion_compiler::{ constraints::Constraint, @@ -33,12 +33,9 @@ impl Groth16Prover { pub fn new() -> Self { Self } - /// Executes the prover in testing mode with a circuit definition and witness. pub fn test(constraints: Vec, witness: Witness) { let serialized = serde_json::to_string(&constraints).unwrap(); - let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - let gnark_dir = manifest_dir.join("../gnark"); // Write constraints. let mut constraints_file = tempfile::NamedTempFile::new().unwrap(); @@ -50,42 +47,14 @@ impl Groth16Prover { let serialized = serde_json::to_string(&gnark_witness).unwrap(); witness_file.write_all(serialized.as_bytes()).unwrap(); - // Run `make`. - Self::run_make(&gnark_dir); - - let result = Command::new("go") - .args([ - "test", - "-tags=release_checks", - "-v", - "-timeout", - "100000s", - "-run", - "^TestMain$", - "github.com/succinctlabs/sp1-recursion-gnark", - ]) - .current_dir(gnark_dir) - .env("WITNESS_JSON", witness_file.path().to_str().unwrap()) - .env( - "CONSTRAINTS_JSON", - constraints_file.path().to_str().unwrap(), - ) - .stderr(Stdio::inherit()) - .stdout(Stdio::inherit()) - .stdin(Stdio::inherit()) - .output() - .unwrap(); - - if !result.status.success() { - panic!("failed to run test circuit"); - } + test_groth16( + witness_file.path().to_str().unwrap(), + constraints_file.path().to_str().unwrap(), + ); } pub fn build(constraints: Vec, witness: Witness, build_dir: PathBuf) { let serialized = serde_json::to_string(&constraints).unwrap(); - let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - let gnark_dir = manifest_dir.join("../gnark"); - let cwd = std::env::current_dir().unwrap(); // Write constraints. let constraints_path = build_dir.join("constraints_groth16.json"); @@ -99,146 +68,52 @@ impl Groth16Prover { let serialized = serde_json::to_string(&gnark_witness).unwrap(); file.write_all(serialized.as_bytes()).unwrap(); - // Run `make`. - Self::run_make(&gnark_dir); - - // Run the build script. - Self::run_cmd( - &gnark_dir, - "build".to_string(), - vec![ - "--data".to_string(), - cwd.join(&build_dir).to_str().unwrap().to_string(), - ], - ); + build_groth16(build_dir.to_str().unwrap()); // Extend the built verifier with the sp1 verifier contract. - let sp1_verifier_contract_path = build_dir.join("SP1Verifier.sol"); + let groth16_verifier_path = build_dir.join("SP1Verifier.sol"); // Open the file in append mode. - let mut sp1_verifier_contract = OpenOptions::new() + let mut groth16_verifier_file = OpenOptions::new() .append(true) - .open(sp1_verifier_contract_path) + .open(groth16_verifier_path) .expect("failed to open file"); // Write the string to the file let sp1_verifier_str = include_str!("../assets/SP1Verifier.txt"); - sp1_verifier_contract + groth16_verifier_file .write_all(sp1_verifier_str.as_bytes()) .expect("Failed to write to file"); } /// Generates a Groth16 proof by sending a request to the Gnark server. pub fn prove(&self, witness: Witness, build_dir: PathBuf) -> Groth16Proof { - let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - let gnark_dir = manifest_dir.join("../gnark"); - let cwd = std::env::current_dir().unwrap(); - // Write witness. let mut witness_file = tempfile::NamedTempFile::new().unwrap(); let gnark_witness = GnarkWitness::new(witness); let serialized = serde_json::to_string(&gnark_witness).unwrap(); witness_file.write_all(serialized.as_bytes()).unwrap(); - // Run `make`. - Self::run_make(&gnark_dir); - - // Run the build script. - let proof_file = tempfile::NamedTempFile::new().unwrap(); - Self::run_cmd( - &gnark_dir, - "prove".to_string(), - vec![ - "--data".to_string(), - cwd.join(build_dir).to_str().unwrap().to_string(), - "--witness".to_string(), - witness_file.path().to_str().unwrap().to_string(), - "--proof".to_string(), - proof_file.path().to_str().unwrap().to_string(), - ], - ); - - // Read the contents back from the tempfile. - let mut buffer = String::new(); - proof_file - .reopen() - .unwrap() - .read_to_string(&mut buffer) - .unwrap(); - - // Deserialize the JSON string back to a Groth16Proof instance - let deserialized: Groth16Proof = - serde_json::from_str(&buffer).expect("Error deserializing the proof"); - - deserialized + prove_groth16( + build_dir.to_str().unwrap(), + witness_file.path().to_str().unwrap(), + ) } - pub fn verify( + pub fn verify( &self, - proof: Groth16Proof, - vkey_hash: C::N, - commited_values_digest: C::N, - build_dir: PathBuf, + proof: &Groth16Proof, + vkey_hash: &BigUint, + commited_values_digest: &BigUint, + build_dir: &Path, ) { - let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - let gnark_dir = manifest_dir.join("../gnark"); - let cwd = std::env::current_dir().unwrap(); - - // Run `make`. - Self::run_make(&gnark_dir); - - // Run the build script. - Self::run_cmd( - &gnark_dir, - "verify".to_string(), - vec![ - "--data".to_string(), - cwd.join(build_dir).to_str().unwrap().to_string(), - "--proof".to_string(), - proof.raw_proof, - "--vkey-hash".to_string(), - vkey_hash.as_canonical_biguint().to_string(), - "--commited-values-digest".to_string(), - commited_values_digest.as_canonical_biguint().to_string(), - ], - ); - } - - /// Runs the `make` command to generate the Go bindings for the Gnark library for FFI. - fn run_make(gnark_dir: &PathBuf) { - let make = Command::new("make") - .current_dir(gnark_dir) - .stderr(Stdio::inherit()) - .stdin(Stdio::inherit()) - .output() - .unwrap(); - if !make.status.success() { - panic!("failed to run make"); - } - } - - /// Runs the FFI command to interface with the Gnark library. Command is one of the commands - /// defined in recursion/gnark/main.go. - fn run_cmd(gnark_dir: &PathBuf, command: String, args: Vec) { - let mut command_args = vec!["run".to_string(), "main.go".to_string(), command.clone()]; - - command_args.extend(args); - - let result = Command::new("go") - .args(command_args) - .current_dir(gnark_dir) - .stderr(Stdio::inherit()) - .stdout(Stdio::inherit()) - .stdin(Stdio::inherit()) - .output() - .unwrap(); - - if !result.status.success() { - panic!( - "failed to run script for {:?}: {:?}", - command, result.status - ); - } + verify_groth16( + build_dir.to_str().unwrap(), + &proof.raw_proof, + &vkey_hash.to_string(), + &commited_values_digest.to_string(), + ) + .expect("failed to verify proof") } } diff --git a/recursion/gnark-ffi/src/lib.rs b/recursion/gnark-ffi/src/lib.rs index 42a5ad95bb..31ea294d62 100644 --- a/recursion/gnark-ffi/src/lib.rs +++ b/recursion/gnark-ffi/src/lib.rs @@ -1,3 +1,5 @@ +mod babybear; +pub mod ffi; pub mod groth16; pub mod plonk_bn254; pub mod witness; diff --git a/recursion/gnark/Makefile b/recursion/gnark/Makefile deleted file mode 100644 index 9aa5c4110b..0000000000 --- a/recursion/gnark/Makefile +++ /dev/null @@ -1,32 +0,0 @@ -ROOT_DIR := $(dir $(realpath $(lastword $(MAKEFILE_LIST)))) - -# PHONY means that it doesn't correspond to a file; it always runs the build commands. - -.PHONY: build-all -build-all: build-static - -.PHONY: run-all -run-all: run-static - -# File target to check if re-building is necessary -lib/babybear/target/release/libbabybear.a: $(shell find lib/babybear/src -type f) - cd lib/babybear && cargo build --release - touch $@ - -.PHONY: build-static -build-static: lib/babybear/target/release/libbabybear.a - cp lib/babybear/target/release/libbabybear.a lib/ - go build main.go - -.PHONY: run-static -run-static: build-static - @./main_static - -# This is just for running the Rust lib tests natively via cargo -.PHONY: test-rust-lib -test-rust-lib: - cd lib/babybear && cargo test -- --nocapture - -.PHONY: clean -clean: - rm -rf main_dynamic main_static lib/libbabybear.so lib/libbabybear.a lib/babybear/target \ No newline at end of file diff --git a/recursion/gnark/cmd/build.go b/recursion/gnark/cmd/build.go deleted file mode 100644 index 2d39fb95da..0000000000 --- a/recursion/gnark/cmd/build.go +++ /dev/null @@ -1,127 +0,0 @@ -package cmd - -import ( - "crypto/sha256" - "encoding/json" - "os" - - "github.com/consensys/gnark-crypto/ecc" - "github.com/consensys/gnark/backend" - "github.com/consensys/gnark/backend/groth16" - "github.com/consensys/gnark/frontend" - "github.com/consensys/gnark/frontend/cs/r1cs" - "github.com/spf13/cobra" - "github.com/succinctlabs/sp1-recursion-gnark/sp1" -) - -var buildCmdDataDir string - -func init() { - buildCmd.Flags().StringVar(&buildCmdDataDir, "data", "", "") -} - -var buildCmd = &cobra.Command{ - Use: "build", - Run: func(cmd *cobra.Command, args []string) { - // Sanity check the required arguments have been provided. - if buildCmdDataDir == "" { - panic("--data is required") - } - - // Set the enviroment variable for the constraints file. - // - // TODO: There might be some non-determinism if a single processe is running this command - // multiple times. - os.Setenv("CONSTRAINTS_JSON", buildCmdDataDir+"/"+CONSTRAINTS_JSON_FILE) - - // Read the file. - witnessInputPath := buildCmdDataDir + "/witness_groth16.json" - data, err := os.ReadFile(witnessInputPath) - if err != nil { - panic(err) - } - - // Deserialize the JSON data into a slice of Instruction structs - var witnessInput sp1.WitnessInput - err = json.Unmarshal(data, &witnessInput) - if err != nil { - panic(err) - } - - // Initialize the circuit. - circuit := sp1.NewCircuit(witnessInput) - - // Compile the circuit. - r1cs, err := frontend.Compile(ecc.BN254.ScalarField(), r1cs.NewBuilder, &circuit) - if err != nil { - panic(err) - } - - // Perform the trusted setup. - pk, vk, err := groth16.Setup(r1cs) - if err != nil { - panic(err) - } - - // Generate proof. - assignment := sp1.NewCircuit(witnessInput) - witness, err := frontend.NewWitness(&assignment, ecc.BN254.ScalarField()) - if err != nil { - panic(err) - } - proof, err := groth16.Prove(r1cs, pk, witness, backend.WithProverHashToFieldFunction(sha256.New())) - if err != nil { - panic(err) - } - - // Verify proof. - publicWitness, err := witness.Public() - if err != nil { - panic(err) - } - err = groth16.Verify(proof, vk, publicWitness, backend.WithVerifierHashToFieldFunction(sha256.New())) - if err != nil { - panic(err) - } - - // Create the build directory. - os.MkdirAll(buildCmdDataDir, 0755) - - // Write the solidity verifier. - solidityVerifierFile, err := os.Create(buildCmdDataDir + "/" + VERIFIER_CONTRACT_PATH) - if err != nil { - panic(err) - } - vk.ExportSolidity(solidityVerifierFile) - - // Write the R1CS. - r1csFile, err := os.Create(buildCmdDataDir + "/" + CIRCUIT_PATH) - if err != nil { - panic(err) - } - defer r1csFile.Close() - _, err = r1cs.WriteTo(r1csFile) - if err != nil { - panic(err) - } - - // Write the verifier key. - vkFile, err := os.Create(buildCmdDataDir + "/" + VK_PATH) - if err != nil { - panic(err) - } - defer vkFile.Close() - _, err = vk.WriteTo(vkFile) - if err != nil { - panic(err) - } - - // Write the proving key. - pkFile, err := os.Create(buildCmdDataDir + "/" + PK_PATH) - if err != nil { - panic(err) - } - defer pkFile.Close() - pk.WriteDump(pkFile) - }, -} diff --git a/recursion/gnark/cmd/prove.go b/recursion/gnark/cmd/prove.go deleted file mode 100644 index be8ba41e24..0000000000 --- a/recursion/gnark/cmd/prove.go +++ /dev/null @@ -1,106 +0,0 @@ -package cmd - -import ( - "crypto/sha256" - "encoding/json" - "os" - - "github.com/consensys/gnark-crypto/ecc" - "github.com/consensys/gnark/backend" - "github.com/consensys/gnark/backend/groth16" - "github.com/consensys/gnark/frontend" - "github.com/spf13/cobra" - "github.com/succinctlabs/sp1-recursion-gnark/sp1" -) - -var proveCmdDataDir string -var proveCmdWitnessPath string -var proveCmdProofPath string - -func init() { - proveCmd.Flags().StringVar(&proveCmdDataDir, "data", "", "") - proveCmd.Flags().StringVar(&proveCmdWitnessPath, "witness", "", "") - proveCmd.Flags().StringVar(&proveCmdProofPath, "proof", "", "") -} - -var proveCmd = &cobra.Command{ - Use: "prove", - Run: func(cmd *cobra.Command, args []string) { - // Sanity check the required arguments have been provided. - if proveCmdDataDir == "" { - panic("--data is required") - } - os.Setenv("CONSTRAINTS_JSON", buildCmdDataDir+"/"+CONSTRAINTS_JSON_FILE) - - // Read the R1CS. - r1csFile, err := os.Open(proveCmdDataDir + "/" + CIRCUIT_PATH) - if err != nil { - panic(err) - } - r1cs := groth16.NewCS(ecc.BN254) - r1cs.ReadFrom(r1csFile) - - // Read the proving key. - pkFile, err := os.Open(proveCmdDataDir + "/" + PK_PATH) - if err != nil { - panic(err) - } - pk := groth16.NewProvingKey(ecc.BN254) - pk.ReadDump(pkFile) - - // Read the verifier key. - vkFile, err := os.Open(proveCmdDataDir + "/" + VK_PATH) - if err != nil { - panic(err) - } - vk := groth16.NewVerifyingKey(ecc.BN254) - vk.ReadFrom(vkFile) - - // Read the file. - data, err := os.ReadFile(proveCmdDataDir + "/" + WITNESS_JSON_FILE) - if err != nil { - panic(err) - } - - // Deserialize the JSON data into a slice of Instruction structs - var witnessInput sp1.WitnessInput - err = json.Unmarshal(data, &witnessInput) - if err != nil { - panic(err) - } - - // Generate the witness. - assignment := sp1.NewCircuit(witnessInput) - witness, err := frontend.NewWitness(&assignment, ecc.BN254.ScalarField()) - if err != nil { - panic(err) - } - publicWitness, err := witness.Public() - if err != nil { - panic(err) - } - - // Generate the proof. - proof, err := groth16.Prove(r1cs, pk, witness, backend.WithProverHashToFieldFunction(sha256.New())) - if err != nil { - panic(err) - } - - // Verify proof. - err = groth16.Verify(proof, vk, publicWitness, backend.WithVerifierHashToFieldFunction(sha256.New())) - if err != nil { - panic(err) - } - - // Serialize the proof to a file. - sp1Groth16Proof := sp1.NewSP1Groth16Proof(&proof, witnessInput) - jsonData, err := json.Marshal(sp1Groth16Proof) - if err != nil { - panic(err) - } - err = os.WriteFile(proveCmdProofPath, jsonData, 0644) - if err != nil { - panic(err) - } - }, -} diff --git a/recursion/gnark/cmd/root.go b/recursion/gnark/cmd/root.go deleted file mode 100644 index d337ceb75b..0000000000 --- a/recursion/gnark/cmd/root.go +++ /dev/null @@ -1,35 +0,0 @@ -package cmd - -import ( - "fmt" - "os" - - "github.com/spf13/cobra" -) - -var CONSTRAINTS_JSON_FILE string = "constraints_groth16.json" -var WITNESS_JSON_FILE string = "witness_groth16.json" -var VERIFIER_CONTRACT_PATH string = "SP1Verifier.sol" -var CIRCUIT_PATH string = "circuit_groth16.bin" -var VK_PATH string = "vk_groth16.bin" -var PK_PATH string = "pk_groth16.bin" - -var rootCmd = &cobra.Command{ - Use: "sp1-recursion-gnark", - Run: func(cmd *cobra.Command, args []string) { - fmt.Println("SP1 Recursion Gnark CLI") - }, -} - -func init() { - rootCmd.AddCommand(buildCmd) - rootCmd.AddCommand(proveCmd) - rootCmd.AddCommand(verifyCmd) -} - -func Execute() { - if err := rootCmd.Execute(); err != nil { - fmt.Println(err) - os.Exit(1) - } -} diff --git a/recursion/gnark/cmd/verify.go b/recursion/gnark/cmd/verify.go deleted file mode 100644 index 479fd0d1c2..0000000000 --- a/recursion/gnark/cmd/verify.go +++ /dev/null @@ -1,78 +0,0 @@ -package cmd - -import ( - "bytes" - "encoding/hex" - "os" - - "github.com/consensys/gnark-crypto/ecc" - "github.com/consensys/gnark/backend/groth16" - "github.com/consensys/gnark/frontend" - "github.com/spf13/cobra" - "github.com/succinctlabs/sp1-recursion-gnark/sp1" - "github.com/succinctlabs/sp1-recursion-gnark/sp1/babybear" -) - -var verifyCmdDataDir string -var verifyCmdProof string -var verifyCmdVkeyHash string -var verifyCmdCommitedValuesDigest string - -func init() { - verifyCmd.Flags().StringVar(&verifyCmdDataDir, "data", "", "") - verifyCmd.Flags().StringVar(&verifyCmdProof, "proof", "", "") - verifyCmd.Flags().StringVar(&verifyCmdVkeyHash, "vkey-hash", "", "") - verifyCmd.Flags().StringVar(&verifyCmdCommitedValuesDigest, "commited-values-digest", "", "") -} - -var verifyCmd = &cobra.Command{ - Use: "verify", - Run: func(cmd *cobra.Command, args []string) { - // Sanity check the required arguments have been provided. - if verifyCmdDataDir == "" { - panic("--data is required") - } - - // Decode the proof. - proofDecodedBytes, err := hex.DecodeString(verifyCmdProof) - if err != nil { - panic(err) - } - proof := groth16.NewProof(ecc.BN254) - if _, err := proof.ReadFrom(bytes.NewReader(proofDecodedBytes)); err != nil { - panic(err) - } - - // Read the verifier key. - vkFile, err := os.Open(verifyCmdDataDir + "/" + VK_PATH) - if err != nil { - panic(err) - } - vk := groth16.NewVerifyingKey(ecc.BN254) - vk.ReadFrom(vkFile) - - // Compute the public witness. - circuit := sp1.Circuit{ - Vars: []frontend.Variable{}, - Felts: []babybear.Variable{}, - Exts: []babybear.ExtensionVariable{}, - VkeyHash: verifyCmdVkeyHash, - CommitedValuesDigest: verifyCmdCommitedValuesDigest, - } - witness, err := frontend.NewWitness(&circuit, ecc.BN254.ScalarField()) - if err != nil { - panic(err) - } - _, err = witness.Public() - if err != nil { - panic(err) - } - - // // Verify proof. - // err = groth16.Verify(proof, vk, publicWitness, backend.WithVerifierHashToFieldFunction(sha256.New())) - // if err != nil { - // panic(err) - // } - - }, -} diff --git a/recursion/gnark/lib/babybear/Cargo.lock b/recursion/gnark/lib/babybear/Cargo.lock deleted file mode 100644 index b451cf3759..0000000000 --- a/recursion/gnark/lib/babybear/Cargo.lock +++ /dev/null @@ -1,334 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 3 - -[[package]] -name = "autocfg" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" - -[[package]] -name = "babybear" -version = "0.1.0" -dependencies = [ - "libc", - "p3-baby-bear", - "p3-field", -] - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "either" -version = "1.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" - -[[package]] -name = "getrandom" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - -[[package]] -name = "itertools" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" -dependencies = [ - "either", -] - -[[package]] -name = "itertools" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" -dependencies = [ - "either", -] - -[[package]] -name = "libc" -version = "0.2.153" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" - -[[package]] -name = "num-bigint" -version = "0.4.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-integer" -version = "0.1.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" -dependencies = [ - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" -dependencies = [ - "autocfg", -] - -[[package]] -name = "once_cell" -version = "1.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" - -[[package]] -name = "p3-baby-bear" -version = "0.1.0" -source = "git+https://github.com/Plonky3/Plonky3.git?branch=sp1#683ca1a9e083729c015b981c035af7428a0d85c6" -dependencies = [ - "num-bigint", - "p3-field", - "p3-mds", - "p3-poseidon2", - "p3-symmetric", - "rand", - "serde", -] - -[[package]] -name = "p3-dft" -version = "0.1.0" -source = "git+https://github.com/Plonky3/Plonky3.git?branch=sp1#683ca1a9e083729c015b981c035af7428a0d85c6" -dependencies = [ - "p3-field", - "p3-matrix", - "p3-maybe-rayon", - "p3-util", - "tracing", -] - -[[package]] -name = "p3-field" -version = "0.1.0" -source = "git+https://github.com/Plonky3/Plonky3.git?branch=sp1#683ca1a9e083729c015b981c035af7428a0d85c6" -dependencies = [ - "itertools 0.12.1", - "num-bigint", - "num-traits", - "p3-util", - "rand", - "serde", -] - -[[package]] -name = "p3-matrix" -version = "0.1.0" -source = "git+https://github.com/Plonky3/Plonky3.git?branch=sp1#683ca1a9e083729c015b981c035af7428a0d85c6" -dependencies = [ - "itertools 0.12.1", - "p3-field", - "p3-maybe-rayon", - "p3-util", - "rand", - "serde", - "tracing", -] - -[[package]] -name = "p3-maybe-rayon" -version = "0.1.0" -source = "git+https://github.com/Plonky3/Plonky3.git?branch=sp1#683ca1a9e083729c015b981c035af7428a0d85c6" - -[[package]] -name = "p3-mds" -version = "0.1.0" -source = "git+https://github.com/Plonky3/Plonky3.git?branch=sp1#683ca1a9e083729c015b981c035af7428a0d85c6" -dependencies = [ - "itertools 0.11.0", - "p3-dft", - "p3-field", - "p3-matrix", - "p3-symmetric", - "p3-util", - "rand", -] - -[[package]] -name = "p3-poseidon2" -version = "0.1.0" -source = "git+https://github.com/Plonky3/Plonky3.git?branch=sp1#683ca1a9e083729c015b981c035af7428a0d85c6" -dependencies = [ - "p3-field", - "p3-symmetric", - "rand", -] - -[[package]] -name = "p3-symmetric" -version = "0.1.0" -source = "git+https://github.com/Plonky3/Plonky3.git?branch=sp1#683ca1a9e083729c015b981c035af7428a0d85c6" -dependencies = [ - "itertools 0.12.1", - "p3-field", - "serde", -] - -[[package]] -name = "p3-util" -version = "0.1.0" -source = "git+https://github.com/Plonky3/Plonky3.git?branch=sp1#683ca1a9e083729c015b981c035af7428a0d85c6" -dependencies = [ - "serde", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" - -[[package]] -name = "ppv-lite86" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" - -[[package]] -name = "proc-macro2" -version = "1.0.79" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha", - "rand_core", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom", -] - -[[package]] -name = "serde" -version = "1.0.197" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.197" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "syn" -version = "2.0.55" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "002a1b3dbf967edfafc32655d0f377ab0bb7b994aa1d32c8cc7e9b8bf3ebb8f0" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "tracing" -version = "0.1.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" -dependencies = [ - "pin-project-lite", - "tracing-attributes", - "tracing-core", -] - -[[package]] -name = "tracing-attributes" -version = "0.1.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "tracing-core" -version = "0.1.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" -dependencies = [ - "once_cell", -] - -[[package]] -name = "unicode-ident" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" diff --git a/recursion/gnark/lib/babybear/Cargo.toml b/recursion/gnark/lib/babybear/Cargo.toml deleted file mode 100644 index c1fafee62b..0000000000 --- a/recursion/gnark/lib/babybear/Cargo.toml +++ /dev/null @@ -1,15 +0,0 @@ -[workspace] -[package] -name = "babybear" -version = "0.1.0" - -[lib] -# If you only wanted dynamic library, you'd use only "cdylib". -# If you only wanted static library, you'd use only "staticlib". -crate-type = ["staticlib"] - - -[dependencies] -libc = "0.2.2" -p3-baby-bear = { git = "https://github.com/Plonky3/Plonky3.git", branch = "sp1" } -p3-field = { git = "https://github.com/Plonky3/Plonky3.git", branch = "sp1" } \ No newline at end of file diff --git a/recursion/gnark/main.go b/recursion/gnark/main.go deleted file mode 100644 index d7d94cb6a4..0000000000 --- a/recursion/gnark/main.go +++ /dev/null @@ -1,15 +0,0 @@ -package main - -/* -#cgo LDFLAGS: ./lib/libbabybear.a -ldl -#include "./lib/babybear.h" -*/ -import "C" - -import ( - root "github.com/succinctlabs/sp1-recursion-gnark/cmd" -) - -func main() { - root.Execute() -} diff --git a/recursion/gnark/main_test.go b/recursion/gnark/main_test.go deleted file mode 100644 index 0c4338318c..0000000000 --- a/recursion/gnark/main_test.go +++ /dev/null @@ -1,65 +0,0 @@ -package main - -import ( - "encoding/json" - "fmt" - "os" - "testing" - - "github.com/consensys/gnark-crypto/ecc" - "github.com/consensys/gnark/backend/groth16" - "github.com/consensys/gnark/frontend" - "github.com/consensys/gnark/frontend/cs/r1cs" - "github.com/succinctlabs/sp1-recursion-gnark/sp1" -) - -func TestMain(t *testing.T) { - // Get the file name from an environment variable. - fileName := os.Getenv("WITNESS_JSON") - if fileName == "" { - fileName = "witness.json" - } - - // Read the file. - data, err := os.ReadFile(fileName) - if err != nil { - panic(err) - } - - // Deserialize the JSON data into a slice of Instruction structs - var inputs sp1.WitnessInput - err = json.Unmarshal(data, &inputs) - if err != nil { - panic(err) - } - - // Compile the circuit. - circuit := sp1.NewCircuit(inputs) - builder := r1cs.NewBuilder - r1cs, err := frontend.Compile(ecc.BN254.ScalarField(), builder, &circuit) - if err != nil { - panic(err) - } - fmt.Println("[sp1] groth16 verifier constraints:", r1cs.GetNbConstraints()) - - // Run the dummy setup. - var pk groth16.ProvingKey - pk, err = groth16.DummySetup(r1cs) - if err != nil { - panic(err) - } - - // Generate witness. - assignment := sp1.NewCircuit(inputs) - witness, err := frontend.NewWitness(&assignment, ecc.BN254.ScalarField()) - if err != nil { - panic(err) - } - - // Generate the proof. - _, err = groth16.Prove(r1cs, pk, witness) - if err != nil { - panic(err) - } - -} diff --git a/recursion/program/src/constraints.rs b/recursion/program/src/constraints.rs index c7ab21108a..01ec477182 100644 --- a/recursion/program/src/constraints.rs +++ b/recursion/program/src/constraints.rs @@ -284,7 +284,7 @@ mod tests { let (_, vk) = machine.setup(&Program::from(elf)); let mut challenger = machine.config().challenger(); let (proof, _) = - sp1_core::utils::run_and_prove(Program::from(elf), &SP1Stdin::new(), SC::default()); + sp1_core::utils::prove(Program::from(elf), &SP1Stdin::new(), SC::default()).unwrap(); machine.verify(&vk, &proof, &mut challenger).unwrap(); println!("Proof generated and verified successfully"); diff --git a/recursion/program/src/machine/compress.rs b/recursion/program/src/machine/compress.rs index 441f648944..dcc85fed1a 100644 --- a/recursion/program/src/machine/compress.rs +++ b/recursion/program/src/machine/compress.rs @@ -8,6 +8,7 @@ use p3_air::Air; use p3_baby_bear::BabyBear; use p3_commit::TwoAdicMultiplicativeCoset; use p3_field::{AbstractField, PrimeField32, TwoAdicField}; +use serde::{Deserialize, Serialize}; use sp1_core::air::MachineAir; use sp1_core::air::{Word, POSEIDON_NUM_WORDS, PV_DIGEST_NUM_WORDS}; use sp1_core::stark::StarkMachine; @@ -32,7 +33,7 @@ use crate::utils::{ get_challenger_public_values, hash_vkey, }; -use super::utils::proof_data_from_vk; +use super::utils::{commit_public_values, proof_data_from_vk, verify_public_values_hash}; /// A program to verify a batch of recursive proofs and aggregate their public values. #[derive(Debug, Clone, Copy)] @@ -41,7 +42,7 @@ pub struct SP1CompressVerifier { } /// The different types of programs that can be verified by the `SP1ReduceVerifier`. -#[derive(Debug, Clone, Copy)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] pub enum ReduceProgramType { /// A batch of proofs that are all SP1 Core proofs. Core = 0, @@ -250,6 +251,9 @@ where let current_public_values: &RecursionPublicValues> = current_public_values_elements.as_slice().borrow(); + // Check that the public values digest is correct. + verify_public_values_hash(builder, current_public_values); + // If the proof is the first proof, initialize the values. builder.if_eq(i, C::N::zero()).then(|builder| { // Initialize global and accumulated values. @@ -467,10 +471,7 @@ where }, ); - // Commit the public values. - for value in reduce_public_values_stream { - builder.commit_public_value(value); - } + commit_public_values(builder, reduce_public_values); builder.halt(); } diff --git a/recursion/program/src/machine/core.rs b/recursion/program/src/machine/core.rs index 2ae14ffba2..65b6b3e8dc 100644 --- a/recursion/program/src/machine/core.rs +++ b/recursion/program/src/machine/core.rs @@ -27,7 +27,7 @@ use crate::types::ShardProofVariable; use crate::types::VerifyingKeyVariable; use crate::utils::{const_fri_config, felt2var, get_challenger_public_values, hash_vkey, var2felt}; -use super::utils::assert_complete; +use super::utils::{assert_complete, commit_public_values}; /// A program for recursively verifying a batch of SP1 proofs. #[derive(Debug, Clone, Copy)] @@ -223,8 +223,6 @@ where // Assert that the start_pc of the proof is equal to the current pc. builder.assert_felt_eq(current_pc, public_values.start_pc); - // Assert that the next_pc is different from the start_pc. - builder.assert_felt_ne(public_values.start_pc, public_values.next_pc); // Assert that the start_pc is not zero (this means program has halted in a non-last // shard). builder.assert_felt_ne(public_values.start_pc, C::F::zero()); @@ -319,10 +317,7 @@ where assert_complete(builder, recursion_public_values, &reconstruct_challenger) }); - // Commit to the public values. - for value in recursion_public_values_stream { - builder.commit_public_value(value); - } + commit_public_values(builder, recursion_public_values); builder.halt(); } diff --git a/recursion/program/src/machine/deferred.rs b/recursion/program/src/machine/deferred.rs index 29708825df..3df497d445 100644 --- a/recursion/program/src/machine/deferred.rs +++ b/recursion/program/src/machine/deferred.rs @@ -27,6 +27,8 @@ use crate::types::ShardProofVariable; use crate::types::VerifyingKeyVariable; use crate::utils::{const_fri_config, get_challenger_public_values, hash_vkey, var2felt}; +use super::utils::{commit_public_values, verify_public_values_hash}; + #[derive(Debug, Clone, Copy)] pub struct SP1DeferredVerifier { _phantom: PhantomData<(C, SC, A)>, @@ -201,6 +203,9 @@ where let current_public_values: &RecursionPublicValues> = current_public_values_elements.as_slice().borrow(); + // Check that the public values digest is correct. + verify_public_values_hash(builder, current_public_values); + // Assert that the proof is complete. builder.assert_felt_eq(current_public_values.is_complete, C::F::one()); @@ -283,10 +288,7 @@ where // Set the is_complete flag. deferred_public_values.is_complete = var2felt(builder, is_complete); - // Commit the public values. - for value in deferred_public_values_stream { - builder.commit_public_value(value); - } + commit_public_values(builder, deferred_public_values); builder.halt(); } diff --git a/recursion/program/src/machine/mod.rs b/recursion/program/src/machine/mod.rs index 2d262332a0..cef2cbccc5 100644 --- a/recursion/program/src/machine/mod.rs +++ b/recursion/program/src/machine/mod.rs @@ -8,6 +8,7 @@ pub use compress::*; pub use core::*; pub use deferred::*; pub use root::*; +pub use utils::*; #[cfg(test)] mod tests { @@ -82,7 +83,7 @@ mod tests { let mut challenger = machine.config().challenger(); let time = std::time::Instant::now(); - let (proof, _) = sp1_core::utils::run_and_prove(program, &SP1Stdin::new(), SC::default()); + let (proof, _) = sp1_core::utils::prove(program, &SP1Stdin::new(), SC::default()).unwrap(); machine.verify(&vk, &proof, &mut challenger).unwrap(); tracing::info!("Proof generated successfully"); let elapsed = time.elapsed(); diff --git a/recursion/program/src/machine/root.rs b/recursion/program/src/machine/root.rs index 57e6f646a5..8e8eb72c67 100644 --- a/recursion/program/src/machine/root.rs +++ b/recursion/program/src/machine/root.rs @@ -24,6 +24,8 @@ use crate::stark::{RecursiveVerifierConstraintFolder, ShardProofHint, StarkVerif use crate::types::ShardProofVariable; use crate::utils::{const_fri_config, hash_vkey}; +use super::utils::{commit_public_values, verify_public_values_hash}; + /// The program that gets a final verifier at the root of the tree. #[derive(Debug, Clone, Copy)] pub struct SP1RootVerifier { @@ -114,6 +116,9 @@ where let public_values: &RecursionPublicValues> = public_values_elements.as_slice().borrow(); + // Check that the public values digest is correct. + verify_public_values_hash(builder, public_values); + // Assert that the proof is complete. // // *Remark*: here we are assuming on that the program we are verifying indludes the check @@ -131,10 +136,7 @@ where } } - // Commit to the public values, broadcasting the same ones. - for value in public_values_elements { - builder.commit_public_value(value); - } + commit_public_values(builder, public_values); builder.halt(); } diff --git a/recursion/program/src/machine/utils.rs b/recursion/program/src/machine/utils.rs index 61500a8d3a..f3d6d408cb 100644 --- a/recursion/program/src/machine/utils.rs +++ b/recursion/program/src/machine/utils.rs @@ -1,3 +1,5 @@ +use std::mem::transmute; + use itertools::Itertools; use p3_commit::TwoAdicMultiplicativeCoset; use p3_field::AbstractField; @@ -6,8 +8,11 @@ use sp1_core::{ air::MachineAir, stark::{Com, StarkGenericConfig, StarkMachine, StarkVerifyingKey}, }; -use sp1_recursion_compiler::ir::{Builder, Config, Felt, Var}; -use sp1_recursion_core::{air::RecursionPublicValues, runtime::DIGEST_SIZE}; +use sp1_recursion_compiler::ir::{Array, Builder, Config, Felt, Var}; +use sp1_recursion_core::{ + air::{RecursionPublicValues, RECURSIVE_PROOF_NUM_PV_ELTS}, + runtime::DIGEST_SIZE, +}; use crate::{ challenger::DuplexChallengerVariable, @@ -115,3 +120,53 @@ where prep_domains, } } + +/// Calculates the digest of the recursion public values. +pub(crate) fn calculate_public_values_digest( + builder: &mut Builder, + public_values: &RecursionPublicValues>, +) -> Array> { + let pv_elements: [Felt<_>; RECURSIVE_PROOF_NUM_PV_ELTS] = unsafe { transmute(*public_values) }; + const NUM_ELMS_TO_HASH: usize = RECURSIVE_PROOF_NUM_PV_ELTS - DIGEST_SIZE; + let mut poseidon_inputs = builder.array(NUM_ELMS_TO_HASH); + for (i, elm) in pv_elements[0..NUM_ELMS_TO_HASH].iter().enumerate() { + builder.set(&mut poseidon_inputs, i, *elm); + } + builder.poseidon2_hash(&poseidon_inputs) +} + +/// Verifies the digest of a recursive public values struct. +pub(crate) fn verify_public_values_hash( + builder: &mut Builder, + public_values: &RecursionPublicValues>, +) { + // Check that the public values digest is correct. + let calculated_digest = calculate_public_values_digest(builder, public_values); + + let expected_digest = public_values.digest; + for (i, expected_elm) in expected_digest.iter().enumerate() { + let calculated_elm = builder.get(&calculated_digest, i); + builder.assert_felt_eq(*expected_elm, calculated_elm); + } +} + +/// Register and commits the recursion public values. +pub fn commit_public_values( + builder: &mut Builder, + public_values: &RecursionPublicValues>, +) { + let pv_elements: [Felt<_>; RECURSIVE_PROOF_NUM_PV_ELTS] = unsafe { transmute(*public_values) }; + const NUM_ELMS_TO_HASH: usize = RECURSIVE_PROOF_NUM_PV_ELTS - DIGEST_SIZE; + let pv_elms_no_digest = &pv_elements[0..NUM_ELMS_TO_HASH]; + + for value in pv_elms_no_digest.iter() { + builder.register_public_value(*value); + } + + // Hash the public values. + let pv_digest = calculate_public_values_digest(builder, public_values); + for i in 0..DIGEST_SIZE { + let digest_element = builder.get(&pv_digest, i); + builder.commit_public_value(digest_element); + } +} diff --git a/recursion/program/src/stark.rs b/recursion/program/src/stark.rs index cd59a8804d..a65dad9c47 100644 --- a/recursion/program/src/stark.rs +++ b/recursion/program/src/stark.rs @@ -396,11 +396,13 @@ where #[cfg(test)] pub(crate) mod tests { + use std::borrow::BorrowMut; use std::time::Instant; use crate::challenger::CanObserveVariable; use crate::challenger::FeltChallenger; use crate::hints::Hintable; + use crate::machine::commit_public_values; use crate::stark::DuplexChallengerVariable; use crate::stark::Ext; use crate::stark::ShardProofHint; @@ -408,8 +410,10 @@ pub(crate) mod tests { use p3_challenger::{CanObserve, FieldChallenger}; use p3_field::AbstractField; use rand::Rng; + use sp1_core::air::POSEIDON_NUM_WORDS; use sp1_core::io::SP1Stdin; use sp1_core::runtime::Program; + use sp1_core::stark::LocalProver; use sp1_core::utils::setup_logger; use sp1_core::utils::InnerChallenge; use sp1_core::utils::InnerVal; @@ -419,6 +423,7 @@ pub(crate) mod tests { }; use sp1_recursion_compiler::config::InnerConfig; use sp1_recursion_compiler::ir::Array; + use sp1_recursion_compiler::ir::Config; use sp1_recursion_compiler::ir::Felt; use sp1_recursion_compiler::prelude::Usize; use sp1_recursion_compiler::{ @@ -426,12 +431,19 @@ pub(crate) mod tests { ir::{Builder, ExtConst}, }; + use sp1_recursion_core::air::RecursionPublicValues; + use sp1_recursion_core::air::RECURSION_PUBLIC_VALUES_COL_MAP; + use sp1_recursion_core::air::RECURSIVE_PROOF_NUM_PV_ELTS; + use sp1_recursion_core::runtime::RecursionProgram; + use sp1_recursion_core::runtime::Runtime; use sp1_recursion_core::runtime::DIGEST_SIZE; use sp1_recursion_core::stark::utils::run_test_recursion; use sp1_recursion_core::stark::utils::TestConfig; + use sp1_recursion_core::stark::RecursionAir; type SC = BabyBearPoseidon2; + type Challenge = ::Challenge; type F = InnerVal; type EF = InnerChallenge; type C = InnerConfig; @@ -447,7 +459,7 @@ pub(crate) mod tests { let (_, vk) = machine.setup(&Program::from(elf)); let mut challenger_val = machine.config().challenger(); let (proof, _) = - sp1_core::utils::run_and_prove(Program::from(elf), &SP1Stdin::new(), SC::default()); + sp1_core::utils::prove(Program::from(elf), &SP1Stdin::new(), SC::default()).unwrap(); let proofs = proof.shard_proofs; println!("Proof generated successfully"); @@ -503,6 +515,66 @@ pub(crate) mod tests { run_test_recursion(program, Some(witness_stream.into()), TestConfig::All); } + fn test_public_values_program() -> RecursionProgram { + let mut builder = Builder::::default(); + + let mut public_values_stream: Vec> = (0..RECURSIVE_PROOF_NUM_PV_ELTS) + .map(|_| builder.uninit()) + .collect(); + + let public_values: &mut RecursionPublicValues<_> = + public_values_stream.as_mut_slice().borrow_mut(); + + public_values.sp1_vk_digest = [builder.constant(::F::zero()); DIGEST_SIZE]; + public_values.next_pc = builder.constant(::F::one()); + public_values.next_shard = builder.constant(::F::two()); + public_values.end_reconstruct_deferred_digest = + [builder.constant(::F::from_canonical_usize(3)); POSEIDON_NUM_WORDS]; + + public_values.deferred_proofs_digest = + [builder.constant(::F::from_canonical_usize(4)); POSEIDON_NUM_WORDS]; + + public_values.cumulative_sum = + [builder.constant(::F::from_canonical_usize(5)); 4]; + + commit_public_values(&mut builder, public_values); + builder.halt(); + + builder.compile_program() + } + + #[test] + fn test_public_values_failure() { + let program = test_public_values_program(); + + let config = SC::default(); + + let mut runtime = Runtime::::new(&program, config.perm.clone()); + runtime.run(); + + let machine = RecursionAir::<_, 3>::machine(SC::default()); + let (pk, vk) = machine.setup(&program); + let record = runtime.record.clone(); + + let mut challenger = machine.config().challenger(); + let mut proof = + machine.prove::>>(&pk, record, &mut challenger); + + let mut challenger = machine.config().challenger(); + let verification_result = machine.verify(&vk, &proof, &mut challenger); + if verification_result.is_err() { + panic!("Proof should verify successfully"); + } + + // Corrupt the public values. + proof.shard_proofs[0].public_values[RECURSION_PUBLIC_VALUES_COL_MAP.digest[0]] = + InnerVal::zero(); + let verification_result = machine.verify(&vk, &proof, &mut challenger); + if verification_result.is_ok() { + panic!("Proof should not verify successfully"); + } + } + #[test] #[ignore] fn test_kitchen_sink() { diff --git a/recursion/program/src/utils.rs b/recursion/program/src/utils.rs index 92b46c546e..e6f621f508 100644 --- a/recursion/program/src/utils.rs +++ b/recursion/program/src/utils.rs @@ -147,26 +147,6 @@ pub fn assign_challenger_from_pv( } } -/// Commits a challenger variable to public values. -pub fn commit_challenger(builder: &mut Builder, var: &DuplexChallengerVariable) { - for i in 0..PERMUTATION_WIDTH { - let element = builder.get(&var.sponge_state, i); - builder.commit_public_value(element); - } - let num_inputs_felt = var2felt(builder, var.nb_inputs); - builder.commit_public_value(num_inputs_felt); - for i in 0..PERMUTATION_WIDTH { - let element = builder.get(&var.input_buffer, i); - builder.commit_public_value(element); - } - let num_outputs_felt = var2felt(builder, var.nb_outputs); - builder.commit_public_value(num_outputs_felt); - for i in 0..PERMUTATION_WIDTH { - let element = builder.get(&var.output_buffer, i); - builder.commit_public_value(element); - } -} - pub fn get_challenger_public_values( builder: &mut Builder, var: &DuplexChallengerVariable, diff --git a/sdk/Cargo.toml b/sdk/Cargo.toml index 21cccd3046..4217581948 100644 --- a/sdk/Cargo.toml +++ b/sdk/Cargo.toml @@ -27,7 +27,7 @@ indicatif = "0.17.8" tracing = "0.1.40" hex = "0.4.3" log = "0.4.21" -axum = "=0.7.4" +axum = "=0.7.5" alloy = { git = "https://github.com/alloy-rs/alloy", rev = "bfd0fda", features = [ "contract", "signer-wallet", @@ -36,6 +36,7 @@ dotenv = "0.15.0" sha2 = "0.10.8" dirs = "5.0.1" tempfile = "3.10.1" +num-bigint = "0.4.5" [features] neon = ["sp1-core/neon"] diff --git a/sdk/src/artifacts.rs b/sdk/src/artifacts.rs index 0caee50843..52f1c97376 100644 --- a/sdk/src/artifacts.rs +++ b/sdk/src/artifacts.rs @@ -4,15 +4,19 @@ use anyhow::{Context, Result}; use futures::StreamExt; use indicatif::{ProgressBar, ProgressStyle}; use reqwest::Client; -pub use sp1_prover::build::{build_groth16_artifacts_with_dummy, get_groth16_artifacts_dir}; +pub use sp1_prover::build::{build_groth16_artifacts_with_dummy, try_install_groth16_artifacts}; /// Exports the soliditiy verifier for Groth16 proofs to the specified output directory. /// -/// WARNING: This function may take some time to complete if `SP1_DEV` is enabled (which -/// is the default) as it needs to generate an end-to-end dummy proof to export the verifier. +/// WARNING: If you are on development mode, this function assumes that the Groth16 artifacts have +/// already been built. pub fn export_solidity_groth16_verifier(output_dir: impl Into) -> Result<()> { let output_dir: PathBuf = output_dir.into(); - let artifacts_dir = sp1_prover::build::get_groth16_artifacts_dir(); + let artifacts_dir = if sp1_prover::build::sp1_dev_mode() { + sp1_prover::build::groth16_artifacts_dev_dir() + } else { + sp1_prover::build::try_install_groth16_artifacts() + }; let verifier_path = artifacts_dir.join("SP1Verifier.sol"); if !verifier_path.exists() { diff --git a/sdk/src/lib.rs b/sdk/src/lib.rs index 7d4e47cc06..1249fc0c88 100644 --- a/sdk/src/lib.rs +++ b/sdk/src/lib.rs @@ -26,8 +26,8 @@ pub use provers::{LocalProver, MockProver, NetworkProver, Prover}; use serde::{de::DeserializeOwned, Deserialize, Serialize}; use sp1_core::stark::{MachineVerificationError, ShardProof}; pub use sp1_prover::{ - CoreSC, Groth16Proof, HashableKey, InnerSC, PlonkBn254Proof, SP1CoreProof, SP1Prover, - SP1ProvingKey, SP1PublicValues, SP1Stdin, SP1VerifyingKey, + CoreSC, Groth16Proof, HashableKey, InnerSC, OuterSC, PlonkBn254Proof, SP1Prover, SP1ProvingKey, + SP1PublicValues, SP1Stdin, SP1VerifyingKey, }; /// A client for interacting with SP1. @@ -173,7 +173,7 @@ impl ProverClient { /// let public_values = client.execute(elf, stdin).unwrap(); /// ``` pub fn execute(&self, elf: &[u8], stdin: SP1Stdin) -> Result { - Ok(SP1Prover::execute(elf, &stdin)) + Ok(SP1Prover::execute(elf, &stdin)?) } /// Setup a program to be proven and verified by the SP1 RISC-V zkVM by computing the proving @@ -473,7 +473,18 @@ mod tests { include_bytes!("../../examples/fibonacci/program/elf/riscv32im-succinct-zkvm-elf"); let mut stdin = SP1Stdin::new(); stdin.write(&10usize); - let _ = client.execute(elf, stdin).unwrap(); + client.execute(elf, stdin).unwrap(); + } + + #[test] + #[should_panic] + fn test_execute_panic() { + utils::setup_logger(); + let client = ProverClient::local(); + let elf = include_bytes!("../../tests/panic/elf/riscv32im-succinct-zkvm-elf"); + let mut stdin = SP1Stdin::new(); + stdin.write(&10usize); + client.execute(elf, stdin).unwrap(); } #[test] @@ -514,4 +525,17 @@ mod tests { let proof = client.prove(&pk, stdin).unwrap(); client.verify(&proof, &vk).unwrap(); } + + #[test] + fn test_e2e_prove_groth16_mock() { + utils::setup_logger(); + let client = ProverClient::mock(); + let elf = + include_bytes!("../../examples/fibonacci/program/elf/riscv32im-succinct-zkvm-elf"); + let (pk, vk) = client.setup(elf); + let mut stdin = SP1Stdin::new(); + stdin.write(&10usize); + let proof = client.prove_groth16(&pk, stdin).unwrap(); + client.verify_groth16(&proof, &vk).unwrap(); + } } diff --git a/sdk/src/provers/local.rs b/sdk/src/provers/local.rs index 3c062bdeea..261c9e771f 100644 --- a/sdk/src/provers/local.rs +++ b/sdk/src/provers/local.rs @@ -33,7 +33,7 @@ impl Prover for LocalProver { } fn prove(&self, pk: &SP1ProvingKey, stdin: SP1Stdin) -> Result { - let proof = self.prover.prove_core(pk, &stdin); + let proof = self.prover.prove_core(pk, &stdin)?; Ok(SP1ProofWithPublicValues { proof: proof.proof.0, stdin: proof.stdin, @@ -42,10 +42,10 @@ impl Prover for LocalProver { } fn prove_compressed(&self, pk: &SP1ProvingKey, stdin: SP1Stdin) -> Result { - let proof = self.prover.prove_core(pk, &stdin); + let proof = self.prover.prove_core(pk, &stdin)?; let deferred_proofs = stdin.proofs.iter().map(|p| p.0.clone()).collect(); let public_values = proof.public_values.clone(); - let reduce_proof = self.prover.compress(&pk.vk, proof, deferred_proofs); + let reduce_proof = self.prover.compress(&pk.vk, proof, deferred_proofs)?; Ok(SP1CompressedProof { proof: reduce_proof.proof, stdin, @@ -54,16 +54,22 @@ impl Prover for LocalProver { } fn prove_groth16(&self, pk: &SP1ProvingKey, stdin: SP1Stdin) -> Result { - sp1_prover::build::get_groth16_artifacts_dir(); - - let proof = self.prover.prove_core(pk, &stdin); + let proof = self.prover.prove_core(pk, &stdin)?; let deferred_proofs = stdin.proofs.iter().map(|p| p.0.clone()).collect(); let public_values = proof.public_values.clone(); - let reduce_proof = self.prover.compress(&pk.vk, proof, deferred_proofs); - let compress_proof = self.prover.shrink(reduce_proof); - let outer_proof = self.prover.wrap_bn254(compress_proof); - let artifacts_dir = sp1_prover::build::get_groth16_artifacts_dir(); - let proof = self.prover.wrap_groth16(outer_proof, artifacts_dir); + let reduce_proof = self.prover.compress(&pk.vk, proof, deferred_proofs)?; + let compress_proof = self.prover.shrink(reduce_proof)?; + let outer_proof = self.prover.wrap_bn254(compress_proof)?; + + let groth16_aritfacts = if sp1_prover::build::sp1_dev_mode() { + sp1_prover::build::try_build_groth16_artifacts_dev( + &self.prover.wrap_vk, + &outer_proof.proof, + ) + } else { + sp1_prover::build::try_install_groth16_artifacts() + }; + let proof = self.prover.wrap_groth16(outer_proof, &groth16_aritfacts); Ok(SP1ProofWithPublicValues { proof, stdin, diff --git a/sdk/src/provers/mock.rs b/sdk/src/provers/mock.rs index addb91b257..390c494240 100644 --- a/sdk/src/provers/mock.rs +++ b/sdk/src/provers/mock.rs @@ -4,7 +4,10 @@ use crate::{ SP1ProofVerificationError, SP1ProofWithPublicValues, SP1ProvingKey, SP1VerifyingKey, }; use anyhow::Result; -use sp1_prover::{SP1Prover, SP1Stdin}; +use p3_field::PrimeField; +use sp1_prover::{ + verify::verify_groth16_public_inputs, Groth16Proof, HashableKey, SP1Prover, SP1Stdin, +}; /// An implementation of [crate::ProverClient] that can generate mock proofs. pub struct MockProver { @@ -33,7 +36,7 @@ impl Prover for MockProver { } fn prove(&self, pk: &SP1ProvingKey, stdin: SP1Stdin) -> Result { - let public_values = SP1Prover::execute(&pk.elf, &stdin); + let public_values = SP1Prover::execute(&pk.elf, &stdin)?; Ok(SP1ProofWithPublicValues { proof: vec![], stdin, @@ -50,7 +53,19 @@ impl Prover for MockProver { } fn prove_groth16(&self, pk: &SP1ProvingKey, stdin: SP1Stdin) -> Result { - todo!() + let public_values = SP1Prover::execute(&pk.elf, &stdin)?; + Ok(SP1Groth16Proof { + proof: Groth16Proof { + public_inputs: [ + pk.vk.hash_bn254().as_canonical_biguint().to_string(), + public_values.hash().to_string(), + ], + encoded_proof: "".to_string(), + raw_proof: "".to_string(), + }, + stdin, + public_values, + }) } fn prove_plonk(&self, pk: &SP1ProvingKey, stdin: SP1Stdin) -> Result { @@ -73,7 +88,8 @@ impl Prover for MockProver { Ok(()) } - fn verify_groth16(&self, _proof: &SP1Groth16Proof, _vkey: &SP1VerifyingKey) -> Result<()> { + fn verify_groth16(&self, proof: &SP1Groth16Proof, vkey: &SP1VerifyingKey) -> Result<()> { + verify_groth16_public_inputs(vkey, &proof.public_values, &proof.proof.public_inputs)?; Ok(()) } diff --git a/sdk/src/provers/mod.rs b/sdk/src/provers/mod.rs index 3c3eb9450f..8475a0f7aa 100644 --- a/sdk/src/provers/mod.rs +++ b/sdk/src/provers/mod.rs @@ -56,8 +56,18 @@ pub trait Prover: Send + Sync { .map_err(|e| e.into()) } - /// Verify that a SP1 Groth16 proof is valid given its vkey and metadata. - fn verify_groth16(&self, _proof: &SP1Groth16Proof, _vkey: &SP1VerifyingKey) -> Result<()> { + /// Verify that a SP1 Groth16 proof is valid. Verify that the public inputs of the Groth16Proof match + /// the hash of the VK and the committed public values of the SP1ProofWithPublicValues. + fn verify_groth16(&self, proof: &SP1Groth16Proof, vkey: &SP1VerifyingKey) -> Result<()> { + let sp1_prover = self.sp1_prover(); + + let groth16_aritfacts = if sp1_prover::build::sp1_dev_mode() { + sp1_prover::build::groth16_artifacts_dev_dir() + } else { + sp1_prover::build::groth16_artifacts_dir() + }; + sp1_prover.verify_groth16(&proof.proof, vkey, &proof.public_values, &groth16_aritfacts)?; + Ok(()) } diff --git a/sdk/src/provers/network.rs b/sdk/src/provers/network.rs index 17e9c5b301..a5b2e58e3c 100644 --- a/sdk/src/provers/network.rs +++ b/sdk/src/provers/network.rs @@ -11,6 +11,7 @@ use crate::{ }; use anyhow::{Context, Result}; use serde::de::DeserializeOwned; +use sp1_prover::utils::block_on; use sp1_prover::{SP1Prover, SP1Stdin}; use tokio::{runtime, time::sleep}; @@ -153,26 +154,19 @@ impl Prover for NetworkProver { } fn prove(&self, pk: &SP1ProvingKey, stdin: SP1Stdin) -> Result { - let rt = tokio::runtime::Runtime::new()?; - rt.block_on(async { self.prove_async(&pk.elf, stdin, ProofMode::Core).await }) + block_on(self.prove_async(&pk.elf, stdin, ProofMode::Core)) } fn prove_compressed(&self, pk: &SP1ProvingKey, stdin: SP1Stdin) -> Result { - let rt = tokio::runtime::Runtime::new()?; - rt.block_on(async { - self.prove_async(&pk.elf, stdin, ProofMode::Compressed) - .await - }) + block_on(self.prove_async(&pk.elf, stdin, ProofMode::Compressed)) } fn prove_groth16(&self, pk: &SP1ProvingKey, stdin: SP1Stdin) -> Result { - let rt = tokio::runtime::Runtime::new()?; - rt.block_on(async { self.prove_async(&pk.elf, stdin, ProofMode::Groth16).await }) + block_on(self.prove_async(&pk.elf, stdin, ProofMode::Groth16)) } fn prove_plonk(&self, pk: &SP1ProvingKey, stdin: SP1Stdin) -> Result { - let rt = tokio::runtime::Runtime::new()?; - rt.block_on(async { self.prove_async(&pk.elf, stdin, ProofMode::Plonk).await }) + block_on(self.prove_async(&pk.elf, stdin, ProofMode::Plonk)) } } diff --git a/tests/panic/Cargo.lock b/tests/panic/Cargo.lock new file mode 100644 index 0000000000..9a09c235c9 --- /dev/null +++ b/tests/panic/Cargo.lock @@ -0,0 +1,748 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "anyhow" +version = "1.0.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25bdb32cbbdce2b519a9cd7df3a678443100e265d5e25ca763b7572a5104f5f3" + +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + +[[package]] +name = "autocfg" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "byte-slice-cast" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "der" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "derive_more" +version = "0.99.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest", + "elliptic-curve", + "rfc6979", + "signature", + "spki", +] + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "pkcs8", + "rand_core", + "sec1", + "subtle", + "tap", + "zeroize", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "bitvec", + "rand_core", + "subtle", +] + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core", + "subtle", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "indexmap" +version = "2.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "k256" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "956ff9b67e26e1a6a866cb758f12c6f8746208489e3e4a4b5580802f2f0a587b" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "once_cell", + "sha2", + "signature", +] + +[[package]] +name = "libc" +version = "0.2.154" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" + +[[package]] +name = "libm" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" + +[[package]] +name = "memchr" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" + +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c165a9ab64cf766f73521c0dd2cfdff64f488b8f0b3e621face3462d3db536d7" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "panic-test" +version = "0.1.0" +dependencies = [ + "sp1-derive", + "sp1-zkvm", +] + +[[package]] +name = "parity-scale-codec" +version = "3.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "306800abfa29c7f16596b5970a588435e3d5b3149683d00c12b699cc19f895ee" +dependencies = [ + "arrayvec", + "byte-slice-cast", + "impl-trait-for-tuples", + "parity-scale-codec-derive", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d830939c76d294956402033aee57a6da7b438f2294eb94864c37b0569053a42c" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro-crate" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.82" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ad3d49ab951a01fbaafe34f2ec74122942fe18a3f9814c3268f1bb72042131b" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + +[[package]] +name = "scale-info" +version = "2.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eca070c12893629e2cc820a9761bedf6ce1dcddc9852984d1dc734b8bd9bd024" +dependencies = [ + "cfg-if", + "derive_more", + "parity-scale-codec", + "scale-info-derive", +] + +[[package]] +name = "scale-info-derive" +version = "2.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d35494501194174bda522a32605929eefc9ecf7e0a326c26db1fdd85881eb62" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "subtle", + "zeroize", +] + +[[package]] +name = "serde" +version = "1.0.201" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "780f1cebed1629e4753a1a38a3c72d30b97ec044f0aef68cb26650a3c5cf363c" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.201" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e405930b9796f1c00bee880d03fc7e0bb4b9a11afc776885ffe84320da2865" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.63", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core", +] + +[[package]] +name = "snowbridge-amcl" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460a9ed63cdf03c1b9847e8a12a5f5ba19c4efd5869e4a737e05be25d7c427e5" +dependencies = [ + "parity-scale-codec", + "scale-info", +] + +[[package]] +name = "sp1-derive" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "sp1-precompiles" +version = "0.1.0" +dependencies = [ + "anyhow", + "bincode", + "cfg-if", + "getrandom", + "hex", + "k256", + "num", + "rand", + "serde", + "snowbridge-amcl", +] + +[[package]] +name = "sp1-zkvm" +version = "0.1.0" +dependencies = [ + "bincode", + "cfg-if", + "getrandom", + "k256", + "libm", + "once_cell", + "rand", + "serde", + "sha2", + "sp1-precompiles", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "subtle" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf5be731623ca1a1fb7d8be6f261a3be6d3e2337b8a1f97be944d020c8fcb704" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "toml_datetime" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3550f4e9685620ac18a50ed434eb3aec30db8ba93b0287467bca5826ea25baf1" + +[[package]] +name = "toml_edit" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "zeroize" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" diff --git a/tests/panic/Cargo.toml b/tests/panic/Cargo.toml new file mode 100644 index 0000000000..c81641abb9 --- /dev/null +++ b/tests/panic/Cargo.toml @@ -0,0 +1,9 @@ +[workspace] +[package] +name = "panic-test" +version = "0.1.0" +edition = "2021" + +[dependencies] +sp1-zkvm = { path = "../../zkvm/entrypoint" } +sp1-derive = { path = "../../derive" } diff --git a/tests/panic/elf/riscv32im-succinct-zkvm-elf b/tests/panic/elf/riscv32im-succinct-zkvm-elf new file mode 100755 index 0000000000..8f81cde6d3 Binary files /dev/null and b/tests/panic/elf/riscv32im-succinct-zkvm-elf differ diff --git a/tests/panic/src/main.rs b/tests/panic/src/main.rs new file mode 100644 index 0000000000..cae572d078 --- /dev/null +++ b/tests/panic/src/main.rs @@ -0,0 +1,6 @@ +#![no_main] +sp1_zkvm::entrypoint!(main); + +pub fn main() { + assert_eq!(0, 1); +}