orchard/pczt/
tx_extractor.rs1use 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 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 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 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#[derive(Debug)]
109#[non_exhaustive]
110pub enum TxExtractorError {
111 MissingBindingSignatureSigningKey,
113 MissingProof,
115 MissingSpendAuthSig,
117 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#[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 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}