Compare commits

...

168 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
Cayman 12d64eeb56
Merge pull request #89 from ChainSafe/dapplion/update-yarn-lock
Update yarn.lock
2021-04-05 15:00:42 -05:00
dapplion b7afcf24cb Update yarn.lock 2021-04-05 21:56:31 +02:00
Lion - dapplion 0f26fe3012
Merge pull request #88 from ChainSafe/dapplion/v6.0.0
v6.0.0 Release
2021-04-05 21:47:23 +02:00
dapplion b3f5285cbc v6.0.0 Release 2021-04-05 21:41:37 +02:00
Lion - dapplion 2decf4ea75
Merge pull request #87 from ChainSafe/dapplion/run-node12-14
Run tests on node 14 too
2021-04-05 21:39:44 +02:00
Lion - dapplion 922774cd8d
Merge pull request #86 from ChainSafe/dapplion/speedup-benchmark
Speedup benchmarks
2021-04-05 21:14:31 +02:00
dapplion 52f4c43802 Run tests on node 14 too 2021-04-05 21:10:34 +02:00
dapplion f694dd3754 Speed-up benchmarks
Simplify benchmarks code
2021-04-05 21:02:07 +02:00
Lion - dapplion 0f895b4795
Merge pull request #85 from ChainSafe/dapplion/multi-threading
Enable multi-threading for all implementations
2021-04-05 20:54:20 +02:00
dapplion f7128ad53a Remove un-used import 2021-04-05 20:29:34 +02:00
dapplion bf2b457f48 Fix type issue with class type declaration 2021-04-05 20:27:17 +02:00
dapplion 1236f7a2cc Document default value of optional params 2021-04-05 19:47:25 +02:00
dapplion 3bf2d48e2e Update benchmark API 2021-04-05 18:03:49 +02:00
dapplion ac9564d53a Skip herumi in multi-thread tests 2021-04-05 17:52:21 +02:00
dapplion 7d9aebd3fe Remove warmup workers function 2021-04-05 17:49:18 +02:00
dapplion 69c3408964 Enable multi-threading for all implementations 2021-04-05 17:49:18 +02:00
Cayman 3ca1e26843
Merge pull request #84 from ChainSafe/dapplion/use-mocharc
Use mocharc
2021-04-05 08:47:41 -05:00
dapplion e81764f710 Use mocharc 2021-04-04 23:00:37 +02:00
Marin Petrunić e0799decc6
Merge pull request #83 from ChainSafe/dependabot/npm_and_yarn/y18n-4.0.1
Bump y18n from 4.0.0 to 4.0.1
2021-03-31 16:55:04 +02:00
dependabot[bot] 7ee37f779c
Bump y18n from 4.0.0 to 4.0.1
Bumps [y18n](https://github.com/yargs/y18n) from 4.0.0 to 4.0.1.
- [Release notes](https://github.com/yargs/y18n/releases)
- [Changelog](https://github.com/yargs/y18n/blob/master/CHANGELOG.md)
- [Commits](https://github.com/yargs/y18n/commits)

Signed-off-by: dependabot[bot] <support@github.com>
2021-03-31 14:36:44 +00:00
Marin Petrunić 364a8f20d2
Merge pull request #74 from ChainSafe/dependabot/npm_and_yarn/ini-1.3.8
Bump ini from 1.3.5 to 1.3.8
2021-03-10 13:00:23 +01:00
dependabot[bot] d906c508ad
Bump ini from 1.3.5 to 1.3.8
Bumps [ini](https://github.com/isaacs/ini) from 1.3.5 to 1.3.8.
- [Release notes](https://github.com/isaacs/ini/releases)
- [Commits](https://github.com/isaacs/ini/compare/v1.3.5...v1.3.8)

Signed-off-by: dependabot[bot] <support@github.com>
2021-03-10 09:02:41 +00:00
Marin Petrunić c12735455c
Merge pull request #82 from ChainSafe/dependabot/npm_and_yarn/elliptic-6.5.4
Bump elliptic from 6.5.3 to 6.5.4
2021-03-10 10:01:16 +01:00
dependabot[bot] f407dda705
Bump elliptic from 6.5.3 to 6.5.4
Bumps [elliptic](https://github.com/indutny/elliptic) from 6.5.3 to 6.5.4.
- [Release notes](https://github.com/indutny/elliptic/releases)
- [Commits](https://github.com/indutny/elliptic/compare/v6.5.3...v6.5.4)

Signed-off-by: dependabot[bot] <support@github.com>
2021-03-10 08:40:47 +00:00
Lion - dapplion c0f49f4fb5
Merge pull request #80 from ChainSafe/dapplion/blst-ts-0.1.6
Bump blst-ts to v0.1.6
2021-01-27 19:02:27 +01:00
dapplion 618556e9fb Bump blst-ts to v0.1.6 2021-01-27 18:51:09 +01:00
Lion - dapplion f4fea28ba1
Merge pull request #79 from ChainSafe/dependabot/npm_and_yarn/axios-0.21.1
Bump axios from 0.21.0 to 0.21.1
2021-01-06 12:39:07 +01:00
dependabot[bot] 4da4cc4ddf
Bump axios from 0.21.0 to 0.21.1
Bumps [axios](https://github.com/axios/axios) from 0.21.0 to 0.21.1.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v0.21.1/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v0.21.0...v0.21.1)

Signed-off-by: dependabot[bot] <support@github.com>
2021-01-06 10:28:38 +00:00
Cayman a525c89fb2
Merge pull request #78 from ChainSafe/dapplion/5.1.1
Bump to v5.1.1
2020-12-18 10:35:29 -07:00
dapplion 190e0ce570 Update changelog 2020-12-18 13:28:24 +01:00
dapplion af5f136a15 Bump to v5.1.1 2020-12-18 13:08:51 +01:00
Lion - dapplion 7c20b2e1d2
Merge pull request #76 from ChainSafe/blst-worker-threads
Enable worker-threads support for blst
2020-12-18 13:00:46 +01:00
Lion - dapplion 916a9e92f1
Merge pull request #77 from ChainSafe/dapplion/github-meta
Add Github metadata
2020-12-17 19:48:55 +01:00
dapplion affabe1df4 Add Github metadata 2020-12-17 18:49:20 +01:00
dapplion 027c707264 Enable worker-threads support for blst 2020-12-17 18:45:38 +01:00
Lion - dapplion e0b5089cd9
Merge pull request #75 from ChainSafe/mpetrunic/fix/1810
Fix silent pass if no spec tests - #1810
2020-12-17 18:42:05 +01:00
Marin Petrunić 112668bd4e
fix silent pass if no spec tests 2020-12-14 10:13:56 +01:00
Lion - dapplion 0228256251
Merge pull request #71 from ChainSafe/v5.1.0-release
v5.1.0 Release
2020-12-08 22:00:56 +00:00
dapplion 3fdd17b718 v5.1.0 Release 2020-12-08 21:52:17 +00:00
Cayman c700075914
Merge pull request #69 from paulmillr/patch-3
Readme: update benchmarks.
2020-12-08 08:24:08 -07:00
Lion - dapplion d82a0e9862
Merge pull request #68 from ChainSafe/dapplion/tsc
Simplify build setup with tsc
2020-12-08 15:22:40 +00:00
Paul Miller f4f17392ff
Readme: update benchmarks. 2020-12-08 00:22:37 +02:00
Cayman 945e10b852
Merge pull request #67 from ChainSafe/dapplion/strictNullChecks
Set strictNullChecks to true
2020-12-07 15:04:31 -07:00
Cayman caf5b74390
Merge pull request #64 from ChainSafe/dapplion/verifyMultipleSignatures
Benchmark verifyMultipleSignatures savings
2020-12-07 15:04:04 -07:00
Cayman 65cb5f2f35
Merge pull request #62 from paulmillr/patch-3
Benchmarks: update noble to use decompressed points.
2020-12-07 15:03:27 -07:00
dapplion 430841f9bd Simplify build setup with tsc 2020-12-06 11:44:32 +00:00
dapplion 25b1fdcb1b Set strictNullChecks to true 2020-12-06 11:38:05 +00:00
Paul Miller a303f77120
Update package.json 2020-12-05 11:13:40 +04:00
dapplion fa8a89c485 Benchmark verifyMultipleSignatures savings 2020-12-04 21:05:48 +00:00
Paul Miller 94d8fce815 Fix noble 2020-12-04 18:54:47 +00:00
Cayman d834657542
Merge pull request #58 from ChainSafe/dapplion/verifyMultipleSignatures
Add verifyMultipleSignatures method
2020-12-04 07:53:06 -07:00
dapplion 7ef455157a Allow window to not be prefixed 2020-12-04 09:31:35 +00:00
Paul Miller 86c956e12a
Quick fix for types 2020-12-03 23:37:42 +04:00
dapplion c8798707d9 Remove 2 tsconfig-ignore 2020-12-03 19:09:15 +00:00
Paul Miller 94caa9bedd
Merge branch 'master' into patch-3 2020-12-03 20:23:21 +04:00
Paul Miller e995e07fbb
Fix conflicts 2020-12-03 20:22:28 +04:00
Paul Miller 78c289cb21
Update noble. 2020-12-03 20:18:13 +04:00
Paul Miller fcdb61b193
Fix aggregate sigs. 2020-12-03 20:17:28 +04:00
Cayman a3a1831418
Merge pull request #60 from ChainSafe/dapplion/benchmark-sign
Benchmark sign()
2020-12-03 09:17:12 -07:00
Cayman 4729f97342
Merge pull request #59 from paulmillr/patch-1
Benchmarks: Clarify tech.
2020-12-03 09:13:59 -07:00
Paul Miller 1ba1247978
Benchmarks: update noble to use decompressed points.
Much faster now
2020-12-03 20:01:14 +04:00
Paul Miller 8d6c4df745
Apply suggestions from code review
Co-authored-by: Marin Petrunić <mpetrunic@users.noreply.github.com>
2020-12-03 18:53:00 +04:00
dapplion 9b6369c07a Benchmark sign() 2020-12-03 09:38:13 +00:00
Paul Miller 61e5a37cff
Benchmarks: Clarify tech.
It may be useful for folks to know that blst is node-only and noble is pure JS.
2020-12-03 13:12:22 +04:00
dapplion 0b10c2cb4e Add deleted method in tests util 2020-12-03 00:31:52 +00:00
dapplion 84488456de Patch herumi's multiVerify() on browsers 2020-12-03 00:24:30 +00:00
dapplion 2a27b19287 Run webpack on production mode 2020-12-03 00:06:46 +00:00
dapplion 7f76672a40 Use browser friendly concatUint8Arrays instead of Buffer.concat 2020-12-03 00:06:36 +00:00
dapplion 13ea412c3f Move web test to a different folder to not run herumi tests twice 2020-12-03 00:05:49 +00:00
dapplion d4d97795ca Remove comment 2020-12-03 00:05:14 +00:00
dapplion 2f0db3f39f Disable webpack minification for better debugging on Karma tests 2020-12-02 23:36:58 +00:00
dapplion 93499b3f34 Fix class method usage 2020-12-02 23:33:44 +00:00
dapplion d2c11ed16c Use class interface for more transparent errors 2020-12-02 23:32:42 +00:00
dapplion fc868ffd27 Add benchmark 2020-12-02 23:21:48 +00:00
dapplion 092165a5f4 Move test data to file 2020-12-02 23:19:33 +00:00
dapplion 8d5206c31f Test verifyMultipleSignatures against malicious input 2020-12-02 23:19:33 +00:00
dapplion a8f7256fcd Add verifyMultipleSignatures method 2020-12-02 23:19:12 +00:00
Lion - dapplion e8d81fef96
Merge pull request #57 from ChainSafe/dapplion/benchmarks
Add benchmark results
2020-12-02 23:18:28 +00:00
dapplion b324213292 Update benchmark results from CI run 2020-12-02 21:10:34 +00:00
dapplion 324285c38a Run benchmarks for verifyMultiple 2020-12-02 20:54:46 +00:00
dapplion e356bd1b37 Present results as a table 2020-12-02 20:45:29 +00:00
dapplion a207c23f3e Link to CI run 2020-12-01 09:32:15 +00:00
dapplion 88caa9bebe Add benchmark results 2020-12-01 09:01:45 +00:00
Lion - dapplion 5340e029bd
Merge pull request #56 from ChainSafe/bump-lodestar
Bump @chainsafe/lodestar-spec-test-util
2020-12-01 08:53:07 +00:00
dapplion 9e3fe5a2d0 Run benchmark:all on benchmark:all 2020-12-01 08:40:51 +00:00
dapplion b2101553b1 Run download-test-cases in CI 2020-12-01 08:38:05 +00:00
dapplion 4e2f60b5b8 Fix benchmark setup 2020-12-01 08:29:19 +00:00
dapplion fa3c9a8c47 Store spec tests data in local directory 2020-12-01 08:22:58 +00:00
dapplion b64ff7f1d4 bump @chainsafe/lodestar-spec-test-util 2020-12-01 08:17:25 +00:00
130 changed files with 4729 additions and 5054 deletions

View File

@ -1,15 +0,0 @@
/*
See
https://github.com/babel/babel/issues/8652
https://github.com/babel/babel/pull/6027
Babel isn't currently configured by default to read .ts files and
can only be configured to do so via cli or configuration below.
This file is used by mocha to interpret test files using a properly
configured babel.
This can (probably) be removed in babel 8.x.
*/
require('@babel/register')({
extensions: ['.ts'],
})

View File

@ -1,22 +0,0 @@
{
"presets": [
[
"@babel/env",
{
"targets": {
"node": "10.4",
"browsers": ">1%, not ie 11"
},
"exclude": [
"transform-regenerator"
]
}
],
"@babel/typescript"
],
"plugins": [
"@babel/proposal-class-properties",
"@babel/proposal-object-rest-spread",
"@babel/plugin-syntax-bigint"
]
}

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": [
{

11
.github/CODEOWNERS vendored Normal file
View File

@ -0,0 +1,11 @@
# These owners will be the default owners for everything in
# the repo. Unless a later match takes precedence,
# They will be requested for
# review when someone opens a pull request.
* @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 @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

31
.github/ISSUE_TEMPLATE/bug_report.md vendored Normal file
View File

@ -0,0 +1,31 @@
---
name: Bug report
about: Create a report to help us improve
---
<!--NOTE: -->
<!--- General questions should go to the discord chat instead of the issue tracker.-->
**Describe the bug**
<!--A clear and concise description of what the bug is and steps to reproduce it.-->
**Expected behavior**
<!--A clear and concise description of what you expected to happen.-->
**Steps to Reproduce**
<!--Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
-->
**Screenshots**
<!--If applicable, add screenshots to help explain your problem.-->
**Desktop (please complete the following information):**
- OS: <!--[e.g. ubuntu, OSX High Siera]-->
- Version: <!--[e.g. 22]-->
- Branch: <!--[Master]-->
- Commit hash: <!--[e8232]-->

View File

@ -0,0 +1,21 @@
---
name: Feature request
about: Suggest an idea for this project
---
<!--NOTE:
- General questions should go to the discord chat instead of the issue tracker.
-->
**Is your feature request related to a problem? Please describe.**
<!--A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]-->
**Describe the solution you'd like**
<!--A clear and concise description of what you want to happen.-->
**Describe alternatives you've considered**
<!--A clear and concise description of any alternative solutions or features you've considered.-->
**Additional context**
<!--Add any other context or screenshots about the feature request here.-->

View File

@ -0,0 +1,13 @@
---
name: Architecture/Planning Question
about: Suggest an idea for this project
---
<!--
NOTE:
- General questions should go to the discord chat instead of the issue tracker.
-->
**What is your question?**
<!--A clear and concise description of what the problem is.-->

69
.github/stale.yml vendored Normal file
View File

@ -0,0 +1,69 @@
# Configuration for probot-stale - https://github.com/probot/stale
# Number of days of inactivity before an Issue or Pull Request becomes stale
daysUntilStale: 60
# Number of days of inactivity before an Issue or Pull Request with the stale label is closed.
# Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale.
daysUntilClose: 15
# Only issues or pull requests with all of these labels are check if stale. Defaults to `[]` (disabled)
onlyLabels: []
# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable
exemptLabels:
- "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
# Set to true to ignore issues in a milestone (defaults to false)
exemptMilestones: true
# Set to true to ignore issues with an assignee (defaults to false)
exemptAssignees: true
# Label to use when marking as stale
staleLabel: meta-stale
# Comment to post when marking as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed in 15 days if no further activity occurs. Thank you
for your contributions.
# Comment to post when removing the stale label.
# unmarkComment: >
# Your comment here
# Comment to post when closing a stale Issue or Pull Request.
closeComment: >
This issue or pull request has been automatically been closed due to inactivity.
# Limit the number of actions per hour, from 1-30. Default is 30
limitPerRun: 30
# Limit to only `issues` or `pulls`
# only: issues
# Optionally, specify configuration settings that are specific to just 'issues' or 'pulls':
# pulls:
# daysUntilStale: 30
# markComment: >
# This pull request has been automatically marked as stale because it has not had
# recent activity. It will be closed if no further activity occurs. Thank you
# for your contributions.
# issues:
# exemptLabels:
# - confirmed

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

@ -6,16 +6,17 @@ jobs:
tests:
name: Tests
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
node: [14, 16]
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: "12.x"
registry-url: "https://registry.npmjs.org"
node-version: ${{matrix.node}}
- name: Install deps
run: yarn
- name: Check types
run: yarn check-types
run: yarn --non-interactive --frozen-lockfile
- name: Lint
run: yarn lint
- name: Test build
@ -23,6 +24,8 @@ jobs:
- name: Unit tests
run: yarn test:unit
- name: Download spec tests
run: yarn download-test-cases
- name: Spec tests
run: yarn test:spec
- name: Web tests

3
.gitignore vendored
View File

@ -67,3 +67,6 @@ lib/
benchmark-reports
.vscode/
# Eth2.0 spec tests data
test/spec-tests

6
.mocharc.yml Normal file
View File

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

View File

@ -5,7 +5,56 @@ 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
- Allow to export points compressed and uncompressed [#85](https://github.com/ChainSafe/bls/pull/85)
- For BLST impl allow to choose what point coordinates to deserialize to [#85](https://github.com/ChainSafe/bls/pull/85)
- Update function signature of `verifyMultipleSignatures()` to use grouped signature sets [#85](https://github.com/ChainSafe/bls/pull/85)
- Bump BLST implementation to fix multi-thread issues [#85](https://github.com/ChainSafe/bls/pull/85)
## [5.1.1] - 2020-12-18
- Enable worker-threads support for blst [#76](https://github.com/ChainSafe/bls/pull/76)
## [5.1.0] - 2020-11-30
- Bump @chainsafe/lodestar-spec-test-util [#56](https://github.com/ChainSafe/bls/pull/56)
- Add benchmark results [#57](https://github.com/ChainSafe/bls/pull/57)
- Add verifyMultipleSignatures method [#58](https://github.com/ChainSafe/bls/pull/58)
- Set strictNullChecks to true [#67](https://github.com/ChainSafe/bls/pull/67)
- Simplify build setup with tsc [#68](https://github.com/ChainSafe/bls/pull/68)
## [5.0.1] - 2020-11-30

103
README.md
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,79 +19,103 @@ 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-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)
Results are in `ops/sec (x times slower)`, where `x times slower` = times slower than fastest implementation (`blst`).
| Function - `ops/sec` | `blst` | `herumi` | `noble` |
| -------------------------------- | :----: | :----------: | :-----------: |
| `verify` | 326.38 | 47.674 (x7) | 17.906 (x18) |
| `verifyAggregate` (30) | 453.29 | 51.151 (x9) | 18.372 (x25) |
| `verifyMultiple` (30) | 34.497 | 3.5233 (x10) | 2.0286 (x17) |
| `verifyMultipleSignatures` (30) | 26.381 | 3.1633 (x8) | - |
| `aggregate` (pubkeys, 30) | 15686 | 2898.9 (x5) | 1875.0 (x8) |
| `aggregate` (sigs, 30) | 6373.4 | 1033.0 (x6) | 526.25 (x12) |
| `sign` | 925.49 | 108.81 (x9) | 10.246 (x90) |
\* `blst` and `herumi` performed 100 runs each, `noble` 10 runs.
Results from CI run https://github.com/ChainSafe/bls/runs/1513710175?check_suite_focus=true#step:12:13
## Spec versioning
| Version | Bls spec hash-to-curve version |
| ------- | :--------------: |
| 5.x.x | draft #9 |
| 2.x.x | draft #7 |
| 1.x.x | draft #6 |
| 0.3.x | initial version |
| ------- | :----------------------------: |
| 5.x.x | draft #9 |
| 2.x.x | draft #7 |
| 1.x.x | draft #6 |
| 0.3.x | initial version |
> [spec](https://github.com/ethereum/eth2.0-specs/blob/v1.0.0/specs/phase0/beacon-chain.md#bls-signatures)

84
benchmark/index.ts Normal file
View File

@ -0,0 +1,84 @@
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) => {
const msgSame = Buffer.alloc(32, 255);
const sameMessage: {pk: PublicKey; msg: Uint8Array; sig: Signature}[] = [];
const diffMessage: {pk: PublicKey; msg: Uint8Array; sig: Signature}[] = [];
for (let i = 0; i < aggCount; i++) {
const msg = Buffer.alloc(32, i + 1);
const sk = bls.SecretKey.fromBytes(Buffer.alloc(32, i + 1));
const pk = sk.toPublicKey();
sameMessage.push({pk, msg: msgSame, sig: sk.sign(msgSame)});
diffMessage.push({pk, msg, sig: sk.sign(msg)});
}
const {pk, msg, sig} = diffMessage[0];
const sameMessageSig = bls.Signature.aggregate(sameMessage.map((s) => s.sig));
const diffMessageSig = bls.Signature.aggregate(diffMessage.map((s) => s.sig));
// verify
await runBenchmark({
id: `${implementation} verify`,
prepareTest: () => ({pk, msg, sig}),
testRunner: ({pk, msg, sig}) => sig.verify(pk, msg),
});
// Fast aggregate
await runBenchmark({
id: `${implementation} verifyAggregate (${aggCount})`,
prepareTest: () => ({pks: sameMessage.map((s) => s.pk), msg: msgSame, sig: sameMessageSig}),
testRunner: ({pks, msg, sig}) => sig.verifyAggregate(pks, msg),
});
// Verify multiple
await runBenchmark({
id: `${implementation} verifyMultiple (${aggCount})`,
prepareTest: () => ({
pks: diffMessage.map((s) => s.pk),
msgs: diffMessage.map((s) => s.msg),
sig: diffMessageSig,
}),
testRunner: ({pks, msgs, sig}) => sig.verifyMultiple(pks, msgs),
});
// Verify multiple signatures
await runBenchmark({
id: `${implementation} verifyMultipleSignatures (${aggCount})`,
prepareTest: () => diffMessage,
testRunner: (sets) =>
bls.Signature.verifyMultipleSignatures(sets.map((s) => ({publicKey: s.pk, message: s.msg, signature: s.sig}))),
});
// Aggregate pubkeys
await runBenchmark({
id: `${implementation} aggregate pubkeys (${aggCount})`,
prepareTest: () => diffMessage.map((s) => s.pk),
testRunner: (pks) => bls.PublicKey.aggregate(pks),
});
// Aggregate sigs
await runBenchmark({
id: `${implementation} aggregate signatures (${aggCount})`,
prepareTest: () => diffMessage.map((s) => s.sig),
testRunner: (sigs) => bls.Signature.aggregate(sigs),
});
// Sign
await runBenchmark({
id: `${implementation} sign`,
prepareTest: () => ({sk: bls.SecretKey.fromKeygen(), msg: msgSame}),
testRunner: ({sk, msg}) => sk.sign(msg),
});
});
})();

109
benchmark/noble.ts Normal file
View File

@ -0,0 +1,109 @@
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.js";
(async function () {
{
// verify
const priv = generateRandomSecretKey();
const msg = randomMessage();
const pk = noble.PointG1.fromPrivateKey(priv);
const sig = noble.PointG2.fromSignature(await noble.sign(msg, priv));
await runBenchmark<{pk: noble.PointG1; msg: noble.PointG2; sig: noble.PointG2}, boolean>({
id: `noble verify`,
prepareTest: async () => ({pk, msg: await noble.PointG2.hashToCurve(msg), sig}),
testRunner: async ({pk, msg, sig}) => await noble.verify(sig, msg, pk),
runs: runsNoble,
});
}
{
// Fast aggregate
const msg = randomMessage();
const dataArr = await Promise.all(
range(aggCount).map(async () => {
const sk = generateRandomSecretKey();
const pk = noble.PointG1.fromPrivateKey(sk);
const sig = noble.PointG2.fromSignature(await noble.sign(msg, sk));
return {pk, sig};
})
);
const pks = dataArr.map((data) => data.pk);
const sig = (noble.aggregateSignatures(dataArr.map((data) => data.sig)) as any) as noble.PointG2;
await runBenchmark({
id: `noble verifyAggregate (${aggCount})`,
prepareTest: async () => ({pks, msg: await noble.PointG2.hashToCurve(msg), sig}),
testRunner: async ({pks, msg, sig}) =>
await noble.verify(sig, msg, (noble.aggregatePublicKeys(pks) as any) as noble.PointG1),
runs: runsNoble,
});
}
{
// Verify multiple
const dataArr = await Promise.all(
range(aggCount).map(async () => {
const sk = generateRandomSecretKey();
const pk = noble.PointG1.fromPrivateKey(sk);
const msg = randomMessage();
const sig = noble.PointG2.fromSignature(await noble.sign(msg, sk));
return {pk, msg: await noble.PointG2.hashToCurve(msg), sig};
})
);
const pks = dataArr.map((data) => data.pk);
const msgs = dataArr.map((data) => data.msg);
const sig = (noble.aggregateSignatures(dataArr.map((data) => data.sig)) as any) as noble.PointG2;
await runBenchmark({
id: `noble verifyMultiple (${aggCount})`,
prepareTest: async () => ({pks, msgs, sig}),
testRunner: async ({pks, msgs, sig}) => await noble.verifyBatch(msgs, pks, sig),
runs: runsNoble,
});
}
{
// Aggregate pubkeys
const pubkeys = range(aggCount).map(() => noble.PointG1.fromPrivateKey(generateRandomSecretKey()));
await runBenchmark({
id: `noble aggregate pubkeys (${aggCount})`,
prepareTest: () => pubkeys,
testRunner: async (pks) => noble.aggregatePublicKeys(pks),
runs: runsNoble,
});
}
const hashes = await Promise.all(
range(aggCount)
.map(() => generateRandomSecretKey())
.map(noble.PointG2.hashToCurve)
);
await runBenchmark({
id: `noble aggregate signatures (${aggCount})`,
prepareTest: () => hashes,
testRunner: async (sigs) => noble.aggregateSignatures(sigs),
runs: runsNoble,
});
const sk = generateRandomSecretKey();
const msg = await noble.PointG2.hashToCurve(randomMessage());
await runBenchmark({
id: `noble sign`,
prepareTest: () => ({sk, msg}),
testRunner: async ({sk, msg}) => await noble.sign(msg, sk),
runs: runsNoble,
});
})();

14
benchmark/package.json Normal file
View File

@ -0,0 +1,14 @@
{
"name": "bls-libs-benchmark",
"version": "1.0.0",
"type": "module",
"exports": "./index.js",
"license": "MIT",
"scripts": {
"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"
}
}

3
benchmark/params.ts Normal file
View File

@ -0,0 +1,3 @@
export const aggCount = 30;
export const runs = 100;
export const runsNoble = 10;

View File

@ -6,7 +6,7 @@ export async function runBenchmark<T, R>({
runs = 100,
id,
}: {
prepareTest: (i: number) => PromiseOptional<{input: T; resultCheck?: (result: R) => boolean}>;
prepareTest: (i: number) => PromiseOptional<T>;
testRunner: (input: T) => PromiseOptional<R>;
runs?: number;
id: string;
@ -14,13 +14,12 @@ export async function runBenchmark<T, R>({
const diffsNanoSec: bigint[] = [];
for (let i = 0; i < runs; i++) {
const {input, resultCheck} = await prepareTest(i);
const input = await prepareTest(i);
const start = process.hrtime.bigint();
const result = await testRunner(input);
const end = process.hrtime.bigint();
if (resultCheck && !resultCheck(result)) throw Error("Result fails check test");
diffsNanoSec.push(end - start);
}

View File

@ -0,0 +1,34 @@
import {runForAllImplementations} from "../test/switch.js";
import {range, randomMessage} from "../test/util.js";
(async function () {
console.log("verifyMultipleSignatures savings");
console.log(["Impl", "# sigs", "ratio multi/single"].join("\t"));
await runForAllImplementations(async (bls, implementation) => {
for (const aggCount of [2, 5, 10, 20, 50, 100]) {
const dataArr = range(aggCount).map(() => {
const sk = bls.SecretKey.fromKeygen();
const pk = sk.toPublicKey();
const msg = randomMessage();
const sig = sk.sign(msg);
return {publicKey: pk, message: msg, signature: sig};
});
const startMulti = process.hrtime.bigint();
bls.Signature.verifyMultipleSignatures(dataArr);
const endMulti = process.hrtime.bigint();
const diffMulti = endMulti - startMulti;
const startSingle = process.hrtime.bigint();
for (const {publicKey, message, signature} of dataArr) {
signature.verify(publicKey, message);
}
const endSingle = process.hrtime.bigint();
const diffSingle = endSingle - startSingle;
const ratio = Number(diffMulti) / Number(diffSingle);
console.log([implementation, aggCount, ratio.toPrecision(2)].join("\t"));
}
});
})();

8
benchmark/yarn.lock Normal file
View File

@ -0,0 +1,8 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
noble-bls12-381@^0.7.2:
version "0.7.2"
resolved "https://registry.yarnpkg.com/noble-bls12-381/-/noble-bls12-381-0.7.2.tgz#9a9384891569ba32785d6e4ff8588b783487eae4"
integrity sha512-Z5isbU6opuWPL3dxsGqO5BdOE8WP1XUM7HFIn/xeE5pATTnml/PEIy4MFQQrktHiitkuJdsCDtzEOnS9eIpC3Q==

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

34
karma.conf.cjs Normal file
View File

@ -0,0 +1,34 @@
// eslint-disable-next-line @typescript-eslint/no-require-imports
const webpackConfig = require("./webpack.config.cjs");
module.exports = function (config) {
config.set({
basePath: "",
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",
module: webpackConfig.module,
resolve: webpackConfig.resolve,
experiments: webpackConfig.experiments,
optimization: webpackConfig.optimization,
stats: {warnings:false},
},
reporters: ["spec"],
browsers: ["ChromeHeadless"],
singleRun: true,
});
};

View File

@ -1,26 +0,0 @@
// eslint-disable-next-line @typescript-eslint/no-require-imports
const webpackConfig = require("./webpack.config");
module.exports = function(config) {
config.set({
basePath: "",
frameworks: ["mocha", "chai"],
files: ["test/unit/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
},
reporters: ["spec"],
browsers: ["ChromeHeadless"],
singleRun: true
});
};

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": "5.0.1",
"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,72 +62,63 @@
],
"scripts": {
"clean": "rm -rf lib && rm -rf dist && rm -f tsconfig.tsbuildinfo",
"build": "yarn build-lib && yarn build-types",
"build:release": "yarn clean && yarn build && yarn build-web",
"build-lib": "babel src -x .ts -d lib",
"build-types": "tsc --declaration --incremental --outDir lib --project tsconfig.build.json --emitDeclarationOnly",
"build-web": "webpack --mode production --entry ./lib/web.js --output ./dist/bls.min.js",
"check-types": "tsc --noEmit",
"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",
"pretest": "yarn check-types",
"prepublishOnly": "yarn build",
"test:web": "karma start",
"test:unit": "mocha --colors -r ts-node/register 'test/unit/**/*.test.ts'",
"test:coverage": "nyc --cache-dir .nyc_output/.cache -r lcov -e .ts mocha --colors -r ts-node/register 'test/unit/**/*.test.ts' && nyc report",
"test:spec": "mocha --colors -r ts-node/register 'test/spec/**/*.test.ts'",
"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-esm test/downloadSpecTests.ts",
"coverage": "codecov -F bls",
"benchmark": "ts-node test/benchmark",
"benchmark:all": "cd test/benchmark && yarn install && yarn 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": {
"@babel/cli": "^7.8.4",
"@babel/core": "^7.8.4",
"@babel/plugin-proposal-class-properties": "^7.8.3",
"@babel/plugin-proposal-object-rest-spread": "^7.8.3",
"@babel/plugin-syntax-bigint": "^7.8.3",
"@babel/preset-env": "^7.8.4",
"@babel/preset-typescript": "^7.8.3",
"@babel/register": "^7.8.3",
"@chainsafe/blst": "^0.1.3",
"@chainsafe/eth2-spec-tests": "0.12.0",
"@chainsafe/lodestar-spec-test-util": "^0.11.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",
"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.1.3"
"@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

@ -0,0 +1,42 @@
import * as blst from "@chainsafe/blst";
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]) {
super(value);
}
/** @param type Defaults to `CoordType.jacobian` */
static fromBytes(bytes: Uint8Array, type?: blst.CoordType, validate?: boolean): PublicKey {
const pk = blst.PublicKey.fromBytes(bytes, type);
if (validate) pk.keyValidate();
return new PublicKey(pk.value);
}
static fromHex(hex: string): PublicKey {
return this.fromBytes(hexToBytes(hex));
}
static aggregate(publicKeys: PublicKey[]): PublicKey {
if (publicKeys.length === 0) {
throw new EmptyAggregateError();
}
const pk = blst.aggregatePubkeys(publicKeys);
return new PublicKey(pk.value);
}
toBytes(format?: PointFormat): Uint8Array {
if (format === PointFormat.uncompressed) {
return this.value.serialize();
} else {
return this.value.compress();
}
}
toHex(format?: PointFormat): string {
return bytesToHex(this.toBytes(format));
}
}

View File

@ -1,14 +1,13 @@
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;
constructor(value: blst.SecretKey) {
this.value = value;
}
@ -33,13 +32,12 @@ export class SecretKey implements ISecretKey {
}
sign(message: Uint8Array): Signature {
return new Signature(this.value.sign(message));
return new Signature(this.value.sign(message).value);
}
toPublicKey(): PublicKey {
const jacobian = this.value.toAggregatePublicKey();
const affine = jacobian.toPublicKey();
return new PublicKey(affine, jacobian);
const pk = this.value.toPublicKey();
return new PublicKey(pk.value);
}
toBytes(): Uint8Array {

View File

@ -0,0 +1,77 @@
import * as blst from "@chainsafe/blst";
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]) {
super(value);
}
/** @param type Defaults to `CoordType.affine` */
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);
}
static fromHex(hex: string): Signature {
return this.fromBytes(hexToBytes(hex));
}
static aggregate(signatures: Signature[]): Signature {
if (signatures.length === 0) {
throw new EmptyAggregateError();
}
const agg = blst.aggregateSignatures(signatures);
return new Signature(agg.value);
}
static verifyMultipleSignatures(sets: {publicKey: PublicKey; message: Uint8Array; signature: Signature}[]): boolean {
return blst.verifyMultipleAggregateSignatures(
sets.map((s) => ({msg: s.message, pk: s.publicKey, sig: s.signature}))
);
}
verify(publicKey: PublicKey, message: Uint8Array): boolean {
// 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: PublicKey[], message: Uint8Array): boolean {
return blst.fastAggregateVerify(message, publicKeys, this);
}
verifyMultiple(publicKeys: PublicKey[], messages: Uint8Array[]): boolean {
return blst.aggregateVerify(messages, publicKeys, this);
}
toBytes(format?: PointFormat): Uint8Array {
if (format === PointFormat.uncompressed) {
return this.value.serialize();
} else {
return this.value.compress();
}
}
toHex(format?: PointFormat): string {
return bytesToHex(this.toBytes(format));
}
private aggregateVerify(msgs: Uint8Array[], pks: blst.PublicKey[]): boolean {
// 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);
}
}

View File

@ -1,27 +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 = {
SecretKey,
PublicKey,
Signature,
...functionalInterfaceFactory({SecretKey, PublicKey, Signature}),
init,
destroy,
};
export default bls;

View File

@ -1,46 +0,0 @@
import * as blst from "@chainsafe/blst";
import {EmptyAggregateError, ZeroPublicKeyError} from "../errors";
import {bytesToHex, hexToBytes} from "../helpers";
import {PublicKey as IPublicKey} from "../interface";
export class PublicKey implements IPublicKey {
readonly affine: blst.PublicKey;
readonly jacobian: blst.AggregatePublicKey;
constructor(affine: blst.PublicKey, jacobian: blst.AggregatePublicKey) {
this.affine = affine;
this.jacobian = jacobian;
}
static fromBytes(bytes: Uint8Array): PublicKey {
const affine = blst.PublicKey.fromBytes(bytes);
if (affine.value.is_inf()) {
throw new ZeroPublicKeyError();
}
const jacobian = blst.AggregatePublicKey.fromPublicKey(affine);
return new PublicKey(affine, jacobian);
}
static fromHex(hex: string): PublicKey {
return this.fromBytes(hexToBytes(hex));
}
static aggregate(publicKeys: PublicKey[]): PublicKey {
if (publicKeys.length === 0) {
throw new EmptyAggregateError();
}
const jacobian = blst.aggregatePubkeys(publicKeys.map((pk) => pk.jacobian));
const affine = jacobian.toPublicKey();
return new PublicKey(affine, jacobian);
}
toBytes(): Uint8Array {
return this.affine.toBytes();
}
toHex(): string {
return bytesToHex(this.toBytes());
}
}

View File

@ -1,70 +0,0 @@
import * as blst from "@chainsafe/blst";
import {bytesToHex, hexToBytes} from "../helpers";
import {Signature as ISignature} from "../interface";
import {PublicKey} from "./publicKey";
import {EmptyAggregateError, ZeroSignatureError} from "../errors";
export class Signature implements ISignature {
readonly affine: blst.Signature;
constructor(value: blst.Signature) {
this.affine = value;
}
static fromBytes(bytes: Uint8Array): Signature {
return new Signature(blst.Signature.fromBytes(bytes));
}
static fromHex(hex: string): Signature {
return this.fromBytes(hexToBytes(hex));
}
static aggregate(signatures: Signature[]): Signature {
if (signatures.length === 0) {
throw new EmptyAggregateError();
}
const agg = blst.AggregateSignature.fromSignatures(signatures.map((sig) => sig.affine));
return new Signature(agg.toSignature());
}
verify(publicKey: PublicKey, message: Uint8Array): boolean {
// Individual infinity signatures are NOT okay. Aggregated signatures MAY be infinity
if (this.affine.value.is_inf()) {
throw new ZeroSignatureError();
}
return this.aggregateVerify([message], [publicKey.affine]);
}
verifyAggregate(publicKeys: PublicKey[], message: Uint8Array): boolean {
const agg = PublicKey.aggregate(publicKeys);
return this.aggregateVerify([message], [agg.affine]);
}
verifyMultiple(publicKeys: PublicKey[], messages: Uint8Array[]): boolean {
return this.aggregateVerify(
messages,
publicKeys.map((pk) => pk.affine)
);
}
toBytes(): Uint8Array {
return this.affine.toBytes();
}
toHex(): string {
return bytesToHex(this.toBytes());
}
private aggregateVerify(msgs: Uint8Array[], pks: blst.PublicKey[]): boolean {
// 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.affine.value.is_inf() && pks.length === 1 && pks[0].value.is_inf()) {
return true;
}
return blst.aggregateVerify(msgs, pks, this.affine);
}
}

View File

@ -1,5 +1,5 @@
export const SECRET_KEY_LENGTH = 32;
export const SIGNATURE_LENGTH = 96;
export const FP_POINT_LENGTH = 48;
export const PUBLIC_KEY_LENGTH = FP_POINT_LENGTH;
export const G2_HASH_PADDING = 16;
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;

View File

@ -40,6 +40,6 @@ export class InvalidOrderError extends Error {
export class InvalidLengthError extends Error {
constructor(arg: string, length: number) {
super(`INVALID_LENGTH: ${arg} must have ${length} bytes`);
super(`INVALID_LENGTH: ${arg} - ${length} bytes`);
}
}

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,
@ -106,6 +107,35 @@ export function functionalInterfaceFactory({
}
}
/**
* 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: {publicKey: Uint8Array; message: Uint8Array; signature: Uint8Array}[]
): boolean {
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
*/
@ -121,6 +151,7 @@ export function functionalInterfaceFactory({
verify,
verifyAggregate,
verifyMultiple,
verifyMultipleSignatures,
secretKeyToPublicKey,
};
}

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

@ -26,7 +26,6 @@ export function hexToBytes(hex: string): Uint8Array {
* From https://github.com/herumi/bls-eth-wasm/blob/04eedb77aa96e66b4f65a0ab477228adf8090c36/src/bls.js#L50
*/
export function bytesToHex(bytes: Uint8Array): string {
// return "0x" + Buffer.from(bytes).toString("hex");
let s = "";
const n = bytes.length;

View File

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

View File

@ -20,3 +20,17 @@ export function validateBytes(
export function isZeroUint8Array(bytes: Uint8Array): boolean {
return bytes.every((byte) => byte === 0);
}
export function concatUint8Arrays(bytesArr: Uint8Array[]): Uint8Array {
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;
}

View File

@ -1,14 +1,30 @@
/* 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;
let blsGlobalPromise: Promise<void> | null = null;
// Patch to fix multiVerify() calls on a browser with polyfilled NodeJS crypto
declare global {
interface Window {
msCrypto: typeof window["crypto"];
}
}
export async function setupBls(): Promise<void> {
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;
}
}

View File

@ -1,21 +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 = {
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 {PUBLIC_KEY_LENGTH} from "../constants";
import {bytesToHex, hexToBytes, isZeroUint8Array} from "../helpers";
import {PublicKey as IPublicKey} from "../interface";
import {EmptyAggregateError, InvalidLengthError, ZeroPublicKeyError} from "../errors";
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;
@ -17,14 +17,16 @@ export class PublicKey implements IPublicKey {
}
static fromBytes(bytes: Uint8Array): PublicKey {
if (bytes.length !== PUBLIC_KEY_LENGTH) {
throw new InvalidLengthError("PublicKey", PUBLIC_KEY_LENGTH);
}
const context = getContext();
const publicKey = new context.PublicKey();
if (!isZeroUint8Array(bytes)) {
publicKey.deserialize(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);
}
@ -45,11 +47,15 @@ export class PublicKey implements IPublicKey {
return agg;
}
toBytes(): Uint8Array {
return this.value.serialize();
toBytes(format?: PointFormat): Uint8Array {
if (format === PointFormat.uncompressed) {
return this.value.serializeUncompressed();
} else {
return this.value.serialize();
}
}
toHex(): string {
return bytesToHex(this.toBytes());
toHex(format?: PointFormat): string {
return bytesToHex(this.toBytes(format));
}
}

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 {SIGNATURE_LENGTH} from "../constants";
import {SignatureType} from "bls-eth-wasm";
import {getContext} from "./context";
import {PublicKey} from "./publicKey";
import {bytesToHex, hexToBytes, isZeroUint8Array} from "../helpers";
import {Signature as ISignature} from "../interface";
import {EmptyAggregateError, InvalidLengthError, InvalidOrderError} from "../errors";
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,14 +17,21 @@ export class Signature implements ISignature {
this.value = value;
}
static fromBytes(bytes: Uint8Array): Signature {
if (bytes.length !== SIGNATURE_LENGTH) {
throw new InvalidLengthError("Signature", SIGNATURE_LENGTH);
}
/**
* @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)) {
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);
@ -45,6 +52,15 @@ export class Signature implements ISignature {
return new Signature(signature);
}
static verifyMultipleSignatures(sets: {publicKey: PublicKey; message: Uint8Array; signature: Signature}[]): boolean {
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: PublicKey, message: Uint8Array): boolean {
return publicKey.value.verify(this.value, message);
}
@ -57,18 +73,21 @@ export class Signature implements ISignature {
}
verifyMultiple(publicKeys: PublicKey[], messages: Uint8Array[]): boolean {
const msgs = Buffer.concat(messages);
return this.value.aggregateVerifyNoCheck(
publicKeys.map((key) => key.value),
msgs
concatUint8Arrays(messages)
);
}
toBytes(): Uint8Array {
return this.value.serialize();
toBytes(format?: PointFormat): Uint8Array {
if (format === PointFormat.uncompressed) {
return this.value.serializeUncompressed();
} else {
return this.value.serialize();
}
}
toHex(): string {
return bytesToHex(this.toBytes());
toHex(format?: PointFormat): string {
return bytesToHex(this.toBytes(format));
}
}

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;

View File

@ -1,57 +0,0 @@
export interface IBls {
SecretKey: {
fromBytes(bytes: Uint8Array): SecretKey;
fromHex(hex: string): SecretKey;
fromKeygen(ikm?: Uint8Array): SecretKey;
};
PublicKey: {
fromBytes(bytes: Uint8Array): PublicKey;
fromHex(hex: string): PublicKey;
aggregate(publicKeys: PublicKey[]): PublicKey;
};
Signature: {
fromBytes(bytes: Uint8Array): Signature;
fromHex(hex: string): Signature;
aggregate(signatures: Signature[]): 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;
secretKeyToPublicKey(secretKey: Uint8Array): Uint8Array;
init(): Promise<void>;
destroy(): void;
}
export declare class 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;
}
export declare class PublicKey {
static fromBytes(bytes: Uint8Array): PublicKey;
static fromHex(hex: string): PublicKey;
static aggregate(publicKeys: PublicKey[]): PublicKey;
toBytes(): Uint8Array;
toHex(): string;
}
export declare class Signature {
static fromBytes(bytes: Uint8Array): Signature;
static fromHex(hex: string): Signature;
static aggregate(signatures: Signature[]): Signature;
verify(publicKey: PublicKey, message: Uint8Array): boolean;
verifyAggregate(publicKeys: PublicKey[], message: Uint8Array): boolean;
verifyMultiple(publicKeys: PublicKey[], messages: Uint8Array[]): boolean;
toBytes(): Uint8Array;
toHex(): string;
}

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

68
src/types.ts Normal file
View File

@ -0,0 +1,68 @@
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 {
// 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;
sign(message: Uint8Array): Signature;
toPublicKey(): PublicKey;
toBytes(): Uint8Array;
toHex(): string;
}
export declare class PublicKey {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private constructor(...value: any);
/** @param type Only for impl `blst-native`. Defaults to `CoordType.jacobian` */
static fromBytes(bytes: Uint8Array, type?: CoordType, validate?: boolean): PublicKey;
static fromHex(hex: string): PublicKey;
static aggregate(publicKeys: PublicKey[]): PublicKey;
/** @param format Defaults to `PointFormat.compressed` */
toBytes(format?: PointFormat): Uint8Array;
toHex(format?: PointFormat): string;
}
export declare class Signature {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private constructor(...value: any);
/** @param type Only for impl `blst-native`. Defaults to `CoordType.affine`
* @param validate When using `herumi` implementation, signature validation is always on regardless of this flag. */
static fromBytes(bytes: Uint8Array, type?: CoordType, validate?: boolean): Signature;
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 type Implementation = "herumi" | "blst-native";
export enum PointFormat {
compressed = "compressed",
uncompressed = "uncompressed",
}
export enum CoordType {
affine,
jacobian,
}

View File

@ -1,92 +0,0 @@
import {runBenchmark} from "./runner";
import {runForAllImplementations} from "../switch";
import {PublicKey, Signature} from "../../src/interface";
import {range, randomMessage} from "../util";
const aggCount = 30;
(async function () {
await runForAllImplementations(async (bls, implementation) => {
// verify
await runBenchmark<{pk: PublicKey; msg: Uint8Array; sig: Signature}, boolean>({
id: `${implementation} verify`,
prepareTest: () => {
const msg = randomMessage();
const sk = bls.SecretKey.fromKeygen();
const pk = sk.toPublicKey();
const sig = sk.sign(msg);
return {
input: {pk, msg, sig},
resultCheck: (valid) => valid === true,
};
},
testRunner: ({pk, msg, sig}) => {
return sig.verify(pk, msg);
},
});
// Fast aggregate
await runBenchmark<{pks: PublicKey[]; msg: Uint8Array; sig: Signature}, boolean>({
id: `${implementation} verifyAggregate`,
prepareTest: () => {
const msg = randomMessage();
const dataArr = range(aggCount).map(() => {
const sk = bls.SecretKey.fromKeygen();
const pk = sk.toPublicKey();
const sig = sk.sign(msg);
return {pk, sig};
});
const pks = dataArr.map((data) => data.pk);
const sig = bls.Signature.aggregate(dataArr.map((data) => data.sig));
return {
input: {pks, msg, sig},
resultCheck: (valid) => valid === true,
};
},
testRunner: ({pks, msg, sig}) => {
return sig.verifyAggregate(pks, msg);
},
});
// Aggregate pubkeys
await runBenchmark<PublicKey[], void>({
id: `${implementation} aggregate pubkeys (${aggCount})`,
prepareTest: () => {
return {
input: range(aggCount).map(() => bls.SecretKey.fromKeygen().toPublicKey()),
};
},
testRunner: (pks) => {
bls.PublicKey.aggregate(pks);
},
});
// Aggregate sigs
await runBenchmark<Signature[], void>({
id: `${implementation} aggregate signatures (${aggCount})`,
prepareTest: () => {
const msg = randomMessage();
const sigs = range(aggCount).map(() => {
const sk = bls.SecretKey.fromKeygen();
return sk.sign(msg);
});
return {
input: sigs,
};
},
testRunner: (sigs) => {
bls.Signature.aggregate(sigs);
},
});
});
})();

View File

@ -1,78 +0,0 @@
import {runBenchmark} from "./runner";
import {range, randomMessage} from "../util";
import {generateRandomSecretKey} from "@chainsafe/bls-keygen";
import * as noble from "noble-bls12-381";
const aggCount = 30;
const nobleRuns = 10;
(async function () {
// verify
await runBenchmark<{pk: Uint8Array; msg: Uint8Array; sig: Uint8Array}, boolean>({
id: `noble verify`,
prepareTest: async () => {
const priv = generateRandomSecretKey();
const msg = randomMessage();
const sig = await noble.sign(msg, priv);
const pk = noble.getPublicKey(priv);
return {
input: {pk, msg, sig},
resultCheck: (valid: boolean) => valid === true,
};
},
testRunner: async ({pk, msg, sig}) => {
return await noble.verify(sig, msg, pk);
},
runs: nobleRuns,
});
// Fast aggregate
await runBenchmark<{pks: Uint8Array[]; msg: Uint8Array; sig: Uint8Array}, boolean>({
id: `noble verifyAggregate`,
prepareTest: async () => {
const msg = randomMessage();
const dataArr = await Promise.all(
range(aggCount).map(async () => {
const sk = generateRandomSecretKey();
const pk = noble.getPublicKey(sk);
const sig = await noble.sign(msg, sk);
return {pk, sig};
})
);
const pks = dataArr.map((data) => data.pk);
const sig = noble.aggregateSignatures(dataArr.map((data) => data.sig));
return {
input: {pks, msg, sig},
resultCheck: (valid: boolean) => valid === true,
};
},
testRunner: async ({pks, msg, sig}) => {
const pk = noble.aggregatePublicKeys(pks);
return await noble.verify(sig, msg, pk);
},
runs: nobleRuns,
});
// Aggregate pubkeys
await runBenchmark<Uint8Array[], void>({
id: `noble aggregate pubkeys (${aggCount})`,
prepareTest: () => {
return {
input: range(aggCount).map(() => noble.getPublicKey(generateRandomSecretKey())),
};
},
testRunner: async (pks) => {
noble.aggregatePublicKeys(pks);
},
runs: nobleRuns,
});
})();

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