-
Notifications
You must be signed in to change notification settings - Fork 106
/
shielded_data.rs
350 lines (315 loc) · 12.8 KB
/
shielded_data.rs
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
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
//! Sapling shielded data for `V4` and `V5` `Transaction`s.
//!
//! Zebra uses a generic shielded data type for `V4` and `V5` transactions.
//! The `value_balance` change is handled using the default zero value.
//! The anchor change is handled using the `AnchorVariant` type trait.
use serde::{de::DeserializeOwned, Serialize};
use crate::{
amount::Amount,
primitives::{
redjubjub::{Binding, Signature},
Groth16Proof,
},
sapling::{
output::OutputPrefixInTransactionV5, spend::SpendPrefixInTransactionV5, tree, Nullifier,
Output, Spend, ValueCommitment,
},
serialization::{AtLeastOne, TrustedPreallocate},
};
use std::{
cmp::{max, Eq, PartialEq},
fmt::Debug,
};
/// Per-Spend Sapling anchors, used in Transaction V4 and the
/// `spends_per_anchor` method.
#[derive(Copy, Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
pub struct PerSpendAnchor {}
/// Shared Sapling anchors, used in Transaction V5.
#[derive(Copy, Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
pub struct SharedAnchor {}
/// This field is not present in this transaction version.
#[derive(Copy, Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
pub struct FieldNotPresent;
impl AnchorVariant for PerSpendAnchor {
type Shared = FieldNotPresent;
type PerSpend = tree::Root;
}
impl AnchorVariant for SharedAnchor {
type Shared = tree::Root;
type PerSpend = FieldNotPresent;
}
/// A type trait to handle structural differences between V4 and V5 Sapling
/// Transaction anchors.
///
/// In Transaction V4, anchors are per-Spend. In Transaction V5, there is a
/// single transaction anchor for all Spends in a transaction.
pub trait AnchorVariant {
/// The type of the shared anchor.
type Shared: Clone + Debug + DeserializeOwned + Serialize + Eq + PartialEq;
/// The type of the per-spend anchor.
type PerSpend: Clone + Debug + DeserializeOwned + Serialize + Eq + PartialEq;
}
/// A bundle of [`Spend`] and [`Output`] descriptions and signature data.
///
/// Spend and Output descriptions are optional, but Zcash transactions must
/// include a binding signature if and only if there is at least one Spend *or*
/// Output description. This wrapper type bundles at least one Spend or Output
/// description with the required signature data, so that an
/// `Option<ShieldedData>` correctly models the presence or absence of any
/// shielded data.
///
/// # Differences between Transaction Versions
///
/// The Sapling `value_balance` field is optional in `Transaction::V5`, but
/// required in `Transaction::V4`. In both cases, if there is no `ShieldedData`,
/// then the field value must be zero. Therefore, we only need to store
/// `value_balance` when there is some Sapling `ShieldedData`.
///
/// In `Transaction::V4`, each `Spend` has its own anchor. In `Transaction::V5`,
/// there is a single `shared_anchor` for the entire transaction, which is only
/// present when there is at least one spend. These structural differences are
/// modeled using the `AnchorVariant` type trait and `TransferData` enum.
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct ShieldedData<AnchorV>
where
AnchorV: AnchorVariant + Clone,
{
/// The net value of Sapling spend transfers minus output transfers.
///
/// [`ShieldedData`] validates this [value balance consensus
/// rule](https://zips.z.cash/protocol/nu5.pdf#txnencodingandconsensus):
///
/// "If effectiveVersion = 4 and there are no Spend descriptions or Output
/// descriptions, then valueBalanceSapling MUST be 0."
///
/// During deserialization, this rule is checked when there are no spends and
/// no outputs.
///
/// During serialization, this rule is structurally validated by [`ShieldedData`].
/// `value_balance` is a field in [`ShieldedData`], which must have at least
/// one spend or output in its `transfers` field. If [`ShieldedData`] is `None`
/// then there can not possibly be any spends or outputs, and the
/// `value_balance` is always serialized as zero.
pub value_balance: Amount,
/// A bundle of spends and outputs, containing at least one spend or
/// output.
///
/// In V5 transactions, also contains a shared anchor, if there are any
/// spends.
pub transfers: TransferData<AnchorV>,
/// A signature on the transaction hash.
pub binding_sig: Signature<Binding>,
}
/// A bundle of [`Spend`] and [`Output`] descriptions, and a shared anchor.
///
/// This wrapper type bundles at least one Spend or Output description with
/// the required anchor data, so that an `Option<ShieldedData>` (which contains
/// this type) correctly models the presence or absence of any spends and
/// shielded data, across both V4 and V5 transactions.
///
/// Specifically, TransferData ensures that:
/// * there is at least one spend or output, and
/// * the shared anchor is only present when there are spends.
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub enum TransferData<AnchorV>
where
AnchorV: AnchorVariant + Clone,
{
/// A bundle containing at least one spend, and the shared spend anchor.
/// There can also be zero or more outputs.
///
/// In Transaction::V5, if there are any spends, there must also be a shared
/// spend anchor.
SpendsAndMaybeOutputs {
/// The shared anchor for all `Spend`s in this transaction.
///
/// The anchor is the root of the Sapling note commitment tree in a previous
/// block. This root should be in the best chain for a transaction to be
/// mined, and it must be in the relevant chain for a transaction to be
/// valid.
///
/// Some transaction versions have a per-spend anchor, rather than a shared
/// anchor.
///
/// Use the `shared_anchor` method to access this field.
shared_anchor: AnchorV::Shared,
/// At least one spend.
///
/// Use the [`ShieldedData::spends`] method to get an iterator over the
/// [`Spend`]s in this `TransferData`.
spends: AtLeastOne<Spend<AnchorV>>,
/// Maybe some outputs (can be empty).
///
/// Use the [`ShieldedData::outputs`] method to get an iterator over the
/// [`Outputs`]s in this `TransferData`.
maybe_outputs: Vec<Output>,
},
/// A bundle containing at least one output, with no spends and no shared
/// spend anchor.
///
/// In Transaction::V5, if there are no spends, there must not be a shared
/// anchor.
JustOutputs {
/// At least one output.
///
/// Use the [`ShieldedData::outputs`] method to get an iterator over the
/// [`Outputs`]s in this `TransferData`.
outputs: AtLeastOne<Output>,
},
}
impl<AnchorV> ShieldedData<AnchorV>
where
AnchorV: AnchorVariant + Clone,
Spend<PerSpendAnchor>: From<(Spend<AnchorV>, AnchorV::Shared)>,
{
/// Iterate over the [`Spend`]s for this transaction, returning
/// `Spend<PerSpendAnchor>` regardless of the underlying transaction version.
///
/// # Correctness
///
/// Do not use this function for serialization.
pub fn spends_per_anchor(&self) -> impl Iterator<Item = Spend<PerSpendAnchor>> + '_ {
self.transfers.spends_per_anchor()
}
}
impl<AnchorV> ShieldedData<AnchorV>
where
AnchorV: AnchorVariant + Clone,
{
/// Iterate over the [`Spend`]s for this transaction, returning them as
/// their generic type.
///
/// # Correctness
///
/// Use this function for serialization.
pub fn spends(&self) -> impl Iterator<Item = &Spend<AnchorV>> {
self.transfers.spends()
}
/// Iterate over the [`Output`]s for this transaction.
pub fn outputs(&self) -> impl Iterator<Item = &Output> {
self.transfers.outputs()
}
/// Provide the shared anchor for this transaction, if present.
///
/// The shared anchor is only present if:
/// * there is at least one spend, and
/// * this is a `V5` transaction.
pub fn shared_anchor(&self) -> Option<AnchorV::Shared> {
self.transfers.shared_anchor()
}
/// Collect the [`Nullifier`]s for this transaction, if it contains
/// [`Spend`]s.
pub fn nullifiers(&self) -> impl Iterator<Item = &Nullifier> {
self.spends().map(|spend| &spend.nullifier)
}
/// Collect the cm_u's for this transaction, if it contains [`Output`]s.
pub fn note_commitments(&self) -> impl Iterator<Item = &jubjub::Fq> {
self.outputs().map(|output| &output.cm_u)
}
/// Calculate the Spend/Output binding verification key.
///
/// Getting the binding signature validating key from the Spend and Output
/// description value commitments and the balancing value implicitly checks
/// that the balancing value is consistent with the value transfered in the
/// Spend and Output descriptions but also proves that the signer knew the
/// randomness used for the Spend and Output value commitments, which
/// prevents replays of Output descriptions.
///
/// The net value of Spend transfers minus Output transfers in a transaction
/// is called the balancing value, measured in zatoshi as a signed integer
/// v_balance.
///
/// Consistency of v_balance with the value commitments in Spend
/// descriptions and Output descriptions is enforced by the binding
/// signature.
///
/// Instead of generating a key pair at random, we generate it as a function
/// of the value commitments in the Spend descriptions and Output
/// descriptions of the transaction, and the balancing value.
///
/// https://zips.z.cash/protocol/protocol.pdf#saplingbalance
pub fn binding_verification_key(&self) -> redjubjub::VerificationKeyBytes<Binding> {
let cv_old: ValueCommitment = self.spends().map(|spend| spend.cv).sum();
let cv_new: ValueCommitment = self.outputs().map(|output| output.cv).sum();
let cv_balance: ValueCommitment =
ValueCommitment::new(jubjub::Fr::zero(), self.value_balance);
let key_bytes: [u8; 32] = (cv_old - cv_new - cv_balance).into();
key_bytes.into()
}
}
impl<AnchorV> TransferData<AnchorV>
where
AnchorV: AnchorVariant + Clone,
Spend<PerSpendAnchor>: From<(Spend<AnchorV>, AnchorV::Shared)>,
{
/// Iterate over the [`Spend`]s for this transaction, returning
/// `Spend<PerSpendAnchor>` regardless of the underlying transaction version.
///
/// Allows generic operations over V4 and V5 transactions, including:
/// * spend verification, and
/// * non-malleable transaction ID generation.
///
/// # Correctness
///
/// Do not use this function for serialization.
pub fn spends_per_anchor(&self) -> impl Iterator<Item = Spend<PerSpendAnchor>> + '_ {
self.spends().cloned().map(move |spend| {
Spend::<PerSpendAnchor>::from((
spend,
self.shared_anchor()
.expect("shared anchor must be Some if there are any spends"),
))
})
}
}
impl<AnchorV> TransferData<AnchorV>
where
AnchorV: AnchorVariant + Clone,
{
/// Iterate over the [`Spend`]s for this transaction, returning them as
/// their generic type.
pub fn spends(&self) -> impl Iterator<Item = &Spend<AnchorV>> {
use TransferData::*;
let spends = match self {
SpendsAndMaybeOutputs { spends, .. } => Some(spends.iter()),
JustOutputs { .. } => None,
};
// this awkward construction avoids returning a newtype struct or
// type-erased boxed iterator
spends.into_iter().flatten()
}
/// Iterate over the [`Output`]s for this transaction.
pub fn outputs(&self) -> impl Iterator<Item = &Output> {
use TransferData::*;
match self {
SpendsAndMaybeOutputs { maybe_outputs, .. } => maybe_outputs,
JustOutputs { outputs, .. } => outputs.as_vec(),
}
.iter()
}
/// Provide the shared anchor for this transaction, if present.
///
/// The shared anchor is only present if:
/// * there is at least one spend, and
/// * this is a `V5` transaction.
pub fn shared_anchor(&self) -> Option<AnchorV::Shared> {
use TransferData::*;
match self {
SpendsAndMaybeOutputs { shared_anchor, .. } => Some(shared_anchor.clone()),
JustOutputs { .. } => None,
}
}
}
impl TrustedPreallocate for Groth16Proof {
fn max_allocation() -> u64 {
// Each V5 transaction proof array entry must have a corresponding
// spend or output prefix. We use the larger limit, so we don't reject
// any valid large blocks.
//
// TODO: put a separate limit on proofs in spends and outputs
max(
SpendPrefixInTransactionV5::max_allocation(),
OutputPrefixInTransactionV5::max_allocation(),
)
}
}