Skip to main content

orchard/bundle/
batch.rs

1use alloc::vec::Vec;
2
3use halo2_proofs::plonk;
4use pasta_curves::vesta;
5use rand::{CryptoRng, RngCore};
6use tracing::debug;
7
8use super::{Authorized, Bundle};
9use crate::{
10    circuit::VerifyingKey,
11    primitives::redpallas::{self, Binding, SpendAuth},
12};
13
14/// A signature within an authorized Orchard bundle.
15#[derive(Debug)]
16struct BundleSignature {
17    /// The signature item for validation.
18    signature: redpallas::batch::Item<SpendAuth, Binding>,
19}
20
21/// Batch validation context for Orchard.
22///
23/// This batch-validates proofs and RedPallas signatures.
24#[derive(Debug, Default)]
25pub struct BatchValidator {
26    proofs: plonk::BatchVerifier<vesta::Affine>,
27    signatures: Vec<BundleSignature>,
28}
29
30impl BatchValidator {
31    /// Constructs a new batch validation context.
32    pub fn new() -> Self {
33        BatchValidator {
34            proofs: plonk::BatchVerifier::new(),
35            signatures: vec![],
36        }
37    }
38
39    /// Adds the proof and RedPallas signatures from the given bundle to the validator.
40    pub fn add_bundle<V: Copy + Into<i64>>(
41        &mut self,
42        bundle: &Bundle<Authorized, V>,
43        sighash: [u8; 32],
44    ) {
45        for action in bundle.actions().iter() {
46            self.signatures.push(BundleSignature {
47                signature: action
48                    .rk()
49                    .create_batch_item(action.authorization().clone(), &sighash),
50            });
51        }
52
53        self.signatures.push(BundleSignature {
54            signature: bundle
55                .binding_validating_key()
56                .create_batch_item(bundle.authorization().binding_signature().clone(), &sighash),
57        });
58
59        bundle
60            .authorization()
61            .proof()
62            .add_to_batch(&mut self.proofs, bundle.to_instances());
63    }
64
65    /// Batch-validates the accumulated bundles.
66    ///
67    /// Returns `true` if every proof and signature in every bundle added to the batch
68    /// validator is valid, or `false` if one or more are invalid. No attempt is made to
69    /// figure out which of the accumulated bundles might be invalid; if that information
70    /// is desired, construct separate [`BatchValidator`]s for sub-batches of the bundles.
71    pub fn validate<R: RngCore + CryptoRng>(self, vk: &VerifyingKey, rng: R) -> bool {
72        // https://p.z.cash/TCR:bad-txns-orchard-binding-signature-invalid?partial
73
74        if self.signatures.is_empty() {
75            // An empty batch is always valid, but is not free to run; skip it.
76            // Note that a transaction has at least a binding signature, so if
77            // there are no signatures, there are also no proofs.
78            return true;
79        }
80
81        let mut validator = redpallas::batch::Verifier::new();
82        for sig in self.signatures.iter() {
83            validator.queue(sig.signature.clone());
84        }
85
86        match validator.verify(rng) {
87            // If signatures are valid, check the proofs.
88            Ok(()) => self.proofs.finalize(&vk.params, &vk.vk),
89            Err(e) => {
90                debug!("RedPallas batch validation failed: {}", e);
91                false
92            }
93        }
94    }
95}