Skip to content

ziren: add prover and verifier#616

Draft
gballet wants to merge 3 commits intomainfrom
verify-and-prove-ziren
Draft

ziren: add prover and verifier#616
gballet wants to merge 3 commits intomainfrom
verify-and-prove-ziren

Conversation

@gballet
Copy link
Contributor

@gballet gballet commented Feb 27, 2026

No description provided.

Copilot AI review requested due to automatic review settings February 27, 2026 21:40
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds new Rust staticlib “glue” layers for SP1 and Ziren proving/verification, wires them into the Zig state-proving manager and build system, and implements OpenVM receipt verification (previously a stub).

Changes:

  • Add sp1-glue and ziren-glue crates exposing *_prove / *_verify C-ABI entrypoints and include them in the Rust workspace.
  • Implement openvm_verify by deserializing a proof package, checking an ELF hash, and verifying the proof with OpenVM SDK.
  • Extend Zig build + manager plumbing to select/link SP1/Ziren libraries and route prove/verify calls.

Reviewed changes

Copilot reviewed 9 out of 10 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
rust/ziren-glue/src/lib.rs New Ziren prover/verifier FFI surface and proof packaging.
rust/ziren-glue/Cargo.toml New crate definition + Ziren SDK dependency.
rust/sp1-glue/src/lib.rs New SP1 prover/verifier FFI surface and proof packaging.
rust/sp1-glue/Cargo.toml New crate definition + SP1 SDK dependency.
rust/openvm-glue/src/lib.rs Replace OpenVM verify stub with real verification flow.
rust/openvm-glue/Cargo.toml Add openvm-circuit dependency to deserialize proof type.
rust/Cargo.toml Add new workspace members + add SP1/Ziren build profiles.
pkgs/state-proving-manager/src/manager.zig Add SP1/Ziren options and route calls via conditional externs.
build.zig Add SP1/Ziren prover choices, link the right Rust archives, and build with the right Cargo profiles.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +158 to +178
// Deserialize the ZKM proof
let proof: ZKMProofWithPublicValues = match bincode::deserialize(&proof_package.proof_bytes) {
Ok(p) => p,
Err(e) => {
eprintln!("ziren_verify: failed to deserialize proof: {}", e);
return false;
}
};

// Deserialize the verifying key
let vk: ZKMVerifyingKey = match bincode::deserialize(&proof_package.vk_bytes) {
Ok(k) => k,
Err(e) => {
eprintln!("ziren_verify: failed to deserialize verifying key: {}", e);
return false;
}
};

// Verify the proof using the ZKM SDK
let client = ProverClient::new();
match client.verify(&proof, &vk) {
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ziren_verify trusts a verifying key provided inside the untrusted receipt (vk_bytes). An attacker can set elf_hash to match the local ELF but supply a (proof, vk) pair for a different program/circuit, and verification will still succeed because nothing ties vk to binary_path. Derive the verifying key from the ELF on the verifier side (e.g., run client.setup(&elf_bytes) and use/compare the resulting vk), or otherwise authenticate the verifying key out-of-band instead of accepting it from the receipt.

Suggested change
// Deserialize the ZKM proof
let proof: ZKMProofWithPublicValues = match bincode::deserialize(&proof_package.proof_bytes) {
Ok(p) => p,
Err(e) => {
eprintln!("ziren_verify: failed to deserialize proof: {}", e);
return false;
}
};
// Deserialize the verifying key
let vk: ZKMVerifyingKey = match bincode::deserialize(&proof_package.vk_bytes) {
Ok(k) => k,
Err(e) => {
eprintln!("ziren_verify: failed to deserialize verifying key: {}", e);
return false;
}
};
// Verify the proof using the ZKM SDK
let client = ProverClient::new();
match client.verify(&proof, &vk) {
// Derive the verifying key from the local ELF to avoid trusting vk_bytes from the receipt
let client = ProverClient::new();
let (_proving_key, vk_from_elf) = match client.setup(&elf_bytes) {
Ok(result) => result,
Err(e) => {
eprintln!("ziren_verify: failed to derive verifying key from ELF: {}", e);
return false;
}
};
// Deserialize the ZKM proof
let proof: ZKMProofWithPublicValues = match bincode::deserialize(&proof_package.proof_bytes) {
Ok(p) => p,
Err(e) => {
eprintln!("ziren_verify: failed to deserialize proof: {}", e);
return false;
}
};
// Verify the proof using the ZKM SDK and the verifying key derived from the ELF
match client.verify(&proof, &vk_from_elf) {

Copilot uses AI. Check for mistakes.
Comment on lines +126 to +133
// Deserialize the proof package from receipt bytes
let proof_package: ZirenProofPackage = match bincode::deserialize(receipt_slice) {
Ok(p) => p,
Err(e) => {
eprintln!("ziren_verify: failed to deserialize proof package: {}", e);
return false;
}
};
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bincode::deserialize(receipt_slice) is performed on untrusted input without any size limit. Because ZirenProofPackage contains Vec<u8> fields, a crafted receipt can request huge allocations and cause OOM/DoS during verification. Consider using bincode::DefaultOptions::new().with_limit(...) (or an equivalent bounded deserializer) and reject receipts exceeding a maximum expected size.

Copilot uses AI. Check for mistakes.
Comment on lines +180 to +233
// Deserialize the proof package from receipt bytes
let proof_package: OpenVMProofPackage = match bincode::deserialize(receipt_slice) {
Ok(p) => p,
Err(e) => {
eprintln!("openvm_verify: failed to deserialize proof package: {}", e);
return false;
}
};

// Verify ELF hash: read the binary ELF and check it matches the hash in the proof
let elf_bytes = match fs::read(binary_path) {
Ok(b) => b,
Err(e) => {
eprintln!("openvm_verify: failed to read ELF at {}: {}", binary_path, e);
return false;
}
};

let mut hasher = Sha256::new();
hasher.update(&elf_bytes);
let computed_hash = hasher.finalize().to_vec();

if computed_hash != proof_package.elf_hash {
eprintln!("openvm_verify: ELF hash mismatch — proof was generated for a different binary");
return false;
}

// Deserialize the continuation VM proof
let proof: ContinuationVmProof<SC> = match bincode::deserialize(&proof_package.proof_bytes) {
Ok(p) => p,
Err(e) => {
eprintln!("openvm_verify: failed to deserialize proof: {}", e);
return false;
}
};

// Deserialize the app verifying key
let app_vk: AppVerifyingKey = match bincode::deserialize(&proof_package.vk_bytes) {
Ok(k) => k,
Err(e) => {
eprintln!("openvm_verify: failed to deserialize verifying key: {}", e);
return false;
}
};

// Verify the proof using the SDK
let sdk = Sdk::new();
match sdk.verify_app_proof(&app_vk, &proof) {
Ok(_) => true,
Err(e) => {
eprintln!("openvm_verify: proof verification failed: {}", e);
false
}
}
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

openvm_verify accepts vk_bytes from the untrusted receipt and verifies the proof against that key. Because the ELF hash check is independent of the verifying key, a malicious receipt can set elf_hash to match the local ELF while supplying a (proof, vk) pair for a different circuit, and verification can still succeed. The verifier should derive the expected AppVerifyingKey from the ELF/config on its side (or compare against a trusted/precomputed key) rather than trusting vk_bytes from the receipt.

Copilot uses AI. Check for mistakes.
Comment on lines +180 to +187
// Deserialize the proof package from receipt bytes
let proof_package: OpenVMProofPackage = match bincode::deserialize(receipt_slice) {
Ok(p) => p,
Err(e) => {
eprintln!("openvm_verify: failed to deserialize proof package: {}", e);
return false;
}
};
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bincode::deserialize(receipt_slice) is called on untrusted receipt bytes without a size limit. Since OpenVMProofPackage contains Vec<u8> fields, a crafted receipt can trigger large allocations and OOM/DoS during verification. Consider switching to bounded deserialization (e.g., bincode::DefaultOptions::new().with_limit(...)) and rejecting oversized receipts early.

Copilot uses AI. Check for mistakes.
Comment on lines +164 to +176
// Deserialize the verifying key
let vk: SP1VerifyingKey = match bincode::deserialize(&proof_package.vk_bytes) {
Ok(k) => k,
Err(e) => {
eprintln!("sp1_verify: failed to deserialize verifying key: {}", e);
return false;
}
};

// Verify the proof using the SP1 SDK
let client = ProverClient::new();
match client.verify(&proof, &vk) {
Ok(()) => true,
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sp1_verify trusts a verifying key provided inside the untrusted receipt (vk_bytes). An attacker can set elf_hash to match the local ELF but supply a (proof, vk) pair for a different program/circuit, and verification will still succeed because nothing ties vk to binary_path. Derive the verifying key from the ELF on the verifier side (e.g., run client.setup(&elf_bytes) and use/compare the resulting vk), or otherwise authenticate the verifying key out-of-band instead of accepting it from the receipt.

Copilot uses AI. Check for mistakes.
Comment on lines +126 to +133
// Deserialize the proof package from receipt bytes
let proof_package: SP1ProofPackage = match bincode::deserialize(receipt_slice) {
Ok(p) => p,
Err(e) => {
eprintln!("sp1_verify: failed to deserialize proof package: {}", e);
return false;
}
};
Copy link

Copilot AI Feb 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bincode::deserialize(receipt_slice) is performed on untrusted input without any size limit. Because SP1ProofPackage contains Vec<u8> fields, a crafted receipt can request huge allocations and cause OOM/DoS during verification. Consider using bincode::DefaultOptions::new().with_limit(...) (or an equivalent bounded deserializer) and reject receipts exceeding a maximum expected size.

Copilot uses AI. Check for mistakes.
@gballet gballet force-pushed the verify-and-prove-ziren branch from e88ce57 to e9640be Compare March 6, 2026 11:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants