Merge pull request #121 from ChainSafe/cayman/esm

ESM Support
This commit is contained in:
Cayman 2022-05-05 15:04:19 +02:00 committed by GitHub
commit a112b21347
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
61 changed files with 1594 additions and 2849 deletions

View File

@ -13,13 +13,15 @@ module.exports = {
},
parser: "@typescript-eslint/parser",
parserOptions: {
ecmaVersion: 10,
project: "./tsconfig.json"
ecmaVersion: "latest",
project: "./tsconfig.json",
sourceType: "module",
},
plugins: [
"@typescript-eslint",
"eslint-plugin-import",
"prettier"
"prettier",
"@chainsafe/eslint-plugin-node"
],
extends: [
"eslint:recommended",
@ -33,13 +35,13 @@ module.exports = {
"prettier/prettier": "error",
//doesnt work, it reports false errors
"constructor-super": "off",
"@typescript-eslint/class-name-casing": "error",
//"@typescript-eslint/class-name-casing": "error",
"@typescript-eslint/explicit-function-return-type": ["error", {
"allowExpressions": true
}],
"@typescript-eslint/func-call-spacing": "error",
"@typescript-eslint/indent": ["error", 2],
"@typescript-eslint/interface-name-prefix": ["error", "always"],
//"@typescript-eslint/interface-name-prefix": ["error", "always"],
"@typescript-eslint/member-ordering": "error",
"@typescript-eslint/no-explicit-any": "error",
"@typescript-eslint/no-require-imports": "error",
@ -47,7 +49,7 @@ module.exports = {
"varsIgnorePattern": "^_",
"argsIgnorePattern": "^_",
}],
"@typescript-eslint/ban-ts-ignore": "warn",
"@typescript-eslint/ban-ts-comment": "warn",
"@typescript-eslint/no-use-before-define": "off",
"@typescript-eslint/semi": "error",
"@typescript-eslint/type-annotation-spacing": "error",
@ -76,7 +78,15 @@ module.exports = {
"no-prototype-builtins": 0,
"prefer-const": "error",
"quotes": ["error", "double"],
"semi": "off"
"semi": "off",
"@chainsafe/node/file-extension-in-import": [
"error",
"always",
{
"esm": true
}
],
"import/no-unresolved": "off",
},
"overrides": [
{

View File

@ -9,7 +9,7 @@ jobs:
strategy:
fail-fast: false
matrix:
node: [12, 14]
node: [14, 16]
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1

View File

@ -1,2 +1,6 @@
extension: ["ts"]
colors: true
require: ts-node/register
node-option:
- "experimental-specifier-resolution=node"
- "loader=ts-node/esm"

View File

@ -2,8 +2,8 @@
[![codecov](https://codecov.io/gh/ChainSafe/lodestar/branch/master/graph/badge.svg)](https://codecov.io/gh/ChainSafe/lodestar)
![ETH2.0_Spec_Version 1.0.0](https://img.shields.io/badge/ETH2.0_Spec_Version-1.0.0-2e86c1.svg)
![ES Version](https://img.shields.io/badge/ES-2017-yellow)
![Node Version](https://img.shields.io/badge/node-12.x-green)
![ES Version](https://img.shields.io/badge/ES-2022-yellow)
![Node Version](https://img.shields.io/badge/node-14.8-green)
Javascript library for BLS (Boneh-Lynn-Shacham) signatures and signature aggregation, tailored for use in Eth2.
@ -19,10 +19,10 @@ To use native bindings you must install peer dependency `@chainsafe/blst`
yarn add @chainsafe/bls @chainsafe/blst
```
You must initialize the library once in your application before using it. The result is cached and use across all your imports
By default, native bindings will be used if in NodeJS and they are installed. A WASM implementation ("herumi") is used as a fallback in case any error occurs.
```ts
import {init, SecretKey, secretKeyToPublicKey, sign, verify} from "@chainsafe/bls";
import {SecretKey, secretKeyToPublicKey, sign, verify} from "@chainsafe/bls";
(async () => {
await init("herumi");
@ -45,48 +45,52 @@ import {init, SecretKey, secretKeyToPublicKey, sign, verify} from "@chainsafe/bl
### Browser
If you are in the browser, import from `/browser` to import directly the WASM version
If you are in the browser, import from `/herumi` to explicitly import the WASM version
```ts
import bls from "@chainsafe/bls/browser";
import bls from "@chainsafe/bls/herumi";
```
### Native bindings only
If you are in NodeJS, import from `/node` to skip browser specific code. Also install peer dependency `@chainsafe/blst` which has the native bindings
If you are in NodeJS, import from `/blst-native` to explicitly import the native bindings. Also install peer dependency `@chainsafe/blst` which has the native bindings
```bash
yarn add @chainsafe/bls @chainsafe/blst
```
```ts
import bls from "@chainsafe/bls/node";
import bls from "@chainsafe/bls/blst-native";
```
### Native bindings + WASM fallback
### Get implementation at runtime
If you want to offer a fallback in NodeJS, first try to load native bindings and then fallback to WASM. Also install peer dependency `@chainsafe/blst` which has the native bindings
```bash
yarn add @chainsafe/bls @chainsafe/blst
```
If you need to get a bls implementation at runtime, import from `/getImplementation`.
```ts
import {init} from "@chainsafe/bls";
import {getImplementation} from "@chainsafe/bls/getImplementation";
try {
await init("blst-native");
} catch (e) {
await init("herumi");
console.warn("Using WASM");
}
const bls = await getImplementation("herumi");
```
### Switchable singleton
If you need a singleton that is switchable at runtime (the default behavior in <=v6), import from `/switchable`.
```ts
import bls, {init} from "@chainsafe/bls/switchable";
// here `bls` is uninitialized
await init("herumi");
// here `bls` is initialized
// now other modules can `import bls from "@chainsafe/bls/switchable"` and it will be initialized
```
The API is identical for all implementations.
## Benchmarks
- `blst`: [src/blst](src/blst) (node.js-only, bindings to C via node-gyp)
- `blst`: [src/blst-native](src/blst-native) (node.js-only, bindings to C via node-gyp)
- `herumi`: [src/herumi](src/herumi) (node.js & browser, wasm)
- `noble`: [noble-bls12-381](https://github.com/paulmillr/noble-bls12-381) (node.js & browser, pure JS)

View File

@ -1,7 +1,7 @@
import {runBenchmark} from "./runner";
import {runForAllImplementations} from "../test/switch";
import {PublicKey, Signature} from "../src/interface";
import {aggCount} from "./params";
import {runBenchmark} from "./runner.js";
import {runForAllImplementations} from "../test/switch.js";
import {PublicKey, Signature} from "../src/types.js";
import {aggCount} from "./params.js";
(async function () {
await runForAllImplementations(async (bls, implementation) => {

View File

@ -1,8 +1,8 @@
import {runBenchmark} from "./runner";
import {range, randomMessage} from "../test/util";
import {runBenchmark} from "./runner.js";
import {range, randomMessage} from "../test/util.js";
import {generateRandomSecretKey} from "@chainsafe/bls-keygen";
import * as noble from "noble-bls12-381";
import {aggCount, runsNoble} from "./params";
import {aggCount, runsNoble} from "./params.js";
(async function () {
{

View File

@ -1,11 +1,12 @@
{
"name": "bls-libs-benchmark",
"version": "1.0.0",
"main": "index.js",
"type": "module",
"exports": "./index.js",
"license": "MIT",
"scripts": {
"benchmark": "ts-node index",
"benchmark:all": "ts-node index && ts-node noble && ts-node verifyMultipleSignaturesSavings"
"benchmark": "ts-node-esm index",
"benchmark:all": "ts-node-esm index && ts-node-esm noble && ts-node-esm verifyMultipleSignaturesSavings"
},
"dependencies": {
"noble-bls12-381": "^0.7.2"

View File

@ -1,5 +1,5 @@
import {runForAllImplementations} from "../test/switch";
import {range, randomMessage} from "../test/util";
import {runForAllImplementations} from "../test/switch.js";
import {range, randomMessage} from "../test/util.js";
(async function () {
console.log("verifyMultipleSignatures savings");

1
blst-native.d.ts vendored
View File

@ -1 +0,0 @@
export * from "./lib/blst";

View File

@ -1 +0,0 @@
module.exports = require("./lib/blst");

1
browser.d.ts vendored
View File

@ -1 +0,0 @@
export * from "./lib/herumi";

View File

@ -1 +0,0 @@
export * from "./lib/herumi";

1
herumi.d.ts vendored
View File

@ -1 +0,0 @@
export * from "./lib/herumi";

View File

@ -1 +0,0 @@
module.exports = require("./lib/herumi");

View File

@ -1,20 +1,29 @@
// eslint-disable-next-line @typescript-eslint/no-require-imports
const webpackConfig = require("./webpack.config");
const webpackConfig = require("./webpack.config.cjs");
module.exports = function (config) {
config.set({
basePath: "",
frameworks: ["mocha", "chai"],
files: ["test/unit-web/run-web-implementation.test.ts", "test/unit/index-named-exports.test.ts"],
frameworks: [
"webpack",
"mocha",
"chai",
],
files: [
"test/unit-web/run-web-implementation.test.ts",
"test/unit/index-named-exports.test.ts",
],
exclude: [],
preprocessors: {
"test/**/*.ts": ["webpack"],
},
webpack: {
mode: "production",
node: webpackConfig.node,
module: webpackConfig.module,
resolve: webpackConfig.resolve,
experiments: webpackConfig.experiments,
optimization: webpackConfig.optimization,
stats: {warnings:false},
},
reporters: ["spec"],

1
node.d.ts vendored
View File

@ -1 +0,0 @@
export * from "./lib/blst";

View File

@ -1 +0,0 @@
module.exports = require("./lib/blst");

View File

@ -2,10 +2,42 @@
"name": "@chainsafe/bls",
"version": "6.0.3",
"description": "Implementation of bls signature verification for ethereum 2.0",
"main": "lib/index.js",
"engines": {
"node": ">=14.8.0"
},
"type": "module",
"exports": {
".": {
"import": "./lib/index.js"
},
"./types": {
"import": "./lib/types.js"
},
"./getImplementation": {
"import": "./lib/getImplementation.js"
},
"./switchable": {
"import": "./lib/switchable.js"
},
"./blst-native": {
"import": "./lib/blst-native/index.js"
},
"./herumi": {
"import": "./lib/herumi/index.js"
}
},
"types": "lib/index.d.ts",
"module": "./browser",
"browser": "./browser",
"typesVersions": {
"*": {
"*": [
"*",
"lib/*",
"lib/*/index"
]
}
},
"module": "./lib/index.js",
"browser": "./lib/herumi.js",
"homepage": "https://github.com/chainsafe/bls",
"author": "ChainSafe Systems",
"license": "Apache-2.0",
@ -24,18 +56,19 @@
],
"scripts": {
"clean": "rm -rf lib && rm -rf dist && rm -f tsconfig.tsbuildinfo",
"check-build": "node -e \"(async function() { await import('./lib/index.js') })()\"",
"build": "tsc --incremental --project tsconfig.build.json",
"lint": "eslint --color --ext .ts src/ test/",
"lint:fix": "yarn run lint --fix",
"prepublishOnly": "yarn build",
"test:web": "karma start",
"test:web": "karma start karma.conf.cjs",
"test:unit": "mocha 'test/unit/**/*.test.ts'",
"test:coverage": "nyc --cache-dir .nyc_output/.cache -r lcov -e .ts mocha 'test/unit/**/*.test.ts' && nyc report",
"test:spec": "mocha 'test/spec/**/*.test.ts'",
"test": "yarn run test:unit && yarn run test:spec",
"download-test-cases": "ts-node test/downloadSpecTests.ts",
"download-test-cases": "ts-node-esm test/downloadSpecTests.ts",
"coverage": "codecov -F bls",
"benchmark": "ts-node benchmark",
"benchmark": "ts-node-esm benchmark",
"benchmark:all": "cd benchmark && yarn install && yarn benchmark:all"
},
"dependencies": {
@ -45,35 +78,38 @@
},
"devDependencies": {
"@chainsafe/blst": "^0.2.0",
"@chainsafe/eslint-plugin-node": "^11.2.3",
"@chainsafe/lodestar-spec-test-util": "^0.18.0",
"@chainsafe/threads": "^1.9.0",
"@types/chai": "^4.2.9",
"@types/mocha": "^8.0.4",
"@types/randombytes": "^2.0.0",
"@typescript-eslint/eslint-plugin": "^2.20.0",
"@typescript-eslint/parser": "^2.20.0",
"chai": "^4.2.0",
"eslint": "^6.8.0",
"@typescript-eslint/eslint-plugin": "^4.31.1",
"@typescript-eslint/parser": "^4.31.1",
"buffer": "^6.0.3",
"chai": "^4.3.6",
"eslint": "^7.14.0",
"eslint-plugin-import": "^2.20.1",
"eslint-plugin-prettier": "^3.1.4",
"karma": "^6.3.16",
"karma": "^6.3.18",
"karma-chai": "^0.1.0",
"karma-chrome-launcher": "^3.1.0",
"karma-cli": "^2.0.0",
"karma-mocha": "^1.3.0",
"karma-mocha": "^2.0.1",
"karma-spec-reporter": "^0.0.32",
"karma-webpack": "^4.0.2",
"mocha": "^8.2.1",
"karma-webpack": "^5.0.0",
"mocha": "^9.2.2",
"nyc": "^15.0.0",
"prettier": "^2.1.2",
"threads": "^1.6.3",
"ts-loader": "^6.2.1",
"ts-node": "^8.6.2",
"typescript": "^3.7.5",
"webpack": "^4.30.0",
"webpack-cli": "^3.3.2"
"resolve-typescript-plugin": "^1.2.0",
"ts-loader": "^9.2.8",
"ts-node": "^10.7.0",
"typescript": "^4.6.3",
"webpack": "^5.72.0",
"webpack-cli": "^4.9.2"
},
"resolutions": {
"mocha": "^8.2.1",
"mocha": "^9.2.2",
"v8-profiler-next": "1.3.0"
},
"peerDependencies": {

View File

@ -1,14 +0,0 @@
const {init} = require("./lib");
// -----------------------------------------
// To be used in NodeJS testing environments
// node -r @chainsafe/bls/register
// -----------------------------------------
// blst-native initialization is syncronous
// Initialize bls here instead of in before() so it's available inside describe() blocks
init("blst-native").catch((e) => {
// eslint-disable-next-line no-console
console.error(e);
process.exit(1);
});

19
src/blst-native/index.ts Normal file
View File

@ -0,0 +1,19 @@
import {SecretKey} from "./secretKey.js";
import {PublicKey} from "./publicKey.js";
import {Signature} from "./signature.js";
import {IBls} from "../types.js";
import {functionalInterfaceFactory} from "../functional.js";
export * from "../constants.js";
export {SecretKey, PublicKey, Signature};
export const bls: IBls = {
implementation: "blst-native",
SecretKey,
PublicKey,
Signature,
...functionalInterfaceFactory({SecretKey, PublicKey, Signature}),
};
export default bls;

View File

@ -1,7 +1,7 @@
import * as blst from "@chainsafe/blst";
import {EmptyAggregateError} from "../errors";
import {bytesToHex, hexToBytes} from "../helpers";
import {PointFormat, PublicKey as IPublicKey} from "../interface";
import {EmptyAggregateError} from "../errors.js";
import {bytesToHex, hexToBytes} from "../helpers/index.js";
import {PointFormat, PublicKey as IPublicKey} from "../types.js";
export class PublicKey extends blst.PublicKey implements IPublicKey {
constructor(value: ConstructorParameters<typeof blst.PublicKey>[0]) {

View File

@ -1,10 +1,10 @@
import * as blst from "@chainsafe/blst";
import {bytesToHex, hexToBytes, isZeroUint8Array, randomBytes} from "../helpers";
import {SECRET_KEY_LENGTH} from "../constants";
import {SecretKey as ISecretKey} from "../interface";
import {PublicKey} from "./publicKey";
import {Signature} from "./signature";
import {ZeroSecretKeyError} from "../errors";
import {bytesToHex, hexToBytes, isZeroUint8Array, randomBytes} from "../helpers/index.js";
import {SECRET_KEY_LENGTH} from "../constants.js";
import {SecretKey as ISecretKey} from "../types.js";
import {PublicKey} from "./publicKey.js";
import {Signature} from "./signature.js";
import {ZeroSecretKeyError} from "../errors.js";
export class SecretKey implements ISecretKey {
readonly value: blst.SecretKey;

View File

@ -1,8 +1,8 @@
import * as blst from "@chainsafe/blst";
import {bytesToHex, hexToBytes} from "../helpers";
import {PointFormat, Signature as ISignature} from "../interface";
import {PublicKey} from "./publicKey";
import {EmptyAggregateError, ZeroSignatureError} from "../errors";
import {bytesToHex, hexToBytes} from "../helpers/index.js";
import {PointFormat, Signature as ISignature} from "../types.js";
import {PublicKey} from "./publicKey.js";
import {EmptyAggregateError, ZeroSignatureError} from "../errors.js";
export class Signature extends blst.Signature implements ISignature {
constructor(value: ConstructorParameters<typeof blst.Signature>[0]) {

View File

@ -1,28 +0,0 @@
import {SecretKey} from "./secretKey";
import {PublicKey} from "./publicKey";
import {Signature} from "./signature";
import {IBls} from "../interface";
import {functionalInterfaceFactory} from "../functional";
export * from "../constants";
export {SecretKey, PublicKey, Signature};
export async function init(): Promise<void> {
// Native bindings require no init() call
}
export function destroy(): void {
// Native bindings require no destroy() call
}
export const bls: IBls = {
implementation: "blst-native",
SecretKey,
PublicKey,
Signature,
...functionalInterfaceFactory({SecretKey, PublicKey, Signature}),
init,
destroy,
};
export default bls;

View File

@ -1,9 +1,10 @@
import {IBls} from "./interface";
import {validateBytes} from "./helpers";
import {NotInitializedError} from "./errors";
import {IBls} from "./types.js";
import {validateBytes} from "./helpers/index.js";
import {NotInitializedError} from "./errors.js";
// Returned type is enforced at each implementation's index
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
// eslint-disable-next-line max-len
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type,@typescript-eslint/explicit-module-boundary-types
export function functionalInterfaceFactory({
SecretKey,
PublicKey,

22
src/getImplementation.ts Normal file
View File

@ -0,0 +1,22 @@
import type {IBls, Implementation} from "./types.js";
// Thanks https://github.com/iliakan/detect-node/blob/master/index.esm.js
const isNode = Object.prototype.toString.call(typeof process !== "undefined" ? process : 0) === "[object process]";
export async function getImplementation(impl: Implementation = "herumi"): Promise<IBls> {
switch (impl) {
case "herumi": {
return (await import("./herumi/index.js")).bls;
}
case "blst-native":
// Lazy import native bindings to prevent automatically importing binding.node files
if (!isNode) {
throw Error("blst-native is only supported in NodeJS");
}
return (await import("./blst-native/index.js")).bls;
default:
throw new Error(`Unsupported implementation - ${impl}`);
}
}

View File

@ -1,2 +1,2 @@
export * from "./hex";
export * from "./utils";
export * from "./hex.js";
export * from "./utils.js";

View File

@ -1,6 +1,6 @@
/* eslint-disable require-atomic-updates */
import bls from "bls-eth-wasm";
import {NotInitializedError} from "../errors";
import {NotInitializedError} from "../errors.js";
type Bls = typeof bls;
let blsGlobal: Bls | null = null;
@ -8,7 +8,6 @@ let blsGlobalPromise: Promise<void> | null = null;
// Patch to fix multiVerify() calls on a browser with polyfilled NodeJS crypto
declare global {
// eslint-disable-next-line @typescript-eslint/interface-name-prefix
interface Window {
msCrypto: typeof window["crypto"];
}

View File

@ -1,10 +1,13 @@
import {SecretKey} from "./secretKey";
import {PublicKey} from "./publicKey";
import {Signature} from "./signature";
import {init, destroy} from "./context";
import {IBls} from "../interface";
import {functionalInterfaceFactory} from "../functional";
export * from "../constants";
import {SecretKey} from "./secretKey.js";
import {PublicKey} from "./publicKey.js";
import {Signature} from "./signature.js";
import {init, destroy} from "./context.js";
import {IBls} from "../types.js";
import {functionalInterfaceFactory} from "../functional.js";
await init();
export * from "../constants.js";
export {SecretKey, PublicKey, Signature, init, destroy};
@ -15,8 +18,6 @@ export const bls: IBls = {
Signature,
...functionalInterfaceFactory({SecretKey, PublicKey, Signature}),
init,
destroy,
};
export default bls;

View File

@ -1,9 +1,9 @@
import {PublicKeyType} from "bls-eth-wasm";
import {getContext} from "./context";
import {bytesToHex, hexToBytes, isZeroUint8Array} from "../helpers";
import {PointFormat, PublicKey as IPublicKey} from "../interface";
import {EmptyAggregateError, InvalidLengthError, ZeroPublicKeyError} from "../errors";
import {PUBLIC_KEY_LENGTH_COMPRESSED, PUBLIC_KEY_LENGTH_UNCOMPRESSED} from "../constants";
import type {PublicKeyType} from "bls-eth-wasm";
import {getContext} from "./context.js";
import {bytesToHex, hexToBytes, isZeroUint8Array} from "../helpers/index.js";
import {PointFormat, PublicKey as IPublicKey} from "../types.js";
import {EmptyAggregateError, InvalidLengthError, ZeroPublicKeyError} from "../errors.js";
import {PUBLIC_KEY_LENGTH_COMPRESSED, PUBLIC_KEY_LENGTH_UNCOMPRESSED} from "../constants.js";
export class PublicKey implements IPublicKey {
readonly value: PublicKeyType;

View File

@ -1,12 +1,12 @@
import {SecretKeyType} from "bls-eth-wasm";
import type {SecretKeyType} from "bls-eth-wasm";
import {generateRandomSecretKey} from "@chainsafe/bls-keygen";
import {SECRET_KEY_LENGTH} from "../constants";
import {getContext} from "./context";
import {PublicKey} from "./publicKey";
import {Signature} from "./signature";
import {bytesToHex, hexToBytes} from "../helpers";
import {SecretKey as ISecretKey} from "../interface";
import {InvalidLengthError, ZeroSecretKeyError} from "../errors";
import {SECRET_KEY_LENGTH} from "../constants.js";
import {getContext} from "./context.js";
import {PublicKey} from "./publicKey.js";
import {Signature} from "./signature.js";
import {bytesToHex, hexToBytes} from "../helpers/index.js";
import {SecretKey as ISecretKey} from "../types.js";
import {InvalidLengthError, ZeroSecretKeyError} from "../errors.js";
export class SecretKey implements ISecretKey {
readonly value: SecretKeyType;
@ -35,7 +35,7 @@ export class SecretKey implements ISecretKey {
}
static fromKeygen(entropy?: Uint8Array): SecretKey {
const sk = generateRandomSecretKey(entropy && Buffer.from(entropy));
const sk = generateRandomSecretKey(entropy);
return this.fromBytes(sk);
}

View File

@ -1,10 +1,10 @@
import {SignatureType, multiVerify} from "bls-eth-wasm";
import {getContext} from "./context";
import {PublicKey} from "./publicKey";
import {bytesToHex, concatUint8Arrays, hexToBytes, isZeroUint8Array} from "../helpers";
import {PointFormat, Signature as ISignature, CoordType} from "../interface";
import {EmptyAggregateError, InvalidLengthError, InvalidOrderError} from "../errors";
import {SIGNATURE_LENGTH_COMPRESSED, SIGNATURE_LENGTH_UNCOMPRESSED} from "../constants";
import type {SignatureType} from "bls-eth-wasm";
import {getContext} from "./context.js";
import {PublicKey} from "./publicKey.js";
import {bytesToHex, concatUint8Arrays, hexToBytes, isZeroUint8Array} from "../helpers/index.js";
import {PointFormat, Signature as ISignature, CoordType} from "../types.js";
import {EmptyAggregateError, InvalidLengthError, InvalidOrderError} from "../errors.js";
import {SIGNATURE_LENGTH_COMPRESSED, SIGNATURE_LENGTH_UNCOMPRESSED} from "../constants.js";
export class Signature implements ISignature {
readonly value: SignatureType;
@ -53,7 +53,8 @@ export class Signature implements ISignature {
}
static verifyMultipleSignatures(sets: {publicKey: PublicKey; message: Uint8Array; signature: Signature}[]): boolean {
return multiVerify(
const context = getContext();
return context.multiVerify(
sets.map((s) => s.publicKey.value),
sets.map((s) => s.signature.value),
sets.map((s) => s.message)

View File

@ -1,4 +1,4 @@
import {bls} from "./index";
import {bls} from "./index.js";
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(function (window: any) {

View File

@ -1,47 +1,14 @@
import {IBls} from "./interface";
import {bls as blsHerumi} from "./herumi";
import type {IBls} from "./types.js";
import {getImplementation} from "./getImplementation.js";
export type Implementation = "herumi" | "blst-native";
// Thanks https://github.com/iliakan/detect-node/blob/master/index.esm.js
const isNode = Object.prototype.toString.call(typeof process !== "undefined" ? process : 0) === "[object process]";
export * from "./interface";
let bls: IBls;
try {
bls = await getImplementation(isNode ? "blst-native" : "herumi");
} catch (e) {
bls = await getImplementation("herumi");
}
// TODO: Use a Proxy for example to throw an error if it's not initialized yet
export const bls: IBls = {} as IBls;
export default bls;
async function getImplementation(impl: Implementation = "herumi"): Promise<IBls> {
switch (impl) {
case "herumi":
await blsHerumi.init();
return blsHerumi;
case "blst-native":
// Lazy import native bindings to prevent automatically importing binding.node files
if (typeof require !== "function") {
throw Error("blst-native is only supported in NodeJS");
}
// eslint-disable-next-line @typescript-eslint/no-require-imports
return require("./blst").bls;
default:
throw new Error(`Unsupported implementation - ${impl}`);
}
}
export async function init(impl: Implementation): Promise<void> {
// Using Object.assign instead of just bls = getImplementation()
// because otherwise the default import breaks. The reference is lost
// and the imported object is still undefined after calling init()
const blsImpl = await getImplementation(impl);
Object.assign(bls, blsImpl);
Object.assign(exports, blsImpl);
}
// Proxy named exports, will get set by `Object.assign(exports, blsImpl)`
export declare let sign: IBls["sign"];
export declare let aggregateSignatures: IBls["aggregateSignatures"];
export declare let aggregatePublicKeys: IBls["aggregatePublicKeys"];
export declare let verify: IBls["verify"];
export declare let verifyAggregate: IBls["verifyAggregate"];
export declare let verifyMultiple: IBls["verifyMultiple"];
export declare let secretKeyToPublicKey: IBls["secretKeyToPublicKey"];

14
src/switchable.ts Normal file
View File

@ -0,0 +1,14 @@
import type {IBls, Implementation} from "./types.js";
import {getImplementation} from "./getImplementation.js";
// TODO: Use a Proxy for example to throw an error if it's not initialized yet
const bls: IBls = {} as IBls;
export default bls;
export async function init(impl: Implementation): Promise<void> {
// Using Object.assign instead of just bls = getImplementation()
// because otherwise the default import breaks. The reference is lost
// and the imported object is still undefined after calling init()
const blsImpl = await getImplementation(impl);
Object.assign(bls, blsImpl);
}

View File

@ -1,8 +1,8 @@
export interface IBls {
implementation: Implementation;
SecretKey: Omit<typeof SecretKey, "prototype">;
PublicKey: Omit<typeof PublicKey, "prototype">;
Signature: Omit<typeof Signature, "prototype">;
SecretKey: typeof SecretKey;
PublicKey: typeof PublicKey;
Signature: typeof Signature;
sign(secretKey: Uint8Array, message: Uint8Array): Uint8Array;
aggregatePublicKeys(publicKeys: Uint8Array[]): Uint8Array;
@ -12,12 +12,11 @@ export interface IBls {
verifyMultiple(publicKeys: Uint8Array[], messages: Uint8Array[], signature: Uint8Array): boolean;
verifyMultipleSignatures(sets: {publicKey: Uint8Array; message: Uint8Array; signature: Uint8Array}[]): boolean;
secretKeyToPublicKey(secretKey: Uint8Array): Uint8Array;
init(): Promise<void>;
destroy(): void;
}
export declare class SecretKey {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private constructor(...value: any);
static fromBytes(bytes: Uint8Array): SecretKey;
static fromHex(hex: string): SecretKey;
static fromKeygen(entropy?: Uint8Array): SecretKey;
@ -28,6 +27,8 @@ export declare class SecretKey {
}
export declare class PublicKey {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private constructor(...value: any);
/** @param type Only for impl `blst-native`. Defaults to `CoordType.jacobian` */
static fromBytes(bytes: Uint8Array, type?: CoordType, validate?: boolean): PublicKey;
static fromHex(hex: string): PublicKey;
@ -38,6 +39,8 @@ export declare class PublicKey {
}
export declare class Signature {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private constructor(...value: any);
/** @param type Only for impl `blst-native`. Defaults to `CoordType.affine`
* @param validate When using `herumi` implementation, signature validation is always on regardless of this flag. */
static fromBytes(bytes: Uint8Array, type?: CoordType, validate?: boolean): Signature;

View File

@ -1,5 +1,5 @@
import {downloadTests} from "@chainsafe/lodestar-spec-test-util";
import {SPEC_TEST_VERSION, SPEC_TESTS_DIR, SPEC_TEST_TO_DOWNLOAD} from "./params";
import {SPEC_TEST_VERSION, SPEC_TESTS_DIR, SPEC_TEST_TO_DOWNLOAD} from "./params.js";
/* eslint-disable no-console */

View File

@ -1,5 +1,8 @@
import path from "path";
import {fileURLToPath} from "url";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
export const SPEC_TEST_VERSION = "v1.0.0";
export const SPEC_TEST_TO_DOWNLOAD = ["general" as "general"];
export const SPEC_TEST_TO_DOWNLOAD = ["general" as const];
export const SPEC_TESTS_DIR = path.join(__dirname, "spec-tests");

View File

@ -1,9 +1,9 @@
import path from "path";
import {describeDirectorySpecTest, InputType} from "@chainsafe/lodestar-spec-test-util";
import {bytesToHex, hexToBytes} from "../../src/helpers";
import {SPEC_TESTS_DIR} from "../params";
import {describeForAllImplementations} from "../switch";
import {EmptyAggregateError} from "../../src/errors";
import {bytesToHex, hexToBytes} from "../../src/helpers/index.js";
import {SPEC_TESTS_DIR} from "../params.js";
import {describeForAllImplementations} from "../switch.js";
import {EmptyAggregateError} from "../../src/errors.js";
interface IAggregateSigsTestCase {
data: {

View File

@ -1,8 +1,8 @@
import path from "path";
import {describeDirectorySpecTest, InputType} from "@chainsafe/lodestar-spec-test-util";
import {hexToBytes} from "../../src/helpers";
import {SPEC_TESTS_DIR} from "../params";
import {describeForAllImplementations} from "../switch";
import {hexToBytes} from "../../src/helpers/index.js";
import {SPEC_TESTS_DIR} from "../params.js";
import {describeForAllImplementations} from "../switch.js";
interface IAggregateSigsVerifyTestCase {
data: {

View File

@ -1,8 +1,8 @@
import path from "path";
import {describeDirectorySpecTest, InputType} from "@chainsafe/lodestar-spec-test-util";
import {hexToBytes} from "../../src/helpers";
import {SPEC_TESTS_DIR} from "../params";
import {describeForAllImplementations} from "../switch";
import {hexToBytes} from "../../src/helpers/index.js";
import {SPEC_TESTS_DIR} from "../params.js";
import {describeForAllImplementations} from "../switch.js";
import {CoordType} from "@chainsafe/blst";
interface IAggregateSigsVerifyTestCase {

View File

@ -1,9 +1,9 @@
import path from "path";
import {describeDirectorySpecTest, InputType} from "@chainsafe/lodestar-spec-test-util";
import {bytesToHex, hexToBytes} from "../../src/helpers";
import {SPEC_TESTS_DIR} from "../params";
import {describeForAllImplementations} from "../switch";
import {ZeroSecretKeyError} from "../../src/errors";
import {bytesToHex, hexToBytes} from "../../src/helpers/index.js";
import {SPEC_TESTS_DIR} from "../params.js";
import {describeForAllImplementations} from "../switch.js";
import {ZeroSecretKeyError} from "../../src/errors.js";
interface ISignMessageTestCase {
data: {

View File

@ -1,8 +1,8 @@
import path from "path";
import {describeDirectorySpecTest, InputType} from "@chainsafe/lodestar-spec-test-util";
import {hexToBytes} from "../../src/helpers";
import {SPEC_TESTS_DIR} from "../params";
import {describeForAllImplementations} from "../switch";
import {hexToBytes} from "../../src/helpers/index.js";
import {SPEC_TESTS_DIR} from "../params.js";
import {describeForAllImplementations} from "../switch.js";
interface IVerifyTestCase {
data: {

View File

@ -1,6 +1,6 @@
import blst from "../src/blst";
import herumi from "../src/herumi";
import {IBls} from "../src/interface";
import blst from "../src/blst-native/index.js";
import herumi from "../src/herumi/index.js";
import {IBls} from "../src/types.js";
export type Implementation = "blst" | "herumi";
@ -19,7 +19,6 @@ export async function runForAllImplementations(
): Promise<void> {
for (const implementation of ["blst", "herumi"] as Implementation[]) {
const bls = getBls(implementation);
await bls.init();
callback(bls, implementation);
}
}
@ -27,10 +26,6 @@ export async function runForAllImplementations(
export function describeForAllImplementations(callback: (bls: IBls) => void): void {
runForAllImplementations((bls, implementation) => {
describe(implementation, function () {
before(async () => {
await bls.init();
});
try {
callback(bls);
} catch (e) {

View File

@ -1,18 +1,12 @@
import herumi from "../../src/herumi";
import {runSecretKeyTests} from "../unit/secretKey.test";
import {runPublicKeyTests} from "../unit/publicKey.test";
import {runIndexTests} from "../unit/index.test";
import {runSecretKeyTests} from "../unit/secretKey.test.js";
import {runPublicKeyTests} from "../unit/publicKey.test.js";
import {runIndexTests} from "../unit/index.test.js";
// This file is intended to be compiled and run by Karma
// Do not import the node.bindings or it will break with:
// Error: BLST bindings loader should only run in a NodeJS context: process.platform
describe("herumi", () => {
before(async () => {
// For consistency with describeForAllImplementations
// eslint-disable-next-line import/no-named-as-default-member
await herumi.init();
});
describe("herumi", async () => {
const herumi = (await import("../../src/herumi/index.js")).default;
runSecretKeyTests(herumi);
runPublicKeyTests(herumi);
runIndexTests(herumi);

View File

@ -1,6 +1,6 @@
import {expect} from "chai";
import {concatUint8Arrays, isZeroUint8Array} from "../../../src/helpers/utils";
import {hexToBytesNode} from "../../util";
import {concatUint8Arrays, isZeroUint8Array} from "../../../src/helpers/utils.js";
import {hexToBytesNode} from "../../util.js";
describe("helpers / bytes", () => {
describe("isZeroUint8Array", () => {

View File

@ -1,6 +1,6 @@
import {expect} from "chai";
import {hexToBytes, bytesToHex} from "../../../src/helpers/hex";
import {hexToBytesNode} from "../../util";
import {hexToBytes, bytesToHex} from "../../../src/helpers/hex.js";
import {hexToBytesNode} from "../../util.js";
describe("helpers / hex", () => {
const testCases: {id: string; hex: string}[] = [

View File

@ -1,10 +1,14 @@
import {expect} from "chai";
import {SecretKey, PublicKey, Signature, init, bls} from "../../src";
import type {SecretKey, PublicKey, Signature, IBls} from "../../src/types.js";
describe("types named exports", async () => {
let bls: IBls;
before(async () => {
bls = (await import("../../src/index.js")).default;
});
describe("index named exports", () => {
it("Classes and methods should be defined", async () => {
await init("herumi");
/**
* Sample helper to test argument typing
*/
@ -12,7 +16,7 @@ describe("index named exports", () => {
return sig.verify(pk, msg);
}
const sk = SecretKey.fromKeygen();
const sk = bls.SecretKey.fromKeygen();
const msg = new Uint8Array(32);
const sig = sk.sign(msg);
const pk = sk.toPublicKey();

View File

@ -1,8 +1,9 @@
import {expect} from "chai";
import {IBls, PointFormat} from "../../src/interface";
import {getN, randomMessage, hexToBytesNode} from "../util";
import {hexToBytes} from "../../src/helpers";
import {maliciousVerifyMultipleSignaturesData} from "../data/malicious-signature-test-data";
import {Buffer} from "buffer";
import {IBls, PointFormat} from "../../src/types.js";
import {getN, randomMessage} from "../util.js";
import {hexToBytes} from "../../src/helpers/index.js";
import {maliciousVerifyMultipleSignaturesData} from "../data/malicious-signature-test-data.js";
export function runIndexTests(bls: IBls): void {
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
@ -178,13 +179,13 @@ export function runIndexTests(bls: IBls): void {
"0x0a1a1c26055a329817a5759d877a2795f9499b97d6056edde0eea39512f24e8bc874b4471f0501127abb1ea0d9f68ac111392125a1c3750363c2c97d9650fb78696e6428db8ff9efaf0471cbfd20324916ab545746db83756d335e92f9e8c8b8";
it("Should serialize comp pubkey", () => {
const sk = bls.SecretKey.fromBytes(hexToBytesNode(skHex));
const sk = bls.SecretKey.fromBytes(hexToBytes(skHex));
const pkHexComp = sk.toPublicKey().toHex(PointFormat.compressed);
expect(pkHexComp).to.equal(pkHexCompExpected, "Wrong pkHexComp");
});
it("Should serialize uncomp pubkey", () => {
const sk = bls.SecretKey.fromBytes(hexToBytesNode(skHex));
const sk = bls.SecretKey.fromBytes(hexToBytes(skHex));
const pkHexUncomp = sk.toPublicKey().toHex(PointFormat.uncompressed);
expect(pkHexUncomp).to.equal(pkHexUncompExpected, "Wrong pkHexUncomp");
});

View File

@ -1,5 +1,5 @@
import {expect} from "chai";
import {chunkify} from "./utils";
import {chunkify} from "./utils.js";
describe("chunkify", () => {
const arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 13, 14, 15];

View File

@ -1,11 +1,16 @@
import {spawn, Pool, Worker, Thread} from "threads";
import {Implementation, PointFormat, PublicKey, Signature} from "../../../../src";
import {WorkerApi} from "./worker";
import {spawn, Pool, Worker, Thread} from "@chainsafe/threads";
import {Implementation, PointFormat, PublicKey, Signature} from "../../../../src/types.js";
import {WorkerApi} from "./worker.js";
type ThreadType = {
[K in keyof WorkerApi]: (...args: Parameters<WorkerApi[K]>) => Promise<ReturnType<WorkerApi[K]>>;
};
import path from "path";
import {fileURLToPath} from "url";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
export class BlsMultiThreadNaive {
impl: Implementation;
pool: Pool<Thread & ThreadType>;
@ -17,7 +22,18 @@ export class BlsMultiThreadNaive {
// THe worker is not able to deserialize from uncompressed
// `Error: err _wrapDeserialize`
this.format = impl === "blst-native" ? PointFormat.uncompressed : PointFormat.compressed;
this.pool = Pool(() => (spawn(new Worker("./worker")) as any) as Promise<Thread & ThreadType>, workerCount);
this.pool = Pool(
() =>
(spawn(
// There is still an annoyance dealing with ESM imports here:
// threads.js attempts to require.resolve any files passed to Worker, and
// the esm module resolver requires the .js extension, even though the .js file does not actually exist.
// The solution for now:
// Pass in the script path as an absolute path and suppress threads.js default behavior when importing
new Worker(path.join(__dirname, "./worker.js"), {suppressResolveScript: true, suppressTranspileTS: true})
) as any) as Promise<Thread & ThreadType>,
workerCount
);
}
async destroy(): Promise<void> {

View File

@ -1,6 +1,7 @@
import {expect} from "chai";
import {IBls, PublicKey, Signature} from "../../../../src";
import {BlsMultiThreadNaive} from "./index";
import {IBls} from "../../../../src/types.js";
import type {PublicKey, Signature} from "../../../../src/types.js";
import {BlsMultiThreadNaive} from "./index.js";
export function runMultithreadTests(bls: IBls): void {
describe("bls pool naive", function () {

View File

@ -1,5 +1,6 @@
import {expose} from "threads/worker";
import {bls, init, CoordType, Implementation} from "../../../../src";
import {expose} from "@chainsafe/threads/worker";
import {CoordType, Implementation} from "../../../../src/types.js";
import bls, {init} from "../../../../src/switchable.js";
export type WorkerApi = typeof workerApi;

View File

@ -1,5 +1,5 @@
import {expect} from "chai";
import {IBls} from "../../src/interface";
import {IBls} from "../../src/types.js";
export function runPublicKeyTests(bls: IBls): void {
describe("PublicKey", () => {

View File

@ -2,7 +2,7 @@ import {runSecretKeyTests} from "./secretKey.test";
import {runPublicKeyTests} from "./publicKey.test";
import {runIndexTests} from "./index.test";
import {runMultithreadTests} from "./multithread/naive/naive.test";
import {describeForAllImplementations} from "../switch";
import {describeForAllImplementations} from "../switch.js";
// Import test's bls lib lazily to prevent breaking test with Karma
describeForAllImplementations((bls) => {

View File

@ -1,5 +1,5 @@
import {expect} from "chai";
import {IBls} from "../../src/interface";
import {IBls} from "../../src/types.js";
export function runSecretKeyTests(bls: IBls): void {
describe("SecretKey", () => {

View File

@ -1,4 +1,4 @@
import {randomBytes} from "../src/helpers";
import {randomBytes} from "../src/helpers/index.js";
export function randomMessage(): Uint8Array {
return randomBytes(32);

View File

@ -3,7 +3,8 @@
"compilerOptions": {
"outDir": "lib",
"target": "es2019",
"module": "commonjs",
"module": "esnext",
"moduleResolution": "Node",
"pretty": true,
"lib": ["esnext.bigint", "DOM"],
"typeRoots": ["./node_modules/@types"],

29
webpack.config.cjs Normal file
View File

@ -0,0 +1,29 @@
const ResolveTypeScriptPlugin = require("resolve-typescript-plugin");
module.exports = {
entry: "./src/index.ts",
mode: "production",
output: {
filename: "dist/bundle.js",
},
module: {
rules: [{test: /\.(ts)$/, use: {loader: "ts-loader", options: {transpileOnly: true}}}],
},
optimization: {
// Disable minification for better debugging on Karma tests
minimize: false,
//splitChunks: false, runtimeChunk: false,
},
devtool: "source-map",
resolve: {
plugins: [new ResolveTypeScriptPlugin()],
fallback: {
crypto: false,
fs: false,
path: false,
},
},
experiments: {
topLevelAwait: true,
},
};

View File

@ -1,21 +0,0 @@
module.exports = {
entry: "./src/index.ts",
mode: "production",
node: {
fs: "empty",
},
output: {
filename: "dist/bundle.js",
},
resolve: {
extensions: [".ts", ".js"],
},
module: {
rules: [{test: /\.ts$/, use: {loader: "ts-loader", options: {transpileOnly: true}}}],
},
optimization: {
// Disable minification for better debugging on Karma tests
minimize: false,
},
devtool: "source-map",
};

3760
yarn.lock

File diff suppressed because it is too large Load Diff