Skip to content

Commit

Permalink
Use ShieldedOutput trait for note encryption/decryption.
Browse files Browse the repository at this point in the history
This change modifies note encryption and decryption functions
to treat a shielded output as a single value instead of handling
the parts of an output as independent arguments.
  • Loading branch information
nuttycom committed Apr 12, 2021
1 parent 4f22f1d commit 324fc36
Show file tree
Hide file tree
Showing 9 changed files with 378 additions and 442 deletions.
71 changes: 37 additions & 34 deletions components/zcash_note_encryption/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

use crypto_api_chachapoly::{ChaCha20Ietf, ChachaPolyIetf};
use rand_core::RngCore;
use subtle::{ConstantTimeEq, Choice};
use subtle::{Choice, ConstantTimeEq};

pub const COMPACT_NOTE_SIZE: usize = 1 + // version
11 + // diversifier
Expand Down Expand Up @@ -148,10 +148,10 @@ pub trait Domain {
fn extract_esk(out_plaintext: &[u8; OUT_CIPHERTEXT_SIZE]) -> Option<Self::EphemeralSecretKey>;
}

pub trait ShieldedOutput<'a, D: Domain> {
fn ivk(&'a self) -> &'a D::IncomingViewingKey;
fn epk(&'a self) -> &'a D::EphemeralPublicKey;
fn cmstar(&'a self) -> &'a D::ExtractedCommitment;
pub trait ShieldedOutput<D: Domain> {
fn epk(&self) -> &D::EphemeralPublicKey;
fn cmstar(&self) -> D::ExtractedCommitment;
fn enc_ciphertext(&self) -> &[u8];
}

/// A struct containing context required for encrypting Sapling and Orchard notes.
Expand Down Expand Up @@ -315,25 +315,22 @@ impl<D: Domain> NoteEncryption<D> {
/// `PaymentAddress` to which the note was sent.
///
/// Implements section 4.17.2 of the Zcash Protocol Specification.
pub fn try_note_decryption<D: Domain>(
pub fn try_note_decryption<D: Domain, Output: ShieldedOutput<D>>(
domain: &D,
//output: &ShieldedOutput<D>,
ivk: &D::IncomingViewingKey,
epk: &D::EphemeralPublicKey,
cmstar: &D::ExtractedCommitment,
enc_ciphertext: &[u8],
output: &Output,
) -> Option<(D::Note, D::Recipient, D::Memo)> {
assert_eq!(enc_ciphertext.len(), ENC_CIPHERTEXT_SIZE);
assert_eq!(output.enc_ciphertext().len(), ENC_CIPHERTEXT_SIZE);

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

let mut plaintext = [0; ENC_CIPHERTEXT_SIZE];
assert_eq!(
ChachaPolyIetf::aead_cipher()
.open_to(
&mut plaintext,
&enc_ciphertext,
output.enc_ciphertext(),
&[],
key.as_ref(),
&[0u8; 12]
Expand All @@ -342,7 +339,13 @@ pub fn try_note_decryption<D: Domain>(
NOTE_PLAINTEXT_SIZE
);

let (note, to) = parse_note_plaintext_without_memo_ivk(domain, ivk, epk, cmstar, &plaintext)?;
let (note, to) = parse_note_plaintext_without_memo_ivk(
domain,
ivk,
output.epk(),
&output.cmstar(),
&plaintext,
)?;
let memo = domain.extract_memo(&plaintext);

Some((note, to, memo))
Expand Down Expand Up @@ -375,7 +378,10 @@ fn check_note_validity<D: Domain>(
} else {
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).into() {
if D::epk_bytes(&D::ka_derive_public(&note, &derived_esk))
.ct_eq(&epk_bytes)
.into()
{
NoteValidity::Valid
} else {
NoteValidity::Invalid
Expand All @@ -393,24 +399,22 @@ fn check_note_validity<D: Domain>(
/// Implements the procedure specified in [`ZIP 307`].
///
/// [`ZIP 307`]: https://zips.z.cash/zip-0307
pub fn try_compact_note_decryption<D: Domain>(
pub fn try_compact_note_decryption<D: Domain, Output: ShieldedOutput<D>>(
domain: &D,
ivk: &D::IncomingViewingKey,
epk: &D::EphemeralPublicKey,
cmstar: &D::ExtractedCommitment,
enc_ciphertext: &[u8],
output: &Output,
) -> Option<(D::Note, D::Recipient)> {
assert_eq!(enc_ciphertext.len(), COMPACT_NOTE_SIZE);
assert_eq!(output.enc_ciphertext().len(), COMPACT_NOTE_SIZE);

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

// Start from block 1 to skip over Poly1305 keying output
let mut plaintext = [0; COMPACT_NOTE_SIZE];
plaintext.copy_from_slice(&enc_ciphertext);
plaintext.copy_from_slice(output.enc_ciphertext());
ChaCha20Ietf::xor(key.as_ref(), &[0u8; 12], 1, &mut plaintext);

parse_note_plaintext_without_memo_ivk(domain, ivk, epk, cmstar, &plaintext)
parse_note_plaintext_without_memo_ivk(domain, ivk, output.epk(), &output.cmstar(), &plaintext)
}

/// Recovery of the full note plaintext by the sender.
Expand All @@ -421,15 +425,13 @@ pub fn try_compact_note_decryption<D: Domain>(
///
/// Implements part of section 4.17.3 of the Zcash Protocol Specification.
/// For decryption using a Full Viewing Key see [`try_sapling_output_recovery`].
pub fn try_output_recovery_with_ock<D: Domain>(
pub fn try_output_recovery_with_ock<D: Domain, Output: ShieldedOutput<D>>(
domain: &D,
ock: &OutgoingCipherKey,
cmstar: &D::ExtractedCommitment,
epk: &D::EphemeralPublicKey,
enc_ciphertext: &[u8],
output: &Output,
out_ciphertext: &[u8],
) -> Option<(D::Note, D::Recipient, D::Memo)> {
assert_eq!(enc_ciphertext.len(), ENC_CIPHERTEXT_SIZE);
assert_eq!(output.enc_ciphertext().len(), ENC_CIPHERTEXT_SIZE);
assert_eq!(out_ciphertext.len(), OUT_CIPHERTEXT_SIZE);

let mut op = [0; OUT_CIPHERTEXT_SIZE];
Expand All @@ -444,14 +446,14 @@ pub fn try_output_recovery_with_ock<D: Domain>(
let esk = D::extract_esk(&op)?;

let shared_secret = D::ka_agree_enc(&esk, &pk_d);
let key = D::kdf(shared_secret, &epk);
let key = D::kdf(shared_secret, output.epk());

let mut plaintext = [0; ENC_CIPHERTEXT_SIZE];
assert_eq!(
ChachaPolyIetf::aead_cipher()
.open_to(
&mut plaintext,
&enc_ciphertext,
output.enc_ciphertext(),
&[],
key.as_ref(),
&[0u8; 12]
Expand All @@ -460,10 +462,11 @@ pub fn try_output_recovery_with_ock<D: Domain>(
NOTE_PLAINTEXT_SIZE
);

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

if let NoteValidity::Valid = check_note_validity::<D>(&note, epk, cmstar) {
if let NoteValidity::Valid = check_note_validity::<D>(&note, output.epk(), &output.cmstar()) {
Some((note, to, memo))
} else {
None
Expand Down
5 changes: 3 additions & 2 deletions zcash_client_backend/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,14 @@ group = "0.8"
hex = "0.4"
jubjub = "0.5.1"
nom = "6.1"
percent-encoding = "2.1.0"
proptest = { version = "0.10.1", optional = true }
protobuf = "2.20"
rand_core = "0.5.1"
subtle = "2.2.3"
time = "0.2"
zcash_note_encryption = { version = "0.0", path = "../components/zcash_note_encryption" }
zcash_primitives = { version = "0.5", path = "../zcash_primitives" }
proptest = { version = "0.10.1", optional = true }
percent-encoding = "2.1.0"

[build-dependencies]
protobuf-codegen-pure = "2.20"
Expand Down
38 changes: 15 additions & 23 deletions zcash_client_backend/src/decrypt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,29 +49,21 @@ pub fn decrypt_transaction<P: consensus::Parameters>(
let ovk = extfvk.fvk.ovk;

for (index, output) in tx.shielded_outputs.iter().enumerate() {
let ((note, to, memo), outgoing) = match try_sapling_note_decryption(
params,
height,
&ivk,
&output.ephemeral_key,
&output.cmu,
&output.enc_ciphertext,
) {
Some(ret) => (ret, false),
None => match try_sapling_output_recovery(
params,
height,
&ovk,
&output.cv,
&output.cmu,
&output.ephemeral_key,
&output.enc_ciphertext,
&output.out_ciphertext,
) {
Some(ret) => (ret, true),
None => continue,
},
};
let ((note, to, memo), outgoing) =
match try_sapling_note_decryption(params, height, &ivk, output) {
Some(ret) => (ret, false),
None => match try_sapling_output_recovery(
params,
height,
&ovk,
&output.cv,
output,
&output.out_ciphertext,
) {
Some(ret) => (ret, true),
None => continue,
},
};
decrypted.push(DecryptedOutput {
index,
note,
Expand Down
27 changes: 26 additions & 1 deletion zcash_client_backend/src/proto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@

use ff::PrimeField;
use group::GroupEncoding;
use std::convert::TryInto;
use std::convert::{TryFrom, TryInto};

use zcash_primitives::{
block::{BlockHash, BlockHeader},
consensus::BlockHeight,
sapling::Nullifier,
transaction::components::sapling::{CompactOutputDescription, OutputDescription},
};

use zcash_note_encryption::COMPACT_NOTE_SIZE;

pub mod compact_formats;

impl compact_formats::CompactBlock {
Expand Down Expand Up @@ -98,6 +101,28 @@ impl compact_formats::CompactOutput {
}
}

impl From<OutputDescription> for compact_formats::CompactOutput {
fn from(out: OutputDescription) -> compact_formats::CompactOutput {
let mut result = compact_formats::CompactOutput::new();
result.set_cmu(out.cmu.to_repr().to_vec());
result.set_epk(out.ephemeral_key.to_bytes().to_vec());
result.set_ciphertext(out.enc_ciphertext[..COMPACT_NOTE_SIZE].to_vec());
result
}
}

impl TryFrom<compact_formats::CompactOutput> for CompactOutputDescription {
type Error = ();

fn try_from(value: compact_formats::CompactOutput) -> Result<Self, Self::Error> {
Ok(CompactOutputDescription {
cmu: value.cmu()?,
epk: value.epk()?,
enc_ciphertext: value.ciphertext,
})
}
}

impl compact_formats::CompactSpend {
pub fn nf(&self) -> Result<Nullifier, ()> {
Nullifier::from_slice(&self.nf).map_err(|_| ())
Expand Down
41 changes: 17 additions & 24 deletions zcash_client_backend/src/welding_rig.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@

use ff::PrimeField;
use std::collections::HashSet;
use std::convert::TryFrom;
use subtle::{ConditionallySelectable, ConstantTimeEq, CtOption};
use zcash_primitives::{
consensus::{self, BlockHeight},
merkle_tree::{CommitmentTree, IncrementalWitness},
sapling::{
note_encryption::try_sapling_compact_note_decryption, Node, Note, Nullifier,
PaymentAddress, SaplingIvk,
note_encryption::{try_sapling_compact_note_decryption, SaplingShieldedOutput},
Node, Note, Nullifier, PaymentAddress, SaplingIvk,
},
transaction::TxId,
transaction::{components::sapling::CompactOutputDescription, TxId},
zip32::ExtendedFullViewingKey,
};

Expand Down Expand Up @@ -38,12 +39,10 @@ fn scan_output<P: consensus::Parameters, K: ScanningKey>(
block_witnesses: &mut [&mut IncrementalWitness<Node>],
new_witnesses: &mut [&mut IncrementalWitness<Node>],
) -> Option<WalletShieldedOutput<K::Nf>> {
let cmu = output.cmu().ok()?;
let epk = output.epk().ok()?;
let ct = output.ciphertext;
let output = CompactOutputDescription::try_from(output).ok()?;

// Increment tree and witnesses
let node = Node::new(cmu.to_repr());
let node = Node::new(output.cmu.to_repr());
for witness in existing_witnesses {
witness.append(node).unwrap();
}
Expand All @@ -56,7 +55,7 @@ fn scan_output<P: consensus::Parameters, K: ScanningKey>(
tree.append(node).unwrap();

for (account, vk) in vks.iter() {
let (note, to) = match vk.try_decryption(params, height, &epk, &cmu, &ct) {
let (note, to) = match vk.try_decryption(params, height, &output) {
Some(ret) => ret,
None => continue,
};
Expand All @@ -74,8 +73,8 @@ fn scan_output<P: consensus::Parameters, K: ScanningKey>(

return Some(WalletShieldedOutput {
index,
cmu,
epk,
cmu: output.cmu,
epk: output.epk,
account: **account,
note,
to,
Expand Down Expand Up @@ -108,13 +107,11 @@ pub trait ScanningKey {

/// Attempts to decrypt a Sapling note and payment address
/// from the specified ciphertext using this scanning key.
fn try_decryption<P: consensus::Parameters>(
fn try_decryption<P: consensus::Parameters, Output: SaplingShieldedOutput<P>>(
&self,
params: &P,
height: BlockHeight,
epk: &jubjub::ExtendedPoint,
cmu: &bls12_381::Scalar,
ct: &[u8],
output: &Output,
) -> Option<(Note, PaymentAddress)>;

/// Produces the nullifier for the specified note and witness, if possible.
Expand All @@ -132,15 +129,13 @@ pub trait ScanningKey {
impl ScanningKey for ExtendedFullViewingKey {
type Nf = Nullifier;

fn try_decryption<P: consensus::Parameters>(
fn try_decryption<P: consensus::Parameters, Output: SaplingShieldedOutput<P>>(
&self,
params: &P,
height: BlockHeight,
epk: &jubjub::ExtendedPoint,
cmu: &bls12_381::Scalar,
ct: &[u8],
output: &Output,
) -> Option<(Note, PaymentAddress)> {
try_sapling_compact_note_decryption(params, height, &self.fvk.vk.ivk(), &epk, &cmu, &ct)
try_sapling_compact_note_decryption(params, height, &self.fvk.vk.ivk(), output)
}

fn nf(&self, note: &Note, witness: &IncrementalWitness<Node>) -> Self::Nf {
Expand All @@ -155,15 +150,13 @@ impl ScanningKey for ExtendedFullViewingKey {
impl ScanningKey for SaplingIvk {
type Nf = ();

fn try_decryption<P: consensus::Parameters>(
fn try_decryption<P: consensus::Parameters, Output: SaplingShieldedOutput<P>>(
&self,
params: &P,
height: BlockHeight,
epk: &jubjub::ExtendedPoint,
cmu: &bls12_381::Scalar,
ct: &[u8],
output: &Output,
) -> Option<(Note, PaymentAddress)> {
try_sapling_compact_note_decryption(params, height, self, &epk, &cmu, &ct)
try_sapling_compact_note_decryption(params, height, self, output)
}

fn nf(&self, _note: &Note, _witness: &IncrementalWitness<Node>) {}
Expand Down
4 changes: 1 addition & 3 deletions zcash_client_sqlite/src/wallet/transact.rs
Original file line number Diff line number Diff line change
Expand Up @@ -635,9 +635,7 @@ mod tests {
sapling_activation_height(),
&extfvk.fvk.ovk,
&output.cv,
&output.cmu,
&output.ephemeral_key,
&output.enc_ciphertext,
output,
&output.out_ciphertext,
)
};
Expand Down

0 comments on commit 324fc36

Please sign in to comment.