1use core::fmt;
2
3use rand::{CryptoRng, Rng};
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 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 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 .clone()
48 .ok_or(TxExtractorError::MissingBindingSignatureSigningKey)?,
49 })
50 },
51 )
52 }
53
54 fn to_tx_data<A, V, E, F, G, H, I>(
55 &self,
56 spend_proof: F,
57 spend_auth: G,
58 output_proof: H,
59 bundle_auth: I,
60 ) -> Result<Option<crate::Bundle<A, V>>, E>
61 where
62 A: Authorization,
63 E: From<TxExtractorError>,
64 F: Fn(&Spend) -> Result<<A as Authorization>::SpendProof, E>,
65 G: Fn(&Spend) -> Result<<A as Authorization>::AuthSig, E>,
66 H: Fn(&Output) -> Result<<A as Authorization>::OutputProof, E>,
67 I: FnOnce(&Self) -> Result<A, E>,
68 V: TryFrom<i64>,
69 {
70 let spends = self
71 .spends
72 .iter()
73 .map(|spend| {
74 Ok(SpendDescription::from_parts(
75 spend.cv.clone(),
76 self.anchor.inner(),
77 spend.nullifier,
78 spend.rk,
79 spend_proof(spend)?,
80 spend_auth(spend)?,
81 ))
82 })
83 .collect::<Result<_, E>>()?;
84
85 let outputs = self
86 .outputs
87 .iter()
88 .map(|output| {
89 Ok(OutputDescription::from_parts(
90 output.cv.clone(),
91 output.cmu,
92 output.ephemeral_key.clone(),
93 output.enc_ciphertext,
94 output.out_ciphertext,
95 output_proof(output)?,
96 ))
97 })
98 .collect::<Result<_, E>>()?;
99
100 let value_balance = i64::try_from(self.value_sum)
101 .ok()
102 .and_then(|v| v.try_into().ok())
103 .ok_or(TxExtractorError::ValueSumOutOfRange)?;
104
105 let authorization = bundle_auth(self)?;
106
107 Ok(Bundle::from_parts(
108 spends,
109 outputs,
110 value_balance,
111 authorization,
112 ))
113 }
114}
115
116#[derive(Debug)]
118#[non_exhaustive]
119pub enum TxExtractorError {
120 MissingBindingSignatureSigningKey,
122 MissingProof,
124 MissingSpendAuthSig,
126 ValueSumOutOfRange,
128}
129
130impl fmt::Display for TxExtractorError {
131 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
132 match self {
133 TxExtractorError::MissingBindingSignatureSigningKey => {
134 write!(f, "`bsk` must be set for the Transaction Extractor role")
135 }
136 TxExtractorError::MissingProof => write!(
137 f,
138 "`zkproof` fields must all be set for the Transaction Extractor role"
139 ),
140 TxExtractorError::MissingSpendAuthSig => write!(
141 f,
142 "`spend_auth_sig` fields must all be set for the Transaction Extractor role"
143 ),
144 TxExtractorError::ValueSumOutOfRange => {
145 write!(f, "value sum does not fit into a `valueBalance`")
146 }
147 }
148 }
149}
150
151#[cfg(feature = "std")]
152impl std::error::Error for TxExtractorError {}
153
154#[derive(Debug)]
156pub struct Unbound {
157 bsk: redjubjub::SigningKey<redjubjub::Binding>,
158}
159
160impl Authorization for Unbound {
161 type SpendProof = GrothProofBytes;
162 type OutputProof = GrothProofBytes;
163 type AuthSig = redjubjub::Signature<redjubjub::SpendAuth>;
164}
165
166impl<V> crate::Bundle<Unbound, V> {
167 pub fn apply_binding_signature<R: Rng + CryptoRng>(
171 self,
172 sighash: [u8; 32],
173 rng: R,
174 ) -> Option<crate::Bundle<Authorized, V>> {
175 if self
176 .shielded_spends()
177 .iter()
178 .all(|spend| spend.rk().verify(&sighash, spend.spend_auth_sig()).is_ok())
179 {
180 Some(self.map_authorization(
181 &mut (),
182 |_, p| p,
183 |_, p| p,
184 |_, s| s,
185 |_, Unbound { bsk }| Authorized {
186 binding_sig: bsk.sign(rng, &sighash),
187 },
188 ))
189 } else {
190 None
191 }
192 }
193}