Skip to main content

pczt/roles/tx_extractor/
mod.rs

1//! The Transaction Extractor role (anyone can execute).
2//!
3//! - Creates bindingSig and extracts the final transaction.
4
5use core::{cell::RefCell, marker::PhantomData};
6use rand_core::{CryptoRng, Rng};
7
8use zcash_primitives::transaction::{
9    Authorization, Transaction,
10    sighash::{PrecomputedSighashData, SignableInput},
11};
12
13use crate::Pczt;
14
15mod orchard;
16pub use self::orchard::OrchardError;
17pub type IronwoodError = OrchardError;
18
19mod sapling;
20pub use self::sapling::SaplingError;
21
22mod transparent;
23pub use self::transparent::TransparentError;
24
25pub struct TransactionExtractor<'a> {
26    pczt: Pczt,
27    sapling_vk: Option<(
28        &'a ::sapling::circuit::SpendVerifyingKey,
29        &'a ::sapling::circuit::OutputVerifyingKey,
30    )>,
31    orchard_vk: Option<&'a ::orchard::circuit::VerifyingKey>,
32    _unused: PhantomData<&'a ()>,
33}
34
35impl<'a> TransactionExtractor<'a> {
36    /// Instantiates the Transaction Extractor role with the given PCZT.
37    pub fn new(pczt: Pczt) -> Self {
38        Self {
39            pczt,
40            sapling_vk: None,
41            orchard_vk: None,
42            _unused: PhantomData,
43        }
44    }
45
46    /// Provides the Sapling Spend and Output verifying keys for validating the Sapling
47    /// proofs (if any).
48    ///
49    /// If not provided, and the PCZT has a Sapling bundle, [`Self::extract`] will return
50    /// an error.
51    pub fn with_sapling(
52        mut self,
53        spend_vk: &'a ::sapling::circuit::SpendVerifyingKey,
54        output_vk: &'a ::sapling::circuit::OutputVerifyingKey,
55    ) -> Self {
56        self.sapling_vk = Some((spend_vk, output_vk));
57        self
58    }
59
60    /// Provides an existing Orchard verifying key for validating the Orchard proof (if
61    /// any).
62    ///
63    /// If not provided, and the PCZT has an Orchard bundle, an Orchard verifying key will
64    /// be generated on the fly.
65    pub fn with_orchard(mut self, orchard_vk: &'a ::orchard::circuit::VerifyingKey) -> Self {
66        self.orchard_vk = Some(orchard_vk);
67        self
68    }
69
70    /// Attempts to extract a valid transaction from the PCZT.
71    ///
72    /// `rng` provides the randomness for the binding signatures and for the batch
73    /// validation of the extracted transaction.
74    pub fn extract<R: Rng + CryptoRng>(self, mut rng: R) -> Result<Transaction, Error> {
75        let Self {
76            pczt,
77            sapling_vk,
78            orchard_vk,
79            _unused,
80        } = self;
81
82        let crate::ParsedPczt { tx_data, .. } = pczt.extract_tx_data::<Unbound, Error>(
83            crate::common::AnchorRequirement::Required,
84            |t| {
85                t.extract()
86                    .map_err(|e| Error::Transparent(TransparentError::Extract(e)))
87            },
88            |s| {
89                s.extract()
90                    .map_err(|e| Error::Sapling(SaplingError::Extract(e)))
91            },
92            |o| {
93                o.extract()
94                    .map_err(|e| Error::Orchard(OrchardError::Extract(e)))
95            },
96            |i| {
97                i.extract()
98                    .map_err(|e| Error::Ironwood(IronwoodError::Extract(e)))
99            },
100        )?;
101
102        // The commitment being signed is shared across all shielded inputs.
103        let sighash_data = PrecomputedSighashData::new(tx_data);
104        let shielded_sighash = sighash_data.signature_hash(&SignableInput::Shielded);
105        let tx_data = sighash_data.into_tx();
106
107        // Create the binding signatures. The bundle mappers each need the RNG, and
108        // `try_map_bundles` calls them one at a time.
109        let binding_rng = RefCell::new(&mut rng);
110        let tx_data = tx_data.try_map_bundles(
111            |t| Ok(t.map(|t| t.map_authorization(transparent::RemoveInputInfo))),
112            |s| {
113                s.map(|s| {
114                    s.apply_binding_signature(
115                        *shielded_sighash.as_ref(),
116                        &mut **binding_rng.borrow_mut(),
117                    )
118                    .ok_or(Error::SighashMismatch)
119                })
120                .transpose()
121            },
122            |o| {
123                o.map(|o| {
124                    o.apply_binding_signature(
125                        *shielded_sighash.as_ref(),
126                        &mut **binding_rng.borrow_mut(),
127                    )
128                    .ok_or(Error::SighashMismatch)
129                })
130                .transpose()
131            },
132        )?;
133
134        let tx = tx_data.freeze().expect("txid construction can't fail here");
135
136        // Now that we have a supposedly fully-authorized transaction, verify it.
137        if let Some(bundle) = tx.sapling_bundle() {
138            let (spend_vk, output_vk) = sapling_vk.ok_or(Error::SaplingRequired)?;
139
140            sapling::verify_bundle(
141                &mut rng,
142                bundle,
143                spend_vk,
144                output_vk,
145                *shielded_sighash.as_ref(),
146            )
147            .map_err(Error::Sapling)?;
148        }
149        if let Some(bundle) = tx.orchard_bundle() {
150            orchard::verify_bundle(&mut rng, bundle, orchard_vk, *shielded_sighash.as_ref())
151                .map_err(Error::Orchard)?;
152        }
153        if let Some(bundle) = tx.ironwood_bundle() {
154            orchard::verify_bundle(&mut rng, bundle, orchard_vk, *shielded_sighash.as_ref())
155                .map_err(Error::Ironwood)?;
156        }
157
158        Ok(tx)
159    }
160}
161
162struct Unbound;
163
164impl Authorization for Unbound {
165    type TransparentAuth = ::transparent::pczt::Unbound;
166    type SaplingAuth = ::sapling::pczt::Unbound;
167    type OrchardAuth = ::orchard::pczt::Unbound;
168}
169
170/// Errors that can occur while extracting a transaction from a PCZT.
171#[derive(Debug)]
172pub enum Error {
173    Extract(crate::ExtractError),
174    Ironwood(IronwoodError),
175    Orchard(OrchardError),
176    Sapling(SaplingError),
177    SaplingRequired,
178    SighashMismatch,
179    Transparent(TransparentError),
180}
181
182impl From<crate::ExtractError> for Error {
183    fn from(e: crate::ExtractError) -> Self {
184        Error::Extract(e)
185    }
186}