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