Skip to content

perf(precompiles): dedupe secp256k1 work in the ECIES path - #1385

Closed
mattsse wants to merge 1 commit into
mainfrom
mattsse/perf-ec-lincomb-and-dedupe
Closed

perf(precompiles): dedupe secp256k1 work in the ECIES path#1385
mattsse wants to merge 1 commit into
mainfrom
mattsse/perf-ec-lincomb-and-dedupe

Conversation

@mattsse

@mattsse mattsse commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Chaum-Pedersen verification computed s*G - c*pubSeq and s*ephemeralPub - c*sharedSecret as four separate scalar multiplications and re-encoded the generator on every challenge hash. The ECIES side recomputed privSeq * G per deposit and eph * G twice per authenticated withdrawal.

Each verification equation is now one two-term lincomb (Straus with the GLV endomorphism, the routine k256 uses for its own ECDSA verify), the generator encoding is a constant pinned by a test, and generator multiplications use mul_by_generator so they hit k256's precomputed tables where that feature is enabled, as it is for the node binary. A new PreparedDecryptionKey derives the sequencer public point once per key; compute_ecdh_proof and decrypt_deposit remain as thin wrappers. Adopting it in the L1 deposit loop is a follow-up.

All outputs are byte-identical, and the proof scalars and the deterministic withdrawal ciphertext are now pinned by fixed vectors captured from main. Measured with a throwaway interleaved A/B harness on the release profile: verify 172 → 137 µs, challenge_hash 5.6 → 0.7 µs, compute_ecdh_proof 173 → 132 µs (110 µs with a reused key), decrypt_deposit 177 → 136 µs (114 µs reused), encrypt_authenticated_withdrawal_deterministic 134 → 76 µs.

Chaum-Pedersen verification evaluated `R1 = s*G - c*pubSeq` and
`R2 = s*ephemeralPub - c*sharedSecret` as four independent scalar
multiplications, and re-derived the generator's 65-byte SEC1 encoding on every
challenge hash. On the prover side each deposit recomputed `privSeq * G`, which
depends only on the key, and the deterministic authenticated-withdrawal path
computed `eph * G` twice per withdrawal.

Both verification equations are now single two-term linear combinations
evaluated with k256's `lincomb` (Straus with the GLV endomorphism, the routine
its own ECDSA verification uses), the generator encoding is a constant pinned by
a test, and generator multiplications go through `mul_by_generator` so they use
k256's precomputed basepoint tables where that feature is resolved on, which it
is for the node binary. `PreparedDecryptionKey` derives the sequencer public
point once so a caller decrypting several deposits under one key stops repeating
it; `compute_ecdh_proof` and `decrypt_deposit` remain as thin wrappers, so
callers outside the crate are unchanged. Adopting the prepared key in the L1
deposit loop is left as a follow-up.

Every output stays byte-identical. The Chaum-Pedersen proof scalars and the
deterministic authenticated-withdrawal ciphertext are now pinned by fixed-vector
assertions whose expected values were captured from main.

Measured with a throwaway in-crate A/B harness (release profile, old and new
alternating in small chunks, minimum per chunk, k256 features matching the node
binary):

  ChaumPedersenVerify::verify                       172 us -> 137 us
  challenge_hash                                    5.6 us -> 0.7 us
  compute_ecdh_proof                                173 us -> 132 us
  compute_ecdh_proof, key reused                    173 us -> 110 us
  decrypt_deposit                                   177 us -> 136 us
  decrypt_deposit, key reused                       177 us -> 114 us
  encrypt_authenticated_withdrawal_deterministic    134 us ->  76 us
@mattsse

mattsse commented Sep 3, 2026

Copy link
Copy Markdown
Contributor Author

not worth it

@mattsse mattsse closed this Sep 3, 2026
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.

1 participant