diff --git a/src/helpers/g1point.ts b/src/helpers/g1point.ts index 7133f3b..7a52a5f 100644 --- a/src/helpers/g1point.ts +++ b/src/helpers/g1point.ts @@ -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()); } diff --git a/src/index.ts b/src/index.ts index 2888c71..5a69466 100644 --- a/src/index.ts +++ b/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,20 +96,50 @@ 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); - eCombined.mul( - ElipticCurvePairing.pair( - PublicKey.fromBytes(publicKey).getPoint(), - g2 - ) - ); - }); - const e2 = ElipticCurvePairing.pair(g1Generated, Signature.fromCompressedBytes(signature).getPoint()); + + 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( + previous.publicKey, + g2 + ) + ); + 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) { + console.log(e); return false; } } diff --git a/test/spec/spec-tests b/test/spec/spec-tests index 7567342..15a1d85 160000 --- a/test/spec/spec-tests +++ b/test/spec/spec-tests @@ -1 +1 @@ -Subproject commit 7567342c966c4e020f6ab3889f93cedb65ea9bfe +Subproject commit 15a1d85125682d3ffc09a1ee9639f46c4a1653f6 diff --git a/test/unit/index.test.ts b/test/unit/index.test.ts index a8416e4..ee819f2 100644 --- a/test/unit/index.test.ts +++ b/test/unit/index.test.ts @@ -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);