1use alloc::vec::Vec;
6use blake2b_simd::{Hash as Blake2bHash, Params as Blake2bParams};
7use ff::PrimeField;
8use memuse::DynamicUsage;
9use rand_core::Rng;
10
11use zcash_note_encryption::{
12 BatchDomain, COMPACT_NOTE_SIZE, Domain, ENC_CIPHERTEXT_SIZE, EphemeralKeyBytes,
13 NOTE_PLAINTEXT_SIZE, NoteEncryption, NotePlaintextBytes, OUT_PLAINTEXT_SIZE, OutPlaintextBytes,
14 OutgoingCipherKey, ShieldedOutput, try_compact_note_decryption, try_note_decryption,
15 try_output_recovery_with_ock, try_output_recovery_with_ovk,
16};
17
18use crate::{
19 Diversifier, Note, PaymentAddress, Rseed,
20 bundle::{GrothProofBytes, OutputDescription},
21 keys::{
22 DiversifiedTransmissionKey, EphemeralPublicKey, EphemeralSecretKey, OutgoingViewingKey,
23 SharedSecret,
24 },
25 value::{NoteValue, ValueCommitment},
26};
27
28use super::note::ExtractedNoteCommitment;
29
30pub use crate::keys::{PreparedEphemeralPublicKey, PreparedIncomingViewingKey};
31
32pub const KDF_SAPLING_PERSONALIZATION: &[u8; 16] = b"Zcash_SaplingKDF";
33pub const PRF_OCK_PERSONALIZATION: &[u8; 16] = b"Zcash_Derive_ock";
34
35pub fn prf_ock(
39 ovk: &OutgoingViewingKey,
40 cv: &ValueCommitment,
41 cmu_bytes: &[u8; 32],
42 ephemeral_key: &EphemeralKeyBytes,
43) -> OutgoingCipherKey {
44 OutgoingCipherKey(
45 Blake2bParams::new()
46 .hash_length(32)
47 .personal(PRF_OCK_PERSONALIZATION)
48 .to_state()
49 .update(&ovk.0)
50 .update(&cv.to_bytes())
51 .update(cmu_bytes)
52 .update(ephemeral_key.as_ref())
53 .finalize()
54 .as_bytes()
55 .try_into()
56 .unwrap(),
57 )
58}
59
60#[derive(Clone, Copy, Debug, PartialEq, Eq)]
63pub enum Zip212Enforcement {
64 Off,
65 GracePeriod,
66 On,
67}
68
69fn sapling_parse_note_plaintext_without_memo<F>(
72 domain: &SaplingDomain,
73 plaintext: &[u8],
74 get_pk_d: F,
75) -> Option<(Note, PaymentAddress)>
76where
77 F: FnOnce(&Diversifier) -> Option<DiversifiedTransmissionKey>,
78{
79 assert!(plaintext.len() >= COMPACT_NOTE_SIZE);
80
81 if !plaintext_version_is_valid(domain.zip212_enforcement, plaintext[0]) {
83 return None;
84 }
85
86 let diversifier = Diversifier(
88 plaintext[1..12]
89 .try_into()
90 .expect("Note plaintext is checked to have length >= COMPACT_NOTE_SIZE."),
91 );
92 let value = NoteValue::from_bytes(
93 plaintext[12..20]
94 .try_into()
95 .expect("Note plaintext is checked to have length >= COMPACT_NOTE_SIZE."),
96 );
97 let r: [u8; 32] = plaintext[20..COMPACT_NOTE_SIZE]
98 .try_into()
99 .expect("Note plaintext is checked to have length >= COMPACT_NOTE_SIZE.");
100
101 let rseed = if plaintext[0] == 0x01 {
102 let rcm = Option::from(jubjub::Fr::from_repr(r))?;
103 Rseed::BeforeZip212(rcm)
104 } else {
105 Rseed::AfterZip212(r)
106 };
107
108 let pk_d = get_pk_d(&diversifier)?;
109
110 let to = PaymentAddress::from_parts_unchecked(diversifier, pk_d)?;
112 let note = to.create_note(value, rseed);
113 Some((note, to))
114}
115
116pub struct SaplingDomain {
117 zip212_enforcement: Zip212Enforcement,
118}
119
120memuse::impl_no_dynamic_usage!(SaplingDomain);
121
122impl SaplingDomain {
123 pub fn new(zip212_enforcement: Zip212Enforcement) -> Self {
124 Self { zip212_enforcement }
125 }
126}
127
128impl Domain for SaplingDomain {
129 type EphemeralSecretKey = EphemeralSecretKey;
130 type EphemeralPublicKey = EphemeralPublicKey;
134 type PreparedEphemeralPublicKey = PreparedEphemeralPublicKey;
135 type SharedSecret = SharedSecret;
136 type SymmetricKey = Blake2bHash;
137 type Note = Note;
138 type Recipient = PaymentAddress;
139 type DiversifiedTransmissionKey = DiversifiedTransmissionKey;
140 type IncomingViewingKey = PreparedIncomingViewingKey;
141 type OutgoingViewingKey = OutgoingViewingKey;
142 type ValueCommitment = ValueCommitment;
143 type ExtractedCommitment = ExtractedNoteCommitment;
144 type ExtractedCommitmentBytes = [u8; 32];
145 type Memo = [u8; 512];
146
147 fn derive_esk(note: &Self::Note) -> Option<Self::EphemeralSecretKey> {
148 note.derive_esk()
149 }
150
151 fn get_pk_d(note: &Self::Note) -> Self::DiversifiedTransmissionKey {
152 *note.recipient().pk_d()
153 }
154
155 fn prepare_epk(epk: Self::EphemeralPublicKey) -> Self::PreparedEphemeralPublicKey {
156 PreparedEphemeralPublicKey::new(epk)
157 }
158
159 fn ka_derive_public(
160 note: &Self::Note,
161 esk: &Self::EphemeralSecretKey,
162 ) -> Self::EphemeralPublicKey {
163 esk.derive_public(note.recipient().g_d().into())
164 }
165
166 fn ka_agree_enc(
167 esk: &Self::EphemeralSecretKey,
168 pk_d: &Self::DiversifiedTransmissionKey,
169 ) -> Self::SharedSecret {
170 esk.agree(pk_d)
171 }
172
173 fn ka_agree_dec(
174 ivk: &Self::IncomingViewingKey,
175 epk: &Self::PreparedEphemeralPublicKey,
176 ) -> Self::SharedSecret {
177 epk.agree(ivk)
178 }
179
180 fn kdf(dhsecret: SharedSecret, epk: &EphemeralKeyBytes) -> Blake2bHash {
184 dhsecret.kdf_sapling(epk)
185 }
186
187 fn note_plaintext_bytes(note: &Self::Note, memo: &Self::Memo) -> NotePlaintextBytes {
188 let mut input = [0; NOTE_PLAINTEXT_SIZE];
191 input[0] = match note.rseed() {
192 Rseed::BeforeZip212(_) => 1,
193 Rseed::AfterZip212(_) => 2,
194 };
195 input[1..12].copy_from_slice(¬e.recipient().diversifier().0);
196 input[12..20].copy_from_slice(¬e.value().inner().to_le_bytes());
197
198 match note.rseed() {
199 Rseed::BeforeZip212(rcm) => {
200 input[20..COMPACT_NOTE_SIZE].copy_from_slice(rcm.to_repr().as_ref());
201 }
202 Rseed::AfterZip212(rseed) => {
203 input[20..COMPACT_NOTE_SIZE].copy_from_slice(rseed);
204 }
205 }
206
207 input[COMPACT_NOTE_SIZE..NOTE_PLAINTEXT_SIZE].copy_from_slice(&memo[..]);
208
209 NotePlaintextBytes(input)
210 }
211
212 fn derive_ock(
213 ovk: &Self::OutgoingViewingKey,
214 cv: &Self::ValueCommitment,
215 cmu_bytes: &Self::ExtractedCommitmentBytes,
216 epk: &EphemeralKeyBytes,
217 ) -> OutgoingCipherKey {
218 prf_ock(ovk, cv, cmu_bytes, epk)
219 }
220
221 fn outgoing_plaintext_bytes(
222 note: &Self::Note,
223 esk: &Self::EphemeralSecretKey,
224 ) -> OutPlaintextBytes {
225 let mut input = [0u8; OUT_PLAINTEXT_SIZE];
226 input[0..32].copy_from_slice(¬e.recipient().pk_d().to_bytes());
227 input[32..OUT_PLAINTEXT_SIZE].copy_from_slice(esk.0.to_repr().as_ref());
228
229 OutPlaintextBytes(input)
230 }
231
232 fn epk_bytes(epk: &Self::EphemeralPublicKey) -> EphemeralKeyBytes {
233 epk.to_bytes()
234 }
235
236 fn epk(ephemeral_key: &EphemeralKeyBytes) -> Option<Self::EphemeralPublicKey> {
237 EphemeralPublicKey::from_bytes(&ephemeral_key.0).into()
241 }
242
243 fn parse_note_plaintext_without_memo_ivk(
244 &self,
245 ivk: &Self::IncomingViewingKey,
246 plaintext: &[u8],
247 ) -> Option<(Self::Note, Self::Recipient)> {
248 sapling_parse_note_plaintext_without_memo(self, plaintext, |diversifier| {
249 DiversifiedTransmissionKey::derive(ivk, diversifier)
250 })
251 }
252
253 fn parse_note_plaintext_without_memo_ovk(
254 &self,
255 pk_d: &Self::DiversifiedTransmissionKey,
256 plaintext: &NotePlaintextBytes,
257 ) -> Option<(Self::Note, Self::Recipient)> {
258 sapling_parse_note_plaintext_without_memo(self, &plaintext.0, |diversifier| {
259 diversifier.g_d().map(|_| *pk_d)
260 })
261 }
262
263 fn cmstar(note: &Self::Note) -> Self::ExtractedCommitment {
264 note.cmu()
265 }
266
267 fn extract_pk_d(op: &OutPlaintextBytes) -> Option<Self::DiversifiedTransmissionKey> {
268 DiversifiedTransmissionKey::from_bytes(
269 op.0[0..32].try_into().expect("slice is the correct length"),
270 )
271 .into()
272 }
273
274 fn extract_esk(op: &OutPlaintextBytes) -> Option<Self::EphemeralSecretKey> {
275 EphemeralSecretKey::from_bytes(
276 op.0[32..OUT_PLAINTEXT_SIZE]
277 .try_into()
278 .expect("slice is the correct length"),
279 )
280 .into()
281 }
282
283 fn extract_memo(&self, plaintext: &NotePlaintextBytes) -> Self::Memo {
284 plaintext.0[COMPACT_NOTE_SIZE..NOTE_PLAINTEXT_SIZE]
285 .try_into()
286 .expect("correct length")
287 }
288}
289
290impl BatchDomain for SaplingDomain {
291 fn batch_kdf<'a>(
292 items: impl Iterator<Item = (Option<Self::SharedSecret>, &'a EphemeralKeyBytes)>,
293 ) -> Vec<Option<Self::SymmetricKey>> {
294 let (shared_secrets, ephemeral_keys): (Vec<_>, Vec<_>) = items.unzip();
295
296 SharedSecret::batch_to_affine(shared_secrets)
297 .zip(ephemeral_keys)
298 .map(|(secret, ephemeral_key)| {
299 secret.map(|dhsecret| SharedSecret::kdf_sapling_inner(dhsecret, ephemeral_key))
300 })
301 .collect()
302 }
303
304 fn batch_epk(
305 ephemeral_keys: impl Iterator<Item = EphemeralKeyBytes>,
306 ) -> Vec<(Option<Self::PreparedEphemeralPublicKey>, EphemeralKeyBytes)> {
307 let ephemeral_keys: Vec<_> = ephemeral_keys.collect();
308 let epks = jubjub::AffinePoint::batch_from_bytes(ephemeral_keys.iter().map(|b| b.0));
309 epks.into_iter()
310 .zip(ephemeral_keys)
311 .map(|(epk, ephemeral_key)| {
312 (
313 Option::from(epk)
314 .map(EphemeralPublicKey::from_affine)
315 .map(Self::prepare_epk),
316 ephemeral_key,
317 )
318 })
319 .collect()
320 }
321}
322
323#[derive(Clone)]
324pub struct CompactOutputDescription {
325 pub ephemeral_key: EphemeralKeyBytes,
326 pub cmu: ExtractedNoteCommitment,
327 pub enc_ciphertext: [u8; COMPACT_NOTE_SIZE],
328}
329
330memuse::impl_no_dynamic_usage!(CompactOutputDescription);
331
332impl ShieldedOutput<SaplingDomain, COMPACT_NOTE_SIZE> for CompactOutputDescription {
333 fn ephemeral_key(&self) -> EphemeralKeyBytes {
334 self.ephemeral_key.clone()
335 }
336
337 fn cmstar_bytes(&self) -> [u8; 32] {
338 self.cmu.to_bytes()
339 }
340
341 fn enc_ciphertext(&self) -> &[u8; COMPACT_NOTE_SIZE] {
342 &self.enc_ciphertext
343 }
344}
345
346pub fn sapling_note_encryption<R: Rng>(
389 ovk: Option<OutgoingViewingKey>,
390 note: Note,
391 memo: [u8; 512],
392 rng: &mut R,
393) -> NoteEncryption<SaplingDomain> {
394 let esk = note.generate_or_derive_esk_internal(rng);
395 NoteEncryption::new_with_esk(esk, ovk, note, memo)
396}
397
398#[allow(clippy::if_same_then_else)]
399#[allow(clippy::needless_bool)]
400pub fn plaintext_version_is_valid(zip212_enforcement: Zip212Enforcement, leadbyte: u8) -> bool {
401 match zip212_enforcement {
402 Zip212Enforcement::Off => leadbyte == 0x01,
403 Zip212Enforcement::GracePeriod => leadbyte == 0x01 || leadbyte == 0x02,
404 Zip212Enforcement::On => leadbyte == 0x02,
405 }
406}
407
408pub fn try_sapling_note_decryption<Output: ShieldedOutput<SaplingDomain, ENC_CIPHERTEXT_SIZE>>(
409 ivk: &PreparedIncomingViewingKey,
410 output: &Output,
411 zip212_enforcement: Zip212Enforcement,
412) -> Option<(Note, PaymentAddress, [u8; 512])> {
413 let domain = SaplingDomain::new(zip212_enforcement);
414 try_note_decryption(&domain, ivk, output)
415}
416
417pub fn try_sapling_compact_note_decryption<
418 Output: ShieldedOutput<SaplingDomain, COMPACT_NOTE_SIZE>,
419>(
420 ivk: &PreparedIncomingViewingKey,
421 output: &Output,
422 zip212_enforcement: Zip212Enforcement,
423) -> Option<(Note, PaymentAddress)> {
424 let domain = SaplingDomain::new(zip212_enforcement);
425 try_compact_note_decryption(&domain, ivk, output)
426}
427
428pub fn try_sapling_output_recovery_with_ock(
437 ock: &OutgoingCipherKey,
438 output: &OutputDescription<GrothProofBytes>,
439 zip212_enforcement: Zip212Enforcement,
440) -> Option<(Note, PaymentAddress, [u8; 512])> {
441 let domain = SaplingDomain::new(zip212_enforcement);
442 try_output_recovery_with_ock(&domain, ock, output, output.out_ciphertext())
443}
444
445#[allow(clippy::too_many_arguments)]
453pub fn try_sapling_output_recovery(
454 ovk: &OutgoingViewingKey,
455 output: &OutputDescription<GrothProofBytes>,
456 zip212_enforcement: Zip212Enforcement,
457) -> Option<(Note, PaymentAddress, [u8; 512])> {
458 let domain = SaplingDomain::new(zip212_enforcement);
459 try_output_recovery_with_ovk(&domain, ovk, output, output.cv(), output.out_ciphertext())
460}
461
462#[cfg(test)]
463mod tests {
464 use alloc::vec::Vec;
465 use chacha20poly1305::{
466 ChaCha20Poly1305,
467 aead::{AeadInOut, KeyInit},
468 };
469 use ff::{Field, PrimeField};
470 use group::Group;
471 use group::GroupEncoding;
472 use rand_core::{CryptoRng, Rng};
473
474 use zcash_note_encryption::{
475 ENC_CIPHERTEXT_SIZE, EphemeralKeyBytes, NOTE_PLAINTEXT_SIZE, NoteEncryption,
476 OUT_CIPHERTEXT_SIZE, OUT_PLAINTEXT_SIZE, OutgoingCipherKey, batch,
477 };
478
479 use super::{
480 CompactOutputDescription, SaplingDomain, Zip212Enforcement, prf_ock,
481 sapling_note_encryption, try_sapling_compact_note_decryption, try_sapling_note_decryption,
482 try_sapling_output_recovery, try_sapling_output_recovery_with_ock,
483 };
484
485 use crate::{
486 Diversifier, PaymentAddress, Rseed, SaplingIvk,
487 bundle::{GrothProofBytes, OutputDescription},
488 constants::GROTH_PROOF_SIZE,
489 keys::{DiversifiedTransmissionKey, EphemeralSecretKey, OutgoingViewingKey},
490 note::ExtractedNoteCommitment,
491 note_encryption::PreparedIncomingViewingKey,
492 rng_compat::{OsRng, RngCore06},
493 util::generate_random_rseed,
494 value::{NoteValue, ValueCommitTrapdoor, ValueCommitment},
495 };
496
497 fn random_enc_ciphertext<R: Rng + CryptoRng>(
498 zip212_enforcement: Zip212Enforcement,
499 mut rng: &mut R,
500 ) -> (
501 OutgoingViewingKey,
502 OutgoingCipherKey,
503 PreparedIncomingViewingKey,
504 OutputDescription<GrothProofBytes>,
505 ) {
506 let ivk = SaplingIvk(jubjub::Fr::random(&mut rng));
507 let prepared_ivk = PreparedIncomingViewingKey::new(&ivk);
508
509 let (ovk, ock, output) = random_enc_ciphertext_with(&ivk, zip212_enforcement, rng);
510
511 assert!(try_sapling_note_decryption(&prepared_ivk, &output, zip212_enforcement).is_some());
512 assert!(
513 try_sapling_compact_note_decryption(
514 &prepared_ivk,
515 &CompactOutputDescription::from(output.clone()),
516 zip212_enforcement,
517 )
518 .is_some()
519 );
520
521 let ovk_output_recovery = try_sapling_output_recovery(&ovk, &output, zip212_enforcement);
522
523 let ock_output_recovery =
524 try_sapling_output_recovery_with_ock(&ock, &output, zip212_enforcement);
525 assert!(ovk_output_recovery.is_some());
526 assert!(ock_output_recovery.is_some());
527 assert_eq!(ovk_output_recovery, ock_output_recovery);
528
529 (ovk, ock, prepared_ivk, output)
530 }
531
532 fn random_enc_ciphertext_with<R: Rng + CryptoRng>(
533 ivk: &SaplingIvk,
534 zip212_enforcement: Zip212Enforcement,
535 mut rng: &mut R,
536 ) -> (
537 OutgoingViewingKey,
538 OutgoingCipherKey,
539 OutputDescription<GrothProofBytes>,
540 ) {
541 let diversifier = Diversifier([0; 11]);
542 let pa = ivk.to_payment_address(diversifier).unwrap();
543
544 let value = NoteValue::from_raw(100);
546 let rcv = ValueCommitTrapdoor::random(&mut rng);
547 let cv = ValueCommitment::derive(value, rcv);
548
549 let rseed = generate_random_rseed(zip212_enforcement, &mut rng);
550
551 let note = pa.create_note(value, rseed);
552 let cmu = note.cmu();
553
554 let ovk = OutgoingViewingKey([0; 32]);
555 let ne = sapling_note_encryption(Some(ovk), note, [0x37; 512], &mut rng);
556 let epk = ne.epk();
557 let ock = prf_ock(&ovk, &cv, &cmu.to_bytes(), &epk.to_bytes());
558
559 let out_ciphertext = ne.encrypt_outgoing_plaintext(&cv, &cmu, &mut RngCore06::new(rng));
560 let output = OutputDescription::from_parts(
561 cv,
562 cmu,
563 epk.to_bytes(),
564 ne.encrypt_note_plaintext(),
565 out_ciphertext,
566 [0u8; GROTH_PROOF_SIZE],
567 );
568
569 (ovk, ock, output)
570 }
571
572 fn reencrypt_out_ciphertext(
573 ovk: &OutgoingViewingKey,
574 cv: &ValueCommitment,
575 cmu: &ExtractedNoteCommitment,
576 ephemeral_key: &EphemeralKeyBytes,
577 out_ciphertext: &[u8; OUT_CIPHERTEXT_SIZE],
578 modify_plaintext: impl Fn(&mut [u8; OUT_PLAINTEXT_SIZE]),
579 ) -> [u8; OUT_CIPHERTEXT_SIZE] {
580 let ock = prf_ock(ovk, cv, &cmu.to_bytes(), ephemeral_key);
581
582 let mut op = [0; OUT_PLAINTEXT_SIZE];
583 op.copy_from_slice(&out_ciphertext[..OUT_PLAINTEXT_SIZE]);
584
585 ChaCha20Poly1305::new_from_slice(ock.as_ref())
586 .unwrap()
587 .decrypt_inout_detached(
588 (&[0u8; 12]).into(),
589 &[],
590 (&mut op[..]).into(),
591 out_ciphertext[OUT_PLAINTEXT_SIZE..].try_into().unwrap(),
592 )
593 .unwrap();
594
595 modify_plaintext(&mut op);
596
597 let tag = ChaCha20Poly1305::new_from_slice(ock.as_ref())
598 .unwrap()
599 .encrypt_inout_detached((&[0u8; 12]).into(), &[], (&mut op[..]).into())
600 .unwrap();
601
602 let mut out_ciphertext = [0u8; OUT_CIPHERTEXT_SIZE];
603 out_ciphertext[..OUT_PLAINTEXT_SIZE].copy_from_slice(&op);
604 out_ciphertext[OUT_PLAINTEXT_SIZE..].copy_from_slice(&tag);
605 out_ciphertext
606 }
607
608 fn reencrypt_enc_ciphertext(
609 ovk: &OutgoingViewingKey,
610 cv: &ValueCommitment,
611 cmu: &ExtractedNoteCommitment,
612 ephemeral_key: &EphemeralKeyBytes,
613 enc_ciphertext: &[u8; ENC_CIPHERTEXT_SIZE],
614 out_ciphertext: &[u8; OUT_CIPHERTEXT_SIZE],
615 modify_plaintext: impl Fn(&mut [u8; NOTE_PLAINTEXT_SIZE]),
616 ) -> [u8; ENC_CIPHERTEXT_SIZE] {
617 let ock = prf_ock(ovk, cv, &cmu.to_bytes(), ephemeral_key);
618
619 let mut op = [0; OUT_PLAINTEXT_SIZE];
620 op.copy_from_slice(&out_ciphertext[..OUT_PLAINTEXT_SIZE]);
621
622 ChaCha20Poly1305::new_from_slice(ock.as_ref())
623 .unwrap()
624 .decrypt_inout_detached(
625 (&[0u8; 12]).into(),
626 &[],
627 (&mut op[..]).into(),
628 out_ciphertext[OUT_PLAINTEXT_SIZE..].try_into().unwrap(),
629 )
630 .unwrap();
631
632 let pk_d = DiversifiedTransmissionKey::from_bytes(&op[0..32].try_into().unwrap()).unwrap();
633
634 let esk = jubjub::Fr::from_repr(op[32..OUT_PLAINTEXT_SIZE].try_into().unwrap()).unwrap();
635
636 let shared_secret = EphemeralSecretKey(esk).agree(&pk_d);
637 let key = shared_secret.kdf_sapling(ephemeral_key);
638
639 let mut plaintext = [0; NOTE_PLAINTEXT_SIZE];
640 plaintext.copy_from_slice(&enc_ciphertext[..NOTE_PLAINTEXT_SIZE]);
641
642 ChaCha20Poly1305::new_from_slice(key.as_bytes())
643 .unwrap()
644 .decrypt_inout_detached(
645 (&[0u8; 12]).into(),
646 &[],
647 (&mut plaintext[..]).into(),
648 enc_ciphertext[NOTE_PLAINTEXT_SIZE..].try_into().unwrap(),
649 )
650 .unwrap();
651
652 modify_plaintext(&mut plaintext);
653
654 let tag = ChaCha20Poly1305::new_from_slice(key.as_ref())
655 .unwrap()
656 .encrypt_inout_detached((&[0u8; 12]).into(), &[], (&mut plaintext[..]).into())
657 .unwrap();
658
659 let mut enc_ciphertext = [0u8; ENC_CIPHERTEXT_SIZE];
660 enc_ciphertext[..NOTE_PLAINTEXT_SIZE].copy_from_slice(&plaintext);
661 enc_ciphertext[NOTE_PLAINTEXT_SIZE..].copy_from_slice(&tag);
662 enc_ciphertext
663 }
664
665 fn find_invalid_diversifier() -> Diversifier {
666 let mut d = Diversifier([0; 11]);
668 loop {
669 for k in 0..11 {
670 d.0[k] = d.0[k].wrapping_add(1);
671 if d.0[k] != 0 {
672 break;
673 }
674 }
675 if d.g_d().is_none() {
676 break;
677 }
678 }
679 d
680 }
681
682 fn find_valid_diversifier() -> Diversifier {
683 let mut d = Diversifier([0; 11]);
685 loop {
686 for k in 0..11 {
687 d.0[k] = d.0[k].wrapping_add(1);
688 if d.0[k] != 0 {
689 break;
690 }
691 }
692 if d.g_d().is_some() {
693 break;
694 }
695 }
696 d
697 }
698
699 #[test]
700 fn decryption_with_invalid_ivk() {
701 let mut rng = OsRng;
702 let zip212_states = [
703 Zip212Enforcement::Off,
704 Zip212Enforcement::GracePeriod,
705 Zip212Enforcement::On,
706 ];
707
708 for zip212_enforcement in zip212_states {
709 let (_, _, _, output) = random_enc_ciphertext(zip212_enforcement, &mut rng);
710
711 assert_eq!(
712 try_sapling_note_decryption(
713 &PreparedIncomingViewingKey::new(&SaplingIvk(jubjub::Fr::random(&mut rng))),
714 &output,
715 zip212_enforcement,
716 ),
717 None
718 );
719 }
720 }
721
722 #[test]
723 fn decryption_with_invalid_epk() {
724 let mut rng = OsRng;
725 let zip212_states = [
726 Zip212Enforcement::Off,
727 Zip212Enforcement::GracePeriod,
728 Zip212Enforcement::On,
729 ];
730
731 for zip212_enforcement in zip212_states {
732 let (_, _, ivk, mut output) = random_enc_ciphertext(zip212_enforcement, &mut rng);
733
734 *output.ephemeral_key_mut() = jubjub::ExtendedPoint::random(&mut rng).to_bytes().into();
735
736 assert_eq!(
737 try_sapling_note_decryption(&ivk, &output, zip212_enforcement,),
738 None
739 );
740 }
741 }
742
743 #[test]
744 fn decryption_with_invalid_cmu() {
745 let mut rng = OsRng;
746 let zip212_states = [
747 Zip212Enforcement::Off,
748 Zip212Enforcement::GracePeriod,
749 Zip212Enforcement::On,
750 ];
751
752 for zip212_enforcement in zip212_states {
753 let (_, _, ivk, mut output) = random_enc_ciphertext(zip212_enforcement, &mut rng);
754 *output.cmu_mut() =
755 ExtractedNoteCommitment::from_bytes(&bls12_381::Scalar::random(&mut rng).to_repr())
756 .unwrap();
757
758 assert_eq!(
759 try_sapling_note_decryption(&ivk, &output, zip212_enforcement,),
760 None
761 );
762 }
763 }
764
765 #[test]
766 fn decryption_with_invalid_tag() {
767 let mut rng = OsRng;
768 let zip212_states = [
769 Zip212Enforcement::Off,
770 Zip212Enforcement::GracePeriod,
771 Zip212Enforcement::On,
772 ];
773
774 for zip212_enforcement in zip212_states {
775 let (_, _, ivk, mut output) = random_enc_ciphertext(zip212_enforcement, &mut rng);
776 output.enc_ciphertext_mut()[ENC_CIPHERTEXT_SIZE - 1] ^= 0xff;
777
778 assert_eq!(
779 try_sapling_note_decryption(&ivk, &output, zip212_enforcement,),
780 None
781 );
782 }
783 }
784
785 #[test]
786 fn decryption_with_invalid_version_byte() {
787 let mut rng = OsRng;
788 let zip212_states = [
789 Zip212Enforcement::Off,
790 Zip212Enforcement::GracePeriod,
791 Zip212Enforcement::On,
792 ];
793 let leadbytes = [0x02, 0x03, 0x01];
794
795 for (zip212_enforcement, leadbyte) in zip212_states.into_iter().zip(leadbytes) {
796 let (ovk, _, ivk, mut output) = random_enc_ciphertext(zip212_enforcement, &mut rng);
797
798 *output.enc_ciphertext_mut() = reencrypt_enc_ciphertext(
799 &ovk,
800 output.cv(),
801 output.cmu(),
802 output.ephemeral_key(),
803 output.enc_ciphertext(),
804 output.out_ciphertext(),
805 |pt| pt[0] = leadbyte,
806 );
807 assert_eq!(
808 try_sapling_note_decryption(&ivk, &output, zip212_enforcement),
809 None
810 );
811 }
812 }
813
814 #[test]
815 fn decryption_with_invalid_diversifier() {
816 let mut rng = OsRng;
817 let zip212_states = [
818 Zip212Enforcement::Off,
819 Zip212Enforcement::GracePeriod,
820 Zip212Enforcement::On,
821 ];
822
823 for zip212_enforcement in zip212_states {
824 let (ovk, _, ivk, mut output) = random_enc_ciphertext(zip212_enforcement, &mut rng);
825
826 *output.enc_ciphertext_mut() = reencrypt_enc_ciphertext(
827 &ovk,
828 output.cv(),
829 output.cmu(),
830 output.ephemeral_key(),
831 output.enc_ciphertext(),
832 output.out_ciphertext(),
833 |pt| pt[1..12].copy_from_slice(&find_invalid_diversifier().0),
834 );
835 assert_eq!(
836 try_sapling_note_decryption(&ivk, &output, zip212_enforcement),
837 None
838 );
839 }
840 }
841
842 #[test]
843 fn decryption_with_incorrect_diversifier() {
844 let mut rng = OsRng;
845 let zip212_states = [
846 Zip212Enforcement::Off,
847 Zip212Enforcement::GracePeriod,
848 Zip212Enforcement::On,
849 ];
850
851 for zip212_enforcement in zip212_states {
852 let (ovk, _, ivk, mut output) = random_enc_ciphertext(zip212_enforcement, &mut rng);
853
854 *output.enc_ciphertext_mut() = reencrypt_enc_ciphertext(
855 &ovk,
856 output.cv(),
857 output.cmu(),
858 output.ephemeral_key(),
859 output.enc_ciphertext(),
860 output.out_ciphertext(),
861 |pt| pt[1..12].copy_from_slice(&find_valid_diversifier().0),
862 );
863
864 assert_eq!(
865 try_sapling_note_decryption(&ivk, &output, zip212_enforcement),
866 None
867 );
868 }
869 }
870
871 #[test]
872 fn compact_decryption_with_invalid_ivk() {
873 let mut rng = OsRng;
874 let zip212_states = [
875 Zip212Enforcement::Off,
876 Zip212Enforcement::GracePeriod,
877 Zip212Enforcement::On,
878 ];
879
880 for zip212_enforcement in zip212_states {
881 let (_, _, _, output) = random_enc_ciphertext(zip212_enforcement, &mut rng);
882
883 assert_eq!(
884 try_sapling_compact_note_decryption(
885 &PreparedIncomingViewingKey::new(&SaplingIvk(jubjub::Fr::random(&mut rng))),
886 &CompactOutputDescription::from(output),
887 zip212_enforcement,
888 ),
889 None
890 );
891 }
892 }
893
894 #[test]
895 fn compact_decryption_with_invalid_epk() {
896 let mut rng = OsRng;
897 let zip212_states = [
898 Zip212Enforcement::Off,
899 Zip212Enforcement::GracePeriod,
900 Zip212Enforcement::On,
901 ];
902
903 for zip212_enforcement in zip212_states {
904 let (_, _, ivk, mut output) = random_enc_ciphertext(zip212_enforcement, &mut rng);
905 *output.ephemeral_key_mut() = jubjub::ExtendedPoint::random(&mut rng).to_bytes().into();
906
907 assert_eq!(
908 try_sapling_compact_note_decryption(
909 &ivk,
910 &CompactOutputDescription::from(output),
911 zip212_enforcement,
912 ),
913 None
914 );
915 }
916 }
917
918 #[test]
919 fn compact_decryption_with_invalid_cmu() {
920 let mut rng = OsRng;
921 let zip212_states = [
922 Zip212Enforcement::Off,
923 Zip212Enforcement::GracePeriod,
924 Zip212Enforcement::On,
925 ];
926
927 for zip212_enforcement in zip212_states {
928 let (_, _, ivk, mut output) = random_enc_ciphertext(zip212_enforcement, &mut rng);
929 *output.cmu_mut() =
930 ExtractedNoteCommitment::from_bytes(&bls12_381::Scalar::random(&mut rng).to_repr())
931 .unwrap();
932
933 assert_eq!(
934 try_sapling_compact_note_decryption(
935 &ivk,
936 &CompactOutputDescription::from(output),
937 zip212_enforcement,
938 ),
939 None
940 );
941 }
942 }
943
944 #[test]
945 fn compact_decryption_with_invalid_version_byte() {
946 let mut rng = OsRng;
947 let zip212_states = [
948 Zip212Enforcement::Off,
949 Zip212Enforcement::GracePeriod,
950 Zip212Enforcement::On,
951 ];
952 let leadbytes = [0x02, 0x03, 0x01];
953
954 for (zip212_enforcement, leadbyte) in zip212_states.into_iter().zip(leadbytes) {
955 let (ovk, _, ivk, mut output) = random_enc_ciphertext(zip212_enforcement, &mut rng);
956
957 *output.enc_ciphertext_mut() = reencrypt_enc_ciphertext(
958 &ovk,
959 output.cv(),
960 output.cmu(),
961 output.ephemeral_key(),
962 output.enc_ciphertext(),
963 output.out_ciphertext(),
964 |pt| pt[0] = leadbyte,
965 );
966 assert_eq!(
967 try_sapling_compact_note_decryption(
968 &ivk,
969 &CompactOutputDescription::from(output),
970 zip212_enforcement,
971 ),
972 None
973 );
974 }
975 }
976
977 #[test]
978 fn compact_decryption_with_invalid_diversifier() {
979 let mut rng = OsRng;
980 let zip212_states = [
981 Zip212Enforcement::Off,
982 Zip212Enforcement::GracePeriod,
983 Zip212Enforcement::On,
984 ];
985
986 for zip212_enforcement in zip212_states {
987 let (ovk, _, ivk, mut output) = random_enc_ciphertext(zip212_enforcement, &mut rng);
988
989 *output.enc_ciphertext_mut() = reencrypt_enc_ciphertext(
990 &ovk,
991 output.cv(),
992 output.cmu(),
993 output.ephemeral_key(),
994 output.enc_ciphertext(),
995 output.out_ciphertext(),
996 |pt| pt[1..12].copy_from_slice(&find_invalid_diversifier().0),
997 );
998 assert_eq!(
999 try_sapling_compact_note_decryption(
1000 &ivk,
1001 &CompactOutputDescription::from(output),
1002 zip212_enforcement,
1003 ),
1004 None
1005 );
1006 }
1007 }
1008
1009 #[test]
1010 fn compact_decryption_with_incorrect_diversifier() {
1011 let mut rng = OsRng;
1012 let zip212_states = [
1013 Zip212Enforcement::Off,
1014 Zip212Enforcement::GracePeriod,
1015 Zip212Enforcement::On,
1016 ];
1017
1018 for zip212_enforcement in zip212_states {
1019 let (ovk, _, ivk, mut output) = random_enc_ciphertext(zip212_enforcement, &mut rng);
1020
1021 *output.enc_ciphertext_mut() = reencrypt_enc_ciphertext(
1022 &ovk,
1023 output.cv(),
1024 output.cmu(),
1025 output.ephemeral_key(),
1026 output.enc_ciphertext(),
1027 output.out_ciphertext(),
1028 |pt| pt[1..12].copy_from_slice(&find_valid_diversifier().0),
1029 );
1030 assert_eq!(
1031 try_sapling_compact_note_decryption(
1032 &ivk,
1033 &CompactOutputDescription::from(output),
1034 zip212_enforcement,
1035 ),
1036 None
1037 );
1038 }
1039 }
1040
1041 #[test]
1042 fn recovery_with_invalid_ovk() {
1043 let mut rng = OsRng;
1044 let zip212_states = [
1045 Zip212Enforcement::Off,
1046 Zip212Enforcement::GracePeriod,
1047 Zip212Enforcement::On,
1048 ];
1049
1050 for zip212_enforcement in zip212_states {
1051 let (mut ovk, _, _, output) = random_enc_ciphertext(zip212_enforcement, &mut rng);
1052
1053 ovk.0[0] ^= 0xff;
1054 assert_eq!(
1055 try_sapling_output_recovery(&ovk, &output, zip212_enforcement),
1056 None
1057 );
1058 }
1059 }
1060
1061 #[test]
1062 fn recovery_with_invalid_ock() {
1063 let mut rng = OsRng;
1064 let zip212_states = [
1065 Zip212Enforcement::Off,
1066 Zip212Enforcement::GracePeriod,
1067 Zip212Enforcement::On,
1068 ];
1069
1070 for zip212_enforcement in zip212_states {
1071 let (_, _, _, output) = random_enc_ciphertext(zip212_enforcement, &mut rng);
1072
1073 assert_eq!(
1074 try_sapling_output_recovery_with_ock(
1075 &OutgoingCipherKey([0u8; 32]),
1076 &output,
1077 zip212_enforcement,
1078 ),
1079 None
1080 );
1081 }
1082 }
1083
1084 #[test]
1085 fn recovery_with_invalid_cv() {
1086 let mut rng = OsRng;
1087 let zip212_states = [
1088 Zip212Enforcement::Off,
1089 Zip212Enforcement::GracePeriod,
1090 Zip212Enforcement::On,
1091 ];
1092
1093 for zip212_enforcement in zip212_states {
1094 let (ovk, _, _, mut output) = random_enc_ciphertext(zip212_enforcement, &mut rng);
1095 *output.cv_mut() = ValueCommitment::derive(
1096 NoteValue::from_raw(7),
1097 ValueCommitTrapdoor::random(&mut rng),
1098 );
1099
1100 assert_eq!(
1101 try_sapling_output_recovery(&ovk, &output, zip212_enforcement,),
1102 None
1103 );
1104 }
1105 }
1106
1107 #[test]
1108 fn recovery_with_invalid_cmu() {
1109 let mut rng = OsRng;
1110 let zip212_states = [
1111 Zip212Enforcement::Off,
1112 Zip212Enforcement::GracePeriod,
1113 Zip212Enforcement::On,
1114 ];
1115
1116 for zip212_enforcement in zip212_states {
1117 let (ovk, ock, _, mut output) = random_enc_ciphertext(zip212_enforcement, &mut rng);
1118 *output.cmu_mut() =
1119 ExtractedNoteCommitment::from_bytes(&bls12_381::Scalar::random(&mut rng).to_repr())
1120 .unwrap();
1121
1122 assert_eq!(
1123 try_sapling_output_recovery(&ovk, &output, zip212_enforcement),
1124 None
1125 );
1126
1127 assert_eq!(
1128 try_sapling_output_recovery_with_ock(&ock, &output, zip212_enforcement),
1129 None
1130 );
1131 }
1132 }
1133
1134 #[test]
1135 fn recovery_with_invalid_epk() {
1136 let mut rng = OsRng;
1137 let zip212_states = [
1138 Zip212Enforcement::Off,
1139 Zip212Enforcement::GracePeriod,
1140 Zip212Enforcement::On,
1141 ];
1142
1143 for zip212_enforcement in zip212_states {
1144 let (ovk, ock, _, mut output) = random_enc_ciphertext(zip212_enforcement, &mut rng);
1145 *output.ephemeral_key_mut() = jubjub::ExtendedPoint::random(&mut rng).to_bytes().into();
1146
1147 assert_eq!(
1148 try_sapling_output_recovery(&ovk, &output, zip212_enforcement),
1149 None
1150 );
1151
1152 assert_eq!(
1153 try_sapling_output_recovery_with_ock(&ock, &output, zip212_enforcement),
1154 None
1155 );
1156 }
1157 }
1158
1159 #[test]
1160 fn recovery_with_invalid_enc_tag() {
1161 let mut rng = OsRng;
1162 let zip212_states = [
1163 Zip212Enforcement::Off,
1164 Zip212Enforcement::GracePeriod,
1165 Zip212Enforcement::On,
1166 ];
1167
1168 for zip212_enforcement in zip212_states {
1169 let (ovk, ock, _, mut output) = random_enc_ciphertext(zip212_enforcement, &mut rng);
1170
1171 output.enc_ciphertext_mut()[ENC_CIPHERTEXT_SIZE - 1] ^= 0xff;
1172 assert_eq!(
1173 try_sapling_output_recovery(&ovk, &output, zip212_enforcement),
1174 None
1175 );
1176 assert_eq!(
1177 try_sapling_output_recovery_with_ock(&ock, &output, zip212_enforcement),
1178 None
1179 );
1180 }
1181 }
1182
1183 #[test]
1184 fn recovery_with_invalid_out_tag() {
1185 let mut rng = OsRng;
1186 let zip212_states = [
1187 Zip212Enforcement::Off,
1188 Zip212Enforcement::GracePeriod,
1189 Zip212Enforcement::On,
1190 ];
1191
1192 for zip212_enforcement in zip212_states {
1193 let (ovk, ock, _, mut output) = random_enc_ciphertext(zip212_enforcement, &mut rng);
1194
1195 output.out_ciphertext_mut()[OUT_CIPHERTEXT_SIZE - 1] ^= 0xff;
1196 assert_eq!(
1197 try_sapling_output_recovery(&ovk, &output, zip212_enforcement),
1198 None
1199 );
1200 assert_eq!(
1201 try_sapling_output_recovery_with_ock(&ock, &output, zip212_enforcement),
1202 None
1203 );
1204 }
1205 }
1206
1207 #[test]
1208 fn recovery_with_invalid_version_byte() {
1209 let mut rng = OsRng;
1210 let zip212_states = [
1211 Zip212Enforcement::Off,
1212 Zip212Enforcement::GracePeriod,
1213 Zip212Enforcement::On,
1214 ];
1215 let leadbytes = [0x02, 0x03, 0x01];
1216
1217 for (zip212_enforcement, leadbyte) in zip212_states.into_iter().zip(leadbytes) {
1218 let (ovk, ock, _, mut output) = random_enc_ciphertext(zip212_enforcement, &mut rng);
1219
1220 *output.enc_ciphertext_mut() = reencrypt_enc_ciphertext(
1221 &ovk,
1222 output.cv(),
1223 output.cmu(),
1224 output.ephemeral_key(),
1225 output.enc_ciphertext(),
1226 output.out_ciphertext(),
1227 |pt| pt[0] = leadbyte,
1228 );
1229 assert_eq!(
1230 try_sapling_output_recovery(&ovk, &output, zip212_enforcement),
1231 None
1232 );
1233 assert_eq!(
1234 try_sapling_output_recovery_with_ock(&ock, &output, zip212_enforcement),
1235 None
1236 );
1237 }
1238 }
1239
1240 #[test]
1241 fn recovery_with_invalid_diversifier() {
1242 let mut rng = OsRng;
1243 let zip212_states = [
1244 Zip212Enforcement::Off,
1245 Zip212Enforcement::GracePeriod,
1246 Zip212Enforcement::On,
1247 ];
1248
1249 for zip212_enforcement in zip212_states {
1250 let (ovk, ock, _, mut output) = random_enc_ciphertext(zip212_enforcement, &mut rng);
1251
1252 *output.enc_ciphertext_mut() = reencrypt_enc_ciphertext(
1253 &ovk,
1254 output.cv(),
1255 output.cmu(),
1256 output.ephemeral_key(),
1257 output.enc_ciphertext(),
1258 output.out_ciphertext(),
1259 |pt| pt[1..12].copy_from_slice(&find_invalid_diversifier().0),
1260 );
1261 assert_eq!(
1262 try_sapling_output_recovery(&ovk, &output, zip212_enforcement),
1263 None
1264 );
1265 assert_eq!(
1266 try_sapling_output_recovery_with_ock(&ock, &output, zip212_enforcement),
1267 None
1268 );
1269 }
1270 }
1271
1272 #[test]
1273 fn recovery_with_incorrect_diversifier() {
1274 let mut rng = OsRng;
1275 let zip212_states = [
1276 Zip212Enforcement::Off,
1277 Zip212Enforcement::GracePeriod,
1278 Zip212Enforcement::On,
1279 ];
1280
1281 for zip212_enforcement in zip212_states {
1282 let (ovk, ock, _, mut output) = random_enc_ciphertext(zip212_enforcement, &mut rng);
1283
1284 *output.enc_ciphertext_mut() = reencrypt_enc_ciphertext(
1285 &ovk,
1286 output.cv(),
1287 output.cmu(),
1288 output.ephemeral_key(),
1289 output.enc_ciphertext(),
1290 output.out_ciphertext(),
1291 |pt| pt[1..12].copy_from_slice(&find_valid_diversifier().0),
1292 );
1293 assert_eq!(
1294 try_sapling_output_recovery(&ovk, &output, zip212_enforcement),
1295 None
1296 );
1297 assert_eq!(
1298 try_sapling_output_recovery_with_ock(&ock, &output, zip212_enforcement),
1299 None
1300 );
1301 }
1302 }
1303
1304 #[test]
1305 fn recovery_with_invalid_pk_d() {
1306 let mut rng = OsRng;
1307 let zip212_states = [
1308 Zip212Enforcement::Off,
1309 Zip212Enforcement::GracePeriod,
1310 Zip212Enforcement::On,
1311 ];
1312
1313 for zip212_enforcement in zip212_states {
1314 let (ovk, ock, _, mut output) = random_enc_ciphertext(zip212_enforcement, &mut rng);
1315 let invalid_pk_d = jubjub::ExtendedPoint::random(&mut rng).to_bytes();
1316
1317 *output.out_ciphertext_mut() = reencrypt_out_ciphertext(
1318 &ovk,
1319 output.cv(),
1320 output.cmu(),
1321 output.ephemeral_key(),
1322 output.out_ciphertext(),
1323 |pt| pt[0..32].copy_from_slice(&invalid_pk_d),
1324 );
1325 assert_eq!(
1326 try_sapling_output_recovery(&ovk, &output, zip212_enforcement),
1327 None
1328 );
1329 assert_eq!(
1330 try_sapling_output_recovery_with_ock(&ock, &output, zip212_enforcement),
1331 None
1332 );
1333 }
1334 }
1335
1336 #[test]
1337 fn test_vectors() {
1338 let test_vectors = crate::test_vectors::note_encryption::make_test_vectors();
1339
1340 macro_rules! read_cmu {
1341 ($field:expr_2021) => {{ ExtractedNoteCommitment::from_bytes($field[..].try_into().unwrap()).unwrap() }};
1342 }
1343
1344 macro_rules! read_jubjub_scalar {
1345 ($field:expr_2021) => {{ jubjub::Fr::from_repr($field[..].try_into().unwrap()).unwrap() }};
1346 }
1347
1348 macro_rules! read_pk_d {
1349 ($field:expr_2021) => {
1350 DiversifiedTransmissionKey::from_bytes(&$field).unwrap()
1351 };
1352 }
1353
1354 macro_rules! read_cv {
1355 ($field:expr_2021) => {
1356 ValueCommitment::from_bytes_not_small_order(&$field).unwrap()
1357 };
1358 }
1359
1360 let zip212_enforcement = Zip212Enforcement::Off;
1361
1362 for tv in test_vectors {
1363 let ivk = PreparedIncomingViewingKey::new(&SaplingIvk(read_jubjub_scalar!(tv.ivk)));
1368 let pk_d = read_pk_d!(tv.default_pk_d);
1369 let rcm = read_jubjub_scalar!(tv.rcm);
1370 let cv = read_cv!(tv.cv);
1371 let cmu = read_cmu!(tv.cmu);
1372 let esk = EphemeralSecretKey(read_jubjub_scalar!(tv.esk));
1373 let ephemeral_key = EphemeralKeyBytes(tv.epk);
1374
1375 let shared_secret = esk.agree(&pk_d);
1380 assert_eq!(shared_secret.to_bytes(), tv.shared_secret);
1381
1382 let k_enc = shared_secret.kdf_sapling(&ephemeral_key);
1383 assert_eq!(k_enc.as_bytes(), tv.k_enc);
1384
1385 let ovk = OutgoingViewingKey(tv.ovk);
1386 let ock = prf_ock(&ovk, &cv, &cmu.to_bytes(), &ephemeral_key);
1387 assert_eq!(ock.as_ref(), tv.ock);
1388
1389 let to = PaymentAddress::from_parts(Diversifier(tv.default_d), pk_d).unwrap();
1390 let note = to.create_note(NoteValue::from_raw(tv.v), Rseed::BeforeZip212(rcm));
1391 assert_eq!(note.cmu(), cmu);
1392
1393 let output = OutputDescription::from_parts(
1394 cv.clone(),
1395 cmu,
1396 ephemeral_key,
1397 tv.c_enc,
1398 tv.c_out,
1399 [0u8; GROTH_PROOF_SIZE],
1400 );
1401
1402 match try_sapling_note_decryption(&ivk, &output, zip212_enforcement) {
1408 Some((decrypted_note, decrypted_to, decrypted_memo)) => {
1409 assert_eq!(decrypted_note, note);
1410 assert_eq!(decrypted_to, to);
1411 assert_eq!(&decrypted_memo[..], &tv.memo[..]);
1412 }
1413 None => panic!("Note decryption failed"),
1414 }
1415
1416 match try_sapling_compact_note_decryption(
1417 &ivk,
1418 &CompactOutputDescription::from(output.clone()),
1419 zip212_enforcement,
1420 ) {
1421 Some((decrypted_note, decrypted_to)) => {
1422 assert_eq!(decrypted_note, note);
1423 assert_eq!(decrypted_to, to);
1424 }
1425 None => panic!("Compact note decryption failed"),
1426 }
1427
1428 match try_sapling_output_recovery(&ovk, &output, zip212_enforcement) {
1429 Some((decrypted_note, decrypted_to, decrypted_memo)) => {
1430 assert_eq!(decrypted_note, note);
1431 assert_eq!(decrypted_to, to);
1432 assert_eq!(&decrypted_memo[..], &tv.memo[..]);
1433 }
1434 None => panic!("Output recovery failed"),
1435 }
1436
1437 match &batch::try_note_decryption(
1438 core::slice::from_ref(&ivk),
1439 &[(SaplingDomain::new(zip212_enforcement), output.clone())],
1440 )[..]
1441 {
1442 [Some(((decrypted_note, decrypted_to, decrypted_memo), i))] => {
1443 assert_eq!(decrypted_note, ¬e);
1444 assert_eq!(decrypted_to, &to);
1445 assert_eq!(&decrypted_memo[..], &tv.memo[..]);
1446 assert_eq!(*i, 0);
1447 }
1448 _ => panic!("Note decryption failed"),
1449 }
1450
1451 match &batch::try_compact_note_decryption(
1452 core::slice::from_ref(&ivk),
1453 &[(
1454 SaplingDomain::new(zip212_enforcement),
1455 CompactOutputDescription::from(output.clone()),
1456 )],
1457 )[..]
1458 {
1459 [Some(((decrypted_note, decrypted_to), i))] => {
1460 assert_eq!(decrypted_note, ¬e);
1461 assert_eq!(decrypted_to, &to);
1462 assert_eq!(*i, 0);
1463 }
1464 _ => panic!("Note decryption failed"),
1465 }
1466
1467 let ne = NoteEncryption::<SaplingDomain>::new_with_esk(esk, Some(ovk), note, tv.memo);
1472
1473 assert_eq!(ne.encrypt_note_plaintext().as_ref(), &tv.c_enc[..]);
1474 assert_eq!(
1475 &ne.encrypt_outgoing_plaintext(&cv, &cmu, &mut RngCore06::new(&mut OsRng),)[..],
1476 &tv.c_out[..]
1477 );
1478 }
1479 }
1480
1481 #[test]
1482 fn batching() {
1483 let mut rng = OsRng;
1484 let zip212_enforcement = Zip212Enforcement::On;
1485
1486 let invalid_ivk =
1488 PreparedIncomingViewingKey::new(&SaplingIvk(jubjub::Fr::random(&mut rng)));
1489 let valid_ivk = SaplingIvk(jubjub::Fr::random(&mut rng));
1490 let outputs: Vec<_> = (0..10)
1491 .map(|_| {
1492 (
1493 SaplingDomain::new(zip212_enforcement),
1494 random_enc_ciphertext_with(&valid_ivk, zip212_enforcement, &mut rng).2,
1495 )
1496 })
1497 .collect();
1498 let valid_ivk = PreparedIncomingViewingKey::new(&valid_ivk);
1499
1500 let res = batch::try_note_decryption(core::slice::from_ref(&invalid_ivk), &outputs);
1502 assert_eq!(res.len(), 10);
1503 assert_eq!(&res[..], &vec![None; 10][..]);
1504
1505 let res = batch::try_note_decryption(&[invalid_ivk, valid_ivk.clone()], &outputs);
1507 assert_eq!(res.len(), 10);
1508 for (result, (_, output)) in res.iter().zip(outputs.iter()) {
1509 assert!(result.is_some());
1512 assert_eq!(
1513 result,
1514 &try_sapling_note_decryption(&valid_ivk, output, zip212_enforcement)
1515 .map(|r| (r, 1))
1516 );
1517 }
1518 }
1519}