Skip to main content

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::Rng;
10
11use zcash_note_encryption::{
12    BatchDomain, COMPACT_NOTE_SIZE, Domain, ENC_CIPHERTEXT_SIZE, EphemeralKeyBytes,
13    NOTE_PLAINTEXT_SIZE, NoteEncryption, NotePlaintextBytes, OUT_PLAINTEXT_SIZE, OutPlaintextBytes,
14    OutgoingCipherKey, ShieldedOutput, try_compact_note_decryption, try_note_decryption,
15    try_output_recovery_with_ock, try_output_recovery_with_ovk,
16};
17
18use crate::{
19    Diversifier, Note, PaymentAddress, Rseed,
20    bundle::{GrothProofBytes, OutputDescription},
21    keys::{
22        DiversifiedTransmissionKey, EphemeralPublicKey, EphemeralSecretKey, OutgoingViewingKey,
23        SharedSecret,
24    },
25    value::{NoteValue, ValueCommitment},
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::rng;
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 = rng();
367/// # let mut outgoing_rng = rand_core_06::OsRng;
368///
369/// let ivk = SaplingIvk(jubjub::Scalar::random(&mut rng));
370/// let diversifier = Diversifier([0; 11]);
371/// let to = ivk.to_payment_address(diversifier).unwrap();
372/// let ovk = Some(OutgoingViewingKey([0; 32]));
373///
374/// let value = NoteValue::from_raw(1000);
375/// let rcv = ValueCommitTrapdoor::random(&mut rng);
376/// let cv = ValueCommitment::derive(value, rcv);
377/// let rseed = generate_random_rseed(
378///     Zip212Enforcement::GracePeriod,
379///     &mut rng,
380/// );
381/// let note = to.create_note(value, rseed);
382/// let cmu = note.cmu();
383///
384/// let mut enc = sapling_note_encryption(ovk, note, [0x37; 512], &mut rng);
385/// let encCiphertext = enc.encrypt_note_plaintext();
386/// let outCiphertext = enc.encrypt_outgoing_plaintext(&cv, &cmu, &mut outgoing_rng);
387/// ```
388pub fn sapling_note_encryption<R: Rng>(
389    ovk: Option<OutgoingViewingKey>,
390    note: Note,
391    memo: [u8; 512],
392    rng: &mut R,
393) -> NoteEncryption<SaplingDomain> {
394    let esk = note.generate_or_derive_esk_internal(rng);
395    NoteEncryption::new_with_esk(esk, ovk, note, memo)
396}
397
398#[allow(clippy::if_same_then_else)]
399#[allow(clippy::needless_bool)]
400pub fn plaintext_version_is_valid(zip212_enforcement: Zip212Enforcement, leadbyte: u8) -> bool {
401    match zip212_enforcement {
402        Zip212Enforcement::Off => leadbyte == 0x01,
403        Zip212Enforcement::GracePeriod => leadbyte == 0x01 || leadbyte == 0x02,
404        Zip212Enforcement::On => leadbyte == 0x02,
405    }
406}
407
408pub fn try_sapling_note_decryption<Output: ShieldedOutput<SaplingDomain, ENC_CIPHERTEXT_SIZE>>(
409    ivk: &PreparedIncomingViewingKey,
410    output: &Output,
411    zip212_enforcement: Zip212Enforcement,
412) -> Option<(Note, PaymentAddress, [u8; 512])> {
413    let domain = SaplingDomain::new(zip212_enforcement);
414    try_note_decryption(&domain, ivk, output)
415}
416
417pub fn try_sapling_compact_note_decryption<
418    Output: ShieldedOutput<SaplingDomain, COMPACT_NOTE_SIZE>,
419>(
420    ivk: &PreparedIncomingViewingKey,
421    output: &Output,
422    zip212_enforcement: Zip212Enforcement,
423) -> Option<(Note, PaymentAddress)> {
424    let domain = SaplingDomain::new(zip212_enforcement);
425    try_compact_note_decryption(&domain, ivk, output)
426}
427
428/// Recovery of the full note plaintext by the sender.
429///
430/// Attempts to decrypt and validate the given `enc_ciphertext` using the given `ock`.
431/// If successful, the corresponding Sapling note and memo are returned, along with the
432/// `PaymentAddress` to which the note was sent.
433///
434/// Implements part of section 4.19.3 of the Zcash Protocol Specification.
435/// For decryption using a Full Viewing Key see [`try_sapling_output_recovery`].
436pub fn try_sapling_output_recovery_with_ock(
437    ock: &OutgoingCipherKey,
438    output: &OutputDescription<GrothProofBytes>,
439    zip212_enforcement: Zip212Enforcement,
440) -> Option<(Note, PaymentAddress, [u8; 512])> {
441    let domain = SaplingDomain::new(zip212_enforcement);
442    try_output_recovery_with_ock(&domain, ock, output, output.out_ciphertext())
443}
444
445/// Recovery of the full note plaintext by the sender.
446///
447/// Attempts to decrypt and validate the given `enc_ciphertext` using the given `ovk`.
448/// If successful, the corresponding Sapling note and memo are returned, along with the
449/// `PaymentAddress` to which the note was sent.
450///
451/// Implements section 4.19.3 of the Zcash Protocol Specification.
452#[allow(clippy::too_many_arguments)]
453pub fn try_sapling_output_recovery(
454    ovk: &OutgoingViewingKey,
455    output: &OutputDescription<GrothProofBytes>,
456    zip212_enforcement: Zip212Enforcement,
457) -> Option<(Note, PaymentAddress, [u8; 512])> {
458    let domain = SaplingDomain::new(zip212_enforcement);
459    try_output_recovery_with_ovk(&domain, ovk, output, output.cv(), output.out_ciphertext())
460}
461
462#[cfg(test)]
463mod tests {
464    use alloc::vec::Vec;
465    use chacha20poly1305::{
466        ChaCha20Poly1305,
467        aead::{AeadInOut, KeyInit},
468    };
469    use ff::{Field, PrimeField};
470    use group::Group;
471    use group::GroupEncoding;
472    use rand_core::{CryptoRng, Rng};
473
474    use zcash_note_encryption::{
475        ENC_CIPHERTEXT_SIZE, EphemeralKeyBytes, NOTE_PLAINTEXT_SIZE, NoteEncryption,
476        OUT_CIPHERTEXT_SIZE, OUT_PLAINTEXT_SIZE, OutgoingCipherKey, batch,
477    };
478
479    use super::{
480        CompactOutputDescription, SaplingDomain, Zip212Enforcement, prf_ock,
481        sapling_note_encryption, try_sapling_compact_note_decryption, try_sapling_note_decryption,
482        try_sapling_output_recovery, try_sapling_output_recovery_with_ock,
483    };
484
485    use crate::{
486        Diversifier, PaymentAddress, Rseed, SaplingIvk,
487        bundle::{GrothProofBytes, OutputDescription},
488        constants::GROTH_PROOF_SIZE,
489        keys::{DiversifiedTransmissionKey, EphemeralSecretKey, OutgoingViewingKey},
490        note::ExtractedNoteCommitment,
491        note_encryption::PreparedIncomingViewingKey,
492        rng_compat::{OsRng, RngCore06},
493        util::generate_random_rseed,
494        value::{NoteValue, ValueCommitTrapdoor, ValueCommitment},
495    };
496
497    fn random_enc_ciphertext<R: Rng + 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!(
513            try_sapling_compact_note_decryption(
514                &prepared_ivk,
515                &CompactOutputDescription::from(output.clone()),
516                zip212_enforcement,
517            )
518            .is_some()
519        );
520
521        let ovk_output_recovery = try_sapling_output_recovery(&ovk, &output, zip212_enforcement);
522
523        let ock_output_recovery =
524            try_sapling_output_recovery_with_ock(&ock, &output, zip212_enforcement);
525        assert!(ovk_output_recovery.is_some());
526        assert!(ock_output_recovery.is_some());
527        assert_eq!(ovk_output_recovery, ock_output_recovery);
528
529        (ovk, ock, prepared_ivk, output)
530    }
531
532    fn random_enc_ciphertext_with<R: Rng + CryptoRng>(
533        ivk: &SaplingIvk,
534        zip212_enforcement: Zip212Enforcement,
535        mut rng: &mut R,
536    ) -> (
537        OutgoingViewingKey,
538        OutgoingCipherKey,
539        OutputDescription<GrothProofBytes>,
540    ) {
541        let diversifier = Diversifier([0; 11]);
542        let pa = ivk.to_payment_address(diversifier).unwrap();
543
544        // Construct the value commitment for the proof instance
545        let value = NoteValue::from_raw(100);
546        let rcv = ValueCommitTrapdoor::random(&mut rng);
547        let cv = ValueCommitment::derive(value, rcv);
548
549        let rseed = generate_random_rseed(zip212_enforcement, &mut rng);
550
551        let note = pa.create_note(value, rseed);
552        let cmu = note.cmu();
553
554        let ovk = OutgoingViewingKey([0; 32]);
555        let ne = sapling_note_encryption(Some(ovk), note, [0x37; 512], &mut rng);
556        let epk = ne.epk();
557        let ock = prf_ock(&ovk, &cv, &cmu.to_bytes(), &epk.to_bytes());
558
559        let out_ciphertext = ne.encrypt_outgoing_plaintext(&cv, &cmu, &mut RngCore06::new(rng));
560        let output = OutputDescription::from_parts(
561            cv,
562            cmu,
563            epk.to_bytes(),
564            ne.encrypt_note_plaintext(),
565            out_ciphertext,
566            [0u8; GROTH_PROOF_SIZE],
567        );
568
569        (ovk, ock, output)
570    }
571
572    fn reencrypt_out_ciphertext(
573        ovk: &OutgoingViewingKey,
574        cv: &ValueCommitment,
575        cmu: &ExtractedNoteCommitment,
576        ephemeral_key: &EphemeralKeyBytes,
577        out_ciphertext: &[u8; OUT_CIPHERTEXT_SIZE],
578        modify_plaintext: impl Fn(&mut [u8; OUT_PLAINTEXT_SIZE]),
579    ) -> [u8; OUT_CIPHERTEXT_SIZE] {
580        let ock = prf_ock(ovk, cv, &cmu.to_bytes(), ephemeral_key);
581
582        let mut op = [0; OUT_PLAINTEXT_SIZE];
583        op.copy_from_slice(&out_ciphertext[..OUT_PLAINTEXT_SIZE]);
584
585        ChaCha20Poly1305::new_from_slice(ock.as_ref())
586            .unwrap()
587            .decrypt_inout_detached(
588                (&[0u8; 12]).into(),
589                &[],
590                (&mut op[..]).into(),
591                out_ciphertext[OUT_PLAINTEXT_SIZE..].try_into().unwrap(),
592            )
593            .unwrap();
594
595        modify_plaintext(&mut op);
596
597        let tag = ChaCha20Poly1305::new_from_slice(ock.as_ref())
598            .unwrap()
599            .encrypt_inout_detached((&[0u8; 12]).into(), &[], (&mut op[..]).into())
600            .unwrap();
601
602        let mut out_ciphertext = [0u8; OUT_CIPHERTEXT_SIZE];
603        out_ciphertext[..OUT_PLAINTEXT_SIZE].copy_from_slice(&op);
604        out_ciphertext[OUT_PLAINTEXT_SIZE..].copy_from_slice(&tag);
605        out_ciphertext
606    }
607
608    fn reencrypt_enc_ciphertext(
609        ovk: &OutgoingViewingKey,
610        cv: &ValueCommitment,
611        cmu: &ExtractedNoteCommitment,
612        ephemeral_key: &EphemeralKeyBytes,
613        enc_ciphertext: &[u8; ENC_CIPHERTEXT_SIZE],
614        out_ciphertext: &[u8; OUT_CIPHERTEXT_SIZE],
615        modify_plaintext: impl Fn(&mut [u8; NOTE_PLAINTEXT_SIZE]),
616    ) -> [u8; ENC_CIPHERTEXT_SIZE] {
617        let ock = prf_ock(ovk, cv, &cmu.to_bytes(), ephemeral_key);
618
619        let mut op = [0; OUT_PLAINTEXT_SIZE];
620        op.copy_from_slice(&out_ciphertext[..OUT_PLAINTEXT_SIZE]);
621
622        ChaCha20Poly1305::new_from_slice(ock.as_ref())
623            .unwrap()
624            .decrypt_inout_detached(
625                (&[0u8; 12]).into(),
626                &[],
627                (&mut op[..]).into(),
628                out_ciphertext[OUT_PLAINTEXT_SIZE..].try_into().unwrap(),
629            )
630            .unwrap();
631
632        let pk_d = DiversifiedTransmissionKey::from_bytes(&op[0..32].try_into().unwrap()).unwrap();
633
634        let esk = jubjub::Fr::from_repr(op[32..OUT_PLAINTEXT_SIZE].try_into().unwrap()).unwrap();
635
636        let shared_secret = EphemeralSecretKey(esk).agree(&pk_d);
637        let key = shared_secret.kdf_sapling(ephemeral_key);
638
639        let mut plaintext = [0; NOTE_PLAINTEXT_SIZE];
640        plaintext.copy_from_slice(&enc_ciphertext[..NOTE_PLAINTEXT_SIZE]);
641
642        ChaCha20Poly1305::new_from_slice(key.as_bytes())
643            .unwrap()
644            .decrypt_inout_detached(
645                (&[0u8; 12]).into(),
646                &[],
647                (&mut plaintext[..]).into(),
648                enc_ciphertext[NOTE_PLAINTEXT_SIZE..].try_into().unwrap(),
649            )
650            .unwrap();
651
652        modify_plaintext(&mut plaintext);
653
654        let tag = ChaCha20Poly1305::new_from_slice(key.as_ref())
655            .unwrap()
656            .encrypt_inout_detached((&[0u8; 12]).into(), &[], (&mut plaintext[..]).into())
657            .unwrap();
658
659        let mut enc_ciphertext = [0u8; ENC_CIPHERTEXT_SIZE];
660        enc_ciphertext[..NOTE_PLAINTEXT_SIZE].copy_from_slice(&plaintext);
661        enc_ciphertext[NOTE_PLAINTEXT_SIZE..].copy_from_slice(&tag);
662        enc_ciphertext
663    }
664
665    fn find_invalid_diversifier() -> Diversifier {
666        // Find an invalid diversifier
667        let mut d = Diversifier([0; 11]);
668        loop {
669            for k in 0..11 {
670                d.0[k] = d.0[k].wrapping_add(1);
671                if d.0[k] != 0 {
672                    break;
673                }
674            }
675            if d.g_d().is_none() {
676                break;
677            }
678        }
679        d
680    }
681
682    fn find_valid_diversifier() -> Diversifier {
683        // Find a different valid diversifier
684        let mut d = Diversifier([0; 11]);
685        loop {
686            for k in 0..11 {
687                d.0[k] = d.0[k].wrapping_add(1);
688                if d.0[k] != 0 {
689                    break;
690                }
691            }
692            if d.g_d().is_some() {
693                break;
694            }
695        }
696        d
697    }
698
699    #[test]
700    fn decryption_with_invalid_ivk() {
701        let mut rng = OsRng;
702        let zip212_states = [
703            Zip212Enforcement::Off,
704            Zip212Enforcement::GracePeriod,
705            Zip212Enforcement::On,
706        ];
707
708        for zip212_enforcement in zip212_states {
709            let (_, _, _, output) = random_enc_ciphertext(zip212_enforcement, &mut rng);
710
711            assert_eq!(
712                try_sapling_note_decryption(
713                    &PreparedIncomingViewingKey::new(&SaplingIvk(jubjub::Fr::random(&mut rng))),
714                    &output,
715                    zip212_enforcement,
716                ),
717                None
718            );
719        }
720    }
721
722    #[test]
723    fn decryption_with_invalid_epk() {
724        let mut rng = OsRng;
725        let zip212_states = [
726            Zip212Enforcement::Off,
727            Zip212Enforcement::GracePeriod,
728            Zip212Enforcement::On,
729        ];
730
731        for zip212_enforcement in zip212_states {
732            let (_, _, ivk, mut output) = random_enc_ciphertext(zip212_enforcement, &mut rng);
733
734            *output.ephemeral_key_mut() = jubjub::ExtendedPoint::random(&mut rng).to_bytes().into();
735
736            assert_eq!(
737                try_sapling_note_decryption(&ivk, &output, zip212_enforcement,),
738                None
739            );
740        }
741    }
742
743    #[test]
744    fn decryption_with_invalid_cmu() {
745        let mut rng = OsRng;
746        let zip212_states = [
747            Zip212Enforcement::Off,
748            Zip212Enforcement::GracePeriod,
749            Zip212Enforcement::On,
750        ];
751
752        for zip212_enforcement in zip212_states {
753            let (_, _, ivk, mut output) = random_enc_ciphertext(zip212_enforcement, &mut rng);
754            *output.cmu_mut() =
755                ExtractedNoteCommitment::from_bytes(&bls12_381::Scalar::random(&mut rng).to_repr())
756                    .unwrap();
757
758            assert_eq!(
759                try_sapling_note_decryption(&ivk, &output, zip212_enforcement,),
760                None
761            );
762        }
763    }
764
765    #[test]
766    fn decryption_with_invalid_tag() {
767        let mut rng = OsRng;
768        let zip212_states = [
769            Zip212Enforcement::Off,
770            Zip212Enforcement::GracePeriod,
771            Zip212Enforcement::On,
772        ];
773
774        for zip212_enforcement in zip212_states {
775            let (_, _, ivk, mut output) = random_enc_ciphertext(zip212_enforcement, &mut rng);
776            output.enc_ciphertext_mut()[ENC_CIPHERTEXT_SIZE - 1] ^= 0xff;
777
778            assert_eq!(
779                try_sapling_note_decryption(&ivk, &output, zip212_enforcement,),
780                None
781            );
782        }
783    }
784
785    #[test]
786    fn decryption_with_invalid_version_byte() {
787        let mut rng = OsRng;
788        let zip212_states = [
789            Zip212Enforcement::Off,
790            Zip212Enforcement::GracePeriod,
791            Zip212Enforcement::On,
792        ];
793        let leadbytes = [0x02, 0x03, 0x01];
794
795        for (zip212_enforcement, leadbyte) in zip212_states.into_iter().zip(leadbytes) {
796            let (ovk, _, ivk, mut output) = random_enc_ciphertext(zip212_enforcement, &mut rng);
797
798            *output.enc_ciphertext_mut() = reencrypt_enc_ciphertext(
799                &ovk,
800                output.cv(),
801                output.cmu(),
802                output.ephemeral_key(),
803                output.enc_ciphertext(),
804                output.out_ciphertext(),
805                |pt| pt[0] = leadbyte,
806            );
807            assert_eq!(
808                try_sapling_note_decryption(&ivk, &output, zip212_enforcement),
809                None
810            );
811        }
812    }
813
814    #[test]
815    fn decryption_with_invalid_diversifier() {
816        let mut rng = OsRng;
817        let zip212_states = [
818            Zip212Enforcement::Off,
819            Zip212Enforcement::GracePeriod,
820            Zip212Enforcement::On,
821        ];
822
823        for zip212_enforcement in zip212_states {
824            let (ovk, _, ivk, mut output) = random_enc_ciphertext(zip212_enforcement, &mut rng);
825
826            *output.enc_ciphertext_mut() = reencrypt_enc_ciphertext(
827                &ovk,
828                output.cv(),
829                output.cmu(),
830                output.ephemeral_key(),
831                output.enc_ciphertext(),
832                output.out_ciphertext(),
833                |pt| pt[1..12].copy_from_slice(&find_invalid_diversifier().0),
834            );
835            assert_eq!(
836                try_sapling_note_decryption(&ivk, &output, zip212_enforcement),
837                None
838            );
839        }
840    }
841
842    #[test]
843    fn decryption_with_incorrect_diversifier() {
844        let mut rng = OsRng;
845        let zip212_states = [
846            Zip212Enforcement::Off,
847            Zip212Enforcement::GracePeriod,
848            Zip212Enforcement::On,
849        ];
850
851        for zip212_enforcement in zip212_states {
852            let (ovk, _, ivk, mut output) = random_enc_ciphertext(zip212_enforcement, &mut rng);
853
854            *output.enc_ciphertext_mut() = reencrypt_enc_ciphertext(
855                &ovk,
856                output.cv(),
857                output.cmu(),
858                output.ephemeral_key(),
859                output.enc_ciphertext(),
860                output.out_ciphertext(),
861                |pt| pt[1..12].copy_from_slice(&find_valid_diversifier().0),
862            );
863
864            assert_eq!(
865                try_sapling_note_decryption(&ivk, &output, zip212_enforcement),
866                None
867            );
868        }
869    }
870
871    #[test]
872    fn compact_decryption_with_invalid_ivk() {
873        let mut rng = OsRng;
874        let zip212_states = [
875            Zip212Enforcement::Off,
876            Zip212Enforcement::GracePeriod,
877            Zip212Enforcement::On,
878        ];
879
880        for zip212_enforcement in zip212_states {
881            let (_, _, _, output) = random_enc_ciphertext(zip212_enforcement, &mut rng);
882
883            assert_eq!(
884                try_sapling_compact_note_decryption(
885                    &PreparedIncomingViewingKey::new(&SaplingIvk(jubjub::Fr::random(&mut rng))),
886                    &CompactOutputDescription::from(output),
887                    zip212_enforcement,
888                ),
889                None
890            );
891        }
892    }
893
894    #[test]
895    fn compact_decryption_with_invalid_epk() {
896        let mut rng = OsRng;
897        let zip212_states = [
898            Zip212Enforcement::Off,
899            Zip212Enforcement::GracePeriod,
900            Zip212Enforcement::On,
901        ];
902
903        for zip212_enforcement in zip212_states {
904            let (_, _, ivk, mut output) = random_enc_ciphertext(zip212_enforcement, &mut rng);
905            *output.ephemeral_key_mut() = jubjub::ExtendedPoint::random(&mut rng).to_bytes().into();
906
907            assert_eq!(
908                try_sapling_compact_note_decryption(
909                    &ivk,
910                    &CompactOutputDescription::from(output),
911                    zip212_enforcement,
912                ),
913                None
914            );
915        }
916    }
917
918    #[test]
919    fn compact_decryption_with_invalid_cmu() {
920        let mut rng = OsRng;
921        let zip212_states = [
922            Zip212Enforcement::Off,
923            Zip212Enforcement::GracePeriod,
924            Zip212Enforcement::On,
925        ];
926
927        for zip212_enforcement in zip212_states {
928            let (_, _, ivk, mut output) = random_enc_ciphertext(zip212_enforcement, &mut rng);
929            *output.cmu_mut() =
930                ExtractedNoteCommitment::from_bytes(&bls12_381::Scalar::random(&mut rng).to_repr())
931                    .unwrap();
932
933            assert_eq!(
934                try_sapling_compact_note_decryption(
935                    &ivk,
936                    &CompactOutputDescription::from(output),
937                    zip212_enforcement,
938                ),
939                None
940            );
941        }
942    }
943
944    #[test]
945    fn compact_decryption_with_invalid_version_byte() {
946        let mut rng = OsRng;
947        let zip212_states = [
948            Zip212Enforcement::Off,
949            Zip212Enforcement::GracePeriod,
950            Zip212Enforcement::On,
951        ];
952        let leadbytes = [0x02, 0x03, 0x01];
953
954        for (zip212_enforcement, leadbyte) in zip212_states.into_iter().zip(leadbytes) {
955            let (ovk, _, ivk, mut output) = random_enc_ciphertext(zip212_enforcement, &mut rng);
956
957            *output.enc_ciphertext_mut() = reencrypt_enc_ciphertext(
958                &ovk,
959                output.cv(),
960                output.cmu(),
961                output.ephemeral_key(),
962                output.enc_ciphertext(),
963                output.out_ciphertext(),
964                |pt| pt[0] = leadbyte,
965            );
966            assert_eq!(
967                try_sapling_compact_note_decryption(
968                    &ivk,
969                    &CompactOutputDescription::from(output),
970                    zip212_enforcement,
971                ),
972                None
973            );
974        }
975    }
976
977    #[test]
978    fn compact_decryption_with_invalid_diversifier() {
979        let mut rng = OsRng;
980        let zip212_states = [
981            Zip212Enforcement::Off,
982            Zip212Enforcement::GracePeriod,
983            Zip212Enforcement::On,
984        ];
985
986        for zip212_enforcement in zip212_states {
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[1..12].copy_from_slice(&find_invalid_diversifier().0),
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_incorrect_diversifier() {
1011        let mut rng = OsRng;
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_valid_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 recovery_with_invalid_ovk() {
1043        let mut rng = OsRng;
1044        let zip212_states = [
1045            Zip212Enforcement::Off,
1046            Zip212Enforcement::GracePeriod,
1047            Zip212Enforcement::On,
1048        ];
1049
1050        for zip212_enforcement in zip212_states {
1051            let (mut ovk, _, _, output) = random_enc_ciphertext(zip212_enforcement, &mut rng);
1052
1053            ovk.0[0] ^= 0xff;
1054            assert_eq!(
1055                try_sapling_output_recovery(&ovk, &output, zip212_enforcement),
1056                None
1057            );
1058        }
1059    }
1060
1061    #[test]
1062    fn recovery_with_invalid_ock() {
1063        let mut rng = OsRng;
1064        let zip212_states = [
1065            Zip212Enforcement::Off,
1066            Zip212Enforcement::GracePeriod,
1067            Zip212Enforcement::On,
1068        ];
1069
1070        for zip212_enforcement in zip212_states {
1071            let (_, _, _, output) = random_enc_ciphertext(zip212_enforcement, &mut rng);
1072
1073            assert_eq!(
1074                try_sapling_output_recovery_with_ock(
1075                    &OutgoingCipherKey([0u8; 32]),
1076                    &output,
1077                    zip212_enforcement,
1078                ),
1079                None
1080            );
1081        }
1082    }
1083
1084    #[test]
1085    fn recovery_with_invalid_cv() {
1086        let mut rng = OsRng;
1087        let zip212_states = [
1088            Zip212Enforcement::Off,
1089            Zip212Enforcement::GracePeriod,
1090            Zip212Enforcement::On,
1091        ];
1092
1093        for zip212_enforcement in zip212_states {
1094            let (ovk, _, _, mut output) = random_enc_ciphertext(zip212_enforcement, &mut rng);
1095            *output.cv_mut() = ValueCommitment::derive(
1096                NoteValue::from_raw(7),
1097                ValueCommitTrapdoor::random(&mut rng),
1098            );
1099
1100            assert_eq!(
1101                try_sapling_output_recovery(&ovk, &output, zip212_enforcement,),
1102                None
1103            );
1104        }
1105    }
1106
1107    #[test]
1108    fn recovery_with_invalid_cmu() {
1109        let mut rng = OsRng;
1110        let zip212_states = [
1111            Zip212Enforcement::Off,
1112            Zip212Enforcement::GracePeriod,
1113            Zip212Enforcement::On,
1114        ];
1115
1116        for zip212_enforcement in zip212_states {
1117            let (ovk, ock, _, mut output) = random_enc_ciphertext(zip212_enforcement, &mut rng);
1118            *output.cmu_mut() =
1119                ExtractedNoteCommitment::from_bytes(&bls12_381::Scalar::random(&mut rng).to_repr())
1120                    .unwrap();
1121
1122            assert_eq!(
1123                try_sapling_output_recovery(&ovk, &output, zip212_enforcement),
1124                None
1125            );
1126
1127            assert_eq!(
1128                try_sapling_output_recovery_with_ock(&ock, &output, zip212_enforcement),
1129                None
1130            );
1131        }
1132    }
1133
1134    #[test]
1135    fn recovery_with_invalid_epk() {
1136        let mut rng = OsRng;
1137        let zip212_states = [
1138            Zip212Enforcement::Off,
1139            Zip212Enforcement::GracePeriod,
1140            Zip212Enforcement::On,
1141        ];
1142
1143        for zip212_enforcement in zip212_states {
1144            let (ovk, ock, _, mut output) = random_enc_ciphertext(zip212_enforcement, &mut rng);
1145            *output.ephemeral_key_mut() = jubjub::ExtendedPoint::random(&mut rng).to_bytes().into();
1146
1147            assert_eq!(
1148                try_sapling_output_recovery(&ovk, &output, zip212_enforcement),
1149                None
1150            );
1151
1152            assert_eq!(
1153                try_sapling_output_recovery_with_ock(&ock, &output, zip212_enforcement),
1154                None
1155            );
1156        }
1157    }
1158
1159    #[test]
1160    fn recovery_with_invalid_enc_tag() {
1161        let mut rng = OsRng;
1162        let zip212_states = [
1163            Zip212Enforcement::Off,
1164            Zip212Enforcement::GracePeriod,
1165            Zip212Enforcement::On,
1166        ];
1167
1168        for zip212_enforcement in zip212_states {
1169            let (ovk, ock, _, mut output) = random_enc_ciphertext(zip212_enforcement, &mut rng);
1170
1171            output.enc_ciphertext_mut()[ENC_CIPHERTEXT_SIZE - 1] ^= 0xff;
1172            assert_eq!(
1173                try_sapling_output_recovery(&ovk, &output, zip212_enforcement),
1174                None
1175            );
1176            assert_eq!(
1177                try_sapling_output_recovery_with_ock(&ock, &output, zip212_enforcement),
1178                None
1179            );
1180        }
1181    }
1182
1183    #[test]
1184    fn recovery_with_invalid_out_tag() {
1185        let mut rng = OsRng;
1186        let zip212_states = [
1187            Zip212Enforcement::Off,
1188            Zip212Enforcement::GracePeriod,
1189            Zip212Enforcement::On,
1190        ];
1191
1192        for zip212_enforcement in zip212_states {
1193            let (ovk, ock, _, mut output) = random_enc_ciphertext(zip212_enforcement, &mut rng);
1194
1195            output.out_ciphertext_mut()[OUT_CIPHERTEXT_SIZE - 1] ^= 0xff;
1196            assert_eq!(
1197                try_sapling_output_recovery(&ovk, &output, zip212_enforcement),
1198                None
1199            );
1200            assert_eq!(
1201                try_sapling_output_recovery_with_ock(&ock, &output, zip212_enforcement),
1202                None
1203            );
1204        }
1205    }
1206
1207    #[test]
1208    fn recovery_with_invalid_version_byte() {
1209        let mut rng = OsRng;
1210        let zip212_states = [
1211            Zip212Enforcement::Off,
1212            Zip212Enforcement::GracePeriod,
1213            Zip212Enforcement::On,
1214        ];
1215        let leadbytes = [0x02, 0x03, 0x01];
1216
1217        for (zip212_enforcement, leadbyte) in zip212_states.into_iter().zip(leadbytes) {
1218            let (ovk, ock, _, mut output) = random_enc_ciphertext(zip212_enforcement, &mut rng);
1219
1220            *output.enc_ciphertext_mut() = reencrypt_enc_ciphertext(
1221                &ovk,
1222                output.cv(),
1223                output.cmu(),
1224                output.ephemeral_key(),
1225                output.enc_ciphertext(),
1226                output.out_ciphertext(),
1227                |pt| pt[0] = leadbyte,
1228            );
1229            assert_eq!(
1230                try_sapling_output_recovery(&ovk, &output, zip212_enforcement),
1231                None
1232            );
1233            assert_eq!(
1234                try_sapling_output_recovery_with_ock(&ock, &output, zip212_enforcement),
1235                None
1236            );
1237        }
1238    }
1239
1240    #[test]
1241    fn recovery_with_invalid_diversifier() {
1242        let mut rng = OsRng;
1243        let zip212_states = [
1244            Zip212Enforcement::Off,
1245            Zip212Enforcement::GracePeriod,
1246            Zip212Enforcement::On,
1247        ];
1248
1249        for zip212_enforcement in zip212_states {
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[1..12].copy_from_slice(&find_invalid_diversifier().0),
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_incorrect_diversifier() {
1274        let mut rng = OsRng;
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_valid_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_invalid_pk_d() {
1306        let mut rng = OsRng;
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            let invalid_pk_d = jubjub::ExtendedPoint::random(&mut rng).to_bytes();
1316
1317            *output.out_ciphertext_mut() = reencrypt_out_ciphertext(
1318                &ovk,
1319                output.cv(),
1320                output.cmu(),
1321                output.ephemeral_key(),
1322                output.out_ciphertext(),
1323                |pt| pt[0..32].copy_from_slice(&invalid_pk_d),
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 test_vectors() {
1338        let test_vectors = crate::test_vectors::note_encryption::make_test_vectors();
1339
1340        macro_rules! read_cmu {
1341            ($field:expr_2021) => {{ ExtractedNoteCommitment::from_bytes($field[..].try_into().unwrap()).unwrap() }};
1342        }
1343
1344        macro_rules! read_jubjub_scalar {
1345            ($field:expr_2021) => {{ jubjub::Fr::from_repr($field[..].try_into().unwrap()).unwrap() }};
1346        }
1347
1348        macro_rules! read_pk_d {
1349            ($field:expr_2021) => {
1350                DiversifiedTransmissionKey::from_bytes(&$field).unwrap()
1351            };
1352        }
1353
1354        macro_rules! read_cv {
1355            ($field:expr_2021) => {
1356                ValueCommitment::from_bytes_not_small_order(&$field).unwrap()
1357            };
1358        }
1359
1360        let zip212_enforcement = Zip212Enforcement::Off;
1361
1362        for tv in test_vectors {
1363            //
1364            // Load the test vector components
1365            //
1366
1367            let ivk = PreparedIncomingViewingKey::new(&SaplingIvk(read_jubjub_scalar!(tv.ivk)));
1368            let pk_d = read_pk_d!(tv.default_pk_d);
1369            let rcm = read_jubjub_scalar!(tv.rcm);
1370            let cv = read_cv!(tv.cv);
1371            let cmu = read_cmu!(tv.cmu);
1372            let esk = EphemeralSecretKey(read_jubjub_scalar!(tv.esk));
1373            let ephemeral_key = EphemeralKeyBytes(tv.epk);
1374
1375            //
1376            // Test the individual components
1377            //
1378
1379            let shared_secret = esk.agree(&pk_d);
1380            assert_eq!(shared_secret.to_bytes(), tv.shared_secret);
1381
1382            let k_enc = shared_secret.kdf_sapling(&ephemeral_key);
1383            assert_eq!(k_enc.as_bytes(), tv.k_enc);
1384
1385            let ovk = OutgoingViewingKey(tv.ovk);
1386            let ock = prf_ock(&ovk, &cv, &cmu.to_bytes(), &ephemeral_key);
1387            assert_eq!(ock.as_ref(), tv.ock);
1388
1389            let to = PaymentAddress::from_parts(Diversifier(tv.default_d), pk_d).unwrap();
1390            let note = to.create_note(NoteValue::from_raw(tv.v), Rseed::BeforeZip212(rcm));
1391            assert_eq!(note.cmu(), cmu);
1392
1393            let output = OutputDescription::from_parts(
1394                cv.clone(),
1395                cmu,
1396                ephemeral_key,
1397                tv.c_enc,
1398                tv.c_out,
1399                [0u8; GROTH_PROOF_SIZE],
1400            );
1401
1402            //
1403            // Test decryption
1404            // (Tested first because it only requires immutable references.)
1405            //
1406
1407            match try_sapling_note_decryption(&ivk, &output, zip212_enforcement) {
1408                Some((decrypted_note, decrypted_to, decrypted_memo)) => {
1409                    assert_eq!(decrypted_note, note);
1410                    assert_eq!(decrypted_to, to);
1411                    assert_eq!(&decrypted_memo[..], &tv.memo[..]);
1412                }
1413                None => panic!("Note decryption failed"),
1414            }
1415
1416            match try_sapling_compact_note_decryption(
1417                &ivk,
1418                &CompactOutputDescription::from(output.clone()),
1419                zip212_enforcement,
1420            ) {
1421                Some((decrypted_note, decrypted_to)) => {
1422                    assert_eq!(decrypted_note, note);
1423                    assert_eq!(decrypted_to, to);
1424                }
1425                None => panic!("Compact note decryption failed"),
1426            }
1427
1428            match try_sapling_output_recovery(&ovk, &output, zip212_enforcement) {
1429                Some((decrypted_note, decrypted_to, decrypted_memo)) => {
1430                    assert_eq!(decrypted_note, note);
1431                    assert_eq!(decrypted_to, to);
1432                    assert_eq!(&decrypted_memo[..], &tv.memo[..]);
1433                }
1434                None => panic!("Output recovery failed"),
1435            }
1436
1437            match &batch::try_note_decryption(
1438                core::slice::from_ref(&ivk),
1439                &[(SaplingDomain::new(zip212_enforcement), output.clone())],
1440            )[..]
1441            {
1442                [Some(((decrypted_note, decrypted_to, decrypted_memo), i))] => {
1443                    assert_eq!(decrypted_note, &note);
1444                    assert_eq!(decrypted_to, &to);
1445                    assert_eq!(&decrypted_memo[..], &tv.memo[..]);
1446                    assert_eq!(*i, 0);
1447                }
1448                _ => panic!("Note decryption failed"),
1449            }
1450
1451            match &batch::try_compact_note_decryption(
1452                core::slice::from_ref(&ivk),
1453                &[(
1454                    SaplingDomain::new(zip212_enforcement),
1455                    CompactOutputDescription::from(output.clone()),
1456                )],
1457            )[..]
1458            {
1459                [Some(((decrypted_note, decrypted_to), i))] => {
1460                    assert_eq!(decrypted_note, &note);
1461                    assert_eq!(decrypted_to, &to);
1462                    assert_eq!(*i, 0);
1463                }
1464                _ => panic!("Note decryption failed"),
1465            }
1466
1467            //
1468            // Test encryption
1469            //
1470
1471            let ne = NoteEncryption::<SaplingDomain>::new_with_esk(esk, Some(ovk), note, tv.memo);
1472
1473            assert_eq!(ne.encrypt_note_plaintext().as_ref(), &tv.c_enc[..]);
1474            assert_eq!(
1475                &ne.encrypt_outgoing_plaintext(&cv, &cmu, &mut RngCore06::new(&mut OsRng),)[..],
1476                &tv.c_out[..]
1477            );
1478        }
1479    }
1480
1481    #[test]
1482    fn batching() {
1483        let mut rng = OsRng;
1484        let zip212_enforcement = Zip212Enforcement::On;
1485
1486        // Test batch trial-decryption with multiple IVKs and outputs.
1487        let invalid_ivk =
1488            PreparedIncomingViewingKey::new(&SaplingIvk(jubjub::Fr::random(&mut rng)));
1489        let valid_ivk = SaplingIvk(jubjub::Fr::random(&mut rng));
1490        let outputs: Vec<_> = (0..10)
1491            .map(|_| {
1492                (
1493                    SaplingDomain::new(zip212_enforcement),
1494                    random_enc_ciphertext_with(&valid_ivk, zip212_enforcement, &mut rng).2,
1495                )
1496            })
1497            .collect();
1498        let valid_ivk = PreparedIncomingViewingKey::new(&valid_ivk);
1499
1500        // Check that batched trial decryptions with invalid_ivk fails.
1501        let res = batch::try_note_decryption(core::slice::from_ref(&invalid_ivk), &outputs);
1502        assert_eq!(res.len(), 10);
1503        assert_eq!(&res[..], &vec![None; 10][..]);
1504
1505        // Check that batched trial decryptions with valid_ivk succeeds.
1506        let res = batch::try_note_decryption(&[invalid_ivk, valid_ivk.clone()], &outputs);
1507        assert_eq!(res.len(), 10);
1508        for (result, (_, output)) in res.iter().zip(outputs.iter()) {
1509            // Confirm the successful batched trial decryptions gave the same result.
1510            // In all cases, the index of the valid ivk is returned.
1511            assert!(result.is_some());
1512            assert_eq!(
1513                result,
1514                &try_sapling_note_decryption(&valid_ivk, output, zip212_enforcement)
1515                    .map(|r| (r, 1))
1516            );
1517        }
1518    }
1519}