From 518bea3cb2172f2a733429ac32ebb573b10e7dc3 Mon Sep 17 00:00:00 2001 From: John Guibas Date: Mon, 13 May 2024 17:27:48 -0700 Subject: [PATCH] feat: fix execution + proving errors (#715) --- .github/workflows/pr.yml | 4 +- Cargo.lock | 2 + core/Cargo.toml | 1 + core/benches/main.rs | 6 +- core/src/cpu/trace.rs | 2 +- core/src/lookup/debug.rs | 2 +- core/src/memory/global.rs | 8 +- core/src/runtime/io.rs | 8 +- core/src/runtime/mod.rs | 175 ++-- core/src/stark/machine.rs | 8 +- core/src/syscall/halt.rs | 8 - core/src/syscall/hint.rs | 4 +- core/src/syscall/precompiles/keccak256/air.rs | 4 +- core/src/syscall/precompiles/keccak256/mod.rs | 2 +- core/src/utils/env.rs | 3 +- core/src/utils/programs.rs | 3 + core/src/utils/prove.rs | 367 ++++----- eval/src/main.rs | 14 +- prover/Cargo.toml | 13 +- prover/src/build.rs | 8 +- prover/src/lib.rs | 118 +-- prover/src/types.rs | 4 + recursion/program/src/constraints.rs | 2 +- recursion/program/src/machine/mod.rs | 2 +- recursion/program/src/stark.rs | 2 +- sdk/src/lib.rs | 15 +- sdk/src/provers/local.rs | 14 +- sdk/src/provers/mock.rs | 2 +- tests/panic/Cargo.lock | 748 ++++++++++++++++++ tests/panic/Cargo.toml | 9 + tests/panic/elf/riscv32im-succinct-zkvm-elf | Bin 0 -> 97448 bytes tests/panic/src/main.rs | 6 + 32 files changed, 1195 insertions(+), 369 deletions(-) create mode 100644 tests/panic/Cargo.lock create mode 100644 tests/panic/Cargo.toml create mode 100755 tests/panic/elf/riscv32im-succinct-zkvm-elf create mode 100644 tests/panic/src/main.rs diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index ceb72c905..259497a7d 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -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/Cargo.lock b/Cargo.lock index 27a887dc0..3a0e9cb91 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4337,6 +4337,7 @@ dependencies = [ "strum", "strum_macros", "tempfile", + "thiserror", "tiny-keccak", "tracing", "tracing-forest", @@ -4435,6 +4436,7 @@ dependencies = [ "sp1-recursion-program", "subtle-encoding", "tempfile", + "thiserror", "tokio", "tracing", "tracing-appender", diff --git a/core/Cargo.toml b/core/Cargo.toml index e477ec263..4758fc939 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -58,6 +58,7 @@ strum = "0.26" web-time = "1.1.0" rayon-scan = "0.1.1" serial_test = "3.1.1" +thiserror = "1.0.60" [dev-dependencies] tiny-keccak = { version = "2.0.2", features = ["keccak"] } diff --git a/core/benches/main.rs b/core/benches/main.rs index 86e608344..ca63a750f 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 e5d5b8320..7000fe062 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/lookup/debug.rs b/core/src/lookup/debug.rs index d9d1dd241..c58d882b7 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 e5fa81d37..5c9974e09 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 14ba34450..0be9da0e1 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 d7df28228..282ce0365 100644 --- a/core/src/runtime/mod.rs +++ b/core/src/runtime/mod.rs @@ -28,13 +28,13 @@ 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,10 +68,8 @@ 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, @@ -85,6 +83,20 @@ pub struct Runtime { 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 +117,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,7 +126,6 @@ impl Runtime { .unwrap_or(0); let shard_size = env::shard_size() as u32; - Self { record, state: ExecutionState::new(program.pc_start), @@ -125,7 +136,6 @@ impl Runtime { cycle_tracker: HashMap::new(), io_buf: HashMap::new(), trace_buf, - fail_on_panic: true, unconstrained: false, unconstrained_state: ForkState::default(), syscall_map, @@ -215,6 +225,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 +273,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 +514,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 +590,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 +604,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 +620,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 +648,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 +661,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 +733,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 +743,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 +751,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 +779,8 @@ impl Runtime { self.state.clk += precompile_cycles; exit_code = returned_exit_code; } - Opcode::EBREAK => { - todo!() + return Err(ExecutionError::Breakpoint()); } // Multiply instructions. @@ -814,14 +841,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 +868,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 +883,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 +895,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,18 +937,19 @@ impl Runtime { tracing::info!("starting execution"); } - pub fn run(&mut self) { + 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() {} + 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(); @@ -929,7 +960,7 @@ 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; } @@ -947,7 +978,7 @@ impl Runtime { self.postprocess(); } - done + Ok(done) } fn postprocess(&mut self) { @@ -1019,7 +1050,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}; @@ -1041,14 +1072,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: @@ -1062,7 +1105,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); } @@ -1079,7 +1122,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); } @@ -1096,7 +1139,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); } @@ -1114,7 +1157,7 @@ pub mod tests { let mut runtime = Runtime::new(program); - runtime.run(); + runtime.run().unwrap(); assert_eq!(runtime.register(Register::X31), 37); } @@ -1131,7 +1174,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); } @@ -1148,7 +1191,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); } @@ -1165,7 +1208,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); } @@ -1182,7 +1225,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); } @@ -1199,7 +1242,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); } @@ -1216,7 +1259,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); } @@ -1233,7 +1276,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); } @@ -1249,7 +1292,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); } @@ -1265,7 +1308,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); } @@ -1281,7 +1324,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); } @@ -1297,7 +1340,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); } @@ -1311,7 +1354,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); } @@ -1325,7 +1368,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); } @@ -1339,7 +1382,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); } @@ -1353,7 +1396,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); } @@ -1367,7 +1410,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); } @@ -1386,7 +1429,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); @@ -1400,7 +1443,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); } @@ -1618,7 +1661,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/stark/machine.rs b/core/src/stark/machine.rs index 89638765f..948ceca9a 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/syscall/halt.rs b/core/src/syscall/halt.rs index 68e3a39f4..9572beecb 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 d11b86dbc..e79fedfec 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 36360af21..ceda18305 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 f0b0a7114..b44ce53f0 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 371bdc561..2ee11fef5 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 839a55b37..58af5a08c 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 515672a64..47a96d1c1 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 ee637ba69..c11e4413f 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/prover/Cargo.toml b/prover/Cargo.toml index 4b3d0885f..8c08fa743 100644 --- a/prover/Cargo.toml +++ b/prover/Cargo.toml @@ -40,18 +40,7 @@ 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" +thiserror = "1.0.60" [[bin]] name = "build_groth16" diff --git a/prover/src/build.rs b/prover/src/build.rs index 0c3325e43..e3b79386e 100644 --- a/prover/src/build.rs +++ b/prover/src/build.rs @@ -75,16 +75,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) } diff --git a/prover/src/lib.rs b/prover/src/lib.rs index 5281728cb..266884e7e 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,11 +14,13 @@ 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::PathBuf; use crate::utils::RECONSTRUCT_COMMITMENTS_ENV_VAR; use p3_baby_bear::BabyBear; @@ -28,27 +30,23 @@ use rayon::iter::{IntoParallelIterator, ParallelIterator}; use rayon::prelude::*; use sp1_core::air::PublicValues; pub use sp1_core::io::{SP1PublicValues, SP1Stdin}; -use sp1_core::runtime::Runtime; -use sp1_core::stark::MachineVerificationError; -use sp1_core::stark::{Challenge, StarkProvingKey}; -use sp1_core::utils::DIGEST_SIZE; +use sp1_core::runtime::{ExecutionError, Runtime}; +use sp1_core::stark::{Challenge, MachineVerificationError, StarkProvingKey}; use sp1_core::{ runtime::Program, stark::{ LocalProver, RiscvAir, ShardProof, StarkGenericConfig, StarkMachine, StarkVerifyingKey, Val, }, - utils::{run_and_prove, BabyBearPoseidon2}, + utils::{BabyBearPoseidon2, SP1CoreProverError, DIGEST_SIZE}, }; 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::config::{InnerConfig, 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; @@ -60,8 +58,6 @@ use sp1_recursion_program::machine::{ SP1RecursionMemoryLayout, SP1RecursiveVerifier, SP1ReduceMemoryLayout, SP1RootMemoryLayout, SP1RootVerifier, }; -use std::env; -use std::path::PathBuf; use tracing::instrument; pub use types::*; use utils::words_to_bytes; @@ -83,7 +79,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,30 +213,34 @@ 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()?; + 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. @@ -250,10 +250,8 @@ impl SP1Prover { vk: &SP1VerifyingKey, proof: SP1CoreProof, deferred_proofs: Vec>, - ) -> SP1ReduceProof { - // Set the batch size for the reduction tree. + ) -> Result, SP1RecursionProverError> { let batch_size = 2; - let shard_proofs = &proof.proof.0; // Setup the reconstruct commitments flags to false and save its state. @@ -267,11 +265,10 @@ impl SP1Prover { 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; - let mut core_inputs = Vec::new(); - let mut reconstruct_challenger = self.core_machine.config().challenger(); vk.vk.observe_into(&mut reconstruct_challenger); @@ -460,14 +457,17 @@ impl SP1Prover { // Restore the prover parameters. env::set_var(RECONSTRUCT_COMMITMENTS_ENV_VAR, rc); - SP1ReduceProof { + Ok(SP1ReduceProof { proof: reduce_proof.0, - } + }) } /// 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 +504,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,9 +565,9 @@ 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. @@ -624,9 +627,10 @@ mod tests { use std::fs::File; use std::io::{Read, Write}; + use super::*; use crate::build::{build_groth16_artifacts, get_groth16_artifacts_dir}; - use super::*; + use anyhow::Result; use p3_field::PrimeField32; use serial_test::serial; use sp1_core::io::SP1Stdin; @@ -636,7 +640,7 @@ mod tests { /// pipeline. #[test] #[serial] - fn test_e2e() { + fn test_e2e() -> Result<()> { setup_logger(); let elf = include_bytes!("../../tests/fibonacci/elf/riscv32im-succinct-zkvm-elf"); @@ -648,25 +652,25 @@ 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)?; 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. @@ -696,13 +700,15 @@ mod tests { build_groth16_artifacts(&prover.wrap_vk, &wrapped_bn254_proof.proof, &artifacts_dir); let groth16_proof = prover.wrap_groth16(wrapped_bn254_proof, artifacts_dir); println!("{:?}", groth16_proof); + + 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 +730,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 +740,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 +767,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 +779,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 547eac770..94ffcc3ce 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}; @@ -194,3 +195,6 @@ pub enum SP1ReduceProofWrapper { Core(SP1ReduceProof), Recursive(SP1ReduceProof), } + +#[derive(Error, Debug)] +pub enum SP1RecursionProverError {} diff --git a/recursion/program/src/constraints.rs b/recursion/program/src/constraints.rs index c7ab21108..01ec47718 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/mod.rs b/recursion/program/src/machine/mod.rs index 2d262332a..a298427d4 100644 --- a/recursion/program/src/machine/mod.rs +++ b/recursion/program/src/machine/mod.rs @@ -82,7 +82,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/stark.rs b/recursion/program/src/stark.rs index cd59a8804..9f43af62f 100644 --- a/recursion/program/src/stark.rs +++ b/recursion/program/src/stark.rs @@ -447,7 +447,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"); diff --git a/sdk/src/lib.rs b/sdk/src/lib.rs index 7d4e47cc0..7a53636ec 100644 --- a/sdk/src/lib.rs +++ b/sdk/src/lib.rs @@ -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] diff --git a/sdk/src/provers/local.rs b/sdk/src/provers/local.rs index 3c062bdee..5136de43b 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, @@ -56,12 +56,12 @@ 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 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); Ok(SP1ProofWithPublicValues { diff --git a/sdk/src/provers/mock.rs b/sdk/src/provers/mock.rs index addb91b25..9f25eafd3 100644 --- a/sdk/src/provers/mock.rs +++ b/sdk/src/provers/mock.rs @@ -33,7 +33,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, diff --git a/tests/panic/Cargo.lock b/tests/panic/Cargo.lock new file mode 100644 index 000000000..9a09c235c --- /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 000000000..c81641abb --- /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 0000000000000000000000000000000000000000..8f81cde6d3606666fd8f191667bee4d5aa88e5f6 GIT binary patch literal 97448 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
  • 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�?xN_gvVw;$ar$|Vjdg7h}+B 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 literal 0 HcmV?d00001 diff --git a/tests/panic/src/main.rs b/tests/panic/src/main.rs new file mode 100644 index 000000000..cae572d07 --- /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); +}