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