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, RngCore};
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)]
121pub enum Error {
122 AnchorMismatch,
123 BindingSig,
124 DuplicateSignature,
127 InvalidAddress,
128 InvalidAmount,
129 InvalidExternalSignature,
131 MissingSignatures,
133 MissingSpendingKey,
135 PcztRequiresZip212,
137 SpendProof,
138 BundleTypeNotSatisfiable,
140 WrongSpendingKey,
142}
143
144impl fmt::Display for Error {
145 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
146 match self {
147 Error::AnchorMismatch => {
148 write!(f, "Anchor mismatch (anchors for all spends must be equal)")
149 }
150 Error::BindingSig => write!(f, "Failed to create bindingSig"),
151 Error::DuplicateSignature => write!(f, "Signature valid for more than one input"),
152 Error::InvalidAddress => write!(f, "Invalid address"),
153 Error::InvalidAmount => write!(f, "Invalid amount"),
154 Error::InvalidExternalSignature => write!(f, "External signature was invalid"),
155 Error::MissingSignatures => write!(f, "Required signatures were missing during build"),
156 Error::MissingSpendingKey => write!(f, "A required spending key was not provided"),
157 Error::PcztRequiresZip212 => {
158 write!(f, "PCZTs require that ZIP 212 is enforced for outputs")
159 }
160 Error::SpendProof => write!(f, "Failed to create Sapling spend proof"),
161 Error::BundleTypeNotSatisfiable => {
162 f.write_str("Bundle structure did not conform to requested bundle type.")
163 }
164 Error::WrongSpendingKey => write!(f, "The wrong spending key was provided"),
165 }
166 }
167}
168
169#[derive(Debug, Clone)]
171pub struct SpendInfo {
172 fvk: FullViewingKey,
173 note: Note,
174 merkle_path: MerklePath,
175 dummy_expsk: Option<ExpandedSpendingKey>,
176}
177
178impl SpendInfo {
179 pub fn new(fvk: FullViewingKey, note: Note, merkle_path: MerklePath) -> Self {
181 Self {
182 fvk,
183 note,
184 merkle_path,
185 dummy_expsk: None,
186 }
187 }
188
189 pub fn value(&self) -> NoteValue {
191 self.note.value()
192 }
193
194 fn dummy<R: RngCore>(mut rng: R) -> Self {
198 let (sk, _, note) = Note::dummy(&mut rng);
199 let merkle_path = MerklePath::from_parts(
200 iter::repeat_with(|| Node::from_scalar(jubjub::Base::random(&mut rng)))
201 .take(NOTE_COMMITMENT_TREE_DEPTH.into())
202 .collect(),
203 Position::from(0),
204 )
205 .expect("The path length corresponds to the length of the generated vector.");
206
207 SpendInfo {
208 fvk: FullViewingKey {
209 vk: sk.proof_generation_key().to_viewing_key(),
210 ovk: sk.ovk,
211 },
212 note,
213 merkle_path,
214 dummy_expsk: Some(sk),
215 }
216 }
217
218 fn has_matching_anchor(&self, anchor: &Anchor) -> bool {
219 if self.note.value() == NoteValue::ZERO {
220 true
221 } else {
222 let node = Node::from_cmu(&self.note.cmu());
223 &Anchor::from(self.merkle_path.root(node)) == anchor
224 }
225 }
226
227 fn prepare<R: RngCore>(self, rng: R) -> PreparedSpendInfo {
228 PreparedSpendInfo {
229 fvk: self.fvk,
230 note: self.note,
231 merkle_path: self.merkle_path,
232 rcv: ValueCommitTrapdoor::random(rng),
233 dummy_expsk: self.dummy_expsk,
234 }
235 }
236}
237
238#[derive(Debug, Clone)]
239struct PreparedSpendInfo {
240 fvk: FullViewingKey,
241 note: Note,
242 merkle_path: MerklePath,
243 rcv: ValueCommitTrapdoor,
244 dummy_expsk: Option<ExpandedSpendingKey>,
245}
246
247impl PreparedSpendInfo {
248 fn build_inner<R: RngCore>(
249 &self,
250 mut rng: R,
251 ) -> (
252 ValueCommitment,
253 Nullifier,
254 redjubjub::VerificationKey<SpendAuth>,
255 jubjub::Scalar,
256 ) {
257 let alpha = jubjub::Fr::random(&mut rng);
259 let cv = ValueCommitment::derive(self.note.value(), self.rcv.clone());
260
261 let ak = self.fvk.vk.ak.clone();
262
263 let rk = ak.randomize(&alpha);
265
266 let nullifier = self
267 .note
268 .nf(&self.fvk.vk.nk, u64::from(self.merkle_path.position()));
269
270 (cv, nullifier, rk, alpha)
271 }
272
273 #[cfg(feature = "circuit")]
274 fn build<Pr: SpendProver, R: RngCore>(
275 self,
276 proof_generation_key: Option<ProofGenerationKey>,
277 rng: R,
278 ) -> Result<SpendDescription<InProgress<Unproven, Unsigned>>, Error> {
279 let proof_generation_key = match (proof_generation_key, self.dummy_expsk.as_ref()) {
280 (Some(proof_generation_key), None) => Ok(proof_generation_key),
281 (None, Some(expsk)) => Ok(expsk.proof_generation_key()),
282 (Some(_), Some(_)) => Err(Error::WrongSpendingKey),
283 (None, None) => Err(Error::MissingSpendingKey),
284 }?;
285 let expected_vk = proof_generation_key.to_viewing_key();
286 if (&expected_vk.ak, &expected_vk.nk) != (&self.fvk.vk.ak, &self.fvk.vk.nk) {
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),
318 parts: SigningParts { ak, alpha },
319 },
320 ))
321 }
322
323 fn into_pczt<R: RngCore>(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),
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: RngCore>(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(jubjub::Fr::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: RngCore>(
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: RngCore>(
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();
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: RngCore>(
468 self,
469 rng: &mut R,
470 ) -> OutputDescription<circuit::Output> {
471 let (cv, cmu, ephemeral_key, enc_ciphertext, out_ciphertext, zkproof) = self.build_inner(
472 |esk| {
473 Pr::prepare_circuit(
474 esk,
475 self.note.recipient(),
476 self.note.rcm(),
477 self.note.value(),
478 self.rcv.clone(),
479 )
480 },
481 rng,
482 );
483
484 OutputDescription::from_parts(
485 cv,
486 cmu,
487 ephemeral_key,
488 enc_ciphertext,
489 out_ciphertext,
490 zkproof,
491 )
492 }
493
494 fn into_pczt<R: RngCore>(self, rng: &mut R) -> crate::pczt::Output {
495 let (cv, cmu, ephemeral_key, enc_ciphertext, out_ciphertext, _) =
496 self.build_inner(|_| (), rng);
497
498 let rseed = match self.note.rseed() {
499 crate::Rseed::BeforeZip212(_) => {
500 panic!("Builder::build_for_pczt should prevent pre-ZIP 212 outputs")
501 }
502 crate::Rseed::AfterZip212(rseed) => Some(*rseed),
503 };
504
505 crate::pczt::Output {
506 cv,
507 cmu,
508 ephemeral_key,
509 enc_ciphertext,
510 out_ciphertext,
511 zkproof: None,
512 recipient: Some(self.note.recipient()),
513 value: Some(self.note.value()),
514 rseed,
515 rcv: Some(self.rcv),
516 ock: None,
518 zip32_derivation: None,
519 user_address: None,
520 proprietary: BTreeMap::new(),
521 }
522 }
523}
524
525#[derive(Debug, Clone, PartialEq, Eq)]
527pub struct SaplingMetadata {
528 spend_indices: Vec<usize>,
529 output_indices: Vec<usize>,
530}
531
532impl SaplingMetadata {
533 pub fn empty() -> Self {
534 SaplingMetadata {
535 spend_indices: vec![],
536 output_indices: vec![],
537 }
538 }
539
540 pub fn spend_index(&self, n: usize) -> Option<usize> {
548 self.spend_indices.get(n).copied()
549 }
550
551 pub fn output_index(&self, n: usize) -> Option<usize> {
559 self.output_indices.get(n).copied()
560 }
561}
562
563pub struct Builder {
565 value_balance: ValueSum,
566 spends: Vec<SpendInfo>,
567 outputs: Vec<OutputInfo>,
568 zip212_enforcement: Zip212Enforcement,
569 bundle_type: BundleType,
570 anchor: Anchor,
571}
572
573impl Builder {
574 pub fn new(
575 zip212_enforcement: Zip212Enforcement,
576 bundle_type: BundleType,
577 anchor: Anchor,
578 ) -> Self {
579 Builder {
580 value_balance: ValueSum::zero(),
581 spends: vec![],
582 outputs: vec![],
583 zip212_enforcement,
584 bundle_type,
585 anchor,
586 }
587 }
588
589 pub fn inputs(&self) -> &[SpendInfo] {
591 &self.spends
592 }
593
594 pub fn outputs(&self) -> &[OutputInfo] {
596 &self.outputs
597 }
598
599 fn try_value_balance<V: TryFrom<i64>>(&self) -> Result<V, Error> {
603 self.value_balance
604 .try_into()
605 .map_err(|_| ())
606 .and_then(|vb| V::try_from(vb).map_err(|_| ()))
607 .map_err(|()| Error::InvalidAmount)
608 }
609
610 pub fn value_balance<V: TryFrom<i64>>(&self) -> V {
612 self.try_value_balance()
613 .expect("we check this when mutating self.value_balance")
614 }
615
616 pub fn add_spend(
621 &mut self,
622 fvk: FullViewingKey,
623 note: Note,
624 merkle_path: MerklePath,
625 ) -> Result<(), Error> {
626 let spend = SpendInfo::new(fvk, note, merkle_path);
627
628 match self.bundle_type {
630 BundleType::Transactional { .. } => {
631 if !spend.has_matching_anchor(&self.anchor) {
632 return Err(Error::AnchorMismatch);
633 }
634 }
635 BundleType::Coinbase => {
636 return Err(Error::BundleTypeNotSatisfiable);
637 }
638 }
639
640 self.value_balance = (self.value_balance + spend.value()).ok_or(Error::InvalidAmount)?;
641 self.try_value_balance::<i64>()?;
642
643 self.spends.push(spend);
644
645 Ok(())
646 }
647
648 pub fn add_output(
650 &mut self,
651 ovk: Option<OutgoingViewingKey>,
652 to: PaymentAddress,
653 value: NoteValue,
654 memo: [u8; 512],
655 ) -> Result<(), Error> {
656 let output = OutputInfo::new(ovk, to, value, memo);
657
658 self.value_balance = (self.value_balance - value).ok_or(Error::InvalidAddress)?;
659 self.try_value_balance::<i64>()?;
660
661 self.outputs.push(output);
662
663 Ok(())
664 }
665
666 #[cfg(feature = "circuit")]
668 pub fn build<SP: SpendProver, OP: OutputProver, R: RngCore, V: TryFrom<i64>>(
669 self,
670 extsks: &[ExtendedSpendingKey],
671 rng: R,
672 ) -> Result<Option<(UnauthorizedBundle<V>, SaplingMetadata)>, Error> {
673 bundle::<SP, OP, _, _>(
674 rng,
675 self.bundle_type,
676 self.zip212_enforcement,
677 self.anchor,
678 self.spends,
679 self.outputs,
680 extsks,
681 )
682 }
683
684 pub fn build_for_pczt(
687 self,
688 rng: impl RngCore,
689 ) -> Result<(crate::pczt::Bundle, SaplingMetadata), Error> {
690 match self.zip212_enforcement {
691 Zip212Enforcement::Off | Zip212Enforcement::GracePeriod => {
692 Err(Error::PcztRequiresZip212)
693 }
694 Zip212Enforcement::On => build_bundle(
695 rng,
696 self.bundle_type,
697 Zip212Enforcement::On,
698 self.anchor,
699 self.spends,
700 self.outputs,
701 |spend_infos, output_infos, value_sum, tx_metadata, mut rng| {
702 let spends = spend_infos
704 .into_iter()
705 .map(|a| a.into_pczt(&mut rng))
706 .collect::<Vec<_>>();
707 let outputs = output_infos
708 .into_iter()
709 .map(|a| a.into_pczt(&mut rng))
710 .collect::<Vec<_>>();
711
712 Ok((
713 crate::pczt::Bundle {
714 spends,
715 outputs,
716 value_sum,
717 anchor: self.anchor,
718 bsk: None,
719 },
720 tx_metadata,
721 ))
722 },
723 ),
724 }
725 }
726}
727
728#[cfg(feature = "circuit")]
731pub fn bundle<SP: SpendProver, OP: OutputProver, R: RngCore, V: TryFrom<i64>>(
732 rng: R,
733 bundle_type: BundleType,
734 zip212_enforcement: Zip212Enforcement,
735 anchor: Anchor,
736 spends: Vec<SpendInfo>,
737 outputs: Vec<OutputInfo>,
738 extsks: &[ExtendedSpendingKey],
739) -> Result<Option<(UnauthorizedBundle<V>, SaplingMetadata)>, Error> {
740 build_bundle(
741 rng,
742 bundle_type,
743 zip212_enforcement,
744 anchor,
745 spends,
746 outputs,
747 |spend_infos, output_infos, value_balance, tx_metadata, mut rng| {
748 let value_balance_i64 =
749 i64::try_from(value_balance).map_err(|_| Error::InvalidAmount)?;
750
751 let bsk = {
753 let spends: TrapdoorSum = spend_infos.iter().map(|spend| &spend.rcv).sum();
754 let outputs: TrapdoorSum = output_infos.iter().map(|output| &output.rcv).sum();
755 (spends - outputs).into_bsk()
756 };
757
758 let shielded_spends = spend_infos
760 .into_iter()
761 .map(|a| {
762 let proof_generation_key = a
764 .dummy_expsk
765 .is_none()
766 .then(|| {
767 extsks.iter().find_map(|extsk| {
768 let dfvk = extsk.to_diversifiable_full_viewing_key();
769 (dfvk.fvk().to_bytes() == a.fvk.to_bytes())
770 .then(|| extsk.expsk.proof_generation_key())
771 })
772 })
773 .flatten();
774 a.build::<SP, _>(proof_generation_key, &mut rng)
775 })
776 .collect::<Result<Vec<_>, _>>()?;
777 let shielded_outputs = output_infos
778 .into_iter()
779 .map(|a| a.build::<OP, _>(&mut rng))
780 .collect::<Vec<_>>();
781
782 let bvk = {
784 let spends = shielded_spends
785 .iter()
786 .map(|spend| spend.cv())
787 .sum::<CommitmentSum>();
788 let outputs = shielded_outputs
789 .iter()
790 .map(|output| output.cv())
791 .sum::<CommitmentSum>();
792 (spends - outputs).into_bvk(value_balance_i64)
793 };
794 assert_eq!(redjubjub::VerificationKey::from(&bsk), bvk);
795
796 Ok(Bundle::from_parts(
797 shielded_spends,
798 shielded_outputs,
799 V::try_from(value_balance_i64).map_err(|_| Error::InvalidAmount)?,
800 InProgress {
801 sigs: Unsigned { bsk },
802 _proof_state: PhantomData,
803 },
804 )
805 .map(|b| (b, tx_metadata)))
806 },
807 )
808}
809
810fn build_bundle<B, R: RngCore>(
811 mut rng: R,
812 bundle_type: BundleType,
813 zip212_enforcement: Zip212Enforcement,
814 anchor: Anchor,
815 spends: Vec<SpendInfo>,
816 outputs: Vec<OutputInfo>,
817 finisher: impl FnOnce(
818 Vec<PreparedSpendInfo>,
819 Vec<PreparedOutputInfo>,
820 ValueSum,
821 SaplingMetadata,
822 R,
823 ) -> Result<B, Error>,
824) -> Result<B, Error> {
825 match bundle_type {
826 BundleType::Transactional { .. } => {
827 for spend in &spends {
828 if !spend.has_matching_anchor(&anchor) {
829 return Err(Error::AnchorMismatch);
830 }
831 }
832 }
833 BundleType::Coinbase => {
834 if !spends.is_empty() {
835 return Err(Error::BundleTypeNotSatisfiable);
836 }
837 }
838 }
839
840 let requested_spend_count = spends.len();
841 let bundle_spend_count = bundle_type
842 .num_spends(requested_spend_count)
843 .map_err(|_| Error::BundleTypeNotSatisfiable)?;
844
845 let requested_output_count = outputs.len();
846 let bundle_output_count = bundle_type
847 .num_outputs(spends.len(), requested_output_count)
848 .map_err(|_| Error::BundleTypeNotSatisfiable)?;
849 assert!(requested_output_count <= bundle_output_count);
850
851 let mut tx_metadata = SaplingMetadata::empty();
854 tx_metadata.spend_indices.resize(spends.len(), 0);
855 tx_metadata.output_indices.resize(requested_output_count, 0);
856
857 let mut indexed_spends: Vec<_> = spends
859 .into_iter()
860 .chain(iter::repeat_with(|| SpendInfo::dummy(&mut rng)))
861 .enumerate()
862 .take(bundle_spend_count)
863 .collect();
864
865 let mut indexed_outputs: Vec<_> = outputs
867 .into_iter()
868 .chain(iter::repeat_with(|| OutputInfo::dummy(&mut rng)))
869 .enumerate()
870 .take(bundle_output_count)
871 .collect();
872
873 indexed_spends.shuffle(&mut rng);
875 indexed_outputs.shuffle(&mut rng);
876
877 let spend_infos = indexed_spends
879 .into_iter()
880 .enumerate()
881 .map(|(i, (pos, spend))| {
882 if pos < requested_spend_count {
884 tx_metadata.spend_indices[pos] = i;
885 }
886
887 spend.prepare(&mut rng)
888 })
889 .collect::<Vec<_>>();
890 let output_infos = indexed_outputs
891 .into_iter()
892 .enumerate()
893 .map(|(i, (pos, output))| {
894 if pos < requested_output_count {
897 tx_metadata.output_indices[pos] = i;
898 }
899
900 output.prepare(&mut rng, zip212_enforcement)
901 })
902 .collect::<Vec<_>>();
903
904 let input_total = spend_infos
906 .iter()
907 .try_fold(ValueSum::zero(), |balance, spend| {
908 (balance + spend.note.value()).ok_or(Error::InvalidAmount)
909 })?;
910 let value_balance = output_infos
911 .iter()
912 .try_fold(input_total, |balance, output| {
913 (balance - output.note.value()).ok_or(Error::InvalidAmount)
914 })?;
915
916 finisher(spend_infos, output_infos, value_balance, tx_metadata, rng)
917}
918
919#[cfg(feature = "circuit")]
923pub type UnauthorizedBundle<V> = Bundle<InProgress<Unproven, Unsigned>, V>;
924
925pub trait InProgressProofs: fmt::Debug {
927 type SpendProof: Clone + fmt::Debug;
929 type OutputProof: Clone + fmt::Debug;
931}
932
933pub trait InProgressSignatures: fmt::Debug {
935 type AuthSig: Clone + fmt::Debug;
938}
939
940#[derive(Clone, Debug)]
942pub struct InProgress<P: InProgressProofs, S: InProgressSignatures> {
943 sigs: S,
944 _proof_state: PhantomData<P>,
945}
946
947impl<P: InProgressProofs, S: InProgressSignatures> Authorization for InProgress<P, S> {
948 type SpendProof = P::SpendProof;
949 type OutputProof = P::OutputProof;
950 type AuthSig = S::AuthSig;
951}
952
953#[cfg(feature = "circuit")]
958#[derive(Clone, Copy, Debug)]
959pub struct Unproven;
960
961#[cfg(feature = "circuit")]
962impl InProgressProofs for Unproven {
963 type SpendProof = circuit::Spend;
964 type OutputProof = circuit::Output;
965}
966
967#[derive(Clone, Copy, Debug)]
969pub struct Proven;
970
971impl InProgressProofs for Proven {
972 type SpendProof = GrothProofBytes;
973 type OutputProof = GrothProofBytes;
974}
975
976pub trait ProverProgress {
978 fn update(&mut self, cur: u32, end: u32);
981}
982
983impl ProverProgress for () {
984 fn update(&mut self, _: u32, _: u32) {}
985}
986
987#[cfg(feature = "std")]
988impl<U: From<(u32, u32)>> ProverProgress for std::sync::mpsc::Sender<U> {
989 fn update(&mut self, cur: u32, end: u32) {
990 self.send(U::from((cur, end))).unwrap_or(());
992 }
993}
994
995impl<U: ProverProgress> ProverProgress for &mut U {
996 fn update(&mut self, cur: u32, end: u32) {
997 (*self).update(cur, end);
998 }
999}
1000
1001#[cfg(feature = "circuit")]
1002struct CreateProofs<'a, SP: SpendProver, OP: OutputProver, R: RngCore, U: ProverProgress> {
1003 spend_prover: &'a SP,
1004 output_prover: &'a OP,
1005 rng: R,
1006 progress_notifier: U,
1007 total_progress: u32,
1008 progress: u32,
1009}
1010
1011#[cfg(feature = "circuit")]
1012impl<'a, SP: SpendProver, OP: OutputProver, R: RngCore, U: ProverProgress>
1013 CreateProofs<'a, SP, OP, R, U>
1014{
1015 fn new(
1016 spend_prover: &'a SP,
1017 output_prover: &'a OP,
1018 rng: R,
1019 progress_notifier: U,
1020 total_progress: u32,
1021 ) -> Self {
1022 Self {
1024 spend_prover,
1025 output_prover,
1026 rng,
1027 progress_notifier,
1028 total_progress,
1029 progress: 0u32,
1030 }
1031 }
1032
1033 fn update_progress(&mut self) {
1034 self.progress += 1;
1036 self.progress_notifier
1037 .update(self.progress, self.total_progress);
1038 }
1039
1040 fn map_spend_proof(&mut self, spend: circuit::Spend) -> GrothProofBytes {
1041 let proof = self.spend_prover.create_proof(spend, &mut self.rng);
1042 self.update_progress();
1043 SP::encode_proof(proof)
1044 }
1045
1046 fn map_output_proof(&mut self, output: circuit::Output) -> GrothProofBytes {
1047 let proof = self.output_prover.create_proof(output, &mut self.rng);
1048 self.update_progress();
1049 OP::encode_proof(proof)
1050 }
1051
1052 fn map_authorization<S: InProgressSignatures>(
1053 &mut self,
1054 a: InProgress<Unproven, S>,
1055 ) -> InProgress<Proven, S> {
1056 InProgress {
1057 sigs: a.sigs,
1058 _proof_state: PhantomData,
1059 }
1060 }
1061}
1062
1063#[cfg(feature = "circuit")]
1064impl<S: InProgressSignatures, V> Bundle<InProgress<Unproven, S>, V> {
1065 pub fn create_proofs<SP: SpendProver, OP: OutputProver>(
1067 self,
1068 spend_prover: &SP,
1069 output_prover: &OP,
1070 rng: impl RngCore,
1071 progress_notifier: impl ProverProgress,
1072 ) -> Bundle<InProgress<Proven, S>, V> {
1073 let total_progress =
1074 self.shielded_spends().len() as u32 + self.shielded_outputs().len() as u32;
1075 let mut cp = CreateProofs::new(
1076 spend_prover,
1077 output_prover,
1078 rng,
1079 progress_notifier,
1080 total_progress,
1081 );
1082
1083 self.map_authorization(
1084 &mut cp,
1085 |cp, spend| cp.map_spend_proof(spend),
1086 |cp, output| cp.map_output_proof(output),
1087 |_cp, sig| sig,
1088 |cp, auth| cp.map_authorization(auth),
1089 )
1090 }
1091}
1092
1093#[derive(Clone)]
1095pub struct Unsigned {
1096 bsk: redjubjub::SigningKey<Binding>,
1097}
1098
1099impl fmt::Debug for Unsigned {
1100 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1101 f.debug_struct("Unsigned").finish_non_exhaustive()
1102 }
1103}
1104
1105impl InProgressSignatures for Unsigned {
1106 type AuthSig = SigningMetadata;
1107}
1108
1109#[derive(Clone, Debug)]
1111pub struct SigningParts {
1112 ak: SpendValidatingKey,
1115 alpha: jubjub::Scalar,
1117}
1118
1119#[derive(Clone, Debug)]
1121pub struct PartiallyAuthorized {
1122 binding_signature: redjubjub::Signature<Binding>,
1123 sighash: [u8; 32],
1124}
1125
1126#[derive(Clone, Debug)]
1128pub struct SigningMetadata {
1129 dummy_ask: Option<SpendAuthorizingKey>,
1135 parts: SigningParts,
1136}
1137
1138impl InProgressSignatures for PartiallyAuthorized {
1139 type AuthSig = MaybeSigned;
1140}
1141
1142#[derive(Clone, Debug)]
1146pub enum MaybeSigned {
1147 SigningParts(SigningParts),
1149 Signature(redjubjub::Signature<SpendAuth>),
1151}
1152
1153impl MaybeSigned {
1154 fn finalize(self) -> Result<redjubjub::Signature<SpendAuth>, Error> {
1155 match self {
1156 Self::Signature(sig) => Ok(sig),
1157 _ => Err(Error::MissingSignatures),
1158 }
1159 }
1160}
1161
1162impl<P: InProgressProofs, V> Bundle<InProgress<P, Unsigned>, V> {
1163 pub fn prepare<R: RngCore + CryptoRng>(
1167 self,
1168 mut rng: R,
1169 sighash: [u8; 32],
1170 ) -> Bundle<InProgress<P, PartiallyAuthorized>, V> {
1171 self.map_authorization(
1172 &mut rng,
1173 |_, proof| proof,
1174 |_, proof| proof,
1175 |rng, SigningMetadata { dummy_ask, parts }| match dummy_ask {
1176 None => MaybeSigned::SigningParts(parts),
1177 Some(ask) => {
1178 MaybeSigned::Signature(ask.randomize(&parts.alpha).sign(rng, &sighash))
1179 }
1180 },
1181 |rng, auth: InProgress<P, Unsigned>| InProgress {
1182 sigs: PartiallyAuthorized {
1183 binding_signature: auth.sigs.bsk.sign(rng, &sighash),
1184 sighash,
1185 },
1186 _proof_state: PhantomData,
1187 },
1188 )
1189 }
1190}
1191
1192impl<V> Bundle<InProgress<Proven, Unsigned>, V> {
1193 pub fn apply_signatures<R: RngCore + CryptoRng>(
1198 self,
1199 mut rng: R,
1200 sighash: [u8; 32],
1201 signing_keys: &[SpendAuthorizingKey],
1202 ) -> Result<Bundle<Authorized, V>, Error> {
1203 signing_keys
1204 .iter()
1205 .fold(self.prepare(&mut rng, sighash), |partial, ask| {
1206 partial.sign(&mut rng, ask)
1207 })
1208 .finalize()
1209 }
1210}
1211
1212impl<P: InProgressProofs, V> Bundle<InProgress<P, PartiallyAuthorized>, V> {
1213 pub fn sign<R: RngCore + CryptoRng>(self, mut rng: R, ask: &SpendAuthorizingKey) -> Self {
1217 let expected_ak = ask.into();
1218 let sighash = self.authorization().sigs.sighash;
1219 self.map_authorization(
1220 &mut rng,
1221 |_, proof| proof,
1222 |_, proof| proof,
1223 |rng, maybe| match maybe {
1224 MaybeSigned::SigningParts(parts) if parts.ak == expected_ak => {
1225 MaybeSigned::Signature(ask.randomize(&parts.alpha).sign(rng, &sighash))
1226 }
1227 s => s,
1228 },
1229 |_, partial| partial,
1230 )
1231 }
1232
1233 pub fn append_signatures(
1239 self,
1240 signatures: &[redjubjub::Signature<SpendAuth>],
1241 ) -> Result<Self, Error> {
1242 signatures.iter().try_fold(self, Self::append_signature)
1243 }
1244
1245 fn append_signature(self, signature: &redjubjub::Signature<SpendAuth>) -> Result<Self, Error> {
1246 let sighash = self.authorization().sigs.sighash;
1247 let mut signature_valid_for = 0usize;
1248 let bundle = self.map_authorization(
1249 &mut signature_valid_for,
1250 |_, proof| proof,
1251 |_, proof| proof,
1252 |ctx, maybe| match maybe {
1253 MaybeSigned::SigningParts(parts) => {
1254 let rk = parts.ak.randomize(&parts.alpha);
1255 if rk.verify(&sighash, signature).is_ok() {
1256 **ctx += 1;
1257 MaybeSigned::Signature(*signature)
1258 } else {
1259 MaybeSigned::SigningParts(parts)
1261 }
1262 }
1263 s => s,
1264 },
1265 |_, partial| partial,
1266 );
1267 match signature_valid_for {
1268 0 => Err(Error::InvalidExternalSignature),
1269 1 => Ok(bundle),
1270 _ => Err(Error::DuplicateSignature),
1271 }
1272 }
1273}
1274
1275impl<V> Bundle<InProgress<Proven, PartiallyAuthorized>, V> {
1276 pub fn finalize(self) -> Result<Bundle<Authorized, V>, Error> {
1280 self.try_map_authorization(
1281 (),
1282 |_, v| Ok(v),
1283 |_, v| Ok(v),
1284 |_, maybe: MaybeSigned| maybe.finalize(),
1285 |_, partial: InProgress<Proven, PartiallyAuthorized>| {
1286 Ok(Authorized {
1287 binding_sig: partial.sigs.binding_signature,
1288 })
1289 },
1290 )
1291 }
1292}
1293
1294#[cfg(all(feature = "circuit", any(test, feature = "test-dependencies")))]
1295pub(crate) mod testing {
1296 use core::fmt;
1297
1298 use proptest::collection::vec;
1299 use proptest::prelude::*;
1300 use rand::{rngs::StdRng, SeedableRng};
1301
1302 use crate::{
1303 bundle::{Authorized, Bundle},
1304 note_encryption::Zip212Enforcement,
1305 testing::{arb_node, arb_note},
1306 value::testing::arb_positive_note_value,
1307 zip32::testing::arb_extended_spending_key,
1308 Anchor, Node,
1309 };
1310 use incrementalmerkletree::{
1311 frontier::testing::arb_commitment_tree, witness::IncrementalWitness,
1312 };
1313
1314 use super::{Builder, BundleType};
1315
1316 #[cfg(feature = "circuit")]
1317 use crate::prover::mock::{MockOutputProver, MockSpendProver};
1318
1319 #[allow(dead_code)]
1320 #[cfg(feature = "circuit")]
1321 fn arb_bundle<V: fmt::Debug + From<i64>>(
1322 max_money: u64,
1323 zip212_enforcement: Zip212Enforcement,
1324 ) -> impl Strategy<Value = Bundle<Authorized, V>> {
1325 (1..30usize)
1326 .prop_flat_map(move |n_notes| {
1327 (
1328 arb_extended_spending_key(),
1329 vec(
1330 arb_positive_note_value(max_money / 10000).prop_flat_map(arb_note),
1331 n_notes,
1332 ),
1333 vec(
1334 arb_commitment_tree::<_, _, 32>(n_notes, arb_node()).prop_map(|t| {
1335 IncrementalWitness::from_tree(t)
1336 .expect("valid encoding of an incremental witness")
1337 .path()
1338 .unwrap()
1339 }),
1340 n_notes,
1341 ),
1342 prop::array::uniform32(any::<u8>()),
1343 prop::array::uniform32(any::<u8>()),
1344 )
1345 })
1346 .prop_map(
1347 move |(extsk, spendable_notes, commitment_trees, rng_seed, fake_sighash_bytes)| {
1348 let dfvk = extsk.to_diversifiable_full_viewing_key();
1349 let anchor = spendable_notes
1350 .first()
1351 .zip(commitment_trees.first())
1352 .map_or_else(Anchor::empty_tree, |(note, tree)| {
1353 let node = Node::from_cmu(¬e.cmu());
1354 Anchor::from(*tree.root(node).inner())
1355 });
1356 let mut builder = Builder::new(zip212_enforcement, BundleType::DEFAULT, anchor);
1357 let mut rng = StdRng::from_seed(rng_seed);
1358
1359 for (note, path) in spendable_notes
1360 .into_iter()
1361 .zip(commitment_trees.into_iter())
1362 {
1363 builder.add_spend(dfvk.fvk().clone(), note, path).unwrap();
1364 }
1365
1366 let (bundle, _) = builder
1367 .build::<MockSpendProver, MockOutputProver, _, _>(
1368 &[extsk.clone()],
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}