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