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, SecretKeyBytes, 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, PartialEq, 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: Option<[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<SecretKeyBytes>,
46}
47
48/// The canonical empty Sapling bundle: the form the Sapling bundle of a PCZT takes
49/// when it carries no Sapling data.
50pub(crate) const EMPTY_BUNDLE: Bundle = Bundle {
51 spends: Vec::new(),
52 outputs: Vec::new(),
53 value_sum: 0,
54 anchor: None,
55 bsk: None,
56};
57
58/// The default anchor used as a parse-time placeholder for redacted empty
59/// Sapling bundles.
60pub(crate) const DEFAULT_ANCHOR: [u8; 32] = [0; 32];
61
62/// Information about a Sapling spend within a transaction.
63#[serde_as]
64#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Getters)]
65pub struct Spend {
66 //
67 // SpendDescription effecting data.
68 //
69 // These are required fields that are part of the final transaction, and are filled in
70 // by the Constructor when adding an output.
71 //
72 #[getset(get = "pub")]
73 pub(crate) cv: [u8; 32],
74 #[getset(get = "pub")]
75 pub(crate) nullifier: [u8; 32],
76 #[getset(get = "pub")]
77 pub(crate) rk: [u8; 32],
78
79 /// The Spend proof.
80 ///
81 /// This is set by the Prover.
82 #[serde_as(as = "Option<[_; GROTH_PROOF_SIZE]>")]
83 pub(crate) zkproof: Option<[u8; GROTH_PROOF_SIZE]>,
84
85 /// The spend authorization signature.
86 ///
87 /// This is set by the Signer.
88 #[serde_as(as = "Option<[_; 64]>")]
89 pub(crate) spend_auth_sig: Option<[u8; 64]>,
90
91 /// The [raw encoding] of the Sapling payment address that received the note being spent.
92 ///
93 /// - This is set by the Constructor.
94 /// - This is required by the Prover.
95 ///
96 /// [raw encoding]: https://zips.z.cash/protocol/protocol.pdf#saplingpaymentaddrencoding
97 #[serde_as(as = "Option<[_; 43]>")]
98 pub(crate) recipient: Option<[u8; 43]>,
99
100 /// The value of the input being spent.
101 ///
102 /// This may be used by Signers to verify that the value matches `cv`, and to confirm
103 /// the values and change involved in the transaction.
104 ///
105 /// This exposes the input value to all participants. For Signers who don't need this
106 /// information, or after signatures have been applied, this can be redacted.
107 pub(crate) value: Option<u64>,
108
109 /// The note commitment randomness.
110 ///
111 /// - This is set by the Constructor. It MUST NOT be set if the note has an `rseed`
112 /// (i.e. was created after [ZIP 212] activation).
113 /// - The Prover requires either this or `rseed`.
114 ///
115 /// [ZIP 212]: https://zips.z.cash/zip-0212
116 pub(crate) rcm: Option<[u8; 32]>,
117
118 /// The seed randomness for the note being spent.
119 ///
120 /// - This is set by the Constructor. It MUST NOT be set if the note has no `rseed`
121 /// (i.e. was created before [ZIP 212] activation).
122 /// - The Prover requires either this or `rcm`.
123 ///
124 /// [ZIP 212]: https://zips.z.cash/zip-0212
125 pub(crate) rseed: Option<[u8; 32]>,
126
127 /// The value commitment randomness.
128 ///
129 /// - This is set by the Constructor.
130 /// - The IO Finalizer compresses it into `bsk`.
131 /// - This is required by the Prover.
132 /// - This may be used by Signers to verify that the value correctly matches `cv`.
133 ///
134 /// This opens `cv` for all participants. For Signers who don't need this information,
135 /// or after proofs / signatures have been applied, this can be redacted.
136 pub(crate) rcv: Option<[u8; 32]>,
137
138 /// The proof generation key `(ak, nsk)` corresponding to the recipient that received
139 /// the note being spent.
140 ///
141 /// - This is set by the Updater.
142 /// - This is required by the Prover.
143 pub(crate) proof_generation_key: Option<([u8; 32], [u8; 32])>,
144
145 /// A witness from the note to the bundle's anchor.
146 ///
147 /// - This is set by the Updater.
148 /// - This is required by the Prover.
149 #[getset(get = "pub")]
150 pub(crate) witness: Option<(u32, [[u8; 32]; 32])>,
151
152 /// The spend authorization randomizer.
153 ///
154 /// - This is chosen by the Constructor.
155 /// - This is required by the Signer for creating `spend_auth_sig`, and may be used to
156 /// validate `rk`.
157 /// - After `zkproof` / `spend_auth_sig` has been set, this can be redacted.
158 pub(crate) alpha: Option<[u8; 32]>,
159
160 /// The ZIP 32 derivation path at which the spending key can be found for the note
161 /// being spent.
162 pub(crate) zip32_derivation: Option<Zip32Derivation>,
163
164 /// The spend authorizing key for this spent note, if it is a dummy note.
165 ///
166 /// - This is chosen by the Constructor.
167 /// - This is required by the IO Finalizer, and is cleared by it once used.
168 /// - Signers MUST reject PCZTs that contain `dummy_ask` values.
169 pub(crate) dummy_ask: Option<SecretKeyBytes>,
170
171 /// Proprietary fields related to the note being spent.
172 #[getset(get = "pub")]
173 pub(crate) proprietary: BTreeMap<String, Vec<u8>>,
174}
175
176/// Information about a Sapling output within a transaction.
177#[serde_as]
178#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Getters)]
179pub struct Output {
180 //
181 // OutputDescription effecting data.
182 //
183 // These are required fields that are part of the final transaction, and are filled in
184 // by the Constructor when adding an output.
185 //
186 #[getset(get = "pub")]
187 pub(crate) cv: [u8; 32],
188 #[getset(get = "pub")]
189 pub(crate) cmu: [u8; 32],
190 #[getset(get = "pub")]
191 pub(crate) ephemeral_key: [u8; 32],
192 /// The encrypted note plaintext for the output.
193 ///
194 /// Encoded as a `Vec<u8>` because its length depends on the transaction version.
195 ///
196 /// Once we have [memo bundles], we will be able to set memos independently of
197 /// Outputs. For now, the Constructor sets both at the same time.
198 ///
199 /// [memo bundles]: https://zips.z.cash/zip-0231
200 #[getset(get = "pub")]
201 pub(crate) enc_ciphertext: Vec<u8>,
202 /// The encrypted note plaintext for the output.
203 ///
204 /// Encoded as a `Vec<u8>` because its length depends on the transaction version.
205 #[getset(get = "pub")]
206 pub(crate) out_ciphertext: Vec<u8>,
207
208 /// The Output proof.
209 ///
210 /// This is set by the Prover.
211 #[serde_as(as = "Option<[_; GROTH_PROOF_SIZE]>")]
212 pub(crate) zkproof: Option<[u8; GROTH_PROOF_SIZE]>,
213
214 /// The [raw encoding] of the Sapling payment address that will receive the output.
215 ///
216 /// - This is set by the Constructor.
217 /// - This is required by the Prover.
218 ///
219 /// [raw encoding]: https://zips.z.cash/protocol/protocol.pdf#saplingpaymentaddrencoding
220 #[serde_as(as = "Option<[_; 43]>")]
221 #[getset(get = "pub")]
222 pub(crate) recipient: Option<[u8; 43]>,
223
224 /// The value of the output.
225 ///
226 /// This may be used by Signers to verify that the value matches `cv`, and to confirm
227 /// the values and change involved in the transaction.
228 ///
229 /// This exposes the output value to all participants. For Signers who don't need this
230 /// information, or after signatures have been applied, this can be redacted.
231 #[getset(get = "pub")]
232 pub(crate) value: Option<u64>,
233
234 /// The seed randomness for the output.
235 ///
236 /// - This is set by the Constructor.
237 /// - This is required by the Prover, instead of disclosing `shared_secret` to them.
238 #[getset(get = "pub")]
239 pub(crate) rseed: Option<[u8; 32]>,
240
241 /// The value commitment randomness.
242 ///
243 /// - This is set by the Constructor.
244 /// - The IO Finalizer compresses it into `bsk`.
245 /// - This is required by the Prover.
246 /// - This may be used by Signers to verify that the value correctly matches `cv`.
247 ///
248 /// This opens `cv` for all participants. For Signers who don't need this information,
249 /// or after proofs / signatures have been applied, this can be redacted.
250 pub(crate) rcv: Option<[u8; 32]>,
251
252 /// The `ock` value used to encrypt `out_ciphertext`.
253 ///
254 /// This enables Signers to verify that `out_ciphertext` is correctly encrypted.
255 ///
256 /// This may be `None` if the Constructor added the output using an OVK policy of
257 /// "None", to make the output unrecoverable from the chain by the sender.
258 pub(crate) ock: Option<[u8; 32]>,
259
260 /// The ZIP 32 derivation path at which the spending key can be found for the output.
261 pub(crate) zip32_derivation: Option<Zip32Derivation>,
262
263 /// The user-facing address to which this output is being sent, if any.
264 ///
265 /// - This is set by an Updater.
266 /// - Signers must parse this address (if present) and confirm that it contains
267 /// `recipient` (either directly, or e.g. as a receiver within a Unified Address).
268 #[getset(get = "pub")]
269 pub(crate) user_address: Option<String>,
270
271 /// Proprietary fields related to the note being spent.
272 #[getset(get = "pub")]
273 pub(crate) proprietary: BTreeMap<String, Vec<u8>>,
274}
275
276impl Bundle {
277 /// Merges this bundle with another.
278 ///
279 /// Returns `None` if the bundles have conflicting data.
280 pub(crate) fn merge(
281 mut self,
282 other: Self,
283 self_global: &Global,
284 other_global: &Global,
285 ) -> Option<Self> {
286 // Destructure `other` to ensure we handle everything.
287 let Self {
288 mut spends,
289 mut outputs,
290 value_sum,
291 anchor,
292 bsk,
293 } = other;
294
295 // If `bsk` is set on either bundle, the IO Finalizer has run, which means we
296 // cannot have differing numbers of spends or outputs, and the value balances must
297 // match.
298 match (self.bsk.as_mut(), bsk) {
299 (Some(lhs), Some(rhs)) if lhs != &rhs => return None,
300 (Some(_), _) | (_, Some(_))
301 if self.spends.len() != spends.len()
302 || self.outputs.len() != outputs.len()
303 || self.value_sum != value_sum =>
304 {
305 return None;
306 }
307 // IO Finalizer has run, and neither bundle has excess spends or outputs.
308 (Some(_), _) | (_, Some(_)) => (),
309 // IO Finalizer has not run on either bundle.
310 (None, None) => {
311 let (spends_cmp_other, outputs_cmp_other) = match (
312 self.spends.len().cmp(&spends.len()),
313 self.outputs.len().cmp(&outputs.len()),
314 ) {
315 // These cases require us to recalculate the value sum, which we can't
316 // do without a parsed bundle.
317 (Ordering::Less, Ordering::Greater) | (Ordering::Greater, Ordering::Less) => {
318 return None;
319 }
320 // These cases mean that at least one of the two value sums is correct
321 // and we can use it directly.
322 (spends, outputs) => (spends, outputs),
323 };
324
325 match (
326 self_global.shielded_modifiable(),
327 other_global.shielded_modifiable(),
328 spends_cmp_other,
329 ) {
330 // Fail if the merge would add spends to a non-modifiable bundle.
331 (false, _, Ordering::Less) | (_, false, Ordering::Greater) => return None,
332 // If the other bundle has more spends than us, move them over; these cannot
333 // conflict by construction.
334 (true, _, Ordering::Less) => {
335 self.spends.extend(spends.drain(self.spends.len()..))
336 }
337 // Do nothing otherwise.
338 (_, _, Ordering::Equal) | (_, true, Ordering::Greater) => (),
339 }
340
341 match (
342 self_global.shielded_modifiable(),
343 other_global.shielded_modifiable(),
344 outputs_cmp_other,
345 ) {
346 // Fail if the merge would add outputs to a non-modifiable bundle.
347 (false, _, Ordering::Less) | (_, false, Ordering::Greater) => return None,
348 // If the other bundle has more outputs than us, move them over; these cannot
349 // conflict by construction.
350 (true, _, Ordering::Less) => {
351 self.outputs.extend(outputs.drain(self.outputs.len()..))
352 }
353 // Do nothing otherwise.
354 (_, _, Ordering::Equal) | (_, true, Ordering::Greater) => (),
355 }
356
357 if matches!(spends_cmp_other, Ordering::Less)
358 || matches!(outputs_cmp_other, Ordering::Less)
359 {
360 // We check below that the overlapping spends and outputs match.
361 // Assuming here that they will, we take the other bundle's value sum.
362 self.value_sum = value_sum;
363 }
364 }
365 }
366
367 if !merge_optional(&mut self.anchor, anchor) {
368 return None;
369 }
370
371 // Leverage the early-exit behaviour of zip to confirm that the remaining data in
372 // the other bundle matches this one.
373 for (lhs, rhs) in self.spends.iter_mut().zip(spends) {
374 // Destructure `rhs` to ensure we handle everything.
375 let Spend {
376 cv,
377 nullifier,
378 rk,
379 zkproof,
380 spend_auth_sig,
381 recipient,
382 value,
383 rcm,
384 rseed,
385 rcv,
386 proof_generation_key,
387 witness,
388 alpha,
389 zip32_derivation,
390 dummy_ask,
391 proprietary,
392 } = rhs;
393
394 if lhs.cv != cv || lhs.nullifier != nullifier || lhs.rk != rk {
395 return None;
396 }
397
398 if !(merge_optional(&mut lhs.zkproof, zkproof)
399 && merge_optional(&mut lhs.spend_auth_sig, spend_auth_sig)
400 && merge_optional(&mut lhs.recipient, recipient)
401 && merge_optional(&mut lhs.value, value)
402 && merge_optional(&mut lhs.rcm, rcm)
403 && merge_optional(&mut lhs.rseed, rseed)
404 && merge_optional(&mut lhs.rcv, rcv)
405 && merge_optional(&mut lhs.proof_generation_key, proof_generation_key)
406 && merge_optional(&mut lhs.witness, witness)
407 && merge_optional(&mut lhs.alpha, alpha)
408 && merge_optional(&mut lhs.zip32_derivation, zip32_derivation)
409 && merge_optional(&mut lhs.dummy_ask, dummy_ask)
410 && merge_map(&mut lhs.proprietary, proprietary))
411 {
412 return None;
413 }
414 }
415
416 for (lhs, rhs) in self.outputs.iter_mut().zip(outputs) {
417 // Destructure `rhs` to ensure we handle everything.
418 let Output {
419 cv,
420 cmu,
421 ephemeral_key,
422 enc_ciphertext,
423 out_ciphertext,
424 zkproof,
425 recipient,
426 value,
427 rseed,
428 rcv,
429 ock,
430 zip32_derivation,
431 user_address,
432 proprietary,
433 } = rhs;
434
435 if lhs.cv != cv
436 || lhs.cmu != cmu
437 || lhs.ephemeral_key != ephemeral_key
438 || lhs.enc_ciphertext != enc_ciphertext
439 || lhs.out_ciphertext != out_ciphertext
440 {
441 return None;
442 }
443
444 if !(merge_optional(&mut lhs.zkproof, zkproof)
445 && merge_optional(&mut lhs.recipient, recipient)
446 && merge_optional(&mut lhs.value, value)
447 && merge_optional(&mut lhs.rseed, rseed)
448 && merge_optional(&mut lhs.rcv, rcv)
449 && merge_optional(&mut lhs.ock, ock)
450 && merge_optional(&mut lhs.zip32_derivation, zip32_derivation)
451 && merge_optional(&mut lhs.user_address, user_address)
452 && merge_map(&mut lhs.proprietary, proprietary))
453 {
454 return None;
455 }
456 }
457
458 Some(self)
459 }
460}
461
462/// Types for the v1 Sapling PCZT encoding.
463pub(crate) mod v1 {
464 use alloc::vec::Vec;
465
466 use serde::{Deserialize, Serialize};
467
468 /// PCZT fields that are specific to producing the transaction's Sapling
469 /// bundle.
470 #[derive(Clone, Debug, Serialize, Deserialize)]
471 pub(crate) struct Bundle {
472 spends: Vec<super::Spend>,
473 outputs: Vec<super::Output>,
474 value_sum: i128,
475 anchor: [u8; 32],
476 bsk: Option<crate::common::SecretKeyBytes>,
477 }
478
479 impl TryFrom<super::Bundle> for Bundle {
480 type Error = crate::EncodingError;
481
482 fn try_from(bundle: super::Bundle) -> Result<Self, Self::Error> {
483 let anchor = match bundle.anchor {
484 Some(anchor) => anchor,
485 None if bundle.spends.is_empty() => super::DEFAULT_ANCHOR,
486 None => return Err(crate::EncodingError::RequiresV2),
487 };
488
489 Ok(Self {
490 spends: bundle.spends,
491 outputs: bundle.outputs,
492 value_sum: bundle.value_sum,
493 anchor,
494 bsk: bundle.bsk,
495 })
496 }
497 }
498
499 impl From<Bundle> for super::Bundle {
500 fn from(bundle: Bundle) -> Self {
501 Self {
502 spends: bundle.spends,
503 outputs: bundle.outputs,
504 value_sum: bundle.value_sum,
505 anchor: Some(bundle.anchor),
506 bsk: bundle.bsk,
507 }
508 }
509 }
510}
511
512/// Types and operations for the v2 Sapling PCZT encoding.
513pub(crate) mod v2 {
514 /// Encodes a logical Sapling bundle for the v2 PCZT format, owning the
515 /// decision of whether the bundle can be omitted. A bundle that is exactly
516 /// equal to [`super::EMPTY_BUNDLE`] serializes to `None` and is dropped from
517 /// the encoding. An otherwise-empty bundle carrying
518 /// [`super::DEFAULT_ANCHOR`] is treated as empty for this purpose.
519 pub(crate) fn encode(bundle: super::Bundle) -> Option<super::Bundle> {
520 (!is_default_empty(&bundle)).then_some(bundle)
521 }
522
523 fn is_default_empty(bundle: &super::Bundle) -> bool {
524 let mut bundle = bundle.clone();
525 if bundle.anchor == Some(super::DEFAULT_ANCHOR) {
526 bundle.anchor = None;
527 }
528
529 bundle == super::EMPTY_BUNDLE
530 }
531}
532
533#[cfg(feature = "sapling")]
534impl Bundle {
535 /// Parses this bundle into the form used by the `sapling` crate.
536 ///
537 /// If the bundle's `anchor` is absent and `anchor_requirement` is
538 /// [`AnchorRequirement::Required`], returns [`ParseError::MissingAnchor`] unless the
539 /// bundle has no spends or outputs (in which case there is nothing to prove or
540 /// extract).
541 ///
542 /// [`AnchorRequirement::Required`]: crate::common::AnchorRequirement::Required
543 pub(crate) fn into_parsed(
544 self,
545 anchor_requirement: crate::common::AnchorRequirement,
546 ) -> Result<Parsed, ParseError> {
547 let wire_anchor = self.anchor;
548 let anchor = anchor_requirement
549 .resolve(
550 wire_anchor,
551 self.spends.is_empty() && self.outputs.is_empty(),
552 )
553 .ok_or(ParseError::MissingAnchor)?;
554
555 let spends = self
556 .spends
557 .into_iter()
558 .map(|spend| {
559 sapling::pczt::Spend::parse(
560 spend.cv,
561 spend.nullifier,
562 spend.rk,
563 spend.zkproof,
564 spend.spend_auth_sig,
565 spend.recipient,
566 spend.value,
567 spend.rcm,
568 spend.rseed,
569 spend.rcv,
570 spend.proof_generation_key,
571 spend.witness,
572 spend.alpha,
573 spend
574 .zip32_derivation
575 .map(|z| {
576 sapling::pczt::Zip32Derivation::parse(
577 z.seed_fingerprint,
578 z.derivation_path,
579 )
580 })
581 .transpose()?,
582 spend.dummy_ask.as_ref().map(|ask| *ask.expose_secret()),
583 spend.proprietary,
584 )
585 })
586 .collect::<Result<_, _>>()?;
587
588 let outputs = self
589 .outputs
590 .into_iter()
591 .map(|output| {
592 sapling::pczt::Output::parse(
593 output.cv,
594 output.cmu,
595 output.ephemeral_key,
596 output.enc_ciphertext,
597 output.out_ciphertext,
598 output.zkproof,
599 output.recipient,
600 output.value,
601 output.rseed,
602 output.rcv,
603 output.ock,
604 output
605 .zip32_derivation
606 .map(|z| {
607 sapling::pczt::Zip32Derivation::parse(
608 z.seed_fingerprint,
609 z.derivation_path,
610 )
611 })
612 .transpose()?,
613 output.user_address,
614 output.proprietary,
615 )
616 })
617 .collect::<Result<_, _>>()?;
618
619 let bundle = sapling::pczt::Bundle::parse(
620 spends,
621 outputs,
622 self.value_sum,
623 anchor,
624 self.bsk.as_ref().map(|bsk| *bsk.expose_secret()),
625 )?;
626
627 Ok(Parsed {
628 bundle,
629 wire_anchor,
630 })
631 }
632
633 pub(crate) fn serialize_from(bundle: sapling::pczt::Bundle) -> Self {
634 let spends = bundle
635 .spends()
636 .iter()
637 .map(|spend| {
638 let (rcm, rseed) = match spend.rseed() {
639 Some(sapling::Rseed::BeforeZip212(rcm)) => (Some(rcm.to_bytes()), None),
640 Some(sapling::Rseed::AfterZip212(rseed)) => (None, Some(*rseed)),
641 None => (None, None),
642 };
643
644 Spend {
645 cv: spend.cv().to_bytes(),
646 nullifier: spend.nullifier().0,
647 rk: (*spend.rk()).into(),
648 zkproof: *spend.zkproof(),
649 spend_auth_sig: spend.spend_auth_sig().map(|s| s.into()),
650 recipient: spend.recipient().map(|recipient| recipient.to_bytes()),
651 value: spend.value().map(|value| value.inner()),
652 rcm,
653 rseed,
654 rcv: spend.rcv().as_ref().map(|rcv| rcv.inner().to_bytes()),
655 proof_generation_key: spend
656 .proof_generation_key()
657 .as_ref()
658 .map(|key| (key.ak().to_bytes(), key.nsk().to_bytes())),
659 witness: spend.witness().as_ref().map(|witness| {
660 (
661 u32::try_from(u64::from(witness.position()))
662 .expect("Sapling positions fit in u32"),
663 witness
664 .path_elems()
665 .iter()
666 .map(|node| node.to_bytes())
667 .collect::<Vec<_>>()[..]
668 .try_into()
669 .expect("path is length 32"),
670 )
671 }),
672 alpha: spend.alpha().map(|alpha| alpha.to_bytes()),
673 zip32_derivation: spend.zip32_derivation().as_ref().map(|z| Zip32Derivation {
674 seed_fingerprint: *z.seed_fingerprint(),
675 derivation_path: z.derivation_path().iter().map(|i| i.index()).collect(),
676 }),
677 dummy_ask: spend
678 .dummy_ask()
679 .as_ref()
680 .map(|dummy_ask| SecretKeyBytes::new(dummy_ask.to_bytes())),
681 proprietary: spend.proprietary().clone(),
682 }
683 })
684 .collect();
685
686 let outputs = bundle
687 .outputs()
688 .iter()
689 .map(|output| Output {
690 cv: output.cv().to_bytes(),
691 cmu: output.cmu().to_bytes(),
692 ephemeral_key: output.ephemeral_key().0,
693 enc_ciphertext: output.enc_ciphertext().to_vec(),
694 out_ciphertext: output.out_ciphertext().to_vec(),
695 zkproof: *output.zkproof(),
696 recipient: output.recipient().map(|recipient| recipient.to_bytes()),
697 value: output.value().map(|value| value.inner()),
698 rseed: *output.rseed(),
699 rcv: output.rcv().as_ref().map(|rcv| rcv.inner().to_bytes()),
700 ock: output.ock().as_ref().map(|ock| ock.0),
701 zip32_derivation: output.zip32_derivation().as_ref().map(|z| Zip32Derivation {
702 seed_fingerprint: *z.seed_fingerprint(),
703 derivation_path: z.derivation_path().iter().map(|i| i.index()).collect(),
704 }),
705 user_address: output.user_address().clone(),
706 proprietary: output.proprietary().clone(),
707 })
708 .collect();
709
710 Self {
711 spends,
712 outputs,
713 value_sum: bundle.value_sum().to_raw(),
714 anchor: Some(bundle.anchor().to_bytes()),
715 bsk: bundle
716 .bsk()
717 .as_ref()
718 .map(|bsk| SecretKeyBytes::new(bsk.to_bytes())),
719 }
720 }
721}
722
723/// Errors that can occur while parsing a Sapling bundle into the form used by
724/// the `sapling` crate.
725#[cfg(feature = "sapling")]
726#[derive(Debug)]
727#[non_exhaustive]
728pub enum ParseError {
729 /// The operation requires the bundle's `anchor` to be set, but it was absent.
730 ///
731 /// For a v6 transaction, an Updater can resolve this by setting the anchor; see
732 /// [ZIP 374: Anchors and pre-authorization](https://zips.z.cash/zip-0374#anchorsandpre-authorization).
733 MissingAnchor,
734 /// The bundle's remaining fields were structurally invalid.
735 Bundle(sapling::pczt::ParseError),
736}
737
738#[cfg(feature = "sapling")]
739impl From<sapling::pczt::ParseError> for ParseError {
740 fn from(e: sapling::pczt::ParseError) -> Self {
741 ParseError::Bundle(e)
742 }
743}
744
745/// Errors that can occur while checking that a Sapling bundle's spend witnesses are
746/// consistent with its anchor.
747#[cfg(all(feature = "sapling", feature = "prover"))]
748#[derive(Debug)]
749#[non_exhaustive]
750pub enum AnchorConsistencyError {
751 /// A non-zero-valued spend has a `witness` but is missing other note data required
752 /// to compute its Merkle path root.
753 IncompleteSpendData,
754 /// A non-zero-valued spend's `witness` does not root to the given anchor.
755 WitnessDoesNotRootToAnchor,
756}
757
758/// Checks that every non-zero-valued spend in `bundle` whose `witness` is present has a
759/// Merkle path that roots to `anchor` (\[ZIP 374\] "Anchors and pre-authorization").
760///
761/// Zero-valued spends are skipped, as their Merkle paths are not checked by the Sapling
762/// circuit.
763///
764/// [ZIP 374]: https://zips.z.cash/zip-0374#anchorsandpre-authorization
765#[cfg(all(feature = "sapling", feature = "prover"))]
766pub(crate) fn verify_witnesses_root_to_anchor(
767 bundle: &sapling::pczt::Bundle,
768 anchor: sapling::Anchor,
769) -> Result<(), AnchorConsistencyError> {
770 for spend in bundle.spends() {
771 let Some(witness) = spend.witness() else {
772 continue;
773 };
774 let Some(value) = spend.value() else {
775 continue;
776 };
777 if value.inner() == 0 {
778 continue;
779 }
780
781 let recipient = spend
782 .recipient()
783 .ok_or(AnchorConsistencyError::IncompleteSpendData)?;
784 let rseed = (*spend.rseed()).ok_or(AnchorConsistencyError::IncompleteSpendData)?;
785
786 let note = sapling::Note::from_parts(recipient, *value, rseed);
787 let leaf = sapling::Node::from_cmu(¬e.cmu());
788 let computed_anchor: sapling::Anchor = witness.root(leaf).into();
789
790 if computed_anchor != anchor {
791 return Err(AnchorConsistencyError::WitnessDoesNotRootToAnchor);
792 }
793 }
794
795 Ok(())
796}
797
798/// The result of parsing a Sapling bundle via [`Bundle::into_parsed`].
799///
800/// Carries the bundle's original wire `anchor` alongside the parsed form, so that
801/// [`Parsed::reserialize`] can restore it after an operation that does not itself
802/// change the anchor, even though parsing may have substituted a placeholder for it
803/// (see [ZIP 374: Anchors and pre-authorization](https://zips.z.cash/zip-0374#anchorsandpre-authorization)).
804#[cfg(feature = "sapling")]
805pub(crate) struct Parsed {
806 pub(crate) bundle: sapling::pczt::Bundle,
807 pub(crate) wire_anchor: Option<[u8; 32]>,
808}
809
810#[cfg(feature = "sapling")]
811impl Parsed {
812 /// Serializes the parsed bundle back into its wire representation, using
813 /// [`Self::wire_anchor`] as the result's `anchor` in place of any placeholder
814 /// substituted while parsing.
815 ///
816 /// Must not be used after an operation that legitimately changes the anchor;
817 /// such operations should set `wire_anchor` to the new value first.
818 pub(crate) fn reserialize(self) -> Bundle {
819 Bundle {
820 anchor: self.wire_anchor,
821 ..Bundle::serialize_from(self.bundle)
822 }
823 }
824}
825
826#[cfg(all(test, feature = "sapling"))]
827mod tests {
828 use rand_chacha::{ChaCha20Rng, rand_core::SeedableRng};
829
830 use super::{Bundle, ParseError};
831 use crate::common::AnchorRequirement;
832
833 fn output_only_bundle() -> Bundle {
834 let recipient = sapling::zip32::ExtendedSpendingKey::master(&[0; 32])
835 .expect("the derivation path yields a valid key")
836 .to_diversifiable_full_viewing_key()
837 .default_address()
838 .1;
839 let mut builder = sapling::builder::Builder::new(
840 sapling::note_encryption::Zip212Enforcement::On,
841 sapling::builder::BundleType::DEFAULT,
842 sapling::Anchor::empty_tree(),
843 );
844 builder
845 .add_output(
846 None,
847 recipient,
848 sapling::value::NoteValue::from_raw(1),
849 [0; 512],
850 )
851 .unwrap();
852 let (bundle, _) = builder
853 .build_for_pczt(ChaCha20Rng::from_seed([0; 32]))
854 .unwrap();
855 let mut bundle = Bundle::serialize_from(bundle);
856 bundle.anchor = None;
857 bundle
858 }
859
860 #[test]
861 fn output_only_bundle_requires_anchor_when_required() {
862 assert!(matches!(
863 output_only_bundle().into_parsed(AnchorRequirement::Required),
864 Err(ParseError::MissingAnchor)
865 ));
866 }
867
868 #[test]
869 fn output_only_bundle_preserves_absent_anchor_when_not_required() {
870 let bundle = output_only_bundle();
871 let output_count = bundle.outputs.len();
872
873 let parsed = bundle.into_parsed(AnchorRequirement::NotRequired).unwrap();
874 assert!(parsed.bundle.spends().is_empty());
875 assert_eq!(parsed.bundle.outputs().len(), output_count);
876
877 let reserialized = parsed.reserialize();
878 assert!(reserialized.anchor.is_none());
879 assert_eq!(reserialized.outputs.len(), output_count);
880 }
881}