Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

zcash_note_encryption: Add ShieldedOutput::ephemeral_key() -> EphemeralKeyBytes #400

Merged
merged 1 commit into from
Jun 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 23 additions & 22 deletions components/zcash_note_encryption/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@ pub trait Domain {

fn epk_bytes(epk: &Self::EphemeralPublicKey) -> EphemeralKeyBytes;

fn epk(ephemeral_key: &EphemeralKeyBytes) -> Option<Self::EphemeralPublicKey>;

fn check_epk_bytes<F: Fn(&Self::EphemeralSecretKey) -> NoteValidity>(
note: &Self::Note,
check: F,
Expand All @@ -138,7 +140,7 @@ pub trait Domain {
&self,
pk_d: &Self::DiversifiedTransmissionKey,
esk: &Self::EphemeralSecretKey,
epk: &Self::EphemeralPublicKey,
ephemeral_key: &EphemeralKeyBytes,
plaintext: &[u8],
) -> Option<(Self::Note, Self::Recipient)>;

Expand All @@ -155,7 +157,7 @@ pub trait Domain {
}

pub trait ShieldedOutput<D: Domain> {
fn epk(&self) -> &D::EphemeralPublicKey;
fn ephemeral_key(&self) -> EphemeralKeyBytes;
fn cmstar_bytes(&self) -> D::ExtractedCommitmentBytes;
fn enc_ciphertext(&self) -> &[u8];
}
Expand Down Expand Up @@ -332,9 +334,11 @@ pub fn try_note_decryption<D: Domain, Output: ShieldedOutput<D>>(
output: &Output,
) -> Option<(D::Note, D::Recipient, D::Memo)> {
assert_eq!(output.enc_ciphertext().len(), ENC_CIPHERTEXT_SIZE);
let ephemeral_key = output.ephemeral_key();

let shared_secret = D::ka_agree_dec(ivk, output.epk());
let key = D::kdf(shared_secret, &D::epk_bytes(output.epk()));
let epk = D::epk(&ephemeral_key)?;
let shared_secret = D::ka_agree_dec(ivk, &epk);
let key = D::kdf(shared_secret, &ephemeral_key);

let mut plaintext = [0; ENC_CIPHERTEXT_SIZE];
assert_eq!(
Expand All @@ -353,7 +357,7 @@ pub fn try_note_decryption<D: Domain, Output: ShieldedOutput<D>>(
let (note, to) = parse_note_plaintext_without_memo_ivk(
domain,
ivk,
output.epk(),
&ephemeral_key,
&output.cmstar_bytes(),
&plaintext,
)?;
Expand All @@ -365,13 +369,13 @@ pub fn try_note_decryption<D: Domain, Output: ShieldedOutput<D>>(
fn parse_note_plaintext_without_memo_ivk<D: Domain>(
domain: &D,
ivk: &D::IncomingViewingKey,
epk: &D::EphemeralPublicKey,
ephemeral_key: &EphemeralKeyBytes,
cmstar_bytes: &D::ExtractedCommitmentBytes,
plaintext: &[u8],
) -> Option<(D::Note, D::Recipient)> {
let (note, to) = domain.parse_note_plaintext_without_memo_ivk(ivk, &plaintext)?;

if let NoteValidity::Valid = check_note_validity::<D>(&note, epk, cmstar_bytes) {
if let NoteValidity::Valid = check_note_validity::<D>(&note, ephemeral_key, cmstar_bytes) {
Some((note, to))
} else {
None
Expand All @@ -380,14 +384,13 @@ fn parse_note_plaintext_without_memo_ivk<D: Domain>(

fn check_note_validity<D: Domain>(
note: &D::Note,
epk: &D::EphemeralPublicKey,
ephemeral_key: &EphemeralKeyBytes,
cmstar_bytes: &D::ExtractedCommitmentBytes,
) -> NoteValidity {
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))
.ct_eq(&epk_bytes)
.ct_eq(&ephemeral_key)
.into()
{
NoteValidity::Valid
Expand Down Expand Up @@ -416,9 +419,11 @@ pub fn try_compact_note_decryption<D: Domain, Output: ShieldedOutput<D>>(
output: &Output,
) -> Option<(D::Note, D::Recipient)> {
assert_eq!(output.enc_ciphertext().len(), COMPACT_NOTE_SIZE);
let ephemeral_key = output.ephemeral_key();

let shared_secret = D::ka_agree_dec(&ivk, output.epk());
let key = D::kdf(shared_secret, &D::epk_bytes(output.epk()));
let epk = D::epk(&ephemeral_key)?;
let shared_secret = D::ka_agree_dec(&ivk, &epk);
let key = D::kdf(shared_secret, &ephemeral_key);

// Start from block 1 to skip over Poly1305 keying output
let mut plaintext = [0; COMPACT_NOTE_SIZE];
Expand All @@ -428,7 +433,7 @@ pub fn try_compact_note_decryption<D: Domain, Output: ShieldedOutput<D>>(
parse_note_plaintext_without_memo_ivk(
domain,
ivk,
output.epk(),
&ephemeral_key,
&output.cmstar_bytes(),
&plaintext,
)
Expand All @@ -450,12 +455,7 @@ pub fn try_output_recovery_with_ovk<D: Domain, Output: ShieldedOutput<D>>(
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()),
);
let ock = D::derive_ock(ovk, &cv, &output.cmstar_bytes(), &output.ephemeral_key());
try_output_recovery_with_ock(domain, &ock, output, out_ciphertext)
}

Expand Down Expand Up @@ -488,11 +488,12 @@ pub fn try_output_recovery_with_ock<D: Domain, Output: ShieldedOutput<D>>(
let pk_d = D::extract_pk_d(&op)?;
let esk = D::extract_esk(&op)?;

let ephemeral_key = output.ephemeral_key();
let shared_secret = D::ka_agree_enc(&esk, &pk_d);
// The small-order point check at the point of output parsing rejects
// non-canonical encodings, so reencoding here for the KDF should
// be okay.
let key = D::kdf(shared_secret, &D::epk_bytes(output.epk()));
let key = D::kdf(shared_secret, &ephemeral_key);

let mut plaintext = [0; ENC_CIPHERTEXT_SIZE];
assert_eq!(
Expand All @@ -509,7 +510,7 @@ pub fn try_output_recovery_with_ock<D: Domain, Output: ShieldedOutput<D>>(
);

let (note, to) =
domain.parse_note_plaintext_without_memo_ovk(&pk_d, &esk, output.epk(), &plaintext)?;
domain.parse_note_plaintext_without_memo_ovk(&pk_d, &esk, &ephemeral_key, &plaintext)?;
let memo = domain.extract_memo(&plaintext);

// ZIP 212: Check that the esk provided to this function is consistent with the esk we
Expand All @@ -521,7 +522,7 @@ pub fn try_output_recovery_with_ock<D: Domain, Output: ShieldedOutput<D>>(
}

if let NoteValidity::Valid =
check_note_validity::<D>(&note, output.epk(), &output.cmstar_bytes())
check_note_validity::<D>(&note, &ephemeral_key, &output.cmstar_bytes())
{
Some((note, to, memo))
} else {
Expand Down
11 changes: 9 additions & 2 deletions zcash_primitives/src/sapling/note_encryption.rs
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,13 @@ impl<P: consensus::Parameters> Domain for SaplingDomain<P> {
epk_bytes(epk)
}

fn epk(ephemeral_key: &EphemeralKeyBytes) -> Option<Self::EphemeralPublicKey> {
// ZIP 216: We unconditionally reject non-canonical encodings, because these have
// always been rejected by consensus (due to small-order checks).
// https://zips.z.cash/zip-0216#specification
jubjub::ExtendedPoint::from_bytes(&ephemeral_key.0).into()
}

fn check_epk_bytes<F: FnOnce(&Self::EphemeralSecretKey) -> NoteValidity>(
note: &Note,
check: F,
Expand All @@ -259,11 +266,11 @@ impl<P: consensus::Parameters> Domain for SaplingDomain<P> {
&self,
pk_d: &Self::DiversifiedTransmissionKey,
esk: &Self::EphemeralSecretKey,
epk: &Self::EphemeralPublicKey,
ephemeral_key: &EphemeralKeyBytes,
plaintext: &[u8],
) -> Option<(Self::Note, Self::Recipient)> {
sapling_parse_note_plaintext_without_memo(&self, plaintext, |diversifier| {
if (diversifier.g_d()? * esk).to_bytes() == epk.to_bytes() {
if (diversifier.g_d()? * esk).to_bytes() == ephemeral_key.0 {
Some(*pk_d)
} else {
None
Expand Down
10 changes: 5 additions & 5 deletions zcash_primitives/src/transaction/components/sapling.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use ff::PrimeField;
use group::GroupEncoding;
use std::io::{self, Read, Write};

use zcash_note_encryption::{ShieldedOutput, COMPACT_NOTE_SIZE};
use zcash_note_encryption::{EphemeralKeyBytes, ShieldedOutput, COMPACT_NOTE_SIZE};

use crate::{
consensus,
Expand Down Expand Up @@ -220,8 +220,8 @@ pub struct OutputDescription<Proof> {
}

impl<P: consensus::Parameters, A> ShieldedOutput<SaplingDomain<P>> for OutputDescription<A> {
fn epk(&self) -> &jubjub::ExtendedPoint {
&self.ephemeral_key
fn ephemeral_key(&self) -> EphemeralKeyBytes {
EphemeralKeyBytes(self.ephemeral_key.to_bytes())
}

fn cmstar_bytes(&self) -> [u8; 32] {
Expand Down Expand Up @@ -355,8 +355,8 @@ impl<A> From<OutputDescription<A>> for CompactOutputDescription {
}

impl<P: consensus::Parameters> ShieldedOutput<SaplingDomain<P>> for CompactOutputDescription {
fn epk(&self) -> &jubjub::ExtendedPoint {
&self.epk
fn ephemeral_key(&self) -> EphemeralKeyBytes {
EphemeralKeyBytes(self.epk.to_bytes())
}

fn cmstar_bytes(&self) -> [u8; 32] {
Expand Down