Merge pull request #633 from daira/prepare-epks-and-ivks

Add APIs to prepare ivk and epk and implement them for Sapling
This commit is contained in:
str4d 2022-09-15 12:45:33 +01:00 committed by GitHub
commit 84835035d5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 156 additions and 73 deletions

View File

@ -21,3 +21,5 @@ codegen-units = 1
[patch.crates-io] [patch.crates-io]
zcash_encoding = { path = "components/zcash_encoding" } zcash_encoding = { path = "components/zcash_encoding" }
zcash_note_encryption = { path = "components/zcash_note_encryption" } 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" }

View File

@ -6,7 +6,17 @@ and this library adheres to Rust's notion of
[Semantic Versioning](https://semver.org/spec/v2.0.0.html). [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased] ## [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: - Changes to batch decryption APIs:
- The return types of `batch::try_note_decryption` and - The return types of `batch::try_note_decryption` and
`batch::try_compact_note_decryption` have changed. Now, instead of `batch::try_compact_note_decryption` have changed. Now, instead of
@ -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 argument to the function. Each successful result includes the index of the
entry in `ivks` used to decrypt the value. entry in `ivks` used to decrypt the value.
### Changed
- MSRV is now 1.56.1.
## [0.1.0] - 2021-12-17 ## [0.1.0] - 2021-12-17
Initial release. Initial release.

View File

@ -21,6 +21,7 @@ rustdoc-args = ["--cfg", "docsrs"]
[dependencies] [dependencies]
chacha20 = { version = "0.8", default-features = false } chacha20 = { version = "0.8", default-features = false }
chacha20poly1305 = { version = "0.9", default-features = false } chacha20poly1305 = { version = "0.9", default-features = false }
group = "0.12"
rand_core = { version = "0.6", default-features = false } rand_core = { version = "0.6", default-features = false }
subtle = { version = "2.2.3", default-features = false } subtle = { version = "2.2.3", default-features = false }

View File

@ -51,7 +51,7 @@ where
return (0..outputs.len()).map(|_| None).collect(); 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())); let ephemeral_keys = D::batch_epk(outputs.iter().map(|(_, output)| output.ephemeral_key()));
// Derive the shared secrets for all combinations of (ivk, output). // Derive the shared secrets for all combinations of (ivk, output).

View File

@ -113,6 +113,7 @@ enum NoteValidity {
pub trait Domain { pub trait Domain {
type EphemeralSecretKey: ConstantTimeEq; type EphemeralSecretKey: ConstantTimeEq;
type EphemeralPublicKey; type EphemeralPublicKey;
type PreparedEphemeralPublicKey;
type SharedSecret; type SharedSecret;
type SymmetricKey: AsRef<[u8]>; type SymmetricKey: AsRef<[u8]>;
type Note; type Note;
@ -136,6 +137,9 @@ pub trait Domain {
/// Extracts the `DiversifiedTransmissionKey` from the note. /// Extracts the `DiversifiedTransmissionKey` from the note.
fn get_pk_d(note: &Self::Note) -> Self::DiversifiedTransmissionKey; 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. /// Derives `EphemeralPublicKey` from `esk` and the note's diversifier.
fn ka_derive_public( fn ka_derive_public(
note: &Self::Note, note: &Self::Note,
@ -152,7 +156,7 @@ pub trait Domain {
/// decryption. /// decryption.
fn ka_agree_dec( fn ka_agree_dec(
ivk: &Self::IncomingViewingKey, ivk: &Self::IncomingViewingKey,
epk: &Self::EphemeralPublicKey, epk: &Self::PreparedEphemeralPublicKey,
) -> Self::SharedSecret; ) -> Self::SharedSecret;
/// Derives the `SymmetricKey` used to encrypt the note plaintext. /// Derives the `SymmetricKey` used to encrypt the note plaintext.
@ -306,10 +310,15 @@ pub trait BatchDomain: Domain {
/// them. /// them.
fn batch_epk( fn batch_epk(
ephemeral_keys: impl Iterator<Item = EphemeralKeyBytes>, ephemeral_keys: impl Iterator<Item = EphemeralKeyBytes>,
) -> Vec<(Option<Self::EphemeralPublicKey>, EphemeralKeyBytes)> { ) -> Vec<(Option<Self::PreparedEphemeralPublicKey>, EphemeralKeyBytes)> {
// Default implementation: do the non-batched thing. // Default implementation: do the non-batched thing.
ephemeral_keys 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() .collect()
} }
} }
@ -514,7 +523,7 @@ pub fn try_note_decryption<D: Domain, Output: ShieldedOutput<D, ENC_CIPHERTEXT_S
) -> Option<(D::Note, D::Recipient, D::Memo)> { ) -> Option<(D::Note, D::Recipient, D::Memo)> {
let ephemeral_key = output.ephemeral_key(); 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 shared_secret = D::ka_agree_dec(ivk, &epk);
let key = D::kdf(shared_secret, &ephemeral_key); let key = D::kdf(shared_secret, &ephemeral_key);
@ -611,7 +620,7 @@ pub fn try_compact_note_decryption<D: Domain, Output: ShieldedOutput<D, COMPACT_
) -> Option<(D::Note, D::Recipient)> { ) -> Option<(D::Note, D::Recipient)> {
let ephemeral_key = output.ephemeral_key(); 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 shared_secret = D::ka_agree_dec(ivk, &epk);
let key = D::kdf(shared_secret, &ephemeral_key); let key = D::kdf(shared_secret, &ephemeral_key);

View File

@ -85,7 +85,7 @@ use zcash_primitives::{
block::BlockHash, block::BlockHash,
consensus::{self, BlockHeight, NetworkUpgrade}, consensus::{self, BlockHeight, NetworkUpgrade},
merkle_tree::CommitmentTree, merkle_tree::CommitmentTree,
sapling::{keys::Scope, Nullifier}, sapling::{keys::Scope, note_encryption::PreparedIncomingViewingKey, Nullifier},
}; };
use crate::{ use crate::{
@ -234,12 +234,15 @@ where
let mut batch_runner = BatchRunner::new( let mut batch_runner = BatchRunner::new(
100, 100,
dfvks.iter().flat_map(|(account, dfvk)| { dfvks
[ .iter()
((**account, Scope::External), dfvk.to_ivk(Scope::External)), .flat_map(|(account, dfvk)| {
((**account, Scope::Internal), dfvk.to_ivk(Scope::Internal)), [
] ((**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| { cache.with_blocks(last_height, limit, |block: CompactBlock| {

View File

@ -4,7 +4,9 @@ use zcash_primitives::{
consensus::{self, BlockHeight}, consensus::{self, BlockHeight},
memo::MemoBytes, memo::MemoBytes,
sapling::{ sapling::{
note_encryption::{try_sapling_note_decryption, try_sapling_output_recovery}, note_encryption::{
try_sapling_note_decryption, try_sapling_output_recovery, PreparedIncomingViewingKey,
},
Note, PaymentAddress, Note, PaymentAddress,
}, },
transaction::Transaction, transaction::Transaction,
@ -47,7 +49,7 @@ pub fn decrypt_transaction<P: consensus::Parameters>(
if let Some(bundle) = tx.sapling_bundle() { if let Some(bundle) = tx.sapling_bundle() {
for (account, ufvk) in ufvks.iter() { for (account, ufvk) in ufvks.iter() {
if let Some(dfvk) = ufvk.sapling() { 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; let ovk = dfvk.fvk().ovk;
for (index, output) in bundle.shielded_outputs.iter().enumerate() { for (index, output) in bundle.shielded_outputs.iter().enumerate() {

View File

@ -11,7 +11,7 @@ use zcash_primitives::{
sapling::{ sapling::{
self, self,
keys::{DiversifiableFullViewingKey, Scope}, keys::{DiversifiableFullViewingKey, Scope},
note_encryption::SaplingDomain, note_encryption::{PreparedIncomingViewingKey, SaplingDomain},
Node, Note, Nullifier, NullifierDerivingKey, SaplingIvk, Node, Note, Nullifier, NullifierDerivingKey, SaplingIvk,
}, },
transaction::components::sapling::CompactOutputDescription, transaction::components::sapling::CompactOutputDescription,
@ -325,7 +325,8 @@ pub(crate) fn scan_block_with_runner<P: consensus::Parameters + Send + 'static,
let ivks = vks let ivks = vks
.iter() .iter()
.map(|(_, ivk, _)| (*ivk).clone()) .map(|(_, ivk, _)| ivk)
.map(PreparedIncomingViewingKey::new)
.collect::<Vec<_>>(); .collect::<Vec<_>>();
batch::try_compact_note_decryption(&ivks, decoded) batch::try_compact_note_decryption(&ivks, decoded)
@ -413,8 +414,9 @@ mod tests {
memo::MemoBytes, memo::MemoBytes,
merkle_tree::CommitmentTree, merkle_tree::CommitmentTree,
sapling::{ sapling::{
note_encryption::sapling_note_encryption, util::generate_random_rseed, Note, Nullifier, note_encryption::{sapling_note_encryption, PreparedIncomingViewingKey},
SaplingIvk, util::generate_random_rseed,
Note, Nullifier, SaplingIvk,
}, },
transaction::components::Amount, transaction::components::Amount,
zip32::{AccountId, ExtendedFullViewingKey, ExtendedSpendingKey}, zip32::{AccountId, ExtendedFullViewingKey, ExtendedSpendingKey},
@ -557,7 +559,8 @@ mod tests {
extfvk extfvk
.to_sapling_keys() .to_sapling_keys()
.iter() .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); add_block_to_runner(&Network::TestNetwork, cb.clone(), &mut runner);
@ -620,7 +623,8 @@ mod tests {
extfvk extfvk
.to_sapling_keys() .to_sapling_keys()
.iter() .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); add_block_to_runner(&Network::TestNetwork, cb.clone(), &mut runner);

View File

@ -17,6 +17,9 @@ and this library adheres to Rust's notion of
- `Scope` - `Scope`
- `ExpandedSpendingKey::from_bytes` - `ExpandedSpendingKey::from_bytes`
- `ExtendedSpendingKey::{from_bytes, to_bytes}` - `ExtendedSpendingKey::{from_bytes, to_bytes}`
- `zcash_primitives::sapling::note_encryption`:
- `PreparedIncomingViewingKey`
- `PreparedEphemeralPublicKey`
- Added in `zcash_primitives::zip32` - Added in `zcash_primitives::zip32`
- `ChainCode::as_bytes` - `ChainCode::as_bytes`
- `DiversifierKey::{from_bytes, as_bytes}` - `DiversifierKey::{from_bytes, as_bytes}`
@ -35,6 +38,7 @@ and this library adheres to Rust's notion of
- `Bundle::value_balance` - `Bundle::value_balance`
### Changed ### Changed
- Migrated to `group 0.13`.
- `zcash_primitives::sapling::ViewingKey` now stores `nk` as a - `zcash_primitives::sapling::ViewingKey` now stores `nk` as a
`NullifierDerivingKey` instead of as a bare `jubjub::SubgroupPoint`. `NullifierDerivingKey` instead of as a bare `jubjub::SubgroupPoint`.
- The signature of `zcash_primitives::sapling::Note::nf` has changed to - The signature of `zcash_primitives::sapling::Note::nf` has changed to
@ -42,6 +46,15 @@ and this library adheres to Rust's notion of
rather than the full `ViewingKey` as its first argument. rather than the full `ViewingKey` as its first argument.
- Made the internals of `zip32::DiversifierKey` private; use `from_bytes` and - Made the internals of `zip32::DiversifierKey` private; use `from_bytes` and
`as_bytes` on this type instead. `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 ## [0.7.0] - 2022-06-24
### Changed ### Changed

View File

@ -11,7 +11,7 @@ use zcash_primitives::{
sapling::{ sapling::{
note_encryption::{ note_encryption::{
sapling_note_encryption, try_sapling_compact_note_decryption, sapling_note_encryption, try_sapling_compact_note_decryption,
try_sapling_note_decryption, SaplingDomain, try_sapling_note_decryption, PreparedIncomingViewingKey, SaplingDomain,
}, },
util::generate_random_rseed, util::generate_random_rseed,
Diversifier, PaymentAddress, SaplingIvk, ValueCommitment, Diversifier, PaymentAddress, SaplingIvk, ValueCommitment,
@ -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"); let mut group = c.benchmark_group("sapling-note-decryption");
group.throughput(Throughput::Elements(1)); group.throughput(Throughput::Elements(1));
@ -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"); 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| { for (nivks, noutputs) in [(1, 10), (10, 1), (10, 10), (50, 50)] {
b.iter(|| batch::try_note_decryption(&valid_ivks, &outputs)) let invalid_ivks: Vec<_> = iter::repeat(invalid_ivk.clone()).take(nivks).collect();
}); let valid_ivks: Vec<_> = iter::repeat(valid_ivk.clone()).take(nivks).collect();
group.bench_function(BenchmarkId::new("invalid", size), |b| { let outputs: Vec<_> = iter::repeat(output.clone())
b.iter(|| batch::try_note_decryption(&invalid_ivks, &outputs)) .take(noutputs)
}); .map(|output| (SaplingDomain::for_height(TEST_NETWORK, height), output))
.collect();
let compact: Vec<_> = outputs group.bench_function(
.into_iter() BenchmarkId::new(format!("valid-{}", nivks), noutputs),
.map(|(domain, output)| (domain, CompactOutputDescription::from(output))) |b| b.iter(|| batch::try_note_decryption(&valid_ivks, &outputs)),
.collect(); );
group.bench_function(BenchmarkId::new("compact-valid", size), |b| { group.bench_function(
b.iter(|| batch::try_compact_note_decryption(&valid_ivks, &compact)) BenchmarkId::new(format!("invalid-{}", nivks), noutputs),
}); |b| b.iter(|| batch::try_note_decryption(&invalid_ivks, &outputs)),
);
group.bench_function(BenchmarkId::new("compact-invalid", size), |b| { let compact: Vec<_> = outputs
b.iter(|| batch::try_compact_note_decryption(&invalid_ivks, &compact)) .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)),
);
}
} }
} }

View File

@ -2,7 +2,7 @@
use blake2b_simd::{Hash as Blake2bHash, Params as Blake2bParams}; use blake2b_simd::{Hash as Blake2bHash, Params as Blake2bParams};
use byteorder::{LittleEndian, WriteBytesExt}; use byteorder::{LittleEndian, WriteBytesExt};
use ff::PrimeField; use ff::PrimeField;
use group::{cofactor::CofactorGroup, GroupEncoding}; use group::{cofactor::CofactorGroup, GroupEncoding, WnafBase, WnafScalar};
use jubjub::{AffinePoint, ExtendedPoint}; use jubjub::{AffinePoint, ExtendedPoint};
use rand_core::RngCore; use rand_core::RngCore;
@ -27,16 +27,39 @@ use crate::{
pub const KDF_SAPLING_PERSONALIZATION: &[u8; 16] = b"Zcash_SaplingKDF"; pub const KDF_SAPLING_PERSONALIZATION: &[u8; 16] = b"Zcash_SaplingKDF";
pub const PRF_OCK_PERSONALIZATION: &[u8; 16] = b"Zcash_Derive_ock"; pub const PRF_OCK_PERSONALIZATION: &[u8; 16] = b"Zcash_Derive_ock";
const PREPARED_WINDOW_SIZE: usize = 4;
type PreparedBase = WnafBase<jubjub::ExtendedPoint, PREPARED_WINDOW_SIZE>;
type PreparedBaseSubgroup = WnafBase<jubjub::SubgroupPoint, PREPARED_WINDOW_SIZE>;
type PreparedScalar = WnafScalar<jubjub::Scalar, PREPARED_WINDOW_SIZE>;
/// A Sapling incoming viewing key that has been precomputed for trial decryption.
#[derive(Clone, Debug)]
pub struct PreparedIncomingViewingKey(PreparedScalar);
impl PreparedIncomingViewingKey {
/// Performs the necessary precomputations to use a `SaplingIvk` for note decryption.
pub fn new(ivk: &SaplingIvk) -> Self {
Self(PreparedScalar::new(&ivk.0))
}
}
/// A Sapling ephemeral public key that has been precomputed for trial decryption.
#[derive(Clone, Debug)]
pub struct PreparedEphemeralPublicKey(PreparedBase);
/// Sapling key agreement for note encryption. /// Sapling key agreement for note encryption.
/// ///
/// Implements section 5.4.4.3 of the Zcash Protocol Specification. /// Implements section 5.4.4.3 of the Zcash Protocol Specification.
pub fn sapling_ka_agree(esk: &jubjub::Fr, pk_d: &jubjub::ExtendedPoint) -> jubjub::SubgroupPoint { pub fn sapling_ka_agree(esk: &jubjub::Fr, pk_d: &jubjub::ExtendedPoint) -> jubjub::SubgroupPoint {
sapling_ka_agree_prepared(&PreparedScalar::new(esk), &PreparedBase::new(*pk_d))
}
fn sapling_ka_agree_prepared(esk: &PreparedScalar, pk_d: &PreparedBase) -> jubjub::SubgroupPoint {
// [8 esk] pk_d // [8 esk] pk_d
// <ExtendedPoint as CofactorGroup>::clear_cofactor is implemented using // <ExtendedPoint as CofactorGroup>::clear_cofactor is implemented using
// ExtendedPoint::mul_by_cofactor in the jubjub crate. // ExtendedPoint::mul_by_cofactor in the jubjub crate.
let mut wnaf = group::Wnaf::new(); (pk_d * esk).clear_cofactor()
wnaf.scalar(esk).base(*pk_d).clear_cofactor()
} }
/// Sapling KDF for note encryption. /// Sapling KDF for note encryption.
@ -132,12 +155,13 @@ impl<P: consensus::Parameters> Domain for SaplingDomain<P> {
// points must not be small-order, and all points with non-canonical serialization // points must not be small-order, and all points with non-canonical serialization
// are small-order. // are small-order.
type EphemeralPublicKey = jubjub::ExtendedPoint; type EphemeralPublicKey = jubjub::ExtendedPoint;
type PreparedEphemeralPublicKey = PreparedEphemeralPublicKey;
type SharedSecret = jubjub::SubgroupPoint; type SharedSecret = jubjub::SubgroupPoint;
type SymmetricKey = Blake2bHash; type SymmetricKey = Blake2bHash;
type Note = Note; type Note = Note;
type Recipient = PaymentAddress; type Recipient = PaymentAddress;
type DiversifiedTransmissionKey = jubjub::SubgroupPoint; type DiversifiedTransmissionKey = jubjub::SubgroupPoint;
type IncomingViewingKey = SaplingIvk; type IncomingViewingKey = PreparedIncomingViewingKey;
type OutgoingViewingKey = OutgoingViewingKey; type OutgoingViewingKey = OutgoingViewingKey;
type ValueCommitment = jubjub::ExtendedPoint; type ValueCommitment = jubjub::ExtendedPoint;
type ExtractedCommitment = bls12_381::Scalar; type ExtractedCommitment = bls12_381::Scalar;
@ -152,6 +176,10 @@ impl<P: consensus::Parameters> Domain for SaplingDomain<P> {
note.pk_d note.pk_d
} }
fn prepare_epk(epk: Self::EphemeralPublicKey) -> Self::PreparedEphemeralPublicKey {
PreparedEphemeralPublicKey(PreparedBase::new(epk))
}
fn ka_derive_public( fn ka_derive_public(
note: &Self::Note, note: &Self::Note,
esk: &Self::EphemeralSecretKey, esk: &Self::EphemeralSecretKey,
@ -173,9 +201,9 @@ impl<P: consensus::Parameters> Domain for SaplingDomain<P> {
fn ka_agree_dec( fn ka_agree_dec(
ivk: &Self::IncomingViewingKey, ivk: &Self::IncomingViewingKey,
epk: &Self::EphemeralPublicKey, epk: &Self::PreparedEphemeralPublicKey,
) -> Self::SharedSecret { ) -> Self::SharedSecret {
sapling_ka_agree(&ivk.0, epk) sapling_ka_agree_prepared(&ivk.0, &epk.0)
} }
/// Sapling KDF for note encryption. /// Sapling KDF for note encryption.
@ -253,7 +281,7 @@ impl<P: consensus::Parameters> Domain for SaplingDomain<P> {
plaintext: &[u8], plaintext: &[u8],
) -> Option<(Self::Note, Self::Recipient)> { ) -> Option<(Self::Note, Self::Recipient)> {
sapling_parse_note_plaintext_without_memo(self, plaintext, |diversifier| { sapling_parse_note_plaintext_without_memo(self, plaintext, |diversifier| {
Some(diversifier.g_d()? * ivk.0) Some(&PreparedBaseSubgroup::new(diversifier.g_d()?) * &ivk.0)
}) })
} }
@ -332,13 +360,18 @@ impl<P: consensus::Parameters> BatchDomain for SaplingDomain<P> {
fn batch_epk( fn batch_epk(
ephemeral_keys: impl Iterator<Item = EphemeralKeyBytes>, ephemeral_keys: impl Iterator<Item = EphemeralKeyBytes>,
) -> Vec<(Option<Self::EphemeralPublicKey>, EphemeralKeyBytes)> { ) -> Vec<(Option<Self::PreparedEphemeralPublicKey>, EphemeralKeyBytes)> {
let ephemeral_keys: Vec<_> = ephemeral_keys.collect(); let ephemeral_keys: Vec<_> = ephemeral_keys.collect();
let epks = jubjub::AffinePoint::batch_from_bytes(ephemeral_keys.iter().map(|b| b.0)); let epks = jubjub::AffinePoint::batch_from_bytes(ephemeral_keys.iter().map(|b| b.0));
epks.into_iter() epks.into_iter()
.zip(ephemeral_keys.into_iter()) .zip(ephemeral_keys.into_iter())
.map(|(epk, ephemeral_key)| { .map(|(epk, ephemeral_key)| {
(epk.map(jubjub::ExtendedPoint::from).into(), ephemeral_key) (
epk.map(jubjub::ExtendedPoint::from)
.map(Self::prepare_epk)
.into(),
ephemeral_key,
)
}) })
.collect() .collect()
} }
@ -391,7 +424,7 @@ pub fn try_sapling_note_decryption<
>( >(
params: &P, params: &P,
height: BlockHeight, height: BlockHeight,
ivk: &SaplingIvk, ivk: &PreparedIncomingViewingKey,
output: &Output, output: &Output,
) -> Option<(Note, PaymentAddress, MemoBytes)> { ) -> Option<(Note, PaymentAddress, MemoBytes)> {
let domain = SaplingDomain { let domain = SaplingDomain {
@ -407,7 +440,7 @@ pub fn try_sapling_compact_note_decryption<
>( >(
params: &P, params: &P,
height: BlockHeight, height: BlockHeight,
ivk: &SaplingIvk, ivk: &PreparedIncomingViewingKey,
output: &Output, output: &Output,
) -> Option<(Note, PaymentAddress)> { ) -> Option<(Note, PaymentAddress)> {
let domain = SaplingDomain { let domain = SaplingDomain {
@ -493,7 +526,7 @@ mod tests {
}, },
keys::OutgoingViewingKey, keys::OutgoingViewingKey,
memo::MemoBytes, memo::MemoBytes,
sapling::util::generate_random_rseed, sapling::{note_encryption::PreparedIncomingViewingKey, util::generate_random_rseed},
sapling::{Diversifier, PaymentAddress, Rseed, SaplingIvk, ValueCommitment}, sapling::{Diversifier, PaymentAddress, Rseed, SaplingIvk, ValueCommitment},
transaction::components::{ transaction::components::{
amount::Amount, amount::Amount,
@ -508,18 +541,21 @@ mod tests {
) -> ( ) -> (
OutgoingViewingKey, OutgoingViewingKey,
OutgoingCipherKey, OutgoingCipherKey,
SaplingIvk, PreparedIncomingViewingKey,
OutputDescription<sapling::GrothProofBytes>, OutputDescription<sapling::GrothProofBytes>,
) { ) {
let ivk = SaplingIvk(jubjub::Fr::random(&mut rng)); let ivk = SaplingIvk(jubjub::Fr::random(&mut rng));
let prepared_ivk = PreparedIncomingViewingKey::new(&ivk);
let (ovk, ock, output) = random_enc_ciphertext_with(height, &ivk, rng); let (ovk, ock, output) = random_enc_ciphertext_with(height, &ivk, rng);
assert!(try_sapling_note_decryption(&TEST_NETWORK, height, &ivk, &output).is_some()); assert!(
try_sapling_note_decryption(&TEST_NETWORK, height, &prepared_ivk, &output).is_some()
);
assert!(try_sapling_compact_note_decryption( assert!(try_sapling_compact_note_decryption(
&TEST_NETWORK, &TEST_NETWORK,
height, height,
&ivk, &prepared_ivk,
&CompactOutputDescription::from(output.clone()), &CompactOutputDescription::from(output.clone()),
) )
.is_some()); .is_some());
@ -532,7 +568,7 @@ mod tests {
assert!(ock_output_recovery.is_some()); assert!(ock_output_recovery.is_some());
assert_eq!(ovk_output_recovery, ock_output_recovery); assert_eq!(ovk_output_recovery, ock_output_recovery);
(ovk, ock, ivk, output) (ovk, ock, prepared_ivk, output)
} }
fn random_enc_ciphertext_with<R: RngCore + CryptoRng>( fn random_enc_ciphertext_with<R: RngCore + CryptoRng>(
@ -685,7 +721,7 @@ mod tests {
try_sapling_note_decryption( try_sapling_note_decryption(
&TEST_NETWORK, &TEST_NETWORK,
height, height,
&SaplingIvk(jubjub::Fr::random(&mut rng)), &PreparedIncomingViewingKey::new(&SaplingIvk(jubjub::Fr::random(&mut rng))),
&output &output
), ),
None None
@ -851,7 +887,7 @@ mod tests {
try_sapling_compact_note_decryption( try_sapling_compact_note_decryption(
&TEST_NETWORK, &TEST_NETWORK,
height, height,
&SaplingIvk(jubjub::Fr::random(&mut rng)), &PreparedIncomingViewingKey::new(&SaplingIvk(jubjub::Fr::random(&mut rng))),
&CompactOutputDescription::from(output) &CompactOutputDescription::from(output)
), ),
None None
@ -1309,7 +1345,7 @@ mod tests {
// Load the test vector components // Load the test vector components
// //
let ivk = SaplingIvk(read_jubjub_scalar!(tv.ivk)); let ivk = PreparedIncomingViewingKey::new(&SaplingIvk(read_jubjub_scalar!(tv.ivk)));
let pk_d = read_point!(tv.default_pk_d).into_subgroup().unwrap(); let pk_d = read_point!(tv.default_pk_d).into_subgroup().unwrap();
let rcm = read_jubjub_scalar!(tv.rcm); let rcm = read_jubjub_scalar!(tv.rcm);
let cv = read_point!(tv.cv); let cv = read_point!(tv.cv);
@ -1439,7 +1475,7 @@ mod tests {
let height = TEST_NETWORK.activation_height(Canopy).unwrap(); let height = TEST_NETWORK.activation_height(Canopy).unwrap();
// Test batch trial-decryption with multiple IVKs and outputs. // Test batch trial-decryption with multiple IVKs and outputs.
let invalid_ivk = SaplingIvk(jubjub::Fr::random(rng)); let invalid_ivk = PreparedIncomingViewingKey::new(&SaplingIvk(jubjub::Fr::random(rng)));
let valid_ivk = SaplingIvk(jubjub::Fr::random(rng)); let valid_ivk = SaplingIvk(jubjub::Fr::random(rng));
let outputs: Vec<_> = (0..10) let outputs: Vec<_> = (0..10)
.map(|_| { .map(|_| {
@ -1449,6 +1485,7 @@ mod tests {
) )
}) })
.collect(); .collect();
let valid_ivk = PreparedIncomingViewingKey::new(&valid_ivk);
// Check that batched trial decryptions with invalid_ivk fails. // Check that batched trial decryptions with invalid_ivk fails.
let res = batch::try_note_decryption(&[invalid_ivk.clone()], &outputs); let res = batch::try_note_decryption(&[invalid_ivk.clone()], &outputs);