Skip to content

Split Bls into per-type files; generate G1/G2 from a shared template - #10

Merged
Marchhill merged 14 commits into
mainfrom
refactor/generate-point-groups
Jul 17, 2026
Merged

Split Bls into per-type files; generate G1/G2 from a shared template#10
Marchhill merged 14 commits into
mainfrom
refactor/generate-point-groups

Conversation

@Marchhill

@Marchhill Marchhill commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

Summary

  1. Pure move: Bls.cs (~1,900 lines) is split into per-type partial class files — Bls.cs (loader, ERROR, BlsException, shared constants), Bls.SecretKey.cs, Bls.Scalar.cs, Bls.G1.cs, Bls.G2.cs, Bls.Gt.cs, Bls.Pairing.cs — with each type's native imports next to it. No code changes; zero API impact since the class was already partial.

  2. Generate G1/G2 from one template: the two point groups were ~95% duplicated code whose subtle intentional differences (fp ordering in P2.TryDecode, coordinate counts, the sign_pk_in_* swap) could drift apart silently. Bls.G1.cs and Bls.G2.cs are now rendered from src/Nethermind.Crypto.Bls.Gen/PointGroup.template by a small C# console project (dotnet run --project src/Nethermind.Crypto.Bls.Gen); the differences live in explicit //<<g1-only / //<<g2-only blocks and named tokens. A new CI job regenerates and fails on drift.

Why checked-in generation rather than a Roslyn source generator

For a crypto library the shipped logic should be auditable in the repo: generated-but-committed files mean reviewers see actual C# in every PR diff, IDE navigation/debugging work on real files, and the package build stays plain (no analyzer wiring). The CI drift check gives the same can't-diverge guarantee a source generator would.

Normalizations from unifying the two sides (no behavioral change)

  • P2 gains FromSk (parity with P1, additive API)
  • P2.Mult(BigInteger) refactored to the same PrepareMult/GetSize shape as P1
  • consistent readonly member modifiers and scoped parameters across both groups
  • constructor placement, comments and blank lines aligned

All 57 tests pass; the generator is idempotent (regenerating on a clean tree produces no diff).

Marchhill and others added 12 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>
Pure move: each nested type and the native imports it uses now live in
their own partial class file

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The two point groups were near-identical copies that could drift apart
silently. Bls.G1.cs and Bls.G2.cs are now rendered from
PointGroup.template by the Nethermind.Crypto.Bls.Gen console project;
the generated files stay checked in and reviewable, and CI verifies
they match the template.

Normalizations from unifying the two sides: P2 gains FromSk (parity
with P1), P2.Mult(BigInteger) uses the same helper shape as P1,
redundant readonly/scoped modifiers and comments are now consistent.
No behavioral changes; all tests pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…mments

- add DefaultPairingConstructorInitializesContext regression test for the
  new Pairing() null-buffer crash
- guard MSM/MillerLoopN length checks against int overflow ((long)npoints)
- bump copyright to 2026 on files added in this PR
- trim verbose comments to the load-bearing "why"

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Merge review/fixes-tests-benchmarks (#9) into the point-group refactor and
carry its improvements into the new template/split structure:

- port the (long)npoints/npairs overflow-safe MSM/MillerLoopN guards into
  PointGroup.template and Bls.Gt.cs
- keep #9's DefaultPairingConstructorInitializesContext regression test

Review feedback on #10:
- CI drift check stages first (git add -A && git diff --cached) so newly
  generated files are also compared
- CI now enforces whitespace (dotnet format --verify-no-changes); fixed the
  one pre-existing violation in BlsTests.cs
- drop the unused CopyToOutputDirectory in the generator csproj (the template
  is read from the source tree)
- note in Program.cs that conditional blocks must be flat and paired

Also: trim verbose comments and bump copyright to 2026 on the new files.
Generation stays idempotent; all 58 tests pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@Marchhill
Marchhill requested a review from rubo July 16, 2026 12:20
Comment thread src/Nethermind.Crypto.Bls/Bls.G1.cs Outdated
Write the little-endian scalar magnitude straight to a stackalloc span via
BigInteger.TryWriteBytes(isUnsigned) instead of ToByteArray(); a positive
scalar now allocates nothing, and a negative one only the negated BigInteger.
Drops the PrepareMult/GetSize helpers (the unsigned write needs no sign-byte
trimming). Behaviour unchanged — covered by the One/Zero/MinusOne/large Mult
tests.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@Marchhill
Marchhill requested a review from benaadams July 16, 2026 17:13
Base automatically changed from review/fixes-tests-benchmarks to main July 17, 2026 09:21
# Conflicts:
#	.github/CODEOWNERS
#	README.md
#	src/Nethermind.Crypto.Bls.slnx
#	src/Nethermind.Crypto.Bls/Bls.cs
@Marchhill
Marchhill merged commit 77442f2 into main Jul 17, 2026
9 checks passed
@Marchhill
Marchhill deleted the refactor/generate-point-groups branch July 17, 2026 21:17
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.

3 participants