diff --git a/zcash_primitives/src/note_encryption.rs b/zcash_primitives/src/note_encryption.rs index b6ce046e6..3fb2c6573 100644 --- a/zcash_primitives/src/note_encryption.rs +++ b/zcash_primitives/src/note_encryption.rs @@ -466,17 +466,20 @@ pub fn try_sapling_output_recovery( mod tests { use ff::{PrimeField, PrimeFieldRepr}; use pairing::bls12_381::{Bls12, Fr, FrRepr}; + use rand::{thread_rng, Rand, Rng}; use sapling_crypto::{ jubjub::{ edwards, fs::{Fs, FsRepr}, + PrimeOrder, Unknown, }, - primitives::{Diversifier, PaymentAddress}, + primitives::{Diversifier, PaymentAddress, ValueCommitment}, }; use super::{ kdf_sapling, prf_ock, sapling_ka_agree, try_sapling_compact_note_decryption, try_sapling_note_decryption, try_sapling_output_recovery, Memo, SaplingNoteEncryption, + COMPACT_NOTE_SIZE, ENC_CIPHERTEXT_SIZE, OUT_CIPHERTEXT_SIZE, }; use crate::{keys::OutgoingViewingKey, JUBJUB}; @@ -594,6 +597,284 @@ mod tests { assert_eq!(Memo::default().to_utf8(), None); } + fn random_enc_ciphertext( + mut rng: &mut Rng, + ) -> ( + OutgoingViewingKey, + Fs, + edwards::Point, + Fr, + edwards::Point, + [u8; ENC_CIPHERTEXT_SIZE], + [u8; OUT_CIPHERTEXT_SIZE], + ) { + let diversifier = Diversifier([0; 11]); + let ivk = Fs::rand(&mut rng); + let pk_d = diversifier.g_d::(&JUBJUB).unwrap().mul(ivk, &JUBJUB); + let pa = PaymentAddress { diversifier, pk_d }; + + // Construct the value commitment for the proof instance + let value = 100; + let value_commitment = ValueCommitment:: { + value, + randomness: Fs::rand(&mut rng), + }; + let cv = value_commitment.cm(&JUBJUB).into(); + + let note = pa.create_note(value, Fs::rand(&mut rng), &JUBJUB).unwrap(); + let cmu = note.cm(&JUBJUB); + + let ovk = OutgoingViewingKey([0; 32]); + let ne = SaplingNoteEncryption::new(ovk, note, pa, Memo([0; 512])); + let epk = ne.epk(); + let enc_ciphertext = ne.encrypt_note_plaintext(); + let out_ciphertext = ne.encrypt_outgoing_plaintext(&cv, &cmu); + + assert!(try_sapling_note_decryption(&ivk, epk, &cmu, &enc_ciphertext).is_some()); + assert!(try_sapling_output_recovery( + &ovk, + &cv, + &cmu, + &epk, + &enc_ciphertext, + &out_ciphertext + ) + .is_some()); + + ( + ovk, + ivk, + cv, + cmu, + epk.clone(), + enc_ciphertext, + out_ciphertext, + ) + } + + #[test] + fn decryption_with_invalid_ivk() { + let mut rng = thread_rng(); + + let (_, _, _, cmu, epk, enc_ciphertext, _) = random_enc_ciphertext(&mut rng); + + assert_eq!( + try_sapling_note_decryption(&Fs::rand(&mut rng), &epk, &cmu, &enc_ciphertext), + None + ); + } + + #[test] + fn decryption_with_invalid_epk() { + let mut rng = thread_rng(); + + let (_, ivk, _, cmu, _, enc_ciphertext, _) = random_enc_ciphertext(&mut rng); + + assert_eq!( + try_sapling_note_decryption( + &ivk, + &edwards::Point::::rand(&mut rng, &JUBJUB).mul_by_cofactor(&JUBJUB), + &cmu, + &enc_ciphertext + ), + None + ); + } + + #[test] + fn decryption_with_invalid_cmu() { + let mut rng = thread_rng(); + + let (_, ivk, _, _, epk, enc_ciphertext, _) = random_enc_ciphertext(&mut rng); + + assert_eq!( + try_sapling_note_decryption(&ivk, &epk, &Fr::rand(&mut rng), &enc_ciphertext), + None + ); + } + + #[test] + fn decryption_with_invalid_tag() { + let mut rng = thread_rng(); + + let (_, ivk, _, cmu, epk, mut enc_ciphertext, _) = random_enc_ciphertext(&mut rng); + + enc_ciphertext[ENC_CIPHERTEXT_SIZE - 1] ^= 0xff; + assert_eq!( + try_sapling_note_decryption(&ivk, &epk, &cmu, &enc_ciphertext), + None + ); + } + + #[test] + fn compact_decryption_with_invalid_ivk() { + let mut rng = thread_rng(); + + let (_, _, _, cmu, epk, enc_ciphertext, _) = random_enc_ciphertext(&mut rng); + + assert_eq!( + try_sapling_compact_note_decryption( + &Fs::rand(&mut rng), + &epk, + &cmu, + &enc_ciphertext[..COMPACT_NOTE_SIZE] + ), + None + ); + } + + #[test] + fn compact_decryption_with_invalid_epk() { + let mut rng = thread_rng(); + + let (_, ivk, _, cmu, _, enc_ciphertext, _) = random_enc_ciphertext(&mut rng); + + assert_eq!( + try_sapling_compact_note_decryption( + &ivk, + &edwards::Point::::rand(&mut rng, &JUBJUB).mul_by_cofactor(&JUBJUB), + &cmu, + &enc_ciphertext[..COMPACT_NOTE_SIZE] + ), + None + ); + } + + #[test] + fn compact_decryption_with_invalid_cmu() { + let mut rng = thread_rng(); + + let (_, ivk, _, _, epk, enc_ciphertext, _) = random_enc_ciphertext(&mut rng); + + assert_eq!( + try_sapling_compact_note_decryption( + &ivk, + &epk, + &Fr::rand(&mut rng), + &enc_ciphertext[..COMPACT_NOTE_SIZE] + ), + None + ); + } + + #[test] + fn compact_decryption_with_invalid_diversifier() { + let mut rng = thread_rng(); + + let (_, ivk, _, cmu, epk, mut enc_ciphertext, _) = random_enc_ciphertext(&mut rng); + + // In compact decryption, this will result in an altered diversifier + enc_ciphertext[1] ^= 0xff; + assert_eq!( + try_sapling_compact_note_decryption( + &ivk, + &epk, + &cmu, + &enc_ciphertext[..COMPACT_NOTE_SIZE] + ), + None + ); + } + + #[test] + fn recovery_with_invalid_ovk() { + let mut rng = thread_rng(); + + let (mut ovk, _, cv, cmu, epk, enc_ciphertext, out_ciphertext) = + random_enc_ciphertext(&mut rng); + + ovk.0[0] ^= 0xff; + assert_eq!( + try_sapling_output_recovery(&ovk, &cv, &cmu, &epk, &enc_ciphertext, &out_ciphertext), + None + ); + } + + #[test] + fn recovery_with_invalid_cv() { + let mut rng = thread_rng(); + + let (ovk, _, _, cmu, epk, enc_ciphertext, out_ciphertext) = random_enc_ciphertext(&mut rng); + + assert_eq!( + try_sapling_output_recovery( + &ovk, + &edwards::Point::::rand(&mut rng, &JUBJUB), + &cmu, + &epk, + &enc_ciphertext, + &out_ciphertext + ), + None + ); + } + + #[test] + fn recovery_with_invalid_cmu() { + let mut rng = thread_rng(); + + let (ovk, _, cv, _, epk, enc_ciphertext, out_ciphertext) = random_enc_ciphertext(&mut rng); + + assert_eq!( + try_sapling_output_recovery( + &ovk, + &cv, + &Fr::rand(&mut rng), + &epk, + &enc_ciphertext, + &out_ciphertext + ), + None + ); + } + + #[test] + fn recovery_with_invalid_epk() { + let mut rng = thread_rng(); + + let (ovk, _, cv, cmu, _, enc_ciphertext, out_ciphertext) = random_enc_ciphertext(&mut rng); + + assert_eq!( + try_sapling_output_recovery( + &ovk, + &cv, + &cmu, + &edwards::Point::::rand(&mut rng, &JUBJUB).mul_by_cofactor(&JUBJUB), + &enc_ciphertext, + &out_ciphertext + ), + None + ); + } + + #[test] + fn recovery_with_invalid_enc_tag() { + let mut rng = thread_rng(); + + let (ovk, _, cv, cmu, epk, mut enc_ciphertext, out_ciphertext) = + random_enc_ciphertext(&mut rng); + + enc_ciphertext[ENC_CIPHERTEXT_SIZE - 1] ^= 0xff; + assert_eq!( + try_sapling_output_recovery(&ovk, &cv, &cmu, &epk, &enc_ciphertext, &out_ciphertext), + None + ); + } + + #[test] + fn recovery_with_invalid_out_tag() { + let mut rng = thread_rng(); + + let (ovk, _, cv, cmu, epk, enc_ciphertext, mut out_ciphertext) = + random_enc_ciphertext(&mut rng); + + out_ciphertext[OUT_CIPHERTEXT_SIZE - 1] ^= 0xff; + assert_eq!( + try_sapling_output_recovery(&ovk, &cv, &cmu, &epk, &enc_ciphertext, &out_ciphertext), + None + ); + } + #[test] fn test_vectors() { let test_vectors = crate::test_vectors::note_encryption::make_test_vectors();