Compare commits
79 Commits
Author | SHA1 | Date |
---|---|---|
Derrick Hammer | 99f7fe9976 | |
Derrick Hammer | 1028fc1bb0 | |
Derrick Hammer | a74045db4a | |
Derrick Hammer | d58fc82e08 | |
Lion - dapplion | 902896968f | |
Lion - dapplion | 86078f9b6d | |
Lion - dapplion | c4ea70afd0 | |
Phil Ngo | 710e7f9f5e | |
dadepo | 6057e93208 | |
dependabot[bot] | e3ba38c938 | |
dependabot[bot] | fbe7e36355 | |
dependabot[bot] | 25f9cb8c48 | |
Cayman | 04931736ff | |
Cayman | 87ecb9c523 | |
Cayman | 097068b713 | |
Cayman | 7a40f17c64 | |
Cayman | 42cbc2b1e4 | |
Cayman | 86792ee65e | |
Cayman | 4747cbc573 | |
Cayman | 41ef77a071 | |
dependabot[bot] | c1949c9c8c | |
Cayman | 23d9388232 | |
Cayman | acacf17c4a | |
Cayman | b7a66ff845 | |
Cayman | 42022a176f | |
Cayman | a112b21347 | |
Cayman | 35b0028ca7 | |
dependabot[bot] | 0500f74eb5 | |
dependabot[bot] | f62d26c665 | |
dependabot[bot] | c9d077d4f4 | |
dependabot[bot] | 5e95102038 | |
Lion - dapplion | 924322f651 | |
dependabot[bot] | 2fc5d60f85 | |
Lion - dapplion | 89f857a40d | |
dependabot[bot] | 24555b5ada | |
Cayman | 1845268ed0 | |
Cayman | 8678f48dd5 | |
Marin Petrunic | ed9f26f593 | |
Marin Petrunic | 35bf7684d7 | |
Cayman | 7c2e883527 | |
Cayman | 8e8447d2f9 | |
Cayman | 269c96832b | |
Cayman | 501f93e333 | |
Cayman | a8aa891768 | |
Cayman | b02fbc57a0 | |
Cayman | 9a851a095a | |
Cayman | 31c236ab05 | |
Phil Ngo | 5d9ca0fb0b | |
Cayman | 8881334330 | |
Cayman | 0056e13ee5 | |
Cayman | 53df67a2dd | |
Cayman | e88ae5b04a | |
Paul Miller | 1459ad5052 | |
Lion - dapplion | 474160eb68 | |
g11tech | 97d0047b58 | |
Lion - dapplion | dae5bb6fab | |
g11tech | 40fc4b33a2 | |
g11tech | 5731f49454 | |
g11tech | 6854661b29 | |
Cayman | bb1449d878 | |
dapplion | b02b7aabdc | |
Cayman | f830b41ecc | |
dapplion | 61ce6b1ead | |
Marin Petrunić | 5d323c0d62 | |
dependabot[bot] | e23dd96158 | |
Marin Petrunić | d90f62c97c | |
dependabot[bot] | 4064e28d94 | |
Cayman | a158b704ea | |
3xtr4t3rr3str14l | 67c43fbb5f | |
Marin Petrunić | 1f5c183d9b | |
Marin Petrunić | 3de12e402b | |
Marin Petrunić | f7096f9ac9 | |
dependabot[bot] | 8ecdf5e27b | |
dependabot[bot] | 6820df9590 | |
dependabot[bot] | 76c3dab8fc | |
Lion - dapplion | d0957daff1 | |
dapplion | 9c0e071405 | |
Cayman | 30eea7095e | |
dapplion | adf89c8360 |
|
@ -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,20 +35,21 @@ 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",
|
||||
"@typescript-eslint/no-unused-vars": ["error", {
|
||||
"varsIgnorePattern": "^_"
|
||||
"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",
|
||||
|
@ -75,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": [
|
||||
{
|
|
@ -2,10 +2,10 @@
|
|||
# the repo. Unless a later match takes precedence,
|
||||
# They will be requested for
|
||||
# review when someone opens a pull request.
|
||||
* @wemeetagain @mpetrunic @dapplion
|
||||
* @ChainSafe/lodestar
|
||||
|
||||
# Order is important; the last matching pattern takes the most
|
||||
# precedence. When someone opens a pull request that only
|
||||
# modifies md files, only md owners and not the global
|
||||
# owner(s) will be requested for a review.
|
||||
*.md @ColinSchwarz @wemeetagain @mpetrunic @dapplion
|
||||
*.md @ChainSafe/lodestar
|
||||
|
|
|
@ -1 +1 @@
|
|||
custom: https://gitcoin.co/grants/79/lodestar-eth20-client
|
||||
custom: https://gitcoin.co/grants/6034/lodestar-typescript-ethereum-consensus-client
|
||||
|
|
|
@ -12,18 +12,17 @@ onlyLabels: []
|
|||
|
||||
# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable
|
||||
exemptLabels:
|
||||
- "PR state: on-ice"
|
||||
- "Good First Issue"
|
||||
- "Status: On Ice"
|
||||
- "Priority: 4 - Low"
|
||||
- "Priority: 3 - Medium"
|
||||
- "Priority: 2 - High"
|
||||
- "Priority: 1 - Critical"
|
||||
- "discussion"
|
||||
- "Discussion"
|
||||
- "Epic"
|
||||
- "Good First Issue"
|
||||
- "help wanted"
|
||||
- "Epic"
|
||||
- "meta-good-first-issue"
|
||||
- "meta-help-wanted"
|
||||
- "meta-discussion"
|
||||
- "meta-pm"
|
||||
- "prio-critical"
|
||||
- "prio-high"
|
||||
- "prio-medium"
|
||||
- "prio-low"
|
||||
- "status-blocked"
|
||||
- "status-do-not-merge"
|
||||
|
||||
# Set to true to ignore issues in a project (defaults to false)
|
||||
exemptProjects: false
|
||||
|
@ -35,7 +34,7 @@ exemptMilestones: true
|
|||
exemptAssignees: true
|
||||
|
||||
# Label to use when marking as stale
|
||||
staleLabel: bot:stale
|
||||
staleLabel: meta-stale
|
||||
|
||||
# Comment to post when marking as stale. Set to `false` to disable
|
||||
markComment: >
|
||||
|
|
|
@ -34,7 +34,7 @@ jobs:
|
|||
- name: Setup Nodejs
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: "12.x"
|
||||
node-version: "14.x"
|
||||
registry-url: "https://registry.npmjs.org"
|
||||
- name: Install dependencies
|
||||
run: yarn install --non-interactive --frozen-lockfile
|
||||
|
@ -62,4 +62,4 @@ jobs:
|
|||
tag: ${{ needs.tag.outputs.tag }}
|
||||
delete_orphan_tag: true
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1,2 +1,6 @@
|
|||
extension: ["ts"]
|
||||
colors: true
|
||||
require: ts-node/register
|
||||
node-option:
|
||||
- "experimental-specifier-resolution=node"
|
||||
- "loader=ts-node/esm"
|
||||
|
||||
|
|
32
CHANGELOG.md
32
CHANGELOG.md
|
@ -5,7 +5,37 @@ All notable changes to this project will be documented in this file.
|
|||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [dev]
|
||||
## [7.1.1] - 2022-05-15
|
||||
|
||||
### Chores
|
||||
|
||||
- bump blst peer dependency [#130](https://github.com/ChainSafe/bls/pull/130)
|
||||
|
||||
## [7.1.0] - 2022-05-09
|
||||
|
||||
### Features
|
||||
|
||||
- add errors and constants to exports [#128](https://github.com/ChainSafe/bls/pull/128)
|
||||
|
||||
## [7.0.0] - 2022-05-05
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
- ESM Support [#121](https://github.com/ChainSafe/bls/pull/121)
|
||||
|
||||
## [6.0.3] - 2021-09-25
|
||||
|
||||
- bls-eth-wasm (herumi) package update to 0.4.8 for invalidating signature not in G2 [#106](https://github.com/ChainSafe/bls/pull/106)
|
||||
- Signature.fromBytes now has default verification on [#106](https://github.com/ChainSafe/bls/pull/106)
|
||||
|
||||
|
||||
## [6.0.2] - 2021-08-23
|
||||
|
||||
- Add register script [#102](https://github.com/ChainSafe/bls/pull/102)
|
||||
|
||||
## [6.0.1] - 2021-04-05
|
||||
|
||||
- Add validate key option to PublicKey.fromBytes() [#90](https://github.com/ChainSafe/bls/pull/90)
|
||||
|
||||
## [6.0.0] - 2021-04-05
|
||||
|
||||
|
|
73
README.md
73
README.md
|
@ -1,10 +1,9 @@
|
|||
# bls
|
||||
|
||||
[![Build Status](https://travis-ci.org/ChainSafe/lodestar.svg?branch=master)](https://travis-ci.org/ChainSafe/lodestar)
|
||||
[![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.
|
||||
|
||||
|
@ -20,74 +19,76 @@ 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 bls from "@chainsafe/bls";
|
||||
|
||||
(async () => {
|
||||
await init("herumi");
|
||||
// class-based interface
|
||||
const secretKey = bls.SecretKey.fromKeygen();
|
||||
const publicKey = secretKey.toPublicKey();
|
||||
const message = new Uint8Array(32);
|
||||
|
||||
// class-based interface
|
||||
const secretKey = SecretKey.fromKeygen();
|
||||
const publicKey = secretKey.toPublicKey();
|
||||
const message = new Uint8Array(32);
|
||||
const signature = secretKey.sign(message);
|
||||
console.log("Is valid: ", signature.verify(publicKey, message));
|
||||
|
||||
const signature = secretKey.sign(message);
|
||||
console.log("Is valid: ", signature.verify(publicKey, message));
|
||||
|
||||
// functional interface
|
||||
const sk = secretKey.toBytes();
|
||||
const pk = secretKeyToPublicKey(sk);
|
||||
const sig = sign(sk, message);
|
||||
console.log("Is valid: ", verify(pk, message, sig));
|
||||
// functional interface
|
||||
const sk = secretKey.toBytes();
|
||||
const pk = bls.secretKeyToPublicKey(sk);
|
||||
const sig = bls.sign(sk, message);
|
||||
console.log("Is valid: ", bls.verify(pk, message, sig));
|
||||
})();
|
||||
```
|
||||
|
||||
### 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)
|
||||
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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 () {
|
||||
{
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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 +0,0 @@
|
|||
export * from "./lib/blst";
|
|
@ -1 +0,0 @@
|
|||
module.exports = require("./lib/blst");
|
|
@ -1 +0,0 @@
|
|||
export * from "./lib/herumi";
|
|
@ -1 +0,0 @@
|
|||
export * from "./lib/herumi";
|
|
@ -1 +0,0 @@
|
|||
export * from "./lib/herumi";
|
|
@ -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"],
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
import { SecretKey } from "./secretKey.js";
|
||||
import { PublicKey } from "./publicKey.js";
|
||||
import { Signature } from "./signature.js";
|
||||
import { IBls } from "../types.js";
|
||||
export * from "../constants.js";
|
||||
export { SecretKey, PublicKey, Signature };
|
||||
export declare const bls: IBls;
|
||||
export default bls;
|
|
@ -0,0 +1,14 @@
|
|||
import { SecretKey } from "./secretKey.js";
|
||||
import { PublicKey } from "./publicKey.js";
|
||||
import { Signature } from "./signature.js";
|
||||
import { functionalInterfaceFactory } from "../functional.js";
|
||||
export * from "../constants.js";
|
||||
export { SecretKey, PublicKey, Signature };
|
||||
export const bls = {
|
||||
implementation: "blst-native",
|
||||
SecretKey,
|
||||
PublicKey,
|
||||
Signature,
|
||||
...functionalInterfaceFactory({ SecretKey, PublicKey, Signature }),
|
||||
};
|
||||
export default bls;
|
|
@ -0,0 +1,11 @@
|
|||
import * as blst from "@chainsafe/blst";
|
||||
import { PointFormat, PublicKey as IPublicKey } from "../types.js";
|
||||
export declare class PublicKey extends blst.PublicKey implements IPublicKey {
|
||||
constructor(value: ConstructorParameters<typeof blst.PublicKey>[0]);
|
||||
/** @param type Defaults to `CoordType.jacobian` */
|
||||
static fromBytes(bytes: Uint8Array, type?: blst.CoordType, validate?: boolean): PublicKey;
|
||||
static fromHex(hex: string): PublicKey;
|
||||
static aggregate(publicKeys: PublicKey[]): PublicKey;
|
||||
toBytes(format?: PointFormat): Uint8Array;
|
||||
toHex(format?: PointFormat): string;
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
import * as blst from "@chainsafe/blst";
|
||||
import { EmptyAggregateError } from "../errors.js";
|
||||
import { bytesToHex, hexToBytes } from "../helpers/index.js";
|
||||
import { PointFormat } from "../types.js";
|
||||
export class PublicKey extends blst.PublicKey {
|
||||
constructor(value) {
|
||||
super(value);
|
||||
}
|
||||
/** @param type Defaults to `CoordType.jacobian` */
|
||||
static fromBytes(bytes, type, validate) {
|
||||
const pk = blst.PublicKey.fromBytes(bytes, type);
|
||||
if (validate)
|
||||
pk.keyValidate();
|
||||
return new PublicKey(pk.value);
|
||||
}
|
||||
static fromHex(hex) {
|
||||
return this.fromBytes(hexToBytes(hex));
|
||||
}
|
||||
static aggregate(publicKeys) {
|
||||
if (publicKeys.length === 0) {
|
||||
throw new EmptyAggregateError();
|
||||
}
|
||||
const pk = blst.aggregatePubkeys(publicKeys);
|
||||
return new PublicKey(pk.value);
|
||||
}
|
||||
toBytes(format) {
|
||||
if (format === PointFormat.uncompressed) {
|
||||
return this.value.serialize();
|
||||
}
|
||||
else {
|
||||
return this.value.compress();
|
||||
}
|
||||
}
|
||||
toHex(format) {
|
||||
return bytesToHex(this.toBytes(format));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
import * as blst from "@chainsafe/blst";
|
||||
import { SecretKey as ISecretKey } from "../types.js";
|
||||
import { PublicKey } from "./publicKey.js";
|
||||
import { Signature } from "./signature.js";
|
||||
export declare class SecretKey implements ISecretKey {
|
||||
readonly value: blst.SecretKey;
|
||||
constructor(value: blst.SecretKey);
|
||||
static fromBytes(bytes: Uint8Array): SecretKey;
|
||||
static fromHex(hex: string): SecretKey;
|
||||
static fromKeygen(entropy?: Uint8Array): SecretKey;
|
||||
sign(message: Uint8Array): Signature;
|
||||
toPublicKey(): PublicKey;
|
||||
toBytes(): Uint8Array;
|
||||
toHex(): string;
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
import * as blst from "@chainsafe/blst";
|
||||
import { bytesToHex, hexToBytes, isZeroUint8Array, randomBytes } from "../helpers/index.js";
|
||||
import { SECRET_KEY_LENGTH } from "../constants.js";
|
||||
import { PublicKey } from "./publicKey.js";
|
||||
import { Signature } from "./signature.js";
|
||||
import { ZeroSecretKeyError } from "../errors.js";
|
||||
export class SecretKey {
|
||||
constructor(value) {
|
||||
this.value = value;
|
||||
}
|
||||
static fromBytes(bytes) {
|
||||
// draft-irtf-cfrg-bls-signature-04 does not allow SK == 0
|
||||
if (isZeroUint8Array(bytes)) {
|
||||
throw new ZeroSecretKeyError();
|
||||
}
|
||||
const sk = blst.SecretKey.fromBytes(bytes);
|
||||
return new SecretKey(sk);
|
||||
}
|
||||
static fromHex(hex) {
|
||||
return this.fromBytes(hexToBytes(hex));
|
||||
}
|
||||
static fromKeygen(entropy) {
|
||||
const sk = blst.SecretKey.fromKeygen(entropy || randomBytes(SECRET_KEY_LENGTH));
|
||||
return new SecretKey(sk);
|
||||
}
|
||||
sign(message) {
|
||||
return new Signature(this.value.sign(message).value);
|
||||
}
|
||||
toPublicKey() {
|
||||
const pk = this.value.toPublicKey();
|
||||
return new PublicKey(pk.value);
|
||||
}
|
||||
toBytes() {
|
||||
return this.value.toBytes();
|
||||
}
|
||||
toHex() {
|
||||
return bytesToHex(this.toBytes());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
import * as blst from "@chainsafe/blst";
|
||||
import { PointFormat, Signature as ISignature } from "../types.js";
|
||||
import { PublicKey } from "./publicKey.js";
|
||||
export declare class Signature extends blst.Signature implements ISignature {
|
||||
constructor(value: ConstructorParameters<typeof blst.Signature>[0]);
|
||||
/** @param type Defaults to `CoordType.affine` */
|
||||
static fromBytes(bytes: Uint8Array, type?: blst.CoordType, validate?: boolean): Signature;
|
||||
static fromHex(hex: string): Signature;
|
||||
static aggregate(signatures: Signature[]): Signature;
|
||||
static verifyMultipleSignatures(sets: {
|
||||
publicKey: PublicKey;
|
||||
message: Uint8Array;
|
||||
signature: Signature;
|
||||
}[]): boolean;
|
||||
verify(publicKey: PublicKey, message: Uint8Array): boolean;
|
||||
verifyAggregate(publicKeys: PublicKey[], message: Uint8Array): boolean;
|
||||
verifyMultiple(publicKeys: PublicKey[], messages: Uint8Array[]): boolean;
|
||||
toBytes(format?: PointFormat): Uint8Array;
|
||||
toHex(format?: PointFormat): string;
|
||||
private aggregateVerify;
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
import * as blst from "@chainsafe/blst";
|
||||
import { bytesToHex, hexToBytes } from "../helpers/index.js";
|
||||
import { PointFormat } from "../types.js";
|
||||
import { EmptyAggregateError, ZeroSignatureError } from "../errors.js";
|
||||
export class Signature extends blst.Signature {
|
||||
constructor(value) {
|
||||
super(value);
|
||||
}
|
||||
/** @param type Defaults to `CoordType.affine` */
|
||||
static fromBytes(bytes, type, validate = true) {
|
||||
const sig = blst.Signature.fromBytes(bytes, type);
|
||||
if (validate)
|
||||
sig.sigValidate();
|
||||
return new Signature(sig.value);
|
||||
}
|
||||
static fromHex(hex) {
|
||||
return this.fromBytes(hexToBytes(hex));
|
||||
}
|
||||
static aggregate(signatures) {
|
||||
if (signatures.length === 0) {
|
||||
throw new EmptyAggregateError();
|
||||
}
|
||||
const agg = blst.aggregateSignatures(signatures);
|
||||
return new Signature(agg.value);
|
||||
}
|
||||
static verifyMultipleSignatures(sets) {
|
||||
return blst.verifyMultipleAggregateSignatures(sets.map((s) => ({ msg: s.message, pk: s.publicKey, sig: s.signature })));
|
||||
}
|
||||
verify(publicKey, message) {
|
||||
// Individual infinity signatures are NOT okay. Aggregated signatures MAY be infinity
|
||||
if (this.value.is_inf()) {
|
||||
throw new ZeroSignatureError();
|
||||
}
|
||||
return blst.verify(message, publicKey, this);
|
||||
}
|
||||
verifyAggregate(publicKeys, message) {
|
||||
return blst.fastAggregateVerify(message, publicKeys, this);
|
||||
}
|
||||
verifyMultiple(publicKeys, messages) {
|
||||
return blst.aggregateVerify(messages, publicKeys, this);
|
||||
}
|
||||
toBytes(format) {
|
||||
if (format === PointFormat.uncompressed) {
|
||||
return this.value.serialize();
|
||||
}
|
||||
else {
|
||||
return this.value.compress();
|
||||
}
|
||||
}
|
||||
toHex(format) {
|
||||
return bytesToHex(this.toBytes(format));
|
||||
}
|
||||
aggregateVerify(msgs, pks) {
|
||||
// If this set is simply an infinity signature and infinity publicKey then skip verification.
|
||||
// This has the effect of always declaring that this sig/publicKey combination is valid.
|
||||
// for Eth2.0 specs tests
|
||||
if (this.value.is_inf() && pks.length === 1 && pks[0].value.is_inf()) {
|
||||
return true;
|
||||
}
|
||||
return blst.aggregateVerify(msgs, pks, this);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
export declare const SECRET_KEY_LENGTH = 32;
|
||||
export declare const PUBLIC_KEY_LENGTH_COMPRESSED = 48;
|
||||
export declare const PUBLIC_KEY_LENGTH_UNCOMPRESSED: number;
|
||||
export declare const SIGNATURE_LENGTH_COMPRESSED = 96;
|
||||
export declare const SIGNATURE_LENGTH_UNCOMPRESSED: number;
|
|
@ -0,0 +1,5 @@
|
|||
export const SECRET_KEY_LENGTH = 32;
|
||||
export const PUBLIC_KEY_LENGTH_COMPRESSED = 48;
|
||||
export const PUBLIC_KEY_LENGTH_UNCOMPRESSED = 48 * 2;
|
||||
export const SIGNATURE_LENGTH_COMPRESSED = 96;
|
||||
export const SIGNATURE_LENGTH_UNCOMPRESSED = 96 * 2;
|
|
@ -0,0 +1,25 @@
|
|||
/**
|
||||
* This error should not be ignored by the functional interface
|
||||
* try / catch blocks, to prevent false negatives
|
||||
*/
|
||||
export declare class NotInitializedError extends Error {
|
||||
constructor(implementation: string);
|
||||
}
|
||||
export declare class ZeroSecretKeyError extends Error {
|
||||
constructor();
|
||||
}
|
||||
export declare class ZeroPublicKeyError extends Error {
|
||||
constructor();
|
||||
}
|
||||
export declare class ZeroSignatureError extends Error {
|
||||
constructor();
|
||||
}
|
||||
export declare class EmptyAggregateError extends Error {
|
||||
constructor();
|
||||
}
|
||||
export declare class InvalidOrderError extends Error {
|
||||
constructor();
|
||||
}
|
||||
export declare class InvalidLengthError extends Error {
|
||||
constructor(arg: string, length: number);
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
/**
|
||||
* This error should not be ignored by the functional interface
|
||||
* try / catch blocks, to prevent false negatives
|
||||
*/
|
||||
export class NotInitializedError extends Error {
|
||||
constructor(implementation) {
|
||||
super(`NOT_INITIALIZED: ${implementation}`);
|
||||
}
|
||||
}
|
||||
export class ZeroSecretKeyError extends Error {
|
||||
constructor() {
|
||||
super("ZERO_SECRET_KEY");
|
||||
}
|
||||
}
|
||||
export class ZeroPublicKeyError extends Error {
|
||||
constructor() {
|
||||
super("ZERO_PUBLIC_KEY");
|
||||
}
|
||||
}
|
||||
export class ZeroSignatureError extends Error {
|
||||
constructor() {
|
||||
super("ZERO_SIGNATURE");
|
||||
}
|
||||
}
|
||||
export class EmptyAggregateError extends Error {
|
||||
constructor() {
|
||||
super("EMPTY_AGGREGATE_ARRAY");
|
||||
}
|
||||
}
|
||||
export class InvalidOrderError extends Error {
|
||||
constructor() {
|
||||
super("INVALID_ORDER");
|
||||
}
|
||||
}
|
||||
export class InvalidLengthError extends Error {
|
||||
constructor(arg, length) {
|
||||
super(`INVALID_LENGTH: ${arg} - ${length} bytes`);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
import { IBls } from "./types.js";
|
||||
export declare function functionalInterfaceFactory({ SecretKey, PublicKey, Signature, }: Pick<IBls, "SecretKey" | "PublicKey" | "Signature">): {
|
||||
sign: (secretKey: Uint8Array, message: Uint8Array) => Uint8Array;
|
||||
aggregateSignatures: (signatures: Uint8Array[]) => Uint8Array;
|
||||
aggregatePublicKeys: (publicKeys: Uint8Array[]) => Uint8Array;
|
||||
verify: (publicKey: Uint8Array, message: Uint8Array, signature: Uint8Array) => boolean;
|
||||
verifyAggregate: (publicKeys: Uint8Array[], message: Uint8Array, signature: Uint8Array) => boolean;
|
||||
verifyMultiple: (publicKeys: Uint8Array[], messages: Uint8Array[], signature: Uint8Array) => boolean;
|
||||
verifyMultipleSignatures: (sets: {
|
||||
publicKey: Uint8Array;
|
||||
message: Uint8Array;
|
||||
signature: Uint8Array;
|
||||
}[]) => boolean;
|
||||
secretKeyToPublicKey: (secretKey: Uint8Array) => Uint8Array;
|
||||
};
|
|
@ -0,0 +1,137 @@
|
|||
import { validateBytes } from "./helpers/index.js";
|
||||
import { NotInitializedError } from "./errors.js";
|
||||
// Returned type is enforced at each implementation's index
|
||||
// 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, Signature, }) {
|
||||
/**
|
||||
* Signs given message using secret key.
|
||||
* @param secretKey
|
||||
* @param message
|
||||
*/
|
||||
function sign(secretKey, message) {
|
||||
validateBytes(secretKey, "secretKey");
|
||||
validateBytes(message, "message");
|
||||
return SecretKey.fromBytes(secretKey).sign(message).toBytes();
|
||||
}
|
||||
/**
|
||||
* Compines all given signature into one.
|
||||
* @param signatures
|
||||
*/
|
||||
function aggregateSignatures(signatures) {
|
||||
const agg = Signature.aggregate(signatures.map((p) => Signature.fromBytes(p)));
|
||||
return agg.toBytes();
|
||||
}
|
||||
/**
|
||||
* Combines all given public keys into single one
|
||||
* @param publicKeys
|
||||
*/
|
||||
function aggregatePublicKeys(publicKeys) {
|
||||
const agg = PublicKey.aggregate(publicKeys.map((p) => PublicKey.fromBytes(p)));
|
||||
return agg.toBytes();
|
||||
}
|
||||
/**
|
||||
* Verifies if signature is message signed with given public key.
|
||||
* @param publicKey
|
||||
* @param message
|
||||
* @param signature
|
||||
*/
|
||||
function verify(publicKey, message, signature) {
|
||||
validateBytes(publicKey, "publicKey");
|
||||
validateBytes(message, "message");
|
||||
validateBytes(signature, "signature");
|
||||
try {
|
||||
return Signature.fromBytes(signature).verify(PublicKey.fromBytes(publicKey), message);
|
||||
}
|
||||
catch (e) {
|
||||
if (e instanceof NotInitializedError)
|
||||
throw e;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Verifies if aggregated signature is same message signed with given public keys.
|
||||
* @param publicKeys
|
||||
* @param message
|
||||
* @param signature
|
||||
*/
|
||||
function verifyAggregate(publicKeys, message, signature) {
|
||||
validateBytes(publicKeys, "publicKey");
|
||||
validateBytes(message, "message");
|
||||
validateBytes(signature, "signature");
|
||||
try {
|
||||
return Signature.fromBytes(signature).verifyAggregate(publicKeys.map((publicKey) => PublicKey.fromBytes(publicKey)), message);
|
||||
}
|
||||
catch (e) {
|
||||
if (e instanceof NotInitializedError)
|
||||
throw e;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Verifies if signature is list of message signed with corresponding public key.
|
||||
* @param publicKeys
|
||||
* @param messages
|
||||
* @param signature
|
||||
* @param fast Check if all messages are different
|
||||
*/
|
||||
function verifyMultiple(publicKeys, messages, signature) {
|
||||
validateBytes(publicKeys, "publicKey");
|
||||
validateBytes(messages, "message");
|
||||
validateBytes(signature, "signature");
|
||||
if (publicKeys.length === 0 || publicKeys.length != messages.length) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
return Signature.fromBytes(signature).verifyMultiple(publicKeys.map((publicKey) => PublicKey.fromBytes(publicKey)), messages.map((msg) => msg));
|
||||
}
|
||||
catch (e) {
|
||||
if (e instanceof NotInitializedError)
|
||||
throw e;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Verifies multiple signatures at once returning true if all valid or false
|
||||
* if at least one is not. Optimization useful when knowing which signature is
|
||||
* wrong is not relevant, i.e. verifying an entire Eth2.0 block.
|
||||
*
|
||||
* This method provides a safe way to do so by multiplying each signature by
|
||||
* a random number so an attacker cannot craft a malicious signature that won't
|
||||
* verify on its own but will if it's added to a specific predictable signature
|
||||
* https://ethresear.ch/t/fast-verification-of-multiple-bls-signatures/5407
|
||||
*/
|
||||
function verifyMultipleSignatures(sets) {
|
||||
if (!sets)
|
||||
throw Error("sets is null or undefined");
|
||||
try {
|
||||
return Signature.verifyMultipleSignatures(sets.map((s) => ({
|
||||
publicKey: PublicKey.fromBytes(s.publicKey),
|
||||
message: s.message,
|
||||
signature: Signature.fromBytes(s.signature),
|
||||
})));
|
||||
}
|
||||
catch (e) {
|
||||
if (e instanceof NotInitializedError)
|
||||
throw e;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Computes a public key from a secret key
|
||||
*/
|
||||
function secretKeyToPublicKey(secretKey) {
|
||||
validateBytes(secretKey, "secretKey");
|
||||
return SecretKey.fromBytes(secretKey).toPublicKey().toBytes();
|
||||
}
|
||||
return {
|
||||
sign,
|
||||
aggregateSignatures,
|
||||
aggregatePublicKeys,
|
||||
verify,
|
||||
verifyAggregate,
|
||||
verifyMultiple,
|
||||
verifyMultipleSignatures,
|
||||
secretKeyToPublicKey,
|
||||
};
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
import type { IBls, Implementation } from "./types.js";
|
||||
export declare function getImplementation(impl?: Implementation): Promise<IBls>;
|
|
@ -0,0 +1,17 @@
|
|||
// 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 = "herumi") {
|
||||
switch (impl) {
|
||||
case "herumi": {
|
||||
return await (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}`);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
/**
|
||||
* Browser compatible fromHex method
|
||||
* From https://github.com/herumi/bls-eth-wasm/blob/04eedb77aa96e66b4f65a0ab477228adf8090c36/src/bls.js#L62
|
||||
*/
|
||||
export declare function hexToBytes(hex: string): Uint8Array;
|
||||
/**
|
||||
* Browser compatible toHex method
|
||||
* From https://github.com/herumi/bls-eth-wasm/blob/04eedb77aa96e66b4f65a0ab477228adf8090c36/src/bls.js#L50
|
||||
*/
|
||||
export declare function bytesToHex(bytes: Uint8Array): string;
|
|
@ -0,0 +1,30 @@
|
|||
/**
|
||||
* Browser compatible fromHex method
|
||||
* From https://github.com/herumi/bls-eth-wasm/blob/04eedb77aa96e66b4f65a0ab477228adf8090c36/src/bls.js#L62
|
||||
*/
|
||||
export function hexToBytes(hex) {
|
||||
if (hex.startsWith("0x")) {
|
||||
hex = hex.slice(2);
|
||||
}
|
||||
if (hex.length & 1) {
|
||||
throw Error("hexToBytes:length must be even " + hex.length);
|
||||
}
|
||||
const n = hex.length / 2;
|
||||
const a = new Uint8Array(n);
|
||||
for (let i = 0; i < n; i++) {
|
||||
a[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
/**
|
||||
* Browser compatible toHex method
|
||||
* From https://github.com/herumi/bls-eth-wasm/blob/04eedb77aa96e66b4f65a0ab477228adf8090c36/src/bls.js#L50
|
||||
*/
|
||||
export function bytesToHex(bytes) {
|
||||
let s = "";
|
||||
const n = bytes.length;
|
||||
for (let i = 0; i < n; i++) {
|
||||
s += ("0" + bytes[i].toString(16)).slice(-2);
|
||||
}
|
||||
return "0x" + s;
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
export * from "./hex.js";
|
||||
export * from "./utils.js";
|
|
@ -0,0 +1,2 @@
|
|||
export * from "./hex.js";
|
||||
export * from "./utils.js";
|
|
@ -0,0 +1,8 @@
|
|||
import randomBytes from "randombytes";
|
||||
export { randomBytes };
|
||||
/**
|
||||
* Validate bytes to prevent confusing WASM errors downstream if bytes is null
|
||||
*/
|
||||
export declare function validateBytes(bytes: Uint8Array | Uint8Array[] | null, argName?: string): asserts bytes is NonNullable<typeof bytes>;
|
||||
export declare function isZeroUint8Array(bytes: Uint8Array): boolean;
|
||||
export declare function concatUint8Arrays(bytesArr: Uint8Array[]): Uint8Array;
|
|
@ -0,0 +1,26 @@
|
|||
import randomBytes from "randombytes";
|
||||
// Single import to ease changing this lib if necessary
|
||||
export { randomBytes };
|
||||
/**
|
||||
* Validate bytes to prevent confusing WASM errors downstream if bytes is null
|
||||
*/
|
||||
export function validateBytes(bytes, argName) {
|
||||
for (const item of Array.isArray(bytes) ? bytes : [bytes]) {
|
||||
if (item == null) {
|
||||
throw Error(`${argName || "bytes"} is null or undefined`);
|
||||
}
|
||||
}
|
||||
}
|
||||
export function isZeroUint8Array(bytes) {
|
||||
return bytes.every((byte) => byte === 0);
|
||||
}
|
||||
export function concatUint8Arrays(bytesArr) {
|
||||
const totalLen = bytesArr.reduce((total, bytes) => total + bytes.length, 0);
|
||||
const merged = new Uint8Array(totalLen);
|
||||
let mergedLen = 0;
|
||||
for (const bytes of bytesArr) {
|
||||
merged.set(bytes, mergedLen);
|
||||
mergedLen += bytes.length;
|
||||
}
|
||||
return merged;
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
import bls from "bls-eth-wasm";
|
||||
declare type Bls = typeof bls;
|
||||
declare global {
|
||||
interface Window {
|
||||
msCrypto: typeof window["crypto"];
|
||||
}
|
||||
}
|
||||
export declare function setupBls(): Promise<void>;
|
||||
export declare function init(): Promise<void>;
|
||||
export declare function destroy(): void;
|
||||
export declare function getContext(): Bls;
|
||||
export {};
|
|
@ -0,0 +1,35 @@
|
|||
/* eslint-disable require-atomic-updates */
|
||||
import bls from "bls-eth-wasm";
|
||||
import { NotInitializedError } from "../errors.js";
|
||||
let blsGlobal = null;
|
||||
let blsGlobalPromise = null;
|
||||
export async function setupBls() {
|
||||
if (!blsGlobal) {
|
||||
await bls.init(bls.BLS12_381);
|
||||
// Patch to fix multiVerify() calls on a browser with polyfilled NodeJS crypto
|
||||
if (typeof window === "object") {
|
||||
const crypto = window.crypto || window.msCrypto;
|
||||
// getRandomValues is not typed in `bls-eth-wasm` because it's not meant to be exposed
|
||||
// @ts-ignore
|
||||
bls.getRandomValues = (x) => crypto.getRandomValues(x);
|
||||
}
|
||||
blsGlobal = bls;
|
||||
}
|
||||
}
|
||||
// Cache a promise for Bls instead of Bls to make sure it is initialized only once
|
||||
export async function init() {
|
||||
if (!blsGlobalPromise) {
|
||||
blsGlobalPromise = setupBls();
|
||||
}
|
||||
return blsGlobalPromise;
|
||||
}
|
||||
export function destroy() {
|
||||
blsGlobal = null;
|
||||
blsGlobalPromise = null;
|
||||
}
|
||||
export function getContext() {
|
||||
if (!blsGlobal) {
|
||||
throw new NotInitializedError("herumi");
|
||||
}
|
||||
return blsGlobal;
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
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";
|
||||
export * from "../constants.js";
|
||||
export { SecretKey, PublicKey, Signature, init, destroy };
|
||||
export declare const bls: () => Promise<IBls>;
|
||||
export default bls;
|
|
@ -0,0 +1,18 @@
|
|||
import { SecretKey } from "./secretKey.js";
|
||||
import { PublicKey } from "./publicKey.js";
|
||||
import { Signature } from "./signature.js";
|
||||
import { init, destroy } from "./context.js";
|
||||
import { functionalInterfaceFactory } from "../functional.js";
|
||||
export * from "../constants.js";
|
||||
export { SecretKey, PublicKey, Signature, init, destroy };
|
||||
export const bls = async () => {
|
||||
await init();
|
||||
return {
|
||||
implementation: "herumi",
|
||||
SecretKey,
|
||||
PublicKey,
|
||||
Signature,
|
||||
...functionalInterfaceFactory({ SecretKey, PublicKey, Signature }),
|
||||
};
|
||||
};
|
||||
export default bls;
|
|
@ -0,0 +1,11 @@
|
|||
import type { PublicKeyType } from "bls-eth-wasm";
|
||||
import { PointFormat, PublicKey as IPublicKey } from "../types.js";
|
||||
export declare class PublicKey implements IPublicKey {
|
||||
readonly value: PublicKeyType;
|
||||
constructor(value: PublicKeyType);
|
||||
static fromBytes(bytes: Uint8Array): PublicKey;
|
||||
static fromHex(hex: string): PublicKey;
|
||||
static aggregate(publicKeys: PublicKey[]): PublicKey;
|
||||
toBytes(format?: PointFormat): Uint8Array;
|
||||
toHex(format?: PointFormat): string;
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
import { getContext } from "./context.js";
|
||||
import { bytesToHex, hexToBytes, isZeroUint8Array } from "../helpers/index.js";
|
||||
import { PointFormat } 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 {
|
||||
constructor(value) {
|
||||
if (value.isZero()) {
|
||||
throw new ZeroPublicKeyError();
|
||||
}
|
||||
this.value = value;
|
||||
}
|
||||
static fromBytes(bytes) {
|
||||
const context = getContext();
|
||||
const publicKey = new context.PublicKey();
|
||||
if (!isZeroUint8Array(bytes)) {
|
||||
if (bytes.length === PUBLIC_KEY_LENGTH_COMPRESSED) {
|
||||
publicKey.deserialize(bytes);
|
||||
}
|
||||
else if (bytes.length === PUBLIC_KEY_LENGTH_UNCOMPRESSED) {
|
||||
publicKey.deserializeUncompressed(bytes);
|
||||
}
|
||||
else {
|
||||
throw new InvalidLengthError("PublicKey", bytes.length);
|
||||
}
|
||||
}
|
||||
return new PublicKey(publicKey);
|
||||
}
|
||||
static fromHex(hex) {
|
||||
return this.fromBytes(hexToBytes(hex));
|
||||
}
|
||||
static aggregate(publicKeys) {
|
||||
if (publicKeys.length === 0) {
|
||||
throw new EmptyAggregateError();
|
||||
}
|
||||
const agg = new PublicKey(publicKeys[0].value.clone());
|
||||
for (const pk of publicKeys.slice(1)) {
|
||||
agg.value.add(pk.value);
|
||||
}
|
||||
return agg;
|
||||
}
|
||||
toBytes(format) {
|
||||
if (format === PointFormat.uncompressed) {
|
||||
return this.value.serializeUncompressed();
|
||||
}
|
||||
else {
|
||||
return this.value.serialize();
|
||||
}
|
||||
}
|
||||
toHex(format) {
|
||||
return bytesToHex(this.toBytes(format));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
import type { SecretKeyType } from "bls-eth-wasm";
|
||||
import { PublicKey } from "./publicKey.js";
|
||||
import { Signature } from "./signature.js";
|
||||
import { SecretKey as ISecretKey } from "../types.js";
|
||||
export declare class SecretKey implements ISecretKey {
|
||||
readonly value: SecretKeyType;
|
||||
constructor(value: SecretKeyType);
|
||||
static fromBytes(bytes: Uint8Array): SecretKey;
|
||||
static fromHex(hex: string): SecretKey;
|
||||
static fromKeygen(entropy?: Uint8Array): SecretKey;
|
||||
sign(message: Uint8Array): Signature;
|
||||
toPublicKey(): PublicKey;
|
||||
toBytes(): Uint8Array;
|
||||
toHex(): string;
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
import { generateRandomSecretKey } from "@chainsafe/bls-keygen";
|
||||
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 { InvalidLengthError, ZeroSecretKeyError } from "../errors.js";
|
||||
export class SecretKey {
|
||||
constructor(value) {
|
||||
if (value.isZero()) {
|
||||
throw new ZeroSecretKeyError();
|
||||
}
|
||||
this.value = value;
|
||||
}
|
||||
static fromBytes(bytes) {
|
||||
if (bytes.length !== SECRET_KEY_LENGTH) {
|
||||
throw new InvalidLengthError("SecretKey", SECRET_KEY_LENGTH);
|
||||
}
|
||||
const context = getContext();
|
||||
const secretKey = new context.SecretKey();
|
||||
secretKey.deserialize(bytes);
|
||||
return new SecretKey(secretKey);
|
||||
}
|
||||
static fromHex(hex) {
|
||||
return this.fromBytes(hexToBytes(hex));
|
||||
}
|
||||
static fromKeygen(entropy) {
|
||||
const sk = generateRandomSecretKey(entropy);
|
||||
return this.fromBytes(sk);
|
||||
}
|
||||
sign(message) {
|
||||
return new Signature(this.value.sign(message));
|
||||
}
|
||||
toPublicKey() {
|
||||
return new PublicKey(this.value.getPublicKey());
|
||||
}
|
||||
toBytes() {
|
||||
return this.value.serialize();
|
||||
}
|
||||
toHex() {
|
||||
return bytesToHex(this.toBytes());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
import type { SignatureType } from "bls-eth-wasm";
|
||||
import { PublicKey } from "./publicKey.js";
|
||||
import { PointFormat, Signature as ISignature, CoordType } from "../types.js";
|
||||
export declare class Signature implements ISignature {
|
||||
readonly value: SignatureType;
|
||||
constructor(value: SignatureType);
|
||||
/**
|
||||
* @param type Does not affect `herumi` implementation, always de-serializes to `jacobian`
|
||||
* @param validate With `herumi` implementation signature validation is always on regardless of this flag.
|
||||
*/
|
||||
static fromBytes(bytes: Uint8Array, _type?: CoordType, _validate?: boolean): Signature;
|
||||
static fromHex(hex: string): Signature;
|
||||
static aggregate(signatures: Signature[]): Signature;
|
||||
static verifyMultipleSignatures(sets: {
|
||||
publicKey: PublicKey;
|
||||
message: Uint8Array;
|
||||
signature: Signature;
|
||||
}[]): boolean;
|
||||
verify(publicKey: PublicKey, message: Uint8Array): boolean;
|
||||
verifyAggregate(publicKeys: PublicKey[], message: Uint8Array): boolean;
|
||||
verifyMultiple(publicKeys: PublicKey[], messages: Uint8Array[]): boolean;
|
||||
toBytes(format?: PointFormat): Uint8Array;
|
||||
toHex(format?: PointFormat): string;
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
import { getContext } from "./context.js";
|
||||
import { bytesToHex, concatUint8Arrays, hexToBytes, isZeroUint8Array } from "../helpers/index.js";
|
||||
import { PointFormat } from "../types.js";
|
||||
import { EmptyAggregateError, InvalidLengthError, InvalidOrderError } from "../errors.js";
|
||||
import { SIGNATURE_LENGTH_COMPRESSED, SIGNATURE_LENGTH_UNCOMPRESSED } from "../constants.js";
|
||||
export class Signature {
|
||||
constructor(value) {
|
||||
if (!value.isValidOrder()) {
|
||||
throw new InvalidOrderError();
|
||||
}
|
||||
this.value = value;
|
||||
}
|
||||
/**
|
||||
* @param type Does not affect `herumi` implementation, always de-serializes to `jacobian`
|
||||
* @param validate With `herumi` implementation signature validation is always on regardless of this flag.
|
||||
*/
|
||||
static fromBytes(bytes, _type, _validate = true) {
|
||||
const context = getContext();
|
||||
const signature = new context.Signature();
|
||||
if (!isZeroUint8Array(bytes)) {
|
||||
if (bytes.length === SIGNATURE_LENGTH_COMPRESSED) {
|
||||
signature.deserialize(bytes);
|
||||
}
|
||||
else if (bytes.length === SIGNATURE_LENGTH_UNCOMPRESSED) {
|
||||
signature.deserializeUncompressed(bytes);
|
||||
}
|
||||
else {
|
||||
throw new InvalidLengthError("Signature", bytes.length);
|
||||
}
|
||||
signature.deserialize(bytes);
|
||||
}
|
||||
return new Signature(signature);
|
||||
}
|
||||
static fromHex(hex) {
|
||||
return this.fromBytes(hexToBytes(hex));
|
||||
}
|
||||
static aggregate(signatures) {
|
||||
if (signatures.length === 0) {
|
||||
throw new EmptyAggregateError();
|
||||
}
|
||||
const context = getContext();
|
||||
const signature = new context.Signature();
|
||||
signature.aggregate(signatures.map((sig) => sig.value));
|
||||
return new Signature(signature);
|
||||
}
|
||||
static verifyMultipleSignatures(sets) {
|
||||
const context = getContext();
|
||||
return context.multiVerify(sets.map((s) => s.publicKey.value), sets.map((s) => s.signature.value), sets.map((s) => s.message));
|
||||
}
|
||||
verify(publicKey, message) {
|
||||
return publicKey.value.verify(this.value, message);
|
||||
}
|
||||
verifyAggregate(publicKeys, message) {
|
||||
return this.value.fastAggregateVerify(publicKeys.map((key) => key.value), message);
|
||||
}
|
||||
verifyMultiple(publicKeys, messages) {
|
||||
return this.value.aggregateVerifyNoCheck(publicKeys.map((key) => key.value), concatUint8Arrays(messages));
|
||||
}
|
||||
toBytes(format) {
|
||||
if (format === PointFormat.uncompressed) {
|
||||
return this.value.serializeUncompressed();
|
||||
}
|
||||
else {
|
||||
return this.value.serialize();
|
||||
}
|
||||
}
|
||||
toHex(format) {
|
||||
return bytesToHex(this.toBytes(format));
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
export {};
|
|
@ -0,0 +1,6 @@
|
|||
import { bls } from "./index.js";
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(function (window) {
|
||||
window.bls = bls;
|
||||
// @ts-ignore
|
||||
})(window);
|
|
@ -0,0 +1,3 @@
|
|||
import type { IBls } from "./types.js";
|
||||
export declare const bls: () => Promise<IBls>;
|
||||
export default bls;
|
|
@ -0,0 +1,14 @@
|
|||
import { getImplementation } from "./getImplementation.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 const bls = async () => {
|
||||
let bls;
|
||||
try {
|
||||
bls = await getImplementation(isNode ? "blst-native" : "herumi");
|
||||
}
|
||||
catch (e) {
|
||||
bls = await getImplementation("herumi");
|
||||
}
|
||||
return bls;
|
||||
};
|
||||
export default bls;
|
|
@ -0,0 +1,4 @@
|
|||
import type { IBls, Implementation } from "./types.js";
|
||||
declare const bls: IBls;
|
||||
export default bls;
|
||||
export declare function init(impl: Implementation): Promise<void>;
|
|
@ -0,0 +1,11 @@
|
|||
import { getImplementation } from "./getImplementation.js";
|
||||
// TODO: Use a Proxy for example to throw an error if it's not initialized yet
|
||||
const bls = {};
|
||||
export default bls;
|
||||
export async function init(impl) {
|
||||
// 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);
|
||||
}
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,66 @@
|
|||
export interface IBls {
|
||||
implementation: Implementation;
|
||||
SecretKey: typeof SecretKey;
|
||||
PublicKey: typeof PublicKey;
|
||||
Signature: typeof Signature;
|
||||
sign(secretKey: Uint8Array, message: Uint8Array): Uint8Array;
|
||||
aggregatePublicKeys(publicKeys: Uint8Array[]): Uint8Array;
|
||||
aggregateSignatures(signatures: Uint8Array[]): Uint8Array;
|
||||
verify(publicKey: Uint8Array, message: Uint8Array, signature: Uint8Array): boolean;
|
||||
verifyAggregate(publicKeys: Uint8Array[], message: Uint8Array, signature: Uint8Array): boolean;
|
||||
verifyMultiple(publicKeys: Uint8Array[], messages: Uint8Array[], signature: Uint8Array): boolean;
|
||||
verifyMultipleSignatures(sets: {
|
||||
publicKey: Uint8Array;
|
||||
message: Uint8Array;
|
||||
signature: Uint8Array;
|
||||
}[]): boolean;
|
||||
secretKeyToPublicKey(secretKey: Uint8Array): Uint8Array;
|
||||
}
|
||||
export declare class SecretKey {
|
||||
private constructor();
|
||||
static fromBytes(bytes: Uint8Array): SecretKey;
|
||||
static fromHex(hex: string): SecretKey;
|
||||
static fromKeygen(entropy?: Uint8Array): SecretKey;
|
||||
sign(message: Uint8Array): Signature;
|
||||
toPublicKey(): PublicKey;
|
||||
toBytes(): Uint8Array;
|
||||
toHex(): string;
|
||||
}
|
||||
export declare class PublicKey {
|
||||
private constructor();
|
||||
/** @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;
|
||||
static aggregate(publicKeys: PublicKey[]): PublicKey;
|
||||
/** @param format Defaults to `PointFormat.compressed` */
|
||||
toBytes(format?: PointFormat): Uint8Array;
|
||||
toHex(format?: PointFormat): string;
|
||||
}
|
||||
export declare class Signature {
|
||||
private constructor();
|
||||
/** @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;
|
||||
static fromHex(hex: string): Signature;
|
||||
static aggregate(signatures: Signature[]): Signature;
|
||||
static verifyMultipleSignatures(sets: {
|
||||
publicKey: PublicKey;
|
||||
message: Uint8Array;
|
||||
signature: Signature;
|
||||
}[]): boolean;
|
||||
verify(publicKey: PublicKey, message: Uint8Array): boolean;
|
||||
verifyAggregate(publicKeys: PublicKey[], message: Uint8Array): boolean;
|
||||
verifyMultiple(publicKeys: PublicKey[], messages: Uint8Array[]): boolean;
|
||||
/** @param format Defaults to `PointFormat.compressed` */
|
||||
toBytes(format?: PointFormat): Uint8Array;
|
||||
toHex(format?: PointFormat): string;
|
||||
}
|
||||
export declare type Implementation = "herumi" | "blst-native";
|
||||
export declare enum PointFormat {
|
||||
compressed = "compressed",
|
||||
uncompressed = "uncompressed"
|
||||
}
|
||||
export declare enum CoordType {
|
||||
affine = 0,
|
||||
jacobian = 1
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
export var PointFormat;
|
||||
(function (PointFormat) {
|
||||
PointFormat["compressed"] = "compressed";
|
||||
PointFormat["uncompressed"] = "uncompressed";
|
||||
})(PointFormat || (PointFormat = {}));
|
||||
export var CoordType;
|
||||
(function (CoordType) {
|
||||
CoordType[CoordType["affine"] = 0] = "affine";
|
||||
CoordType[CoordType["jacobian"] = 1] = "jacobian";
|
||||
})(CoordType || (CoordType = {}));
|
102
package.json
102
package.json
|
@ -1,11 +1,49 @@
|
|||
{
|
||||
"name": "@chainsafe/bls",
|
||||
"version": "6.0.0",
|
||||
"version": "7.1.1",
|
||||
"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"
|
||||
},
|
||||
"./errors": {
|
||||
"import": "./lib/errors.js"
|
||||
},
|
||||
"./constants": {
|
||||
"import": "./lib/constants.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",
|
||||
|
@ -13,10 +51,8 @@
|
|||
"lib/**/*.js",
|
||||
"lib/**/*.js.map",
|
||||
"lib/**/*.d.ts",
|
||||
"blst-native.*",
|
||||
"browser.*",
|
||||
"herumi.*",
|
||||
"node."
|
||||
"*.d.ts",
|
||||
"*.js"
|
||||
],
|
||||
"keywords": [
|
||||
"ethereum",
|
||||
|
@ -26,59 +62,63 @@
|
|||
],
|
||||
"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": {
|
||||
"@chainsafe/bls-keygen": "^0.3.0",
|
||||
"bls-eth-wasm": "^0.4.4",
|
||||
"@chainsafe/bls-keygen": "^0.4.0",
|
||||
"bls-eth-wasm": "^0.4.8",
|
||||
"randombytes": "^2.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@chainsafe/blst": "^0.2.0",
|
||||
"@chainsafe/blst": "^0.2.4",
|
||||
"@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/mocha": "^10.0.0",
|
||||
"@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": "^4.4.1",
|
||||
"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": "^10.0.0",
|
||||
"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.3.1",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "4.7.4",
|
||||
"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": {
|
||||
"@chainsafe/blst": "^0.2.0"
|
||||
"@chainsafe/blst": "^0.2.4"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
|
@ -1,7 +1,7 @@
|
|||
import * as blst from "@chainsafe/blst";
|
||||
import {EmptyAggregateError, ZeroPublicKeyError} 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]) {
|
||||
|
@ -9,12 +9,9 @@ export class PublicKey extends blst.PublicKey implements IPublicKey {
|
|||
}
|
||||
|
||||
/** @param type Defaults to `CoordType.jacobian` */
|
||||
static fromBytes(bytes: Uint8Array, type?: blst.CoordType): PublicKey {
|
||||
static fromBytes(bytes: Uint8Array, type?: blst.CoordType, validate?: boolean): PublicKey {
|
||||
const pk = blst.PublicKey.fromBytes(bytes, type);
|
||||
if (pk.value.is_inf()) {
|
||||
throw new ZeroPublicKeyError();
|
||||
}
|
||||
|
||||
if (validate) pk.keyValidate();
|
||||
return new PublicKey(pk.value);
|
||||
}
|
||||
|
|
@ -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;
|
|
@ -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]) {
|
||||
|
@ -10,7 +10,7 @@ export class Signature extends blst.Signature implements ISignature {
|
|||
}
|
||||
|
||||
/** @param type Defaults to `CoordType.affine` */
|
||||
static fromBytes(bytes: Uint8Array, type?: blst.CoordType, validate?: boolean): Signature {
|
||||
static fromBytes(bytes: Uint8Array, type?: blst.CoordType, validate = true): Signature {
|
||||
const sig = blst.Signature.fromBytes(bytes, type);
|
||||
if (validate) sig.sigValidate();
|
||||
return new Signature(sig.value);
|
|
@ -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;
|
|
@ -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,
|
||||
|
|
|
@ -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 (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}`);
|
||||
}
|
||||
}
|
|
@ -1,2 +1,2 @@
|
|||
export * from "./hex";
|
||||
export * from "./utils";
|
||||
export * from "./hex.js";
|
||||
export * from "./utils.js";
|
||||
|
|
|
@ -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"];
|
||||
}
|
||||
|
|
|
@ -1,22 +1,23 @@
|
|||
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";
|
||||
|
||||
export * from "../constants.js";
|
||||
|
||||
export {SecretKey, PublicKey, Signature, init, destroy};
|
||||
|
||||
export const bls: IBls = {
|
||||
implementation: "herumi",
|
||||
SecretKey,
|
||||
PublicKey,
|
||||
Signature,
|
||||
export const bls = async (): Promise<IBls> => {
|
||||
await init();
|
||||
return {
|
||||
implementation: "herumi",
|
||||
SecretKey,
|
||||
PublicKey,
|
||||
Signature,
|
||||
|
||||
...functionalInterfaceFactory({SecretKey, PublicKey, Signature}),
|
||||
init,
|
||||
destroy,
|
||||
...functionalInterfaceFactory({SecretKey, PublicKey, Signature}),
|
||||
};
|
||||
};
|
||||
|
||||
export default bls;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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} 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;
|
||||
|
@ -17,7 +17,11 @@ export class Signature implements ISignature {
|
|||
this.value = value;
|
||||
}
|
||||
|
||||
static fromBytes(bytes: Uint8Array): Signature {
|
||||
/**
|
||||
* @param type Does not affect `herumi` implementation, always de-serializes to `jacobian`
|
||||
* @param validate With `herumi` implementation signature validation is always on regardless of this flag.
|
||||
*/
|
||||
static fromBytes(bytes: Uint8Array, _type?: CoordType, _validate = true): Signature {
|
||||
const context = getContext();
|
||||
const signature = new context.Signature();
|
||||
if (!isZeroUint8Array(bytes)) {
|
||||
|
@ -49,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)
|
||||
|
|
|
@ -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) {
|
||||
|
|
54
src/index.ts
54
src/index.ts
|
@ -1,47 +1,19 @@
|
|||
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";
|
||||
export const bls = async (): Promise<IBls> => {
|
||||
let bls: IBls;
|
||||
|
||||
// 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}`);
|
||||
try {
|
||||
bls = await getImplementation(isNode ? "blst-native" : "herumi");
|
||||
} catch (e) {
|
||||
bls = await getImplementation("herumi");
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
return bls;
|
||||
};
|
||||
|
||||
// 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"];
|
||||
export default bls;
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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,8 +27,10 @@ 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): PublicKey;
|
||||
static fromBytes(bytes: Uint8Array, type?: CoordType, validate?: boolean): PublicKey;
|
||||
static fromHex(hex: string): PublicKey;
|
||||
static aggregate(publicKeys: PublicKey[]): PublicKey;
|
||||
/** @param format Defaults to `PointFormat.compressed` */
|
||||
|
@ -38,7 +39,10 @@ export declare class PublicKey {
|
|||
}
|
||||
|
||||
export declare class Signature {
|
||||
/** @param type Only for impl `blst-native`. Defaults to `CoordType.affine` */
|
||||
// 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;
|
||||
static fromHex(hex: string): Signature;
|
||||
static aggregate(signatures: Signature[]): Signature;
|
|
@ -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 */
|
||||
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
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 {
|
||||
data: {
|
||||
|
@ -21,7 +22,14 @@ describeForAllImplementations((bls) => {
|
|||
path.join(SPEC_TESTS_DIR, "tests/general/phase0/bls/fast_aggregate_verify/small"),
|
||||
(testCase) => {
|
||||
const {pubkeys, message, signature} = testCase.data.input;
|
||||
return bls.verifyAggregate(pubkeys.map(hexToBytes), hexToBytes(message), hexToBytes(signature));
|
||||
try {
|
||||
return bls.Signature.fromBytes(hexToBytes(signature)).verifyAggregate(
|
||||
pubkeys.map((hex) => bls.PublicKey.fromBytes(hexToBytes(hex), CoordType.jacobian, true)),
|
||||
hexToBytes(message)
|
||||
);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
{
|
||||
inputTypes: {data: InputType.YAML},
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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: {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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", () => {
|
||||
|
|
|
@ -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}[] = [
|
||||
|
@ -14,7 +14,7 @@ describe("helpers / hex", () => {
|
|||
it(`${id} hexToBytes`, () => {
|
||||
const expectedBytes = hexToBytesNode(hex);
|
||||
const bytes = hexToBytes(hex);
|
||||
expect(expectedBytes.equals(bytes)).to.be.true;
|
||||
expect(expectedBytes.equals(bytes)).equals(true);
|
||||
});
|
||||
|
||||
it(`${id} bytesToHex`, () => {
|
||||
|
|
|
@ -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,11 +16,11 @@ 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();
|
||||
expect(verifyHelper(pk, sig, msg)).to.be.true;
|
||||
expect(verifyHelper(pk, sig, msg)).equals(true);
|
||||
});
|
||||
|
||||
it("Make sure exported classes are compatible with interface", () => {
|
||||
|
|
|
@ -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
|
||||
|
@ -14,36 +15,60 @@ export function runIndexTests(bls: IBls): void {
|
|||
return {sk, pk, msg, sig};
|
||||
}
|
||||
|
||||
describe("signature", () => {
|
||||
it("should fail loading an invalid signature point (not in G2)", () => {
|
||||
/* eslint-disable max-len */
|
||||
const POINT_NOT_IN_G2 = Buffer.from(
|
||||
"8123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
|
||||
"hex"
|
||||
);
|
||||
let sig;
|
||||
try {
|
||||
sig = bls.Signature.fromBytes(POINT_NOT_IN_G2, undefined, true);
|
||||
} catch {
|
||||
/* eslint-disable no-empty */
|
||||
}
|
||||
expect(sig === undefined).equals(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("verify", () => {
|
||||
it("should verify signature", () => {
|
||||
const {pk, msg, sig} = getRandomData();
|
||||
const pkHex = pk.toHex();
|
||||
const isValid = bls.verify(pk.toBytes(), msg, sig.toBytes());
|
||||
expect(isValid, "fail verify").to.be.true;
|
||||
expect(isValid, "fail verify").equals(true);
|
||||
|
||||
// Make sure to not modify original pubkey when verifying
|
||||
expect(pk.toHex()).to.be.equal(pkHex, "pubkey modified when verifying");
|
||||
expect(pk.toHex()).equals(pkHex, "pubkey modified when verifying");
|
||||
});
|
||||
|
||||
it("should fail verify empty signature", () => {
|
||||
const {pk, msg} = getRandomData();
|
||||
const emptySig = Buffer.alloc(96);
|
||||
const isValid = bls.verify(pk.toBytes(), msg, emptySig);
|
||||
expect(isValid).to.be.false;
|
||||
expect(isValid).equals(false);
|
||||
});
|
||||
|
||||
it("should fail verify signature of different message", () => {
|
||||
const {pk, sig} = getRandomData();
|
||||
const msg2 = randomMessage();
|
||||
const isValid = bls.verify(pk.toBytes(), msg2, sig.toBytes());
|
||||
expect(isValid).to.be.false;
|
||||
expect(isValid).equals(false);
|
||||
});
|
||||
|
||||
it("should fail verify signature signed by different key", () => {
|
||||
const {msg, sig} = getRandomData();
|
||||
const {pk: pk2} = getRandomData();
|
||||
const isValid = bls.verify(pk2.toBytes(), msg, sig.toBytes());
|
||||
expect(isValid).to.be.false;
|
||||
expect(isValid).equals(false);
|
||||
});
|
||||
|
||||
it("should fail verify empty message", () => {
|
||||
const emptyMsg = new Uint8Array(0);
|
||||
const {pk, sig} = getRandomData();
|
||||
const isValid = sig.verify(pk, emptyMsg);
|
||||
expect(isValid).equals(false);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -62,8 +87,8 @@ export function runIndexTests(bls: IBls): void {
|
|||
|
||||
const aggSig = bls.aggregateSignatures(sigs.map((sig) => sig.toBytes()));
|
||||
|
||||
expect(bls.verifyMultiple(aggPubkeys, msgs, aggSig), "should be valid").to.be.true;
|
||||
expect(bls.verifyMultiple(aggPubkeys.reverse(), msgs, aggSig), "should fail - swaped pubkeys").to.be.false;
|
||||
expect(bls.verifyMultiple(aggPubkeys, msgs, aggSig), "should be valid").equals(true);
|
||||
expect(bls.verifyMultiple(aggPubkeys.reverse(), msgs, aggSig), "should fail - swaped pubkeys").equals(false);
|
||||
});
|
||||
|
||||
it("should verify aggregated signatures - same message", () => {
|
||||
|
@ -80,7 +105,7 @@ export function runIndexTests(bls: IBls): void {
|
|||
getN(4, () => msg), // Same message n times
|
||||
aggregateSignature
|
||||
);
|
||||
expect(isValid).to.be.true;
|
||||
expect(isValid).equals(true);
|
||||
});
|
||||
|
||||
it("should fail to verify aggregated signatures - no public keys", () => {
|
||||
|
@ -89,7 +114,19 @@ export function runIndexTests(bls: IBls): void {
|
|||
const msg2 = randomMessage();
|
||||
|
||||
const isValid = bls.verifyMultiple([], [msg2, msg1], sig);
|
||||
expect(isValid).to.be.false;
|
||||
expect(isValid).equals(false);
|
||||
});
|
||||
|
||||
it("should fail verify empty message", () => {
|
||||
const sks = getN(2, () => bls.SecretKey.fromKeygen());
|
||||
const msgs = getN(2, () => randomMessage());
|
||||
const pks = sks.map((sk) => sk.toPublicKey());
|
||||
const sigs = [sks[0].sign(msgs[0]), sks[1].sign(msgs[1])];
|
||||
const aggSig = bls.Signature.aggregate(sigs);
|
||||
|
||||
const emptyMsgs = msgs.map(() => new Uint8Array(0));
|
||||
const isValid = aggSig.verifyMultiple(pks, emptyMsgs);
|
||||
expect(isValid).equals(false);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -149,6 +186,22 @@ export function runIndexTests(bls: IBls): void {
|
|||
"Malicous signature should not validate with bls.verifyMultipleSignatures"
|
||||
);
|
||||
});
|
||||
|
||||
it("should fail verify empty message", () => {
|
||||
const n = 4;
|
||||
const sets = getN(n, () => {
|
||||
const sk = bls.SecretKey.fromKeygen();
|
||||
const publicKey = sk.toPublicKey();
|
||||
const message = randomMessage();
|
||||
const signature = sk.sign(message);
|
||||
return {publicKey, message, signature};
|
||||
});
|
||||
|
||||
const setsWithEmptyMsgs = sets.map((set) => ({...set, message: new Uint8Array(0)}));
|
||||
|
||||
const isValid = bls.Signature.verifyMultipleSignatures(setsWithEmptyMsgs);
|
||||
expect(isValid).equals(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("serialize deserialize", () => {
|
||||
|
@ -161,13 +214,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");
|
||||
});
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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 () {
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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", () => {
|
||||
|
@ -7,11 +7,11 @@ export function runPublicKeyTests(bls: IBls): void {
|
|||
"0xb6f21199594b56d77670564bf422cb331d5281ca2c1f9a45588a56881d8287ef8619efa6456d6cd2ef61306aa5b21311";
|
||||
|
||||
it("should export public key to hex string", () => {
|
||||
expect(bls.PublicKey.fromHex(publicKey).toHex()).to.be.equal(publicKey);
|
||||
expect(bls.PublicKey.fromHex(publicKey).toHex()).equals(publicKey);
|
||||
});
|
||||
|
||||
it("should export public key to hex string from non-prefixed hex", () => {
|
||||
expect(bls.PublicKey.fromHex(publicKey).toHex()).to.be.equal(publicKey);
|
||||
expect(bls.PublicKey.fromHex(publicKey).toHex()).equals(publicKey);
|
||||
});
|
||||
|
||||
it("from secret key", () => {
|
||||
|
|
|
@ -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) => {
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue