Skip to main content

pczt/roles/prover/
orchard.rs

1use orchard::circuit::ProvingKey;
2use rand_core::{CryptoRng, Rng};
3
4use crate::{Pczt, common::AnchorRequirement};
5
6impl super::Prover {
7    /// Creates the Orchard proof for this PCZT.
8    ///
9    /// `rng` provides the randomness for the proof.
10    pub fn create_orchard_proof<R: Rng + CryptoRng>(
11        self,
12        rng: R,
13        pk: &ProvingKey,
14    ) -> Result<Self, OrchardError> {
15        let Pczt {
16            global,
17            transparent,
18            sapling,
19            orchard,
20            ironwood,
21        } = self.pczt;
22
23        let mut parsed = orchard
24            .into_parsed_with_version(
25                crate::orchard::orchard_bundle_version(&global)
26                    .ok_or(OrchardError::UnsupportedConsensusBranchId)?,
27                AnchorRequirement::Required,
28            )
29            .map_err(OrchardError::Parser)?;
30
31        crate::orchard::verify_witnesses_root_to_anchor(&parsed.bundle, *parsed.bundle.anchor())
32            .map_err(OrchardError::InconsistentWitness)?;
33
34        parsed
35            .bundle
36            .create_proof(pk, rng)
37            .map_err(OrchardError::Prover)?;
38
39        Ok(Self {
40            pczt: Pczt {
41                global,
42                transparent,
43                sapling,
44                orchard: parsed.reserialize(),
45                ironwood,
46            },
47        })
48    }
49
50    /// Creates the Ironwood proof for this PCZT.
51    ///
52    /// `rng` provides the randomness for the proof.
53    pub fn create_ironwood_proof<R: Rng + CryptoRng>(
54        self,
55        rng: R,
56        pk: &ProvingKey,
57    ) -> Result<Self, IronwoodError> {
58        let Pczt {
59            global,
60            transparent,
61            sapling,
62            orchard,
63            ironwood,
64        } = self.pczt;
65
66        let mut parsed = ironwood
67            .into_ironwood_parsed(AnchorRequirement::Required)
68            .map_err(IronwoodError::Parser)?;
69
70        crate::orchard::verify_witnesses_root_to_anchor(&parsed.bundle, *parsed.bundle.anchor())
71            .map_err(IronwoodError::InconsistentWitness)?;
72
73        parsed
74            .bundle
75            .create_proof(pk, rng)
76            .map_err(IronwoodError::Prover)?;
77
78        Ok(Self {
79            pczt: Pczt {
80                global,
81                transparent,
82                sapling,
83                orchard,
84                ironwood: parsed.reserialize(),
85            },
86        })
87    }
88}
89
90/// Errors that can occur while creating Orchard proofs for a PCZT.
91#[derive(Debug)]
92pub enum OrchardError {
93    /// A non-zero-valued spend's `witness` does not root to the bundle's anchor.
94    InconsistentWitness(crate::orchard::AnchorConsistencyError),
95    Parser(crate::orchard::ParseError),
96    Prover(orchard::pczt::ProverError),
97    /// The PCZT's consensus branch ID is unrecognized, or predates NU5 (under which
98    /// the Orchard protocol is not supported).
99    UnsupportedConsensusBranchId,
100}
101
102/// Errors that can occur while creating Ironwood proofs for a PCZT.
103#[derive(Debug)]
104pub enum IronwoodError {
105    /// A non-zero-valued spend's `witness` does not root to the bundle's anchor.
106    InconsistentWitness(crate::orchard::AnchorConsistencyError),
107    Parser(crate::orchard::ParseError),
108    Prover(orchard::pczt::ProverError),
109}
110
111#[cfg(test)]
112mod tests {
113    use rand::{rand_core::UnwrapErr, rngs::SysRng};
114    extern crate std;
115
116    use std::sync::OnceLock;
117
118    use zcash_protocol::consensus::BranchId;
119
120    use super::{IronwoodError, OrchardError};
121    use crate::{orchard::testing::dummy_action, roles::creator::Creator, roles::prover::Prover};
122
123    static PROVING_KEY: OnceLock<orchard::circuit::ProvingKey> = OnceLock::new();
124
125    fn proving_key() -> &'static orchard::circuit::ProvingKey {
126        PROVING_KEY.get_or_init(|| {
127            orchard::circuit::ProvingKey::build(
128                orchard::circuit::OrchardCircuitVersion::FixedPostNu6_2,
129            )
130        })
131    }
132
133    #[test]
134    fn create_orchard_proof_fails_when_anchor_absent() {
135        let mut pczt = Creator::new(BranchId::Nu6_3.into(), 100, 133, None, None)
136            .unwrap()
137            .build()
138            .unwrap();
139        pczt.orchard.actions.push(dummy_action());
140
141        assert!(matches!(
142            Prover::new(pczt).create_orchard_proof(UnwrapErr(SysRng), proving_key()),
143            Err(OrchardError::Parser(
144                crate::orchard::ParseError::MissingAnchor
145            ))
146        ));
147    }
148
149    #[test]
150    fn create_ironwood_proof_fails_when_anchor_absent() {
151        let mut pczt = Creator::new(BranchId::Nu6_3.into(), 100, 133, None, None)
152            .unwrap()
153            .build()
154            .unwrap();
155        pczt.ironwood.actions.push(dummy_action());
156
157        assert!(matches!(
158            Prover::new(pczt).create_ironwood_proof(UnwrapErr(SysRng), proving_key()),
159            Err(IronwoodError::Parser(
160                crate::orchard::ParseError::MissingAnchor
161            ))
162        ));
163    }
164
165    /// An untouched, empty Orchard bundle has no actions, so parsing substitutes a
166    /// placeholder anchor even though the Prover requires the anchor to be set. Proving
167    /// such a bundle is a no-op, but the wire anchor must stay absent afterwards.
168    #[test]
169    fn create_orchard_proof_preserves_absent_anchor_for_untouched_bundle() {
170        let pczt = Creator::new(BranchId::Nu6_3.into(), 100, 133, None, None)
171            .unwrap()
172            .build()
173            .unwrap();
174
175        let pczt = Prover::new(pczt)
176            .create_orchard_proof(UnwrapErr(SysRng), proving_key())
177            .unwrap()
178            .finish();
179
180        assert_eq!(pczt.orchard, crate::orchard::EMPTY_ORCHARD);
181
182        let bytes = pczt.clone().serialize().unwrap();
183        assert_eq!(
184            crate::parse(&bytes).unwrap().orchard,
185            crate::orchard::EMPTY_ORCHARD
186        );
187    }
188
189    /// See [`create_orchard_proof_preserves_absent_anchor_for_untouched_bundle`].
190    #[test]
191    fn create_ironwood_proof_preserves_absent_anchor_for_untouched_bundle() {
192        let pczt = Creator::new(BranchId::Nu6_3.into(), 100, 133, None, None)
193            .unwrap()
194            .build()
195            .unwrap();
196
197        let pczt = Prover::new(pczt)
198            .create_ironwood_proof(UnwrapErr(SysRng), proving_key())
199            .unwrap()
200            .finish();
201
202        assert_eq!(pczt.ironwood, crate::orchard::EMPTY_IRONWOOD);
203
204        let bytes = pczt.clone().serialize().unwrap();
205        assert_eq!(
206            crate::parse(&bytes).unwrap().ironwood,
207            crate::orchard::EMPTY_IRONWOOD
208        );
209    }
210}