Compare commits

...
This repository has been archived on 2023-04-09. You can view files and clone it, but cannot push or open issues or pull requests.

273 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
Cayman 34d8710a86
Merge pull request #53 from ChainSafe/cayman/bump-version
v5.0.1
2020-11-30 17:37:34 -07:00
Cayman 7a0b30ec1b
v5.0.1 2020-11-30 18:10:26 -06:00
Cayman 2932863392
Merge pull request #55 from ChainSafe/dapplion/deprecate-keypair
Deprecate IKeypair
2020-11-30 17:08:45 -07:00
dapplion e4cf34ab90 Deprecate IKeypair 2020-12-01 00:07:45 +00:00
Cayman 668ab1b293
Merge pull request #54 from ChainSafe/dapplion/deduplicate-interface
De-duplicate interface
2020-11-30 17:03:06 -07:00
dapplion bf5cf8b094 Ignore benchmark in tsconfig 2020-12-01 00:01:48 +00:00
dapplion a51022f802 De-duplicate interface 2020-11-30 23:54:03 +00:00
Cayman ccd870f189
Merge pull request #52 from ChainSafe/dapplion/breaking-property
Remove foreign property breaking types
2020-11-30 16:50:33 -07:00
dapplion 4f11731ed8 Remove foreign property breaking types 2020-11-30 23:49:39 +00:00
Cayman 5fee5e367b
Merge pull request #51 from ChainSafe/dapplion/noble-benchmark
Benchmark noble-bls12-381
2020-11-30 16:42:44 -07:00
dapplion 46e26a621c Expose benchmark:all script 2020-11-30 22:07:26 +00:00
dapplion 138c8e99fa Install libs to benchmark in benchmark project 2020-11-30 22:05:40 +00:00
dapplion b02cd11c96 Benchmark noble-bls12-381
f
2020-11-30 22:05:40 +00:00
Cayman be59b6d66a
Merge pull request #50 from ChainSafe/cayman/bump-version
v5.0.0
2020-11-30 13:29:09 -07:00
Cayman 6b290fe817
v5.0.0 2020-11-30 14:26:12 -06:00
Cayman 69ebeadd1a
Merge pull request #49 from ChainSafe/cayman/update-readme
Update readme
2020-11-30 13:21:30 -07:00
Cayman 01eb42fbd6
Update readme 2020-11-30 14:19:10 -06:00
Cayman 380487e410
Merge pull request #48 from ChainSafe/cayman/secretKeyToPublicKey
Add secretKeyToPublicKey function
2020-11-30 13:13:56 -07:00
Cayman 021fc7111e
Add secretKeyToPublicKey declaration 2020-11-30 13:45:23 -06:00
Cayman e0076d0f27
Add secretKeyToPublicKey function 2020-11-30 13:40:18 -06:00
Cayman f3e9392847
Merge pull request #47 from ChainSafe/cayman/consistent-naming
Consistent naming
2020-11-30 12:32:54 -07:00
Cayman b5b2e6dfc7
Consistent naming 2020-11-30 12:03:49 -06:00
Cayman ab0baac84b
Merge pull request #46 from ChainSafe/dapplion/differentiate-types
Make PublicKey type != Signature type
2020-11-30 09:56:39 -07:00
dapplion 2df6743c6c Make PublicKey type != Signature type 2020-11-30 16:28:52 +00:00
Cayman cb3bf183d2
Merge pull request #44 from ChainSafe/dapplion/proxy-named-exports
Proxy named exports in root index
2020-11-30 09:13:03 -07:00
Cayman a44d1bf2d0
Add named export test to browser test cases 2020-11-30 10:12:18 -06:00
Cayman dacb875007
Merge pull request #45 from ChainSafe/mpetrunic/release-flow
Add release flow
2020-11-30 08:38:04 -07:00
Marin Petrunić d225aaff04
add release flow 2020-11-30 14:15:05 +01:00
dapplion 3004076a4a Remove console.log 2020-11-30 11:49:47 +00:00
dapplion 2c938ddeed Test named exports 2020-11-30 11:49:37 +00:00
dapplion 9b7e5f5f95 Export class interfaces too 2020-11-30 11:43:47 +00:00
dapplion 0a3f6f5659 Use declare only 2020-11-30 11:32:36 +00:00
dapplion 999a5d5e22 Proxy named exports in root index 2020-11-30 11:29:24 +00:00
Cayman c8ddfcaa3b
Merge pull request #43 from ChainSafe/fix-tests
Fix v1.0.0 spec tests and error conditions
2020-11-29 19:59:57 -07:00
Cayman 21b319711f
Fix linter error 2020-11-29 20:43:57 -06:00
dapplion c557db4a78 Make errors consistent 2020-11-30 00:20:55 +00:00
dapplion 60d795c694 Fix blst tests 2020-11-30 00:01:48 +00:00
dapplion 32266dae20 Use isZeroUint8Array to check for empty bytes 2020-11-29 23:56:31 +00:00
dapplion 6fe9b09a92 Fix herumi tests 2020-11-29 23:55:45 +00:00
dapplion d3616e50f6 Remove async from runForAllImplementations 2020-11-29 23:55:45 +00:00
Cayman 46b317b528
Merge pull request #42 from ChainSafe/clean-methods
Clean methods not in the interface
2020-11-29 15:35:50 -07:00
dapplion 2208922aa6 Clean methods not in the interface 2020-11-29 22:30:47 +00:00
Cayman 3bb136cfcc
Merge pull request #41 from ChainSafe/cayman/fromKeygen-ikm
Optional ikm param for bls.PrivateKey.fromKeygen
2020-11-29 14:32:11 -07:00
Cayman d743d95487
Optional ikm param for bls.PrivateKey.fromKeygen 2020-11-29 15:25:18 -06:00
Cayman 138b6de063
Merge pull request #40 from ChainSafe/cayman/export-types
Export types
2020-11-29 11:59:23 -07:00
Cayman 203122ee10
Export types 2020-11-29 12:54:33 -06:00
Cayman c0723488d3
Merge pull request #39 from ChainSafe/dapplion/fix-default-export
Use Object.assign to fix default import
2020-11-29 10:21:01 -07:00
dapplion 61c1baea3f Use const in main bls export 2020-11-29 17:10:20 +00:00
Cayman 06dc0e47b0
Merge pull request #38 from ChainSafe/dependabot/npm_and_yarn/yargs-parser-13.1.2
Bump yargs-parser from 13.1.1 to 13.1.2
2020-11-29 10:04:43 -07:00
Cayman 326842465c
Merge pull request #25 from ChainSafe/dependabot/npm_and_yarn/http-proxy-1.18.1
Bump http-proxy from 1.18.0 to 1.18.1
2020-11-29 10:04:21 -07:00
dapplion e51c1784a7 Document lazy import 2020-11-29 16:59:24 +00:00
dapplion 2235e7be89 Import named export from blst 2020-11-29 16:59:16 +00:00
dapplion ef98683424 Use Object.assign to fix default import 2020-11-29 16:52:29 +00:00
Cayman 3a34ccbacc
Merge pull request #37 from ChainSafe/expected-errors
Prevent "BLS not initialized" error from causing a false negative
2020-11-29 09:51:06 -07:00
dependabot[bot] d60ac317b7
Bump http-proxy from 1.18.0 to 1.18.1
Bumps [http-proxy](https://github.com/http-party/node-http-proxy) from 1.18.0 to 1.18.1.
- [Release notes](https://github.com/http-party/node-http-proxy/releases)
- [Changelog](https://github.com/http-party/node-http-proxy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/http-party/node-http-proxy/compare/1.18.0...1.18.1)

Signed-off-by: dependabot[bot] <support@github.com>
2020-11-29 16:02:20 +00:00
dependabot[bot] f718b9d9e0
Bump yargs-parser from 13.1.1 to 13.1.2
Bumps [yargs-parser](https://github.com/yargs/yargs-parser) from 13.1.1 to 13.1.2.
- [Release notes](https://github.com/yargs/yargs-parser/releases)
- [Changelog](https://github.com/yargs/yargs-parser/blob/master/docs/CHANGELOG-full.md)
- [Commits](https://github.com/yargs/yargs-parser/commits)

Signed-off-by: dependabot[bot] <support@github.com>
2020-11-29 16:01:59 +00:00
Cayman 804ed42354
Merge pull request #31 from ChainSafe/blst
Add BLST  🚀
2020-11-29 09:01:17 -07:00
dapplion a379898589 Bump @chainsafe/blst 2020-11-29 15:08:17 +00:00
dapplion 2245559e0e Prevent "BLS not initialized" error from causing a false negative 2020-11-29 14:28:30 +00:00
dapplion 5db911d470 Use validateBytes instead of generic assert 2020-11-29 14:06:09 +00:00
dapplion 70574b45c1 Fix herumi's bytes length checks 2020-11-29 13:57:08 +00:00
dapplion 78f66280de Add note in README to install blst 2020-11-29 12:21:31 +00:00
dapplion d9c83feb10 Remove unnecessary eslint-disable 2020-11-29 12:19:52 +00:00
dapplion 8055f73afb Use browser friendly hexToBytes, bytesToHex methods 2020-11-28 19:52:32 +00:00
dapplion 2c34db8b8e Remove unnecessary casting of Buffer to Unit8Array 2020-11-28 19:30:56 +00:00
dapplion b507ed28c0 Use randombytes instead of node's crypto 2020-11-28 19:27:59 +00:00
dapplion 9194769d62 Export constants consistently 2020-11-28 19:14:08 +00:00
dapplion bd17160713 Use 'message' arg name consistently 2020-11-28 19:11:21 +00:00
dapplion b0ed0e8cb5 Include root import files in package.json 2020-11-28 19:06:29 +00:00
dapplion 104d6dd269 Use upstream types for bls-eth-wasm 2020-11-28 19:05:34 +00:00
dapplion 406419cac7 Set default value for implementation 2020-11-25 17:58:03 +00:00
dapplion 4ca6532171 Use toBuffer to prevent allocation 2020-11-25 17:57:33 +00:00
dapplion e32ea6d7a5 Buffer is subclass of Uint8Array 2020-11-25 17:56:33 +00:00
dapplion ca5cac64b3 Fix lint warnings and errors 2020-11-25 16:23:53 +00:00
dapplion 591105c553 Document peer dependency in README 2020-11-25 16:17:48 +00:00
dapplion 0e69b819e0 Remove keypair test 2020-11-25 16:11:44 +00:00
dapplion 49d509aca4 Clean interface 2020-11-25 16:09:47 +00:00
dapplion 021e741d17 Move bls-eth-wasm to the root dir 2020-11-25 15:35:53 +00:00
dapplion b424842cd5 Reduce karma.conf.js diff 2020-11-25 15:16:19 +00:00
dapplion 4cb49674d5 Fix test util import 2020-11-25 15:11:02 +00:00
dapplion 847ec46ac9 Remove crypto from common helpers 2020-11-25 15:06:05 +00:00
dapplion 530e86d98f Export strategies 2020-11-25 15:03:15 +00:00
dapplion cd5b7cba47 Update bls-eth-wasm types 2020-11-25 14:00:18 +00:00
dapplion 6b1fdb0971 Fix lint issues 2020-11-25 11:50:47 +00:00
dapplion 507ed94995 Split CI test steps 2020-11-25 11:41:28 +00:00
dapplion ec859a1c32 Fix backing dependencies 2020-11-25 11:39:49 +00:00
dapplion c59cf98695 Merge 'dev' into 'blst' 2020-11-25 11:10:06 +00:00
dapplion 84c95ba069 Remove Keypair class 2020-11-25 10:45:19 +00:00
dapplion 4da10180d9 Remove code duplication in the functional interface 2020-11-25 10:41:52 +00:00
dapplion 2073d31a15 Run benchmarks on CI 2020-11-20 19:37:20 +00:00
dapplion 990258dbd9 Fix type errors in benchmark runner
Lint issues
2020-11-20 19:37:20 +00:00
dapplion 70ccbfbe5b Run only web implementation in Karma 2020-11-20 19:09:05 +00:00
dapplion 4424bed87d Define common implementation 2020-11-20 19:03:17 +00:00
dapplion c354386dab Fix lint errors 2020-11-20 12:27:35 +00:00
dapplion 1e92f6311b Setup NodeJS 12 on CI 2020-11-20 12:16:25 +00:00
dapplion 6e7782b306 Bump blst-ts to v0.1.2 2020-11-20 09:38:44 +00:00
dapplion 1e9f778846 Use isEqualBytes helper 2020-11-20 09:37:44 +00:00
dapplion fa12879651 Bump blst-ts 2020-11-19 21:46:18 +00:00
dapplion 523d547171 Add Keypair test 2020-11-19 14:50:18 +00:00
dapplion f8cd6e7afa Update tests to run both implementations 2020-11-19 14:41:45 +00:00
dapplion 57694c2e54 Co-exist implementations 2020-11-19 13:22:41 +00:00
dapplion 5b06e4f61e Replace herumi src with BLST 2020-11-19 00:23:34 +00:00
dapplion f29898d9dc benchmark as jacobian 2020-11-14 22:25:22 +00:00
dapplion 928b86a9fb Benchmark BLST 2020-11-13 23:09:13 +00:00
dapplion aca18fbcda Test blst-ts with spec tests 2020-11-13 21:43:20 +00:00
125 changed files with 5767 additions and 5040 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",
@ -55,7 +58,7 @@ module.exports = {
"import/no-extraneous-dependencies": ["error", {
"devDependencies": false,
"optionalDependencies": false,
"peerDependencies": false
"peerDependencies": true
}],
"func-call-spacing": "off",
"max-len": ["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

65
.github/workflows/release.yml vendored Normal file
View File

@ -0,0 +1,65 @@
name: Release
on:
push:
branches:
- 'master'
jobs:
tag:
name: Check and Tag
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Create tag
id: tag
uses: butlerlogic/action-autotag@1.1.1
with:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
strategy: package # Optional, since "package" is the default strategy
tag_prefix: "v"
outputs:
tag: ${{ steps.tag.outputs.tagname }}
version: ${{ steps.tag.outputs.version }}
release:
name: Publish
runs-on: ubuntu-latest
needs: tag
if: needs.tag.outputs.tag != ''
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Setup Nodejs
uses: actions/setup-node@v1
with:
node-version: "14.x"
registry-url: "https://registry.npmjs.org"
- name: Install dependencies
run: yarn install --non-interactive --frozen-lockfile
- name: Build
run: yarn run build
- name: Publish to npm registry
run: yarn publish --no-git-tag-version --no-commit-hooks --non-interactive
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Create Release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ needs.tag.outputs.tag }}
body: ""
release_name: Release ${{ needs.tag.outputs.tag }}
#in case of failure
- name: Rollback on failure
if: failure()
uses: author/action-rollback@9ec72a6af74774e00343c6de3e946b0901c23013
with:
id: ${{ steps.create_release.outputs.id }}
tag: ${{ needs.tag.outputs.tag }}
delete_orphan_tag: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -1,18 +1,35 @@
name: CI Tests
name: Main
on: [pull_request, push]
jobs:
lint:
name: Quick tests
tests:
name: Tests
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
node: [14, 16]
steps:
- uses: actions/checkout@v1
- name: Bootstrap
run: yarn
- name: Check types
run: yarn check-types
- name: Lint
run: yarn lint
- name: Tests
run: yarn test
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
with:
node-version: ${{matrix.node}}
- name: Install deps
run: yarn --non-interactive --frozen-lockfile
- name: Lint
run: yarn lint
- name: Test build
run: yarn build
- 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
run: yarn test:web
- name: Benchmark
run: yarn benchmark:all

5
.gitignore vendored
View File

@ -65,3 +65,8 @@ typings/
dist/
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"

1
.prettierignore Normal file
View File

@ -0,0 +1 @@
.eslintrc.js

View File

@ -5,7 +5,71 @@ 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
- Remove foreign property breaking types ([ccd870](https://github.com/chainsafe/bls/commit/ccd870))
- Deduplicate interface ([0bf6e9](https://github.com/chainsafe/bls/commit/0bf6e9))
- Deprecate IKeypair ([293286](https://github.com/chainsafe/bls/commit/293286))
## [5.0.0] - 2020-11-30
### BREAKING CHANGES
- Compatible with [Eth2 spec 1.0.0](https://github.com/ethereum/eth2.0-specs/blob/v1.0.0/specs/phase0/beacon-chain.md#bls-signatures)
- Update bls-keygen to latest EIP-2333 standard
- Refactored class-based interface, minor functional interface changes
- BLST support
## [4.0.0] - 2020-08-31

131
README.md
View File

@ -1,26 +1,125 @@
# 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 0.12.0](https://img.shields.io/badge/ETH2.0_Spec_Version-0.12.0-2e86c1.svg)
![ES Version](https://img.shields.io/badge/ES-2017-yellow)
![Node Version](https://img.shields.io/badge/node-12.x-green)
![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-2022-yellow)
![Node Version](https://img.shields.io/badge/node-14.8-green)
This is a Javascript library that implements BLS (Boneh-Lynn-Shacham) signatures and supports signature aggregation.
| Version | Bls spec version |
| ------- | :--------------: |
| 0.3.x | initial version |
| 1.x.x | draft #6 |
| 2.x.x | draft #7 |
> [spec](https://github.com/ethereum/eth2.0-specs/blob/v0.11.1/specs/phase0/beacon-chain.md#bls-signatures)
> [test vectors](https://github.com/ethereum/eth2.0-spec-tests/tree/master/tests/bls)
Javascript library for BLS (Boneh-Lynn-Shacham) signatures and signature aggregation, tailored for use in Eth2.
## Usage
- `yarn add @chainsafe/bls`
```bash
yarn add @chainsafe/bls
```
To use native bindings you must install peer dependency `@chainsafe/blst`
```bash
yarn add @chainsafe/bls @chainsafe/blst
```
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 bls from "@chainsafe/bls";
(async () => {
// class-based interface
const secretKey = bls.SecretKey.fromKeygen();
const publicKey = secretKey.toPublicKey();
const message = new Uint8Array(32);
const signature = secretKey.sign(message);
console.log("Is valid: ", signature.verify(publicKey, message));
// 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 `/herumi` to explicitly import the WASM version
```ts
import bls from "@chainsafe/bls/herumi";
```
### Native bindings only
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/blst-native";
```
### Get implementation at runtime
If you need to get a bls implementation at runtime, import from `/getImplementation`.
```ts
import {getImplementation} from "@chainsafe/bls/getImplementation";
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 |
> [spec](https://github.com/ethereum/eth2.0-specs/blob/v1.0.0/specs/phase0/beacon-chain.md#bls-signatures)
> [test vectors](https://github.com/ethereum/eth2.0-spec-tests/tree/master/tests/bls)
## License

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;

35
benchmark/runner.ts Normal file
View File

@ -0,0 +1,35 @@
type PromiseOptional<T> = T | Promise<T>;
export async function runBenchmark<T, R>({
prepareTest,
testRunner,
runs = 100,
id,
}: {
prepareTest: (i: number) => PromiseOptional<T>;
testRunner: (input: T) => PromiseOptional<R>;
runs?: number;
id: string;
}): Promise<void> {
const diffsNanoSec: bigint[] = [];
for (let i = 0; i < runs; i++) {
const input = await prepareTest(i);
const start = process.hrtime.bigint();
const result = await testRunner(input);
const end = process.hrtime.bigint();
diffsNanoSec.push(end - start);
}
const average = averageBigint(diffsNanoSec);
const opsPerSec = 1e9 / Number(average);
// eslint-disable-next-line no-console
console.log(`${id}: ${opsPerSec.toPrecision(5)} ops/sec (${runs} runs)`); // ±1.74%
}
function averageBigint(arr: bigint[]): bigint {
const total = arr.reduce((total, value) => total + value);
return total / BigInt(arr.length);
}

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

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/*.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 = {}));

View File

@ -1,16 +1,58 @@
{
"name": "@chainsafe/bls",
"version": "4.0.0",
"version": "7.1.1",
"description": "Implementation of bls signature verification for ethereum 2.0",
"main": "lib/index.js",
"engines": {
"node": ">=14.8.0"
},
"type": "module",
"exports": {
".": {
"import": "./lib/index.js"
},
"./types": {
"import": "./lib/types.js"
},
"./errors": {
"import": "./lib/errors.js"
},
"./constants": {
"import": "./lib/constants.js"
},
"./getImplementation": {
"import": "./lib/getImplementation.js"
},
"./switchable": {
"import": "./lib/switchable.js"
},
"./blst-native": {
"import": "./lib/blst-native/index.js"
},
"./herumi": {
"import": "./lib/herumi/index.js"
}
},
"types": "lib/index.d.ts",
"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",
"files": [
"lib/**/*.js",
"lib/**/*.js.map",
"lib/**/*.d.ts"
"lib/**/*.d.ts",
"*.d.ts",
"*.js"
],
"keywords": [
"ethereum",
@ -20,66 +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:unit": "karma start",
"test:node:unit": "nyc --cache-dir .nyc_output/.cache -r lcov -e .ts mocha --colors -r ts-node/register 'test/unit/**/*.test.ts' && nyc report",
"test:unit": "yarn run test:node:unit && yarn run test:web:unit",
"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": "node -r ./.babel-register test/benchmarks"
"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.1"
"@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/as-sha256": "0.2.0",
"@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",
"@typescript-eslint/eslint-plugin": "^2.20.0",
"@typescript-eslint/parser": "^2.20.0",
"chai": "^4.2.0",
"eslint": "^6.8.0",
"@types/mocha": "^10.0.0",
"@types/randombytes": "^2.0.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.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

@ -0,0 +1,50 @@
import * as blst from "@chainsafe/blst";
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;
}
static fromBytes(bytes: Uint8Array): SecretKey {
// 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: string): SecretKey {
return this.fromBytes(hexToBytes(hex));
}
static fromKeygen(entropy?: Uint8Array): SecretKey {
const sk = blst.SecretKey.fromKeygen(entropy || randomBytes(SECRET_KEY_LENGTH));
return new SecretKey(sk);
}
sign(message: Uint8Array): Signature {
return new Signature(this.value.sign(message).value);
}
toPublicKey(): PublicKey {
const pk = this.value.toPublicKey();
return new PublicKey(pk.value);
}
toBytes(): Uint8Array {
return this.value.toBytes();
}
toHex(): string {
return bytesToHex(this.toBytes());
}
}

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

@ -1,34 +0,0 @@
/* eslint-disable require-atomic-updates */
import bls from "bls-eth-wasm";
type Bls = typeof bls;
let blsGlobal: Bls | null = null;
let blsGlobalPromise: Promise<Bls> | null = null;
export async function setupBls(): Promise<Bls> {
if (!blsGlobal) {
await bls.init();
blsGlobal = bls;
}
return blsGlobal;
}
// Cache a promise for Bls instead of Bls to make sure it is initialized only once
export async function init(): Promise<Bls> {
if (!blsGlobalPromise) {
blsGlobalPromise = setupBls();
}
return blsGlobalPromise;
}
export function destroy(): void {
blsGlobal = null;
blsGlobalPromise = null;
}
export function getContext(): Bls {
if (!blsGlobal) {
throw new Error("BLS not initialized");
}
return blsGlobal;
}

45
src/errors.ts Normal file
View File

@ -0,0 +1,45 @@
/**
* This error should not be ignored by the functional interface
* try / catch blocks, to prevent false negatives
*/
export class NotInitializedError extends Error {
constructor(implementation: string) {
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: string, length: number) {
super(`INVALID_LENGTH: ${arg} - ${length} bytes`);
}
}

157
src/functional.ts Normal file
View File

@ -0,0 +1,157 @@
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 max-len
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type,@typescript-eslint/explicit-module-boundary-types
export function functionalInterfaceFactory({
SecretKey,
PublicKey,
Signature,
}: Pick<IBls, "SecretKey" | "PublicKey" | "Signature">) {
/**
* Signs given message using secret key.
* @param secretKey
* @param message
*/
function sign(secretKey: Uint8Array, message: Uint8Array): Uint8Array {
validateBytes(secretKey, "secretKey");
validateBytes(message, "message");
return SecretKey.fromBytes(secretKey).sign(message).toBytes();
}
/**
* Compines all given signature into one.
* @param signatures
*/
function aggregateSignatures(signatures: Uint8Array[]): Uint8Array {
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: Uint8Array[]): Uint8Array {
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: Uint8Array, message: Uint8Array, signature: Uint8Array): boolean {
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: Uint8Array[], message: Uint8Array, signature: Uint8Array): boolean {
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: Uint8Array[], messages: Uint8Array[], signature: Uint8Array): boolean {
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: {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
*/
function secretKeyToPublicKey(secretKey: Uint8Array): Uint8Array {
validateBytes(secretKey, "secretKey");
return SecretKey.fromBytes(secretKey).toPublicKey().toBytes();
}
return {
sign,
aggregateSignatures,
aggregatePublicKeys,
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}`);
}
}

37
src/helpers/hex.ts Normal file
View File

@ -0,0 +1,37 @@
/**
* Browser compatible fromHex method
* From https://github.com/herumi/bls-eth-wasm/blob/04eedb77aa96e66b4f65a0ab477228adf8090c36/src/bls.js#L62
*/
export function hexToBytes(hex: string): Uint8Array {
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: Uint8Array): string {
let s = "";
const n = bytes.length;
for (let i = 0; i < n; i++) {
s += ("0" + bytes[i].toString(16)).slice(-2);
}
return "0x" + s;
}

View File

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

View File

@ -1,10 +1,36 @@
import {PUBLIC_KEY_LENGTH, SIGNATURE_LENGTH} from "../constants";
import randomBytes from "randombytes";
export function assert(condition: unknown, message = "Assertion failed"): asserts condition {
if (!condition) {
throw new Error(message);
// 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: Uint8Array | Uint8Array[] | null,
argName?: string
): asserts bytes is NonNullable<typeof bytes> {
for (const item of Array.isArray(bytes) ? bytes : [bytes]) {
if (item == null) {
throw Error(`${argName || "bytes"} is null or undefined`);
}
}
}
export const EMPTY_PUBLIC_KEY = Buffer.alloc(PUBLIC_KEY_LENGTH);
export const EMPTY_SIGNATURE = Buffer.alloc(SIGNATURE_LENGTH);
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;
}

50
src/herumi/context.ts Normal file
View File

@ -0,0 +1,50 @@
/* eslint-disable require-atomic-updates */
import bls from "bls-eth-wasm";
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;
}
}
// Cache a promise for Bls instead of Bls to make sure it is initialized only once
export async function init(): Promise<void> {
if (!blsGlobalPromise) {
blsGlobalPromise = setupBls();
}
return blsGlobalPromise;
}
export function destroy(): void {
blsGlobal = null;
blsGlobalPromise = null;
}
export function getContext(): Bls {
if (!blsGlobal) {
throw new NotInitializedError("herumi");
}
return blsGlobal;
}

23
src/herumi/index.ts Normal file
View File

@ -0,0 +1,23 @@
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 = async (): Promise<IBls> => {
await init();
return {
implementation: "herumi",
SecretKey,
PublicKey,
Signature,
...functionalInterfaceFactory({SecretKey, PublicKey, Signature}),
};
};
export default bls;

61
src/herumi/publicKey.ts Normal file
View File

@ -0,0 +1,61 @@
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;
constructor(value: PublicKeyType) {
if (value.isZero()) {
throw new ZeroPublicKeyError();
}
this.value = value;
}
static fromBytes(bytes: Uint8Array): PublicKey {
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: string): PublicKey {
return this.fromBytes(hexToBytes(hex));
}
static aggregate(publicKeys: PublicKey[]): PublicKey {
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?: PointFormat): Uint8Array {
if (format === PointFormat.uncompressed) {
return this.value.serializeUncompressed();
} else {
return this.value.serialize();
}
}
toHex(format?: PointFormat): string {
return bytesToHex(this.toBytes(format));
}
}

57
src/herumi/secretKey.ts Normal file
View File

@ -0,0 +1,57 @@
import type {SecretKeyType} from "bls-eth-wasm";
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 {SecretKey as ISecretKey} from "../types.js";
import {InvalidLengthError, ZeroSecretKeyError} from "../errors.js";
export class SecretKey implements ISecretKey {
readonly value: SecretKeyType;
constructor(value: SecretKeyType) {
if (value.isZero()) {
throw new ZeroSecretKeyError();
}
this.value = value;
}
static fromBytes(bytes: Uint8Array): SecretKey {
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: string): SecretKey {
return this.fromBytes(hexToBytes(hex));
}
static fromKeygen(entropy?: Uint8Array): SecretKey {
const sk = generateRandomSecretKey(entropy);
return this.fromBytes(sk);
}
sign(message: Uint8Array): Signature {
return new Signature(this.value.sign(message));
}
toPublicKey(): PublicKey {
return new PublicKey(this.value.getPublicKey());
}
toBytes(): Uint8Array {
return this.value.serialize();
}
toHex(): string {
return bytesToHex(this.toBytes());
}
}

93
src/herumi/signature.ts Normal file
View File

@ -0,0 +1,93 @@
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;
constructor(value: SignatureType) {
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: 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);
}
static fromHex(hex: string): Signature {
return this.fromBytes(hexToBytes(hex));
}
static aggregate(signatures: Signature[]): Signature {
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: {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);
}
verifyAggregate(publicKeys: PublicKey[], message: Uint8Array): boolean {
return this.value.fastAggregateVerify(
publicKeys.map((key) => key.value),
message
);
}
verifyMultiple(publicKeys: PublicKey[], messages: Uint8Array[]): boolean {
return this.value.aggregateVerifyNoCheck(
publicKeys.map((key) => key.value),
concatUint8Arrays(messages)
);
}
toBytes(format?: PointFormat): Uint8Array {
if (format === PointFormat.uncompressed) {
return this.value.serializeUncompressed();
} else {
return this.value.serialize();
}
}
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,155 +1,19 @@
import {Keypair} from "./keypair";
import {PrivateKey} from "./privateKey";
import {PublicKey} from "./publicKey";
import {Signature} from "./signature";
import {PUBLIC_KEY_LENGTH} from "./constants";
import {assert} from "./helpers";
import type {IBls} from "./types.js";
import {getImplementation} from "./getImplementation.js";
export {Keypair, PrivateKey, PublicKey, Signature};
// 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 {init as initBLS} from "./context";
export const bls = async (): Promise<IBls> => {
let bls: IBls;
function toBuffer(input: Uint8Array): Buffer {
return Buffer.from(input.buffer, input.byteOffset, input.length);
}
/**
* Generates new secret and public key
*/
export function generateKeyPair(): Keypair {
return Keypair.generate();
}
/**
* Generates public key from given secret.
* @param {BLSSecretKey} secretKey
*/
export function generatePublicKey(secretKey: Uint8Array): Buffer {
assert(secretKey, "secretKey is null or undefined");
const keypair = new Keypair(PrivateKey.fromBytes(toBuffer(secretKey)));
return keypair.publicKey.toBytesCompressed();
}
/**
* Signs given message using secret key.
* @param secretKey
* @param messageHash
*/
export function sign(secretKey: Uint8Array, messageHash: Uint8Array): Buffer {
assert(secretKey, "secretKey is null or undefined");
assert(messageHash, "messageHash is null or undefined");
const privateKey = PrivateKey.fromBytes(toBuffer(secretKey));
return privateKey.signMessage(toBuffer(messageHash)).toBytesCompressed();
}
/**
* Compines all given signature into one.
* @param signatures
*/
export function aggregateSignatures(signatures: Uint8Array[]): Buffer {
assert(signatures && signatures.length > 0, "signatures is null or undefined or empty array");
return Signature.aggregate(
signatures.map(
(signature): Signature => {
return Signature.fromCompressedBytes(signature);
}
)
).toBytesCompressed();
}
/**
* Combines all given public keys into single one
* @param publicKeys
*/
export function aggregatePubkeys(publicKeys: Uint8Array[]): Buffer {
assert(publicKeys, "publicKeys is null or undefined");
if (publicKeys.length === 0) {
return Buffer.alloc(PUBLIC_KEY_LENGTH);
}
return publicKeys
.map((p) => PublicKey.fromBytes(toBuffer(p)))
.reduce((agg, pubKey) => agg.add(pubKey))
.toBytesCompressed();
}
/**
* Verifies if signature is message signed with given public key.
* @param publicKey
* @param messageHash
* @param signature
*/
export function verify(publicKey: Uint8Array, messageHash: Uint8Array, signature: Uint8Array): boolean {
assert(publicKey, "publicKey is null or undefined");
assert(messageHash, "messageHash is null or undefined");
assert(signature, "signature is null or undefined");
try {
return PublicKey.fromBytes(publicKey).verifyMessage(
Signature.fromCompressedBytes(toBuffer(signature)),
toBuffer(messageHash)
);
bls = await getImplementation(isNode ? "blst-native" : "herumi");
} catch (e) {
return false;
bls = await getImplementation("herumi");
}
}
/**
* Verifies if aggregated signature is same message signed with given public keys.
* @param publicKeys
* @param messageHash
* @param signature
*/
export function verifyAggregate(publicKeys: Uint8Array[], messageHash: Uint8Array, signature: Uint8Array): boolean {
assert(publicKeys, "publicKey is null or undefined");
assert(messageHash, "messageHash is null or undefined");
assert(signature, "signature is null or undefined");
try {
return Signature.fromCompressedBytes(signature).verifyAggregate(
publicKeys.map((pubkey) => PublicKey.fromBytes(pubkey)),
messageHash
);
} catch (e) {
return false;
}
}
/**
* Verifies if signature is list of message signed with corresponding public key.
* @param publicKeys
* @param messageHashes
* @param signature
* @param fast Check if all messages are different
*/
export function verifyMultiple(
publicKeys: Uint8Array[],
messageHashes: Uint8Array[],
signature: Uint8Array,
fast = false
): boolean {
assert(publicKeys, "publicKey is null or undefined");
assert(messageHashes, "messageHash is null or undefined");
assert(signature, "signature is null or undefined");
if (publicKeys.length === 0 || publicKeys.length != messageHashes.length) {
return false;
}
try {
return Signature.fromCompressedBytes(toBuffer(signature)).verifyMultiple(
publicKeys.map((key) => PublicKey.fromBytes(toBuffer(key))),
messageHashes.map((m) => toBuffer(m)),
fast
);
} catch (e) {
return false;
}
}
export default {
generateKeyPair,
generatePublicKey,
sign,
aggregateSignatures,
aggregatePubkeys,
verify,
verifyAggregate,
verifyMultiple,
return bls;
};
export default bls;

View File

@ -1,29 +0,0 @@
import {PublicKey} from "./publicKey";
import {PrivateKey} from "./privateKey";
export class Keypair {
private readonly _publicKey: PublicKey;
private readonly _privateKey: PrivateKey;
public constructor(privateKey: PrivateKey, publicKey?: PublicKey) {
this._privateKey = privateKey;
if (!publicKey) {
this._publicKey = PublicKey.fromPrivateKey(this._privateKey);
} else {
this._publicKey = publicKey;
}
}
public get publicKey(): PublicKey {
return this._publicKey;
}
public get privateKey(): PrivateKey {
return this._privateKey;
}
public static generate(): Keypair {
return new Keypair(PrivateKey.random());
}
}

View File

@ -1,66 +0,0 @@
import {SECRET_KEY_LENGTH} from "./constants";
import {assert} from "./helpers";
import {SecretKeyType} from "bls-eth-wasm";
import {generateRandomSecretKey} from "@chainsafe/bls-keygen";
import {getContext} from "./context";
import {PublicKey} from "./publicKey";
import {Signature} from "./signature";
export class PrivateKey {
private value: SecretKeyType;
protected constructor(value: SecretKeyType) {
this.value = value;
}
public static fromBytes(bytes: Uint8Array): PrivateKey {
assert(bytes.length === SECRET_KEY_LENGTH, "Private key should have 32 bytes");
const context = getContext();
const secretKey = new context.SecretKey();
secretKey.deserialize(Buffer.from(bytes));
return new PrivateKey(secretKey);
}
public static fromHexString(value: string): PrivateKey {
value = value.replace("0x", "");
assert(value.length === SECRET_KEY_LENGTH * 2, "secret key must have 32 bytes");
const context = getContext();
return new PrivateKey(context.deserializeHexStrToSecretKey(value));
}
public static fromInt(num: number): PrivateKey {
const context = getContext();
const secretKey = new context.SecretKey();
secretKey.setInt(num);
return new PrivateKey(secretKey);
}
public static random(): PrivateKey {
const randomKey: Buffer = generateRandomSecretKey();
return this.fromBytes(randomKey);
}
public getValue(): SecretKeyType {
return this.value;
}
// public sign(message: Uint8Array): Signature {
// return Signature.fromValue(this.value.sign(message));
// }
public signMessage(message: Uint8Array): Signature {
return Signature.fromValue(this.value.sign(message));
}
public toPublicKey(): PublicKey {
return PublicKey.fromPublicKeyType(this.value.getPublicKey());
}
public toBytes(): Buffer {
return Buffer.from(this.value.serialize());
}
public toHexString(): string {
return `0x${this.value.serializeToHexStr()}`;
}
}

View File

@ -1,61 +0,0 @@
import {PrivateKey} from "./privateKey";
import {PublicKeyType} from "bls-eth-wasm";
import {getContext} from "./context";
import {PUBLIC_KEY_LENGTH} from "./constants";
import {assert} from "./helpers";
import {Signature} from "./signature";
import {EMPTY_PUBLIC_KEY} from "./helpers/utils";
export class PublicKey {
private value: PublicKeyType;
protected constructor(value: PublicKeyType) {
this.value = value;
}
public static fromPrivateKey(privateKey: PrivateKey): PublicKey {
return privateKey.toPublicKey();
}
public static fromBytes(bytes: Uint8Array): PublicKey {
const context = getContext();
const publicKey = new context.PublicKey();
if (!EMPTY_PUBLIC_KEY.equals(bytes)) {
publicKey.deserialize(bytes);
}
return new PublicKey(publicKey);
}
public static fromHex(value: string): PublicKey {
value = value.replace("0x", "");
assert(value.length === PUBLIC_KEY_LENGTH * 2);
const context = getContext();
return new PublicKey(context.deserializeHexStrToPublicKey(value));
}
public static fromPublicKeyType(value: PublicKeyType): PublicKey {
return new PublicKey(value);
}
public add(other: PublicKey): PublicKey {
const agg = new PublicKey(this.value.clone());
agg.value.add(other.value);
return agg;
}
public verifyMessage(signature: Signature, messageHash: Uint8Array): boolean {
return this.value.verify(signature.getValue(), messageHash);
}
public toBytesCompressed(): Buffer {
return Buffer.from(this.value.serialize());
}
public toHexString(): string {
return `0x${this.toBytesCompressed().toString("hex")}`;
}
public getValue(): PublicKeyType {
return this.value;
}
}

View File

@ -1,72 +0,0 @@
import {assert} from "./helpers";
import {FP_POINT_LENGTH} from "./constants";
import {SignatureType} from "bls-eth-wasm";
import {getContext} from "./context";
import {PublicKey} from "./publicKey";
import {EMPTY_SIGNATURE} from "./helpers/utils";
export class Signature {
private value: SignatureType;
protected constructor(value: SignatureType) {
this.value = value;
assert(this.value.isValidOrder());
}
public static fromCompressedBytes(value: Uint8Array): Signature {
assert(value.length === 2 * FP_POINT_LENGTH, `Signature must have ${2 * FP_POINT_LENGTH} bytes`);
const context = getContext();
const signature = new context.Signature();
if (!EMPTY_SIGNATURE.equals(value)) {
signature.deserialize(value);
}
return new Signature(signature);
}
public static fromValue(signature: SignatureType): Signature {
return new Signature(signature);
}
public static aggregate(signatures: Signature[]): Signature {
const context = getContext();
const signature = new context.Signature();
signature.aggregate(signatures.map((sig) => sig.getValue()));
return new Signature(signature);
}
public add(other: Signature): Signature {
const agg = this.value.clone();
agg.add(other.value);
return new Signature(agg);
}
public getValue(): SignatureType {
return this.value;
}
public verifyAggregate(publicKeys: PublicKey[], message: Uint8Array): boolean {
return this.value.fastAggregateVerify(
publicKeys.map((key) => key.getValue()),
message
);
}
public verifyMultiple(publicKeys: PublicKey[], messages: Uint8Array[], fast = false): boolean {
const msgs = Buffer.concat(messages);
if (!fast && !getContext().areAllMsgDifferent(msgs, messages[0].length)) {
return false;
}
return this.value.aggregateVerifyNoCheck(
publicKeys.map((key) => key.getValue()),
msgs
);
}
public toBytesCompressed(): Buffer {
return Buffer.from(this.value.serialize());
}
public toHex(): string {
return "0x" + this.value.serializeToHexStr();
}
}

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

@ -0,0 +1,32 @@
/* eslint-disable max-len */
/**
* Data computed with lower level BLS primitives
* https://github.com/dapplion/eth2-bls-wasm/blob/2d2f3e6a0487e96706bfd8a1b8039c7d6c79f71f/verifyMultipleSignatures.test.js#L40
* It creates N valid signatures
* It messes with the last 2 signatures, modifying their values
* such that they wqould not fail in aggregate signature verification.
* Creates a random G2 point with is added to the last signature
* and substracted to the second to last signature
*/
export const maliciousVerifyMultipleSignaturesData = {
pks: [
"b836ccf44fa01e46745ccc3a47855e959783ef5df5cdcc607354b98d52c16b6613761339bfb833fd525cdca7c8071c6b",
"a317ce36dcf2bf6fd262dbad80427f890bc166152682cb6c600a66eb7d525f200839ab798ca4877c3143a31201905de4",
"b9b7b4f4a88d98f34b4c9ba8ae10e935ba51164ddc045d6ae26b403c87a6934e6c75f9fb5cc4b3b29a1255b316d08de5",
"a386a2bc7e9d13cf9b4ad3c819547534c768aeae6a2414bfcebee50f38aaf85a9d610974db931278c08fe86a91eb2999",
],
msgs: [
"690a91fc0a7a49bbc5afe9516c1831ca8845f281ef2e414f7dfeb71b5e91a902",
"3829d4fc2332afc2634079823b89598f3674be5da324b1092b3d8aeb7af5e164",
"9a9406647ed6af16b5ce3e828c5f5ef35f1221ed10476209476c12776ce417ac",
"3e8e4bcb78fda59a43ebfb90970cc6036ce18dc3d3a1b714cc4c1bfc00b8258e",
],
sigs: [
"864ed65f224cf4e49e9bbf313d3dc243649885d9bd432a15e6c1259f2e4c29fcefa7a4c3aafaac01519f7c92239702d7096df2971b1801cd26d0ca0d5e7743ccb0abe79d8c383f9bb04ebe553a3094e84d55bc79be7eff5ffdb9b322205acfd1",
"90efd8c82c356956fc170bec2aed874d14cea079625dfe69d8bc375e10fcd96e2c0348dfeb713f1889629ccb9ec95fee0e0c9cc7a728d8a7068701a04192ed585ec761edf6e2c1e44ceaaa61732052af81a6033fa7d375d7f7157909549322da",
"9023f43cc8e05a3e842b242b9f6781a9e2eadbfcbebd1242563e56bb47cd273ef20fc0c5099e05e83093581907bfd02915b5ef8c553918d4524c274a8856950c87c6314a2c003a2ed28e5fb56ddfdb233a2b895c2397bd15629325d95ca43b83",
"82c8fedc6ad43e945bbf7529d55b73d7ce593bc9ea94dfaf91d720b2ab0e51ce551f7fcda96d428b627ff776c94d6f360af425fe7fb4e4469b893071149db747f27a8bd488af7ba7f0edf86c7e551af89d7a55d4fc86968e10f91ed76e68e373",
],
manipulated: [false, false, true, true],
};

16
test/downloadSpecTests.ts Normal file
View File

@ -0,0 +1,16 @@
import {downloadTests} from "@chainsafe/lodestar-spec-test-util";
import {SPEC_TEST_VERSION, SPEC_TESTS_DIR, SPEC_TEST_TO_DOWNLOAD} from "./params.js";
/* eslint-disable no-console */
downloadTests(
{
specVersion: SPEC_TEST_VERSION,
outputDir: SPEC_TESTS_DIR,
testsToDownload: SPEC_TEST_TO_DOWNLOAD,
},
console.log
).catch((e) => {
console.error(e);
process.exit(1);
});

8
test/params.ts Normal file
View File

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

View File

@ -0,0 +1,34 @@
import path from "path";
import {describeDirectorySpecTest, InputType} from "@chainsafe/lodestar-spec-test-util";
import {bytesToHex, hexToBytes} from "../../src/helpers/index.js";
import {SPEC_TESTS_DIR} from "../params.js";
import {describeForAllImplementations} from "../switch.js";
import {EmptyAggregateError} from "../../src/errors.js";
interface IAggregateSigsTestCase {
data: {
input: string[];
output: string;
};
}
describeForAllImplementations((bls) => {
describeDirectorySpecTest<IAggregateSigsTestCase, string | null>(
"bls/aggregate/small",
path.join(SPEC_TESTS_DIR, "tests/general/phase0/bls/aggregate/small"),
(testCase) => {
try {
const signatures = testCase.data.input;
const agg = bls.aggregateSignatures(signatures.map(hexToBytes));
return bytesToHex(agg);
} catch (e) {
if (e instanceof EmptyAggregateError) return null;
throw e;
}
},
{
inputTypes: {data: InputType.YAML},
getExpected: (testCase) => testCase.data.output,
}
);
});

View File

@ -1,40 +0,0 @@
import path from "path";
import bls, {initBLS} from "../../src";
import {describeDirectorySpecTest, InputType} from "@chainsafe/lodestar-spec-test-util";
interface IAggregateSigsTestCase {
data: {
input: string[];
output: string;
};
}
before(async function f() {
await initBLS();
});
describeDirectorySpecTest<IAggregateSigsTestCase, string>(
"BLS - aggregate sigs",
path.join(__dirname, "../../node_modules/@chainsafe/eth2-spec-tests/tests/general/phase0/bls/aggregate/small"),
(testCase) => {
try {
const result = bls.aggregateSignatures(
testCase.data.input.map((pubKey) => {
return Buffer.from(pubKey.replace("0x", ""), "hex");
})
);
return `0x${result.toString("hex")}`;
} catch (e) {
if (e.message === "signatures is null or undefined or empty array") {
return null;
}
throw e;
}
},
{
inputTypes: {
data: InputType.YAML,
},
getExpected: (testCase) => testCase.data.output,
}
);

View File

@ -1,42 +0,0 @@
import path from "path";
import bls, {initBLS} from "../../src";
import {describeDirectorySpecTest, InputType} from "@chainsafe/lodestar-spec-test-util";
interface IAggregateSigsVerifyTestCase {
data: {
input: {
pubkeys: string[];
messages: string[];
signature: string;
};
output: boolean;
};
}
before(async function f() {
try {
await initBLS();
} catch (e) {
console.log(e);
}
});
describeDirectorySpecTest<IAggregateSigsVerifyTestCase, boolean>(
"BLS - aggregate sigs verify",
path.join(__dirname, "../../node_modules/@chainsafe/eth2-spec-tests/tests/general/phase0/bls/aggregate_verify/small"),
(testCase) => {
const pubkeys = testCase.data.input.pubkeys.map((pubkey) => {
return Buffer.from(pubkey.replace("0x", ""), "hex");
});
const messages = testCase.data.input.messages.map((msg) => {
return Buffer.from(msg.replace("0x", ""), "hex");
});
return bls.verifyMultiple(pubkeys, messages, Buffer.from(testCase.data.input.signature.replace("0x", ""), "hex"));
},
{
inputTypes: {
data: InputType.YAML,
},
getExpected: (testCase) => testCase.data.output,
}
);

View File

@ -0,0 +1,31 @@
import path from "path";
import {describeDirectorySpecTest, InputType} from "@chainsafe/lodestar-spec-test-util";
import {hexToBytes} from "../../src/helpers/index.js";
import {SPEC_TESTS_DIR} from "../params.js";
import {describeForAllImplementations} from "../switch.js";
interface IAggregateSigsVerifyTestCase {
data: {
input: {
pubkeys: string[];
messages: string[];
signature: string;
};
output: boolean;
};
}
describeForAllImplementations((bls) => {
describeDirectorySpecTest<IAggregateSigsVerifyTestCase, boolean>(
"bls/aggregate_verify/small",
path.join(SPEC_TESTS_DIR, "tests/general/phase0/bls/aggregate_verify/small"),
(testCase) => {
const {pubkeys, messages, signature} = testCase.data.input;
return bls.verifyMultiple(pubkeys.map(hexToBytes), messages.map(hexToBytes), hexToBytes(signature));
},
{
inputTypes: {data: InputType.YAML},
getExpected: (testCase) => testCase.data.output,
}
);
});

View File

@ -1,6 +1,9 @@
import path from "path";
import bls, {initBLS} from "../../src";
import {describeDirectorySpecTest, InputType} from "@chainsafe/lodestar-spec-test-util";
import {hexToBytes} from "../../src/helpers/index.js";
import {SPEC_TESTS_DIR} from "../params.js";
import {describeForAllImplementations} from "../switch.js";
import {CoordType} from "@chainsafe/blst";
interface IAggregateSigsVerifyTestCase {
data: {
@ -13,31 +16,24 @@ interface IAggregateSigsVerifyTestCase {
};
}
before(async function f() {
try {
await initBLS();
} catch (e) {
console.log(e);
}
});
describeDirectorySpecTest<IAggregateSigsVerifyTestCase, boolean>(
"BLS - aggregate sigs verify",
path.join(
__dirname,
"../../node_modules/@chainsafe/eth2-spec-tests/tests/general/phase0/bls/fast_aggregate_verify/small"
),
(testCase) => {
return bls.verifyAggregate(
testCase.data.input.pubkeys.map((key) => Buffer.from(key.replace("0x", ""), "hex")),
Buffer.from(testCase.data.input.message.replace("0x", ""), "hex"),
Buffer.from(testCase.data.input.signature.replace("0x", ""), "hex")
);
},
{
inputTypes: {
data: InputType.YAML,
describeForAllImplementations((bls) => {
describeDirectorySpecTest<IAggregateSigsVerifyTestCase, boolean>(
"bls/fast_aggregate_verify/small",
path.join(SPEC_TESTS_DIR, "tests/general/phase0/bls/fast_aggregate_verify/small"),
(testCase) => {
const {pubkeys, message, signature} = testCase.data.input;
try {
return bls.Signature.fromBytes(hexToBytes(signature)).verifyAggregate(
pubkeys.map((hex) => bls.PublicKey.fromBytes(hexToBytes(hex), CoordType.jacobian, true)),
hexToBytes(message)
);
} catch (e) {
return false;
}
},
getExpected: (testCase) => testCase.data.output,
}
);
{
inputTypes: {data: InputType.YAML},
getExpected: (testCase) => testCase.data.output,
}
);
});

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