From 28abe5816c4a032c99685cb8161da4cd5a042786 Mon Sep 17 00:00:00 2001 From: Bikem Date: Mon, 20 Oct 2025 21:37:56 +0100 Subject: [PATCH 1/5] scafoold recursive proof --- crates/proof/src/guest.rs | 26 ++++++++++++++++++-------- crates/proof/src/guest/recursive.rs | 11 +++++++++++ 2 files changed, 29 insertions(+), 8 deletions(-) create mode 100644 crates/proof/src/guest/recursive.rs diff --git a/crates/proof/src/guest.rs b/crates/proof/src/guest.rs index bc2593e..20ae1e3 100644 --- a/crates/proof/src/guest.rs +++ b/crates/proof/src/guest.rs @@ -1,8 +1,11 @@ use risc0_zkvm::{guest, sha::Sha256}; use void_types::Block; +pub mod recursive; + pub struct Output { pub block_hash: [u8; 32], + pub parent_hash: [u8; 32], pub block_height: u64, pub pre_digest: D, pub post_digest: D, @@ -15,6 +18,7 @@ where D: for<'a> From<&'a W>, Vec: From, { + // Read block from guest env and deserialize let block_bytes_len: u64 = guest::env::read(); let mut block_bytes = vec![0u8; block_bytes_len as usize]; guest::env::read_slice(&mut block_bytes); @@ -23,22 +27,25 @@ where let block = Block::from_bytes(&block_bytes).expect("Block failed to deserialize"); + // Read witness from guest env and deserialize let witness_bytes_len: u64 = guest::env::read(); let mut witness_bytes = vec![0u8; witness_bytes_len as usize]; guest::env::read_slice(&mut witness_bytes); let mut witness = W::from(witness_bytes); + // Expensive part #1 let pre_digest = D::from(&witness); state_transition_function(&block, &mut witness); + // Expensive part #2 let post_digest = D::from(&witness); let output = Output { block_hash: (*block_hash).into(), - // block_height: block.height, - block_height: 0, + parent_hash: block.parent_hash, + block_height: block.height, pre_digest, post_digest, }; @@ -54,6 +61,7 @@ where { let mut bytes = Vec::new(); bytes.extend(&output.block_hash); + bytes.extend(&output.parent_hash); bytes.extend(&output.block_height.to_be_bytes()); let pre_bytes: Vec = output.pre_digest.into(); bytes.extend(&(pre_bytes.len() as u64).to_be_bytes()); @@ -68,19 +76,20 @@ pub fn from_output_bytes(bytes: &[u8]) -> Option> where D: for<'a> TryFrom<&'a [u8]>, { - if bytes.len() < 32 + 8 + 8 { + if bytes.len() < 32 + 32 + 8 + 8 { return None; } let block_hash: [u8; 32] = bytes[0..32].try_into().expect("Converting to sized array"); + let parent_hash: [u8; 32] = bytes[32..64].try_into().expect("Converting to sized array"); let block_height = - u64::from_be_bytes(bytes[32..40].try_into().expect("Converting to sized array")); + u64::from_be_bytes(bytes[64..72].try_into().expect("Converting to sized array")); let pre_len = - u64::from_be_bytes(bytes[40..48].try_into().expect("Converting to sized array")) as usize; - if bytes.len() < 48 + pre_len + 8 { + u64::from_be_bytes(bytes[72..80].try_into().expect("Converting to sized array")) as usize; + if bytes.len() < 80 + pre_len + 8 { return None; } - let pre_bytes = &bytes[48..48 + pre_len]; - let post_len_start = 48 + pre_len; + let pre_bytes = &bytes[80..80 + pre_len]; + let post_len_start = 80 + pre_len; let post_len = u64::from_be_bytes( bytes[post_len_start..post_len_start + 8] .try_into() @@ -92,6 +101,7 @@ where let post_bytes = &bytes[post_len_start + 8..post_len_start + 8 + post_len]; Some(Output { block_hash, + parent_hash, block_height, // TODO: check that it is safe to lose the error pre_digest: D::try_from(pre_bytes).ok()?, diff --git a/crates/proof/src/guest/recursive.rs b/crates/proof/src/guest/recursive.rs new file mode 100644 index 0000000..4233a5d --- /dev/null +++ b/crates/proof/src/guest/recursive.rs @@ -0,0 +1,11 @@ +use crate::guest::Output; +use risc0_zkvm::Receipt; + +pub struct Input { + state_output: Output, + receipt: Receipt, +} + +pub fn proof(inputs: Vec>) { + +} From 7ad67e31fa2a8efdee78d2e97f4a92b36b08b596 Mon Sep 17 00:00:00 2001 From: Bikem Date: Mon, 20 Oct 2025 21:39:44 +0100 Subject: [PATCH 2/5] fmt --- crates/proof/src/guest/recursive.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/proof/src/guest/recursive.rs b/crates/proof/src/guest/recursive.rs index 4233a5d..79d932a 100644 --- a/crates/proof/src/guest/recursive.rs +++ b/crates/proof/src/guest/recursive.rs @@ -6,6 +6,4 @@ pub struct Input { receipt: Receipt, } -pub fn proof(inputs: Vec>) { - -} +pub fn proof(inputs: Vec>) {} From b8823b7c21a51636657568767e42431ed942aaf7 Mon Sep 17 00:00:00 2001 From: Bikem Date: Thu, 23 Oct 2025 22:14:31 +0100 Subject: [PATCH 3/5] add recursive proof to host --- Cargo.lock | 1 + crates/proof/src/host.rs | 2 + crates/proof/src/host/recursive.rs | 145 +++++++++++++++++++++++++++++ crates/proof/src/lib.rs | 2 + crates/proof/src/types.rs | 12 +++ 5 files changed, 162 insertions(+) create mode 100644 crates/proof/src/host/recursive.rs create mode 100644 crates/proof/src/types.rs diff --git a/Cargo.lock b/Cargo.lock index 0d555bf..6e3d8fc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5273,6 +5273,7 @@ name = "void-proof" version = "0.1.0" dependencies = [ "risc0-zkvm", + "serde", "serde_json", "thiserror", "tracing", diff --git a/crates/proof/src/host.rs b/crates/proof/src/host.rs index dbff356..6a3b1e2 100644 --- a/crates/proof/src/host.rs +++ b/crates/proof/src/host.rs @@ -3,6 +3,8 @@ use thiserror::Error; use tracing::{debug, info, instrument, warn}; use void_types::Block; +pub mod recursive; + #[derive(Debug, Error)] pub enum ProverError { #[error("Failed to write to environment: {0}")] diff --git a/crates/proof/src/host/recursive.rs b/crates/proof/src/host/recursive.rs new file mode 100644 index 0000000..f3f5af9 --- /dev/null +++ b/crates/proof/src/host/recursive.rs @@ -0,0 +1,145 @@ +use crate::types::RecursiveCommit; +use risc0_zkvm::{ExecutorEnv, Receipt, default_prover}; +use thiserror::Error; +use tracing::{debug, info}; + +pub type StateReceipt = Receipt; +pub type RecursiveReceipt = Receipt; + +pub struct Input { + pub recursive: RecursiveArgs, + pub state_receipts: Vec, +} + +pub enum RecursiveArgs { + Init(Init), + Step(RecursiveReceipt), +} + +pub enum RecursiveInput { + Init(Init), + Step(Step), +} + +pub struct Init { + pub this_image_id: [u32; 8], +} + +pub struct Step { + pub previous_commit: RecursiveCommit, +} + +#[derive(Error, Debug)] +pub enum RecursiveProofError { + #[error("Error writing to environment: {0}")] + Write(String), + #[error("Failed to build environment: {0}")] + Build(String), + #[error("Failed to prove: {0}")] + Proof(String), + #[error("Failed to serialize receipt: {0}")] + Serialization(#[from] serde_json::Error), +} + +#[derive(serde::Serialize, serde::Deserialize)] +pub enum RecursiveType { + Init, + Step, +} + +pub fn proof(input: Input, elf: &[u8]) -> Result, RecursiveProofError> +where + D: for<'a> TryFrom<&'a [u8]>, + Vec: From, +{ + info!("starting zkVM proof generation"); + let mut env = ExecutorEnv::builder(); + + // Write the number of state receipts to env. + env.write(&(input.state_receipts.len() as u64)) + .inspect_err(|e| { + tracing::error!( + "Failed to write number of state receipts to environment: {}", + e + ) + }) + .map_err(|e| RecursiveProofError::Write(e.to_string()))?; + + for state_receipt in input.state_receipts { + // Write state receipt bytes to env. + env.write(&(state_receipt.journal.bytes.len() as u64)) + .inspect_err(|e| { + tracing::error!( + "Failed to write state commit bytes length to environment: {}", + e + ) + }) + .map_err(|e| RecursiveProofError::Write(e.to_string()))?; + env.write_slice(&state_receipt.journal.bytes); + + // Add state receipt as assumption. + env.add_assumption(state_receipt); + } + + match input.recursive { + RecursiveArgs::Init(init) => { + // Write recursion type to env. + env.write(&RecursiveType::Init) + .inspect_err(|e| { + tracing::error!( + "Failed to write recursion type (Init) to environment: {}", + e + ) + }) + .map_err(|e| RecursiveProofError::Write(e.to_string()))?; + + // Write image id to env. + env.write(&init.this_image_id) + .inspect_err(|e| tracing::error!("Failed to write image id to environment: {}", e)) + .map_err(|e| RecursiveProofError::Write(e.to_string()))?; + } + RecursiveArgs::Step(recursive_receipt) => { + // Write recursion type to env. + env.write(&RecursiveType::Step) + .inspect_err(|e| { + tracing::error!( + "Failed to write recursion type (Step) to environment: {}", + e + ) + }) + .map_err(|e| RecursiveProofError::Write(e.to_string()))?; + + // Write recursive receipt bytes to env. + env.write(&(recursive_receipt.journal.bytes.len() as u64)) + .inspect_err(|e| { + tracing::error!( + "Failed to write recursive receipt length to environment: {}", + e + ) + }) + .map_err(|e| RecursiveProofError::Write(e.to_string()))?; + env.write_slice(&recursive_receipt.journal.bytes); + + // Add recursive receipt as assumption. + env.add_assumption(recursive_receipt); + } + }; + + let env = env + .build() + .inspect_err(|e| tracing::error!("Failed to build executor environment: {}", e)) + .map_err(|e| RecursiveProofError::Build(e.to_string()))?; + + debug!("executor environment built"); + let prover = default_prover(); + + debug!("running prover"); + let receipt = prover + .prove(env, elf) + .inspect_err(|e| tracing::error!("Failed to prove: {}", e)) + .map_err(|e| RecursiveProofError::Proof(e.to_string()))? + .receipt; + + info!("proof generation completed"); + Ok(serde_json::to_vec(&receipt)?) +} diff --git a/crates/proof/src/lib.rs b/crates/proof/src/lib.rs index 170aff8..7295df8 100644 --- a/crates/proof/src/lib.rs +++ b/crates/proof/src/lib.rs @@ -2,3 +2,5 @@ pub mod guest; #[cfg(feature = "host")] pub mod host; + +pub mod types; diff --git a/crates/proof/src/types.rs b/crates/proof/src/types.rs new file mode 100644 index 0000000..eff7541 --- /dev/null +++ b/crates/proof/src/types.rs @@ -0,0 +1,12 @@ +pub struct RecursiveCommit { + pub this_image_id: [u32; 8], + pub state_commit: StateCommit, +} + +pub struct StateCommit { + pub block_hash: [u8; 32], + pub parent_hash: [u8; 32], + pub block_height: u64, + pub pre_digest: D, + pub post_digest: D, +} From 0266b49cb914de43a076f0ce0cce2dc2658d1d87 Mon Sep 17 00:00:00 2001 From: Bikem Date: Mon, 27 Oct 2025 21:06:42 +0000 Subject: [PATCH 4/5] implement recursive proof --- crates/proof/Cargo.toml | 1 + crates/proof/src/guest.rs | 32 ++---- crates/proof/src/guest/recursive.rs | 168 +++++++++++++++++++++++++++- 3 files changed, 175 insertions(+), 26 deletions(-) diff --git a/crates/proof/Cargo.toml b/crates/proof/Cargo.toml index 05b6195..f9b226a 100644 --- a/crates/proof/Cargo.toml +++ b/crates/proof/Cargo.toml @@ -9,6 +9,7 @@ repository.workspace = true [dependencies] risc0-zkvm.workspace = true +serde.workspace = true serde_json.workspace = true thiserror = { workspace = true, optional = true } tracing = { workspace = true, optional = true } diff --git a/crates/proof/src/guest.rs b/crates/proof/src/guest.rs index 20ae1e3..34d7f53 100644 --- a/crates/proof/src/guest.rs +++ b/crates/proof/src/guest.rs @@ -1,22 +1,15 @@ +use crate::types::StateCommit; use risc0_zkvm::{guest, sha::Sha256}; use void_types::Block; pub mod recursive; -pub struct Output { - pub block_hash: [u8; 32], - pub parent_hash: [u8; 32], - pub block_height: u64, - pub pre_digest: D, - pub post_digest: D, -} - pub fn proof(state_transition_function: F) where W: From>, F: Fn(&Block, &mut W), D: for<'a> From<&'a W>, - Vec: From, + Vec: for<'a> From<&'a D>, { // Read block from guest env and deserialize let block_bytes_len: u64 = guest::env::read(); @@ -42,7 +35,7 @@ where // Expensive part #2 let post_digest = D::from(&witness); - let output = Output { + let output = StateCommit { block_hash: (*block_hash).into(), parent_hash: block.parent_hash, block_height: block.height, @@ -50,29 +43,28 @@ where post_digest, }; - let output = to_output_bytes(output); + let mut bytes = Vec::new(); + to_output_bytes(&mut bytes, &output); - guest::env::commit_slice(&output); + guest::env::commit_slice(&bytes); } -pub fn to_output_bytes(output: Output) -> Vec +pub fn to_output_bytes(bytes: &mut Vec, output: &StateCommit) where - Vec: From, + Vec: for<'a> From<&'a D>, { - let mut bytes = Vec::new(); bytes.extend(&output.block_hash); bytes.extend(&output.parent_hash); bytes.extend(&output.block_height.to_be_bytes()); - let pre_bytes: Vec = output.pre_digest.into(); + let pre_bytes: Vec = (&output.pre_digest).into(); bytes.extend(&(pre_bytes.len() as u64).to_be_bytes()); bytes.extend(&pre_bytes); - let post_bytes: Vec = output.post_digest.into(); + let post_bytes: Vec = (&output.post_digest).into(); bytes.extend(&(post_bytes.len() as u64).to_be_bytes()); bytes.extend(&post_bytes); - bytes } -pub fn from_output_bytes(bytes: &[u8]) -> Option> +pub fn from_output_bytes(bytes: &[u8]) -> Option> where D: for<'a> TryFrom<&'a [u8]>, { @@ -99,7 +91,7 @@ where return None; } let post_bytes = &bytes[post_len_start + 8..post_len_start + 8 + post_len]; - Some(Output { + Some(StateCommit { block_hash, parent_hash, block_height, diff --git a/crates/proof/src/guest/recursive.rs b/crates/proof/src/guest/recursive.rs index 79d932a..137748d 100644 --- a/crates/proof/src/guest/recursive.rs +++ b/crates/proof/src/guest/recursive.rs @@ -1,9 +1,165 @@ -use crate::guest::Output; -use risc0_zkvm::Receipt; +use crate::{ + host::recursive::RecursiveType, + types::{RecursiveCommit, StateCommit}, +}; +use risc0_zkvm::guest::env; +use std::fmt::Debug; -pub struct Input { - state_output: Output, - receipt: Receipt, +pub fn proof(state_image_id: [u32; 8]) +where + D: for<'a> TryFrom<&'a [u8]> + Debug + Default + PartialEq, + Vec: for<'a> From<&'a D>, +{ + let state_commits_count = env::read(); + let mut state_commits = vec![]; + + for _ in 0..state_commits_count { + let state_commit_len = env::read(); + + let mut state_commit_bytes = vec![0u8; state_commit_len]; + env::read_slice(&mut state_commit_bytes); + + let state_commit: StateCommit = super::from_output_bytes(&state_commit_bytes) + .expect("Failed to deserialize state commit"); + + state_commits.push(state_commit); + } + + let recursive_type: RecursiveType = env::read(); + + match recursive_type { + RecursiveType::Init => { + let image_id: [u32; 8] = env::read(); + + let first_state_commit = state_commits + .first() + .expect("Must have at least one state commit"); + + assert_eq!(first_state_commit.parent_hash, [0u8; 32]); + assert_eq!(first_state_commit.pre_digest, D::default()); + assert_eq!(first_state_commit.block_height, 0); + + let mut bytes = Vec::new(); + super::to_output_bytes(&mut bytes, first_state_commit); + + env::verify(state_image_id, &bytes).expect("init state proof failed to verify"); + + verify_state_steps(state_image_id, &state_commits); + + let recursive_commit = RecursiveCommit { + this_image_id: image_id, + state_commit: state_commits + .pop() + .expect("Must have at least one state commit"), + }; + + let mut bytes = Vec::new(); + to_output_bytes(&mut bytes, &recursive_commit); + env::commit_slice(&bytes[..]); + } + RecursiveType::Step => { + let recursive_commit_len = env::read(); + + let mut recursive_commit_bytes = vec![0u8; recursive_commit_len]; + env::read_slice(&mut recursive_commit_bytes); + + let recursive_commit: RecursiveCommit = from_output_bytes(&recursive_commit_bytes) + .expect("Failed to deserialize state commit"); + + let first_state_commit = state_commits + .first() + .expect("Must have at least one state commit"); + + verify_step( + state_image_id, + &recursive_commit.state_commit, + first_state_commit, + ); + verify_state_steps(state_image_id, &state_commits); + + let recursive_commit = RecursiveCommit { + this_image_id: recursive_commit.this_image_id, + state_commit: state_commits + .pop() + .expect("Must have at least one state commit"), + }; + + let mut bytes = Vec::new(); + to_output_bytes(&mut bytes, &recursive_commit); + env::commit_slice(&bytes[..]); + } + } +} + +// Verify a single step in the state commit chain. +fn verify_step(image_id: [u32; 8], parent: &StateCommit, commit: &StateCommit) +where + D: Debug + PartialEq, + Vec: for<'a> From<&'a D>, +{ + assert_eq!(commit.parent_hash, parent.block_hash); + assert_eq!(commit.pre_digest, parent.post_digest); + assert_eq!( + commit.block_height, + parent.block_height.checked_add(1).unwrap() + ); + + let mut bytes = Vec::new(); + super::to_output_bytes(&mut bytes, commit); + + env::verify(image_id, &bytes).expect("step state proof failed to verify"); +} + +// Verify a state commit chain. +fn verify_state_steps(image_id: [u32; 8], state_commits: &[StateCommit]) +where + D: Debug + PartialEq, + Vec: for<'a> From<&'a D>, +{ + for (parent, commit) in state_commits.iter().zip(state_commits.iter().skip(1)) { + verify_step(image_id, parent, commit); + } +} + +pub fn to_output_bytes(bytes: &mut Vec, commit: &RecursiveCommit) +where + Vec: for<'a> From<&'a D>, +{ + let mut image_id: [u8; 32] = [0u8; 32]; + for (input, output) in commit + .this_image_id + .iter() + .map(|e| e.to_be_bytes()) + .flatten() + .zip(image_id.iter_mut()) + { + *output = input; + } + bytes.extend(image_id); + + super::to_output_bytes(bytes, &commit.state_commit); } -pub fn proof(inputs: Vec>) {} +pub fn from_output_bytes(bytes: &[u8]) -> Option> +where + D: for<'a> TryFrom<&'a [u8]>, +{ + if bytes.len() < 32 { + return None; + } + + let mut image_id: [u32; 8] = [0u32; 8]; + for (input, output) in bytes[0..32] + .chunks_exact(std::mem::size_of::()) + .zip(image_id.iter_mut()) + { + *output = u32::from_be_bytes(input.try_into().expect("The sizes match")); + } + + let state_commit = super::from_output_bytes(&bytes[32..])?; + + Some(RecursiveCommit { + this_image_id: image_id, + state_commit, + }) +} From fbdd0e3a2ec749d5cd7d724825a848480b99d6d6 Mon Sep 17 00:00:00 2001 From: Bikem Date: Tue, 4 Nov 2025 14:24:37 +0000 Subject: [PATCH 5/5] optimizations and moves --- crates/proof/src/guest.rs | 2 -- crates/proof/src/guest/recursive.rs | 7 ++----- crates/proof/src/host/recursive.rs | 12 +++--------- crates/proof/src/types.rs | 6 ++++++ 4 files changed, 11 insertions(+), 16 deletions(-) diff --git a/crates/proof/src/guest.rs b/crates/proof/src/guest.rs index 34d7f53..d9b6830 100644 --- a/crates/proof/src/guest.rs +++ b/crates/proof/src/guest.rs @@ -27,12 +27,10 @@ where let mut witness = W::from(witness_bytes); - // Expensive part #1 let pre_digest = D::from(&witness); state_transition_function(&block, &mut witness); - // Expensive part #2 let post_digest = D::from(&witness); let output = StateCommit { diff --git a/crates/proof/src/guest/recursive.rs b/crates/proof/src/guest/recursive.rs index 137748d..85f5810 100644 --- a/crates/proof/src/guest/recursive.rs +++ b/crates/proof/src/guest/recursive.rs @@ -1,7 +1,4 @@ -use crate::{ - host::recursive::RecursiveType, - types::{RecursiveCommit, StateCommit}, -}; +use crate::types::{RecursiveCommit, RecursiveType, StateCommit}; use risc0_zkvm::guest::env; use std::fmt::Debug; @@ -11,7 +8,7 @@ where Vec: for<'a> From<&'a D>, { let state_commits_count = env::read(); - let mut state_commits = vec![]; + let mut state_commits = Vec::with_capacity(state_commits_count); for _ in 0..state_commits_count { let state_commit_len = env::read(); diff --git a/crates/proof/src/host/recursive.rs b/crates/proof/src/host/recursive.rs index f3f5af9..80141a7 100644 --- a/crates/proof/src/host/recursive.rs +++ b/crates/proof/src/host/recursive.rs @@ -1,4 +1,4 @@ -use crate::types::RecursiveCommit; +use crate::types::{RecursiveCommit, RecursiveType}; use risc0_zkvm::{ExecutorEnv, Receipt, default_prover}; use thiserror::Error; use tracing::{debug, info}; @@ -13,7 +13,7 @@ pub struct Input { pub enum RecursiveArgs { Init(Init), - Step(RecursiveReceipt), + Step(Box), } pub enum RecursiveInput { @@ -41,12 +41,6 @@ pub enum RecursiveProofError { Serialization(#[from] serde_json::Error), } -#[derive(serde::Serialize, serde::Deserialize)] -pub enum RecursiveType { - Init, - Step, -} - pub fn proof(input: Input, elf: &[u8]) -> Result, RecursiveProofError> where D: for<'a> TryFrom<&'a [u8]>, @@ -121,7 +115,7 @@ where env.write_slice(&recursive_receipt.journal.bytes); // Add recursive receipt as assumption. - env.add_assumption(recursive_receipt); + env.add_assumption(*recursive_receipt); } }; diff --git a/crates/proof/src/types.rs b/crates/proof/src/types.rs index eff7541..1da1600 100644 --- a/crates/proof/src/types.rs +++ b/crates/proof/src/types.rs @@ -3,6 +3,12 @@ pub struct RecursiveCommit { pub state_commit: StateCommit, } +#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, Copy, PartialEq)] +pub enum RecursiveType { + Init, + Step, +} + pub struct StateCommit { pub block_hash: [u8; 32], pub parent_hash: [u8; 32],