Merge pull request #390 from str4d/note-decryption-fixes

zcash_note_encryption: Note decryption fixes

Extracted from: c13a5d3f3d
This commit is contained in:
str4d 2021-06-02 22:22:35 +01:00 committed by GitHub
commit 5204d69665
1 changed files with 50 additions and 19 deletions

View File

@ -5,7 +5,6 @@
use crypto_api_chachapoly::{ChaCha20Ietf, ChachaPolyIetf};
use rand_core::RngCore;
use std::convert::TryFrom;
use subtle::{Choice, ConstantTimeEq};
pub const COMPACT_NOTE_SIZE: usize = 1 + // version
@ -64,7 +63,7 @@ pub enum NoteValidity {
}
pub trait Domain {
type EphemeralSecretKey;
type EphemeralSecretKey: ConstantTimeEq;
type EphemeralPublicKey;
type SharedSecret;
type SymmetricKey: AsRef<[u8]>;
@ -75,7 +74,7 @@ pub trait Domain {
type OutgoingViewingKey;
type ValueCommitment;
type ExtractedCommitment;
type ExtractedCommitmentBytes: Eq + TryFrom<Self::ExtractedCommitment>;
type ExtractedCommitmentBytes: Eq + for<'a> From<&'a Self::ExtractedCommitment>;
type Memo;
fn derive_esk(note: &Self::Note) -> Option<Self::EphemeralSecretKey>;
@ -111,7 +110,7 @@ pub trait Domain {
fn derive_ock(
ovk: &Self::OutgoingViewingKey,
cv: &Self::ValueCommitment,
cmstar: &Self::ExtractedCommitment,
cmstar_bytes: &Self::ExtractedCommitmentBytes,
ephemeral_key: &EphemeralKeyBytes,
) -> OutgoingCipherKey;
@ -149,10 +148,10 @@ pub trait Domain {
fn extract_memo(&self, plaintext: &[u8]) -> Self::Memo;
fn extract_pk_d(
out_plaintext: &[u8; OUT_CIPHERTEXT_SIZE],
out_plaintext: &[u8; OUT_PLAINTEXT_SIZE],
) -> Option<Self::DiversifiedTransmissionKey>;
fn extract_esk(out_plaintext: &[u8; OUT_CIPHERTEXT_SIZE]) -> Option<Self::EphemeralSecretKey>;
fn extract_esk(out_plaintext: &[u8; OUT_PLAINTEXT_SIZE]) -> Option<Self::EphemeralSecretKey>;
}
pub trait ShieldedOutput<D: Domain> {
@ -292,7 +291,7 @@ impl<D: Domain> NoteEncryption<D> {
rng: &mut R,
) -> [u8; OUT_CIPHERTEXT_SIZE] {
let (ock, input) = if let Some(ovk) = &self.ovk {
let ock = D::derive_ock(ovk, &cv, &cmstar, &D::epk_bytes(&self.epk));
let ock = D::derive_ock(ovk, &cv, &cmstar.into(), &D::epk_bytes(&self.epk));
let input = D::outgoing_plaintext_bytes(&self.note, &self.esk);
(ock, input)
@ -322,11 +321,11 @@ impl<D: Domain> NoteEncryption<D> {
/// Trial decryption of the full note plaintext by the recipient.
///
/// Attempts to decrypt and validate the given `enc_ciphertext` using the given `ivk`.
/// If successful, the corresponding Sapling note and memo are returned, along with the
/// `PaymentAddress` to which the note was sent.
/// If successful, the corresponding note and memo are returned, along with the address to
/// which the note was sent.
///
/// Implements section 4.19.2 of the
/// [Zcash Protocol Specification](https://zips.z.cash/protocol/nu5.pdf#decryptivk)
/// [Zcash Protocol Specification](https://zips.z.cash/protocol/nu5.pdf#decryptivk).
pub fn try_note_decryption<D: Domain, Output: ShieldedOutput<D>>(
domain: &D,
ivk: &D::IncomingViewingKey,
@ -384,9 +383,7 @@ fn check_note_validity<D: Domain>(
epk: &D::EphemeralPublicKey,
cmstar_bytes: &D::ExtractedCommitmentBytes,
) -> NoteValidity {
if D::ExtractedCommitmentBytes::try_from(D::cmstar(&note))
.map_or(false, |cs| &cs == cmstar_bytes)
{
if &D::ExtractedCommitmentBytes::from(&D::cmstar(&note)) == cmstar_bytes {
let epk_bytes = D::epk_bytes(epk);
D::check_epk_bytes(&note, |derived_esk| {
if D::epk_bytes(&D::ka_derive_public(&note, &derived_esk))
@ -407,8 +404,8 @@ fn check_note_validity<D: Domain>(
/// Trial decryption of the compact note plaintext by the recipient for light clients.
///
/// Attempts to decrypt and validate the first 52 bytes of `enc_ciphertext` using the
/// given `ivk`. If successful, the corresponding Sapling note is returned, along with the
/// `PaymentAddress` to which the note was sent.
/// given `ivk`. If successful, the corresponding note is returned, along with the address
/// to which the note was sent.
///
/// Implements the procedure specified in [`ZIP 307`].
///
@ -437,14 +434,40 @@ pub fn try_compact_note_decryption<D: Domain, Output: ShieldedOutput<D>>(
)
}
/// Recovery of the full note plaintext by the sender.
///
/// Attempts to decrypt and validate the given `enc_ciphertext` using the given `ovk`.
/// If successful, the corresponding note and memo are returned, along with the address to
/// which the note was sent.
///
/// Implements [Zcash Protocol Specification section 4.19.3][decryptovk].
///
/// [decryptovk]: https://zips.z.cash/protocol/nu5.pdf#decryptovk
pub fn try_output_recovery_with_ovk<D: Domain, Output: ShieldedOutput<D>>(
domain: &D,
ovk: &D::OutgoingViewingKey,
output: &Output,
cv: &D::ValueCommitment,
out_ciphertext: &[u8],
) -> Option<(D::Note, D::Recipient, D::Memo)> {
let ock = D::derive_ock(
ovk,
&cv,
&output.cmstar_bytes(),
&D::epk_bytes(&output.epk()),
);
try_output_recovery_with_ock(domain, &ock, output, out_ciphertext)
}
/// Recovery of the full note plaintext by the sender.
///
/// Attempts to decrypt and validate the given `enc_ciphertext` using the given `ock`.
/// If successful, the corresponding Sapling note and memo are returned, along with the
/// `PaymentAddress` to which the note was sent.
/// If successful, the corresponding note and memo are returned, along with the address to
/// which the note was sent.
///
/// Implements part of section 4.19.3 of the
/// [Zcash Protocol Specification](https://zips.z.cash/protocol/nu5.pdf#decryptovk)
/// [Zcash Protocol Specification](https://zips.z.cash/protocol/nu5.pdf#decryptovk).
/// For decryption using a Full Viewing Key see [`try_output_recovery_with_ovk`].
pub fn try_output_recovery_with_ock<D: Domain, Output: ShieldedOutput<D>>(
domain: &D,
ock: &OutgoingCipherKey,
@ -454,7 +477,7 @@ pub fn try_output_recovery_with_ock<D: Domain, Output: ShieldedOutput<D>>(
assert_eq!(output.enc_ciphertext().len(), ENC_CIPHERTEXT_SIZE);
assert_eq!(out_ciphertext.len(), OUT_CIPHERTEXT_SIZE);
let mut op = [0; OUT_CIPHERTEXT_SIZE];
let mut op = [0; OUT_PLAINTEXT_SIZE];
assert_eq!(
ChachaPolyIetf::aead_cipher()
.open_to(&mut op, &out_ciphertext, &[], ock.as_ref(), &[0u8; 12])
@ -489,6 +512,14 @@ pub fn try_output_recovery_with_ock<D: Domain, Output: ShieldedOutput<D>>(
domain.parse_note_plaintext_without_memo_ovk(&pk_d, &esk, output.epk(), &plaintext)?;
let memo = domain.extract_memo(&plaintext);
// ZIP 212: Check that the esk provided to this function is consistent with the esk we
// can derive from the note.
if let Some(derived_esk) = D::derive_esk(&note) {
if (!derived_esk.ct_eq(&esk)).into() {
return None;
}
}
if let NoteValidity::Valid =
check_note_validity::<D>(&note, output.epk(), &output.cmstar_bytes())
{