Skip to main content

orchard/pczt/
tx_extractor.rs

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