sapling_crypto/pczt/
tx_extractor.rs

1use core::fmt;
2
3use rand::{CryptoRng, RngCore};
4
5use crate::{
6    bundle::{
7        Authorization, Authorized, EffectsOnly, GrothProofBytes, OutputDescription,
8        SpendDescription,
9    },
10    Bundle,
11};
12
13use super::{Output, Spend};
14
15impl super::Bundle {
16    /// Extracts the effects of this PCZT bundle as a [regular `Bundle`].
17    ///
18    /// This is used by the Signer role to produce the transaction sighash.
19    ///
20    /// [regular `Bundle`]: crate::Bundle
21    pub fn extract_effects<V: TryFrom<i64>>(
22        &self,
23    ) -> Result<Option<crate::Bundle<EffectsOnly, V>>, TxExtractorError> {
24        self.to_tx_data(|_| Ok(()), |_| Ok(()), |_| Ok(()), |_| Ok(EffectsOnly))
25    }
26
27    /// Extracts a fully authorized [regular `Bundle`] from this PCZT bundle.
28    ///
29    /// This is used by the Transaction Extractor role to produce the final transaction.
30    ///
31    /// [regular `Bundle`]: crate::Bundle
32    pub fn extract<V: TryFrom<i64>>(
33        self,
34    ) -> Result<Option<crate::Bundle<Unbound, V>>, TxExtractorError> {
35        self.to_tx_data(
36            |spend| spend.zkproof.ok_or(TxExtractorError::MissingProof),
37            |spend| {
38                spend
39                    .spend_auth_sig
40                    .ok_or(TxExtractorError::MissingSpendAuthSig)
41            },
42            |output| output.zkproof.ok_or(TxExtractorError::MissingProof),
43            |bundle| {
44                Ok(Unbound {
45                    bsk: bundle
46                        .bsk
47                        .ok_or(TxExtractorError::MissingBindingSignatureSigningKey)?,
48                })
49            },
50        )
51    }
52
53    fn to_tx_data<A, V, E, F, G, H, I>(
54        &self,
55        spend_proof: F,
56        spend_auth: G,
57        output_proof: H,
58        bundle_auth: I,
59    ) -> Result<Option<crate::Bundle<A, V>>, E>
60    where
61        A: Authorization,
62        E: From<TxExtractorError>,
63        F: Fn(&Spend) -> Result<<A as Authorization>::SpendProof, E>,
64        G: Fn(&Spend) -> Result<<A as Authorization>::AuthSig, E>,
65        H: Fn(&Output) -> Result<<A as Authorization>::OutputProof, E>,
66        I: FnOnce(&Self) -> Result<A, E>,
67        V: TryFrom<i64>,
68    {
69        let spends = self
70            .spends
71            .iter()
72            .map(|spend| {
73                Ok(SpendDescription::from_parts(
74                    spend.cv.clone(),
75                    self.anchor.inner(),
76                    spend.nullifier,
77                    spend.rk,
78                    spend_proof(spend)?,
79                    spend_auth(spend)?,
80                ))
81            })
82            .collect::<Result<_, E>>()?;
83
84        let outputs = self
85            .outputs
86            .iter()
87            .map(|output| {
88                Ok(OutputDescription::from_parts(
89                    output.cv.clone(),
90                    output.cmu,
91                    output.ephemeral_key.clone(),
92                    output.enc_ciphertext,
93                    output.out_ciphertext,
94                    output_proof(output)?,
95                ))
96            })
97            .collect::<Result<_, E>>()?;
98
99        let value_balance = i64::try_from(self.value_sum)
100            .ok()
101            .and_then(|v| v.try_into().ok())
102            .ok_or(TxExtractorError::ValueSumOutOfRange)?;
103
104        let authorization = bundle_auth(self)?;
105
106        Ok(Bundle::from_parts(
107            spends,
108            outputs,
109            value_balance,
110            authorization,
111        ))
112    }
113}
114
115/// Errors that can occur while extracting a regular Sapling bundle from a PCZT bundle.
116#[derive(Debug)]
117#[non_exhaustive]
118pub enum TxExtractorError {
119    /// The Transaction Extractor role requires `bsk` to be set.
120    MissingBindingSignatureSigningKey,
121    /// The Transaction Extractor role requires all `zkproof` fields to be set.
122    MissingProof,
123    /// The Transaction Extractor role requires all `spend_auth_sig` fields to be set.
124    MissingSpendAuthSig,
125    /// The value sum does not fit into a `valueBalance`.
126    ValueSumOutOfRange,
127}
128
129impl fmt::Display for TxExtractorError {
130    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
131        match self {
132            TxExtractorError::MissingBindingSignatureSigningKey => {
133                write!(f, "`bsk` must be set for the Transaction Extractor role")
134            }
135            TxExtractorError::MissingProof => write!(
136                f,
137                "`zkproof` fields must all be set for the Transaction Extractor role"
138            ),
139            TxExtractorError::MissingSpendAuthSig => write!(
140                f,
141                "`spend_auth_sig` fields must all be set for the Transaction Extractor role"
142            ),
143            TxExtractorError::ValueSumOutOfRange => {
144                write!(f, "value sum does not fit into a `valueBalance`")
145            }
146        }
147    }
148}
149
150#[cfg(feature = "std")]
151impl std::error::Error for TxExtractorError {}
152
153/// Authorizing data for a bundle of actions that is just missing a binding signature.
154#[derive(Debug)]
155pub struct Unbound {
156    bsk: redjubjub::SigningKey<redjubjub::Binding>,
157}
158
159impl Authorization for Unbound {
160    type SpendProof = GrothProofBytes;
161    type OutputProof = GrothProofBytes;
162    type AuthSig = redjubjub::Signature<redjubjub::SpendAuth>;
163}
164
165impl<V> crate::Bundle<Unbound, V> {
166    /// Verifies the given sighash with every `spend_auth_sig`, and then binds the bundle.
167    ///
168    /// Returns `None` if the given sighash does not validate against every `spend_auth_sig`.
169    pub fn apply_binding_signature<R: RngCore + CryptoRng>(
170        self,
171        sighash: [u8; 32],
172        rng: R,
173    ) -> Option<crate::Bundle<Authorized, V>> {
174        if self
175            .shielded_spends()
176            .iter()
177            .all(|spend| spend.rk().verify(&sighash, spend.spend_auth_sig()).is_ok())
178        {
179            Some(self.map_authorization(
180                &mut (),
181                |_, p| p,
182                |_, p| p,
183                |_, s| s,
184                |_, Unbound { bsk }| Authorized {
185                    binding_sig: bsk.sign(rng, &sighash),
186                },
187            ))
188        } else {
189            None
190        }
191    }
192}