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