Skip to main content

sapling_crypto/
builder.rs

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