Skip to content
Merged
Show file tree
Hide file tree
Changes from 43 commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
a223812
wip
wemeetagain Jul 2, 2025
9f5e701
wip: refactor the refactor (#50)
spiral-ladder Sep 25, 2025
6c3db51
pairing: remove unnecessary assert
spiral-ladder Sep 26, 2025
b0d51da
remove redundant portable
spiral-ladder Sep 27, 2025
3458ad3
pairing: explain that pairing owns memory
spiral-ladder Sep 27, 2025
3cb5c4e
remove all usage of c pointers in native zig code
spiral-ladder Sep 27, 2025
704c411
remove `min_pk.zig` and move declarations
spiral-ladder Sep 27, 2025
f783845
fix bun lints
spiral-ladder Sep 27, 2025
9b5a4e0
pairing: refactor pairing as top-level struct
spiral-ladder Sep 27, 2025
0fbfccf
secret_key: refactor secret_key as top-level struct
spiral-ladder Sep 27, 2025
bfeed94
pk: make PublicKey top level struct
spiral-ladder Sep 27, 2025
5fa5c90
move fast verification to own file
spiral-ladder Sep 27, 2025
2b0752d
signature: make Signature top-level struct
spiral-ladder Sep 27, 2025
fe96b7b
fix PublicKey.point
spiral-ladder Sep 27, 2025
ad21ee4
move aggregateVerify test to Signature.zig
spiral-ladder Sep 27, 2025
0ce15b8
move more tests around
spiral-ladder Sep 27, 2025
bfbae85
fix abi
spiral-ladder Sep 27, 2025
a4c7453
more descriptive errorFromInt
spiral-ladder Sep 27, 2025
ca85a6b
chore: more descriptive namings
spiral-ladder Sep 27, 2025
aa5bd95
fix and expose fast verif
spiral-ladder Sep 27, 2025
c5c0384
remove blst from gitignore now that we use zig package
spiral-ladder Sep 27, 2025
834db5d
remove redundant rands
spiral-ladder Sep 29, 2025
e8bfa3e
use defined MAX_AGGREGATE_PER_JOB
spiral-ladder Sep 29, 2025
ce19316
uncomment pk validation
spiral-ladder Sep 29, 2025
fb51da4
bun lint
spiral-ladder Sep 29, 2025
724e87d
fix: use same rands for aggregateWithRandomness
spiral-ladder Sep 29, 2025
8e882a7
bun lint --fix
spiral-ladder Sep 29, 2025
97bffae
fix incorrect usage of ptrCast
spiral-ladder Sep 29, 2025
6794916
public key back to extern struct
spiral-ladder Sep 29, 2025
231f74a
bun lint fix
spiral-ladder Sep 29, 2025
a62beca
signature back to extern struct
spiral-ladder Sep 29, 2025
7099c29
bun lint --fix
spiral-ladder Sep 29, 2025
fcc141c
undo extern struct changes
spiral-ladder Sep 29, 2025
19e0b22
bun lint fix'
spiral-ladder Sep 29, 2025
6c0bceb
minor AggregateSignature fixes
spiral-ladder Sep 29, 2025
f2a8d1c
do not copy pks
spiral-ladder Sep 29, 2025
d20d683
bun lint
spiral-ladder Sep 29, 2025
7bb299f
use raw C types
spiral-ladder Sep 29, 2025
62380af
refactor entire eth_c_abi.zig to use raw C types as params
spiral-ladder Sep 30, 2025
1506925
bun lint --fix
spiral-ladder Sep 30, 2025
58cc49e
fix signature.zig
spiral-ladder Sep 30, 2025
6ec2cdd
bun lint --fix
spiral-ladder Sep 30, 2025
7b8c78c
Update README
spiral-ladder Sep 30, 2025
20e7b73
fix aggregateVerify test count
spiral-ladder Sep 30, 2025
2bb5395
bun lint --fix
spiral-ladder Sep 30, 2025
ea0ae94
expose c
spiral-ladder Sep 30, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
zig-out/
.zig-cache/
bun/node_modules/
bun/benchmark_data/
bun/benchmark_data/
43 changes: 36 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,45 @@
# blst-z
Zig wrapper for [supranational/blst](https://github.com/supranational/blst) native bindings, a highly performant BLS12-381 signature library.
Zig bindings for [supranational's blst](https://github.com/supranational/blst) native bindings, a highly performant BLS12-381 signature library.

This set of bindings only support the `min_pk` variant.

## Installation
- clone blst to root: `git clone --recurse-submodules https://github.com/supranational/blst.git`
- `zig build test`

Run zig tests:

```sh
zig build test
```

Build zig library:

```sh
zig build -Doptimize=ReleaseSafe
```

Install and generate bun bindings:

Comment on lines +20 to +21
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.

```sh
cd bun && bun install && bun run build && bun generate
```

Run bun tests:

```sh
cd bun && bun test
```

Run bun benchmarks:

```sh
cd bun && bun benchmark
```

## Usage
The below shows how to use min_pk namespace, should be the same for min_sig.

```zig
pub const min_pk = @import("blst").min_pk;
const SecretKey = min_pk.SecretKey;
pub const blst = @import("blst");
const SecretKey = blst.SecretKey;
const ikm: [32]u8 = [_]u8{
0x93, 0xad, 0x7e, 0x65, 0xde, 0xad, 0x05, 0x2a, 0x08, 0x3a,
0x91, 0x0c, 0x8b, 0x72, 0x85, 0x91, 0x46, 0x4c, 0xca, 0x56,
Expand All @@ -27,4 +56,4 @@ const sig = sk.sign(msg[0..], dst[0..], null);

// aug is null
try sig.verify(true, msg[0..], dst[0..], null, &pk, true);
```
```
16 changes: 2 additions & 14 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ pub fn build(b: *std.Build) !void {
});

const lib_blst_c = blst_c.artifact("blst");

// blst module (for downstream zig consumers)
const blst_mod = b.addModule("blst", .{
.root_source_file = b.path("src/root.zig"),
Expand All @@ -25,7 +26,7 @@ pub fn build(b: *std.Build) !void {
// blst dynamic library (for bun consumers)
const blst_dylib = b.addLibrary(.{
.root_module = b.createModule(.{
.root_source_file = b.path("src/root_c_abi_min_pk.zig"),
.root_source_file = b.path("src/eth_c_abi.zig"),
.target = target,
.optimize = optimize,
// blst does not need libc, however we need to link it to enable threading
Expand All @@ -40,8 +41,6 @@ pub fn build(b: *std.Build) !void {

b.installArtifact(blst_dylib);

// Creates a step for unit testing. This only builds the test executable
// but does not run it.
const lib_unit_tests = b.addTest(.{
.root_source_file = b.path("src/root.zig"),
.target = target,
Expand All @@ -50,17 +49,6 @@ pub fn build(b: *std.Build) !void {

lib_unit_tests.linkLibrary(lib_blst_c);
const run_lib_unit_tests = b.addRunArtifact(lib_unit_tests);

const exe_unit_tests = b.addTest(.{
.root_source_file = b.path("src/main.zig"),
.target = target,
.optimize = optimize,
});

exe_unit_tests.linkLibrary(lib_blst_c);
const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests);

const test_step = b.step("test", "Run unit tests");
test_step.dependOn(&run_lib_unit_tests.step);
test_step.dependOn(&run_exe_unit_tests.step);
}
4 changes: 2 additions & 2 deletions bun/bun.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"": {
"name": "blst-bun",
"dependencies": {
"@chainsafe/bun-ffi-z": "^v1.0.0",
"@chainsafe/bun-ffi-z": "^1.1.1",
},
"devDependencies": {
"@biomejs/biome": "^1.9.3",
Expand Down Expand Up @@ -83,7 +83,7 @@

"@chainsafe/benchmark": ["@chainsafe/[email protected]", "", { "dependencies": { "@actions/cache": "^4.0.0", "@actions/github": "^6.0.0", "@vitest/runner": "^2.1.8", "ajv": "^8.17.1", "aws-sdk": "^2.932.0", "cli-table3": "^0.6.5", "csv-parse": "^5.6.0", "csv-stringify": "^6.5.2", "debug": "^4.4.0", "glob": "^10.4.5", "log-symbols": "^7.0.0", "yaml": "^2.7.0", "yargs": "^17.7.2" }, "bin": { "benchmark": "bin/index.js" } }, "sha512-5rQKK2ar1k8DwqEMU5hTQlTrtrPzNdlfrMtbRgtrfYT/QpSADXGq1vBlywofY1D8HuEuv1LhRrn3p2rgibIXew=="],

"@chainsafe/bun-ffi-z": ["@chainsafe/bun-ffi-z@1.0.0", "", { "peerDependencies": { "typescript": "^5" }, "bin": { "bun-ffi-z": "src/cli.ts" } }, "sha512-IRQrGWvDVSWQOcNYPZqPeqMN8qRotad/84RHSxQ8xzIuhVhkpUp2lx4VeD92Tnkcwbp5Gr+tUT4Lb47nbIe1Xg=="],
"@chainsafe/bun-ffi-z": ["@chainsafe/bun-ffi-z@1.1.1", "", { "peerDependencies": { "typescript": "^5" }, "bin": { "bun-ffi-z": "src/cli.ts" } }, "sha512-RSJhdS8Ytj3NuEvwshAFRI/y9/GbD1IhVuYEjVt0u16Hw2owDvVUVAgiN4/g8WWcFMhs8i5a4VXiFzK25fARSA=="],

"@colors/colors": ["@colors/[email protected]", "", {}, "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ=="],

Expand Down
8 changes: 6 additions & 2 deletions bun/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"module": "src/index.ts",
"type": "module",
"dependencies": {
"@chainsafe/bun-ffi-z": "^v1.0.0"
"@chainsafe/bun-ffi-z": "^1.1.1"
},
"devDependencies": {
"@types/bun": "latest",
Expand All @@ -20,6 +20,7 @@
},
"scripts": {
"build": "bun ./node_modules/.bin/bun-ffi-z build",
"generate": "bun ./node_modules/.bin/bun-ffi-z generate-binding",
"prepublishOnly": "bun ./node_modules/.bin/bun-ffi-z prepublish --artifacts artifacts",
"publish": "bun ./node_modules/.bin/bun-ffi-z publish",
"test:unit": "bun test test/unit",
Expand All @@ -39,6 +40,9 @@
"darwin-arm64"
],
"optimize": "ReleaseSafe",
"zigCwd": ".."
"zigCwd": "..",
"zigExportFiles": [
"src/eth_c_abi.zig"
]
}
}
47 changes: 19 additions & 28 deletions bun/src/aggregate.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import {binding, writeUint8ArrayArray} from "./binding.js";
import {MAX_AGGREGATE_PER_JOB} from "./const.js";
import {PublicKey, writePublicKeysReference} from "./publicKey.js";
import {Signature, writeSignaturesReference} from "./signature.js";
import {binding} from "./binding.js";
import {pksU8, sigsU8, writePublicKeys, writeSignatures} from "./buffer.ts";
import {MAX_AGGREGATE_PER_JOB, PUBLIC_KEY_SIZE, SIGNATURE_LENGTH} from "./const.js";
import {PublicKey} from "./publicKey.js";
import {Signature} from "./signature.js";
import {writePublicKeysReference, writeSignaturesReference, writeUint8ArrayArray} from "./writers.ts";

// global public keys reference to be reused across multiple calls
// each 2 items are 8 bytes, store the reference of each public key
const publicKeysRef = new Uint32Array(MAX_AGGREGATE_PER_JOB * 2);

const signaturesRef = new Uint32Array(MAX_AGGREGATE_PER_JOB * 2);

/**
Expand All @@ -23,9 +24,9 @@ export function aggregatePublicKeys(pks: Array<PublicKey>, pksValidate?: boolean

for (let i = 0; i < pks.length; i += MAX_AGGREGATE_PER_JOB) {
const pksBatch = pks.slice(i, Math.min(pks.length, i + MAX_AGGREGATE_PER_JOB));
const pksRef = writePublicKeysReference(pksBatch);
const outPk = PublicKey.defaultPublicKey();
const res = binding.aggregatePublicKeys(outPk.blst_point, pksRef, pksBatch.length, pksValidate ?? false);
writePublicKeys(pksBatch);
const outPk = new PublicKey(new Uint8Array(PUBLIC_KEY_SIZE));
const res = binding.publicKeyAggregate(outPk.ptr, pksU8, pksBatch.length, pksValidate ?? false);

if (res !== 0) {
throw new Error(`Failed to aggregate public keys: ${res}`);
Expand All @@ -50,9 +51,9 @@ export function aggregateSignatures(sigs: Array<Signature>, sigsGroupcheck?: boo

for (let i = 0; i < sigs.length; i += MAX_AGGREGATE_PER_JOB) {
const sigsBatch = sigs.slice(i, Math.min(sigs.length, i + MAX_AGGREGATE_PER_JOB));
const sigsRef = writeSignaturesReference(sigsBatch);
const outSig = Signature.defaultSignature();
const res = binding.aggregateSignatures(outSig.blst_point, sigsRef, sigsBatch.length, sigsGroupcheck ?? false);
writeSignatures(sigsBatch);
const outSig = new Signature(new Uint8Array(SIGNATURE_LENGTH));
const res = binding.signatureAggregate(outSig.ptr, sigsU8, sigsBatch.length, sigsGroupcheck ?? false);

if (res !== 0) {
throw new Error(`Failed to aggregate signatures: ${res}`);
Expand Down Expand Up @@ -81,14 +82,8 @@ export function aggregateSerializedPublicKeys(
for (let i = 0; i < pks.length; i += MAX_AGGREGATE_PER_JOB) {
const pksBatch = pks.slice(i, Math.min(pks.length, i + MAX_AGGREGATE_PER_JOB));
const pksRef = writeSerializedPublicKeysReference(pksBatch);
const outPk = PublicKey.defaultPublicKey();
const res = binding.aggregateSerializedPublicKeys(
outPk.blst_point,
pksRef,
pksBatch.length,
pks[0].length,
pksValidate ?? false
);
const outPk = new PublicKey(new Uint8Array(PUBLIC_KEY_SIZE));
const res = binding.aggregatePublicKeys(outPk.ptr, pksRef, pksBatch.length, pks[0].length, pksValidate ?? false);

if (res !== 0) {
throw new Error(`Failed to aggregate serialized public keys: ${res}`);
Expand Down Expand Up @@ -117,22 +112,18 @@ export function aggregateSerializedSignatures(
for (let i = 0; i < sigs.length; i += MAX_AGGREGATE_PER_JOB) {
const sigsBatch = sigs.slice(i, Math.min(sigs.length, i + MAX_AGGREGATE_PER_JOB));
const sigsRef = writeSerializedSignaturesReference(sigsBatch);
const outSig = Signature.defaultSignature();
const res = binding.aggregateSerializedSignatures(
outSig.blst_point,
sigsRef,
sigsBatch.length,
sigs[0].length,
sigsGroupcheck ?? false
);
const outSig = new Signature(new Uint8Array(SIGNATURE_LENGTH));
const res = binding.signatureAggregate(outSig.ptr, sigsRef, sigsBatch.length, sigsGroupcheck ?? false);

if (res !== 0) {
throw new Error(`Failed to aggregate serialized signatures: ${res}`);
}
resultSignatures.push(outSig);
}

return resultSignatures.length === 1 ? resultSignatures[0] : aggregateSignatures(resultSignatures, sigsGroupcheck);
return resultSignatures.length === 1
? resultSignatures[0]
: aggregateSerializedSignatures(resultSignatures, sigsGroupcheck);
}

function writeSerializedPublicKeysReference(pks: Uint8Array[]): Uint32Array {
Expand Down
35 changes: 16 additions & 19 deletions bun/src/aggregateWithRandomness.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import {JSCallback} from "bun:ffi";
import {binding, writeNumber, writeReference} from "./binding.js";
import {MAX_AGGREGATE_WITH_RANDOMNESS_PER_JOB} from "./const.js";
import {binding} from "./binding.js";
import {pksU8, writePublicKeys} from "./buffer.ts";
import {MAX_AGGREGATE_WITH_RANDOMNESS_PER_JOB, PUBLIC_KEY_SIZE, SIGNATURE_LENGTH} from "./const.js";
import {PublicKey} from "./publicKey.js";
import {Signature} from "./signature.js";
import {writeNumber, writePublicKeysReference, writeReference, writeSignaturesReference} from "./writers.ts";

export interface PkAndSerializedSig {
pk: PublicKey;
Expand All @@ -14,10 +16,6 @@ export interface PkAndSig {
sig: Signature;
}

// global signature sets reference to be reused across multiple calls
// each 2 tems are 8 bytes, store the reference of each PkAndSerializedSig
const pkAndSerializedSigsRefs = new Uint32Array(MAX_AGGREGATE_WITH_RANDOMNESS_PER_JOB * 2);

/**
* Aggregate multiple public keys and multiple serialized signatures into a single blinded public key and blinded signature.
*
Expand All @@ -33,14 +31,14 @@ export function aggregateWithRandomness(sets: Array<PkAndSerializedSig>): PkAndS
throw new Error("At least one PkAndSerializedSig is required");
}

const refs = pkAndSerializedSigsRefs.subarray(0, sets.length * 2);
writePkAndSerializedSigsReference(sets, refs);
const pkOut = PublicKey.defaultPublicKey();
const sigOut = Signature.defaultSignature();
const pksRef = writePublicKeysReference(sets.map((s) => s.pk));
const sigsRef = writeSignaturesReference(sets.map((s) => Signature.fromBytes(s.sig, true)));
const pkOut = new PublicKey(new Uint8Array(PUBLIC_KEY_SIZE));
const sigOut = new Signature(new Uint8Array(SIGNATURE_LENGTH));

const res = binding.aggregateWithRandomness(refs, sets.length, pkOut.blst_point, sigOut.blst_point);
const res = binding.aggregateWithRandomness(pkOut.ptr, sigOut.ptr, sets.length, pksRef, sigsRef, false, false);

if (res !== 0) {
if (res) {
throw new Error("Failed to aggregate with randomness res = " + res);
}

Expand Down Expand Up @@ -69,8 +67,8 @@ export function asyncAggregateWithRandomness(sets: Array<PkAndSerializedSig>): P

// 1s timeout
const TIMEOUT_MS = 1_000;
const pkOut = PublicKey.defaultPublicKey();
const sigOut = Signature.defaultSignature();
const pkOut = new PublicKey(new Uint8Array(PUBLIC_KEY_SIZE));
const sigOut = new Signature(new Uint8Array(SIGNATURE_LENGTH));

return new Promise((resolve, reject) => {
let jscallback: JSCallback | null = null;
Expand Down Expand Up @@ -107,15 +105,14 @@ export function asyncAggregateWithRandomness(sets: Array<PkAndSerializedSig>): P
}
);

// cannot reuse pkAndSerializedSigsRefs() due to async nature
const refs = new Uint32Array(sets.length * 2);
writePkAndSerializedSigsReference(sets, refs);

const res = binding.asyncAggregateWithRandomness(
const res = binding.aggregateWithRandomness(
refs,
sets.length,
pkOut.blst_point,
sigOut.blst_point,
pkOut.ptr,
sigOut.ptr,
// it's noted in bun:ffi doc that using JSCallback.prototype.ptr is faster than JSCallback object
jscallback.ptr
);
Expand Down Expand Up @@ -156,7 +153,7 @@ function writePkAndSerializedSigsReference(sets: PkAndSerializedSig[], out: Uint
*
*/
function writePkAndSerializedSigReference(set: PkAndSerializedSig, out: Uint32Array, offset: number): void {
set.pk.writeReference(out, offset);
writeReference(set.pk, out, offset);
writeReference(set.sig, out, offset + 2);
writeNumber(set.sig.length, out, offset + 4);
}
Loading
Loading