Repository navigation
perf(crypto): decode, pair and check cells through silver_crypto_simd - #764
Closed
MegaRedHand wants to merge 2 commits into
Closed
MegaRedHand wants to merge 2 commits into
MegaRedHand wants to merge 2 commits into
Conversation
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.
Collaborator
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
silver_crypto_simd(the crypto crate of gattaca-com/silver) runs BLS12-381 point decoding, subgroup checks, pairing products and the PeerDAS cell operations eight lanes at a time on AVX-512 IFMA. Every Zen 4/5 host we run has IFMA. On other CPUs it falls back to blst and portable Rust through the same API. Its decoders return blst's own verdict on every input and its cell verifier returns the spec's, with Lean proofs for the IFMA routes (see the crate README for what they cover).Based on
nightly(2b51a33), as asked. Two commits: the swap, then the batch made parallel after the first benchmark showed it slower than blst.What moves, what stays
The crate does not replace every blst or c-kzg API, so both stay as dependencies.
KeyValidateon cache misseskey_validate, one key per rayon taskuncompress_in_g1in chunks ofKEY_DECODE_CHUNKacross rayon, identity refused by ussig_validate(_, false))uncompress_in_g2verify,fast_aggregate_verify,aggregate_verify,SignatureSet::verifyPairingProduct/two_pairs_are_oneverify_signature_sets(block batch)verify_multiple_aggregate_signatures(on blst's thread pool)LANES - 1sets checked in parallel on rayon, each with its ownG2Point::weighted_sumsignature sum, weighted keys andPairingProduct(one 8-lane Miller pass)aggregate(signatures)compute_cellsCellExtenderverify_cell_kzg_proof_batchCellBatchVerifiercompute_cells_and_kzg_proofs,recover_cells_and_kzg_proofsTwo semantic differences, handled
PairingProductdrops any pair that holds the identity, and an empty product istrue. blst refused an identity key (BLST_PK_IS_INFINITY). Without a guard, a cancelling key set (P,-P, both valid keys) would verify under the identity signature.core_verifyand the batch now refuse the identity key before any pairing. The existingkeys_that_cancel_never_verify_alone_or_in_a_batchanda_key_and_its_negation_sum_to_the_identity_and_never_verifytests cover it.ErrvsOk(false)for cell batches.CellBatchVerifier::verifyanswersfalsefor malformed input, where the spec raises.verify_cell_kzg_proof_batchreturnsOk(true)ontrue. Onfalseit runs the spec's own input assertions in the spec's order (lengths,cell_index < CELLS_PER_EXT_BLOB, commitments and proofs decode, cells canonical) and returnsErrif one fails, elseOk(false). That second pass only runs on a rejection.Other changes
beacon::kzgnow has its ownCell = [u8; BYTES_PER_CELL]. It is converted to c-kzg's type only at the calls into c-kzg, so the hot verify path no longer copies throughc_kzg::Cell. Callers infork_choice.rs,data_columns.rs,gloas_block_production.rsand the spec runner were updated.&mut selfand precompute tables on construction, so they live in a smallPool(aMutex<Vec<_>>, take and return). Design choice to review:POOL_MAX_IDLEidle instances at most, and a verifier goes back only if its batch had at mostMAX_RETAINED_BATCH_CELLScells. A verifier keeps its buffers at its largest batch, so this keeps a whole-block batch from pinning its buffers.Cargo.lockonly gains thesilver_crypto_simdentry. Its only dependency is blst 0.3.16, which we already have.Tests
verify/fast_aggregate_verify/aggregate_verifyat signer counts 1 to 17;verify_signature_setsat 2 to 17 sets with a bad set in several positions;compute_cells;verify_cell_kzg_proof_batchon 15 valid and broken batches, comparingOk(true)/Ok(false)/Err. They run the IFMA kernels on an IFMA host and the fallback elsewhere.ethlambda-crypto,ethlambda-state-transition,ethlambda-p2plib tests pass; clippy clean.ethlambda-crypto45,ethlambda-state-transition699,ethlambda-p2p371 at 7bacadc) and silver's own suite withSILVER_REQUIRE_IFMA=1(119 passed, so the IFMA kernels really ran) all pass.Benchmarks (ethlambda-5, Ryzen 9700X, node stopped)
Setup. ethlambda-5 (Ryzen 7 9700X, Zen 5, AVX-512 IFMA). The node was stopped 15:09:37Z to 16:00:29Z on 2026-11-03. The control is nightly 2b51a33; the candidate is this PR at b6819ef. Both were built with
cargo build --release --locked.beacon::blsandbeacon::kzg. The same file was built against both trees (release-fast) and run for 3 rounds with the legs alternated, 9 repetitions per case. The table shows the median of the per-round medians.benchmark import replayon three mainnet corpora,--block-delay 1000, legs rotated. "warm" runs pass--warm-committee-sums(3 rounds, 18 runs); "cold" runs omit it (2 rounds, 12 runs), so the first timed block validates every key it meets. "Steady" means ordinary blocks after the run's first epoch.Microbench, µs per call
verify, key cachedfast_aggregate_verify, 64 keys cachedfast_aggregate_verify, 512 keys cachedverify_signature_sets, 12 setsverify_signature_sets, 64 setsaggregate, 64 signaturesaggregate, 512 signatureseth_aggregate_pubkeys, 16,384 unseen keyseth_aggregate_pubkeys, 256 unseen keyskey_validate, one unseen keyverify_cell_kzg_proof_batch, 1 column × 6 blobsverify_cell_kzg_proof_batch, 1 column × 21 blobsverify_cell_kzg_proof_batch, 8 columns × 21 blobsverify_cell_kzg_proof_batch, 128 columns × 6 blobscompute_cells, one blobImport replay, STF ms per block (mean / p50)
batch_signaturesblock_signatureattestationsNotes:
batch_signaturesandblock_signature, and those drop by a quarter. The headline gains are the first block after a start (unseen keys decoded eight per step) and KZG, which the replay does not exercise (it has no custody columns). In a live node KZG means gossip columns (a 21-blob column goes from 4.3 to 1.6 ms) andcompute_cellsforgetBlobs/block production (17x).execution_payload(+2.1 ms), a phase that times withdrawals and the payload and calls no BLS or KZG code. Warm runs show +0.3 ms mean (+0.05 ms p50) there, and corpus C moves the other way (−1.3 ms STF). I read it as variance, but I have not explained it.uncompress_in_g1_blst(the same verdict).LANES - 1sets, which gives the numbers above.Known CI failure
silver_crypto_simd 0.1.0was published 2026-11-03, somake cooldown-checkfails until the 14-day cooldown ends on 2026-11-17. I left it failing: neither bypassing the check nor pinning a path dependency is right.Questions for review
POOL_MAX_IDLE,MAX_RETAINED_BATCH_CELLS): keep, or size fromavailable_parallelism?