masp_primitives/transaction/components/sapling/
builder.rs

1//! Types and functions for building MASP shielded transaction components.
2
3use core::fmt;
4use std::sync::mpsc::Sender;
5
6use ff::Field;
7use ff::PrimeField;
8use group::GroupEncoding;
9use rand::{seq::SliceRandom, CryptoRng, RngCore};
10
11use crate::MaybeArbitrary;
12use crate::{
13    asset_type::AssetType,
14    consensus::{self, BlockHeight},
15    convert::AllowedConversion,
16    keys::OutgoingViewingKey,
17    memo::MemoBytes,
18    merkle_tree::MerklePath,
19    sapling::{
20        note_encryption::sapling_note_encryption,
21        prover::TxProver,
22        redjubjub::{PrivateKey, Signature},
23        spend_sig_internal,
24        util::generate_random_rseed_internal,
25        Diversifier, Node, Note, PaymentAddress, ProofGenerationKey, Rseed,
26    },
27    transaction::{
28        builder::Progress,
29        components::{
30            amount::{I128Sum, ValueSum, MAX_MONEY},
31            sapling::{
32                fees, Authorization, Authorized, Bundle, ConvertDescription, GrothProofBytes,
33                OutputDescription, SpendDescription,
34            },
35        },
36    },
37    zip32::{ExtendedKey, ExtendedSpendingKey},
38};
39use borsh::schema::add_definition;
40use borsh::schema::Declaration;
41use borsh::schema::Definition;
42use borsh::schema::Fields;
43use borsh::{BorshDeserialize, BorshSchema, BorshSerialize};
44use std::collections::BTreeMap;
45use std::fmt::Debug;
46use std::io::Write;
47use std::marker::PhantomData;
48
49/// A subset of the parameters necessary to build a transaction
50pub trait BuildParams {
51    /// Get the commitment value randomness for the ith spend description
52    fn spend_rcv(&mut self, i: usize) -> jubjub::Fr;
53    /// Get the spend authorization randomizer for the ith spend description
54    fn spend_alpha(&mut self, i: usize) -> jubjub::Fr;
55    /// Get the commitment value randomness for the ith convert description
56    fn convert_rcv(&mut self, i: usize) -> jubjub::Fr;
57    /// Get the commitment value randomness for the ith output description
58    fn output_rcv(&mut self, i: usize) -> jubjub::Fr;
59    /// Get the note RCM for the ith output description
60    fn output_rcm(&mut self, i: usize) -> jubjub::Fr;
61    /// Get the random seed for the ith output description
62    fn output_rseed(&mut self, i: usize) -> [u8; 32];
63}
64
65// Allow build parameters to be boxed
66impl<B: BuildParams + ?Sized> BuildParams for Box<B> {
67    fn spend_rcv(&mut self, i: usize) -> jubjub::Fr {
68        (**self).spend_rcv(i)
69    }
70    fn spend_alpha(&mut self, i: usize) -> jubjub::Fr {
71        (**self).spend_alpha(i)
72    }
73    fn convert_rcv(&mut self, i: usize) -> jubjub::Fr {
74        (**self).convert_rcv(i)
75    }
76    fn output_rcv(&mut self, i: usize) -> jubjub::Fr {
77        (**self).output_rcv(i)
78    }
79    fn output_rcm(&mut self, i: usize) -> jubjub::Fr {
80        (**self).output_rcm(i)
81    }
82    fn output_rseed(&mut self, i: usize) -> [u8; 32] {
83        (**self).output_rseed(i)
84    }
85}
86
87/// Parameters that go into constructing a spend description
88#[derive(Clone, Debug, Default)]
89pub struct SpendBuildParams {
90    /// The commitment value randomness
91    pub rcv: jubjub::Fr,
92    /// The spend authorization randomizer
93    pub alpha: jubjub::Fr,
94}
95
96impl BorshSerialize for SpendBuildParams {
97    fn serialize<W: std::io::Write>(&self, writer: &mut W) -> std::io::Result<()> {
98        // Write the commitment value randomness
99        writer.write_all(&self.rcv.to_repr())?;
100        // Write spend authorization randomizer
101        writer.write_all(&self.alpha.to_repr())?;
102        Ok(())
103    }
104}
105
106impl BorshDeserialize for SpendBuildParams {
107    fn deserialize_reader<R: std::io::Read>(reader: &mut R) -> std::io::Result<Self> {
108        // Read the commitment value randomness
109        let rcv_bytes = <[u8; 32]>::deserialize_reader(reader)?;
110        let rcv = Option::from(jubjub::Fr::from_bytes(&rcv_bytes)).ok_or_else(|| {
111            std::io::Error::new(std::io::ErrorKind::InvalidData, "rcv not in field")
112        })?;
113        // Read the spend authorization randomizer
114        let alpha_bytes = <[u8; 32]>::deserialize_reader(reader)?;
115        let alpha = Option::from(jubjub::Fr::from_bytes(&alpha_bytes)).ok_or_else(|| {
116            std::io::Error::new(std::io::ErrorKind::InvalidData, "alpha not in field")
117        })?;
118        // Finally, aggregate the spend parameters
119        Ok(SpendBuildParams { rcv, alpha })
120    }
121}
122
123impl BorshSchema for SpendBuildParams {
124    fn add_definitions_recursively(definitions: &mut BTreeMap<Declaration, Definition>) {
125        let definition = Definition::Struct {
126            fields: Fields::NamedFields(vec![
127                ("rcv".into(), <[u8; 32]>::declaration()),
128                ("alpha".into(), <[u8; 32]>::declaration()),
129                ("auth_sig".into(), Option::<Signature>::declaration()),
130                (
131                    "proof_generation_key".into(),
132                    Option::<ProofGenerationKey>::declaration(),
133                ),
134            ]),
135        };
136        add_definition(Self::declaration(), definition, definitions);
137        <[u8; 32]>::add_definitions_recursively(definitions);
138        Option::<Signature>::add_definitions_recursively(definitions);
139        Option::<ProofGenerationKey>::add_definitions_recursively(definitions);
140    }
141
142    fn declaration() -> Declaration {
143        "SpendBuildParams".into()
144    }
145}
146
147/// Parameters that go into constructing an output description
148#[derive(Clone, Copy, Debug, Default)]
149pub struct ConvertBuildParams {
150    /// The commitment value randomness
151    pub rcv: jubjub::Fr,
152}
153
154impl BorshSerialize for ConvertBuildParams {
155    fn serialize<W: std::io::Write>(&self, writer: &mut W) -> std::io::Result<()> {
156        // Write the commitment value randomness
157        writer.write_all(&self.rcv.to_repr())?;
158        Ok(())
159    }
160}
161
162impl BorshDeserialize for ConvertBuildParams {
163    fn deserialize_reader<R: std::io::Read>(reader: &mut R) -> std::io::Result<Self> {
164        // Read the commitment value randomness
165        let rcv_bytes = <[u8; 32]>::deserialize_reader(reader)?;
166        let rcv = Option::from(jubjub::Fr::from_bytes(&rcv_bytes)).ok_or_else(|| {
167            std::io::Error::new(std::io::ErrorKind::InvalidData, "rcv not in field")
168        })?;
169        // Finally, aggregate the convert parameters
170        Ok(ConvertBuildParams { rcv })
171    }
172}
173
174impl BorshSchema for ConvertBuildParams {
175    fn add_definitions_recursively(definitions: &mut BTreeMap<Declaration, Definition>) {
176        let definition = Definition::Struct {
177            fields: Fields::NamedFields(vec![("rcv".into(), <[u8; 32]>::declaration())]),
178        };
179        add_definition(Self::declaration(), definition, definitions);
180        <[u8; 32]>::add_definitions_recursively(definitions);
181    }
182
183    fn declaration() -> Declaration {
184        "ConvertBuildParams".into()
185    }
186}
187
188/// Parameters that go into constructing an output description
189#[derive(Clone, Copy, Debug, Default)]
190pub struct OutputBuildParams {
191    /// The commitment value randomness
192    pub rcv: jubjub::Fr,
193    /// The note rcm value
194    pub rcm: jubjub::Fr,
195    /// The note's random seed
196    pub rseed: [u8; 32],
197}
198
199impl BorshSerialize for OutputBuildParams {
200    fn serialize<W: std::io::Write>(&self, writer: &mut W) -> std::io::Result<()> {
201        // Write the commitment value randomness
202        writer.write_all(&self.rcv.to_repr())?;
203        // Write the note rcm value
204        writer.write_all(&self.rcm.to_repr())?;
205        // Write the note's random seed
206        self.rseed.serialize(writer)?;
207        Ok(())
208    }
209}
210
211impl BorshDeserialize for OutputBuildParams {
212    fn deserialize_reader<R: std::io::Read>(reader: &mut R) -> std::io::Result<Self> {
213        // Read the commitment value randomness
214        let rcv_bytes = <[u8; 32]>::deserialize_reader(reader)?;
215        let rcv = Option::from(jubjub::Fr::from_bytes(&rcv_bytes)).ok_or_else(|| {
216            std::io::Error::new(std::io::ErrorKind::InvalidData, "rcv not in field")
217        })?;
218        // Read the note rcm value
219        let rcm_bytes = <[u8; 32]>::deserialize_reader(reader)?;
220        let rcm = Option::from(jubjub::Fr::from_bytes(&rcm_bytes)).ok_or_else(|| {
221            std::io::Error::new(std::io::ErrorKind::InvalidData, "rcm not in field")
222        })?;
223        // Read the note's random seed
224        let rseed = <[u8; 32]>::deserialize_reader(reader)?;
225        // Finally, aggregate the output parameters
226        Ok(OutputBuildParams { rcv, rcm, rseed })
227    }
228}
229
230impl BorshSchema for OutputBuildParams {
231    fn add_definitions_recursively(definitions: &mut BTreeMap<Declaration, Definition>) {
232        let definition = Definition::Struct {
233            fields: Fields::NamedFields(vec![
234                ("rcv".into(), <[u8; 32]>::declaration()),
235                ("rcm".into(), <[u8; 32]>::declaration()),
236                ("rseed".into(), <[u8; 32]>::declaration()),
237            ]),
238        };
239        add_definition(Self::declaration(), definition, definitions);
240        <[u8; 32]>::add_definitions_recursively(definitions);
241    }
242
243    fn declaration() -> Declaration {
244        "OutputBuildParams".into()
245    }
246}
247
248/// Pre-generated random parameters for MASPtTransactions
249#[derive(Default, Clone, BorshDeserialize, BorshSchema, BorshSerialize, Debug)]
250pub struct StoredBuildParams {
251    /// The parameters required to construct spend descriptions
252    pub spend_params: Vec<SpendBuildParams>,
253    /// The parameters required to construct convert descriptions
254    pub convert_params: Vec<ConvertBuildParams>,
255    /// The parameters required to construct output descriptions
256    pub output_params: Vec<OutputBuildParams>,
257}
258
259impl BuildParams for StoredBuildParams {
260    fn spend_rcv(&mut self, i: usize) -> jubjub::Fr {
261        self.spend_params[i].rcv
262    }
263
264    fn spend_alpha(&mut self, i: usize) -> jubjub::Fr {
265        self.spend_params[i].alpha
266    }
267
268    fn convert_rcv(&mut self, i: usize) -> jubjub::Fr {
269        self.convert_params[i].rcv
270    }
271
272    fn output_rcv(&mut self, i: usize) -> jubjub::Fr {
273        self.output_params[i].rcv
274    }
275
276    fn output_rcm(&mut self, i: usize) -> jubjub::Fr {
277        self.output_params[i].rcm
278    }
279
280    fn output_rseed(&mut self, i: usize) -> [u8; 32] {
281        self.output_params[i].rseed
282    }
283}
284
285/// Lazily generated random parameters for MASP transactions
286pub struct RngBuildParams<R: CryptoRng + RngCore> {
287    /// The RNG used to generate the build parameters
288    rng: R,
289    /// The parameters required to construct spend descriptions
290    spends: BTreeMap<usize, SpendBuildParams>,
291    /// The parameters required to construct convert descriptions
292    converts: BTreeMap<usize, ConvertBuildParams>,
293    /// The parameters required to construct output descriptions
294    outputs: BTreeMap<usize, OutputBuildParams>,
295}
296
297impl<R: CryptoRng + RngCore> RngBuildParams<R> {
298    /// Construct a build parameter generator using the given RNG
299    pub fn new(rng: R) -> Self {
300        Self {
301            rng,
302            spends: BTreeMap::new(),
303            converts: BTreeMap::new(),
304            outputs: BTreeMap::new(),
305        }
306    }
307
308    /// Convert these build parameters to their stored equivalent
309    pub fn to_stored(mut self) -> Option<StoredBuildParams> {
310        let mut stored = StoredBuildParams::default();
311        // Store the spends
312        for i in 0..self.spends.len() {
313            stored.spend_params.push(self.spends.remove(&i)?);
314        }
315        // Store the converts
316        for i in 0..self.converts.len() {
317            stored.convert_params.push(self.converts.remove(&i)?);
318        }
319        // Store the outputs
320        for i in 0..self.outputs.len() {
321            stored.output_params.push(self.outputs.remove(&i)?);
322        }
323        Some(stored)
324    }
325}
326
327impl<R: CryptoRng + RngCore> RngBuildParams<R> {
328    /// Get the parameters necessary to build the ith spend description
329    pub fn spend_params(&mut self, i: usize) -> &SpendBuildParams {
330        self.spends.entry(i).or_insert_with(|| SpendBuildParams {
331            rcv: jubjub::Fr::random(&mut self.rng),
332            alpha: jubjub::Fr::random(&mut self.rng),
333        })
334    }
335
336    /// Get the parameters necessary to build the ith convert description
337    pub fn convert_params(&mut self, i: usize) -> &ConvertBuildParams {
338        self.converts
339            .entry(i)
340            .or_insert_with(|| ConvertBuildParams {
341                rcv: jubjub::Fr::random(&mut self.rng),
342            })
343    }
344
345    /// Get the parameters necessary to build the ith output description
346    pub fn output_params(&mut self, i: usize) -> &OutputBuildParams {
347        self.outputs.entry(i).or_insert_with(|| OutputBuildParams {
348            rcv: jubjub::Fr::random(&mut self.rng),
349            rcm: jubjub::Fr::random(&mut self.rng),
350            rseed: {
351                let mut buffer = [0u8; 32];
352                self.rng.fill_bytes(&mut buffer);
353                buffer
354            },
355        })
356    }
357}
358
359impl<R: CryptoRng + RngCore> BuildParams for RngBuildParams<R> {
360    fn spend_rcv(&mut self, i: usize) -> jubjub::Fr {
361        self.spend_params(i).rcv
362    }
363
364    fn spend_alpha(&mut self, i: usize) -> jubjub::Fr {
365        self.spend_params(i).alpha
366    }
367
368    fn convert_rcv(&mut self, i: usize) -> jubjub::Fr {
369        self.convert_params(i).rcv
370    }
371
372    fn output_rcv(&mut self, i: usize) -> jubjub::Fr {
373        self.output_params(i).rcv
374    }
375
376    fn output_rcm(&mut self, i: usize) -> jubjub::Fr {
377        self.output_params(i).rcm
378    }
379
380    fn output_rseed(&mut self, i: usize) -> [u8; 32] {
381        self.output_params(i).rseed
382    }
383}
384
385/// If there are any shielded inputs, always have at least two shielded outputs, padding
386/// with dummy outputs if necessary. See <https://github.com/zcash/zcash/issues/3615>.
387const MIN_SHIELDED_OUTPUTS: usize = 2;
388
389#[derive(Debug, PartialEq, Eq)]
390pub enum Error {
391    AnchorMismatch,
392    BindingSig,
393    InvalidAddress,
394    InvalidAmount,
395    SpendProof,
396    ConvertProof,
397}
398
399impl fmt::Display for Error {
400    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
401        match self {
402            Error::AnchorMismatch => {
403                write!(f, "Anchor mismatch (anchors for all spends must be equal)")
404            }
405            Error::BindingSig => write!(f, "Failed to create bindingSig"),
406            Error::InvalidAddress => write!(f, "Invalid address"),
407            Error::InvalidAmount => write!(f, "Invalid amount"),
408            Error::SpendProof => write!(f, "Failed to create MASP spend proof"),
409            Error::ConvertProof => write!(f, "Failed to create MASP convert proof"),
410        }
411    }
412}
413
414#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
415#[derive(Debug, Clone, PartialEq)]
416pub struct SpendDescriptionInfo<Key = ExtendedSpendingKey> {
417    extsk: Key,
418    diversifier: Diversifier,
419    note: Note,
420    merkle_path: MerklePath<Node>,
421}
422
423impl<Key: BorshSchema> BorshSchema for SpendDescriptionInfo<Key> {
424    fn add_definitions_recursively(definitions: &mut BTreeMap<Declaration, Definition>) {
425        let definition = Definition::Struct {
426            fields: Fields::NamedFields(vec![
427                ("extsk".into(), Key::declaration()),
428                ("diversifier".into(), Diversifier::declaration()),
429                ("note".into(), Note::<Rseed>::declaration()),
430                ("merkle_path".into(), MerklePath::<[u8; 32]>::declaration()),
431            ]),
432        };
433        add_definition(Self::declaration(), definition, definitions);
434        Key::add_definitions_recursively(definitions);
435        Diversifier::add_definitions_recursively(definitions);
436        Note::<Rseed>::add_definitions_recursively(definitions);
437        MerklePath::<[u8; 32]>::add_definitions_recursively(definitions);
438    }
439
440    fn declaration() -> Declaration {
441        format!(r#"SpendDescriptionInfo<{}>"#, Key::declaration())
442    }
443}
444
445impl<Key: BorshSerialize> BorshSerialize for SpendDescriptionInfo<Key> {
446    fn serialize<W: Write>(&self, writer: &mut W) -> std::io::Result<()> {
447        self.extsk.serialize(writer)?;
448        self.diversifier.serialize(writer)?;
449        self.note.serialize(writer)?;
450        self.merkle_path.serialize(writer)
451    }
452}
453
454impl<Key: BorshDeserialize> BorshDeserialize for SpendDescriptionInfo<Key> {
455    fn deserialize_reader<R: std::io::Read>(reader: &mut R) -> std::io::Result<Self> {
456        let extsk = Key::deserialize_reader(reader)?;
457        let diversifier = Diversifier::deserialize_reader(reader)?;
458        let note = Note::deserialize_reader(reader)?;
459        let merkle_path = MerklePath::<Node>::deserialize_reader(reader)?;
460        Ok(SpendDescriptionInfo {
461            extsk,
462            diversifier,
463            note,
464            merkle_path,
465        })
466    }
467}
468
469impl<K> fees::InputView<(), K> for SpendDescriptionInfo<K> {
470    fn note_id(&self) -> &() {
471        // The builder does not make use of note identifiers, so we can just return the unit value.
472        &()
473    }
474
475    fn value(&self) -> u64 {
476        self.note.value
477    }
478
479    fn asset_type(&self) -> AssetType {
480        self.note.asset_type
481    }
482
483    fn key(&self) -> &K {
484        &self.extsk
485    }
486
487    fn address(&self) -> Option<PaymentAddress> {
488        PaymentAddress::from_parts(self.diversifier, self.note.pk_d)
489    }
490}
491
492/// A struct containing the information required in order to construct a
493/// MASP output to a transaction.
494#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema)]
495pub struct SaplingOutputInfo {
496    /// `None` represents the `ovk = ⊥` case.
497    ovk: Option<OutgoingViewingKey>,
498    to: PaymentAddress,
499    note: Note<()>,
500    memo: MemoBytes,
501}
502
503impl SaplingOutputInfo {
504    #[allow(clippy::too_many_arguments)]
505    fn new_internal(
506        ovk: Option<OutgoingViewingKey>,
507        to: PaymentAddress,
508        asset_type: AssetType,
509        value: u64,
510        memo: MemoBytes,
511    ) -> Result<Self, Error> {
512        let g_d = to.g_d().ok_or(Error::InvalidAddress)?;
513        if value > MAX_MONEY {
514            return Err(Error::InvalidAmount);
515        }
516
517        let note = Note {
518            g_d,
519            pk_d: *to.pk_d(),
520            value,
521            rseed: (),
522            asset_type,
523        };
524
525        Ok(SaplingOutputInfo {
526            ovk,
527            to,
528            note,
529            memo,
530        })
531    }
532
533    fn build<P: consensus::Parameters, Pr: TxProver, R: RngCore>(
534        self,
535        prover: &Pr,
536        ctx: &mut Pr::SaplingProvingContext,
537        rng: &mut R,
538        rcv: jubjub::Fr,
539        rseed: Rseed,
540    ) -> OutputDescription<GrothProofBytes> {
541        let note = Note {
542            rseed,
543            value: self.note.value,
544            g_d: self.note.g_d,
545            pk_d: self.note.pk_d,
546            asset_type: self.note.asset_type,
547        };
548        let encryptor = sapling_note_encryption::<P>(self.ovk, note, self.to, self.memo);
549
550        let (zkproof, cv) = prover.output_proof(
551            ctx,
552            *encryptor.esk(),
553            self.to,
554            note.rcm(),
555            self.note.asset_type,
556            self.note.value,
557            rcv,
558        );
559
560        let cmu = note.cmu();
561
562        let enc_ciphertext = encryptor.encrypt_note_plaintext();
563        let out_ciphertext = encryptor.encrypt_outgoing_plaintext(&cv, &cmu, rng);
564
565        let epk = *encryptor.epk();
566
567        OutputDescription {
568            cv,
569            cmu,
570            ephemeral_key: epk.to_bytes().into(),
571            enc_ciphertext,
572            out_ciphertext,
573            zkproof,
574        }
575    }
576}
577
578impl fees::OutputView for SaplingOutputInfo {
579    fn value(&self) -> u64 {
580        self.note.value
581    }
582
583    fn asset_type(&self) -> AssetType {
584        self.note.asset_type
585    }
586
587    fn address(&self) -> PaymentAddress {
588        self.to
589    }
590}
591
592/// Metadata about a transaction created by a [`SaplingBuilder`].
593#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
594#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize, BorshSchema)]
595pub struct SaplingMetadata {
596    spend_indices: Vec<usize>,
597    convert_indices: Vec<usize>,
598    output_indices: Vec<usize>,
599}
600
601impl SaplingMetadata {
602    pub fn empty() -> Self {
603        SaplingMetadata {
604            spend_indices: vec![],
605            convert_indices: vec![],
606            output_indices: vec![],
607        }
608    }
609
610    /// Returns the index within the transaction of the [`SpendDescription`] corresponding
611    /// to the `n`-th call to [`SaplingBuilder::add_spend`].
612    ///
613    /// Note positions are randomized when building transactions for indistinguishability.
614    /// This means that the transaction consumer cannot assume that e.g. the first spend
615    /// they added (via the first call to [`SaplingBuilder::add_spend`]) is the first
616    /// [`SpendDescription`] in the transaction.
617    pub fn spend_index(&self, n: usize) -> Option<usize> {
618        self.spend_indices.get(n).copied()
619    }
620
621    /// Returns the index within the transaction of the [`OutputDescription`] corresponding
622    /// to the `n`-th call to [`SaplingBuilder::add_output`].
623    ///
624    /// Note positions are randomized when building transactions for indistinguishability.
625    /// This means that the transaction consumer cannot assume that e.g. the first output
626    /// they added (via the first call to [`SaplingBuilder::add_output`]) is the first
627    /// [`OutputDescription`] in the transaction.
628    pub fn output_index(&self, n: usize) -> Option<usize> {
629        self.output_indices.get(n).copied()
630    }
631    /// Returns the index within the transaction of the [`ConvertDescription`] corresponding
632    /// to the `n`-th call to [`SaplingBuilder::add_convert`].
633    ///
634    /// Note positions are randomized when building transactions for indistinguishability.
635    /// This means that the transaction consumer cannot assume that e.g. the first output
636    /// they added (via the first call to [`SaplingBuilder::add_output`]) is the first
637    /// [`ConvertDescription`] in the transaction.
638    pub fn convert_index(&self, n: usize) -> Option<usize> {
639        self.convert_indices.get(n).copied()
640    }
641}
642
643#[derive(Clone, Debug)]
644pub struct SaplingBuilder<P, Key = ExtendedSpendingKey> {
645    params: P,
646    spend_anchor: Option<bls12_381::Scalar>,
647    target_height: BlockHeight,
648    value_balance: I128Sum,
649    convert_anchor: Option<bls12_381::Scalar>,
650    spends: Vec<SpendDescriptionInfo<Key>>,
651    converts: Vec<ConvertDescriptionInfo>,
652    outputs: Vec<SaplingOutputInfo>,
653}
654
655impl<P: BorshSchema, Key: BorshSchema> BorshSchema for SaplingBuilder<P, Key> {
656    fn add_definitions_recursively(definitions: &mut BTreeMap<Declaration, Definition>) {
657        let definition = Definition::Struct {
658            fields: Fields::NamedFields(vec![
659                ("params".into(), P::declaration()),
660                ("spend_anchor".into(), Option::<[u8; 32]>::declaration()),
661                ("target_height".into(), BlockHeight::declaration()),
662                ("value_balance".into(), I128Sum::declaration()),
663                ("convert_anchor".into(), Option::<[u8; 32]>::declaration()),
664                (
665                    "spends".into(),
666                    Vec::<SpendDescriptionInfo<Key>>::declaration(),
667                ),
668                (
669                    "converts".into(),
670                    Vec::<ConvertDescriptionInfo>::declaration(),
671                ),
672                ("outputs".into(), Vec::<SaplingOutputInfo>::declaration()),
673            ]),
674        };
675        add_definition(Self::declaration(), definition, definitions);
676        P::add_definitions_recursively(definitions);
677        Option::<[u8; 32]>::add_definitions_recursively(definitions);
678        BlockHeight::add_definitions_recursively(definitions);
679        I128Sum::add_definitions_recursively(definitions);
680        Vec::<SpendDescriptionInfo<Key>>::add_definitions_recursively(definitions);
681        Vec::<ConvertDescriptionInfo>::add_definitions_recursively(definitions);
682        Vec::<SaplingOutputInfo>::add_definitions_recursively(definitions);
683    }
684
685    fn declaration() -> Declaration {
686        format!(
687            r#"SaplingBuilder<{}, {}>"#,
688            P::declaration(),
689            Key::declaration()
690        )
691    }
692}
693
694impl<P: BorshSerialize, Key: BorshSerialize> BorshSerialize for SaplingBuilder<P, Key> {
695    fn serialize<W: Write>(&self, writer: &mut W) -> std::io::Result<()> {
696        self.params.serialize(writer)?;
697        self.spend_anchor.map(|x| x.to_bytes()).serialize(writer)?;
698        self.target_height.serialize(writer)?;
699        self.value_balance.serialize(writer)?;
700        self.convert_anchor
701            .map(|x| x.to_bytes())
702            .serialize(writer)?;
703        self.spends.serialize(writer)?;
704        self.converts.serialize(writer)?;
705        self.outputs.serialize(writer)
706    }
707}
708
709impl<P: BorshDeserialize, Key: BorshDeserialize> BorshDeserialize for SaplingBuilder<P, Key> {
710    fn deserialize_reader<R: std::io::Read>(reader: &mut R) -> std::io::Result<Self> {
711        let params = P::deserialize_reader(reader)?;
712        let spend_anchor: Option<Option<_>> = Option::<[u8; 32]>::deserialize_reader(reader)?
713            .map(|x| bls12_381::Scalar::from_bytes(&x).into());
714        let spend_anchor = spend_anchor
715            .map(|x| x.ok_or_else(|| std::io::Error::from(std::io::ErrorKind::InvalidData)))
716            .transpose()?;
717        let target_height = BlockHeight::deserialize_reader(reader)?;
718        let value_balance = I128Sum::deserialize_reader(reader)?;
719        let convert_anchor: Option<Option<_>> = Option::<[u8; 32]>::deserialize_reader(reader)?
720            .map(|x| bls12_381::Scalar::from_bytes(&x).into());
721        let convert_anchor = convert_anchor
722            .map(|x| x.ok_or_else(|| std::io::Error::from(std::io::ErrorKind::InvalidData)))
723            .transpose()?;
724        let spends = Vec::<SpendDescriptionInfo<Key>>::deserialize_reader(reader)?;
725        let converts = Vec::<ConvertDescriptionInfo>::deserialize_reader(reader)?;
726        let outputs = Vec::<SaplingOutputInfo>::deserialize_reader(reader)?;
727        Ok(SaplingBuilder {
728            params,
729            spend_anchor,
730            target_height,
731            value_balance,
732            convert_anchor,
733            spends,
734            converts,
735            outputs,
736        })
737    }
738}
739
740#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))]
741#[derive(Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
742pub struct Unauthorized<K: ExtendedKey> {
743    tx_metadata: SaplingMetadata,
744    phantom: PhantomData<K>,
745}
746
747impl<K: ExtendedKey> std::fmt::Debug for Unauthorized<K> {
748    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
749        write!(f, "Unauthorized")
750    }
751}
752
753impl<K: ExtendedKey + Clone + Debug + PartialEq + for<'a> MaybeArbitrary<'a>> Authorization
754    for Unauthorized<K>
755{
756    type Proof = GrothProofBytes;
757    type AuthSig = SpendDescriptionInfo<K>;
758}
759
760impl<P, K> SaplingBuilder<P, K> {
761    pub fn new(params: P, target_height: BlockHeight) -> Self {
762        SaplingBuilder {
763            params,
764            spend_anchor: None,
765            target_height,
766            value_balance: ValueSum::zero(),
767            convert_anchor: None,
768            spends: vec![],
769            converts: vec![],
770            outputs: vec![],
771        }
772    }
773
774    /// Returns the list of Sapling inputs that will be consumed by the transaction being
775    /// constructed.
776    pub fn inputs(&self) -> &[impl fees::InputView<(), K>] {
777        &self.spends
778    }
779
780    pub fn converts(&self) -> &[impl fees::ConvertView] {
781        &self.converts
782    }
783    /// Returns the Sapling outputs that will be produced by the transaction being constructed
784    pub fn outputs(&self) -> &[impl fees::OutputView] {
785        &self.outputs
786    }
787
788    /// Returns the net value represented by the spends and outputs added to this builder.
789    pub fn value_balance(&self) -> I128Sum {
790        self.value_balance.clone()
791    }
792}
793
794impl<
795        P: consensus::Parameters,
796        K: ExtendedKey + Debug + Clone + PartialEq + for<'a> MaybeArbitrary<'a>,
797    > SaplingBuilder<P, K>
798{
799    /// Adds a Sapling note to be spent in this transaction.
800    ///
801    /// Returns an error if the given Merkle path does not have the same anchor as the
802    /// paths for previous Sapling notes.
803    pub fn add_spend(
804        &mut self,
805        extsk: K,
806        diversifier: Diversifier,
807        note: Note,
808        merkle_path: MerklePath<Node>,
809    ) -> Result<(), Error> {
810        // Consistency check: all anchors must equal the first one
811        let node = note.commitment();
812        if let Some(anchor) = self.spend_anchor {
813            let path_root: bls12_381::Scalar = merkle_path.root(node).into();
814            if path_root != anchor {
815                return Err(Error::AnchorMismatch);
816            }
817        } else {
818            self.spend_anchor = Some(merkle_path.root(node).into())
819        }
820
821        self.value_balance += ValueSum::from_pair(note.asset_type, i128::from(note.value));
822
823        self.spends.push(SpendDescriptionInfo {
824            extsk,
825            diversifier,
826            note,
827            merkle_path,
828        });
829
830        Ok(())
831    }
832
833    /// Adds a convert note to be applied in this transaction.
834    ///
835    /// Returns an error if the given Merkle path does not have the same anchor as the
836    /// paths for previous convert notes.
837    pub fn add_convert(
838        &mut self,
839        allowed: AllowedConversion,
840        value: u64,
841        merkle_path: MerklePath<Node>,
842    ) -> Result<(), Error> {
843        // Consistency check: all anchors must equal the first one
844
845        let node = allowed.commitment();
846        if let Some(anchor) = self.convert_anchor {
847            let path_root: bls12_381::Scalar = merkle_path.root(node).into();
848            if path_root != anchor {
849                return Err(Error::AnchorMismatch);
850            }
851        } else {
852            self.convert_anchor = Some(merkle_path.root(node).into())
853        }
854
855        let allowed_amt: I128Sum = allowed.clone().into();
856        self.value_balance += I128Sum::from_sum(allowed_amt) * (value as i128);
857
858        self.converts.push(ConvertDescriptionInfo {
859            allowed,
860            value,
861            merkle_path,
862        });
863
864        Ok(())
865    }
866
867    /// Adds a Sapling address to send funds to.
868    #[allow(clippy::too_many_arguments)]
869    pub fn add_output(
870        &mut self,
871        ovk: Option<OutgoingViewingKey>,
872        to: PaymentAddress,
873        asset_type: AssetType,
874        value: u64,
875        memo: MemoBytes,
876    ) -> Result<(), Error> {
877        let output = SaplingOutputInfo::new_internal(ovk, to, asset_type, value, memo)?;
878
879        self.value_balance -= ValueSum::from_pair(asset_type, i128::from(value));
880
881        self.outputs.push(output);
882
883        Ok(())
884    }
885
886    pub fn build<Pr: TxProver>(
887        self,
888        prover: &Pr,
889        ctx: &mut Pr::SaplingProvingContext,
890        rng: &mut (impl CryptoRng + RngCore),
891        bparams: &mut impl BuildParams,
892        target_height: BlockHeight,
893        progress_notifier: Option<&Sender<Progress>>,
894    ) -> Result<Option<Bundle<Unauthorized<K>>>, Error> {
895        // Record initial positions of spends and outputs
896        let value_balance = self.value_balance();
897        let params = self.params;
898        let mut indexed_spends: Vec<_> = self.spends.into_iter().enumerate().collect();
899        let mut indexed_converts: Vec<_> = self.converts.into_iter().enumerate().collect();
900        let mut indexed_outputs: Vec<_> = self
901            .outputs
902            .iter()
903            .enumerate()
904            .map(|(i, o)| Some((i, o)))
905            .collect();
906
907        // Set up the transaction metadata that will be used to record how
908        // inputs and outputs are shuffled.
909        let mut tx_metadata = SaplingMetadata::empty();
910        tx_metadata.spend_indices.resize(indexed_spends.len(), 0);
911        tx_metadata
912            .convert_indices
913            .resize(indexed_converts.len(), 0);
914        tx_metadata.output_indices.resize(indexed_outputs.len(), 0);
915
916        // Pad Sapling outputs
917        if !indexed_spends.is_empty() {
918            while indexed_outputs.len() < MIN_SHIELDED_OUTPUTS {
919                indexed_outputs.push(None);
920            }
921        }
922
923        // Randomize order of inputs and outputs
924        indexed_spends.shuffle(rng);
925        indexed_converts.shuffle(rng);
926        indexed_outputs.shuffle(rng);
927
928        // Keep track of the total number of steps computed
929        let total_progress = indexed_spends.len() as u32 + indexed_outputs.len() as u32;
930        let mut progress = 0u32;
931
932        // Create Sapling SpendDescriptions
933        let shielded_spends: Vec<SpendDescription<Unauthorized<K>>> = if !indexed_spends.is_empty()
934        {
935            let anchor = self
936                .spend_anchor
937                .expect("MASP Spend anchor must be set if MASP spends are present.");
938
939            indexed_spends
940                .into_iter()
941                .enumerate()
942                .map(|(i, (pos, spend))| {
943                    let proof_generation_key = spend
944                        .extsk
945                        .to_proof_generation_key()
946                        .expect("Proof generation key must be known for each MASP spend.");
947
948                    let nullifier = spend.note.nf(
949                        &proof_generation_key.to_viewing_key().nk,
950                        spend.merkle_path.position,
951                    );
952
953                    let (zkproof, cv, rk) = prover
954                        .spend_proof(
955                            ctx,
956                            proof_generation_key,
957                            spend.diversifier,
958                            spend.note.rseed,
959                            bparams.spend_alpha(i),
960                            spend.note.asset_type,
961                            spend.note.value,
962                            anchor,
963                            spend.merkle_path.clone(),
964                            bparams.spend_rcv(i),
965                        )
966                        .map_err(|_| Error::SpendProof)?;
967
968                    // Record the post-randomized spend location
969                    tx_metadata.spend_indices[pos] = i;
970
971                    // Update progress and send a notification on the channel
972                    progress += 1;
973                    if let Some(sender) = progress_notifier {
974                        // If the send fails, we should ignore the error, not crash.
975                        sender
976                            .send(Progress::new(progress, Some(total_progress)))
977                            .unwrap_or(());
978                    }
979
980                    Ok(SpendDescription {
981                        cv,
982                        anchor,
983                        nullifier,
984                        rk,
985                        zkproof,
986                        spend_auth_sig: spend,
987                    })
988                })
989                .collect::<Result<Vec<_>, Error>>()?
990        } else {
991            vec![]
992        };
993
994        // Create Sapling ConvertDescriptions
995        let shielded_converts: Vec<ConvertDescription<GrothProofBytes>> =
996            if !indexed_converts.is_empty() {
997                let anchor = self
998                    .convert_anchor
999                    .expect("MASP convert_anchor must be set if MASP converts are present.");
1000
1001                indexed_converts
1002                    .into_iter()
1003                    .enumerate()
1004                    .map(|(i, (pos, convert))| {
1005                        let (zkproof, cv) = prover
1006                            .convert_proof(
1007                                ctx,
1008                                convert.allowed.clone(),
1009                                convert.value,
1010                                anchor,
1011                                convert.merkle_path,
1012                                bparams.convert_rcv(i),
1013                            )
1014                            .map_err(|_| Error::ConvertProof)?;
1015
1016                        // Record the post-randomized spend location
1017                        tx_metadata.convert_indices[pos] = i;
1018
1019                        // Update progress and send a notification on the channel
1020                        progress += 1;
1021                        if let Some(sender) = progress_notifier {
1022                            // If the send fails, we should ignore the error, not crash.
1023                            sender
1024                                .send(Progress::new(progress, Some(total_progress)))
1025                                .unwrap_or(());
1026                        }
1027
1028                        Ok(ConvertDescription {
1029                            cv,
1030                            anchor,
1031                            zkproof,
1032                        })
1033                    })
1034                    .collect::<Result<Vec<_>, Error>>()?
1035            } else {
1036                vec![]
1037            };
1038
1039        // Create Sapling OutputDescriptions
1040        let shielded_outputs: Vec<OutputDescription<GrothProofBytes>> = indexed_outputs
1041            .into_iter()
1042            .enumerate()
1043            .map(|(i, output)| {
1044                let rseed = generate_random_rseed_internal(
1045                    &params,
1046                    target_height,
1047                    bparams.output_rcm(i),
1048                    bparams.output_rseed(i),
1049                );
1050
1051                let result = if let Some((pos, output)) = output {
1052                    // Record the post-randomized output location
1053                    tx_metadata.output_indices[pos] = i;
1054
1055                    output
1056                        .clone()
1057                        .build::<P, _, _>(prover, ctx, rng, bparams.output_rcv(i), rseed)
1058                } else {
1059                    // This is a dummy output
1060                    let (dummy_to, dummy_note) = {
1061                        let (diversifier, g_d) = {
1062                            let mut diversifier;
1063                            let g_d;
1064                            loop {
1065                                let mut d = [0; 11];
1066                                rng.fill_bytes(&mut d);
1067                                diversifier = Diversifier(d);
1068                                if let Some(val) = diversifier.g_d() {
1069                                    g_d = val;
1070                                    break;
1071                                }
1072                            }
1073                            (diversifier, g_d)
1074                        };
1075                        let (pk_d, payment_address) = loop {
1076                            let mut buf = [0; 64];
1077                            rng.fill_bytes(&mut buf);
1078                            let dummy_ivk = jubjub::Fr::from_bytes_wide(&buf);
1079                            let pk_d = g_d * dummy_ivk;
1080                            if let Some(addr) = PaymentAddress::from_parts(diversifier, pk_d) {
1081                                break (pk_d, addr);
1082                            }
1083                        };
1084
1085                        (
1086                            payment_address,
1087                            Note {
1088                                g_d,
1089                                pk_d,
1090                                rseed,
1091                                value: 0,
1092                                asset_type: AssetType::new(b"dummy").unwrap(),
1093                            },
1094                        )
1095                    };
1096
1097                    let esk = dummy_note.generate_or_derive_esk_internal(rng);
1098                    let epk = dummy_note.g_d * esk;
1099
1100                    let (zkproof, cv) = prover.output_proof(
1101                        ctx,
1102                        esk,
1103                        dummy_to,
1104                        dummy_note.rcm(),
1105                        dummy_note.asset_type,
1106                        dummy_note.value,
1107                        bparams.output_rcv(i),
1108                    );
1109
1110                    let cmu = dummy_note.cmu();
1111
1112                    let mut enc_ciphertext = [0u8; 580 + 32];
1113                    let mut out_ciphertext = [0u8; 80];
1114                    rng.fill_bytes(&mut enc_ciphertext[..]);
1115                    rng.fill_bytes(&mut out_ciphertext[..]);
1116
1117                    OutputDescription {
1118                        cv,
1119                        cmu,
1120                        ephemeral_key: epk.to_bytes().into(),
1121                        enc_ciphertext,
1122                        out_ciphertext,
1123                        zkproof,
1124                    }
1125                };
1126
1127                // Update progress and send a notification on the channel
1128                progress += 1;
1129                if let Some(sender) = progress_notifier {
1130                    // If the send fails, we should ignore the error, not crash.
1131                    sender
1132                        .send(Progress::new(progress, Some(total_progress)))
1133                        .unwrap_or(());
1134                }
1135
1136                result
1137            })
1138            .collect();
1139
1140        let bundle = if shielded_spends.is_empty() && shielded_outputs.is_empty() {
1141            None
1142        } else {
1143            Some(Bundle {
1144                shielded_spends,
1145                shielded_converts,
1146                shielded_outputs,
1147                value_balance,
1148                authorization: Unauthorized {
1149                    tx_metadata,
1150                    phantom: PhantomData,
1151                },
1152            })
1153        };
1154
1155        Ok(bundle)
1156    }
1157}
1158
1159impl<K: ExtendedKey + Debug + Clone + PartialEq + for<'a> MaybeArbitrary<'a>>
1160    SpendDescription<Unauthorized<K>>
1161{
1162    pub fn apply_signature(&self, spend_auth_sig: Signature) -> SpendDescription<Authorized> {
1163        SpendDescription {
1164            cv: self.cv,
1165            anchor: self.anchor,
1166            nullifier: self.nullifier,
1167            rk: self.rk,
1168            zkproof: self.zkproof,
1169            spend_auth_sig,
1170        }
1171    }
1172}
1173
1174impl<K: ExtendedKey + Debug + Clone + PartialEq + for<'a> MaybeArbitrary<'a>>
1175    Bundle<Unauthorized<K>>
1176{
1177    pub fn apply_signatures<Pr: TxProver, R: RngCore, S: BuildParams>(
1178        self,
1179        prover: &Pr,
1180        ctx: &mut Pr::SaplingProvingContext,
1181        rng: &mut R,
1182        bparams: &mut S,
1183        sighash_bytes: &[u8; 32],
1184    ) -> Result<(Bundle<Authorized>, SaplingMetadata), Error> {
1185        let binding_sig = prover
1186            .binding_sig(ctx, &self.value_balance, sighash_bytes)
1187            .map_err(|_| Error::BindingSig)?;
1188
1189        Ok((
1190            Bundle {
1191                shielded_spends: self
1192                    .shielded_spends
1193                    .iter()
1194                    .enumerate()
1195                    .map(|(i, spend)| {
1196                        spend.apply_signature(spend_sig_internal(
1197                            PrivateKey(spend.spend_auth_sig.extsk.to_spending_key().expect("Spend authorization key must be known for each MASP spend.").expsk.ask),
1198                            bparams.spend_alpha(i),
1199                            sighash_bytes,
1200                            rng,
1201                        ))
1202                    })
1203                    .collect(),
1204                shielded_converts: self.shielded_converts,
1205                shielded_outputs: self.shielded_outputs,
1206                value_balance: self.value_balance,
1207                authorization: Authorized { binding_sig },
1208            },
1209            self.authorization.tx_metadata,
1210        ))
1211    }
1212}
1213
1214/// A struct containing the information required in order to construct a
1215/// MASP conversion in a transaction.
1216#[derive(Clone, Debug, BorshSerialize, BorshDeserialize)]
1217pub struct ConvertDescriptionInfo {
1218    allowed: AllowedConversion,
1219    value: u64,
1220    merkle_path: MerklePath<Node>,
1221}
1222
1223impl BorshSchema for ConvertDescriptionInfo {
1224    fn add_definitions_recursively(definitions: &mut BTreeMap<Declaration, Definition>) {
1225        let definition = Definition::Struct {
1226            fields: Fields::NamedFields(vec![
1227                ("allowed".into(), AllowedConversion::declaration()),
1228                ("value".into(), u64::declaration()),
1229                ("merkle_path".into(), MerklePath::<[u8; 32]>::declaration()),
1230            ]),
1231        };
1232        add_definition(Self::declaration(), definition, definitions);
1233        AllowedConversion::add_definitions_recursively(definitions);
1234        u64::add_definitions_recursively(definitions);
1235        MerklePath::<[u8; 32]>::add_definitions_recursively(definitions);
1236    }
1237
1238    fn declaration() -> Declaration {
1239        "ConvertDescriptionInfo".into()
1240    }
1241}
1242
1243impl fees::ConvertView for ConvertDescriptionInfo {
1244    fn value(&self) -> u64 {
1245        self.value
1246    }
1247
1248    fn conversion(&self) -> &AllowedConversion {
1249        &self.allowed
1250    }
1251}
1252
1253pub trait MapBuilder<P1, K1, P2, K2> {
1254    fn map_params(&self, s: P1) -> P2;
1255    fn map_key(&self, s: K1) -> K2;
1256}
1257
1258impl<P1, K1> SaplingBuilder<P1, K1> {
1259    pub fn map_builder<P2, K2, F: MapBuilder<P1, K1, P2, K2>>(
1260        self,
1261        f: F,
1262    ) -> SaplingBuilder<P2, K2> {
1263        SaplingBuilder::<P2, K2> {
1264            params: f.map_params(self.params),
1265            spend_anchor: self.spend_anchor,
1266            target_height: self.target_height,
1267            value_balance: self.value_balance,
1268            convert_anchor: self.convert_anchor,
1269            converts: self.converts,
1270            outputs: self.outputs,
1271            spends: self
1272                .spends
1273                .into_iter()
1274                .map(|x| SpendDescriptionInfo {
1275                    extsk: f.map_key(x.extsk),
1276                    diversifier: x.diversifier,
1277                    note: x.note,
1278                    merkle_path: x.merkle_path,
1279                })
1280                .collect(),
1281        }
1282    }
1283}
1284
1285#[cfg(any(test, feature = "test-dependencies"))]
1286pub mod testing {
1287    use proptest::collection::vec;
1288    use proptest::prelude::*;
1289    use rand::{rngs::StdRng, SeedableRng};
1290
1291    use crate::{
1292        consensus::{
1293            testing::{arb_branch_id, arb_height},
1294            TEST_NETWORK,
1295        },
1296        merkle_tree::{testing::arb_commitment_tree, IncrementalWitness},
1297        sapling::{
1298            prover::mock::MockTxProver,
1299            testing::{arb_node, arb_note, arb_positive_note_value},
1300            Diversifier,
1301        },
1302        transaction::components::{
1303            amount::MAX_MONEY,
1304            sapling::{Authorized, Bundle},
1305        },
1306        zip32::sapling::testing::arb_extended_spending_key,
1307    };
1308
1309    use super::{RngBuildParams, SaplingBuilder};
1310
1311    prop_compose! {
1312        fn arb_bundle()(n_notes in 1..30usize)(
1313            extsk in arb_extended_spending_key(),
1314            spendable_notes in vec(
1315                arb_positive_note_value(MAX_MONEY / 10000).prop_flat_map(arb_note),
1316                n_notes
1317            ),
1318            commitment_trees in vec(
1319                arb_commitment_tree(n_notes, arb_node(), 32).prop_map(
1320                    |t| IncrementalWitness::from_tree(&t).path().unwrap()
1321                ),
1322                n_notes
1323            ),
1324            diversifiers in vec(prop::array::uniform11(any::<u8>()).prop_map(Diversifier), n_notes),
1325            target_height in arb_branch_id().prop_flat_map(|b| arb_height(b, &TEST_NETWORK)),
1326            rng_seed in prop::array::uniform32(any::<u8>()),
1327            bparams_seed in prop::array::uniform32(any::<u8>()),
1328            fake_sighash_bytes in prop::array::uniform32(any::<u8>()),
1329        ) -> Bundle<Authorized> {
1330            let mut builder = SaplingBuilder::new(TEST_NETWORK, target_height.unwrap());
1331            let mut rng = StdRng::from_seed(rng_seed);
1332
1333            for ((note, path), diversifier) in spendable_notes.into_iter().zip(commitment_trees.into_iter()).zip(diversifiers.into_iter()) {
1334                builder.add_spend(
1335                    extsk,
1336                    diversifier,
1337                    note,
1338                    path
1339                ).unwrap();
1340            }
1341
1342            let prover = MockTxProver;
1343            let mut bparams = RngBuildParams::new(StdRng::from_seed(bparams_seed));
1344
1345            let bundle = builder.build(
1346                &prover,
1347                &mut (),
1348                &mut rng,
1349                &mut bparams,
1350                target_height.unwrap(),
1351                None,
1352            ).unwrap().unwrap();
1353
1354            let (bundle, _) = bundle.apply_signatures(
1355                &prover,
1356                &mut (),
1357                &mut rng,
1358                &mut bparams,
1359                &fake_sighash_bytes,
1360            ).unwrap();
1361
1362            bundle
1363        }
1364    }
1365}