perf(point_evaluation): rearrange KZG verifier to use G1 scalar mul#660
perf(point_evaluation): rearrange KZG verifier to use G1 scalar mul#6600xVolosnikov wants to merge 1 commit into
Conversation
ff58d23 to
4e6a520
Compare
f58aaf1 to
8b5affe
Compare
4e6a520 to
a6b35e7
Compare
There was a problem hiding this comment.
Pull request overview
This PR optimizes the BLS12-381 KZG point-evaluation verification path by algebraically rearranging the pairing equation to move the z scalar multiplication from G2 into G1, reducing expensive G2 operations while preserving verification semantics. It introduces a local verify_kzg_proof implementation in the point-evaluation system function module and wires it into both the EIP-4844 point-evaluation precompile path and the blob commitment generator code path, with a test that checks equivalence against the existing reference verifier.
Changes:
- Add a locally defined
verify_kzg_proofthat verifies KZG proofs viae(y·G1 − P − z·proof, G2) · e(proof, τ·G2) == 1. - Switch point-evaluation precompile verification to use the new local verifier.
- Switch blob commitment verification in the bootloader to use the same local verifier and add a matches-reference test.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| basic_system/src/system_functions/point_evaluation.rs | Adds the rearranged KZG verifier and uses it in the point-evaluation system function; adds a test comparing against the reference implementation. |
| basic_bootloader/src/bootloader/block_flow/zk/post_tx_op/da_commitment_generator/blob_commitment_generator/mod.rs | Replaces the reference KZG verifier call with the new local verifier for blob proof checking. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| assert!(verify_kzg_proof(commitment, proof, z, y)); | ||
| assert_eq!( | ||
| verify_kzg_proof(commitment, proof, z, y), | ||
| crypto::bls12_381::verify_kzg_proof(commitment, proof, z, y) | ||
| ); | ||
|
|
||
| let unrelated_128_bit_z = | ||
| hex!("000000000000000000000000000000000123456789abcdef0123456789abcdef"); | ||
| let unrelated_128_bit_z = parse_scalar(&unrelated_128_bit_z).unwrap(); | ||
| assert_eq!( | ||
| verify_kzg_proof(commitment, proof, unrelated_128_bit_z, y), | ||
| crypto::bls12_381::verify_kzg_proof(commitment, proof, unrelated_128_bit_z, y) | ||
| ); | ||
|
|
||
| let zero_y = parse_scalar(&[0u8; 32]).unwrap(); | ||
| assert_eq!( | ||
| verify_kzg_proof(commitment, proof, z, zero_y), | ||
| crypto::bls12_381::verify_kzg_proof(commitment, proof, z, zero_y) | ||
| ); |
Move the scalar multiplication by z from G2 to G1: before: e(yG1 - P, G2) · e(proof, τG2 - zG2) == 1 after: e(yG1 - P - z·proof, G2) · e(proof, τG2) == 1 Algebraically equivalent by bilinearity. G2 lives in Fq², so a G2 scalar mul is materially more expensive than the G1 equivalent; the rearranged form also eliminates one G2 subtraction and one G2 into_affine (Montgomery inversion). Implemented as a local function in basic_system::system_functions:: point_evaluation, parallel to the existing crypto::bls12_381:: verify_kzg_proof (kept upstream as the reference). Wired into both the EIP-4844 point-evaluation precompile and the bootloader's blob commitment generator post-tx-op. Bench (test_kzg_regression under ZKSYNC_RISC_V_RUN=true, for-tests-benchmarking RISC-V binary, A/B at same parent commit): point_evaluation raw cycles 38,992,778 → 31,791,155 −18.5% point_evaluation bigint delegations 3,087,009 → 2,513,437 −18.6% effective (raw + 4·bigint) 51,340,814 → 41,844,903 −18.5% dist/for_tests/app.bin size 1,353,100 → 1,337,196 −1.18% Binary shrinks, so no RISC-V I-cache regression risk despite #[inline(always)] on the new function. Includes test_rearranged_kzg_verifier_matches_reference that asserts byte-equal output vs crypto::bls12_381::verify_kzg_proof on a valid proof, an unrelated 128-bit z, and a zero y. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
a6b35e7 to
7e05a97
Compare
Block-level effective cyclesAverage across all block fixtures (
Per-block effective cycles
Block-level sub-phases
Precompiles test-crate bench (synthetic workload, all labels)
FRI precompile bench (FriProofTx + sidecar + contract call)
Per-precompilePer-precompile per-execution ratios (head) |
What ❔
Rearranges the KZG point-evaluation verifier so the scalar multiplication by
zlives in G1 instead of G2:e(y·G1 − P, G2) · e(proof, τ·G2 − z·G2) == 1e(y·G1 − P − z·proof, G2) · e(proof, τ·G2) == 1Algebraically equivalent by bilinearity. The rearrangement trades one G2 scalar-mul + G2 subtraction + G2
into_affine(Montgomery inversion) for one G1 scalar-mul + G1 subtraction; G2 lives in Fq² so every G2 op is materially more expensive than its G1 equivalent.The new
verify_kzg_proofis defined locally inbasic_system::system_functions::point_evaluation, in parallel with the existing reference atcrypto::bls12_381::verify_kzg_proof(kept upstream and used by the new matches-reference test). Wired into both call sites — the EIP-4844 point-evaluation precompile andblob_commitment_generator::blob_versioned_hash_with_advisor.Why ❔
KZG verification is one of the heaviest BLS12-381 paths in the prover. The rearranged form makes the dominant scalar-mul cheaper without changing any external semantics or pricing.
Benchmark —
test_kzg_regressionunderZKSYNC_RISC_V_RUN=truewith thefor-tests-benchmarkingRISC-V binary, A/B at the same parent commit:point_evaluationraw cyclespoint_evaluationbigint delegationspoint_evaluationeffective (raw + 4·bigint)process_transactionrawprocess_blockrawdist/for_tests/app.binsizeTx- and block-level deltas track the marker delta — the saving is fully attributable to
point_evaluation. Binary size decreases, so no RISC-V I-cache regression risk even with#[inline(always)]on the new function.Is this a breaking change?
Checklist
Stacked on
Based on
vv-jump-inline-riscv-draft. The bootloader call site lives at a path (basic_bootloader/.../blob_commitment_generator/mod.rs) that does not exist onmainyet, so the diff cannot rebase cleanly ontomain. Oncevv-jump-inline-riscv-draftlands (or merges through), this PR auto-retargets.Follow-ups not in this PR
(commitment, proof, z, y)tuples) — current coverage is 3 explicit inputs plus az=0/y=0edge.PREPARED_G2_BY_TAUconst in airbender-crypto next toPREPARED_G2_GENERATOR; today the G2Prepared conversion for τ·G2 is done at every call, but the value is a fixed setup constant and can be precomputed at compile time for further savings.crypto::bls12_381::verify_kzg_proofso the two copies do not drift.🤖 Generated with Claude Code