Skip to main content

pczt/
orchard.rs

1//! The Orchard fields of a PCZT.
2
3use alloc::{collections::BTreeMap, string::String, vec::Vec};
4use core::{cmp::Ordering, fmt};
5
6use getset::Getters;
7use serde::{Deserialize, Deserializer, Serialize, Serializer, de};
8
9// `NoteVersion` is re-exported, so it keeps its own statement.
10#[cfg(feature = "orchard")]
11pub(crate) use orchard::note::NoteVersion;
12
13#[cfg(feature = "orchard")]
14use {
15    ::orchard::{
16        Address, Note, ValuePool,
17        bundle::BundleVersion,
18        note::{ExtractedNoteCommitment, Nullifier, RandomSeed, Rho},
19        note_encryption::{
20            COMPACT_NOTE_SIZE, CompactAction, CompactNoteCiphertextBytes, IronwoodDomain,
21            NoteBytesData, NoteCiphertextBytes, OrchardDomain, OrchardNoteEncryption,
22        },
23        value::{NoteValue, ValueCommitTrapdoor, ValueCommitment},
24    },
25    ff::PrimeField,
26    zcash_note_encryption::{
27        Domain, EphemeralKeyBytes, ShieldedOutput, note_bytes::NoteBytes,
28        try_output_recovery_with_pkd_esk,
29    },
30    zcash_protocol::consensus::{BranchId, OrchardProtocolRevision},
31};
32
33use crate::{
34    common::{Global, SecretKeyBytes, Zip32Derivation},
35    roles::combiner::{merge_map, merge_optional},
36};
37
38#[cfg(not(feature = "orchard"))]
39#[derive(Clone, Copy, Debug, PartialEq, Eq)]
40pub(crate) enum NoteVersion {
41    V2,
42    V3,
43}
44
45/// PCZT fields that are specific to producing the transaction's Orchard bundle (if any).
46#[derive(Clone, Debug, PartialEq, Getters)]
47pub struct Bundle {
48    /// The Orchard actions in this bundle.
49    ///
50    /// Entries are added by the Constructor, and modified by an Updater, IO Finalizer,
51    /// Signer, Combiner, or Spend Finalizer.
52    #[getset(get = "pub")]
53    pub(crate) actions: Vec<Action>,
54
55    /// The flags for the Orchard bundle.
56    ///
57    /// Contains:
58    /// - `enableSpendsOrchard` flag (bit 0)
59    /// - `enableOutputsOrchard` flag (bit 1)
60    /// - Reserved, zeros (bits 2..=7)
61    ///
62    /// This is set by the Creator. The Constructor MUST only add spends and outputs that
63    /// are consistent with these flags (i.e. are dummies as appropriate).
64    #[getset(get = "pub")]
65    pub(crate) flags: u8,
66
67    /// The net value of Orchard spends minus outputs.
68    ///
69    /// This is initialized by the Creator, and updated by the Constructor as spends or
70    /// outputs are added to the PCZT. It enables per-spend and per-output values to be
71    /// redacted from the PCZT after they are no longer necessary.
72    #[getset(get = "pub")]
73    pub(crate) value_sum: (u64, bool),
74
75    /// The Orchard anchor for this transaction.
76    ///
77    /// Set by the Creator.
78    #[getset(get = "pub")]
79    pub(crate) anchor: Option<[u8; 32]>,
80
81    /// The note plaintext version for notes in this bundle.
82    pub(crate) note_version: NoteVersion,
83
84    /// The Orchard bundle proof.
85    ///
86    /// This is `None` until it is set by the Prover.
87    #[getset(get = "pub")]
88    pub(crate) zkproof: Option<Vec<u8>>,
89
90    /// The Orchard binding signature signing key.
91    ///
92    /// - This is `None` until it is set by the IO Finalizer.
93    /// - The Transaction Extractor uses this to produce the binding signature.
94    pub(crate) bsk: Option<SecretKeyBytes>,
95}
96
97/// The default Orchard bundle flags: both spends and outputs enabled (bits 0 and
98/// 1). This is the value the Creator sets on a new bundle, and the flag value of
99/// an empty bundle for serialization purposes.
100pub(crate) const ORCHARD_SPENDS_AND_OUTPUTS_ENABLED: u8 = 0b0000_0011;
101
102/// The default Ironwood bundle flags: spends, outputs, and cross-address transfers
103/// enabled (bits 0, 1, and 2). This is the value the Creator sets on a new bundle,
104/// and the flag value of an empty bundle for serialization purposes.
105pub(crate) const IRONWOOD_SPENDS_OUTPUTS_AND_CROSS_ADDRESS_ENABLED: u8 = 0b0000_0111;
106
107/// The size in bytes of the memo portion of an Orchard note plaintext.
108pub(crate) const MEMO_SIZE: usize = 512;
109
110pub(crate) const DEFAULT_ANCHOR: [u8; 32] = [0; 32];
111
112/// The canonical empty Orchard-pool bundle: the form the Orchard slot of a PCZT takes
113/// when it carries no Orchard-protocol data. The Creator, the v1 decoder, and the v2
114/// decoder all produce exactly this value for an absent bundle, so that copies of a
115/// PCZT that take different serialization paths continue to merge successfully.
116pub(crate) const EMPTY_ORCHARD: Bundle = Bundle {
117    actions: Vec::new(),
118    flags: ORCHARD_SPENDS_AND_OUTPUTS_ENABLED,
119    value_sum: (0, false),
120    anchor: None,
121    note_version: NoteVersion::V2,
122    zkproof: None,
123    bsk: None,
124};
125
126/// The canonical empty Ironwood bundle; see [`EMPTY_ORCHARD`].
127pub(crate) const EMPTY_IRONWOOD: Bundle = Bundle {
128    actions: Vec::new(),
129    flags: IRONWOOD_SPENDS_OUTPUTS_AND_CROSS_ADDRESS_ENABLED,
130    value_sum: (0, false),
131    anchor: None,
132    note_version: NoteVersion::V3,
133    zkproof: None,
134    bsk: None,
135};
136
137/// Errors that can occur while constructing a single note's [`MemoPlaintext`].
138#[derive(Debug, PartialEq, Eq)]
139#[non_exhaustive]
140enum MemoPlaintextError {
141    /// The stripped memo plaintext exceeds [`MEMO_SIZE`].
142    TooLong,
143    /// The memo plaintext was not encoded with all trailing zero bytes stripped.
144    NotStripped,
145}
146
147impl fmt::Display for MemoPlaintextError {
148    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
149        match self {
150            MemoPlaintextError::TooLong => {
151                write!(f, "memo plaintext exceeds {MEMO_SIZE} bytes")
152            }
153            MemoPlaintextError::NotStripped => {
154                write!(f, "memo plaintext has trailing zero bytes")
155            }
156        }
157    }
158}
159
160/// The result of parsing a logical Orchard-protocol bundle (Orchard or Ironwood) via
161/// [`Bundle::into_parsed_with_version`] or one of its siblings.
162///
163/// Carries the bundle's original wire `anchor` alongside the parsed form, so that
164/// [`Parsed::reserialize`] can restore it after an operation that does not itself
165/// change the anchor, even though parsing may have substituted a placeholder for it
166/// (see [ZIP 374: Anchors and pre-authorization](https://zips.z.cash/zip-0374#anchorsandpre-authorization)).
167#[cfg(feature = "orchard")]
168pub(crate) struct Parsed {
169    pub(crate) bundle: orchard::pczt::Bundle,
170    pub(crate) wire_anchor: Option<[u8; 32]>,
171}
172
173#[cfg(feature = "orchard")]
174impl Parsed {
175    /// Serializes the parsed bundle back into its wire representation, using
176    /// [`Self::wire_anchor`] as the result's `anchor` in place of any placeholder
177    /// substituted while parsing.
178    ///
179    /// Must not be used after an operation that legitimately changes the anchor;
180    /// such operations should set `wire_anchor` to the new value first.
181    pub(crate) fn reserialize(self) -> Bundle {
182        Bundle {
183            anchor: self.wire_anchor,
184            ..Bundle::serialize_from(self.bundle)
185        }
186    }
187}
188
189/// Shared fixtures for hand-crafting Orchard-protocol PCZT test data.
190#[cfg(all(test, feature = "orchard"))]
191pub(crate) mod testing {
192    #[cfg(any(feature = "prover", all(feature = "signer", feature = "io-finalizer")))]
193    use {
194        super::{Action, EncCiphertext, Output, Spend},
195        alloc::collections::BTreeMap,
196        ff::Field,
197        pasta_curves::pallas,
198    };
199
200    /// Derives a valid Orchard value commitment encoding for the given value and
201    /// trapdoor, so that hand-crafted `Action`s pass the structural validity check
202    /// applied when parsing (regardless of anchor consistency, which is unrelated).
203    pub(crate) fn value_commitment(value: u64, rcv: [u8; 32]) -> [u8; 32] {
204        let rcv = orchard::value::ValueCommitTrapdoor::from_bytes(rcv)
205            .into_option()
206            .unwrap();
207        let value_sum =
208            orchard::value::NoteValue::from_raw(value) - orchard::value::NoteValue::from_raw(0);
209        orchard::value::ValueCommitment::derive(value_sum, rcv).to_bytes()
210    }
211
212    /// Derives a valid, randomized `rk` encoding (a curve point, unlike an arbitrary
213    /// byte string) so that hand-crafted `Spend`s pass the structural validity check
214    /// applied when parsing.
215    #[cfg(any(feature = "prover", all(feature = "signer", feature = "io-finalizer")))]
216    pub(crate) fn randomized_verification_key() -> [u8; 32] {
217        let sk = orchard::keys::SpendingKey::from_bytes([7; 32]).unwrap();
218        let ask = orchard::keys::SpendAuthorizingKey::from(&sk);
219        let randomized_signing_key = ask.randomize(&pallas::Scalar::ONE);
220        let rk: orchard::primitives::redpallas::VerificationKey<
221            orchard::primitives::redpallas::SpendAuth,
222        > = (&randomized_signing_key).into();
223        (&rk).into()
224    }
225
226    /// A structurally valid dummy Orchard action with no witness (so it is exempt
227    /// from anchor-consistency checks), for use as a base in hand-crafted test PCZTs.
228    #[cfg(any(feature = "prover", all(feature = "signer", feature = "io-finalizer")))]
229    pub(crate) fn dummy_action() -> Action {
230        Action {
231            cv_net: Some(value_commitment(0, [3; 32])),
232            spend: Spend {
233                nullifier: [2; 32],
234                rk: randomized_verification_key(),
235                spend_auth_sig: None,
236                recipient: None,
237                value: None,
238                rho: None,
239                rseed: None,
240                fvk: None,
241                witness: None,
242                alpha: None,
243                zip32_derivation: None,
244                dummy_sk: None,
245                proprietary: BTreeMap::new(),
246            },
247            output: Output {
248                cmx: Some([4; 32]),
249                ephemeral_key: [5; 32],
250                enc_ciphertext: EncCiphertext::Encrypted(alloc::vec![6; 580]),
251                out_ciphertext: alloc::vec![7; 80],
252                recipient: None,
253                value: None,
254                rseed: None,
255                ock: None,
256                zip32_derivation: None,
257                user_address: None,
258                proprietary: BTreeMap::new(),
259            },
260            rcv: None,
261        }
262    }
263}
264
265/// A memo plaintext with all trailing zero bytes stripped.
266///
267/// This is the memo portion of an Orchard note plaintext, not the full note
268/// plaintext. It is expanded back to the protocol memo size before recomputing
269/// [`EncCiphertext::Encrypted`].
270#[derive(Clone, Debug, PartialEq, Eq)]
271pub struct MemoPlaintext(Vec<u8>);
272
273impl MemoPlaintext {
274    /// Constructs a stripped memo plaintext from a full memo.
275    pub fn from_memo(memo: [u8; MEMO_SIZE]) -> Self {
276        let len = memo.iter().rposition(|b| *b != 0).map_or(0, |i| i + 1);
277        Self(memo[..len].to_vec())
278    }
279
280    /// Constructs a stripped memo plaintext from its encoded bytes.
281    ///
282    /// Returns an error if `bytes` is longer than [`MEMO_SIZE`], or if it
283    /// contains any trailing zero bytes.
284    fn from_stripped_bytes(bytes: Vec<u8>) -> Result<Self, MemoPlaintextError> {
285        if bytes.len() > MEMO_SIZE {
286            Err(MemoPlaintextError::TooLong)
287        } else if bytes.last() == Some(&0) {
288            Err(MemoPlaintextError::NotStripped)
289        } else {
290            Ok(Self(bytes))
291        }
292    }
293
294    /// Returns the trailing-zero-stripped memo plaintext bytes.
295    pub fn as_stripped_bytes(&self) -> &[u8] {
296        &self.0
297    }
298
299    /// Expands this memo plaintext to its full-size protocol encoding.
300    pub fn to_memo(&self) -> [u8; MEMO_SIZE] {
301        let mut memo = [0; MEMO_SIZE];
302        memo[..self.0.len()].copy_from_slice(&self.0);
303        memo
304    }
305}
306
307impl Serialize for MemoPlaintext {
308    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
309    where
310        S: Serializer,
311    {
312        self.0.serialize(serializer)
313    }
314}
315
316impl<'de> Deserialize<'de> for MemoPlaintext {
317    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
318    where
319        D: Deserializer<'de>,
320    {
321        let bytes = Vec::<u8>::deserialize(deserializer)?;
322        Self::from_stripped_bytes(bytes).map_err(de::Error::custom)
323    }
324}
325
326/// The encrypted note plaintext for an output, or the memo plaintext needed to
327/// recompute it.
328///
329/// [`EncCiphertext::MemoPlaintext`] can be resolved to
330/// [`EncCiphertext::Encrypted`] from the output note fields and the action's
331/// spend nullifier.
332///
333/// The variant order is part of the v2 wire encoding.
334#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
335pub enum EncCiphertext {
336    /// The encrypted note plaintext for the output.
337    Encrypted(Vec<u8>),
338    /// The note's memo plaintext, with trailing zero bytes stripped.
339    MemoPlaintext(MemoPlaintext),
340}
341
342impl EncCiphertext {
343    /// Consumes this value and returns the encrypted note plaintext, if present.
344    pub fn into_encrypted(self) -> Option<Vec<u8>> {
345        match self {
346            EncCiphertext::Encrypted(ciphertext) => Some(ciphertext),
347            EncCiphertext::MemoPlaintext(_) => None,
348        }
349    }
350}
351
352#[cfg(feature = "orchard")]
353fn recover_memo_plaintext_from_ciphertext_and_action(
354    action: &Action,
355    note_version: NoteVersion,
356) -> Option<MemoPlaintext> {
357    struct OutputRecoveryData {
358        cmx: ExtractedNoteCommitment,
359        ephemeral_key: [u8; 32],
360        enc_ciphertext: NoteCiphertextBytes,
361    }
362
363    impl<D> ShieldedOutput<D> for OutputRecoveryData
364    where
365        D: Domain<
366                ExtractedCommitment = ExtractedNoteCommitment,
367                NoteCiphertextBytes = NoteCiphertextBytes,
368                CompactNoteCiphertextBytes = CompactNoteCiphertextBytes,
369            >,
370    {
371        fn ephemeral_key(&self) -> EphemeralKeyBytes {
372            EphemeralKeyBytes(self.ephemeral_key)
373        }
374
375        fn cmstar(&self) -> &ExtractedNoteCommitment {
376            &self.cmx
377        }
378
379        fn enc_ciphertext(&self) -> Option<&NoteCiphertextBytes> {
380            Some(&self.enc_ciphertext)
381        }
382
383        fn enc_ciphertext_compact(&self) -> CompactNoteCiphertextBytes {
384            NoteBytesData(
385                self.enc_ciphertext.0[..COMPACT_NOTE_SIZE]
386                    .try_into()
387                    .expect("COMPACT_NOTE_SIZE <= ENC_CIPHERTEXT_SIZE"),
388            )
389        }
390    }
391
392    fn recover_with_domain<D>(
393        domain: &D,
394        note: &Note,
395        output: &OutputRecoveryData,
396    ) -> Option<MemoPlaintext>
397    where
398        D: Domain<
399                Note = Note,
400                Memo = [u8; MEMO_SIZE],
401                ExtractedCommitment = ExtractedNoteCommitment,
402                NoteCiphertextBytes = NoteCiphertextBytes,
403                CompactNoteCiphertextBytes = CompactNoteCiphertextBytes,
404            >,
405    {
406        let pk_d = D::get_pk_d(note);
407        let esk = D::derive_esk(note)?;
408
409        try_output_recovery_with_pkd_esk(domain, pk_d, esk, output)
410            .map(|(_, _, memo)| MemoPlaintext::from_memo(memo))
411    }
412
413    let enc_ciphertext = match &action.output.enc_ciphertext {
414        EncCiphertext::Encrypted(ciphertext) => NoteCiphertextBytes::from_slice(ciphertext)?,
415        // we return None here to avoid excess sets or clone operations, as the caller need not do anything in this case.
416        EncCiphertext::MemoPlaintext(_) => return None,
417    };
418    let recipient = Option::from(Address::from_raw_address_bytes(
419        action.output.recipient.as_ref()?,
420    ))?;
421    let rho = Option::from(Rho::from_bytes(&action.spend.nullifier))?;
422    let rseed = Option::from(RandomSeed::from_bytes(*action.output.rseed.as_ref()?, &rho))?;
423    let note = Option::from(Note::from_parts(
424        recipient,
425        NoteValue::from_raw(action.output.value?),
426        rho,
427        rseed,
428        note_version,
429    ))?;
430
431    let nullifier = Option::from(Nullifier::from_bytes(&action.spend.nullifier))?;
432    // Memo recovery is best-effort and should not resolve redacted fields.
433    // Callers that want redacted `cmx` restored should use `resolve_fields`.
434    let cmx = Option::from(ExtractedNoteCommitment::from_bytes(&action.output.cmx?))?;
435    let output = OutputRecoveryData {
436        cmx,
437        ephemeral_key: action.output.ephemeral_key,
438        enc_ciphertext,
439    };
440    let compact_action = CompactAction::from_parts(
441        nullifier,
442        cmx,
443        EphemeralKeyBytes(action.output.ephemeral_key),
444        output.enc_ciphertext.0[..COMPACT_NOTE_SIZE]
445            .try_into()
446            .ok()?,
447    );
448
449    match note_version {
450        NoteVersion::V2 => recover_with_domain(
451            &OrchardDomain::for_compact_action(&compact_action),
452            &note,
453            &output,
454        ),
455        NoteVersion::V3 => recover_with_domain(
456            &IronwoodDomain::for_compact_action(&compact_action),
457            &note,
458            &output,
459        ),
460    }
461}
462
463#[cfg(feature = "orchard")]
464impl Action {
465    pub(crate) fn replace_enc_ciphertext_with_decrypted_memo_plaintext(
466        &mut self,
467        note_version: NoteVersion,
468    ) {
469        if let Some(memo) = recover_memo_plaintext_from_ciphertext_and_action(self, note_version) {
470            self.output.enc_ciphertext = EncCiphertext::MemoPlaintext(memo);
471        }
472    }
473
474    pub(crate) fn compact_resolvable_fields(&mut self, note_version: NoteVersion) {
475        let original_enc_ciphertext = self.output.enc_ciphertext.clone();
476        self.replace_enc_ciphertext_with_decrypted_memo_plaintext(note_version);
477        if self.output.enc_ciphertext != original_enc_ciphertext {
478            let mut resolved = self.output.clone();
479            if resolved
480                .encrypt_ciphertext_from_memo(note_version, self.spend.nullifier)
481                .is_err()
482                || resolved.enc_ciphertext != original_enc_ciphertext
483            {
484                self.output.enc_ciphertext = original_enc_ciphertext;
485            }
486        }
487
488        if let Some(original_cv_net) = self.cv_net {
489            let mut resolved = self.clone();
490            resolved.cv_net = None;
491            if resolved.resolve_cv_net().is_ok() && resolved.cv_net == Some(original_cv_net) {
492                self.cv_net = None;
493            }
494        }
495
496        if let Some(original_cmx) = self.output.cmx {
497            let mut resolved = self.output.clone();
498            resolved.cmx = None;
499            if resolved
500                .resolve_cmx(note_version, self.spend.nullifier)
501                .is_ok()
502                && resolved.cmx == Some(original_cmx)
503            {
504                self.output.cmx = None;
505            }
506        }
507    }
508}
509
510/// Information about an Orchard action within a transaction.
511#[derive(Clone, Debug, PartialEq, Getters)]
512pub struct Action {
513    //
514    // Action effecting data.
515    //
516    // These fields describe the action as a whole. `cv_net` is part of the
517    // final transaction, but may be redacted in v2 and recomputed from the note
518    // values and `rcv`.
519    //
520    #[getset(get = "pub")]
521    pub(crate) cv_net: Option<[u8; 32]>,
522    #[getset(get = "pub")]
523    pub(crate) spend: Spend,
524    #[getset(get = "pub")]
525    pub(crate) output: Output,
526
527    /// The value commitment randomness.
528    ///
529    /// - This is set by the Constructor.
530    /// - The IO Finalizer compresses it into the bsk.
531    /// - This is required by the Prover.
532    /// - This may be used by Signers to verify that the value correctly matches `cv`.
533    ///
534    /// This opens `cv` for all participants. For Signers who don't need this information,
535    /// or after proofs / signatures have been applied, this can be redacted.
536    pub(crate) rcv: Option<[u8; 32]>,
537}
538
539/// Information about the spend part of an Orchard action.
540#[derive(Clone, Debug, PartialEq, Getters)]
541pub struct Spend {
542    //
543    // Spend-specific Action effecting data.
544    //
545    // These are required fields that are part of the final transaction, and are filled in
546    // by the Constructor when adding a spend.
547    //
548    #[getset(get = "pub")]
549    pub(crate) nullifier: [u8; 32],
550    #[getset(get = "pub")]
551    pub(crate) rk: [u8; 32],
552
553    /// The spend authorization signature.
554    ///
555    /// This is set by the Signer.
556    #[getset(get = "pub")]
557    pub(crate) spend_auth_sig: Option<[u8; 64]>,
558
559    /// The [raw encoding] of the Orchard payment address that received the note being spent.
560    ///
561    /// - This is set by the Constructor.
562    /// - This is required by the Prover.
563    ///
564    /// [raw encoding]: https://zips.z.cash/protocol/protocol.pdf#orchardpaymentaddrencoding
565    pub(crate) recipient: Option<[u8; 43]>,
566
567    /// The value of the input being spent.
568    ///
569    /// - This is required by the Prover.
570    /// - This may be used by Signers to verify that the value matches `cv`, and to
571    ///   confirm the values and change involved in the transaction.
572    ///
573    /// This exposes the input value to all participants. For Signers who don't need this
574    /// information, or after signatures have been applied, this can be redacted.
575    pub(crate) value: Option<u64>,
576
577    /// The rho value for the note being spent.
578    ///
579    /// - This is set by the Constructor.
580    /// - This is required by the Prover.
581    pub(crate) rho: Option<[u8; 32]>,
582
583    /// The seed randomness for the note being spent.
584    ///
585    /// - This is set by the Constructor.
586    /// - This is required by the Prover.
587    pub(crate) rseed: Option<[u8; 32]>,
588
589    /// The full viewing key that received the note being spent.
590    ///
591    /// - This is set by the Updater.
592    /// - This is required by the Prover.
593    pub(crate) fvk: Option<[u8; 96]>,
594
595    /// A witness from the note to the bundle's anchor.
596    ///
597    /// - This is set by the Updater.
598    /// - This is required by the Prover.
599    #[getset(get = "pub")]
600    pub(crate) witness: Option<(u32, [[u8; 32]; 32])>,
601
602    /// The spend authorization randomizer.
603    ///
604    /// - This is chosen by the Constructor.
605    /// - This is required by the Signer for creating `spend_auth_sig`, and may be used to
606    ///   validate `rk`.
607    /// - After `zkproof` / `spend_auth_sig` has been set, this can be redacted.
608    pub(crate) alpha: Option<[u8; 32]>,
609
610    /// The ZIP 32 derivation path at which the spending key can be found for the note
611    /// being spent.
612    pub(crate) zip32_derivation: Option<Zip32Derivation>,
613
614    /// The spending key for this spent note, if it is a dummy note.
615    ///
616    /// - This is chosen by the Constructor.
617    /// - This is required by the IO Finalizer, and is cleared by it once used.
618    /// - Signers MUST reject PCZTs that contain `dummy_sk` values.
619    #[getset(get = "pub")]
620    pub(crate) dummy_sk: Option<SecretKeyBytes>,
621
622    /// Proprietary fields related to the note being spent.
623    #[getset(get = "pub")]
624    pub(crate) proprietary: BTreeMap<String, Vec<u8>>,
625}
626
627/// Information about the output part of an Orchard action.
628#[derive(Clone, Debug, PartialEq, Getters)]
629pub struct Output {
630    //
631    // Output-specific Action effecting data.
632    //
633    // These are required fields that are part of the final transaction, and are filled in
634    // by the Constructor when adding an output.
635    //
636    #[getset(get = "pub")]
637    pub(crate) cmx: Option<[u8; 32]>,
638    #[getset(get = "pub")]
639    pub(crate) ephemeral_key: [u8; 32],
640    /// The encrypted note plaintext for the output, or the memo plaintext
641    /// needed to recompute it.
642    #[getset(get = "pub")]
643    pub(crate) enc_ciphertext: EncCiphertext,
644    /// The encrypted note plaintext for the output.
645    ///
646    /// Encoded as a `Vec<u8>` because its length depends on the transaction version.
647    #[getset(get = "pub")]
648    pub(crate) out_ciphertext: Vec<u8>,
649
650    /// The [raw encoding] of the Orchard payment address that will receive the output.
651    ///
652    /// - This is set by the Constructor.
653    /// - This is required by the Prover.
654    ///
655    /// [raw encoding]: https://zips.z.cash/protocol/protocol.pdf#orchardpaymentaddrencoding
656    #[getset(get = "pub")]
657    pub(crate) recipient: Option<[u8; 43]>,
658
659    /// The value of the output.
660    ///
661    /// This may be used by Signers to verify that the value matches `cv`, and to confirm
662    /// the values and change involved in the transaction.
663    ///
664    /// This exposes the value to all participants. For Signers who don't need this
665    /// information, we can drop the values and compress the rcvs into the bsk global.
666    #[getset(get = "pub")]
667    pub(crate) value: Option<u64>,
668
669    /// The seed randomness for the output.
670    ///
671    /// - This is set by the Constructor.
672    /// - This is required by the Prover, instead of disclosing `shared_secret` to them.
673    #[getset(get = "pub")]
674    pub(crate) rseed: Option<[u8; 32]>,
675
676    /// The `ock` value used to encrypt `out_ciphertext`.
677    ///
678    /// This enables Signers to verify that `out_ciphertext` is correctly encrypted.
679    ///
680    /// This may be `None` if the Constructor added the output using an OVK policy of
681    /// "None", to make the output unrecoverable from the chain by the sender.
682    pub(crate) ock: Option<[u8; 32]>,
683
684    /// The ZIP 32 derivation path at which the spending key can be found for the output.
685    pub(crate) zip32_derivation: Option<Zip32Derivation>,
686
687    /// The user-facing address to which this output is being sent, if any.
688    ///
689    /// - This is set by an Updater.
690    /// - Signers must parse this address (if present) and confirm that it contains
691    ///   `recipient` (either directly, or e.g. as a receiver within a Unified Address).
692    #[getset(get = "pub")]
693    pub(crate) user_address: Option<String>,
694
695    /// Proprietary fields related to the note being created.
696    #[getset(get = "pub")]
697    pub(crate) proprietary: BTreeMap<String, Vec<u8>>,
698}
699
700/// Types for the v1 Orchard PCZT encoding.
701pub mod v1 {
702    use alloc::{collections::BTreeMap, string::String, vec::Vec};
703
704    use serde::{Deserialize, Serialize};
705    use serde_with::serde_as;
706
707    use crate::common::Zip32Derivation;
708
709    use super::NoteVersion;
710
711    /// PCZT fields that are specific to producing the transaction's Orchard bundle.
712    #[derive(Clone, Debug, Serialize, Deserialize)]
713    pub struct Bundle {
714        actions: Vec<Action>,
715        flags: u8,
716        value_sum: (u64, bool),
717        anchor: [u8; 32],
718        zkproof: Option<Vec<u8>>,
719        bsk: Option<crate::common::SecretKeyBytes>,
720    }
721
722    /// Information about an Orchard action within a transaction.
723    #[derive(Clone, Debug, Serialize, Deserialize)]
724    pub(crate) struct Action {
725        cv_net: [u8; 32],
726        spend: Spend,
727        output: Output,
728        rcv: Option<[u8; 32]>,
729    }
730
731    /// Information about the spend part of an Orchard action.
732    #[serde_as]
733    #[derive(Clone, Debug, Serialize, Deserialize)]
734    pub(crate) struct Spend {
735        nullifier: [u8; 32],
736        rk: [u8; 32],
737        #[serde_as(as = "Option<[_; 64]>")]
738        spend_auth_sig: Option<[u8; 64]>,
739        #[serde_as(as = "Option<[_; 43]>")]
740        recipient: Option<[u8; 43]>,
741        value: Option<u64>,
742        rho: Option<[u8; 32]>,
743        rseed: Option<[u8; 32]>,
744        #[serde_as(as = "Option<[_; 96]>")]
745        fvk: Option<[u8; 96]>,
746        witness: Option<(u32, [[u8; 32]; 32])>,
747        alpha: Option<[u8; 32]>,
748        zip32_derivation: Option<Zip32Derivation>,
749        dummy_sk: Option<crate::common::SecretKeyBytes>,
750        proprietary: BTreeMap<String, Vec<u8>>,
751    }
752
753    /// Information about the output part of an Orchard action.
754    #[serde_as]
755    #[derive(Clone, Debug, Serialize, Deserialize)]
756    pub(crate) struct Output {
757        cmx: [u8; 32],
758        ephemeral_key: [u8; 32],
759        enc_ciphertext: Vec<u8>,
760        out_ciphertext: Vec<u8>,
761        #[serde_as(as = "Option<[_; 43]>")]
762        recipient: Option<[u8; 43]>,
763        value: Option<u64>,
764        rseed: Option<[u8; 32]>,
765        ock: Option<[u8; 32]>,
766        zip32_derivation: Option<Zip32Derivation>,
767        user_address: Option<String>,
768        proprietary: BTreeMap<String, Vec<u8>>,
769    }
770
771    impl TryFrom<super::Bundle> for Bundle {
772        type Error = crate::EncodingError;
773
774        fn try_from(bundle: super::Bundle) -> Result<Self, Self::Error> {
775            if bundle.note_version != NoteVersion::V2 {
776                return Err(crate::EncodingError::UnsupportedOrchardNoteVersion);
777            }
778
779            let anchor = match bundle.anchor {
780                Some(anchor) => anchor,
781                None if bundle.actions.is_empty() => super::DEFAULT_ANCHOR,
782                None => return Err(crate::EncodingError::RequiresV2),
783            };
784
785            Ok(Self {
786                actions: bundle
787                    .actions
788                    .into_iter()
789                    .map(Action::try_from)
790                    .collect::<Result<Vec<_>, _>>()?,
791                flags: bundle.flags,
792                value_sum: bundle.value_sum,
793                anchor,
794                zkproof: bundle.zkproof,
795                bsk: bundle.bsk,
796            })
797        }
798    }
799
800    impl From<Bundle> for super::Bundle {
801        fn from(bundle: Bundle) -> Self {
802            // The v1 encoding has no placeholder for a missing anchor, so an empty
803            // bundle's anchor is substituted with `DEFAULT_ANCHOR` on encode (see
804            // `TryFrom<super::Bundle> for Bundle` above). Undo that substitution here
805            // so that decoding an empty bundle reproduces `super::EMPTY_ORCHARD`
806            // exactly, mirroring the `is_default_empty` treatment the v2 encoder
807            // applies for the same case.
808            let anchor = if bundle.actions.is_empty() && bundle.anchor == super::DEFAULT_ANCHOR {
809                None
810            } else {
811                Some(bundle.anchor)
812            };
813
814            Self {
815                actions: bundle
816                    .actions
817                    .into_iter()
818                    .map(super::Action::from)
819                    .collect(),
820                flags: bundle.flags,
821                value_sum: bundle.value_sum,
822                anchor,
823                note_version: NoteVersion::V2,
824                zkproof: bundle.zkproof,
825                bsk: bundle.bsk,
826            }
827        }
828    }
829
830    impl TryFrom<super::Action> for Action {
831        type Error = crate::EncodingError;
832
833        fn try_from(action: super::Action) -> Result<Self, Self::Error> {
834            Ok(Self {
835                cv_net: action.cv_net.ok_or(crate::EncodingError::RequiresV2)?,
836                spend: Spend::from(action.spend),
837                output: Output::try_from(action.output)?,
838                rcv: action.rcv,
839            })
840        }
841    }
842
843    impl From<Action> for super::Action {
844        fn from(action: Action) -> Self {
845            Self {
846                cv_net: Some(action.cv_net),
847                spend: super::Spend::from(action.spend),
848                output: super::Output::from(action.output),
849                rcv: action.rcv,
850            }
851        }
852    }
853
854    impl From<super::Spend> for Spend {
855        fn from(spend: super::Spend) -> Self {
856            Self {
857                nullifier: spend.nullifier,
858                rk: spend.rk,
859                spend_auth_sig: spend.spend_auth_sig,
860                recipient: spend.recipient,
861                value: spend.value,
862                rho: spend.rho,
863                rseed: spend.rseed,
864                fvk: spend.fvk,
865                witness: spend.witness,
866                alpha: spend.alpha,
867                zip32_derivation: spend.zip32_derivation,
868                dummy_sk: spend.dummy_sk,
869                proprietary: spend.proprietary,
870            }
871        }
872    }
873
874    impl From<Spend> for super::Spend {
875        fn from(spend: Spend) -> Self {
876            Self {
877                nullifier: spend.nullifier,
878                rk: spend.rk,
879                spend_auth_sig: spend.spend_auth_sig,
880                recipient: spend.recipient,
881                value: spend.value,
882                rho: spend.rho,
883                rseed: spend.rseed,
884                fvk: spend.fvk,
885                witness: spend.witness,
886                alpha: spend.alpha,
887                zip32_derivation: spend.zip32_derivation,
888                dummy_sk: spend.dummy_sk,
889                proprietary: spend.proprietary,
890            }
891        }
892    }
893
894    impl TryFrom<super::Output> for Output {
895        type Error = crate::EncodingError;
896
897        fn try_from(output: super::Output) -> Result<Self, Self::Error> {
898            let enc_ciphertext = output
899                .enc_ciphertext
900                .into_encrypted()
901                .ok_or(crate::EncodingError::RequiresV2)?;
902
903            Ok(Self {
904                cmx: output.cmx.ok_or(crate::EncodingError::RequiresV2)?,
905                ephemeral_key: output.ephemeral_key,
906                enc_ciphertext,
907                out_ciphertext: output.out_ciphertext,
908                recipient: output.recipient,
909                value: output.value,
910                rseed: output.rseed,
911                ock: output.ock,
912                zip32_derivation: output.zip32_derivation,
913                user_address: output.user_address,
914                proprietary: output.proprietary,
915            })
916        }
917    }
918
919    impl From<Output> for super::Output {
920        fn from(output: Output) -> Self {
921            Self {
922                cmx: Some(output.cmx),
923                ephemeral_key: output.ephemeral_key,
924                enc_ciphertext: super::EncCiphertext::Encrypted(output.enc_ciphertext),
925                out_ciphertext: output.out_ciphertext,
926                recipient: output.recipient,
927                value: output.value,
928                rseed: output.rseed,
929                ock: output.ock,
930                zip32_derivation: output.zip32_derivation,
931                user_address: output.user_address,
932                proprietary: output.proprietary,
933            }
934        }
935    }
936}
937
938/// Types for the v2 Orchard PCZT encoding.
939pub(crate) mod v2 {
940    use alloc::{collections::BTreeMap, string::String, vec::Vec};
941
942    use getset::Getters;
943    use serde::{Deserialize, Serialize};
944    use serde_with::serde_as;
945
946    use super::NoteVersion;
947
948    /// A serializable representation of Orchard note plaintext versions.
949    #[derive(Clone, Copy, Debug, Serialize, Deserialize)]
950    enum SerializedNoteVersion {
951        V2,
952        V3,
953    }
954
955    impl From<NoteVersion> for SerializedNoteVersion {
956        fn from(note_version: NoteVersion) -> Self {
957            match note_version {
958                NoteVersion::V2 => Self::V2,
959                NoteVersion::V3 => Self::V3,
960            }
961        }
962    }
963
964    impl From<SerializedNoteVersion> for NoteVersion {
965        fn from(note_version: SerializedNoteVersion) -> Self {
966            match note_version {
967                SerializedNoteVersion::V2 => Self::V2,
968                SerializedNoteVersion::V3 => Self::V3,
969            }
970        }
971    }
972
973    /// PCZT fields that are specific to producing the transaction's Orchard bundle.
974    #[derive(Clone, Debug, Serialize, Deserialize, Getters)]
975    pub struct Bundle {
976        actions: Vec<Action>,
977        flags: u8,
978        value_sum: (u64, bool),
979        anchor: Option<[u8; 32]>,
980        note_version: SerializedNoteVersion,
981        zkproof: Option<Vec<u8>>,
982        bsk: Option<crate::common::SecretKeyBytes>,
983    }
984
985    /// Information about an Orchard action within a transaction.
986    #[derive(Clone, Debug, Serialize, Deserialize)]
987    pub(crate) struct Action {
988        cv_net: Option<[u8; 32]>,
989        spend: Spend,
990        output: Output,
991        rcv: Option<[u8; 32]>,
992    }
993
994    /// Information about the spend part of an Orchard action.
995    #[serde_as]
996    #[derive(Clone, Debug, Serialize, Deserialize)]
997    pub(crate) struct Spend {
998        #[serde_as(as = "Option<[_; 32]>")]
999        nullifier: Option<[u8; 32]>,
1000        #[serde_as(as = "Option<[_; 32]>")]
1001        rk: Option<[u8; 32]>,
1002        #[serde_as(as = "Option<[_; 64]>")]
1003        spend_auth_sig: Option<[u8; 64]>,
1004        #[serde_as(as = "Option<[_; 43]>")]
1005        recipient: Option<[u8; 43]>,
1006        value: Option<u64>,
1007        rho: Option<[u8; 32]>,
1008        rseed: Option<[u8; 32]>,
1009        #[serde_as(as = "Option<[_; 96]>")]
1010        fvk: Option<[u8; 96]>,
1011        witness: Option<(u32, [[u8; 32]; 32])>,
1012        alpha: Option<[u8; 32]>,
1013        zip32_derivation: Option<crate::common::Zip32Derivation>,
1014        dummy_sk: Option<crate::common::SecretKeyBytes>,
1015        proprietary: BTreeMap<String, Vec<u8>>,
1016    }
1017
1018    /// Information about the output part of an Orchard action.
1019    #[serde_as]
1020    #[derive(Clone, Debug, Serialize, Deserialize)]
1021    pub(crate) struct Output {
1022        cmx: Option<[u8; 32]>,
1023        ephemeral_key: [u8; 32],
1024        enc_ciphertext: super::EncCiphertext,
1025        out_ciphertext: Vec<u8>,
1026        #[serde_as(as = "Option<[_; 43]>")]
1027        recipient: Option<[u8; 43]>,
1028        value: Option<u64>,
1029        rseed: Option<[u8; 32]>,
1030        ock: Option<[u8; 32]>,
1031        zip32_derivation: Option<crate::common::Zip32Derivation>,
1032        user_address: Option<String>,
1033        proprietary: BTreeMap<String, Vec<u8>>,
1034    }
1035
1036    impl TryFrom<super::Bundle> for Bundle {
1037        type Error = crate::EncodingError;
1038
1039        fn try_from(bundle: super::Bundle) -> Result<Self, Self::Error> {
1040            Ok(Self {
1041                actions: bundle
1042                    .actions
1043                    .into_iter()
1044                    .map(Action::from)
1045                    .collect::<Vec<_>>(),
1046                flags: bundle.flags,
1047                value_sum: bundle.value_sum,
1048                anchor: bundle.anchor,
1049                note_version: bundle.note_version.into(),
1050                zkproof: bundle.zkproof,
1051                bsk: bundle.bsk,
1052            })
1053        }
1054    }
1055
1056    impl Bundle {
1057        pub(crate) fn into_logical(self) -> Result<super::Bundle, crate::ParseError> {
1058            Ok(super::Bundle {
1059                actions: self
1060                    .actions
1061                    .into_iter()
1062                    .map(Action::into_logical)
1063                    .collect::<Result<Vec<_>, _>>()?,
1064                flags: self.flags,
1065                value_sum: self.value_sum,
1066                anchor: self.anchor,
1067                note_version: self.note_version.into(),
1068                zkproof: self.zkproof,
1069                bsk: self.bsk,
1070            })
1071        }
1072    }
1073
1074    impl From<super::Action> for Action {
1075        fn from(action: super::Action) -> Self {
1076            Self {
1077                cv_net: action.cv_net,
1078                spend: Spend::from(action.spend),
1079                output: Output::from(action.output),
1080                rcv: action.rcv,
1081            }
1082        }
1083    }
1084
1085    impl Action {
1086        fn into_logical(self) -> Result<super::Action, crate::ParseError> {
1087            Ok(super::Action {
1088                cv_net: self.cv_net,
1089                spend: self.spend.into_logical()?,
1090                output: super::Output::from(self.output),
1091                rcv: self.rcv,
1092            })
1093        }
1094    }
1095
1096    impl From<super::Spend> for Spend {
1097        fn from(spend: super::Spend) -> Self {
1098            Self {
1099                nullifier: Some(spend.nullifier),
1100                rk: Some(spend.rk),
1101                spend_auth_sig: spend.spend_auth_sig,
1102                recipient: spend.recipient,
1103                value: spend.value,
1104                rho: spend.rho,
1105                rseed: spend.rseed,
1106                fvk: spend.fvk,
1107                witness: spend.witness,
1108                alpha: spend.alpha,
1109                zip32_derivation: spend.zip32_derivation,
1110                dummy_sk: spend.dummy_sk,
1111                proprietary: spend.proprietary,
1112            }
1113        }
1114    }
1115
1116    impl Spend {
1117        fn into_logical(self) -> Result<super::Spend, crate::ParseError> {
1118            Ok(super::Spend {
1119                nullifier: self
1120                    .nullifier
1121                    .ok_or(crate::ParseError::MissingRequiredField(
1122                        "orchard.actions[].spend.nullifier",
1123                    ))?,
1124                rk: self.rk.ok_or(crate::ParseError::MissingRequiredField(
1125                    "orchard.actions[].spend.rk",
1126                ))?,
1127                spend_auth_sig: self.spend_auth_sig,
1128                recipient: self.recipient,
1129                value: self.value,
1130                rho: self.rho,
1131                rseed: self.rseed,
1132                fvk: self.fvk,
1133                witness: self.witness,
1134                alpha: self.alpha,
1135                zip32_derivation: self.zip32_derivation,
1136                dummy_sk: self.dummy_sk,
1137                proprietary: self.proprietary,
1138            })
1139        }
1140    }
1141
1142    impl From<super::Output> for Output {
1143        fn from(output: super::Output) -> Self {
1144            Self {
1145                cmx: output.cmx,
1146                ephemeral_key: output.ephemeral_key,
1147                enc_ciphertext: output.enc_ciphertext,
1148                out_ciphertext: output.out_ciphertext,
1149                recipient: output.recipient,
1150                value: output.value,
1151                rseed: output.rseed,
1152                ock: output.ock,
1153                zip32_derivation: output.zip32_derivation,
1154                user_address: output.user_address,
1155                proprietary: output.proprietary,
1156            }
1157        }
1158    }
1159
1160    impl From<Output> for super::Output {
1161        fn from(output: Output) -> Self {
1162            Self {
1163                cmx: output.cmx,
1164                ephemeral_key: output.ephemeral_key,
1165                enc_ciphertext: output.enc_ciphertext,
1166                out_ciphertext: output.out_ciphertext,
1167                recipient: output.recipient,
1168                value: output.value,
1169                rseed: output.rseed,
1170                ock: output.ock,
1171                zip32_derivation: output.zip32_derivation,
1172                user_address: output.user_address,
1173                proprietary: output.proprietary,
1174            }
1175        }
1176    }
1177
1178    /// Encodes a logical Orchard-protocol bundle for the v2 PCZT format, owning the
1179    /// decision of whether the bundle can be omitted. A bundle that is exactly equal
1180    /// to `empty` (the canonical empty bundle for its slot, [`super::EMPTY_ORCHARD`]
1181    /// or [`super::EMPTY_IRONWOOD`]) serializes to `None` and is dropped from the
1182    /// encoding. An otherwise-empty bundle carrying [`super::DEFAULT_ANCHOR`] is
1183    /// treated as empty for this purpose; any other bundle is converted via the
1184    /// [`Bundle`]-producing [`TryFrom`] impl. The reverse direction is
1185    /// [`From<Bundle>`] plus the canonical empty bundle for the omitted case.
1186    pub(crate) fn encode(
1187        bundle: super::Bundle,
1188        empty: &super::Bundle,
1189    ) -> Result<Option<Bundle>, crate::EncodingError> {
1190        (!is_default_empty(&bundle, empty))
1191            .then(|| Bundle::try_from(bundle))
1192            .transpose()
1193    }
1194
1195    fn is_default_empty(bundle: &super::Bundle, empty: &super::Bundle) -> bool {
1196        let mut bundle = bundle.clone();
1197        if bundle.anchor == Some(super::DEFAULT_ANCHOR) {
1198            bundle.anchor = None;
1199        }
1200
1201        bundle == *empty
1202    }
1203
1204    #[cfg(test)]
1205    mod tests {
1206        use alloc::{collections::BTreeMap, vec::Vec};
1207
1208        #[cfg(feature = "orchard")]
1209        use {
1210            crate::roles::{creator::Creator, redactor::Redactor},
1211            ::orchard::{
1212                Note,
1213                keys::{FullViewingKey, Scope, SpendingKey},
1214                note::{ExtractedNoteCommitment, RandomSeed, Rho},
1215                note_encryption::{OrchardDomain, OrchardNoteEncryption},
1216                value::NoteValue,
1217            },
1218            zcash_note_encryption::Domain,
1219            zcash_protocol::consensus::BranchId,
1220        };
1221
1222        use super::super::{
1223            Action as LogicalAction, Bundle as LogicalBundle, EMPTY_ORCHARD, EncCiphertext,
1224            MEMO_SIZE, MemoPlaintext, NoteVersion, ORCHARD_SPENDS_AND_OUTPUTS_ENABLED, Output,
1225            Spend,
1226        };
1227
1228        fn logical_action(cv_net: Option<[u8; 32]>, cmx: Option<[u8; 32]>) -> LogicalAction {
1229            LogicalAction {
1230                cv_net,
1231                spend: Spend {
1232                    nullifier: [1; 32],
1233                    rk: [2; 32],
1234                    spend_auth_sig: None,
1235                    recipient: None,
1236                    value: None,
1237                    rho: None,
1238                    rseed: None,
1239                    fvk: None,
1240                    witness: None,
1241                    alpha: None,
1242                    zip32_derivation: None,
1243                    dummy_sk: None,
1244                    proprietary: BTreeMap::new(),
1245                },
1246                output: Output {
1247                    cmx,
1248                    ephemeral_key: [4; 32],
1249                    enc_ciphertext: EncCiphertext::Encrypted(Vec::new()),
1250                    out_ciphertext: Vec::new(),
1251                    recipient: None,
1252                    value: None,
1253                    rseed: None,
1254                    ock: None,
1255                    zip32_derivation: None,
1256                    user_address: None,
1257                    proprietary: BTreeMap::new(),
1258                },
1259                rcv: None,
1260            }
1261        }
1262
1263        fn logical_bundle(anchor: Option<[u8; 32]>, cv_net: Option<[u8; 32]>) -> LogicalBundle {
1264            logical_bundle_with_cmx(anchor, cv_net, Some([3; 32]))
1265        }
1266
1267        fn logical_bundle_with_cmx(
1268            anchor: Option<[u8; 32]>,
1269            cv_net: Option<[u8; 32]>,
1270            cmx: Option<[u8; 32]>,
1271        ) -> LogicalBundle {
1272            LogicalBundle {
1273                actions: vec![logical_action(cv_net, cmx)],
1274                flags: ORCHARD_SPENDS_AND_OUTPUTS_ENABLED,
1275                value_sum: (0, false),
1276                anchor,
1277                note_version: NoteVersion::V2,
1278                zkproof: None,
1279                bsk: None,
1280            }
1281        }
1282
1283        #[test]
1284        fn anchor_cv_net_and_cmx_round_trip_optional_encoding() {
1285            for (anchor, cv_net, cmx) in [
1286                (None, None, None),
1287                (Some([5; 32]), Some([6; 32]), Some([7; 32])),
1288            ] {
1289                let bundle = logical_bundle_with_cmx(anchor, cv_net, cmx);
1290
1291                let encoded = super::Bundle::try_from(bundle.clone()).unwrap();
1292                assert_eq!(encoded.anchor, anchor);
1293                assert_eq!(encoded.actions[0].cv_net, cv_net);
1294                assert_eq!(encoded.actions[0].output.cmx, cmx);
1295
1296                let decoded = encoded.into_logical().unwrap();
1297                assert_eq!(decoded, bundle);
1298            }
1299        }
1300
1301        #[test]
1302        fn missing_spend_nullifier_or_rk_is_rejected() {
1303            let bundle = logical_bundle(Some([5; 32]), Some([6; 32]));
1304
1305            for (clear_field, missing_field) in [
1306                (
1307                    (|spend: &mut super::Spend| spend.nullifier = None) as fn(&mut super::Spend),
1308                    "orchard.actions[].spend.nullifier",
1309                ),
1310                (
1311                    |spend: &mut super::Spend| spend.rk = None,
1312                    "orchard.actions[].spend.rk",
1313                ),
1314            ] {
1315                let mut encoded = super::Bundle::try_from(bundle.clone()).unwrap();
1316                assert_eq!(encoded.actions[0].spend.nullifier, Some([1; 32]));
1317                assert_eq!(encoded.actions[0].spend.rk, Some([2; 32]));
1318
1319                clear_field(&mut encoded.actions[0].spend);
1320
1321                assert!(matches!(
1322                    encoded.into_logical(),
1323                    Err(crate::ParseError::MissingRequiredField(field)) if field == missing_field
1324                ));
1325            }
1326        }
1327
1328        #[test]
1329        fn memo_plaintext_strips_and_expands_trailing_zeroes() {
1330            let mut memo = [0; MEMO_SIZE];
1331            memo[..5].copy_from_slice(b"hello");
1332
1333            let plaintext = MemoPlaintext::from_memo(memo);
1334
1335            assert_eq!(plaintext.as_stripped_bytes(), b"hello");
1336            assert_eq!(plaintext.to_memo(), memo);
1337        }
1338
1339        #[cfg(feature = "orchard")]
1340        fn decryptable_action_with_memo(memo: [u8; MEMO_SIZE]) -> LogicalAction {
1341            let mut nullifier = [0; 32];
1342            nullifier[0] = 1;
1343            let rho = Option::from(Rho::from_bytes(&nullifier)).unwrap();
1344            let (_, rseed) = (0u8..)
1345                .find_map(|i| {
1346                    let mut rseed = [0; 32];
1347                    rseed[0] = i;
1348                    Option::from(RandomSeed::from_bytes(rseed, &rho)).map(|parsed| (rseed, parsed))
1349                })
1350                .unwrap();
1351            let recipient = FullViewingKey::from(&SpendingKey::from_bytes([0; 32]).unwrap())
1352                .address_at(0u32, Scope::External);
1353            let value = NoteValue::from_raw(100_000);
1354            let note = Option::from(Note::from_parts(
1355                recipient,
1356                value,
1357                rho,
1358                rseed,
1359                NoteVersion::V2,
1360            ))
1361            .unwrap();
1362
1363            let encryptor = OrchardNoteEncryption::new(None, note, memo);
1364
1365            LogicalAction {
1366                cv_net: Some([0; 32]),
1367                spend: Spend {
1368                    nullifier,
1369                    rk: [2; 32],
1370                    spend_auth_sig: None,
1371                    recipient: None,
1372                    value: None,
1373                    rho: None,
1374                    rseed: None,
1375                    fvk: None,
1376                    witness: None,
1377                    alpha: None,
1378                    zip32_derivation: None,
1379                    dummy_sk: None,
1380                    proprietary: BTreeMap::new(),
1381                },
1382                output: Output {
1383                    cmx: Some(ExtractedNoteCommitment::from(note.commitment()).to_bytes()),
1384                    ephemeral_key: OrchardDomain::epk_bytes(encryptor.epk()).0,
1385                    enc_ciphertext: EncCiphertext::Encrypted(
1386                        encryptor.encrypt_note_plaintext().0.to_vec(),
1387                    ),
1388                    out_ciphertext: Vec::new(),
1389                    recipient: Some(recipient.to_raw_address_bytes()),
1390                    value: Some(value.inner()),
1391                    rseed: Some(*note.rseed().as_bytes()),
1392                    ock: None,
1393                    zip32_derivation: None,
1394                    user_address: None,
1395                    proprietary: BTreeMap::new(),
1396                },
1397                rcv: None,
1398            }
1399        }
1400
1401        /// A fabricated same-address output, of the shape [ZIP 326] requires: a
1402        /// zero-valued note addressed to the spent note's own receiver, whose
1403        /// `enc_ciphertext` is bytes that do not decrypt to it rather than an
1404        /// encryption of the note plaintext. `recipient`, `value`, and `rseed` stay
1405        /// explicit, `user_address` is absent, and the note commitment is unchanged.
1406        ///
1407        /// [ZIP 326]: https://zips.z.cash/zip-0326#fabricatedsame-addressoutputsandrandomizednoteciphertexts
1408        #[cfg(feature = "orchard")]
1409        fn fabricated_same_address_action() -> LogicalAction {
1410            let mut nullifier = [0; 32];
1411            nullifier[0] = 1;
1412            let rho = Option::from(Rho::from_bytes(&nullifier)).unwrap();
1413            let (_, rseed) = (0u8..)
1414                .find_map(|i| {
1415                    let mut rseed = [0; 32];
1416                    rseed[0] = i;
1417                    Option::from(RandomSeed::from_bytes(rseed, &rho)).map(|parsed| (rseed, parsed))
1418                })
1419                .unwrap();
1420            let recipient = FullViewingKey::from(&SpendingKey::from_bytes([0; 32]).unwrap())
1421                .address_at(0u32, Scope::External);
1422            let value = NoteValue::from_raw(0);
1423            let note = Option::from(Note::from_parts(
1424                recipient,
1425                value,
1426                rho,
1427                rseed,
1428                NoteVersion::V2,
1429            ))
1430            .unwrap();
1431
1432            // Only the ephemeral key is taken from the encryptor; the ciphertext itself
1433            // is replaced with bytes that trial-decryption cannot recover a note from.
1434            let encryptor = OrchardNoteEncryption::new(None, note, [0; MEMO_SIZE]);
1435            let mut enc_ciphertext = encryptor.encrypt_note_plaintext().0.to_vec();
1436            enc_ciphertext.fill(0xab);
1437
1438            LogicalAction {
1439                cv_net: Some([0; 32]),
1440                spend: Spend {
1441                    nullifier,
1442                    rk: [2; 32],
1443                    spend_auth_sig: None,
1444                    recipient: None,
1445                    value: None,
1446                    rho: None,
1447                    rseed: None,
1448                    fvk: None,
1449                    witness: None,
1450                    alpha: None,
1451                    zip32_derivation: None,
1452                    dummy_sk: None,
1453                    proprietary: BTreeMap::new(),
1454                },
1455                output: Output {
1456                    cmx: Some(ExtractedNoteCommitment::from(note.commitment()).to_bytes()),
1457                    ephemeral_key: OrchardDomain::epk_bytes(encryptor.epk()).0,
1458                    enc_ciphertext: EncCiphertext::Encrypted(enc_ciphertext),
1459                    out_ciphertext: Vec::new(),
1460                    recipient: Some(recipient.to_raw_address_bytes()),
1461                    value: Some(value.inner()),
1462                    rseed: Some(*note.rseed().as_bytes()),
1463                    ock: None,
1464                    zip32_derivation: None,
1465                    user_address: None,
1466                    proprietary: BTreeMap::new(),
1467                },
1468                rcv: None,
1469            }
1470        }
1471
1472        /// Compaction leaves a fabricated same-address output's randomized ciphertext
1473        /// byte-identical: it cannot be decrypted, so there is no memo plaintext to
1474        /// stand in for it, and re-encrypting the note would not reproduce it. The
1475        /// derivable `cmx` is still compacted, since the note itself is ordinary.
1476        #[cfg(feature = "orchard")]
1477        #[test]
1478        fn resolvable_field_compaction_retains_randomized_ciphertext() {
1479            let mut action = fabricated_same_address_action();
1480            let original_enc_ciphertext = action.output.enc_ciphertext.clone();
1481
1482            action.compact_resolvable_fields(NoteVersion::V2);
1483
1484            assert_eq!(action.output.enc_ciphertext, original_enc_ciphertext);
1485            assert_eq!(action.output.cmx, None);
1486        }
1487
1488        /// Resolving a bundle carrying a fabricated same-address output succeeds and
1489        /// leaves the randomized ciphertext untouched, so a round trip through the
1490        /// compact signer view does not corrupt it.
1491        #[cfg(feature = "orchard")]
1492        #[test]
1493        fn resolve_fields_preserves_randomized_ciphertext() {
1494            let action = fabricated_same_address_action();
1495            let original_enc_ciphertext = action.output.enc_ciphertext.clone();
1496            let mut bundle = LogicalBundle {
1497                actions: vec![action],
1498                flags: ORCHARD_SPENDS_AND_OUTPUTS_ENABLED,
1499                value_sum: (0, false),
1500                anchor: None,
1501                note_version: NoteVersion::V2,
1502                zkproof: None,
1503                bsk: None,
1504            };
1505            bundle.actions[0].output.cmx = None;
1506
1507            bundle.resolve_fields().unwrap();
1508
1509            assert_eq!(
1510                bundle.actions[0].output.enc_ciphertext,
1511                original_enc_ciphertext
1512            );
1513            assert!(bundle.actions[0].output.cmx.is_some());
1514        }
1515
1516        #[cfg(feature = "orchard")]
1517        #[test]
1518        fn v2_round_trips_memo_plaintext_ciphertext_data() {
1519            const HELLO_MEMO_PAYLOAD_SIZE_REDUCTION: usize = 575;
1520            // The serialized reduction is one byte larger because postcard uses
1521            // a shorter length prefix for the stripped memo plaintext.
1522            const HELLO_MEMO_SERIALIZED_SIZE_REDUCTION: usize =
1523                HELLO_MEMO_PAYLOAD_SIZE_REDUCTION + 1;
1524
1525            let mut memo = [0; MEMO_SIZE];
1526            memo[..5].copy_from_slice(b"hello");
1527
1528            let mut pczt = Creator::new(
1529                BranchId::Nu6.into(),
1530                10_000_000,
1531                133,
1532                Some([0; 32]),
1533                Some([0; 32]),
1534            )
1535            .unwrap()
1536            .build()
1537            .unwrap();
1538            pczt.orchard
1539                .actions
1540                .push(decryptable_action_with_memo(memo));
1541
1542            // Pin both sides to the v2 encoding explicitly: `Pczt::serialize` now
1543            // picks the minimal encoding capable of representing its content, and
1544            // this comparison is measuring the size effect of the memo-plaintext
1545            // compaction, not of the encoding version chosen for either PCZT.
1546            let encrypted_size = crate::v2::Pczt::try_from(pczt.clone())
1547                .unwrap()
1548                .serialize()
1549                .len();
1550            let redacted = Redactor::new(pczt)
1551                .redact_orchard_with(|mut orchard| {
1552                    orchard.redact_actions(|mut action| {
1553                        action
1554                            .replace_enc_ciphertext_with_decrypted_memo_plaintext(NoteVersion::V2);
1555                    });
1556                })
1557                .finish();
1558            let redacted_size = crate::v2::Pczt::try_from(redacted.clone())
1559                .unwrap()
1560                .serialize()
1561                .len();
1562
1563            assert_eq!(
1564                redacted.orchard.actions[0].output.enc_ciphertext,
1565                EncCiphertext::MemoPlaintext(MemoPlaintext::from_memo(memo))
1566            );
1567            assert_eq!(
1568                encrypted_size - redacted_size,
1569                HELLO_MEMO_SERIALIZED_SIZE_REDUCTION
1570            );
1571
1572            let decoded = crate::parse(&redacted.serialize().unwrap()).unwrap();
1573
1574            assert_eq!(
1575                decoded.orchard.actions[0].output.enc_ciphertext,
1576                EncCiphertext::MemoPlaintext(MemoPlaintext::from_memo(memo))
1577            );
1578        }
1579
1580        #[cfg(feature = "orchard")]
1581        #[test]
1582        fn resolve_fields_recomputes_cmx() {
1583            let action = decryptable_action_with_memo([0; MEMO_SIZE]);
1584            let expected_cmx = action.output.cmx;
1585            let mut bundle = LogicalBundle {
1586                actions: vec![action],
1587                flags: ORCHARD_SPENDS_AND_OUTPUTS_ENABLED,
1588                value_sum: (0, false),
1589                anchor: None,
1590                note_version: NoteVersion::V2,
1591                zkproof: None,
1592                bsk: None,
1593            };
1594            bundle.actions[0].output.cmx = None;
1595
1596            bundle.resolve_fields().unwrap();
1597
1598            assert_eq!(bundle.actions[0].output.cmx, expected_cmx);
1599        }
1600
1601        #[cfg(feature = "orchard")]
1602        #[test]
1603        fn decrypted_memo_plaintext_compaction_skips_decryption_failure() {
1604            let mut action = decryptable_action_with_memo([0; MEMO_SIZE]);
1605            let original_enc_ciphertext = match &mut action.output.enc_ciphertext {
1606                EncCiphertext::Encrypted(enc_ciphertext) => {
1607                    enc_ciphertext[0] ^= 1;
1608                    enc_ciphertext.clone()
1609                }
1610                EncCiphertext::MemoPlaintext(_) => unreachable!("helper encrypts memo plaintext"),
1611            };
1612
1613            action.replace_enc_ciphertext_with_decrypted_memo_plaintext(NoteVersion::V2);
1614
1615            assert_eq!(
1616                action.output.enc_ciphertext,
1617                EncCiphertext::Encrypted(original_enc_ciphertext)
1618            );
1619        }
1620
1621        #[cfg(feature = "orchard")]
1622        #[test]
1623        fn resolvable_field_compaction_checks_derived_values() {
1624            let mut memo = [0; MEMO_SIZE];
1625            memo[..5].copy_from_slice(b"hello");
1626            let mut action = decryptable_action_with_memo(memo);
1627            action.spend.value = Some(200_000);
1628            action.rcv = Some([3; 32]);
1629            action.cv_net = Some(super::super::testing::value_commitment(100_000, [3; 32]));
1630
1631            action.compact_resolvable_fields(NoteVersion::V2);
1632
1633            assert_eq!(action.cv_net, None);
1634            assert_eq!(action.output.cmx, None);
1635            assert_eq!(
1636                action.output.enc_ciphertext,
1637                EncCiphertext::MemoPlaintext(MemoPlaintext::from_memo(memo))
1638            );
1639        }
1640
1641        #[cfg(feature = "orchard")]
1642        #[test]
1643        fn resolvable_field_compaction_retains_unverifiable_values() {
1644            let mut memo = [0; MEMO_SIZE];
1645            memo[..5].copy_from_slice(b"hello");
1646            let mut action = decryptable_action_with_memo(memo);
1647            let original_cv_net = action.cv_net;
1648            let original_cmx = action.output.cmx;
1649            let original_enc_ciphertext = action.output.enc_ciphertext.clone();
1650            action.output.recipient = None;
1651
1652            action.compact_resolvable_fields(NoteVersion::V2);
1653
1654            assert_eq!(action.cv_net, original_cv_net);
1655            assert_eq!(action.output.cmx, original_cmx);
1656            assert_eq!(action.output.enc_ciphertext, original_enc_ciphertext);
1657        }
1658
1659        #[cfg(feature = "orchard")]
1660        #[test]
1661        fn resolvable_field_compaction_retains_mismatched_values() {
1662            let mut memo = [0; MEMO_SIZE];
1663            memo[..5].copy_from_slice(b"hello");
1664            let mut action = decryptable_action_with_memo(memo);
1665            action.spend.value = Some(200_000);
1666            action.rcv = Some([3; 32]);
1667            let mut cv_net = super::super::testing::value_commitment(100_000, [3; 32]);
1668            cv_net[0] ^= 1;
1669            action.cv_net = Some(cv_net);
1670            let mut cmx = action.output.cmx.unwrap();
1671            cmx[0] ^= 1;
1672            action.output.cmx = Some(cmx);
1673            let original_enc_ciphertext = action.output.enc_ciphertext.clone();
1674
1675            action.compact_resolvable_fields(NoteVersion::V2);
1676
1677            assert_eq!(action.cv_net, Some(cv_net));
1678            assert_eq!(action.output.cmx, Some(cmx));
1679            assert_eq!(action.output.enc_ciphertext, original_enc_ciphertext);
1680        }
1681
1682        #[test]
1683        fn v1_rejects_memo_plaintext_ciphertext_data() {
1684            let mut bundle = logical_bundle(Some([5; 32]), Some([6; 32]));
1685            bundle.actions[0].output.enc_ciphertext =
1686                EncCiphertext::MemoPlaintext(MemoPlaintext::from_memo([0; MEMO_SIZE]));
1687
1688            assert!(matches!(
1689                crate::orchard::v1::Bundle::try_from(bundle),
1690                Err(crate::EncodingError::RequiresV2)
1691            ));
1692        }
1693
1694        #[test]
1695        fn v1_rejects_missing_anchor_and_cv_net() {
1696            assert!(matches!(
1697                crate::orchard::v1::Bundle::try_from(logical_bundle(None, Some([6; 32]))),
1698                Err(crate::EncodingError::RequiresV2)
1699            ));
1700
1701            assert!(matches!(
1702                crate::orchard::v1::Bundle::try_from(logical_bundle(Some([5; 32]), None)),
1703                Err(crate::EncodingError::RequiresV2)
1704            ));
1705
1706            assert!(matches!(
1707                crate::orchard::v1::Bundle::try_from(logical_bundle_with_cmx(
1708                    Some([5; 32]),
1709                    Some([6; 32]),
1710                    None
1711                )),
1712                Err(crate::EncodingError::RequiresV2)
1713            ));
1714        }
1715
1716        #[test]
1717        fn v1_empty_bundle_anchor_falls_back_to_default() {
1718            // An empty bundle has no anchor to commit to, so the v1 encoding (whose
1719            // anchor field is mandatory) falls back to `DEFAULT_ANCHOR`.
1720            let bundle = LogicalBundle {
1721                actions: Vec::new(),
1722                flags: ORCHARD_SPENDS_AND_OUTPUTS_ENABLED,
1723                value_sum: (0, false),
1724                anchor: None,
1725                note_version: NoteVersion::V2,
1726                zkproof: None,
1727                bsk: None,
1728            };
1729
1730            let encoded = crate::orchard::v1::Bundle::try_from(bundle)
1731                .expect("an empty bundle's anchor falls back to DEFAULT_ANCHOR");
1732
1733            // Decoding must undo the fallback substitution, reproducing the logical
1734            // empty bundle exactly rather than materializing the placeholder anchor.
1735            let decoded = super::super::Bundle::from(encoded);
1736            assert_eq!(decoded, EMPTY_ORCHARD);
1737        }
1738    }
1739}
1740
1741impl Bundle {
1742    /// This bundle's sole action, or `None` if it does not have exactly one.
1743    pub fn sole_action(&self) -> Option<&Action> {
1744        match self.actions.as_slice() {
1745            [action] => Some(action),
1746            _ => None,
1747        }
1748    }
1749
1750    /// Whether every output of this bundle that carries value pays `recipient`, given as the [raw
1751    /// encoding] of an Orchard payment address, which is what makes the bundle a send-to-self.
1752    ///
1753    /// Zero-valued padding dummies are excluded: a Constructor fabricates them to bring the bundle
1754    /// up to a required action count and they pay nobody in particular, so counting them would make
1755    /// every padded bundle fail. Returns `None` if any output has had the value or the recipient it
1756    /// would be judged on redacted, since the question cannot then be answered rather than answered
1757    /// negatively.
1758    ///
1759    /// [raw encoding]: https://zips.z.cash/protocol/protocol.pdf#orchardpaymentaddrencoding
1760    pub fn value_carrying_outputs_all_pay(&self, recipient: &[u8; 43]) -> Option<bool> {
1761        for action in &self.actions {
1762            let output = &action.output;
1763            if output.value? == 0 {
1764                continue;
1765            }
1766            if &output.recipient? != recipient {
1767                return Some(false);
1768            }
1769        }
1770        Some(true)
1771    }
1772
1773    /// Merges this bundle with another.
1774    ///
1775    /// Returns `None` if the bundles have conflicting data.
1776    pub(crate) fn merge(
1777        mut self,
1778        other: Self,
1779        self_global: &Global,
1780        other_global: &Global,
1781    ) -> Option<Self> {
1782        // Destructure `other` to ensure we handle everything.
1783        let Self {
1784            mut actions,
1785            flags,
1786            value_sum,
1787            anchor,
1788            note_version,
1789            zkproof,
1790            bsk,
1791        } = other;
1792
1793        if self.flags != flags || self.note_version != note_version {
1794            return None;
1795        }
1796
1797        // If `bsk` is set on either bundle, the IO Finalizer has run, which means we
1798        // cannot have differing numbers of actions, and the value sums must match.
1799        match (self.bsk.as_mut(), bsk) {
1800            (Some(lhs), Some(rhs)) if lhs != &rhs => return None,
1801            (Some(_), _) | (_, Some(_))
1802                if self.actions.len() != actions.len() || self.value_sum != value_sum =>
1803            {
1804                return None;
1805            }
1806            // IO Finalizer has run, and neither bundle has excess spends or outputs.
1807            (Some(_), _) | (_, Some(_)) => (),
1808            // IO Finalizer has not run on either bundle.
1809            (None, None) => match (
1810                self_global.shielded_modifiable(),
1811                other_global.shielded_modifiable(),
1812                self.actions.len().cmp(&actions.len()),
1813            ) {
1814                // Fail if the merge would add actions to a non-modifiable bundle.
1815                (false, _, Ordering::Less) | (_, false, Ordering::Greater) => return None,
1816                // If the other bundle has more actions than us, move them over; these
1817                // cannot conflict by construction.
1818                (true, _, Ordering::Less) => {
1819                    self.actions.extend(actions.drain(self.actions.len()..));
1820
1821                    // We check below that the overlapping actions match. Assuming here
1822                    // that they will, we can take the other bundle's value sum.
1823                    self.value_sum = value_sum;
1824                }
1825                // Do nothing otherwise.
1826                (_, _, Ordering::Equal) | (_, true, Ordering::Greater) => (),
1827            },
1828        }
1829
1830        if !merge_optional(&mut self.anchor, anchor) {
1831            return None;
1832        }
1833
1834        if !merge_optional(&mut self.zkproof, zkproof) {
1835            return None;
1836        }
1837
1838        // Leverage the early-exit behaviour of zip to confirm that the remaining data in
1839        // the other bundle matches this one.
1840        for (lhs, rhs) in self.actions.iter_mut().zip(actions) {
1841            // Destructure `rhs` to ensure we handle everything.
1842            let Action {
1843                cv_net,
1844                spend:
1845                    Spend {
1846                        nullifier,
1847                        rk,
1848                        spend_auth_sig,
1849                        recipient,
1850                        value,
1851                        rho,
1852                        rseed,
1853                        fvk,
1854                        witness,
1855                        alpha,
1856                        zip32_derivation: spend_zip32_derivation,
1857                        dummy_sk,
1858                        proprietary: spend_proprietary,
1859                    },
1860                output:
1861                    Output {
1862                        cmx,
1863                        ephemeral_key,
1864                        enc_ciphertext,
1865                        out_ciphertext,
1866                        recipient: output_recipient,
1867                        value: output_value,
1868                        rseed: output_rseed,
1869                        ock,
1870                        zip32_derivation: output_zip32_derivation,
1871                        user_address,
1872                        proprietary: output_proprietary,
1873                    },
1874                rcv,
1875            } = rhs;
1876
1877            if lhs.spend.nullifier != nullifier
1878                || lhs.spend.rk != rk
1879                || lhs.output.ephemeral_key != ephemeral_key
1880                || lhs.output.enc_ciphertext != enc_ciphertext
1881                || lhs.output.out_ciphertext != out_ciphertext
1882            {
1883                return None;
1884            }
1885
1886            if !(merge_optional(&mut lhs.cv_net, cv_net)
1887                && merge_optional(&mut lhs.spend.spend_auth_sig, spend_auth_sig)
1888                && merge_optional(&mut lhs.spend.recipient, recipient)
1889                && merge_optional(&mut lhs.spend.value, value)
1890                && merge_optional(&mut lhs.spend.rho, rho)
1891                && merge_optional(&mut lhs.spend.rseed, rseed)
1892                && merge_optional(&mut lhs.spend.fvk, fvk)
1893                && merge_optional(&mut lhs.spend.witness, witness)
1894                && merge_optional(&mut lhs.spend.alpha, alpha)
1895                && merge_optional(&mut lhs.spend.zip32_derivation, spend_zip32_derivation)
1896                && merge_optional(&mut lhs.spend.dummy_sk, dummy_sk)
1897                && merge_map(&mut lhs.spend.proprietary, spend_proprietary)
1898                && merge_optional(&mut lhs.output.cmx, cmx)
1899                && merge_optional(&mut lhs.output.recipient, output_recipient)
1900                && merge_optional(&mut lhs.output.value, output_value)
1901                && merge_optional(&mut lhs.output.rseed, output_rseed)
1902                && merge_optional(&mut lhs.output.ock, ock)
1903                && merge_optional(&mut lhs.output.zip32_derivation, output_zip32_derivation)
1904                && merge_optional(&mut lhs.output.user_address, user_address)
1905                && merge_map(&mut lhs.output.proprietary, output_proprietary)
1906                && merge_optional(&mut lhs.rcv, rcv))
1907            {
1908                return None;
1909            }
1910        }
1911
1912        Some(self)
1913    }
1914}
1915
1916/// Returns the [`BundleVersion`] in effect for the given Orchard-protocol value pool
1917/// under the given Orchard protocol revision, or `None` if the pool is not supported
1918/// under that revision (the Ironwood pool exists only from revision V3).
1919#[cfg(feature = "orchard")]
1920pub(crate) fn bundle_version_for_revision(
1921    revision: zcash_protocol::consensus::OrchardProtocolRevision,
1922    pool: orchard::ValuePool,
1923) -> Option<BundleVersion> {
1924    match pool {
1925        ValuePool::Orchard => Some(match revision {
1926            OrchardProtocolRevision::InsecureV1 => BundleVersion::orchard_insecure_v1(),
1927            OrchardProtocolRevision::V2 => BundleVersion::orchard_v2(),
1928            OrchardProtocolRevision::V3 => BundleVersion::orchard_v3(),
1929        }),
1930        ValuePool::Ironwood => match revision {
1931            OrchardProtocolRevision::InsecureV1 | OrchardProtocolRevision::V2 => None,
1932            OrchardProtocolRevision::V3 => Some(BundleVersion::ironwood_v3()),
1933        },
1934    }
1935}
1936
1937/// Returns the Orchard-pool [`BundleVersion`] implied by the given PCZT global data,
1938/// or `None` if the PCZT's consensus branch ID is unrecognized or predates NU5 (under
1939/// which the Orchard protocol is not supported).
1940#[cfg(feature = "orchard")]
1941pub(crate) fn orchard_bundle_version(global: &crate::common::Global) -> Option<BundleVersion> {
1942    BranchId::try_from(global.consensus_branch_id)
1943        .ok()?
1944        .orchard_protocol_revision()
1945        .and_then(|revision| bundle_version_for_revision(revision, orchard::ValuePool::Orchard))
1946}
1947
1948/// Errors that can occur while parsing a logical Orchard-protocol bundle (Orchard or
1949/// Ironwood) into the form used by the `orchard` crate.
1950#[cfg(feature = "orchard")]
1951#[derive(Debug)]
1952#[non_exhaustive]
1953pub enum ParseError {
1954    /// The operation requires the bundle's `anchor` to be set, but it was absent.
1955    ///
1956    /// For a v6 transaction, an Updater can resolve this by setting the anchor; see
1957    /// [ZIP 374: Anchors and pre-authorization](https://zips.z.cash/zip-0374#anchorsandpre-authorization).
1958    MissingAnchor,
1959    /// The bundle's remaining fields were structurally invalid.
1960    Bundle(orchard::pczt::ParseError),
1961}
1962
1963#[cfg(feature = "orchard")]
1964impl From<orchard::pczt::ParseError> for ParseError {
1965    fn from(e: orchard::pczt::ParseError) -> Self {
1966        ParseError::Bundle(e)
1967    }
1968}
1969
1970/// Errors that can occur while checking that an Orchard-protocol bundle's spend
1971/// witnesses are consistent with its anchor.
1972#[cfg(all(feature = "orchard", feature = "prover"))]
1973#[derive(Debug)]
1974#[non_exhaustive]
1975pub enum AnchorConsistencyError {
1976    /// A non-zero-valued spend has a `witness` but is missing other note data required
1977    /// to compute its Merkle path root.
1978    IncompleteSpendData,
1979    /// A non-zero-valued spend's `witness` does not root to the given anchor.
1980    WitnessDoesNotRootToAnchor,
1981}
1982
1983/// Checks that every non-zero-valued spend in `bundle` whose `witness` is present has a
1984/// Merkle path that roots to `anchor` (\[ZIP 374\] "Anchors and pre-authorization").
1985///
1986/// Zero-valued spends are skipped, as their Merkle paths are not checked by the Orchard
1987/// circuit.
1988///
1989/// [ZIP 374]: https://zips.z.cash/zip-0374#anchorsandpre-authorization
1990#[cfg(all(feature = "orchard", feature = "prover"))]
1991pub(crate) fn verify_witnesses_root_to_anchor(
1992    bundle: &orchard::pczt::Bundle,
1993    anchor: orchard::Anchor,
1994) -> Result<(), AnchorConsistencyError> {
1995    for action in bundle.actions() {
1996        let spend = action.spend();
1997
1998        let Some(witness) = spend.witness() else {
1999            continue;
2000        };
2001        let Some(value) = spend.value() else {
2002            continue;
2003        };
2004        if value.inner() == 0 {
2005            continue;
2006        }
2007
2008        let recipient = spend
2009            .recipient()
2010            .ok_or(AnchorConsistencyError::IncompleteSpendData)?;
2011        let rho = spend
2012            .rho()
2013            .ok_or(AnchorConsistencyError::IncompleteSpendData)?;
2014        let rseed = spend
2015            .rseed()
2016            .ok_or(AnchorConsistencyError::IncompleteSpendData)?;
2017
2018        let note = orchard::Note::from_parts(recipient, *value, rho, rseed, *spend.note_version())
2019            .into_option()
2020            .ok_or(AnchorConsistencyError::IncompleteSpendData)?;
2021        let cmx = orchard::note::ExtractedNoteCommitment::from(note.commitment());
2022        let computed_anchor = witness.root(cmx);
2023
2024        if computed_anchor != anchor {
2025            return Err(AnchorConsistencyError::WitnessDoesNotRootToAnchor);
2026        }
2027    }
2028
2029    Ok(())
2030}
2031
2032#[cfg(feature = "orchard")]
2033impl Output {
2034    /// Recomputes `cmx`, if this output carries it as an omitted field.
2035    fn resolve_cmx(
2036        &mut self,
2037        note_version: NoteVersion,
2038        spend_nullifier: [u8; 32],
2039    ) -> Result<(), orchard::pczt::ParseError> {
2040        if self.cmx.is_some() {
2041            return Ok(());
2042        }
2043
2044        let recipient = Address::from_raw_address_bytes(
2045            self.recipient
2046                .as_ref()
2047                .ok_or(orchard::pczt::ParseError::InvalidExtractedNoteCommitment)?,
2048        )
2049        .into_option()
2050        .ok_or(orchard::pczt::ParseError::InvalidExtractedNoteCommitment)?;
2051        let rho = Rho::from_bytes(&spend_nullifier)
2052            .into_option()
2053            .ok_or(orchard::pczt::ParseError::InvalidExtractedNoteCommitment)?;
2054        let rseed = RandomSeed::from_bytes(
2055            *self
2056                .rseed
2057                .as_ref()
2058                .ok_or(orchard::pczt::ParseError::InvalidExtractedNoteCommitment)?,
2059            &rho,
2060        )
2061        .into_option()
2062        .ok_or(orchard::pczt::ParseError::InvalidExtractedNoteCommitment)?;
2063        let note = Note::from_parts(
2064            recipient,
2065            NoteValue::from_raw(
2066                self.value
2067                    .ok_or(orchard::pczt::ParseError::InvalidExtractedNoteCommitment)?,
2068            ),
2069            rho,
2070            rseed,
2071            note_version,
2072        )
2073        .into_option()
2074        .ok_or(orchard::pczt::ParseError::InvalidExtractedNoteCommitment)?;
2075
2076        self.cmx = Some(ExtractedNoteCommitment::from(note.commitment()).to_bytes());
2077        Ok(())
2078    }
2079
2080    /// Recomputes [`Self::enc_ciphertext`] from memo plaintext, if present.
2081    ///
2082    /// If [`Self::enc_ciphertext`] is [`EncCiphertext::MemoPlaintext`], this
2083    /// computes the encrypted note plaintext and replaces it with
2084    /// [`EncCiphertext::Encrypted`]. If it is already encrypted, this is a
2085    /// no-op.
2086    ///
2087    /// This requires the action's spend nullifier because the output note's
2088    /// [`rho`](::orchard::note::Rho) is derived from it.
2089    fn encrypt_ciphertext_from_memo(
2090        &mut self,
2091        note_version: NoteVersion,
2092        spend_nullifier: [u8; 32],
2093    ) -> Result<(), orchard::pczt::ParseError> {
2094        let memo: [u8; 512] = match &self.enc_ciphertext {
2095            EncCiphertext::Encrypted(_) => return Ok(()),
2096            EncCiphertext::MemoPlaintext(memo) => memo.to_memo(),
2097        };
2098
2099        let recipient = Address::from_raw_address_bytes(
2100            self.recipient
2101                .as_ref()
2102                .ok_or(orchard::pczt::ParseError::InvalidRecipient)?,
2103        )
2104        .into_option()
2105        .ok_or(orchard::pczt::ParseError::InvalidRecipient)?;
2106        let rho = Rho::from_bytes(&spend_nullifier)
2107            .into_option()
2108            .ok_or(orchard::pczt::ParseError::InvalidNullifier)?;
2109        let rseed = RandomSeed::from_bytes(
2110            *self
2111                .rseed
2112                .as_ref()
2113                .ok_or(orchard::pczt::ParseError::InvalidRandomSeed)?,
2114            &rho,
2115        )
2116        .into_option()
2117        .ok_or(orchard::pczt::ParseError::InvalidRandomSeed)?;
2118        let note = Note::from_parts(
2119            recipient,
2120            NoteValue::from_raw(
2121                self.value
2122                    .ok_or(orchard::pczt::ParseError::InvalidEncCiphertext)?,
2123            ),
2124            rho,
2125            rseed,
2126            note_version,
2127        )
2128        .into_option()
2129        .ok_or(orchard::pczt::ParseError::InvalidEncCiphertext)?;
2130        let encryptor = OrchardNoteEncryption::new(None, note, memo);
2131        let ephemeral_key = OrchardDomain::epk_bytes(encryptor.epk()).0;
2132        let enc_ciphertext = encryptor.encrypt_note_plaintext().0.to_vec();
2133
2134        if ephemeral_key != self.ephemeral_key {
2135            return Err(orchard::pczt::ParseError::InvalidEncCiphertext);
2136        }
2137
2138        self.enc_ciphertext = EncCiphertext::Encrypted(enc_ciphertext);
2139        Ok(())
2140    }
2141}
2142
2143#[cfg(feature = "orchard")]
2144impl Action {
2145    /// Recomputes `cv_net`, if this action carries it as an omitted field.
2146    fn resolve_cv_net(&mut self) -> Result<(), orchard::pczt::ParseError> {
2147        if self.cv_net.is_some() {
2148            return Ok(());
2149        }
2150
2151        let spend_value: NoteValue = NoteValue::from_raw(
2152            self.spend
2153                .value
2154                .ok_or(orchard::pczt::ParseError::InvalidValueCommitment)?,
2155        );
2156        let output_value = NoteValue::from_raw(
2157            self.output
2158                .value
2159                .ok_or(orchard::pczt::ParseError::InvalidValueCommitment)?,
2160        );
2161        let rcv = ValueCommitTrapdoor::from_bytes(
2162            self.rcv
2163                .ok_or(orchard::pczt::ParseError::InvalidValueCommitment)?,
2164        )
2165        .into_option()
2166        .ok_or(orchard::pczt::ParseError::InvalidValueCommitment)?;
2167
2168        self.cv_net = Some(ValueCommitment::derive(spend_value - output_value, rcv).to_bytes());
2169        Ok(())
2170    }
2171}
2172
2173#[cfg(feature = "orchard")]
2174impl Bundle {
2175    /// Resolves fields that are optionally redacted in the PCZT but implied by
2176    /// other known fields.
2177    ///
2178    /// This currently recomputes:
2179    /// - [`Action::cv_net`] if it is redacted.
2180    /// - [`Output::cmx`] if it is redacted.
2181    /// - [`Output::enc_ciphertext`] if it is represented by memo plaintext.
2182    ///
2183    /// For improved efficiency, callers that will pass the same bundle through
2184    /// multiple roles should call this once up front, not in each role. Parsing
2185    /// also resolves fields defensively.
2186    pub fn resolve_fields(&mut self) -> Result<(), orchard::pczt::ParseError> {
2187        for action in &mut self.actions {
2188            action.resolve_cv_net()?;
2189            action
2190                .output
2191                .resolve_cmx(self.note_version, action.spend.nullifier)?;
2192            action
2193                .output
2194                .encrypt_ciphertext_from_memo(self.note_version, action.spend.nullifier)?;
2195        }
2196
2197        Ok(())
2198    }
2199
2200    /// Parses this bundle as an Ironwood-pool bundle, deriving each spend's
2201    /// `FullViewingKey` from its wire `fvk` bytes.
2202    pub(crate) fn into_ironwood_parsed(
2203        self,
2204        anchor_requirement: crate::common::AnchorRequirement,
2205    ) -> Result<Parsed, ParseError> {
2206        self.into_parsed_with_version(BundleVersion::ironwood_v3(), anchor_requirement)
2207    }
2208
2209    /// Parses this bundle as an Ironwood-pool bundle for a preverified signing
2210    /// pass, skipping each spend's `FullViewingKey` derivation. See
2211    /// [`Bundle::into_parsed_with_version_preverified_for_signing`] for the invariant
2212    /// callers must uphold.
2213    pub(crate) fn into_ironwood_parsed_preverified_for_signing(
2214        self,
2215        anchor_requirement: crate::common::AnchorRequirement,
2216    ) -> Result<Parsed, ParseError> {
2217        self.into_parsed_with_version_preverified_for_signing(
2218            BundleVersion::ironwood_v3(),
2219            anchor_requirement,
2220        )
2221    }
2222
2223    /// Parses this bundle with the given bundle version, deriving each spend's
2224    /// `FullViewingKey` from its wire `fvk` bytes.
2225    ///
2226    /// Callers should prefer [`Bundle::resolve_fields`] before parsing, so
2227    /// derivations happen once for all uses. This method still resolves
2228    /// fields defensively for direct callers.
2229    #[allow(dead_code)]
2230    pub(crate) fn into_parsed_with_version(
2231        self,
2232        bundle_version: BundleVersion,
2233        anchor_requirement: crate::common::AnchorRequirement,
2234    ) -> Result<Parsed, ParseError> {
2235        self.into_parsed_inner(bundle_version, anchor_requirement, false)
2236    }
2237
2238    /// Parses this bundle with the given bundle version for a preverified signing
2239    /// pass, skipping each spend's `FullViewingKey` derivation (an expensive step the
2240    /// spend authorization signature does not depend on).
2241    ///
2242    /// Callers should prefer [`Bundle::resolve_fields`] before parsing, so
2243    /// derivations happen once for all uses. This method still resolves
2244    /// fields defensively for direct callers.
2245    ///
2246    /// Callers MUST have already run the full Verifier checks over the identical PCZT
2247    /// bytes: the wire `fvk` bytes are neither validated nor retained here (each spend
2248    /// has `fvk: None`), so the result must not go to the Verifier check path or the
2249    /// Prover, and re-serializing it drops the wire `fvk`s (the low-level Signer
2250    /// restores them from a pre-parse snapshot).
2251    #[allow(dead_code)]
2252    pub(crate) fn into_parsed_with_version_preverified_for_signing(
2253        self,
2254        bundle_version: BundleVersion,
2255        anchor_requirement: crate::common::AnchorRequirement,
2256    ) -> Result<Parsed, ParseError> {
2257        self.into_parsed_inner(bundle_version, anchor_requirement, true)
2258    }
2259
2260    /// The shared body of [`Bundle::into_parsed_with_version`] and
2261    /// [`Bundle::into_parsed_with_version_preverified_for_signing`]: `preverified`
2262    /// selects between the full parse and the preverified signing parse.
2263    fn into_parsed_inner(
2264        mut self,
2265        bundle_version: BundleVersion,
2266        anchor_requirement: crate::common::AnchorRequirement,
2267        preverified: bool,
2268    ) -> Result<Parsed, ParseError> {
2269        self.resolve_fields()?;
2270        let wire_anchor = self.anchor;
2271        let anchor = anchor_requirement
2272            .resolve(wire_anchor, self.actions.is_empty())
2273            .ok_or(ParseError::MissingAnchor)?;
2274
2275        // We parse actions through a helper that is specifically `#[inline(never)]`.
2276        // This is because if this gets inlined in a loop (e.g. `.map(..).collect()`),
2277        // it could compile into a stack frame that is tens of KB deep.
2278        // This can overflow stacks of embedded signers for high action count
2279        // transactions.
2280        #[inline(never)]
2281        fn parse_action_inner(
2282            mut action: Action,
2283            note_version: NoteVersion,
2284            preverified: bool,
2285        ) -> Result<orchard::pczt::Action, orchard::pczt::ParseError> {
2286            let spend_zip32_derivation = action
2287                .spend
2288                .zip32_derivation
2289                .map(|z| {
2290                    orchard::pczt::Zip32Derivation::parse(z.seed_fingerprint, z.derivation_path)
2291                })
2292                .transpose()?;
2293            let spend_nullifier = action.spend.nullifier;
2294
2295            let spend = if preverified {
2296                orchard::pczt::Spend::parse_preverified_for_signing(
2297                    action.spend.nullifier,
2298                    action.spend.rk,
2299                    action.spend.spend_auth_sig,
2300                    action.spend.recipient,
2301                    action.spend.value,
2302                    action.spend.rho,
2303                    action.spend.rseed,
2304                    action.spend.fvk,
2305                    action.spend.witness,
2306                    action.spend.alpha,
2307                    spend_zip32_derivation,
2308                    action.spend.dummy_sk.as_ref().map(|sk| *sk.expose_secret()),
2309                    note_version,
2310                    action.spend.proprietary,
2311                )
2312            } else {
2313                orchard::pczt::Spend::parse(
2314                    action.spend.nullifier,
2315                    action.spend.rk,
2316                    action.spend.spend_auth_sig,
2317                    action.spend.recipient,
2318                    action.spend.value,
2319                    action.spend.rho,
2320                    action.spend.rseed,
2321                    action.spend.fvk,
2322                    action.spend.witness,
2323                    action.spend.alpha,
2324                    spend_zip32_derivation,
2325                    action.spend.dummy_sk.as_ref().map(|sk| *sk.expose_secret()),
2326                    note_version,
2327                    action.spend.proprietary,
2328                )
2329            }?;
2330
2331            action
2332                .output
2333                .encrypt_ciphertext_from_memo(note_version, spend_nullifier)?;
2334
2335            let enc_ciphertext = action
2336                .output
2337                .enc_ciphertext
2338                .into_encrypted()
2339                .ok_or(orchard::pczt::ParseError::InvalidEncCiphertext)?;
2340            let cv_net = action
2341                .cv_net
2342                .ok_or(orchard::pczt::ParseError::InvalidValueCommitment)?;
2343
2344            let output = orchard::pczt::Output::parse(
2345                *spend.nullifier(),
2346                action
2347                    .output
2348                    .cmx
2349                    .ok_or(orchard::pczt::ParseError::InvalidExtractedNoteCommitment)?,
2350                action.output.ephemeral_key,
2351                enc_ciphertext,
2352                action.output.out_ciphertext,
2353                action.output.recipient,
2354                action.output.value,
2355                action.output.rseed,
2356                action.output.ock,
2357                action
2358                    .output
2359                    .zip32_derivation
2360                    .map(|z| {
2361                        orchard::pczt::Zip32Derivation::parse(z.seed_fingerprint, z.derivation_path)
2362                    })
2363                    .transpose()?,
2364                action.output.user_address,
2365                note_version,
2366                action.output.proprietary,
2367            )?;
2368
2369            orchard::pczt::Action::parse(cv_net, spend, output, action.rcv)
2370        }
2371
2372        let note_version = self.note_version;
2373        let mut actions = Vec::with_capacity(self.actions.len());
2374        for action in self.actions {
2375            actions.push(parse_action_inner(action, note_version, preverified)?);
2376        }
2377
2378        let bundle = orchard::pczt::Bundle::parse(
2379            actions,
2380            self.flags,
2381            bundle_version,
2382            self.value_sum,
2383            anchor,
2384            self.zkproof,
2385            self.bsk.as_ref().map(|bsk| *bsk.expose_secret()),
2386        )?;
2387
2388        Ok(Parsed {
2389            bundle,
2390            wire_anchor,
2391        })
2392    }
2393
2394    #[allow(dead_code)]
2395    pub(crate) fn serialize_from(bundle: orchard::pczt::Bundle) -> Self {
2396        let note_version = bundle.bundle_version().note_version();
2397
2398        assert!(
2399            bundle.actions().iter().all(|action| {
2400                action.spend().note_version() == &note_version
2401                    && action.output().note_version() == &note_version
2402            }),
2403            "Orchard PCZT bundle must have a single note version"
2404        );
2405
2406        let actions = bundle
2407            .actions()
2408            .iter()
2409            .map(|action| {
2410                let spend = action.spend();
2411                let output = action.output();
2412
2413                Action {
2414                    cv_net: Some(action.cv_net().to_bytes()),
2415                    spend: Spend {
2416                        nullifier: spend.nullifier().to_bytes(),
2417                        rk: spend.rk().into(),
2418                        spend_auth_sig: spend.spend_auth_sig().as_ref().map(|s| s.into()),
2419                        recipient: action
2420                            .spend()
2421                            .recipient()
2422                            .map(|recipient| recipient.to_raw_address_bytes()),
2423                        value: spend.value().map(|value| value.inner()),
2424                        rho: spend.rho().map(|rho| rho.to_bytes()),
2425                        rseed: spend.rseed().map(|rseed| *rseed.as_bytes()),
2426                        fvk: spend.fvk().as_ref().map(|fvk| fvk.to_bytes()),
2427                        witness: spend.witness().as_ref().map(|witness| {
2428                            (
2429                                u32::try_from(u64::from(witness.position()))
2430                                    .expect("Sapling positions fit in u32"),
2431                                witness
2432                                    .auth_path()
2433                                    .iter()
2434                                    .map(|node| node.to_bytes())
2435                                    .collect::<Vec<_>>()[..]
2436                                    .try_into()
2437                                    .expect("path is length 32"),
2438                            )
2439                        }),
2440                        alpha: spend.alpha().map(|alpha| alpha.to_repr()),
2441                        zip32_derivation: spend.zip32_derivation().as_ref().map(|z| {
2442                            Zip32Derivation {
2443                                seed_fingerprint: *z.seed_fingerprint(),
2444                                derivation_path: z
2445                                    .derivation_path()
2446                                    .iter()
2447                                    .map(|i| i.index())
2448                                    .collect(),
2449                            }
2450                        }),
2451                        dummy_sk: action
2452                            .spend()
2453                            .dummy_sk()
2454                            .as_ref()
2455                            .map(|dummy_sk| SecretKeyBytes::new(*dummy_sk.to_bytes())),
2456                        proprietary: spend.proprietary().clone(),
2457                    },
2458                    output: Output {
2459                        cmx: Some(output.cmx().to_bytes()),
2460                        ephemeral_key: output.encrypted_note().epk_bytes,
2461                        enc_ciphertext: EncCiphertext::Encrypted(
2462                            output.encrypted_note().enc_ciphertext.0.to_vec(),
2463                        ),
2464                        out_ciphertext: output.encrypted_note().out_ciphertext.to_vec(),
2465                        recipient: action
2466                            .output()
2467                            .recipient()
2468                            .map(|recipient| recipient.to_raw_address_bytes()),
2469                        value: output.value().map(|value| value.inner()),
2470                        rseed: output.rseed().map(|rseed| *rseed.as_bytes()),
2471                        ock: output.ock().as_ref().map(|ock| ock.0),
2472                        zip32_derivation: output.zip32_derivation().as_ref().map(|z| {
2473                            Zip32Derivation {
2474                                seed_fingerprint: *z.seed_fingerprint(),
2475                                derivation_path: z
2476                                    .derivation_path()
2477                                    .iter()
2478                                    .map(|i| i.index())
2479                                    .collect(),
2480                            }
2481                        }),
2482                        user_address: output.user_address().clone(),
2483                        proprietary: output.proprietary().clone(),
2484                    },
2485                    rcv: action.rcv().as_ref().map(|rcv| rcv.to_bytes()),
2486                }
2487            })
2488            .collect();
2489
2490        let value_sum = {
2491            let (magnitude, sign) = bundle.value_sum().magnitude_sign();
2492            (magnitude, matches!(sign, orchard::value::Sign::Negative))
2493        };
2494        // A bundle built with its anchor deferred to proving time (ZIP 374) carries the
2495        // empty-tree root purely as a placeholder; emit the anchor as ABSENT, for the
2496        // Updater role to install before proving.
2497        let anchor = (!bundle.anchor_deferred()).then(|| bundle.anchor().to_bytes());
2498
2499        Self {
2500            actions,
2501            flags: bundle.flag_byte(),
2502            value_sum,
2503            anchor,
2504            note_version,
2505            zkproof: bundle
2506                .zkproof()
2507                .as_ref()
2508                .map(|zkproof| zkproof.as_ref().to_vec()),
2509            bsk: bundle
2510                .bsk()
2511                .as_ref()
2512                .map(|bsk| SecretKeyBytes::new(bsk.to_bytes())),
2513        }
2514    }
2515}
2516
2517#[cfg(test)]
2518mod shape_tests {
2519    use alloc::collections::BTreeMap;
2520    use alloc::vec::Vec;
2521
2522    use proptest::prelude::*;
2523    use proptest::sample::Index;
2524
2525    use super::{
2526        Action, Bundle, EncCiphertext, NoteVersion, ORCHARD_SPENDS_AND_OUTPUTS_ENABLED, Output,
2527        Spend,
2528    };
2529
2530    /// Two distinguishable payment addresses. Only their inequality matters, so any two distinct
2531    /// byte strings do; these are never parsed as addresses.
2532    const OURS: [u8; 43] = [1; 43];
2533    const THEIRS: [u8; 43] = [2; 43];
2534
2535    fn output(value: Option<u64>, recipient: Option<[u8; 43]>) -> Output {
2536        Output {
2537            cmx: None,
2538            ephemeral_key: [0; 32],
2539            enc_ciphertext: EncCiphertext::Encrypted(Vec::new()),
2540            out_ciphertext: Vec::new(),
2541            recipient,
2542            value,
2543            rseed: None,
2544            ock: None,
2545            zip32_derivation: None,
2546            user_address: None,
2547            proprietary: BTreeMap::new(),
2548        }
2549    }
2550
2551    fn spend() -> Spend {
2552        Spend {
2553            nullifier: [0; 32],
2554            rk: [0; 32],
2555            spend_auth_sig: None,
2556            recipient: None,
2557            value: None,
2558            rho: None,
2559            rseed: None,
2560            fvk: None,
2561            witness: None,
2562            alpha: None,
2563            zip32_derivation: None,
2564            dummy_sk: None,
2565            proprietary: BTreeMap::new(),
2566        }
2567    }
2568
2569    /// An action carrying the given output. Neither method under test reads the spend half or any
2570    /// bundle-level field, so those stay at fixed values.
2571    fn action(value: Option<u64>, recipient: Option<[u8; 43]>) -> Action {
2572        Action {
2573            cv_net: None,
2574            spend: spend(),
2575            output: output(value, recipient),
2576            rcv: None,
2577        }
2578    }
2579
2580    fn bundle(actions: Vec<Action>) -> Bundle {
2581        Bundle {
2582            actions,
2583            flags: ORCHARD_SPENDS_AND_OUTPUTS_ENABLED,
2584            value_sum: (0, false),
2585            anchor: None,
2586            note_version: NoteVersion::V2,
2587            zkproof: None,
2588            bsk: None,
2589        }
2590    }
2591
2592    fn arb_recipient() -> impl Strategy<Value = [u8; 43]> {
2593        prop_oneof![Just(OURS), Just(THEIRS)]
2594    }
2595
2596    /// Zero is drawn as often as any other value, because zero is what separates a padding dummy
2597    /// from a real output and so is the case the predicate turns on.
2598    fn arb_value() -> impl Strategy<Value = u64> {
2599        prop_oneof![Just(0u64), 1u64..1_000]
2600    }
2601
2602    /// An action whose judged fields may each have been redacted, which is the state a Redactor
2603    /// can leave a PCZT in.
2604    fn arb_action() -> impl Strategy<Value = Action> {
2605        (
2606            prop_oneof![Just(None), arb_value().prop_map(Some)],
2607            prop_oneof![Just(None), arb_recipient().prop_map(Some)],
2608        )
2609            .prop_map(|(value, recipient)| action(value, recipient))
2610    }
2611
2612    fn arb_bundle() -> impl Strategy<Value = Bundle> {
2613        prop::collection::vec(arb_action(), 0..6).prop_map(bundle)
2614    }
2615
2616    /// A bundle with nothing redacted, so the predicate is always able to answer.
2617    fn arb_specified_bundle() -> impl Strategy<Value = Bundle> {
2618        prop::collection::vec((arb_value(), arb_recipient()), 0..6).prop_map(|outputs| {
2619            bundle(
2620                outputs
2621                    .into_iter()
2622                    .map(|(value, recipient)| action(Some(value), Some(recipient)))
2623                    .collect(),
2624            )
2625        })
2626    }
2627
2628    /// A bundle whose value-carrying outputs all pay `OURS`, so the predicate answers
2629    /// `Some(true)`. Its zero-valued dummies pay anyone: a Constructor fabricates them to reach an
2630    /// action count and does not choose who they name.
2631    fn arb_send_to_self_bundle() -> impl Strategy<Value = Bundle> {
2632        prop::collection::vec((arb_value(), arb_recipient()), 1..6).prop_map(|outputs| {
2633            bundle(
2634                outputs
2635                    .into_iter()
2636                    .map(|(value, dummy_recipient)| {
2637                        let recipient = if value == 0 { dummy_recipient } else { OURS };
2638                        action(Some(value), Some(recipient))
2639                    })
2640                    .collect(),
2641            )
2642        })
2643    }
2644
2645    proptest! {
2646        /// `sole_action` is about the count and nothing else.
2647        #[test]
2648        fn sole_action_answers_exactly_when_there_is_one_action(bundle in arb_bundle()) {
2649            match bundle.sole_action() {
2650                Some(action) => {
2651                    prop_assert_eq!(bundle.actions.len(), 1);
2652                    prop_assert_eq!(action, &bundle.actions[0]);
2653                }
2654                None => prop_assert_ne!(bundle.actions.len(), 1),
2655            }
2656        }
2657
2658        /// With nothing redacted the predicate is a direct reading of the outputs: every one that
2659        /// carries value pays the recipient asked about.
2660        #[test]
2661        fn all_pay_reads_the_value_carrying_outputs(bundle in arb_specified_bundle()) {
2662            let expected = bundle
2663                .actions
2664                .iter()
2665                .all(|a| a.output.value == Some(0) || a.output.recipient == Some(OURS));
2666
2667            prop_assert_eq!(bundle.value_carrying_outputs_all_pay(&OURS), Some(expected));
2668        }
2669
2670        /// Padding dummies do not participate, whoever they name and even with that name redacted.
2671        /// Counting them would make every padded bundle fail, which is the whole point of the
2672        /// zero-value skip.
2673        #[test]
2674        fn zero_valued_dummies_do_not_change_the_answer(
2675            bundle in arb_bundle(),
2676            dummy_recipient in prop_oneof![Just(None), arb_recipient().prop_map(Some)],
2677            at in any::<Index>(),
2678        ) {
2679            let expected = bundle.value_carrying_outputs_all_pay(&OURS);
2680
2681            let mut padded = bundle.clone();
2682            let at = at.index(padded.actions.len() + 1);
2683            padded.actions.insert(at, action(Some(0), dummy_recipient));
2684
2685            prop_assert_eq!(padded.value_carrying_outputs_all_pay(&OURS), expected);
2686        }
2687
2688        /// A redacted value leaves the question unanswered rather than answered negatively: the
2689        /// output cannot even be sorted into dummy or real.
2690        #[test]
2691        fn a_redacted_value_is_unanswerable(bundle in arb_send_to_self_bundle(), at in any::<Index>()) {
2692            prop_assert_eq!(bundle.value_carrying_outputs_all_pay(&OURS), Some(true));
2693
2694            let mut redacted = bundle.clone();
2695            let at = at.index(redacted.actions.len());
2696            redacted.actions[at].output.value = None;
2697
2698            prop_assert_eq!(redacted.value_carrying_outputs_all_pay(&OURS), None);
2699        }
2700
2701        /// A redacted recipient is unanswerable only where it would have been judged: on an output
2702        /// that carries value. On a dummy it is skipped before the recipient is ever read.
2703        #[test]
2704        fn a_redacted_recipient_is_unanswerable_only_where_it_is_judged(
2705            bundle in arb_send_to_self_bundle(),
2706            at in any::<Index>(),
2707        ) {
2708            let mut redacted = bundle.clone();
2709            let at = at.index(redacted.actions.len());
2710            let carries_value = redacted.actions[at].output.value != Some(0);
2711            redacted.actions[at].output.recipient = None;
2712
2713            let expected = if carries_value { None } else { Some(true) };
2714            prop_assert_eq!(redacted.value_carrying_outputs_all_pay(&OURS), expected);
2715        }
2716
2717        /// The recipient asked about is a parameter, not a constant: the same bundle answers
2718        /// differently for a recipient it does not pay.
2719        #[test]
2720        fn all_pay_is_asked_about_a_specific_recipient(bundle in arb_send_to_self_bundle()) {
2721            let pays_nobody = bundle
2722                .actions
2723                .iter()
2724                .all(|a| a.output.value == Some(0));
2725
2726            prop_assert_eq!(bundle.value_carrying_outputs_all_pay(&THEIRS), Some(pays_nobody));
2727        }
2728    }
2729}