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