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