use ligerito::transcript::{FiatShamir, Transcript};
use ligerito::{data_structures::FinalizedLigeritoProof, prove_with_transcript, ProverConfig};
use ligerito_binary_fields::{BinaryElem128, BinaryElem32, BinaryFieldElement};
use serde::{Deserialize, Serialize};
use crate::error::ZyncError;
use crate::trace::{HeaderChainTrace, FIELDS_PER_HEADER, TIP_SENTINEL_SIZE};
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ProofPublicOutputs {
pub start_height: u32,
pub end_height: u32,
pub start_hash: [u8; 32],
pub start_prev_hash: [u8; 32],
pub tip_hash: [u8; 32],
pub tip_prev_hash: [u8; 32],
pub cumulative_difficulty: u64,
pub final_commitment: [u8; 32],
pub final_state_commitment: [u8; 32],
pub num_headers: u32,
pub tip_tree_root: [u8; 32],
pub tip_nullifier_root: [u8; 32],
pub final_actions_commitment: [u8; 32],
}
pub struct HeaderChainProof {
pub proof_bytes: Vec<u8>,
pub public_outputs: ProofPublicOutputs,
pub trace_log_size: u32,
}
impl HeaderChainProof {
pub fn prove(
config: &ProverConfig<BinaryElem32, BinaryElem128>,
trace: &HeaderChainTrace,
) -> Result<Self, ZyncError> {
let public_outputs = Self::extract_public_outputs(trace)?;
let mut transcript = FiatShamir::new_sha256(0);
let public_bytes = bincode::serialize(&public_outputs)
.map_err(|e| ZyncError::Serialization(format!("bincode public outputs: {}", e)))?;
transcript.absorb_bytes(b"public_outputs", &public_bytes);
let proof = prove_with_transcript(config, &trace.trace, transcript)
.map_err(|e| ZyncError::Ligerito(format!("{:?}", e)))?;
let trace_log_size = (trace.trace.len() as f64).log2().ceil() as u32;
let proof_bytes = Self::serialize_proof_with_config(&proof, trace_log_size as u8)?;
Ok(Self {
proof_bytes,
public_outputs,
trace_log_size,
})
}
pub fn prove_auto(trace: &mut HeaderChainTrace) -> Result<Self, ZyncError> {
let (config, required_size) = crate::prover_config_for_size(trace.trace.len());
if trace.trace.len() < required_size {
trace.trace.resize(required_size, BinaryElem32::zero());
}
Self::prove(&config, trace)
}
fn extract_public_outputs(trace: &HeaderChainTrace) -> Result<ProofPublicOutputs, ZyncError> {
if trace.num_headers == 0 {
return Err(ZyncError::InvalidData("empty trace".into()));
}
let extract_hash = |base_offset: usize, field_start: usize| -> [u8; 32] {
let mut hash = [0u8; 32];
for j in 0..8 {
let field_val = trace.trace[base_offset + field_start + j].poly().value();
hash[j * 4..(j + 1) * 4].copy_from_slice(&field_val.to_le_bytes());
}
hash
};
let first_offset = 0;
let start_hash = extract_hash(first_offset, 1);
let start_prev_hash = extract_hash(first_offset, 9);
let last_offset = (trace.num_headers - 1) * FIELDS_PER_HEADER;
let tip_hash = extract_hash(last_offset, 1);
let tip_prev_hash = extract_hash(last_offset, 9);
let sentinel_offset = trace.num_headers * FIELDS_PER_HEADER;
let tip_tree_root = extract_hash(sentinel_offset, 0);
let tip_nullifier_root = extract_hash(sentinel_offset, 8);
let final_actions_commitment = extract_hash(sentinel_offset, 16);
Ok(ProofPublicOutputs {
start_height: trace.start_height,
end_height: trace.end_height,
start_hash,
start_prev_hash,
tip_hash,
tip_prev_hash,
cumulative_difficulty: trace.cumulative_difficulty,
final_commitment: trace.final_commitment,
final_state_commitment: trace.final_state_commitment,
num_headers: trace.num_headers as u32,
tip_tree_root,
tip_nullifier_root,
final_actions_commitment,
})
}
pub fn serialize_full(&self) -> Result<Vec<u8>, ZyncError> {
let public_bytes = bincode::serialize(&self.public_outputs)
.map_err(|e| ZyncError::Serialization(format!("bincode: {}", e)))?;
let mut result = Vec::with_capacity(4 + public_bytes.len() + self.proof_bytes.len());
result.extend_from_slice(&(public_bytes.len() as u32).to_le_bytes());
result.extend(public_bytes);
result.extend(&self.proof_bytes);
Ok(result)
}
pub fn deserialize_full(
bytes: &[u8],
) -> Result<(ProofPublicOutputs, Vec<u8>, u8), ZyncError> {
if bytes.len() < 5 {
return Err(ZyncError::Serialization("proof too short".into()));
}
let public_len =
u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) as usize;
if bytes.len() < 4 + public_len + 1 {
return Err(ZyncError::Serialization("proof truncated".into()));
}
let public_outputs: ProofPublicOutputs =
bincode::deserialize(&bytes[4..4 + public_len])
.map_err(|e| ZyncError::Serialization(format!("bincode: {}", e)))?;
let proof_bytes = bytes[4 + public_len..].to_vec();
let log_size = if !proof_bytes.is_empty() {
proof_bytes[0]
} else {
0
};
Ok((public_outputs, proof_bytes, log_size))
}
fn serialize_proof_with_config(
proof: &FinalizedLigeritoProof<BinaryElem32, BinaryElem128>,
log_size: u8,
) -> Result<Vec<u8>, ZyncError> {
let proof_bytes = bincode::serialize(proof)
.map_err(|e| ZyncError::Serialization(format!("bincode: {}", e)))?;
let mut result = Vec::with_capacity(1 + proof_bytes.len());
result.push(log_size);
result.extend(proof_bytes);
Ok(result)
}
pub fn deserialize_proof_with_config(
bytes: &[u8],
) -> Result<(FinalizedLigeritoProof<BinaryElem32, BinaryElem128>, u8), ZyncError> {
if bytes.is_empty() {
return Err(ZyncError::Serialization("empty proof bytes".into()));
}
let log_size = bytes[0];
let proof = bincode::deserialize(&bytes[1..])
.map_err(|e| ZyncError::Serialization(format!("bincode: {}", e)))?;
Ok((proof, log_size))
}
}