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