sapling_crypto/pczt/
io_finalizer.rs

1use alloc::vec::Vec;
2use rand::{CryptoRng, RngCore};
3
4use crate::value::{CommitmentSum, TrapdoorSum};
5
6use super::SignerError;
7
8impl super::Bundle {
9    /// Finalizes the IO for this bundle.
10    pub fn finalize_io<R: RngCore + CryptoRng>(
11        &mut self,
12        sighash: [u8; 32],
13        mut rng: R,
14    ) -> Result<(), IoFinalizerError> {
15        // Compute the transaction binding signing key.
16        let bsk = {
17            let spend_rcvs = self
18                .spends
19                .iter()
20                .map(|spend| {
21                    spend
22                        .rcv
23                        .as_ref()
24                        .ok_or(IoFinalizerError::MissingValueCommitTrapdoor)
25                })
26                .collect::<Result<Vec<_>, _>>()?;
27
28            let output_rcvs = self
29                .outputs
30                .iter()
31                .map(|output| {
32                    output
33                        .rcv
34                        .as_ref()
35                        .ok_or(IoFinalizerError::MissingValueCommitTrapdoor)
36                })
37                .collect::<Result<Vec<_>, _>>()?;
38
39            let spends: TrapdoorSum = spend_rcvs.into_iter().sum();
40            let outputs: TrapdoorSum = output_rcvs.into_iter().sum();
41            (spends - outputs).into_bsk()
42        };
43
44        // Verify that bsk and bvk are consistent.
45        let bvk = {
46            let spends = self
47                .spends
48                .iter()
49                .map(|spend| spend.cv())
50                .sum::<CommitmentSum>();
51            let outputs = self
52                .outputs
53                .iter()
54                .map(|output| output.cv())
55                .sum::<CommitmentSum>();
56            (spends - outputs).into_bvk(
57                i64::try_from(self.value_sum).map_err(|_| IoFinalizerError::InvalidValueSum)?,
58            )
59        };
60        if redjubjub::VerificationKey::from(&bsk) != bvk {
61            return Err(IoFinalizerError::ValueCommitMismatch);
62        }
63        self.bsk = Some(bsk);
64
65        // Add signatures to dummy spends.
66        for spend in self.spends.iter_mut() {
67            // The `Option::take` ensures we don't have any spend authorizing keys in the
68            // PCZT after the IO Finalizer has run.
69            if let Some(ask) = spend.dummy_ask.take() {
70                spend
71                    .sign(sighash, &ask, &mut rng)
72                    .map_err(IoFinalizerError::DummySignature)?;
73            }
74        }
75
76        Ok(())
77    }
78}
79
80/// Errors that can occur while finalizing the I/O for a PCZT bundle.
81#[derive(Debug)]
82pub enum IoFinalizerError {
83    /// An error occurred while signing a dummy spend.
84    DummySignature(SignerError),
85    /// The `value_sum` is too large for the `value_balance` field.
86    InvalidValueSum,
87    /// The IO Finalizer role requires all `rcv` fields to be set.
88    MissingValueCommitTrapdoor,
89    /// The `cv_net`, `rcv`, and `value_sum` values within the Orchard bundle are
90    /// inconsistent.
91    ValueCommitMismatch,
92}