1use 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 + 11 + 8 + 32; const NOTE_PLAINTEXT_SIZE: usize = COMPACT_NOTE_SIZE + 512;
27const OUT_PLAINTEXT_SIZE: usize = 32 + 32; pub const ENC_CIPHERTEXT_SIZE: usize = NOTE_PLAINTEXT_SIZE + 16;
30pub const OUT_CIPHERTEXT_SIZE: usize = OUT_PLAINTEXT_SIZE + 16;
31
32fn 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#[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 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 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 None
97 }
98 }
99
100 pub fn as_bytes(&self) -> &[u8] {
102 &self.0[..]
103 }
104
105 pub fn to_utf8(&self) -> Option<Result<String, str::Utf8Error>> {
110 if self.0[0] < 0xF5 {
112 Some(str::from_utf8(&self.0).map(|memo| {
114 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 fn from_str(memo: &str) -> Result<Self, Self::Err> {
128 Memo::from_bytes(memo.as_bytes()).ok_or(())
129 }
130}
131
132pub fn sapling_ka_agree(esk: &jubjub::Fr, pk_d: &jubjub::ExtendedPoint) -> jubjub::SubgroupPoint {
136 let mut wnaf = group::Wnaf::new();
141 wnaf.scalar(esk).base(*pk_d).clear_cofactor()
142}
143
144fn 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
157pub 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
172pub 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
197pub struct SaplingNoteEncryption<R: RngCore> {
243 epk: jubjub::SubgroupPoint,
244 esk: jubjub::Fr,
245 note: Note,
246 to: PaymentAddress,
247 memo: Memo,
248 ovk: Option<OutgoingViewingKey>,
250 rng: R,
251}
252
253impl<R: RngCore + CryptoRng> SaplingNoteEncryption<R> {
254 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 pub fn esk(&self) -> &jubjub::Fr {
293 &self.esk
294 }
295
296 pub fn epk(&self) -> &jubjub::SubgroupPoint {
298 &self.epk
299 }
300
301 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 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 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 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 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 return None;
413 }
414
415 if let Some(derived_esk) = note.derive_esk() {
416 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 false
439 } else if height >= grace_period_end_height && leadbyte != 0x02 {
440 false
442 } else {
443 true
444 }
445 } else {
446 leadbyte == 0x01
448 }
449}
450
451pub 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
493pub 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 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
523pub 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 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 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 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
631pub 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 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 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 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 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 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 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 let mut ne = SaplingNoteEncryption::new(Some(ovk), note, to, Memo(tv.memo), OsRng);
1933 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}