1use alloc::collections::BTreeMap;
4use alloc::vec::Vec;
5use core::{fmt, iter, marker::PhantomData};
6
7use group::ff::Field;
8use incrementalmerkletree::Position;
9use rand::{seq::SliceRandom, Rng};
10use rand_core::CryptoRng;
11use redjubjub::{Binding, SpendAuth};
12use zcash_note_encryption::EphemeralKeyBytes;
13
14use crate::{
15 bundle::{Authorization, Authorized, Bundle, GrothProofBytes},
16 keys::{
17 EphemeralSecretKey, ExpandedSpendingKey, FullViewingKey, OutgoingViewingKey,
18 SpendAuthorizingKey, SpendValidatingKey,
19 },
20 note::ExtractedNoteCommitment,
21 note_encryption::{sapling_note_encryption, Zip212Enforcement},
22 util::generate_random_rseed_internal,
23 value::{NoteValue, ValueCommitTrapdoor, ValueCommitment, ValueSum},
24 Anchor, Diversifier, MerklePath, Node, Note, Nullifier, PaymentAddress, SaplingIvk,
25 NOTE_COMMITMENT_TREE_DEPTH,
26};
27
28#[cfg(feature = "circuit")]
29use crate::{
30 bundle::{OutputDescription, SpendDescription},
31 circuit,
32 prover::{OutputProver, SpendProver},
33 value::{CommitmentSum, TrapdoorSum},
34 zip32::ExtendedSpendingKey,
35 ProofGenerationKey,
36};
37
38const MIN_SHIELDED_OUTPUTS: usize = 2;
41
42#[derive(Clone, Copy, Debug, PartialEq, Eq)]
44pub enum BundleType {
45 Transactional {
48 bundle_required: bool,
55 },
56 Coinbase,
58}
59
60impl BundleType {
61 pub const DEFAULT: BundleType = BundleType::Transactional {
64 bundle_required: false,
65 };
66
67 pub fn num_spends(&self, requested_spends: usize) -> Result<usize, &'static str> {
73 match self {
74 BundleType::Transactional { bundle_required } => {
75 Ok(if *bundle_required || requested_spends > 0 {
76 core::cmp::max(requested_spends, 1)
77 } else {
78 0
79 })
80 }
81 BundleType::Coinbase => {
82 if requested_spends == 0 {
83 Ok(0)
84 } else {
85 Err("Spends not allowed in coinbase bundles")
86 }
87 }
88 }
89 }
90
91 pub fn num_outputs(
97 &self,
98 requested_spends: usize,
99 requested_outputs: usize,
100 ) -> Result<usize, &'static str> {
101 match self {
102 BundleType::Transactional { bundle_required } => Ok(
103 if *bundle_required || requested_spends > 0 || requested_outputs > 0 {
104 core::cmp::max(requested_outputs, MIN_SHIELDED_OUTPUTS)
105 } else {
106 0
107 },
108 ),
109 BundleType::Coinbase => {
110 if requested_spends == 0 {
111 Ok(requested_outputs)
112 } else {
113 Err("Spends not allowed in coinbase bundles")
114 }
115 }
116 }
117 }
118}
119
120#[derive(Debug, PartialEq, Eq)]
121#[non_exhaustive]
122pub enum Error {
123 AnchorMismatch,
124 BindingSig,
125 DuplicateSignature,
128 InvalidAddress,
129 InvalidAmount,
130 InvalidExternalSignature,
132 MissingSignatures,
134 MissingSpendingKey,
136 PcztRequiresZip212,
138 SpendProof,
139 BundleTypeNotSatisfiable,
141 WrongSpendingKey,
143}
144
145impl fmt::Display for Error {
146 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
147 match self {
148 Error::AnchorMismatch => {
149 write!(f, "Anchor mismatch (anchors for all spends must be equal)")
150 }
151 Error::BindingSig => write!(f, "Failed to create bindingSig"),
152 Error::DuplicateSignature => write!(f, "Signature valid for more than one input"),
153 Error::InvalidAddress => write!(f, "Invalid address"),
154 Error::InvalidAmount => write!(f, "Invalid amount"),
155 Error::InvalidExternalSignature => write!(f, "External signature was invalid"),
156 Error::MissingSignatures => write!(f, "Required signatures were missing during build"),
157 Error::MissingSpendingKey => write!(f, "A required spending key was not provided"),
158 Error::PcztRequiresZip212 => {
159 write!(f, "PCZTs require that ZIP 212 is enforced for outputs")
160 }
161 Error::SpendProof => write!(f, "Failed to create Sapling spend proof"),
162 Error::BundleTypeNotSatisfiable => {
163 f.write_str("Bundle structure did not conform to requested bundle type.")
164 }
165 Error::WrongSpendingKey => write!(f, "The wrong spending key was provided"),
166 }
167 }
168}
169
170#[cfg(feature = "std")]
171impl std::error::Error for Error {}
172
173#[derive(Debug, Clone)]
175pub struct SpendInfo {
176 fvk: FullViewingKey,
177 note: Note,
178 merkle_path: MerklePath,
179 dummy_expsk: Option<ExpandedSpendingKey>,
180}
181
182impl SpendInfo {
183 pub fn new(fvk: FullViewingKey, note: Note, merkle_path: MerklePath) -> Self {
185 Self {
186 fvk,
187 note,
188 merkle_path,
189 dummy_expsk: None,
190 }
191 }
192
193 pub fn value(&self) -> NoteValue {
195 self.note.value()
196 }
197
198 fn dummy<R: Rng>(mut rng: R) -> Self {
202 let (sk, _, note) = Note::dummy(&mut rng);
203 let merkle_path = MerklePath::from_parts(
204 iter::repeat_with(|| Node::from_scalar(jubjub::Base::random(&mut rng)))
205 .take(NOTE_COMMITMENT_TREE_DEPTH.into())
206 .collect(),
207 Position::from(0),
208 )
209 .expect("The path length corresponds to the length of the generated vector.");
210
211 SpendInfo {
212 fvk: FullViewingKey::from_expanded_spending_key(&sk),
213 note,
214 merkle_path,
215 dummy_expsk: Some(sk),
216 }
217 }
218
219 fn has_matching_anchor(&self, anchor: &Anchor) -> bool {
220 if self.note.value() == NoteValue::ZERO {
221 true
222 } else {
223 let node = Node::from_cmu(&self.note.cmu());
224 &Anchor::from(self.merkle_path.root(node)) == anchor
225 }
226 }
227
228 fn prepare<R: Rng>(self, rng: R) -> PreparedSpendInfo {
229 PreparedSpendInfo {
230 fvk: self.fvk,
231 note: self.note,
232 merkle_path: self.merkle_path,
233 rcv: ValueCommitTrapdoor::random(rng),
234 dummy_expsk: self.dummy_expsk,
235 }
236 }
237}
238
239#[derive(Debug, Clone)]
240struct PreparedSpendInfo {
241 fvk: FullViewingKey,
242 note: Note,
243 merkle_path: MerklePath,
244 rcv: ValueCommitTrapdoor,
245 dummy_expsk: Option<ExpandedSpendingKey>,
246}
247
248impl PreparedSpendInfo {
249 fn build_inner<R: Rng>(
250 &self,
251 mut rng: R,
252 ) -> (
253 ValueCommitment,
254 Nullifier,
255 redjubjub::VerificationKey<SpendAuth>,
256 jubjub::Scalar,
257 ) {
258 let alpha = jubjub::Fr::random(&mut rng);
260 let cv = ValueCommitment::derive(self.note.value(), self.rcv.clone());
261
262 let ak = self.fvk.vk.ak().clone();
263
264 let rk = ak.randomize(&alpha);
266
267 let nullifier = self
268 .note
269 .nf(self.fvk.vk.nk(), u64::from(self.merkle_path.position()));
270
271 (cv, nullifier, rk, alpha)
272 }
273
274 #[cfg(feature = "circuit")]
275 fn build<Pr: SpendProver, R: Rng>(
276 self,
277 proof_generation_key: Option<ProofGenerationKey>,
278 rng: R,
279 ) -> Result<SpendDescription<InProgress<Unproven, Unsigned>>, Error> {
280 let proof_generation_key = match (proof_generation_key, self.dummy_expsk.as_ref()) {
281 (Some(proof_generation_key), None) => Ok(proof_generation_key),
282 (None, Some(expsk)) => Ok(expsk.proof_generation_key()),
283 (Some(_), Some(_)) => Err(Error::WrongSpendingKey),
284 (None, None) => Err(Error::MissingSpendingKey),
285 }?;
286 if proof_generation_key.to_viewing_key() != self.fvk.vk {
287 return Err(Error::WrongSpendingKey);
288 }
289
290 let (cv, nullifier, rk, alpha) = self.build_inner(rng);
291
292 let node = Node::from_cmu(&self.note.cmu());
294 let anchor = *self.merkle_path.root(node).inner();
295
296 let ak = self.fvk.vk.ak().clone();
297
298 let zkproof = Pr::prepare_circuit(
299 proof_generation_key,
300 *self.note.recipient().diversifier(),
301 *self.note.rseed(),
302 self.note.value(),
303 alpha,
304 self.rcv,
305 anchor,
306 self.merkle_path.clone(),
307 )
308 .ok_or(Error::SpendProof)?;
309
310 Ok(SpendDescription::from_parts(
311 cv,
312 anchor,
313 nullifier,
314 rk,
315 zkproof,
316 SigningMetadata {
317 dummy_ask: self.dummy_expsk.map(|expsk| expsk.ask().clone()),
318 parts: SigningParts { ak, alpha },
319 },
320 ))
321 }
322
323 fn into_pczt<R: Rng>(self, rng: R) -> crate::pczt::Spend {
324 let (cv, nullifier, rk, alpha) = self.build_inner(rng);
325
326 crate::pczt::Spend {
327 cv,
328 nullifier,
329 rk,
330 zkproof: None,
331 spend_auth_sig: None,
332 recipient: Some(self.note.recipient()),
333 value: Some(self.note.value()),
334 rseed: Some(*self.note.rseed()),
335 rcv: Some(self.rcv),
336 proof_generation_key: self
337 .dummy_expsk
338 .as_ref()
339 .map(|expsk| expsk.proof_generation_key()),
340 witness: Some(self.merkle_path),
341 alpha: Some(alpha),
342 zip32_derivation: None,
343 dummy_ask: self.dummy_expsk.map(|expsk| expsk.ask().clone()),
344 proprietary: BTreeMap::new(),
345 }
346 }
347}
348
349#[derive(Clone)]
352pub struct OutputInfo {
353 ovk: Option<OutgoingViewingKey>,
355 to: PaymentAddress,
356 value: NoteValue,
357 memo: [u8; 512],
358}
359
360impl OutputInfo {
361 pub fn new(
363 ovk: Option<OutgoingViewingKey>,
364 to: PaymentAddress,
365 value: NoteValue,
366 memo: [u8; 512],
367 ) -> Self {
368 Self {
369 ovk,
370 to,
371 value,
372 memo,
373 }
374 }
375
376 pub fn recipient(&self) -> PaymentAddress {
378 self.to
379 }
380
381 pub fn value(&self) -> NoteValue {
383 self.value
384 }
385
386 pub fn dummy<R: Rng>(mut rng: &mut R) -> Self {
388 let dummy_to = {
390 let mut diversifier = Diversifier([0; 11]);
391 loop {
392 rng.fill_bytes(&mut diversifier.0);
393 let dummy_ivk = SaplingIvk::random(&mut rng);
394 if let Some(addr) = dummy_ivk.to_payment_address(diversifier) {
395 break addr;
396 }
397 }
398 };
399
400 Self::new(None, dummy_to, NoteValue::ZERO, [0u8; 512])
401 }
402
403 fn prepare<R: Rng>(
404 self,
405 rng: &mut R,
406 zip212_enforcement: Zip212Enforcement,
407 ) -> PreparedOutputInfo {
408 let rseed = generate_random_rseed_internal(zip212_enforcement, rng);
409
410 let note = Note::from_parts(self.to, self.value, rseed);
411
412 PreparedOutputInfo {
413 ovk: self.ovk,
414 note,
415 memo: self.memo,
416 rcv: ValueCommitTrapdoor::random(rng),
417 }
418 }
419}
420
421struct PreparedOutputInfo {
422 ovk: Option<OutgoingViewingKey>,
424 note: Note,
425 memo: [u8; 512],
426 rcv: ValueCommitTrapdoor,
427}
428
429impl PreparedOutputInfo {
430 fn build_inner<P, R: Rng>(
431 &self,
432 zkproof: impl FnOnce(&EphemeralSecretKey) -> P,
433 rng: &mut R,
434 ) -> (
435 ValueCommitment,
436 ExtractedNoteCommitment,
437 EphemeralKeyBytes,
438 [u8; 580],
439 [u8; 80],
440 P,
441 ) {
442 let encryptor = sapling_note_encryption::<R>(self.ovk, self.note.clone(), self.memo, rng);
443
444 let cv = ValueCommitment::derive(self.note.value(), self.rcv.clone());
446
447 let zkproof = zkproof(encryptor.esk());
448
449 let cmu = self.note.cmu();
450
451 let enc_ciphertext = encryptor.encrypt_note_plaintext().0;
452 let out_ciphertext = encryptor.encrypt_outgoing_plaintext(&cv, &cmu, rng);
453
454 let epk = encryptor.epk();
455
456 (
457 cv,
458 cmu,
459 epk.to_bytes(),
460 enc_ciphertext,
461 out_ciphertext,
462 zkproof,
463 )
464 }
465
466 #[cfg(feature = "circuit")]
467 fn build<Pr: OutputProver, R: Rng>(self, rng: &mut R) -> OutputDescription<circuit::Output> {
468 let (cv, cmu, ephemeral_key, enc_ciphertext, out_ciphertext, zkproof) = self.build_inner(
469 |esk| {
470 Pr::prepare_circuit(
471 esk,
472 self.note.recipient(),
473 self.note.rcm(),
474 self.note.value(),
475 self.rcv.clone(),
476 )
477 },
478 rng,
479 );
480
481 OutputDescription::from_parts(
482 cv,
483 cmu,
484 ephemeral_key,
485 enc_ciphertext,
486 out_ciphertext,
487 zkproof,
488 )
489 }
490
491 fn into_pczt<R: Rng>(self, rng: &mut R) -> crate::pczt::Output {
492 let (cv, cmu, ephemeral_key, enc_ciphertext, out_ciphertext, _) =
493 self.build_inner(|_| (), rng);
494
495 let rseed = match self.note.rseed() {
496 crate::Rseed::BeforeZip212(_) => {
497 panic!("Builder::build_for_pczt should prevent pre-ZIP 212 outputs")
498 }
499 crate::Rseed::AfterZip212(rseed) => Some(*rseed),
500 };
501
502 crate::pczt::Output {
503 cv,
504 cmu,
505 ephemeral_key,
506 enc_ciphertext,
507 out_ciphertext,
508 zkproof: None,
509 recipient: Some(self.note.recipient()),
510 value: Some(self.note.value()),
511 rseed,
512 rcv: Some(self.rcv),
513 ock: None,
515 zip32_derivation: None,
516 user_address: None,
517 proprietary: BTreeMap::new(),
518 }
519 }
520}
521
522#[derive(Debug, Clone, PartialEq, Eq)]
524pub struct SaplingMetadata {
525 spend_indices: Vec<usize>,
526 output_indices: Vec<usize>,
527}
528
529impl SaplingMetadata {
530 pub fn empty() -> Self {
531 SaplingMetadata {
532 spend_indices: vec![],
533 output_indices: vec![],
534 }
535 }
536
537 pub fn spend_index(&self, n: usize) -> Option<usize> {
545 self.spend_indices.get(n).copied()
546 }
547
548 pub fn output_index(&self, n: usize) -> Option<usize> {
556 self.output_indices.get(n).copied()
557 }
558}
559
560pub struct Builder {
562 value_balance: ValueSum,
563 spends: Vec<SpendInfo>,
564 outputs: Vec<OutputInfo>,
565 zip212_enforcement: Zip212Enforcement,
566 bundle_type: BundleType,
567 anchor: Anchor,
568}
569
570impl Builder {
571 pub fn new(
572 zip212_enforcement: Zip212Enforcement,
573 bundle_type: BundleType,
574 anchor: Anchor,
575 ) -> Self {
576 Builder {
577 value_balance: ValueSum::zero(),
578 spends: vec![],
579 outputs: vec![],
580 zip212_enforcement,
581 bundle_type,
582 anchor,
583 }
584 }
585
586 pub fn inputs(&self) -> &[SpendInfo] {
588 &self.spends
589 }
590
591 pub fn outputs(&self) -> &[OutputInfo] {
593 &self.outputs
594 }
595
596 fn try_value_balance<V: TryFrom<i64>>(&self) -> Result<V, Error> {
600 self.value_balance
601 .try_into()
602 .map_err(|_| ())
603 .and_then(|vb| V::try_from(vb).map_err(|_| ()))
604 .map_err(|()| Error::InvalidAmount)
605 }
606
607 pub fn value_balance<V: TryFrom<i64>>(&self) -> V {
609 self.try_value_balance()
610 .expect("we check this when mutating self.value_balance")
611 }
612
613 pub fn add_spend(
618 &mut self,
619 fvk: FullViewingKey,
620 note: Note,
621 merkle_path: MerklePath,
622 ) -> Result<(), Error> {
623 let spend = SpendInfo::new(fvk, note, merkle_path);
624
625 match self.bundle_type {
627 BundleType::Transactional { .. } => {
628 if !spend.has_matching_anchor(&self.anchor) {
629 return Err(Error::AnchorMismatch);
630 }
631 }
632 BundleType::Coinbase => {
633 return Err(Error::BundleTypeNotSatisfiable);
634 }
635 }
636
637 self.value_balance = (self.value_balance + spend.value()).ok_or(Error::InvalidAmount)?;
638 self.try_value_balance::<i64>()?;
639
640 self.spends.push(spend);
641
642 Ok(())
643 }
644
645 pub fn add_output(
647 &mut self,
648 ovk: Option<OutgoingViewingKey>,
649 to: PaymentAddress,
650 value: NoteValue,
651 memo: [u8; 512],
652 ) -> Result<(), Error> {
653 let output = OutputInfo::new(ovk, to, value, memo);
654
655 self.value_balance = (self.value_balance - value).ok_or(Error::InvalidAddress)?;
656 self.try_value_balance::<i64>()?;
657
658 self.outputs.push(output);
659
660 Ok(())
661 }
662
663 #[cfg(feature = "circuit")]
665 pub fn build<SP: SpendProver, OP: OutputProver, R: Rng, V: TryFrom<i64>>(
666 self,
667 extsks: &[ExtendedSpendingKey],
668 rng: R,
669 ) -> Result<Option<(UnauthorizedBundle<V>, SaplingMetadata)>, Error> {
670 bundle::<SP, OP, _, _>(
671 rng,
672 self.bundle_type,
673 self.zip212_enforcement,
674 self.anchor,
675 self.spends,
676 self.outputs,
677 extsks,
678 )
679 }
680
681 pub fn build_for_pczt(
684 self,
685 rng: impl Rng,
686 ) -> Result<(crate::pczt::Bundle, SaplingMetadata), Error> {
687 match self.zip212_enforcement {
688 Zip212Enforcement::Off | Zip212Enforcement::GracePeriod => {
689 Err(Error::PcztRequiresZip212)
690 }
691 Zip212Enforcement::On => build_bundle(
692 rng,
693 self.bundle_type,
694 Zip212Enforcement::On,
695 self.anchor,
696 self.spends,
697 self.outputs,
698 |spend_infos, output_infos, value_sum, tx_metadata, mut rng| {
699 let spends = spend_infos
701 .into_iter()
702 .map(|a| a.into_pczt(&mut rng))
703 .collect::<Vec<_>>();
704 let outputs = output_infos
705 .into_iter()
706 .map(|a| a.into_pczt(&mut rng))
707 .collect::<Vec<_>>();
708
709 Ok((
710 crate::pczt::Bundle {
711 spends,
712 outputs,
713 value_sum,
714 anchor: self.anchor,
715 bsk: None,
716 },
717 tx_metadata,
718 ))
719 },
720 ),
721 }
722 }
723}
724
725#[cfg(feature = "circuit")]
728pub fn bundle<SP: SpendProver, OP: OutputProver, R: Rng, V: TryFrom<i64>>(
729 rng: R,
730 bundle_type: BundleType,
731 zip212_enforcement: Zip212Enforcement,
732 anchor: Anchor,
733 spends: Vec<SpendInfo>,
734 outputs: Vec<OutputInfo>,
735 extsks: &[ExtendedSpendingKey],
736) -> Result<Option<(UnauthorizedBundle<V>, SaplingMetadata)>, Error> {
737 build_bundle(
738 rng,
739 bundle_type,
740 zip212_enforcement,
741 anchor,
742 spends,
743 outputs,
744 |spend_infos, output_infos, value_balance, tx_metadata, mut rng| {
745 let value_balance_i64 =
746 i64::try_from(value_balance).map_err(|_| Error::InvalidAmount)?;
747
748 let bsk = {
750 let spends: TrapdoorSum = spend_infos.iter().map(|spend| &spend.rcv).sum();
751 let outputs: TrapdoorSum = output_infos.iter().map(|output| &output.rcv).sum();
752 (spends - outputs).into_bsk()
753 };
754
755 let shielded_spends = spend_infos
757 .into_iter()
758 .map(|a| {
759 let proof_generation_key = a
761 .dummy_expsk
762 .is_none()
763 .then(|| {
764 extsks.iter().find_map(|extsk| {
765 let dfvk = extsk.to_diversifiable_full_viewing_key();
766 (dfvk.fvk().to_bytes() == a.fvk.to_bytes())
767 .then(|| extsk.expsk().proof_generation_key())
768 })
769 })
770 .flatten();
771 a.build::<SP, _>(proof_generation_key, &mut rng)
772 })
773 .collect::<Result<Vec<_>, _>>()?;
774 let shielded_outputs = output_infos
775 .into_iter()
776 .map(|a| a.build::<OP, _>(&mut rng))
777 .collect::<Vec<_>>();
778
779 let bvk = {
781 let spends = shielded_spends
782 .iter()
783 .map(|spend| spend.cv())
784 .sum::<CommitmentSum>();
785 let outputs = shielded_outputs
786 .iter()
787 .map(|output| output.cv())
788 .sum::<CommitmentSum>();
789 (spends - outputs).into_bvk(value_balance_i64)
790 };
791 assert_eq!(redjubjub::VerificationKey::from(&bsk), bvk);
792
793 Ok(Bundle::from_parts(
794 shielded_spends,
795 shielded_outputs,
796 V::try_from(value_balance_i64).map_err(|_| Error::InvalidAmount)?,
797 InProgress {
798 sigs: Unsigned { bsk },
799 _proof_state: PhantomData,
800 },
801 )
802 .map(|b| (b, tx_metadata)))
803 },
804 )
805}
806
807fn build_bundle<B, R: Rng>(
808 mut rng: R,
809 bundle_type: BundleType,
810 zip212_enforcement: Zip212Enforcement,
811 anchor: Anchor,
812 spends: Vec<SpendInfo>,
813 outputs: Vec<OutputInfo>,
814 finisher: impl FnOnce(
815 Vec<PreparedSpendInfo>,
816 Vec<PreparedOutputInfo>,
817 ValueSum,
818 SaplingMetadata,
819 R,
820 ) -> Result<B, Error>,
821) -> Result<B, Error> {
822 match bundle_type {
823 BundleType::Transactional { .. } => {
824 for spend in &spends {
825 if !spend.has_matching_anchor(&anchor) {
826 return Err(Error::AnchorMismatch);
827 }
828 }
829 }
830 BundleType::Coinbase => {
831 if !spends.is_empty() {
832 return Err(Error::BundleTypeNotSatisfiable);
833 }
834 }
835 }
836
837 let requested_spend_count = spends.len();
838 let bundle_spend_count = bundle_type
839 .num_spends(requested_spend_count)
840 .map_err(|_| Error::BundleTypeNotSatisfiable)?;
841
842 let requested_output_count = outputs.len();
843 let bundle_output_count = bundle_type
844 .num_outputs(spends.len(), requested_output_count)
845 .map_err(|_| Error::BundleTypeNotSatisfiable)?;
846 assert!(requested_output_count <= bundle_output_count);
847
848 let mut tx_metadata = SaplingMetadata::empty();
851 tx_metadata.spend_indices.resize(spends.len(), 0);
852 tx_metadata.output_indices.resize(requested_output_count, 0);
853
854 let mut indexed_spends: Vec<_> = spends
856 .into_iter()
857 .chain(iter::repeat_with(|| SpendInfo::dummy(&mut rng)))
858 .enumerate()
859 .take(bundle_spend_count)
860 .collect();
861
862 let mut indexed_outputs: Vec<_> = outputs
864 .into_iter()
865 .chain(iter::repeat_with(|| OutputInfo::dummy(&mut rng)))
866 .enumerate()
867 .take(bundle_output_count)
868 .collect();
869
870 indexed_spends.shuffle(&mut rng);
872 indexed_outputs.shuffle(&mut rng);
873
874 let spend_infos = indexed_spends
876 .into_iter()
877 .enumerate()
878 .map(|(i, (pos, spend))| {
879 if pos < requested_spend_count {
881 tx_metadata.spend_indices[pos] = i;
882 }
883
884 spend.prepare(&mut rng)
885 })
886 .collect::<Vec<_>>();
887 let output_infos = indexed_outputs
888 .into_iter()
889 .enumerate()
890 .map(|(i, (pos, output))| {
891 if pos < requested_output_count {
894 tx_metadata.output_indices[pos] = i;
895 }
896
897 output.prepare(&mut rng, zip212_enforcement)
898 })
899 .collect::<Vec<_>>();
900
901 let input_total = spend_infos
903 .iter()
904 .try_fold(ValueSum::zero(), |balance, spend| {
905 (balance + spend.note.value()).ok_or(Error::InvalidAmount)
906 })?;
907 let value_balance = output_infos
908 .iter()
909 .try_fold(input_total, |balance, output| {
910 (balance - output.note.value()).ok_or(Error::InvalidAmount)
911 })?;
912
913 finisher(spend_infos, output_infos, value_balance, tx_metadata, rng)
914}
915
916#[cfg(feature = "circuit")]
920pub type UnauthorizedBundle<V> = Bundle<InProgress<Unproven, Unsigned>, V>;
921
922pub trait InProgressProofs: fmt::Debug {
924 type SpendProof: Clone + fmt::Debug;
926 type OutputProof: Clone + fmt::Debug;
928}
929
930pub trait InProgressSignatures: fmt::Debug {
932 type AuthSig: Clone + fmt::Debug;
935}
936
937#[derive(Clone, Debug)]
939pub struct InProgress<P: InProgressProofs, S: InProgressSignatures> {
940 sigs: S,
941 _proof_state: PhantomData<P>,
942}
943
944impl<P: InProgressProofs, S: InProgressSignatures> Authorization for InProgress<P, S> {
945 type SpendProof = P::SpendProof;
946 type OutputProof = P::OutputProof;
947 type AuthSig = S::AuthSig;
948}
949
950#[cfg(feature = "circuit")]
955#[derive(Clone, Copy, Debug)]
956pub struct Unproven;
957
958#[cfg(feature = "circuit")]
959impl InProgressProofs for Unproven {
960 type SpendProof = circuit::Spend;
961 type OutputProof = circuit::Output;
962}
963
964#[derive(Clone, Copy, Debug)]
966pub struct Proven;
967
968impl InProgressProofs for Proven {
969 type SpendProof = GrothProofBytes;
970 type OutputProof = GrothProofBytes;
971}
972
973pub trait ProverProgress {
975 fn update(&mut self, cur: u32, end: u32);
978}
979
980impl ProverProgress for () {
981 fn update(&mut self, _: u32, _: u32) {}
982}
983
984#[cfg(feature = "std")]
985impl<U: From<(u32, u32)>> ProverProgress for std::sync::mpsc::Sender<U> {
986 fn update(&mut self, cur: u32, end: u32) {
987 self.send(U::from((cur, end))).unwrap_or(());
989 }
990}
991
992impl<U: ProverProgress> ProverProgress for &mut U {
993 fn update(&mut self, cur: u32, end: u32) {
994 (*self).update(cur, end);
995 }
996}
997
998#[cfg(feature = "circuit")]
999struct CreateProofs<'a, SP: SpendProver, OP: OutputProver, R: Rng, U: ProverProgress> {
1000 spend_prover: &'a SP,
1001 output_prover: &'a OP,
1002 rng: R,
1003 progress_notifier: U,
1004 total_progress: u32,
1005 progress: u32,
1006}
1007
1008#[cfg(feature = "circuit")]
1009impl<'a, SP: SpendProver, OP: OutputProver, R: Rng, U: ProverProgress>
1010 CreateProofs<'a, SP, OP, R, U>
1011{
1012 fn new(
1013 spend_prover: &'a SP,
1014 output_prover: &'a OP,
1015 rng: R,
1016 progress_notifier: U,
1017 total_progress: u32,
1018 ) -> Self {
1019 Self {
1021 spend_prover,
1022 output_prover,
1023 rng,
1024 progress_notifier,
1025 total_progress,
1026 progress: 0u32,
1027 }
1028 }
1029
1030 fn update_progress(&mut self) {
1031 self.progress += 1;
1033 self.progress_notifier
1034 .update(self.progress, self.total_progress);
1035 }
1036
1037 fn map_spend_proof(&mut self, spend: circuit::Spend) -> GrothProofBytes {
1038 let proof = self.spend_prover.create_proof(spend, &mut self.rng);
1039 self.update_progress();
1040 SP::encode_proof(proof)
1041 }
1042
1043 fn map_output_proof(&mut self, output: circuit::Output) -> GrothProofBytes {
1044 let proof = self.output_prover.create_proof(output, &mut self.rng);
1045 self.update_progress();
1046 OP::encode_proof(proof)
1047 }
1048
1049 fn map_authorization<S: InProgressSignatures>(
1050 &mut self,
1051 a: InProgress<Unproven, S>,
1052 ) -> InProgress<Proven, S> {
1053 InProgress {
1054 sigs: a.sigs,
1055 _proof_state: PhantomData,
1056 }
1057 }
1058}
1059
1060#[cfg(feature = "circuit")]
1061impl<S: InProgressSignatures, V> Bundle<InProgress<Unproven, S>, V> {
1062 pub fn create_proofs<SP: SpendProver, OP: OutputProver>(
1064 self,
1065 spend_prover: &SP,
1066 output_prover: &OP,
1067 rng: impl Rng,
1068 progress_notifier: impl ProverProgress,
1069 ) -> Bundle<InProgress<Proven, S>, V> {
1070 let total_progress =
1071 self.shielded_spends().len() as u32 + self.shielded_outputs().len() as u32;
1072 let mut cp = CreateProofs::new(
1073 spend_prover,
1074 output_prover,
1075 rng,
1076 progress_notifier,
1077 total_progress,
1078 );
1079
1080 self.map_authorization(
1081 &mut cp,
1082 |cp, spend| cp.map_spend_proof(spend),
1083 |cp, output| cp.map_output_proof(output),
1084 |_cp, sig| sig,
1085 |cp, auth| cp.map_authorization(auth),
1086 )
1087 }
1088}
1089
1090#[derive(Clone)]
1092pub struct Unsigned {
1093 bsk: redjubjub::SigningKey<Binding>,
1094}
1095
1096impl fmt::Debug for Unsigned {
1097 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1098 f.debug_struct("Unsigned").finish_non_exhaustive()
1099 }
1100}
1101
1102impl InProgressSignatures for Unsigned {
1103 type AuthSig = SigningMetadata;
1104}
1105
1106#[derive(Clone, Debug)]
1108pub struct SigningParts {
1109 ak: SpendValidatingKey,
1112 alpha: jubjub::Scalar,
1114}
1115
1116#[derive(Clone, Debug)]
1118pub struct PartiallyAuthorized {
1119 binding_signature: redjubjub::Signature<Binding>,
1120 sighash: [u8; 32],
1121}
1122
1123#[derive(Clone, Debug)]
1125pub struct SigningMetadata {
1126 dummy_ask: Option<SpendAuthorizingKey>,
1132 parts: SigningParts,
1133}
1134
1135impl InProgressSignatures for PartiallyAuthorized {
1136 type AuthSig = MaybeSigned;
1137}
1138
1139#[derive(Clone, Debug)]
1143pub enum MaybeSigned {
1144 SigningParts(SigningParts),
1146 Signature(redjubjub::Signature<SpendAuth>),
1148}
1149
1150impl MaybeSigned {
1151 fn finalize(self) -> Result<redjubjub::Signature<SpendAuth>, Error> {
1152 match self {
1153 Self::Signature(sig) => Ok(sig),
1154 _ => Err(Error::MissingSignatures),
1155 }
1156 }
1157}
1158
1159impl<P: InProgressProofs, V> Bundle<InProgress<P, Unsigned>, V> {
1160 pub fn prepare<R: Rng + CryptoRng>(
1164 self,
1165 mut rng: R,
1166 sighash: [u8; 32],
1167 ) -> Bundle<InProgress<P, PartiallyAuthorized>, V> {
1168 self.map_authorization(
1169 &mut rng,
1170 |_, proof| proof,
1171 |_, proof| proof,
1172 |rng, SigningMetadata { dummy_ask, parts }| match dummy_ask {
1173 None => MaybeSigned::SigningParts(parts),
1174 Some(ask) => {
1175 MaybeSigned::Signature(ask.randomize(&parts.alpha).sign(rng, &sighash))
1176 }
1177 },
1178 |rng, auth: InProgress<P, Unsigned>| InProgress {
1179 sigs: PartiallyAuthorized {
1180 binding_signature: auth.sigs.bsk.sign(rng, &sighash),
1181 sighash,
1182 },
1183 _proof_state: PhantomData,
1184 },
1185 )
1186 }
1187}
1188
1189impl<V> Bundle<InProgress<Proven, Unsigned>, V> {
1190 pub fn apply_signatures<R: Rng + CryptoRng>(
1195 self,
1196 mut rng: R,
1197 sighash: [u8; 32],
1198 signing_keys: &[SpendAuthorizingKey],
1199 ) -> Result<Bundle<Authorized, V>, Error> {
1200 signing_keys
1201 .iter()
1202 .fold(self.prepare(&mut rng, sighash), |partial, ask| {
1203 partial.sign(&mut rng, ask)
1204 })
1205 .finalize()
1206 }
1207}
1208
1209impl<P: InProgressProofs, V> Bundle<InProgress<P, PartiallyAuthorized>, V> {
1210 pub fn sign<R: Rng + CryptoRng>(self, mut rng: R, ask: &SpendAuthorizingKey) -> Self {
1214 let expected_ak = ask.into();
1215 let sighash = self.authorization().sigs.sighash;
1216 self.map_authorization(
1217 &mut rng,
1218 |_, proof| proof,
1219 |_, proof| proof,
1220 |rng, maybe| match maybe {
1221 MaybeSigned::SigningParts(parts) if parts.ak == expected_ak => {
1222 MaybeSigned::Signature(ask.randomize(&parts.alpha).sign(rng, &sighash))
1223 }
1224 s => s,
1225 },
1226 |_, partial| partial,
1227 )
1228 }
1229
1230 pub fn append_signatures(
1236 self,
1237 signatures: &[redjubjub::Signature<SpendAuth>],
1238 ) -> Result<Self, Error> {
1239 signatures.iter().try_fold(self, Self::append_signature)
1240 }
1241
1242 fn append_signature(self, signature: &redjubjub::Signature<SpendAuth>) -> Result<Self, Error> {
1243 let sighash = self.authorization().sigs.sighash;
1244 let mut signature_valid_for = 0usize;
1245 let bundle = self.map_authorization(
1246 &mut signature_valid_for,
1247 |_, proof| proof,
1248 |_, proof| proof,
1249 |ctx, maybe| match maybe {
1250 MaybeSigned::SigningParts(parts) => {
1251 let rk = parts.ak.randomize(&parts.alpha);
1252 if rk.verify(&sighash, signature).is_ok() {
1253 **ctx += 1;
1254 MaybeSigned::Signature(*signature)
1255 } else {
1256 MaybeSigned::SigningParts(parts)
1258 }
1259 }
1260 s => s,
1261 },
1262 |_, partial| partial,
1263 );
1264 match signature_valid_for {
1265 0 => Err(Error::InvalidExternalSignature),
1266 1 => Ok(bundle),
1267 _ => Err(Error::DuplicateSignature),
1268 }
1269 }
1270}
1271
1272impl<V> Bundle<InProgress<Proven, PartiallyAuthorized>, V> {
1273 pub fn finalize(self) -> Result<Bundle<Authorized, V>, Error> {
1277 self.try_map_authorization(
1278 (),
1279 |_, v| Ok(v),
1280 |_, v| Ok(v),
1281 |_, maybe: MaybeSigned| maybe.finalize(),
1282 |_, partial: InProgress<Proven, PartiallyAuthorized>| {
1283 Ok(Authorized {
1284 binding_sig: partial.sigs.binding_signature,
1285 })
1286 },
1287 )
1288 }
1289}
1290
1291#[cfg(all(feature = "circuit", any(test, feature = "test-dependencies")))]
1292pub(crate) mod testing {
1293 use core::fmt;
1294
1295 use proptest::collection::vec;
1296 use proptest::prelude::*;
1297 use rand::{rngs::StdRng, SeedableRng};
1298
1299 use crate::{
1300 bundle::{Authorized, Bundle},
1301 note_encryption::Zip212Enforcement,
1302 testing::{arb_node, arb_note},
1303 value::testing::arb_positive_note_value,
1304 zip32::testing::arb_extended_spending_key,
1305 Anchor, Node,
1306 };
1307 use incrementalmerkletree::{
1308 frontier::testing::arb_commitment_tree, witness::IncrementalWitness,
1309 };
1310
1311 use super::{Builder, BundleType};
1312
1313 #[cfg(feature = "circuit")]
1314 use crate::prover::mock::{MockOutputProver, MockSpendProver};
1315
1316 #[allow(dead_code)]
1317 #[cfg(feature = "circuit")]
1318 fn arb_bundle<V: fmt::Debug + From<i64>>(
1319 max_money: u64,
1320 zip212_enforcement: Zip212Enforcement,
1321 ) -> impl Strategy<Value = Bundle<Authorized, V>> {
1322 (1..30usize)
1323 .prop_flat_map(move |n_notes| {
1324 (
1325 arb_extended_spending_key(),
1326 vec(
1327 arb_positive_note_value(max_money / 10000).prop_flat_map(arb_note),
1328 n_notes,
1329 ),
1330 vec(
1331 arb_commitment_tree::<_, _, 32>(n_notes, arb_node()).prop_map(|t| {
1332 IncrementalWitness::from_tree(t)
1333 .expect("valid encoding of an incremental witness")
1334 .path()
1335 .unwrap()
1336 }),
1337 n_notes,
1338 ),
1339 prop::array::uniform32(any::<u8>()),
1340 prop::array::uniform32(any::<u8>()),
1341 )
1342 })
1343 .prop_map(
1344 move |(extsk, spendable_notes, commitment_trees, rng_seed, fake_sighash_bytes)| {
1345 let dfvk = extsk.to_diversifiable_full_viewing_key();
1346 let anchor = spendable_notes
1347 .first()
1348 .zip(commitment_trees.first())
1349 .map_or_else(Anchor::empty_tree, |(note, tree)| {
1350 let node = Node::from_cmu(¬e.cmu());
1351 Anchor::from(*tree.root(node).inner())
1352 });
1353 let mut builder = Builder::new(zip212_enforcement, BundleType::DEFAULT, anchor);
1354 let mut rng = StdRng::from_seed(rng_seed);
1355
1356 for (note, path) in spendable_notes
1357 .into_iter()
1358 .zip(commitment_trees.into_iter())
1359 {
1360 builder.add_spend(dfvk.fvk().clone(), note, path).unwrap();
1361 }
1362
1363 let (bundle, _) = builder
1364 .build::<MockSpendProver, MockOutputProver, _, _>(
1365 core::slice::from_ref(&extsk),
1366 &mut rng,
1367 )
1368 .unwrap()
1369 .unwrap();
1370
1371 let bundle =
1372 bundle.create_proofs(&MockSpendProver, &MockOutputProver, &mut rng, ());
1373
1374 bundle
1375 .apply_signatures(
1376 &mut rng,
1377 fake_sighash_bytes,
1378 &[extsk.expsk().ask().clone()],
1379 )
1380 .unwrap()
1381 },
1382 )
1383 }
1384}