Skip to content

Commit

Permalink
Merge pull request #633 from daira/prepare-epks-and-ivks
Browse files Browse the repository at this point in the history
Add APIs to prepare ivk and epk and implement them for Sapling
  • Loading branch information
str4d committed Sep 15, 2022
2 parents 3bc8627 + 20e869f commit 8483503
Show file tree
Hide file tree
Showing 11 changed files with 160 additions and 77 deletions.
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,5 @@ codegen-units = 1
[patch.crates-io]
zcash_encoding = { path = "components/zcash_encoding" }
zcash_note_encryption = { path = "components/zcash_note_encryption" }
orchard = { git = "https://github.com/zcash/orchard.git", rev = "33f1c1141e50adb68715f3359bd75378b4756cca" }
group = { git = "https://github.com/zkcrypto/group.git", rev = "a7f3ceb2373e9fe536996f7b4d55c797f3e667f0" }
13 changes: 10 additions & 3 deletions components/zcash_note_encryption/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,17 @@ and this library adheres to Rust's notion of
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
### Added
- `zcash_note_encryption::Domain`:
- `Domain::PreparedEphemeralPublicKey` associated type.
- `Domain::prepare_epk` method, which produces the above type.

### Changed
- MSRV is now 1.56.1.
- Migrated to `group 0.13`.
- `zcash_note_encryption::Domain` now requires `epk` to be converted to
`Domain::PreparedEphemeralPublicKey` before being passed to
`Domain::ka_agree_dec`.
- Changes to batch decryption APIs:
- The return types of `batch::try_note_decryption` and
`batch::try_compact_note_decryption` have changed. Now, instead of
Expand All @@ -16,8 +26,5 @@ and this library adheres to Rust's notion of
argument to the function. Each successful result includes the index of the
entry in `ivks` used to decrypt the value.

### Changed
- MSRV is now 1.56.1.

## [0.1.0] - 2021-12-17
Initial release.
1 change: 1 addition & 0 deletions components/zcash_note_encryption/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ rustdoc-args = ["--cfg", "docsrs"]
[dependencies]
chacha20 = { version = "0.8", default-features = false }
chacha20poly1305 = { version = "0.9", default-features = false }
group = "0.12"
rand_core = { version = "0.6", default-features = false }
subtle = { version = "2.2.3", default-features = false }

Expand Down
2 changes: 1 addition & 1 deletion components/zcash_note_encryption/src/batch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ where
return (0..outputs.len()).map(|_| None).collect();
};

// Fetch the ephemeral keys for each output and batch-parse them.
// Fetch the ephemeral keys for each output, and batch-parse and prepare them.
let ephemeral_keys = D::batch_epk(outputs.iter().map(|(_, output)| output.ephemeral_key()));

// Derive the shared secrets for all combinations of (ivk, output).
Expand Down
19 changes: 14 additions & 5 deletions components/zcash_note_encryption/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ enum NoteValidity {
pub trait Domain {
type EphemeralSecretKey: ConstantTimeEq;
type EphemeralPublicKey;
type PreparedEphemeralPublicKey;
type SharedSecret;
type SymmetricKey: AsRef<[u8]>;
type Note;
Expand All @@ -136,6 +137,9 @@ pub trait Domain {
/// Extracts the `DiversifiedTransmissionKey` from the note.
fn get_pk_d(note: &Self::Note) -> Self::DiversifiedTransmissionKey;

/// Prepare an ephemeral public key for more efficient scalar multiplication.
fn prepare_epk(epk: Self::EphemeralPublicKey) -> Self::PreparedEphemeralPublicKey;

/// Derives `EphemeralPublicKey` from `esk` and the note's diversifier.
fn ka_derive_public(
note: &Self::Note,
Expand All @@ -152,7 +156,7 @@ pub trait Domain {
/// decryption.
fn ka_agree_dec(
ivk: &Self::IncomingViewingKey,
epk: &Self::EphemeralPublicKey,
epk: &Self::PreparedEphemeralPublicKey,
) -> Self::SharedSecret;

/// Derives the `SymmetricKey` used to encrypt the note plaintext.
Expand Down Expand Up @@ -306,10 +310,15 @@ pub trait BatchDomain: Domain {
/// them.
fn batch_epk(
ephemeral_keys: impl Iterator<Item = EphemeralKeyBytes>,
) -> Vec<(Option<Self::EphemeralPublicKey>, EphemeralKeyBytes)> {
) -> Vec<(Option<Self::PreparedEphemeralPublicKey>, EphemeralKeyBytes)> {
// Default implementation: do the non-batched thing.
ephemeral_keys
.map(|ephemeral_key| (Self::epk(&ephemeral_key), ephemeral_key))
.map(|ephemeral_key| {
(
Self::epk(&ephemeral_key).map(Self::prepare_epk),
ephemeral_key,
)
})
.collect()
}
}
Expand Down Expand Up @@ -514,7 +523,7 @@ pub fn try_note_decryption<D: Domain, Output: ShieldedOutput<D, ENC_CIPHERTEXT_S
) -> Option<(D::Note, D::Recipient, D::Memo)> {
let ephemeral_key = output.ephemeral_key();

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

Expand Down Expand Up @@ -611,7 +620,7 @@ pub fn try_compact_note_decryption<D: Domain, Output: ShieldedOutput<D, COMPACT_
) -> Option<(D::Note, D::Recipient)> {
let ephemeral_key = output.ephemeral_key();

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

Expand Down
17 changes: 10 additions & 7 deletions zcash_client_backend/src/data_api/chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ use zcash_primitives::{
block::BlockHash,
consensus::{self, BlockHeight, NetworkUpgrade},
merkle_tree::CommitmentTree,
sapling::{keys::Scope, Nullifier},
sapling::{keys::Scope, note_encryption::PreparedIncomingViewingKey, Nullifier},
};

use crate::{
Expand Down Expand Up @@ -234,12 +234,15 @@ where

let mut batch_runner = BatchRunner::new(
100,
dfvks.iter().flat_map(|(account, dfvk)| {
[
((**account, Scope::External), dfvk.to_ivk(Scope::External)),
((**account, Scope::Internal), dfvk.to_ivk(Scope::Internal)),
]
}),
dfvks
.iter()
.flat_map(|(account, dfvk)| {
[
((**account, Scope::External), dfvk.to_ivk(Scope::External)),
((**account, Scope::Internal), dfvk.to_ivk(Scope::Internal)),
]
})
.map(|(tag, ivk)| (tag, PreparedIncomingViewingKey::new(&ivk))),
);

cache.with_blocks(last_height, limit, |block: CompactBlock| {
Expand Down
6 changes: 4 additions & 2 deletions zcash_client_backend/src/decrypt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ use zcash_primitives::{
consensus::{self, BlockHeight},
memo::MemoBytes,
sapling::{
note_encryption::{try_sapling_note_decryption, try_sapling_output_recovery},
note_encryption::{
try_sapling_note_decryption, try_sapling_output_recovery, PreparedIncomingViewingKey,
},
Note, PaymentAddress,
},
transaction::Transaction,
Expand Down Expand Up @@ -47,7 +49,7 @@ pub fn decrypt_transaction<P: consensus::Parameters>(
if let Some(bundle) = tx.sapling_bundle() {
for (account, ufvk) in ufvks.iter() {
if let Some(dfvk) = ufvk.sapling() {
let ivk = dfvk.fvk().vk.ivk();
let ivk = PreparedIncomingViewingKey::new(&dfvk.fvk().vk.ivk());
let ovk = dfvk.fvk().ovk;

for (index, output) in bundle.shielded_outputs.iter().enumerate() {
Expand Down
16 changes: 10 additions & 6 deletions zcash_client_backend/src/welding_rig.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use zcash_primitives::{
sapling::{
self,
keys::{DiversifiableFullViewingKey, Scope},
note_encryption::SaplingDomain,
note_encryption::{PreparedIncomingViewingKey, SaplingDomain},
Node, Note, Nullifier, NullifierDerivingKey, SaplingIvk,
},
transaction::components::sapling::CompactOutputDescription,
Expand Down Expand Up @@ -325,7 +325,8 @@ pub(crate) fn scan_block_with_runner<P: consensus::Parameters + Send + 'static,

let ivks = vks
.iter()
.map(|(_, ivk, _)| (*ivk).clone())
.map(|(_, ivk, _)| ivk)
.map(PreparedIncomingViewingKey::new)
.collect::<Vec<_>>();

batch::try_compact_note_decryption(&ivks, decoded)
Expand Down Expand Up @@ -413,8 +414,9 @@ mod tests {
memo::MemoBytes,
merkle_tree::CommitmentTree,
sapling::{
note_encryption::sapling_note_encryption, util::generate_random_rseed, Note, Nullifier,
SaplingIvk,
note_encryption::{sapling_note_encryption, PreparedIncomingViewingKey},
util::generate_random_rseed,
Note, Nullifier, SaplingIvk,
},
transaction::components::Amount,
zip32::{AccountId, ExtendedFullViewingKey, ExtendedSpendingKey},
Expand Down Expand Up @@ -557,7 +559,8 @@ mod tests {
extfvk
.to_sapling_keys()
.iter()
.map(|(scope, ivk, _)| ((account, *scope), ivk.clone())),
.map(|(scope, ivk, _)| ((account, *scope), ivk))
.map(|(tag, ivk)| (tag, PreparedIncomingViewingKey::new(ivk))),
);

add_block_to_runner(&Network::TestNetwork, cb.clone(), &mut runner);
Expand Down Expand Up @@ -620,7 +623,8 @@ mod tests {
extfvk
.to_sapling_keys()
.iter()
.map(|(scope, ivk, _)| ((account, *scope), ivk.clone())),
.map(|(scope, ivk, _)| ((account, *scope), ivk))
.map(|(tag, ivk)| (tag, PreparedIncomingViewingKey::new(ivk))),
);

add_block_to_runner(&Network::TestNetwork, cb.clone(), &mut runner);
Expand Down
13 changes: 13 additions & 0 deletions zcash_primitives/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ and this library adheres to Rust's notion of
- `Scope`
- `ExpandedSpendingKey::from_bytes`
- `ExtendedSpendingKey::{from_bytes, to_bytes}`
- `zcash_primitives::sapling::note_encryption`:
- `PreparedIncomingViewingKey`
- `PreparedEphemeralPublicKey`
- Added in `zcash_primitives::zip32`
- `ChainCode::as_bytes`
- `DiversifierKey::{from_bytes, as_bytes}`
Expand All @@ -35,13 +38,23 @@ and this library adheres to Rust's notion of
- `Bundle::value_balance`

### Changed
- Migrated to `group 0.13`.
- `zcash_primitives::sapling::ViewingKey` now stores `nk` as a
`NullifierDerivingKey` instead of as a bare `jubjub::SubgroupPoint`.
- The signature of `zcash_primitives::sapling::Note::nf` has changed to
take just a `NullifierDerivingKey` (the only capability it actually required)
rather than the full `ViewingKey` as its first argument.
- Made the internals of `zip32::DiversifierKey` private; use `from_bytes` and
`as_bytes` on this type instead.
- `zcash_primitives::sapling::note_encryption` APIs now expose precomputations
explicitly (where previously they were performed internally), to enable users
to avoid recomputing incoming viewing key precomputations. Users now need to
call `PreparedIncomingViewingKey::new` to convert their `SaplingIvk`s into
their precomputed forms, and can do so wherever it makes sense in their stack.
- `SaplingDomain::IncomingViewingKey` is now `PreparedIncomingViewingKey`
instead of `SaplingIvk`.
- `try_sapling_note_decryption` and `try_sapling_compact_note_decryption` now
take `&PreparedIncomingViewingKey` instead of `&SaplingIvk`.

## [0.7.0] - 2022-06-24
### Changed
Expand Down
71 changes: 38 additions & 33 deletions zcash_primitives/benches/note_decryption.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use zcash_primitives::{
sapling::{
note_encryption::{
sapling_note_encryption, try_sapling_compact_note_decryption,
try_sapling_note_decryption, SaplingDomain,
try_sapling_note_decryption, PreparedIncomingViewingKey, SaplingDomain,
},
util::generate_random_rseed,
Diversifier, PaymentAddress, SaplingIvk, ValueCommitment,
Expand Down Expand Up @@ -67,6 +67,9 @@ fn bench_note_decryption(c: &mut Criterion) {
}
};

let valid_ivk = PreparedIncomingViewingKey::new(&valid_ivk);
let invalid_ivk = PreparedIncomingViewingKey::new(&invalid_ivk);

{
let mut group = c.benchmark_group("sapling-note-decryption");
group.throughput(Throughput::Elements(1));
Expand Down Expand Up @@ -98,40 +101,42 @@ fn bench_note_decryption(c: &mut Criterion) {
}

{
let valid_ivks = vec![valid_ivk];
let invalid_ivks = vec![invalid_ivk];

// We benchmark with one IVK so the overall batch size is equal to the number of
// outputs.
let size = 10;
let outputs: Vec<_> = iter::repeat(output)
.take(size)
.map(|output| (SaplingDomain::for_height(TEST_NETWORK, height), output))
.collect();

let mut group = c.benchmark_group("sapling-batch-note-decryption");
group.throughput(Throughput::Elements(size as u64));

group.bench_function(BenchmarkId::new("valid", size), |b| {
b.iter(|| batch::try_note_decryption(&valid_ivks, &outputs))
});

group.bench_function(BenchmarkId::new("invalid", size), |b| {
b.iter(|| batch::try_note_decryption(&invalid_ivks, &outputs))
});

let compact: Vec<_> = outputs
.into_iter()
.map(|(domain, output)| (domain, CompactOutputDescription::from(output)))
.collect();

group.bench_function(BenchmarkId::new("compact-valid", size), |b| {
b.iter(|| batch::try_compact_note_decryption(&valid_ivks, &compact))
});

group.bench_function(BenchmarkId::new("compact-invalid", size), |b| {
b.iter(|| batch::try_compact_note_decryption(&invalid_ivks, &compact))
});
for (nivks, noutputs) in [(1, 10), (10, 1), (10, 10), (50, 50)] {
let invalid_ivks: Vec<_> = iter::repeat(invalid_ivk.clone()).take(nivks).collect();
let valid_ivks: Vec<_> = iter::repeat(valid_ivk.clone()).take(nivks).collect();

let outputs: Vec<_> = iter::repeat(output.clone())
.take(noutputs)
.map(|output| (SaplingDomain::for_height(TEST_NETWORK, height), output))
.collect();

group.bench_function(
BenchmarkId::new(format!("valid-{}", nivks), noutputs),
|b| b.iter(|| batch::try_note_decryption(&valid_ivks, &outputs)),
);

group.bench_function(
BenchmarkId::new(format!("invalid-{}", nivks), noutputs),
|b| b.iter(|| batch::try_note_decryption(&invalid_ivks, &outputs)),
);

let compact: Vec<_> = outputs
.into_iter()
.map(|(domain, output)| (domain, CompactOutputDescription::from(output)))
.collect();

group.bench_function(
BenchmarkId::new(format!("compact-valid-{}", nivks), noutputs),
|b| b.iter(|| batch::try_compact_note_decryption(&valid_ivks, &compact)),
);

group.bench_function(
BenchmarkId::new(format!("compact-invalid-{}", nivks), noutputs),
|b| b.iter(|| batch::try_compact_note_decryption(&invalid_ivks, &compact)),
);
}
}
}

Expand Down

0 comments on commit 8483503

Please sign in to comment.