Skip to main content

pczt/roles/io_finalizer/
mod.rs

1//! The IO Finalizer role (anyone can execute).
2//!
3//! - Sets the appropriate bits in `Global.tx_modifiable` to 0.
4//! - Updates the various bsk values using the rcv information from spends and outputs.
5
6use rand_core::{CryptoRng, Rng};
7use zcash_primitives::transaction::sighash::SignableInput;
8
9use crate::{
10    ExtractError, ParsedPczt, Pczt,
11    common::{
12        FLAG_SHIELDED_MODIFIABLE, FLAG_TRANSPARENT_INPUTS_MODIFIABLE,
13        FLAG_TRANSPARENT_OUTPUTS_MODIFIABLE,
14    },
15    precompute_sighash_data, sighash,
16};
17
18pub struct IoFinalizer {
19    pczt: Pczt,
20}
21
22impl IoFinalizer {
23    /// Instantiates the IO Finalizer role with the given PCZT.
24    pub fn new(pczt: Pczt) -> Self {
25        Self { pczt }
26    }
27
28    /// Finalizes the IO of the PCZT.
29    ///
30    /// `rng` provides the randomness for the signatures of any dummy shielded spends.
31    pub fn finalize_io<R: Rng + CryptoRng>(self, mut rng: R) -> Result<Pczt, Error> {
32        let Self { pczt } = self;
33
34        let has_orchard_actions = !pczt.orchard.actions.is_empty();
35        let has_ironwood_actions = !pczt.ironwood.actions.is_empty();
36        let has_sapling_spends = !pczt.sapling.spends.is_empty();
37        let has_shielded_spends = has_sapling_spends || has_orchard_actions || has_ironwood_actions;
38        let has_shielded_outputs =
39            !(pczt.sapling.outputs.is_empty() && !has_orchard_actions && !has_ironwood_actions);
40
41        // We can't build a transaction that has no spends or outputs.
42        // However, we don't attempt to reject an entirely dummy transaction.
43        if pczt.transparent.inputs.is_empty() && !has_shielded_spends {
44            return Err(Error::NoSpends);
45        }
46        if pczt.transparent.outputs.is_empty() && !has_shielded_outputs {
47            return Err(Error::NoOutputs);
48        }
49
50        let anchor_requirement =
51            crate::common::AnchorRequirement::for_pre_authorization(pczt.global.tx_version);
52
53        let ParsedPczt {
54            mut global,
55            transparent,
56            mut sapling,
57            mut orchard,
58            mut ironwood,
59            tx_data,
60        } = pczt.extract_tx_data(
61            anchor_requirement,
62            |t| {
63                t.extract_effects()
64                    .map_err(ExtractError::TransparentExtract)
65            },
66            |s| s.extract_effects().map_err(ExtractError::SaplingExtract),
67            |o| o.extract_effects().map_err(ExtractError::OrchardExtract),
68            |i| i.extract_effects().map_err(ExtractError::IronwoodExtract),
69        )?;
70
71        // After shielded IO finalization, the transaction effects cannot be modified
72        // because dummy spends will have been signed.
73        if has_shielded_spends || has_shielded_outputs {
74            global.tx_modifiable &= !(FLAG_TRANSPARENT_INPUTS_MODIFIABLE
75                | FLAG_TRANSPARENT_OUTPUTS_MODIFIABLE
76                | FLAG_SHIELDED_MODIFIABLE);
77        }
78        let sighash_data = precompute_sighash_data(&global, tx_data)?;
79        let shielded_sighash = sighash(&sighash_data, &SignableInput::Shielded);
80
81        // The Sapling bundle is always finalized: unlike the Orchard-protocol
82        // Transaction Extractor, the Sapling one requires `bsk` to be set even when
83        // the bundle is empty.
84        sapling
85            .bundle
86            .finalize_io(shielded_sighash, &mut rng)
87            .map_err(Error::SaplingFinalize)?;
88        // An empty Orchard-protocol bundle carries no value commitment information
89        // and contributes nothing to the transaction; leave its `bsk` unset so that
90        // it stays in its canonical empty form (and so remains omissible by, or
91        // representable in, the serialization formats).
92        if has_orchard_actions {
93            orchard
94                .bundle
95                .finalize_io(shielded_sighash, &mut rng)
96                .map_err(Error::OrchardFinalize)?;
97        }
98        if has_ironwood_actions {
99            ironwood
100                .bundle
101                .finalize_io(shielded_sighash, &mut rng)
102                .map_err(Error::IronwoodFinalize)?;
103        }
104
105        Ok(Pczt {
106            global,
107            transparent: crate::transparent::Bundle::serialize_from(transparent),
108            sapling: sapling.reserialize(),
109            orchard: orchard.reserialize(),
110            ironwood: ironwood.reserialize(),
111        })
112    }
113}
114
115/// Errors that can occur while finalizing the IO of a PCZT.
116#[derive(Debug)]
117pub enum Error {
118    Extract(crate::ExtractError),
119    NoOutputs,
120    NoSpends,
121    IronwoodFinalize(orchard::pczt::IoFinalizerError),
122    OrchardFinalize(orchard::pczt::IoFinalizerError),
123    SaplingFinalize(sapling::pczt::IoFinalizerError),
124}
125
126impl From<crate::ExtractError> for Error {
127    fn from(e: crate::ExtractError) -> Self {
128        Error::Extract(e)
129    }
130}