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 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 .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#[derive(Debug)]
117#[non_exhaustive]
118pub enum TxExtractorError {
119 MissingBindingSignatureSigningKey,
121 MissingProof,
123 MissingSpendAuthSig,
125 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#[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 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}