1use alloc::vec::Vec;
4
5pub mod commitments;
6
7#[cfg(feature = "circuit")]
8mod batch;
9#[cfg(feature = "circuit")]
10pub use batch::BatchValidator;
11
12use core::fmt;
13
14use blake2b_simd::Hash as Blake2bHash;
15use nonempty::NonEmpty;
16use zcash_note_encryption::{try_note_decryption, try_output_recovery_with_ovk};
17
18#[cfg(feature = "std")]
19use memuse::DynamicUsage;
20
21use crate::{
22 action::Action,
23 address::Address,
24 bundle::commitments::{hash_bundle_auth_data, hash_bundle_txid_data},
25 keys::{IncomingViewingKey, OutgoingViewingKey, PreparedIncomingViewingKey},
26 note::Note,
27 note_encryption::OrchardDomain,
28 primitives::redpallas::{self, Binding, SpendAuth},
29 tree::Anchor,
30 value::{ValueCommitTrapdoor, ValueCommitment, ValueSum},
31 Proof,
32};
33
34#[cfg(feature = "circuit")]
35use crate::circuit::{Instance, VerifyingKey};
36
37#[cfg(feature = "circuit")]
38impl<T> Action<T> {
39 pub fn to_instance(&self, flags: Flags, anchor: Anchor) -> Instance {
42 Instance {
43 anchor,
44 cv_net: self.cv_net().clone(),
45 nf_old: *self.nullifier(),
46 rk: self.rk().clone(),
47 cmx: *self.cmx(),
48 enable_spend: flags.spends_enabled,
49 enable_output: flags.outputs_enabled,
50 }
51 }
52}
53
54#[derive(Clone, Copy, Debug, PartialEq, Eq)]
56pub struct Flags {
57 spends_enabled: bool,
63 outputs_enabled: bool,
69}
70
71const FLAG_SPENDS_ENABLED: u8 = 0b0000_0001;
72const FLAG_OUTPUTS_ENABLED: u8 = 0b0000_0010;
73const FLAGS_EXPECTED_UNSET: u8 = !(FLAG_SPENDS_ENABLED | FLAG_OUTPUTS_ENABLED);
74
75impl Flags {
76 pub(crate) const fn from_parts(spends_enabled: bool, outputs_enabled: bool) -> Self {
78 Flags {
79 spends_enabled,
80 outputs_enabled,
81 }
82 }
83
84 pub const ENABLED: Flags = Flags {
86 spends_enabled: true,
87 outputs_enabled: true,
88 };
89
90 pub const SPENDS_DISABLED: Flags = Flags {
92 spends_enabled: false,
93 outputs_enabled: true,
94 };
95
96 pub const OUTPUTS_DISABLED: Flags = Flags {
98 spends_enabled: true,
99 outputs_enabled: false,
100 };
101
102 pub fn spends_enabled(&self) -> bool {
108 self.spends_enabled
109 }
110
111 pub fn outputs_enabled(&self) -> bool {
117 self.outputs_enabled
118 }
119
120 pub fn to_byte(&self) -> u8 {
125 let mut value = 0u8;
126 if self.spends_enabled {
127 value |= FLAG_SPENDS_ENABLED;
128 }
129 if self.outputs_enabled {
130 value |= FLAG_OUTPUTS_ENABLED;
131 }
132 value
133 }
134
135 pub fn from_byte(value: u8) -> Option<Self> {
142 if value & FLAGS_EXPECTED_UNSET == 0 {
144 Some(Self {
145 spends_enabled: value & FLAG_SPENDS_ENABLED != 0,
146 outputs_enabled: value & FLAG_OUTPUTS_ENABLED != 0,
147 })
148 } else {
149 None
150 }
151 }
152}
153
154pub trait Authorization: fmt::Debug {
156 type SpendAuth: fmt::Debug;
158}
159
160#[derive(Clone)]
162pub struct Bundle<T: Authorization, V> {
163 actions: NonEmpty<Action<T::SpendAuth>>,
165 flags: Flags,
167 value_balance: V,
171 anchor: Anchor,
173 authorization: T,
175}
176
177impl<T: Authorization, V: fmt::Debug> fmt::Debug for Bundle<T, V> {
178 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
179 struct Actions<'a, T>(&'a NonEmpty<Action<T>>);
181 impl<T: fmt::Debug> fmt::Debug for Actions<'_, T> {
182 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
183 f.debug_list().entries(self.0.iter()).finish()
184 }
185 }
186
187 f.debug_struct("Bundle")
188 .field("actions", &Actions(&self.actions))
189 .field("flags", &self.flags)
190 .field("value_balance", &self.value_balance)
191 .field("anchor", &self.anchor)
192 .field("authorization", &self.authorization)
193 .finish()
194 }
195}
196
197impl<T: Authorization, V> Bundle<T, V> {
198 pub fn from_parts(
200 actions: NonEmpty<Action<T::SpendAuth>>,
201 flags: Flags,
202 value_balance: V,
203 anchor: Anchor,
204 authorization: T,
205 ) -> Self {
206 Bundle {
207 actions,
208 flags,
209 value_balance,
210 anchor,
211 authorization,
212 }
213 }
214
215 pub fn actions(&self) -> &NonEmpty<Action<T::SpendAuth>> {
217 &self.actions
218 }
219
220 pub fn flags(&self) -> &Flags {
222 &self.flags
223 }
224
225 pub fn value_balance(&self) -> &V {
229 &self.value_balance
230 }
231
232 pub fn anchor(&self) -> &Anchor {
234 &self.anchor
235 }
236
237 pub fn authorization(&self) -> &T {
241 &self.authorization
242 }
243
244 pub fn try_map_value_balance<V0, E, F: FnOnce(V) -> Result<V0, E>>(
247 self,
248 f: F,
249 ) -> Result<Bundle<T, V0>, E> {
250 Ok(Bundle {
251 actions: self.actions,
252 flags: self.flags,
253 value_balance: f(self.value_balance)?,
254 anchor: self.anchor,
255 authorization: self.authorization,
256 })
257 }
258
259 pub fn map_authorization<R, U: Authorization>(
261 self,
262 context: &mut R,
263 mut spend_auth: impl FnMut(&mut R, &T, T::SpendAuth) -> U::SpendAuth,
264 step: impl FnOnce(&mut R, T) -> U,
265 ) -> Bundle<U, V> {
266 let authorization = self.authorization;
267 Bundle {
268 actions: self
269 .actions
270 .map(|a| a.map(|a_auth| spend_auth(context, &authorization, a_auth))),
271 flags: self.flags,
272 value_balance: self.value_balance,
273 anchor: self.anchor,
274 authorization: step(context, authorization),
275 }
276 }
277
278 pub fn try_map_authorization<R, U: Authorization, E>(
280 self,
281 context: &mut R,
282 mut spend_auth: impl FnMut(&mut R, &T, T::SpendAuth) -> Result<U::SpendAuth, E>,
283 step: impl FnOnce(&mut R, T) -> Result<U, E>,
284 ) -> Result<Bundle<U, V>, E> {
285 let authorization = self.authorization;
286 let new_actions = self
287 .actions
288 .into_iter()
289 .map(|a| a.try_map(|a_auth| spend_auth(context, &authorization, a_auth)))
290 .collect::<Result<Vec<_>, E>>()?;
291
292 Ok(Bundle {
293 actions: NonEmpty::from_vec(new_actions).unwrap(),
294 flags: self.flags,
295 value_balance: self.value_balance,
296 anchor: self.anchor,
297 authorization: step(context, authorization)?,
298 })
299 }
300
301 #[cfg(feature = "circuit")]
302 pub(crate) fn to_instances(&self) -> Vec<Instance> {
303 self.actions
304 .iter()
305 .map(|a| a.to_instance(self.flags, self.anchor))
306 .collect()
307 }
308
309 pub fn decrypt_outputs_with_keys(
314 &self,
315 keys: &[IncomingViewingKey],
316 ) -> Vec<(usize, IncomingViewingKey, Note, Address, [u8; 512])> {
317 let prepared_keys: Vec<_> = keys
318 .iter()
319 .map(|ivk| (ivk, PreparedIncomingViewingKey::new(ivk)))
320 .collect();
321 self.actions
322 .iter()
323 .enumerate()
324 .filter_map(|(idx, action)| {
325 let domain = OrchardDomain::for_action(action);
326 prepared_keys.iter().find_map(|(ivk, prepared_ivk)| {
327 try_note_decryption(&domain, prepared_ivk, action)
328 .map(|(n, a, m)| (idx, (*ivk).clone(), n, a, m))
329 })
330 })
331 .collect()
332 }
333
334 pub fn decrypt_output_with_key(
338 &self,
339 action_idx: usize,
340 key: &IncomingViewingKey,
341 ) -> Option<(Note, Address, [u8; 512])> {
342 let prepared_ivk = PreparedIncomingViewingKey::new(key);
343 self.actions.get(action_idx).and_then(move |action| {
344 let domain = OrchardDomain::for_action(action);
345 try_note_decryption(&domain, &prepared_ivk, action)
346 })
347 }
348
349 pub fn recover_outputs_with_ovks(
354 &self,
355 keys: &[OutgoingViewingKey],
356 ) -> Vec<(usize, OutgoingViewingKey, Note, Address, [u8; 512])> {
357 self.actions
358 .iter()
359 .enumerate()
360 .filter_map(|(idx, action)| {
361 let domain = OrchardDomain::for_action(action);
362 keys.iter().find_map(move |key| {
363 try_output_recovery_with_ovk(
364 &domain,
365 key,
366 action,
367 action.cv_net(),
368 &action.encrypted_note().out_ciphertext,
369 )
370 .map(|(n, a, m)| (idx, key.clone(), n, a, m))
371 })
372 })
373 .collect()
374 }
375
376 pub fn recover_output_with_ovk(
380 &self,
381 action_idx: usize,
382 key: &OutgoingViewingKey,
383 ) -> Option<(Note, Address, [u8; 512])> {
384 self.actions.get(action_idx).and_then(move |action| {
385 let domain = OrchardDomain::for_action(action);
386 try_output_recovery_with_ovk(
387 &domain,
388 key,
389 action,
390 action.cv_net(),
391 &action.encrypted_note().out_ciphertext,
392 )
393 })
394 }
395}
396
397impl<T: Authorization, V: Copy + Into<i64>> Bundle<T, V> {
398 pub fn commitment(&self) -> BundleCommitment {
401 BundleCommitment(hash_bundle_txid_data(self))
402 }
403
404 pub fn binding_validating_key(&self) -> redpallas::VerificationKey<Binding> {
409 (self
411 .actions
412 .iter()
413 .map(|a| a.cv_net())
414 .sum::<ValueCommitment>()
415 - ValueCommitment::derive(
416 ValueSum::from_raw(self.value_balance.into()),
417 ValueCommitTrapdoor::zero(),
418 ))
419 .into_bvk()
420 }
421}
422
423#[derive(Clone, Debug)]
425pub struct EffectsOnly;
426
427impl Authorization for EffectsOnly {
428 type SpendAuth = ();
429}
430
431#[derive(Debug, Clone)]
433pub struct Authorized {
434 proof: Proof,
435 binding_signature: redpallas::Signature<Binding>,
436}
437
438impl Authorization for Authorized {
439 type SpendAuth = redpallas::Signature<SpendAuth>;
440}
441
442impl Authorized {
443 pub fn from_parts(proof: Proof, binding_signature: redpallas::Signature<Binding>) -> Self {
445 Authorized {
446 proof,
447 binding_signature,
448 }
449 }
450
451 pub fn proof(&self) -> &Proof {
453 &self.proof
454 }
455
456 pub fn binding_signature(&self) -> &redpallas::Signature<Binding> {
458 &self.binding_signature
459 }
460}
461
462impl<V> Bundle<Authorized, V> {
463 pub fn authorizing_commitment(&self) -> BundleAuthorizingCommitment {
467 BundleAuthorizingCommitment(hash_bundle_auth_data(self))
468 }
469
470 #[cfg(feature = "circuit")]
472 pub fn verify_proof(&self, vk: &VerifyingKey) -> Result<(), halo2_proofs::plonk::Error> {
473 self.authorization()
474 .proof()
475 .verify(vk, &self.to_instances())
476 }
477}
478
479#[cfg(feature = "std")]
480impl<V: DynamicUsage> DynamicUsage for Bundle<Authorized, V> {
481 fn dynamic_usage(&self) -> usize {
482 self.actions.tail.dynamic_usage()
483 + self.value_balance.dynamic_usage()
484 + self.authorization.proof.dynamic_usage()
485 }
486
487 fn dynamic_usage_bounds(&self) -> (usize, Option<usize>) {
488 let bounds = (
489 self.actions.tail.dynamic_usage_bounds(),
490 self.value_balance.dynamic_usage_bounds(),
491 self.authorization.proof.dynamic_usage_bounds(),
492 );
493 (
494 bounds.0 .0 + bounds.1 .0 + bounds.2 .0,
495 bounds
496 .0
497 .1
498 .zip(bounds.1 .1)
499 .zip(bounds.2 .1)
500 .map(|((a, b), c)| a + b + c),
501 )
502 }
503}
504
505#[derive(Debug)]
510pub struct BundleCommitment(pub Blake2bHash);
511
512impl From<BundleCommitment> for [u8; 32] {
513 fn from(commitment: BundleCommitment) -> Self {
514 commitment.0.as_bytes().try_into().unwrap()
516 }
517}
518
519#[derive(Debug)]
521pub struct BundleAuthorizingCommitment(pub Blake2bHash);
522
523#[cfg(any(test, feature = "test-dependencies"))]
525#[cfg_attr(docsrs, doc(cfg(feature = "test-dependencies")))]
526pub mod testing {
527 use alloc::vec::Vec;
528
529 use group::ff::FromUniformBytes;
530 use nonempty::NonEmpty;
531 use pasta_curves::pallas;
532 use rand::{rngs::StdRng, SeedableRng};
533 use reddsa::orchard::SpendAuth;
534
535 use proptest::collection::vec;
536 use proptest::prelude::*;
537
538 use crate::{
539 primitives::redpallas::{self, testing::arb_binding_signing_key},
540 value::{testing::arb_note_value_bounded, NoteValue, ValueSum, MAX_NOTE_VALUE},
541 Anchor, Proof,
542 };
543
544 use super::{Action, Authorized, Bundle, Flags};
545
546 pub use crate::action::testing::{arb_action, arb_unauthorized_action};
547
548 pub type Unauthorized = super::EffectsOnly;
550
551 pub fn arb_unauthorized_action_n(
553 n_actions: usize,
554 flags: Flags,
555 ) -> impl Strategy<Value = (ValueSum, Action<()>)> {
556 let spend_value_gen = if flags.spends_enabled {
557 Strategy::boxed(arb_note_value_bounded(MAX_NOTE_VALUE / n_actions as u64))
558 } else {
559 Strategy::boxed(Just(NoteValue::zero()))
560 };
561
562 spend_value_gen.prop_flat_map(move |spend_value| {
563 let output_value_gen = if flags.outputs_enabled {
564 Strategy::boxed(arb_note_value_bounded(MAX_NOTE_VALUE / n_actions as u64))
565 } else {
566 Strategy::boxed(Just(NoteValue::zero()))
567 };
568
569 output_value_gen.prop_flat_map(move |output_value| {
570 arb_unauthorized_action(spend_value, output_value)
571 .prop_map(move |a| (spend_value - output_value, a))
572 })
573 })
574 }
575
576 pub fn arb_action_n(
578 n_actions: usize,
579 flags: Flags,
580 ) -> impl Strategy<Value = (ValueSum, Action<redpallas::Signature<SpendAuth>>)> {
581 let spend_value_gen = if flags.spends_enabled {
582 Strategy::boxed(arb_note_value_bounded(MAX_NOTE_VALUE / n_actions as u64))
583 } else {
584 Strategy::boxed(Just(NoteValue::zero()))
585 };
586
587 spend_value_gen.prop_flat_map(move |spend_value| {
588 let output_value_gen = if flags.outputs_enabled {
589 Strategy::boxed(arb_note_value_bounded(MAX_NOTE_VALUE / n_actions as u64))
590 } else {
591 Strategy::boxed(Just(NoteValue::zero()))
592 };
593
594 output_value_gen.prop_flat_map(move |output_value| {
595 arb_action(spend_value, output_value)
596 .prop_map(move |a| (spend_value - output_value, a))
597 })
598 })
599 }
600
601 prop_compose! {
602 pub fn arb_flags()(spends_enabled in prop::bool::ANY, outputs_enabled in prop::bool::ANY) -> Flags {
604 Flags::from_parts(spends_enabled, outputs_enabled)
605 }
606 }
607
608 prop_compose! {
609 fn arb_base()(bytes in prop::array::uniform32(0u8..)) -> pallas::Base {
610 let mut buf = [0; 64];
612 buf[..32].copy_from_slice(&bytes);
613 pallas::Base::from_uniform_bytes(&buf)
614 }
615 }
616
617 prop_compose! {
618 pub fn arb_unauthorized_bundle(n_actions: usize)
622 (
623 flags in arb_flags(),
624 )
625 (
626 acts in vec(arb_unauthorized_action_n(n_actions, flags), n_actions),
627 anchor in arb_base().prop_map(Anchor::from),
628 flags in Just(flags)
629 ) -> Bundle<Unauthorized, ValueSum> {
630 let (balances, actions): (Vec<ValueSum>, Vec<Action<_>>) = acts.into_iter().unzip();
631
632 Bundle::from_parts(
633 NonEmpty::from_vec(actions).unwrap(),
634 flags,
635 balances.into_iter().sum::<Result<ValueSum, _>>().unwrap(),
636 anchor,
637 super::EffectsOnly,
638 )
639 }
640 }
641
642 prop_compose! {
643 pub fn arb_bundle(n_actions: usize)
647 (
648 flags in arb_flags(),
649 )
650 (
651 acts in vec(arb_action_n(n_actions, flags), n_actions),
652 anchor in arb_base().prop_map(Anchor::from),
653 sk in arb_binding_signing_key(),
654 rng_seed in prop::array::uniform32(prop::num::u8::ANY),
655 fake_proof in vec(prop::num::u8::ANY, 1973),
656 fake_sighash in prop::array::uniform32(prop::num::u8::ANY),
657 flags in Just(flags)
658 ) -> Bundle<Authorized, ValueSum> {
659 let (balances, actions): (Vec<ValueSum>, Vec<Action<_>>) = acts.into_iter().unzip();
660 let rng = StdRng::from_seed(rng_seed);
661
662 Bundle::from_parts(
663 NonEmpty::from_vec(actions).unwrap(),
664 flags,
665 balances.into_iter().sum::<Result<ValueSum, _>>().unwrap(),
666 anchor,
667 Authorized {
668 proof: Proof::new(fake_proof),
669 binding_signature: sk.sign(rng, &fake_sighash),
670 }
671 )
672 }
673 }
674}