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