Merge pull request #406 from ChainSafe/mpetrunic/bls-benchmark
Bls benchmarking and optimisation
This commit is contained in:
commit
89e68ff31d
|
@ -64,3 +64,4 @@ typings/
|
|||
|
||||
dist/
|
||||
lib/
|
||||
benchmark-reports
|
||||
|
|
|
@ -30,7 +30,8 @@
|
|||
"test:unit": "nyc --cache-dir .nyc_output/.cache -r lcov -e .ts mocha -r ./.babel-register 'test/unit/**/*.test.ts' && nyc report",
|
||||
"test:spec": "mocha -r ./.babel-register 'test/spec/**/*.test.ts'",
|
||||
"test": "yarn test:unit && yarn test:spec",
|
||||
"coverage": "codecov -F bls"
|
||||
"coverage": "codecov -F bls",
|
||||
"benchmark": "node -r ./.babel-register test/benchmarks"
|
||||
},
|
||||
"dependencies": {
|
||||
"@chainsafe/eth2.0-types": "^0.1.0",
|
||||
|
@ -49,11 +50,12 @@
|
|||
"@babel/preset-typescript": "^7.3.3",
|
||||
"@babel/register": "^7.0.0",
|
||||
"@babel/runtime": "^7.4.4",
|
||||
"@chainsafe/benchmark-utils": "^0.1.0",
|
||||
"@chainsafe/eth2.0-spec-test-util": "^0.2.3",
|
||||
"@types/assert": "^1.4.2",
|
||||
"@types/chai": "^4.1.7",
|
||||
"@types/mocha": "^5.2.5",
|
||||
"@types/node": "^10.12.17",
|
||||
"@types/node": "^12.7.2",
|
||||
"@typescript-eslint/eslint-plugin": "^1.3.0",
|
||||
"@typescript-eslint/parser": "^1.3.0",
|
||||
"babel-plugin-rewire-exports": "^1.1.0",
|
||||
|
|
|
@ -5,7 +5,7 @@ import assert from "assert";
|
|||
import {calculateYFlag, getModulus} from "./utils";
|
||||
import * as random from "secure-random";
|
||||
import {FP_POINT_LENGTH} from "../constants";
|
||||
import {bytes48} from "@chainsafe/eth2.0-types";
|
||||
import {BLSPubkey, bytes48} from "@chainsafe/eth2.0-types";
|
||||
|
||||
export class G1point {
|
||||
|
||||
|
@ -28,6 +28,10 @@ export class G1point {
|
|||
return new G1point(sum);
|
||||
}
|
||||
|
||||
public addRaw(other: bytes48): G1point {
|
||||
return this.add(G1point.fromBytesCompressed(other));
|
||||
}
|
||||
|
||||
public equal(other: G1point): boolean {
|
||||
return this.point.equals(other.point);
|
||||
}
|
||||
|
@ -102,6 +106,14 @@ export class G1point {
|
|||
return new G1point(point);
|
||||
}
|
||||
|
||||
public static aggregate(values: bytes48[]): G1point {
|
||||
return values.map((value) => {
|
||||
return G1point.fromBytesCompressed(value);
|
||||
}).reduce((previousValue, currentValue): G1point => {
|
||||
return previousValue.add(currentValue);
|
||||
});
|
||||
}
|
||||
|
||||
public static generator(): G1point {
|
||||
return new G1point(ctx.ECP.generator());
|
||||
}
|
||||
|
|
49
src/index.ts
49
src/index.ts
|
@ -58,11 +58,7 @@ export function aggregatePubkeys(publicKeys: BLSPubkey[]): BLSPubkey {
|
|||
if(publicKeys.length === 0) {
|
||||
return new G1point(new ctx.ECP()).toBytesCompressed();
|
||||
}
|
||||
return publicKeys.map((publicKey): G1point => {
|
||||
return G1point.fromBytesCompressed(publicKey);
|
||||
}).reduce((previousValue, currentValue): G1point => {
|
||||
return previousValue.add(currentValue);
|
||||
}).toBytesCompressed();
|
||||
return G1point.aggregate(publicKeys).toBytesCompressed();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -77,6 +73,8 @@ export function verify(publicKey: BLSPubkey, messageHash: bytes32, signature: BL
|
|||
const key = PublicKey.fromBytes(publicKey);
|
||||
const sig = Signature.fromCompressedBytes(signature);
|
||||
|
||||
key.getPoint().getPoint().affine();
|
||||
sig.getPoint().getPoint().affine();
|
||||
const g1Generated = G1point.generator();
|
||||
const e1 = ElipticCurvePairing.pair(key.getPoint(), G2point.hashToG2(messageHash, domain));
|
||||
const e2 = ElipticCurvePairing.pair(g1Generated, sig.getPoint());
|
||||
|
@ -98,18 +96,47 @@ export function verifyMultiple(publicKeys: BLSPubkey[], messageHashes: bytes32[]
|
|||
return false;
|
||||
}
|
||||
try {
|
||||
const g1Generated = G1point.generator();
|
||||
const sig = Signature.fromCompressedBytes(signature).getPoint();
|
||||
sig.getPoint().affine();
|
||||
|
||||
const eCombined = new ctx.FP12(1);
|
||||
publicKeys.forEach((publicKey, index): void => {
|
||||
const g2 = G2point.hashToG2(messageHashes[index], domain);
|
||||
|
||||
const reduction = messageHashes.reduce((previous, current, index) => {
|
||||
if(previous.hash && current.equals(previous.hash)) {
|
||||
return {
|
||||
hash: previous.hash,
|
||||
publicKey: previous.publicKey ?
|
||||
previous.publicKey.addRaw(publicKeys[index])
|
||||
:
|
||||
G1point.fromBytesCompressed(publicKeys[index]),
|
||||
};
|
||||
} else if(!!previous.hash) {
|
||||
const g2 = G2point.hashToG2(previous.hash, domain);
|
||||
eCombined.mul(
|
||||
ElipticCurvePairing.pair(
|
||||
PublicKey.fromBytes(publicKey).getPoint(),
|
||||
previous.publicKey,
|
||||
g2
|
||||
)
|
||||
);
|
||||
});
|
||||
const e2 = ElipticCurvePairing.pair(g1Generated, Signature.fromCompressedBytes(signature).getPoint());
|
||||
return {hash: current, publicKey: G1point.fromBytesCompressed(publicKeys[index])};
|
||||
} else {
|
||||
return {
|
||||
hash: current,
|
||||
publicKey: G1point.fromBytesCompressed(publicKeys[index])
|
||||
};
|
||||
}
|
||||
}, {hash: null, publicKey: null});
|
||||
|
||||
const g2Final = G2point.hashToG2(reduction.hash, domain);
|
||||
const keyFinal = reduction.publicKey;
|
||||
eCombined.mul(
|
||||
ElipticCurvePairing.pair(
|
||||
keyFinal,
|
||||
g2Final
|
||||
)
|
||||
);
|
||||
|
||||
const e2 = ElipticCurvePairing.pair(G1point.generator(), sig);
|
||||
return e2.equals(eCombined);
|
||||
} catch (e) {
|
||||
return false;
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
// Import benchmarks
|
||||
import * as suites from "./suites";
|
||||
import {createReportDir, runSuite} from "@chainsafe/benchmark-utils";
|
||||
// Create file
|
||||
const directory: string = createReportDir();
|
||||
|
||||
|
||||
// Run benchmarks
|
||||
Object.values(suites).forEach((suite) => {
|
||||
runSuite(suite(directory));
|
||||
});
|
|
@ -0,0 +1,5 @@
|
|||
// export {verifyInValidSignatureBenchmark} from './verifyInValidSignature';
|
||||
// export {verifyValidSignatureBenchmark} from './verifyValidSignature';
|
||||
export {verifyValidAggregatedSignature} from './verifyValidAggregatedSignature';
|
||||
// export {verifyInvalidAggregatedSignature} from './verifyInvalidAggregatedSignature';
|
||||
// export {aggregateSignaturesBenchmark} from './signatureAggregation';
|
|
@ -0,0 +1,39 @@
|
|||
/* eslint-disable @typescript-eslint/no-require-imports,@typescript-eslint/no-var-requires */
|
||||
|
||||
import {BenchSuite} from "@chainsafe/benchmark-utils";
|
||||
import {aggregateSignatures} from "../../../src";
|
||||
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
declare namespace global {
|
||||
export let signatures: Buffer[];
|
||||
export let aggregateSignatures: Function;
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
global.require = require;
|
||||
|
||||
global.aggregateSignatures = aggregateSignatures;
|
||||
|
||||
export function aggregateSignaturesBenchmark(dir: string): BenchSuite {
|
||||
|
||||
// Set the function test
|
||||
const FUNCTION_NAME = "verifyValidSignature"; // PLEASE FILL THIS OUT
|
||||
|
||||
const aggregateSignatures = function (): void {
|
||||
global.aggregateSignatures(global.signatures);
|
||||
};
|
||||
|
||||
return {
|
||||
testFunctions: [aggregateSignatures],
|
||||
setup: function() {
|
||||
global.signatures = [];
|
||||
const {Keypair} = require("../../../src");
|
||||
const {sha256} = require('js-sha256');
|
||||
const keypair = Keypair.generate();
|
||||
const message = Buffer.from(sha256.arrayBuffer(Math.random().toString(36)));
|
||||
global.signatures.push(keypair.privateKey.signMessage(Buffer.from(message), Buffer.alloc(8)).toBytesCompressed());
|
||||
},
|
||||
file: dir + FUNCTION_NAME + ".txt"
|
||||
};
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
import {BenchSuite} from "@chainsafe/benchmark-utils";
|
||||
import {verify} from "../../../src";
|
||||
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
declare namespace global {
|
||||
export let domain: Buffer;
|
||||
export let message: Buffer;
|
||||
export let signature: Buffer;
|
||||
export let publicKey: Buffer;
|
||||
export let verify: Function;
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
global.require = require;
|
||||
|
||||
global.domain = Buffer.alloc(8);
|
||||
global.verify = verify;
|
||||
|
||||
export function verifyInValidSignatureBenchmark(dir: string): BenchSuite {
|
||||
|
||||
// Set the function test
|
||||
const FUNCTION_NAME = "verifyInValidSignature"; // PLEASE FILL THIS OUT
|
||||
|
||||
const verifyInValidSignature = function (): void {
|
||||
global.verify(global.publicKey, global.message, global.signature, global.domain);
|
||||
};
|
||||
|
||||
return {
|
||||
testFunctions: [verifyInValidSignature],
|
||||
setup: function() {
|
||||
const {Keypair} = require("../../../src");
|
||||
const {sha256} = require('js-sha256');
|
||||
const keypair = Keypair.generate();
|
||||
const keypair2 = Keypair.generate();
|
||||
global.publicKey = keypair2.publicKey.toBytesCompressed();
|
||||
global.message = Buffer.from(sha256.arrayBuffer(Math.random().toString(36)));
|
||||
global.signature = keypair.privateKey.signMessage(Buffer.from(global.message), global.domain).toBytesCompressed();
|
||||
},
|
||||
file: dir + FUNCTION_NAME + ".txt"
|
||||
};
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
import {BenchSuite} from "@chainsafe/benchmark-utils";
|
||||
import {aggregateSignatures, verifyMultiple} from "../../../src";
|
||||
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
declare namespace global {
|
||||
export let domain: Buffer;
|
||||
export let messages: Buffer[];
|
||||
export let signature: Buffer;
|
||||
export let publicKeys: Buffer[];
|
||||
export let verify: Function;
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
global.require = require;
|
||||
|
||||
global.domain = Buffer.alloc(8);
|
||||
global.verify = verifyMultiple;
|
||||
|
||||
export function verifyInvalidAggregatedSignature(dir: string): BenchSuite {
|
||||
|
||||
// Set the function test
|
||||
const FUNCTION_NAME = "verifyInvalidAggregatedSignature"; // PLEASE FILL THIS OUT
|
||||
|
||||
const verifyInvalidAggregatedSignature = function (): void {
|
||||
global.verify(global.publicKeys, global.messages, global.signature, global.domain);
|
||||
};
|
||||
|
||||
return {
|
||||
testFunctions: [verifyInvalidAggregatedSignature],
|
||||
setup: function() {
|
||||
const {Keypair, aggregateSignatures} = require("../../../src");
|
||||
const {sha256} = require('js-sha256');
|
||||
const signatures = [];
|
||||
global.publicKeys = [];
|
||||
const message = Buffer.from(sha256.arrayBuffer(Math.random().toString(36)));
|
||||
const message2 = Buffer.from(sha256.arrayBuffer(Math.random().toString(36)));
|
||||
for(let i = 0; i < 128; i++) {
|
||||
const keypair = Keypair.generate();
|
||||
global.publicKeys.push(keypair.publicKey.toBytesCompressed());
|
||||
signatures.push(keypair.privateKey.signMessage(Buffer.from(message), global.domain).toBytesCompressed());
|
||||
}
|
||||
global.messages = global.publicKeys.map(() => message2);
|
||||
global.signature = aggregateSignatures(signatures);
|
||||
},
|
||||
file: dir + FUNCTION_NAME + ".txt"
|
||||
};
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
import {BenchSuite} from "@chainsafe/benchmark-utils";
|
||||
import {aggregateSignatures, Keypair, verifyMultiple} from "../../../src";
|
||||
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
declare namespace global {
|
||||
export let domain: Buffer;
|
||||
export let messages: Buffer[];
|
||||
export let signature: Buffer;
|
||||
export let publicKeys: Buffer[];
|
||||
export let keypairs: Keypair[];
|
||||
export let verify: Function;
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
global.require = require;
|
||||
|
||||
global.domain = Buffer.alloc(8);
|
||||
global.verify = verifyMultiple;
|
||||
|
||||
export function verifyValidAggregatedSignature(dir: string): BenchSuite {
|
||||
|
||||
global.publicKeys = [];
|
||||
global.keypairs = [];
|
||||
for(let i = 0; i < 128; i++) {
|
||||
const keypair = Keypair.generate();
|
||||
global.keypairs.push(keypair);
|
||||
global.publicKeys.push(keypair.publicKey.toBytesCompressed());
|
||||
}
|
||||
|
||||
// Set the function test
|
||||
const FUNCTION_NAME = "verifyValidAggregatedSignature"; // PLEASE FILL THIS OUT
|
||||
|
||||
const verifyValidAggregatedSignature = function (): void {
|
||||
global.verify(global.publicKeys, global.messages, global.signature, global.domain)
|
||||
};
|
||||
|
||||
return {
|
||||
testFunctions: [verifyValidAggregatedSignature],
|
||||
setup: function() {
|
||||
const sha256 = require('js-sha256');
|
||||
const {aggregateSignatures} = require("../../../src");
|
||||
const message = Buffer.from(sha256.arrayBuffer(Math.random().toString(36)));
|
||||
const signatures = [];
|
||||
global.messages = [];
|
||||
global.keypairs.forEach((keypair) => {
|
||||
signatures.push(keypair.privateKey.signMessage(message, global.domain).toBytesCompressed());
|
||||
global.messages.push(message);
|
||||
});
|
||||
global.signature = aggregateSignatures(signatures);
|
||||
},
|
||||
file: dir + FUNCTION_NAME + ".txt",
|
||||
// profile: true,
|
||||
name: FUNCTION_NAME,
|
||||
};
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
/* eslint-disable @typescript-eslint/no-require-imports,@typescript-eslint/no-var-requires */
|
||||
|
||||
import {BenchSuite} from "@chainsafe/benchmark-utils";
|
||||
import {verify} from "../../../src";
|
||||
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
declare namespace global {
|
||||
export let domain: Buffer;
|
||||
export let message: Buffer;
|
||||
export let signature: Buffer;
|
||||
export let publicKey: Buffer;
|
||||
export let verify: Function;
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
global.require = require;
|
||||
|
||||
global.domain = Buffer.alloc(8);
|
||||
global.verify = verify;
|
||||
|
||||
export function verifyValidSignatureBenchmark(dir: string): BenchSuite {
|
||||
|
||||
// Set the function test
|
||||
const FUNCTION_NAME = "verifyValidSignature"; // PLEASE FILL THIS OUT
|
||||
|
||||
const verifyValidSignature = function (): void {
|
||||
global.verify(global.publicKey, global.message, global.signature, global.domain);
|
||||
};
|
||||
|
||||
return {
|
||||
testFunctions: [verifyValidSignature],
|
||||
setup: function() {
|
||||
const {Keypair} = require("../../../src");
|
||||
const {sha256} = require('js-sha256');
|
||||
const keypair = Keypair.generate();
|
||||
global.publicKey = keypair.publicKey.toBytesCompressed();
|
||||
global.message = Buffer.from(sha256.arrayBuffer(Math.random().toString(36)));
|
||||
global.signature = keypair.privateKey.signMessage(Buffer.from(global.message), global.domain).toBytesCompressed();
|
||||
},
|
||||
file: dir + FUNCTION_NAME + ".txt"
|
||||
};
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
import bls from "../../src";
|
||||
import {Keypair} from "../../src/keypair";
|
||||
import { sha256 } from 'js-sha256';
|
||||
import {sha256} from 'js-sha256';
|
||||
import {G2point} from "../../src/helpers/g2point";
|
||||
import {expect} from "chai";
|
||||
|
||||
|
@ -65,7 +65,7 @@ describe('test bls', function () {
|
|||
it('should fail verify signature of different message', () => {
|
||||
const keypair = Keypair.generate();
|
||||
const messageHash = Buffer.from(sha256.arrayBuffer("Test message"));
|
||||
const messageHash2 = Buffer.from(sha256.arrayBuffer("Test message2"))
|
||||
const messageHash2 = Buffer.from(sha256.arrayBuffer("Test message2"));
|
||||
const domain = Buffer.from("01", 'hex');
|
||||
const signature = keypair.privateKey.sign(
|
||||
G2point.hashToG2(messageHash, domain)
|
||||
|
@ -117,7 +117,7 @@ describe('test bls', function () {
|
|||
describe('verify multiple', function() {
|
||||
|
||||
it('should verify aggregated signatures', function () {
|
||||
this.timeout(5000)
|
||||
this.timeout(5000);
|
||||
|
||||
|
||||
const domain = Buffer.alloc(8, 0);
|
||||
|
@ -162,8 +162,48 @@ describe('test bls', function () {
|
|||
expect(result).to.be.true;
|
||||
});
|
||||
|
||||
it('should verify aggregated signatures - same message', function () {
|
||||
this.timeout(5000);
|
||||
|
||||
|
||||
const domain = Buffer.alloc(8, 0);
|
||||
|
||||
const keypair1 = Keypair.generate();
|
||||
const keypair2 = Keypair.generate();
|
||||
const keypair3 = Keypair.generate();
|
||||
const keypair4 = Keypair.generate();
|
||||
|
||||
const message = Buffer.from("Test1", 'utf-8');
|
||||
|
||||
const signature1 = keypair1.privateKey.signMessage(message, domain);
|
||||
const signature2 = keypair2.privateKey.signMessage(message, domain);
|
||||
const signature3 = keypair3.privateKey.signMessage(message, domain);
|
||||
const signature4 = keypair4.privateKey.signMessage(message, domain);
|
||||
|
||||
const aggregateSignature = bls.aggregateSignatures([
|
||||
signature1.toBytesCompressed(),
|
||||
signature2.toBytesCompressed(),
|
||||
signature3.toBytesCompressed(),
|
||||
signature4.toBytesCompressed(),
|
||||
]);
|
||||
|
||||
const result = bls.verifyMultiple(
|
||||
[
|
||||
keypair1.publicKey.toBytesCompressed(),
|
||||
keypair2.publicKey.toBytesCompressed(),
|
||||
keypair3.publicKey.toBytesCompressed(),
|
||||
keypair4.publicKey.toBytesCompressed()
|
||||
],
|
||||
[message, message, message, message],
|
||||
aggregateSignature,
|
||||
domain
|
||||
);
|
||||
|
||||
expect(result).to.be.true;
|
||||
});
|
||||
|
||||
it('should fail to verify aggregated signatures - swapped messages', function () {
|
||||
this.timeout(5000)
|
||||
this.timeout(5000);
|
||||
|
||||
const domain = Buffer.alloc(8, 0);
|
||||
|
||||
|
|
Reference in New Issue