1use anyhow::Result;
9use ligerito::{FinalizedLigeritoProof, verify_with_transcript, transcript::FiatShamir};
10use ligerito_binary_fields::{BinaryElem32, BinaryElem128};
11use serde::{Serialize, Deserialize};
12use crate::verifier_config_for_log_size;
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}
31
32#[derive(Clone, Debug)]
34pub struct VerifyResult {
35 pub gigaproof_valid: bool,
36 pub tip_valid: bool,
37 pub continuous: bool,
38 pub giga_outputs: ProofPublicOutputs,
39 pub tip_outputs: Option<ProofPublicOutputs>,
40}
41
42fn split_full_proof(full: &[u8]) -> Result<(ProofPublicOutputs, Vec<u8>)> {
44 if full.len() < 4 {
45 anyhow::bail!("proof too short");
46 }
47 let public_len = u32::from_le_bytes([full[0], full[1], full[2], full[3]]) as usize;
48 if full.len() < 4 + public_len + 1 {
49 anyhow::bail!("proof truncated");
50 }
51 let outputs: ProofPublicOutputs = bincode::deserialize(&full[4..4 + public_len])
52 .map_err(|e| anyhow::anyhow!("deserialize public outputs: {}", e))?;
53 let raw = full[4 + public_len..].to_vec();
54 Ok((outputs, raw))
55}
56
57fn deserialize_proof(bytes: &[u8]) -> Result<(FinalizedLigeritoProof<BinaryElem32, BinaryElem128>, u8)> {
59 if bytes.is_empty() {
60 anyhow::bail!("empty proof bytes");
61 }
62 let log_size = bytes[0];
63 let proof = bincode::deserialize(&bytes[1..])
64 .map_err(|e| anyhow::anyhow!("failed to deserialize proof: {}", e))?;
65 Ok((proof, log_size))
66}
67
68fn verify_single(proof_bytes: &[u8]) -> Result<bool> {
70 let (proof, log_size) = deserialize_proof(proof_bytes)?;
71 let config = verifier_config_for_log_size(log_size as u32);
72 let transcript = FiatShamir::new_sha256(0);
73 verify_with_transcript(&config, &proof, transcript)
74 .map_err(|e| anyhow::anyhow!("verification error: {}", e))
75}
76
77#[cfg(not(target_arch = "wasm32"))]
86pub fn verify_proofs(combined_proof: &[u8]) -> Result<(bool, bool)> {
87 let result = verify_proofs_full(combined_proof)?;
88 Ok((result.gigaproof_valid, result.tip_valid && result.continuous))
89}
90
91#[cfg(not(target_arch = "wasm32"))]
93pub fn verify_proofs_full(combined_proof: &[u8]) -> Result<VerifyResult> {
94 if combined_proof.len() < 4 {
95 anyhow::bail!("proof too small");
96 }
97
98 let giga_full_size = u32::from_le_bytes([
99 combined_proof[0], combined_proof[1],
100 combined_proof[2], combined_proof[3],
101 ]) as usize;
102
103 if combined_proof.len() < 4 + giga_full_size {
104 anyhow::bail!("invalid proof format");
105 }
106
107 let giga_full = &combined_proof[4..4 + giga_full_size];
108 let tip_full = &combined_proof[4 + giga_full_size..];
109
110 let (giga_outputs, giga_raw) = split_full_proof(giga_full)?;
112 let (tip_outputs, tip_raw) = if !tip_full.is_empty() {
113 let (o, r) = split_full_proof(tip_full)?;
114 (Some(o), r)
115 } else {
116 (None, vec![])
117 };
118
119 let giga_raw_clone = giga_raw;
121 let tip_raw_clone = tip_raw;
122 let giga_handle = thread::spawn(move || verify_single(&giga_raw_clone));
123 let tip_handle = if !tip_raw_clone.is_empty() {
124 Some(thread::spawn(move || verify_single(&tip_raw_clone)))
125 } else {
126 None
127 };
128
129 let gigaproof_valid = giga_handle.join()
130 .map_err(|_| anyhow::anyhow!("gigaproof thread panicked"))??;
131 let tip_valid = match tip_handle {
132 Some(h) => h.join().map_err(|_| anyhow::anyhow!("tip thread panicked"))??,
133 None => true,
134 };
135
136 let continuous = match &tip_outputs {
138 Some(tip) => tip.start_prev_hash == giga_outputs.tip_hash,
139 None => true, };
141
142 Ok(VerifyResult {
143 gigaproof_valid,
144 tip_valid,
145 continuous,
146 giga_outputs,
147 tip_outputs,
148 })
149}
150
151#[cfg(target_arch = "wasm32")]
153pub fn verify_proofs(combined_proof: &[u8]) -> Result<(bool, bool)> {
154 let result = verify_proofs_full(combined_proof)?;
155 Ok((result.gigaproof_valid, result.tip_valid && result.continuous))
156}
157
158#[cfg(target_arch = "wasm32")]
159pub fn verify_proofs_full(combined_proof: &[u8]) -> Result<VerifyResult> {
160 if combined_proof.len() < 4 {
161 anyhow::bail!("proof too small");
162 }
163
164 let giga_full_size = u32::from_le_bytes([
165 combined_proof[0], combined_proof[1],
166 combined_proof[2], combined_proof[3],
167 ]) as usize;
168
169 if combined_proof.len() < 4 + giga_full_size {
170 anyhow::bail!("invalid proof format");
171 }
172
173 let giga_full = &combined_proof[4..4 + giga_full_size];
174 let tip_full = &combined_proof[4 + giga_full_size..];
175
176 let (giga_outputs, giga_raw) = split_full_proof(giga_full)?;
177 let (tip_outputs, tip_raw) = if !tip_full.is_empty() {
178 let (o, r) = split_full_proof(tip_full)?;
179 (Some(o), r)
180 } else {
181 (None, vec![])
182 };
183
184 let gigaproof_valid = verify_single(&giga_raw)?;
185 let tip_valid = if !tip_raw.is_empty() {
186 verify_single(&tip_raw)?
187 } else {
188 true
189 };
190
191 let continuous = match &tip_outputs {
192 Some(tip) => tip.start_prev_hash == giga_outputs.tip_hash,
193 None => true,
194 };
195
196 Ok(VerifyResult {
197 gigaproof_valid,
198 tip_valid,
199 continuous,
200 giga_outputs,
201 tip_outputs,
202 })
203}
204
205pub fn verify_tip(tip_proof: &[u8]) -> Result<bool> {
207 verify_single(tip_proof)
208}
209
210#[cfg(test)]
211mod tests {
212 use super::*;
213
214 #[test]
215 fn test_empty_proof_fails() {
216 let result = verify_proofs(&[]);
217 assert!(result.is_err());
218 }
219
220 #[test]
221 fn test_too_small_proof_fails() {
222 let result = verify_proofs(&[1, 2, 3]);
223 assert!(result.is_err());
224 }
225}