From e01db53ecd9b6c85b6fa7f34c634735bfdad6d7d Mon Sep 17 00:00:00 2001 From: Lukas Korba Date: Sat, 11 Jul 2026 09:41:53 +0200 Subject: [PATCH 1/2] Add BatchDomain::batch_ka_agree_dec, a per-viewing-key batched agreement hook Trial decryption computes ka_agree_dec for many ephemeral keys against the same incoming viewing key. For domains where same-scalar multiplications can share work (for example, lockstep ladders over a shared batched field inversion), this dominates the cost of batched trial decryption, but the per-item Domain::ka_agree_dec interface gives implementations no opportunity to exploit it. This adds a provided BatchDomain method that computes the agreements for a whole batch against one viewing key, defaulting to the per-item computation, and routes the batch module's shared-secret derivation through it (one call per viewing key, preserving the output-major order the downstream batch KDF expects). Existing BatchDomain implementations are unaffected. Co-Authored-By: Claude Fable 5 --- CHANGELOG.md | 11 +++++++++++ src/batch.rs | 38 +++++++++++++++++++++++++++++--------- src/lib.rs | 23 +++++++++++++++++++++++ 3 files changed, 63 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9af97c0..8b2b163 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,17 @@ and this library adheres to Rust's notion of ## [Unreleased] +### Added +- A blanket `impl ShieldedOutput for &O` + where `D: Domain, O: ShieldedOutput` +- `BatchDomain::batch_ka_agree_dec`, a provided method that computes + `Domain::ka_agree_dec` for a batch of prepared ephemeral keys against a single + incoming viewing key. Domains for which same-scalar multiplications can share + work can override it; the default implementation (and therefore the behavior + of existing `BatchDomain` implementations) is the per-item computation. The + batch decryption functions in the `batch` module now derive their shared + secrets through this method, one call per viewing key. + ## [0.4.1] - 2024-12-06 ### Added - `zcash_note_encryption::try_output_recovery_with_pkd_esk` diff --git a/src/batch.rs b/src/batch.rs index 59577b5..84a236e 100644 --- a/src/batch.rs +++ b/src/batch.rs @@ -54,16 +54,36 @@ where // 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). - // The scalar multiplications cannot benefit from batching. - let items = ephemeral_keys.iter().flat_map(|(epk, ephemeral_key)| { - ivks.iter().map(move |ivk| { - ( - epk.as_ref().map(|epk| D::ka_agree_dec(ivk, epk)), - ephemeral_key, - ) + // Derive the shared secrets for all combinations of (ivk, output), one batched + // same-key agreement per ivk: domains for which same-scalar multiplications can + // share work accelerate here, and the default `batch_ka_agree_dec` implementation + // is exactly the previous per-item computation. + // Reassembly below is in the (output-major, ivk-minor) order the batch-KDF + // expects, moving values out of the per-ivk columns (`SharedSecret` need not be + // `Clone`). + let mut columns: Vec<_> = ivks + .iter() + .map(|ivk| { + D::batch_ka_agree_dec(ivk, ephemeral_keys.iter().map(|(epk, _)| epk.as_ref())) + .into_iter() }) - }); + .collect(); + let mut secrets: Vec> = + Vec::with_capacity(ephemeral_keys.len() * ivks.len()); + for _ in 0..ephemeral_keys.len() { + for column in columns.iter_mut() { + secrets.push( + column + .next() + .expect("all columns have one entry per output"), + ); + } + } + let items = secrets.into_iter().zip( + ephemeral_keys + .iter() + .flat_map(|(_, ephemeral_key)| core::iter::repeat(ephemeral_key).take(ivks.len())), + ); // Run the batch-KDF to obtain the symmetric keys from the shared secrets. let keys = D::batch_kdf(items); diff --git a/src/lib.rs b/src/lib.rs index d5b1274..fa07947 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -323,6 +323,29 @@ pub trait BatchDomain: Domain { }) .collect() } + + /// Computes `Self::ka_agree_dec` on a batch of prepared ephemeral keys against a + /// single incoming viewing key. + /// + /// For each item, if the prepared ephemeral key is `None` (i.e. its encoding could + /// not be parsed), this returns `None` at that position. + /// + /// Trial decryption multiplies many ephemeral keys by the same viewing key, so + /// domains for which same-scalar multiplications can share work (for example, + /// lockstep ladders over a shared batched field inversion) can override this to + /// reduce the cost of the scalar multiplications, which dominate batched trial + /// decryption. The default implementation performs the per-item computation. + fn batch_ka_agree_dec<'a>( + ivk: &Self::IncomingViewingKey, + epks: impl Iterator>, + ) -> Vec> + where + Self::PreparedEphemeralPublicKey: 'a, + { + // Default implementation: do the non-batched thing. + epks.map(|epk| epk.map(|epk| Self::ka_agree_dec(ivk, epk))) + .collect() + } } /// Trait that provides access to the components of an encrypted transaction output. From a8c90d1ce3737cc5898e0bf232ea325a5a61ec9f Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Sat, 11 Jul 2026 04:36:19 -0600 Subject: [PATCH 2/2] Release zcash_note_encryption version 0.4.2 --- CHANGELOG.md | 2 +- Cargo.lock | 2 +- Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b2b163..a30a892 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this library adheres to Rust's notion of [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [Unreleased] +## [0.4.2] - 2026-07-11 ### Added - A blanket `impl ShieldedOutput for &O` diff --git a/Cargo.lock b/Cargo.lock index 0d41754..15cfcc1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -150,7 +150,7 @@ checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "zcash_note_encryption" -version = "0.4.1" +version = "0.4.2" dependencies = [ "chacha20", "chacha20poly1305", diff --git a/Cargo.toml b/Cargo.toml index fccff47..c17f225 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "zcash_note_encryption" description = "Note encryption for Zcash transactions" -version = "0.4.1" +version = "0.4.2" authors = [ "Jack Grigg ", "Kris Nuttycombe "