Skip to main content

pczt/roles/prover/
sapling.rs

1use rand_core::{CryptoRng, Rng};
2use sapling::prover::{OutputProver, SpendProver};
3
4use crate::{Pczt, common::AnchorRequirement};
5
6impl super::Prover {
7    /// Creates the Sapling proofs for this PCZT.
8    ///
9    /// `rng` provides the randomness for the proofs.
10    pub fn create_sapling_proofs<S, O, R>(
11        self,
12        rng: R,
13        spend_prover: &S,
14        output_prover: &O,
15    ) -> Result<Self, SaplingError>
16    where
17        S: SpendProver,
18        O: OutputProver,
19        R: Rng + CryptoRng,
20    {
21        let Pczt {
22            global,
23            transparent,
24            sapling,
25            orchard,
26            ironwood,
27        } = self.pczt;
28
29        let mut parsed = sapling
30            .into_parsed(AnchorRequirement::Required)
31            .map_err(SaplingError::Parser)?;
32
33        crate::sapling::verify_witnesses_root_to_anchor(&parsed.bundle, *parsed.bundle.anchor())
34            .map_err(SaplingError::InconsistentWitness)?;
35
36        parsed
37            .bundle
38            .create_proofs(spend_prover, output_prover, rng)
39            .map_err(SaplingError::Prover)?;
40
41        Ok(Self {
42            pczt: Pczt {
43                global,
44                transparent,
45                sapling: parsed.reserialize(),
46                orchard,
47                ironwood,
48            },
49        })
50    }
51}
52
53/// Errors that can occur while creating Sapling proofs for a PCZT.
54#[derive(Debug)]
55pub enum SaplingError {
56    /// A non-zero-valued spend's `witness` does not root to the bundle's anchor.
57    InconsistentWitness(crate::sapling::AnchorConsistencyError),
58    Parser(crate::sapling::ParseError),
59    Prover(sapling::pczt::ProverError),
60}
61
62#[cfg(test)]
63mod tests {
64    use alloc::collections::BTreeMap;
65    use rand::{rand_core::UnwrapErr, rngs::SysRng};
66
67    use zcash_proofs::prover::LocalTxProver;
68    use zcash_protocol::consensus::BranchId;
69
70    use super::SaplingError;
71    use crate::{roles::creator::Creator, roles::prover::Prover, sapling::Spend};
72
73    #[test]
74    fn create_sapling_proofs_fails_when_anchor_absent() {
75        let mut pczt = Creator::new(BranchId::Nu6_3.into(), 100, 133, None, None)
76            .unwrap()
77            .build()
78            .unwrap();
79        // A non-empty bundle whose anchor is absent MUST be rejected with a typed
80        // error before proving is attempted, rather than panicking.
81        pczt.sapling.spends.push(Spend {
82            cv: [1; 32],
83            nullifier: [2; 32],
84            rk: [3; 32],
85            zkproof: None,
86            spend_auth_sig: None,
87            recipient: None,
88            value: None,
89            rcm: None,
90            rseed: None,
91            rcv: None,
92            proof_generation_key: None,
93            witness: None,
94            alpha: None,
95            zip32_derivation: None,
96            dummy_ask: None,
97            proprietary: BTreeMap::new(),
98        });
99
100        let prover = LocalTxProver::bundled();
101        assert!(matches!(
102            Prover::new(pczt).create_sapling_proofs(UnwrapErr(SysRng), &prover, &prover),
103            Err(SaplingError::Parser(
104                crate::sapling::ParseError::MissingAnchor
105            ))
106        ));
107    }
108
109    /// An untouched, empty Sapling bundle has no spends or outputs, so `into_parsed`
110    /// substitutes a placeholder anchor for its absent one even though the Prover
111    /// requires the anchor to be set. Proving such a bundle is a no-op, but the wire
112    /// anchor MUST stay absent afterwards rather than picking up the placeholder, so
113    /// that the bundle remains canonically empty.
114    #[test]
115    fn create_sapling_proofs_preserves_absent_anchor_for_untouched_bundle() {
116        let pczt = Creator::new(BranchId::Nu6_3.into(), 100, 133, None, None)
117            .unwrap()
118            .build()
119            .unwrap();
120
121        let prover = LocalTxProver::bundled();
122        let pczt = Prover::new(pczt)
123            .create_sapling_proofs(UnwrapErr(SysRng), &prover, &prover)
124            .unwrap()
125            .finish();
126
127        assert_eq!(pczt.sapling, crate::sapling::EMPTY_BUNDLE);
128
129        let bytes = pczt.clone().serialize().unwrap();
130        assert_eq!(
131            crate::parse(&bytes).unwrap().sapling,
132            crate::sapling::EMPTY_BUNDLE
133        );
134    }
135}