1use crate::verifier_config_for_log_size;
9use anyhow::Result;
10use ligerito::{transcript::FiatShamir, verify_with_transcript, FinalizedLigeritoProof};
11use ligerito_binary_fields::{BinaryElem128, BinaryElem32};
12use serde::{Deserialize, Serialize};
13
14#[cfg(not(target_arch = "wasm32"))]
15use std::thread;
16
17#[derive(Clone, Debug, Serialize, Deserialize)]
19pub struct ProofPublicOutputs {
20 pub start_height: u32,
21 pub end_height: u32,
22 pub start_hash: [u8; 32],
23 pub start_prev_hash: [u8; 32],
24 pub tip_hash: [u8; 32],
25 pub tip_prev_hash: [u8; 32],
26 pub cumulative_difficulty: u64,
27 pub final_commitment: [u8; 32],
28 pub final_state_commitment: [u8; 32],
29 pub num_headers: u32,
30 pub tip_tree_root: [u8; 32],
32 pub tip_nullifier_root: [u8; 32],
34 pub final_actions_commitment: [u8; 32],
36}
37
38#[derive(Clone, Debug)]
40pub struct VerifyResult {
41 pub gigaproof_valid: bool,
42 pub tip_valid: bool,
43 pub continuous: bool,
44 pub giga_outputs: ProofPublicOutputs,
45 pub tip_outputs: Option<ProofPublicOutputs>,
46}
47
48fn split_full_proof(full: &[u8]) -> Result<(ProofPublicOutputs, Vec<u8>)> {
50 if full.len() < 4 {
51 anyhow::bail!("proof too short");
52 }
53 let public_len = u32::from_le_bytes([full[0], full[1], full[2], full[3]]) as usize;
54 if full.len() < 4 + public_len + 1 {
55 anyhow::bail!("proof truncated");
56 }
57 let outputs: ProofPublicOutputs = bincode::deserialize(&full[4..4 + public_len])
58 .map_err(|e| anyhow::anyhow!("deserialize public outputs: {}", e))?;
59 let raw = full[4 + public_len..].to_vec();
60 Ok((outputs, raw))
61}
62
63fn deserialize_proof(
65 bytes: &[u8],
66) -> Result<(FinalizedLigeritoProof<BinaryElem32, BinaryElem128>, u8)> {
67 if bytes.is_empty() {
68 anyhow::bail!("empty proof bytes");
69 }
70 let log_size = bytes[0];
71 let proof = bincode::deserialize(&bytes[1..])
72 .map_err(|e| anyhow::anyhow!("failed to deserialize proof: {}", e))?;
73 Ok((proof, log_size))
74}
75
76fn verify_single(proof_bytes: &[u8]) -> Result<bool> {
78 let (proof, log_size) = deserialize_proof(proof_bytes)?;
79 let config = verifier_config_for_log_size(log_size as u32);
80 let transcript = FiatShamir::new_sha256(0);
81 verify_with_transcript(&config, &proof, transcript)
82 .map_err(|e| anyhow::anyhow!("verification error: {}", e))
83}
84
85#[cfg(not(target_arch = "wasm32"))]
94pub fn verify_proofs(combined_proof: &[u8]) -> Result<(bool, bool)> {
95 let result = verify_proofs_full(combined_proof)?;
96 Ok((
97 result.gigaproof_valid,
98 result.tip_valid && result.continuous,
99 ))
100}
101
102#[cfg(not(target_arch = "wasm32"))]
104pub fn verify_proofs_full(combined_proof: &[u8]) -> Result<VerifyResult> {
105 if combined_proof.len() < 4 {
106 anyhow::bail!("proof too small");
107 }
108
109 let giga_full_size = u32::from_le_bytes([
110 combined_proof[0],
111 combined_proof[1],
112 combined_proof[2],
113 combined_proof[3],
114 ]) as usize;
115
116 if combined_proof.len() < 4 + giga_full_size {
117 anyhow::bail!("invalid proof format");
118 }
119
120 let giga_full = &combined_proof[4..4 + giga_full_size];
121 let tip_full = &combined_proof[4 + giga_full_size..];
122
123 let (giga_outputs, giga_raw) = split_full_proof(giga_full)?;
125 let (tip_outputs, tip_raw) = if !tip_full.is_empty() {
126 let (o, r) = split_full_proof(tip_full)?;
127 (Some(o), r)
128 } else {
129 (None, vec![])
130 };
131
132 let giga_raw_clone = giga_raw;
134 let tip_raw_clone = tip_raw;
135 let giga_handle = thread::spawn(move || verify_single(&giga_raw_clone));
136 let tip_handle = if !tip_raw_clone.is_empty() {
137 Some(thread::spawn(move || verify_single(&tip_raw_clone)))
138 } else {
139 None
140 };
141
142 let gigaproof_valid = giga_handle
143 .join()
144 .map_err(|_| anyhow::anyhow!("gigaproof thread panicked"))??;
145 let tip_valid = match tip_handle {
146 Some(h) => h
147 .join()
148 .map_err(|_| anyhow::anyhow!("tip thread panicked"))??,
149 None => true,
150 };
151
152 let continuous = match &tip_outputs {
154 Some(tip) => tip.start_prev_hash == giga_outputs.tip_hash,
155 None => true, };
157
158 Ok(VerifyResult {
159 gigaproof_valid,
160 tip_valid,
161 continuous,
162 giga_outputs,
163 tip_outputs,
164 })
165}
166
167#[cfg(target_arch = "wasm32")]
169pub fn verify_proofs(combined_proof: &[u8]) -> Result<(bool, bool)> {
170 let result = verify_proofs_full(combined_proof)?;
171 Ok((
172 result.gigaproof_valid,
173 result.tip_valid && result.continuous,
174 ))
175}
176
177#[cfg(target_arch = "wasm32")]
178pub fn verify_proofs_full(combined_proof: &[u8]) -> Result<VerifyResult> {
179 if combined_proof.len() < 4 {
180 anyhow::bail!("proof too small");
181 }
182
183 let giga_full_size = u32::from_le_bytes([
184 combined_proof[0],
185 combined_proof[1],
186 combined_proof[2],
187 combined_proof[3],
188 ]) as usize;
189
190 if combined_proof.len() < 4 + giga_full_size {
191 anyhow::bail!("invalid proof format");
192 }
193
194 let giga_full = &combined_proof[4..4 + giga_full_size];
195 let tip_full = &combined_proof[4 + giga_full_size..];
196
197 let (giga_outputs, giga_raw) = split_full_proof(giga_full)?;
198 let (tip_outputs, tip_raw) = if !tip_full.is_empty() {
199 let (o, r) = split_full_proof(tip_full)?;
200 (Some(o), r)
201 } else {
202 (None, vec![])
203 };
204
205 let gigaproof_valid = verify_single(&giga_raw)?;
206 let tip_valid = if !tip_raw.is_empty() {
207 verify_single(&tip_raw)?
208 } else {
209 true
210 };
211
212 let continuous = match &tip_outputs {
213 Some(tip) => tip.start_prev_hash == giga_outputs.tip_hash,
214 None => true,
215 };
216
217 Ok(VerifyResult {
218 gigaproof_valid,
219 tip_valid,
220 continuous,
221 giga_outputs,
222 tip_outputs,
223 })
224}
225
226pub fn verify_tip(tip_proof: &[u8]) -> Result<bool> {
228 verify_single(tip_proof)
229}
230
231#[cfg(test)]
232mod tests {
233 use super::*;
234
235 #[test]
236 fn test_empty_proof_fails() {
237 let result = verify_proofs(&[]);
238 assert!(result.is_err());
239 }
240
241 #[test]
242 fn test_too_small_proof_fails() {
243 let result = verify_proofs(&[1, 2, 3]);
244 assert!(result.is_err());
245 }
246}