Skip to content

perf(crypto): run the Silver-style verification on silver_crypto_simd - #772

Draft
MegaRedHand wants to merge 8 commits into
perf/silver-style-verificationfrom
perf/silver-style-simd
Draft

MegaRedHand wants to merge 8 commits into
perf/silver-style-verificationfrom
perf/silver-style-simd

Conversation

@MegaRedHand

Copy link
Copy Markdown
Collaborator

Stacked on #771. Runs the parent's Silver usage patterns on silver_crypto_simd = "0.1.0" (AVX-512 IFMA, 8 lanes), and supersedes #764, whose backend swap this merges in.

What changes on top of the parent

  • Backend from perf(crypto): decode, pair and check cells through silver_crypto_simd #764: signatures decode to silver's G2Point; key decode, pairings and compute_cells / verify_cell_kzg_proof_batch run on silver (CellBatchVerifier / CellExtender pools). blst stays for sums, signing, keygen and the cached PublicKeys; c-kzg stays for commitments, blob proofs, cell proofs and recovery.
  • bls/primitives.rs now holds silver calls and no unsafe. Hash-to-G2 is silver's, and the hash cache stores G2Point.
  • Grouped batch on PairingProduct: runs of equal messages are split into chunks of at most LANES - 1 runs. Each chunk is its own random-weight equation, with its own weights and its own refusal of an infinite signature sum. Chunks run in parallel, and so do the runs inside a chunk. An identity key is refused before the product, since silver's PairingProduct drops identity pairs.
  • Registry warm-up decodes misses with silver's batched decoder: 0.87 s for 2.4M keys, against 4.5 s on blst.
  • Gossip column batching runs on CellBatchVerifier through the merged verify_cell_kzg_proof_batch. No p2p or gossip code changes here.

Benchmark (ethrex-office-4, Ryzen 9 9950X3D, idle; nightly 2b51a3336 as control)

nightly parent this PR
replay, warm steady STF ms 17.7 17.6 17.1 (-3%)
replay, warm steady batch signatures ms 1.3 1.4 1.1 (-19%)
replay, cold first block STF ms 239.5 184.7 186.2
512 one-message votes as one batch, ms 9.7 (nightly's own batch) 1.07 0.84
8 columns × 21 blobs as one KZG batch, ms 12.2 12.2 3.3
compute_cells µs 1,729 1,726 99.5
single verify µs (cached message) 668 520 306
eth_aggregate_pubkeys, 256 cached keys, µs 53.7 59.8 105.5 (1.96x)

The parent PR has the full tables and caveats.

Known issues

  • CI cooldown-check fails until 2026-11-17: the crate was published 2026-11-03.
  • execution_payload is slower with the crate present, though that phase has no BLS or KZG: by p50, +0.8 ms cold and +0.2 ms warm. perf(crypto): decode, pair and check cells through silver_crypto_simd #764 showed the same on ethlambda-5. Unexplained.
  • Signature decode is not faster: decoding 512 sets one by one is 22.3 ms against nightly's 22.1. Silver's own client batches G2 decodes (SignatureChecker::check_all); that is not ported.

Tests

make lint; ethlambda-crypto lib 67; ethlambda-state-transition lib 734; beacon spec tests 7493 passed, 977 ignored; ethlambda-p2p lib 384; ethlambda-blockchain lib 317; ethlambda bin 287. #764's differential tests against blst and c-kzg are kept.

silver_crypto_simd (gattaca-com/silver's crypto crate) runs BLS12-381 point
decoding, subgroup checks, pairing products and the PeerDAS cell operations
eight lanes at a time on AVX-512 IFMA, which every Zen 4/5 host we run has,
and falls back to blst and portable Rust elsewhere through the same API. Its
decoders return blst's own verdict on every input, and its cell verifier the
spec's, so it can sit behind the existing spec-named functions unchanged.

BLS (`ethlambda_crypto::bls`): key and signature decoding, `verify`,
`fast_aggregate_verify`, `aggregate_verify` and the block batch now go through
it; cache misses are decoded in chunks of `KEY_DECODE_CHUNK` across rayon.
Point sums, committee sums, signing and key generation stay on blst, since
the crate has no faster route for them. Its pairing product drops a pair
holding the identity where blst refused an identity key, so `core_verify` and
the batch refuse that key themselves before any pairing (the `P, -P`
cancellation tests cover it).

KZG (`beacon::kzg`): `compute_cells` and `verify_cell_kzg_proof_batch` run on
it; commitments, blob proofs, cell proofs and recovery stay on c-kzg. Its
verifier answers false for malformed input, so a rejection is followed by the
spec's own input assertions to keep `Err` (fixture `output: null`) apart from
`Ok(false)`. The module now has its own `Cell` byte array, converted to
c-kzg's type only at calls into c-kzg. The verifier and extender take
`&mut self` and are costly to build, so they live in small pools.

Differential tests compare every swapped function with blst or c-kzg on
valid, corrupt, off-subgroup and malformed inputs; on an IFMA host they
exercise the vector kernels.
blst's batch spread each set's hashing, key weighting and Miller loop over
its thread pool; the first silver version did all of it on the calling
thread and measured 2.2x slower than blst at 12 sets and 4.1x at 64 on
ethlambda-5. A batch is now cut into chunks of `LANES - 1` sets, each its own
random linear combination (fresh weights, own signature sum, own final
exponentiation), checked in parallel with each chunk's hashes in parallel
too. A chunk plus its signature-sum pair fills one pass of the Miller-loop
lanes, and soundness holds per chunk, since a forged set must still pass its
own chunk's check.
…ypto_simd

Merge perf/silver-crypto-simd into perf/silver-style-verification and
re-point the latter's blst usage patterns at the new backend, so the two
speedups compound instead of excluding each other: the H(m) cache, message-
grouped batching and registry warm-up keep their shape, and the pairings,
hashes, weighted sums and key decoding behind them run on 8-lane IFMA code.

- primitives.rs now holds the silver point and pairing calls; the blst
  hash-to-G2 `unsafe` and the blst Pairing code are gone. Every pairing
  check still refuses an identity key, since the silver product drops pairs
  holding one.
- hashed_messages caches G2Point, same two generations, cap and metrics.
- verify, fast_aggregate_verify and SignatureSet::verify use core_verify with
  H(m) from the cache; aggregate_verify hashes through it in parallel.
- verify_signature_sets sorts by message, groups runs of equal messages and
  checks chunks of at most LANES - 1 runs in parallel, each its own random
  linear combination; a chunk whose weighted signature sum is the point at
  infinity is refused.
- warm_pubkeys decodes the cache misses with the batched decoder, one
  rayon task per decode chunk.
…rayon

A chunk holds up to a lane-width of distinct messages, and a cache miss costs a full hash to G2 per message, so computing them one after another left a block's batch waiting on serial hashing that the blst-backed batch already ran in parallel.
@Bronek

Bronek commented Nov 9, 2026

Copy link
Copy Markdown

Hi, thank you for looking to adopt silver_crypto_simd.

Following up on "Signature decode is not faster" in the description: decoding is now the dominant cost (22.3 ms of the 512-vote batch, against 0.84 ms of pairing). uncompress_in_g2 routes calls with fewer than 3 inputs to blst, so decoding one signature at a time never reaches the IFMA decoder. Keeping the raw signature bytes in the prepared steps and decoding a whole batch in one uncompress_in_g2 call before building the sets (as Silver's SignatureChecker::check_all does) should cut most of that. The block path in stf/signatures.rs could do the same. On a Ryzen 9 9950X, decoding and subgroup-checking eight signatures took 355.9 µs with blst and 97.9 µs with the IFMA decoder.

This branch has not been deployed

No deployments
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