sapling_crypto/
note_encryption.rs

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