1use ligerito::transcript::{FiatShamir, Transcript};
34use ligerito::{data_structures::FinalizedLigeritoProof, prove_with_transcript, ProverConfig};
35use ligerito_binary_fields::{BinaryElem128, BinaryElem32, BinaryFieldElement};
36use serde::{Deserialize, Serialize};
37
38use crate::error::ZyncError;
39use crate::trace::{HeaderChainTrace, FIELDS_PER_HEADER, TIP_SENTINEL_SIZE};
40
41#[derive(Clone, Debug, Serialize, Deserialize)]
59pub struct ProofPublicOutputs {
60 pub start_height: u32,
61 pub end_height: u32,
62 pub start_hash: [u8; 32],
64 pub start_prev_hash: [u8; 32],
66 pub tip_hash: [u8; 32],
68 pub tip_prev_hash: [u8; 32],
69 pub cumulative_difficulty: u64,
70 pub final_commitment: [u8; 32],
72 pub final_state_commitment: [u8; 32],
74 pub num_headers: u32,
75 pub tip_tree_root: [u8; 32],
77 pub tip_nullifier_root: [u8; 32],
79 pub final_actions_commitment: [u8; 32],
81}
82
83pub struct HeaderChainProof {
85 pub proof_bytes: Vec<u8>,
86 pub public_outputs: ProofPublicOutputs,
87 pub trace_log_size: u32,
88}
89
90impl HeaderChainProof {
91 pub fn prove(
100 config: &ProverConfig<BinaryElem32, BinaryElem128>,
101 trace: &HeaderChainTrace,
102 ) -> Result<Self, ZyncError> {
103 let public_outputs = Self::extract_public_outputs(trace)?;
104
105 let mut transcript = FiatShamir::new_sha256(0);
106
107 let public_bytes = bincode::serialize(&public_outputs)
109 .map_err(|e| ZyncError::Serialization(format!("bincode public outputs: {}", e)))?;
110 transcript.absorb_bytes(b"public_outputs", &public_bytes);
111
112 let proof = prove_with_transcript(config, &trace.trace, transcript)
113 .map_err(|e| ZyncError::Ligerito(format!("{:?}", e)))?;
114
115 let trace_log_size = (trace.trace.len() as f64).log2().ceil() as u32;
116 let proof_bytes = Self::serialize_proof_with_config(&proof, trace_log_size as u8)?;
117
118 Ok(Self {
119 proof_bytes,
120 public_outputs,
121 trace_log_size,
122 })
123 }
124
125 pub fn prove_auto(trace: &mut HeaderChainTrace) -> Result<Self, ZyncError> {
128 let (config, required_size) = crate::prover_config_for_size(trace.trace.len());
129
130 if trace.trace.len() < required_size {
131 trace.trace.resize(required_size, BinaryElem32::zero());
132 }
133
134 Self::prove(&config, trace)
135 }
136
137 fn extract_public_outputs(trace: &HeaderChainTrace) -> Result<ProofPublicOutputs, ZyncError> {
143 if trace.num_headers == 0 {
144 return Err(ZyncError::InvalidData("empty trace".into()));
145 }
146
147 let extract_hash = |base_offset: usize, field_start: usize| -> [u8; 32] {
148 let mut hash = [0u8; 32];
149 for j in 0..8 {
150 let field_val = trace.trace[base_offset + field_start + j].poly().value();
151 hash[j * 4..(j + 1) * 4].copy_from_slice(&field_val.to_le_bytes());
152 }
153 hash
154 };
155
156 let first_offset = 0;
157 let start_hash = extract_hash(first_offset, 1);
158 let start_prev_hash = extract_hash(first_offset, 9);
159
160 let last_offset = (trace.num_headers - 1) * FIELDS_PER_HEADER;
161 let tip_hash = extract_hash(last_offset, 1);
162 let tip_prev_hash = extract_hash(last_offset, 9);
163
164 let sentinel_offset = trace.num_headers * FIELDS_PER_HEADER;
165 let tip_tree_root = extract_hash(sentinel_offset, 0);
166 let tip_nullifier_root = extract_hash(sentinel_offset, 8);
167 let final_actions_commitment = extract_hash(sentinel_offset, 16);
168
169 Ok(ProofPublicOutputs {
170 start_height: trace.start_height,
171 end_height: trace.end_height,
172 start_hash,
173 start_prev_hash,
174 tip_hash,
175 tip_prev_hash,
176 cumulative_difficulty: trace.cumulative_difficulty,
177 final_commitment: trace.final_commitment,
178 final_state_commitment: trace.final_state_commitment,
179 num_headers: trace.num_headers as u32,
180 tip_tree_root,
181 tip_nullifier_root,
182 final_actions_commitment,
183 })
184 }
185
186 pub fn serialize_full(&self) -> Result<Vec<u8>, ZyncError> {
188 let public_bytes = bincode::serialize(&self.public_outputs)
189 .map_err(|e| ZyncError::Serialization(format!("bincode: {}", e)))?;
190
191 let mut result = Vec::with_capacity(4 + public_bytes.len() + self.proof_bytes.len());
192 result.extend_from_slice(&(public_bytes.len() as u32).to_le_bytes());
193 result.extend(public_bytes);
194 result.extend(&self.proof_bytes);
195 Ok(result)
196 }
197
198 pub fn deserialize_full(
200 bytes: &[u8],
201 ) -> Result<(ProofPublicOutputs, Vec<u8>, u8), ZyncError> {
202 if bytes.len() < 5 {
203 return Err(ZyncError::Serialization("proof too short".into()));
204 }
205
206 let public_len =
207 u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) as usize;
208 if bytes.len() < 4 + public_len + 1 {
209 return Err(ZyncError::Serialization("proof truncated".into()));
210 }
211
212 let public_outputs: ProofPublicOutputs =
213 bincode::deserialize(&bytes[4..4 + public_len])
214 .map_err(|e| ZyncError::Serialization(format!("bincode: {}", e)))?;
215
216 let proof_bytes = bytes[4 + public_len..].to_vec();
217 let log_size = if !proof_bytes.is_empty() {
218 proof_bytes[0]
219 } else {
220 0
221 };
222
223 Ok((public_outputs, proof_bytes, log_size))
224 }
225
226 fn serialize_proof_with_config(
228 proof: &FinalizedLigeritoProof<BinaryElem32, BinaryElem128>,
229 log_size: u8,
230 ) -> Result<Vec<u8>, ZyncError> {
231 let proof_bytes = bincode::serialize(proof)
232 .map_err(|e| ZyncError::Serialization(format!("bincode: {}", e)))?;
233
234 let mut result = Vec::with_capacity(1 + proof_bytes.len());
235 result.push(log_size);
236 result.extend(proof_bytes);
237 Ok(result)
238 }
239
240 pub fn deserialize_proof_with_config(
242 bytes: &[u8],
243 ) -> Result<(FinalizedLigeritoProof<BinaryElem32, BinaryElem128>, u8), ZyncError> {
244 if bytes.is_empty() {
245 return Err(ZyncError::Serialization("empty proof bytes".into()));
246 }
247 let log_size = bytes[0];
248 let proof = bincode::deserialize(&bytes[1..])
249 .map_err(|e| ZyncError::Serialization(format!("bincode: {}", e)))?;
250 Ok((proof, log_size))
251 }
252}