Skip to content

primitives: the NEON Keccak absorbs and stores digests through ZIP1/ZIP2 - #653

Merged
TomWambsgans merged 1 commit into
leanEthereum:mainfrom
bithose:neon-keccak-zip
Nov 2, 2026
Merged

TomWambsgans merged 1 commit into
leanEthereum:mainfrom
bithose:neon-keccak-zip

Conversation

@bithose

@bithose bithose commented Nov 2, 2026

Copy link
Copy Markdown
Contributor

Summary

The aarch64 batched Keccak (hash_many, used by the PCS Merkle leaves and nodes and by lean_da) now absorbs input blocks and stores digests with NEON loads, ZIP1/ZIP2 and NEON stores. Before, both went through the default Lanes64 forms, which read every word with a scalar load into a staging buffer, reload it as a vector, and do the reverse for the digests. The permutation is unchanged, and so is every digest and proof.

Mechanism

  • absorb_words: for the two inputs of a NEON group, one 16-byte load per input yields two words of each. ZIP1 of the two loads is lane 2p (word 2p of input 0, word 2p of input 1), ZIP2 is lane 2p + 1. An odd last word is combined from two scalar words. The partial word and padding stay in hash_group as before.
  • store_digests: ZIP1/ZIP2 of digest lanes 0,1 and 2,3 give 16 consecutive bytes of each input's digest, written with four 16-byte stores.
  • These are overrides in the aarch64 arm module, with the same shape as the AVX-512 transposes on gattaca-com/leanVM keccak-opt (c7d959e), so a later port of that file merges without conflicts here.
  • With the faster absorb, hashing 64-byte inputs runs as fast as the bare 2-way permutation (about 13 M/s on one M1 performance core). Before, it was about 20% slower.

Numbers

Apple M1 (Mac mini, 4P + 4E, 16 GB). Base (origin/main 1acdff1) and patched binaries were run interleaved, A/B in odd rounds and B/A in even rounds, 10 rounds each, with no cooldown between passes. Prove times are 5 passes per round (n = 50 per side), and trace spans come from the last pass of each round (n = 10). The paired statistic is the per-round difference of medians, with two-sided exact sign and Wilcoxon signed-rank tests.

metric base median [IQR] patched median [IQR] paired median diff rounds patched better p (sign / Wilcoxon)
recursion 2 to 1, 900 per leaf, rate 1/4 0.9245 s [0.922, 0.930] 0.8975 s [0.895, 0.903] -28.0 ms (-3.0%) 10/10 0.002 / 0.002
base encode span (log 21, 40 lanes) 300.5 ms [299, 301] 284.5 ms [284, 286] -15.5 ms (-5.2%) 10/10 0.002 / 0.002
extension encode spans, sum of four 250.9 ms [249.7, 251.9] 240.7 ms [238.3, 241.8] -10.2 ms (-4.1%) 10/10 0.002 / 0.002
aggregate 256 leanXMSS, rate 1/2 0.817 s [0.815, 0.820] 0.802 s [0.801, 0.804] -14.5 ms (-1.8%) 10/10 0.002 / 0.002
hash_throughput 512 B, 7 threads 14.75 Mhash/s [14.67, 14.77] 15.73 [15.71, 15.76] +6.8% 10/10 0.002 / 0.002
hash_throughput 512 B, 1 thread 3.07 [3.07, 3.08] 3.29 [3.29, 3.30] +7.2% 10/10 0.002 / 0.002
hash_throughput 64 B, 7 threads 51.99 [51.72, 52.01] 62.30 [62.09, 62.46] +19.8% 10/10 0.002 / 0.002
hash_throughput 64 B, 1 thread 10.71 [10.70, 10.71] 13.06 [13.05, 13.06] +22.0% 10/10 0.002 / 0.002

Per extension encode: 2^20 149 to 144.5 ms, 2^19 64.3 to 60.6 ms, 2^18 26.4 to 24.9 ms, 2^17 11.7 to 10.8 ms (paired medians -3.4% to -7.7%).

Measured on Apple M1 only; M4 not measured. Linux arm64 (the CI neoverse-n2 runner) and x86 are not measured. x86 code is unchanged.

Correctness and byte-identity

  • The existing every_backend_matches_scalar and batched_matches_one_shot_across_lengths tests check the NEON backend against the scalar hash and permutation. Their lengths have odd and even word counts, partial words, multi-block inputs and the zero-prefix state. No test is added.
  • cargo clippyall, cargo fmt --all --check, and cargo test --release -p primitives -p pcs pass.
  • Proofs are byte-identical: I compared Keccak-256 of EthereumProof::to_bytes() from base and patched binaries, using a throwaway CLI hook that is not part of this PR.
command threads proof Keccak-256 (base = patched)
aggregate --xmss 2 --log-inv-rate 1 4, 8 d98f9495...0a0f9f81
aggregate --xmss 8 --log-inv-rate 1 4, 8 ec9edf77...5cb0d149
aggregate --xmss 2 --log-inv-rate 2 4, 8 73b04f64...8ba7cb50
aggregate --xmss 8 --log-inv-rate 2 4, 8 66eca954...a2636ac7
recursion --n 2 --xmss-per-leaf 64 --log-inv-rate 2 4, 8 850320ff...2ee2a331

@TomWambsgans
TomWambsgans merged commit 575df81 into leanEthereum:main Nov 2, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants