1use rand::{CryptoRng, RngCore};
2
3use crate::{
4 bundle::{
5 Authorization, Authorized, EffectsOnly, GrothProofBytes, OutputDescription,
6 SpendDescription,
7 },
8 Bundle,
9};
10
11use super::{Output, Spend};
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(()), |_| 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 |spend| spend.zkproof.ok_or(TxExtractorError::MissingProof),
35 |spend| {
36 spend
37 .spend_auth_sig
38 .ok_or(TxExtractorError::MissingSpendAuthSig)
39 },
40 |output| output.zkproof.ok_or(TxExtractorError::MissingProof),
41 |bundle| {
42 Ok(Unbound {
43 bsk: bundle
44 .bsk
45 .ok_or(TxExtractorError::MissingBindingSignatureSigningKey)?,
46 })
47 },
48 )
49 }
50
51 fn to_tx_data<A, V, E, F, G, H, I>(
52 &self,
53 spend_proof: F,
54 spend_auth: G,
55 output_proof: H,
56 bundle_auth: I,
57 ) -> Result<Option<crate::Bundle<A, V>>, E>
58 where
59 A: Authorization,
60 E: From<TxExtractorError>,
61 F: Fn(&Spend) -> Result<<A as Authorization>::SpendProof, E>,
62 G: Fn(&Spend) -> Result<<A as Authorization>::AuthSig, E>,
63 H: Fn(&Output) -> Result<<A as Authorization>::OutputProof, E>,
64 I: FnOnce(&Self) -> Result<A, E>,
65 V: TryFrom<i64>,
66 {
67 let spends = self
68 .spends
69 .iter()
70 .map(|spend| {
71 Ok(SpendDescription::from_parts(
72 spend.cv.clone(),
73 self.anchor.inner(),
74 spend.nullifier,
75 spend.rk,
76 spend_proof(spend)?,
77 spend_auth(spend)?,
78 ))
79 })
80 .collect::<Result<_, E>>()?;
81
82 let outputs = self
83 .outputs
84 .iter()
85 .map(|output| {
86 Ok(OutputDescription::from_parts(
87 output.cv.clone(),
88 output.cmu,
89 output.ephemeral_key.clone(),
90 output.enc_ciphertext,
91 output.out_ciphertext,
92 output_proof(output)?,
93 ))
94 })
95 .collect::<Result<_, E>>()?;
96
97 let value_balance = i64::try_from(self.value_sum)
98 .ok()
99 .and_then(|v| v.try_into().ok())
100 .ok_or(TxExtractorError::ValueSumOutOfRange)?;
101
102 let authorization = bundle_auth(self)?;
103
104 Ok(Bundle::from_parts(
105 spends,
106 outputs,
107 value_balance,
108 authorization,
109 ))
110 }
111}
112
113#[derive(Debug)]
115pub enum TxExtractorError {
116 MissingBindingSignatureSigningKey,
118 MissingProof,
120 MissingSpendAuthSig,
122 ValueSumOutOfRange,
124}
125
126#[derive(Debug)]
128pub struct Unbound {
129 bsk: redjubjub::SigningKey<redjubjub::Binding>,
130}
131
132impl Authorization for Unbound {
133 type SpendProof = GrothProofBytes;
134 type OutputProof = GrothProofBytes;
135 type AuthSig = redjubjub::Signature<redjubjub::SpendAuth>;
136}
137
138impl<V> crate::Bundle<Unbound, V> {
139 pub fn apply_binding_signature<R: RngCore + CryptoRng>(
143 self,
144 sighash: [u8; 32],
145 rng: R,
146 ) -> Option<crate::Bundle<Authorized, V>> {
147 if self
148 .shielded_spends()
149 .iter()
150 .all(|spend| spend.rk().verify(&sighash, spend.spend_auth_sig()).is_ok())
151 {
152 Some(self.map_authorization(
153 &mut (),
154 |_, p| p,
155 |_, p| p,
156 |_, s| s,
157 |_, Unbound { bsk }| Authorized {
158 binding_sig: bsk.sign(rng, &sighash),
159 },
160 ))
161 } else {
162 None
163 }
164 }
165}