sapling_crypto/verifier/
batch.rs

1use bellman::groth16;
2use bls12_381::Bls12;
3use group::GroupEncoding;
4use rand_core::{CryptoRng, RngCore};
5
6use super::SaplingVerificationContextInner;
7use crate::{
8    bundle::{Authorized, Bundle},
9    circuit::{OutputVerifyingKey, SpendVerifyingKey},
10};
11
12/// Batch validation context for Sapling.
13///
14/// This batch-validates Spend and Output proofs, and RedJubjub signatures.
15///
16/// Signatures are verified assuming ZIP 216 is active.
17pub struct BatchValidator {
18    bundles_added: bool,
19    spend_proofs: groth16::batch::Verifier<Bls12>,
20    output_proofs: groth16::batch::Verifier<Bls12>,
21    signatures: redjubjub::batch::Verifier,
22}
23
24impl Default for BatchValidator {
25    fn default() -> Self {
26        Self::new()
27    }
28}
29
30impl BatchValidator {
31    /// Constructs a new batch validation context.
32    pub fn new() -> Self {
33        BatchValidator {
34            bundles_added: false,
35            spend_proofs: groth16::batch::Verifier::new(),
36            output_proofs: groth16::batch::Verifier::new(),
37            signatures: redjubjub::batch::Verifier::new(),
38        }
39    }
40
41    /// Checks the bundle against Sapling-specific consensus rules, and adds its proof and
42    /// signatures to the validator.
43    ///
44    /// Returns `false` if the bundle doesn't satisfy all of the consensus rules. This
45    /// `BatchValidator` can continue to be used regardless, but some or all of the proofs
46    /// and signatures from this bundle may have already been added to the batch even if
47    /// it fails other consensus rules.
48    pub fn check_bundle<V: Copy + Into<i64>>(
49        &mut self,
50        bundle: Bundle<Authorized, V>,
51        sighash: [u8; 32],
52    ) -> bool {
53        self.bundles_added = true;
54
55        let mut ctx = SaplingVerificationContextInner::new();
56
57        for spend in bundle.shielded_spends() {
58            // Deserialize the proof
59            let zkproof = match groth16::Proof::read(&spend.zkproof()[..]) {
60                Ok(p) => p,
61                Err(_) => return false,
62            };
63
64            // Check the Spend consensus rules, and batch its proof and spend
65            // authorization signature.
66            let consensus_rules_passed = ctx.check_spend(
67                spend.cv(),
68                *spend.anchor(),
69                &spend.nullifier().0,
70                spend.rk(),
71                zkproof,
72                self,
73                |this, rk| {
74                    this.signatures
75                        .queue(((*rk).into(), *spend.spend_auth_sig(), &sighash));
76                    true
77                },
78                |this, proof, public_inputs| {
79                    this.spend_proofs.queue((proof, public_inputs.to_vec()));
80                    true
81                },
82            );
83            if !consensus_rules_passed {
84                return false;
85            }
86        }
87
88        for output in bundle.shielded_outputs() {
89            // Deserialize the ephemeral key
90            let epk = match jubjub::ExtendedPoint::from_bytes(&output.ephemeral_key().0).into() {
91                Some(p) => p,
92                None => return false,
93            };
94
95            // Deserialize the proof
96            let zkproof = match groth16::Proof::read(&output.zkproof()[..]) {
97                Ok(p) => p,
98                Err(_) => return false,
99            };
100
101            // Check the Output consensus rules, and batch its proof.
102            let consensus_rules_passed = ctx.check_output(
103                output.cv(),
104                *output.cmu(),
105                epk,
106                zkproof,
107                |proof, public_inputs| {
108                    self.output_proofs.queue((proof, public_inputs.to_vec()));
109                    true
110                },
111            );
112            if !consensus_rules_passed {
113                return false;
114            }
115        }
116
117        // Check the whole-bundle consensus rules, and batch the binding signature.
118        ctx.final_check(*bundle.value_balance(), |bvk| {
119            self.signatures
120                .queue((bvk.into(), bundle.authorization().binding_sig, &sighash));
121            true
122        })
123    }
124
125    /// Batch-validates the accumulated bundles.
126    ///
127    /// Returns `true` if every proof and signature in every bundle added to the batch
128    /// validator is valid, or `false` if one or more are invalid. No attempt is made to
129    /// figure out which of the accumulated bundles might be invalid; if that information
130    /// is desired, construct separate [`BatchValidator`]s for sub-batches of the bundles.
131    pub fn validate<R: RngCore + CryptoRng>(
132        self,
133        spend_vk: &SpendVerifyingKey,
134        output_vk: &OutputVerifyingKey,
135        mut rng: R,
136    ) -> bool {
137        if !self.bundles_added {
138            // An empty batch is always valid, but is not free to run; skip it.
139            return true;
140        }
141
142        if let Err(e) = self.signatures.verify(&mut rng) {
143            #[cfg(feature = "std")]
144            tracing::debug!("Signature batch validation failed: {}", e);
145            #[cfg(not(feature = "std"))]
146            tracing::debug!("Signature batch validation failed: {:?}", e);
147            return false;
148        }
149
150        #[cfg(feature = "multicore")]
151        let verify_proofs = |batch: groth16::batch::Verifier<Bls12>, vk| batch.verify_multicore(vk);
152
153        #[cfg(not(feature = "multicore"))]
154        let mut verify_proofs =
155            |batch: groth16::batch::Verifier<Bls12>, vk| batch.verify(&mut rng, vk);
156
157        if verify_proofs(self.spend_proofs, &spend_vk.0).is_err() {
158            tracing::debug!("Spend proof batch validation failed");
159            return false;
160        }
161
162        if verify_proofs(self.output_proofs, &output_vk.0).is_err() {
163            tracing::debug!("Output proof batch validation failed");
164            return false;
165        }
166
167        true
168    }
169}