Compare commits
4 Commits
master
...
mpetrunic/
Author | SHA1 | Date |
---|---|---|
Marin Petrunić | 5d92af17fc | |
Marin Petrunić | c153de6e24 | |
Marin Petrunić | 135625ccc0 | |
Marin Petrunić | 0d273a7d36 |
|
@ -0,0 +1,15 @@
|
||||||
|
/*
|
||||||
|
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'],
|
||||||
|
})
|
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"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"
|
||||||
|
]
|
||||||
|
}
|
|
@ -13,15 +13,13 @@ module.exports = {
|
||||||
},
|
},
|
||||||
parser: "@typescript-eslint/parser",
|
parser: "@typescript-eslint/parser",
|
||||||
parserOptions: {
|
parserOptions: {
|
||||||
ecmaVersion: "latest",
|
ecmaVersion: 10,
|
||||||
project: "./tsconfig.json",
|
project: "./tsconfig.json"
|
||||||
sourceType: "module",
|
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
"@typescript-eslint",
|
"@typescript-eslint",
|
||||||
"eslint-plugin-import",
|
"eslint-plugin-import",
|
||||||
"prettier",
|
"prettier"
|
||||||
"@chainsafe/eslint-plugin-node"
|
|
||||||
],
|
],
|
||||||
extends: [
|
extends: [
|
||||||
"eslint:recommended",
|
"eslint:recommended",
|
||||||
|
@ -35,21 +33,20 @@ module.exports = {
|
||||||
"prettier/prettier": "error",
|
"prettier/prettier": "error",
|
||||||
//doesnt work, it reports false errors
|
//doesnt work, it reports false errors
|
||||||
"constructor-super": "off",
|
"constructor-super": "off",
|
||||||
//"@typescript-eslint/class-name-casing": "error",
|
"@typescript-eslint/class-name-casing": "error",
|
||||||
"@typescript-eslint/explicit-function-return-type": ["error", {
|
"@typescript-eslint/explicit-function-return-type": ["error", {
|
||||||
"allowExpressions": true
|
"allowExpressions": true
|
||||||
}],
|
}],
|
||||||
"@typescript-eslint/func-call-spacing": "error",
|
"@typescript-eslint/func-call-spacing": "error",
|
||||||
"@typescript-eslint/indent": ["error", 2],
|
"@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/member-ordering": "error",
|
||||||
"@typescript-eslint/no-explicit-any": "error",
|
"@typescript-eslint/no-explicit-any": "error",
|
||||||
"@typescript-eslint/no-require-imports": "error",
|
"@typescript-eslint/no-require-imports": "error",
|
||||||
"@typescript-eslint/no-unused-vars": ["error", {
|
"@typescript-eslint/no-unused-vars": ["error", {
|
||||||
"varsIgnorePattern": "^_",
|
"varsIgnorePattern": "^_"
|
||||||
"argsIgnorePattern": "^_",
|
|
||||||
}],
|
}],
|
||||||
"@typescript-eslint/ban-ts-comment": "warn",
|
"@typescript-eslint/ban-ts-ignore": "warn",
|
||||||
"@typescript-eslint/no-use-before-define": "off",
|
"@typescript-eslint/no-use-before-define": "off",
|
||||||
"@typescript-eslint/semi": "error",
|
"@typescript-eslint/semi": "error",
|
||||||
"@typescript-eslint/type-annotation-spacing": "error",
|
"@typescript-eslint/type-annotation-spacing": "error",
|
||||||
|
@ -58,7 +55,7 @@ module.exports = {
|
||||||
"import/no-extraneous-dependencies": ["error", {
|
"import/no-extraneous-dependencies": ["error", {
|
||||||
"devDependencies": false,
|
"devDependencies": false,
|
||||||
"optionalDependencies": false,
|
"optionalDependencies": false,
|
||||||
"peerDependencies": true
|
"peerDependencies": false
|
||||||
}],
|
}],
|
||||||
"func-call-spacing": "off",
|
"func-call-spacing": "off",
|
||||||
"max-len": ["error", {
|
"max-len": ["error", {
|
||||||
|
@ -78,15 +75,7 @@ module.exports = {
|
||||||
"no-prototype-builtins": 0,
|
"no-prototype-builtins": 0,
|
||||||
"prefer-const": "error",
|
"prefer-const": "error",
|
||||||
"quotes": ["error", "double"],
|
"quotes": ["error", "double"],
|
||||||
"semi": "off",
|
"semi": "off"
|
||||||
"@chainsafe/node/file-extension-in-import": [
|
|
||||||
"error",
|
|
||||||
"always",
|
|
||||||
{
|
|
||||||
"esm": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"import/no-unresolved": "off",
|
|
||||||
},
|
},
|
||||||
"overrides": [
|
"overrides": [
|
||||||
{
|
{
|
|
@ -1,11 +0,0 @@
|
||||||
# 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
|
|
|
@ -1 +1 @@
|
||||||
custom: https://gitcoin.co/grants/6034/lodestar-typescript-ethereum-consensus-client
|
custom: https://gitcoin.co/grants/79/lodestar-eth20-client
|
||||||
|
|
|
@ -1,31 +0,0 @@
|
||||||
---
|
|
||||||
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]-->
|
|
|
@ -1,21 +0,0 @@
|
||||||
---
|
|
||||||
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.-->
|
|
|
@ -1,13 +0,0 @@
|
||||||
---
|
|
||||||
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.-->
|
|
|
@ -1,69 +0,0 @@
|
||||||
# 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
|
|
|
@ -1,65 +0,0 @@
|
||||||
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 }}
|
|
|
@ -1,35 +1,18 @@
|
||||||
name: Main
|
name: CI Tests
|
||||||
|
|
||||||
on: [pull_request, push]
|
on: [pull_request, push]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
tests:
|
lint:
|
||||||
name: Tests
|
name: Quick tests
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
node: [14, 16]
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v1
|
||||||
- uses: actions/setup-node@v1
|
- name: Bootstrap
|
||||||
with:
|
run: yarn
|
||||||
node-version: ${{matrix.node}}
|
- name: Check types
|
||||||
- name: Install deps
|
run: yarn check-types
|
||||||
run: yarn --non-interactive --frozen-lockfile
|
- name: Lint
|
||||||
- name: Lint
|
run: yarn lint
|
||||||
run: yarn lint
|
- name: Tests
|
||||||
- name: Test build
|
run: yarn test
|
||||||
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
|
|
||||||
|
|
|
@ -65,8 +65,3 @@ typings/
|
||||||
dist/
|
dist/
|
||||||
lib/
|
lib/
|
||||||
benchmark-reports
|
benchmark-reports
|
||||||
|
|
||||||
.vscode/
|
|
||||||
|
|
||||||
# Eth2.0 spec tests data
|
|
||||||
test/spec-tests
|
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
extension: ["ts"]
|
|
||||||
colors: true
|
|
||||||
node-option:
|
|
||||||
- "experimental-specifier-resolution=node"
|
|
||||||
- "loader=ts-node/esm"
|
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
.eslintrc.js
|
|
66
CHANGELOG.md
66
CHANGELOG.md
|
@ -5,71 +5,7 @@ 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/),
|
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).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
## [7.1.1] - 2022-05-15
|
## [dev]
|
||||||
|
|
||||||
### 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
|
## [4.0.0] - 2020-08-31
|
||||||
|
|
||||||
|
|
131
README.md
131
README.md
|
@ -1,125 +1,26 @@
|
||||||
# bls
|
# 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)
|
[![codecov](https://codecov.io/gh/ChainSafe/lodestar/branch/master/graph/badge.svg)](https://codecov.io/gh/ChainSafe/lodestar)
|
||||||
![ETH2.0_Spec_Version 1.0.0](https://img.shields.io/badge/ETH2.0_Spec_Version-1.0.0-2e86c1.svg)
|
![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-2022-yellow)
|
![ES Version](https://img.shields.io/badge/ES-2017-yellow)
|
||||||
![Node Version](https://img.shields.io/badge/node-14.8-green)
|
![Node Version](https://img.shields.io/badge/node-12.x-green)
|
||||||
|
|
||||||
Javascript library for BLS (Boneh-Lynn-Shacham) signatures and signature aggregation, tailored for use in Eth2.
|
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)
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
```bash
|
- `yarn add @chainsafe/bls`
|
||||||
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
|
## License
|
||||||
|
|
||||||
|
|
|
@ -1,84 +0,0 @@
|
||||||
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),
|
|
||||||
});
|
|
||||||
});
|
|
||||||
})();
|
|
|
@ -1,109 +0,0 @@
|
||||||
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,
|
|
||||||
});
|
|
||||||
})();
|
|
|
@ -1,14 +0,0 @@
|
||||||
{
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,3 +0,0 @@
|
||||||
export const aggCount = 30;
|
|
||||||
export const runs = 100;
|
|
||||||
export const runsNoble = 10;
|
|
|
@ -1,35 +0,0 @@
|
||||||
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);
|
|
||||||
}
|
|
|
@ -1,34 +0,0 @@
|
||||||
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"));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})();
|
|
|
@ -1,8 +0,0 @@
|
||||||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
|
||||||
# yarn lockfile v1
|
|
||||||
|
|
||||||
|
|
||||||
noble-bls12-381@^0.7.2:
|
|
||||||
version "0.7.2"
|
|
||||||
resolved "https://registry.yarnpkg.com/noble-bls12-381/-/noble-bls12-381-0.7.2.tgz#9a9384891569ba32785d6e4ff8588b783487eae4"
|
|
||||||
integrity sha512-Z5isbU6opuWPL3dxsGqO5BdOE8WP1XUM7HFIn/xeE5pATTnml/PEIy4MFQQrktHiitkuJdsCDtzEOnS9eIpC3Q==
|
|
|
@ -1,34 +0,0 @@
|
||||||
// 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,
|
|
||||||
});
|
|
||||||
};
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
// 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
|
||||||
|
});
|
||||||
|
};
|
|
@ -1,8 +0,0 @@
|
||||||
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;
|
|
|
@ -1,14 +0,0 @@
|
||||||
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;
|
|
|
@ -1,11 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
|
@ -1,37 +0,0 @@
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
|
@ -1,39 +0,0 @@
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,21 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
|
@ -1,62 +0,0 @@
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
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;
|
|
|
@ -1,5 +0,0 @@
|
||||||
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;
|
|
|
@ -1,25 +0,0 @@
|
||||||
/**
|
|
||||||
* 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);
|
|
||||||
}
|
|
|
@ -1,39 +0,0 @@
|
||||||
/**
|
|
||||||
* 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`);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
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;
|
|
||||||
};
|
|
|
@ -1,137 +0,0 @@
|
||||||
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,
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,2 +0,0 @@
|
||||||
import type { IBls, Implementation } from "./types.js";
|
|
||||||
export declare function getImplementation(impl?: Implementation): Promise<IBls>;
|
|
|
@ -1,17 +0,0 @@
|
||||||
// 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}`);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,10 +0,0 @@
|
||||||
/**
|
|
||||||
* 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;
|
|
|
@ -1,30 +0,0 @@
|
||||||
/**
|
|
||||||
* 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;
|
|
||||||
}
|
|
|
@ -1,2 +0,0 @@
|
||||||
export * from "./hex.js";
|
|
||||||
export * from "./utils.js";
|
|
|
@ -1,2 +0,0 @@
|
||||||
export * from "./hex.js";
|
|
||||||
export * from "./utils.js";
|
|
|
@ -1,8 +0,0 @@
|
||||||
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;
|
|
|
@ -1,26 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
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 {};
|
|
|
@ -1,35 +0,0 @@
|
||||||
/* 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;
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
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;
|
|
|
@ -1,18 +0,0 @@
|
||||||
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;
|
|
|
@ -1,11 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
|
@ -1,53 +0,0 @@
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
|
@ -1,43 +0,0 @@
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,24 +0,0 @@
|
||||||
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;
|
|
||||||
}
|
|
|
@ -1,70 +0,0 @@
|
||||||
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 +0,0 @@
|
||||||
export {};
|
|
|
@ -1,6 +0,0 @@
|
||||||
import { bls } from "./index.js";
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
(function (window) {
|
|
||||||
window.bls = bls;
|
|
||||||
// @ts-ignore
|
|
||||||
})(window);
|
|
|
@ -1,3 +0,0 @@
|
||||||
import type { IBls } from "./types.js";
|
|
||||||
export declare const bls: () => Promise<IBls>;
|
|
||||||
export default bls;
|
|
14
lib/index.js
14
lib/index.js
|
@ -1,14 +0,0 @@
|
||||||
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;
|
|
|
@ -1,4 +0,0 @@
|
||||||
import type { IBls, Implementation } from "./types.js";
|
|
||||||
declare const bls: IBls;
|
|
||||||
export default bls;
|
|
||||||
export declare function init(impl: Implementation): Promise<void>;
|
|
|
@ -1,11 +0,0 @@
|
||||||
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
|
@ -1,66 +0,0 @@
|
||||||
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
10
lib/types.js
|
@ -1,10 +0,0 @@
|
||||||
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 = {}));
|
|
125
package.json
125
package.json
|
@ -1,58 +1,16 @@
|
||||||
{
|
{
|
||||||
"name": "@chainsafe/bls",
|
"name": "@chainsafe/bls",
|
||||||
"version": "7.1.1",
|
"version": "4.0.0",
|
||||||
"description": "Implementation of bls signature verification for ethereum 2.0",
|
"description": "Implementation of bls signature verification for ethereum 2.0",
|
||||||
"engines": {
|
"main": "lib/index.js",
|
||||||
"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",
|
"types": "lib/index.d.ts",
|
||||||
"typesVersions": {
|
|
||||||
"*": {
|
|
||||||
"*": [
|
|
||||||
"*",
|
|
||||||
"lib/*",
|
|
||||||
"lib/*/index"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"module": "./lib/index.js",
|
|
||||||
"browser": "./lib/herumi.js",
|
|
||||||
"homepage": "https://github.com/chainsafe/bls",
|
"homepage": "https://github.com/chainsafe/bls",
|
||||||
"author": "ChainSafe Systems",
|
"author": "ChainSafe Systems",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"files": [
|
"files": [
|
||||||
"lib/**/*.js",
|
"lib/**/*.js",
|
||||||
"lib/**/*.js.map",
|
"lib/**/*.js.map",
|
||||||
"lib/**/*.d.ts",
|
"lib/**/*.d.ts"
|
||||||
"*.d.ts",
|
|
||||||
"*.js"
|
|
||||||
],
|
],
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"ethereum",
|
"ethereum",
|
||||||
|
@ -62,63 +20,66 @@
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"clean": "rm -rf lib && rm -rf dist && rm -f tsconfig.tsbuildinfo",
|
"clean": "rm -rf lib && rm -rf dist && rm -f tsconfig.tsbuildinfo",
|
||||||
"check-build": "node -e \"(async function() { await import('./lib/index.js') })()\"",
|
"build": "yarn build-lib && yarn build-types",
|
||||||
"build": "tsc --incremental --project tsconfig.build.json",
|
"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",
|
||||||
"lint": "eslint --color --ext .ts src/ test/",
|
"lint": "eslint --color --ext .ts src/ test/",
|
||||||
"lint:fix": "yarn run lint --fix",
|
"lint:fix": "yarn run lint --fix",
|
||||||
|
"pretest": "yarn check-types",
|
||||||
"prepublishOnly": "yarn build",
|
"prepublishOnly": "yarn build",
|
||||||
"test:web": "karma start karma.conf.cjs",
|
"test:web:unit": "karma start",
|
||||||
"test:unit": "mocha 'test/unit/**/*.test.ts'",
|
"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:coverage": "nyc --cache-dir .nyc_output/.cache -r lcov -e .ts mocha 'test/unit/**/*.test.ts' && nyc report",
|
"test:unit": "yarn run test:node:unit && yarn run test:web:unit",
|
||||||
"test:spec": "mocha 'test/spec/**/*.test.ts'",
|
"test:spec": "mocha --colors -r ts-node/register 'test/spec/**/*.test.ts'",
|
||||||
"test": "yarn run test:unit && yarn run test:spec",
|
"test": "yarn run test:unit && yarn run test:spec",
|
||||||
"download-test-cases": "ts-node-esm test/downloadSpecTests.ts",
|
|
||||||
"coverage": "codecov -F bls",
|
"coverage": "codecov -F bls",
|
||||||
"benchmark": "ts-node-esm benchmark",
|
"benchmark": "node -r ./.babel-register test/benchmarks"
|
||||||
"benchmark:all": "cd benchmark && yarn install && yarn benchmark:all"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@chainsafe/bls-keygen": "^0.4.0",
|
"@chainsafe/bls-keygen": "^0.3.0",
|
||||||
"bls-eth-wasm": "^0.4.8",
|
"bls-eth-wasm": "^0.4.1"
|
||||||
"randombytes": "^2.1.0"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@chainsafe/blst": "^0.2.4",
|
"@babel/cli": "^7.8.4",
|
||||||
"@chainsafe/eslint-plugin-node": "^11.2.3",
|
"@babel/core": "^7.8.4",
|
||||||
"@chainsafe/lodestar-spec-test-util": "^0.18.0",
|
"@babel/plugin-proposal-class-properties": "^7.8.3",
|
||||||
"@chainsafe/threads": "^1.9.0",
|
"@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",
|
||||||
"@types/chai": "^4.2.9",
|
"@types/chai": "^4.2.9",
|
||||||
"@types/mocha": "^10.0.0",
|
"@types/mocha": "^8.0.4",
|
||||||
"@types/randombytes": "^2.0.0",
|
"@typescript-eslint/eslint-plugin": "^2.20.0",
|
||||||
"@typescript-eslint/eslint-plugin": "^4.31.1",
|
"@typescript-eslint/parser": "^2.20.0",
|
||||||
"@typescript-eslint/parser": "^4.31.1",
|
"chai": "^4.2.0",
|
||||||
"buffer": "^6.0.3",
|
"eslint": "^6.8.0",
|
||||||
"chai": "^4.3.6",
|
|
||||||
"eslint": "^7.14.0",
|
|
||||||
"eslint-plugin-import": "^2.20.1",
|
"eslint-plugin-import": "^2.20.1",
|
||||||
"eslint-plugin-prettier": "^3.1.4",
|
"eslint-plugin-prettier": "^3.1.4",
|
||||||
"karma": "^6.3.18",
|
"karma": "^4.4.1",
|
||||||
"karma-chai": "^0.1.0",
|
"karma-chai": "^0.1.0",
|
||||||
"karma-chrome-launcher": "^3.1.0",
|
"karma-chrome-launcher": "^3.1.0",
|
||||||
"karma-cli": "^2.0.0",
|
"karma-cli": "^2.0.0",
|
||||||
"karma-mocha": "^2.0.1",
|
"karma-mocha": "^1.3.0",
|
||||||
"karma-spec-reporter": "^0.0.32",
|
"karma-spec-reporter": "^0.0.32",
|
||||||
"karma-webpack": "^5.0.0",
|
"karma-webpack": "^4.0.2",
|
||||||
"mocha": "^10.0.0",
|
"mocha": "^8.2.1",
|
||||||
"nyc": "^15.0.0",
|
"nyc": "^15.0.0",
|
||||||
"prettier": "^2.1.2",
|
"prettier": "^2.1.2",
|
||||||
"resolve-typescript-plugin": "^1.2.0",
|
"ts-loader": "^6.2.1",
|
||||||
"ts-loader": "^9.3.1",
|
"ts-node": "^8.6.2",
|
||||||
"ts-node": "^10.9.1",
|
"typescript": "^3.7.5",
|
||||||
"typescript": "4.7.4",
|
"webpack": "^4.30.0",
|
||||||
"webpack": "^5.72.0",
|
"webpack-cli": "^3.3.2"
|
||||||
"webpack-cli": "^4.9.2"
|
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"mocha": "^9.2.2",
|
"mocha": "^8.2.1",
|
||||||
"v8-profiler-next": "1.3.0"
|
"v8-profiler-next": "1.3.0"
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"@chainsafe/blst": "^0.2.4"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +0,0 @@
|
||||||
import {SecretKey} from "./secretKey.js";
|
|
||||||
import {PublicKey} from "./publicKey.js";
|
|
||||||
import {Signature} from "./signature.js";
|
|
||||||
import {IBls} from "../types.js";
|
|
||||||
import {functionalInterfaceFactory} from "../functional.js";
|
|
||||||
export * from "../constants.js";
|
|
||||||
|
|
||||||
export {SecretKey, PublicKey, Signature};
|
|
||||||
|
|
||||||
export const bls: IBls = {
|
|
||||||
implementation: "blst-native",
|
|
||||||
SecretKey,
|
|
||||||
PublicKey,
|
|
||||||
Signature,
|
|
||||||
|
|
||||||
...functionalInterfaceFactory({SecretKey, PublicKey, Signature}),
|
|
||||||
};
|
|
||||||
|
|
||||||
export default bls;
|
|
|
@ -1,42 +0,0 @@
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,50 +0,0 @@
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,77 +0,0 @@
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,5 +1,5 @@
|
||||||
export const SECRET_KEY_LENGTH = 32;
|
export const SECRET_KEY_LENGTH = 32;
|
||||||
export const PUBLIC_KEY_LENGTH_COMPRESSED = 48;
|
export const SIGNATURE_LENGTH = 96;
|
||||||
export const PUBLIC_KEY_LENGTH_UNCOMPRESSED = 48 * 2;
|
export const FP_POINT_LENGTH = 48;
|
||||||
export const SIGNATURE_LENGTH_COMPRESSED = 96;
|
export const PUBLIC_KEY_LENGTH = FP_POINT_LENGTH;
|
||||||
export const SIGNATURE_LENGTH_UNCOMPRESSED = 96 * 2;
|
export const G2_HASH_PADDING = 16;
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
import {herumiToIBls} from "./herumi";
|
||||||
|
import {IBls} from "./interface";
|
||||||
|
|
||||||
|
export type Backing = "herumi" | "blst-native" | "blst-wasm";
|
||||||
|
|
||||||
|
let contextBacking: Backing | undefined = undefined;
|
||||||
|
let context: IBls | undefined = undefined;
|
||||||
|
|
||||||
|
//to maintain api compatible, add all backing context to return type
|
||||||
|
export async function init(backing: Backing = "herumi"): Promise<IBls> {
|
||||||
|
if (!context) {
|
||||||
|
switch (backing) {
|
||||||
|
case "herumi":
|
||||||
|
{
|
||||||
|
context = await herumiToIBls();
|
||||||
|
contextBacking = backing;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error(`Unsupported backing - ${backing}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await context.init();
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function destroy(): void {
|
||||||
|
if (context) {
|
||||||
|
context.destroy();
|
||||||
|
}
|
||||||
|
context = undefined;
|
||||||
|
contextBacking = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getContext(): IBls {
|
||||||
|
if (!context) {
|
||||||
|
throw new Error("BLS not initialized");
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getContextBacking(): Backing {
|
||||||
|
if (!context) {
|
||||||
|
throw new Error("BLS not initialized");
|
||||||
|
}
|
||||||
|
return contextBacking;
|
||||||
|
}
|
|
@ -1,45 +0,0 @@
|
||||||
/**
|
|
||||||
* 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`);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,157 +0,0 @@
|
||||||
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,
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
import type {IBls, Implementation} from "./types.js";
|
|
||||||
|
|
||||||
// Thanks https://github.com/iliakan/detect-node/blob/master/index.esm.js
|
|
||||||
const isNode = Object.prototype.toString.call(typeof process !== "undefined" ? process : 0) === "[object process]";
|
|
||||||
|
|
||||||
export async function getImplementation(impl: Implementation = "herumi"): Promise<IBls> {
|
|
||||||
switch (impl) {
|
|
||||||
case "herumi": {
|
|
||||||
return await (await import("./herumi/index.js")).bls();
|
|
||||||
}
|
|
||||||
|
|
||||||
case "blst-native":
|
|
||||||
// Lazy import native bindings to prevent automatically importing binding.node files
|
|
||||||
if (!isNode) {
|
|
||||||
throw Error("blst-native is only supported in NodeJS");
|
|
||||||
}
|
|
||||||
return (await import("./blst-native/index.js")).bls;
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw new Error(`Unsupported implementation - ${impl}`);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,37 +0,0 @@
|
||||||
/**
|
|
||||||
* 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;
|
|
||||||
}
|
|
|
@ -1,2 +1 @@
|
||||||
export * from "./hex.js";
|
export * from "./utils";
|
||||||
export * from "./utils.js";
|
|
||||||
|
|
|
@ -1,36 +1,10 @@
|
||||||
import randomBytes from "randombytes";
|
import {PUBLIC_KEY_LENGTH, SIGNATURE_LENGTH} from "../constants";
|
||||||
|
|
||||||
// Single import to ease changing this lib if necessary
|
export function assert(condition: unknown, message = "Assertion failed"): asserts condition {
|
||||||
export {randomBytes};
|
if (!condition) {
|
||||||
|
throw new Error(message);
|
||||||
/**
|
|
||||||
* 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 function isZeroUint8Array(bytes: Uint8Array): boolean {
|
export const EMPTY_PUBLIC_KEY = Buffer.alloc(PUBLIC_KEY_LENGTH);
|
||||||
return bytes.every((byte) => byte === 0);
|
export const EMPTY_SIGNATURE = Buffer.alloc(SIGNATURE_LENGTH);
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
import herumi from "bls-eth-wasm";
|
||||||
|
import {init as initHerumi, destroy, getContext} from "./context";
|
||||||
|
import {IBls, IPrivateKeyValue, IPublicKeyValue, ISignatureValue} from "../interface";
|
||||||
|
|
||||||
|
export async function herumiToIBls(): Promise<IBls> {
|
||||||
|
await initHerumi();
|
||||||
|
const context: IBls = {
|
||||||
|
SecretKey: getContext().SecretKey as IPrivateKeyValue,
|
||||||
|
PublicKey: getContext().PublicKey as IPublicKeyValue,
|
||||||
|
Signature: getContext().Signature as ISignatureValue,
|
||||||
|
|
||||||
|
toHex: herumi.toHex,
|
||||||
|
toHexStr: herumi.toHexStr,
|
||||||
|
fromHexStr: herumi.fromHexStr,
|
||||||
|
getCurveOrder: herumi.getCurveOrder,
|
||||||
|
getFieldOrder: herumi.getFieldOrder,
|
||||||
|
verifySignatureOrder: herumi.verifySignatureOrder,
|
||||||
|
verifyPublicKeyOrder: herumi.verifyPublicKeyOrder,
|
||||||
|
areAllMsgDifferent: herumi.areAllMsgDifferent,
|
||||||
|
shouldVerifyBlsSignatureOrder: herumi.shouldVerifyBlsSignatureOrder,
|
||||||
|
shouldVerifyBlsPublicKeyOrder: herumi.shouldVerifyBlsPublicKeyOrder,
|
||||||
|
deserializeHexStrToSecretKey: herumi.deserializeHexStrToSecretKey as IBls["deserializeHexStrToSecretKey"],
|
||||||
|
deserializeHexStrToPublicKey: herumi.deserializeHexStrToPublicKey as IBls["deserializeHexStrToPublicKey"],
|
||||||
|
deserializeHexStrToSignature: herumi.deserializeHexStrToSignature as IBls["deserializeHexStrToSignature"],
|
||||||
|
init: async () => {
|
||||||
|
return context;
|
||||||
|
},
|
||||||
|
destroy: () => {
|
||||||
|
destroy();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
return context;
|
||||||
|
}
|
|
@ -1,36 +1,20 @@
|
||||||
/* eslint-disable require-atomic-updates */
|
/* eslint-disable require-atomic-updates */
|
||||||
import bls from "bls-eth-wasm";
|
import bls from "bls-eth-wasm";
|
||||||
import {NotInitializedError} from "../errors.js";
|
|
||||||
|
|
||||||
type Bls = typeof bls;
|
type Bls = typeof bls;
|
||||||
let blsGlobal: Bls | null = null;
|
let blsGlobal: Bls | null = null;
|
||||||
let blsGlobalPromise: Promise<void> | null = null;
|
let blsGlobalPromise: Promise<Bls> | null = null;
|
||||||
|
|
||||||
// Patch to fix multiVerify() calls on a browser with polyfilled NodeJS crypto
|
export async function setupBls(): Promise<Bls> {
|
||||||
declare global {
|
|
||||||
interface Window {
|
|
||||||
msCrypto: typeof window["crypto"];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function setupBls(): Promise<void> {
|
|
||||||
if (!blsGlobal) {
|
if (!blsGlobal) {
|
||||||
await bls.init(bls.BLS12_381);
|
await bls.init();
|
||||||
|
|
||||||
// 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;
|
blsGlobal = bls;
|
||||||
}
|
}
|
||||||
|
return blsGlobal;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cache a promise for Bls instead of Bls to make sure it is initialized only once
|
// Cache a promise for Bls instead of Bls to make sure it is initialized only once
|
||||||
export async function init(): Promise<void> {
|
export async function init(): Promise<Bls> {
|
||||||
if (!blsGlobalPromise) {
|
if (!blsGlobalPromise) {
|
||||||
blsGlobalPromise = setupBls();
|
blsGlobalPromise = setupBls();
|
||||||
}
|
}
|
||||||
|
@ -44,7 +28,7 @@ export function destroy(): void {
|
||||||
|
|
||||||
export function getContext(): Bls {
|
export function getContext(): Bls {
|
||||||
if (!blsGlobal) {
|
if (!blsGlobal) {
|
||||||
throw new NotInitializedError("herumi");
|
throw new Error("BLS not initialized");
|
||||||
}
|
}
|
||||||
return blsGlobal;
|
return blsGlobal;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,23 +1 @@
|
||||||
import {SecretKey} from "./secretKey.js";
|
export {herumiToIBls} from "./adapter";
|
||||||
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;
|
|
||||||
|
|
|
@ -1,61 +0,0 @@
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,57 +0,0 @@
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,93 +0,0 @@
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
162
src/index.ts
162
src/index.ts
|
@ -1,19 +1,155 @@
|
||||||
import type {IBls} from "./types.js";
|
import {PUBLIC_KEY_LENGTH} from "./constants";
|
||||||
import {getImplementation} from "./getImplementation.js";
|
import {Keypair} from "./keypair";
|
||||||
|
import {PrivateKey} from "./privateKey";
|
||||||
|
import {PublicKey} from "./publicKey";
|
||||||
|
import {Signature} from "./signature";
|
||||||
|
import {assert} from "./helpers";
|
||||||
|
|
||||||
// Thanks https://github.com/iliakan/detect-node/blob/master/index.esm.js
|
export {Keypair, PrivateKey, PublicKey, Signature};
|
||||||
const isNode = Object.prototype.toString.call(typeof process !== "undefined" ? process : 0) === "[object process]";
|
|
||||||
|
|
||||||
export const bls = async (): Promise<IBls> => {
|
export {init as initBLS} from "./context";
|
||||||
let bls: IBls;
|
|
||||||
|
|
||||||
try {
|
function toBuffer(input: Uint8Array): Buffer {
|
||||||
bls = await getImplementation(isNode ? "blst-native" : "herumi");
|
return Buffer.from(input.buffer, input.byteOffset, input.length);
|
||||||
} catch (e) {
|
}
|
||||||
bls = await getImplementation("herumi");
|
|
||||||
|
/**
|
||||||
|
* 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();
|
||||||
|
}
|
||||||
|
|
||||||
return bls;
|
/**
|
||||||
|
* 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)
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default bls;
|
|
||||||
|
|
|
@ -0,0 +1,85 @@
|
||||||
|
interface ICommon {
|
||||||
|
new (): this;
|
||||||
|
|
||||||
|
deserializeHexStr(s: string): void;
|
||||||
|
|
||||||
|
serializeToHexStr(): string;
|
||||||
|
|
||||||
|
isEqual(rhs: this): boolean;
|
||||||
|
|
||||||
|
deserialize(v: Uint8Array): void;
|
||||||
|
|
||||||
|
serialize(): Uint8Array;
|
||||||
|
|
||||||
|
add(rhs: this): void;
|
||||||
|
|
||||||
|
dump(msg?: string): string;
|
||||||
|
|
||||||
|
clear(): void;
|
||||||
|
|
||||||
|
clone(): this;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IPrivateKeyValue extends ICommon {
|
||||||
|
setInt(x: number): void;
|
||||||
|
|
||||||
|
setHashOf(a: Uint8Array): void;
|
||||||
|
|
||||||
|
setLittleEndian(a: Uint8Array): void;
|
||||||
|
|
||||||
|
setByCSPRNG(): void;
|
||||||
|
|
||||||
|
getPublicKey(): IPublicKeyValue;
|
||||||
|
|
||||||
|
sign(m: string | Uint8Array): ISignatureValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IPublicKeyValue extends ICommon {
|
||||||
|
verify(signature: ISignatureValue, m: Uint8Array | string): boolean;
|
||||||
|
isValidOrder(): boolean;
|
||||||
|
deserializeUncompressed(s: Uint8Array): void;
|
||||||
|
serializeUncompressed(): Uint8Array;
|
||||||
|
deserializeUncompressedHexStr(s: string): void;
|
||||||
|
serializeUncompressedToHexStr(): string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ISignatureValue extends ICommon {
|
||||||
|
deserializeUncompressed(s: Uint8Array): void;
|
||||||
|
serializeUncompressed(): Uint8Array;
|
||||||
|
deserializeUncompressedHexStr(s: string): void;
|
||||||
|
serializeUncompressedToHexStr(): string;
|
||||||
|
isValidOrder(): boolean;
|
||||||
|
aggregate(others: ISignatureValue[]): boolean;
|
||||||
|
aggregateVerifyNoCheck(publicKeys: IPublicKeyValue[], messages: Uint8Array): boolean;
|
||||||
|
fastAggregateVerify(publicKeys: IPublicKeyValue[], message: Uint8Array): boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IBls {
|
||||||
|
//property names are like that for api compatibility
|
||||||
|
SecretKey: InstanceType<IPrivateKeyValue>;
|
||||||
|
PublicKey: InstanceType<IPublicKeyValue>;
|
||||||
|
Signature: InstanceType<ISignatureValue>;
|
||||||
|
|
||||||
|
toHex(a: Uint8Array, start: number, length: number): string;
|
||||||
|
toHexStr(a: Uint8Array): string;
|
||||||
|
fromHexStr(s: string): Uint8Array;
|
||||||
|
getCurveOrder(): string;
|
||||||
|
getFieldOrder(): string;
|
||||||
|
verifySignatureOrder(doVerify: boolean): void;
|
||||||
|
verifyPublicKeyOrder(doVerify: boolean): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param msgs single array with concatenated messages
|
||||||
|
* @param msgSize defaults to MSG_SIZE
|
||||||
|
*/
|
||||||
|
areAllMsgDifferent(msgs: Uint8Array, msgSize?: number): boolean;
|
||||||
|
shouldVerifyBlsSignatureOrder(b: string): void;
|
||||||
|
shouldVerifyBlsPublicKeyOrder(b: string): void;
|
||||||
|
deserializeHexStrToSecretKey(s: string): IPrivateKeyValue;
|
||||||
|
deserializeHexStrToPublicKey(s: string): IPublicKeyValue;
|
||||||
|
deserializeHexStrToSignature(s: string): ISignatureValue;
|
||||||
|
|
||||||
|
init(): Promise<this>;
|
||||||
|
destroy(): void;
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,67 @@
|
||||||
|
import {SECRET_KEY_LENGTH} from "./constants";
|
||||||
|
import {assert} from "./helpers";
|
||||||
|
import {generateRandomSecretKey} from "@chainsafe/bls-keygen";
|
||||||
|
import {IPrivateKeyValue} from "./interface";
|
||||||
|
import {getContext} from "./context";
|
||||||
|
import {PublicKey, Signature} from ".";
|
||||||
|
|
||||||
|
export class PrivateKey {
|
||||||
|
private value: IPrivateKeyValue;
|
||||||
|
|
||||||
|
protected constructor(value: IPrivateKeyValue) {
|
||||||
|
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();
|
||||||
|
const secretKeyValue = new context.SecretKey();
|
||||||
|
secretKeyValue.deserializeHexStr(value);
|
||||||
|
return new PrivateKey(secretKeyValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
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(): IPrivateKeyValue {
|
||||||
|
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()}`;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
import {PrivateKey} from "./privateKey";
|
||||||
|
import {PUBLIC_KEY_LENGTH} from "./constants";
|
||||||
|
import {assert} from "./helpers";
|
||||||
|
import {Signature} from "./signature";
|
||||||
|
import {EMPTY_PUBLIC_KEY} from "./helpers/utils";
|
||||||
|
import {IPublicKeyValue} from "./interface";
|
||||||
|
import {getContext} from "./context";
|
||||||
|
|
||||||
|
export class PublicKey {
|
||||||
|
private value: IPublicKeyValue;
|
||||||
|
|
||||||
|
protected constructor(value: IPublicKeyValue) {
|
||||||
|
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();
|
||||||
|
const pubkeyValue = new context.PublicKey();
|
||||||
|
pubkeyValue.deserializeHexStr(value);
|
||||||
|
return new PublicKey(pubkeyValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static fromPublicKeyType(value: IPublicKeyValue): 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(): IPublicKeyValue {
|
||||||
|
return this.value;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
import {assert} from "./helpers";
|
||||||
|
import {FP_POINT_LENGTH} from "./constants";
|
||||||
|
import {getContext} from "./context";
|
||||||
|
import {EMPTY_SIGNATURE} from "./helpers/utils";
|
||||||
|
import {ISignatureValue} from "./interface";
|
||||||
|
import {PublicKey} from "./publicKey";
|
||||||
|
|
||||||
|
export class Signature {
|
||||||
|
private value: ISignatureValue;
|
||||||
|
|
||||||
|
protected constructor(value: ISignatureValue) {
|
||||||
|
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: ISignatureValue): 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(): ISignatureValue {
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,14 +0,0 @@
|
||||||
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
68
src/types.ts
|
@ -1,68 +0,0 @@
|
||||||
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,
|
|
||||||
}
|
|
|
@ -1,4 +1,4 @@
|
||||||
import {bls} from "./index.js";
|
import bls from "./index";
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
(function (window: any) {
|
(function (window: any) {
|
|
@ -1,32 +0,0 @@
|
||||||
/* 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],
|
|
||||||
};
|
|
|
@ -1,16 +0,0 @@
|
||||||
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);
|
|
||||||
});
|
|
|
@ -1,8 +0,0 @@
|
||||||
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");
|
|
|
@ -1,34 +0,0 @@
|
||||||
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,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
);
|
|
@ -0,0 +1,42 @@
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
);
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue