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