Skip to main content

sapling_crypto/
builder.rs

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