zync_core/
verifier.rs

1//! ligerito proof verification
2//!
3//! supports both native (with rayon parallelism) and wasm targets
4
5use anyhow::Result;
6use ligerito::{FinalizedLigeritoProof, verify};
7use ligerito_binary_fields::{BinaryElem32, BinaryElem128};
8use crate::verifier_config_for_log_size;
9
10#[cfg(not(target_arch = "wasm32"))]
11use std::thread;
12
13/// deserialize proof with config prefix
14/// format: [log_size: u8][proof_bytes...]
15fn deserialize_proof(bytes: &[u8]) -> Result<(FinalizedLigeritoProof<BinaryElem32, BinaryElem128>, u8)> {
16    if bytes.is_empty() {
17        anyhow::bail!("empty proof bytes");
18    }
19    let log_size = bytes[0];
20    let proof = bincode::deserialize(&bytes[1..])
21        .map_err(|e| anyhow::anyhow!("failed to deserialize proof: {}", e))?;
22    Ok((proof, log_size))
23}
24
25/// verify a single proof
26fn verify_single(proof_bytes: &[u8]) -> Result<bool> {
27    let (proof, log_size) = deserialize_proof(proof_bytes)?;
28    let config = verifier_config_for_log_size(log_size as u32);
29    verify(&config, &proof).map_err(|e| anyhow::anyhow!("verification error: {}", e))
30}
31
32/// verify combined gigaproof + tip proof
33/// format: [gigaproof_size: u32][gigaproof_bytes][tip_bytes]
34/// returns (gigaproof_valid, tip_valid)
35#[cfg(not(target_arch = "wasm32"))]
36pub fn verify_proofs(combined_proof: &[u8]) -> Result<(bool, bool)> {
37    if combined_proof.len() < 4 {
38        anyhow::bail!("proof too small");
39    }
40
41    let gigaproof_size = u32::from_le_bytes([
42        combined_proof[0],
43        combined_proof[1],
44        combined_proof[2],
45        combined_proof[3],
46    ]) as usize;
47
48    if combined_proof.len() < 4 + gigaproof_size {
49        anyhow::bail!("invalid proof format");
50    }
51
52    let gigaproof_bytes = combined_proof[4..4 + gigaproof_size].to_vec();
53    let tip_bytes = combined_proof[4 + gigaproof_size..].to_vec();
54
55    // verify both proofs in parallel using native threads
56    let giga_handle = thread::spawn(move || verify_single(&gigaproof_bytes));
57    let tip_handle = thread::spawn(move || verify_single(&tip_bytes));
58
59    let gigaproof_valid = giga_handle.join()
60        .map_err(|_| anyhow::anyhow!("gigaproof thread panicked"))??;
61    let tip_valid = tip_handle.join()
62        .map_err(|_| anyhow::anyhow!("tip thread panicked"))??;
63
64    Ok((gigaproof_valid, tip_valid))
65}
66
67/// verify combined gigaproof + tip proof (wasm - sequential)
68#[cfg(target_arch = "wasm32")]
69pub fn verify_proofs(combined_proof: &[u8]) -> Result<(bool, bool)> {
70    if combined_proof.len() < 4 {
71        anyhow::bail!("proof too small");
72    }
73
74    let gigaproof_size = u32::from_le_bytes([
75        combined_proof[0],
76        combined_proof[1],
77        combined_proof[2],
78        combined_proof[3],
79    ]) as usize;
80
81    if combined_proof.len() < 4 + gigaproof_size {
82        anyhow::bail!("invalid proof format");
83    }
84
85    let gigaproof_bytes = &combined_proof[4..4 + gigaproof_size];
86    let tip_bytes = &combined_proof[4 + gigaproof_size..];
87
88    // wasm: verify sequentially (web workers would need different setup)
89    let gigaproof_valid = verify_single(gigaproof_bytes)?;
90    let tip_valid = verify_single(tip_bytes)?;
91
92    Ok((gigaproof_valid, tip_valid))
93}
94
95/// verify just tip proof (for incremental sync)
96pub fn verify_tip(tip_proof: &[u8]) -> Result<bool> {
97    verify_single(tip_proof)
98}
99
100/// verify multiple proofs in parallel (native only)
101#[cfg(not(target_arch = "wasm32"))]
102pub fn verify_batch(proofs: Vec<Vec<u8>>) -> Result<Vec<bool>> {
103    use std::sync::mpsc;
104
105    let (tx, rx) = mpsc::channel();
106    let num_proofs = proofs.len();
107
108    for (i, proof) in proofs.into_iter().enumerate() {
109        let tx = tx.clone();
110        thread::spawn(move || {
111            let result = verify_single(&proof);
112            let _ = tx.send((i, result));
113        });
114    }
115    drop(tx);
116
117    let mut results = vec![false; num_proofs];
118    for _ in 0..num_proofs {
119        let (i, result) = rx.recv()
120            .map_err(|_| anyhow::anyhow!("channel error"))?;
121        results[i] = result?;
122    }
123
124    Ok(results)
125}
126
127/// verify multiple proofs (wasm - sequential)
128#[cfg(target_arch = "wasm32")]
129pub fn verify_batch(proofs: Vec<Vec<u8>>) -> Result<Vec<bool>> {
130    proofs.iter().map(|p| verify_single(p)).collect()
131}
132
133#[cfg(test)]
134mod tests {
135    use super::*;
136
137    #[test]
138    fn test_empty_proof_fails() {
139        let result = verify_proofs(&[]);
140        assert!(result.is_err());
141    }
142
143    #[test]
144    fn test_too_small_proof_fails() {
145        let result = verify_proofs(&[1, 2, 3]);
146        assert!(result.is_err());
147    }
148
149    #[test]
150    fn test_invalid_format_fails() {
151        // says gigaproof is 1000 bytes but we only have 10
152        let mut data = vec![0xe8, 0x03, 0x00, 0x00]; // 1000 in little endian
153        data.extend_from_slice(&[0u8; 10]);
154        let result = verify_proofs(&data);
155        assert!(result.is_err());
156    }
157}