pczt/sapling.rs
1//! The Sapling fields of a PCZT.
2
3use alloc::collections::BTreeMap;
4use alloc::string::String;
5use alloc::vec::Vec;
6use core::cmp::Ordering;
7
8use getset::Getters;
9use serde::{Deserialize, Serialize};
10use serde_with::serde_as;
11
12use crate::{
13 common::{Global, Zip32Derivation},
14 roles::combiner::{merge_map, merge_optional},
15};
16
17const GROTH_PROOF_SIZE: usize = 48 + 96 + 48;
18
19/// PCZT fields that are specific to producing the transaction's Sapling bundle (if any).
20#[derive(Clone, Debug, Serialize, Deserialize, Getters)]
21pub struct Bundle {
22 #[getset(get = "pub")]
23 pub(crate) spends: Vec<Spend>,
24 #[getset(get = "pub")]
25 pub(crate) outputs: Vec<Output>,
26
27 /// The net value of Sapling spends minus outputs.
28 ///
29 /// This is initialized by the Creator, and updated by the Constructor as spends or
30 /// outputs are added to the PCZT. It enables per-spend and per-output values to be
31 /// redacted from the PCZT after they are no longer necessary.
32 #[getset(get = "pub")]
33 pub(crate) value_sum: i128,
34
35 /// The Sapling anchor for this transaction.
36 ///
37 /// Set by the Creator.
38 #[getset(get = "pub")]
39 pub(crate) anchor: [u8; 32],
40
41 /// The Sapling binding signature signing key.
42 ///
43 /// - This is `None` until it is set by the IO Finalizer.
44 /// - The Transaction Extractor uses this to produce the binding signature.
45 pub(crate) bsk: Option<[u8; 32]>,
46}
47
48/// Information about a Sapling spend within a transaction.
49#[serde_as]
50#[derive(Clone, Debug, Serialize, Deserialize, Getters)]
51pub struct Spend {
52 //
53 // SpendDescription effecting data.
54 //
55 // These are required fields that are part of the final transaction, and are filled in
56 // by the Constructor when adding an output.
57 //
58 #[getset(get = "pub")]
59 pub(crate) cv: [u8; 32],
60 #[getset(get = "pub")]
61 pub(crate) nullifier: [u8; 32],
62 #[getset(get = "pub")]
63 pub(crate) rk: [u8; 32],
64
65 /// The Spend proof.
66 ///
67 /// This is set by the Prover.
68 #[serde_as(as = "Option<[_; GROTH_PROOF_SIZE]>")]
69 pub(crate) zkproof: Option<[u8; GROTH_PROOF_SIZE]>,
70
71 /// The spend authorization signature.
72 ///
73 /// This is set by the Signer.
74 #[serde_as(as = "Option<[_; 64]>")]
75 pub(crate) spend_auth_sig: Option<[u8; 64]>,
76
77 /// The [raw encoding] of the Sapling payment address that received the note being spent.
78 ///
79 /// - This is set by the Constructor.
80 /// - This is required by the Prover.
81 ///
82 /// [raw encoding]: https://zips.z.cash/protocol/protocol.pdf#saplingpaymentaddrencoding
83 #[serde_as(as = "Option<[_; 43]>")]
84 pub(crate) recipient: Option<[u8; 43]>,
85
86 /// The value of the input being spent.
87 ///
88 /// This may be used by Signers to verify that the value matches `cv`, and to confirm
89 /// the values and change involved in the transaction.
90 ///
91 /// This exposes the input value to all participants. For Signers who don't need this
92 /// information, or after signatures have been applied, this can be redacted.
93 pub(crate) value: Option<u64>,
94
95 /// The note commitment randomness.
96 ///
97 /// - This is set by the Constructor. It MUST NOT be set if the note has an `rseed`
98 /// (i.e. was created after [ZIP 212] activation).
99 /// - The Prover requires either this or `rseed`.
100 ///
101 /// [ZIP 212]: https://zips.z.cash/zip-0212
102 pub(crate) rcm: Option<[u8; 32]>,
103
104 /// The seed randomness for the note being spent.
105 ///
106 /// - This is set by the Constructor. It MUST NOT be set if the note has no `rseed`
107 /// (i.e. was created before [ZIP 212] activation).
108 /// - The Prover requires either this or `rcm`.
109 ///
110 /// [ZIP 212]: https://zips.z.cash/zip-0212
111 pub(crate) rseed: Option<[u8; 32]>,
112
113 /// The value commitment randomness.
114 ///
115 /// - This is set by the Constructor.
116 /// - The IO Finalizer compresses it into `bsk`.
117 /// - This is required by the Prover.
118 /// - This may be used by Signers to verify that the value correctly matches `cv`.
119 ///
120 /// This opens `cv` for all participants. For Signers who don't need this information,
121 /// or after proofs / signatures have been applied, this can be redacted.
122 pub(crate) rcv: Option<[u8; 32]>,
123
124 /// The proof generation key `(ak, nsk)` corresponding to the recipient that received
125 /// the note being spent.
126 ///
127 /// - This is set by the Updater.
128 /// - This is required by the Prover.
129 pub(crate) proof_generation_key: Option<([u8; 32], [u8; 32])>,
130
131 /// A witness from the note to the bundle's anchor.
132 ///
133 /// - This is set by the Updater.
134 /// - This is required by the Prover.
135 pub(crate) witness: Option<(u32, [[u8; 32]; 32])>,
136
137 /// The spend authorization randomizer.
138 ///
139 /// - This is chosen by the Constructor.
140 /// - This is required by the Signer for creating `spend_auth_sig`, and may be used to
141 /// validate `rk`.
142 /// - After `zkproof` / `spend_auth_sig` has been set, this can be redacted.
143 pub(crate) alpha: Option<[u8; 32]>,
144
145 /// The ZIP 32 derivation path at which the spending key can be found for the note
146 /// being spent.
147 pub(crate) zip32_derivation: Option<Zip32Derivation>,
148
149 /// The spend authorizing key for this spent note, if it is a dummy note.
150 ///
151 /// - This is chosen by the Constructor.
152 /// - This is required by the IO Finalizer, and is cleared by it once used.
153 /// - Signers MUST reject PCZTs that contain `dummy_ask` values.
154 pub(crate) dummy_ask: Option<[u8; 32]>,
155
156 /// Proprietary fields related to the note being spent.
157 #[getset(get = "pub")]
158 pub(crate) proprietary: BTreeMap<String, Vec<u8>>,
159}
160
161/// Information about a Sapling output within a transaction.
162#[serde_as]
163#[derive(Clone, Debug, Serialize, Deserialize, Getters)]
164pub struct Output {
165 //
166 // OutputDescription effecting data.
167 //
168 // These are required fields that are part of the final transaction, and are filled in
169 // by the Constructor when adding an output.
170 //
171 #[getset(get = "pub")]
172 pub(crate) cv: [u8; 32],
173 #[getset(get = "pub")]
174 pub(crate) cmu: [u8; 32],
175 #[getset(get = "pub")]
176 pub(crate) ephemeral_key: [u8; 32],
177 /// The encrypted note plaintext for the output.
178 ///
179 /// Encoded as a `Vec<u8>` because its length depends on the transaction version.
180 ///
181 /// Once we have [memo bundles], we will be able to set memos independently of
182 /// Outputs. For now, the Constructor sets both at the same time.
183 ///
184 /// [memo bundles]: https://zips.z.cash/zip-0231
185 #[getset(get = "pub")]
186 pub(crate) enc_ciphertext: Vec<u8>,
187 /// The encrypted note plaintext for the output.
188 ///
189 /// Encoded as a `Vec<u8>` because its length depends on the transaction version.
190 #[getset(get = "pub")]
191 pub(crate) out_ciphertext: Vec<u8>,
192
193 /// The Output proof.
194 ///
195 /// This is set by the Prover.
196 #[serde_as(as = "Option<[_; GROTH_PROOF_SIZE]>")]
197 pub(crate) zkproof: Option<[u8; GROTH_PROOF_SIZE]>,
198
199 /// The [raw encoding] of the Sapling payment address that will receive the output.
200 ///
201 /// - This is set by the Constructor.
202 /// - This is required by the Prover.
203 ///
204 /// [raw encoding]: https://zips.z.cash/protocol/protocol.pdf#saplingpaymentaddrencoding
205 #[serde_as(as = "Option<[_; 43]>")]
206 #[getset(get = "pub")]
207 pub(crate) recipient: Option<[u8; 43]>,
208
209 /// The value of the output.
210 ///
211 /// This may be used by Signers to verify that the value matches `cv`, and to confirm
212 /// the values and change involved in the transaction.
213 ///
214 /// This exposes the output value to all participants. For Signers who don't need this
215 /// information, or after signatures have been applied, this can be redacted.
216 #[getset(get = "pub")]
217 pub(crate) value: Option<u64>,
218
219 /// The seed randomness for the output.
220 ///
221 /// - This is set by the Constructor.
222 /// - This is required by the Prover, instead of disclosing `shared_secret` to them.
223 #[getset(get = "pub")]
224 pub(crate) rseed: Option<[u8; 32]>,
225
226 /// The value commitment randomness.
227 ///
228 /// - This is set by the Constructor.
229 /// - The IO Finalizer compresses it into `bsk`.
230 /// - This is required by the Prover.
231 /// - This may be used by Signers to verify that the value correctly matches `cv`.
232 ///
233 /// This opens `cv` for all participants. For Signers who don't need this information,
234 /// or after proofs / signatures have been applied, this can be redacted.
235 pub(crate) rcv: Option<[u8; 32]>,
236
237 /// The `ock` value used to encrypt `out_ciphertext`.
238 ///
239 /// This enables Signers to verify that `out_ciphertext` is correctly encrypted.
240 ///
241 /// This may be `None` if the Constructor added the output using an OVK policy of
242 /// "None", to make the output unrecoverable from the chain by the sender.
243 pub(crate) ock: Option<[u8; 32]>,
244
245 /// The ZIP 32 derivation path at which the spending key can be found for the output.
246 pub(crate) zip32_derivation: Option<Zip32Derivation>,
247
248 /// The user-facing address to which this output is being sent, if any.
249 ///
250 /// - This is set by an Updater.
251 /// - Signers must parse this address (if present) and confirm that it contains
252 /// `recipient` (either directly, or e.g. as a receiver within a Unified Address).
253 #[getset(get = "pub")]
254 pub(crate) user_address: Option<String>,
255
256 /// Proprietary fields related to the note being spent.
257 #[getset(get = "pub")]
258 pub(crate) proprietary: BTreeMap<String, Vec<u8>>,
259}
260
261impl Bundle {
262 /// Merges this bundle with another.
263 ///
264 /// Returns `None` if the bundles have conflicting data.
265 pub(crate) fn merge(
266 mut self,
267 other: Self,
268 self_global: &Global,
269 other_global: &Global,
270 ) -> Option<Self> {
271 // Destructure `other` to ensure we handle everything.
272 let Self {
273 mut spends,
274 mut outputs,
275 value_sum,
276 anchor,
277 bsk,
278 } = other;
279
280 // If `bsk` is set on either bundle, the IO Finalizer has run, which means we
281 // cannot have differing numbers of spends or outputs, and the value balances must
282 // match.
283 match (self.bsk.as_mut(), bsk) {
284 (Some(lhs), Some(rhs)) if lhs != &rhs => return None,
285 (Some(_), _) | (_, Some(_))
286 if self.spends.len() != spends.len()
287 || self.outputs.len() != outputs.len()
288 || self.value_sum != value_sum =>
289 {
290 return None
291 }
292 // IO Finalizer has run, and neither bundle has excess spends or outputs.
293 (Some(_), _) | (_, Some(_)) => (),
294 // IO Finalizer has not run on either bundle.
295 (None, None) => {
296 let (spends_cmp_other, outputs_cmp_other) = match (
297 self.spends.len().cmp(&spends.len()),
298 self.outputs.len().cmp(&outputs.len()),
299 ) {
300 // These cases require us to recalculate the value sum, which we can't
301 // do without a parsed bundle.
302 (Ordering::Less, Ordering::Greater) | (Ordering::Greater, Ordering::Less) => {
303 return None
304 }
305 // These cases mean that at least one of the two value sums is correct
306 // and we can use it directly.
307 (spends, outputs) => (spends, outputs),
308 };
309
310 match (
311 self_global.shielded_modifiable(),
312 other_global.shielded_modifiable(),
313 spends_cmp_other,
314 ) {
315 // Fail if the merge would add spends to a non-modifiable bundle.
316 (false, _, Ordering::Less) | (_, false, Ordering::Greater) => return None,
317 // If the other bundle has more spends than us, move them over; these cannot
318 // conflict by construction.
319 (true, _, Ordering::Less) => {
320 self.spends.extend(spends.drain(self.spends.len()..))
321 }
322 // Do nothing otherwise.
323 (_, _, Ordering::Equal) | (_, true, Ordering::Greater) => (),
324 }
325
326 match (
327 self_global.shielded_modifiable(),
328 other_global.shielded_modifiable(),
329 outputs_cmp_other,
330 ) {
331 // Fail if the merge would add outputs to a non-modifiable bundle.
332 (false, _, Ordering::Less) | (_, false, Ordering::Greater) => return None,
333 // If the other bundle has more outputs than us, move them over; these cannot
334 // conflict by construction.
335 (true, _, Ordering::Less) => {
336 self.outputs.extend(outputs.drain(self.outputs.len()..))
337 }
338 // Do nothing otherwise.
339 (_, _, Ordering::Equal) | (_, true, Ordering::Greater) => (),
340 }
341
342 if matches!(spends_cmp_other, Ordering::Less)
343 || matches!(outputs_cmp_other, Ordering::Less)
344 {
345 // We check below that the overlapping spends and outputs match.
346 // Assuming here that they will, we take the other bundle's value sum.
347 self.value_sum = value_sum;
348 }
349 }
350 }
351
352 if self.anchor != anchor {
353 return None;
354 }
355
356 // Leverage the early-exit behaviour of zip to confirm that the remaining data in
357 // the other bundle matches this one.
358 for (lhs, rhs) in self.spends.iter_mut().zip(spends.into_iter()) {
359 // Destructure `rhs` to ensure we handle everything.
360 let Spend {
361 cv,
362 nullifier,
363 rk,
364 zkproof,
365 spend_auth_sig,
366 recipient,
367 value,
368 rcm,
369 rseed,
370 rcv,
371 proof_generation_key,
372 witness,
373 alpha,
374 zip32_derivation,
375 dummy_ask,
376 proprietary,
377 } = rhs;
378
379 if lhs.cv != cv || lhs.nullifier != nullifier || lhs.rk != rk {
380 return None;
381 }
382
383 if !(merge_optional(&mut lhs.zkproof, zkproof)
384 && merge_optional(&mut lhs.spend_auth_sig, spend_auth_sig)
385 && merge_optional(&mut lhs.recipient, recipient)
386 && merge_optional(&mut lhs.value, value)
387 && merge_optional(&mut lhs.rcm, rcm)
388 && merge_optional(&mut lhs.rseed, rseed)
389 && merge_optional(&mut lhs.rcv, rcv)
390 && merge_optional(&mut lhs.proof_generation_key, proof_generation_key)
391 && merge_optional(&mut lhs.witness, witness)
392 && merge_optional(&mut lhs.alpha, alpha)
393 && merge_optional(&mut lhs.zip32_derivation, zip32_derivation)
394 && merge_optional(&mut lhs.dummy_ask, dummy_ask)
395 && merge_map(&mut lhs.proprietary, proprietary))
396 {
397 return None;
398 }
399 }
400
401 for (lhs, rhs) in self.outputs.iter_mut().zip(outputs.into_iter()) {
402 // Destructure `rhs` to ensure we handle everything.
403 let Output {
404 cv,
405 cmu,
406 ephemeral_key,
407 enc_ciphertext,
408 out_ciphertext,
409 zkproof,
410 recipient,
411 value,
412 rseed,
413 rcv,
414 ock,
415 zip32_derivation,
416 user_address,
417 proprietary,
418 } = rhs;
419
420 if lhs.cv != cv
421 || lhs.cmu != cmu
422 || lhs.ephemeral_key != ephemeral_key
423 || lhs.enc_ciphertext != enc_ciphertext
424 || lhs.out_ciphertext != out_ciphertext
425 {
426 return None;
427 }
428
429 if !(merge_optional(&mut lhs.zkproof, zkproof)
430 && merge_optional(&mut lhs.recipient, recipient)
431 && merge_optional(&mut lhs.value, value)
432 && merge_optional(&mut lhs.rseed, rseed)
433 && merge_optional(&mut lhs.rcv, rcv)
434 && merge_optional(&mut lhs.ock, ock)
435 && merge_optional(&mut lhs.zip32_derivation, zip32_derivation)
436 && merge_optional(&mut lhs.user_address, user_address)
437 && merge_map(&mut lhs.proprietary, proprietary))
438 {
439 return None;
440 }
441 }
442
443 Some(self)
444 }
445}
446
447#[cfg(feature = "sapling")]
448impl Bundle {
449 pub(crate) fn into_parsed(self) -> Result<sapling::pczt::Bundle, sapling::pczt::ParseError> {
450 let spends = self
451 .spends
452 .into_iter()
453 .map(|spend| {
454 sapling::pczt::Spend::parse(
455 spend.cv,
456 spend.nullifier,
457 spend.rk,
458 spend.zkproof,
459 spend.spend_auth_sig,
460 spend.recipient,
461 spend.value,
462 spend.rcm,
463 spend.rseed,
464 spend.rcv,
465 spend.proof_generation_key,
466 spend.witness,
467 spend.alpha,
468 spend
469 .zip32_derivation
470 .map(|z| {
471 sapling::pczt::Zip32Derivation::parse(
472 z.seed_fingerprint,
473 z.derivation_path,
474 )
475 })
476 .transpose()?,
477 spend.dummy_ask,
478 spend.proprietary,
479 )
480 })
481 .collect::<Result<_, _>>()?;
482
483 let outputs = self
484 .outputs
485 .into_iter()
486 .map(|output| {
487 sapling::pczt::Output::parse(
488 output.cv,
489 output.cmu,
490 output.ephemeral_key,
491 output.enc_ciphertext,
492 output.out_ciphertext,
493 output.zkproof,
494 output.recipient,
495 output.value,
496 output.rseed,
497 output.rcv,
498 output.ock,
499 output
500 .zip32_derivation
501 .map(|z| {
502 sapling::pczt::Zip32Derivation::parse(
503 z.seed_fingerprint,
504 z.derivation_path,
505 )
506 })
507 .transpose()?,
508 output.user_address,
509 output.proprietary,
510 )
511 })
512 .collect::<Result<_, _>>()?;
513
514 sapling::pczt::Bundle::parse(spends, outputs, self.value_sum, self.anchor, self.bsk)
515 }
516
517 pub(crate) fn serialize_from(bundle: sapling::pczt::Bundle) -> Self {
518 let spends = bundle
519 .spends()
520 .iter()
521 .map(|spend| {
522 let (rcm, rseed) = match spend.rseed() {
523 Some(sapling::Rseed::BeforeZip212(rcm)) => (Some(rcm.to_bytes()), None),
524 Some(sapling::Rseed::AfterZip212(rseed)) => (None, Some(*rseed)),
525 None => (None, None),
526 };
527
528 Spend {
529 cv: spend.cv().to_bytes(),
530 nullifier: spend.nullifier().0,
531 rk: (*spend.rk()).into(),
532 zkproof: *spend.zkproof(),
533 spend_auth_sig: spend.spend_auth_sig().map(|s| s.into()),
534 recipient: spend.recipient().map(|recipient| recipient.to_bytes()),
535 value: spend.value().map(|value| value.inner()),
536 rcm,
537 rseed,
538 rcv: spend.rcv().as_ref().map(|rcv| rcv.inner().to_bytes()),
539 proof_generation_key: spend
540 .proof_generation_key()
541 .as_ref()
542 .map(|key| (key.ak.to_bytes(), key.nsk.to_bytes())),
543 witness: spend.witness().as_ref().map(|witness| {
544 (
545 u32::try_from(u64::from(witness.position()))
546 .expect("Sapling positions fit in u32"),
547 witness
548 .path_elems()
549 .iter()
550 .map(|node| node.to_bytes())
551 .collect::<Vec<_>>()[..]
552 .try_into()
553 .expect("path is length 32"),
554 )
555 }),
556 alpha: spend.alpha().map(|alpha| alpha.to_bytes()),
557 zip32_derivation: spend.zip32_derivation().as_ref().map(|z| Zip32Derivation {
558 seed_fingerprint: *z.seed_fingerprint(),
559 derivation_path: z.derivation_path().iter().map(|i| i.index()).collect(),
560 }),
561 dummy_ask: spend
562 .dummy_ask()
563 .as_ref()
564 .map(|dummy_ask| dummy_ask.to_bytes()),
565 proprietary: spend.proprietary().clone(),
566 }
567 })
568 .collect();
569
570 let outputs = bundle
571 .outputs()
572 .iter()
573 .map(|output| Output {
574 cv: output.cv().to_bytes(),
575 cmu: output.cmu().to_bytes(),
576 ephemeral_key: output.ephemeral_key().0,
577 enc_ciphertext: output.enc_ciphertext().to_vec(),
578 out_ciphertext: output.out_ciphertext().to_vec(),
579 zkproof: *output.zkproof(),
580 recipient: output.recipient().map(|recipient| recipient.to_bytes()),
581 value: output.value().map(|value| value.inner()),
582 rseed: *output.rseed(),
583 rcv: output.rcv().as_ref().map(|rcv| rcv.inner().to_bytes()),
584 ock: output.ock().as_ref().map(|ock| ock.0),
585 zip32_derivation: output.zip32_derivation().as_ref().map(|z| Zip32Derivation {
586 seed_fingerprint: *z.seed_fingerprint(),
587 derivation_path: z.derivation_path().iter().map(|i| i.index()).collect(),
588 }),
589 user_address: output.user_address().clone(),
590 proprietary: output.proprietary().clone(),
591 })
592 .collect();
593
594 Self {
595 spends,
596 outputs,
597 value_sum: bundle.value_sum().to_raw(),
598 anchor: bundle.anchor().to_bytes(),
599 bsk: bundle.bsk().map(|bsk| bsk.into()),
600 }
601 }
602}