Update tests to run both implementations

This commit is contained in:
dapplion 2020-11-19 14:41:45 +00:00
parent 57694c2e54
commit f8cd6e7afa
16 changed files with 141 additions and 277 deletions

View File

@ -53,7 +53,6 @@
"@babel/preset-env": "^7.8.4",
"@babel/preset-typescript": "^7.8.3",
"@babel/register": "^7.8.3",
"@chainsafe/as-sha256": "0.2.0",
"@chainsafe/eth2-spec-tests": "0.12.0",
"@chainsafe/lodestar-spec-test-util": "^0.5.0",
"@types/chai": "^4.2.9",

View File

@ -43,7 +43,7 @@ export function sign(secretKey: Uint8Array, messageHash: Uint8Array): Buffer {
* @param signatures
*/
export function aggregateSignatures(signatures: Uint8Array[]): Buffer {
assert(signatures && signatures.length > 0, "EMPTY_AGGREGATE_ARRAY");
assert(signatures, "signatures is null or undefined");
const agg = Signature.aggregate(signatures.map((signature): Signature => Signature.fromBytes(signature)));
return agg.toBytes();
}
@ -54,13 +54,8 @@ export function aggregateSignatures(signatures: Uint8Array[]): Buffer {
*/
export function aggregatePubkeys(publicKeys: Uint8Array[]): Buffer {
assert(publicKeys, "publicKeys is null or undefined");
if (publicKeys.length === 0) {
return Buffer.alloc(PUBLIC_KEY_LENGTH);
}
return publicKeys
.map((p) => PublicKey.fromBytes(toBuffer(p)))
.reduce((agg, pubKey) => agg.add(pubKey))
.toBytes();
const agg = PublicKey.aggregate(publicKeys.map((pk) => PublicKey.fromBytes(pk)));
return agg.toBytes();
}
/**

View File

@ -27,7 +27,8 @@ export class PrivateKey {
}
static fromKeygen(entropy?: Uint8Array): PrivateKey {
return this.fromBytes(generateRandomSecretKey(Buffer.from(entropy)));
const sk = generateRandomSecretKey(entropy && Buffer.from(entropy));
return this.fromBytes(sk);
}
getValue(): SecretKeyType {

View File

@ -24,6 +24,18 @@ export class PublicKey {
return this.fromBytes(hexToBytes(hex));
}
static aggregate(pubkeys: PublicKey[]): PublicKey {
if (pubkeys.length === 0) {
throw Error("EMPTY_AGGREGATE_ARRAY");
}
const agg = new PublicKey(pubkeys[0].value.clone());
for (const pk of pubkeys.slice(1)) {
agg.value.add(pk.value);
}
return agg;
}
add(other: PublicKey): PublicKey {
const agg = new PublicKey(this.value.clone());
agg.value.add(other.value);

View File

@ -28,6 +28,10 @@ export class Signature {
}
static aggregate(signatures: Signature[]): Signature {
if (signatures.length === 0) {
throw Error("EMPTY_AGGREGATE_ARRAY");
}
const context = getContext();
const signature = new context.Signature();
signature.aggregate(signatures.map((sig) => sig.value));

View File

@ -11,9 +11,9 @@ interface IAggregateSigsTestCase {
};
}
forEachImplementation((bls, implementation) => {
forEachImplementation((bls) => {
describeDirectorySpecTest<IAggregateSigsTestCase, string>(
`${implementation} - bls/aggregate/small`,
"bls/aggregate/small",
path.join(SPEC_TESTS_DIR, "general/phase0/bls/aggregate/small"),
(testCase) => {
try {

View File

@ -15,9 +15,9 @@ interface IAggregateSigsVerifyTestCase {
};
}
forEachImplementation((bls, implementation) => {
forEachImplementation((bls) => {
describeDirectorySpecTest<IAggregateSigsVerifyTestCase, boolean>(
`${implementation} - bls/aggregate_verify/small`,
"bls/aggregate_verify/small",
path.join(SPEC_TESTS_DIR, "general/phase0/bls/aggregate_verify/small"),
(testCase) => {
const {pubkeys, messages, signature} = testCase.data.input;

View File

@ -15,9 +15,9 @@ interface IAggregateSigsVerifyTestCase {
};
}
forEachImplementation((bls, implementation) => {
forEachImplementation((bls) => {
describeDirectorySpecTest<IAggregateSigsVerifyTestCase, boolean>(
`${implementation} - bls/fast_aggregate_verify/small`,
"bls/fast_aggregate_verify/small",
path.join(SPEC_TESTS_DIR, "general/phase0/bls/fast_aggregate_verify/small"),
(testCase) => {
const {pubkeys, message, signature} = testCase.data.input;

View File

@ -14,9 +14,9 @@ interface ISignMessageTestCase {
};
}
forEachImplementation((bls, implementation) => {
forEachImplementation((bls) => {
describeDirectorySpecTest<ISignMessageTestCase, string>(
`${implementation} - bls/sign/small`,
"bls/sign/small",
path.join(SPEC_TESTS_DIR, "general/phase0/bls/sign/small"),
(testCase) => {
const {privkey, message} = testCase.data.input;

View File

@ -15,9 +15,9 @@ interface IVerifyTestCase {
};
}
forEachImplementation((bls, implementation) => {
forEachImplementation((bls) => {
describeDirectorySpecTest<IVerifyTestCase, boolean>(
`${implementation} - bls/verify/small`,
"bls/verify/small",
path.join(SPEC_TESTS_DIR, "general/phase0/bls/verify/small"),
(testCase) => {
const {pubkey, message, signature} = testCase.data.input;

View File

@ -17,14 +17,16 @@ export function forEachImplementation(
callback: (bls: ReturnType<typeof getBls>, implementation: Implementation) => void
): void {
for (const implementation of implementations) {
const bls = getBls(implementation);
describe(implementation, () => {
const bls = getBls(implementation);
if (implementation === "herumi") {
before(async () => {
await bls.initBLS();
});
}
if (implementation === "herumi") {
before(async () => {
await bls.initBLS();
});
}
callback(bls, implementation);
callback(bls, implementation);
});
}
}

View File

@ -1,195 +1,105 @@
import {aggregatePubkeys, aggregateSignatures, initBLS, Keypair, verify, verifyMultiple} from "../../src";
import SHA256 from "@chainsafe/as-sha256";
import {expect} from "chai";
import {forEachImplementation} from "../switch";
import {getRandomBytes} from "../../src/helpers/utils";
describe("test bls", function () {
before(async function () {
await initBLS();
});
function randomMessage(): Uint8Array {
return getRandomBytes(32);
}
describe("verify", function () {
function getN<T>(n: number, getter: () => T): T[] {
return Array.from({length: n}, () => getter());
}
forEachImplementation((bls) => {
function getRandomData() {
const sk = bls.PrivateKey.fromKeygen();
const pk = sk.toPublicKey();
const msg = randomMessage();
const sig = sk.signMessage(msg);
return {sk, pk, msg, sig};
}
describe("verify", () => {
it("should verify signature", () => {
const keypair = Keypair.generate();
const messageHash = Buffer.from(SHA256.digest(Buffer.from("Test")));
const signature = keypair.privateKey.signMessage(messageHash);
const result = verify(keypair.publicKey.toBytes(), messageHash, signature.toBytes());
expect(result).to.be.true;
});
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;
it("should not modify original pubkey when verifying", () => {
const keypair = Keypair.generate();
const messageHash = Buffer.from(SHA256.digest(Buffer.from("Test")));
const signature = keypair.privateKey.signMessage(messageHash);
const pubKey = keypair.publicKey.toBytes();
verify(pubKey, messageHash, signature.toBytes());
expect("0x" + pubKey.toString("hex")).to.be.equal(keypair.publicKey.toHex());
// Make sure to not modify original pubkey when verifying
expect(pk.toHex()).to.be.equal(pkHex, "pubkey modified when verifying");
});
it("should fail verify empty signature", () => {
const keypair = Keypair.generate();
const messageHash2 = Buffer.from(SHA256.digest(Buffer.from("Test message2")));
const signature = Buffer.alloc(96);
const result = verify(keypair.publicKey.toBytes(), messageHash2, signature);
expect(result).to.be.false;
const {pk, msg} = getRandomData();
const emptySig = Buffer.alloc(96);
const isValid = bls.verify(pk.toBytes(), msg, emptySig);
expect(isValid).to.be.false;
});
it("should fail verify signature of different message", () => {
const keypair = Keypair.generate();
const messageHash = Buffer.from(SHA256.digest(Buffer.from("Test message")));
const messageHash2 = Buffer.from(SHA256.digest(Buffer.from("Test message2")));
const signature = keypair.privateKey.signMessage(messageHash);
const result = verify(keypair.publicKey.toBytes(), messageHash2, signature.toBytes());
expect(result).to.be.false;
const {pk, sig} = getRandomData();
const msg2 = randomMessage();
const isValid = bls.verify(pk.toBytes(), msg2, sig.toBytes());
expect(isValid).to.be.false;
});
it("should fail verify signature signed by different key", () => {
const keypair = Keypair.generate();
const keypair2 = Keypair.generate();
const messageHash = Buffer.from(SHA256.digest(Buffer.from("Test message")));
const signature = keypair.privateKey.signMessage(messageHash);
const result = verify(keypair2.publicKey.toBytes(), messageHash, signature.toBytes());
expect(result).to.be.false;
const {msg, sig} = getRandomData();
const {pk: pk2} = getRandomData();
const isValid = bls.verify(pk2.toBytes(), msg, sig.toBytes());
expect(isValid).to.be.false;
});
});
describe("verify multiple", function () {
it("should verify aggregated signatures", function () {
this.timeout(5000);
describe("verify multiple", () => {
it(`should verify aggregated signatures`, () => {
const sks = getN(4, () => bls.PrivateKey.fromKeygen());
const msgs = getN(2, () => randomMessage());
const pks = sks.map((sk) => sk.toPublicKey());
const keypair1 = Keypair.generate();
const keypair2 = Keypair.generate();
const keypair3 = Keypair.generate();
const keypair4 = Keypair.generate();
const sigs = [
sks[0].signMessage(msgs[0]),
sks[1].signMessage(msgs[0]),
sks[2].signMessage(msgs[1]),
sks[3].signMessage(msgs[1]),
];
const message1 = Buffer.from(SHA256.digest(Buffer.from("Test1")));
const message2 = Buffer.from(SHA256.digest(Buffer.from("Test2")));
const aggPubkeys = [
bls.aggregatePubkeys([pks[0], pks[1]].map((pk) => pk.toBytes())),
bls.aggregatePubkeys([pks[2], pks[3]].map((pk) => pk.toBytes())),
];
const signature1 = keypair1.privateKey.signMessage(message1);
const signature2 = keypair2.privateKey.signMessage(message1);
const signature3 = keypair3.privateKey.signMessage(message2);
const signature4 = keypair4.privateKey.signMessage(message2);
const aggSig = bls.aggregateSignatures(sigs.map((sig) => sig.toBytes()));
const aggregatePubKey12 = aggregatePubkeys([keypair1.publicKey.toBytes(), keypair2.publicKey.toBytes()]);
const aggregatePubKey34 = aggregatePubkeys([keypair3.publicKey.toBytes(), keypair4.publicKey.toBytes()]);
const aggregateSignature = aggregateSignatures([
signature1.toBytes(),
signature2.toBytes(),
signature3.toBytes(),
signature4.toBytes(),
]);
const result = verifyMultiple([aggregatePubKey12, aggregatePubKey34], [message1, message2], aggregateSignature);
expect(result).to.be.true;
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;
});
it("should verify aggregated signatures - same message", function () {
this.timeout(5000);
it("should verify aggregated signatures - same message", () => {
const n = 4;
const msg = randomMessage();
const sks = getN(n, () => bls.PrivateKey.fromKeygen());
const pks = sks.map((sk) => sk.toPublicKey());
const sigs = sks.map((sk) => sk.signMessage(msg));
const keypair1 = Keypair.generate();
const keypair2 = Keypair.generate();
const keypair3 = Keypair.generate();
const keypair4 = Keypair.generate();
const aggregateSignature = bls.aggregateSignatures(sigs.map((sig) => sig.toBytes()));
const message = Buffer.from(SHA256.digest(Buffer.from("Test1")));
const signature1 = keypair1.privateKey.signMessage(message);
const signature2 = keypair2.privateKey.signMessage(message);
const signature3 = keypair3.privateKey.signMessage(message);
const signature4 = keypair4.privateKey.signMessage(message);
const aggregateSignature = aggregateSignatures([
signature1.toBytes(),
signature2.toBytes(),
signature3.toBytes(),
signature4.toBytes(),
]);
const result = verifyMultiple(
[
keypair1.publicKey.toBytes(),
keypair2.publicKey.toBytes(),
keypair3.publicKey.toBytes(),
keypair4.publicKey.toBytes(),
],
[message, message, message, message],
const isValid = bls.verifyMultiple(
pks.map((pk) => pk.toBytes()),
getN(4, () => msg), // Same message n times
aggregateSignature
);
expect(result).to.be.true;
});
it("should fail to verify aggregated signatures - swapped messages", function () {
this.timeout(5000);
const keypair1 = Keypair.generate();
const keypair2 = Keypair.generate();
const keypair3 = Keypair.generate();
const keypair4 = Keypair.generate();
const message1 = Buffer.from(SHA256.digest(Buffer.from("Test1")));
const message2 = Buffer.from(SHA256.digest(Buffer.from("Test2")));
const signature1 = keypair1.privateKey.signMessage(message1);
const signature2 = keypair2.privateKey.signMessage(message1);
const signature3 = keypair3.privateKey.signMessage(message2);
const signature4 = keypair4.privateKey.signMessage(message2);
const aggregatePubKey12 = aggregatePubkeys([keypair1.publicKey.toBytes(), keypair2.publicKey.toBytes()]);
const aggregatePubKey34 = aggregatePubkeys([keypair3.publicKey.toBytes(), keypair4.publicKey.toBytes()]);
const aggregateSignature = aggregateSignatures([
signature1.toBytes(),
signature2.toBytes(),
signature3.toBytes(),
signature4.toBytes(),
]);
const result = verifyMultiple([aggregatePubKey12, aggregatePubKey34], [message2, message1], aggregateSignature);
expect(result).to.be.false;
});
it("should fail to verify aggregated signatures - different pubkeys and messsages", () => {
const keypair1 = Keypair.generate();
const keypair2 = Keypair.generate();
const keypair3 = Keypair.generate();
const keypair4 = Keypair.generate();
const message1 = Buffer.from(SHA256.digest(Buffer.from("Test1")));
const message2 = Buffer.from(SHA256.digest(Buffer.from("Test2")));
const signature1 = keypair1.privateKey.signMessage(message1);
const signature2 = keypair2.privateKey.signMessage(message1);
const signature3 = keypair3.privateKey.signMessage(message2);
const signature4 = keypair4.privateKey.signMessage(message2);
const aggregatePubKey12 = aggregatePubkeys([keypair1.publicKey.toBytes(), keypair2.publicKey.toBytes()]);
const aggregateSignature = aggregateSignatures([
signature1.toBytes(),
signature2.toBytes(),
signature3.toBytes(),
signature4.toBytes(),
]);
const result = verifyMultiple([aggregatePubKey12], [message2, message1], aggregateSignature);
expect(result).to.be.false;
expect(isValid).to.be.true;
});
it("should fail to verify aggregated signatures - no public keys", () => {
const signature = Buffer.alloc(96);
const sig = Buffer.alloc(96);
const msg1 = randomMessage();
const msg2 = randomMessage();
const message1 = Buffer.from(SHA256.digest(Buffer.from("Test1")));
const message2 = Buffer.from(SHA256.digest(Buffer.from("Test2")));
const result = verifyMultiple([], [message2, message1], signature);
expect(result).to.be.false;
const isValid = bls.verifyMultiple([], [msg2, msg1], sig);
expect(isValid).to.be.false;
});
});
});

View File

@ -1,29 +0,0 @@
import {PrivateKey, PublicKey, Keypair, destroy, initBLS} from "../../src";
import {expect} from "chai";
describe("keypair", function () {
before(async function () {
await initBLS();
});
after(function () {
destroy();
});
it("should create from private and public key", () => {
const secret = PrivateKey.fromKeygen();
const secret2 = PrivateKey.fromKeygen();
const publicKey = PublicKey.fromBytes(secret2.toPublicKey().toBytes());
const keypair = new Keypair(secret, publicKey);
expect(keypair.publicKey).to.be.equal(publicKey);
expect(keypair.privateKey).to.be.equal(secret);
expect(keypair.privateKey).to.not.be.equal(secret2);
});
it("should create from private", () => {
const secret = PrivateKey.fromKeygen();
const publicKey = secret.toPublicKey();
const keypair = new Keypair(secret);
expect(keypair.publicKey.toBytes().toString("hex")).to.be.equal(publicKey.toBytes().toString("hex"));
});
});

View File

@ -1,36 +1,26 @@
import {PrivateKey, initBLS, destroy, SECRET_KEY_LENGTH} from "../../src";
import {expect} from "chai";
import {forEachImplementation} from "../switch";
describe("privateKey", function () {
before(async function () {
await initBLS();
});
forEachImplementation((bls) => {
describe("PrivateKey", () => {
it("should generate fromKeygen private key", () => {
const privateKey1 = bls.PrivateKey.fromKeygen();
const privateKey2 = bls.PrivateKey.fromKeygen();
expect(privateKey1.toHex()).to.not.be.equal(privateKey2.toHex());
});
after(function () {
destroy();
});
it("should generate fromKeygen private key", function () {
const privateKey1 = PrivateKey.fromKeygen();
const privateKey2 = PrivateKey.fromKeygen();
expect(privateKey1.toHex()).to.not.be.equal(privateKey2.toHex());
});
it("should export private key to hex string", function () {
const privateKey = "0x07656fd676da43883d163f49566c72b9cbf0a5a294f26808c807700732456da7";
expect(PrivateKey.fromHex(privateKey).toHex()).to.be.equal(privateKey);
it("should export private key to hex string", () => {
expect(bls.PrivateKey.fromHex(privateKey).toHex()).to.be.equal(privateKey);
});
const privateKey2 = "07656fd676da43883d163f49566c72b9cbf0a5a294f26808c807700732456da7";
it("should export private key to hex string from non-prefixed hex", () => {
expect(bls.PrivateKey.fromHex(privateKey.replace("0x", "")).toHex()).to.be.equal(privateKey);
});
expect(PrivateKey.fromHex(privateKey2).toHex()).to.be.equal(privateKey);
});
it("should export private key to bytes", function () {
expect(PrivateKey.fromKeygen().toBytes().length).to.be.equal(SECRET_KEY_LENGTH);
});
it("should not accept too short private key", function () {
expect(() => PrivateKey.fromHex("0x2123")).to.throw();
it("should not accept too short private key", () => {
expect(() => bls.PrivateKey.fromHex("0x2123")).to.throw();
});
});
});

View File

@ -1,28 +1,21 @@
import {PublicKey, PrivateKey, initBLS, destroy} from "../../src";
import {expect} from "chai";
import {forEachImplementation} from "../switch";
describe("public key", function () {
before(async function f() {
await initBLS();
});
after(function () {
destroy();
});
it("from hex", function () {
forEachImplementation((bls) => {
describe("PublicKey", () => {
const publicKey =
"0xb6f21199594b56d77670564bf422cb331d5281ca2c1f9a45588a56881d8287ef8619efa6456d6cd2ef61306aa5b21311";
expect(PublicKey.fromHex(publicKey).toHex()).to.be.equal(publicKey);
});
it("from bytes", function () {
const publicKey =
"b6f21199594b56d77670564bf422cb331d5281ca2c1f9a45588a56881d8287ef8619efa6456d6cd2ef61306aa5b21311";
expect(PublicKey.fromBytes(Buffer.from(publicKey, "hex")).toHex()).to.be.equal(`0x${publicKey}`);
});
it("should export public key to hex string", () => {
expect(bls.PublicKey.fromHex(publicKey).toHex()).to.be.equal(publicKey);
});
it("from private key", function () {
PrivateKey.fromKeygen().toPublicKey();
it("should export public key to hex string from non-prefixed hex", () => {
expect(bls.PublicKey.fromHex(publicKey.replace("0x", "")).toHex()).to.be.equal(publicKey);
});
it("from private key", () => {
bls.PrivateKey.fromKeygen().toPublicKey();
});
});
});

View File

@ -2,11 +2,6 @@
# yarn lockfile v1
"@assemblyscript/loader@^0.9.2":
version "0.9.2"
resolved "https://registry.yarnpkg.com/@assemblyscript/loader/-/loader-0.9.2.tgz#5e1563c9bb5839ff9c8b3ae667e9255d8a166a37"
integrity sha512-fSt+ARVyhRwtqYUcFaLP2LUQ3DRHTsE6V9I2Iw7xaxop1ryePEaCcctIzHspvthww/2RVgtBIbmf/ICDZWkcLw==
"@babel/cli@^7.8.4":
version "7.8.4"
resolved "https://registry.yarnpkg.com/@babel/cli/-/cli-7.8.4.tgz#505fb053721a98777b2b175323ea4f090b7d3c1c"
@ -791,14 +786,6 @@
lodash "^4.17.13"
to-fast-properties "^2.0.0"
"@chainsafe/as-sha256@0.2.0":
version "0.2.0"
resolved "https://registry.yarnpkg.com/@chainsafe/as-sha256/-/as-sha256-0.2.0.tgz#3ebe061d59d30af9e95a8c22ff4813cbf0e89dbc"
integrity sha512-reKklZhY4jSj7JdxdAjUfsaiMt2pdm8V/IqlOR5c4m6Y4tRCxt4f0HBMfyiE2ZQF4tqPPqRVf/ulXwK+LjLIxw==
dependencies:
"@assemblyscript/loader" "^0.9.2"
buffer "^5.4.3"
"@chainsafe/bls-hd-key@^0.1.0":
version "0.1.0"
resolved "https://registry.yarnpkg.com/@chainsafe/bls-hd-key/-/bls-hd-key-0.1.0.tgz#5e51de16801f4b4b421e418f0d1ef0692df0c585"