Compare commits

...

12 Commits

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

* Update funding.yml

* Update to tag with meta-stable label

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

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

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

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

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

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

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

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

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

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-05-15 22:40:54 -05:00
54 changed files with 1139 additions and 116 deletions

2
.github/FUNDING.yml vendored
View File

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

25
.github/stale.yml vendored
View File

@ -12,18 +12,17 @@ onlyLabels: []
# Issues or Pull Requests with these labels will never be considered stale. Set to `[]` to disable
exemptLabels:
- "PR state: on-ice"
- "Good First Issue"
- "Status: On Ice"
- "Priority: 4 - Low"
- "Priority: 3 - Medium"
- "Priority: 2 - High"
- "Priority: 1 - Critical"
- "discussion"
- "Discussion"
- "Epic"
- "Good First Issue"
- "help wanted"
- "Epic"
- "meta-good-first-issue"
- "meta-help-wanted"
- "meta-discussion"
- "meta-pm"
- "prio-critical"
- "prio-high"
- "prio-medium"
- "prio-low"
- "status-blocked"
- "status-do-not-merge"
# Set to true to ignore issues in a project (defaults to false)
exemptProjects: false
@ -35,7 +34,7 @@ exemptMilestones: true
exemptAssignees: true
# Label to use when marking as stale
staleLabel: bot:stale
staleLabel: meta-stale
# Comment to post when marking as stale. Set to `false` to disable
markComment: >

View File

@ -22,24 +22,22 @@ 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 {SecretKey, secretKeyToPublicKey, sign, verify} from "@chainsafe/bls";
import bls from "@chainsafe/bls";
(async () => {
await init("herumi");
// class-based interface
const secretKey = bls.SecretKey.fromKeygen();
const publicKey = secretKey.toPublicKey();
const message = new Uint8Array(32);
// class-based interface
const secretKey = SecretKey.fromKeygen();
const publicKey = secretKey.toPublicKey();
const message = new Uint8Array(32);
const signature = secretKey.sign(message);
console.log("Is valid: ", signature.verify(publicKey, message));
const signature = secretKey.sign(message);
console.log("Is valid: ", signature.verify(publicKey, message));
// functional interface
const sk = secretKey.toBytes();
const pk = secretKeyToPublicKey(sk);
const sig = sign(sk, message);
console.log("Is valid: ", verify(pk, message, sig));
// functional interface
const sk = secretKey.toBytes();
const pk = bls.secretKeyToPublicKey(sk);
const sig = bls.sign(sk, message);
console.log("Is valid: ", bls.verify(pk, message, sig));
})();
```

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

@ -0,0 +1,8 @@
import { SecretKey } from "./secretKey.js";
import { PublicKey } from "./publicKey.js";
import { Signature } from "./signature.js";
import { IBls } from "../types.js";
export * from "../constants.js";
export { SecretKey, PublicKey, Signature };
export declare const bls: IBls;
export default bls;

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

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

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

@ -0,0 +1,11 @@
import * as blst from "@chainsafe/blst";
import { PointFormat, PublicKey as IPublicKey } from "../types.js";
export declare class PublicKey extends blst.PublicKey implements IPublicKey {
constructor(value: ConstructorParameters<typeof blst.PublicKey>[0]);
/** @param type Defaults to `CoordType.jacobian` */
static fromBytes(bytes: Uint8Array, type?: blst.CoordType, validate?: boolean): PublicKey;
static fromHex(hex: string): PublicKey;
static aggregate(publicKeys: PublicKey[]): PublicKey;
toBytes(format?: PointFormat): Uint8Array;
toHex(format?: PointFormat): string;
}

View File

@ -0,0 +1,37 @@
import * as blst from "@chainsafe/blst";
import { EmptyAggregateError } from "../errors.js";
import { bytesToHex, hexToBytes } from "../helpers/index.js";
import { PointFormat } from "../types.js";
export class PublicKey extends blst.PublicKey {
constructor(value) {
super(value);
}
/** @param type Defaults to `CoordType.jacobian` */
static fromBytes(bytes, type, validate) {
const pk = blst.PublicKey.fromBytes(bytes, type);
if (validate)
pk.keyValidate();
return new PublicKey(pk.value);
}
static fromHex(hex) {
return this.fromBytes(hexToBytes(hex));
}
static aggregate(publicKeys) {
if (publicKeys.length === 0) {
throw new EmptyAggregateError();
}
const pk = blst.aggregatePubkeys(publicKeys);
return new PublicKey(pk.value);
}
toBytes(format) {
if (format === PointFormat.uncompressed) {
return this.value.serialize();
}
else {
return this.value.compress();
}
}
toHex(format) {
return bytesToHex(this.toBytes(format));
}
}

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

@ -0,0 +1,15 @@
import * as blst from "@chainsafe/blst";
import { SecretKey as ISecretKey } from "../types.js";
import { PublicKey } from "./publicKey.js";
import { Signature } from "./signature.js";
export declare class SecretKey implements ISecretKey {
readonly value: blst.SecretKey;
constructor(value: blst.SecretKey);
static fromBytes(bytes: Uint8Array): SecretKey;
static fromHex(hex: string): SecretKey;
static fromKeygen(entropy?: Uint8Array): SecretKey;
sign(message: Uint8Array): Signature;
toPublicKey(): PublicKey;
toBytes(): Uint8Array;
toHex(): string;
}

View File

@ -0,0 +1,39 @@
import * as blst from "@chainsafe/blst";
import { bytesToHex, hexToBytes, isZeroUint8Array, randomBytes } from "../helpers/index.js";
import { SECRET_KEY_LENGTH } from "../constants.js";
import { PublicKey } from "./publicKey.js";
import { Signature } from "./signature.js";
import { ZeroSecretKeyError } from "../errors.js";
export class SecretKey {
constructor(value) {
this.value = value;
}
static fromBytes(bytes) {
// draft-irtf-cfrg-bls-signature-04 does not allow SK == 0
if (isZeroUint8Array(bytes)) {
throw new ZeroSecretKeyError();
}
const sk = blst.SecretKey.fromBytes(bytes);
return new SecretKey(sk);
}
static fromHex(hex) {
return this.fromBytes(hexToBytes(hex));
}
static fromKeygen(entropy) {
const sk = blst.SecretKey.fromKeygen(entropy || randomBytes(SECRET_KEY_LENGTH));
return new SecretKey(sk);
}
sign(message) {
return new Signature(this.value.sign(message).value);
}
toPublicKey() {
const pk = this.value.toPublicKey();
return new PublicKey(pk.value);
}
toBytes() {
return this.value.toBytes();
}
toHex() {
return bytesToHex(this.toBytes());
}
}

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

@ -0,0 +1,21 @@
import * as blst from "@chainsafe/blst";
import { PointFormat, Signature as ISignature } from "../types.js";
import { PublicKey } from "./publicKey.js";
export declare class Signature extends blst.Signature implements ISignature {
constructor(value: ConstructorParameters<typeof blst.Signature>[0]);
/** @param type Defaults to `CoordType.affine` */
static fromBytes(bytes: Uint8Array, type?: blst.CoordType, validate?: boolean): Signature;
static fromHex(hex: string): Signature;
static aggregate(signatures: Signature[]): Signature;
static verifyMultipleSignatures(sets: {
publicKey: PublicKey;
message: Uint8Array;
signature: Signature;
}[]): boolean;
verify(publicKey: PublicKey, message: Uint8Array): boolean;
verifyAggregate(publicKeys: PublicKey[], message: Uint8Array): boolean;
verifyMultiple(publicKeys: PublicKey[], messages: Uint8Array[]): boolean;
toBytes(format?: PointFormat): Uint8Array;
toHex(format?: PointFormat): string;
private aggregateVerify;
}

View File

@ -0,0 +1,62 @@
import * as blst from "@chainsafe/blst";
import { bytesToHex, hexToBytes } from "../helpers/index.js";
import { PointFormat } from "../types.js";
import { EmptyAggregateError, ZeroSignatureError } from "../errors.js";
export class Signature extends blst.Signature {
constructor(value) {
super(value);
}
/** @param type Defaults to `CoordType.affine` */
static fromBytes(bytes, type, validate = true) {
const sig = blst.Signature.fromBytes(bytes, type);
if (validate)
sig.sigValidate();
return new Signature(sig.value);
}
static fromHex(hex) {
return this.fromBytes(hexToBytes(hex));
}
static aggregate(signatures) {
if (signatures.length === 0) {
throw new EmptyAggregateError();
}
const agg = blst.aggregateSignatures(signatures);
return new Signature(agg.value);
}
static verifyMultipleSignatures(sets) {
return blst.verifyMultipleAggregateSignatures(sets.map((s) => ({ msg: s.message, pk: s.publicKey, sig: s.signature })));
}
verify(publicKey, message) {
// Individual infinity signatures are NOT okay. Aggregated signatures MAY be infinity
if (this.value.is_inf()) {
throw new ZeroSignatureError();
}
return blst.verify(message, publicKey, this);
}
verifyAggregate(publicKeys, message) {
return blst.fastAggregateVerify(message, publicKeys, this);
}
verifyMultiple(publicKeys, messages) {
return blst.aggregateVerify(messages, publicKeys, this);
}
toBytes(format) {
if (format === PointFormat.uncompressed) {
return this.value.serialize();
}
else {
return this.value.compress();
}
}
toHex(format) {
return bytesToHex(this.toBytes(format));
}
aggregateVerify(msgs, pks) {
// If this set is simply an infinity signature and infinity publicKey then skip verification.
// This has the effect of always declaring that this sig/publicKey combination is valid.
// for Eth2.0 specs tests
if (this.value.is_inf() && pks.length === 1 && pks[0].value.is_inf()) {
return true;
}
return blst.aggregateVerify(msgs, pks, this);
}
}

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

@ -0,0 +1,5 @@
export declare const SECRET_KEY_LENGTH = 32;
export declare const PUBLIC_KEY_LENGTH_COMPRESSED = 48;
export declare const PUBLIC_KEY_LENGTH_UNCOMPRESSED: number;
export declare const SIGNATURE_LENGTH_COMPRESSED = 96;
export declare const SIGNATURE_LENGTH_UNCOMPRESSED: number;

5
lib/constants.js Normal file
View File

@ -0,0 +1,5 @@
export const SECRET_KEY_LENGTH = 32;
export const PUBLIC_KEY_LENGTH_COMPRESSED = 48;
export const PUBLIC_KEY_LENGTH_UNCOMPRESSED = 48 * 2;
export const SIGNATURE_LENGTH_COMPRESSED = 96;
export const SIGNATURE_LENGTH_UNCOMPRESSED = 96 * 2;

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

@ -0,0 +1,25 @@
/**
* This error should not be ignored by the functional interface
* try / catch blocks, to prevent false negatives
*/
export declare class NotInitializedError extends Error {
constructor(implementation: string);
}
export declare class ZeroSecretKeyError extends Error {
constructor();
}
export declare class ZeroPublicKeyError extends Error {
constructor();
}
export declare class ZeroSignatureError extends Error {
constructor();
}
export declare class EmptyAggregateError extends Error {
constructor();
}
export declare class InvalidOrderError extends Error {
constructor();
}
export declare class InvalidLengthError extends Error {
constructor(arg: string, length: number);
}

39
lib/errors.js Normal file
View File

@ -0,0 +1,39 @@
/**
* This error should not be ignored by the functional interface
* try / catch blocks, to prevent false negatives
*/
export class NotInitializedError extends Error {
constructor(implementation) {
super(`NOT_INITIALIZED: ${implementation}`);
}
}
export class ZeroSecretKeyError extends Error {
constructor() {
super("ZERO_SECRET_KEY");
}
}
export class ZeroPublicKeyError extends Error {
constructor() {
super("ZERO_PUBLIC_KEY");
}
}
export class ZeroSignatureError extends Error {
constructor() {
super("ZERO_SIGNATURE");
}
}
export class EmptyAggregateError extends Error {
constructor() {
super("EMPTY_AGGREGATE_ARRAY");
}
}
export class InvalidOrderError extends Error {
constructor() {
super("INVALID_ORDER");
}
}
export class InvalidLengthError extends Error {
constructor(arg, length) {
super(`INVALID_LENGTH: ${arg} - ${length} bytes`);
}
}

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

@ -0,0 +1,15 @@
import { IBls } from "./types.js";
export declare function functionalInterfaceFactory({ SecretKey, PublicKey, Signature, }: Pick<IBls, "SecretKey" | "PublicKey" | "Signature">): {
sign: (secretKey: Uint8Array, message: Uint8Array) => Uint8Array;
aggregateSignatures: (signatures: Uint8Array[]) => Uint8Array;
aggregatePublicKeys: (publicKeys: Uint8Array[]) => Uint8Array;
verify: (publicKey: Uint8Array, message: Uint8Array, signature: Uint8Array) => boolean;
verifyAggregate: (publicKeys: Uint8Array[], message: Uint8Array, signature: Uint8Array) => boolean;
verifyMultiple: (publicKeys: Uint8Array[], messages: Uint8Array[], signature: Uint8Array) => boolean;
verifyMultipleSignatures: (sets: {
publicKey: Uint8Array;
message: Uint8Array;
signature: Uint8Array;
}[]) => boolean;
secretKeyToPublicKey: (secretKey: Uint8Array) => Uint8Array;
};

137
lib/functional.js Normal file
View File

@ -0,0 +1,137 @@
import { validateBytes } from "./helpers/index.js";
import { NotInitializedError } from "./errors.js";
// Returned type is enforced at each implementation's index
// eslint-disable-next-line max-len
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type,@typescript-eslint/explicit-module-boundary-types
export function functionalInterfaceFactory({ SecretKey, PublicKey, Signature, }) {
/**
* Signs given message using secret key.
* @param secretKey
* @param message
*/
function sign(secretKey, message) {
validateBytes(secretKey, "secretKey");
validateBytes(message, "message");
return SecretKey.fromBytes(secretKey).sign(message).toBytes();
}
/**
* Compines all given signature into one.
* @param signatures
*/
function aggregateSignatures(signatures) {
const agg = Signature.aggregate(signatures.map((p) => Signature.fromBytes(p)));
return agg.toBytes();
}
/**
* Combines all given public keys into single one
* @param publicKeys
*/
function aggregatePublicKeys(publicKeys) {
const agg = PublicKey.aggregate(publicKeys.map((p) => PublicKey.fromBytes(p)));
return agg.toBytes();
}
/**
* Verifies if signature is message signed with given public key.
* @param publicKey
* @param message
* @param signature
*/
function verify(publicKey, message, signature) {
validateBytes(publicKey, "publicKey");
validateBytes(message, "message");
validateBytes(signature, "signature");
try {
return Signature.fromBytes(signature).verify(PublicKey.fromBytes(publicKey), message);
}
catch (e) {
if (e instanceof NotInitializedError)
throw e;
return false;
}
}
/**
* Verifies if aggregated signature is same message signed with given public keys.
* @param publicKeys
* @param message
* @param signature
*/
function verifyAggregate(publicKeys, message, signature) {
validateBytes(publicKeys, "publicKey");
validateBytes(message, "message");
validateBytes(signature, "signature");
try {
return Signature.fromBytes(signature).verifyAggregate(publicKeys.map((publicKey) => PublicKey.fromBytes(publicKey)), message);
}
catch (e) {
if (e instanceof NotInitializedError)
throw e;
return false;
}
}
/**
* Verifies if signature is list of message signed with corresponding public key.
* @param publicKeys
* @param messages
* @param signature
* @param fast Check if all messages are different
*/
function verifyMultiple(publicKeys, messages, signature) {
validateBytes(publicKeys, "publicKey");
validateBytes(messages, "message");
validateBytes(signature, "signature");
if (publicKeys.length === 0 || publicKeys.length != messages.length) {
return false;
}
try {
return Signature.fromBytes(signature).verifyMultiple(publicKeys.map((publicKey) => PublicKey.fromBytes(publicKey)), messages.map((msg) => msg));
}
catch (e) {
if (e instanceof NotInitializedError)
throw e;
return false;
}
}
/**
* Verifies multiple signatures at once returning true if all valid or false
* if at least one is not. Optimization useful when knowing which signature is
* wrong is not relevant, i.e. verifying an entire Eth2.0 block.
*
* This method provides a safe way to do so by multiplying each signature by
* a random number so an attacker cannot craft a malicious signature that won't
* verify on its own but will if it's added to a specific predictable signature
* https://ethresear.ch/t/fast-verification-of-multiple-bls-signatures/5407
*/
function verifyMultipleSignatures(sets) {
if (!sets)
throw Error("sets is null or undefined");
try {
return Signature.verifyMultipleSignatures(sets.map((s) => ({
publicKey: PublicKey.fromBytes(s.publicKey),
message: s.message,
signature: Signature.fromBytes(s.signature),
})));
}
catch (e) {
if (e instanceof NotInitializedError)
throw e;
return false;
}
}
/**
* Computes a public key from a secret key
*/
function secretKeyToPublicKey(secretKey) {
validateBytes(secretKey, "secretKey");
return SecretKey.fromBytes(secretKey).toPublicKey().toBytes();
}
return {
sign,
aggregateSignatures,
aggregatePublicKeys,
verify,
verifyAggregate,
verifyMultiple,
verifyMultipleSignatures,
secretKeyToPublicKey,
};
}

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

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

17
lib/getImplementation.js Normal file
View File

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

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

@ -0,0 +1,10 @@
/**
* Browser compatible fromHex method
* From https://github.com/herumi/bls-eth-wasm/blob/04eedb77aa96e66b4f65a0ab477228adf8090c36/src/bls.js#L62
*/
export declare function hexToBytes(hex: string): Uint8Array;
/**
* Browser compatible toHex method
* From https://github.com/herumi/bls-eth-wasm/blob/04eedb77aa96e66b4f65a0ab477228adf8090c36/src/bls.js#L50
*/
export declare function bytesToHex(bytes: Uint8Array): string;

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

@ -0,0 +1,30 @@
/**
* Browser compatible fromHex method
* From https://github.com/herumi/bls-eth-wasm/blob/04eedb77aa96e66b4f65a0ab477228adf8090c36/src/bls.js#L62
*/
export function hexToBytes(hex) {
if (hex.startsWith("0x")) {
hex = hex.slice(2);
}
if (hex.length & 1) {
throw Error("hexToBytes:length must be even " + hex.length);
}
const n = hex.length / 2;
const a = new Uint8Array(n);
for (let i = 0; i < n; i++) {
a[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
}
return a;
}
/**
* Browser compatible toHex method
* From https://github.com/herumi/bls-eth-wasm/blob/04eedb77aa96e66b4f65a0ab477228adf8090c36/src/bls.js#L50
*/
export function bytesToHex(bytes) {
let s = "";
const n = bytes.length;
for (let i = 0; i < n; i++) {
s += ("0" + bytes[i].toString(16)).slice(-2);
}
return "0x" + s;
}

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

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

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

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

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

@ -0,0 +1,8 @@
import randomBytes from "randombytes";
export { randomBytes };
/**
* Validate bytes to prevent confusing WASM errors downstream if bytes is null
*/
export declare function validateBytes(bytes: Uint8Array | Uint8Array[] | null, argName?: string): asserts bytes is NonNullable<typeof bytes>;
export declare function isZeroUint8Array(bytes: Uint8Array): boolean;
export declare function concatUint8Arrays(bytesArr: Uint8Array[]): Uint8Array;

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

@ -0,0 +1,26 @@
import randomBytes from "randombytes";
// Single import to ease changing this lib if necessary
export { randomBytes };
/**
* Validate bytes to prevent confusing WASM errors downstream if bytes is null
*/
export function validateBytes(bytes, argName) {
for (const item of Array.isArray(bytes) ? bytes : [bytes]) {
if (item == null) {
throw Error(`${argName || "bytes"} is null or undefined`);
}
}
}
export function isZeroUint8Array(bytes) {
return bytes.every((byte) => byte === 0);
}
export function concatUint8Arrays(bytesArr) {
const totalLen = bytesArr.reduce((total, bytes) => total + bytes.length, 0);
const merged = new Uint8Array(totalLen);
let mergedLen = 0;
for (const bytes of bytesArr) {
merged.set(bytes, mergedLen);
mergedLen += bytes.length;
}
return merged;
}

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

@ -0,0 +1,12 @@
import bls from "bls-eth-wasm";
declare type Bls = typeof bls;
declare global {
interface Window {
msCrypto: typeof window["crypto"];
}
}
export declare function setupBls(): Promise<void>;
export declare function init(): Promise<void>;
export declare function destroy(): void;
export declare function getContext(): Bls;
export {};

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

@ -0,0 +1,35 @@
/* eslint-disable require-atomic-updates */
import bls from "bls-eth-wasm";
import { NotInitializedError } from "../errors.js";
let blsGlobal = null;
let blsGlobalPromise = null;
export async function setupBls() {
if (!blsGlobal) {
await bls.init(bls.BLS12_381);
// Patch to fix multiVerify() calls on a browser with polyfilled NodeJS crypto
if (typeof window === "object") {
const crypto = window.crypto || window.msCrypto;
// getRandomValues is not typed in `bls-eth-wasm` because it's not meant to be exposed
// @ts-ignore
bls.getRandomValues = (x) => crypto.getRandomValues(x);
}
blsGlobal = bls;
}
}
// Cache a promise for Bls instead of Bls to make sure it is initialized only once
export async function init() {
if (!blsGlobalPromise) {
blsGlobalPromise = setupBls();
}
return blsGlobalPromise;
}
export function destroy() {
blsGlobal = null;
blsGlobalPromise = null;
}
export function getContext() {
if (!blsGlobal) {
throw new NotInitializedError("herumi");
}
return blsGlobal;
}

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

@ -0,0 +1,9 @@
import { SecretKey } from "./secretKey.js";
import { PublicKey } from "./publicKey.js";
import { Signature } from "./signature.js";
import { init, destroy } from "./context.js";
import { IBls } from "../types.js";
export * from "../constants.js";
export { SecretKey, PublicKey, Signature, init, destroy };
export declare const bls: () => Promise<IBls>;
export default bls;

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

@ -0,0 +1,18 @@
import { SecretKey } from "./secretKey.js";
import { PublicKey } from "./publicKey.js";
import { Signature } from "./signature.js";
import { init, destroy } from "./context.js";
import { functionalInterfaceFactory } from "../functional.js";
export * from "../constants.js";
export { SecretKey, PublicKey, Signature, init, destroy };
export const bls = async () => {
await init();
return {
implementation: "herumi",
SecretKey,
PublicKey,
Signature,
...functionalInterfaceFactory({ SecretKey, PublicKey, Signature }),
};
};
export default bls;

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

@ -0,0 +1,11 @@
import type { PublicKeyType } from "bls-eth-wasm";
import { PointFormat, PublicKey as IPublicKey } from "../types.js";
export declare class PublicKey implements IPublicKey {
readonly value: PublicKeyType;
constructor(value: PublicKeyType);
static fromBytes(bytes: Uint8Array): PublicKey;
static fromHex(hex: string): PublicKey;
static aggregate(publicKeys: PublicKey[]): PublicKey;
toBytes(format?: PointFormat): Uint8Array;
toHex(format?: PointFormat): string;
}

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

@ -0,0 +1,53 @@
import { getContext } from "./context.js";
import { bytesToHex, hexToBytes, isZeroUint8Array } from "../helpers/index.js";
import { PointFormat } from "../types.js";
import { EmptyAggregateError, InvalidLengthError, ZeroPublicKeyError } from "../errors.js";
import { PUBLIC_KEY_LENGTH_COMPRESSED, PUBLIC_KEY_LENGTH_UNCOMPRESSED } from "../constants.js";
export class PublicKey {
constructor(value) {
if (value.isZero()) {
throw new ZeroPublicKeyError();
}
this.value = value;
}
static fromBytes(bytes) {
const context = getContext();
const publicKey = new context.PublicKey();
if (!isZeroUint8Array(bytes)) {
if (bytes.length === PUBLIC_KEY_LENGTH_COMPRESSED) {
publicKey.deserialize(bytes);
}
else if (bytes.length === PUBLIC_KEY_LENGTH_UNCOMPRESSED) {
publicKey.deserializeUncompressed(bytes);
}
else {
throw new InvalidLengthError("PublicKey", bytes.length);
}
}
return new PublicKey(publicKey);
}
static fromHex(hex) {
return this.fromBytes(hexToBytes(hex));
}
static aggregate(publicKeys) {
if (publicKeys.length === 0) {
throw new EmptyAggregateError();
}
const agg = new PublicKey(publicKeys[0].value.clone());
for (const pk of publicKeys.slice(1)) {
agg.value.add(pk.value);
}
return agg;
}
toBytes(format) {
if (format === PointFormat.uncompressed) {
return this.value.serializeUncompressed();
}
else {
return this.value.serialize();
}
}
toHex(format) {
return bytesToHex(this.toBytes(format));
}
}

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

@ -0,0 +1,15 @@
import type { SecretKeyType } from "bls-eth-wasm";
import { PublicKey } from "./publicKey.js";
import { Signature } from "./signature.js";
import { SecretKey as ISecretKey } from "../types.js";
export declare class SecretKey implements ISecretKey {
readonly value: SecretKeyType;
constructor(value: SecretKeyType);
static fromBytes(bytes: Uint8Array): SecretKey;
static fromHex(hex: string): SecretKey;
static fromKeygen(entropy?: Uint8Array): SecretKey;
sign(message: Uint8Array): Signature;
toPublicKey(): PublicKey;
toBytes(): Uint8Array;
toHex(): string;
}

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

@ -0,0 +1,43 @@
import { generateRandomSecretKey } from "@chainsafe/bls-keygen";
import { SECRET_KEY_LENGTH } from "../constants.js";
import { getContext } from "./context.js";
import { PublicKey } from "./publicKey.js";
import { Signature } from "./signature.js";
import { bytesToHex, hexToBytes } from "../helpers/index.js";
import { InvalidLengthError, ZeroSecretKeyError } from "../errors.js";
export class SecretKey {
constructor(value) {
if (value.isZero()) {
throw new ZeroSecretKeyError();
}
this.value = value;
}
static fromBytes(bytes) {
if (bytes.length !== SECRET_KEY_LENGTH) {
throw new InvalidLengthError("SecretKey", SECRET_KEY_LENGTH);
}
const context = getContext();
const secretKey = new context.SecretKey();
secretKey.deserialize(bytes);
return new SecretKey(secretKey);
}
static fromHex(hex) {
return this.fromBytes(hexToBytes(hex));
}
static fromKeygen(entropy) {
const sk = generateRandomSecretKey(entropy);
return this.fromBytes(sk);
}
sign(message) {
return new Signature(this.value.sign(message));
}
toPublicKey() {
return new PublicKey(this.value.getPublicKey());
}
toBytes() {
return this.value.serialize();
}
toHex() {
return bytesToHex(this.toBytes());
}
}

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

@ -0,0 +1,24 @@
import type { SignatureType } from "bls-eth-wasm";
import { PublicKey } from "./publicKey.js";
import { PointFormat, Signature as ISignature, CoordType } from "../types.js";
export declare class Signature implements ISignature {
readonly value: SignatureType;
constructor(value: SignatureType);
/**
* @param type Does not affect `herumi` implementation, always de-serializes to `jacobian`
* @param validate With `herumi` implementation signature validation is always on regardless of this flag.
*/
static fromBytes(bytes: Uint8Array, _type?: CoordType, _validate?: boolean): Signature;
static fromHex(hex: string): Signature;
static aggregate(signatures: Signature[]): Signature;
static verifyMultipleSignatures(sets: {
publicKey: PublicKey;
message: Uint8Array;
signature: Signature;
}[]): boolean;
verify(publicKey: PublicKey, message: Uint8Array): boolean;
verifyAggregate(publicKeys: PublicKey[], message: Uint8Array): boolean;
verifyMultiple(publicKeys: PublicKey[], messages: Uint8Array[]): boolean;
toBytes(format?: PointFormat): Uint8Array;
toHex(format?: PointFormat): string;
}

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

@ -0,0 +1,70 @@
import { getContext } from "./context.js";
import { bytesToHex, concatUint8Arrays, hexToBytes, isZeroUint8Array } from "../helpers/index.js";
import { PointFormat } from "../types.js";
import { EmptyAggregateError, InvalidLengthError, InvalidOrderError } from "../errors.js";
import { SIGNATURE_LENGTH_COMPRESSED, SIGNATURE_LENGTH_UNCOMPRESSED } from "../constants.js";
export class Signature {
constructor(value) {
if (!value.isValidOrder()) {
throw new InvalidOrderError();
}
this.value = value;
}
/**
* @param type Does not affect `herumi` implementation, always de-serializes to `jacobian`
* @param validate With `herumi` implementation signature validation is always on regardless of this flag.
*/
static fromBytes(bytes, _type, _validate = true) {
const context = getContext();
const signature = new context.Signature();
if (!isZeroUint8Array(bytes)) {
if (bytes.length === SIGNATURE_LENGTH_COMPRESSED) {
signature.deserialize(bytes);
}
else if (bytes.length === SIGNATURE_LENGTH_UNCOMPRESSED) {
signature.deserializeUncompressed(bytes);
}
else {
throw new InvalidLengthError("Signature", bytes.length);
}
signature.deserialize(bytes);
}
return new Signature(signature);
}
static fromHex(hex) {
return this.fromBytes(hexToBytes(hex));
}
static aggregate(signatures) {
if (signatures.length === 0) {
throw new EmptyAggregateError();
}
const context = getContext();
const signature = new context.Signature();
signature.aggregate(signatures.map((sig) => sig.value));
return new Signature(signature);
}
static verifyMultipleSignatures(sets) {
const context = getContext();
return context.multiVerify(sets.map((s) => s.publicKey.value), sets.map((s) => s.signature.value), sets.map((s) => s.message));
}
verify(publicKey, message) {
return publicKey.value.verify(this.value, message);
}
verifyAggregate(publicKeys, message) {
return this.value.fastAggregateVerify(publicKeys.map((key) => key.value), message);
}
verifyMultiple(publicKeys, messages) {
return this.value.aggregateVerifyNoCheck(publicKeys.map((key) => key.value), concatUint8Arrays(messages));
}
toBytes(format) {
if (format === PointFormat.uncompressed) {
return this.value.serializeUncompressed();
}
else {
return this.value.serialize();
}
}
toHex(format) {
return bytesToHex(this.toBytes(format));
}
}

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

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

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

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

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

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

14
lib/index.js Normal file
View File

@ -0,0 +1,14 @@
import { getImplementation } from "./getImplementation.js";
// Thanks https://github.com/iliakan/detect-node/blob/master/index.esm.js
const isNode = Object.prototype.toString.call(typeof process !== "undefined" ? process : 0) === "[object process]";
export const bls = async () => {
let bls;
try {
bls = await getImplementation(isNode ? "blst-native" : "herumi");
}
catch (e) {
bls = await getImplementation("herumi");
}
return bls;
};
export default bls;

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

@ -0,0 +1,4 @@
import type { IBls, Implementation } from "./types.js";
declare const bls: IBls;
export default bls;
export declare function init(impl: Implementation): Promise<void>;

11
lib/switchable.js Normal file
View File

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

File diff suppressed because one or more lines are too long

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

@ -0,0 +1,66 @@
export interface IBls {
implementation: Implementation;
SecretKey: typeof SecretKey;
PublicKey: typeof PublicKey;
Signature: typeof Signature;
sign(secretKey: Uint8Array, message: Uint8Array): Uint8Array;
aggregatePublicKeys(publicKeys: Uint8Array[]): Uint8Array;
aggregateSignatures(signatures: Uint8Array[]): Uint8Array;
verify(publicKey: Uint8Array, message: Uint8Array, signature: Uint8Array): boolean;
verifyAggregate(publicKeys: Uint8Array[], message: Uint8Array, signature: Uint8Array): boolean;
verifyMultiple(publicKeys: Uint8Array[], messages: Uint8Array[], signature: Uint8Array): boolean;
verifyMultipleSignatures(sets: {
publicKey: Uint8Array;
message: Uint8Array;
signature: Uint8Array;
}[]): boolean;
secretKeyToPublicKey(secretKey: Uint8Array): Uint8Array;
}
export declare class SecretKey {
private constructor();
static fromBytes(bytes: Uint8Array): SecretKey;
static fromHex(hex: string): SecretKey;
static fromKeygen(entropy?: Uint8Array): SecretKey;
sign(message: Uint8Array): Signature;
toPublicKey(): PublicKey;
toBytes(): Uint8Array;
toHex(): string;
}
export declare class PublicKey {
private constructor();
/** @param type Only for impl `blst-native`. Defaults to `CoordType.jacobian` */
static fromBytes(bytes: Uint8Array, type?: CoordType, validate?: boolean): PublicKey;
static fromHex(hex: string): PublicKey;
static aggregate(publicKeys: PublicKey[]): PublicKey;
/** @param format Defaults to `PointFormat.compressed` */
toBytes(format?: PointFormat): Uint8Array;
toHex(format?: PointFormat): string;
}
export declare class Signature {
private constructor();
/** @param type Only for impl `blst-native`. Defaults to `CoordType.affine`
* @param validate When using `herumi` implementation, signature validation is always on regardless of this flag. */
static fromBytes(bytes: Uint8Array, type?: CoordType, validate?: boolean): Signature;
static fromHex(hex: string): Signature;
static aggregate(signatures: Signature[]): Signature;
static verifyMultipleSignatures(sets: {
publicKey: PublicKey;
message: Uint8Array;
signature: Signature;
}[]): boolean;
verify(publicKey: PublicKey, message: Uint8Array): boolean;
verifyAggregate(publicKeys: PublicKey[], message: Uint8Array): boolean;
verifyMultiple(publicKeys: PublicKey[], messages: Uint8Array[]): boolean;
/** @param format Defaults to `PointFormat.compressed` */
toBytes(format?: PointFormat): Uint8Array;
toHex(format?: PointFormat): string;
}
export declare type Implementation = "herumi" | "blst-native";
export declare enum PointFormat {
compressed = "compressed",
uncompressed = "uncompressed"
}
export declare enum CoordType {
affine = 0,
jacobian = 1
}

10
lib/types.js Normal file
View File

@ -0,0 +1,10 @@
export var PointFormat;
(function (PointFormat) {
PointFormat["compressed"] = "compressed";
PointFormat["uncompressed"] = "uncompressed";
})(PointFormat || (PointFormat = {}));
export var CoordType;
(function (CoordType) {
CoordType[CoordType["affine"] = 0] = "affine";
CoordType[CoordType["jacobian"] = 1] = "jacobian";
})(CoordType || (CoordType = {}));

View File

@ -88,7 +88,7 @@
"@chainsafe/lodestar-spec-test-util": "^0.18.0",
"@chainsafe/threads": "^1.9.0",
"@types/chai": "^4.2.9",
"@types/mocha": "^8.0.4",
"@types/mocha": "^10.0.0",
"@types/randombytes": "^2.0.0",
"@typescript-eslint/eslint-plugin": "^4.31.1",
"@typescript-eslint/parser": "^4.31.1",
@ -104,13 +104,13 @@
"karma-mocha": "^2.0.1",
"karma-spec-reporter": "^0.0.32",
"karma-webpack": "^5.0.0",
"mocha": "^9.2.2",
"mocha": "^10.0.0",
"nyc": "^15.0.0",
"prettier": "^2.1.2",
"resolve-typescript-plugin": "^1.2.0",
"ts-loader": "^9.2.8",
"ts-node": "^10.7.0",
"typescript": "^4.6.3",
"ts-loader": "^9.3.1",
"ts-node": "^10.9.1",
"typescript": "4.7.4",
"webpack": "^5.72.0",
"webpack-cli": "^4.9.2"
},

View File

@ -6,7 +6,7 @@ const isNode = Object.prototype.toString.call(typeof process !== "undefined" ? p
export async function getImplementation(impl: Implementation = "herumi"): Promise<IBls> {
switch (impl) {
case "herumi": {
return (await import("./herumi/index.js")).bls;
return await (await import("./herumi/index.js")).bls();
}
case "blst-native":

View File

@ -5,19 +5,19 @@ import {init, destroy} from "./context.js";
import {IBls} from "../types.js";
import {functionalInterfaceFactory} from "../functional.js";
await init();
export * from "../constants.js";
export {SecretKey, PublicKey, Signature, init, destroy};
export const bls: IBls = {
implementation: "herumi",
SecretKey,
PublicKey,
Signature,
export const bls = async (): Promise<IBls> => {
await init();
return {
implementation: "herumi",
SecretKey,
PublicKey,
Signature,
...functionalInterfaceFactory({SecretKey, PublicKey, Signature}),
...functionalInterfaceFactory({SecretKey, PublicKey, Signature}),
};
};
export default bls;

View File

@ -4,11 +4,16 @@ 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]";
let bls: IBls;
try {
bls = await getImplementation(isNode ? "blst-native" : "herumi");
} catch (e) {
bls = await getImplementation("herumi");
}
export const bls = async (): Promise<IBls> => {
let bls: IBls;
try {
bls = await getImplementation(isNode ? "blst-native" : "herumi");
} catch (e) {
bls = await getImplementation("herumi");
}
return bls;
};
export default bls;

View File

@ -14,7 +14,7 @@ describe("helpers / hex", () => {
it(`${id} hexToBytes`, () => {
const expectedBytes = hexToBytesNode(hex);
const bytes = hexToBytes(hex);
expect(expectedBytes.equals(bytes)).to.be.true;
expect(expectedBytes.equals(bytes)).equals(true);
});
it(`${id} bytesToHex`, () => {

View File

@ -20,7 +20,7 @@ describe("types named exports", async () => {
const msg = new Uint8Array(32);
const sig = sk.sign(msg);
const pk = sk.toPublicKey();
expect(verifyHelper(pk, sig, msg)).to.be.true;
expect(verifyHelper(pk, sig, msg)).equals(true);
});
it("Make sure exported classes are compatible with interface", () => {

View File

@ -28,7 +28,7 @@ export function runIndexTests(bls: IBls): void {
} catch {
/* eslint-disable no-empty */
}
expect(sig === undefined).to.be.true;
expect(sig === undefined).equals(true);
});
});
@ -37,31 +37,38 @@ export function runIndexTests(bls: IBls): void {
const {pk, msg, sig} = getRandomData();
const pkHex = pk.toHex();
const isValid = bls.verify(pk.toBytes(), msg, sig.toBytes());
expect(isValid, "fail verify").to.be.true;
expect(isValid, "fail verify").equals(true);
// Make sure to not modify original pubkey when verifying
expect(pk.toHex()).to.be.equal(pkHex, "pubkey modified when verifying");
expect(pk.toHex()).equals(pkHex, "pubkey modified when verifying");
});
it("should fail verify empty signature", () => {
const {pk, msg} = getRandomData();
const emptySig = Buffer.alloc(96);
const isValid = bls.verify(pk.toBytes(), msg, emptySig);
expect(isValid).to.be.false;
expect(isValid).equals(false);
});
it("should fail verify signature of different message", () => {
const {pk, sig} = getRandomData();
const msg2 = randomMessage();
const isValid = bls.verify(pk.toBytes(), msg2, sig.toBytes());
expect(isValid).to.be.false;
expect(isValid).equals(false);
});
it("should fail verify signature signed by different key", () => {
const {msg, sig} = getRandomData();
const {pk: pk2} = getRandomData();
const isValid = bls.verify(pk2.toBytes(), msg, sig.toBytes());
expect(isValid).to.be.false;
expect(isValid).equals(false);
});
it("should fail verify empty message", () => {
const emptyMsg = new Uint8Array(0);
const {pk, sig} = getRandomData();
const isValid = sig.verify(pk, emptyMsg);
expect(isValid).equals(false);
});
});
@ -80,8 +87,8 @@ export function runIndexTests(bls: IBls): void {
const aggSig = bls.aggregateSignatures(sigs.map((sig) => sig.toBytes()));
expect(bls.verifyMultiple(aggPubkeys, msgs, aggSig), "should be valid").to.be.true;
expect(bls.verifyMultiple(aggPubkeys.reverse(), msgs, aggSig), "should fail - swaped pubkeys").to.be.false;
expect(bls.verifyMultiple(aggPubkeys, msgs, aggSig), "should be valid").equals(true);
expect(bls.verifyMultiple(aggPubkeys.reverse(), msgs, aggSig), "should fail - swaped pubkeys").equals(false);
});
it("should verify aggregated signatures - same message", () => {
@ -98,7 +105,7 @@ export function runIndexTests(bls: IBls): void {
getN(4, () => msg), // Same message n times
aggregateSignature
);
expect(isValid).to.be.true;
expect(isValid).equals(true);
});
it("should fail to verify aggregated signatures - no public keys", () => {
@ -107,7 +114,19 @@ export function runIndexTests(bls: IBls): void {
const msg2 = randomMessage();
const isValid = bls.verifyMultiple([], [msg2, msg1], sig);
expect(isValid).to.be.false;
expect(isValid).equals(false);
});
it("should fail verify empty message", () => {
const sks = getN(2, () => bls.SecretKey.fromKeygen());
const msgs = getN(2, () => randomMessage());
const pks = sks.map((sk) => sk.toPublicKey());
const sigs = [sks[0].sign(msgs[0]), sks[1].sign(msgs[1])];
const aggSig = bls.Signature.aggregate(sigs);
const emptyMsgs = msgs.map(() => new Uint8Array(0));
const isValid = aggSig.verifyMultiple(pks, emptyMsgs);
expect(isValid).equals(false);
});
});
@ -167,6 +186,22 @@ export function runIndexTests(bls: IBls): void {
"Malicous signature should not validate with bls.verifyMultipleSignatures"
);
});
it("should fail verify empty message", () => {
const n = 4;
const sets = getN(n, () => {
const sk = bls.SecretKey.fromKeygen();
const publicKey = sk.toPublicKey();
const message = randomMessage();
const signature = sk.sign(message);
return {publicKey, message, signature};
});
const setsWithEmptyMsgs = sets.map((set) => ({...set, message: new Uint8Array(0)}));
const isValid = bls.Signature.verifyMultipleSignatures(setsWithEmptyMsgs);
expect(isValid).equals(false);
});
});
describe("serialize deserialize", () => {

View File

@ -7,11 +7,11 @@ export function runPublicKeyTests(bls: IBls): void {
"0xb6f21199594b56d77670564bf422cb331d5281ca2c1f9a45588a56881d8287ef8619efa6456d6cd2ef61306aa5b21311";
it("should export public key to hex string", () => {
expect(bls.PublicKey.fromHex(publicKey).toHex()).to.be.equal(publicKey);
expect(bls.PublicKey.fromHex(publicKey).toHex()).equals(publicKey);
});
it("should export public key to hex string from non-prefixed hex", () => {
expect(bls.PublicKey.fromHex(publicKey).toHex()).to.be.equal(publicKey);
expect(bls.PublicKey.fromHex(publicKey).toHex()).equals(publicKey);
});
it("from secret key", () => {

View File

@ -12,11 +12,11 @@ export function runSecretKeyTests(bls: IBls): void {
const secretKey = "0x07656fd676da43883d163f49566c72b9cbf0a5a294f26808c807700732456da7";
it("should export secret key to hex string", () => {
expect(bls.SecretKey.fromHex(secretKey).toHex()).to.be.equal(secretKey);
expect(bls.SecretKey.fromHex(secretKey).toHex()).equals(secretKey);
});
it("should export secret key to hex string from non-prefixed hex", () => {
expect(bls.SecretKey.fromHex(secretKey).toHex()).to.be.equal(secretKey);
expect(bls.SecretKey.fromHex(secretKey).toHex()).equals(secretKey);
});
it("should not accept too short secret key", () => {

152
yarn.lock
View File

@ -283,17 +283,12 @@
resolved "https://registry.yarnpkg.com/@colors/colors/-/colors-1.5.0.tgz#bb504579c1cae923e6576a4f5da43d25f97bdbd9"
integrity sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==
"@cspotcode/source-map-consumer@0.8.0":
version "0.8.0"
resolved "https://registry.yarnpkg.com/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz#33bf4b7b39c178821606f669bbc447a6a629786b"
integrity sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==
"@cspotcode/source-map-support@0.7.0":
version "0.7.0"
resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz#4789840aa859e46d2f3173727ab707c66bf344f5"
integrity sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==
"@cspotcode/source-map-support@^0.8.0":
version "0.8.1"
resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1"
integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==
dependencies:
"@cspotcode/source-map-consumer" "0.8.0"
"@jridgewell/trace-mapping" "0.3.9"
"@dabh/diagnostics@^2.0.2":
version "2.0.2"
@ -358,6 +353,24 @@
resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.2.tgz#26520bf09abe4a5644cd5414e37125a8954241dd"
integrity sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw==
"@jridgewell/resolve-uri@^3.0.3":
version "3.1.0"
resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78"
integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==
"@jridgewell/sourcemap-codec@^1.4.10":
version "1.4.14"
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24"
integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==
"@jridgewell/trace-mapping@0.3.9":
version "0.3.9"
resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9"
integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==
dependencies:
"@jridgewell/resolve-uri" "^3.0.3"
"@jridgewell/sourcemap-codec" "^1.4.10"
"@noble/hashes@^1.0.0", "@noble/hashes@~1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.0.0.tgz#d5e38bfbdaba174805a4e649f13be9a9ed3351ae"
@ -489,10 +502,10 @@
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3"
integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==
"@types/mocha@^8.0.4":
version "8.0.4"
resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-8.0.4.tgz#b840c2dce46bacf286e237bfb59a29e843399148"
integrity sha512-M4BwiTJjHmLq6kjON7ZoI2JMlBvpY3BYSdiP6s/qCT3jb1s9/DeJF0JELpAxiVSIxXDzfNKe+r7yedMIoLbknQ==
"@types/mocha@^10.0.0":
version "10.0.0"
resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-10.0.0.tgz#3d9018c575f0e3f7386c1de80ee66cc21fbb7a52"
integrity sha512-rADY+HtTOA52l9VZWtgQfn4p+UDVM2eDVkMZT1I6syp0YKxW2F9v+0pbRZLsvskhQv/vMb6ZfCay81GHbz5SHg==
"@types/node@*":
version "14.14.10"
@ -1075,6 +1088,13 @@ brace-expansion@^1.1.7:
balanced-match "^1.0.0"
concat-map "0.0.1"
brace-expansion@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae"
integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==
dependencies:
balanced-match "^1.0.0"
braces@^3.0.1, braces@^3.0.2, braces@~3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
@ -1519,7 +1539,7 @@ debug@2.6.9, debug@^2.6.9:
dependencies:
ms "2.0.0"
debug@4, debug@^4.2.0, debug@^4.3.1, debug@^4.3.3, debug@^4.3.4, debug@~4.3.1, debug@~4.3.2:
debug@4, debug@4.3.4, debug@^4.2.0, debug@^4.3.1, debug@^4.3.3, debug@^4.3.4, debug@~4.3.1, debug@~4.3.2:
version "4.3.4"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
@ -3140,6 +3160,13 @@ minimatch@4.2.1:
dependencies:
brace-expansion "^1.1.7"
minimatch@5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.0.1.tgz#fb9022f7528125187c92bd9e9b6366be1cf3415b"
integrity sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==
dependencies:
brace-expansion "^2.0.1"
minimatch@^3.0.4:
version "3.0.4"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
@ -3147,12 +3174,7 @@ minimatch@^3.0.4:
dependencies:
brace-expansion "^1.1.7"
minimist@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=
minimist@^1.2.3, minimist@^1.2.6:
minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.6:
version "1.2.6"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
@ -3223,6 +3245,34 @@ mkdirp@^1.0.3, mkdirp@^1.0.4:
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
mocha@^10.0.0:
version "10.0.0"
resolved "https://registry.yarnpkg.com/mocha/-/mocha-10.0.0.tgz#205447d8993ec755335c4b13deba3d3a13c4def9"
integrity sha512-0Wl+elVUD43Y0BqPZBzZt8Tnkw9CMUdNYnUsTfOM1vuhJVZL+kiesFYsqwBkEEuEixaiPe5ZQdqDgX2jddhmoA==
dependencies:
"@ungap/promise-all-settled" "1.1.2"
ansi-colors "4.1.1"
browser-stdout "1.3.1"
chokidar "3.5.3"
debug "4.3.4"
diff "5.0.0"
escape-string-regexp "4.0.0"
find-up "5.0.0"
glob "7.2.0"
he "1.2.0"
js-yaml "4.1.0"
log-symbols "4.1.0"
minimatch "5.0.1"
ms "2.1.3"
nanoid "3.3.3"
serialize-javascript "6.0.0"
strip-json-comments "3.1.1"
supports-color "8.1.1"
workerpool "6.2.1"
yargs "16.2.0"
yargs-parser "20.2.4"
yargs-unparser "2.0.0"
mocha@^8.1.1, mocha@^9.2.2:
version "9.2.2"
resolved "https://registry.yarnpkg.com/mocha/-/mocha-9.2.2.tgz#d70db46bdb93ca57402c809333e5a84977a88fb9"
@ -3278,6 +3328,11 @@ nanoid@3.3.1:
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.1.tgz#6347a18cac88af88f58af0b3594b723d5e99bb35"
integrity sha512-n6Vs/3KGyxPQd6uO0eH4Bv0ojGSUvuLlIHtC3Y0kEO23YRge8H9x1GCzLn28YX0H66pMkxuaeESFq4tKISKwdw==
nanoid@3.3.3:
version "3.3.3"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.3.tgz#fd8e8b7aa761fe807dba2d1b98fb7241bb724a25"
integrity sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==
native-abort-controller@0.0.3:
version "0.0.3"
resolved "https://registry.yarnpkg.com/native-abort-controller/-/native-abort-controller-0.0.3.tgz#4c528a6c9c7d3eafefdc2c196ac9deb1a5edf2f8"
@ -3635,12 +3690,7 @@ path-key@^3.0.0, path-key@^3.1.0:
resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375"
integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==
path-parse@^1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c"
integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==
path-parse@^1.0.7:
path-parse@^1.0.6, path-parse@^1.0.7:
version "1.0.7"
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
@ -3657,12 +3707,7 @@ path-type@^4.0.0:
resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==
pathval@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.0.tgz#b942e6d4bde653005ef6b71361def8727d0645e0"
integrity sha1-uULm1L3mUwBe9rcTYd74cn0GReA=
pathval@^1.1.1:
pathval@^1.1.0, pathval@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.1.tgz#8534e77a77ce7ac5a2512ea21e0fdb8fcf6c3d8d"
integrity sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==
@ -4462,22 +4507,22 @@ triple-beam@^1.2.0, triple-beam@^1.3.0:
resolved "https://registry.yarnpkg.com/triple-beam/-/triple-beam-1.3.0.tgz#a595214c7298db8339eeeee083e4d10bd8cb8dd9"
integrity sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==
ts-loader@^9.2.8:
version "9.2.8"
resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-9.2.8.tgz#e89aa32fa829c5cad0a1d023d6b3adecd51d5a48"
integrity sha512-gxSak7IHUuRtwKf3FIPSW1VpZcqF9+MBrHOvBp9cjHh+525SjtCIJKVGjRKIAfxBwDGDGCFF00rTfzB1quxdSw==
ts-loader@^9.3.1:
version "9.4.1"
resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-9.4.1.tgz#b6f3d82db0eac5a8295994f8cb5e4940ff6b1060"
integrity sha512-384TYAqGs70rn9F0VBnh6BPTfhga7yFNdC5gXbQpDrBj9/KsT4iRkGqKXhziofHOlE2j6YEaiTYVGKKvPhGWvw==
dependencies:
chalk "^4.1.0"
enhanced-resolve "^5.0.0"
micromatch "^4.0.0"
semver "^7.3.4"
ts-node@^10.7.0:
version "10.7.0"
resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.7.0.tgz#35d503d0fab3e2baa672a0e94f4b40653c2463f5"
integrity sha512-TbIGS4xgJoX2i3do417KSaep1uRAW/Lu+WAL2doDHC0D6ummjirVOXU5/7aiZotbQ5p1Zp9tP7U6cYhA0O7M8A==
ts-node@^10.9.1:
version "10.9.1"
resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.1.tgz#e73de9102958af9e1f0b168a6ff320e25adcff4b"
integrity sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==
dependencies:
"@cspotcode/source-map-support" "0.7.0"
"@cspotcode/source-map-support" "^0.8.0"
"@tsconfig/node10" "^1.0.7"
"@tsconfig/node12" "^1.0.7"
"@tsconfig/node14" "^1.0.0"
@ -4488,7 +4533,7 @@ ts-node@^10.7.0:
create-require "^1.1.0"
diff "^4.0.1"
make-error "^1.1.1"
v8-compile-cache-lib "^3.0.0"
v8-compile-cache-lib "^3.0.1"
yn "3.1.1"
tslib@2.3.1:
@ -4545,10 +4590,10 @@ typedarray-to-buffer@^3.1.5:
dependencies:
is-typedarray "^1.0.0"
typescript@^4.6.3:
version "4.6.3"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.3.tgz#eefeafa6afdd31d725584c67a0eaba80f6fc6c6c"
integrity sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==
typescript@4.7.4:
version "4.7.4"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.4.tgz#1a88596d1cf47d59507a1bcdfb5b9dfe4d488235"
integrity sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==
ua-parser-js@^0.7.30:
version "0.7.31"
@ -4612,10 +4657,10 @@ uuid@^3.3.3:
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
v8-compile-cache-lib@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.0.tgz#0582bcb1c74f3a2ee46487ceecf372e46bce53e8"
integrity sha512-mpSYqfsFvASnSn5qMiwrr4VKfumbPyONLCOPmsR3A6pTY/r0+tSaVbgPWSAIuzbk3lCTa+FForeTiO+wBQGkjA==
v8-compile-cache-lib@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf"
integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==
v8-compile-cache@^2.0.3:
version "2.1.0"
@ -4808,6 +4853,11 @@ workerpool@6.2.0:
resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.0.tgz#827d93c9ba23ee2019c3ffaff5c27fccea289e8b"
integrity sha512-Rsk5qQHJ9eowMH28Jwhe8HEbmdYDX4lwoMWshiCXugjtHqMD9ZbiqSDLxcsfdqsETPzVUtX5s1Z5kStiIM6l4A==
workerpool@6.2.1:
version "6.2.1"
resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.1.tgz#46fc150c17d826b86a008e5a4508656777e9c343"
integrity sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==
wrap-ansi@^6.2.0:
version "6.2.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53"