Skip to main content

sapling_crypto/verifier/
batch.rs

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