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