sapling_crypto/pczt/
io_finalizer.rs

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