1use alloc::vec::Vec;
2use core::fmt::Debug;
3
4use memuse::DynamicUsage;
5
6use redjubjub::{Binding, SpendAuth};
7
8use zcash_note_encryption::{
9 note_bytes::NoteBytesData, EphemeralKeyBytes, ShieldedOutput, OUT_CIPHERTEXT_SIZE,
10};
11
12use crate::{
13 constants::GROTH_PROOF_SIZE,
14 note::ExtractedNoteCommitment,
15 note_encryption::{
16 CompactOutputDescription, NoteCiphertextBytes, SaplingDomain, COMPACT_NOTE_SIZE,
17 ENC_CIPHERTEXT_SIZE,
18 },
19 value::ValueCommitment,
20 Nullifier,
21};
22
23pub type GrothProofBytes = [u8; GROTH_PROOF_SIZE];
24
25pub trait Authorization: Debug {
27 type SpendProof: Clone + Debug;
28 type OutputProof: Clone + Debug;
29 type AuthSig: Clone + Debug;
30}
31
32#[derive(Debug)]
34pub struct EffectsOnly;
35
36impl Authorization for EffectsOnly {
37 type SpendProof = ();
38 type OutputProof = ();
39 type AuthSig = ();
40}
41
42#[derive(Debug, Copy, Clone)]
45pub struct Authorized {
46 pub binding_sig: redjubjub::Signature<Binding>,
48}
49
50impl Authorization for Authorized {
51 type SpendProof = GrothProofBytes;
52 type OutputProof = GrothProofBytes;
53 type AuthSig = redjubjub::Signature<SpendAuth>;
54}
55
56#[derive(Debug, Clone)]
57pub struct Bundle<A: Authorization, V> {
58 shielded_spends: Vec<SpendDescription<A>>,
59 shielded_outputs: Vec<OutputDescription<A::OutputProof>>,
60 value_balance: V,
61 authorization: A,
62}
63
64impl<A: Authorization, V> Bundle<A, V> {
65 pub fn from_parts(
67 shielded_spends: Vec<SpendDescription<A>>,
68 shielded_outputs: Vec<OutputDescription<A::OutputProof>>,
69 value_balance: V,
70 authorization: A,
71 ) -> Option<Self> {
72 if shielded_spends.is_empty() && shielded_outputs.is_empty() {
73 None
74 } else {
75 Some(Bundle {
76 shielded_spends,
77 shielded_outputs,
78 value_balance,
79 authorization,
80 })
81 }
82 }
83
84 pub fn shielded_spends(&self) -> &[SpendDescription<A>] {
86 &self.shielded_spends
87 }
88
89 pub fn shielded_outputs(&self) -> &[OutputDescription<A::OutputProof>] {
91 &self.shielded_outputs
92 }
93
94 pub fn value_balance(&self) -> &V {
98 &self.value_balance
99 }
100
101 pub fn authorization(&self) -> &A {
105 &self.authorization
106 }
107
108 pub fn map_authorization<R, B: Authorization>(
110 self,
111 mut context: R,
112 spend_proof: impl Fn(&mut R, A::SpendProof) -> B::SpendProof,
113 output_proof: impl Fn(&mut R, A::OutputProof) -> B::OutputProof,
114 auth_sig: impl Fn(&mut R, A::AuthSig) -> B::AuthSig,
115 auth: impl FnOnce(&mut R, A) -> B,
116 ) -> Bundle<B, V> {
117 Bundle {
118 shielded_spends: self
119 .shielded_spends
120 .into_iter()
121 .map(|d| SpendDescription {
122 cv: d.cv,
123 anchor: d.anchor,
124 nullifier: d.nullifier,
125 rk: d.rk,
126 zkproof: spend_proof(&mut context, d.zkproof),
127 spend_auth_sig: auth_sig(&mut context, d.spend_auth_sig),
128 })
129 .collect(),
130 shielded_outputs: self
131 .shielded_outputs
132 .into_iter()
133 .map(|o| OutputDescription {
134 cv: o.cv,
135 cmu: o.cmu,
136 ephemeral_key: o.ephemeral_key,
137 enc_ciphertext: o.enc_ciphertext,
138 out_ciphertext: o.out_ciphertext,
139 zkproof: output_proof(&mut context, o.zkproof),
140 })
141 .collect(),
142 value_balance: self.value_balance,
143 authorization: auth(&mut context, self.authorization),
144 }
145 }
146
147 pub fn try_map_authorization<R, B: Authorization, Error>(
149 self,
150 mut context: R,
151 spend_proof: impl Fn(&mut R, A::SpendProof) -> Result<B::SpendProof, Error>,
152 output_proof: impl Fn(&mut R, A::OutputProof) -> Result<B::OutputProof, Error>,
153 auth_sig: impl Fn(&mut R, A::AuthSig) -> Result<B::AuthSig, Error>,
154 auth: impl Fn(&mut R, A) -> Result<B, Error>,
155 ) -> Result<Bundle<B, V>, Error> {
156 Ok(Bundle {
157 shielded_spends: self
158 .shielded_spends
159 .into_iter()
160 .map(|d| {
161 Ok(SpendDescription {
162 cv: d.cv,
163 anchor: d.anchor,
164 nullifier: d.nullifier,
165 rk: d.rk,
166 zkproof: spend_proof(&mut context, d.zkproof)?,
167 spend_auth_sig: auth_sig(&mut context, d.spend_auth_sig)?,
168 })
169 })
170 .collect::<Result<_, _>>()?,
171 shielded_outputs: self
172 .shielded_outputs
173 .into_iter()
174 .map(|o| {
175 Ok(OutputDescription {
176 cv: o.cv,
177 cmu: o.cmu,
178 ephemeral_key: o.ephemeral_key,
179 enc_ciphertext: o.enc_ciphertext,
180 out_ciphertext: o.out_ciphertext,
181 zkproof: output_proof(&mut context, o.zkproof)?,
182 })
183 })
184 .collect::<Result<_, _>>()?,
185 value_balance: self.value_balance,
186 authorization: auth(&mut context, self.authorization)?,
187 })
188 }
189}
190
191impl<V: DynamicUsage> DynamicUsage for Bundle<Authorized, V> {
192 fn dynamic_usage(&self) -> usize {
193 self.shielded_spends.dynamic_usage()
194 + self.shielded_outputs.dynamic_usage()
195 + self.value_balance.dynamic_usage()
196 }
197
198 fn dynamic_usage_bounds(&self) -> (usize, Option<usize>) {
199 let bounds = (
200 self.shielded_spends.dynamic_usage_bounds(),
201 self.shielded_outputs.dynamic_usage_bounds(),
202 self.value_balance.dynamic_usage_bounds(),
203 );
204
205 (
206 bounds.0 .0 + bounds.1 .0 + bounds.2 .0,
207 bounds
208 .0
209 .1
210 .zip(bounds.1 .1)
211 .zip(bounds.2 .1)
212 .map(|((a, b), c)| a + b + c),
213 )
214 }
215}
216
217#[derive(Clone)]
218pub struct SpendDescription<A: Authorization> {
219 cv: ValueCommitment,
220 anchor: bls12_381::Scalar,
221 nullifier: Nullifier,
222 rk: redjubjub::VerificationKey<SpendAuth>,
223 zkproof: A::SpendProof,
224 spend_auth_sig: A::AuthSig,
225}
226
227impl<A: Authorization> core::fmt::Debug for SpendDescription<A> {
228 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
229 write!(
230 f,
231 "SpendDescription(cv = {:?}, anchor = {:?}, nullifier = {:?}, rk = {:?}, spend_auth_sig = {:?})",
232 self.cv, self.anchor, self.nullifier, self.rk, self.spend_auth_sig
233 )
234 }
235}
236
237impl<A: Authorization> SpendDescription<A> {
238 pub fn from_parts(
240 cv: ValueCommitment,
241 anchor: bls12_381::Scalar,
242 nullifier: Nullifier,
243 rk: redjubjub::VerificationKey<SpendAuth>,
244 zkproof: A::SpendProof,
245 spend_auth_sig: A::AuthSig,
246 ) -> Self {
247 Self {
248 cv,
249 anchor,
250 nullifier,
251 rk,
252 zkproof,
253 spend_auth_sig,
254 }
255 }
256
257 pub fn cv(&self) -> &ValueCommitment {
259 &self.cv
260 }
261
262 pub fn anchor(&self) -> &bls12_381::Scalar {
264 &self.anchor
265 }
266
267 pub fn nullifier(&self) -> &Nullifier {
269 &self.nullifier
270 }
271
272 pub fn rk(&self) -> &redjubjub::VerificationKey<SpendAuth> {
274 &self.rk
275 }
276
277 pub fn zkproof(&self) -> &A::SpendProof {
279 &self.zkproof
280 }
281
282 pub fn spend_auth_sig(&self) -> &A::AuthSig {
284 &self.spend_auth_sig
285 }
286}
287
288impl DynamicUsage for SpendDescription<Authorized> {
289 fn dynamic_usage(&self) -> usize {
290 self.zkproof.dynamic_usage()
291 }
292
293 fn dynamic_usage_bounds(&self) -> (usize, Option<usize>) {
294 self.zkproof.dynamic_usage_bounds()
295 }
296}
297
298#[derive(Clone)]
299pub struct SpendDescriptionV5 {
300 cv: ValueCommitment,
301 nullifier: Nullifier,
302 rk: redjubjub::VerificationKey<SpendAuth>,
303}
304
305impl SpendDescriptionV5 {
306 pub fn from_parts(
308 cv: ValueCommitment,
309 nullifier: Nullifier,
310 rk: redjubjub::VerificationKey<SpendAuth>,
311 ) -> Self {
312 Self { cv, nullifier, rk }
313 }
314
315 pub fn into_spend_description<A>(
316 self,
317 anchor: bls12_381::Scalar,
318 zkproof: GrothProofBytes,
319 spend_auth_sig: redjubjub::Signature<SpendAuth>,
320 ) -> SpendDescription<A>
321 where
322 A: Authorization<SpendProof = GrothProofBytes, AuthSig = redjubjub::Signature<SpendAuth>>,
323 {
324 SpendDescription {
325 cv: self.cv,
326 anchor,
327 nullifier: self.nullifier,
328 rk: self.rk,
329 zkproof,
330 spend_auth_sig,
331 }
332 }
333}
334
335#[derive(Clone)]
336pub struct OutputDescription<Proof> {
337 cv: ValueCommitment,
338 cmu: ExtractedNoteCommitment,
339 ephemeral_key: EphemeralKeyBytes,
340 enc_ciphertext: NoteCiphertextBytes,
341 out_ciphertext: [u8; OUT_CIPHERTEXT_SIZE],
342 zkproof: Proof,
343}
344
345impl<Proof> OutputDescription<Proof> {
346 pub fn cv(&self) -> &ValueCommitment {
348 &self.cv
349 }
350
351 pub fn cmu(&self) -> &ExtractedNoteCommitment {
353 &self.cmu
354 }
355
356 pub fn ephemeral_key(&self) -> &EphemeralKeyBytes {
357 &self.ephemeral_key
358 }
359
360 pub fn enc_ciphertext(&self) -> &[u8; ENC_CIPHERTEXT_SIZE] {
362 &self.enc_ciphertext.0
363 }
364
365 pub fn out_ciphertext(&self) -> &[u8; OUT_CIPHERTEXT_SIZE] {
367 &self.out_ciphertext
368 }
369
370 pub fn zkproof(&self) -> &Proof {
372 &self.zkproof
373 }
374
375 pub fn from_parts(
377 cv: ValueCommitment,
378 cmu: ExtractedNoteCommitment,
379 ephemeral_key: EphemeralKeyBytes,
380 enc_ciphertext: [u8; ENC_CIPHERTEXT_SIZE],
381 out_ciphertext: [u8; OUT_CIPHERTEXT_SIZE],
382 zkproof: Proof,
383 ) -> Self {
384 OutputDescription {
385 cv,
386 cmu,
387 ephemeral_key,
388 enc_ciphertext: NoteBytesData(enc_ciphertext),
389 out_ciphertext,
390 zkproof,
391 }
392 }
393}
394
395#[cfg(test)]
396impl<Proof> OutputDescription<Proof> {
397 pub(crate) fn cv_mut(&mut self) -> &mut ValueCommitment {
398 &mut self.cv
399 }
400 pub(crate) fn cmu_mut(&mut self) -> &mut ExtractedNoteCommitment {
401 &mut self.cmu
402 }
403 pub(crate) fn ephemeral_key_mut(&mut self) -> &mut EphemeralKeyBytes {
404 &mut self.ephemeral_key
405 }
406 pub(crate) fn enc_ciphertext_mut(&mut self) -> &mut [u8; ENC_CIPHERTEXT_SIZE] {
407 &mut self.enc_ciphertext.0
408 }
409 pub(crate) fn out_ciphertext_mut(&mut self) -> &mut [u8; OUT_CIPHERTEXT_SIZE] {
410 &mut self.out_ciphertext
411 }
412}
413
414impl<Proof: DynamicUsage> DynamicUsage for OutputDescription<Proof> {
415 fn dynamic_usage(&self) -> usize {
416 self.zkproof.dynamic_usage()
417 }
418
419 fn dynamic_usage_bounds(&self) -> (usize, Option<usize>) {
420 self.zkproof.dynamic_usage_bounds()
421 }
422}
423
424impl<A> ShieldedOutput<SaplingDomain> for OutputDescription<A> {
425 fn ephemeral_key(&self) -> EphemeralKeyBytes {
426 self.ephemeral_key.clone()
427 }
428
429 fn cmstar(&self) -> &ExtractedNoteCommitment {
430 &self.cmu
431 }
432
433 fn enc_ciphertext(&self) -> Option<&NoteCiphertextBytes> {
434 Some(&self.enc_ciphertext)
435 }
436
437 fn enc_ciphertext_compact(&self) -> NoteBytesData<COMPACT_NOTE_SIZE> {
438 NoteBytesData(
439 self.enc_ciphertext.0[..COMPACT_NOTE_SIZE]
440 .try_into()
441 .expect("slice is the correct length"),
442 )
443 }
444}
445
446impl<A> core::fmt::Debug for OutputDescription<A> {
447 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
448 write!(
449 f,
450 "OutputDescription(cv = {:?}, cmu = {:?}, ephemeral_key = {:?})",
451 self.cv, self.cmu, self.ephemeral_key
452 )
453 }
454}
455
456#[derive(Clone)]
457pub struct OutputDescriptionV5 {
458 cv: ValueCommitment,
459 cmu: ExtractedNoteCommitment,
460 ephemeral_key: EphemeralKeyBytes,
461 enc_ciphertext: [u8; ENC_CIPHERTEXT_SIZE],
462 out_ciphertext: [u8; OUT_CIPHERTEXT_SIZE],
463}
464
465memuse::impl_no_dynamic_usage!(OutputDescriptionV5);
466
467impl OutputDescriptionV5 {
468 pub fn from_parts(
470 cv: ValueCommitment,
471 cmu: ExtractedNoteCommitment,
472 ephemeral_key: EphemeralKeyBytes,
473 enc_ciphertext: [u8; ENC_CIPHERTEXT_SIZE],
474 out_ciphertext: [u8; OUT_CIPHERTEXT_SIZE],
475 ) -> Self {
476 Self {
477 cv,
478 cmu,
479 ephemeral_key,
480 enc_ciphertext,
481 out_ciphertext,
482 }
483 }
484
485 pub fn into_output_description(
486 self,
487 zkproof: GrothProofBytes,
488 ) -> OutputDescription<GrothProofBytes> {
489 OutputDescription {
490 cv: self.cv,
491 cmu: self.cmu,
492 ephemeral_key: self.ephemeral_key,
493 enc_ciphertext: NoteBytesData(self.enc_ciphertext),
494 out_ciphertext: self.out_ciphertext,
495 zkproof,
496 }
497 }
498}
499
500impl<A> From<OutputDescription<A>> for CompactOutputDescription {
501 fn from(out: OutputDescription<A>) -> CompactOutputDescription {
502 CompactOutputDescription {
503 ephemeral_key: out.ephemeral_key,
504 cmu: out.cmu,
505 enc_ciphertext: out.enc_ciphertext.0[..COMPACT_NOTE_SIZE]
506 .try_into()
507 .unwrap(),
508 }
509 }
510}
511
512#[cfg(any(test, feature = "test-dependencies"))]
513#[cfg_attr(docsrs, doc(cfg(feature = "test-dependencies")))]
514pub mod testing {
515 use core::fmt;
516
517 use ff::Field;
518 use group::{Group, GroupEncoding};
519 use proptest::collection::vec;
520 use proptest::prelude::*;
521 use rand::{rngs::StdRng, SeedableRng};
522
523 use crate::{
524 constants::GROTH_PROOF_SIZE,
525 note::testing::arb_cmu,
526 value::{
527 testing::{arb_note_value_bounded, arb_trapdoor},
528 ValueCommitment, MAX_NOTE_VALUE,
529 },
530 Nullifier,
531 };
532
533 use super::{
534 Authorized, Bundle, GrothProofBytes, NoteBytesData, OutputDescription, SpendDescription,
535 ENC_CIPHERTEXT_SIZE, OUT_CIPHERTEXT_SIZE,
536 };
537
538 prop_compose! {
539 fn arb_extended_point()(rng_seed in prop::array::uniform32(any::<u8>())) -> jubjub::ExtendedPoint {
540 let mut rng = StdRng::from_seed(rng_seed);
541 let scalar = jubjub::Scalar::random(&mut rng);
542 jubjub::ExtendedPoint::generator() * scalar
543 }
544 }
545
546 prop_compose! {
547 fn arb_spend_description(n_spends: usize)(
550 value in arb_note_value_bounded(MAX_NOTE_VALUE.checked_div(n_spends as u64).unwrap_or(0)),
551 rcv in arb_trapdoor(),
552 anchor in vec(any::<u8>(), 64)
553 .prop_map(|v| <[u8;64]>::try_from(v.as_slice()).unwrap())
554 .prop_map(|v| bls12_381::Scalar::from_bytes_wide(&v)),
555 nullifier in prop::array::uniform32(any::<u8>())
556 .prop_map(|v| Nullifier::from_slice(&v).unwrap()),
557 zkproof in vec(any::<u8>(), GROTH_PROOF_SIZE)
558 .prop_map(|v| <[u8;GROTH_PROOF_SIZE]>::try_from(v.as_slice()).unwrap()),
559 rng_seed in prop::array::uniform32(prop::num::u8::ANY),
560 fake_sighash_bytes in prop::array::uniform32(prop::num::u8::ANY),
561 ) -> SpendDescription<Authorized> {
562 let mut rng = StdRng::from_seed(rng_seed);
563 let sk1 = redjubjub::SigningKey::new(&mut rng);
564 let rk = redjubjub::VerificationKey::from(&sk1);
565 let cv = ValueCommitment::derive(value, rcv);
566 SpendDescription {
567 cv,
568 anchor,
569 nullifier,
570 rk,
571 zkproof,
572 spend_auth_sig: sk1.sign(&mut rng, &fake_sighash_bytes),
573 }
574 }
575 }
576
577 prop_compose! {
578 pub fn arb_output_description(n_outputs: usize)(
581 value in arb_note_value_bounded(MAX_NOTE_VALUE.checked_div(n_outputs as u64).unwrap_or(0)),
582 rcv in arb_trapdoor(),
583 cmu in arb_cmu(),
584 enc_ciphertext in vec(any::<u8>(), ENC_CIPHERTEXT_SIZE)
585 .prop_map(|v| <[u8; ENC_CIPHERTEXT_SIZE]>::try_from(v.as_slice()).unwrap()),
586 epk in arb_extended_point(),
587 out_ciphertext in vec(any::<u8>(), OUT_CIPHERTEXT_SIZE)
588 .prop_map(|v| <[u8; OUT_CIPHERTEXT_SIZE]>::try_from(v.as_slice()).unwrap()),
589 zkproof in vec(any::<u8>(), GROTH_PROOF_SIZE)
590 .prop_map(|v| <[u8; GROTH_PROOF_SIZE]>::try_from(v.as_slice()).unwrap()),
591 ) -> OutputDescription<GrothProofBytes> {
592 let cv = ValueCommitment::derive(value, rcv);
593 OutputDescription {
594 cv,
595 cmu,
596 ephemeral_key: epk.to_bytes().into(),
597 enc_ciphertext: NoteBytesData(enc_ciphertext),
598 out_ciphertext,
599 zkproof,
600 }
601 }
602 }
603
604 pub fn arb_bundle<V: Copy + fmt::Debug + 'static>(
605 value_balance: V,
606 ) -> impl Strategy<Value = Option<Bundle<Authorized, V>>> {
607 (0usize..30, 0usize..30)
608 .prop_flat_map(|(n_spends, n_outputs)| {
609 (
610 vec(arb_spend_description(n_spends), n_spends),
611 vec(arb_output_description(n_outputs), n_outputs),
612 prop::array::uniform32(prop::num::u8::ANY),
613 prop::array::uniform32(prop::num::u8::ANY),
614 )
615 })
616 .prop_map(
617 move |(shielded_spends, shielded_outputs, rng_seed, fake_bvk_bytes)| {
618 if shielded_spends.is_empty() && shielded_outputs.is_empty() {
619 None
620 } else {
621 let mut rng = StdRng::from_seed(rng_seed);
622 let bsk = redjubjub::SigningKey::new(&mut rng);
623
624 Some(Bundle {
625 shielded_spends,
626 shielded_outputs,
627 value_balance,
628 authorization: Authorized {
629 binding_sig: bsk.sign(&mut rng, &fake_bvk_bytes),
630 },
631 })
632 }
633 },
634 )
635 }
636}