1use alloc::collections::BTreeMap;
4use alloc::vec::Vec;
5use core::fmt;
6use core::iter;
7
8use ff::Field;
9use pasta_curves::pallas;
10use rand::{prelude::SliceRandom, CryptoRng, RngCore};
11
12use crate::{
13 address::Address,
14 bundle::{Authorization, Authorized, Bundle, Flags},
15 keys::{
16 FullViewingKey, OutgoingViewingKey, Scope, SpendAuthorizingKey, SpendValidatingKey,
17 SpendingKey,
18 },
19 note::{ExtractedNoteCommitment, Note, Nullifier, Rho, TransmittedNoteCiphertext},
20 note_encryption::OrchardNoteEncryption,
21 primitives::redpallas::{self, Binding, SpendAuth},
22 tree::{Anchor, MerklePath},
23 value::{self, BalanceError, NoteValue, ValueCommitTrapdoor, ValueCommitment, ValueSum},
24 Proof,
25};
26
27#[cfg(feature = "circuit")]
28use {
29 crate::{
30 action::Action,
31 circuit::{Circuit, Instance, ProvingKey},
32 },
33 nonempty::NonEmpty,
34};
35
36const MIN_ACTIONS: usize = 2;
37
38#[derive(Clone, Copy, Debug, PartialEq, Eq)]
40pub enum BundleType {
41 Transactional {
44 flags: Flags,
46 bundle_required: bool,
50 },
51 Coinbase,
53}
54
55impl BundleType {
56 pub const DEFAULT: BundleType = BundleType::Transactional {
59 flags: Flags::ENABLED,
60 bundle_required: false,
61 };
62
63 pub const DISABLED: BundleType = BundleType::Transactional {
66 flags: Flags::from_parts(false, false),
67 bundle_required: false,
68 };
69
70 pub fn num_actions(
76 &self,
77 num_spends: usize,
78 num_outputs: usize,
79 ) -> Result<usize, &'static str> {
80 let num_requested_actions = core::cmp::max(num_spends, num_outputs);
81
82 match self {
83 BundleType::Transactional {
84 flags,
85 bundle_required,
86 } => {
87 if !flags.spends_enabled() && num_spends > 0 {
88 Err("Spends are disabled, so num_spends must be zero")
89 } else if !flags.outputs_enabled() && num_outputs > 0 {
90 Err("Outputs are disabled, so num_outputs must be zero")
91 } else {
92 Ok(if *bundle_required || num_requested_actions > 0 {
93 core::cmp::max(num_requested_actions, MIN_ACTIONS)
94 } else {
95 0
96 })
97 }
98 }
99 BundleType::Coinbase => {
100 if num_spends > 0 {
101 Err("Coinbase bundles have spends disabled, so num_spends must be zero")
102 } else {
103 Ok(num_outputs)
104 }
105 }
106 }
107 }
108
109 pub fn flags(&self) -> Flags {
111 match self {
112 BundleType::Transactional { flags, .. } => *flags,
113 BundleType::Coinbase => Flags::SPENDS_DISABLED,
114 }
115 }
116}
117
118#[derive(Debug)]
120#[non_exhaustive]
121pub enum BuildError {
122 SpendsDisabled,
124 OutputsDisabled,
126 AnchorMismatch,
128 MissingSignatures,
130 #[cfg(feature = "circuit")]
132 Proof(halo2_proofs::plonk::Error),
133 ValueSum(value::BalanceError),
136 InvalidExternalSignature,
138 DuplicateSignature,
141 BundleTypeNotSatisfiable,
143}
144
145impl fmt::Display for BuildError {
146 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
147 use BuildError::*;
148 match self {
149 MissingSignatures => f.write_str("Required signatures were missing during build"),
150 #[cfg(feature = "circuit")]
151 Proof(e) => f.write_str(&format!("Could not create proof: {}", e)),
152 ValueSum(_) => f.write_str("Overflow occurred during value construction"),
153 InvalidExternalSignature => f.write_str("External signature was invalid"),
154 DuplicateSignature => f.write_str("Signature valid for more than one input"),
155 BundleTypeNotSatisfiable => {
156 f.write_str("Bundle structure did not conform to requested bundle type.")
157 }
158 SpendsDisabled => f.write_str("Spends are not enabled for the requested bundle type."),
159 OutputsDisabled => f.write_str("Spends are not enabled for the requested bundle type."),
160 AnchorMismatch => {
161 f.write_str("All spends must share the anchor requested for the transaction.")
162 }
163 }
164 }
165}
166
167#[cfg(feature = "std")]
168impl std::error::Error for BuildError {}
169
170#[cfg(feature = "circuit")]
171impl From<halo2_proofs::plonk::Error> for BuildError {
172 fn from(e: halo2_proofs::plonk::Error) -> Self {
173 BuildError::Proof(e)
174 }
175}
176
177impl From<value::BalanceError> for BuildError {
178 fn from(e: value::BalanceError) -> Self {
179 BuildError::ValueSum(e)
180 }
181}
182
183#[derive(Debug, PartialEq, Eq)]
185#[non_exhaustive]
186pub enum SpendError {
187 SpendsDisabled,
189 AnchorMismatch,
191 FvkMismatch,
193}
194
195impl fmt::Display for SpendError {
196 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
197 use SpendError::*;
198 f.write_str(match self {
199 SpendsDisabled => "Spends are not enabled for this builder",
200 AnchorMismatch => "All anchors must be equal.",
201 FvkMismatch => "FullViewingKey does not correspond to the given note",
202 })
203 }
204}
205
206#[cfg(feature = "std")]
207impl std::error::Error for SpendError {}
208
209#[derive(Debug, PartialEq, Eq)]
211#[non_exhaustive]
212pub enum OutputError {
213 OutputsDisabled,
215}
216
217impl fmt::Display for OutputError {
218 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
219 use OutputError::*;
220 f.write_str(match self {
221 OutputsDisabled => "Outputs are not enabled for this builder",
222 })
223 }
224}
225
226#[cfg(feature = "std")]
227impl std::error::Error for OutputError {}
228
229#[derive(Debug)]
231pub struct SpendInfo {
232 pub(crate) dummy_sk: Option<SpendingKey>,
233 pub(crate) fvk: FullViewingKey,
234 pub(crate) scope: Scope,
235 pub(crate) note: Note,
236 pub(crate) merkle_path: MerklePath,
237}
238
239impl SpendInfo {
240 pub fn new(fvk: FullViewingKey, note: Note, merkle_path: MerklePath) -> Option<Self> {
250 let scope = fvk.scope_for_address(¬e.recipient())?;
251 Some(SpendInfo {
252 dummy_sk: None,
253 fvk,
254 scope,
255 note,
256 merkle_path,
257 })
258 }
259
260 fn dummy(rng: &mut impl RngCore) -> Self {
264 let (sk, fvk, note) = Note::dummy(rng, None);
265 let merkle_path = MerklePath::dummy(rng);
266
267 SpendInfo {
268 dummy_sk: Some(sk),
269 fvk,
270 scope: Scope::External,
273 note,
274 merkle_path,
275 }
276 }
277
278 fn has_matching_anchor(&self, anchor: &Anchor) -> bool {
279 if self.note.value() == NoteValue::zero() {
280 true
281 } else {
282 let cm = self.note.commitment();
283 let path_root = self.merkle_path.root(cm.into());
284 &path_root == anchor
285 }
286 }
287
288 fn build(
294 &self,
295 mut rng: impl RngCore,
296 ) -> (
297 Nullifier,
298 SpendValidatingKey,
299 pallas::Scalar,
300 redpallas::VerificationKey<SpendAuth>,
301 ) {
302 let nf_old = self.note.nullifier(&self.fvk);
303 let ak: SpendValidatingKey = self.fvk.clone().into();
304 let alpha = pallas::Scalar::random(&mut rng);
305 let rk = ak.randomize(&alpha);
306
307 (nf_old, ak, alpha, rk)
308 }
309
310 fn into_pczt(self, rng: impl RngCore) -> crate::pczt::Spend {
311 let (nf_old, _, alpha, rk) = self.build(rng);
312
313 crate::pczt::Spend {
314 nullifier: nf_old,
315 rk,
316 spend_auth_sig: None,
317 recipient: Some(self.note.recipient()),
318 value: Some(self.note.value()),
319 rho: Some(self.note.rho()),
320 rseed: Some(*self.note.rseed()),
321 fvk: Some(self.fvk),
322 witness: Some(self.merkle_path),
323 alpha: Some(alpha),
324 zip32_derivation: None,
325 dummy_sk: self.dummy_sk,
326 proprietary: BTreeMap::new(),
327 }
328 }
329}
330
331#[derive(Debug)]
333pub struct OutputInfo {
334 ovk: Option<OutgoingViewingKey>,
335 recipient: Address,
336 value: NoteValue,
337 memo: [u8; 512],
338}
339
340impl OutputInfo {
341 pub fn new(
343 ovk: Option<OutgoingViewingKey>,
344 recipient: Address,
345 value: NoteValue,
346 memo: [u8; 512],
347 ) -> Self {
348 Self {
349 ovk,
350 recipient,
351 value,
352 memo,
353 }
354 }
355
356 pub fn dummy(rng: &mut impl RngCore) -> Self {
360 let fvk: FullViewingKey = (&SpendingKey::random(rng)).into();
361 let recipient = fvk.address_at(0u32, Scope::External);
362
363 Self::new(None, recipient, NoteValue::zero(), [0u8; 512])
364 }
365
366 fn build(
372 &self,
373 cv_net: &ValueCommitment,
374 nf_old: Nullifier,
375 mut rng: impl RngCore,
376 ) -> (Note, ExtractedNoteCommitment, TransmittedNoteCiphertext) {
377 let rho = Rho::from_nf_old(nf_old);
378 let note = Note::new(self.recipient, self.value, rho, &mut rng);
379 let cm_new = note.commitment();
380 let cmx = cm_new.into();
381
382 let encryptor = OrchardNoteEncryption::new(self.ovk.clone(), note, self.memo);
383
384 let encrypted_note = TransmittedNoteCiphertext {
385 epk_bytes: encryptor.epk().to_bytes().0,
386 enc_ciphertext: encryptor.encrypt_note_plaintext(),
387 out_ciphertext: encryptor.encrypt_outgoing_plaintext(cv_net, &cmx, &mut rng),
388 };
389
390 (note, cmx, encrypted_note)
391 }
392
393 fn into_pczt(
394 self,
395 cv_net: &ValueCommitment,
396 nf_old: Nullifier,
397 rng: impl RngCore,
398 ) -> crate::pczt::Output {
399 let (note, cmx, encrypted_note) = self.build(cv_net, nf_old, rng);
400
401 crate::pczt::Output {
402 cmx,
403 encrypted_note,
404 recipient: Some(self.recipient),
405 value: Some(self.value),
406 rseed: Some(*note.rseed()),
407 ock: None,
410 zip32_derivation: None,
411 user_address: None,
412 proprietary: BTreeMap::new(),
413 }
414 }
415}
416
417#[derive(Debug)]
419struct ActionInfo {
420 spend: SpendInfo,
421 output: OutputInfo,
422 rcv: ValueCommitTrapdoor,
423}
424
425impl ActionInfo {
426 fn new(spend: SpendInfo, output: OutputInfo, rng: impl RngCore) -> Self {
427 ActionInfo {
428 spend,
429 output,
430 rcv: ValueCommitTrapdoor::random(rng),
431 }
432 }
433
434 fn value_sum(&self) -> ValueSum {
436 self.spend.note.value() - self.output.value
437 }
438
439 #[cfg(feature = "circuit")]
445 fn build(self, mut rng: impl RngCore) -> (Action<SigningMetadata>, Circuit) {
446 let v_net = self.value_sum();
447 let cv_net = ValueCommitment::derive(v_net, self.rcv.clone());
448
449 let (nf_old, ak, alpha, rk) = self.spend.build(&mut rng);
450 let (note, cmx, encrypted_note) = self.output.build(&cv_net, nf_old, &mut rng);
451
452 (
453 Action::from_parts(
454 nf_old,
455 rk,
456 cmx,
457 encrypted_note,
458 cv_net,
459 SigningMetadata {
460 dummy_ask: self.spend.dummy_sk.as_ref().map(SpendAuthorizingKey::from),
461 parts: SigningParts { ak, alpha },
462 },
463 ),
464 Circuit::from_action_context_unchecked(self.spend, note, alpha, self.rcv),
465 )
466 }
467
468 fn build_for_pczt(self, mut rng: impl RngCore) -> crate::pczt::Action {
469 let v_net = self.value_sum();
470 let cv_net = ValueCommitment::derive(v_net, self.rcv.clone());
471
472 let spend = self.spend.into_pczt(&mut rng);
473 let output = self.output.into_pczt(&cv_net, spend.nullifier, &mut rng);
474
475 crate::pczt::Action {
476 cv_net,
477 spend,
478 output,
479 rcv: Some(self.rcv),
480 }
481 }
482}
483
484#[cfg(feature = "circuit")]
488pub type UnauthorizedBundle<V> = Bundle<InProgress<Unproven, Unauthorized>, V>;
489
490#[derive(Debug, Clone, PartialEq, Eq)]
497pub struct BundleMetadata {
498 spend_indices: Vec<usize>,
499 output_indices: Vec<usize>,
500}
501
502impl BundleMetadata {
503 fn new(num_requested_spends: usize, num_requested_outputs: usize) -> Self {
504 BundleMetadata {
505 spend_indices: vec![0; num_requested_spends],
506 output_indices: vec![0; num_requested_outputs],
507 }
508 }
509
510 pub fn empty() -> Self {
512 Self::new(0, 0)
513 }
514
515 pub fn spend_action_index(&self, n: usize) -> Option<usize> {
524 self.spend_indices.get(n).copied()
525 }
526
527 pub fn output_action_index(&self, n: usize) -> Option<usize> {
536 self.output_indices.get(n).copied()
537 }
538}
539
540#[derive(Debug)]
543pub struct Builder {
544 spends: Vec<SpendInfo>,
545 outputs: Vec<OutputInfo>,
546 bundle_type: BundleType,
547 anchor: Anchor,
548}
549
550impl Builder {
551 pub fn new(bundle_type: BundleType, anchor: Anchor) -> Self {
553 Builder {
554 spends: vec![],
555 outputs: vec![],
556 bundle_type,
557 anchor,
558 }
559 }
560
561 pub fn add_spend(
574 &mut self,
575 fvk: FullViewingKey,
576 note: Note,
577 merkle_path: MerklePath,
578 ) -> Result<(), SpendError> {
579 let flags = self.bundle_type.flags();
580 if !flags.spends_enabled() {
581 return Err(SpendError::SpendsDisabled);
582 }
583
584 let spend = SpendInfo::new(fvk, note, merkle_path).ok_or(SpendError::FvkMismatch)?;
585
586 if !spend.has_matching_anchor(&self.anchor) {
588 return Err(SpendError::AnchorMismatch);
589 }
590
591 self.spends.push(spend);
592
593 Ok(())
594 }
595
596 pub fn add_output(
598 &mut self,
599 ovk: Option<OutgoingViewingKey>,
600 recipient: Address,
601 value: NoteValue,
602 memo: [u8; 512],
603 ) -> Result<(), OutputError> {
604 let flags = self.bundle_type.flags();
605 if !flags.outputs_enabled() {
606 return Err(OutputError::OutputsDisabled);
607 }
608
609 self.outputs
610 .push(OutputInfo::new(ovk, recipient, value, memo));
611
612 Ok(())
613 }
614
615 pub fn spends(&self) -> &Vec<impl InputView<()>> {
618 &self.spends
619 }
620
621 pub fn outputs(&self) -> &Vec<impl OutputView> {
624 &self.outputs
625 }
626
627 pub fn value_balance<V: TryFrom<i64>>(&self) -> Result<V, value::BalanceError> {
638 let value_balance = self
639 .spends
640 .iter()
641 .map(|spend| spend.note.value() - NoteValue::zero())
642 .chain(
643 self.outputs
644 .iter()
645 .map(|output| NoteValue::zero() - output.value),
646 )
647 .try_fold(ValueSum::zero(), |acc, note_value| acc + note_value)
648 .ok_or(BalanceError::Overflow)?;
649 i64::try_from(value_balance)
650 .and_then(|i| V::try_from(i).map_err(|_| value::BalanceError::Overflow))
651 }
652
653 #[cfg(feature = "circuit")]
658 pub fn build<V: TryFrom<i64>>(
659 self,
660 rng: impl RngCore,
661 ) -> Result<Option<(UnauthorizedBundle<V>, BundleMetadata)>, BuildError> {
662 bundle(
663 rng,
664 self.anchor,
665 self.bundle_type,
666 self.spends,
667 self.outputs,
668 )
669 }
670
671 pub fn build_for_pczt(
674 self,
675 rng: impl RngCore,
676 ) -> Result<(crate::pczt::Bundle, BundleMetadata), BuildError> {
677 build_bundle(
678 rng,
679 self.anchor,
680 self.bundle_type,
681 self.spends,
682 self.outputs,
683 |pre_actions, flags, value_sum, bundle_meta, mut rng| {
684 let actions = pre_actions
686 .into_iter()
687 .map(|a| a.build_for_pczt(&mut rng))
688 .collect::<Vec<_>>();
689
690 Ok((
691 crate::pczt::Bundle {
692 actions,
693 flags,
694 value_sum,
695 anchor: self.anchor,
696 zkproof: None,
697 bsk: None,
698 },
699 bundle_meta,
700 ))
701 },
702 )
703 }
704}
705
706#[cfg(feature = "circuit")]
711pub fn bundle<V: TryFrom<i64>>(
712 rng: impl RngCore,
713 anchor: Anchor,
714 bundle_type: BundleType,
715 spends: Vec<SpendInfo>,
716 outputs: Vec<OutputInfo>,
717) -> Result<Option<(UnauthorizedBundle<V>, BundleMetadata)>, BuildError> {
718 build_bundle(
719 rng,
720 anchor,
721 bundle_type,
722 spends,
723 outputs,
724 |pre_actions, flags, value_balance, bundle_meta, mut rng| {
725 let result_value_balance: V = i64::try_from(value_balance)
726 .map_err(BuildError::ValueSum)
727 .and_then(|i| {
728 V::try_from(i).map_err(|_| BuildError::ValueSum(value::BalanceError::Overflow))
729 })?;
730
731 let bsk = pre_actions
733 .iter()
734 .map(|a| &a.rcv)
735 .sum::<ValueCommitTrapdoor>()
736 .into_bsk();
737
738 let (actions, circuits): (Vec<_>, Vec<_>) =
740 pre_actions.into_iter().map(|a| a.build(&mut rng)).unzip();
741
742 let bvk = (actions.iter().map(|a| a.cv_net()).sum::<ValueCommitment>()
744 - ValueCommitment::derive(value_balance, ValueCommitTrapdoor::zero()))
745 .into_bvk();
746 assert_eq!(redpallas::VerificationKey::from(&bsk), bvk);
747
748 Ok(NonEmpty::from_vec(actions).map(|actions| {
749 (
750 Bundle::from_parts(
751 actions,
752 flags,
753 result_value_balance,
754 anchor,
755 InProgress {
756 proof: Unproven { circuits },
757 sigs: Unauthorized { bsk },
758 },
759 ),
760 bundle_meta,
761 )
762 }))
763 },
764 )
765}
766
767fn build_bundle<B, R: RngCore>(
768 mut rng: R,
769 anchor: Anchor,
770 bundle_type: BundleType,
771 spends: Vec<SpendInfo>,
772 outputs: Vec<OutputInfo>,
773 finisher: impl FnOnce(Vec<ActionInfo>, Flags, ValueSum, BundleMetadata, R) -> Result<B, BuildError>,
774) -> Result<B, BuildError> {
775 let flags = bundle_type.flags();
776
777 let num_requested_spends = spends.len();
778 if !flags.spends_enabled() && num_requested_spends > 0 {
779 return Err(BuildError::SpendsDisabled);
780 }
781
782 for spend in &spends {
783 if !spend.has_matching_anchor(&anchor) {
784 return Err(BuildError::AnchorMismatch);
785 }
786 }
787
788 let num_requested_outputs = outputs.len();
789 if !flags.outputs_enabled() && num_requested_outputs > 0 {
790 return Err(BuildError::OutputsDisabled);
791 }
792
793 let num_actions = bundle_type
794 .num_actions(num_requested_spends, num_requested_outputs)
795 .map_err(|_| BuildError::BundleTypeNotSatisfiable)?;
796
797 let (pre_actions, bundle_meta) = {
799 let mut indexed_spends = spends
800 .into_iter()
801 .chain(iter::repeat_with(|| SpendInfo::dummy(&mut rng)))
802 .enumerate()
803 .take(num_actions)
804 .collect::<Vec<_>>();
805
806 let mut indexed_outputs = outputs
807 .into_iter()
808 .chain(iter::repeat_with(|| OutputInfo::dummy(&mut rng)))
809 .enumerate()
810 .take(num_actions)
811 .collect::<Vec<_>>();
812
813 indexed_spends.shuffle(&mut rng);
817 indexed_outputs.shuffle(&mut rng);
818
819 let mut bundle_meta = BundleMetadata::new(num_requested_spends, num_requested_outputs);
820 let pre_actions = indexed_spends
821 .into_iter()
822 .zip(indexed_outputs)
823 .enumerate()
824 .map(|(action_idx, ((spend_idx, spend), (out_idx, output)))| {
825 if spend_idx < num_requested_spends {
827 bundle_meta.spend_indices[spend_idx] = action_idx;
828 }
829
830 if out_idx < num_requested_outputs {
832 bundle_meta.output_indices[out_idx] = action_idx;
833 }
834
835 ActionInfo::new(spend, output, &mut rng)
836 })
837 .collect::<Vec<_>>();
838
839 (pre_actions, bundle_meta)
840 };
841
842 let value_balance = pre_actions
844 .iter()
845 .try_fold(ValueSum::zero(), |acc, action| acc + action.value_sum())
846 .ok_or(BalanceError::Overflow)?;
847
848 finisher(pre_actions, flags, value_balance, bundle_meta, rng)
849}
850
851pub trait InProgressSignatures: fmt::Debug {
853 type SpendAuth: fmt::Debug;
855}
856
857#[derive(Clone, Debug)]
859pub struct InProgress<P, S: InProgressSignatures> {
860 proof: P,
861 sigs: S,
862}
863
864impl<P: fmt::Debug, S: InProgressSignatures> Authorization for InProgress<P, S> {
865 type SpendAuth = S::SpendAuth;
866}
867
868#[cfg(feature = "circuit")]
872#[derive(Clone, Debug)]
873pub struct Unproven {
874 circuits: Vec<Circuit>,
875}
876
877#[cfg(feature = "circuit")]
878impl<S: InProgressSignatures> InProgress<Unproven, S> {
879 pub fn create_proof(
881 &self,
882 pk: &ProvingKey,
883 instances: &[Instance],
884 rng: impl RngCore,
885 ) -> Result<Proof, halo2_proofs::plonk::Error> {
886 Proof::create(pk, &self.proof.circuits, instances, rng)
887 }
888}
889
890#[cfg(feature = "circuit")]
891impl<S: InProgressSignatures, V> Bundle<InProgress<Unproven, S>, V> {
892 pub fn create_proof(
894 self,
895 pk: &ProvingKey,
896 mut rng: impl RngCore,
897 ) -> Result<Bundle<InProgress<Proof, S>, V>, BuildError> {
898 let instances: Vec<_> = self
899 .actions()
900 .iter()
901 .map(|a| a.to_instance(*self.flags(), *self.anchor()))
902 .collect();
903 self.try_map_authorization(
904 &mut (),
905 |_, _, a| Ok(a),
906 |_, auth| {
907 let proof = auth.create_proof(pk, &instances, &mut rng)?;
908 Ok(InProgress {
909 proof,
910 sigs: auth.sigs,
911 })
912 },
913 )
914 }
915}
916
917#[derive(Clone, Debug)]
919pub struct SigningParts {
920 ak: SpendValidatingKey,
923 alpha: pallas::Scalar,
925}
926
927#[derive(Clone, Debug)]
929pub struct Unauthorized {
930 bsk: redpallas::SigningKey<Binding>,
931}
932
933impl InProgressSignatures for Unauthorized {
934 type SpendAuth = SigningMetadata;
935}
936
937#[derive(Clone, Debug)]
939pub struct SigningMetadata {
940 dummy_ask: Option<SpendAuthorizingKey>,
946 parts: SigningParts,
947}
948
949#[derive(Debug)]
951pub struct PartiallyAuthorized {
952 binding_signature: redpallas::Signature<Binding>,
953 sighash: [u8; 32],
954}
955
956impl InProgressSignatures for PartiallyAuthorized {
957 type SpendAuth = MaybeSigned;
958}
959
960#[derive(Debug)]
964pub enum MaybeSigned {
965 SigningMetadata(SigningParts),
967 Signature(redpallas::Signature<SpendAuth>),
969}
970
971impl MaybeSigned {
972 fn finalize(self) -> Result<redpallas::Signature<SpendAuth>, BuildError> {
973 match self {
974 Self::Signature(sig) => Ok(sig),
975 _ => Err(BuildError::MissingSignatures),
976 }
977 }
978}
979
980impl<P: fmt::Debug, V> Bundle<InProgress<P, Unauthorized>, V> {
981 pub fn prepare<R: RngCore + CryptoRng>(
985 self,
986 mut rng: R,
987 sighash: [u8; 32],
988 ) -> Bundle<InProgress<P, PartiallyAuthorized>, V> {
989 self.map_authorization(
990 &mut rng,
991 |rng, _, SigningMetadata { dummy_ask, parts }| {
992 dummy_ask
994 .map(|ask| ask.randomize(&parts.alpha).sign(rng, &sighash))
995 .map(MaybeSigned::Signature)
996 .unwrap_or(MaybeSigned::SigningMetadata(parts))
997 },
998 |rng, auth| InProgress {
999 proof: auth.proof,
1000 sigs: PartiallyAuthorized {
1001 binding_signature: auth.sigs.bsk.sign(rng, &sighash),
1002 sighash,
1003 },
1004 },
1005 )
1006 }
1007}
1008
1009impl<V> Bundle<InProgress<Proof, Unauthorized>, V> {
1010 pub fn apply_signatures<R: RngCore + CryptoRng>(
1015 self,
1016 mut rng: R,
1017 sighash: [u8; 32],
1018 signing_keys: &[SpendAuthorizingKey],
1019 ) -> Result<Bundle<Authorized, V>, BuildError> {
1020 signing_keys
1021 .iter()
1022 .fold(self.prepare(&mut rng, sighash), |partial, ask| {
1023 partial.sign(&mut rng, ask)
1024 })
1025 .finalize()
1026 }
1027}
1028
1029impl<P: fmt::Debug, V> Bundle<InProgress<P, PartiallyAuthorized>, V> {
1030 pub fn sign<R: RngCore + CryptoRng>(self, mut rng: R, ask: &SpendAuthorizingKey) -> Self {
1034 let expected_ak = ask.into();
1035 self.map_authorization(
1036 &mut rng,
1037 |rng, partial, maybe| match maybe {
1038 MaybeSigned::SigningMetadata(parts) if parts.ak == expected_ak => {
1039 MaybeSigned::Signature(
1040 ask.randomize(&parts.alpha).sign(rng, &partial.sigs.sighash),
1041 )
1042 }
1043 s => s,
1044 },
1045 |_, partial| partial,
1046 )
1047 }
1048 pub fn append_signatures(
1056 self,
1057 signatures: &[redpallas::Signature<SpendAuth>],
1058 ) -> Result<Self, BuildError> {
1059 signatures.iter().try_fold(self, Self::append_signature)
1060 }
1061
1062 fn append_signature(
1063 self,
1064 signature: &redpallas::Signature<SpendAuth>,
1065 ) -> Result<Self, BuildError> {
1066 let mut signature_valid_for = 0usize;
1067 let bundle = self.map_authorization(
1068 &mut signature_valid_for,
1069 |valid_for, partial, maybe| match maybe {
1070 MaybeSigned::SigningMetadata(parts) => {
1071 let rk = parts.ak.randomize(&parts.alpha);
1072 if rk.verify(&partial.sigs.sighash[..], signature).is_ok() {
1073 *valid_for += 1;
1074 MaybeSigned::Signature(signature.clone())
1075 } else {
1076 MaybeSigned::SigningMetadata(parts)
1078 }
1079 }
1080 s => s,
1081 },
1082 |_, partial| partial,
1083 );
1084 match signature_valid_for {
1085 0 => Err(BuildError::InvalidExternalSignature),
1086 1 => Ok(bundle),
1087 _ => Err(BuildError::DuplicateSignature),
1088 }
1089 }
1090}
1091
1092impl<V> Bundle<InProgress<Proof, PartiallyAuthorized>, V> {
1093 pub fn finalize(self) -> Result<Bundle<Authorized, V>, BuildError> {
1097 self.try_map_authorization(
1098 &mut (),
1099 |_, _, maybe| maybe.finalize(),
1100 |_, partial| {
1101 Ok(Authorized::from_parts(
1102 partial.proof,
1103 partial.sigs.binding_signature,
1104 ))
1105 },
1106 )
1107 }
1108}
1109
1110pub trait InputView<NoteRef> {
1113 fn note_id(&self) -> &NoteRef;
1115 fn value<V: From<u64>>(&self) -> V;
1117}
1118
1119impl InputView<()> for SpendInfo {
1120 fn note_id(&self) -> &() {
1121 &()
1123 }
1124
1125 fn value<V: From<u64>>(&self) -> V {
1126 V::from(self.note.value().inner())
1127 }
1128}
1129
1130pub trait OutputView {
1133 fn value<V: From<u64>>(&self) -> V;
1135}
1136
1137impl OutputView for OutputInfo {
1138 fn value<V: From<u64>>(&self) -> V {
1139 V::from(self.value.inner())
1140 }
1141}
1142
1143#[cfg(all(feature = "circuit", any(test, feature = "test-dependencies")))]
1145#[cfg_attr(docsrs, doc(cfg(feature = "test-dependencies")))]
1146pub mod testing {
1147 use alloc::vec::Vec;
1148 use core::fmt::Debug;
1149
1150 use incrementalmerkletree::{frontier::Frontier, Hashable};
1151 use rand::{rngs::StdRng, CryptoRng, SeedableRng};
1152
1153 use proptest::collection::vec;
1154 use proptest::prelude::*;
1155
1156 use crate::{
1157 address::testing::arb_address,
1158 bundle::{Authorized, Bundle},
1159 circuit::ProvingKey,
1160 keys::{testing::arb_spending_key, FullViewingKey, SpendAuthorizingKey, SpendingKey},
1161 note::testing::arb_note,
1162 tree::{Anchor, MerkleHashOrchard, MerklePath},
1163 value::{testing::arb_positive_note_value, NoteValue, MAX_NOTE_VALUE},
1164 Address, Note,
1165 };
1166
1167 use super::{Builder, BundleType};
1168
1169 #[derive(Debug)]
1178 struct ArbitraryBundleInputs<R> {
1179 rng: R,
1180 sk: SpendingKey,
1181 anchor: Anchor,
1182 notes: Vec<(Note, MerklePath)>,
1183 output_amounts: Vec<(Address, NoteValue)>,
1184 }
1185
1186 impl<R: RngCore + CryptoRng> ArbitraryBundleInputs<R> {
1187 fn into_bundle<V: TryFrom<i64>>(mut self) -> Bundle<Authorized, V> {
1189 let fvk = FullViewingKey::from(&self.sk);
1190 let mut builder = Builder::new(BundleType::DEFAULT, self.anchor);
1191
1192 for (note, path) in self.notes.into_iter() {
1193 builder.add_spend(fvk.clone(), note, path).unwrap();
1194 }
1195
1196 for (addr, value) in self.output_amounts.into_iter() {
1197 let scope = fvk.scope_for_address(&addr).unwrap();
1198 let ovk = fvk.to_ovk(scope);
1199
1200 builder
1201 .add_output(Some(ovk.clone()), addr, value, [0u8; 512])
1202 .unwrap();
1203 }
1204
1205 let pk = ProvingKey::build();
1206 builder
1207 .build(&mut self.rng)
1208 .unwrap()
1209 .unwrap()
1210 .0
1211 .create_proof(&pk, &mut self.rng)
1212 .unwrap()
1213 .prepare(&mut self.rng, [0; 32])
1214 .sign(&mut self.rng, &SpendAuthorizingKey::from(&self.sk))
1215 .finalize()
1216 .unwrap()
1217 }
1218 }
1219
1220 prop_compose! {
1221 fn arb_bundle_inputs(sk: SpendingKey)
1223 (
1224 n_notes in 1usize..30,
1225 n_outputs in 1..30,
1226
1227 )
1228 (
1229 notes in vec(
1231 arb_positive_note_value(MAX_NOTE_VALUE / n_notes as u64).prop_flat_map(arb_note),
1232 n_notes
1233 ),
1234 output_amounts in vec(
1235 arb_address().prop_flat_map(move |a| {
1236 arb_positive_note_value(MAX_NOTE_VALUE / n_outputs as u64)
1237 .prop_map(move |v| (a, v))
1238 }),
1239 n_outputs as usize
1240 ),
1241 rng_seed in prop::array::uniform32(prop::num::u8::ANY)
1242 ) -> ArbitraryBundleInputs<StdRng> {
1243 use crate::constants::MERKLE_DEPTH_ORCHARD;
1244 let mut frontier = Frontier::<MerkleHashOrchard, { MERKLE_DEPTH_ORCHARD as u8 }>::empty();
1245 let mut notes_and_auth_paths: Vec<(Note, MerklePath)> = Vec::new();
1246
1247 for note in notes.iter() {
1248 let leaf = MerkleHashOrchard::from_cmx(¬e.commitment().into());
1249 frontier.append(leaf);
1250
1251 let path = frontier
1252 .witness(|addr| Some(<MerkleHashOrchard as Hashable>::empty_root(addr.level())))
1253 .ok()
1254 .flatten()
1255 .expect("we can always construct a correct Merkle path");
1256 notes_and_auth_paths.push((*note, path.into()));
1257 }
1258
1259 ArbitraryBundleInputs {
1260 rng: StdRng::from_seed(rng_seed),
1261 sk,
1262 anchor: frontier.root().into(),
1263 notes: notes_and_auth_paths,
1264 output_amounts
1265 }
1266 }
1267 }
1268
1269 pub fn arb_bundle<V: TryFrom<i64> + Debug>() -> impl Strategy<Value = Bundle<Authorized, V>> {
1271 arb_spending_key()
1272 .prop_flat_map(arb_bundle_inputs)
1273 .prop_map(|inputs| inputs.into_bundle::<V>())
1274 }
1275
1276 pub fn arb_bundle_with_key<V: TryFrom<i64> + Debug>(
1278 k: SpendingKey,
1279 ) -> impl Strategy<Value = Bundle<Authorized, V>> {
1280 arb_bundle_inputs(k).prop_map(|inputs| inputs.into_bundle::<V>())
1281 }
1282}
1283
1284#[cfg(all(test, feature = "circuit"))]
1285mod tests {
1286 use rand::rngs::OsRng;
1287
1288 use super::Builder;
1289 use crate::{
1290 builder::BundleType,
1291 bundle::{Authorized, Bundle},
1292 circuit::ProvingKey,
1293 constants::MERKLE_DEPTH_ORCHARD,
1294 keys::{FullViewingKey, Scope, SpendingKey},
1295 tree::EMPTY_ROOTS,
1296 value::NoteValue,
1297 };
1298
1299 #[test]
1300 fn shielding_bundle() {
1301 let pk = ProvingKey::build();
1302 let mut rng = OsRng;
1303
1304 let sk = SpendingKey::random(&mut rng);
1305 let fvk = FullViewingKey::from(&sk);
1306 let recipient = fvk.address_at(0u32, Scope::External);
1307
1308 let mut builder = Builder::new(
1309 BundleType::DEFAULT,
1310 EMPTY_ROOTS[MERKLE_DEPTH_ORCHARD].into(),
1311 );
1312
1313 builder
1314 .add_output(None, recipient, NoteValue::from_raw(5000), [0u8; 512])
1315 .unwrap();
1316 let balance: i64 = builder.value_balance().unwrap();
1317 assert_eq!(balance, -5000);
1318
1319 let bundle: Bundle<Authorized, i64> = builder
1320 .build(&mut rng)
1321 .unwrap()
1322 .unwrap()
1323 .0
1324 .create_proof(&pk, &mut rng)
1325 .unwrap()
1326 .prepare(rng, [0; 32])
1327 .finalize()
1328 .unwrap();
1329 assert_eq!(bundle.value_balance(), &(-5000))
1330 }
1331}