mwc_zcash_primitives/
note_encryption.rs

1//! Implementation of in-band secret distribution for Zcash transactions.
2
3use crate::{
4    consensus::{self, BlockHeight, NetworkUpgrade::Canopy, ZIP212_GRACE_PERIOD},
5    primitives::{Diversifier, Note, PaymentAddress, Rseed},
6};
7use blake2b_simd::{Hash as Blake2bHash, Params as Blake2bParams};
8use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
9use crypto_api_chachapoly::{ChaCha20Ietf, ChachaPolyIetf};
10use ff::PrimeField;
11use group::{cofactor::CofactorGroup, GroupEncoding};
12use rand_core::{CryptoRng, RngCore};
13use std::convert::TryInto;
14use std::fmt;
15use std::str;
16
17use crate::keys::OutgoingViewingKey;
18
19pub const KDF_SAPLING_PERSONALIZATION: &[u8; 16] = b"Zcash_SaplingKDF";
20pub const PRF_OCK_PERSONALIZATION: &[u8; 16] = b"Zcash_Derive_ock";
21
22const COMPACT_NOTE_SIZE: usize = 1 + // version
23    11 + // diversifier
24    8  + // value
25    32; // rcv
26const NOTE_PLAINTEXT_SIZE: usize = COMPACT_NOTE_SIZE + 512;
27const OUT_PLAINTEXT_SIZE: usize = 32 + // pk_d
28    32; // esk
29pub const ENC_CIPHERTEXT_SIZE: usize = NOTE_PLAINTEXT_SIZE + 16;
30pub const OUT_CIPHERTEXT_SIZE: usize = OUT_PLAINTEXT_SIZE + 16;
31
32/// Format a byte array as a colon-delimited hex string.
33///
34/// Source: https://github.com/tendermint/signatory
35/// License: MIT / Apache 2.0
36fn fmt_colon_delimited_hex<B>(f: &mut fmt::Formatter<'_>, bytes: B) -> fmt::Result
37where
38    B: AsRef<[u8]>,
39{
40    let len = bytes.as_ref().len();
41
42    for (i, byte) in bytes.as_ref().iter().enumerate() {
43        write!(f, "{:02x}", byte)?;
44
45        if i != len - 1 {
46            write!(f, ":")?;
47        }
48    }
49
50    Ok(())
51}
52
53/// An unencrypted memo received alongside a shielded note in a Zcash transaction.
54#[derive(Clone)]
55pub struct Memo([u8; 512]);
56
57impl fmt::Debug for Memo {
58    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
59        write!(f, "Memo(")?;
60        match self.to_utf8() {
61            Some(Ok(memo)) => write!(f, "\"{}\"", memo)?,
62            _ => fmt_colon_delimited_hex(f, &self.0[..])?,
63        }
64        write!(f, ")")
65    }
66}
67
68impl Default for Memo {
69    fn default() -> Self {
70        // Empty memo field indication per ZIP 302
71        let mut memo = [0u8; 512];
72        memo[0] = 0xF6;
73        Memo(memo)
74    }
75}
76
77impl PartialEq for Memo {
78    fn eq(&self, rhs: &Memo) -> bool {
79        self.0[..] == rhs.0[..]
80    }
81}
82
83impl Memo {
84    /// Returns a `Memo` containing the given slice, appending with zero bytes if
85    /// necessary, or `None` if the slice is too long. If the slice is empty,
86    /// `Memo::default` is returned.
87    pub fn from_bytes(memo: &[u8]) -> Option<Memo> {
88        if memo.is_empty() {
89            Some(Memo::default())
90        } else if memo.len() <= 512 {
91            let mut data = [0; 512];
92            data[0..memo.len()].copy_from_slice(memo);
93            Some(Memo(data))
94        } else {
95            // memo is too long
96            None
97        }
98    }
99
100    /// Returns the underlying bytes of the `Memo`.
101    pub fn as_bytes(&self) -> &[u8] {
102        &self.0[..]
103    }
104
105    /// Returns:
106    /// - `None` if the memo is not text
107    /// - `Some(Ok(memo))` if the memo contains a valid UTF-8 string
108    /// - `Some(Err(e))` if the memo contains invalid UTF-8
109    pub fn to_utf8(&self) -> Option<Result<String, str::Utf8Error>> {
110        // Check if it is a text or binary memo
111        if self.0[0] < 0xF5 {
112            // Check if it is valid UTF8
113            Some(str::from_utf8(&self.0).map(|memo| {
114                // Drop trailing zeroes
115                memo.trim_end_matches(char::from(0)).to_owned()
116            }))
117        } else {
118            None
119        }
120    }
121}
122
123impl str::FromStr for Memo {
124    type Err = ();
125
126    /// Returns a `Memo` containing the given string, or an error if the string is too long.
127    fn from_str(memo: &str) -> Result<Self, Self::Err> {
128        Memo::from_bytes(memo.as_bytes()).ok_or(())
129    }
130}
131
132/// Sapling key agreement for note encryption.
133///
134/// Implements section 5.4.4.3 of the Zcash Protocol Specification.
135pub fn sapling_ka_agree(esk: &jubjub::Fr, pk_d: &jubjub::ExtendedPoint) -> jubjub::SubgroupPoint {
136    // [8 esk] pk_d
137    // <ExtendedPoint as CofactorGroup>::clear_cofactor is implemented using
138    // ExtendedPoint::mul_by_cofactor in the jubjub crate.
139
140    let mut wnaf = group::Wnaf::new();
141    wnaf.scalar(esk).base(*pk_d).clear_cofactor()
142}
143
144/// Sapling KDF for note encryption.
145///
146/// Implements section 5.4.4.4 of the Zcash Protocol Specification.
147fn kdf_sapling(dhsecret: jubjub::SubgroupPoint, epk: &jubjub::ExtendedPoint) -> Blake2bHash {
148    Blake2bParams::new()
149        .hash_length(32)
150        .personal(KDF_SAPLING_PERSONALIZATION)
151        .to_state()
152        .update(&dhsecret.to_bytes())
153        .update(&epk.to_bytes())
154        .finalize()
155}
156
157/// A symmetric key that can be used to recover a single Sapling output.
158pub struct OutgoingCipherKey([u8; 32]);
159
160impl From<[u8; 32]> for OutgoingCipherKey {
161    fn from(ock: [u8; 32]) -> Self {
162        OutgoingCipherKey(ock)
163    }
164}
165
166impl AsRef<[u8]> for OutgoingCipherKey {
167    fn as_ref(&self) -> &[u8] {
168        &self.0
169    }
170}
171
172/// Sapling PRF^ock.
173///
174/// Implemented per section 5.4.2 of the Zcash Protocol Specification.
175pub fn prf_ock(
176    ovk: &OutgoingViewingKey,
177    cv: &jubjub::ExtendedPoint,
178    cmu: &bls12_381::Scalar,
179    epk: &jubjub::ExtendedPoint,
180) -> OutgoingCipherKey {
181    OutgoingCipherKey(
182        Blake2bParams::new()
183            .hash_length(32)
184            .personal(PRF_OCK_PERSONALIZATION)
185            .to_state()
186            .update(&ovk.0)
187            .update(&cv.to_bytes())
188            .update(&cmu.to_repr())
189            .update(&epk.to_bytes())
190            .finalize()
191            .as_bytes()
192            .try_into()
193            .unwrap(),
194    )
195}
196
197/// An API for encrypting Sapling notes.
198///
199/// This struct provides a safe API for encrypting Sapling notes. In particular, it
200/// enforces that fresh ephemeral keys are used for every note, and that the ciphertexts
201/// are consistent with each other.
202///
203/// Implements section 4.17.1 of the Zcash Protocol Specification.
204/// NB: the example code is only covering the pre-Canopy case.
205///
206/// # Examples
207///
208/// ```
209/// extern crate ff;
210/// extern crate rand_core;
211/// extern crate zcash_primitives;
212///
213/// use ff::Field;
214/// use rand_core::OsRng;
215/// use zcash_primitives::{
216///     keys::{OutgoingViewingKey, prf_expand},
217///     note_encryption::{Memo, SaplingNoteEncryption},
218///     primitives::{Diversifier, PaymentAddress, Rseed, ValueCommitment},
219/// };
220///
221/// let mut rng = OsRng;
222///
223/// let diversifier = Diversifier([0; 11]);
224/// let pk_d = diversifier.g_d().unwrap();
225/// let to = PaymentAddress::from_parts(diversifier, pk_d).unwrap();
226/// let ovk = Some(OutgoingViewingKey([0; 32]));
227///
228/// let value = 1000;
229/// let rcv = jubjub::Fr::random(&mut rng);
230/// let cv = ValueCommitment {
231///     value,
232///     randomness: rcv.clone(),
233/// };
234/// let rcm = jubjub::Fr::random(&mut rng);
235/// let note = to.create_note(value, Rseed::BeforeZip212(rcm)).unwrap();
236/// let cmu = note.cmu();
237///
238/// let mut enc = SaplingNoteEncryption::new(ovk, note, to, Memo::default(), &mut rng);
239/// let encCiphertext = enc.encrypt_note_plaintext();
240/// let outCiphertext = enc.encrypt_outgoing_plaintext(&cv.commitment().into(), &cmu);
241/// ```
242pub struct SaplingNoteEncryption<R: RngCore> {
243    epk: jubjub::SubgroupPoint,
244    esk: jubjub::Fr,
245    note: Note,
246    to: PaymentAddress,
247    memo: Memo,
248    /// `None` represents the `ovk = ⊥` case.
249    ovk: Option<OutgoingViewingKey>,
250    rng: R,
251}
252
253impl<R: RngCore + CryptoRng> SaplingNoteEncryption<R> {
254    /// Creates a new encryption context for the given note.
255    ///
256    /// Setting `ovk` to `None` represents the `ovk = ⊥` case, where the note cannot be
257    /// recovered by the sender.
258    pub fn new(
259        ovk: Option<OutgoingViewingKey>,
260        note: Note,
261        to: PaymentAddress,
262        memo: Memo,
263        rng: R,
264    ) -> Self {
265        Self::new_internal(ovk, note, to, memo, rng)
266    }
267}
268
269impl<R: RngCore> SaplingNoteEncryption<R> {
270    pub(crate) fn new_internal(
271        ovk: Option<OutgoingViewingKey>,
272        note: Note,
273        to: PaymentAddress,
274        memo: Memo,
275        mut rng: R,
276    ) -> Self {
277        let esk = note.generate_or_derive_esk_internal(&mut rng);
278        let epk = note.g_d * esk;
279
280        SaplingNoteEncryption {
281            epk,
282            esk,
283            note,
284            to,
285            memo,
286            ovk,
287            rng,
288        }
289    }
290
291    /// Exposes the ephemeral secret key being used to encrypt this note.
292    pub fn esk(&self) -> &jubjub::Fr {
293        &self.esk
294    }
295
296    /// Exposes the ephemeral public key being used to encrypt this note.
297    pub fn epk(&self) -> &jubjub::SubgroupPoint {
298        &self.epk
299    }
300
301    /// Generates `encCiphertext` for this note.
302    pub fn encrypt_note_plaintext(&self) -> [u8; ENC_CIPHERTEXT_SIZE] {
303        let shared_secret = sapling_ka_agree(&self.esk, self.to.pk_d().into());
304        let key = kdf_sapling(shared_secret, &self.epk.into());
305
306        // Note plaintext encoding is defined in section 5.5 of the Zcash Protocol
307        // Specification.
308        let mut input = [0; NOTE_PLAINTEXT_SIZE];
309        input[0] = match self.note.rseed {
310            Rseed::BeforeZip212(_) => 1,
311            Rseed::AfterZip212(_) => 2,
312        };
313        input[1..12].copy_from_slice(&self.to.diversifier().0);
314        (&mut input[12..20])
315            .write_u64::<LittleEndian>(self.note.value)
316            .unwrap();
317        match self.note.rseed {
318            Rseed::BeforeZip212(rcm) => {
319                input[20..COMPACT_NOTE_SIZE].copy_from_slice(rcm.to_repr().as_ref());
320            }
321            Rseed::AfterZip212(rseed) => {
322                input[20..COMPACT_NOTE_SIZE].copy_from_slice(&rseed);
323            }
324        }
325        input[COMPACT_NOTE_SIZE..NOTE_PLAINTEXT_SIZE].copy_from_slice(&self.memo.0);
326
327        let mut output = [0u8; ENC_CIPHERTEXT_SIZE];
328        assert_eq!(
329            ChachaPolyIetf::aead_cipher()
330                .seal_to(&mut output, &input, &[], &key.as_bytes(), &[0u8; 12])
331                .unwrap(),
332            ENC_CIPHERTEXT_SIZE
333        );
334
335        output
336    }
337
338    /// Generates `outCiphertext` for this note.
339    pub fn encrypt_outgoing_plaintext(
340        &mut self,
341        cv: &jubjub::ExtendedPoint,
342        cmu: &bls12_381::Scalar,
343    ) -> [u8; OUT_CIPHERTEXT_SIZE] {
344        let (ock, input) = if let Some(ovk) = &self.ovk {
345            let ock = prf_ock(ovk, &cv, &cmu, &self.epk.into());
346
347            let mut input = [0u8; OUT_PLAINTEXT_SIZE];
348            input[0..32].copy_from_slice(&self.note.pk_d.to_bytes());
349            input[32..OUT_PLAINTEXT_SIZE].copy_from_slice(self.esk.to_repr().as_ref());
350
351            (ock, input)
352        } else {
353            // ovk = ⊥
354            let mut ock = OutgoingCipherKey([0; 32]);
355            let mut input = [0u8; OUT_PLAINTEXT_SIZE];
356
357            self.rng.fill_bytes(&mut ock.0);
358            self.rng.fill_bytes(&mut input);
359
360            (ock, input)
361        };
362
363        let mut output = [0u8; OUT_CIPHERTEXT_SIZE];
364        assert_eq!(
365            ChachaPolyIetf::aead_cipher()
366                .seal_to(&mut output, &input, &[], ock.as_ref(), &[0u8; 12])
367                .unwrap(),
368            OUT_CIPHERTEXT_SIZE
369        );
370
371        output
372    }
373}
374
375fn parse_note_plaintext_without_memo<P: consensus::Parameters>(
376    params: &P,
377    height: BlockHeight,
378    ivk: &jubjub::Fr,
379    epk: &jubjub::ExtendedPoint,
380    cmu: &bls12_381::Scalar,
381    plaintext: &[u8],
382) -> Option<(Note, PaymentAddress)> {
383    // Check note plaintext version
384    if !plaintext_version_is_valid(params, height, plaintext[0]) {
385        return None;
386    }
387
388    let mut d = [0u8; 11];
389    d.copy_from_slice(&plaintext[1..12]);
390
391    let v = (&plaintext[12..20]).read_u64::<LittleEndian>().ok()?;
392
393    let r: [u8; 32] = plaintext[20..COMPACT_NOTE_SIZE]
394        .try_into()
395        .expect("slice is the correct length");
396
397    let rseed = if plaintext[0] == 0x01 {
398        let rcm = jubjub::Fr::from_repr(r)?;
399        Rseed::BeforeZip212(rcm)
400    } else {
401        Rseed::AfterZip212(r)
402    };
403
404    let diversifier = Diversifier(d);
405    let pk_d = diversifier.g_d()? * ivk;
406
407    let to = PaymentAddress::from_parts(diversifier, pk_d)?;
408    let note = to.create_note(v, rseed).unwrap();
409
410    if note.cmu() != *cmu {
411        // Published commitment doesn't match calculated commitment
412        return None;
413    }
414
415    if let Some(derived_esk) = note.derive_esk() {
416        // This enforces that epk is a jubjub::SubgroupPoint.
417        if (note.g_d * derived_esk).to_bytes() != epk.to_bytes() {
418            return None;
419        }
420    }
421
422    Some((note, to))
423}
424
425#[allow(clippy::if_same_then_else)]
426#[allow(clippy::needless_bool)]
427pub fn plaintext_version_is_valid<P: consensus::Parameters>(
428    params: &P,
429    height: BlockHeight,
430    leadbyte: u8,
431) -> bool {
432    if params.is_nu_active(Canopy, height) {
433        let grace_period_end_height =
434            params.activation_height(Canopy).unwrap() + ZIP212_GRACE_PERIOD;
435
436        if height < grace_period_end_height && leadbyte != 0x01 && leadbyte != 0x02 {
437            // non-{0x01,0x02} received after Canopy activation and before grace period has elapsed
438            false
439        } else if height >= grace_period_end_height && leadbyte != 0x02 {
440            // non-0x02 received past (Canopy activation height + grace period)
441            false
442        } else {
443            true
444        }
445    } else {
446        // return false if non-0x01 received when Canopy is not active
447        leadbyte == 0x01
448    }
449}
450
451/// Trial decryption of the full note plaintext by the recipient.
452///
453/// Attempts to decrypt and validate the given `enc_ciphertext` using the given `ivk`.
454/// If successful, the corresponding Sapling note and memo are returned, along with the
455/// `PaymentAddress` to which the note was sent.
456///
457/// Implements section 4.17.2 of the Zcash Protocol Specification.
458pub fn try_sapling_note_decryption<P: consensus::Parameters>(
459    params: &P,
460    height: BlockHeight,
461    ivk: &jubjub::Fr,
462    epk: &jubjub::ExtendedPoint,
463    cmu: &bls12_381::Scalar,
464    enc_ciphertext: &[u8],
465) -> Option<(Note, PaymentAddress, Memo)> {
466    assert_eq!(enc_ciphertext.len(), ENC_CIPHERTEXT_SIZE);
467
468    let shared_secret = sapling_ka_agree(ivk, &epk);
469    let key = kdf_sapling(shared_secret, &epk);
470
471    let mut plaintext = [0; ENC_CIPHERTEXT_SIZE];
472    assert_eq!(
473        ChachaPolyIetf::aead_cipher()
474            .open_to(
475                &mut plaintext,
476                &enc_ciphertext,
477                &[],
478                key.as_bytes(),
479                &[0u8; 12]
480            )
481            .ok()?,
482        NOTE_PLAINTEXT_SIZE
483    );
484
485    let (note, to) = parse_note_plaintext_without_memo(params, height, ivk, epk, cmu, &plaintext)?;
486
487    let mut memo = [0u8; 512];
488    memo.copy_from_slice(&plaintext[COMPACT_NOTE_SIZE..NOTE_PLAINTEXT_SIZE]);
489
490    Some((note, to, Memo(memo)))
491}
492
493/// Trial decryption of the compact note plaintext by the recipient for light clients.
494///
495/// Attempts to decrypt and validate the first 52 bytes of `enc_ciphertext` using the
496/// given `ivk`. If successful, the corresponding Sapling note is returned, along with the
497/// `PaymentAddress` to which the note was sent.
498///
499/// Implements the procedure specified in [`ZIP 307`].
500///
501/// [`ZIP 307`]: https://zips.z.cash/zip-0307
502pub fn try_sapling_compact_note_decryption<P: consensus::Parameters>(
503    params: &P,
504    height: BlockHeight,
505    ivk: &jubjub::Fr,
506    epk: &jubjub::ExtendedPoint,
507    cmu: &bls12_381::Scalar,
508    enc_ciphertext: &[u8],
509) -> Option<(Note, PaymentAddress)> {
510    assert_eq!(enc_ciphertext.len(), COMPACT_NOTE_SIZE);
511
512    let shared_secret = sapling_ka_agree(ivk, epk);
513    let key = kdf_sapling(shared_secret, &epk);
514
515    // Start from block 1 to skip over Poly1305 keying output
516    let mut plaintext = [0; COMPACT_NOTE_SIZE];
517    plaintext.copy_from_slice(&enc_ciphertext);
518    ChaCha20Ietf::xor(key.as_bytes(), &[0u8; 12], 1, &mut plaintext);
519
520    parse_note_plaintext_without_memo(params, height, ivk, epk, cmu, &plaintext)
521}
522
523/// Recovery of the full note plaintext by the sender.
524///
525/// Attempts to decrypt and validate the given `enc_ciphertext` using the given `ock`.
526/// If successful, the corresponding Sapling note and memo are returned, along with the
527/// `PaymentAddress` to which the note was sent.
528///
529/// Implements part of section 4.17.3 of the Zcash Protocol Specification.
530/// For decryption using a Full Viewing Key see [`try_sapling_output_recovery`].
531pub fn try_sapling_output_recovery_with_ock<P: consensus::Parameters>(
532    params: &P,
533    height: BlockHeight,
534    ock: &OutgoingCipherKey,
535    cmu: &bls12_381::Scalar,
536    epk: &jubjub::ExtendedPoint,
537    enc_ciphertext: &[u8],
538    out_ciphertext: &[u8],
539) -> Option<(Note, PaymentAddress, Memo)> {
540    assert_eq!(enc_ciphertext.len(), ENC_CIPHERTEXT_SIZE);
541    assert_eq!(out_ciphertext.len(), OUT_CIPHERTEXT_SIZE);
542
543    let mut op = [0; OUT_CIPHERTEXT_SIZE];
544    assert_eq!(
545        ChachaPolyIetf::aead_cipher()
546            .open_to(&mut op, &out_ciphertext, &[], ock.as_ref(), &[0u8; 12])
547            .ok()?,
548        OUT_PLAINTEXT_SIZE
549    );
550
551    let pk_d = {
552        let pk_d = jubjub::SubgroupPoint::from_bytes(
553            op[0..32].try_into().expect("slice is the correct length"),
554        );
555        if pk_d.is_none().into() {
556            return None;
557        }
558        pk_d.unwrap()
559    };
560
561    let esk = jubjub::Fr::from_repr(
562        op[32..OUT_PLAINTEXT_SIZE]
563            .try_into()
564            .expect("slice is the correct length"),
565    )?;
566
567    let shared_secret = sapling_ka_agree(&esk, &pk_d.into());
568    let key = kdf_sapling(shared_secret, &epk);
569
570    let mut plaintext = [0; ENC_CIPHERTEXT_SIZE];
571    assert_eq!(
572        ChachaPolyIetf::aead_cipher()
573            .open_to(
574                &mut plaintext,
575                &enc_ciphertext,
576                &[],
577                key.as_bytes(),
578                &[0u8; 12]
579            )
580            .ok()?,
581        NOTE_PLAINTEXT_SIZE
582    );
583
584    // Check note plaintext version
585    if !plaintext_version_is_valid(params, height, plaintext[0]) {
586        return None;
587    }
588
589    let mut d = [0u8; 11];
590    d.copy_from_slice(&plaintext[1..12]);
591
592    let v = (&plaintext[12..20]).read_u64::<LittleEndian>().ok()?;
593
594    let r: [u8; 32] = plaintext[20..COMPACT_NOTE_SIZE]
595        .try_into()
596        .expect("slice is the correct length");
597
598    let rseed = if plaintext[0] == 0x01 {
599        let rcm = jubjub::Fr::from_repr(r)?;
600        Rseed::BeforeZip212(rcm)
601    } else {
602        Rseed::AfterZip212(r)
603    };
604
605    let mut memo = [0u8; 512];
606    memo.copy_from_slice(&plaintext[COMPACT_NOTE_SIZE..NOTE_PLAINTEXT_SIZE]);
607
608    let diversifier = Diversifier(d);
609    if (diversifier.g_d()? * esk).to_bytes() != epk.to_bytes() {
610        // Published epk doesn't match calculated epk
611        return None;
612    }
613
614    let to = PaymentAddress::from_parts(diversifier, pk_d)?;
615    let note = to.create_note(v, rseed).unwrap();
616
617    if note.cmu() != *cmu {
618        // Published commitment doesn't match calculated commitment
619        return None;
620    }
621
622    if let Some(derived_esk) = note.derive_esk() {
623        if derived_esk != esk {
624            return None;
625        }
626    }
627
628    Some((note, to, Memo(memo)))
629}
630
631/// Recovery of the full note plaintext by the sender.
632///
633/// Attempts to decrypt and validate the given `enc_ciphertext` using the given `ovk`.
634/// If successful, the corresponding Sapling note and memo are returned, along with the
635/// `PaymentAddress` to which the note was sent.
636///
637/// Implements section 4.17.3 of the Zcash Protocol Specification.
638pub fn try_sapling_output_recovery<P: consensus::Parameters>(
639    params: &P,
640    height: BlockHeight,
641    ovk: &OutgoingViewingKey,
642    cv: &jubjub::ExtendedPoint,
643    cmu: &bls12_381::Scalar,
644    epk: &jubjub::ExtendedPoint,
645    enc_ciphertext: &[u8],
646    out_ciphertext: &[u8],
647) -> Option<(Note, PaymentAddress, Memo)> {
648    try_sapling_output_recovery_with_ock::<P>(
649        params,
650        height,
651        &prf_ock(&ovk, &cv, &cmu, &epk),
652        cmu,
653        epk,
654        enc_ciphertext,
655        out_ciphertext,
656    )
657}
658
659#[cfg(test)]
660mod tests {
661    use crypto_api_chachapoly::ChachaPolyIetf;
662    use ff::{Field, PrimeField};
663    use group::Group;
664    use group::{cofactor::CofactorGroup, GroupEncoding};
665    use rand_core::OsRng;
666    use rand_core::{CryptoRng, RngCore};
667    use std::convert::TryInto;
668    use std::str::FromStr;
669
670    use super::{
671        kdf_sapling, prf_ock, sapling_ka_agree, try_sapling_compact_note_decryption,
672        try_sapling_note_decryption, try_sapling_output_recovery,
673        try_sapling_output_recovery_with_ock, Memo, OutgoingCipherKey, SaplingNoteEncryption,
674        COMPACT_NOTE_SIZE, ENC_CIPHERTEXT_SIZE, NOTE_PLAINTEXT_SIZE, OUT_CIPHERTEXT_SIZE,
675        OUT_PLAINTEXT_SIZE,
676    };
677
678    use crate::{
679        consensus::{
680            BlockHeight,
681            NetworkUpgrade::{Canopy, Sapling},
682            Parameters, TEST_NETWORK, ZIP212_GRACE_PERIOD,
683        },
684        keys::OutgoingViewingKey,
685        primitives::{Diversifier, PaymentAddress, Rseed, ValueCommitment},
686        util::generate_random_rseed,
687    };
688
689    #[test]
690    fn memo_from_str() {
691        assert_eq!(
692            Memo::from_str("").unwrap(),
693            Memo([
694                0xf6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
695                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
696                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
697                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
698                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
699                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
700                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
701                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
702                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
703                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
704                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
705                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
706                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
707                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
708                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
709                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
710                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
711                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
712                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
713                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
714                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
715                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
716                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
717                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
718                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
719                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
720                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
721                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
722                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
723                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
724                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
725                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
726                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
727                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
728                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
729                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
730                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
731            ])
732        );
733        assert_eq!(
734            Memo::from_str(
735                "thiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiis \
736                 iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiis \
737                 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \
738                 veeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeryyyyyyyyyyyyyyyyyyyyyyyyyy \
739                 looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong \
740                 meeeeeeeeeeeeeeeeeeemooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo \
741                 but it's just short enough"
742            )
743            .unwrap(),
744            Memo([
745                0x74, 0x68, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69,
746                0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69,
747                0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69,
748                0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69,
749                0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69,
750                0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x73, 0x20, 0x69, 0x69, 0x69,
751                0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69,
752                0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69,
753                0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69,
754                0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69,
755                0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x69,
756                0x69, 0x69, 0x69, 0x69, 0x69, 0x69, 0x73, 0x20, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
757                0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
758                0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
759                0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
760                0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
761                0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x61,
762                0x61, 0x61, 0x61, 0x61, 0x20, 0x76, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65,
763                0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65,
764                0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65,
765                0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65,
766                0x65, 0x65, 0x72, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79,
767                0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79, 0x79,
768                0x79, 0x20, 0x6c, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f,
769                0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f,
770                0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f,
771                0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f,
772                0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f,
773                0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6e, 0x67, 0x20, 0x6d,
774                0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65, 0x65,
775                0x65, 0x65, 0x65, 0x65, 0x65, 0x6d, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f,
776                0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f,
777                0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f,
778                0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f,
779                0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x6f, 0x20, 0x62, 0x75, 0x74, 0x20,
780                0x69, 0x74, 0x27, 0x73, 0x20, 0x6a, 0x75, 0x73, 0x74, 0x20, 0x73, 0x68, 0x6f, 0x72,
781                0x74, 0x20, 0x65, 0x6e, 0x6f, 0x75, 0x67, 0x68
782            ])
783        );
784        assert_eq!(
785            Memo::from_str(
786                "thiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiis \
787                 iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiis \
788                 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \
789                 veeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeryyyyyyyyyyyyyyyyyyyyyyyyyy \
790                 looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong \
791                 meeeeeeeeeeeeeeeeeeemooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo \
792                 but it's now a bit too long"
793            ),
794            Err(())
795        );
796    }
797
798    #[test]
799    fn memo_to_utf8() {
800        let memo = Memo::from_str("Test memo").unwrap();
801        assert_eq!(memo.to_utf8(), Some(Ok("Test memo".to_owned())));
802        assert_eq!(Memo::default().to_utf8(), None);
803    }
804
805    fn random_enc_ciphertext<R: RngCore + CryptoRng>(
806        height: BlockHeight,
807        mut rng: &mut R,
808    ) -> (
809        OutgoingViewingKey,
810        OutgoingCipherKey,
811        jubjub::Fr,
812        jubjub::ExtendedPoint,
813        bls12_381::Scalar,
814        jubjub::ExtendedPoint,
815        [u8; ENC_CIPHERTEXT_SIZE],
816        [u8; OUT_CIPHERTEXT_SIZE],
817    ) {
818        let ivk = jubjub::Fr::random(&mut rng);
819
820        let (ovk, ock, ivk, cv, cmu, epk, enc_ciphertext, out_ciphertext) =
821            random_enc_ciphertext_with(height, ivk, rng);
822
823        assert!(try_sapling_note_decryption(
824            &TEST_NETWORK,
825            height,
826            &ivk,
827            &epk,
828            &cmu,
829            &enc_ciphertext
830        )
831        .is_some());
832        assert!(try_sapling_compact_note_decryption(
833            &TEST_NETWORK,
834            height,
835            &ivk,
836            &epk,
837            &cmu,
838            &enc_ciphertext[..COMPACT_NOTE_SIZE]
839        )
840        .is_some());
841
842        let ovk_output_recovery = try_sapling_output_recovery(
843            &TEST_NETWORK,
844            height,
845            &ovk,
846            &cv,
847            &cmu,
848            &epk,
849            &enc_ciphertext,
850            &out_ciphertext,
851        );
852
853        let ock_output_recovery = try_sapling_output_recovery_with_ock(
854            &TEST_NETWORK,
855            height,
856            &ock,
857            &cmu,
858            &epk,
859            &enc_ciphertext,
860            &out_ciphertext,
861        );
862        assert!(ovk_output_recovery.is_some());
863        assert!(ock_output_recovery.is_some());
864        assert_eq!(ovk_output_recovery, ock_output_recovery);
865
866        (ovk, ock, ivk, cv, cmu, epk, enc_ciphertext, out_ciphertext)
867    }
868
869    fn random_enc_ciphertext_with<R: RngCore + CryptoRng>(
870        height: BlockHeight,
871        ivk: jubjub::Fr,
872        mut rng: &mut R,
873    ) -> (
874        OutgoingViewingKey,
875        OutgoingCipherKey,
876        jubjub::Fr,
877        jubjub::ExtendedPoint,
878        bls12_381::Scalar,
879        jubjub::ExtendedPoint,
880        [u8; ENC_CIPHERTEXT_SIZE],
881        [u8; OUT_CIPHERTEXT_SIZE],
882    ) {
883        let diversifier = Diversifier([0; 11]);
884        let pk_d = diversifier.g_d().unwrap() * ivk;
885        let pa = PaymentAddress::from_parts_unchecked(diversifier, pk_d);
886
887        // Construct the value commitment for the proof instance
888        let value = 100;
889        let value_commitment = ValueCommitment {
890            value,
891            randomness: jubjub::Fr::random(&mut rng),
892        };
893        let cv = value_commitment.commitment().into();
894
895        let rseed = generate_random_rseed(&TEST_NETWORK, height, &mut rng);
896
897        let note = pa.create_note(value, rseed).unwrap();
898        let cmu = note.cmu();
899
900        let ovk = OutgoingViewingKey([0; 32]);
901        let mut ne = SaplingNoteEncryption::new(Some(ovk), note, pa, Memo([0; 512]), &mut rng);
902        let epk = ne.epk().clone().into();
903        let enc_ciphertext = ne.encrypt_note_plaintext();
904        let out_ciphertext = ne.encrypt_outgoing_plaintext(&cv, &cmu);
905        let ock = prf_ock(&ovk, &cv, &cmu, &epk);
906
907        (ovk, ock, ivk, cv, cmu, epk, enc_ciphertext, out_ciphertext)
908    }
909
910    fn reencrypt_enc_ciphertext(
911        ovk: &OutgoingViewingKey,
912        cv: &jubjub::ExtendedPoint,
913        cmu: &bls12_381::Scalar,
914        epk: &jubjub::ExtendedPoint,
915        enc_ciphertext: &mut [u8; ENC_CIPHERTEXT_SIZE],
916        out_ciphertext: &[u8; OUT_CIPHERTEXT_SIZE],
917        modify_plaintext: impl Fn(&mut [u8; NOTE_PLAINTEXT_SIZE]),
918    ) {
919        let ock = prf_ock(&ovk, &cv, &cmu, &epk);
920
921        let mut op = [0; OUT_CIPHERTEXT_SIZE];
922        assert_eq!(
923            ChachaPolyIetf::aead_cipher()
924                .open_to(&mut op, out_ciphertext, &[], ock.as_ref(), &[0u8; 12])
925                .unwrap(),
926            OUT_PLAINTEXT_SIZE
927        );
928
929        let pk_d = jubjub::SubgroupPoint::from_bytes(&op[0..32].try_into().unwrap()).unwrap();
930
931        let esk = jubjub::Fr::from_repr(op[32..OUT_PLAINTEXT_SIZE].try_into().unwrap()).unwrap();
932
933        let shared_secret = sapling_ka_agree(&esk, &pk_d.into());
934        let key = kdf_sapling(shared_secret, &epk);
935
936        let mut plaintext = {
937            let mut buf = [0; ENC_CIPHERTEXT_SIZE];
938            assert_eq!(
939                ChachaPolyIetf::aead_cipher()
940                    .open_to(&mut buf, enc_ciphertext, &[], key.as_bytes(), &[0u8; 12])
941                    .unwrap(),
942                NOTE_PLAINTEXT_SIZE
943            );
944            let mut pt = [0; NOTE_PLAINTEXT_SIZE];
945            pt.copy_from_slice(&buf[..NOTE_PLAINTEXT_SIZE]);
946            pt
947        };
948
949        modify_plaintext(&mut plaintext);
950
951        assert_eq!(
952            ChachaPolyIetf::aead_cipher()
953                .seal_to(enc_ciphertext, &plaintext, &[], &key.as_bytes(), &[0u8; 12])
954                .unwrap(),
955            ENC_CIPHERTEXT_SIZE
956        );
957    }
958
959    fn find_invalid_diversifier() -> Diversifier {
960        // Find an invalid diversifier
961        let mut d = Diversifier([0; 11]);
962        loop {
963            for k in 0..11 {
964                d.0[k] = d.0[k].wrapping_add(1);
965                if d.0[k] != 0 {
966                    break;
967                }
968            }
969            if d.g_d().is_none() {
970                break;
971            }
972        }
973        d
974    }
975
976    fn find_valid_diversifier() -> Diversifier {
977        // Find a different valid diversifier
978        let mut d = Diversifier([0; 11]);
979        loop {
980            for k in 0..11 {
981                d.0[k] = d.0[k].wrapping_add(1);
982                if d.0[k] != 0 {
983                    break;
984                }
985            }
986            if d.g_d().is_some() {
987                break;
988            }
989        }
990        d
991    }
992
993    #[test]
994    fn decryption_with_invalid_ivk() {
995        let mut rng = OsRng;
996        let heights = [
997            TEST_NETWORK.activation_height(Sapling).unwrap(),
998            TEST_NETWORK.activation_height(Canopy).unwrap(),
999        ];
1000
1001        for &height in heights.iter() {
1002            let (_, _, _, _, cmu, epk, enc_ciphertext, _) = random_enc_ciphertext(height, &mut rng);
1003
1004            assert_eq!(
1005                try_sapling_note_decryption(
1006                    &TEST_NETWORK,
1007                    height,
1008                    &jubjub::Fr::random(&mut rng),
1009                    &epk,
1010                    &cmu,
1011                    &enc_ciphertext
1012                ),
1013                None
1014            );
1015        }
1016    }
1017
1018    #[test]
1019    fn decryption_with_invalid_epk() {
1020        let mut rng = OsRng;
1021        let heights = [
1022            TEST_NETWORK.activation_height(Sapling).unwrap(),
1023            TEST_NETWORK.activation_height(Canopy).unwrap(),
1024        ];
1025
1026        for &height in heights.iter() {
1027            let (_, _, ivk, _, cmu, _, enc_ciphertext, _) = random_enc_ciphertext(height, &mut rng);
1028
1029            assert_eq!(
1030                try_sapling_note_decryption(
1031                    &TEST_NETWORK,
1032                    height,
1033                    &ivk,
1034                    &jubjub::ExtendedPoint::random(&mut rng),
1035                    &cmu,
1036                    &enc_ciphertext
1037                ),
1038                None
1039            );
1040        }
1041    }
1042
1043    #[test]
1044    fn decryption_with_invalid_cmu() {
1045        let mut rng = OsRng;
1046        let heights = [
1047            TEST_NETWORK.activation_height(Sapling).unwrap(),
1048            TEST_NETWORK.activation_height(Canopy).unwrap(),
1049        ];
1050
1051        for &height in heights.iter() {
1052            let (_, _, ivk, _, _, epk, enc_ciphertext, _) = random_enc_ciphertext(height, &mut rng);
1053
1054            assert_eq!(
1055                try_sapling_note_decryption(
1056                    &TEST_NETWORK,
1057                    height,
1058                    &ivk,
1059                    &epk,
1060                    &bls12_381::Scalar::random(&mut rng),
1061                    &enc_ciphertext
1062                ),
1063                None
1064            );
1065        }
1066    }
1067
1068    #[test]
1069    fn decryption_with_invalid_tag() {
1070        let mut rng = OsRng;
1071        let heights = [
1072            TEST_NETWORK.activation_height(Sapling).unwrap(),
1073            TEST_NETWORK.activation_height(Canopy).unwrap(),
1074        ];
1075
1076        for &height in heights.iter() {
1077            let (_, _, ivk, _, cmu, epk, mut enc_ciphertext, _) =
1078                random_enc_ciphertext(height, &mut rng);
1079
1080            enc_ciphertext[ENC_CIPHERTEXT_SIZE - 1] ^= 0xff;
1081            assert_eq!(
1082                try_sapling_note_decryption(
1083                    &TEST_NETWORK,
1084                    height,
1085                    &ivk,
1086                    &epk,
1087                    &cmu,
1088                    &enc_ciphertext
1089                ),
1090                None
1091            );
1092        }
1093    }
1094
1095    #[test]
1096    fn decryption_with_invalid_version_byte() {
1097        let mut rng = OsRng;
1098        let canopy_activation_height = TEST_NETWORK.activation_height(Canopy).unwrap();
1099        let heights = [
1100            canopy_activation_height - 1,
1101            canopy_activation_height,
1102            canopy_activation_height + ZIP212_GRACE_PERIOD,
1103        ];
1104        let leadbytes = [0x02, 0x03, 0x01];
1105
1106        for (&height, &leadbyte) in heights.iter().zip(leadbytes.iter()) {
1107            let (ovk, _, ivk, cv, cmu, epk, mut enc_ciphertext, out_ciphertext) =
1108                random_enc_ciphertext(height, &mut rng);
1109
1110            reencrypt_enc_ciphertext(
1111                &ovk,
1112                &cv,
1113                &cmu,
1114                &epk,
1115                &mut enc_ciphertext,
1116                &out_ciphertext,
1117                |pt| pt[0] = leadbyte,
1118            );
1119            assert_eq!(
1120                try_sapling_note_decryption(
1121                    &TEST_NETWORK,
1122                    height,
1123                    &ivk,
1124                    &epk,
1125                    &cmu,
1126                    &enc_ciphertext
1127                ),
1128                None
1129            );
1130        }
1131    }
1132
1133    #[test]
1134    fn decryption_with_invalid_diversifier() {
1135        let mut rng = OsRng;
1136        let heights = [
1137            TEST_NETWORK.activation_height(Sapling).unwrap(),
1138            TEST_NETWORK.activation_height(Canopy).unwrap(),
1139        ];
1140
1141        for &height in heights.iter() {
1142            let (ovk, _, ivk, cv, cmu, epk, mut enc_ciphertext, out_ciphertext) =
1143                random_enc_ciphertext(height, &mut rng);
1144
1145            reencrypt_enc_ciphertext(
1146                &ovk,
1147                &cv,
1148                &cmu,
1149                &epk,
1150                &mut enc_ciphertext,
1151                &out_ciphertext,
1152                |pt| pt[1..12].copy_from_slice(&find_invalid_diversifier().0),
1153            );
1154            assert_eq!(
1155                try_sapling_note_decryption(
1156                    &TEST_NETWORK,
1157                    height,
1158                    &ivk,
1159                    &epk,
1160                    &cmu,
1161                    &enc_ciphertext
1162                ),
1163                None
1164            );
1165        }
1166    }
1167
1168    #[test]
1169    fn decryption_with_incorrect_diversifier() {
1170        let mut rng = OsRng;
1171        let heights = [
1172            TEST_NETWORK.activation_height(Sapling).unwrap(),
1173            TEST_NETWORK.activation_height(Canopy).unwrap(),
1174        ];
1175
1176        for &height in heights.iter() {
1177            let (ovk, _, ivk, cv, cmu, epk, mut enc_ciphertext, out_ciphertext) =
1178                random_enc_ciphertext(height, &mut rng);
1179
1180            reencrypt_enc_ciphertext(
1181                &ovk,
1182                &cv,
1183                &cmu,
1184                &epk,
1185                &mut enc_ciphertext,
1186                &out_ciphertext,
1187                |pt| pt[1..12].copy_from_slice(&find_valid_diversifier().0),
1188            );
1189
1190            assert_eq!(
1191                try_sapling_note_decryption(
1192                    &TEST_NETWORK,
1193                    height,
1194                    &ivk,
1195                    &epk,
1196                    &cmu,
1197                    &enc_ciphertext
1198                ),
1199                None
1200            );
1201        }
1202    }
1203
1204    #[test]
1205    fn compact_decryption_with_invalid_ivk() {
1206        let mut rng = OsRng;
1207        let heights = [
1208            TEST_NETWORK.activation_height(Sapling).unwrap(),
1209            TEST_NETWORK.activation_height(Canopy).unwrap(),
1210        ];
1211
1212        for &height in heights.iter() {
1213            let (_, _, _, _, cmu, epk, enc_ciphertext, _) = random_enc_ciphertext(height, &mut rng);
1214
1215            assert_eq!(
1216                try_sapling_compact_note_decryption(
1217                    &TEST_NETWORK,
1218                    height,
1219                    &jubjub::Fr::random(&mut rng),
1220                    &epk,
1221                    &cmu,
1222                    &enc_ciphertext[..COMPACT_NOTE_SIZE]
1223                ),
1224                None
1225            );
1226        }
1227    }
1228
1229    #[test]
1230    fn compact_decryption_with_invalid_epk() {
1231        let mut rng = OsRng;
1232        let heights = [
1233            TEST_NETWORK.activation_height(Sapling).unwrap(),
1234            TEST_NETWORK.activation_height(Canopy).unwrap(),
1235        ];
1236
1237        for &height in heights.iter() {
1238            let (_, _, ivk, _, cmu, _, enc_ciphertext, _) = random_enc_ciphertext(height, &mut rng);
1239
1240            assert_eq!(
1241                try_sapling_compact_note_decryption(
1242                    &TEST_NETWORK,
1243                    height,
1244                    &ivk,
1245                    &jubjub::ExtendedPoint::random(&mut rng),
1246                    &cmu,
1247                    &enc_ciphertext[..COMPACT_NOTE_SIZE]
1248                ),
1249                None
1250            );
1251        }
1252    }
1253
1254    #[test]
1255    fn compact_decryption_with_invalid_cmu() {
1256        let mut rng = OsRng;
1257        let heights = [
1258            TEST_NETWORK.activation_height(Sapling).unwrap(),
1259            TEST_NETWORK.activation_height(Canopy).unwrap(),
1260        ];
1261
1262        for &height in heights.iter() {
1263            let (_, _, ivk, _, _, epk, enc_ciphertext, _) = random_enc_ciphertext(height, &mut rng);
1264
1265            assert_eq!(
1266                try_sapling_compact_note_decryption(
1267                    &TEST_NETWORK,
1268                    height,
1269                    &ivk,
1270                    &epk,
1271                    &bls12_381::Scalar::random(&mut rng),
1272                    &enc_ciphertext[..COMPACT_NOTE_SIZE]
1273                ),
1274                None
1275            );
1276        }
1277    }
1278
1279    #[test]
1280    fn compact_decryption_with_invalid_version_byte() {
1281        let mut rng = OsRng;
1282        let canopy_activation_height = TEST_NETWORK.activation_height(Canopy).unwrap();
1283        let heights = [
1284            canopy_activation_height - 1,
1285            canopy_activation_height,
1286            canopy_activation_height + ZIP212_GRACE_PERIOD,
1287        ];
1288        let leadbytes = [0x02, 0x03, 0x01];
1289
1290        for (&height, &leadbyte) in heights.iter().zip(leadbytes.iter()) {
1291            let (ovk, _, ivk, cv, cmu, epk, mut enc_ciphertext, out_ciphertext) =
1292                random_enc_ciphertext(height, &mut rng);
1293
1294            reencrypt_enc_ciphertext(
1295                &ovk,
1296                &cv,
1297                &cmu,
1298                &epk,
1299                &mut enc_ciphertext,
1300                &out_ciphertext,
1301                |pt| pt[0] = leadbyte,
1302            );
1303            assert_eq!(
1304                try_sapling_compact_note_decryption(
1305                    &TEST_NETWORK,
1306                    height,
1307                    &ivk,
1308                    &epk,
1309                    &cmu,
1310                    &enc_ciphertext[..COMPACT_NOTE_SIZE]
1311                ),
1312                None
1313            );
1314        }
1315    }
1316
1317    #[test]
1318    fn compact_decryption_with_invalid_diversifier() {
1319        let mut rng = OsRng;
1320        let heights = [
1321            TEST_NETWORK.activation_height(Sapling).unwrap(),
1322            TEST_NETWORK.activation_height(Canopy).unwrap(),
1323        ];
1324
1325        for &height in heights.iter() {
1326            let (ovk, _, ivk, cv, cmu, epk, mut enc_ciphertext, out_ciphertext) =
1327                random_enc_ciphertext(height, &mut rng);
1328
1329            reencrypt_enc_ciphertext(
1330                &ovk,
1331                &cv,
1332                &cmu,
1333                &epk,
1334                &mut enc_ciphertext,
1335                &out_ciphertext,
1336                |pt| pt[1..12].copy_from_slice(&find_invalid_diversifier().0),
1337            );
1338            assert_eq!(
1339                try_sapling_compact_note_decryption(
1340                    &TEST_NETWORK,
1341                    height,
1342                    &ivk,
1343                    &epk,
1344                    &cmu,
1345                    &enc_ciphertext[..COMPACT_NOTE_SIZE]
1346                ),
1347                None
1348            );
1349        }
1350    }
1351
1352    #[test]
1353    fn compact_decryption_with_incorrect_diversifier() {
1354        let mut rng = OsRng;
1355        let heights = [
1356            TEST_NETWORK.activation_height(Sapling).unwrap(),
1357            TEST_NETWORK.activation_height(Canopy).unwrap(),
1358        ];
1359
1360        for &height in heights.iter() {
1361            let (ovk, _, ivk, cv, cmu, epk, mut enc_ciphertext, out_ciphertext) =
1362                random_enc_ciphertext(height, &mut rng);
1363
1364            reencrypt_enc_ciphertext(
1365                &ovk,
1366                &cv,
1367                &cmu,
1368                &epk,
1369                &mut enc_ciphertext,
1370                &out_ciphertext,
1371                |pt| pt[1..12].copy_from_slice(&find_valid_diversifier().0),
1372            );
1373            assert_eq!(
1374                try_sapling_compact_note_decryption(
1375                    &TEST_NETWORK,
1376                    height,
1377                    &ivk,
1378                    &epk,
1379                    &cmu,
1380                    &enc_ciphertext[..COMPACT_NOTE_SIZE]
1381                ),
1382                None
1383            );
1384        }
1385    }
1386
1387    #[test]
1388    fn recovery_with_invalid_ovk() {
1389        let mut rng = OsRng;
1390        let heights = [
1391            TEST_NETWORK.activation_height(Sapling).unwrap(),
1392            TEST_NETWORK.activation_height(Canopy).unwrap(),
1393        ];
1394
1395        for &height in heights.iter() {
1396            let (mut ovk, _, _, cv, cmu, epk, enc_ciphertext, out_ciphertext) =
1397                random_enc_ciphertext(height, &mut rng);
1398
1399            ovk.0[0] ^= 0xff;
1400            assert_eq!(
1401                try_sapling_output_recovery(
1402                    &TEST_NETWORK,
1403                    height,
1404                    &ovk,
1405                    &cv,
1406                    &cmu,
1407                    &epk,
1408                    &enc_ciphertext,
1409                    &out_ciphertext
1410                ),
1411                None
1412            );
1413        }
1414    }
1415
1416    #[test]
1417    fn recovery_with_invalid_ock() {
1418        let mut rng = OsRng;
1419        let heights = [
1420            TEST_NETWORK.activation_height(Sapling).unwrap(),
1421            TEST_NETWORK.activation_height(Canopy).unwrap(),
1422        ];
1423
1424        for &height in heights.iter() {
1425            let (_, _, _, _, cmu, epk, enc_ciphertext, out_ciphertext) =
1426                random_enc_ciphertext(height, &mut rng);
1427
1428            assert_eq!(
1429                try_sapling_output_recovery_with_ock(
1430                    &TEST_NETWORK,
1431                    height,
1432                    &OutgoingCipherKey([0u8; 32]),
1433                    &cmu,
1434                    &epk,
1435                    &enc_ciphertext,
1436                    &out_ciphertext
1437                ),
1438                None
1439            );
1440        }
1441    }
1442
1443    #[test]
1444    fn recovery_with_invalid_cv() {
1445        let mut rng = OsRng;
1446        let heights = [
1447            TEST_NETWORK.activation_height(Sapling).unwrap(),
1448            TEST_NETWORK.activation_height(Canopy).unwrap(),
1449        ];
1450
1451        for &height in heights.iter() {
1452            let (ovk, _, _, _, cmu, epk, enc_ciphertext, out_ciphertext) =
1453                random_enc_ciphertext(height, &mut rng);
1454
1455            assert_eq!(
1456                try_sapling_output_recovery(
1457                    &TEST_NETWORK,
1458                    height,
1459                    &ovk,
1460                    &jubjub::ExtendedPoint::random(&mut rng),
1461                    &cmu,
1462                    &epk,
1463                    &enc_ciphertext,
1464                    &out_ciphertext
1465                ),
1466                None
1467            );
1468        }
1469    }
1470
1471    #[test]
1472    fn recovery_with_invalid_cmu() {
1473        let mut rng = OsRng;
1474        let heights = [
1475            TEST_NETWORK.activation_height(Sapling).unwrap(),
1476            TEST_NETWORK.activation_height(Canopy).unwrap(),
1477        ];
1478
1479        for &height in heights.iter() {
1480            let (ovk, ock, _, cv, _, epk, enc_ctext, out_ctext) =
1481                random_enc_ciphertext(height, &mut rng);
1482
1483            assert_eq!(
1484                try_sapling_output_recovery(
1485                    &TEST_NETWORK,
1486                    height,
1487                    &ovk,
1488                    &cv,
1489                    &bls12_381::Scalar::random(&mut rng),
1490                    &epk,
1491                    &enc_ctext,
1492                    &out_ctext
1493                ),
1494                None
1495            );
1496
1497            assert_eq!(
1498                try_sapling_output_recovery_with_ock(
1499                    &TEST_NETWORK,
1500                    height,
1501                    &ock,
1502                    &bls12_381::Scalar::random(&mut rng),
1503                    &epk,
1504                    &enc_ctext,
1505                    &out_ctext
1506                ),
1507                None
1508            );
1509        }
1510    }
1511
1512    #[test]
1513    fn recovery_with_invalid_epk() {
1514        let mut rng = OsRng;
1515        let heights = [
1516            TEST_NETWORK.activation_height(Sapling).unwrap(),
1517            TEST_NETWORK.activation_height(Canopy).unwrap(),
1518        ];
1519
1520        for &height in heights.iter() {
1521            let (ovk, ock, _, cv, cmu, _, enc_ciphertext, out_ciphertext) =
1522                random_enc_ciphertext(height, &mut rng);
1523
1524            assert_eq!(
1525                try_sapling_output_recovery(
1526                    &TEST_NETWORK,
1527                    height,
1528                    &ovk,
1529                    &cv,
1530                    &cmu,
1531                    &jubjub::ExtendedPoint::random(&mut rng),
1532                    &enc_ciphertext,
1533                    &out_ciphertext
1534                ),
1535                None
1536            );
1537
1538            assert_eq!(
1539                try_sapling_output_recovery_with_ock(
1540                    &TEST_NETWORK,
1541                    height,
1542                    &ock,
1543                    &cmu,
1544                    &jubjub::ExtendedPoint::random(&mut rng),
1545                    &enc_ciphertext,
1546                    &out_ciphertext
1547                ),
1548                None
1549            );
1550        }
1551    }
1552
1553    #[test]
1554    fn recovery_with_invalid_enc_tag() {
1555        let mut rng = OsRng;
1556        let heights = [
1557            TEST_NETWORK.activation_height(Sapling).unwrap(),
1558            TEST_NETWORK.activation_height(Canopy).unwrap(),
1559        ];
1560
1561        for &height in heights.iter() {
1562            let (ovk, ock, _, cv, cmu, epk, mut enc_ciphertext, out_ciphertext) =
1563                random_enc_ciphertext(height, &mut rng);
1564
1565            enc_ciphertext[ENC_CIPHERTEXT_SIZE - 1] ^= 0xff;
1566            assert_eq!(
1567                try_sapling_output_recovery(
1568                    &TEST_NETWORK,
1569                    height,
1570                    &ovk,
1571                    &cv,
1572                    &cmu,
1573                    &epk,
1574                    &enc_ciphertext,
1575                    &out_ciphertext
1576                ),
1577                None
1578            );
1579            assert_eq!(
1580                try_sapling_output_recovery_with_ock(
1581                    &TEST_NETWORK,
1582                    height,
1583                    &ock,
1584                    &cmu,
1585                    &epk,
1586                    &enc_ciphertext,
1587                    &out_ciphertext
1588                ),
1589                None
1590            );
1591        }
1592    }
1593
1594    #[test]
1595    fn recovery_with_invalid_out_tag() {
1596        let mut rng = OsRng;
1597        let heights = [
1598            TEST_NETWORK.activation_height(Sapling).unwrap(),
1599            TEST_NETWORK.activation_height(Canopy).unwrap(),
1600        ];
1601
1602        for &height in heights.iter() {
1603            let (ovk, ock, _, cv, cmu, epk, enc_ciphertext, mut out_ciphertext) =
1604                random_enc_ciphertext(height, &mut rng);
1605
1606            out_ciphertext[OUT_CIPHERTEXT_SIZE - 1] ^= 0xff;
1607            assert_eq!(
1608                try_sapling_output_recovery(
1609                    &TEST_NETWORK,
1610                    height,
1611                    &ovk,
1612                    &cv,
1613                    &cmu,
1614                    &epk,
1615                    &enc_ciphertext,
1616                    &out_ciphertext
1617                ),
1618                None
1619            );
1620            assert_eq!(
1621                try_sapling_output_recovery_with_ock(
1622                    &TEST_NETWORK,
1623                    height,
1624                    &ock,
1625                    &cmu,
1626                    &epk,
1627                    &enc_ciphertext,
1628                    &out_ciphertext
1629                ),
1630                None
1631            );
1632        }
1633    }
1634
1635    #[test]
1636    fn recovery_with_invalid_version_byte() {
1637        let mut rng = OsRng;
1638        let canopy_activation_height = TEST_NETWORK.activation_height(Canopy).unwrap();
1639        let heights = [
1640            canopy_activation_height - 1,
1641            canopy_activation_height,
1642            canopy_activation_height + ZIP212_GRACE_PERIOD,
1643        ];
1644        let leadbytes = [0x02, 0x03, 0x01];
1645
1646        for (&height, &leadbyte) in heights.iter().zip(leadbytes.iter()) {
1647            let (ovk, ock, _, cv, cmu, epk, mut enc_ciphertext, out_ciphertext) =
1648                random_enc_ciphertext(height, &mut rng);
1649
1650            reencrypt_enc_ciphertext(
1651                &ovk,
1652                &cv,
1653                &cmu,
1654                &epk,
1655                &mut enc_ciphertext,
1656                &out_ciphertext,
1657                |pt| pt[0] = leadbyte,
1658            );
1659            assert_eq!(
1660                try_sapling_output_recovery(
1661                    &TEST_NETWORK,
1662                    height,
1663                    &ovk,
1664                    &cv,
1665                    &cmu,
1666                    &epk,
1667                    &enc_ciphertext,
1668                    &out_ciphertext
1669                ),
1670                None
1671            );
1672            assert_eq!(
1673                try_sapling_output_recovery_with_ock(
1674                    &TEST_NETWORK,
1675                    height,
1676                    &ock,
1677                    &cmu,
1678                    &epk,
1679                    &enc_ciphertext,
1680                    &out_ciphertext
1681                ),
1682                None
1683            );
1684        }
1685    }
1686
1687    #[test]
1688    fn recovery_with_invalid_diversifier() {
1689        let mut rng = OsRng;
1690        let heights = [
1691            TEST_NETWORK.activation_height(Sapling).unwrap(),
1692            TEST_NETWORK.activation_height(Canopy).unwrap(),
1693        ];
1694
1695        for &height in heights.iter() {
1696            let (ovk, ock, _, cv, cmu, epk, mut enc_ciphertext, out_ciphertext) =
1697                random_enc_ciphertext(height, &mut rng);
1698
1699            reencrypt_enc_ciphertext(
1700                &ovk,
1701                &cv,
1702                &cmu,
1703                &epk,
1704                &mut enc_ciphertext,
1705                &out_ciphertext,
1706                |pt| pt[1..12].copy_from_slice(&find_invalid_diversifier().0),
1707            );
1708            assert_eq!(
1709                try_sapling_output_recovery(
1710                    &TEST_NETWORK,
1711                    height,
1712                    &ovk,
1713                    &cv,
1714                    &cmu,
1715                    &epk,
1716                    &enc_ciphertext,
1717                    &out_ciphertext
1718                ),
1719                None
1720            );
1721            assert_eq!(
1722                try_sapling_output_recovery_with_ock(
1723                    &TEST_NETWORK,
1724                    height,
1725                    &ock,
1726                    &cmu,
1727                    &epk,
1728                    &enc_ciphertext,
1729                    &out_ciphertext
1730                ),
1731                None
1732            );
1733        }
1734    }
1735
1736    #[test]
1737    fn recovery_with_incorrect_diversifier() {
1738        let mut rng = OsRng;
1739        let heights = [
1740            TEST_NETWORK.activation_height(Sapling).unwrap(),
1741            TEST_NETWORK.activation_height(Canopy).unwrap(),
1742        ];
1743
1744        for &height in heights.iter() {
1745            let (ovk, ock, _, cv, cmu, epk, mut enc_ciphertext, out_ciphertext) =
1746                random_enc_ciphertext(height, &mut rng);
1747
1748            reencrypt_enc_ciphertext(
1749                &ovk,
1750                &cv,
1751                &cmu,
1752                &epk,
1753                &mut enc_ciphertext,
1754                &out_ciphertext,
1755                |pt| pt[1..12].copy_from_slice(&find_valid_diversifier().0),
1756            );
1757            assert_eq!(
1758                try_sapling_output_recovery(
1759                    &TEST_NETWORK,
1760                    height,
1761                    &ovk,
1762                    &cv,
1763                    &cmu,
1764                    &epk,
1765                    &enc_ciphertext,
1766                    &out_ciphertext
1767                ),
1768                None
1769            );
1770            assert_eq!(
1771                try_sapling_output_recovery_with_ock(
1772                    &TEST_NETWORK,
1773                    height,
1774                    &ock,
1775                    &cmu,
1776                    &epk,
1777                    &enc_ciphertext,
1778                    &out_ciphertext
1779                ),
1780                None
1781            );
1782        }
1783    }
1784
1785    #[test]
1786    fn recovery_with_invalid_pk_d() {
1787        let mut rng = OsRng;
1788        let heights = [
1789            TEST_NETWORK.activation_height(Sapling).unwrap(),
1790            TEST_NETWORK.activation_height(Canopy).unwrap(),
1791        ];
1792
1793        for &height in heights.iter() {
1794            let ivk = jubjub::Fr::zero();
1795            let (ovk, ock, _, cv, cmu, epk, enc_ciphertext, out_ciphertext) =
1796                random_enc_ciphertext_with(height, ivk, &mut rng);
1797
1798            assert_eq!(
1799                try_sapling_output_recovery(
1800                    &TEST_NETWORK,
1801                    height,
1802                    &ovk,
1803                    &cv,
1804                    &cmu,
1805                    &epk,
1806                    &enc_ciphertext,
1807                    &out_ciphertext
1808                ),
1809                None
1810            );
1811            assert_eq!(
1812                try_sapling_output_recovery_with_ock(
1813                    &TEST_NETWORK,
1814                    height,
1815                    &ock,
1816                    &cmu,
1817                    &epk,
1818                    &enc_ciphertext,
1819                    &out_ciphertext
1820                ),
1821                None
1822            );
1823        }
1824    }
1825
1826    #[test]
1827    fn test_vectors() {
1828        let test_vectors = crate::test_vectors::note_encryption::make_test_vectors();
1829
1830        macro_rules! read_bls12_381_scalar {
1831            ($field:expr) => {{
1832                bls12_381::Scalar::from_repr($field[..].try_into().unwrap()).unwrap()
1833            }};
1834        }
1835
1836        macro_rules! read_jubjub_scalar {
1837            ($field:expr) => {{
1838                jubjub::Fr::from_repr($field[..].try_into().unwrap()).unwrap()
1839            }};
1840        }
1841
1842        macro_rules! read_point {
1843            ($field:expr) => {
1844                jubjub::ExtendedPoint::from_bytes(&$field).unwrap()
1845            };
1846        }
1847
1848        let height = TEST_NETWORK.activation_height(Sapling).unwrap();
1849
1850        for tv in test_vectors {
1851            //
1852            // Load the test vector components
1853            //
1854
1855            let ivk = read_jubjub_scalar!(tv.ivk);
1856            let pk_d = read_point!(tv.default_pk_d).into_subgroup().unwrap();
1857            let rcm = read_jubjub_scalar!(tv.rcm);
1858            let cv = read_point!(tv.cv);
1859            let cmu = read_bls12_381_scalar!(tv.cmu);
1860            let esk = read_jubjub_scalar!(tv.esk);
1861            let epk = read_point!(tv.epk);
1862
1863            //
1864            // Test the individual components
1865            //
1866
1867            let shared_secret = sapling_ka_agree(&esk, &pk_d.into());
1868            assert_eq!(shared_secret.to_bytes(), tv.shared_secret);
1869
1870            let k_enc = kdf_sapling(shared_secret, &epk);
1871            assert_eq!(k_enc.as_bytes(), tv.k_enc);
1872
1873            let ovk = OutgoingViewingKey(tv.ovk);
1874            let ock = prf_ock(&ovk, &cv, &cmu, &epk);
1875            assert_eq!(ock.as_ref(), tv.ock);
1876
1877            let to = PaymentAddress::from_parts(Diversifier(tv.default_d), pk_d).unwrap();
1878            let note = to.create_note(tv.v, Rseed::BeforeZip212(rcm)).unwrap();
1879            assert_eq!(note.cmu(), cmu);
1880
1881            //
1882            // Test decryption
1883            // (Tested first because it only requires immutable references.)
1884            //
1885
1886            match try_sapling_note_decryption(&TEST_NETWORK, height, &ivk, &epk, &cmu, &tv.c_enc) {
1887                Some((decrypted_note, decrypted_to, decrypted_memo)) => {
1888                    assert_eq!(decrypted_note, note);
1889                    assert_eq!(decrypted_to, to);
1890                    assert_eq!(&decrypted_memo.0[..], &tv.memo[..]);
1891                }
1892                None => panic!("Note decryption failed"),
1893            }
1894
1895            match try_sapling_compact_note_decryption(
1896                &TEST_NETWORK,
1897                height,
1898                &ivk,
1899                &epk,
1900                &cmu,
1901                &tv.c_enc[..COMPACT_NOTE_SIZE],
1902            ) {
1903                Some((decrypted_note, decrypted_to)) => {
1904                    assert_eq!(decrypted_note, note);
1905                    assert_eq!(decrypted_to, to);
1906                }
1907                None => panic!("Compact note decryption failed"),
1908            }
1909
1910            match try_sapling_output_recovery(
1911                &TEST_NETWORK,
1912                height,
1913                &ovk,
1914                &cv,
1915                &cmu,
1916                &epk,
1917                &tv.c_enc,
1918                &tv.c_out,
1919            ) {
1920                Some((decrypted_note, decrypted_to, decrypted_memo)) => {
1921                    assert_eq!(decrypted_note, note);
1922                    assert_eq!(decrypted_to, to);
1923                    assert_eq!(&decrypted_memo.0[..], &tv.memo[..]);
1924                }
1925                None => panic!("Output recovery failed"),
1926            }
1927
1928            //
1929            // Test encryption
1930            //
1931
1932            let mut ne = SaplingNoteEncryption::new(Some(ovk), note, to, Memo(tv.memo), OsRng);
1933            // Swap in the ephemeral keypair from the test vectors
1934            ne.esk = esk;
1935            ne.epk = epk.into_subgroup().unwrap();
1936
1937            assert_eq!(&ne.encrypt_note_plaintext()[..], &tv.c_enc[..]);
1938            assert_eq!(&ne.encrypt_outgoing_plaintext(&cv, &cmu)[..], &tv.c_out[..]);
1939        }
1940    }
1941}