1use alloc::vec::Vec;
6use blake2b_simd::{Hash as Blake2bHash, Params as Blake2bParams};
7use ff::PrimeField;
8use memuse::DynamicUsage;
9use rand_core::RngCore;
10
11use zcash_note_encryption::{
12 try_compact_note_decryption, try_note_decryption, try_output_recovery_with_ock,
13 try_output_recovery_with_ovk, BatchDomain, Domain, EphemeralKeyBytes, NoteEncryption,
14 NotePlaintextBytes, OutPlaintextBytes, OutgoingCipherKey, ShieldedOutput, COMPACT_NOTE_SIZE,
15 ENC_CIPHERTEXT_SIZE, NOTE_PLAINTEXT_SIZE, OUT_PLAINTEXT_SIZE,
16};
17
18use crate::{
19 bundle::{GrothProofBytes, OutputDescription},
20 keys::{
21 DiversifiedTransmissionKey, EphemeralPublicKey, EphemeralSecretKey, OutgoingViewingKey,
22 SharedSecret,
23 },
24 value::{NoteValue, ValueCommitment},
25 Diversifier, Note, PaymentAddress, Rseed,
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: RngCore>(
388 ovk: Option<OutgoingViewingKey>,
389 note: Note,
390 memo: [u8; 512],
391 rng: &mut R,
392) -> NoteEncryption<SaplingDomain> {
393 let esk = note.generate_or_derive_esk_internal(rng);
394 NoteEncryption::new_with_esk(esk, ovk, note, memo)
395}
396
397#[allow(clippy::if_same_then_else)]
398#[allow(clippy::needless_bool)]
399pub fn plaintext_version_is_valid(zip212_enforcement: Zip212Enforcement, leadbyte: u8) -> bool {
400 match zip212_enforcement {
401 Zip212Enforcement::Off => leadbyte == 0x01,
402 Zip212Enforcement::GracePeriod => leadbyte == 0x01 || leadbyte == 0x02,
403 Zip212Enforcement::On => leadbyte == 0x02,
404 }
405}
406
407pub fn try_sapling_note_decryption<Output: ShieldedOutput<SaplingDomain, ENC_CIPHERTEXT_SIZE>>(
408 ivk: &PreparedIncomingViewingKey,
409 output: &Output,
410 zip212_enforcement: Zip212Enforcement,
411) -> Option<(Note, PaymentAddress, [u8; 512])> {
412 let domain = SaplingDomain::new(zip212_enforcement);
413 try_note_decryption(&domain, ivk, output)
414}
415
416pub fn try_sapling_compact_note_decryption<
417 Output: ShieldedOutput<SaplingDomain, COMPACT_NOTE_SIZE>,
418>(
419 ivk: &PreparedIncomingViewingKey,
420 output: &Output,
421 zip212_enforcement: Zip212Enforcement,
422) -> Option<(Note, PaymentAddress)> {
423 let domain = SaplingDomain::new(zip212_enforcement);
424 try_compact_note_decryption(&domain, ivk, output)
425}
426
427pub fn try_sapling_output_recovery_with_ock(
436 ock: &OutgoingCipherKey,
437 output: &OutputDescription<GrothProofBytes>,
438 zip212_enforcement: Zip212Enforcement,
439) -> Option<(Note, PaymentAddress, [u8; 512])> {
440 let domain = SaplingDomain::new(zip212_enforcement);
441 try_output_recovery_with_ock(&domain, ock, output, output.out_ciphertext())
442}
443
444#[allow(clippy::too_many_arguments)]
452pub fn try_sapling_output_recovery(
453 ovk: &OutgoingViewingKey,
454 output: &OutputDescription<GrothProofBytes>,
455 zip212_enforcement: Zip212Enforcement,
456) -> Option<(Note, PaymentAddress, [u8; 512])> {
457 let domain = SaplingDomain::new(zip212_enforcement);
458 try_output_recovery_with_ovk(&domain, ovk, output, output.cv(), output.out_ciphertext())
459}
460
461#[cfg(test)]
462mod tests {
463 use alloc::vec::Vec;
464 use chacha20poly1305::{
465 aead::{AeadInPlace, KeyInit},
466 ChaCha20Poly1305,
467 };
468 use ff::{Field, PrimeField};
469 use group::Group;
470 use group::GroupEncoding;
471 use rand_core::OsRng;
472 use rand_core::{CryptoRng, RngCore};
473
474 use zcash_note_encryption::{
475 batch, EphemeralKeyBytes, NoteEncryption, OutgoingCipherKey, ENC_CIPHERTEXT_SIZE,
476 NOTE_PLAINTEXT_SIZE, OUT_CIPHERTEXT_SIZE, OUT_PLAINTEXT_SIZE,
477 };
478
479 use super::{
480 prf_ock, sapling_note_encryption, try_sapling_compact_note_decryption,
481 try_sapling_note_decryption, try_sapling_output_recovery,
482 try_sapling_output_recovery_with_ock, CompactOutputDescription, SaplingDomain,
483 Zip212Enforcement,
484 };
485
486 use crate::{
487 bundle::{GrothProofBytes, OutputDescription},
488 constants::GROTH_PROOF_SIZE,
489 keys::{DiversifiedTransmissionKey, EphemeralSecretKey, OutgoingViewingKey},
490 note::ExtractedNoteCommitment,
491 note_encryption::PreparedIncomingViewingKey,
492 util::generate_random_rseed,
493 value::{NoteValue, ValueCommitTrapdoor, ValueCommitment},
494 Diversifier, PaymentAddress, Rseed, SaplingIvk,
495 };
496
497 fn random_enc_ciphertext<R: RngCore + 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!(try_sapling_compact_note_decryption(
513 &prepared_ivk,
514 &CompactOutputDescription::from(output.clone()),
515 zip212_enforcement,
516 )
517 .is_some());
518
519 let ovk_output_recovery = try_sapling_output_recovery(&ovk, &output, zip212_enforcement);
520
521 let ock_output_recovery =
522 try_sapling_output_recovery_with_ock(&ock, &output, zip212_enforcement);
523 assert!(ovk_output_recovery.is_some());
524 assert!(ock_output_recovery.is_some());
525 assert_eq!(ovk_output_recovery, ock_output_recovery);
526
527 (ovk, ock, prepared_ivk, output)
528 }
529
530 fn random_enc_ciphertext_with<R: RngCore + CryptoRng>(
531 ivk: &SaplingIvk,
532 zip212_enforcement: Zip212Enforcement,
533 mut rng: &mut R,
534 ) -> (
535 OutgoingViewingKey,
536 OutgoingCipherKey,
537 OutputDescription<GrothProofBytes>,
538 ) {
539 let diversifier = Diversifier([0; 11]);
540 let pa = ivk.to_payment_address(diversifier).unwrap();
541
542 let value = NoteValue::from_raw(100);
544 let rcv = ValueCommitTrapdoor::random(&mut rng);
545 let cv = ValueCommitment::derive(value, rcv);
546
547 let rseed = generate_random_rseed(zip212_enforcement, &mut rng);
548
549 let note = pa.create_note(value, rseed);
550 let cmu = note.cmu();
551
552 let ovk = OutgoingViewingKey([0; 32]);
553 let ne = sapling_note_encryption(Some(ovk), note, [0x37; 512], &mut rng);
554 let epk = ne.epk();
555 let ock = prf_ock(&ovk, &cv, &cmu.to_bytes(), &epk.to_bytes());
556
557 let out_ciphertext = ne.encrypt_outgoing_plaintext(&cv, &cmu, &mut rng);
558 let output = OutputDescription::from_parts(
559 cv,
560 cmu,
561 epk.to_bytes(),
562 ne.encrypt_note_plaintext(),
563 out_ciphertext,
564 [0u8; GROTH_PROOF_SIZE],
565 );
566
567 (ovk, ock, output)
568 }
569
570 fn reencrypt_out_ciphertext(
571 ovk: &OutgoingViewingKey,
572 cv: &ValueCommitment,
573 cmu: &ExtractedNoteCommitment,
574 ephemeral_key: &EphemeralKeyBytes,
575 out_ciphertext: &[u8; OUT_CIPHERTEXT_SIZE],
576 modify_plaintext: impl Fn(&mut [u8; OUT_PLAINTEXT_SIZE]),
577 ) -> [u8; OUT_CIPHERTEXT_SIZE] {
578 let ock = prf_ock(ovk, cv, &cmu.to_bytes(), ephemeral_key);
579
580 let mut op = [0; OUT_PLAINTEXT_SIZE];
581 op.copy_from_slice(&out_ciphertext[..OUT_PLAINTEXT_SIZE]);
582
583 ChaCha20Poly1305::new(ock.as_ref().into())
584 .decrypt_in_place_detached(
585 [0u8; 12][..].into(),
586 &[],
587 &mut op,
588 out_ciphertext[OUT_PLAINTEXT_SIZE..].into(),
589 )
590 .unwrap();
591
592 modify_plaintext(&mut op);
593
594 let tag = ChaCha20Poly1305::new(ock.as_ref().into())
595 .encrypt_in_place_detached([0u8; 12][..].into(), &[], &mut op)
596 .unwrap();
597
598 let mut out_ciphertext = [0u8; OUT_CIPHERTEXT_SIZE];
599 out_ciphertext[..OUT_PLAINTEXT_SIZE].copy_from_slice(&op);
600 out_ciphertext[OUT_PLAINTEXT_SIZE..].copy_from_slice(&tag);
601 out_ciphertext
602 }
603
604 fn reencrypt_enc_ciphertext(
605 ovk: &OutgoingViewingKey,
606 cv: &ValueCommitment,
607 cmu: &ExtractedNoteCommitment,
608 ephemeral_key: &EphemeralKeyBytes,
609 enc_ciphertext: &[u8; ENC_CIPHERTEXT_SIZE],
610 out_ciphertext: &[u8; OUT_CIPHERTEXT_SIZE],
611 modify_plaintext: impl Fn(&mut [u8; NOTE_PLAINTEXT_SIZE]),
612 ) -> [u8; ENC_CIPHERTEXT_SIZE] {
613 let ock = prf_ock(ovk, cv, &cmu.to_bytes(), ephemeral_key);
614
615 let mut op = [0; OUT_PLAINTEXT_SIZE];
616 op.copy_from_slice(&out_ciphertext[..OUT_PLAINTEXT_SIZE]);
617
618 ChaCha20Poly1305::new(ock.as_ref().into())
619 .decrypt_in_place_detached(
620 [0u8; 12][..].into(),
621 &[],
622 &mut op,
623 out_ciphertext[OUT_PLAINTEXT_SIZE..].into(),
624 )
625 .unwrap();
626
627 let pk_d = DiversifiedTransmissionKey::from_bytes(&op[0..32].try_into().unwrap()).unwrap();
628
629 let esk = jubjub::Fr::from_repr(op[32..OUT_PLAINTEXT_SIZE].try_into().unwrap()).unwrap();
630
631 let shared_secret = EphemeralSecretKey(esk).agree(&pk_d);
632 let key = shared_secret.kdf_sapling(ephemeral_key);
633
634 let mut plaintext = [0; NOTE_PLAINTEXT_SIZE];
635 plaintext.copy_from_slice(&enc_ciphertext[..NOTE_PLAINTEXT_SIZE]);
636
637 ChaCha20Poly1305::new(key.as_bytes().into())
638 .decrypt_in_place_detached(
639 [0u8; 12][..].into(),
640 &[],
641 &mut plaintext,
642 enc_ciphertext[NOTE_PLAINTEXT_SIZE..].into(),
643 )
644 .unwrap();
645
646 modify_plaintext(&mut plaintext);
647
648 let tag = ChaCha20Poly1305::new(key.as_ref().into())
649 .encrypt_in_place_detached([0u8; 12][..].into(), &[], &mut plaintext)
650 .unwrap();
651
652 let mut enc_ciphertext = [0u8; ENC_CIPHERTEXT_SIZE];
653 enc_ciphertext[..NOTE_PLAINTEXT_SIZE].copy_from_slice(&plaintext);
654 enc_ciphertext[NOTE_PLAINTEXT_SIZE..].copy_from_slice(&tag);
655 enc_ciphertext
656 }
657
658 fn find_invalid_diversifier() -> Diversifier {
659 let mut d = Diversifier([0; 11]);
661 loop {
662 for k in 0..11 {
663 d.0[k] = d.0[k].wrapping_add(1);
664 if d.0[k] != 0 {
665 break;
666 }
667 }
668 if d.g_d().is_none() {
669 break;
670 }
671 }
672 d
673 }
674
675 fn find_valid_diversifier() -> Diversifier {
676 let mut d = Diversifier([0; 11]);
678 loop {
679 for k in 0..11 {
680 d.0[k] = d.0[k].wrapping_add(1);
681 if d.0[k] != 0 {
682 break;
683 }
684 }
685 if d.g_d().is_some() {
686 break;
687 }
688 }
689 d
690 }
691
692 #[test]
693 fn decryption_with_invalid_ivk() {
694 let mut rng = OsRng;
695 let zip212_states = [
696 Zip212Enforcement::Off,
697 Zip212Enforcement::GracePeriod,
698 Zip212Enforcement::On,
699 ];
700
701 for zip212_enforcement in zip212_states {
702 let (_, _, _, output) = random_enc_ciphertext(zip212_enforcement, &mut rng);
703
704 assert_eq!(
705 try_sapling_note_decryption(
706 &PreparedIncomingViewingKey::new(&SaplingIvk(jubjub::Fr::random(&mut rng))),
707 &output,
708 zip212_enforcement,
709 ),
710 None
711 );
712 }
713 }
714
715 #[test]
716 fn decryption_with_invalid_epk() {
717 let mut rng = OsRng;
718 let zip212_states = [
719 Zip212Enforcement::Off,
720 Zip212Enforcement::GracePeriod,
721 Zip212Enforcement::On,
722 ];
723
724 for zip212_enforcement in zip212_states {
725 let (_, _, ivk, mut output) = random_enc_ciphertext(zip212_enforcement, &mut rng);
726
727 *output.ephemeral_key_mut() = jubjub::ExtendedPoint::random(&mut rng).to_bytes().into();
728
729 assert_eq!(
730 try_sapling_note_decryption(&ivk, &output, zip212_enforcement,),
731 None
732 );
733 }
734 }
735
736 #[test]
737 fn decryption_with_invalid_cmu() {
738 let mut rng = OsRng;
739 let zip212_states = [
740 Zip212Enforcement::Off,
741 Zip212Enforcement::GracePeriod,
742 Zip212Enforcement::On,
743 ];
744
745 for zip212_enforcement in zip212_states {
746 let (_, _, ivk, mut output) = random_enc_ciphertext(zip212_enforcement, &mut rng);
747 *output.cmu_mut() =
748 ExtractedNoteCommitment::from_bytes(&bls12_381::Scalar::random(&mut rng).to_repr())
749 .unwrap();
750
751 assert_eq!(
752 try_sapling_note_decryption(&ivk, &output, zip212_enforcement,),
753 None
754 );
755 }
756 }
757
758 #[test]
759 fn decryption_with_invalid_tag() {
760 let mut rng = OsRng;
761 let zip212_states = [
762 Zip212Enforcement::Off,
763 Zip212Enforcement::GracePeriod,
764 Zip212Enforcement::On,
765 ];
766
767 for zip212_enforcement in zip212_states {
768 let (_, _, ivk, mut output) = random_enc_ciphertext(zip212_enforcement, &mut rng);
769 output.enc_ciphertext_mut()[ENC_CIPHERTEXT_SIZE - 1] ^= 0xff;
770
771 assert_eq!(
772 try_sapling_note_decryption(&ivk, &output, zip212_enforcement,),
773 None
774 );
775 }
776 }
777
778 #[test]
779 fn decryption_with_invalid_version_byte() {
780 let mut rng = OsRng;
781 let zip212_states = [
782 Zip212Enforcement::Off,
783 Zip212Enforcement::GracePeriod,
784 Zip212Enforcement::On,
785 ];
786 let leadbytes = [0x02, 0x03, 0x01];
787
788 for (zip212_enforcement, leadbyte) in zip212_states.into_iter().zip(leadbytes) {
789 let (ovk, _, ivk, mut output) = random_enc_ciphertext(zip212_enforcement, &mut rng);
790
791 *output.enc_ciphertext_mut() = reencrypt_enc_ciphertext(
792 &ovk,
793 output.cv(),
794 output.cmu(),
795 output.ephemeral_key(),
796 output.enc_ciphertext(),
797 output.out_ciphertext(),
798 |pt| pt[0] = leadbyte,
799 );
800 assert_eq!(
801 try_sapling_note_decryption(&ivk, &output, zip212_enforcement),
802 None
803 );
804 }
805 }
806
807 #[test]
808 fn decryption_with_invalid_diversifier() {
809 let mut rng = OsRng;
810 let zip212_states = [
811 Zip212Enforcement::Off,
812 Zip212Enforcement::GracePeriod,
813 Zip212Enforcement::On,
814 ];
815
816 for zip212_enforcement in zip212_states {
817 let (ovk, _, ivk, mut output) = random_enc_ciphertext(zip212_enforcement, &mut rng);
818
819 *output.enc_ciphertext_mut() = reencrypt_enc_ciphertext(
820 &ovk,
821 output.cv(),
822 output.cmu(),
823 output.ephemeral_key(),
824 output.enc_ciphertext(),
825 output.out_ciphertext(),
826 |pt| pt[1..12].copy_from_slice(&find_invalid_diversifier().0),
827 );
828 assert_eq!(
829 try_sapling_note_decryption(&ivk, &output, zip212_enforcement),
830 None
831 );
832 }
833 }
834
835 #[test]
836 fn decryption_with_incorrect_diversifier() {
837 let mut rng = OsRng;
838 let zip212_states = [
839 Zip212Enforcement::Off,
840 Zip212Enforcement::GracePeriod,
841 Zip212Enforcement::On,
842 ];
843
844 for zip212_enforcement in zip212_states {
845 let (ovk, _, ivk, mut output) = random_enc_ciphertext(zip212_enforcement, &mut rng);
846
847 *output.enc_ciphertext_mut() = reencrypt_enc_ciphertext(
848 &ovk,
849 output.cv(),
850 output.cmu(),
851 output.ephemeral_key(),
852 output.enc_ciphertext(),
853 output.out_ciphertext(),
854 |pt| pt[1..12].copy_from_slice(&find_valid_diversifier().0),
855 );
856
857 assert_eq!(
858 try_sapling_note_decryption(&ivk, &output, zip212_enforcement),
859 None
860 );
861 }
862 }
863
864 #[test]
865 fn compact_decryption_with_invalid_ivk() {
866 let mut rng = OsRng;
867 let zip212_states = [
868 Zip212Enforcement::Off,
869 Zip212Enforcement::GracePeriod,
870 Zip212Enforcement::On,
871 ];
872
873 for zip212_enforcement in zip212_states {
874 let (_, _, _, output) = random_enc_ciphertext(zip212_enforcement, &mut rng);
875
876 assert_eq!(
877 try_sapling_compact_note_decryption(
878 &PreparedIncomingViewingKey::new(&SaplingIvk(jubjub::Fr::random(&mut rng))),
879 &CompactOutputDescription::from(output),
880 zip212_enforcement,
881 ),
882 None
883 );
884 }
885 }
886
887 #[test]
888 fn compact_decryption_with_invalid_epk() {
889 let mut rng = OsRng;
890 let zip212_states = [
891 Zip212Enforcement::Off,
892 Zip212Enforcement::GracePeriod,
893 Zip212Enforcement::On,
894 ];
895
896 for zip212_enforcement in zip212_states {
897 let (_, _, ivk, mut output) = random_enc_ciphertext(zip212_enforcement, &mut rng);
898 *output.ephemeral_key_mut() = jubjub::ExtendedPoint::random(&mut rng).to_bytes().into();
899
900 assert_eq!(
901 try_sapling_compact_note_decryption(
902 &ivk,
903 &CompactOutputDescription::from(output),
904 zip212_enforcement,
905 ),
906 None
907 );
908 }
909 }
910
911 #[test]
912 fn compact_decryption_with_invalid_cmu() {
913 let mut rng = OsRng;
914 let zip212_states = [
915 Zip212Enforcement::Off,
916 Zip212Enforcement::GracePeriod,
917 Zip212Enforcement::On,
918 ];
919
920 for zip212_enforcement in zip212_states {
921 let (_, _, ivk, mut output) = random_enc_ciphertext(zip212_enforcement, &mut rng);
922 *output.cmu_mut() =
923 ExtractedNoteCommitment::from_bytes(&bls12_381::Scalar::random(&mut rng).to_repr())
924 .unwrap();
925
926 assert_eq!(
927 try_sapling_compact_note_decryption(
928 &ivk,
929 &CompactOutputDescription::from(output),
930 zip212_enforcement,
931 ),
932 None
933 );
934 }
935 }
936
937 #[test]
938 fn compact_decryption_with_invalid_version_byte() {
939 let mut rng = OsRng;
940 let zip212_states = [
941 Zip212Enforcement::Off,
942 Zip212Enforcement::GracePeriod,
943 Zip212Enforcement::On,
944 ];
945 let leadbytes = [0x02, 0x03, 0x01];
946
947 for (zip212_enforcement, leadbyte) in zip212_states.into_iter().zip(leadbytes) {
948 let (ovk, _, ivk, mut output) = random_enc_ciphertext(zip212_enforcement, &mut rng);
949
950 *output.enc_ciphertext_mut() = reencrypt_enc_ciphertext(
951 &ovk,
952 output.cv(),
953 output.cmu(),
954 output.ephemeral_key(),
955 output.enc_ciphertext(),
956 output.out_ciphertext(),
957 |pt| pt[0] = leadbyte,
958 );
959 assert_eq!(
960 try_sapling_compact_note_decryption(
961 &ivk,
962 &CompactOutputDescription::from(output),
963 zip212_enforcement,
964 ),
965 None
966 );
967 }
968 }
969
970 #[test]
971 fn compact_decryption_with_invalid_diversifier() {
972 let mut rng = OsRng;
973 let zip212_states = [
974 Zip212Enforcement::Off,
975 Zip212Enforcement::GracePeriod,
976 Zip212Enforcement::On,
977 ];
978
979 for zip212_enforcement in zip212_states {
980 let (ovk, _, ivk, mut output) = random_enc_ciphertext(zip212_enforcement, &mut rng);
981
982 *output.enc_ciphertext_mut() = reencrypt_enc_ciphertext(
983 &ovk,
984 output.cv(),
985 output.cmu(),
986 output.ephemeral_key(),
987 output.enc_ciphertext(),
988 output.out_ciphertext(),
989 |pt| pt[1..12].copy_from_slice(&find_invalid_diversifier().0),
990 );
991 assert_eq!(
992 try_sapling_compact_note_decryption(
993 &ivk,
994 &CompactOutputDescription::from(output),
995 zip212_enforcement,
996 ),
997 None
998 );
999 }
1000 }
1001
1002 #[test]
1003 fn compact_decryption_with_incorrect_diversifier() {
1004 let mut rng = OsRng;
1005 let zip212_states = [
1006 Zip212Enforcement::Off,
1007 Zip212Enforcement::GracePeriod,
1008 Zip212Enforcement::On,
1009 ];
1010
1011 for zip212_enforcement in zip212_states {
1012 let (ovk, _, ivk, mut output) = random_enc_ciphertext(zip212_enforcement, &mut rng);
1013
1014 *output.enc_ciphertext_mut() = reencrypt_enc_ciphertext(
1015 &ovk,
1016 output.cv(),
1017 output.cmu(),
1018 output.ephemeral_key(),
1019 output.enc_ciphertext(),
1020 output.out_ciphertext(),
1021 |pt| pt[1..12].copy_from_slice(&find_valid_diversifier().0),
1022 );
1023 assert_eq!(
1024 try_sapling_compact_note_decryption(
1025 &ivk,
1026 &CompactOutputDescription::from(output),
1027 zip212_enforcement,
1028 ),
1029 None
1030 );
1031 }
1032 }
1033
1034 #[test]
1035 fn recovery_with_invalid_ovk() {
1036 let mut rng = OsRng;
1037 let zip212_states = [
1038 Zip212Enforcement::Off,
1039 Zip212Enforcement::GracePeriod,
1040 Zip212Enforcement::On,
1041 ];
1042
1043 for zip212_enforcement in zip212_states {
1044 let (mut ovk, _, _, output) = random_enc_ciphertext(zip212_enforcement, &mut rng);
1045
1046 ovk.0[0] ^= 0xff;
1047 assert_eq!(
1048 try_sapling_output_recovery(&ovk, &output, zip212_enforcement),
1049 None
1050 );
1051 }
1052 }
1053
1054 #[test]
1055 fn recovery_with_invalid_ock() {
1056 let mut rng = OsRng;
1057 let zip212_states = [
1058 Zip212Enforcement::Off,
1059 Zip212Enforcement::GracePeriod,
1060 Zip212Enforcement::On,
1061 ];
1062
1063 for zip212_enforcement in zip212_states {
1064 let (_, _, _, output) = random_enc_ciphertext(zip212_enforcement, &mut rng);
1065
1066 assert_eq!(
1067 try_sapling_output_recovery_with_ock(
1068 &OutgoingCipherKey([0u8; 32]),
1069 &output,
1070 zip212_enforcement,
1071 ),
1072 None
1073 );
1074 }
1075 }
1076
1077 #[test]
1078 fn recovery_with_invalid_cv() {
1079 let mut rng = OsRng;
1080 let zip212_states = [
1081 Zip212Enforcement::Off,
1082 Zip212Enforcement::GracePeriod,
1083 Zip212Enforcement::On,
1084 ];
1085
1086 for zip212_enforcement in zip212_states {
1087 let (ovk, _, _, mut output) = random_enc_ciphertext(zip212_enforcement, &mut rng);
1088 *output.cv_mut() = ValueCommitment::derive(
1089 NoteValue::from_raw(7),
1090 ValueCommitTrapdoor::random(&mut rng),
1091 );
1092
1093 assert_eq!(
1094 try_sapling_output_recovery(&ovk, &output, zip212_enforcement,),
1095 None
1096 );
1097 }
1098 }
1099
1100 #[test]
1101 fn recovery_with_invalid_cmu() {
1102 let mut rng = OsRng;
1103 let zip212_states = [
1104 Zip212Enforcement::Off,
1105 Zip212Enforcement::GracePeriod,
1106 Zip212Enforcement::On,
1107 ];
1108
1109 for zip212_enforcement in zip212_states {
1110 let (ovk, ock, _, mut output) = random_enc_ciphertext(zip212_enforcement, &mut rng);
1111 *output.cmu_mut() =
1112 ExtractedNoteCommitment::from_bytes(&bls12_381::Scalar::random(&mut rng).to_repr())
1113 .unwrap();
1114
1115 assert_eq!(
1116 try_sapling_output_recovery(&ovk, &output, zip212_enforcement),
1117 None
1118 );
1119
1120 assert_eq!(
1121 try_sapling_output_recovery_with_ock(&ock, &output, zip212_enforcement),
1122 None
1123 );
1124 }
1125 }
1126
1127 #[test]
1128 fn recovery_with_invalid_epk() {
1129 let mut rng = OsRng;
1130 let zip212_states = [
1131 Zip212Enforcement::Off,
1132 Zip212Enforcement::GracePeriod,
1133 Zip212Enforcement::On,
1134 ];
1135
1136 for zip212_enforcement in zip212_states {
1137 let (ovk, ock, _, mut output) = random_enc_ciphertext(zip212_enforcement, &mut rng);
1138 *output.ephemeral_key_mut() = jubjub::ExtendedPoint::random(&mut rng).to_bytes().into();
1139
1140 assert_eq!(
1141 try_sapling_output_recovery(&ovk, &output, zip212_enforcement),
1142 None
1143 );
1144
1145 assert_eq!(
1146 try_sapling_output_recovery_with_ock(&ock, &output, zip212_enforcement),
1147 None
1148 );
1149 }
1150 }
1151
1152 #[test]
1153 fn recovery_with_invalid_enc_tag() {
1154 let mut rng = OsRng;
1155 let zip212_states = [
1156 Zip212Enforcement::Off,
1157 Zip212Enforcement::GracePeriod,
1158 Zip212Enforcement::On,
1159 ];
1160
1161 for zip212_enforcement in zip212_states {
1162 let (ovk, ock, _, mut output) = random_enc_ciphertext(zip212_enforcement, &mut rng);
1163
1164 output.enc_ciphertext_mut()[ENC_CIPHERTEXT_SIZE - 1] ^= 0xff;
1165 assert_eq!(
1166 try_sapling_output_recovery(&ovk, &output, zip212_enforcement),
1167 None
1168 );
1169 assert_eq!(
1170 try_sapling_output_recovery_with_ock(&ock, &output, zip212_enforcement),
1171 None
1172 );
1173 }
1174 }
1175
1176 #[test]
1177 fn recovery_with_invalid_out_tag() {
1178 let mut rng = OsRng;
1179 let zip212_states = [
1180 Zip212Enforcement::Off,
1181 Zip212Enforcement::GracePeriod,
1182 Zip212Enforcement::On,
1183 ];
1184
1185 for zip212_enforcement in zip212_states {
1186 let (ovk, ock, _, mut output) = random_enc_ciphertext(zip212_enforcement, &mut rng);
1187
1188 output.out_ciphertext_mut()[OUT_CIPHERTEXT_SIZE - 1] ^= 0xff;
1189 assert_eq!(
1190 try_sapling_output_recovery(&ovk, &output, zip212_enforcement),
1191 None
1192 );
1193 assert_eq!(
1194 try_sapling_output_recovery_with_ock(&ock, &output, zip212_enforcement),
1195 None
1196 );
1197 }
1198 }
1199
1200 #[test]
1201 fn recovery_with_invalid_version_byte() {
1202 let mut rng = OsRng;
1203 let zip212_states = [
1204 Zip212Enforcement::Off,
1205 Zip212Enforcement::GracePeriod,
1206 Zip212Enforcement::On,
1207 ];
1208 let leadbytes = [0x02, 0x03, 0x01];
1209
1210 for (zip212_enforcement, leadbyte) in zip212_states.into_iter().zip(leadbytes) {
1211 let (ovk, ock, _, mut output) = random_enc_ciphertext(zip212_enforcement, &mut rng);
1212
1213 *output.enc_ciphertext_mut() = reencrypt_enc_ciphertext(
1214 &ovk,
1215 output.cv(),
1216 output.cmu(),
1217 output.ephemeral_key(),
1218 output.enc_ciphertext(),
1219 output.out_ciphertext(),
1220 |pt| pt[0] = leadbyte,
1221 );
1222 assert_eq!(
1223 try_sapling_output_recovery(&ovk, &output, zip212_enforcement),
1224 None
1225 );
1226 assert_eq!(
1227 try_sapling_output_recovery_with_ock(&ock, &output, zip212_enforcement),
1228 None
1229 );
1230 }
1231 }
1232
1233 #[test]
1234 fn recovery_with_invalid_diversifier() {
1235 let mut rng = OsRng;
1236 let zip212_states = [
1237 Zip212Enforcement::Off,
1238 Zip212Enforcement::GracePeriod,
1239 Zip212Enforcement::On,
1240 ];
1241
1242 for zip212_enforcement in zip212_states {
1243 let (ovk, ock, _, mut output) = random_enc_ciphertext(zip212_enforcement, &mut rng);
1244
1245 *output.enc_ciphertext_mut() = reencrypt_enc_ciphertext(
1246 &ovk,
1247 output.cv(),
1248 output.cmu(),
1249 output.ephemeral_key(),
1250 output.enc_ciphertext(),
1251 output.out_ciphertext(),
1252 |pt| pt[1..12].copy_from_slice(&find_invalid_diversifier().0),
1253 );
1254 assert_eq!(
1255 try_sapling_output_recovery(&ovk, &output, zip212_enforcement),
1256 None
1257 );
1258 assert_eq!(
1259 try_sapling_output_recovery_with_ock(&ock, &output, zip212_enforcement),
1260 None
1261 );
1262 }
1263 }
1264
1265 #[test]
1266 fn recovery_with_incorrect_diversifier() {
1267 let mut rng = OsRng;
1268 let zip212_states = [
1269 Zip212Enforcement::Off,
1270 Zip212Enforcement::GracePeriod,
1271 Zip212Enforcement::On,
1272 ];
1273
1274 for zip212_enforcement in zip212_states {
1275 let (ovk, ock, _, mut output) = random_enc_ciphertext(zip212_enforcement, &mut rng);
1276
1277 *output.enc_ciphertext_mut() = reencrypt_enc_ciphertext(
1278 &ovk,
1279 output.cv(),
1280 output.cmu(),
1281 output.ephemeral_key(),
1282 output.enc_ciphertext(),
1283 output.out_ciphertext(),
1284 |pt| pt[1..12].copy_from_slice(&find_valid_diversifier().0),
1285 );
1286 assert_eq!(
1287 try_sapling_output_recovery(&ovk, &output, zip212_enforcement),
1288 None
1289 );
1290 assert_eq!(
1291 try_sapling_output_recovery_with_ock(&ock, &output, zip212_enforcement),
1292 None
1293 );
1294 }
1295 }
1296
1297 #[test]
1298 fn recovery_with_invalid_pk_d() {
1299 let mut rng = OsRng;
1300 let zip212_states = [
1301 Zip212Enforcement::Off,
1302 Zip212Enforcement::GracePeriod,
1303 Zip212Enforcement::On,
1304 ];
1305
1306 for zip212_enforcement in zip212_states {
1307 let (ovk, ock, _, mut output) = random_enc_ciphertext(zip212_enforcement, &mut rng);
1308
1309 *output.out_ciphertext_mut() = reencrypt_out_ciphertext(
1310 &ovk,
1311 output.cv(),
1312 output.cmu(),
1313 output.ephemeral_key(),
1314 output.out_ciphertext(),
1315 |pt| pt[0..32].copy_from_slice(&jubjub::ExtendedPoint::random(rng).to_bytes()),
1316 );
1317 assert_eq!(
1318 try_sapling_output_recovery(&ovk, &output, zip212_enforcement),
1319 None
1320 );
1321 assert_eq!(
1322 try_sapling_output_recovery_with_ock(&ock, &output, zip212_enforcement),
1323 None
1324 );
1325 }
1326 }
1327
1328 #[test]
1329 fn test_vectors() {
1330 let test_vectors = crate::test_vectors::note_encryption::make_test_vectors();
1331
1332 macro_rules! read_cmu {
1333 ($field:expr) => {{
1334 ExtractedNoteCommitment::from_bytes($field[..].try_into().unwrap()).unwrap()
1335 }};
1336 }
1337
1338 macro_rules! read_jubjub_scalar {
1339 ($field:expr) => {{
1340 jubjub::Fr::from_repr($field[..].try_into().unwrap()).unwrap()
1341 }};
1342 }
1343
1344 macro_rules! read_pk_d {
1345 ($field:expr) => {
1346 DiversifiedTransmissionKey::from_bytes(&$field).unwrap()
1347 };
1348 }
1349
1350 macro_rules! read_cv {
1351 ($field:expr) => {
1352 ValueCommitment::from_bytes_not_small_order(&$field).unwrap()
1353 };
1354 }
1355
1356 let zip212_enforcement = Zip212Enforcement::Off;
1357
1358 for tv in test_vectors {
1359 let ivk = PreparedIncomingViewingKey::new(&SaplingIvk(read_jubjub_scalar!(tv.ivk)));
1364 let pk_d = read_pk_d!(tv.default_pk_d);
1365 let rcm = read_jubjub_scalar!(tv.rcm);
1366 let cv = read_cv!(tv.cv);
1367 let cmu = read_cmu!(tv.cmu);
1368 let esk = EphemeralSecretKey(read_jubjub_scalar!(tv.esk));
1369 let ephemeral_key = EphemeralKeyBytes(tv.epk);
1370
1371 let shared_secret = esk.agree(&pk_d);
1376 assert_eq!(shared_secret.to_bytes(), tv.shared_secret);
1377
1378 let k_enc = shared_secret.kdf_sapling(&ephemeral_key);
1379 assert_eq!(k_enc.as_bytes(), tv.k_enc);
1380
1381 let ovk = OutgoingViewingKey(tv.ovk);
1382 let ock = prf_ock(&ovk, &cv, &cmu.to_bytes(), &ephemeral_key);
1383 assert_eq!(ock.as_ref(), tv.ock);
1384
1385 let to = PaymentAddress::from_parts(Diversifier(tv.default_d), pk_d).unwrap();
1386 let note = to.create_note(NoteValue::from_raw(tv.v), Rseed::BeforeZip212(rcm));
1387 assert_eq!(note.cmu(), cmu);
1388
1389 let output = OutputDescription::from_parts(
1390 cv.clone(),
1391 cmu,
1392 ephemeral_key,
1393 tv.c_enc,
1394 tv.c_out,
1395 [0u8; GROTH_PROOF_SIZE],
1396 );
1397
1398 match try_sapling_note_decryption(&ivk, &output, zip212_enforcement) {
1404 Some((decrypted_note, decrypted_to, decrypted_memo)) => {
1405 assert_eq!(decrypted_note, note);
1406 assert_eq!(decrypted_to, to);
1407 assert_eq!(&decrypted_memo[..], &tv.memo[..]);
1408 }
1409 None => panic!("Note decryption failed"),
1410 }
1411
1412 match try_sapling_compact_note_decryption(
1413 &ivk,
1414 &CompactOutputDescription::from(output.clone()),
1415 zip212_enforcement,
1416 ) {
1417 Some((decrypted_note, decrypted_to)) => {
1418 assert_eq!(decrypted_note, note);
1419 assert_eq!(decrypted_to, to);
1420 }
1421 None => panic!("Compact note decryption failed"),
1422 }
1423
1424 match try_sapling_output_recovery(&ovk, &output, zip212_enforcement) {
1425 Some((decrypted_note, decrypted_to, decrypted_memo)) => {
1426 assert_eq!(decrypted_note, note);
1427 assert_eq!(decrypted_to, to);
1428 assert_eq!(&decrypted_memo[..], &tv.memo[..]);
1429 }
1430 None => panic!("Output recovery failed"),
1431 }
1432
1433 match &batch::try_note_decryption(
1434 core::slice::from_ref(&ivk),
1435 &[(SaplingDomain::new(zip212_enforcement), output.clone())],
1436 )[..]
1437 {
1438 [Some(((decrypted_note, decrypted_to, decrypted_memo), i))] => {
1439 assert_eq!(decrypted_note, ¬e);
1440 assert_eq!(decrypted_to, &to);
1441 assert_eq!(&decrypted_memo[..], &tv.memo[..]);
1442 assert_eq!(*i, 0);
1443 }
1444 _ => panic!("Note decryption failed"),
1445 }
1446
1447 match &batch::try_compact_note_decryption(
1448 core::slice::from_ref(&ivk),
1449 &[(
1450 SaplingDomain::new(zip212_enforcement),
1451 CompactOutputDescription::from(output.clone()),
1452 )],
1453 )[..]
1454 {
1455 [Some(((decrypted_note, decrypted_to), i))] => {
1456 assert_eq!(decrypted_note, ¬e);
1457 assert_eq!(decrypted_to, &to);
1458 assert_eq!(*i, 0);
1459 }
1460 _ => panic!("Note decryption failed"),
1461 }
1462
1463 let ne = NoteEncryption::<SaplingDomain>::new_with_esk(esk, Some(ovk), note, tv.memo);
1468
1469 assert_eq!(ne.encrypt_note_plaintext().as_ref(), &tv.c_enc[..]);
1470 assert_eq!(
1471 &ne.encrypt_outgoing_plaintext(&cv, &cmu, &mut OsRng)[..],
1472 &tv.c_out[..]
1473 );
1474 }
1475 }
1476
1477 #[test]
1478 fn batching() {
1479 let mut rng = OsRng;
1480 let zip212_enforcement = Zip212Enforcement::On;
1481
1482 let invalid_ivk = PreparedIncomingViewingKey::new(&SaplingIvk(jubjub::Fr::random(rng)));
1484 let valid_ivk = SaplingIvk(jubjub::Fr::random(rng));
1485 let outputs: Vec<_> = (0..10)
1486 .map(|_| {
1487 (
1488 SaplingDomain::new(zip212_enforcement),
1489 random_enc_ciphertext_with(&valid_ivk, zip212_enforcement, &mut rng).2,
1490 )
1491 })
1492 .collect();
1493 let valid_ivk = PreparedIncomingViewingKey::new(&valid_ivk);
1494
1495 let res = batch::try_note_decryption(core::slice::from_ref(&invalid_ivk), &outputs);
1497 assert_eq!(res.len(), 10);
1498 assert_eq!(&res[..], &vec![None; 10][..]);
1499
1500 let res = batch::try_note_decryption(&[invalid_ivk, valid_ivk.clone()], &outputs);
1502 assert_eq!(res.len(), 10);
1503 for (result, (_, output)) in res.iter().zip(outputs.iter()) {
1504 assert!(result.is_some());
1507 assert_eq!(
1508 result,
1509 &try_sapling_note_decryption(&valid_ivk, output, zip212_enforcement)
1510 .map(|r| (r, 1))
1511 );
1512 }
1513 }
1514}