Compare commits

...

79 Commits

Author SHA1 Message Date
Derrick Hammer 99f7fe9976
*Update dist 2023-04-08 22:32:14 -04:00
Derrick Hammer 1028fc1bb0
*Make bls export an async wrapper to prevent top-level await 2023-04-08 22:31:59 -04:00
Derrick Hammer a74045db4a
*add lib 2023-04-08 22:21:39 -04:00
Derrick Hammer d58fc82e08
*Make bls export an async wrapper to prevent top-level await 2023-04-08 22:20:32 -04:00
Lion - dapplion 902896968f
Bump test deps (#139) 2022-10-07 18:25:44 +02:00
Lion - dapplion 86078f9b6d
Use explicit chai statements (#140) 2022-10-07 21:43:50 +05:30
Lion - dapplion c4ea70afd0
Add tests for empty message case (#138) 2022-10-07 21:35:58 +05:30
Phil Ngo 710e7f9f5e
Update stalebot options and funding url (#135)
* Update stale.yml

* Update funding.yml

* Update to tag with meta-stable label

* Removed extra https in funding url
2022-07-15 08:34:08 -05:00
dadepo 6057e93208
Updated code example that has since diverged from implementation (#133)
* updated code example that has since diverged from implementation

* using default export in example in readme.
2022-07-06 10:05:38 -05:00
dependabot[bot] e3ba38c938
Bump path-parse from 1.0.6 to 1.0.7 (#101)
Bumps [path-parse](https://github.com/jbgutierrez/path-parse) from 1.0.6 to 1.0.7.
- [Release notes](https://github.com/jbgutierrez/path-parse/releases)
- [Commits](https://github.com/jbgutierrez/path-parse/commits/v1.0.7)

---
updated-dependencies:
- dependency-name: path-parse
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-15 22:41:28 -05:00
dependabot[bot] fbe7e36355
Bump pathval from 1.1.0 to 1.1.1 (#111)
Bumps [pathval](https://github.com/chaijs/pathval) from 1.1.0 to 1.1.1.
- [Release notes](https://github.com/chaijs/pathval/releases)
- [Changelog](https://github.com/chaijs/pathval/blob/master/CHANGELOG.md)
- [Commits](https://github.com/chaijs/pathval/compare/v1.1.0...v1.1.1)

---
updated-dependencies:
- dependency-name: pathval
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-15 22:41:12 -05:00
dependabot[bot] 25f9cb8c48
Bump minimist from 1.2.0 to 1.2.6 (#131)
Bumps [minimist](https://github.com/substack/minimist) from 1.2.0 to 1.2.6.
- [Release notes](https://github.com/substack/minimist/releases)
- [Commits](https://github.com/substack/minimist/compare/1.2.0...1.2.6)

---
updated-dependencies:
- dependency-name: minimist
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-15 22:40:54 -05:00
Cayman 04931736ff
Merge pull request #132 from ChainSafe/cayman/release
v7.1.1
2022-05-15 14:07:29 -04:00
Cayman 87ecb9c523
v7.1.1 2022-05-15 12:59:40 -05:00
Cayman 097068b713
Merge pull request #130 from ChainSafe/cayman/bump-blst
chore: bump blst peer dependency
2022-05-15 13:56:46 -04:00
Cayman 7a40f17c64
chore: bump blst peer dependency 2022-05-14 11:25:16 -05:00
Cayman 42cbc2b1e4
Merge pull request #129 from ChainSafe/cayman/release
v7.1.0
2022-05-09 11:53:34 -04:00
Cayman 86792ee65e
v7.1.0 2022-05-09 10:36:08 -05:00
Cayman 4747cbc573
Merge pull request #128 from ChainSafe/cayman/add-exports
feat: add errors and constants to exports
2022-05-09 11:27:36 -04:00
Cayman 41ef77a071
chore: add errors and constants to exports 2022-05-09 09:58:33 -05:00
dependabot[bot] c1949c9c8c
Bump tar from 6.1.4 to 6.1.11 (#105)
Bumps [tar](https://github.com/npm/node-tar) from 6.1.4 to 6.1.11.
- [Release notes](https://github.com/npm/node-tar/releases)
- [Changelog](https://github.com/npm/node-tar/blob/main/CHANGELOG.md)
- [Commits](https://github.com/npm/node-tar/compare/v6.1.4...v6.1.11)

---
updated-dependencies:
- dependency-name: tar
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-05 16:13:22 +02:00
Cayman 23d9388232
Merge pull request #127 from ChainSafe/cayman/fix-ci
Fix release workflow
2022-05-05 15:22:42 +02:00
Cayman acacf17c4a
Fix release workflow 2022-05-05 08:16:28 -05:00
Cayman b7a66ff845
Merge pull request #126 from ChainSafe/cayman/release
v7.0.0
2022-05-05 15:13:04 +02:00
Cayman 42022a176f
v7.0.0 2022-05-05 08:08:35 -05:00
Cayman a112b21347
Merge pull request #121 from ChainSafe/cayman/esm
ESM Support
2022-05-05 15:04:19 +02:00
Cayman 35b0028ca7
Merge branch 'master' into cayman/esm 2022-05-05 07:59:41 -05:00
dependabot[bot] 0500f74eb5
Bump ajv from 6.11.0 to 6.12.6 (#117)
Bumps [ajv](https://github.com/ajv-validator/ajv) from 6.11.0 to 6.12.6.
- [Release notes](https://github.com/ajv-validator/ajv/releases)
- [Commits](https://github.com/ajv-validator/ajv/compare/v6.11.0...v6.12.6)

---
updated-dependencies:
- dependency-name: ajv
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-04 18:55:20 +02:00
dependabot[bot] f62d26c665
Bump node-fetch from 2.6.1 to 2.6.7 (#116)
Bumps [node-fetch](https://github.com/node-fetch/node-fetch) from 2.6.1 to 2.6.7.
- [Release notes](https://github.com/node-fetch/node-fetch/releases)
- [Commits](https://github.com/node-fetch/node-fetch/compare/v2.6.1...v2.6.7)

---
updated-dependencies:
- dependency-name: node-fetch
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-04 18:27:23 +02:00
dependabot[bot] c9d077d4f4
Bump axios from 0.21.1 to 0.21.4 (#107)
Bumps [axios](https://github.com/axios/axios) from 0.21.1 to 0.21.4.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/master/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v0.21.1...v0.21.4)

---
updated-dependencies:
- dependency-name: axios
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-04 18:26:28 +02:00
dependabot[bot] 5e95102038
Bump async from 3.2.0 to 3.2.3 (#124)
Bumps [async](https://github.com/caolan/async) from 3.2.0 to 3.2.3.
- [Release notes](https://github.com/caolan/async/releases)
- [Changelog](https://github.com/caolan/async/blob/master/CHANGELOG.md)
- [Commits](https://github.com/caolan/async/compare/v3.2.0...v3.2.3)

---
updated-dependencies:
- dependency-name: async
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-04 18:26:00 +02:00
Lion - dapplion 924322f651
Merge pull request #118 from ChainSafe/dependabot/npm_and_yarn/karma-6.3.16
Bump karma from 4.4.1 to 6.3.16
2022-05-04 17:02:10 +02:00
dependabot[bot] 2fc5d60f85
Bump karma from 4.4.1 to 6.3.16
Bumps [karma](https://github.com/karma-runner/karma) from 4.4.1 to 6.3.16.
- [Release notes](https://github.com/karma-runner/karma/releases)
- [Changelog](https://github.com/karma-runner/karma/blob/master/CHANGELOG.md)
- [Commits](https://github.com/karma-runner/karma/compare/v4.4.1...v6.3.16)

---
updated-dependencies:
- dependency-name: karma
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-05-04 14:39:21 +00:00
Lion - dapplion 89f857a40d
Merge pull request #123 from ChainSafe/dependabot/npm_and_yarn/async-2.6.4
Bump async from 2.6.3 to 2.6.4
2022-05-04 16:38:21 +02:00
dependabot[bot] 24555b5ada
Bump async from 2.6.3 to 2.6.4
Bumps [async](https://github.com/caolan/async) from 2.6.3 to 2.6.4.
- [Release notes](https://github.com/caolan/async/releases)
- [Changelog](https://github.com/caolan/async/blob/v2.6.4/CHANGELOG.md)
- [Commits](https://github.com/caolan/async/compare/v2.6.3...v2.6.4)

---
updated-dependencies:
- dependency-name: async
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-29 17:18:32 +00:00
Cayman 1845268ed0
Update readme/package.json 2022-04-25 04:21:01 -05:00
Cayman 8678f48dd5
Remove node 12 from ci tests 2022-04-24 17:01:13 -05:00
Marin Petrunic ed9f26f593 fix benchmarks 2022-04-15 11:56:13 +02:00
Marin Petrunic 35bf7684d7 fix web tests 2022-04-15 11:51:06 +02:00
Cayman 7c2e883527
Tweaks and upgrade karma/webpack 2022-04-14 12:16:06 -05:00
Cayman 8e8447d2f9
Don't export types from switchable 2022-04-13 15:06:11 -05:00
Cayman 269c96832b
Simplify exports 2022-04-13 14:36:07 -05:00
Cayman 501f93e333
Add herumi fallback in default export 2022-04-13 13:48:25 -05:00
Cayman a8aa891768
Tweak default export 2022-04-13 13:33:59 -05:00
Cayman b02fbc57a0
Export all types in types.ts 2022-04-13 13:23:27 -05:00
Cayman 9a851a095a
Add default export 2022-04-13 13:14:12 -05:00
Cayman 31c236ab05
Merge pull request #122 from ChainSafe/philknows-codeowners
Update CODEOWNERS
2022-04-12 14:44:33 -05:00
Phil Ngo 5d9ca0fb0b
Update CODEOWNERS 2022-04-11 15:42:37 -06:00
Cayman 8881334330
Use @chainsafe/threads 2022-04-11 15:15:17 -05:00
Cayman 0056e13ee5
feat: use esm
BREAKING CHANGE:
Only esm is exported, no commonjs
Named exports (excluding `bls`) are no longer exported from the root.
`browser` and `node` exports are removed in favor of
implementation-specific naming.
2022-04-11 10:15:43 -05:00
Cayman 53df67a2dd
Merge pull request #115 from paulmillr/patch-1
Update bls-keygen to 0.4
2022-02-22 13:17:55 -07:00
Cayman e88ae5b04a
Update yarn.lock 2022-02-22 14:14:05 -06:00
Paul Miller 1459ad5052
Update bls-keygen to 0.4 2022-02-22 21:35:48 +02:00
Lion - dapplion 474160eb68
Merge pull request #108 from g11tech/v6.0.3
release 6.0.3
2021-09-26 11:42:59 +02:00
g11tech 97d0047b58 release 6.0.3 2021-09-25 11:40:28 +05:30
Lion - dapplion dae5bb6fab
Merge pull request #106 from g11tech/noting2fix
bls-eth-wasm package update for invalidating signature not in g2
2021-09-22 18:44:25 +02:00
g11tech 40fc4b33a2 herumi fromBytes explicit signature 2021-09-22 20:51:31 +05:30
g11tech 5731f49454 ignoring the validate flag on herumi signature impl 2021-09-22 15:19:58 +05:30
g11tech 6854661b29 bls-eth-wasm package update for invalidating signature not in g2, validation on by default 2021-09-22 11:05:53 +05:30
Cayman bb1449d878
Merge pull request #103 from ChainSafe/dapplion/v6.0.2
v6.0.2
2021-08-23 13:02:18 -05:00
dapplion b02b7aabdc v6.0.2 2021-08-23 19:55:04 +02:00
Cayman f830b41ecc
Merge pull request #102 from ChainSafe/dapplion/register
Add register script
2021-08-23 12:45:58 -05:00
dapplion 61ce6b1ead Add register script 2021-08-23 19:27:09 +02:00
Marin Petrunić 5d323c0d62
Merge pull request #100 from ChainSafe/dependabot/npm_and_yarn/color-string-1.6.0
Bump color-string from 1.5.3 to 1.6.0
2021-08-04 09:46:09 +02:00
dependabot[bot] e23dd96158
Bump color-string from 1.5.3 to 1.6.0
Bumps [color-string](https://github.com/Qix-/color-string) from 1.5.3 to 1.6.0.
- [Release notes](https://github.com/Qix-/color-string/releases)
- [Changelog](https://github.com/Qix-/color-string/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Qix-/color-string/commits/1.6.0)

---
updated-dependencies:
- dependency-name: color-string
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-08-04 07:36:20 +00:00
Marin Petrunić d90f62c97c
Merge pull request #99 from ChainSafe/dependabot/npm_and_yarn/tar-6.1.4
Bump tar from 6.0.5 to 6.1.4
2021-08-04 09:35:39 +02:00
dependabot[bot] 4064e28d94
Bump tar from 6.0.5 to 6.1.4
Bumps [tar](https://github.com/npm/node-tar) from 6.0.5 to 6.1.4.
- [Release notes](https://github.com/npm/node-tar/releases)
- [Changelog](https://github.com/npm/node-tar/blob/main/CHANGELOG.md)
- [Commits](https://github.com/npm/node-tar/compare/v6.0.5...v6.1.4)

---
updated-dependencies:
- dependency-name: tar
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-08-03 22:10:44 +00:00
Cayman a158b704ea
Merge pull request #97 from ChainSafe/P0/stale-badges
remove stale badge
2021-06-29 15:56:29 -05:00
3xtr4t3rr3str14l 67c43fbb5f remove stale badge 2021-06-23 15:59:58 -05:00
Marin Petrunić 1f5c183d9b
Merge pull request #95 from ChainSafe/dependabot/npm_and_yarn/hosted-git-info-2.8.9
Bump hosted-git-info from 2.8.5 to 2.8.9
2021-06-08 16:12:08 +02:00
Marin Petrunić 3de12e402b
Merge pull request #94 from ChainSafe/dependabot/npm_and_yarn/lodash-4.17.21
Bump lodash from 4.17.19 to 4.17.21
2021-06-08 16:11:37 +02:00
Marin Petrunić f7096f9ac9
Merge pull request #93 from ChainSafe/dependabot/npm_and_yarn/ssri-6.0.2
Bump ssri from 6.0.1 to 6.0.2
2021-06-08 16:02:26 +02:00
dependabot[bot] 8ecdf5e27b
Bump hosted-git-info from 2.8.5 to 2.8.9
Bumps [hosted-git-info](https://github.com/npm/hosted-git-info) from 2.8.5 to 2.8.9.
- [Release notes](https://github.com/npm/hosted-git-info/releases)
- [Changelog](https://github.com/npm/hosted-git-info/blob/v2.8.9/CHANGELOG.md)
- [Commits](https://github.com/npm/hosted-git-info/compare/v2.8.5...v2.8.9)

Signed-off-by: dependabot[bot] <support@github.com>
2021-05-11 12:45:49 +00:00
dependabot[bot] 6820df9590
Bump lodash from 4.17.19 to 4.17.21
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.19 to 4.17.21.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.19...4.17.21)

Signed-off-by: dependabot[bot] <support@github.com>
2021-05-11 03:55:31 +00:00
dependabot[bot] 76c3dab8fc
Bump ssri from 6.0.1 to 6.0.2
Bumps [ssri](https://github.com/npm/ssri) from 6.0.1 to 6.0.2.
- [Release notes](https://github.com/npm/ssri/releases)
- [Changelog](https://github.com/npm/ssri/blob/v6.0.2/CHANGELOG.md)
- [Commits](https://github.com/npm/ssri/compare/v6.0.1...v6.0.2)

Signed-off-by: dependabot[bot] <support@github.com>
2021-04-30 16:17:10 +00:00
Lion - dapplion d0957daff1
Merge pull request #91 from ChainSafe/dapplion/v6.0.1
v6.0.1 Release
2021-04-05 23:08:23 +02:00
dapplion 9c0e071405 v6.0.1 Release 2021-04-05 22:53:31 +02:00
Cayman 30eea7095e
Merge pull request #90 from ChainSafe/dapplion/keyValidate
Add validate key option to PublicKey.fromBytes()
2021-04-05 15:45:47 -05:00
dapplion adf89c8360 Add validate key option to PublicKey.fromBytes() 2021-04-05 22:38:12 +02:00
106 changed files with 3472 additions and 3717 deletions

View File

@ -13,13 +13,15 @@ module.exports = {
},
parser: "@typescript-eslint/parser",
parserOptions: {
ecmaVersion: 10,
project: "./tsconfig.json"
ecmaVersion: "latest",
project: "./tsconfig.json",
sourceType: "module",
},
plugins: [
"@typescript-eslint",
"eslint-plugin-import",
"prettier"
"prettier",
"@chainsafe/eslint-plugin-node"
],
extends: [
"eslint:recommended",
@ -33,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": [
{

4
.github/CODEOWNERS vendored
View File

@ -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

2
.github/FUNDING.yml vendored
View File

@ -1 +1 @@
custom: https://gitcoin.co/grants/79/lodestar-eth20-client
custom: https://gitcoin.co/grants/6034/lodestar-typescript-ethereum-consensus-client

25
.github/stale.yml vendored
View File

@ -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: >

View File

@ -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 }}

View File

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

View File

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

View File

@ -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

View File

@ -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)

View File

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

View File

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

View File

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

View File

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

1
blst-native.d.ts vendored
View File

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

View File

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

1
browser.d.ts vendored
View File

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

View File

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

1
herumi.d.ts vendored
View File

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

View File

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

View File

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

8
lib/blst-native/index.d.ts vendored Normal file
View File

@ -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;

14
lib/blst-native/index.js Normal file
View File

@ -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;

11
lib/blst-native/publicKey.d.ts vendored Normal file
View File

@ -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;
}

View File

@ -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));
}
}

15
lib/blst-native/secretKey.d.ts vendored Normal file
View File

@ -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;
}

View File

@ -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());
}
}

21
lib/blst-native/signature.d.ts vendored Normal file
View File

@ -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;
}

View File

@ -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);
}
}

5
lib/constants.d.ts vendored Normal file
View File

@ -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;

5
lib/constants.js Normal file
View File

@ -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;

25
lib/errors.d.ts vendored Normal file
View File

@ -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);
}

39
lib/errors.js Normal file
View File

@ -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`);
}
}

15
lib/functional.d.ts vendored Normal file
View File

@ -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;
};

137
lib/functional.js Normal file
View File

@ -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,
};
}

2
lib/getImplementation.d.ts vendored Normal file
View File

@ -0,0 +1,2 @@
import type { IBls, Implementation } from "./types.js";
export declare function getImplementation(impl?: Implementation): Promise<IBls>;

17
lib/getImplementation.js Normal file
View File

@ -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}`);
}
}

10
lib/helpers/hex.d.ts vendored Normal file
View File

@ -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;

30
lib/helpers/hex.js Normal file
View File

@ -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;
}

2
lib/helpers/index.d.ts vendored Normal file
View File

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

2
lib/helpers/index.js Normal file
View File

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

8
lib/helpers/utils.d.ts vendored Normal file
View File

@ -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;

26
lib/helpers/utils.js Normal file
View File

@ -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;
}

12
lib/herumi/context.d.ts vendored Normal file
View File

@ -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 {};

35
lib/herumi/context.js Normal file
View File

@ -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;
}

9
lib/herumi/index.d.ts vendored Normal file
View File

@ -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;

18
lib/herumi/index.js Normal file
View File

@ -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;

11
lib/herumi/publicKey.d.ts vendored Normal file
View File

@ -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;
}

53
lib/herumi/publicKey.js Normal file
View File

@ -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));
}
}

15
lib/herumi/secretKey.d.ts vendored Normal file
View File

@ -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;
}

43
lib/herumi/secretKey.js Normal file
View File

@ -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());
}
}

24
lib/herumi/signature.d.ts vendored Normal file
View File

@ -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;
}

70
lib/herumi/signature.js Normal file
View File

@ -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));
}
}

1
lib/herumi/web.d.ts vendored Normal file
View File

@ -0,0 +1 @@
export {};

6
lib/herumi/web.js Normal file
View File

@ -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);

3
lib/index.d.ts vendored Normal file
View File

@ -0,0 +1,3 @@
import type { IBls } from "./types.js";
export declare const bls: () => Promise<IBls>;
export default bls;

14
lib/index.js Normal file
View File

@ -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;

4
lib/switchable.d.ts vendored Normal file
View File

@ -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>;

11
lib/switchable.js Normal file
View File

@ -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

66
lib/types.d.ts vendored Normal file
View File

@ -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
}

10
lib/types.js Normal file
View File

@ -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 = {}));

1
node.d.ts vendored
View File

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

View File

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

View File

@ -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"
}
}

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

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

View File

@ -1,7 +1,7 @@
import * as blst from "@chainsafe/blst";
import {EmptyAggregateError, 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);
}

View File

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

View File

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

View File

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

View File

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

22
src/getImplementation.ts Normal file
View File

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

View File

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

View File

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

View File

@ -1,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;

View File

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

View File

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

View File

@ -1,10 +1,10 @@
import {SignatureType, multiVerify} from "bls-eth-wasm";
import {getContext} from "./context";
import {PublicKey} from "./publicKey";
import {bytesToHex, concatUint8Arrays, hexToBytes, isZeroUint8Array} from "../helpers";
import {PointFormat, Signature as ISignature} 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)

View File

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

View File

@ -1,47 +1,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;

14
src/switchable.ts Normal file
View File

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

View File

@ -1,8 +1,8 @@
export interface IBls {
implementation: Implementation;
SecretKey: Omit<typeof SecretKey, "prototype">;
PublicKey: Omit<typeof PublicKey, "prototype">;
Signature: Omit<typeof Signature, "prototype">;
SecretKey: typeof SecretKey;
PublicKey: typeof PublicKey;
Signature: typeof Signature;
sign(secretKey: Uint8Array, message: Uint8Array): Uint8Array;
aggregatePublicKeys(publicKeys: Uint8Array[]): Uint8Array;
@ -12,12 +12,11 @@ export interface IBls {
verifyMultiple(publicKeys: Uint8Array[], messages: Uint8Array[], signature: Uint8Array): boolean;
verifyMultipleSignatures(sets: {publicKey: Uint8Array; message: Uint8Array; signature: Uint8Array}[]): boolean;
secretKeyToPublicKey(secretKey: Uint8Array): Uint8Array;
init(): Promise<void>;
destroy(): void;
}
export declare class SecretKey {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private constructor(...value: any);
static fromBytes(bytes: Uint8Array): SecretKey;
static fromHex(hex: string): SecretKey;
static fromKeygen(entropy?: Uint8Array): SecretKey;
@ -28,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;

View File

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

View File

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

View File

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

View File

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

View File

@ -1,8 +1,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},

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,10 +1,14 @@
import {expect} from "chai";
import {SecretKey, PublicKey, Signature, init, bls} from "../../src";
import type {SecretKey, PublicKey, Signature, IBls} from "../../src/types.js";
describe("types named exports", async () => {
let bls: IBls;
before(async () => {
bls = (await import("../../src/index.js")).default;
});
describe("index named exports", () => {
it("Classes and methods should be defined", async () => {
await init("herumi");
/**
* Sample helper to test argument typing
*/
@ -12,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", () => {

View File

@ -1,8 +1,9 @@
import {expect} from "chai";
import {IBls, PointFormat} from "../../src/interface";
import {getN, randomMessage, hexToBytesNode} from "../util";
import {hexToBytes} from "../../src/helpers";
import {maliciousVerifyMultipleSignaturesData} from "../data/malicious-signature-test-data";
import {Buffer} from "buffer";
import {IBls, PointFormat} from "../../src/types.js";
import {getN, randomMessage} from "../util.js";
import {hexToBytes} from "../../src/helpers/index.js";
import {maliciousVerifyMultipleSignaturesData} from "../data/malicious-signature-test-data.js";
export function runIndexTests(bls: IBls): void {
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
@ -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");
});

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

Some files were not shown because too many files have changed in this diff Show More