1use core::fmt::Debug;
2use std::convert::TryInto;
3
4use ff::PrimeField;
5use group::GroupEncoding;
6use std::io::{self, Read, Write};
7
8use zcash_note_encryption::{
9 EphemeralKeyBytes, ShieldedOutput, COMPACT_NOTE_SIZE, ENC_CIPHERTEXT_SIZE,
10};
11
12use crate::{
13 consensus,
14 sapling::{
15 note_encryption::{SaplingDomain, SaplingExtractedCommitmentBytes},
16 redjubjub::{self, PublicKey, Signature},
17 Nullifier,
18 },
19};
20
21use super::{amount::Amount, GROTH_PROOF_SIZE};
22
23pub type GrothProofBytes = [u8; GROTH_PROOF_SIZE];
24
25pub mod builder;
26
27pub trait Authorization: Debug {
28 type Proof: Clone + Debug;
29 type AuthSig: Clone + Debug;
30}
31
32#[derive(Debug, Copy, Clone, PartialEq)]
33pub struct Unproven;
34
35impl Authorization for Unproven {
36 type Proof = ();
37 type AuthSig = ();
38}
39
40#[derive(Debug, Copy, Clone)]
41pub struct Authorized {
42 pub binding_sig: redjubjub::Signature,
43}
44
45impl Authorization for Authorized {
46 type Proof = GrothProofBytes;
47 type AuthSig = redjubjub::Signature;
48}
49
50pub trait MapAuth<A: Authorization, B: Authorization> {
51 fn map_proof(&self, p: A::Proof) -> B::Proof;
52 fn map_auth_sig(&self, s: A::AuthSig) -> B::AuthSig;
53 fn map_authorization(&self, a: A) -> B;
54}
55
56#[derive(Debug, Clone)]
57pub struct Bundle<A: Authorization> {
58 pub shielded_spends: Vec<SpendDescription<A>>,
59 pub shielded_outputs: Vec<OutputDescription<A::Proof>>,
60 pub value_balance: Amount,
61 pub authorization: A,
62}
63
64impl<A: Authorization> Bundle<A> {
65 pub fn map_authorization<B: Authorization, F: MapAuth<A, B>>(self, f: F) -> Bundle<B> {
66 Bundle {
67 shielded_spends: self
68 .shielded_spends
69 .into_iter()
70 .map(|d| SpendDescription {
71 cv: d.cv,
72 anchor: d.anchor,
73 nullifier: d.nullifier,
74 rk: d.rk,
75 zkproof: f.map_proof(d.zkproof),
76 spend_auth_sig: f.map_auth_sig(d.spend_auth_sig),
77 })
78 .collect(),
79 shielded_outputs: self
80 .shielded_outputs
81 .into_iter()
82 .map(|o| OutputDescription {
83 cv: o.cv,
84 cmu: o.cmu,
85 ephemeral_key: o.ephemeral_key,
86 enc_ciphertext: o.enc_ciphertext,
87 out_ciphertext: o.out_ciphertext,
88 zkproof: f.map_proof(o.zkproof),
89 })
90 .collect(),
91 value_balance: self.value_balance,
92 authorization: f.map_authorization(self.authorization),
93 }
94 }
95}
96
97#[derive(Clone)]
98pub struct SpendDescription<A: Authorization> {
99 pub cv: ironfish_jubjub::ExtendedPoint,
100 pub anchor: blstrs::Scalar,
101 pub nullifier: Nullifier,
102 pub rk: PublicKey,
103 pub zkproof: A::Proof,
104 pub spend_auth_sig: A::AuthSig,
105}
106
107impl<A: Authorization> std::fmt::Debug for SpendDescription<A> {
108 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
109 write!(
110 f,
111 "SpendDescription(cv = {:?}, anchor = {:?}, nullifier = {:?}, rk = {:?}, spend_auth_sig = {:?})",
112 self.cv, self.anchor, self.nullifier, self.rk, self.spend_auth_sig
113 )
114 }
115}
116
117pub fn read_point<R: Read>(mut reader: R, field: &str) -> io::Result<ironfish_jubjub::ExtendedPoint> {
122 let mut bytes = [0u8; 32];
123 reader.read_exact(&mut bytes)?;
124 let point = ironfish_jubjub::ExtendedPoint::from_bytes(&bytes);
125
126 if point.is_none().into() {
127 Err(io::Error::new(
128 io::ErrorKind::InvalidInput,
129 format!("invalid {}", field),
130 ))
131 } else {
132 Ok(point.unwrap())
133 }
134}
135
136pub fn read_base<R: Read>(mut reader: R, field: &str) -> io::Result<blstrs::Scalar> {
139 let mut f = [0u8; 32];
140 reader.read_exact(&mut f)?;
141 Option::from(blstrs::Scalar::from_repr(f)).ok_or_else(|| {
142 io::Error::new(
143 io::ErrorKind::InvalidInput,
144 format!("{} not in field", field),
145 )
146 })
147}
148
149pub fn read_zkproof<R: Read>(mut reader: R) -> io::Result<GrothProofBytes> {
156 let mut zkproof = [0u8; GROTH_PROOF_SIZE];
157 reader.read_exact(&mut zkproof)?;
158 Ok(zkproof)
159}
160
161impl SpendDescription<Authorized> {
162 pub fn read_nullifier<R: Read>(mut reader: R) -> io::Result<Nullifier> {
163 let mut nullifier = Nullifier([0u8; 32]);
164 reader.read_exact(&mut nullifier.0)?;
165 Ok(nullifier)
166 }
167
168 pub fn read_rk<R: Read>(mut reader: R) -> io::Result<PublicKey> {
172 PublicKey::read(&mut reader)
173 }
174
175 pub fn read_spend_auth_sig<R: Read>(mut reader: R) -> io::Result<Signature> {
179 Signature::read(&mut reader)
180 }
181
182 pub fn read<R: Read>(mut reader: R) -> io::Result<Self> {
183 let cv = read_point(&mut reader, "cv")?;
188 let anchor = read_base(&mut reader, "anchor")?;
191 let nullifier = Self::read_nullifier(&mut reader)?;
192 let rk = Self::read_rk(&mut reader)?;
193 let zkproof = read_zkproof(&mut reader)?;
194 let spend_auth_sig = Self::read_spend_auth_sig(&mut reader)?;
195
196 Ok(SpendDescription {
197 cv,
198 anchor,
199 nullifier,
200 rk,
201 zkproof,
202 spend_auth_sig,
203 })
204 }
205
206 pub fn write_v4<W: Write>(&self, mut writer: W) -> io::Result<()> {
207 writer.write_all(&self.cv.to_bytes())?;
208 writer.write_all(self.anchor.to_repr().as_ref())?;
209 writer.write_all(&self.nullifier.0)?;
210 self.rk.write(&mut writer)?;
211 writer.write_all(&self.zkproof)?;
212 self.spend_auth_sig.write(&mut writer)
213 }
214
215 pub fn write_v5_without_witness_data<W: Write>(&self, mut writer: W) -> io::Result<()> {
216 writer.write_all(&self.cv.to_bytes())?;
217 writer.write_all(&self.nullifier.0)?;
218 self.rk.write(&mut writer)
219 }
220}
221
222#[derive(Clone)]
223pub struct SpendDescriptionV5 {
224 pub cv: ironfish_jubjub::ExtendedPoint,
225 pub nullifier: Nullifier,
226 pub rk: PublicKey,
227}
228
229impl SpendDescriptionV5 {
230 pub fn read<R: Read>(mut reader: &mut R) -> io::Result<Self> {
231 let cv = read_point(&mut reader, "cv")?;
232 let nullifier = SpendDescription::read_nullifier(&mut reader)?;
233 let rk = SpendDescription::read_rk(&mut reader)?;
234
235 Ok(SpendDescriptionV5 { cv, nullifier, rk })
236 }
237
238 pub fn into_spend_description(
239 self,
240 anchor: blstrs::Scalar,
241 zkproof: GrothProofBytes,
242 spend_auth_sig: Signature,
243 ) -> SpendDescription<Authorized> {
244 SpendDescription {
245 cv: self.cv,
246 anchor,
247 nullifier: self.nullifier,
248 rk: self.rk,
249 zkproof,
250 spend_auth_sig,
251 }
252 }
253}
254
255#[derive(Clone)]
256pub struct OutputDescription<Proof> {
257 pub cv: ironfish_jubjub::ExtendedPoint,
258 pub cmu: blstrs::Scalar,
259 pub ephemeral_key: EphemeralKeyBytes,
260 pub enc_ciphertext: [u8; 580],
261 pub out_ciphertext: [u8; 80],
262 pub zkproof: Proof,
263}
264
265impl<P: consensus::Parameters, A> ShieldedOutput<SaplingDomain<P>, ENC_CIPHERTEXT_SIZE>
266 for OutputDescription<A>
267{
268 fn ephemeral_key(&self) -> EphemeralKeyBytes {
269 self.ephemeral_key.clone()
270 }
271
272 fn cmstar_bytes(&self) -> SaplingExtractedCommitmentBytes {
273 SaplingExtractedCommitmentBytes(self.cmu.to_repr())
274 }
275
276 fn enc_ciphertext(&self) -> &[u8; ENC_CIPHERTEXT_SIZE] {
277 &self.enc_ciphertext
278 }
279}
280
281impl<A> std::fmt::Debug for OutputDescription<A> {
282 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
283 write!(
284 f,
285 "OutputDescription(cv = {:?}, cmu = {:?}, ephemeral_key = {:?})",
286 self.cv, self.cmu, self.ephemeral_key
287 )
288 }
289}
290
291impl OutputDescription<GrothProofBytes> {
292 pub fn read<R: Read>(mut reader: &mut R) -> io::Result<Self> {
293 let cv = read_point(&mut reader, "cv")?;
298
299 let cmu = read_base(&mut reader, "cmu")?;
301
302 let mut ephemeral_key = EphemeralKeyBytes([0u8; 32]);
306 reader.read_exact(&mut ephemeral_key.0)?;
307
308 let mut enc_ciphertext = [0u8; 580];
309 let mut out_ciphertext = [0u8; 80];
310 reader.read_exact(&mut enc_ciphertext)?;
311 reader.read_exact(&mut out_ciphertext)?;
312
313 let zkproof = read_zkproof(&mut reader)?;
314
315 Ok(OutputDescription {
316 cv,
317 cmu,
318 ephemeral_key,
319 enc_ciphertext,
320 out_ciphertext,
321 zkproof,
322 })
323 }
324
325 pub fn write_v4<W: Write>(&self, mut writer: W) -> io::Result<()> {
326 writer.write_all(&self.cv.to_bytes())?;
327 writer.write_all(self.cmu.to_repr().as_ref())?;
328 writer.write_all(self.ephemeral_key.as_ref())?;
329 writer.write_all(&self.enc_ciphertext)?;
330 writer.write_all(&self.out_ciphertext)?;
331 writer.write_all(&self.zkproof)
332 }
333
334 pub fn write_v5_without_proof<W: Write>(&self, mut writer: W) -> io::Result<()> {
335 writer.write_all(&self.cv.to_bytes())?;
336 writer.write_all(self.cmu.to_repr().as_ref())?;
337 writer.write_all(self.ephemeral_key.as_ref())?;
338 writer.write_all(&self.enc_ciphertext)?;
339 writer.write_all(&self.out_ciphertext)
340 }
341}
342
343#[derive(Clone)]
344pub struct OutputDescriptionV5 {
345 pub cv: ironfish_jubjub::ExtendedPoint,
346 pub cmu: blstrs::Scalar,
347 pub ephemeral_key: EphemeralKeyBytes,
348 pub enc_ciphertext: [u8; 580],
349 pub out_ciphertext: [u8; 80],
350}
351
352impl OutputDescriptionV5 {
353 pub fn read<R: Read>(mut reader: &mut R) -> io::Result<Self> {
354 let cv = read_point(&mut reader, "cv")?;
355 let cmu = read_base(&mut reader, "cmu")?;
356
357 let mut ephemeral_key = EphemeralKeyBytes([0u8; 32]);
361 reader.read_exact(&mut ephemeral_key.0)?;
362
363 let mut enc_ciphertext = [0u8; 580];
364 let mut out_ciphertext = [0u8; 80];
365 reader.read_exact(&mut enc_ciphertext)?;
366 reader.read_exact(&mut out_ciphertext)?;
367
368 Ok(OutputDescriptionV5 {
369 cv,
370 cmu,
371 ephemeral_key,
372 enc_ciphertext,
373 out_ciphertext,
374 })
375 }
376
377 pub fn into_output_description(
378 self,
379 zkproof: GrothProofBytes,
380 ) -> OutputDescription<GrothProofBytes> {
381 OutputDescription {
382 cv: self.cv,
383 cmu: self.cmu,
384 ephemeral_key: self.ephemeral_key,
385 enc_ciphertext: self.enc_ciphertext,
386 out_ciphertext: self.out_ciphertext,
387 zkproof,
388 }
389 }
390}
391
392pub struct CompactOutputDescription {
393 pub ephemeral_key: EphemeralKeyBytes,
394 pub cmu: blstrs::Scalar,
395 pub enc_ciphertext: [u8; COMPACT_NOTE_SIZE],
396}
397
398impl<A> From<OutputDescription<A>> for CompactOutputDescription {
399 fn from(out: OutputDescription<A>) -> CompactOutputDescription {
400 CompactOutputDescription {
401 ephemeral_key: out.ephemeral_key,
402 cmu: out.cmu,
403 enc_ciphertext: out.enc_ciphertext[..COMPACT_NOTE_SIZE].try_into().unwrap(),
404 }
405 }
406}
407
408impl<P: consensus::Parameters> ShieldedOutput<SaplingDomain<P>, COMPACT_NOTE_SIZE>
409 for CompactOutputDescription
410{
411 fn ephemeral_key(&self) -> EphemeralKeyBytes {
412 self.ephemeral_key.clone()
413 }
414
415 fn cmstar_bytes(&self) -> SaplingExtractedCommitmentBytes {
416 SaplingExtractedCommitmentBytes(self.cmu.to_repr())
417 }
418
419 fn enc_ciphertext(&self) -> &[u8; COMPACT_NOTE_SIZE] {
420 &self.enc_ciphertext
421 }
422}
423
424#[cfg(any(test, feature = "test-dependencies"))]
425pub mod testing {
426 use ff::Field;
427 use group::{Group, GroupEncoding};
428 use proptest::collection::vec;
429 use proptest::prelude::*;
430 use rand::{rngs::StdRng, SeedableRng};
431 use std::convert::TryFrom;
432
433 use crate::{
434 constants::{SPENDING_KEY_GENERATOR, VALUE_COMMITMENT_RANDOMNESS_GENERATOR},
435 sapling::{
436 redjubjub::{PrivateKey, PublicKey},
437 Nullifier,
438 },
439 transaction::{
440 components::{amount::testing::arb_amount, GROTH_PROOF_SIZE},
441 TxVersion,
442 },
443 };
444
445 use super::{Authorized, Bundle, GrothProofBytes, OutputDescription, SpendDescription};
446
447 prop_compose! {
448 fn arb_extended_point()(rng_seed in prop::array::uniform32(any::<u8>())) -> ironfish_jubjub::ExtendedPoint {
449 let mut rng = StdRng::from_seed(rng_seed);
450 let scalar = ironfish_jubjub::Scalar::random(&mut rng);
451 ironfish_jubjub::ExtendedPoint::generator() * scalar
452 }
453 }
454
455 prop_compose! {
456 fn arb_spend_description()(
459 cv in arb_extended_point(),
460 anchor in vec(any::<u8>(), 32)
461 .prop_map(|v| <[u8;32]>::try_from(v.as_slice()).unwrap())
462 .prop_map(|mut v| { v[0] = 0; v })
463 .prop_map(|v| blstrs::Scalar::from_bytes_be(&v))
464 .prop_map(|v| Option::from(v).unwrap()),
465 nullifier in prop::array::uniform32(any::<u8>())
466 .prop_map(|v| Nullifier::from_slice(&v).unwrap()),
467 zkproof in vec(any::<u8>(), GROTH_PROOF_SIZE)
468 .prop_map(|v| <[u8;GROTH_PROOF_SIZE]>::try_from(v.as_slice()).unwrap()),
469 rng_seed in prop::array::uniform32(prop::num::u8::ANY),
470 fake_sighash_bytes in prop::array::uniform32(prop::num::u8::ANY),
471 ) -> SpendDescription<Authorized> {
472 let mut rng = StdRng::from_seed(rng_seed);
473 let sk1 = PrivateKey(ironfish_jubjub::Fr::random(&mut rng));
474 let rk = PublicKey::from_private(&sk1, *SPENDING_KEY_GENERATOR);
475 SpendDescription {
476 cv,
477 anchor,
478 nullifier,
479 rk,
480 zkproof,
481 spend_auth_sig: sk1.sign(&fake_sighash_bytes, &mut rng, *SPENDING_KEY_GENERATOR),
482 }
483 }
484 }
485
486 prop_compose! {
487 pub fn arb_output_description()(
490 cv in arb_extended_point(),
491 cmu in vec(any::<u8>(), 32)
492 .prop_map(|v| <[u8;32]>::try_from(v.as_slice()).unwrap())
493 .prop_map(|mut v| { v[0] = 0; v })
494 .prop_map(|v| blstrs::Scalar::from_bytes_be(&v))
495 .prop_map(|v| Option::from(v).unwrap()),
496 enc_ciphertext in vec(any::<u8>(), 580)
497 .prop_map(|v| <[u8;580]>::try_from(v.as_slice()).unwrap()),
498 epk in arb_extended_point(),
499 out_ciphertext in vec(any::<u8>(), 80)
500 .prop_map(|v| <[u8;80]>::try_from(v.as_slice()).unwrap()),
501 zkproof in vec(any::<u8>(), GROTH_PROOF_SIZE)
502 .prop_map(|v| <[u8;GROTH_PROOF_SIZE]>::try_from(v.as_slice()).unwrap()),
503 ) -> OutputDescription<GrothProofBytes> {
504 OutputDescription {
505 cv,
506 cmu,
507 ephemeral_key: epk.to_bytes().into(),
508 enc_ciphertext,
509 out_ciphertext,
510 zkproof,
511 }
512 }
513 }
514
515 prop_compose! {
516 pub fn arb_bundle()(
517 shielded_spends in vec(arb_spend_description(), 0..30),
518 shielded_outputs in vec(arb_output_description(), 0..30),
519 value_balance in arb_amount(),
520 rng_seed in prop::array::uniform32(prop::num::u8::ANY),
521 fake_bvk_bytes in prop::array::uniform32(prop::num::u8::ANY),
522 ) -> Option<Bundle<Authorized>> {
523 if shielded_spends.is_empty() && shielded_outputs.is_empty() {
524 None
525 } else {
526 let mut rng = StdRng::from_seed(rng_seed);
527 let bsk = PrivateKey(ironfish_jubjub::Fr::random(&mut rng));
528
529 Some(
530 Bundle {
531 shielded_spends,
532 shielded_outputs,
533 value_balance,
534 authorization: Authorized { binding_sig: bsk.sign(&fake_bvk_bytes, &mut rng, *VALUE_COMMITMENT_RANDOMNESS_GENERATOR) },
535 }
536 )
537 }
538 }
539 }
540
541 pub fn arb_bundle_for_version(
542 v: TxVersion,
543 ) -> impl Strategy<Value = Option<Bundle<Authorized>>> {
544 if v.has_sapling() {
545 Strategy::boxed(arb_bundle())
546 } else {
547 Strategy::boxed(Just(None))
548 }
549 }
550}