Merge pull request #58 from ChainSafe/dapplion/verifyMultipleSignatures
Add verifyMultipleSignatures method
This commit is contained in:
commit
d834657542
|
@ -84,6 +84,35 @@ import {aggCount, runs} from "./params";
|
||||||
runs,
|
runs,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Verify multiple signatures
|
||||||
|
|
||||||
|
await runBenchmark<{pks: PublicKey[]; msgs: Uint8Array[]; sigs: Signature[]}, boolean>({
|
||||||
|
id: `${implementation} verifyMultipleSignatures (${aggCount})`,
|
||||||
|
|
||||||
|
prepareTest: () => {
|
||||||
|
const dataArr = range(aggCount).map(() => {
|
||||||
|
const sk = bls.SecretKey.fromKeygen();
|
||||||
|
const pk = sk.toPublicKey();
|
||||||
|
const msg = randomMessage();
|
||||||
|
const sig = sk.sign(msg);
|
||||||
|
return {pk, msg, sig};
|
||||||
|
});
|
||||||
|
|
||||||
|
const pks = dataArr.map((data) => data.pk);
|
||||||
|
const msgs = dataArr.map((data) => data.msg);
|
||||||
|
const sigs = dataArr.map((data) => data.sig);
|
||||||
|
|
||||||
|
return {
|
||||||
|
input: {pks, msgs, sigs},
|
||||||
|
resultCheck: (valid) => valid === true,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
testRunner: ({pks, msgs, sigs}) => {
|
||||||
|
return bls.Signature.verifyMultipleSignatures(pks, msgs, sigs);
|
||||||
|
},
|
||||||
|
runs,
|
||||||
|
});
|
||||||
|
|
||||||
// Aggregate pubkeys
|
// Aggregate pubkeys
|
||||||
|
|
||||||
await runBenchmark<PublicKey[], void>({
|
await runBenchmark<PublicKey[], void>({
|
||||||
|
|
|
@ -1,26 +1,25 @@
|
||||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||||
const webpackConfig = require("./webpack.config");
|
const webpackConfig = require("./webpack.config");
|
||||||
|
|
||||||
module.exports = function(config) {
|
module.exports = function (config) {
|
||||||
config.set({
|
config.set({
|
||||||
|
basePath: "",
|
||||||
|
frameworks: ["mocha", "chai"],
|
||||||
|
files: ["test/unit-web/run-web-implementation.test.ts", "test/unit/index-named-exports.test.ts"],
|
||||||
|
exclude: [],
|
||||||
|
preprocessors: {
|
||||||
|
"test/**/*.ts": ["webpack"],
|
||||||
|
},
|
||||||
|
webpack: {
|
||||||
|
mode: "production",
|
||||||
|
node: webpackConfig.node,
|
||||||
|
module: webpackConfig.module,
|
||||||
|
resolve: webpackConfig.resolve,
|
||||||
|
},
|
||||||
|
reporters: ["spec"],
|
||||||
|
|
||||||
basePath: "",
|
browsers: ["ChromeHeadless"],
|
||||||
frameworks: ["mocha", "chai"],
|
|
||||||
files: ["test/unit/run-web-implementation.test.ts", "test/unit/index-named-exports.test.ts"],
|
|
||||||
exclude: [],
|
|
||||||
preprocessors: {
|
|
||||||
"test/**/*.ts": ["webpack"]
|
|
||||||
},
|
|
||||||
webpack: {
|
|
||||||
mode: "production",
|
|
||||||
node: webpackConfig.node,
|
|
||||||
module: webpackConfig.module,
|
|
||||||
resolve: webpackConfig.resolve
|
|
||||||
},
|
|
||||||
reporters: ["spec"],
|
|
||||||
|
|
||||||
browsers: ["ChromeHeadless"],
|
singleRun: true,
|
||||||
|
});
|
||||||
singleRun: true
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -28,6 +28,14 @@ export class Signature implements ISignature {
|
||||||
return new Signature(agg.toSignature());
|
return new Signature(agg.toSignature());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static verifyMultipleSignatures(publicKeys: PublicKey[], messages: Uint8Array[], signatures: Signature[]): boolean {
|
||||||
|
return blst.verifyMultipleAggregateSignatures(
|
||||||
|
messages,
|
||||||
|
publicKeys.map((publicKey) => publicKey.affine),
|
||||||
|
signatures.map((signature) => signature.affine)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
verify(publicKey: PublicKey, message: Uint8Array): boolean {
|
verify(publicKey: PublicKey, message: Uint8Array): boolean {
|
||||||
// Individual infinity signatures are NOT okay. Aggregated signatures MAY be infinity
|
// Individual infinity signatures are NOT okay. Aggregated signatures MAY be infinity
|
||||||
if (this.affine.value.is_inf()) {
|
if (this.affine.value.is_inf()) {
|
||||||
|
|
|
@ -106,6 +106,40 @@ export function functionalInterfaceFactory({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verifies multiple signatures at once returning true if all valid or false
|
||||||
|
* if at least one is not. Optimization useful when knowing which signature is
|
||||||
|
* wrong is not relevant, i.e. verifying an entire Eth2.0 block.
|
||||||
|
*
|
||||||
|
* This method provides a safe way to do so by multiplying each signature by
|
||||||
|
* a random number so an attacker cannot craft a malicious signature that won't
|
||||||
|
* verify on its own but will if it's added to a specific predictable signature
|
||||||
|
* https://ethresear.ch/t/fast-verification-of-multiple-bls-signatures/5407
|
||||||
|
*/
|
||||||
|
function verifyMultipleSignatures(
|
||||||
|
publicKeys: Uint8Array[],
|
||||||
|
messages: Uint8Array[],
|
||||||
|
signatures: Uint8Array[]
|
||||||
|
): boolean {
|
||||||
|
validateBytes(publicKeys, "publicKey");
|
||||||
|
validateBytes(messages, "message");
|
||||||
|
validateBytes(signatures, "signatures");
|
||||||
|
|
||||||
|
if (publicKeys.length === 0 || publicKeys.length !== messages.length || publicKeys.length !== signatures.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return Signature.verifyMultipleSignatures(
|
||||||
|
publicKeys.map((publicKey) => PublicKey.fromBytes(publicKey)),
|
||||||
|
messages.map((msg) => msg),
|
||||||
|
signatures.map((signature) => Signature.fromBytes(signature))
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof NotInitializedError) throw e;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Computes a public key from a secret key
|
* Computes a public key from a secret key
|
||||||
*/
|
*/
|
||||||
|
@ -121,6 +155,7 @@ export function functionalInterfaceFactory({
|
||||||
verify,
|
verify,
|
||||||
verifyAggregate,
|
verifyAggregate,
|
||||||
verifyMultiple,
|
verifyMultiple,
|
||||||
|
verifyMultipleSignatures,
|
||||||
secretKeyToPublicKey,
|
secretKeyToPublicKey,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,7 +26,6 @@ export function hexToBytes(hex: string): Uint8Array {
|
||||||
* From https://github.com/herumi/bls-eth-wasm/blob/04eedb77aa96e66b4f65a0ab477228adf8090c36/src/bls.js#L50
|
* From https://github.com/herumi/bls-eth-wasm/blob/04eedb77aa96e66b4f65a0ab477228adf8090c36/src/bls.js#L50
|
||||||
*/
|
*/
|
||||||
export function bytesToHex(bytes: Uint8Array): string {
|
export function bytesToHex(bytes: Uint8Array): string {
|
||||||
// return "0x" + Buffer.from(bytes).toString("hex");
|
|
||||||
let s = "";
|
let s = "";
|
||||||
const n = bytes.length;
|
const n = bytes.length;
|
||||||
|
|
||||||
|
|
|
@ -20,3 +20,17 @@ export function validateBytes(
|
||||||
export function isZeroUint8Array(bytes: Uint8Array): boolean {
|
export function isZeroUint8Array(bytes: Uint8Array): boolean {
|
||||||
return bytes.every((byte) => byte === 0);
|
return bytes.every((byte) => byte === 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function concatUint8Arrays(bytesArr: Uint8Array[]): Uint8Array {
|
||||||
|
const totalLen = bytesArr.reduce((total, bytes) => total + bytes.length, 0);
|
||||||
|
|
||||||
|
const merged = new Uint8Array(totalLen);
|
||||||
|
let mergedLen = 0;
|
||||||
|
|
||||||
|
for (const bytes of bytesArr) {
|
||||||
|
merged.set(bytes, mergedLen);
|
||||||
|
mergedLen += bytes.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
return merged;
|
||||||
|
}
|
||||||
|
|
|
@ -6,9 +6,26 @@ type Bls = typeof bls;
|
||||||
let blsGlobal: Bls | null = null;
|
let blsGlobal: Bls | null = null;
|
||||||
let blsGlobalPromise: Promise<void> | null = null;
|
let blsGlobalPromise: Promise<void> | null = null;
|
||||||
|
|
||||||
|
// Patch to fix multiVerify() calls on a browser with polyfilled NodeJS crypto
|
||||||
|
declare global {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/interface-name-prefix
|
||||||
|
interface Window {
|
||||||
|
msCrypto: typeof window["crypto"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function setupBls(): Promise<void> {
|
export async function setupBls(): Promise<void> {
|
||||||
if (!blsGlobal) {
|
if (!blsGlobal) {
|
||||||
await bls.init(bls.BLS12_381);
|
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;
|
blsGlobal = bls;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import {SIGNATURE_LENGTH} from "../constants";
|
import {SIGNATURE_LENGTH} from "../constants";
|
||||||
import {SignatureType} from "bls-eth-wasm";
|
import {SignatureType, multiVerify} from "bls-eth-wasm";
|
||||||
import {getContext} from "./context";
|
import {getContext} from "./context";
|
||||||
import {PublicKey} from "./publicKey";
|
import {PublicKey} from "./publicKey";
|
||||||
import {bytesToHex, hexToBytes, isZeroUint8Array} from "../helpers";
|
import {bytesToHex, concatUint8Arrays, hexToBytes, isZeroUint8Array} from "../helpers";
|
||||||
import {Signature as ISignature} from "../interface";
|
import {Signature as ISignature} from "../interface";
|
||||||
import {EmptyAggregateError, InvalidLengthError, InvalidOrderError} from "../errors";
|
import {EmptyAggregateError, InvalidLengthError, InvalidOrderError} from "../errors";
|
||||||
|
|
||||||
|
@ -45,6 +45,14 @@ export class Signature implements ISignature {
|
||||||
return new Signature(signature);
|
return new Signature(signature);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static verifyMultipleSignatures(publicKeys: PublicKey[], messages: Uint8Array[], signatures: Signature[]): boolean {
|
||||||
|
return multiVerify(
|
||||||
|
publicKeys.map((publicKey) => publicKey.value),
|
||||||
|
signatures.map((signature) => signature.value),
|
||||||
|
messages
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
verify(publicKey: PublicKey, message: Uint8Array): boolean {
|
verify(publicKey: PublicKey, message: Uint8Array): boolean {
|
||||||
return publicKey.value.verify(this.value, message);
|
return publicKey.value.verify(this.value, message);
|
||||||
}
|
}
|
||||||
|
@ -57,10 +65,9 @@ export class Signature implements ISignature {
|
||||||
}
|
}
|
||||||
|
|
||||||
verifyMultiple(publicKeys: PublicKey[], messages: Uint8Array[]): boolean {
|
verifyMultiple(publicKeys: PublicKey[], messages: Uint8Array[]): boolean {
|
||||||
const msgs = Buffer.concat(messages);
|
|
||||||
return this.value.aggregateVerifyNoCheck(
|
return this.value.aggregateVerifyNoCheck(
|
||||||
publicKeys.map((key) => key.value),
|
publicKeys.map((key) => key.value),
|
||||||
msgs
|
concatUint8Arrays(messages)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@ export interface IBls {
|
||||||
fromBytes(bytes: Uint8Array): Signature;
|
fromBytes(bytes: Uint8Array): Signature;
|
||||||
fromHex(hex: string): Signature;
|
fromHex(hex: string): Signature;
|
||||||
aggregate(signatures: Signature[]): Signature;
|
aggregate(signatures: Signature[]): Signature;
|
||||||
|
verifyMultipleSignatures(publicKeys: PublicKey[], messages: Uint8Array[], signatures: Signature[]): boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
sign(secretKey: Uint8Array, message: Uint8Array): Uint8Array;
|
sign(secretKey: Uint8Array, message: Uint8Array): Uint8Array;
|
||||||
|
@ -21,6 +22,7 @@ export interface IBls {
|
||||||
verify(publicKey: Uint8Array, message: Uint8Array, signature: Uint8Array): boolean;
|
verify(publicKey: Uint8Array, message: Uint8Array, signature: Uint8Array): boolean;
|
||||||
verifyAggregate(publicKeys: Uint8Array[], message: Uint8Array, signature: Uint8Array): boolean;
|
verifyAggregate(publicKeys: Uint8Array[], message: Uint8Array, signature: Uint8Array): boolean;
|
||||||
verifyMultiple(publicKeys: Uint8Array[], messages: Uint8Array[], signature: Uint8Array): boolean;
|
verifyMultiple(publicKeys: Uint8Array[], messages: Uint8Array[], signature: Uint8Array): boolean;
|
||||||
|
verifyMultipleSignatures(publicKeys: Uint8Array[], messages: Uint8Array[], signatures: Uint8Array[]): boolean;
|
||||||
secretKeyToPublicKey(secretKey: Uint8Array): Uint8Array;
|
secretKeyToPublicKey(secretKey: Uint8Array): Uint8Array;
|
||||||
|
|
||||||
init(): Promise<void>;
|
init(): Promise<void>;
|
||||||
|
@ -49,6 +51,7 @@ export declare class Signature {
|
||||||
static fromBytes(bytes: Uint8Array): Signature;
|
static fromBytes(bytes: Uint8Array): Signature;
|
||||||
static fromHex(hex: string): Signature;
|
static fromHex(hex: string): Signature;
|
||||||
static aggregate(signatures: Signature[]): Signature;
|
static aggregate(signatures: Signature[]): Signature;
|
||||||
|
static verifyMultipleSignatures(publicKeys: PublicKey[], messages: Uint8Array[], signatures: Signature[]): boolean;
|
||||||
verify(publicKey: PublicKey, message: Uint8Array): boolean;
|
verify(publicKey: PublicKey, message: Uint8Array): boolean;
|
||||||
verifyAggregate(publicKeys: PublicKey[], message: Uint8Array): boolean;
|
verifyAggregate(publicKeys: PublicKey[], message: Uint8Array): boolean;
|
||||||
verifyMultiple(publicKeys: PublicKey[], messages: Uint8Array[]): boolean;
|
verifyMultiple(publicKeys: PublicKey[], messages: Uint8Array[]): boolean;
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
/* eslint-disable max-len */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data computed with lower level BLS primitives
|
||||||
|
* https://github.com/dapplion/eth2-bls-wasm/blob/2d2f3e6a0487e96706bfd8a1b8039c7d6c79f71f/verifyMultipleSignatures.test.js#L40
|
||||||
|
* It creates N valid signatures
|
||||||
|
* It messes with the last 2 signatures, modifying their values
|
||||||
|
* such that they wqould not fail in aggregate signature verification.
|
||||||
|
* Creates a random G2 point with is added to the last signature
|
||||||
|
* and substracted to the second to last signature
|
||||||
|
*/
|
||||||
|
export const maliciousVerifyMultipleSignaturesData = {
|
||||||
|
pks: [
|
||||||
|
"b836ccf44fa01e46745ccc3a47855e959783ef5df5cdcc607354b98d52c16b6613761339bfb833fd525cdca7c8071c6b",
|
||||||
|
"a317ce36dcf2bf6fd262dbad80427f890bc166152682cb6c600a66eb7d525f200839ab798ca4877c3143a31201905de4",
|
||||||
|
"b9b7b4f4a88d98f34b4c9ba8ae10e935ba51164ddc045d6ae26b403c87a6934e6c75f9fb5cc4b3b29a1255b316d08de5",
|
||||||
|
"a386a2bc7e9d13cf9b4ad3c819547534c768aeae6a2414bfcebee50f38aaf85a9d610974db931278c08fe86a91eb2999",
|
||||||
|
],
|
||||||
|
msgs: [
|
||||||
|
"690a91fc0a7a49bbc5afe9516c1831ca8845f281ef2e414f7dfeb71b5e91a902",
|
||||||
|
"3829d4fc2332afc2634079823b89598f3674be5da324b1092b3d8aeb7af5e164",
|
||||||
|
"9a9406647ed6af16b5ce3e828c5f5ef35f1221ed10476209476c12776ce417ac",
|
||||||
|
"3e8e4bcb78fda59a43ebfb90970cc6036ce18dc3d3a1b714cc4c1bfc00b8258e",
|
||||||
|
],
|
||||||
|
sigs: [
|
||||||
|
"864ed65f224cf4e49e9bbf313d3dc243649885d9bd432a15e6c1259f2e4c29fcefa7a4c3aafaac01519f7c92239702d7096df2971b1801cd26d0ca0d5e7743ccb0abe79d8c383f9bb04ebe553a3094e84d55bc79be7eff5ffdb9b322205acfd1",
|
||||||
|
"90efd8c82c356956fc170bec2aed874d14cea079625dfe69d8bc375e10fcd96e2c0348dfeb713f1889629ccb9ec95fee0e0c9cc7a728d8a7068701a04192ed585ec761edf6e2c1e44ceaaa61732052af81a6033fa7d375d7f7157909549322da",
|
||||||
|
"9023f43cc8e05a3e842b242b9f6781a9e2eadbfcbebd1242563e56bb47cd273ef20fc0c5099e05e83093581907bfd02915b5ef8c553918d4524c274a8856950c87c6314a2c003a2ed28e5fb56ddfdb233a2b895c2397bd15629325d95ca43b83",
|
||||||
|
"82c8fedc6ad43e945bbf7529d55b73d7ce593bc9ea94dfaf91d720b2ab0e51ce551f7fcda96d428b627ff776c94d6f360af425fe7fb4e4469b893071149db747f27a8bd488af7ba7f0edf86c7e551af89d7a55d4fc86968e10f91ed76e68e373",
|
||||||
|
],
|
||||||
|
manipulated: [false, false, true, true],
|
||||||
|
};
|
|
@ -1,7 +1,7 @@
|
||||||
import herumi from "../../src/herumi";
|
import herumi from "../../src/herumi";
|
||||||
import {runSecretKeyTests} from "./secretKey.test";
|
import {runSecretKeyTests} from "../unit/secretKey.test";
|
||||||
import {runPublicKeyTests} from "./publicKey.test";
|
import {runPublicKeyTests} from "../unit/publicKey.test";
|
||||||
import {runIndexTests} from "./index.test";
|
import {runIndexTests} from "../unit/index.test";
|
||||||
|
|
||||||
// This file is intended to be compiled and run by Karma
|
// This file is intended to be compiled and run by Karma
|
||||||
// Do not import the node.bindings or it will break with:
|
// Do not import the node.bindings or it will break with:
|
|
@ -1,5 +1,6 @@
|
||||||
import {expect} from "chai";
|
import {expect} from "chai";
|
||||||
import {isZeroUint8Array} from "../../../src/helpers/utils";
|
import {concatUint8Arrays, isZeroUint8Array} from "../../../src/helpers/utils";
|
||||||
|
import {hexToBytesNode} from "../../util";
|
||||||
|
|
||||||
describe("helpers / bytes", () => {
|
describe("helpers / bytes", () => {
|
||||||
describe("isZeroUint8Array", () => {
|
describe("isZeroUint8Array", () => {
|
||||||
|
@ -21,8 +22,22 @@ describe("helpers / bytes", () => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
function hexToBytesNode(hex: string): Buffer {
|
describe("concatUint8Arrays", () => {
|
||||||
return Buffer.from(hex.replace("0x", ""), "hex");
|
it("Should merge multiple Uint8Array", () => {
|
||||||
}
|
const bytesArr = [
|
||||||
|
new Uint8Array([1, 2, 3]),
|
||||||
|
new Uint8Array([4, 5]),
|
||||||
|
new Uint8Array([6]),
|
||||||
|
new Uint8Array([7, 8]),
|
||||||
|
new Uint8Array([9, 10, 11]),
|
||||||
|
];
|
||||||
|
|
||||||
|
const expectedBytes = new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]);
|
||||||
|
|
||||||
|
const bytes = concatUint8Arrays(bytesArr);
|
||||||
|
|
||||||
|
expect(bytes.toString()).to.equal(expectedBytes.toString());
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import {expect} from "chai";
|
import {expect} from "chai";
|
||||||
import {hexToBytes, bytesToHex} from "../../../src/helpers/hex";
|
import {hexToBytes, bytesToHex} from "../../../src/helpers/hex";
|
||||||
|
import {hexToBytesNode} from "../../util";
|
||||||
|
|
||||||
describe("helpers / hex", () => {
|
describe("helpers / hex", () => {
|
||||||
const testCases: {id: string; hex: string}[] = [
|
const testCases: {id: string; hex: string}[] = [
|
||||||
|
@ -23,7 +24,3 @@ describe("helpers / hex", () => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function hexToBytesNode(hex: string): Buffer {
|
|
||||||
return Buffer.from(hex.replace("0x", ""), "hex");
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import {expect} from "chai";
|
import {expect} from "chai";
|
||||||
import {IBls} from "../../src/interface";
|
import {IBls} from "../../src/interface";
|
||||||
import {getN, randomMessage} from "../util";
|
import {getN, randomMessage} from "../util";
|
||||||
|
import {hexToBytes} from "../../src/helpers";
|
||||||
|
import {maliciousVerifyMultipleSignaturesData} from "../data/malicious-signature-test-data";
|
||||||
|
|
||||||
export function runIndexTests(bls: IBls): void {
|
export function runIndexTests(bls: IBls): void {
|
||||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||||
|
@ -90,4 +92,57 @@ export function runIndexTests(bls: IBls): void {
|
||||||
expect(isValid).to.be.false;
|
expect(isValid).to.be.false;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("verifyMultipleSignatures", () => {
|
||||||
|
it("Should verify multiple signatures", () => {
|
||||||
|
const n = 4;
|
||||||
|
const dataArr = getN(n, () => {
|
||||||
|
const sk = bls.SecretKey.fromKeygen();
|
||||||
|
const pk = sk.toPublicKey();
|
||||||
|
const msg = randomMessage();
|
||||||
|
const sig = sk.sign(msg);
|
||||||
|
return {pk, msg, sig};
|
||||||
|
});
|
||||||
|
const pks = dataArr.map((data) => data.pk);
|
||||||
|
const msgs = dataArr.map((data) => data.msg);
|
||||||
|
const sigs = dataArr.map((data) => data.sig);
|
||||||
|
|
||||||
|
expect(bls.Signature.verifyMultipleSignatures(pks, msgs, sigs)).to.equal(true, "class interface failed");
|
||||||
|
|
||||||
|
expect(
|
||||||
|
bls.verifyMultipleSignatures(
|
||||||
|
pks.map((pk) => pk.toBytes()),
|
||||||
|
msgs,
|
||||||
|
sigs.map((sig) => sig.toBytes())
|
||||||
|
)
|
||||||
|
).to.equal(true, "functional (bytes serialized) interface failed");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Test fails correctly against a malicous signature", async () => {
|
||||||
|
const pks = maliciousVerifyMultipleSignaturesData.pks.map((pk) => bls.PublicKey.fromHex(pk));
|
||||||
|
const msgs = maliciousVerifyMultipleSignaturesData.msgs.map(hexToBytes);
|
||||||
|
const sigs = maliciousVerifyMultipleSignaturesData.sigs.map((sig) => bls.Signature.fromHex(sig));
|
||||||
|
|
||||||
|
maliciousVerifyMultipleSignaturesData.manipulated.forEach((isManipulated, i) => {
|
||||||
|
expect(sigs[i].verify(pks[i], msgs[i])).to.equal(
|
||||||
|
!isManipulated,
|
||||||
|
isManipulated ? "Manipulated signature should not verify" : "Ok signature should verify"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// This method (AggregateVerify in BLS spec lingo) should verify
|
||||||
|
|
||||||
|
const dangerousAggSig = bls.Signature.aggregate(sigs);
|
||||||
|
expect(dangerousAggSig.verifyMultiple(pks, msgs)).to.equal(
|
||||||
|
true,
|
||||||
|
"Malicious signature should be validated with bls.verifyMultiple"
|
||||||
|
);
|
||||||
|
|
||||||
|
// This method is expected to catch the malicious signature and not verify
|
||||||
|
expect(bls.Signature.verifyMultipleSignatures(pks, msgs, sigs)).to.equal(
|
||||||
|
false,
|
||||||
|
"Malicous signature should not validate with bls.verifyMultipleSignatures"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,3 +13,11 @@ export function range(n: number): number[] {
|
||||||
for (let i = 0; i < n; i++) nums.push(i);
|
for (let i = 0; i < n; i++) nums.push(i);
|
||||||
return nums;
|
return nums;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ONLY for NodeJS tests, for any other use src/helpers/hex hexToBytes()
|
||||||
|
* Serves as a "ground-truth" reference
|
||||||
|
*/
|
||||||
|
export function hexToBytesNode(hex: string): Buffer {
|
||||||
|
return Buffer.from(hex.replace("0x", ""), "hex");
|
||||||
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
"target": "esnext",
|
"target": "esnext",
|
||||||
"module": "commonjs",
|
"module": "commonjs",
|
||||||
"pretty": true,
|
"pretty": true,
|
||||||
"lib": ["esnext.bigint"],
|
"lib": ["esnext.bigint", "DOM"],
|
||||||
"typeRoots": ["./node_modules/@types"],
|
"typeRoots": ["./node_modules/@types"],
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
|
|
|
@ -2,17 +2,20 @@ module.exports = {
|
||||||
entry: "./src/index.ts",
|
entry: "./src/index.ts",
|
||||||
mode: "production",
|
mode: "production",
|
||||||
node: {
|
node: {
|
||||||
fs: "empty"
|
fs: "empty",
|
||||||
},
|
},
|
||||||
output: {
|
output: {
|
||||||
filename: "dist/bundle.js"
|
filename: "dist/bundle.js",
|
||||||
},
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
extensions: [".ts", ".js"]
|
extensions: [".ts", ".js"],
|
||||||
},
|
},
|
||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [{test: /\.ts$/, use: {loader: "ts-loader", options: {transpileOnly: true}}}],
|
||||||
{test: /\.ts$/, use: {loader: "ts-loader", options: {transpileOnly: true}}}
|
},
|
||||||
]
|
optimization: {
|
||||||
}
|
// Disable minification for better debugging on Karma tests
|
||||||
|
minimize: false,
|
||||||
|
},
|
||||||
|
devtool: "source-map",
|
||||||
};
|
};
|
Reference in New Issue