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