use std::fs;
use std::path::Path;
use serde::{Deserialize, Serialize};
use zheng::{Statement, TraceProof};
pub const PROOF_FORMAT: &str = "zheng-hypernova-tensor-merkle-v2";
const MAX_ARTIFACT_BYTES: usize = 64 * 1024 * 1024;
const MAX_ASSEMBLY_BYTES: usize = 256 * 1024;
#[derive(Debug, Serialize, Deserialize)]
pub struct ProofArtifact {
pub format: String,
pub statement: Statement,
pub proof: TraceProof,
pub meta: ArtifactMeta,
}
#[derive(Debug, Serialize, Deserialize)]
pub struct ArtifactMeta {
pub program: String,
pub output: Vec<u64>,
pub cycle_count: u64,
#[serde(default)]
pub assembly: Option<String>,
#[serde(default)]
pub assembly_deflate: Option<Vec<u8>>,
}
impl ArtifactMeta {
pub fn assembly_text(&self) -> Result<Option<String>, String> {
if let Some(z) = &self.assembly_deflate {
let bytes = miniz_oxide::inflate::decompress_to_vec_with_limit(z, MAX_ASSEMBLY_BYTES)
.map_err(|_| "corrupt or oversized compressed assembly".to_string())?;
return String::from_utf8(bytes)
.map(Some)
.map_err(|e| format!("assembly is not UTF-8: {}", e));
}
if self
.assembly
.as_ref()
.is_some_and(|text| text.len() > MAX_ASSEMBLY_BYTES)
{
return Err("assembly size limit".into());
}
Ok(self.assembly.clone())
}
pub fn set_assembly(&mut self, assembly: &str) {
self.assembly = None;
self.assembly_deflate = Some(miniz_oxide::deflate::compress_to_vec(
assembly.as_bytes(),
9,
));
}
}
impl ProofArtifact {
pub fn save(&self, path: &Path) -> Result<usize, String> {
let bytes = self.to_bytes()?;
fs::write(path, &bytes).map_err(|e| format!("cannot write {}: {}", path.display(), e))?;
Ok(bytes.len())
}
pub fn to_bytes(&self) -> Result<Vec<u8>, String> {
postcard::to_allocvec(self).map_err(|e| format!("cannot serialize proof artifact: {}", e))
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, String> {
if bytes.len() > MAX_ARTIFACT_BYTES {
return Err("legacy artifact file size limit".into());
}
match postcard::from_bytes::<ProofArtifact>(bytes) {
Ok(a) => Ok(a),
Err(bin_err) => serde_json::from_slice::<ProofArtifact>(bytes).map_err(|json_err| {
format!(
"malformed proof artifact: not postcard ({}) nor current-format JSON ({})",
bin_err, json_err
)
}),
}
}
pub fn load(path: &Path) -> Result<Self, String> {
let bytes = crate::file_input::read(path, MAX_ARTIFACT_BYTES)?;
let artifact =
Self::from_bytes(&bytes).map_err(|e| format!("{} ({})", e, path.display()))?;
if artifact.format != PROOF_FORMAT {
return Err(format!(
"unknown proof format '{}' (this joy verifies '{}')",
artifact.format, PROOF_FORMAT
));
}
Ok(artifact)
}
}
pub fn program_hash(assembly: &str) -> [u8; 32] {
*hemera::hash(assembly.trim().as_bytes()).as_bytes()
}
pub fn require_statement_only(claim: Option<&[u64]>) -> Result<(), String> {
if claim.is_some() {
return Err("zheng currently authenticates the trace statement, not emitted output values; verify --claim by re-execution without a proof artifact".to_string());
}
Ok(())
}