Merge pull request #362 from zcash/batch-scanner-improvements

Batch scanner improvements
This commit is contained in:
Kris Nuttycombe 2022-10-20 09:30:49 -06:00 committed by GitHub
commit d05b6cee9d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 148 additions and 37 deletions

View File

@ -11,12 +11,21 @@ and this project adheres to Rust's notion of
### Added ### Added
- `orchard::Proof::add_to_batch` - `orchard::Proof::add_to_batch`
- `orchard::address::Address::diversifier` - `orchard::address::Address::diversifier`
- `orchard::keys::Diversifier::from_bytes` - `orchard::keys:`:
- `Diversifier::from_bytes`
- `PreparedEphemeralPublicKey`
- `PreparedIncomingViewingKey`
- `orchard::note`: - `orchard::note`:
- `RandomSeed` - `RandomSeed`
- `Note::{from_parts, rseed}` - `Note::{from_parts, rseed}`
- `impl memuse::DynamicUsage for Nullifier`
- `orchard::note_encryption`:
- `impl memuse::DynamicUsage for OrchardDomain`
- `orchard::builder::SpendInfo::new` - `orchard::builder::SpendInfo::new`
- `orchard::circuit::Circuit::from_action_context` - `orchard::circuit::Circuit::from_action_context`
- impls of `Eq` for:
- `orchard::zip32::ChildIndex`
- `orchard::value::ValueSum`
### Changed ### Changed
- Migrated to `zcash_note_encryption 0.2`. - Migrated to `zcash_note_encryption 0.2`.

View File

@ -28,12 +28,12 @@ bitvec = "1"
blake2b_simd = "1" blake2b_simd = "1"
ff = "0.12" ff = "0.12"
fpe = "0.5" fpe = "0.5"
group = "0.12" group = { version = "0.12.1", features = ["wnaf-memuse"] }
halo2_gadgets = "0.2" halo2_gadgets = "0.2"
halo2_proofs = "0.2" halo2_proofs = "0.2"
hex = "0.4" hex = "0.4"
lazy_static = "1" lazy_static = "1"
memuse = { version = "0.2", features = ["nonempty"] } memuse = { version = "0.2.1", features = ["nonempty"] }
pasta_curves = "0.4" pasta_curves = "0.4"
proptest = { version = "1.0.0", optional = true } proptest = { version = "1.0.0", optional = true }
rand = "0.8" rand = "0.8"

View File

@ -3,7 +3,7 @@ use orchard::{
builder::Builder, builder::Builder,
bundle::Flags, bundle::Flags,
circuit::ProvingKey, circuit::ProvingKey,
keys::{FullViewingKey, Scope, SpendingKey}, keys::{FullViewingKey, PreparedIncomingViewingKey, Scope, SpendingKey},
note_encryption::{CompactAction, OrchardDomain}, note_encryption::{CompactAction, OrchardDomain},
value::NoteValue, value::NoteValue,
Anchor, Bundle, Anchor, Bundle,
@ -21,6 +21,7 @@ fn bench_note_decryption(c: &mut Criterion) {
let fvk = FullViewingKey::from(&SpendingKey::from_bytes([7; 32]).unwrap()); let fvk = FullViewingKey::from(&SpendingKey::from_bytes([7; 32]).unwrap());
let valid_ivk = fvk.to_ivk(Scope::External); let valid_ivk = fvk.to_ivk(Scope::External);
let recipient = valid_ivk.address_at(0u32); let recipient = valid_ivk.address_at(0u32);
let valid_ivk = PreparedIncomingViewingKey::new(&valid_ivk);
// Compact actions don't have the full AEAD ciphertext, so ZIP 307 trial-decryption // Compact actions don't have the full AEAD ciphertext, so ZIP 307 trial-decryption
// relies on an invalid ivk resulting in random noise for which the note commitment // relies on an invalid ivk resulting in random noise for which the note commitment
@ -31,15 +32,15 @@ fn bench_note_decryption(c: &mut Criterion) {
// //
// Our fixed (action, invalid ivk) tuple will always fall into a specific rejection // Our fixed (action, invalid ivk) tuple will always fall into a specific rejection
// case. In order to reflect the real behaviour in the benchmarks, we trial-decrypt // case. In order to reflect the real behaviour in the benchmarks, we trial-decrypt
// with 1000 invalid ivks (each of which will result in a different uniformly-random // with 10240 invalid ivks (each of which will result in a different uniformly-random
// plaintext); this is equivalent to trial-decrypting 1000 different actions with the // plaintext); this is equivalent to trial-decrypting 10240 different actions with the
// same ivk, but is faster to set up. // same ivk, but is faster to set up.
let invalid_ivks: Vec<_> = (0u32..1000) let invalid_ivks: Vec<_> = (0u32..10240)
.map(|i| { .map(|i| {
let mut sk = [0; 32]; let mut sk = [0; 32];
sk[..4].copy_from_slice(&i.to_le_bytes()); sk[..4].copy_from_slice(&i.to_le_bytes());
let fvk = FullViewingKey::from(&SpendingKey::from_bytes(sk).unwrap()); let fvk = FullViewingKey::from(&SpendingKey::from_bytes(sk).unwrap());
fvk.to_ivk(Scope::External) PreparedIncomingViewingKey::new(&fvk.to_ivk(Scope::External))
}) })
.collect(); .collect();

View File

@ -17,7 +17,7 @@ use crate::{
address::Address, address::Address,
bundle::commitments::{hash_bundle_auth_data, hash_bundle_txid_data}, bundle::commitments::{hash_bundle_auth_data, hash_bundle_txid_data},
circuit::{Instance, Proof, VerifyingKey}, circuit::{Instance, Proof, VerifyingKey},
keys::{IncomingViewingKey, OutgoingViewingKey}, keys::{IncomingViewingKey, OutgoingViewingKey, PreparedIncomingViewingKey},
note::Note, note::Note,
note_encryption::OrchardDomain, note_encryption::OrchardDomain,
primitives::redpallas::{self, Binding, SpendAuth}, primitives::redpallas::{self, Binding, SpendAuth},
@ -284,14 +284,18 @@ impl<T: Authorization, V> Bundle<T, V> {
&self, &self,
keys: &[IncomingViewingKey], keys: &[IncomingViewingKey],
) -> Vec<(usize, IncomingViewingKey, Note, Address, [u8; 512])> { ) -> Vec<(usize, IncomingViewingKey, Note, Address, [u8; 512])> {
let prepared_keys: Vec<_> = keys
.iter()
.map(|ivk| (ivk, PreparedIncomingViewingKey::new(ivk)))
.collect();
self.actions self.actions
.iter() .iter()
.enumerate() .enumerate()
.filter_map(|(idx, action)| { .filter_map(|(idx, action)| {
let domain = OrchardDomain::for_action(action); let domain = OrchardDomain::for_action(action);
keys.iter().find_map(move |ivk| { prepared_keys.iter().find_map(|(ivk, prepared_ivk)| {
try_note_decryption(&domain, ivk, action) try_note_decryption(&domain, prepared_ivk, action)
.map(|(n, a, m)| (idx, ivk.clone(), n, a, m)) .map(|(n, a, m)| (idx, (*ivk).clone(), n, a, m))
}) })
}) })
.collect() .collect()
@ -305,9 +309,10 @@ impl<T: Authorization, V> Bundle<T, V> {
action_idx: usize, action_idx: usize,
key: &IncomingViewingKey, key: &IncomingViewingKey,
) -> Option<(Note, Address, [u8; 512])> { ) -> Option<(Note, Address, [u8; 512])> {
let prepared_ivk = PreparedIncomingViewingKey::new(key);
self.actions.get(action_idx).and_then(move |action| { self.actions.get(action_idx).and_then(move |action| {
let domain = OrchardDomain::for_action(action); let domain = OrchardDomain::for_action(action);
try_note_decryption(&domain, key, action) try_note_decryption(&domain, &prepared_ivk, action)
}) })
} }

View File

@ -20,8 +20,9 @@ use crate::{
address::Address, address::Address,
primitives::redpallas::{self, SpendAuth}, primitives::redpallas::{self, SpendAuth},
spec::{ spec::{
commit_ivk, diversify_hash, extract_p, ka_orchard, prf_nf, to_base, to_scalar, commit_ivk, diversify_hash, extract_p, ka_orchard, ka_orchard_prepared, prf_nf, to_base,
NonIdentityPallasPoint, NonZeroPallasBase, NonZeroPallasScalar, PrfExpand, to_scalar, NonIdentityPallasPoint, NonZeroPallasBase, NonZeroPallasScalar,
PreparedNonIdentityBase, PreparedNonZeroScalar, PrfExpand,
}, },
zip32::{self, ChildIndex, ExtendedSpendingKey}, zip32::{self, ChildIndex, ExtendedSpendingKey},
}; };
@ -627,7 +628,8 @@ impl KeyAgreementPrivateKey {
/// Returns the payment address for this key corresponding to the given diversifier. /// Returns the payment address for this key corresponding to the given diversifier.
fn address(&self, d: Diversifier) -> Address { fn address(&self, d: Diversifier) -> Address {
let pk_d = DiversifiedTransmissionKey::derive_inner(self, &d); let prepared_ivk = PreparedIncomingViewingKey::new_inner(self);
let pk_d = DiversifiedTransmissionKey::derive(&prepared_ivk, &d);
Address::from_parts(d, pk_d) Address::from_parts(d, pk_d)
} }
} }
@ -704,6 +706,32 @@ impl IncomingViewingKey {
} }
} }
/// An Orchard incoming viewing key that has been precomputed for trial decryption.
#[derive(Clone, Debug)]
pub struct PreparedIncomingViewingKey(PreparedNonZeroScalar);
impl memuse::DynamicUsage for PreparedIncomingViewingKey {
fn dynamic_usage(&self) -> usize {
self.0.dynamic_usage()
}
fn dynamic_usage_bounds(&self) -> (usize, Option<usize>) {
self.0.dynamic_usage_bounds()
}
}
impl PreparedIncomingViewingKey {
/// Performs the necessary precomputations to use an `IncomingViewingKey` for note
/// decryption.
pub fn new(ivk: &IncomingViewingKey) -> Self {
Self::new_inner(&ivk.ivk)
}
fn new_inner(ivk: &KeyAgreementPrivateKey) -> Self {
Self(PreparedNonZeroScalar::new(&ivk.0))
}
}
/// A key that provides the capability to recover outgoing transaction information from /// A key that provides the capability to recover outgoing transaction information from
/// the block chain. /// the block chain.
/// ///
@ -753,13 +781,9 @@ impl DiversifiedTransmissionKey {
/// Defined in [Zcash Protocol Spec § 4.2.3: Orchard Key Components][orchardkeycomponents]. /// Defined in [Zcash Protocol Spec § 4.2.3: Orchard Key Components][orchardkeycomponents].
/// ///
/// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents /// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents
pub(crate) fn derive(ivk: &IncomingViewingKey, d: &Diversifier) -> Self { pub(crate) fn derive(ivk: &PreparedIncomingViewingKey, d: &Diversifier) -> Self {
Self::derive_inner(&ivk.ivk, d) let g_d = PreparedNonIdentityBase::new(diversify_hash(d.as_array()));
} DiversifiedTransmissionKey(ka_orchard_prepared(&ivk.0, &g_d))
fn derive_inner(ivk: &KeyAgreementPrivateKey, d: &Diversifier) -> Self {
let g_d = diversify_hash(d.as_array());
DiversifiedTransmissionKey(ka_orchard(&ivk.0, &g_d))
} }
/// $abst_P(bytes)$ /// $abst_P(bytes)$
@ -841,6 +865,20 @@ impl EphemeralPublicKey {
} }
} }
/// An Orchard ephemeral public key that has been precomputed for trial decryption.
#[derive(Clone, Debug)]
pub struct PreparedEphemeralPublicKey(PreparedNonIdentityBase);
impl PreparedEphemeralPublicKey {
pub(crate) fn new(epk: EphemeralPublicKey) -> Self {
PreparedEphemeralPublicKey(PreparedNonIdentityBase::new(epk.0))
}
pub(crate) fn agree(&self, ivk: &PreparedIncomingViewingKey) -> SharedSecret {
SharedSecret(ka_orchard_prepared(&ivk.0, &self.0))
}
}
/// $\mathsf{KA}^\mathsf{Orchard}.\mathsf{SharedSecret} := \mathbb{P}^{\ast}$ /// $\mathsf{KA}^\mathsf{Orchard}.\mathsf{SharedSecret} := \mathbb{P}^{\ast}$
/// ///
/// Defined in [section 5.4.5.5: Orchard Key Agreement][concreteorchardkeyagreement]. /// Defined in [section 5.4.5.5: Orchard Key Agreement][concreteorchardkeyagreement].

View File

@ -1,5 +1,6 @@
use group::{ff::PrimeField, Group}; use group::{ff::PrimeField, Group};
use halo2_proofs::arithmetic::CurveExt; use halo2_proofs::arithmetic::CurveExt;
use memuse::DynamicUsage;
use pasta_curves::pallas; use pasta_curves::pallas;
use rand::RngCore; use rand::RngCore;
use subtle::CtOption; use subtle::CtOption;
@ -14,6 +15,9 @@ use crate::{
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct Nullifier(pub(crate) pallas::Base); pub struct Nullifier(pub(crate) pallas::Base);
// We know that `pallas::Base` doesn't allocate internally.
memuse::impl_no_dynamic_usage!(Nullifier);
impl Nullifier { impl Nullifier {
/// Generates a dummy nullifier for use as $\rho$ in dummy spent notes. /// Generates a dummy nullifier for use as $\rho$ in dummy spent notes.
/// ///

View File

@ -14,7 +14,7 @@ use crate::{
action::Action, action::Action,
keys::{ keys::{
DiversifiedTransmissionKey, Diversifier, EphemeralPublicKey, EphemeralSecretKey, DiversifiedTransmissionKey, Diversifier, EphemeralPublicKey, EphemeralSecretKey,
IncomingViewingKey, OutgoingViewingKey, SharedSecret, OutgoingViewingKey, PreparedEphemeralPublicKey, PreparedIncomingViewingKey, SharedSecret,
}, },
note::{ExtractedNoteCommitment, Nullifier, RandomSeed}, note::{ExtractedNoteCommitment, Nullifier, RandomSeed},
spec::diversify_hash, spec::diversify_hash,
@ -85,6 +85,16 @@ pub struct OrchardDomain {
rho: Nullifier, rho: Nullifier,
} }
impl memuse::DynamicUsage for OrchardDomain {
fn dynamic_usage(&self) -> usize {
self.rho.dynamic_usage()
}
fn dynamic_usage_bounds(&self) -> (usize, Option<usize>) {
self.rho.dynamic_usage_bounds()
}
}
impl OrchardDomain { impl OrchardDomain {
/// Constructs a domain that can be used to trial-decrypt this action's output note. /// Constructs a domain that can be used to trial-decrypt this action's output note.
pub fn for_action<T>(act: &Action<T>) -> Self { pub fn for_action<T>(act: &Action<T>) -> Self {
@ -102,13 +112,13 @@ impl OrchardDomain {
impl Domain for OrchardDomain { impl Domain for OrchardDomain {
type EphemeralSecretKey = EphemeralSecretKey; type EphemeralSecretKey = EphemeralSecretKey;
type EphemeralPublicKey = EphemeralPublicKey; type EphemeralPublicKey = EphemeralPublicKey;
type PreparedEphemeralPublicKey = EphemeralPublicKey; type PreparedEphemeralPublicKey = PreparedEphemeralPublicKey;
type SharedSecret = SharedSecret; type SharedSecret = SharedSecret;
type SymmetricKey = Hash; type SymmetricKey = Hash;
type Note = Note; type Note = Note;
type Recipient = Address; type Recipient = Address;
type DiversifiedTransmissionKey = DiversifiedTransmissionKey; type DiversifiedTransmissionKey = DiversifiedTransmissionKey;
type IncomingViewingKey = IncomingViewingKey; type IncomingViewingKey = PreparedIncomingViewingKey;
type OutgoingViewingKey = OutgoingViewingKey; type OutgoingViewingKey = OutgoingViewingKey;
type ValueCommitment = ValueCommitment; type ValueCommitment = ValueCommitment;
type ExtractedCommitment = ExtractedNoteCommitment; type ExtractedCommitment = ExtractedNoteCommitment;
@ -124,7 +134,7 @@ impl Domain for OrchardDomain {
} }
fn prepare_epk(epk: Self::EphemeralPublicKey) -> Self::PreparedEphemeralPublicKey { fn prepare_epk(epk: Self::EphemeralPublicKey) -> Self::PreparedEphemeralPublicKey {
epk PreparedEphemeralPublicKey::new(epk)
} }
fn ka_derive_public( fn ka_derive_public(
@ -352,7 +362,7 @@ mod tests {
action::Action, action::Action,
keys::{ keys::{
DiversifiedTransmissionKey, Diversifier, EphemeralSecretKey, IncomingViewingKey, DiversifiedTransmissionKey, Diversifier, EphemeralSecretKey, IncomingViewingKey,
OutgoingViewingKey, OutgoingViewingKey, PreparedIncomingViewingKey,
}, },
note::{ExtractedNoteCommitment, Nullifier, RandomSeed, TransmittedNoteCiphertext}, note::{ExtractedNoteCommitment, Nullifier, RandomSeed, TransmittedNoteCiphertext},
primitives::redpallas, primitives::redpallas,
@ -370,7 +380,9 @@ mod tests {
// //
// Recipient key material // Recipient key material
let ivk = IncomingViewingKey::from_bytes(&tv.incoming_viewing_key).unwrap(); let ivk = PreparedIncomingViewingKey::new(
&IncomingViewingKey::from_bytes(&tv.incoming_viewing_key).unwrap(),
);
let ovk = OutgoingViewingKey::from(tv.ovk); let ovk = OutgoingViewingKey::from(tv.ovk);
let d = Diversifier::from_bytes(tv.default_d); let d = Diversifier::from_bytes(tv.default_d);
let pk_d = DiversifiedTransmissionKey::from_bytes(&tv.default_pk_d).unwrap(); let pk_d = DiversifiedTransmissionKey::from_bytes(&tv.default_pk_d).unwrap();

View File

@ -4,10 +4,10 @@ use core::iter;
use core::ops::Deref; use core::ops::Deref;
use ff::{Field, PrimeField, PrimeFieldBits}; use ff::{Field, PrimeField, PrimeFieldBits};
use group::GroupEncoding; use group::{Curve, Group, GroupEncoding, WnafBase, WnafScalar};
use group::{Curve, Group};
use halo2_gadgets::{poseidon::primitives as poseidon, sinsemilla::primitives as sinsemilla}; use halo2_gadgets::{poseidon::primitives as poseidon, sinsemilla::primitives as sinsemilla};
use halo2_proofs::arithmetic::{CurveAffine, CurveExt, FieldExt}; use halo2_proofs::arithmetic::{CurveAffine, CurveExt, FieldExt};
use memuse::DynamicUsage;
use pasta_curves::pallas; use pasta_curves::pallas;
use subtle::{ConditionallySelectable, CtOption}; use subtle::{ConditionallySelectable, CtOption};
@ -140,6 +140,36 @@ impl Deref for NonZeroPallasScalar {
} }
} }
const PREPARED_WINDOW_SIZE: usize = 4;
#[derive(Clone, Debug)]
pub(crate) struct PreparedNonIdentityBase(WnafBase<pallas::Point, PREPARED_WINDOW_SIZE>);
impl PreparedNonIdentityBase {
pub(crate) fn new(base: NonIdentityPallasPoint) -> Self {
PreparedNonIdentityBase(WnafBase::new(base.0))
}
}
#[derive(Clone, Debug)]
pub(crate) struct PreparedNonZeroScalar(WnafScalar<pallas::Scalar, PREPARED_WINDOW_SIZE>);
impl DynamicUsage for PreparedNonZeroScalar {
fn dynamic_usage(&self) -> usize {
self.0.dynamic_usage()
}
fn dynamic_usage_bounds(&self) -> (usize, Option<usize>) {
self.0.dynamic_usage_bounds()
}
}
impl PreparedNonZeroScalar {
pub(crate) fn new(scalar: &NonZeroPallasScalar) -> Self {
PreparedNonZeroScalar(WnafScalar::new(scalar))
}
}
/// $\mathsf{ToBase}^\mathsf{Orchard}(x) := LEOS2IP_{\ell_\mathsf{PRFexpand}}(x) (mod q_P)$ /// $\mathsf{ToBase}^\mathsf{Orchard}(x) := LEOS2IP_{\ell_\mathsf{PRFexpand}}(x) (mod q_P)$
/// ///
/// Defined in [Zcash Protocol Spec § 4.2.3: Orchard Key Components][orchardkeycomponents]. /// Defined in [Zcash Protocol Spec § 4.2.3: Orchard Key Components][orchardkeycomponents].
@ -213,8 +243,20 @@ pub(crate) fn ka_orchard(
sk: &NonZeroPallasScalar, sk: &NonZeroPallasScalar,
b: &NonIdentityPallasPoint, b: &NonIdentityPallasPoint,
) -> NonIdentityPallasPoint { ) -> NonIdentityPallasPoint {
let mut wnaf = group::Wnaf::new(); ka_orchard_prepared(
NonIdentityPallasPoint(wnaf.scalar(sk.deref()).base(*b.deref())) &PreparedNonZeroScalar::new(sk),
&PreparedNonIdentityBase::new(*b),
)
}
/// Defined in [Zcash Protocol Spec § 5.4.5.5: Orchard Key Agreement][concreteorchardkeyagreement].
///
/// [concreteorchardkeyagreement]: https://zips.z.cash/protocol/nu5.pdf#concreteorchardkeyagreement
pub(crate) fn ka_orchard_prepared(
sk: &PreparedNonZeroScalar,
b: &PreparedNonIdentityBase,
) -> NonIdentityPallasPoint {
NonIdentityPallasPoint(&b.0 * &sk.0)
} }
/// Coordinate extractor for Pallas. /// Coordinate extractor for Pallas.

View File

@ -144,7 +144,7 @@ pub(crate) enum Sign {
} }
/// A sum of Orchard note values. /// A sum of Orchard note values.
#[derive(Clone, Copy, Debug, Default, PartialEq)] #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub struct ValueSum(i128); pub struct ValueSum(i128);
impl ValueSum { impl ValueSum {

View File

@ -65,7 +65,7 @@ impl FvkTag {
} }
/// A hardened child index for a derived key. /// A hardened child index for a derived key.
#[derive(Copy, Clone, Debug, PartialEq)] #[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct ChildIndex(u32); pub struct ChildIndex(u32);
impl TryFrom<u32> for ChildIndex { impl TryFrom<u32> for ChildIndex {

View File

@ -3,7 +3,7 @@ use orchard::{
builder::Builder, builder::Builder,
bundle::{Authorized, Flags}, bundle::{Authorized, Flags},
circuit::{ProvingKey, VerifyingKey}, circuit::{ProvingKey, VerifyingKey},
keys::{FullViewingKey, Scope, SpendAuthorizingKey, SpendingKey}, keys::{FullViewingKey, PreparedIncomingViewingKey, Scope, SpendAuthorizingKey, SpendingKey},
note::ExtractedNoteCommitment, note::ExtractedNoteCommitment,
note_encryption::OrchardDomain, note_encryption::OrchardDomain,
tree::{MerkleHashOrchard, MerklePath}, tree::{MerkleHashOrchard, MerklePath},
@ -57,7 +57,7 @@ fn bundle_chain() {
// Create a shielded bundle spending the previous output. // Create a shielded bundle spending the previous output.
let shielded_bundle: Bundle<_, i64> = { let shielded_bundle: Bundle<_, i64> = {
let ivk = fvk.to_ivk(Scope::External); let ivk = PreparedIncomingViewingKey::new(&fvk.to_ivk(Scope::External));
let (note, _, _) = shielding_bundle let (note, _, _) = shielding_bundle
.actions() .actions()
.iter() .iter()