1use 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
49pub trait BuildParams {
51 fn spend_rcv(&mut self, i: usize) -> jubjub::Fr;
53 fn spend_alpha(&mut self, i: usize) -> jubjub::Fr;
55 fn convert_rcv(&mut self, i: usize) -> jubjub::Fr;
57 fn output_rcv(&mut self, i: usize) -> jubjub::Fr;
59 fn output_rcm(&mut self, i: usize) -> jubjub::Fr;
61 fn output_rseed(&mut self, i: usize) -> [u8; 32];
63}
64
65impl<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#[derive(Clone, Debug, Default)]
89pub struct SpendBuildParams {
90 pub rcv: jubjub::Fr,
92 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 writer.write_all(&self.rcv.to_repr())?;
100 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 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 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 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#[derive(Clone, Copy, Debug, Default)]
149pub struct ConvertBuildParams {
150 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 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 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 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#[derive(Clone, Copy, Debug, Default)]
190pub struct OutputBuildParams {
191 pub rcv: jubjub::Fr,
193 pub rcm: jubjub::Fr,
195 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 writer.write_all(&self.rcv.to_repr())?;
203 writer.write_all(&self.rcm.to_repr())?;
205 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 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 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 let rseed = <[u8; 32]>::deserialize_reader(reader)?;
225 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#[derive(Default, Clone, BorshDeserialize, BorshSchema, BorshSerialize, Debug)]
250pub struct StoredBuildParams {
251 pub spend_params: Vec<SpendBuildParams>,
253 pub convert_params: Vec<ConvertBuildParams>,
255 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
285pub struct RngBuildParams<R: CryptoRng + RngCore> {
287 rng: R,
289 spends: BTreeMap<usize, SpendBuildParams>,
291 converts: BTreeMap<usize, ConvertBuildParams>,
293 outputs: BTreeMap<usize, OutputBuildParams>,
295}
296
297impl<R: CryptoRng + RngCore> RngBuildParams<R> {
298 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 pub fn to_stored(mut self) -> Option<StoredBuildParams> {
310 let mut stored = StoredBuildParams::default();
311 for i in 0..self.spends.len() {
313 stored.spend_params.push(self.spends.remove(&i)?);
314 }
315 for i in 0..self.converts.len() {
317 stored.convert_params.push(self.converts.remove(&i)?);
318 }
319 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 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 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 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
385const 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 &()
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#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema)]
495pub struct SaplingOutputInfo {
496 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#[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 pub fn spend_index(&self, n: usize) -> Option<usize> {
618 self.spend_indices.get(n).copied()
619 }
620
621 pub fn output_index(&self, n: usize) -> Option<usize> {
629 self.output_indices.get(n).copied()
630 }
631 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 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 pub fn outputs(&self) -> &[impl fees::OutputView] {
785 &self.outputs
786 }
787
788 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 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 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 pub fn add_convert(
838 &mut self,
839 allowed: AllowedConversion,
840 value: u64,
841 merkle_path: MerklePath<Node>,
842 ) -> Result<(), Error> {
843 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 #[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 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 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 if !indexed_spends.is_empty() {
918 while indexed_outputs.len() < MIN_SHIELDED_OUTPUTS {
919 indexed_outputs.push(None);
920 }
921 }
922
923 indexed_spends.shuffle(rng);
925 indexed_converts.shuffle(rng);
926 indexed_outputs.shuffle(rng);
927
928 let total_progress = indexed_spends.len() as u32 + indexed_outputs.len() as u32;
930 let mut progress = 0u32;
931
932 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 tx_metadata.spend_indices[pos] = i;
970
971 progress += 1;
973 if let Some(sender) = progress_notifier {
974 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 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 tx_metadata.convert_indices[pos] = i;
1018
1019 progress += 1;
1021 if let Some(sender) = progress_notifier {
1022 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 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 ¶ms,
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 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 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 progress += 1;
1129 if let Some(sender) = progress_notifier {
1130 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#[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}