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