Compare commits
4 Commits
dependabot
...
master
Author | SHA1 | Date |
---|---|---|
Derrick Hammer | 99f7fe9976 | |
Derrick Hammer | 1028fc1bb0 | |
Derrick Hammer | a74045db4a | |
Derrick Hammer | d58fc82e08 |
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
|
@ -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;
|
|
@ -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);
|
||||
}
|
|
@ -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`);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
};
|
|
@ -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,
|
||||
};
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
import type { IBls, Implementation } from "./types.js";
|
||||
export declare function getImplementation(impl?: Implementation): Promise<IBls>;
|
|
@ -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}`);
|
||||
}
|
||||
}
|
|
@ -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;
|
|
@ -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;
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
export * from "./hex.js";
|
||||
export * from "./utils.js";
|
|
@ -0,0 +1,2 @@
|
|||
export * from "./hex.js";
|
||||
export * from "./utils.js";
|
|
@ -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;
|
|
@ -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;
|
||||
}
|
|
@ -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 {};
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
export {};
|
|
@ -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);
|
|
@ -0,0 +1,3 @@
|
|||
import type { IBls } from "./types.js";
|
||||
export declare const bls: () => Promise<IBls>;
|
||||
export default bls;
|
|
@ -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;
|
|
@ -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>;
|
|
@ -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
|
@ -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
|
||||
}
|
|
@ -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 = {}));
|
|
@ -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":
|
||||
|
|
|
@ -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;
|
||||
|
|
17
src/index.ts
17
src/index.ts
|
@ -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;
|
||||
|
|
Reference in New Issue