Skip to content

Correctness fixes, batched miller loop, tests, benchmarks#9

Open
Marchhill wants to merge 8 commits into
mainfrom
review/fixes-tests-benchmarks
Open

Correctness fixes, batched miller loop, tests, benchmarks#9
Marchhill wants to merge 8 commits into
mainfrom
review/fixes-tests-benchmarks

Conversation

@Marchhill

Copy link
Copy Markdown
Collaborator

Summary

Full review of the bindings with fixes, new performance APIs for the EIP-2537 precompiles, a much larger test suite (8 → 57 tests, all passing), and a BenchmarkDotNet project.

Correctness / safety fixes

  • new Pairing() crashed the process on first use: the only constructor has all-optional parameters, so new Pairing() produced default(Pairing) backed by a null buffer; any call segfaulted (confirmed: blst_pairing_raw_aggregate faulting at address 0x4). Added an explicit parameterless constructor.
  • MultiMult with npoints == 0 crashed natively: blst's pippenger dereferences points[0] and underflows npoints. All-infinity MSM inputs are valid EIP-2537 calls; the client-side guard in Nethermind was the only protection. Now returns infinity, and buffer lengths are validated.
  • Pairing.AsFp12 out-of-bounds read + GC hole: copied Pairing.Sz longs (the whole context size) from blst's interior GT pointer instead of PT.Sz, reading past the allocation, and used the pointer after the span was unpinned. Now copies PT.Sz longs under fixed.
  • Generator(Span) / PT.One(Span) corrupted memory on short buffers: Buffer.MemoryCopy ran before length validation. Now validate first and use bounds-checked span copies.
  • Silent all-zero secret keys: blst zeroes the output for IKM < 32 bytes in all Keygen* variants and DeriveMasterEip2333; now throws ArgumentException.
  • Native library fallback path was CWD-relative; now resolved against AppContext.BaseDirectory.
  • MapTo validates 48-byte field element lengths (native code read past shorter inputs).
  • BlsException exposes its ERROR code; added missing Pairing.FinalVerify() overload (the standard blst aggregate-verify flow, previously inexpressible); fixed RepositoryUrl typo.
  • Documented the intentionally unvalidated raw decode paths (P1/P2.TryDecode uncompressed, Decode(fp...)) and the wrap-vs-parse constructor overloads.

New APIs (v1.1.0)

  • PT.MillerLoopN(qAffines, pAffines, npairs) — exposes blst_miller_loop_n, computing the product of n Miller loops in one pass with shared Fp12 squarings. Measured end-to-end pairing-check speedup (M2 Max, portable build): 1.14× at 2 pairs, 1.37× at 8, 1.45× at 16 vs the sequential loop currently used by Nethermind's pairing precompile.
  • P1Affine/P2Affine.Decode(fp...) — raw affine decode from field-element bytes, so protocol code can build contiguous affine buffers without a Jacobian round trip.
  • P1/P2.MultiMultAffine — MSM over affine inputs, skipping the redundant blst_p1s_to_affine batch conversion (the EIP-2537 flow decodes affine coordinates, converts to Jacobian, then converts straight back).

Performance

  • MSM scratch/affine buffers now come from ArrayPool: zero managed allocations per call (previously ~100KB+ per 512-point G2 MSM).
  • Pointer plumbing reworked: stackalloc pointer arrays, bounds-checked span copies instead of raw Buffer.MemoryCopy, reduced unsafe surface.

Tests (8 → 57)

Sign/verify/aggregate roundtrips with rejection cases, official EIP-2333 and RFC 9380 hash-to-curve vectors, generator known-answer tests, serialization/infinity/decode-error cases, scalar arithmetic, MSM/MillerLoopN consistency and edge cases, AsFp12 regression test.

Benchmarks

New Nethermind.Crypto.Bls.Bench (BenchmarkDotNet): keygen, hash-to-curve, sign, verify, point ops, decodes, pairings (incl. precomputed lines and sequential-vs-batched pairing check), G1/G2 MSM.

Infra

  • CI test job now runs on Linux/macOS/Windows (package ships 5 native runtimes; only linux-x64 was tested).
  • Added .github/CODEOWNERS (supersedes Add CODEOWNERS file #8).

🤖 Generated with Claude Code

Marchhill and others added 8 commits July 7, 2026 12:58
- Pairing.AsFp12 read past the end of the context buffer (it copied
  Pairing.Sz longs from the interior GT pointer instead of PT.Sz) and
  used the pointer after the span was unpinned; copy under fixed
- new Pairing() bypassed the all-optional-parameter constructor and
  produced an instance backed by a null buffer that crashed the process
  on first use; add an explicit parameterless constructor
- MultiMult passed npoints == 0 through to blst, which underflows and
  crashes; return infinity instead, and validate buffer lengths
- Generator(Span)/PT.One(Span) copied into the caller's buffer before
  validating its length, corrupting memory for short buffers
- Keygen*/DeriveMasterEip2333 silently produced an all-zero secret key
  for IKM shorter than 32 bytes (blst zeroes the output); throw instead
- MapTo read past the end of inputs shorter than 48 bytes; validate
- native library fallback path resolved runtimes/ against the current
  working directory instead of the application base directory
- expose the error code on BlsException
- fix RepositoryUrl typo (bls-bindings -> blst-bindings)

Also readability: pool MSM scratch buffers instead of allocating per
call, replace Buffer.MemoryCopy with bounds-checked span copies, use
stackalloc pointer arrays for the pippenger wrappers, take MSM inputs
as ReadOnlySpan, normalize modifier order, document the unvalidated
decode paths and wrap-vs-parse constructors, drop dead code

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- sign/verify/aggregate roundtrips for min-pk signatures, including
  Pairing-based verification and rejection cases
- EIP-2333 key derivation test vectors
- RFC 9380 hash-to-curve test vectors for G1 and G2
- secret key range validation and encoding roundtrips
- point serialization/compression roundtrips, infinity encoding,
  decode error cases, generator known-answer tests
- scalar arithmetic identities and reduction
- MSM vs single mult consistency, empty and invalid input handling
- GT operations and Pairing.AsFp12 consistency

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
BenchmarkDotNet project covering keygen, hash-to-curve, sign, verify,
point arithmetic, decode/subgroup checks, pairings (including
precomputed lines) and G1/G2 multi-scalar multiplication

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- PT.MillerLoopN exposes blst_miller_loop_n: computes the product of n
  Miller loops in one pass, sharing the Fp12 squarings across pairs
  (measured 1.14x-1.45x end-to-end for pairing checks of 2-16 pairs)
- P1Affine/P2Affine raw affine Decode from field element bytes, so
  callers can build contiguous affine buffers without a Jacobian
  round trip
- MultiMultAffine skips the Jacobian-to-affine batch conversion for
  callers that already hold affine points
- bump version to 1.1.0

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The folded scalar's deeper-indented continuation line kept its newline,
so -p:VersionSuffix=... executed as its own shell command whenever the
preview input was set

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
dotnet pack --no-build recomputes the package version, so without the
suffix it packed and pushed a non-preview version

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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