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