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)]
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: RngCore>(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 {
213 vk: sk.proof_generation_key().to_viewing_key(),
214 ovk: sk.ovk,
215 },
216 note,
217 merkle_path,
218 dummy_expsk: Some(sk),
219 }
220 }
221
222 fn has_matching_anchor(&self, anchor: &Anchor) -> bool {
223 if self.note.value() == NoteValue::ZERO {
224 true
225 } else {
226 let node = Node::from_cmu(&self.note.cmu());
227 &Anchor::from(self.merkle_path.root(node)) == anchor
228 }
229 }
230
231 fn prepare<R: RngCore>(self, rng: R) -> PreparedSpendInfo {
232 PreparedSpendInfo {
233 fvk: self.fvk,
234 note: self.note,
235 merkle_path: self.merkle_path,
236 rcv: ValueCommitTrapdoor::random(rng),
237 dummy_expsk: self.dummy_expsk,
238 }
239 }
240}
241
242#[derive(Debug, Clone)]
243struct PreparedSpendInfo {
244 fvk: FullViewingKey,
245 note: Note,
246 merkle_path: MerklePath,
247 rcv: ValueCommitTrapdoor,
248 dummy_expsk: Option<ExpandedSpendingKey>,
249}
250
251impl PreparedSpendInfo {
252 fn build_inner<R: RngCore>(
253 &self,
254 mut rng: R,
255 ) -> (
256 ValueCommitment,
257 Nullifier,
258 redjubjub::VerificationKey<SpendAuth>,
259 jubjub::Scalar,
260 ) {
261 let alpha = jubjub::Fr::random(&mut rng);
263 let cv = ValueCommitment::derive(self.note.value(), self.rcv.clone());
264
265 let ak = self.fvk.vk.ak.clone();
266
267 let rk = ak.randomize(&alpha);
269
270 let nullifier = self
271 .note
272 .nf(&self.fvk.vk.nk, u64::from(self.merkle_path.position()));
273
274 (cv, nullifier, rk, alpha)
275 }
276
277 #[cfg(feature = "circuit")]
278 fn build<Pr: SpendProver, R: RngCore>(
279 self,
280 proof_generation_key: Option<ProofGenerationKey>,
281 rng: R,
282 ) -> Result<SpendDescription<InProgress<Unproven, Unsigned>>, Error> {
283 let proof_generation_key = match (proof_generation_key, self.dummy_expsk.as_ref()) {
284 (Some(proof_generation_key), None) => Ok(proof_generation_key),
285 (None, Some(expsk)) => Ok(expsk.proof_generation_key()),
286 (Some(_), Some(_)) => Err(Error::WrongSpendingKey),
287 (None, None) => Err(Error::MissingSpendingKey),
288 }?;
289 let expected_vk = proof_generation_key.to_viewing_key();
290 if (&expected_vk.ak, &expected_vk.nk) != (&self.fvk.vk.ak, &self.fvk.vk.nk) {
291 return Err(Error::WrongSpendingKey);
292 }
293
294 let (cv, nullifier, rk, alpha) = self.build_inner(rng);
295
296 let node = Node::from_cmu(&self.note.cmu());
298 let anchor = *self.merkle_path.root(node).inner();
299
300 let ak = self.fvk.vk.ak.clone();
301
302 let zkproof = Pr::prepare_circuit(
303 proof_generation_key,
304 *self.note.recipient().diversifier(),
305 *self.note.rseed(),
306 self.note.value(),
307 alpha,
308 self.rcv,
309 anchor,
310 self.merkle_path.clone(),
311 )
312 .ok_or(Error::SpendProof)?;
313
314 Ok(SpendDescription::from_parts(
315 cv,
316 anchor,
317 nullifier,
318 rk,
319 zkproof,
320 SigningMetadata {
321 dummy_ask: self.dummy_expsk.map(|expsk| expsk.ask),
322 parts: SigningParts { ak, alpha },
323 },
324 ))
325 }
326
327 fn into_pczt<R: RngCore>(self, rng: R) -> crate::pczt::Spend {
328 let (cv, nullifier, rk, alpha) = self.build_inner(rng);
329
330 crate::pczt::Spend {
331 cv,
332 nullifier,
333 rk,
334 zkproof: None,
335 spend_auth_sig: None,
336 recipient: Some(self.note.recipient()),
337 value: Some(self.note.value()),
338 rseed: Some(*self.note.rseed()),
339 rcv: Some(self.rcv),
340 proof_generation_key: self
341 .dummy_expsk
342 .as_ref()
343 .map(|expsk| expsk.proof_generation_key()),
344 witness: Some(self.merkle_path),
345 alpha: Some(alpha),
346 zip32_derivation: None,
347 dummy_ask: self.dummy_expsk.map(|expsk| expsk.ask),
348 proprietary: BTreeMap::new(),
349 }
350 }
351}
352
353#[derive(Clone)]
356pub struct OutputInfo {
357 ovk: Option<OutgoingViewingKey>,
359 to: PaymentAddress,
360 value: NoteValue,
361 memo: [u8; 512],
362}
363
364impl OutputInfo {
365 pub fn new(
367 ovk: Option<OutgoingViewingKey>,
368 to: PaymentAddress,
369 value: NoteValue,
370 memo: [u8; 512],
371 ) -> Self {
372 Self {
373 ovk,
374 to,
375 value,
376 memo,
377 }
378 }
379
380 pub fn recipient(&self) -> PaymentAddress {
382 self.to
383 }
384
385 pub fn value(&self) -> NoteValue {
387 self.value
388 }
389
390 pub fn dummy<R: RngCore>(mut rng: &mut R) -> Self {
392 let dummy_to = {
394 let mut diversifier = Diversifier([0; 11]);
395 loop {
396 rng.fill_bytes(&mut diversifier.0);
397 let dummy_ivk = SaplingIvk(jubjub::Fr::random(&mut rng));
398 if let Some(addr) = dummy_ivk.to_payment_address(diversifier) {
399 break addr;
400 }
401 }
402 };
403
404 Self::new(None, dummy_to, NoteValue::ZERO, [0u8; 512])
405 }
406
407 fn prepare<R: RngCore>(
408 self,
409 rng: &mut R,
410 zip212_enforcement: Zip212Enforcement,
411 ) -> PreparedOutputInfo {
412 let rseed = generate_random_rseed_internal(zip212_enforcement, rng);
413
414 let note = Note::from_parts(self.to, self.value, rseed);
415
416 PreparedOutputInfo {
417 ovk: self.ovk,
418 note,
419 memo: self.memo,
420 rcv: ValueCommitTrapdoor::random(rng),
421 }
422 }
423}
424
425struct PreparedOutputInfo {
426 ovk: Option<OutgoingViewingKey>,
428 note: Note,
429 memo: [u8; 512],
430 rcv: ValueCommitTrapdoor,
431}
432
433impl PreparedOutputInfo {
434 fn build_inner<P, R: RngCore>(
435 &self,
436 zkproof: impl FnOnce(&EphemeralSecretKey) -> P,
437 rng: &mut R,
438 ) -> (
439 ValueCommitment,
440 ExtractedNoteCommitment,
441 EphemeralKeyBytes,
442 [u8; 580],
443 [u8; 80],
444 P,
445 ) {
446 let encryptor = sapling_note_encryption::<R>(self.ovk, self.note.clone(), self.memo, rng);
447
448 let cv = ValueCommitment::derive(self.note.value(), self.rcv.clone());
450
451 let zkproof = zkproof(encryptor.esk());
452
453 let cmu = self.note.cmu();
454
455 let enc_ciphertext = encryptor.encrypt_note_plaintext();
456 let out_ciphertext = encryptor.encrypt_outgoing_plaintext(&cv, &cmu, rng);
457
458 let epk = encryptor.epk();
459
460 (
461 cv,
462 cmu,
463 epk.to_bytes(),
464 enc_ciphertext,
465 out_ciphertext,
466 zkproof,
467 )
468 }
469
470 #[cfg(feature = "circuit")]
471 fn build<Pr: OutputProver, R: RngCore>(
472 self,
473 rng: &mut R,
474 ) -> OutputDescription<circuit::Output> {
475 let (cv, cmu, ephemeral_key, enc_ciphertext, out_ciphertext, zkproof) = self.build_inner(
476 |esk| {
477 Pr::prepare_circuit(
478 esk,
479 self.note.recipient(),
480 self.note.rcm(),
481 self.note.value(),
482 self.rcv.clone(),
483 )
484 },
485 rng,
486 );
487
488 OutputDescription::from_parts(
489 cv,
490 cmu,
491 ephemeral_key,
492 enc_ciphertext,
493 out_ciphertext,
494 zkproof,
495 )
496 }
497
498 fn into_pczt<R: RngCore>(self, rng: &mut R) -> crate::pczt::Output {
499 let (cv, cmu, ephemeral_key, enc_ciphertext, out_ciphertext, _) =
500 self.build_inner(|_| (), rng);
501
502 let rseed = match self.note.rseed() {
503 crate::Rseed::BeforeZip212(_) => {
504 panic!("Builder::build_for_pczt should prevent pre-ZIP 212 outputs")
505 }
506 crate::Rseed::AfterZip212(rseed) => Some(*rseed),
507 };
508
509 crate::pczt::Output {
510 cv,
511 cmu,
512 ephemeral_key,
513 enc_ciphertext,
514 out_ciphertext,
515 zkproof: None,
516 recipient: Some(self.note.recipient()),
517 value: Some(self.note.value()),
518 rseed,
519 rcv: Some(self.rcv),
520 ock: None,
522 zip32_derivation: None,
523 user_address: None,
524 proprietary: BTreeMap::new(),
525 }
526 }
527}
528
529#[derive(Debug, Clone, PartialEq, Eq)]
531pub struct SaplingMetadata {
532 spend_indices: Vec<usize>,
533 output_indices: Vec<usize>,
534}
535
536impl SaplingMetadata {
537 pub fn empty() -> Self {
538 SaplingMetadata {
539 spend_indices: vec![],
540 output_indices: vec![],
541 }
542 }
543
544 pub fn spend_index(&self, n: usize) -> Option<usize> {
552 self.spend_indices.get(n).copied()
553 }
554
555 pub fn output_index(&self, n: usize) -> Option<usize> {
563 self.output_indices.get(n).copied()
564 }
565}
566
567pub struct Builder {
569 value_balance: ValueSum,
570 spends: Vec<SpendInfo>,
571 outputs: Vec<OutputInfo>,
572 zip212_enforcement: Zip212Enforcement,
573 bundle_type: BundleType,
574 anchor: Anchor,
575}
576
577impl Builder {
578 pub fn new(
579 zip212_enforcement: Zip212Enforcement,
580 bundle_type: BundleType,
581 anchor: Anchor,
582 ) -> Self {
583 Builder {
584 value_balance: ValueSum::zero(),
585 spends: vec![],
586 outputs: vec![],
587 zip212_enforcement,
588 bundle_type,
589 anchor,
590 }
591 }
592
593 pub fn inputs(&self) -> &[SpendInfo] {
595 &self.spends
596 }
597
598 pub fn outputs(&self) -> &[OutputInfo] {
600 &self.outputs
601 }
602
603 fn try_value_balance<V: TryFrom<i64>>(&self) -> Result<V, Error> {
607 self.value_balance
608 .try_into()
609 .map_err(|_| ())
610 .and_then(|vb| V::try_from(vb).map_err(|_| ()))
611 .map_err(|()| Error::InvalidAmount)
612 }
613
614 pub fn value_balance<V: TryFrom<i64>>(&self) -> V {
616 self.try_value_balance()
617 .expect("we check this when mutating self.value_balance")
618 }
619
620 pub fn add_spend(
625 &mut self,
626 fvk: FullViewingKey,
627 note: Note,
628 merkle_path: MerklePath,
629 ) -> Result<(), Error> {
630 let spend = SpendInfo::new(fvk, note, merkle_path);
631
632 match self.bundle_type {
634 BundleType::Transactional { .. } => {
635 if !spend.has_matching_anchor(&self.anchor) {
636 return Err(Error::AnchorMismatch);
637 }
638 }
639 BundleType::Coinbase => {
640 return Err(Error::BundleTypeNotSatisfiable);
641 }
642 }
643
644 self.value_balance = (self.value_balance + spend.value()).ok_or(Error::InvalidAmount)?;
645 self.try_value_balance::<i64>()?;
646
647 self.spends.push(spend);
648
649 Ok(())
650 }
651
652 pub fn add_output(
654 &mut self,
655 ovk: Option<OutgoingViewingKey>,
656 to: PaymentAddress,
657 value: NoteValue,
658 memo: [u8; 512],
659 ) -> Result<(), Error> {
660 let output = OutputInfo::new(ovk, to, value, memo);
661
662 self.value_balance = (self.value_balance - value).ok_or(Error::InvalidAddress)?;
663 self.try_value_balance::<i64>()?;
664
665 self.outputs.push(output);
666
667 Ok(())
668 }
669
670 #[cfg(feature = "circuit")]
672 pub fn build<SP: SpendProver, OP: OutputProver, R: RngCore, V: TryFrom<i64>>(
673 self,
674 extsks: &[ExtendedSpendingKey],
675 rng: R,
676 ) -> Result<Option<(UnauthorizedBundle<V>, SaplingMetadata)>, Error> {
677 bundle::<SP, OP, _, _>(
678 rng,
679 self.bundle_type,
680 self.zip212_enforcement,
681 self.anchor,
682 self.spends,
683 self.outputs,
684 extsks,
685 )
686 }
687
688 pub fn build_for_pczt(
691 self,
692 rng: impl RngCore,
693 ) -> Result<(crate::pczt::Bundle, SaplingMetadata), Error> {
694 match self.zip212_enforcement {
695 Zip212Enforcement::Off | Zip212Enforcement::GracePeriod => {
696 Err(Error::PcztRequiresZip212)
697 }
698 Zip212Enforcement::On => build_bundle(
699 rng,
700 self.bundle_type,
701 Zip212Enforcement::On,
702 self.anchor,
703 self.spends,
704 self.outputs,
705 |spend_infos, output_infos, value_sum, tx_metadata, mut rng| {
706 let spends = spend_infos
708 .into_iter()
709 .map(|a| a.into_pczt(&mut rng))
710 .collect::<Vec<_>>();
711 let outputs = output_infos
712 .into_iter()
713 .map(|a| a.into_pczt(&mut rng))
714 .collect::<Vec<_>>();
715
716 Ok((
717 crate::pczt::Bundle {
718 spends,
719 outputs,
720 value_sum,
721 anchor: self.anchor,
722 bsk: None,
723 },
724 tx_metadata,
725 ))
726 },
727 ),
728 }
729 }
730}
731
732#[cfg(feature = "circuit")]
735pub fn bundle<SP: SpendProver, OP: OutputProver, R: RngCore, V: TryFrom<i64>>(
736 rng: R,
737 bundle_type: BundleType,
738 zip212_enforcement: Zip212Enforcement,
739 anchor: Anchor,
740 spends: Vec<SpendInfo>,
741 outputs: Vec<OutputInfo>,
742 extsks: &[ExtendedSpendingKey],
743) -> Result<Option<(UnauthorizedBundle<V>, SaplingMetadata)>, Error> {
744 build_bundle(
745 rng,
746 bundle_type,
747 zip212_enforcement,
748 anchor,
749 spends,
750 outputs,
751 |spend_infos, output_infos, value_balance, tx_metadata, mut rng| {
752 let value_balance_i64 =
753 i64::try_from(value_balance).map_err(|_| Error::InvalidAmount)?;
754
755 let bsk = {
757 let spends: TrapdoorSum = spend_infos.iter().map(|spend| &spend.rcv).sum();
758 let outputs: TrapdoorSum = output_infos.iter().map(|output| &output.rcv).sum();
759 (spends - outputs).into_bsk()
760 };
761
762 let shielded_spends = spend_infos
764 .into_iter()
765 .map(|a| {
766 let proof_generation_key = a
768 .dummy_expsk
769 .is_none()
770 .then(|| {
771 extsks.iter().find_map(|extsk| {
772 let dfvk = extsk.to_diversifiable_full_viewing_key();
773 (dfvk.fvk().to_bytes() == a.fvk.to_bytes())
774 .then(|| extsk.expsk.proof_generation_key())
775 })
776 })
777 .flatten();
778 a.build::<SP, _>(proof_generation_key, &mut rng)
779 })
780 .collect::<Result<Vec<_>, _>>()?;
781 let shielded_outputs = output_infos
782 .into_iter()
783 .map(|a| a.build::<OP, _>(&mut rng))
784 .collect::<Vec<_>>();
785
786 let bvk = {
788 let spends = shielded_spends
789 .iter()
790 .map(|spend| spend.cv())
791 .sum::<CommitmentSum>();
792 let outputs = shielded_outputs
793 .iter()
794 .map(|output| output.cv())
795 .sum::<CommitmentSum>();
796 (spends - outputs).into_bvk(value_balance_i64)
797 };
798 assert_eq!(redjubjub::VerificationKey::from(&bsk), bvk);
799
800 Ok(Bundle::from_parts(
801 shielded_spends,
802 shielded_outputs,
803 V::try_from(value_balance_i64).map_err(|_| Error::InvalidAmount)?,
804 InProgress {
805 sigs: Unsigned { bsk },
806 _proof_state: PhantomData,
807 },
808 )
809 .map(|b| (b, tx_metadata)))
810 },
811 )
812}
813
814fn build_bundle<B, R: RngCore>(
815 mut rng: R,
816 bundle_type: BundleType,
817 zip212_enforcement: Zip212Enforcement,
818 anchor: Anchor,
819 spends: Vec<SpendInfo>,
820 outputs: Vec<OutputInfo>,
821 finisher: impl FnOnce(
822 Vec<PreparedSpendInfo>,
823 Vec<PreparedOutputInfo>,
824 ValueSum,
825 SaplingMetadata,
826 R,
827 ) -> Result<B, Error>,
828) -> Result<B, Error> {
829 match bundle_type {
830 BundleType::Transactional { .. } => {
831 for spend in &spends {
832 if !spend.has_matching_anchor(&anchor) {
833 return Err(Error::AnchorMismatch);
834 }
835 }
836 }
837 BundleType::Coinbase => {
838 if !spends.is_empty() {
839 return Err(Error::BundleTypeNotSatisfiable);
840 }
841 }
842 }
843
844 let requested_spend_count = spends.len();
845 let bundle_spend_count = bundle_type
846 .num_spends(requested_spend_count)
847 .map_err(|_| Error::BundleTypeNotSatisfiable)?;
848
849 let requested_output_count = outputs.len();
850 let bundle_output_count = bundle_type
851 .num_outputs(spends.len(), requested_output_count)
852 .map_err(|_| Error::BundleTypeNotSatisfiable)?;
853 assert!(requested_output_count <= bundle_output_count);
854
855 let mut tx_metadata = SaplingMetadata::empty();
858 tx_metadata.spend_indices.resize(spends.len(), 0);
859 tx_metadata.output_indices.resize(requested_output_count, 0);
860
861 let mut indexed_spends: Vec<_> = spends
863 .into_iter()
864 .chain(iter::repeat_with(|| SpendInfo::dummy(&mut rng)))
865 .enumerate()
866 .take(bundle_spend_count)
867 .collect();
868
869 let mut indexed_outputs: Vec<_> = outputs
871 .into_iter()
872 .chain(iter::repeat_with(|| OutputInfo::dummy(&mut rng)))
873 .enumerate()
874 .take(bundle_output_count)
875 .collect();
876
877 indexed_spends.shuffle(&mut rng);
879 indexed_outputs.shuffle(&mut rng);
880
881 let spend_infos = indexed_spends
883 .into_iter()
884 .enumerate()
885 .map(|(i, (pos, spend))| {
886 if pos < requested_spend_count {
888 tx_metadata.spend_indices[pos] = i;
889 }
890
891 spend.prepare(&mut rng)
892 })
893 .collect::<Vec<_>>();
894 let output_infos = indexed_outputs
895 .into_iter()
896 .enumerate()
897 .map(|(i, (pos, output))| {
898 if pos < requested_output_count {
901 tx_metadata.output_indices[pos] = i;
902 }
903
904 output.prepare(&mut rng, zip212_enforcement)
905 })
906 .collect::<Vec<_>>();
907
908 let input_total = spend_infos
910 .iter()
911 .try_fold(ValueSum::zero(), |balance, spend| {
912 (balance + spend.note.value()).ok_or(Error::InvalidAmount)
913 })?;
914 let value_balance = output_infos
915 .iter()
916 .try_fold(input_total, |balance, output| {
917 (balance - output.note.value()).ok_or(Error::InvalidAmount)
918 })?;
919
920 finisher(spend_infos, output_infos, value_balance, tx_metadata, rng)
921}
922
923#[cfg(feature = "circuit")]
927pub type UnauthorizedBundle<V> = Bundle<InProgress<Unproven, Unsigned>, V>;
928
929pub trait InProgressProofs: fmt::Debug {
931 type SpendProof: Clone + fmt::Debug;
933 type OutputProof: Clone + fmt::Debug;
935}
936
937pub trait InProgressSignatures: fmt::Debug {
939 type AuthSig: Clone + fmt::Debug;
942}
943
944#[derive(Clone, Debug)]
946pub struct InProgress<P: InProgressProofs, S: InProgressSignatures> {
947 sigs: S,
948 _proof_state: PhantomData<P>,
949}
950
951impl<P: InProgressProofs, S: InProgressSignatures> Authorization for InProgress<P, S> {
952 type SpendProof = P::SpendProof;
953 type OutputProof = P::OutputProof;
954 type AuthSig = S::AuthSig;
955}
956
957#[cfg(feature = "circuit")]
962#[derive(Clone, Copy, Debug)]
963pub struct Unproven;
964
965#[cfg(feature = "circuit")]
966impl InProgressProofs for Unproven {
967 type SpendProof = circuit::Spend;
968 type OutputProof = circuit::Output;
969}
970
971#[derive(Clone, Copy, Debug)]
973pub struct Proven;
974
975impl InProgressProofs for Proven {
976 type SpendProof = GrothProofBytes;
977 type OutputProof = GrothProofBytes;
978}
979
980pub trait ProverProgress {
982 fn update(&mut self, cur: u32, end: u32);
985}
986
987impl ProverProgress for () {
988 fn update(&mut self, _: u32, _: u32) {}
989}
990
991#[cfg(feature = "std")]
992impl<U: From<(u32, u32)>> ProverProgress for std::sync::mpsc::Sender<U> {
993 fn update(&mut self, cur: u32, end: u32) {
994 self.send(U::from((cur, end))).unwrap_or(());
996 }
997}
998
999impl<U: ProverProgress> ProverProgress for &mut U {
1000 fn update(&mut self, cur: u32, end: u32) {
1001 (*self).update(cur, end);
1002 }
1003}
1004
1005#[cfg(feature = "circuit")]
1006struct CreateProofs<'a, SP: SpendProver, OP: OutputProver, R: RngCore, U: ProverProgress> {
1007 spend_prover: &'a SP,
1008 output_prover: &'a OP,
1009 rng: R,
1010 progress_notifier: U,
1011 total_progress: u32,
1012 progress: u32,
1013}
1014
1015#[cfg(feature = "circuit")]
1016impl<'a, SP: SpendProver, OP: OutputProver, R: RngCore, U: ProverProgress>
1017 CreateProofs<'a, SP, OP, R, U>
1018{
1019 fn new(
1020 spend_prover: &'a SP,
1021 output_prover: &'a OP,
1022 rng: R,
1023 progress_notifier: U,
1024 total_progress: u32,
1025 ) -> Self {
1026 Self {
1028 spend_prover,
1029 output_prover,
1030 rng,
1031 progress_notifier,
1032 total_progress,
1033 progress: 0u32,
1034 }
1035 }
1036
1037 fn update_progress(&mut self) {
1038 self.progress += 1;
1040 self.progress_notifier
1041 .update(self.progress, self.total_progress);
1042 }
1043
1044 fn map_spend_proof(&mut self, spend: circuit::Spend) -> GrothProofBytes {
1045 let proof = self.spend_prover.create_proof(spend, &mut self.rng);
1046 self.update_progress();
1047 SP::encode_proof(proof)
1048 }
1049
1050 fn map_output_proof(&mut self, output: circuit::Output) -> GrothProofBytes {
1051 let proof = self.output_prover.create_proof(output, &mut self.rng);
1052 self.update_progress();
1053 OP::encode_proof(proof)
1054 }
1055
1056 fn map_authorization<S: InProgressSignatures>(
1057 &mut self,
1058 a: InProgress<Unproven, S>,
1059 ) -> InProgress<Proven, S> {
1060 InProgress {
1061 sigs: a.sigs,
1062 _proof_state: PhantomData,
1063 }
1064 }
1065}
1066
1067#[cfg(feature = "circuit")]
1068impl<S: InProgressSignatures, V> Bundle<InProgress<Unproven, S>, V> {
1069 pub fn create_proofs<SP: SpendProver, OP: OutputProver>(
1071 self,
1072 spend_prover: &SP,
1073 output_prover: &OP,
1074 rng: impl RngCore,
1075 progress_notifier: impl ProverProgress,
1076 ) -> Bundle<InProgress<Proven, S>, V> {
1077 let total_progress =
1078 self.shielded_spends().len() as u32 + self.shielded_outputs().len() as u32;
1079 let mut cp = CreateProofs::new(
1080 spend_prover,
1081 output_prover,
1082 rng,
1083 progress_notifier,
1084 total_progress,
1085 );
1086
1087 self.map_authorization(
1088 &mut cp,
1089 |cp, spend| cp.map_spend_proof(spend),
1090 |cp, output| cp.map_output_proof(output),
1091 |_cp, sig| sig,
1092 |cp, auth| cp.map_authorization(auth),
1093 )
1094 }
1095}
1096
1097#[derive(Clone)]
1099pub struct Unsigned {
1100 bsk: redjubjub::SigningKey<Binding>,
1101}
1102
1103impl fmt::Debug for Unsigned {
1104 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1105 f.debug_struct("Unsigned").finish_non_exhaustive()
1106 }
1107}
1108
1109impl InProgressSignatures for Unsigned {
1110 type AuthSig = SigningMetadata;
1111}
1112
1113#[derive(Clone, Debug)]
1115pub struct SigningParts {
1116 ak: SpendValidatingKey,
1119 alpha: jubjub::Scalar,
1121}
1122
1123#[derive(Clone, Debug)]
1125pub struct PartiallyAuthorized {
1126 binding_signature: redjubjub::Signature<Binding>,
1127 sighash: [u8; 32],
1128}
1129
1130#[derive(Clone, Debug)]
1132pub struct SigningMetadata {
1133 dummy_ask: Option<SpendAuthorizingKey>,
1139 parts: SigningParts,
1140}
1141
1142impl InProgressSignatures for PartiallyAuthorized {
1143 type AuthSig = MaybeSigned;
1144}
1145
1146#[derive(Clone, Debug)]
1150pub enum MaybeSigned {
1151 SigningParts(SigningParts),
1153 Signature(redjubjub::Signature<SpendAuth>),
1155}
1156
1157impl MaybeSigned {
1158 fn finalize(self) -> Result<redjubjub::Signature<SpendAuth>, Error> {
1159 match self {
1160 Self::Signature(sig) => Ok(sig),
1161 _ => Err(Error::MissingSignatures),
1162 }
1163 }
1164}
1165
1166impl<P: InProgressProofs, V> Bundle<InProgress<P, Unsigned>, V> {
1167 pub fn prepare<R: RngCore + CryptoRng>(
1171 self,
1172 mut rng: R,
1173 sighash: [u8; 32],
1174 ) -> Bundle<InProgress<P, PartiallyAuthorized>, V> {
1175 self.map_authorization(
1176 &mut rng,
1177 |_, proof| proof,
1178 |_, proof| proof,
1179 |rng, SigningMetadata { dummy_ask, parts }| match dummy_ask {
1180 None => MaybeSigned::SigningParts(parts),
1181 Some(ask) => {
1182 MaybeSigned::Signature(ask.randomize(&parts.alpha).sign(rng, &sighash))
1183 }
1184 },
1185 |rng, auth: InProgress<P, Unsigned>| InProgress {
1186 sigs: PartiallyAuthorized {
1187 binding_signature: auth.sigs.bsk.sign(rng, &sighash),
1188 sighash,
1189 },
1190 _proof_state: PhantomData,
1191 },
1192 )
1193 }
1194}
1195
1196impl<V> Bundle<InProgress<Proven, Unsigned>, V> {
1197 pub fn apply_signatures<R: RngCore + CryptoRng>(
1202 self,
1203 mut rng: R,
1204 sighash: [u8; 32],
1205 signing_keys: &[SpendAuthorizingKey],
1206 ) -> Result<Bundle<Authorized, V>, Error> {
1207 signing_keys
1208 .iter()
1209 .fold(self.prepare(&mut rng, sighash), |partial, ask| {
1210 partial.sign(&mut rng, ask)
1211 })
1212 .finalize()
1213 }
1214}
1215
1216impl<P: InProgressProofs, V> Bundle<InProgress<P, PartiallyAuthorized>, V> {
1217 pub fn sign<R: RngCore + CryptoRng>(self, mut rng: R, ask: &SpendAuthorizingKey) -> Self {
1221 let expected_ak = ask.into();
1222 let sighash = self.authorization().sigs.sighash;
1223 self.map_authorization(
1224 &mut rng,
1225 |_, proof| proof,
1226 |_, proof| proof,
1227 |rng, maybe| match maybe {
1228 MaybeSigned::SigningParts(parts) if parts.ak == expected_ak => {
1229 MaybeSigned::Signature(ask.randomize(&parts.alpha).sign(rng, &sighash))
1230 }
1231 s => s,
1232 },
1233 |_, partial| partial,
1234 )
1235 }
1236
1237 pub fn append_signatures(
1243 self,
1244 signatures: &[redjubjub::Signature<SpendAuth>],
1245 ) -> Result<Self, Error> {
1246 signatures.iter().try_fold(self, Self::append_signature)
1247 }
1248
1249 fn append_signature(self, signature: &redjubjub::Signature<SpendAuth>) -> Result<Self, Error> {
1250 let sighash = self.authorization().sigs.sighash;
1251 let mut signature_valid_for = 0usize;
1252 let bundle = self.map_authorization(
1253 &mut signature_valid_for,
1254 |_, proof| proof,
1255 |_, proof| proof,
1256 |ctx, maybe| match maybe {
1257 MaybeSigned::SigningParts(parts) => {
1258 let rk = parts.ak.randomize(&parts.alpha);
1259 if rk.verify(&sighash, signature).is_ok() {
1260 **ctx += 1;
1261 MaybeSigned::Signature(*signature)
1262 } else {
1263 MaybeSigned::SigningParts(parts)
1265 }
1266 }
1267 s => s,
1268 },
1269 |_, partial| partial,
1270 );
1271 match signature_valid_for {
1272 0 => Err(Error::InvalidExternalSignature),
1273 1 => Ok(bundle),
1274 _ => Err(Error::DuplicateSignature),
1275 }
1276 }
1277}
1278
1279impl<V> Bundle<InProgress<Proven, PartiallyAuthorized>, V> {
1280 pub fn finalize(self) -> Result<Bundle<Authorized, V>, Error> {
1284 self.try_map_authorization(
1285 (),
1286 |_, v| Ok(v),
1287 |_, v| Ok(v),
1288 |_, maybe: MaybeSigned| maybe.finalize(),
1289 |_, partial: InProgress<Proven, PartiallyAuthorized>| {
1290 Ok(Authorized {
1291 binding_sig: partial.sigs.binding_signature,
1292 })
1293 },
1294 )
1295 }
1296}
1297
1298#[cfg(all(feature = "circuit", any(test, feature = "test-dependencies")))]
1299pub(crate) mod testing {
1300 use core::fmt;
1301
1302 use proptest::collection::vec;
1303 use proptest::prelude::*;
1304 use rand::{rngs::StdRng, SeedableRng};
1305
1306 use crate::{
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 Anchor, Node,
1313 };
1314 use incrementalmerkletree::{
1315 frontier::testing::arb_commitment_tree, witness::IncrementalWitness,
1316 };
1317
1318 use super::{Builder, BundleType};
1319
1320 #[cfg(feature = "circuit")]
1321 use crate::prover::mock::{MockOutputProver, MockSpendProver};
1322
1323 #[allow(dead_code)]
1324 #[cfg(feature = "circuit")]
1325 fn arb_bundle<V: fmt::Debug + From<i64>>(
1326 max_money: u64,
1327 zip212_enforcement: Zip212Enforcement,
1328 ) -> impl Strategy<Value = Bundle<Authorized, V>> {
1329 (1..30usize)
1330 .prop_flat_map(move |n_notes| {
1331 (
1332 arb_extended_spending_key(),
1333 vec(
1334 arb_positive_note_value(max_money / 10000).prop_flat_map(arb_note),
1335 n_notes,
1336 ),
1337 vec(
1338 arb_commitment_tree::<_, _, 32>(n_notes, arb_node()).prop_map(|t| {
1339 IncrementalWitness::from_tree(t)
1340 .expect("valid encoding of an incremental witness")
1341 .path()
1342 .unwrap()
1343 }),
1344 n_notes,
1345 ),
1346 prop::array::uniform32(any::<u8>()),
1347 prop::array::uniform32(any::<u8>()),
1348 )
1349 })
1350 .prop_map(
1351 move |(extsk, spendable_notes, commitment_trees, rng_seed, fake_sighash_bytes)| {
1352 let dfvk = extsk.to_diversifiable_full_viewing_key();
1353 let anchor = spendable_notes
1354 .first()
1355 .zip(commitment_trees.first())
1356 .map_or_else(Anchor::empty_tree, |(note, tree)| {
1357 let node = Node::from_cmu(¬e.cmu());
1358 Anchor::from(*tree.root(node).inner())
1359 });
1360 let mut builder = Builder::new(zip212_enforcement, BundleType::DEFAULT, anchor);
1361 let mut rng = StdRng::from_seed(rng_seed);
1362
1363 for (note, path) in spendable_notes
1364 .into_iter()
1365 .zip(commitment_trees.into_iter())
1366 {
1367 builder.add_spend(dfvk.fvk().clone(), note, path).unwrap();
1368 }
1369
1370 let (bundle, _) = builder
1371 .build::<MockSpendProver, MockOutputProver, _, _>(
1372 core::slice::from_ref(&extsk),
1373 &mut rng,
1374 )
1375 .unwrap()
1376 .unwrap();
1377
1378 let bundle =
1379 bundle.create_proofs(&MockSpendProver, &MockOutputProver, &mut rng, ());
1380
1381 bundle
1382 .apply_signatures(&mut rng, fake_sighash_bytes, &[extsk.expsk.ask])
1383 .unwrap()
1384 },
1385 )
1386 }
1387}