Skip to content

Conversation

@wemeetagain
Copy link
Member

@wemeetagain wemeetagain commented Jul 9, 2025

This is an opinionated refactoring, an effort to refactor the zig codebase into 1. a zig library (for consumption in zig) and 2. a bun-centric c-abi wrapper (for consumption in js).
build.zig and bun bindings are also tweaked.

leaving as draft because this is still not feature complete with current main branch.

TODO

  • tests
  • multithreaded verify functionality

EDIT (bing):

dcca904 merges #50 into this PR, which is another opinionated refactor of this refactor that only implements the min_pk variant of the blst library.

Here are some notes on what the PR changed:

  • Supports only min_pk variant. This means that we do not support min_sig, and we do not need to have SigVariant types, or the confusing createSigVariant from before, which can be confusing to follow when reading the code.
  • Removed memory_pool.zig and thread_pool.zig implementation. Had some initial discussions in private with @wemeetagain to decouple the memory related implementations from the library, but after some refactor work it feels like a memory/thread pool purpose-built for this library seems necessary?
  • Removed a lot of redundant C ABI functions that aren't called on the bun side. This is mostly from looking at unused code + the used code within production lodestar.

@wemeetagain wemeetagain marked this pull request as ready for review September 25, 2025 14:06
Comment on lines +20 to +21
Install and generate bun bindings:

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the plan in this PR to keep the bun bindings and move to lodestar-bun in a separate PR?

(I'm not planning on giving a deep review of the bun code until a PR into lodestar-bun)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I kept this here for ease of development, just to make sure the bun side still passes tests+benchmarks without the hassle. I will move the bun bindings from this PR to a new PR in lodestar-bun.

@@ -0,0 +1,134 @@
//! AggregatePublicKey definition for BLS signature scheme based on BLS12-381.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there a reason AggregatePublicKey.zig and AggregateSignature.zig are top-level structs vs public_key.zig, signature.zig etc? They shouldn't need to be extern structs imo.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point, I just kept them that way picking up from your PR. Let's change it

src/pairing.zig Outdated
return PairingError.DstTooSmall;
}
pub fn init(buffer: *[Self.sizeOf()]u8, hash_or_encode: bool, dst: []const u8) Self {
std.debug.assert(buffer.len == Self.sizeOf());
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it possible for an array to have a length that's different than its defined length?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, I think we can remove this assert

src/pairing.zig Outdated
Comment on lines 11 to 17
/// Initializes a pairing context with the provided `buffer` and other parameters.
///
/// Note: Rust always use a heap allocation here, but adding an allocator as param for Zig is too complex
/// instead of that we provide a buffer that's big enough for the struct to operate on so that:
/// - it does not have allocator in its api
/// - can use stack allocation at consumer side
/// - can reuse memory if it makes sense at consumer side
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might add that the Pairing takes ownership of the buffer

Comment on lines 28 to 37
pub fn fromAggregate(agg_pk: *const min_pk.AggPublicKey) Self {
var pk_aff = min_pk.PublicKey{};
c.blst_p1_to_affine(&pk_aff.point, &agg_pk.point);
return pk_aff;
}

/// Convert a regular `PublicKey` to a `AggPublicKey`.
pub fn toAggregate(self: *const Self) min_pk.AggPublicKey {
var agg_pk = min_pk.AggPublicKey{};
c.blst_p1_from_affine(&agg_pk.point, &self.point);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

an example of why the min_pk types are a bit confusing.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, what do you mean here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think agg_pk.point exists in either fn. Confusing bc its confused min_pk.AggPublicKey with AggregatePublicKey. If we just use c.blst_pX we won't have this issue

wemeetagain and others added 3 commits September 27, 2025 11:17
* add blst/ to .gitignore

* fix test

* feat: remove sig_groupcheck

* fix(testSets): SECRET_KEY_LENGTH -> SECRET_KEY_SIZE

* fix(index): remove missing verify.js

* rebirth

* chore: a bit more restructuring on zig

* more updates

* feat: implement verifyMultipleAggregateSignatures

* remove redundant log

* chore: simplify signature and pk size consts

* update aggregateWithRandomness with writing pks and sigs

* simple test for AggregateSignature

* revert some changes

* fix(sig): fix fromBytes

* update bindings

* fix(verifyMultipleAggSigs): fix writers for
verifyMultipleAggregateSignatures

* uncomment tests

* missing *Signature

* chore(signature): remove unused sigValidate

* fix(signature.ts): do fromBytes first then validate

* fix test

* fix randomness

* fix: uncomment aggregateVerify test

* chore: simplify naming for signature length constants

* remove unused code

* add writeSignatures

* more test fixes

* test(pk): add test for aggregatePublicKeys

* define and use MAX_AGGREGATE_PER_JOB

* use pointers to PublicKey in aggregatePublicKey

* fix length for aggregate signatures

* remove redundant print

* AggregateSignature: fix rands input for aggregateWithRandomness

* fix scratch for aggsig and aggpk

* remove comment

* uncomment more checks

* chore: remove redundant SignatureSet

* fix rands input to aggregateWithRandomness

* fix stuff related to rands

* fixes

* remove redundant c abi code; mostly unused stuff

* update README

* chore(error): remove redundant error definitions

* chore(aggPk): cleanup AggregatePublicKey.zig

* small correciton

* more minor changes to aggPk

* chore(aggSig): doc comment and tidy up

* add dst as a const

* fix link for DST

* doc comments for pairing.zig

* finish doc comments

* fix comment

* remove comment

* remove unnecessary import
Moved declarations to more appropriate places; for example for sizes of
`PublicKey` (serialize and compressed), moved their declarations to
`PublicKey`.
@github-actions
Copy link

github-actions bot commented Sep 27, 2025

Performance Report

✔️ no performance regression detected

Full benchmark results
Benchmark suite Current: 385918d Previous: null Ratio
SecretKey.fromKeygen 2.4120 us/op
SecretKey serialization 666.00 ns/op
SecretKey deserialization 570.00 ns/op
SecretKey.toPublicKey 154.11 us/op
SecretKey.sign 487.39 us/op
PublicKey serialization 581.00 ns/op
PublicKey deserialize 18.595 us/op
PublicKey deserialize and validate - 1 keys 82.759 us/op
PublicKey deserialize and validate - 100 keys 8.1827 ms/op
PublicKey deserialize and validate - 10000 keys 813.65 ms/op
Signature serialization 944.00 ns/op
Signature deserialize 36.852 us/op
Signatures deserialize and validate - 1 sets 102.90 us/op
Signatures deserialize and validate - 100 sets 10.230 ms/op
Signatures deserialize and validate - 10000 sets 1.0225 s/op
aggregatePublicKeys - 1 sets 675.00 ns/op
aggregatePublicKeys - 8 sets 8.8760 us/op
aggregatePublicKeys - 32 sets 26.763 us/op
aggregatePublicKeys - 128 sets 97.215 us/op
aggregatePublicKeys - 256 sets 198.83 us/op
aggregateSignatures - 1 sets 852.00 ns/op
aggregateSignatures - 8 sets 16.281 us/op
aggregateSignatures - 32 sets 56.403 us/op
aggregateSignatures - 128 sets 217.63 us/op
aggregateSignatures - 256 sets 435.41 us/op
aggregateWithRandomness - 1 sets 270.33 us/op
aggregateWithRandomness - 16 sets 2.7149 ms/op
aggregateWithRandomness - 128 sets 17.114 ms/op
aggregateVerify - 1 sets 1.6529 ms/op
aggregateVerify - 8 sets 5.1001 ms/op
aggregateVerify - 32 sets 17.364 ms/op
aggregateVerify - 128 sets 66.481 ms/op
verifyMultipleAggregateSignatures - 1 sets 1.8816 ms/op
verifyMultipleAggregateSignatures - 8 sets 6.4150 ms/op
verifyMultipleAggregateSignatures - 32 sets 22.414 ms/op
verifyMultipleAggregateSignatures - 128 sets 86.540 ms/op
Same message - 1 sets 1.9109 ms/op
Same message - 8 sets 2.6648 ms/op
Same message - 32 sets 5.1730 ms/op
Same message - 128 sets 15.208 ms/op
Same message - 256 sets 28.590 ms/op

by benchmarkbot/action

@spiral-ladder
Copy link
Contributor

spiral-ladder commented Sep 27, 2025

I've converted all the extern structs to top-level native zig structs, and I think I addressed some of the issues as well:

  • I still think it might be easy to mix up p1 vs p2 vs p1_affine, so I still defined a Point type in each of the relevant struct, for example PublicKey.Point is of type c.blst_p1_affine.
  • I nuked min_pk.zig - I think this way there's less confusion, since I noticed there were double definitions for things with the same name before, eg. a min_pk.PublicKey != PublicKey
  • Removed all the unnecessary C pointers (which should only be used in the ABI)
  • Changed check to errorFromInt (more obvious what it does, and we have a intFromError anyway)

@spiral-ladder
Copy link
Contributor

Seems like there's a flaky test in aggregating public keys, not quite sure what's the issue just yet. Had to re-run the macOS job 3 times for CI to pass. Creating an issue for this.

if (pks_validate) for (pks) |pk| try pk.validate();

var scalars_refs: [MAX_AGGREGATE_PER_JOB]*const u8 = undefined;
var pk_points: [MAX_AGGREGATE_PER_JOB]*const PublicKey.Point = undefined;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is the only use of PublicKey.Point (and Signature.Point is not used).
Consider not copying pointer here, using the same cast as in AggregateSignature.aggregateWithRandomness

Also consider removing Point entirely. (As an alternative, consider adding a doc comment to PublicKey, Signature, etc that says something to the effect of eg: "PublicKey is a p1_affine" or "PublicKey is a point in G1 in affine form." which can mitigate the possible confusion)

["Uint32Array", new Uint32Array()],
["Map", new Map()],
["Set", new Set()],
// ["boolean", true],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'm thinking to even remove these tests because i'm not sure if testing for invalid inputs make sense at all

Copy link
Member Author

@wemeetagain wemeetagain left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

great work, looks really good

const testing = std.testing;

/// Expose c types for lodestar-bun bindings
pub const c = @cImport({
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we reexport the upstream blst artifact in build.zig? would that be equivalent?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can be resolved later

Copy link
Member Author

@wemeetagain wemeetagain left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm

@wemeetagain wemeetagain merged commit 88fea8c into main Oct 1, 2025
11 of 14 checks passed
@wemeetagain wemeetagain deleted the cayman/refactor branch October 1, 2025 19:59
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