Merge pull request #390 from str4d/note-decryption-fixes
zcash_note_encryption: Note decryption fixes
This commit is contained in:
commit
c13a5d3f3d
|
@ -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(¬e))
|
||||
.map_or(false, |cs| &cs == cmstar_bytes)
|
||||
{
|
||||
if &D::ExtractedCommitmentBytes::from(&D::cmstar(¬e)) == cmstar_bytes {
|
||||
let epk_bytes = D::epk_bytes(epk);
|
||||
D::check_epk_bytes(¬e, |derived_esk| {
|
||||
if D::epk_bytes(&D::ka_derive_public(¬e, &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(¬e) {
|
||||
if (!derived_esk.ct_eq(&esk)).into() {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
if let NoteValidity::Valid =
|
||||
check_note_validity::<D>(¬e, output.epk(), &output.cmstar_bytes())
|
||||
{
|
||||
|
|
|
@ -7,10 +7,10 @@ use rand_core::RngCore;
|
|||
use std::convert::TryInto;
|
||||
|
||||
use zcash_note_encryption::{
|
||||
try_compact_note_decryption, try_note_decryption, try_output_recovery_with_ock, Domain,
|
||||
EphemeralKeyBytes, NoteEncryption, NotePlaintextBytes, NoteValidity, OutPlaintextBytes,
|
||||
OutgoingCipherKey, ShieldedOutput, COMPACT_NOTE_SIZE, NOTE_PLAINTEXT_SIZE, OUT_CIPHERTEXT_SIZE,
|
||||
OUT_PLAINTEXT_SIZE,
|
||||
try_compact_note_decryption, try_note_decryption, try_output_recovery_with_ock,
|
||||
try_output_recovery_with_ovk, Domain, EphemeralKeyBytes, NoteEncryption, NotePlaintextBytes,
|
||||
NoteValidity, OutPlaintextBytes, OutgoingCipherKey, ShieldedOutput, COMPACT_NOTE_SIZE,
|
||||
NOTE_PLAINTEXT_SIZE, OUT_PLAINTEXT_SIZE,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
|
@ -54,7 +54,7 @@ fn kdf_sapling(dhsecret: jubjub::SubgroupPoint, ephemeral_key: &EphemeralKeyByte
|
|||
pub fn prf_ock(
|
||||
ovk: &OutgoingViewingKey,
|
||||
cv: &jubjub::ExtendedPoint,
|
||||
cmu: &bls12_381::Scalar,
|
||||
cmu_bytes: &[u8; 32],
|
||||
ephemeral_key: &EphemeralKeyBytes,
|
||||
) -> OutgoingCipherKey {
|
||||
OutgoingCipherKey(
|
||||
|
@ -64,7 +64,7 @@ pub fn prf_ock(
|
|||
.to_state()
|
||||
.update(&ovk.0)
|
||||
.update(&cv.to_bytes())
|
||||
.update(&cmu.to_repr())
|
||||
.update(cmu_bytes)
|
||||
.update(ephemeral_key.as_ref())
|
||||
.finalize()
|
||||
.as_bytes()
|
||||
|
@ -209,10 +209,10 @@ impl<P: consensus::Parameters> Domain for SaplingDomain<P> {
|
|||
fn derive_ock(
|
||||
ovk: &Self::OutgoingViewingKey,
|
||||
cv: &Self::ValueCommitment,
|
||||
cmu: &Self::ExtractedCommitment,
|
||||
cmu_bytes: &Self::ExtractedCommitmentBytes,
|
||||
epk: &EphemeralKeyBytes,
|
||||
) -> OutgoingCipherKey {
|
||||
prf_ock(ovk, cv, cmu, epk)
|
||||
prf_ock(ovk, cv, cmu_bytes, epk)
|
||||
}
|
||||
|
||||
fn outgoing_plaintext_bytes(
|
||||
|
@ -272,7 +272,7 @@ impl<P: consensus::Parameters> Domain for SaplingDomain<P> {
|
|||
note.cmu()
|
||||
}
|
||||
|
||||
fn extract_pk_d(op: &[u8; OUT_CIPHERTEXT_SIZE]) -> Option<Self::DiversifiedTransmissionKey> {
|
||||
fn extract_pk_d(op: &[u8; OUT_PLAINTEXT_SIZE]) -> Option<Self::DiversifiedTransmissionKey> {
|
||||
let pk_d = jubjub::SubgroupPoint::from_bytes(
|
||||
op[0..32].try_into().expect("slice is the correct length"),
|
||||
);
|
||||
|
@ -284,7 +284,7 @@ impl<P: consensus::Parameters> Domain for SaplingDomain<P> {
|
|||
}
|
||||
}
|
||||
|
||||
fn extract_esk(op: &[u8; OUT_CIPHERTEXT_SIZE]) -> Option<Self::EphemeralSecretKey> {
|
||||
fn extract_esk(op: &[u8; OUT_PLAINTEXT_SIZE]) -> Option<Self::EphemeralSecretKey> {
|
||||
jubjub::Fr::from_repr(
|
||||
op[32..OUT_PLAINTEXT_SIZE]
|
||||
.try_into()
|
||||
|
@ -407,17 +407,12 @@ pub fn try_sapling_output_recovery<P: consensus::Parameters>(
|
|||
ovk: &OutgoingViewingKey,
|
||||
output: &OutputDescription,
|
||||
) -> Option<(Note, PaymentAddress, MemoBytes)> {
|
||||
try_sapling_output_recovery_with_ock(
|
||||
params,
|
||||
let domain = SaplingDomain {
|
||||
params: params.clone(),
|
||||
height,
|
||||
&prf_ock(
|
||||
&ovk,
|
||||
&output.cv,
|
||||
&output.cmu,
|
||||
&epk_bytes(&output.ephemeral_key),
|
||||
),
|
||||
output,
|
||||
)
|
||||
};
|
||||
|
||||
try_output_recovery_with_ovk(&domain, ovk, output, &output.cv, &output.out_ciphertext)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -524,7 +519,7 @@ mod tests {
|
|||
&mut rng,
|
||||
);
|
||||
let epk = *ne.epk();
|
||||
let ock = prf_ock(&ovk, &cv, &cmu, &epk_bytes(&epk));
|
||||
let ock = prf_ock(&ovk, &cv, &cmu.to_repr(), &epk_bytes(&epk));
|
||||
|
||||
let output = OutputDescription {
|
||||
cv,
|
||||
|
@ -547,7 +542,7 @@ mod tests {
|
|||
out_ciphertext: &[u8; OUT_CIPHERTEXT_SIZE],
|
||||
modify_plaintext: impl Fn(&mut [u8; NOTE_PLAINTEXT_SIZE]),
|
||||
) {
|
||||
let ock = prf_ock(&ovk, &cv, &cmu, &epk_bytes(epk));
|
||||
let ock = prf_ock(&ovk, &cv, &cmu.to_repr(), &epk_bytes(epk));
|
||||
|
||||
let mut op = [0; OUT_CIPHERTEXT_SIZE];
|
||||
assert_eq!(
|
||||
|
@ -1279,7 +1274,7 @@ mod tests {
|
|||
assert_eq!(k_enc.as_bytes(), tv.k_enc);
|
||||
|
||||
let ovk = OutgoingViewingKey(tv.ovk);
|
||||
let ock = prf_ock(&ovk, &cv, &cmu, &epk_bytes(&epk));
|
||||
let ock = prf_ock(&ovk, &cv, &cmu.to_repr(), &epk_bytes(&epk));
|
||||
assert_eq!(ock.as_ref(), tv.ock);
|
||||
|
||||
let to = PaymentAddress::from_parts(Diversifier(tv.default_d), pk_d).unwrap();
|
||||
|
|
Loading…
Reference in New Issue