Skip to main content

sapling_crypto/
builder.rs

1//! Types and functions for building Sapling transaction components.
2
3#[cfg(feature = "wallet")]
4use alloc::collections::BTreeMap;
5use alloc::vec::Vec;
6use core::{fmt, iter, marker::PhantomData};
7
8use group::ff::Field;
9use incrementalmerkletree::Position;
10use rand::{Rng, seq::SliceRandom};
11use rand_core::CryptoRng;
12use redjubjub::{Binding, SpendAuth};
13use zcash_note_encryption::EphemeralKeyBytes;
14
15use crate::{
16    Anchor, Diversifier, MerklePath, NOTE_COMMITMENT_TREE_DEPTH, Node, Note, Nullifier,
17    PaymentAddress, SaplingIvk,
18    bundle::{Authorization, Authorized, Bundle, GrothProofBytes},
19    keys::{
20        EphemeralSecretKey, ExpandedSpendingKey, FullViewingKey, OutgoingViewingKey,
21        SpendAuthorizingKey, SpendValidatingKey,
22    },
23    note::ExtractedNoteCommitment,
24    note_encryption::{Zip212Enforcement, sapling_note_encryption},
25    util::generate_random_rseed_internal,
26    value::{NoteValue, ValueCommitTrapdoor, ValueCommitment, ValueSum},
27};
28
29#[cfg(feature = "circuit")]
30use crate::{
31    ProofGenerationKey,
32    bundle::{OutputDescription, SpendDescription},
33    circuit,
34    prover::{OutputProver, SpendProver},
35    value::{CommitmentSum, TrapdoorSum},
36};
37
38#[cfg(all(feature = "circuit", feature = "wallet"))]
39use crate::zip32::ExtendedSpendingKey;
40
41/// If there are any shielded inputs, always have at least two shielded outputs, padding
42/// with dummy outputs if necessary. See <https://github.com/zcash/zcash/issues/3615>.
43const MIN_SHIELDED_OUTPUTS: usize = 2;
44
45/// An enumeration of rules for Sapling bundle construction.
46#[derive(Clone, Copy, Debug, PartialEq, Eq)]
47pub enum BundleType {
48    /// A transactional bundle will be padded if necessary to contain at least 2 outputs,
49    /// irrespective of whether any genuine outputs are required.
50    Transactional {
51        /// A flag that, when set to `true`, indicates that the resulting bundle should be
52        /// produced with the minimum required number of spends (1) and outputs (2 with
53        /// padding) to be usable on its own in a transaction, irrespective of whether any
54        /// spends or outputs have been requested. If no explicit spends or outputs have
55        /// been added, all of the spends and outputs in the resulting bundle will be
56        /// dummies.
57        bundle_required: bool,
58    },
59    /// A coinbase bundle is required to have no spends. No output padding is performed.
60    Coinbase,
61}
62
63impl BundleType {
64    /// The default bundle type allows both spends and outputs, and does not require a
65    /// bundle to be produced if no spends or outputs have been added to the bundle.
66    pub const DEFAULT: BundleType = BundleType::Transactional {
67        bundle_required: false,
68    };
69
70    /// Returns the number of logical spends that a 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_spends(&self, requested_spends: usize) -> Result<usize, &'static str> {
76        match self {
77            BundleType::Transactional { bundle_required } => {
78                Ok(if *bundle_required || requested_spends > 0 {
79                    core::cmp::max(requested_spends, 1)
80                } else {
81                    0
82                })
83            }
84            BundleType::Coinbase => {
85                if requested_spends == 0 {
86                    Ok(0)
87                } else {
88                    Err("Spends not allowed in coinbase bundles")
89                }
90            }
91        }
92    }
93
94    /// Returns the number of logical outputs that a builder will produce in constructing a bundle
95    /// of this type, given the specified numbers of spends and outputs.
96    ///
97    /// Returns an error if the specified number of spends and outputs is incompatible with
98    /// this bundle type.
99    pub fn num_outputs(
100        &self,
101        requested_spends: usize,
102        requested_outputs: usize,
103    ) -> Result<usize, &'static str> {
104        match self {
105            BundleType::Transactional { bundle_required } => Ok(
106                if *bundle_required || requested_spends > 0 || requested_outputs > 0 {
107                    core::cmp::max(requested_outputs, MIN_SHIELDED_OUTPUTS)
108                } else {
109                    0
110                },
111            ),
112            BundleType::Coinbase => {
113                if requested_spends == 0 {
114                    Ok(requested_outputs)
115                } else {
116                    Err("Spends not allowed in coinbase bundles")
117                }
118            }
119        }
120    }
121}
122
123#[derive(Debug, PartialEq, Eq)]
124#[non_exhaustive]
125pub enum Error {
126    AnchorMismatch,
127    BindingSig,
128    /// A signature is valid for more than one input. This should never happen if `alpha`
129    /// is sampled correctly, and indicates a critical failure in randomness generation.
130    DuplicateSignature,
131    InvalidAddress,
132    InvalidAmount,
133    /// External signature is not valid.
134    InvalidExternalSignature,
135    /// A bundle could not be built because required signatures were missing.
136    MissingSignatures,
137    /// A required spending key was not provided.
138    MissingSpendingKey,
139    /// `Builder::build_for_pczt` requires [`Zip212Enforcement::On`].
140    PcztRequiresZip212,
141    SpendProof,
142    /// The bundle being constructed violated the construction rules for the requested bundle type.
143    BundleTypeNotSatisfiable,
144    /// The wrong spending key was provided.
145    WrongSpendingKey,
146}
147
148impl fmt::Display for Error {
149    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
150        match self {
151            Error::AnchorMismatch => {
152                write!(f, "Anchor mismatch (anchors for all spends must be equal)")
153            }
154            Error::BindingSig => write!(f, "Failed to create bindingSig"),
155            Error::DuplicateSignature => write!(f, "Signature valid for more than one input"),
156            Error::InvalidAddress => write!(f, "Invalid address"),
157            Error::InvalidAmount => write!(f, "Invalid amount"),
158            Error::InvalidExternalSignature => write!(f, "External signature was invalid"),
159            Error::MissingSignatures => write!(f, "Required signatures were missing during build"),
160            Error::MissingSpendingKey => write!(f, "A required spending key was not provided"),
161            Error::PcztRequiresZip212 => {
162                write!(f, "PCZTs require that ZIP 212 is enforced for outputs")
163            }
164            Error::SpendProof => write!(f, "Failed to create Sapling spend proof"),
165            Error::BundleTypeNotSatisfiable => {
166                f.write_str("Bundle structure did not conform to requested bundle type.")
167            }
168            Error::WrongSpendingKey => write!(f, "The wrong spending key was provided"),
169        }
170    }
171}
172
173#[cfg(feature = "std")]
174impl std::error::Error for Error {}
175
176/// A struct containing the information necessary to add a spend to a bundle.
177#[derive(Debug, Clone)]
178pub struct SpendInfo {
179    fvk: FullViewingKey,
180    note: Note,
181    merkle_path: MerklePath,
182    dummy_expsk: Option<ExpandedSpendingKey>,
183}
184
185impl SpendInfo {
186    /// Constructs a [`SpendInfo`] from its constituent parts.
187    pub fn new(fvk: FullViewingKey, note: Note, merkle_path: MerklePath) -> Self {
188        Self {
189            fvk,
190            note,
191            merkle_path,
192            dummy_expsk: None,
193        }
194    }
195
196    /// Returns the value of the note to be spent.
197    pub fn value(&self) -> NoteValue {
198        self.note.value()
199    }
200
201    /// Defined in [Zcash Protocol Spec § 4.8.2: Dummy Notes (Sapling)][saplingdummynotes].
202    ///
203    /// [saplingdummynotes]: https://zips.z.cash/protocol/protocol.pdf#saplingdummynotes
204    fn dummy<R: Rng>(mut rng: R) -> Self {
205        let (sk, _, note) = Note::dummy(&mut rng);
206        let merkle_path = MerklePath::from_parts(
207            iter::repeat_with(|| Node::from_scalar(jubjub::Base::random(&mut rng)))
208                .take(NOTE_COMMITMENT_TREE_DEPTH.into())
209                .collect(),
210            Position::from(0),
211        )
212        .expect("The path length corresponds to the length of the generated vector.");
213
214        SpendInfo {
215            fvk: FullViewingKey {
216                vk: sk.proof_generation_key().to_viewing_key(),
217                ovk: sk.ovk,
218            },
219            note,
220            merkle_path,
221            dummy_expsk: Some(sk),
222        }
223    }
224
225    fn has_matching_anchor(&self, anchor: &Anchor) -> bool {
226        if self.note.value() == NoteValue::ZERO {
227            true
228        } else {
229            let node = Node::from_cmu(&self.note.cmu());
230            &Anchor::from(self.merkle_path.root(node)) == anchor
231        }
232    }
233
234    fn prepare<R: Rng>(self, rng: R) -> PreparedSpendInfo {
235        PreparedSpendInfo {
236            fvk: self.fvk,
237            note: self.note,
238            merkle_path: self.merkle_path,
239            rcv: ValueCommitTrapdoor::random(rng),
240            dummy_expsk: self.dummy_expsk,
241        }
242    }
243}
244
245#[derive(Debug, Clone)]
246struct PreparedSpendInfo {
247    fvk: FullViewingKey,
248    note: Note,
249    merkle_path: MerklePath,
250    rcv: ValueCommitTrapdoor,
251    dummy_expsk: Option<ExpandedSpendingKey>,
252}
253
254impl PreparedSpendInfo {
255    fn build_inner<R: Rng>(
256        &self,
257        mut rng: R,
258    ) -> (
259        ValueCommitment,
260        Nullifier,
261        redjubjub::VerificationKey<SpendAuth>,
262        jubjub::Scalar,
263    ) {
264        // Construct the value commitment.
265        let alpha = jubjub::Fr::random(&mut rng);
266        let cv = ValueCommitment::derive(self.note.value(), self.rcv.clone());
267
268        let ak = self.fvk.vk.ak.clone();
269
270        // This is the result of the re-randomization, we compute it for the caller
271        let rk = ak.randomize(&alpha);
272
273        let nullifier = self
274            .note
275            .nf(&self.fvk.vk.nk, u64::from(self.merkle_path.position()));
276
277        (cv, nullifier, rk, alpha)
278    }
279
280    #[cfg(feature = "circuit")]
281    fn build<Pr: SpendProver, R: Rng>(
282        self,
283        proof_generation_key: Option<ProofGenerationKey>,
284        rng: R,
285    ) -> Result<SpendDescription<InProgress<Unproven, Unsigned>>, Error> {
286        let proof_generation_key = match (proof_generation_key, self.dummy_expsk.as_ref()) {
287            (Some(proof_generation_key), None) => Ok(proof_generation_key),
288            (None, Some(expsk)) => Ok(expsk.proof_generation_key()),
289            (Some(_), Some(_)) => Err(Error::WrongSpendingKey),
290            (None, None) => Err(Error::MissingSpendingKey),
291        }?;
292        let expected_vk = proof_generation_key.to_viewing_key();
293        if (&expected_vk.ak, &expected_vk.nk) != (&self.fvk.vk.ak, &self.fvk.vk.nk) {
294            return Err(Error::WrongSpendingKey);
295        }
296
297        let (cv, nullifier, rk, alpha) = self.build_inner(rng);
298
299        // Construct the value commitment.
300        let node = Node::from_cmu(&self.note.cmu());
301        let anchor = *self.merkle_path.root(node).inner();
302
303        let ak = self.fvk.vk.ak.clone();
304
305        let zkproof = Pr::prepare_circuit(
306            proof_generation_key,
307            *self.note.recipient().diversifier(),
308            *self.note.rseed(),
309            self.note.value(),
310            alpha,
311            self.rcv,
312            anchor,
313            self.merkle_path.clone(),
314        )
315        .ok_or(Error::SpendProof)?;
316
317        Ok(SpendDescription::from_parts(
318            cv,
319            anchor,
320            nullifier,
321            rk,
322            zkproof,
323            SigningMetadata {
324                dummy_ask: self.dummy_expsk.map(|expsk| expsk.ask),
325                parts: SigningParts { ak, alpha },
326            },
327        ))
328    }
329
330    #[cfg(feature = "wallet")]
331    fn into_pczt<R: Rng>(self, rng: R) -> crate::pczt::Spend {
332        let (cv, nullifier, rk, alpha) = self.build_inner(rng);
333
334        crate::pczt::Spend {
335            cv,
336            nullifier,
337            rk,
338            zkproof: None,
339            spend_auth_sig: None,
340            recipient: Some(self.note.recipient()),
341            value: Some(self.note.value()),
342            rseed: Some(*self.note.rseed()),
343            rcv: Some(self.rcv),
344            proof_generation_key: self
345                .dummy_expsk
346                .as_ref()
347                .map(|expsk| expsk.proof_generation_key()),
348            witness: Some(self.merkle_path),
349            alpha: Some(alpha),
350            zip32_derivation: None,
351            dummy_ask: self.dummy_expsk.map(|expsk| expsk.ask),
352            proprietary: BTreeMap::new(),
353        }
354    }
355}
356
357/// A struct containing the information required in order to construct a
358/// Sapling output to a transaction.
359#[derive(Clone)]
360pub struct OutputInfo {
361    /// `None` represents the `ovk = ⊥` case.
362    ovk: Option<OutgoingViewingKey>,
363    to: PaymentAddress,
364    value: NoteValue,
365    memo: [u8; 512],
366}
367
368impl OutputInfo {
369    /// Constructs a new [`OutputInfo`] from its constituent parts.
370    pub fn new(
371        ovk: Option<OutgoingViewingKey>,
372        to: PaymentAddress,
373        value: NoteValue,
374        memo: [u8; 512],
375    ) -> Self {
376        Self {
377            ovk,
378            to,
379            value,
380            memo,
381        }
382    }
383
384    /// Returns the recipient of the new output.
385    pub fn recipient(&self) -> PaymentAddress {
386        self.to
387    }
388
389    /// Returns the value of the output.
390    pub fn value(&self) -> NoteValue {
391        self.value
392    }
393
394    /// Constructs a new dummy Sapling output.
395    pub fn dummy<R: Rng>(mut rng: &mut R) -> Self {
396        // This is a dummy output
397        let dummy_to = {
398            let mut diversifier = Diversifier([0; 11]);
399            loop {
400                rng.fill_bytes(&mut diversifier.0);
401                let dummy_ivk = SaplingIvk(jubjub::Fr::random(&mut rng));
402                if let Some(addr) = dummy_ivk.to_payment_address(diversifier) {
403                    break addr;
404                }
405            }
406        };
407
408        Self::new(None, dummy_to, NoteValue::ZERO, [0u8; 512])
409    }
410
411    fn prepare<R: Rng>(
412        self,
413        rng: &mut R,
414        zip212_enforcement: Zip212Enforcement,
415    ) -> PreparedOutputInfo {
416        let rseed = generate_random_rseed_internal(zip212_enforcement, rng);
417
418        let note = Note::from_parts(self.to, self.value, rseed);
419
420        PreparedOutputInfo {
421            ovk: self.ovk,
422            note,
423            memo: self.memo,
424            rcv: ValueCommitTrapdoor::random(rng),
425        }
426    }
427}
428
429struct PreparedOutputInfo {
430    /// `None` represents the `ovk = ⊥` case.
431    ovk: Option<OutgoingViewingKey>,
432    note: Note,
433    memo: [u8; 512],
434    rcv: ValueCommitTrapdoor,
435}
436
437impl PreparedOutputInfo {
438    fn build_inner<P, R: Rng>(
439        &self,
440        zkproof: impl FnOnce(&EphemeralSecretKey) -> P,
441        rng: &mut R,
442    ) -> (
443        ValueCommitment,
444        ExtractedNoteCommitment,
445        EphemeralKeyBytes,
446        [u8; 580],
447        [u8; 80],
448        P,
449    ) {
450        let encryptor = sapling_note_encryption::<R>(self.ovk, self.note.clone(), self.memo, rng);
451
452        // Construct the value commitment.
453        let cv = ValueCommitment::derive(self.note.value(), self.rcv.clone());
454
455        let zkproof = zkproof(encryptor.esk());
456
457        let cmu = self.note.cmu();
458
459        let enc_ciphertext = encryptor.encrypt_note_plaintext();
460        let out_ciphertext = encryptor.encrypt_outgoing_plaintext(&cv, &cmu, rng);
461
462        let epk = encryptor.epk();
463
464        (
465            cv,
466            cmu,
467            epk.to_bytes(),
468            enc_ciphertext,
469            out_ciphertext,
470            zkproof,
471        )
472    }
473
474    #[cfg(feature = "circuit")]
475    fn build<Pr: OutputProver, R: Rng>(self, rng: &mut R) -> OutputDescription<circuit::Output> {
476        let (cv, cmu, ephemeral_key, enc_ciphertext, out_ciphertext, zkproof) = self.build_inner(
477            |esk| {
478                Pr::prepare_circuit(
479                    esk,
480                    self.note.recipient(),
481                    self.note.rcm(),
482                    self.note.value(),
483                    self.rcv.clone(),
484                )
485            },
486            rng,
487        );
488
489        OutputDescription::from_parts(
490            cv,
491            cmu,
492            ephemeral_key,
493            enc_ciphertext,
494            out_ciphertext,
495            zkproof,
496        )
497    }
498
499    #[cfg(feature = "wallet")]
500    fn into_pczt<R: Rng>(self, rng: &mut R) -> crate::pczt::Output {
501        let (cv, cmu, ephemeral_key, enc_ciphertext, out_ciphertext, _) =
502            self.build_inner(|_| (), rng);
503
504        let rseed = match self.note.rseed() {
505            crate::Rseed::BeforeZip212(_) => {
506                panic!("Builder::build_for_pczt should prevent pre-ZIP 212 outputs")
507            }
508            crate::Rseed::AfterZip212(rseed) => Some(*rseed),
509        };
510
511        crate::pczt::Output {
512            cv,
513            cmu,
514            ephemeral_key,
515            enc_ciphertext,
516            out_ciphertext,
517            zkproof: None,
518            recipient: Some(self.note.recipient()),
519            value: Some(self.note.value()),
520            rseed,
521            rcv: Some(self.rcv),
522            // TODO: Save this?
523            ock: None,
524            zip32_derivation: None,
525            user_address: None,
526            proprietary: BTreeMap::new(),
527        }
528    }
529}
530
531/// Metadata about a transaction created by a [`Builder`].
532#[derive(Debug, Clone, PartialEq, Eq)]
533pub struct SaplingMetadata {
534    spend_indices: Vec<usize>,
535    output_indices: Vec<usize>,
536}
537
538impl SaplingMetadata {
539    pub fn empty() -> Self {
540        SaplingMetadata {
541            spend_indices: vec![],
542            output_indices: vec![],
543        }
544    }
545
546    /// Returns the index within the transaction of the [`SpendDescription`] corresponding
547    /// to the `n`-th call to [`Builder::add_spend`].
548    ///
549    /// Note positions are randomized when building transactions for indistinguishability.
550    /// This means that the transaction consumer cannot assume that e.g. the first spend
551    /// they added (via the first call to [`Builder::add_spend`]) is the first
552    /// [`SpendDescription`] in the transaction.
553    pub fn spend_index(&self, n: usize) -> Option<usize> {
554        self.spend_indices.get(n).copied()
555    }
556
557    /// Returns the index within the transaction of the [`OutputDescription`] corresponding
558    /// to the `n`-th call to [`Builder::add_output`].
559    ///
560    /// Note positions are randomized when building transactions for indistinguishability.
561    /// This means that the transaction consumer cannot assume that e.g. the first output
562    /// they added (via the first call to [`Builder::add_output`]) is the first
563    /// [`OutputDescription`] in the transaction.
564    pub fn output_index(&self, n: usize) -> Option<usize> {
565        self.output_indices.get(n).copied()
566    }
567}
568
569/// A mutable builder type for constructing Sapling bundles.
570pub struct Builder {
571    value_balance: ValueSum,
572    spends: Vec<SpendInfo>,
573    outputs: Vec<OutputInfo>,
574    zip212_enforcement: Zip212Enforcement,
575    bundle_type: BundleType,
576    anchor: Anchor,
577}
578
579impl Builder {
580    pub fn new(
581        zip212_enforcement: Zip212Enforcement,
582        bundle_type: BundleType,
583        anchor: Anchor,
584    ) -> Self {
585        Builder {
586            value_balance: ValueSum::zero(),
587            spends: vec![],
588            outputs: vec![],
589            zip212_enforcement,
590            bundle_type,
591            anchor,
592        }
593    }
594
595    /// Returns the list of Sapling inputs that have been added to the builder.
596    pub fn inputs(&self) -> &[SpendInfo] {
597        &self.spends
598    }
599
600    /// Returns the Sapling outputs that have been added to the builder.
601    pub fn outputs(&self) -> &[OutputInfo] {
602        &self.outputs
603    }
604
605    /// Returns the net value represented by the spends and outputs added to this builder,
606    /// or an error if the values added to this builder overflow the range of a Zcash
607    /// monetary amount.
608    fn try_value_balance<V: TryFrom<i64>>(&self) -> Result<V, Error> {
609        self.value_balance
610            .try_into()
611            .map_err(|_| ())
612            .and_then(|vb| V::try_from(vb).map_err(|_| ()))
613            .map_err(|()| Error::InvalidAmount)
614    }
615
616    /// Returns the net value represented by the spends and outputs added to this builder.
617    pub fn value_balance<V: TryFrom<i64>>(&self) -> V {
618        self.try_value_balance()
619            .expect("we check this when mutating self.value_balance")
620    }
621
622    /// Adds a Sapling note to be spent in this transaction.
623    ///
624    /// Returns an error if the given Merkle path does not have the same anchor as the
625    /// paths for previous Sapling notes.
626    pub fn add_spend(
627        &mut self,
628        fvk: FullViewingKey,
629        note: Note,
630        merkle_path: MerklePath,
631    ) -> Result<(), Error> {
632        let spend = SpendInfo::new(fvk, note, merkle_path);
633
634        // Consistency check: all anchors must equal the first one
635        match self.bundle_type {
636            BundleType::Transactional { .. } => {
637                if !spend.has_matching_anchor(&self.anchor) {
638                    return Err(Error::AnchorMismatch);
639                }
640            }
641            BundleType::Coinbase => {
642                return Err(Error::BundleTypeNotSatisfiable);
643            }
644        }
645
646        self.value_balance = (self.value_balance + spend.value()).ok_or(Error::InvalidAmount)?;
647        self.try_value_balance::<i64>()?;
648
649        self.spends.push(spend);
650
651        Ok(())
652    }
653
654    /// Adds a Sapling address to send funds to.
655    pub fn add_output(
656        &mut self,
657        ovk: Option<OutgoingViewingKey>,
658        to: PaymentAddress,
659        value: NoteValue,
660        memo: [u8; 512],
661    ) -> Result<(), Error> {
662        let output = OutputInfo::new(ovk, to, value, memo);
663
664        self.value_balance = (self.value_balance - value).ok_or(Error::InvalidAddress)?;
665        self.try_value_balance::<i64>()?;
666
667        self.outputs.push(output);
668
669        Ok(())
670    }
671
672    /// Constructs the Sapling bundle from the builder's accumulated state.
673    #[cfg(all(feature = "circuit", feature = "wallet"))]
674    pub fn build<SP: SpendProver, OP: OutputProver, R: Rng, V: TryFrom<i64>>(
675        self,
676        extsks: &[ExtendedSpendingKey],
677        rng: R,
678    ) -> Result<Option<(UnauthorizedBundle<V>, SaplingMetadata)>, Error> {
679        bundle::<SP, OP, _, _>(
680            rng,
681            self.bundle_type,
682            self.zip212_enforcement,
683            self.anchor,
684            self.spends,
685            self.outputs,
686            extsks,
687        )
688    }
689
690    /// Constructs an output-only coinbase bundle without wallet derivation keys.
691    ///
692    /// Returns [`Error::BundleTypeNotSatisfiable`] if the builder was not created
693    /// with [`BundleType::Coinbase`].
694    #[cfg(feature = "circuit")]
695    pub fn build_coinbase<SP: SpendProver, OP: OutputProver, R: Rng, V: TryFrom<i64>>(
696        self,
697        rng: R,
698    ) -> Result<Option<(UnauthorizedBundle<V>, SaplingMetadata)>, Error> {
699        if self.bundle_type != BundleType::Coinbase {
700            return Err(Error::BundleTypeNotSatisfiable);
701        }
702        bundle_with_key_lookup::<SP, OP, _, _>(
703            rng,
704            self.bundle_type,
705            self.zip212_enforcement,
706            self.anchor,
707            self.spends,
708            self.outputs,
709            |_| None,
710        )
711    }
712
713    /// Builds a bundle containing the given spent notes and outputs along with their
714    /// metadata, for inclusion in a PCZT.
715    #[cfg(feature = "wallet")]
716    pub fn build_for_pczt(
717        self,
718        rng: impl Rng,
719    ) -> Result<(crate::pczt::Bundle, SaplingMetadata), Error> {
720        match self.zip212_enforcement {
721            Zip212Enforcement::Off | Zip212Enforcement::GracePeriod => {
722                Err(Error::PcztRequiresZip212)
723            }
724            Zip212Enforcement::On => build_bundle(
725                rng,
726                self.bundle_type,
727                Zip212Enforcement::On,
728                self.anchor,
729                self.spends,
730                self.outputs,
731                |spend_infos, output_infos, value_sum, tx_metadata, mut rng| {
732                    // Create the PCZT Spends and Outputs.
733                    let spends = spend_infos
734                        .into_iter()
735                        .map(|a| a.into_pczt(&mut rng))
736                        .collect::<Vec<_>>();
737                    let outputs = output_infos
738                        .into_iter()
739                        .map(|a| a.into_pczt(&mut rng))
740                        .collect::<Vec<_>>();
741
742                    Ok((
743                        crate::pczt::Bundle {
744                            spends,
745                            outputs,
746                            value_sum,
747                            anchor: self.anchor,
748                            bsk: None,
749                        },
750                        tx_metadata,
751                    ))
752                },
753            ),
754        }
755    }
756}
757
758/// Constructs a new Sapling transaction bundle of the given type from the specified set of spends
759/// and outputs.
760#[cfg(all(feature = "circuit", feature = "wallet"))]
761pub fn bundle<SP: SpendProver, OP: OutputProver, R: Rng, V: TryFrom<i64>>(
762    rng: R,
763    bundle_type: BundleType,
764    zip212_enforcement: Zip212Enforcement,
765    anchor: Anchor,
766    spends: Vec<SpendInfo>,
767    outputs: Vec<OutputInfo>,
768    extsks: &[ExtendedSpendingKey],
769) -> Result<Option<(UnauthorizedBundle<V>, SaplingMetadata)>, Error> {
770    bundle_with_key_lookup::<SP, OP, _, _>(
771        rng,
772        bundle_type,
773        zip212_enforcement,
774        anchor,
775        spends,
776        outputs,
777        |fvk| {
778            extsks.iter().find_map(|extsk| {
779                let dfvk = extsk.to_diversifiable_full_viewing_key();
780                (dfvk.fvk().to_bytes() == fvk.to_bytes())
781                    .then(|| extsk.expsk.proof_generation_key())
782            })
783        },
784    )
785}
786
787#[allow(clippy::too_many_arguments)]
788#[cfg(feature = "circuit")]
789fn bundle_with_key_lookup<SP: SpendProver, OP: OutputProver, R: Rng, V: TryFrom<i64>>(
790    rng: R,
791    bundle_type: BundleType,
792    zip212_enforcement: Zip212Enforcement,
793    anchor: Anchor,
794    spends: Vec<SpendInfo>,
795    outputs: Vec<OutputInfo>,
796    key_for: impl Fn(&FullViewingKey) -> Option<ProofGenerationKey>,
797) -> Result<Option<(UnauthorizedBundle<V>, SaplingMetadata)>, Error> {
798    build_bundle(
799        rng,
800        bundle_type,
801        zip212_enforcement,
802        anchor,
803        spends,
804        outputs,
805        |spend_infos, output_infos, value_balance, tx_metadata, mut rng| {
806            let value_balance_i64 =
807                i64::try_from(value_balance).map_err(|_| Error::InvalidAmount)?;
808
809            // Compute the transaction binding signing key.
810            let bsk = {
811                let spends: TrapdoorSum = spend_infos.iter().map(|spend| &spend.rcv).sum();
812                let outputs: TrapdoorSum = output_infos.iter().map(|output| &output.rcv).sum();
813                (spends - outputs).into_bsk()
814            };
815
816            // Create the unauthorized Spend and Output descriptions.
817            let shielded_spends = spend_infos
818                .into_iter()
819                .map(|a| {
820                    // Look up the proof generation key for non-dummy spends.
821                    let proof_generation_key =
822                        a.dummy_expsk.is_none().then(|| key_for(&a.fvk)).flatten();
823                    a.build::<SP, _>(proof_generation_key, &mut rng)
824                })
825                .collect::<Result<Vec<_>, _>>()?;
826            let shielded_outputs = output_infos
827                .into_iter()
828                .map(|a| a.build::<OP, _>(&mut rng))
829                .collect::<Vec<_>>();
830
831            // Verify that bsk and bvk are consistent.
832            let bvk = {
833                let spends = shielded_spends
834                    .iter()
835                    .map(|spend| spend.cv())
836                    .sum::<CommitmentSum>();
837                let outputs = shielded_outputs
838                    .iter()
839                    .map(|output| output.cv())
840                    .sum::<CommitmentSum>();
841                (spends - outputs).into_bvk(value_balance_i64)
842            };
843            assert_eq!(redjubjub::VerificationKey::from(&bsk), bvk);
844
845            Ok(Bundle::from_parts(
846                shielded_spends,
847                shielded_outputs,
848                V::try_from(value_balance_i64).map_err(|_| Error::InvalidAmount)?,
849                InProgress {
850                    sigs: Unsigned { bsk },
851                    _proof_state: PhantomData,
852                },
853            )
854            .map(|b| (b, tx_metadata)))
855        },
856    )
857}
858
859fn build_bundle<B, R: Rng>(
860    mut rng: R,
861    bundle_type: BundleType,
862    zip212_enforcement: Zip212Enforcement,
863    anchor: Anchor,
864    spends: Vec<SpendInfo>,
865    outputs: Vec<OutputInfo>,
866    finisher: impl FnOnce(
867        Vec<PreparedSpendInfo>,
868        Vec<PreparedOutputInfo>,
869        ValueSum,
870        SaplingMetadata,
871        R,
872    ) -> Result<B, Error>,
873) -> Result<B, Error> {
874    match bundle_type {
875        BundleType::Transactional { .. } => {
876            for spend in &spends {
877                if !spend.has_matching_anchor(&anchor) {
878                    return Err(Error::AnchorMismatch);
879                }
880            }
881        }
882        BundleType::Coinbase => {
883            if !spends.is_empty() {
884                return Err(Error::BundleTypeNotSatisfiable);
885            }
886        }
887    }
888
889    let requested_spend_count = spends.len();
890    let bundle_spend_count = bundle_type
891        .num_spends(requested_spend_count)
892        .map_err(|_| Error::BundleTypeNotSatisfiable)?;
893
894    let requested_output_count = outputs.len();
895    let bundle_output_count = bundle_type
896        .num_outputs(spends.len(), requested_output_count)
897        .map_err(|_| Error::BundleTypeNotSatisfiable)?;
898    assert!(requested_output_count <= bundle_output_count);
899
900    // Set up the transaction metadata that will be used to record how
901    // inputs and outputs are shuffled.
902    let mut tx_metadata = SaplingMetadata::empty();
903    tx_metadata.spend_indices.resize(spends.len(), 0);
904    tx_metadata.output_indices.resize(requested_output_count, 0);
905
906    // Create any required dummy spends and record initial spend positions
907    let mut indexed_spends: Vec<_> = spends
908        .into_iter()
909        .chain(iter::repeat_with(|| SpendInfo::dummy(&mut rng)))
910        .enumerate()
911        .take(bundle_spend_count)
912        .collect();
913
914    // Create any required dummy outputs and record initial output positions
915    let mut indexed_outputs: Vec<_> = outputs
916        .into_iter()
917        .chain(iter::repeat_with(|| OutputInfo::dummy(&mut rng)))
918        .enumerate()
919        .take(bundle_output_count)
920        .collect();
921
922    // Randomize order of inputs and outputs
923    indexed_spends.shuffle(&mut rng);
924    indexed_outputs.shuffle(&mut rng);
925
926    // Record the transaction metadata and create prepared spends and outputs.
927    let spend_infos = indexed_spends
928        .into_iter()
929        .enumerate()
930        .map(|(i, (pos, spend))| {
931            // Record the post-randomized spend location
932            if pos < requested_spend_count {
933                tx_metadata.spend_indices[pos] = i;
934            }
935
936            spend.prepare(&mut rng)
937        })
938        .collect::<Vec<_>>();
939    let output_infos = indexed_outputs
940        .into_iter()
941        .enumerate()
942        .map(|(i, (pos, output))| {
943            // Record the post-randomized output location. Due to how `indexed_outputs` is
944            // constructed, all non-dummy positions will be less than requested_output_count
945            if pos < requested_output_count {
946                tx_metadata.output_indices[pos] = i;
947            }
948
949            output.prepare(&mut rng, zip212_enforcement)
950        })
951        .collect::<Vec<_>>();
952
953    // Compute the Sapling value balance of the bundle for comparison to `bvk` and `bsk`
954    let input_total = spend_infos
955        .iter()
956        .try_fold(ValueSum::zero(), |balance, spend| {
957            (balance + spend.note.value()).ok_or(Error::InvalidAmount)
958        })?;
959    let value_balance = output_infos
960        .iter()
961        .try_fold(input_total, |balance, output| {
962            (balance - output.note.value()).ok_or(Error::InvalidAmount)
963        })?;
964
965    finisher(spend_infos, output_infos, value_balance, tx_metadata, rng)
966}
967
968/// Type alias for an in-progress bundle that has no proofs or signatures.
969///
970/// This is returned by `Builder::build` or [`Builder::build_coinbase`].
971#[cfg(feature = "circuit")]
972pub type UnauthorizedBundle<V> = Bundle<InProgress<Unproven, Unsigned>, V>;
973
974/// Marker trait representing bundle proofs in the process of being created.
975pub trait InProgressProofs: fmt::Debug {
976    /// The proof type of a Sapling spend in the process of being proven.
977    type SpendProof: Clone + fmt::Debug;
978    /// The proof type of a Sapling output in the process of being proven.
979    type OutputProof: Clone + fmt::Debug;
980}
981
982/// Marker trait representing bundle signatures in the process of being created.
983pub trait InProgressSignatures: fmt::Debug {
984    /// The authorization type of a Sapling spend or output in the process of being
985    /// authorized.
986    type AuthSig: Clone + fmt::Debug;
987}
988
989/// Marker for a bundle in the process of being built.
990#[derive(Clone, Debug)]
991pub struct InProgress<P: InProgressProofs, S: InProgressSignatures> {
992    sigs: S,
993    _proof_state: PhantomData<P>,
994}
995
996impl<P: InProgressProofs, S: InProgressSignatures> Authorization for InProgress<P, S> {
997    type SpendProof = P::SpendProof;
998    type OutputProof = P::OutputProof;
999    type AuthSig = S::AuthSig;
1000}
1001
1002/// Marker for a [`Bundle`] without proofs.
1003///
1004/// The [`SpendDescription`]s and [`OutputDescription`]s within the bundle contain the
1005/// private data needed to create proofs.
1006#[cfg(feature = "circuit")]
1007#[derive(Clone, Copy, Debug)]
1008pub struct Unproven;
1009
1010#[cfg(feature = "circuit")]
1011impl InProgressProofs for Unproven {
1012    type SpendProof = circuit::Spend;
1013    type OutputProof = circuit::Output;
1014}
1015
1016/// Marker for a [`Bundle`] with proofs.
1017#[derive(Clone, Copy, Debug)]
1018pub struct Proven;
1019
1020impl InProgressProofs for Proven {
1021    type SpendProof = GrothProofBytes;
1022    type OutputProof = GrothProofBytes;
1023}
1024
1025/// Reports on the progress made towards creating proofs for a bundle.
1026pub trait ProverProgress {
1027    /// Updates the progress instance with the number of steps completed and the total
1028    /// number of steps.
1029    fn update(&mut self, cur: u32, end: u32);
1030}
1031
1032impl ProverProgress for () {
1033    fn update(&mut self, _: u32, _: u32) {}
1034}
1035
1036#[cfg(feature = "std")]
1037impl<U: From<(u32, u32)>> ProverProgress for std::sync::mpsc::Sender<U> {
1038    fn update(&mut self, cur: u32, end: u32) {
1039        // If the send fails, we should ignore the error, not crash.
1040        self.send(U::from((cur, end))).unwrap_or(());
1041    }
1042}
1043
1044impl<U: ProverProgress> ProverProgress for &mut U {
1045    fn update(&mut self, cur: u32, end: u32) {
1046        (*self).update(cur, end);
1047    }
1048}
1049
1050#[cfg(feature = "circuit")]
1051struct CreateProofs<'a, SP: SpendProver, OP: OutputProver, R: Rng, U: ProverProgress> {
1052    spend_prover: &'a SP,
1053    output_prover: &'a OP,
1054    rng: R,
1055    progress_notifier: U,
1056    total_progress: u32,
1057    progress: u32,
1058}
1059
1060#[cfg(feature = "circuit")]
1061impl<'a, SP: SpendProver, OP: OutputProver, R: Rng, U: ProverProgress>
1062    CreateProofs<'a, SP, OP, R, U>
1063{
1064    fn new(
1065        spend_prover: &'a SP,
1066        output_prover: &'a OP,
1067        rng: R,
1068        progress_notifier: U,
1069        total_progress: u32,
1070    ) -> Self {
1071        // Keep track of the total number of steps computed
1072        Self {
1073            spend_prover,
1074            output_prover,
1075            rng,
1076            progress_notifier,
1077            total_progress,
1078            progress: 0u32,
1079        }
1080    }
1081
1082    fn update_progress(&mut self) {
1083        // Update progress and send a notification on the channel
1084        self.progress += 1;
1085        self.progress_notifier
1086            .update(self.progress, self.total_progress);
1087    }
1088
1089    fn map_spend_proof(&mut self, spend: circuit::Spend) -> GrothProofBytes {
1090        let proof = self.spend_prover.create_proof(spend, &mut self.rng);
1091        self.update_progress();
1092        SP::encode_proof(proof)
1093    }
1094
1095    fn map_output_proof(&mut self, output: circuit::Output) -> GrothProofBytes {
1096        let proof = self.output_prover.create_proof(output, &mut self.rng);
1097        self.update_progress();
1098        OP::encode_proof(proof)
1099    }
1100
1101    fn map_authorization<S: InProgressSignatures>(
1102        &mut self,
1103        a: InProgress<Unproven, S>,
1104    ) -> InProgress<Proven, S> {
1105        InProgress {
1106            sigs: a.sigs,
1107            _proof_state: PhantomData,
1108        }
1109    }
1110}
1111
1112#[cfg(feature = "circuit")]
1113impl<S: InProgressSignatures, V> Bundle<InProgress<Unproven, S>, V> {
1114    /// Creates the proofs for this bundle.
1115    pub fn create_proofs<SP: SpendProver, OP: OutputProver>(
1116        self,
1117        spend_prover: &SP,
1118        output_prover: &OP,
1119        rng: impl Rng,
1120        progress_notifier: impl ProverProgress,
1121    ) -> Bundle<InProgress<Proven, S>, V> {
1122        let total_progress =
1123            self.shielded_spends().len() as u32 + self.shielded_outputs().len() as u32;
1124        let mut cp = CreateProofs::new(
1125            spend_prover,
1126            output_prover,
1127            rng,
1128            progress_notifier,
1129            total_progress,
1130        );
1131
1132        self.map_authorization(
1133            &mut cp,
1134            |cp, spend| cp.map_spend_proof(spend),
1135            |cp, output| cp.map_output_proof(output),
1136            |_cp, sig| sig,
1137            |cp, auth| cp.map_authorization(auth),
1138        )
1139    }
1140}
1141
1142/// Marker for an unauthorized bundle with no signatures.
1143#[derive(Clone)]
1144pub struct Unsigned {
1145    bsk: redjubjub::SigningKey<Binding>,
1146}
1147
1148impl fmt::Debug for Unsigned {
1149    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1150        f.debug_struct("Unsigned").finish_non_exhaustive()
1151    }
1152}
1153
1154impl InProgressSignatures for Unsigned {
1155    type AuthSig = SigningMetadata;
1156}
1157
1158/// The parts needed to sign a [`SpendDescription`].
1159#[derive(Clone, Debug)]
1160pub struct SigningParts {
1161    /// The spend validating key for this spend description. Used to match spend
1162    /// authorizing keys to spend descriptions they can create signatures for.
1163    ak: SpendValidatingKey,
1164    /// The randomization needed to derive the actual signing key for this note.
1165    alpha: jubjub::Scalar,
1166}
1167
1168/// Marker for a partially-authorized bundle, in the process of being signed.
1169#[derive(Clone, Debug)]
1170pub struct PartiallyAuthorized {
1171    binding_signature: redjubjub::Signature<Binding>,
1172    sighash: [u8; 32],
1173}
1174
1175/// Container for metadata needed to sign a Sapling input.
1176#[derive(Clone, Debug)]
1177pub struct SigningMetadata {
1178    /// If this action is spending a dummy note, this field holds that note's spend
1179    /// authorizing key.
1180    ///
1181    /// These keys are used automatically in [`Bundle<Unauthorized>::prepare`] or
1182    /// [`Bundle<Unauthorized>::apply_signatures`] to sign dummy spends.
1183    dummy_ask: Option<SpendAuthorizingKey>,
1184    parts: SigningParts,
1185}
1186
1187impl InProgressSignatures for PartiallyAuthorized {
1188    type AuthSig = MaybeSigned;
1189}
1190
1191/// A heisen[`Signature`] for a particular [`SpendDescription`].
1192///
1193/// [`Signature`]: redjubjub::Signature
1194#[derive(Clone, Debug)]
1195pub enum MaybeSigned {
1196    /// The information needed to sign this [`SpendDescription`].
1197    SigningParts(SigningParts),
1198    /// The signature for this [`SpendDescription`].
1199    Signature(redjubjub::Signature<SpendAuth>),
1200}
1201
1202impl MaybeSigned {
1203    fn finalize(self) -> Result<redjubjub::Signature<SpendAuth>, Error> {
1204        match self {
1205            Self::Signature(sig) => Ok(sig),
1206            _ => Err(Error::MissingSignatures),
1207        }
1208    }
1209}
1210
1211impl<P: InProgressProofs, V> Bundle<InProgress<P, Unsigned>, V> {
1212    /// Loads the sighash into this bundle, preparing it for signing.
1213    ///
1214    /// This API ensures that all signatures are created over the same sighash.
1215    pub fn prepare<R: Rng + CryptoRng>(
1216        self,
1217        mut rng: R,
1218        sighash: [u8; 32],
1219    ) -> Bundle<InProgress<P, PartiallyAuthorized>, V> {
1220        self.map_authorization(
1221            &mut rng,
1222            |_, proof| proof,
1223            |_, proof| proof,
1224            |rng, SigningMetadata { dummy_ask, parts }| match dummy_ask {
1225                None => MaybeSigned::SigningParts(parts),
1226                Some(ask) => {
1227                    MaybeSigned::Signature(ask.randomize(&parts.alpha).sign(rng, &sighash))
1228                }
1229            },
1230            |rng, auth: InProgress<P, Unsigned>| InProgress {
1231                sigs: PartiallyAuthorized {
1232                    binding_signature: auth.sigs.bsk.sign(rng, &sighash),
1233                    sighash,
1234                },
1235                _proof_state: PhantomData,
1236            },
1237        )
1238    }
1239}
1240
1241impl<V> Bundle<InProgress<Proven, Unsigned>, V> {
1242    /// Applies signatures to this bundle, in order to authorize it.
1243    ///
1244    /// This is a helper method that wraps [`Bundle::prepare`], [`Bundle::sign`], and
1245    /// [`Bundle::finalize`].
1246    pub fn apply_signatures<R: Rng + CryptoRng>(
1247        self,
1248        mut rng: R,
1249        sighash: [u8; 32],
1250        signing_keys: &[SpendAuthorizingKey],
1251    ) -> Result<Bundle<Authorized, V>, Error> {
1252        signing_keys
1253            .iter()
1254            .fold(self.prepare(&mut rng, sighash), |partial, ask| {
1255                partial.sign(&mut rng, ask)
1256            })
1257            .finalize()
1258    }
1259}
1260
1261impl<P: InProgressProofs, V> Bundle<InProgress<P, PartiallyAuthorized>, V> {
1262    /// Signs this bundle with the given [`redjubjub::SigningKey`].
1263    ///
1264    /// This will apply signatures for all notes controlled by this spending key.
1265    pub fn sign<R: Rng + CryptoRng>(self, mut rng: R, ask: &SpendAuthorizingKey) -> Self {
1266        let expected_ak = ask.into();
1267        let sighash = self.authorization().sigs.sighash;
1268        self.map_authorization(
1269            &mut rng,
1270            |_, proof| proof,
1271            |_, proof| proof,
1272            |rng, maybe| match maybe {
1273                MaybeSigned::SigningParts(parts) if parts.ak == expected_ak => {
1274                    MaybeSigned::Signature(ask.randomize(&parts.alpha).sign(rng, &sighash))
1275                }
1276                s => s,
1277            },
1278            |_, partial| partial,
1279        )
1280    }
1281
1282    /// Appends externally computed [`redjubjub::Signature`]s.
1283    ///
1284    /// Each signature will be applied to the one input for which it is valid. An error
1285    /// will be returned if the signature is not valid for any inputs, or if it is valid
1286    /// for more than one input.
1287    pub fn append_signatures(
1288        self,
1289        signatures: &[redjubjub::Signature<SpendAuth>],
1290    ) -> Result<Self, Error> {
1291        signatures.iter().try_fold(self, Self::append_signature)
1292    }
1293
1294    fn append_signature(self, signature: &redjubjub::Signature<SpendAuth>) -> Result<Self, Error> {
1295        let sighash = self.authorization().sigs.sighash;
1296        let mut signature_valid_for = 0usize;
1297        let bundle = self.map_authorization(
1298            &mut signature_valid_for,
1299            |_, proof| proof,
1300            |_, proof| proof,
1301            |ctx, maybe| match maybe {
1302                MaybeSigned::SigningParts(parts) => {
1303                    let rk = parts.ak.randomize(&parts.alpha);
1304                    if rk.verify(&sighash, signature).is_ok() {
1305                        **ctx += 1;
1306                        MaybeSigned::Signature(*signature)
1307                    } else {
1308                        // Signature isn't for this input.
1309                        MaybeSigned::SigningParts(parts)
1310                    }
1311                }
1312                s => s,
1313            },
1314            |_, partial| partial,
1315        );
1316        match signature_valid_for {
1317            0 => Err(Error::InvalidExternalSignature),
1318            1 => Ok(bundle),
1319            _ => Err(Error::DuplicateSignature),
1320        }
1321    }
1322}
1323
1324impl<V> Bundle<InProgress<Proven, PartiallyAuthorized>, V> {
1325    /// Finalizes this bundle, enabling it to be included in a transaction.
1326    ///
1327    /// Returns an error if any signatures are missing.
1328    pub fn finalize(self) -> Result<Bundle<Authorized, V>, Error> {
1329        self.try_map_authorization(
1330            (),
1331            |_, v| Ok(v),
1332            |_, v| Ok(v),
1333            |_, maybe: MaybeSigned| maybe.finalize(),
1334            |_, partial: InProgress<Proven, PartiallyAuthorized>| {
1335                Ok(Authorized {
1336                    binding_sig: partial.sigs.binding_signature,
1337                })
1338            },
1339        )
1340    }
1341}