Skip to main content

orchard/
builder.rs

1//! Logic for building Orchard components of transactions.
2
3use alloc::collections::BTreeMap;
4use alloc::vec::Vec;
5use core::fmt;
6use core::iter;
7
8use ff::Field;
9use pasta_curves::pallas;
10use rand::{prelude::SliceRandom, CryptoRng, RngCore};
11
12use crate::{
13    address::Address,
14    bundle::{Authorization, Authorized, Bundle, Flags},
15    keys::{
16        FullViewingKey, OutgoingViewingKey, Scope, SpendAuthorizingKey, SpendValidatingKey,
17        SpendingKey,
18    },
19    note::{ExtractedNoteCommitment, Note, Nullifier, Rho, TransmittedNoteCiphertext},
20    note_encryption::OrchardNoteEncryption,
21    primitives::redpallas::{self, Binding, SpendAuth},
22    tree::{Anchor, MerklePath},
23    value::{self, BalanceError, NoteValue, ValueCommitTrapdoor, ValueCommitment, ValueSum},
24    Proof,
25};
26
27#[cfg(feature = "circuit")]
28use {
29    crate::{
30        action::Action,
31        circuit::{Circuit, Instance, ProvingKey},
32    },
33    nonempty::NonEmpty,
34};
35
36const MIN_ACTIONS: usize = 2;
37
38/// An enumeration of rules for Orchard bundle construction.
39#[derive(Clone, Copy, Debug, PartialEq, Eq)]
40pub enum BundleType {
41    /// A transactional bundle will be padded if necessary to contain at least 2 actions,
42    /// irrespective of whether any genuine actions are required.
43    Transactional {
44        /// The flags that control whether spends and/or outputs are enabled for the bundle.
45        flags: Flags,
46        /// A flag that, when set to `true`, indicates that a bundle should be produced even if no
47        /// spends or outputs have been added to the bundle; in such a circumstance, all of the
48        /// actions in the resulting bundle will be dummies.
49        bundle_required: bool,
50    },
51    /// A coinbase bundle is required to have no non-dummy spends. No padding is performed.
52    Coinbase,
53}
54
55impl BundleType {
56    /// The default bundle type has all flags enabled, and does not require a bundle to be produced
57    /// if no spends or outputs have been added to the bundle.
58    pub const DEFAULT: BundleType = BundleType::Transactional {
59        flags: Flags::ENABLED,
60        bundle_required: false,
61    };
62
63    /// The DISABLED bundle type does not permit any bundle to be produced, and when used in the
64    /// builder will prevent any spends or outputs from being added.
65    pub const DISABLED: BundleType = BundleType::Transactional {
66        flags: Flags::from_parts(false, false),
67        bundle_required: false,
68    };
69
70    /// Returns the number of logical actions that builder will produce in constructing a bundle
71    /// of this type, given the specified numbers of spends and outputs.
72    ///
73    /// Returns an error if the specified number of spends and outputs is incompatible with
74    /// this bundle type.
75    pub fn num_actions(
76        &self,
77        num_spends: usize,
78        num_outputs: usize,
79    ) -> Result<usize, &'static str> {
80        let num_requested_actions = core::cmp::max(num_spends, num_outputs);
81
82        match self {
83            BundleType::Transactional {
84                flags,
85                bundle_required,
86            } => {
87                if !flags.spends_enabled() && num_spends > 0 {
88                    Err("Spends are disabled, so num_spends must be zero")
89                } else if !flags.outputs_enabled() && num_outputs > 0 {
90                    Err("Outputs are disabled, so num_outputs must be zero")
91                } else {
92                    Ok(if *bundle_required || num_requested_actions > 0 {
93                        core::cmp::max(num_requested_actions, MIN_ACTIONS)
94                    } else {
95                        0
96                    })
97                }
98            }
99            BundleType::Coinbase => {
100                if num_spends > 0 {
101                    Err("Coinbase bundles have spends disabled, so num_spends must be zero")
102                } else {
103                    Ok(num_outputs)
104                }
105            }
106        }
107    }
108
109    /// Returns the set of flags and the anchor that will be used for bundle construction.
110    pub fn flags(&self) -> Flags {
111        match self {
112            BundleType::Transactional { flags, .. } => *flags,
113            BundleType::Coinbase => Flags::SPENDS_DISABLED,
114        }
115    }
116}
117
118/// An error type for the kinds of errors that can occur during bundle construction.
119#[derive(Debug)]
120#[non_exhaustive]
121pub enum BuildError {
122    /// Spends are disabled for the provided bundle type.
123    SpendsDisabled,
124    /// Spends are disabled for the provided bundle type.
125    OutputsDisabled,
126    /// The anchor provided to this builder doesn't match the Merkle path used to add a spend.
127    AnchorMismatch,
128    /// A bundle could not be built because required signatures were missing.
129    MissingSignatures,
130    /// An error occurred in the process of producing a proof for a bundle.
131    #[cfg(feature = "circuit")]
132    Proof(halo2_proofs::plonk::Error),
133    /// An overflow error occurred while attempting to construct the value
134    /// for a bundle.
135    ValueSum(value::BalanceError),
136    /// External signature is not valid.
137    InvalidExternalSignature,
138    /// A signature is valid for more than one input. This should never happen if `alpha`
139    /// is sampled correctly, and indicates a critical failure in randomness generation.
140    DuplicateSignature,
141    /// The bundle being constructed violated the construction rules for the requested bundle type.
142    BundleTypeNotSatisfiable,
143}
144
145impl fmt::Display for BuildError {
146    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
147        use BuildError::*;
148        match self {
149            MissingSignatures => f.write_str("Required signatures were missing during build"),
150            #[cfg(feature = "circuit")]
151            Proof(e) => f.write_str(&format!("Could not create proof: {}", e)),
152            ValueSum(_) => f.write_str("Overflow occurred during value construction"),
153            InvalidExternalSignature => f.write_str("External signature was invalid"),
154            DuplicateSignature => f.write_str("Signature valid for more than one input"),
155            BundleTypeNotSatisfiable => {
156                f.write_str("Bundle structure did not conform to requested bundle type.")
157            }
158            SpendsDisabled => f.write_str("Spends are not enabled for the requested bundle type."),
159            OutputsDisabled => f.write_str("Spends are not enabled for the requested bundle type."),
160            AnchorMismatch => {
161                f.write_str("All spends must share the anchor requested for the transaction.")
162            }
163        }
164    }
165}
166
167#[cfg(feature = "std")]
168impl std::error::Error for BuildError {}
169
170#[cfg(feature = "circuit")]
171impl From<halo2_proofs::plonk::Error> for BuildError {
172    fn from(e: halo2_proofs::plonk::Error) -> Self {
173        BuildError::Proof(e)
174    }
175}
176
177impl From<value::BalanceError> for BuildError {
178    fn from(e: value::BalanceError) -> Self {
179        BuildError::ValueSum(e)
180    }
181}
182
183/// An error type for adding a spend to the builder.
184#[derive(Debug, PartialEq, Eq)]
185#[non_exhaustive]
186pub enum SpendError {
187    /// Spends aren't enabled for this builder.
188    SpendsDisabled,
189    /// The anchor provided to this builder doesn't match the merkle path used to add a spend.
190    AnchorMismatch,
191    /// The full viewing key provided didn't match the note provided
192    FvkMismatch,
193}
194
195impl fmt::Display for SpendError {
196    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
197        use SpendError::*;
198        f.write_str(match self {
199            SpendsDisabled => "Spends are not enabled for this builder",
200            AnchorMismatch => "All anchors must be equal.",
201            FvkMismatch => "FullViewingKey does not correspond to the given note",
202        })
203    }
204}
205
206#[cfg(feature = "std")]
207impl std::error::Error for SpendError {}
208
209/// An error type for adding an output to the builder.
210#[derive(Debug, PartialEq, Eq)]
211#[non_exhaustive]
212pub enum OutputError {
213    /// Outputs aren't enabled for this builder.
214    OutputsDisabled,
215}
216
217impl fmt::Display for OutputError {
218    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
219        use OutputError::*;
220        f.write_str(match self {
221            OutputsDisabled => "Outputs are not enabled for this builder",
222        })
223    }
224}
225
226#[cfg(feature = "std")]
227impl std::error::Error for OutputError {}
228
229/// Information about a specific note to be spent in an [`Action`].
230#[derive(Debug)]
231pub struct SpendInfo {
232    pub(crate) dummy_sk: Option<SpendingKey>,
233    pub(crate) fvk: FullViewingKey,
234    pub(crate) scope: Scope,
235    pub(crate) note: Note,
236    pub(crate) merkle_path: MerklePath,
237}
238
239impl SpendInfo {
240    /// This constructor is public to enable creation of custom builders.
241    /// If you are not creating a custom builder, use [`Builder::add_spend`] instead.
242    ///
243    /// Creates a `SpendInfo` from note, full viewing key owning the note,
244    /// and merkle path witness of the note.
245    ///
246    /// Returns `None` if the `fvk` does not own the `note`.
247    ///
248    /// [`Builder::add_spend`]: Builder::add_spend
249    pub fn new(fvk: FullViewingKey, note: Note, merkle_path: MerklePath) -> Option<Self> {
250        let scope = fvk.scope_for_address(&note.recipient())?;
251        Some(SpendInfo {
252            dummy_sk: None,
253            fvk,
254            scope,
255            note,
256            merkle_path,
257        })
258    }
259
260    /// Defined in [Zcash Protocol Spec § 4.8.3: Dummy Notes (Orchard)][orcharddummynotes].
261    ///
262    /// [orcharddummynotes]: https://zips.z.cash/protocol/nu5.pdf#orcharddummynotes
263    fn dummy(rng: &mut impl RngCore) -> Self {
264        let (sk, fvk, note) = Note::dummy(rng, None);
265        let merkle_path = MerklePath::dummy(rng);
266
267        SpendInfo {
268            dummy_sk: Some(sk),
269            fvk,
270            // We use external scope to avoid unnecessary derivations, because the dummy
271            // note's spending key is random and thus scoping is irrelevant.
272            scope: Scope::External,
273            note,
274            merkle_path,
275        }
276    }
277
278    fn has_matching_anchor(&self, anchor: &Anchor) -> bool {
279        if self.note.value() == NoteValue::zero() {
280            true
281        } else {
282            let cm = self.note.commitment();
283            let path_root = self.merkle_path.root(cm.into());
284            &path_root == anchor
285        }
286    }
287
288    /// Builds the spend half of an action.
289    ///
290    /// The returned values are chosen as in [Zcash Protocol Spec § 4.7.3: Sending Notes (Orchard)][orchardsend].
291    ///
292    /// [orchardsend]: https://zips.z.cash/protocol/nu5.pdf#orchardsend
293    fn build(
294        &self,
295        mut rng: impl RngCore,
296    ) -> (
297        Nullifier,
298        SpendValidatingKey,
299        pallas::Scalar,
300        redpallas::VerificationKey<SpendAuth>,
301    ) {
302        let nf_old = self.note.nullifier(&self.fvk);
303        let ak: SpendValidatingKey = self.fvk.clone().into();
304        let alpha = pallas::Scalar::random(&mut rng);
305        let rk = ak.randomize(&alpha);
306
307        (nf_old, ak, alpha, rk)
308    }
309
310    fn into_pczt(self, rng: impl RngCore) -> crate::pczt::Spend {
311        let (nf_old, _, alpha, rk) = self.build(rng);
312
313        crate::pczt::Spend {
314            nullifier: nf_old,
315            rk,
316            spend_auth_sig: None,
317            recipient: Some(self.note.recipient()),
318            value: Some(self.note.value()),
319            rho: Some(self.note.rho()),
320            rseed: Some(*self.note.rseed()),
321            fvk: Some(self.fvk),
322            witness: Some(self.merkle_path),
323            alpha: Some(alpha),
324            zip32_derivation: None,
325            dummy_sk: self.dummy_sk,
326            proprietary: BTreeMap::new(),
327        }
328    }
329}
330
331/// Information about a specific output to receive funds in an [`Action`].
332#[derive(Debug)]
333pub struct OutputInfo {
334    ovk: Option<OutgoingViewingKey>,
335    recipient: Address,
336    value: NoteValue,
337    memo: [u8; 512],
338}
339
340impl OutputInfo {
341    /// Constructs a new OutputInfo from its constituent parts.
342    pub fn new(
343        ovk: Option<OutgoingViewingKey>,
344        recipient: Address,
345        value: NoteValue,
346        memo: [u8; 512],
347    ) -> Self {
348        Self {
349            ovk,
350            recipient,
351            value,
352            memo,
353        }
354    }
355
356    /// Defined in [Zcash Protocol Spec § 4.8.3: Dummy Notes (Orchard)][orcharddummynotes].
357    ///
358    /// [orcharddummynotes]: https://zips.z.cash/protocol/nu5.pdf#orcharddummynotes
359    pub fn dummy(rng: &mut impl RngCore) -> Self {
360        let fvk: FullViewingKey = (&SpendingKey::random(rng)).into();
361        let recipient = fvk.address_at(0u32, Scope::External);
362
363        Self::new(None, recipient, NoteValue::zero(), [0u8; 512])
364    }
365
366    /// Builds the output half of an action.
367    ///
368    /// Defined in [Zcash Protocol Spec § 4.7.3: Sending Notes (Orchard)][orchardsend].
369    ///
370    /// [orchardsend]: https://zips.z.cash/protocol/nu5.pdf#orchardsend
371    fn build(
372        &self,
373        cv_net: &ValueCommitment,
374        nf_old: Nullifier,
375        mut rng: impl RngCore,
376    ) -> (Note, ExtractedNoteCommitment, TransmittedNoteCiphertext) {
377        let rho = Rho::from_nf_old(nf_old);
378        let note = Note::new(self.recipient, self.value, rho, &mut rng);
379        let cm_new = note.commitment();
380        let cmx = cm_new.into();
381
382        let encryptor = OrchardNoteEncryption::new(self.ovk.clone(), note, self.memo);
383
384        let encrypted_note = TransmittedNoteCiphertext {
385            epk_bytes: encryptor.epk().to_bytes().0,
386            enc_ciphertext: encryptor.encrypt_note_plaintext(),
387            out_ciphertext: encryptor.encrypt_outgoing_plaintext(cv_net, &cmx, &mut rng),
388        };
389
390        (note, cmx, encrypted_note)
391    }
392
393    fn into_pczt(
394        self,
395        cv_net: &ValueCommitment,
396        nf_old: Nullifier,
397        rng: impl RngCore,
398    ) -> crate::pczt::Output {
399        let (note, cmx, encrypted_note) = self.build(cv_net, nf_old, rng);
400
401        crate::pczt::Output {
402            cmx,
403            encrypted_note,
404            recipient: Some(self.recipient),
405            value: Some(self.value),
406            rseed: Some(*note.rseed()),
407            // TODO: Extract ock from the encryptor and save it so
408            // Signers can check `out_ciphertext`.
409            ock: None,
410            zip32_derivation: None,
411            user_address: None,
412            proprietary: BTreeMap::new(),
413        }
414    }
415}
416
417/// Information about a specific [`Action`] we plan to build.
418#[derive(Debug)]
419struct ActionInfo {
420    spend: SpendInfo,
421    output: OutputInfo,
422    rcv: ValueCommitTrapdoor,
423}
424
425impl ActionInfo {
426    fn new(spend: SpendInfo, output: OutputInfo, rng: impl RngCore) -> Self {
427        ActionInfo {
428            spend,
429            output,
430            rcv: ValueCommitTrapdoor::random(rng),
431        }
432    }
433
434    /// Returns the value sum for this action.
435    fn value_sum(&self) -> ValueSum {
436        self.spend.note.value() - self.output.value
437    }
438
439    /// Builds the action.
440    ///
441    /// Defined in [Zcash Protocol Spec § 4.7.3: Sending Notes (Orchard)][orchardsend].
442    ///
443    /// [orchardsend]: https://zips.z.cash/protocol/nu5.pdf#orchardsend
444    #[cfg(feature = "circuit")]
445    fn build(self, mut rng: impl RngCore) -> (Action<SigningMetadata>, Circuit) {
446        let v_net = self.value_sum();
447        let cv_net = ValueCommitment::derive(v_net, self.rcv.clone());
448
449        let (nf_old, ak, alpha, rk) = self.spend.build(&mut rng);
450        let (note, cmx, encrypted_note) = self.output.build(&cv_net, nf_old, &mut rng);
451
452        (
453            Action::from_parts(
454                nf_old,
455                rk,
456                cmx,
457                encrypted_note,
458                cv_net,
459                SigningMetadata {
460                    dummy_ask: self.spend.dummy_sk.as_ref().map(SpendAuthorizingKey::from),
461                    parts: SigningParts { ak, alpha },
462                },
463            ),
464            Circuit::from_action_context_unchecked(self.spend, note, alpha, self.rcv),
465        )
466    }
467
468    fn build_for_pczt(self, mut rng: impl RngCore) -> crate::pczt::Action {
469        let v_net = self.value_sum();
470        let cv_net = ValueCommitment::derive(v_net, self.rcv.clone());
471
472        let spend = self.spend.into_pczt(&mut rng);
473        let output = self.output.into_pczt(&cv_net, spend.nullifier, &mut rng);
474
475        crate::pczt::Action {
476            cv_net,
477            spend,
478            output,
479            rcv: Some(self.rcv),
480        }
481    }
482}
483
484/// Type alias for an in-progress bundle that has no proofs or signatures.
485///
486/// This is returned by [`Builder::build`].
487#[cfg(feature = "circuit")]
488pub type UnauthorizedBundle<V> = Bundle<InProgress<Unproven, Unauthorized>, V>;
489
490/// Metadata about a bundle created by [`bundle`] or [`Builder::build`] that is not
491/// necessarily recoverable from the bundle itself.
492///
493/// This includes information about how [`Action`]s within the bundle are ordered (after
494/// padding and randomization) relative to the order in which spends and outputs were
495/// provided (to [`bundle`]), or the order in which [`Builder`] mutations were performed.
496#[derive(Debug, Clone, PartialEq, Eq)]
497pub struct BundleMetadata {
498    spend_indices: Vec<usize>,
499    output_indices: Vec<usize>,
500}
501
502impl BundleMetadata {
503    fn new(num_requested_spends: usize, num_requested_outputs: usize) -> Self {
504        BundleMetadata {
505            spend_indices: vec![0; num_requested_spends],
506            output_indices: vec![0; num_requested_outputs],
507        }
508    }
509
510    /// Returns the metadata for a [`Bundle`] that contains only dummy actions, if any.
511    pub fn empty() -> Self {
512        Self::new(0, 0)
513    }
514
515    /// Returns the index within the bundle of the [`Action`] corresponding to the `n`-th
516    /// spend specified in bundle construction. If a [`Builder`] was used, this refers to
517    /// the spend added by the `n`-th call to [`Builder::add_spend`].
518    ///
519    /// For the purpose of improving indistinguishability, actions are padded and note
520    /// positions are randomized when building bundles. This means that the bundle
521    /// consumer cannot assume that e.g. the first spend they added corresponds to the
522    /// first action in the bundle.
523    pub fn spend_action_index(&self, n: usize) -> Option<usize> {
524        self.spend_indices.get(n).copied()
525    }
526
527    /// Returns the index within the bundle of the [`Action`] corresponding to the `n`-th
528    /// output specified in bundle construction. If a [`Builder`] was used, this refers to
529    /// the output added by the `n`-th call to [`Builder::add_output`].
530    ///
531    /// For the purpose of improving indistinguishability, actions are padded and note
532    /// positions are randomized when building bundles. This means that the bundle
533    /// consumer cannot assume that e.g. the first output they added corresponds to the
534    /// first action in the bundle.
535    pub fn output_action_index(&self, n: usize) -> Option<usize> {
536        self.output_indices.get(n).copied()
537    }
538}
539
540/// A builder that constructs a [`Bundle`] from a set of notes to be spent, and outputs
541/// to receive funds.
542#[derive(Debug)]
543pub struct Builder {
544    spends: Vec<SpendInfo>,
545    outputs: Vec<OutputInfo>,
546    bundle_type: BundleType,
547    anchor: Anchor,
548}
549
550impl Builder {
551    /// Constructs a new empty builder for an Orchard bundle.
552    pub fn new(bundle_type: BundleType, anchor: Anchor) -> Self {
553        Builder {
554            spends: vec![],
555            outputs: vec![],
556            bundle_type,
557            anchor,
558        }
559    }
560
561    /// Adds a note to be spent in this transaction.
562    ///
563    /// - `note` is a spendable note, obtained by trial-decrypting an [`Action`] using the
564    ///   [`zcash_note_encryption`] crate instantiated with [`OrchardDomain`].
565    /// - `merkle_path` can be obtained using the [`incrementalmerkletree`] crate
566    ///   instantiated with [`MerkleHashOrchard`].
567    ///
568    /// Returns an error if the given Merkle path does not have the required anchor for
569    /// the given note.
570    ///
571    /// [`OrchardDomain`]: crate::note_encryption::OrchardDomain
572    /// [`MerkleHashOrchard`]: crate::tree::MerkleHashOrchard
573    pub fn add_spend(
574        &mut self,
575        fvk: FullViewingKey,
576        note: Note,
577        merkle_path: MerklePath,
578    ) -> Result<(), SpendError> {
579        let flags = self.bundle_type.flags();
580        if !flags.spends_enabled() {
581            return Err(SpendError::SpendsDisabled);
582        }
583
584        let spend = SpendInfo::new(fvk, note, merkle_path).ok_or(SpendError::FvkMismatch)?;
585
586        // Consistency check: all anchors must be equal.
587        if !spend.has_matching_anchor(&self.anchor) {
588            return Err(SpendError::AnchorMismatch);
589        }
590
591        self.spends.push(spend);
592
593        Ok(())
594    }
595
596    /// Adds an address which will receive funds in this transaction.
597    pub fn add_output(
598        &mut self,
599        ovk: Option<OutgoingViewingKey>,
600        recipient: Address,
601        value: NoteValue,
602        memo: [u8; 512],
603    ) -> Result<(), OutputError> {
604        let flags = self.bundle_type.flags();
605        if !flags.outputs_enabled() {
606            return Err(OutputError::OutputsDisabled);
607        }
608
609        self.outputs
610            .push(OutputInfo::new(ovk, recipient, value, memo));
611
612        Ok(())
613    }
614
615    /// Returns the action spend components that will be produced by the
616    /// transaction being constructed
617    pub fn spends(&self) -> &Vec<impl InputView<()>> {
618        &self.spends
619    }
620
621    /// Returns the action output components that will be produced by the
622    /// transaction being constructed
623    pub fn outputs(&self) -> &Vec<impl OutputView> {
624        &self.outputs
625    }
626
627    /// The net value of the bundle to be built. The value of all spends,
628    /// minus the value of all outputs.
629    ///
630    /// Useful for balancing a transaction, as the value balance of an individual bundle
631    /// can be non-zero. Each bundle's value balance is [added] to the transparent
632    /// transaction value pool, which [must not have a negative value]. (If it were
633    /// negative, the transaction would output more value than it receives in inputs.)
634    ///
635    /// [added]: https://zips.z.cash/protocol/protocol.pdf#orchardbalance
636    /// [must not have a negative value]: https://zips.z.cash/protocol/protocol.pdf#transactions
637    pub fn value_balance<V: TryFrom<i64>>(&self) -> Result<V, value::BalanceError> {
638        let value_balance = self
639            .spends
640            .iter()
641            .map(|spend| spend.note.value() - NoteValue::zero())
642            .chain(
643                self.outputs
644                    .iter()
645                    .map(|output| NoteValue::zero() - output.value),
646            )
647            .try_fold(ValueSum::zero(), |acc, note_value| acc + note_value)
648            .ok_or(BalanceError::Overflow)?;
649        i64::try_from(value_balance)
650            .and_then(|i| V::try_from(i).map_err(|_| value::BalanceError::Overflow))
651    }
652
653    /// Builds a bundle containing the given spent notes and outputs.
654    ///
655    /// The returned bundle will have no proof or signatures; these can be applied with
656    /// [`Bundle::create_proof`] and [`Bundle::apply_signatures`] respectively.
657    #[cfg(feature = "circuit")]
658    pub fn build<V: TryFrom<i64>>(
659        self,
660        rng: impl RngCore,
661    ) -> Result<Option<(UnauthorizedBundle<V>, BundleMetadata)>, BuildError> {
662        bundle(
663            rng,
664            self.anchor,
665            self.bundle_type,
666            self.spends,
667            self.outputs,
668        )
669    }
670
671    /// Builds a bundle containing the given spent notes and outputs along with their
672    /// metadata, for inclusion in a PCZT.
673    pub fn build_for_pczt(
674        self,
675        rng: impl RngCore,
676    ) -> Result<(crate::pczt::Bundle, BundleMetadata), BuildError> {
677        build_bundle(
678            rng,
679            self.anchor,
680            self.bundle_type,
681            self.spends,
682            self.outputs,
683            |pre_actions, flags, value_sum, bundle_meta, mut rng| {
684                // Create the actions.
685                let actions = pre_actions
686                    .into_iter()
687                    .map(|a| a.build_for_pczt(&mut rng))
688                    .collect::<Vec<_>>();
689
690                Ok((
691                    crate::pczt::Bundle {
692                        actions,
693                        flags,
694                        value_sum,
695                        anchor: self.anchor,
696                        zkproof: None,
697                        bsk: None,
698                    },
699                    bundle_meta,
700                ))
701            },
702        )
703    }
704}
705
706/// Builds a bundle containing the given spent notes and outputs.
707///
708/// The returned bundle will have no proof or signatures; these can be applied with
709/// [`Bundle::create_proof`] and [`Bundle::apply_signatures`] respectively.
710#[cfg(feature = "circuit")]
711pub fn bundle<V: TryFrom<i64>>(
712    rng: impl RngCore,
713    anchor: Anchor,
714    bundle_type: BundleType,
715    spends: Vec<SpendInfo>,
716    outputs: Vec<OutputInfo>,
717) -> Result<Option<(UnauthorizedBundle<V>, BundleMetadata)>, BuildError> {
718    build_bundle(
719        rng,
720        anchor,
721        bundle_type,
722        spends,
723        outputs,
724        |pre_actions, flags, value_balance, bundle_meta, mut rng| {
725            let result_value_balance: V = i64::try_from(value_balance)
726                .map_err(BuildError::ValueSum)
727                .and_then(|i| {
728                    V::try_from(i).map_err(|_| BuildError::ValueSum(value::BalanceError::Overflow))
729                })?;
730
731            // Compute the transaction binding signing key.
732            let bsk = pre_actions
733                .iter()
734                .map(|a| &a.rcv)
735                .sum::<ValueCommitTrapdoor>()
736                .into_bsk();
737
738            // Create the actions.
739            let (actions, circuits): (Vec<_>, Vec<_>) =
740                pre_actions.into_iter().map(|a| a.build(&mut rng)).unzip();
741
742            // Verify that bsk and bvk are consistent.
743            let bvk = (actions.iter().map(|a| a.cv_net()).sum::<ValueCommitment>()
744                - ValueCommitment::derive(value_balance, ValueCommitTrapdoor::zero()))
745            .into_bvk();
746            assert_eq!(redpallas::VerificationKey::from(&bsk), bvk);
747
748            Ok(NonEmpty::from_vec(actions).map(|actions| {
749                (
750                    Bundle::from_parts(
751                        actions,
752                        flags,
753                        result_value_balance,
754                        anchor,
755                        InProgress {
756                            proof: Unproven { circuits },
757                            sigs: Unauthorized { bsk },
758                        },
759                    ),
760                    bundle_meta,
761                )
762            }))
763        },
764    )
765}
766
767fn build_bundle<B, R: RngCore>(
768    mut rng: R,
769    anchor: Anchor,
770    bundle_type: BundleType,
771    spends: Vec<SpendInfo>,
772    outputs: Vec<OutputInfo>,
773    finisher: impl FnOnce(Vec<ActionInfo>, Flags, ValueSum, BundleMetadata, R) -> Result<B, BuildError>,
774) -> Result<B, BuildError> {
775    let flags = bundle_type.flags();
776
777    let num_requested_spends = spends.len();
778    if !flags.spends_enabled() && num_requested_spends > 0 {
779        return Err(BuildError::SpendsDisabled);
780    }
781
782    for spend in &spends {
783        if !spend.has_matching_anchor(&anchor) {
784            return Err(BuildError::AnchorMismatch);
785        }
786    }
787
788    let num_requested_outputs = outputs.len();
789    if !flags.outputs_enabled() && num_requested_outputs > 0 {
790        return Err(BuildError::OutputsDisabled);
791    }
792
793    let num_actions = bundle_type
794        .num_actions(num_requested_spends, num_requested_outputs)
795        .map_err(|_| BuildError::BundleTypeNotSatisfiable)?;
796
797    // Pair up the spends and outputs, extending with dummy values as necessary.
798    let (pre_actions, bundle_meta) = {
799        let mut indexed_spends = spends
800            .into_iter()
801            .chain(iter::repeat_with(|| SpendInfo::dummy(&mut rng)))
802            .enumerate()
803            .take(num_actions)
804            .collect::<Vec<_>>();
805
806        let mut indexed_outputs = outputs
807            .into_iter()
808            .chain(iter::repeat_with(|| OutputInfo::dummy(&mut rng)))
809            .enumerate()
810            .take(num_actions)
811            .collect::<Vec<_>>();
812
813        // Shuffle the spends and outputs, so that learning the position of a
814        // specific spent note or output note doesn't reveal anything on its own about
815        // the meaning of that note in the transaction context.
816        indexed_spends.shuffle(&mut rng);
817        indexed_outputs.shuffle(&mut rng);
818
819        let mut bundle_meta = BundleMetadata::new(num_requested_spends, num_requested_outputs);
820        let pre_actions = indexed_spends
821            .into_iter()
822            .zip(indexed_outputs)
823            .enumerate()
824            .map(|(action_idx, ((spend_idx, spend), (out_idx, output)))| {
825                // Record the post-randomization spend location
826                if spend_idx < num_requested_spends {
827                    bundle_meta.spend_indices[spend_idx] = action_idx;
828                }
829
830                // Record the post-randomization output location
831                if out_idx < num_requested_outputs {
832                    bundle_meta.output_indices[out_idx] = action_idx;
833                }
834
835                ActionInfo::new(spend, output, &mut rng)
836            })
837            .collect::<Vec<_>>();
838
839        (pre_actions, bundle_meta)
840    };
841
842    // Determine the value balance for this bundle, ensuring it is valid.
843    let value_balance = pre_actions
844        .iter()
845        .try_fold(ValueSum::zero(), |acc, action| acc + action.value_sum())
846        .ok_or(BalanceError::Overflow)?;
847
848    finisher(pre_actions, flags, value_balance, bundle_meta, rng)
849}
850
851/// Marker trait representing bundle signatures in the process of being created.
852pub trait InProgressSignatures: fmt::Debug {
853    /// The authorization type of an Orchard action in the process of being authorized.
854    type SpendAuth: fmt::Debug;
855}
856
857/// Marker for a bundle in the process of being built.
858#[derive(Clone, Debug)]
859pub struct InProgress<P, S: InProgressSignatures> {
860    proof: P,
861    sigs: S,
862}
863
864impl<P: fmt::Debug, S: InProgressSignatures> Authorization for InProgress<P, S> {
865    type SpendAuth = S::SpendAuth;
866}
867
868/// Marker for a bundle without a proof.
869///
870/// This struct contains the private data needed to create a [`Proof`] for a [`Bundle`].
871#[cfg(feature = "circuit")]
872#[derive(Clone, Debug)]
873pub struct Unproven {
874    circuits: Vec<Circuit>,
875}
876
877#[cfg(feature = "circuit")]
878impl<S: InProgressSignatures> InProgress<Unproven, S> {
879    /// Creates the proof for this bundle.
880    pub fn create_proof(
881        &self,
882        pk: &ProvingKey,
883        instances: &[Instance],
884        rng: impl RngCore,
885    ) -> Result<Proof, halo2_proofs::plonk::Error> {
886        Proof::create(pk, &self.proof.circuits, instances, rng)
887    }
888}
889
890#[cfg(feature = "circuit")]
891impl<S: InProgressSignatures, V> Bundle<InProgress<Unproven, S>, V> {
892    /// Creates the proof for this bundle.
893    pub fn create_proof(
894        self,
895        pk: &ProvingKey,
896        mut rng: impl RngCore,
897    ) -> Result<Bundle<InProgress<Proof, S>, V>, BuildError> {
898        let instances: Vec<_> = self
899            .actions()
900            .iter()
901            .map(|a| a.to_instance(*self.flags(), *self.anchor()))
902            .collect();
903        self.try_map_authorization(
904            &mut (),
905            |_, _, a| Ok(a),
906            |_, auth| {
907                let proof = auth.create_proof(pk, &instances, &mut rng)?;
908                Ok(InProgress {
909                    proof,
910                    sigs: auth.sigs,
911                })
912            },
913        )
914    }
915}
916
917/// The parts needed to sign an [`Action`].
918#[derive(Clone, Debug)]
919pub struct SigningParts {
920    /// The spend validating key for this action. Used to match spend authorizing keys to
921    /// actions they can create signatures for.
922    ak: SpendValidatingKey,
923    /// The randomization needed to derive the actual signing key for this note.
924    alpha: pallas::Scalar,
925}
926
927/// Marker for an unauthorized bundle with no signatures.
928#[derive(Clone, Debug)]
929pub struct Unauthorized {
930    bsk: redpallas::SigningKey<Binding>,
931}
932
933impl InProgressSignatures for Unauthorized {
934    type SpendAuth = SigningMetadata;
935}
936
937/// Container for metadata needed to sign an [`Action`].
938#[derive(Clone, Debug)]
939pub struct SigningMetadata {
940    /// If this action is spending a dummy note, this field holds that note's spend
941    /// authorizing key.
942    ///
943    /// These keys are used automatically in [`Bundle<Unauthorized>::prepare`] or
944    /// [`Bundle<Unauthorized>::apply_signatures`] to sign dummy spends.
945    dummy_ask: Option<SpendAuthorizingKey>,
946    parts: SigningParts,
947}
948
949/// Marker for a partially-authorized bundle, in the process of being signed.
950#[derive(Debug)]
951pub struct PartiallyAuthorized {
952    binding_signature: redpallas::Signature<Binding>,
953    sighash: [u8; 32],
954}
955
956impl InProgressSignatures for PartiallyAuthorized {
957    type SpendAuth = MaybeSigned;
958}
959
960/// A heisen[`Signature`] for a particular [`Action`].
961///
962/// [`Signature`]: redpallas::Signature
963#[derive(Debug)]
964pub enum MaybeSigned {
965    /// The information needed to sign this [`Action`].
966    SigningMetadata(SigningParts),
967    /// The signature for this [`Action`].
968    Signature(redpallas::Signature<SpendAuth>),
969}
970
971impl MaybeSigned {
972    fn finalize(self) -> Result<redpallas::Signature<SpendAuth>, BuildError> {
973        match self {
974            Self::Signature(sig) => Ok(sig),
975            _ => Err(BuildError::MissingSignatures),
976        }
977    }
978}
979
980impl<P: fmt::Debug, V> Bundle<InProgress<P, Unauthorized>, V> {
981    /// Loads the sighash into this bundle, preparing it for signing.
982    ///
983    /// This API ensures that all signatures are created over the same sighash.
984    pub fn prepare<R: RngCore + CryptoRng>(
985        self,
986        mut rng: R,
987        sighash: [u8; 32],
988    ) -> Bundle<InProgress<P, PartiallyAuthorized>, V> {
989        self.map_authorization(
990            &mut rng,
991            |rng, _, SigningMetadata { dummy_ask, parts }| {
992                // We can create signatures for dummy spends immediately.
993                dummy_ask
994                    .map(|ask| ask.randomize(&parts.alpha).sign(rng, &sighash))
995                    .map(MaybeSigned::Signature)
996                    .unwrap_or(MaybeSigned::SigningMetadata(parts))
997            },
998            |rng, auth| InProgress {
999                proof: auth.proof,
1000                sigs: PartiallyAuthorized {
1001                    binding_signature: auth.sigs.bsk.sign(rng, &sighash),
1002                    sighash,
1003                },
1004            },
1005        )
1006    }
1007}
1008
1009impl<V> Bundle<InProgress<Proof, Unauthorized>, V> {
1010    /// Applies signatures to this bundle, in order to authorize it.
1011    ///
1012    /// This is a helper method that wraps [`Bundle::prepare`], [`Bundle::sign`], and
1013    /// [`Bundle::finalize`].
1014    pub fn apply_signatures<R: RngCore + CryptoRng>(
1015        self,
1016        mut rng: R,
1017        sighash: [u8; 32],
1018        signing_keys: &[SpendAuthorizingKey],
1019    ) -> Result<Bundle<Authorized, V>, BuildError> {
1020        signing_keys
1021            .iter()
1022            .fold(self.prepare(&mut rng, sighash), |partial, ask| {
1023                partial.sign(&mut rng, ask)
1024            })
1025            .finalize()
1026    }
1027}
1028
1029impl<P: fmt::Debug, V> Bundle<InProgress<P, PartiallyAuthorized>, V> {
1030    /// Signs this bundle with the given [`SpendAuthorizingKey`].
1031    ///
1032    /// This will apply signatures for all notes controlled by this spending key.
1033    pub fn sign<R: RngCore + CryptoRng>(self, mut rng: R, ask: &SpendAuthorizingKey) -> Self {
1034        let expected_ak = ask.into();
1035        self.map_authorization(
1036            &mut rng,
1037            |rng, partial, maybe| match maybe {
1038                MaybeSigned::SigningMetadata(parts) if parts.ak == expected_ak => {
1039                    MaybeSigned::Signature(
1040                        ask.randomize(&parts.alpha).sign(rng, &partial.sigs.sighash),
1041                    )
1042                }
1043                s => s,
1044            },
1045            |_, partial| partial,
1046        )
1047    }
1048    /// Appends externally computed [`Signature`]s.
1049    ///
1050    /// Each signature will be applied to the one input for which it is valid. An error
1051    /// will be returned if the signature is not valid for any inputs, or if it is valid
1052    /// for more than one input.
1053    ///
1054    /// [`Signature`]: redpallas::Signature
1055    pub fn append_signatures(
1056        self,
1057        signatures: &[redpallas::Signature<SpendAuth>],
1058    ) -> Result<Self, BuildError> {
1059        signatures.iter().try_fold(self, Self::append_signature)
1060    }
1061
1062    fn append_signature(
1063        self,
1064        signature: &redpallas::Signature<SpendAuth>,
1065    ) -> Result<Self, BuildError> {
1066        let mut signature_valid_for = 0usize;
1067        let bundle = self.map_authorization(
1068            &mut signature_valid_for,
1069            |valid_for, partial, maybe| match maybe {
1070                MaybeSigned::SigningMetadata(parts) => {
1071                    let rk = parts.ak.randomize(&parts.alpha);
1072                    if rk.verify(&partial.sigs.sighash[..], signature).is_ok() {
1073                        *valid_for += 1;
1074                        MaybeSigned::Signature(signature.clone())
1075                    } else {
1076                        // Signature isn't for this input.
1077                        MaybeSigned::SigningMetadata(parts)
1078                    }
1079                }
1080                s => s,
1081            },
1082            |_, partial| partial,
1083        );
1084        match signature_valid_for {
1085            0 => Err(BuildError::InvalidExternalSignature),
1086            1 => Ok(bundle),
1087            _ => Err(BuildError::DuplicateSignature),
1088        }
1089    }
1090}
1091
1092impl<V> Bundle<InProgress<Proof, PartiallyAuthorized>, V> {
1093    /// Finalizes this bundle, enabling it to be included in a transaction.
1094    ///
1095    /// Returns an error if any signatures are missing.
1096    pub fn finalize(self) -> Result<Bundle<Authorized, V>, BuildError> {
1097        self.try_map_authorization(
1098            &mut (),
1099            |_, _, maybe| maybe.finalize(),
1100            |_, partial| {
1101                Ok(Authorized::from_parts(
1102                    partial.proof,
1103                    partial.sigs.binding_signature,
1104                ))
1105            },
1106        )
1107    }
1108}
1109
1110/// A trait that provides a minimized view of an Orchard input suitable for use in
1111/// fee and change calculation.
1112pub trait InputView<NoteRef> {
1113    /// An identifier for the input being spent.
1114    fn note_id(&self) -> &NoteRef;
1115    /// The value of the input being spent.
1116    fn value<V: From<u64>>(&self) -> V;
1117}
1118
1119impl InputView<()> for SpendInfo {
1120    fn note_id(&self) -> &() {
1121        // The builder does not make use of note identifiers, so we can just return the unit value.
1122        &()
1123    }
1124
1125    fn value<V: From<u64>>(&self) -> V {
1126        V::from(self.note.value().inner())
1127    }
1128}
1129
1130/// A trait that provides a minimized view of an Orchard output suitable for use in
1131/// fee and change calculation.
1132pub trait OutputView {
1133    /// The value of the output being produced.
1134    fn value<V: From<u64>>(&self) -> V;
1135}
1136
1137impl OutputView for OutputInfo {
1138    fn value<V: From<u64>>(&self) -> V {
1139        V::from(self.value.inner())
1140    }
1141}
1142
1143/// Generators for property testing.
1144#[cfg(all(feature = "circuit", any(test, feature = "test-dependencies")))]
1145#[cfg_attr(docsrs, doc(cfg(feature = "test-dependencies")))]
1146pub mod testing {
1147    use alloc::vec::Vec;
1148    use core::fmt::Debug;
1149
1150    use incrementalmerkletree::{frontier::Frontier, Hashable};
1151    use rand::{rngs::StdRng, CryptoRng, SeedableRng};
1152
1153    use proptest::collection::vec;
1154    use proptest::prelude::*;
1155
1156    use crate::{
1157        address::testing::arb_address,
1158        bundle::{Authorized, Bundle},
1159        circuit::ProvingKey,
1160        keys::{testing::arb_spending_key, FullViewingKey, SpendAuthorizingKey, SpendingKey},
1161        note::testing::arb_note,
1162        tree::{Anchor, MerkleHashOrchard, MerklePath},
1163        value::{testing::arb_positive_note_value, NoteValue, MAX_NOTE_VALUE},
1164        Address, Note,
1165    };
1166
1167    use super::{Builder, BundleType};
1168
1169    /// An intermediate type used for construction of arbitrary
1170    /// bundle values. This type is required because of a limitation
1171    /// of the proptest prop_compose! macro which does not correctly
1172    /// handle polymorphic generator functions. Instead of generating
1173    /// a bundle directly, we generate the bundle inputs, and then
1174    /// are able to use the `build` function to construct the bundle
1175    /// from these inputs, but using a `ValueBalance` implementation that
1176    /// is defined by the end user.
1177    #[derive(Debug)]
1178    struct ArbitraryBundleInputs<R> {
1179        rng: R,
1180        sk: SpendingKey,
1181        anchor: Anchor,
1182        notes: Vec<(Note, MerklePath)>,
1183        output_amounts: Vec<(Address, NoteValue)>,
1184    }
1185
1186    impl<R: RngCore + CryptoRng> ArbitraryBundleInputs<R> {
1187        /// Create a bundle from the set of arbitrary bundle inputs.
1188        fn into_bundle<V: TryFrom<i64>>(mut self) -> Bundle<Authorized, V> {
1189            let fvk = FullViewingKey::from(&self.sk);
1190            let mut builder = Builder::new(BundleType::DEFAULT, self.anchor);
1191
1192            for (note, path) in self.notes.into_iter() {
1193                builder.add_spend(fvk.clone(), note, path).unwrap();
1194            }
1195
1196            for (addr, value) in self.output_amounts.into_iter() {
1197                let scope = fvk.scope_for_address(&addr).unwrap();
1198                let ovk = fvk.to_ovk(scope);
1199
1200                builder
1201                    .add_output(Some(ovk.clone()), addr, value, [0u8; 512])
1202                    .unwrap();
1203            }
1204
1205            let pk = ProvingKey::build();
1206            builder
1207                .build(&mut self.rng)
1208                .unwrap()
1209                .unwrap()
1210                .0
1211                .create_proof(&pk, &mut self.rng)
1212                .unwrap()
1213                .prepare(&mut self.rng, [0; 32])
1214                .sign(&mut self.rng, &SpendAuthorizingKey::from(&self.sk))
1215                .finalize()
1216                .unwrap()
1217        }
1218    }
1219
1220    prop_compose! {
1221        /// Produce a random valid Orchard bundle.
1222        fn arb_bundle_inputs(sk: SpendingKey)
1223        (
1224            n_notes in 1usize..30,
1225            n_outputs in 1..30,
1226
1227        )
1228        (
1229            // generate note values that we're certain won't exceed MAX_NOTE_VALUE in total
1230            notes in vec(
1231                arb_positive_note_value(MAX_NOTE_VALUE / n_notes as u64).prop_flat_map(arb_note),
1232                n_notes
1233            ),
1234            output_amounts in vec(
1235                arb_address().prop_flat_map(move |a| {
1236                    arb_positive_note_value(MAX_NOTE_VALUE / n_outputs as u64)
1237                        .prop_map(move |v| (a, v))
1238                }),
1239                n_outputs as usize
1240            ),
1241            rng_seed in prop::array::uniform32(prop::num::u8::ANY)
1242        ) -> ArbitraryBundleInputs<StdRng> {
1243            use crate::constants::MERKLE_DEPTH_ORCHARD;
1244            let mut frontier = Frontier::<MerkleHashOrchard, { MERKLE_DEPTH_ORCHARD as u8 }>::empty();
1245            let mut notes_and_auth_paths: Vec<(Note, MerklePath)> = Vec::new();
1246
1247            for note in notes.iter() {
1248                let leaf = MerkleHashOrchard::from_cmx(&note.commitment().into());
1249                frontier.append(leaf);
1250
1251                let path = frontier
1252                    .witness(|addr| Some(<MerkleHashOrchard as Hashable>::empty_root(addr.level())))
1253                    .ok()
1254                    .flatten()
1255                    .expect("we can always construct a correct Merkle path");
1256                notes_and_auth_paths.push((*note, path.into()));
1257            }
1258
1259            ArbitraryBundleInputs {
1260                rng: StdRng::from_seed(rng_seed),
1261                sk,
1262                anchor: frontier.root().into(),
1263                notes: notes_and_auth_paths,
1264                output_amounts
1265            }
1266        }
1267    }
1268
1269    /// Produce an arbitrary valid Orchard bundle using a random spending key.
1270    pub fn arb_bundle<V: TryFrom<i64> + Debug>() -> impl Strategy<Value = Bundle<Authorized, V>> {
1271        arb_spending_key()
1272            .prop_flat_map(arb_bundle_inputs)
1273            .prop_map(|inputs| inputs.into_bundle::<V>())
1274    }
1275
1276    /// Produce an arbitrary valid Orchard bundle using a specified spending key.
1277    pub fn arb_bundle_with_key<V: TryFrom<i64> + Debug>(
1278        k: SpendingKey,
1279    ) -> impl Strategy<Value = Bundle<Authorized, V>> {
1280        arb_bundle_inputs(k).prop_map(|inputs| inputs.into_bundle::<V>())
1281    }
1282}
1283
1284#[cfg(all(test, feature = "circuit"))]
1285mod tests {
1286    use rand::rngs::OsRng;
1287
1288    use super::Builder;
1289    use crate::{
1290        builder::BundleType,
1291        bundle::{Authorized, Bundle},
1292        circuit::ProvingKey,
1293        constants::MERKLE_DEPTH_ORCHARD,
1294        keys::{FullViewingKey, Scope, SpendingKey},
1295        tree::EMPTY_ROOTS,
1296        value::NoteValue,
1297    };
1298
1299    #[test]
1300    fn shielding_bundle() {
1301        let pk = ProvingKey::build();
1302        let mut rng = OsRng;
1303
1304        let sk = SpendingKey::random(&mut rng);
1305        let fvk = FullViewingKey::from(&sk);
1306        let recipient = fvk.address_at(0u32, Scope::External);
1307
1308        let mut builder = Builder::new(
1309            BundleType::DEFAULT,
1310            EMPTY_ROOTS[MERKLE_DEPTH_ORCHARD].into(),
1311        );
1312
1313        builder
1314            .add_output(None, recipient, NoteValue::from_raw(5000), [0u8; 512])
1315            .unwrap();
1316        let balance: i64 = builder.value_balance().unwrap();
1317        assert_eq!(balance, -5000);
1318
1319        let bundle: Bundle<Authorized, i64> = builder
1320            .build(&mut rng)
1321            .unwrap()
1322            .unwrap()
1323            .0
1324            .create_proof(&pk, &mut rng)
1325            .unwrap()
1326            .prepare(rng, [0; 32])
1327            .finalize()
1328            .unwrap();
1329        assert_eq!(bundle.value_balance(), &(-5000))
1330    }
1331}