light_verifier/
lib.rs

1use groth16_solana::{
2    decompression::{decompress_g1, decompress_g2},
3    groth16::{Groth16Verifier, Groth16Verifyingkey},
4};
5use thiserror::Error;
6
7use crate::verifying_keys::*;
8
9pub mod verifying_keys;
10#[derive(Debug, Error, PartialEq)]
11pub enum VerifierError {
12    #[error("PublicInputsTryIntoFailed")]
13    PublicInputsTryIntoFailed,
14    #[error("DecompressG1Failed")]
15    DecompressG1Failed,
16    #[error("DecompressG2Failed")]
17    DecompressG2Failed,
18    #[error("InvalidPublicInputsLength")]
19    InvalidPublicInputsLength,
20    #[error("CreateGroth16VerifierFailed")]
21    CreateGroth16VerifierFailed,
22    #[error("ProofVerificationFailed")]
23    ProofVerificationFailed,
24    #[error("InvalidBatchSize supported batch sizes are 1, 10, 100, 500, 1000")]
25    InvalidBatchSize,
26}
27
28impl From<VerifierError> for u32 {
29    fn from(e: VerifierError) -> u32 {
30        match e {
31            PublicInputsTryIntoFailed => 13001,
32            DecompressG1Failed => 13002,
33            DecompressG2Failed => 13003,
34            InvalidPublicInputsLength => 13004,
35            CreateGroth16VerifierFailed => 13005,
36            ProofVerificationFailed => 13006,
37            InvalidBatchSize => 13007,
38        }
39    }
40}
41
42#[cfg(feature = "solana")]
43impl From<VerifierError> for solana_program_error::ProgramError {
44    fn from(e: VerifierError) -> Self {
45        solana_program_error::ProgramError::Custom(e.into())
46    }
47}
48
49#[cfg(feature = "pinocchio")]
50impl From<VerifierError> for pinocchio::program_error::ProgramError {
51    fn from(e: VerifierError) -> Self {
52        pinocchio::program_error::ProgramError::Custom(e.into())
53    }
54}
55
56pub use light_compressed_account::instruction_data::compressed_proof::CompressedProof;
57use VerifierError::*;
58
59pub fn verify_create_addresses_proof(
60    address_roots: &[[u8; 32]],
61    addresses: &[[u8; 32]],
62    compressed_proof: &CompressedProof,
63) -> Result<(), VerifierError> {
64    let public_inputs = [address_roots, addresses].concat();
65    match addresses.len() {
66        1 => verify::<2>(
67            &public_inputs
68                .try_into()
69                .map_err(|_| PublicInputsTryIntoFailed)?,
70            compressed_proof,
71            &v1_non_inclusion_26_1::VERIFYINGKEY,
72        ),
73        2 => verify::<4>(
74            &public_inputs
75                .try_into()
76                .map_err(|_| PublicInputsTryIntoFailed)?,
77            compressed_proof,
78            &v1_non_inclusion_26_2::VERIFYINGKEY,
79        ),
80        3 => verify::<6>(
81            &public_inputs
82                .try_into()
83                .map_err(|_| PublicInputsTryIntoFailed)?,
84            compressed_proof,
85            &v1_non_inclusion_26_3::VERIFYINGKEY,
86        ),
87        4 => verify::<8>(
88            &public_inputs
89                .try_into()
90                .map_err(|_| PublicInputsTryIntoFailed)?,
91            compressed_proof,
92            &v1_non_inclusion_26_4::VERIFYINGKEY,
93        ),
94        8 => verify::<16>(
95            &public_inputs
96                .try_into()
97                .map_err(|_| PublicInputsTryIntoFailed)?,
98            compressed_proof,
99            &v1_non_inclusion_26_8::VERIFYINGKEY,
100        ),
101        _ => Err(InvalidPublicInputsLength),
102    }
103}
104
105#[inline(never)]
106pub fn verify_create_addresses_and_inclusion_proof(
107    roots: &[[u8; 32]],
108    leaves: &[[u8; 32]],
109    address_roots: &[[u8; 32]],
110    addresses: &[[u8; 32]],
111    compressed_proof: &CompressedProof,
112) -> Result<(), VerifierError> {
113    let public_inputs = [roots, leaves, address_roots, addresses].concat();
114    // The public inputs are expected to be a multiple of 2
115    // 4 inputs means 1 inclusion proof (1 root, 1 leaf, 1 address root, 1 created address)
116    // 6 inputs means 1 inclusion proof (1 root, 1 leaf, 2 address roots, 2 created address) or
117    // 6 inputs means 2 inclusion proofs (2 roots and 2 leaves, 1 address root, 1 created address)
118    // 8 inputs means 2 inclusion proofs (2 roots and 2 leaves, 2 address roots, 2 created address) or
119    // 8 inputs means 3 inclusion proofs (3 roots and 3 leaves, 1 address root, 1 created address)
120    // 10 inputs means 3 inclusion proofs (3 roots and 3 leaves, 2 address roots, 2 created address) or
121    // 10 inputs means 4 inclusion proofs (4 roots and 4 leaves, 1 address root, 1 created address)
122    // 12 inputs means 4 inclusion proofs (4 roots and 4 leaves, 2 address roots, 2 created address)
123    match public_inputs.len() {
124        4 => verify::<4>(
125            &public_inputs
126                .try_into()
127                .map_err(|_| PublicInputsTryIntoFailed)?,
128            compressed_proof,
129            &v1_combined_26_26_1_1::VERIFYINGKEY,
130        ),
131        6 => {
132            let verifying_key = if address_roots.len() == 1 {
133                &v1_combined_26_26_2_1::VERIFYINGKEY
134            } else {
135                &v1_combined_26_26_1_2::VERIFYINGKEY
136            };
137            verify::<6>(
138                &public_inputs
139                    .try_into()
140                    .map_err(|_| PublicInputsTryIntoFailed)?,
141                compressed_proof,
142                verifying_key,
143            )
144        }
145        8 => {
146            let verifying_key = if address_roots.len() == 1 {
147                &v1_combined_26_26_3_1::VERIFYINGKEY
148            } else {
149                &v1_combined_26_26_2_2::VERIFYINGKEY
150            };
151            verify::<8>(
152                &public_inputs
153                    .try_into()
154                    .map_err(|_| PublicInputsTryIntoFailed)?,
155                compressed_proof,
156                verifying_key,
157            )
158        }
159        10 => {
160            let verifying_key = if address_roots.len() == 1 {
161                &v1_combined_26_26_4_1::VERIFYINGKEY
162            } else {
163                &v1_combined_26_26_3_2::VERIFYINGKEY
164            };
165            verify::<10>(
166                &public_inputs
167                    .try_into()
168                    .map_err(|_| PublicInputsTryIntoFailed)?,
169                compressed_proof,
170                verifying_key,
171            )
172        }
173        12 => verify::<12>(
174            &public_inputs
175                .try_into()
176                .map_err(|_| PublicInputsTryIntoFailed)?,
177            compressed_proof,
178            &v1_combined_26_26_4_2::VERIFYINGKEY,
179        ),
180        _ => Err(InvalidPublicInputsLength),
181    }
182}
183
184#[inline(never)]
185pub fn verify_inclusion_proof(
186    roots: &[[u8; 32]],
187    leaves: &[[u8; 32]],
188    compressed_proof: &CompressedProof,
189) -> Result<(), VerifierError> {
190    let public_inputs = [roots, leaves].concat();
191
192    // The public inputs are expected to be a multiple of 2
193    // 2 inputs means 1 inclusion proof (1 root and 1 leaf)
194    // 4 inputs means 2 inclusion proofs (2 roots and 2 leaves)
195    // 6 inputs means 3 inclusion proofs (3 roots and 3 leaves)
196    // 8 inputs means 4 inclusion proofs (4 roots and 4 leaves)
197    // 16 inputs means 8 inclusion proofs (8 roots and 8 leaves)
198    match public_inputs.len() {
199        2 => verify::<2>(
200            &public_inputs
201                .try_into()
202                .map_err(|_| PublicInputsTryIntoFailed)?,
203            compressed_proof,
204            &v1_inclusion_26_1::VERIFYINGKEY,
205        ),
206        4 => verify::<4>(
207            &public_inputs
208                .try_into()
209                .map_err(|_| PublicInputsTryIntoFailed)?,
210            compressed_proof,
211            &v1_inclusion_26_2::VERIFYINGKEY,
212        ),
213        6 => verify::<6>(
214            &public_inputs
215                .try_into()
216                .map_err(|_| PublicInputsTryIntoFailed)?,
217            compressed_proof,
218            &v1_inclusion_26_3::VERIFYINGKEY,
219        ),
220        8 => verify::<8>(
221            &public_inputs
222                .try_into()
223                .map_err(|_| PublicInputsTryIntoFailed)?,
224            compressed_proof,
225            &v1_inclusion_26_4::VERIFYINGKEY,
226        ),
227        16 => verify::<16>(
228            &public_inputs
229                .try_into()
230                .map_err(|_| PublicInputsTryIntoFailed)?,
231            compressed_proof,
232            &v1_inclusion_26_8::VERIFYINGKEY,
233        ),
234        _ => Err(InvalidPublicInputsLength),
235    }
236}
237
238pub fn select_verifying_key<'a>(
239    num_leaves: usize,
240    num_addresses: usize,
241) -> Result<&'a Groth16Verifyingkey<'static>, VerifierError> {
242    #[cfg(all(feature = "solana", target_os = "solana"))]
243    solana_msg::msg!(
244        "select_verifying_key num_leaves: {}, num_addresses: {}",
245        num_leaves,
246        num_addresses
247    );
248    match (num_leaves, num_addresses) {
249        // Combined cases (depend on both num_leaves and num_addresses)
250        (1, 1) => Ok(&v2_combined_32_40_1_1::VERIFYINGKEY),
251        (1, 2) => Ok(&v2_combined_32_40_1_2::VERIFYINGKEY),
252        (1, 3) => Ok(&v2_combined_32_40_1_3::VERIFYINGKEY),
253        (1, 4) => Ok(&v2_combined_32_40_1_4::VERIFYINGKEY),
254        (2, 1) => Ok(&v2_combined_32_40_2_1::VERIFYINGKEY),
255        (2, 2) => Ok(&v2_combined_32_40_2_2::VERIFYINGKEY),
256        (2, 3) => Ok(&v2_combined_32_40_2_3::VERIFYINGKEY),
257        (2, 4) => Ok(&v2_combined_32_40_2_4::VERIFYINGKEY),
258        (3, 1) => Ok(&v2_combined_32_40_3_1::VERIFYINGKEY),
259        (3, 2) => Ok(&v2_combined_32_40_3_2::VERIFYINGKEY),
260        (3, 3) => Ok(&v2_combined_32_40_3_3::VERIFYINGKEY),
261        (3, 4) => Ok(&v2_combined_32_40_3_4::VERIFYINGKEY),
262        (4, 1) => Ok(&v2_combined_32_40_4_1::VERIFYINGKEY),
263        (4, 2) => Ok(&v2_combined_32_40_4_2::VERIFYINGKEY),
264        (4, 3) => Ok(&v2_combined_32_40_4_3::VERIFYINGKEY),
265        (4, 4) => Ok(&v2_combined_32_40_4_4::VERIFYINGKEY),
266
267        // Inclusion cases (depend on num_leaves)
268        (1, _) => Ok(&v2_inclusion_32_1::VERIFYINGKEY),
269        (2, _) => Ok(&v2_inclusion_32_2::VERIFYINGKEY),
270        (3, _) => Ok(&v2_inclusion_32_3::VERIFYINGKEY),
271        (4, _) => Ok(&v2_inclusion_32_4::VERIFYINGKEY),
272        (5, _) => Ok(&v2_inclusion_32_5::VERIFYINGKEY),
273        (6, _) => Ok(&v2_inclusion_32_6::VERIFYINGKEY),
274        (7, _) => Ok(&v2_inclusion_32_7::VERIFYINGKEY),
275        (8, _) => Ok(&v2_inclusion_32_8::VERIFYINGKEY),
276        (9, _) => Ok(&v2_inclusion_32_9::VERIFYINGKEY),
277        (10, _) => Ok(&v2_inclusion_32_10::VERIFYINGKEY),
278        (11, _) => Ok(&v2_inclusion_32_11::VERIFYINGKEY),
279        (12, _) => Ok(&v2_inclusion_32_12::VERIFYINGKEY),
280        (13, _) => Ok(&v2_inclusion_32_13::VERIFYINGKEY),
281        (14, _) => Ok(&v2_inclusion_32_14::VERIFYINGKEY),
282        (15, _) => Ok(&v2_inclusion_32_15::VERIFYINGKEY),
283        (16, _) => Ok(&v2_inclusion_32_16::VERIFYINGKEY),
284        (17, _) => Ok(&v2_inclusion_32_17::VERIFYINGKEY),
285        (18, _) => Ok(&v2_inclusion_32_18::VERIFYINGKEY),
286        (19, _) => Ok(&v2_inclusion_32_19::VERIFYINGKEY),
287        (20, _) => Ok(&v2_inclusion_32_20::VERIFYINGKEY),
288
289        // Non-inclusion cases (depend on num_addresses)
290        (_, 1) => Ok(&v2_non_inclusion_40_1::VERIFYINGKEY),
291        (_, 2) => Ok(&v2_non_inclusion_40_2::VERIFYINGKEY),
292        (_, 3) => Ok(&v2_non_inclusion_40_3::VERIFYINGKEY),
293        (_, 4) => Ok(&v2_non_inclusion_40_4::VERIFYINGKEY),
294        (_, 5) => Ok(&v2_non_inclusion_40_5::VERIFYINGKEY),
295        (_, 6) => Ok(&v2_non_inclusion_40_6::VERIFYINGKEY),
296        (_, 7) => Ok(&v2_non_inclusion_40_7::VERIFYINGKEY),
297        (_, 8) => Ok(&v2_non_inclusion_40_8::VERIFYINGKEY),
298        (_, 9) => Ok(&v2_non_inclusion_40_9::VERIFYINGKEY),
299        (_, 10) => Ok(&v2_non_inclusion_40_10::VERIFYINGKEY),
300        (_, 11) => Ok(&v2_non_inclusion_40_11::VERIFYINGKEY),
301        (_, 12) => Ok(&v2_non_inclusion_40_12::VERIFYINGKEY),
302        (_, 13) => Ok(&v2_non_inclusion_40_13::VERIFYINGKEY),
303        (_, 14) => Ok(&v2_non_inclusion_40_14::VERIFYINGKEY),
304        (_, 15) => Ok(&v2_non_inclusion_40_15::VERIFYINGKEY),
305        (_, 16) => Ok(&v2_non_inclusion_40_16::VERIFYINGKEY),
306        (_, 17) => Ok(&v2_non_inclusion_40_17::VERIFYINGKEY),
307        (_, 18) => Ok(&v2_non_inclusion_40_18::VERIFYINGKEY),
308        (_, 19) => Ok(&v2_non_inclusion_40_19::VERIFYINGKEY),
309        (_, 20) => Ok(&v2_non_inclusion_40_20::VERIFYINGKEY),
310        (_, 21) => Ok(&v2_non_inclusion_40_21::VERIFYINGKEY),
311        (_, 22) => Ok(&v2_non_inclusion_40_22::VERIFYINGKEY),
312        (_, 23) => Ok(&v2_non_inclusion_40_23::VERIFYINGKEY),
313        (_, 24) => Ok(&v2_non_inclusion_40_24::VERIFYINGKEY),
314        (_, 25) => Ok(&v2_non_inclusion_40_25::VERIFYINGKEY),
315        (_, 26) => Ok(&v2_non_inclusion_40_26::VERIFYINGKEY),
316        (_, 27) => Ok(&v2_non_inclusion_40_27::VERIFYINGKEY),
317        (_, 28) => Ok(&v2_non_inclusion_40_28::VERIFYINGKEY),
318        (_, 29) => Ok(&v2_non_inclusion_40_29::VERIFYINGKEY),
319        (_, 30) => Ok(&v2_non_inclusion_40_30::VERIFYINGKEY),
320        (_, 31) => Ok(&v2_non_inclusion_40_31::VERIFYINGKEY),
321        (_, 32) => Ok(&v2_non_inclusion_40_32::VERIFYINGKEY),
322
323        // Invalid configuration
324        _ => Err(InvalidPublicInputsLength),
325    }
326}
327
328#[inline(never)]
329pub fn verify<const N: usize>(
330    public_inputs: &[[u8; 32]; N],
331    proof: &CompressedProof,
332    vk: &Groth16Verifyingkey,
333) -> Result<(), VerifierError> {
334    let proof_a = decompress_g1(&proof.a).map_err(|_| DecompressG1Failed)?;
335    let proof_b = decompress_g2(&proof.b).map_err(|_| DecompressG2Failed)?;
336    let proof_c = decompress_g1(&proof.c).map_err(|_| DecompressG1Failed)?;
337    let mut verifier = Groth16Verifier::new(&proof_a, &proof_b, &proof_c, public_inputs, vk)
338        .map_err(|_| {
339            #[cfg(all(target_os = "solana", feature = "solana"))]
340            {
341                use solana_msg::msg;
342                msg!("Proof verification failed");
343                msg!("Public inputs: {:?}", public_inputs);
344                msg!("Proof A: {:?}", proof_a);
345                msg!("Proof B: {:?}", proof_b);
346                msg!("Proof C: {:?}", proof_c);
347            }
348            CreateGroth16VerifierFailed
349        })?;
350    verifier.verify().map_err(|_| {
351        #[cfg(all(target_os = "solana", feature = "solana"))]
352        {
353            use solana_msg::msg;
354            msg!("Proof verification failed");
355            msg!("Public inputs: {:?}", public_inputs);
356            msg!("Proof A: {:?}", proof_a);
357            msg!("Proof B: {:?}", proof_b);
358            msg!("Proof C: {:?}", proof_c);
359        }
360        ProofVerificationFailed
361    })?;
362    Ok(())
363}
364
365#[inline(never)]
366pub fn verify_batch_append_with_proofs(
367    batch_size: u64,
368    public_input_hash: [u8; 32],
369    compressed_proof: &CompressedProof,
370) -> Result<(), VerifierError> {
371    match batch_size {
372        10 => verify::<1>(
373            &[public_input_hash],
374            compressed_proof,
375            &batch_append_32_10::VERIFYINGKEY,
376        ),
377        500 => verify::<1>(
378            &[public_input_hash],
379            compressed_proof,
380            &batch_append_32_500::VERIFYINGKEY,
381        ),
382        _ => Err(InvalidPublicInputsLength),
383    }
384}
385
386#[inline(never)]
387pub fn verify_batch_update(
388    batch_size: u64,
389    public_input_hash: [u8; 32],
390    compressed_proof: &CompressedProof,
391) -> Result<(), VerifierError> {
392    match batch_size {
393        10 => verify::<1>(
394            &[public_input_hash],
395            compressed_proof,
396            &batch_update_32_10::VERIFYINGKEY,
397        ),
398        500 => verify::<1>(
399            &[public_input_hash],
400            compressed_proof,
401            &batch_update_32_500::VERIFYINGKEY,
402        ),
403        _ => Err(InvalidPublicInputsLength),
404    }
405}
406
407#[inline(never)]
408pub fn verify_batch_address_update(
409    batch_size: u64,
410    public_input_hash: [u8; 32],
411    compressed_proof: &CompressedProof,
412) -> Result<(), VerifierError> {
413    match batch_size {
414        10 => verify::<1>(
415            &[public_input_hash],
416            compressed_proof,
417            &batch_address_append_40_10::VERIFYINGKEY,
418        ),
419        250 => verify::<1>(
420            &[public_input_hash],
421            compressed_proof,
422            &batch_address_append_40_250::VERIFYINGKEY,
423        ),
424        _ => Err(InvalidPublicInputsLength),
425    }
426}