Speed up non-circuit Pedersen hash ~3x via fused precomputation (returns ExtendedPoint)#1
Speed up non-circuit Pedersen hash ~3x via fused precomputation (returns ExtendedPoint)#1p0mvn wants to merge 5 commits into
Conversation
Replace the per-segment "accumulate into an Fr scalar, then 8-bit-window
fixed-base multiply" approach with precomputed tables indexed directly by
the raw input bits. Each segment hash is the linear combination
`sum_j enc(chunk_j) * 2^{4j} * G`; precomputing the scaled points and
folding PEDERSEN_HASH_CHUNKS_PER_BLOCK (C=4) consecutive chunks into one
lookup eliminates the Fr accumulation entirely and roughly halves the
point additions.
Measured ~2.4x faster on a 510-bit Merkle hash (20.8us -> 8.8us) at ~60 MB
of lazily-built tables. C is a one-line tunable constant (C=3 ~1.7x/~11 MB,
C=5 ~2.9x/~380 MB).
Output is bit-for-bit identical (consensus-critical): generators are
unchanged, the existing Zcash test vectors pass, and a new equivalence test
checks against a reference implementation across chunk/block/generator
boundaries. The circuit hasher is untouched; the old exp-table constants
remain exported to avoid a breaking API change.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Store the precomputed Pedersen hash tables in jubjub's precomputed-addition form (AffineNielsPoint, 96 bytes vs 160) and accumulate into an ExtendedPoint via mixed additions. This is both smaller and lower-latency on the accumulator dependency chain. Using mixed addition requires an ExtendedPoint accumulator, and there is no cheap ExtendedPoint -> SubgroupPoint conversion (only via a field inversion), so `pedersen_hash` now returns `jubjub::ExtendedPoint` (breaking). Caller impact is small: tree.rs and the circuit witness/test sites already wrapped the result in `ExtendedPoint::from(...)` (now identity, removed), and `windowed_pedersen_commit` re-wraps to SubgroupPoint with one affine conversion off the hot path. Tables built with a batched inversion (jubjub::batch_normalize) to keep lazy init cheap. Output remains bit-for-bit identical (consensus vectors + the boundary equivalence test pass). With this, the default drops to C=3: ~3.0x faster than the original 8-bit exp-window (raw 510-bit hash, 20.8us -> 6.9us) at ~7 MB, roughly the original table footprint. C=4 gives 3.5x/~36 MB, C=5 gives 4.3x/~227 MB. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
PEDERSEN_HASH_EXP_TABLE, PEDERSEN_HASH_EXP_WINDOW_SIZE, and generate_pedersen_hash_exp_table are no longer used now that pedersen_hash uses the fused AffineNiels tables. The PR is already a breaking API change, so drop them rather than leave dead public items. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Record the pedersen_hash return-type change, the new tunable constant and tables, and the removed exp-window constants under [Unreleased]. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Use C=2 as the default operating point: ~2x faster than the previous exp-window implementation at the smallest memory footprint (~1.4 MB, below the original exp table). Higher C remains a one-line tradeoff for more speed. Output is unchanged (consensus vectors + boundary equivalence test pass). Docs and CHANGELOG updated to match. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
@v12-auditor review |
Analyzed seven files, diff |
Summary
Optimizes the non-circuit Sapling Pedersen hash (
pedersen_hash), which backsNode::combine/merkle_hashand the note-commitment hash. ~3× faster on a raw 510-bit hash (20.8 µs → 6.9 µs), bit-for-bit identical output.Two ideas:
Σ enc(chunk_j)·2^{4j}·G, precompute the scaled points, and foldC = PEDERSEN_HASH_CHUNKS_PER_BLOCK(default 3) consecutive chunks into one table lookup. Eliminates the per-chunkFrscalar accumulation and cuts point additions.AffineNielsPoint, 96 B vs 160 B) and accumulate via fast, low-latency mixed additions.pedersen_hash::pedersen_hashnow returnsjubjub::ExtendedPoint(wasSubgroupPoint). Mixed addition needs anExtendedPointaccumulator, and there is no cheapExtendedPoint → SubgroupPointconversion (only a field inversion), so returningExtendedPointavoids an inversion on every hash. The result is still in the prime-order subgroup.constants::PEDERSEN_HASH_EXP_TABLEandPEDERSEN_HASH_EXP_WINDOW_SIZE(now unused).constants::PEDERSEN_HASH_CHUNKS_PER_BLOCK,PEDERSEN_HASH_SINGLE_TABLE,PEDERSEN_HASH_BLOCK_TABLE.Requires a minor version bump (0.8.0); documented under
CHANGELOG.md[Unreleased].Migration
tree.rsand the circuit witness/test sites already wrapped inExtendedPoint::from(...)(now identity, removed);spec.rs::windowed_pedersen_commitre-wraps toSubgroupPointwith one affine conversion (cold path), so its signature and everything downstream are unchanged.SubgroupPointcan re-derive viato_bytes/from_bytes, or work with theExtendedPointdirectly (most call sites already convert to affine to extract a coordinate).Tradeoff (one-line tunable constant)
CC=3 gives ~3× at roughly the original exp-table's footprint. Tables built lazily with a batched inversion (
jubjub::batch_normalize), so init stays cheap at the default.Correctness
Output is bit-for-bit identical — consensus-critical; generators unchanged.
test_pedersen_hash_points) pass.matches_reference_across_boundariescompares against a reference impl across input lengths straddling chunk / block / generator boundaries (incl. the 6-bit personalization shift) up to capacity.⌈len/3⌉chunks processed; the zero-padded final chunk is folded by zero-filling, chunks beyond the message are never added.Production readiness review
no_stdverified (cargo build --no-default-featurescompiles; onlycore/alloc/jubjubused).clippy --all-targets,cargo fmt --check, andcargo doc(intra-doc links) all clean.statictables are thread-safe; build cost negligible at C=3 (heavier at C=5, documented).pubto match the module's existing convention (PEDERSEN_HASH_GENERATORS, etc.); they're implementation details and could be tightened topub(crate)if preferred.Considered and rejected
[−1], and the hash is now a sum of precomputed points, not a scalar mult.Notes
merkle-hashcriterion bench (fullNode::combinepath). Design write-up indocs/pedersen-hash-fused-precompute.md.🤖 Generated with Claude Code