use crate::error::{CommonError, Result};
use proofman::{verify_snark_proof, SnarkProof, SnarkProtocol};
use proofman_verifier::verifier;
use proofman_verifier::VadcopFinalProof;
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use std::fs::File;
use std::path::Path;
use std::sync::atomic::{AtomicUsize, Ordering};
pub use zisk_verifier::{
program_publics, IS_VADCOP_FINAL_PROOF, PROGRAM_VK_LEN, VADCOP_FINAL_FLAG_LEN, ZISK_PUBLICS,
};
use crate::HashMode;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct SetupKey {
pub hash_id: String,
pub with_hints: bool,
pub emulator_only: bool,
}
impl SetupKey {
pub fn new(hash_id: impl Into<String>, with_hints: bool, emulator_only: bool) -> Self {
Self { hash_id: hash_id.into(), with_hints, emulator_only }
}
}
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
pub struct ProgramVK {
pub vk: Vec<u64>,
pub hash_mode: HashMode,
}
impl ProgramVK {
pub fn new_from_publics_with_mode(publics: &[u64], hash_mode: HashMode) -> Self {
let publics = program_publics(publics);
assert!(
publics.len() >= PROGRAM_VK_LEN,
"Not enough u64 publics to extract program VK (expected at least {})",
PROGRAM_VK_LEN
);
Self { vk: publics[..PROGRAM_VK_LEN].to_vec(), hash_mode }
}
pub fn new_from_publics(publics: &[u64]) -> Self {
Self::new_from_publics_with_mode(publics, HashMode::default())
}
pub fn new_empty() -> Self {
Self { vk: vec![0u64; PROGRAM_VK_LEN], hash_mode: HashMode::default() }
}
}
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum VadcopKind {
#[default]
Final,
Recurser,
Minimal,
}
impl VadcopKind {
pub fn flag(self) -> Option<u64> {
match self {
VadcopKind::Final => Some(IS_VADCOP_FINAL_PROOF),
VadcopKind::Recurser => Some(0),
VadcopKind::Minimal => None,
}
}
pub fn is_minimal(self) -> bool {
matches!(self, VadcopKind::Minimal)
}
pub fn from_publics_full(publics_full: &[u64]) -> Self {
if publics_full.len() == VADCOP_FINAL_FLAG_LEN + PROGRAM_VK_LEN + ZISK_PUBLICS {
if publics_full[0] == 0 {
VadcopKind::Recurser
} else {
VadcopKind::Final
}
} else if publics_full.len() == PROGRAM_VK_LEN + ZISK_PUBLICS {
VadcopKind::Minimal
} else {
VadcopKind::Final
}
}
pub fn stark_publics(self, publics_full: &[u64]) -> Vec<u64> {
match self.flag() {
Some(flag) => {
let mut v = Vec::with_capacity(VADCOP_FINAL_FLAG_LEN + publics_full.len());
v.push(flag);
v.extend_from_slice(publics_full);
v
}
None => publics_full.to_vec(),
}
}
}
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub enum ProofKind {
#[default]
VadcopFinal,
VadcopFinalMinimal,
Plonk,
}
impl From<i32> for ProofKind {
fn from(v: i32) -> Self {
match v {
1 => ProofKind::VadcopFinalMinimal,
2 => ProofKind::Plonk,
_ => ProofKind::VadcopFinal,
}
}
}
impl From<ProofKind> for i32 {
fn from(k: ProofKind) -> Self {
match k {
ProofKind::VadcopFinal => 0,
ProofKind::VadcopFinalMinimal => 1,
ProofKind::Plonk => 2,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct PlonkVkey {
pub protocol: String,
pub curve: String,
#[serde(rename = "nPublic")]
pub n_public: u32,
pub power: u32,
pub k1: String,
pub k2: String,
#[serde(rename = "Qm")]
pub qm: [String; 3],
#[serde(rename = "Ql")]
pub ql: [String; 3],
#[serde(rename = "Qr")]
pub qr: [String; 3],
#[serde(rename = "Qo")]
pub qo: [String; 3],
#[serde(rename = "Qc")]
pub qc: [String; 3],
#[serde(rename = "S1")]
pub s1: [String; 3],
#[serde(rename = "S2")]
pub s2: [String; 3],
#[serde(rename = "S3")]
pub s3: [String; 3],
#[serde(rename = "X_2")]
pub x_2: [[String; 2]; 3],
pub w: String,
}
impl PlonkVkey {
pub fn load(path: impl AsRef<Path>) -> Result<Self> {
let file = File::open(path.as_ref()).map_err(|e| {
CommonError::Io(format!(
"failed to open file for loading PlonkVkey: {}: {e}",
path.as_ref().display()
))
})?;
let vkey: PlonkVkey = serde_json::from_reader(file).map_err(|e| {
CommonError::Deserialization(format!(
"failed to parse PlonkVkey JSON from {}: {e}",
path.as_ref().display()
))
})?;
Ok(vkey)
}
pub fn save(&self, path: impl AsRef<Path>) -> Result<()> {
let path = path.as_ref();
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent).map_err(|e| {
CommonError::Io(format!(
"failed to create parent directory {}: {e}",
parent.display()
))
})?;
}
let file = File::create(path).map_err(|e| {
CommonError::Io(format!(
"failed to create file for saving PlonkVkey: {}: {e}",
path.display()
))
})?;
serde_json::to_writer_pretty(file, self).map_err(|e| {
CommonError::Serialization(format!("PlonkVkey JSON to {}: {e}", path.display()))
})?;
Ok(())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PlonkVkBlob {
pub vadcop_vk: Vec<u64>,
pub plonk_vkey: PlonkVkey,
}
#[derive(Default, Debug, Serialize, Deserialize)]
pub struct PublicValues {
data: Vec<u8>,
#[serde(skip)]
ptr: AtomicUsize,
}
impl Clone for PublicValues {
fn clone(&self) -> Self {
Self { data: self.data.clone(), ptr: AtomicUsize::new(self.ptr.load(Ordering::Relaxed)) }
}
}
impl PublicValues {
pub fn new(publics_bytes: &[u8]) -> Self {
assert!(
publics_bytes.len() == ZISK_PUBLICS * 8 + 32,
"Not enough bytes to fill PublicValues"
);
let mut data = [0u8; ZISK_PUBLICS * 4];
for (i, chunk) in publics_bytes[32..].chunks_exact(8).enumerate() {
let v32 = u32::from_le_bytes(chunk[0..4].try_into().unwrap());
data[i * 4..(i + 1) * 4].copy_from_slice(&v32.to_le_bytes());
}
Self { data: data.to_vec(), ptr: AtomicUsize::new(0) }
}
pub fn new_from_u64(publics: &[u64]) -> Self {
let publics = program_publics(publics);
assert!(
publics.len() == ZISK_PUBLICS + PROGRAM_VK_LEN,
"Expected {} u64 publics, got {}",
ZISK_PUBLICS + PROGRAM_VK_LEN,
publics.len()
);
let mut data = [0u8; ZISK_PUBLICS * 4];
for (i, &val) in publics[PROGRAM_VK_LEN..].iter().enumerate() {
data[i * 4..(i + 1) * 4].copy_from_slice(&(val as u32).to_le_bytes());
}
Self { data: data.to_vec(), ptr: AtomicUsize::new(0) }
}
pub fn new_empty() -> Self {
Self { data: [0u8; ZISK_PUBLICS * 4].to_vec(), ptr: AtomicUsize::new(0) }
}
pub fn write<T: serde::Serialize>(value: &T) -> Result<Self> {
let serialized = bincode::serde::encode_to_vec(value, bincode::config::standard())
.map_err(|e| CommonError::Serialization(e.to_string()))?;
if serialized.len() > ZISK_PUBLICS * 4 {
return Err(CommonError::Invalid(format!(
"Serialized data too large: {} bytes (max {} bytes)",
serialized.len(),
ZISK_PUBLICS * 4
)));
}
let mut data = [0u8; ZISK_PUBLICS * 4];
for (i, chunk) in serialized.chunks(4).enumerate() {
let mut buf = [0u8; 4];
buf[..chunk.len()].copy_from_slice(chunk);
data[i * 4..(i + 1) * 4].copy_from_slice(&buf);
}
Ok(Self { data: data.to_vec(), ptr: AtomicUsize::new(0) })
}
pub fn write_abi<T: alloy_sol_types::SolValue>(value: &T) -> Result<Self> {
let encoded = value.abi_encode();
if encoded.len() > ZISK_PUBLICS * 4 {
return Err(CommonError::Invalid(format!(
"ABI encoded data too large: {} bytes (max {} bytes)",
encoded.len(),
ZISK_PUBLICS * 4
)));
}
let mut data = [0u8; ZISK_PUBLICS * 4];
for (i, chunk) in encoded.chunks(4).enumerate() {
let mut buf = [0u8; 4];
buf[..chunk.len()].copy_from_slice(chunk);
data[i * 4..(i + 1) * 4].copy_from_slice(&buf);
}
Ok(Self { data: data.to_vec(), ptr: AtomicUsize::new(0) })
}
pub fn head(&self) {
self.ptr.store(0, Ordering::Relaxed);
}
pub fn read_slice(&self, slice: &mut [u8]) {
let ptr = self.ptr.load(Ordering::Relaxed);
slice.copy_from_slice(&self.data[ptr..ptr + slice.len()]);
self.ptr.store(ptr + slice.len(), Ordering::Relaxed);
}
pub fn read<T: serde::Serialize + serde::de::DeserializeOwned>(&self) -> Result<T> {
let ptr = self.ptr.load(Ordering::Relaxed);
let (result, nb_bytes): (T, usize) =
bincode::serde::decode_from_slice(&self.data[ptr..], bincode::config::standard())
.map_err(|e| CommonError::Deserialization(e.to_string()))?;
self.ptr.store(ptr + nb_bytes, Ordering::Relaxed);
Ok(result)
}
pub fn read_abi<T>(&self) -> Result<T>
where
T: alloy_sol_types::SolValue + From<<T::SolType as alloy_sol_types::SolType>::RustType>,
{
let ptr = self.ptr.load(Ordering::Relaxed);
let decoded = T::abi_decode(&self.data[ptr..])
.map_err(|e| CommonError::AbiDecoding(e.to_string()))?;
let encoded_size = decoded.abi_encode().len();
self.ptr.store(ptr + encoded_size, Ordering::Relaxed);
Ok(decoded)
}
pub fn public_u64(&self) -> Vec<u64> {
(0..ZISK_PUBLICS)
.map(|i| {
let start = i * 4;
u32::from_le_bytes([
self.data[start],
self.data[start + 1],
self.data[start + 2],
self.data[start + 3],
]) as u64
})
.collect()
}
pub fn hash_solidity(&self, program_vk: &ProgramVK, vadcop_verkey: &[u64]) -> Vec<u8> {
let bytes = self.bytes_solidity(program_vk, vadcop_verkey);
let hash = Sha256::digest(&bytes);
hash.to_vec()
}
}
impl PublicValues {
pub fn bytes_solidity(&self, program_vk: &ProgramVK, vadcop_verkey: &[u64]) -> Vec<u8> {
let mut prefix = [0u8; PROGRAM_VK_LEN * 8];
for (i, val) in program_vk.vk.iter().enumerate() {
prefix[i * 8..(i + 1) * 8].copy_from_slice(&val.to_be_bytes());
}
let mut bytes = prefix.to_vec();
bytes.extend_from_slice(&self.data);
let mut suffix = [0u8; PROGRAM_VK_LEN * 8];
for (i, val) in vadcop_verkey.iter().enumerate() {
suffix[i * 8..(i + 1) * 8].copy_from_slice(&val.to_be_bytes());
}
bytes.extend(&suffix);
bytes
}
}
pub fn snark_inputs_bytes(publics_full: &[u64]) -> Vec<u8> {
let publics_full = program_publics(publics_full);
assert!(
publics_full.len() >= PROGRAM_VK_LEN + ZISK_PUBLICS,
"publics_full too short for snark inputs"
);
publics_full[PROGRAM_VK_LEN..PROGRAM_VK_LEN + ZISK_PUBLICS]
.iter()
.flat_map(|v| v.to_le_bytes())
.collect()
}
pub fn snark_publics_hash(publics_full: &[u64], rootc: &[u64]) -> Vec<u8> {
assert!(rootc.len() >= PROGRAM_VK_LEN, "rootc too short for snark hash");
let publics_full = program_publics(publics_full);
let program_vk = &publics_full[..PROGRAM_VK_LEN];
let mut preimage = Vec::with_capacity((2 * PROGRAM_VK_LEN + ZISK_PUBLICS) * 8);
preimage.extend(program_vk.iter().flat_map(|v| v.to_be_bytes())); preimage.extend(snark_inputs_bytes(publics_full)); preimage.extend(rootc[..PROGRAM_VK_LEN].iter().flat_map(|v| v.to_be_bytes())); let digest = Sha256::digest(&preimage);
let bn254 = num_bigint::BigUint::parse_bytes(
b"21888242871839275222246405745257275088548364400416034343698204186575808495617",
10,
)
.expect("valid BN254 modulus");
let reduced = num_bigint::BigUint::from_bytes_be(&digest) % bn254;
let mut out = reduced.to_bytes_be();
out.splice(0..0, std::iter::repeat(0u8).take(32 - out.len())); out
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ProofBody {
Vadcop {
proof: Vec<u64>,
zisk_vk: Vec<u64>,
kind: VadcopKind,
hash: String,
publics_full: Vec<u64>,
},
Plonk {
proof_bytes: Vec<u8>,
plonk_vk: Box<PlonkVkBlob>,
publics: PublicValues,
publics_full: Vec<u64>,
rootc: Vec<u64>,
},
}
impl Default for ProofBody {
fn default() -> Self {
ProofBody::Vadcop {
proof: Vec::new(),
zisk_vk: vec![0u64; PROGRAM_VK_LEN],
kind: VadcopKind::Final,
hash: String::new(),
publics_full: vec![0u64; PROGRAM_VK_LEN + ZISK_PUBLICS],
}
}
}
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
pub struct Proof {
pub body: ProofBody,
pub program_vk: ProgramVK,
}
pub struct ZiskVerifyBuilder<'a> {
proof_with_values: &'a Proof,
override_publics: Option<&'a PublicValues>,
override_program_vk: Option<&'a ProgramVK>,
}
impl<'a> ZiskVerifyBuilder<'a> {
fn new(proof_with_values: &'a Proof) -> Self {
Self { proof_with_values, override_publics: None, override_program_vk: None }
}
pub fn with_publics(mut self, publics: &'a PublicValues) -> Self {
self.override_publics = Some(publics);
self
}
pub fn with_program_vk(mut self, program_vk: &'a ProgramVK) -> Self {
self.override_program_vk = Some(program_vk);
self
}
pub fn verify(self) -> Result<()> {
let derived_publics = self.proof_with_values.publics();
let has_override = self.override_publics.is_some() || self.override_program_vk.is_some();
let publics = self.override_publics.unwrap_or(&derived_publics);
let program_vk = self.override_program_vk.unwrap_or(&self.proof_with_values.program_vk);
match &self.proof_with_values.body {
ProofBody::Plonk { proof_bytes, plonk_vk, publics_full, rootc, .. } => {
let public_snark_bytes = snark_publics_hash(publics_full, rootc);
let public_bytes = publics.bytes_solidity(program_vk, &plonk_vk.vadcop_vk);
let snark_proof = SnarkProof {
proof_bytes: proof_bytes.clone(),
public_bytes,
public_snark_bytes,
protocol_id: SnarkProtocol::Plonk.protocol_id(),
};
let temp_dir = std::env::temp_dir();
let unique_id = format!(
"{}_{}",
std::process::id(),
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_nanos())
.unwrap_or(0)
);
let temp_file = temp_dir.join(format!("plonk_vkey_{}.json", unique_id));
let plonk_vkey_json = serde_json::to_vec(&plonk_vk.plonk_vkey)
.map_err(|e| CommonError::Serialization(format!("PlonkVkey to JSON: {e}")))?;
std::fs::write(&temp_file, &plonk_vkey_json).map_err(|e| {
CommonError::Io(format!(
"Failed to write PlonkVkey to temporary file: {}: {e}",
temp_file.display()
))
})?;
let result = verify_snark_proof(&snark_proof, &temp_file);
if temp_file.exists() {
std::fs::remove_file(&temp_file).map_err(|e| {
CommonError::Io(format!(
"Failed to delete temporary file: {}: {e}",
temp_file.display()
))
})?;
}
result.map_err(|e| {
CommonError::Invalid(format!("snark proof verification failed: {e}"))
})?;
Ok(())
}
ProofBody::Vadcop { proof, zisk_vk, kind, hash, publics_full } => {
let kind = *kind;
if program_vk.hash_mode.as_str() != hash {
return Err(CommonError::InvalidProof(format!(
"verkey hash mode {} does not match proof hash family {hash:?}",
program_vk.hash_mode.as_str()
)));
}
let v = verifier(hash);
let expected_len = if kind.is_minimal() {
v.expected_vadcop_final_compressed_proof_bytes()
} else {
v.expected_vadcop_final_proof_bytes()
};
if proof.len() * 8 != expected_len {
return Err(CommonError::InvalidProof(format!(
"Malformed proof: expected {} bytes for {:?}, got {}",
expected_len,
self.proof_with_values.kind(),
proof.len() * 8
)));
}
let pubs_u64 = if has_override {
let mut v = Vec::with_capacity(
kind.flag().map_or(0, |_| VADCOP_FINAL_FLAG_LEN)
+ program_vk.vk.len()
+ ZISK_PUBLICS,
);
if let Some(flag) = kind.flag() {
v.push(flag);
}
v.extend_from_slice(&program_vk.vk);
v.extend(publics.public_u64());
v
} else {
kind.stark_publics(publics_full)
};
let vadcop_final_proof =
VadcopFinalProof::new(proof.clone(), pubs_u64, kind.is_minimal(), hash.clone());
let is_valid = if kind.is_minimal() {
v.verify_vadcop_final_compressed(&vadcop_final_proof, zisk_vk)
} else {
v.verify_vadcop_final(&vadcop_final_proof, zisk_vk)
};
if !is_valid {
Err(CommonError::NotVerified)
} else {
Ok(())
}
}
}
}
}
impl Proof {
pub fn new(body: ProofBody, program_vk: ProgramVK) -> Self {
Self { body, program_vk }
}
pub fn publics(&self) -> PublicValues {
match &self.body {
ProofBody::Vadcop { publics_full, .. } => PublicValues::new_from_u64(publics_full),
ProofBody::Plonk { publics, .. } => publics.clone(),
}
}
pub fn publics_full(&self) -> Option<&[u64]> {
match &self.body {
ProofBody::Vadcop { publics_full, .. } => Some(publics_full),
ProofBody::Plonk { .. } => None,
}
}
pub fn kind(&self) -> ProofKind {
match &self.body {
ProofBody::Vadcop { kind: VadcopKind::Minimal, .. } => ProofKind::VadcopFinalMinimal,
ProofBody::Vadcop { .. } => ProofKind::VadcopFinal,
ProofBody::Plonk { .. } => ProofKind::Plonk,
}
}
pub fn is_empty(&self) -> bool {
match &self.body {
ProofBody::Vadcop { proof, .. } => proof.is_empty(),
ProofBody::Plonk { proof_bytes, .. } => proof_bytes.is_empty(),
}
}
pub fn save(&self, path: impl AsRef<Path>) -> Result<()> {
let path = path.as_ref();
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent).map_err(|e| {
CommonError::Io(format!(
"failed to create parent directory {}: {e}",
parent.display()
))
})?;
}
let mut file = File::create(path).map_err(|e| {
CommonError::Io(format!(
"failed to create file for saving proof: {}: {e}",
path.display()
))
})?;
bincode::serde::encode_into_std_write(self, &mut file, bincode::config::standard())
.map(|_| ())
.map_err(|e| CommonError::Io(format!("Failed to save proof: {}", e)))
}
pub fn load(path: impl AsRef<Path>) -> Result<Self> {
let mut file = File::open(path.as_ref()).map_err(|e| {
CommonError::Io(format!(
"failed to open file for loading proof: {}: {e}",
path.as_ref().display()
))
})?;
let proof: Proof =
bincode::serde::decode_from_std_read(&mut file, bincode::config::standard())
.map_err(|e| CommonError::Io(format!("Failed to load proof: {}", e)))?;
Ok(proof)
}
pub fn get_vadcop_final_proof(&self) -> Result<VadcopFinalProof> {
match &self.body {
ProofBody::Vadcop { proof, kind, hash, publics_full, .. } => {
Ok(VadcopFinalProof::new(
proof.clone(),
kind.stark_publics(publics_full),
kind.is_minimal(),
hash.clone(),
))
}
ProofBody::Plonk { .. } => {
Err(CommonError::InvalidProof("Proof is not a Vadcop final proof".to_string()))
}
}
}
pub fn get_proof_u64(&self) -> Result<Vec<u64>> {
match &self.body {
ProofBody::Vadcop { proof, zisk_vk, kind, publics_full, .. } => {
if self.program_vk.vk.len() != PROGRAM_VK_LEN {
return Err(CommonError::InvalidProof(format!(
"Invalid program_vk length: expected {}, got {}",
PROGRAM_VK_LEN,
self.program_vk.vk.len()
)));
}
if zisk_vk.len() != PROGRAM_VK_LEN {
return Err(CommonError::InvalidProof(format!(
"Invalid zisk_vk length: expected {}, got {}",
PROGRAM_VK_LEN,
zisk_vk.len()
)));
}
let stark_publics = kind.stark_publics(publics_full);
let n_publics = stark_publics.len();
let mut words = Vec::with_capacity(2 + n_publics + proof.len() + zisk_vk.len());
words.push(kind.is_minimal() as u64);
words.push(n_publics as u64);
words.extend_from_slice(&stark_publics);
words.extend_from_slice(proof);
words.extend_from_slice(zisk_vk);
Ok(words)
}
ProofBody::Plonk { .. } => Err(CommonError::InvalidProof(
"Proof not suitable for get_proof_u64. Only VadcopFinal and VadcopFinalMinimal proofs are supported.".to_string()
)),
}
}
pub fn get_proof_bytes(&self) -> Result<Vec<u8>> {
let words = self.get_proof_u64()?;
let mut bytes = Vec::with_capacity(words.len() * 8);
for w in &words {
bytes.extend_from_slice(&w.to_le_bytes());
}
Ok(bytes)
}
pub fn get_publics(&self) -> PublicValues {
self.publics()
}
pub fn get_program_vk(&self) -> &ProgramVK {
&self.program_vk
}
pub fn new_from_vadcop_proof(
proof: &[u64],
minimal: bool,
zisk_vk: Vec<u64>,
hash: String,
) -> Result<Self> {
if zisk_vk.len() != PROGRAM_VK_LEN {
return Err(CommonError::InvalidProof(format!(
"Invalid zisk_vk length: expected {}, got {}",
PROGRAM_VK_LEN,
zisk_vk.len()
)));
}
let hash_mode = hash.parse::<HashMode>().map_err(|e| {
CommonError::Invalid(format!("unrecognized proof hash family {hash:?}: {e}"))
})?;
let vadcop_proof =
VadcopFinalProof::new_from_proof(proof, minimal, hash.clone()).map_err(|e| {
CommonError::InvalidProof(format!("Failed to parse Vadcop proof: {}", e))
})?;
let program_vk =
ProgramVK::new_from_publics_with_mode(&vadcop_proof.public_values, hash_mode);
let kind = if minimal {
VadcopKind::Minimal
} else {
VadcopKind::from_publics_full(&vadcop_proof.public_values)
};
let publics_full = program_publics(&vadcop_proof.public_values).to_vec();
Ok(Self {
body: ProofBody::Vadcop {
proof: vadcop_proof.proof,
zisk_vk,
kind,
hash,
publics_full,
},
program_vk,
})
}
pub fn verify(&self) -> Result<()> {
ZiskVerifyBuilder::new(self).verify()
}
pub fn with_publics<'a>(&'a self, publics: &'a PublicValues) -> ZiskVerifyBuilder<'a> {
ZiskVerifyBuilder::new(self).with_publics(publics)
}
pub fn with_program_vk<'a>(&'a self, program_vk: &'a ProgramVK) -> ZiskVerifyBuilder<'a> {
ZiskVerifyBuilder::new(self).with_program_vk(program_vk)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn verify_returns_err_for_malformed_vadcop_final_minimal() {
let result = Proof::new(
ProofBody::Vadcop {
proof: vec![],
zisk_vk: vec![0u64; PROGRAM_VK_LEN],
kind: VadcopKind::Minimal,
hash: "Poseidon2".to_string(),
publics_full: vec![0u64; PROGRAM_VK_LEN + ZISK_PUBLICS],
},
ProgramVK::new_empty(),
)
.verify();
assert!(result.is_err(), "expected Err for malformed proof, got {:?}", result);
}
#[test]
fn verify_returns_err_for_malformed_vadcop_final() {
let result = Proof::new(
ProofBody::Vadcop {
proof: vec![],
zisk_vk: vec![0u64; PROGRAM_VK_LEN],
kind: VadcopKind::Final,
hash: "Poseidon2".to_string(),
publics_full: vec![0u64; PROGRAM_VK_LEN + ZISK_PUBLICS],
},
ProgramVK::new_empty(),
)
.verify();
assert!(result.is_err(), "expected Err for malformed proof, got {:?}", result);
}
#[test]
fn proof_save_load_roundtrip_vadcop() {
let tmp = std::env::temp_dir().join(format!("proof_roundtrip_{}.bin", std::process::id()));
let original = Proof::new(
ProofBody::Vadcop {
proof: vec![1, 2, 3, 4],
zisk_vk: vec![10, 20, 30, 40],
kind: VadcopKind::Minimal,
hash: "Poseidon2".to_string(),
publics_full: vec![0u64; PROGRAM_VK_LEN + ZISK_PUBLICS],
},
ProgramVK::new_from_publics(&[7, 8, 9, 10]),
);
original.save(&tmp).unwrap();
let loaded = Proof::load(&tmp).unwrap();
std::fs::remove_file(&tmp).ok();
assert_eq!(loaded.kind(), ProofKind::VadcopFinalMinimal);
match loaded.body {
ProofBody::Vadcop { proof, zisk_vk, kind, hash, .. } => {
assert_eq!(proof, vec![1, 2, 3, 4]);
assert_eq!(zisk_vk, vec![10, 20, 30, 40]);
assert_eq!(kind, VadcopKind::Minimal);
assert_eq!(hash, "Poseidon2");
}
ProofBody::Plonk { .. } => panic!("expected Vadcop body after roundtrip"),
}
assert_eq!(loaded.program_vk.vk, vec![7, 8, 9, 10]);
}
#[test]
fn proof_kind_derivation() {
let vadcop = Proof::new(
ProofBody::Vadcop {
proof: vec![],
zisk_vk: vec![],
kind: VadcopKind::Final,
hash: "Poseidon2".to_string(),
publics_full: vec![0u64; PROGRAM_VK_LEN + ZISK_PUBLICS],
},
ProgramVK::new_empty(),
);
assert_eq!(vadcop.kind(), ProofKind::VadcopFinal);
assert!(vadcop.is_empty());
let minimal = Proof::new(
ProofBody::Vadcop {
proof: vec![1],
zisk_vk: vec![],
kind: VadcopKind::Minimal,
hash: "Poseidon2".to_string(),
publics_full: vec![0u64; PROGRAM_VK_LEN + ZISK_PUBLICS],
},
ProgramVK::new_empty(),
);
assert_eq!(minimal.kind(), ProofKind::VadcopFinalMinimal);
assert!(!minimal.is_empty());
}
}