From 2d0725a5cc35834c038193a4218c5e88be4ca5ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marin=20Petruni=C4=87?= Date: Wed, 27 Nov 2019 21:58:41 +0100 Subject: [PATCH] rewritten bls to use wasm --- package.json | 4 +- src/@types/secure-random/index.d.ts | 3 - src/ctx.ts | 3 - src/helpers/ec-pairing.ts | 14 - src/helpers/g1point.ts | 134 ---------- src/helpers/g2point.ts | 250 ------------------ src/helpers/utils.ts | 22 +- src/index.ts | 93 ++----- src/keypair.ts | 4 +- src/privateKey.ts | 18 +- src/publicKey.ts | 50 +++- src/signature.ts | 53 +++- test/benchmarks/index.ts | 10 +- test/benchmarks/suites/index.ts | 8 +- .../benchmarks/suites/signatureAggregation.ts | 1 + .../suites/verifyInValidSignature.ts | 1 + .../verifyInvalidAggregatedSignature.ts | 1 + .../suites/verifyValidAggregatedSignature.ts | 4 +- .../benchmarks/suites/verifyValidSignature.ts | 1 + test/spec/aggregate_pubkeys.test.ts | 6 +- test/spec/aggregate_sigs.test.ts | 6 +- test/spec/msg_hash_compressed.test.ts | 74 +++--- test/spec/msg_hash_uncompressed.test.ts | 86 +++--- test/spec/priv_to_public.test.ts | 6 +- test/spec/sign_message.test.ts | 6 +- test/unit/helpers/g1point.test.ts | 109 -------- test/unit/helpers/g2point.test.ts | 153 ----------- test/unit/helpers/utils.test.ts | 27 -- test/unit/index.test.ts | 91 ++++--- test/unit/keypair.test.ts | 43 +-- test/unit/publicKey.test.ts | 33 +++ 31 files changed, 351 insertions(+), 963 deletions(-) delete mode 100644 src/@types/secure-random/index.d.ts delete mode 100644 src/ctx.ts delete mode 100644 src/helpers/ec-pairing.ts delete mode 100644 src/helpers/g1point.ts delete mode 100644 src/helpers/g2point.ts delete mode 100644 test/unit/helpers/g1point.test.ts delete mode 100644 test/unit/helpers/g2point.test.ts delete mode 100644 test/unit/helpers/utils.test.ts create mode 100644 test/unit/publicKey.test.ts diff --git a/package.json b/package.json index 0f7cb9c..a103bfe 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@chainsafe/bls", - "version": "0.1.7", + "version": "0.2.0", "description": "Implementation of bls signature verification for ethereum 2.0", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -23,7 +23,7 @@ "build": "yarn build-lib && yarn build-types", "build:release": "yarn clean && yarn build && yarn build-web", "build-lib": "babel src -x .ts -d lib", - "build-types": "tsc --declaration --incremental --outDir lib --emitDeclarationOnly", + "build-types": "tsc --declaration --project tsconfig.build.json --incremental --outDir lib --emitDeclarationOnly", "build-web": "webpack --mode production --entry ./lib/web.js --output ./dist/bls.min.js", "check-types": "tsc --noEmit", "lint": "eslint --ext .ts src/", diff --git a/src/@types/secure-random/index.d.ts b/src/@types/secure-random/index.d.ts deleted file mode 100644 index 8bc37d3..0000000 --- a/src/@types/secure-random/index.d.ts +++ /dev/null @@ -1,3 +0,0 @@ -declare module "secure-random" { - export function randomBuffer(length: number): Buffer; -} diff --git a/src/ctx.ts b/src/ctx.ts deleted file mode 100644 index 9939f27..0000000 --- a/src/ctx.ts +++ /dev/null @@ -1,3 +0,0 @@ -import CTX from "@chainsafe/milagro-crypto-js"; - -export default new CTX("BLS381"); diff --git a/src/helpers/ec-pairing.ts b/src/helpers/ec-pairing.ts deleted file mode 100644 index c2d2bef..0000000 --- a/src/helpers/ec-pairing.ts +++ /dev/null @@ -1,14 +0,0 @@ -import {G1point} from "./g1point"; -import {G2point} from "./g2point"; -import {FP12} from "@chainsafe/milagro-crypto-js/src/fp12"; -import ctx from "../ctx"; - - -export class ElipticCurvePairing { - - public static pair(p1: G1point, p2: G2point): FP12 { - const e = ctx.PAIR.ate(p2.getPoint(), p1.getPoint()); - return ctx.PAIR.fexp(e); - } - -} diff --git a/src/helpers/g1point.ts b/src/helpers/g1point.ts deleted file mode 100644 index 538194a..0000000 --- a/src/helpers/g1point.ts +++ /dev/null @@ -1,134 +0,0 @@ -import {BIG} from "@chainsafe/milagro-crypto-js/src/big"; -import {ECP} from "@chainsafe/milagro-crypto-js/src/ecp"; -import ctx from "../ctx"; -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"; - -export class G1point { - - private point: ECP; - - public constructor(point: ECP) { - this.point = point; - } - - public static fromBytesCompressed(value: bytes48): G1point { - assert(value.length === FP_POINT_LENGTH, `Expected g1 compressed input to have ${FP_POINT_LENGTH} bytes`); - value = Buffer.from(value); - const aIn = (value[0] & (1 << 5)) != 0; - const bIn = (value[0] & (1 << 6)) != 0; - const cIn = (value[0] & (1 << 7)) != 0; - value[0] &= 31; - - if (!cIn) { - throw new Error("The serialised input does not have the C flag set."); - } - - const x = ctx.BIG.frombytearray(value, 0); - if (bIn) { - if (!aIn && x.iszilch()) { - // This is a correctly formed serialisation of infinity - return new G1point(new ctx.ECP()); - } else { - // The input is malformed - throw new Error( - "The serialised input has B flag set, but A flag is set, or X is non-zero."); - } - } - const modulus = getModulus(); - if (ctx.BIG.comp(modulus, x) <= 0) { - throw new Error("X coordinate is too large."); - } - - const point = new ctx.ECP(); - point.setx(x); - - if (point.is_infinity()) { - throw new Error("X coordinate is not on the curve."); - } - - // Did we get the right branch of the sqrt? - if (!point.is_infinity() && aIn != calculateYFlag(point.getY())) { - // We didn't: so choose the other branch of the sqrt. - const x = new ctx.FP(point.getX()); - const yneg = new ctx.FP(point.getY()); - yneg.neg(); - point.setxy(x.redc(), yneg.redc()); - } - - 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()); - } - - public static random(): G1point { - let ecp: ECP; - do { - ecp = new ctx.ECP(); - ecp.setx( - ctx.BIG.frombytearray( - random.randomBuffer(FP_POINT_LENGTH), - 0 - ) - ); - } while (ecp.is_infinity()); - return new G1point(ecp); - } - - public mul(value: BIG): G1point { - const newPoint = this.point.mul(value); - return new G1point(newPoint); - } - - public add(other: G1point): G1point { - const sum = new ctx.ECP(); - sum.add(this.point); - sum.add(other.point); - sum.affine(); - 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); - } - - public toBytes(): bytes48 { - const buffer = Buffer.alloc(FP_POINT_LENGTH, 0); - this.point.getX().tobytearray(buffer, 0); - return buffer; - } - - public getPoint(): ECP { - return this.point; - } - - public toBytesCompressed(): bytes48 { - const output = this.toBytes(); - const c = true; - const b = this.point.is_infinity(); - const a = !b && calculateYFlag(this.point.getY()); - - const flags = ((a ? 1 << 5 : 0) | (b ? 1 << 6 : 0) | (c ? 1 << 7 : 0)); - const mask = 31; - output[0] &= mask; - output[0] |= flags; - return output; - } -} diff --git a/src/helpers/g2point.ts b/src/helpers/g2point.ts deleted file mode 100644 index c5f6421..0000000 --- a/src/helpers/g2point.ts +++ /dev/null @@ -1,250 +0,0 @@ -import {BIG} from "@chainsafe/milagro-crypto-js/src/big"; -import {ECP2} from "@chainsafe/milagro-crypto-js/src/ecp2"; -import {sha256} from "js-sha256"; -import ctx from "../ctx"; -import * as random from "secure-random"; -import {calculateYFlag, getModulus, padLeft} from "./utils"; -import assert from "assert"; -import {FP_POINT_LENGTH, G2_HASH_PADDING} from "../constants"; -import {bytes48, Domain, Hash} from "@chainsafe/eth2.0-types"; - -export class G2point { - - private point: ECP2; - - public constructor(point: ECP2) { - this.point = point; - } - - - public static hashToG2(message: Hash, domain: Domain): G2point { - const padding = Buffer.alloc(G2_HASH_PADDING, 0); - const xReBytes = Buffer.concat([ - padding, - Buffer.from(sha256.arrayBuffer( - Buffer.concat([ - message, - padLeft(domain, 8), - Buffer.from("01", "hex") - ]) - )) - ]); - const xImBytes = Buffer.concat([ - padding, - Buffer.from(sha256.arrayBuffer( - Buffer.concat([ - message, - padLeft(domain, 8), - Buffer.from("02", "hex") - ]) - )) - ]); - const xRe = ctx.BIG.frombytearray(xReBytes, 0); - const xIm = ctx.BIG.frombytearray(xImBytes, 0); - const one = new ctx.BIG(1); - let point = new ctx.ECP2(); - point.setx(new ctx.FP2(xRe, xIm)); - while (point.is_infinity()) { - xRe.add(one); - xRe.norm(); - point = new ctx.ECP2(); - point.setx(new ctx.FP2(xRe, xIm)); - } - return new G2point(G2point.scaleWithCofactor(G2point.normaliseY(point))); - } - - public static fromCompressedBytes(value: bytes48): G2point { - assert(value.length === 2 * FP_POINT_LENGTH, "Expected signature of 96 bytes"); - value = Buffer.from(value); - const xImBytes = value.slice(0, FP_POINT_LENGTH); - const xReBytes = value.slice(FP_POINT_LENGTH); - const aIn = (xImBytes[0] & (1 << 5)) != 0; - const bIn = (xImBytes[0] & (1 << 6)) != 0; - const cIn = (xImBytes[0] & (1 << 7)) != 0; - //clear bits - xImBytes[0] &= 31; - if((xReBytes[0] & 224) != 0) { - throw new Error("The input has non-zero a2, b2 or c2 flag on xRe"); - } - if(!cIn) { - throw new Error("The serialised input does not have the C flag set."); - } - const xIm = ctx.BIG.frombytearray(xImBytes, 0); - const xRe = ctx.BIG.frombytearray(xReBytes, 0); - if (bIn) { - if (!aIn - && xIm.iszilch() - && xRe.iszilch() ) { - // This is a correctly formed serialisation of infinity - return new G2point(new ctx.ECP2()); - } else { - // The input is malformed - throw new Error( - "The serialised input has B flag set, but A flag is set, or X is non-zero."); - } - } - - const modulus = getModulus(); - if(ctx.BIG.comp(modulus, xRe) <= 0 || ctx.BIG.comp(modulus, xIm) <= 0) { - throw new Error( - "The deserialised X real or imaginary coordinate is too large."); - } - - const point = new ctx.ECP2(); - point.setx(new ctx.FP2(xRe, xIm)); - if(point.is_infinity()) { - throw new Error("X coordinate is not on the curve."); - } - - if (!point.is_infinity() && aIn != calculateYFlag(point.getY().getB())) { - // We didn't: so choose the other branch of the sqrt. - const x = point.getX(); - const yneg = point.getY(); - yneg.neg(); - point.setxy(x, yneg); - } - - return new G2point(point); - } - - public static fromUncompressedInput( - xReBytes: Buffer, - xImBytes: Buffer, - yReBytes: Buffer, - yImBytes: Buffer, - zReBytes: Buffer, - zImBytes: Buffer): G2point { - const xRe = ctx.BIG.frombytearray(padLeft(xReBytes, FP_POINT_LENGTH), 0); - const xIm = ctx.BIG.frombytearray(padLeft(xImBytes, FP_POINT_LENGTH), 0); - const yRe = ctx.BIG.frombytearray(padLeft(yReBytes, FP_POINT_LENGTH), 0); - const yIm = ctx.BIG.frombytearray(padLeft(yImBytes, FP_POINT_LENGTH), 0); - const zRe = ctx.BIG.frombytearray(padLeft(zReBytes, FP_POINT_LENGTH), 0); - const zIm = ctx.BIG.frombytearray(padLeft(zImBytes, FP_POINT_LENGTH), 0); - const x = new ctx.FP2(xRe, xIm); - const y = new ctx.FP2(yRe, yIm); - const z = new ctx.FP2(zRe, zIm); - z.inverse(); - x.mul(z); - x.reduce(); - y.mul(z); - y.reduce(); - const point = new ctx.ECP2(); - point.setxy(x, y); - return new G2point(point); - } - - public static random(): G2point { - let point: ECP2; - do { - point = new ctx.ECP2(); - point.setx( - new ctx.FP2( - ctx.BIG.frombytearray( - random.randomBuffer(FP_POINT_LENGTH), - 0 - ), - ctx.BIG.frombytearray( - random.randomBuffer(FP_POINT_LENGTH), - 0 - ) - ) - ); - } while (point.is_infinity()); - return new G2point(point); - } - - public static scaleWithCofactor(point: ECP2): ECP2 { - const upper = ctx.BIG.frombytearray( - Buffer.from( - "0000000000000000000000000000000005d543a95414e7f1091d50792876a202cd91de4547085abaa68a205b2e5a7ddf", - "hex" - ), - 0 - ); - const lower = ctx.BIG.frombytearray( - Buffer.from( - "00000000000000000000000000000000a628f1cb4d9e82ef21537e293a6691ae1616ec6e786f0c70cf1c38e31c7238e5", - "hex" - ), - 0 - ); - const shift = ctx.BIG.frombytearray( - Buffer.from( - "000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000", - "hex" - ), - 0 - ); - let sum = new ctx.ECP2(); - sum.copy(point); - sum = sum.mul(upper); - sum = sum.mul(shift); - - let tmp = new ctx.ECP2(); - tmp.copy(point); - tmp = tmp.mul(lower); - - sum.add(tmp); - - return sum; - } - - public static normaliseY(point: ECP2): ECP2 { - const y = point.getY(); - const yNeg = new ctx.FP2(y); - yNeg.neg(); - if (ctx.BIG.comp(y.getB(), yNeg.getB()) < 0 - || ((ctx.BIG.comp(y.getB(), yNeg.getB()) == 0) - && ctx.BIG.comp(y.getA(), yNeg.getA()) < 0) - ) { - const newPoint = new ctx.ECP2(); - newPoint.setxy(point.getX(), yNeg); - return newPoint; - } else { - return point; - } - } - - public add(other: G2point): G2point { - const sum = new ctx.ECP2(); - sum.add(this.point); - sum.add(other.point); - sum.affine(); - return new G2point(sum); - } - - public mul(value: BIG): G2point { - const newPoint = this.point.mul(value); - return new G2point(newPoint); - } - - public equal(other: G2point): boolean { - return this.point.equals(other.point); - } - - public getPoint(): ECP2 { - return this.point; - } - - public toBytesCompressed(): Buffer { - const xReBytes = Buffer.alloc(FP_POINT_LENGTH, 0); - const xImBytes = Buffer.alloc(FP_POINT_LENGTH, 0); - this.point.getX().getA().tobytearray(xReBytes, 0); - this.point.getX().getB().tobytearray(xImBytes, 0); - const c1 = true; - const b1 = this.point.is_infinity(); - const a1 = !b1 && calculateYFlag(this.point.getY().getB()); - - const flags = ((a1 ? 1 << 5 : 0) | (b1 ? 1 << 6 : 0) | (c1 ? 1 << 7 : 0)); - const mask = 31; - xImBytes[0] &= mask; - xImBytes[0] |= flags; - xReBytes[0] &= mask; - - return Buffer.concat([ - xImBytes, - xReBytes - ]); - } - -} diff --git a/src/helpers/utils.ts b/src/helpers/utils.ts index 64ad115..6278266 100644 --- a/src/helpers/utils.ts +++ b/src/helpers/utils.ts @@ -1,6 +1,4 @@ import assert from "assert"; -import {BIG} from "@chainsafe/milagro-crypto-js/src/big"; -import ctx from "../ctx"; /** * Pads byte array with zeroes on left side up to desired length. @@ -13,22 +11,4 @@ export function padLeft(source: Buffer, length: number): Buffer { const result = Buffer.alloc(length, 0); source.copy(result, length - source.length); return result; -} - -//TODO: find a way to convert ctx.ROM_FIELD.MODULUS to BIG (MODULUS basebit = 58, BIG basebit=23 -export function getModulus(): BIG { - return ctx.BIG.frombytearray( - Buffer.from( - "1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab", - "hex" - ), - 0 - ); -} - -export function calculateYFlag(yIm: BIG): boolean { - const tmp = new ctx.BIG(yIm); - tmp.add(yIm); - tmp.div(getModulus()); - return tmp.isunity(); -} +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 155275d..7275111 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,15 +1,17 @@ import {Keypair} from "./keypair"; import {PrivateKey} from "./privateKey"; -import {G2point} from "./helpers/g2point"; -import {G1point} from "./helpers/g1point"; import {PublicKey} from "./publicKey"; import {Signature} from "./signature"; -import {ElipticCurvePairing} from "./helpers/ec-pairing"; -import ctx from "./ctx"; import {BLSPubkey, BLSSecretKey, BLSSignature, Domain, Hash} from "@chainsafe/eth2.0-types"; - +import {init} from "./context"; +import {PUBLIC_KEY_LENGTH} from "./constants"; export {Keypair, PrivateKey, PublicKey, Signature}; + +export async function initLibrary(): Promise { + await init(); +} + /** * Generates new secret and public key */ @@ -34,8 +36,7 @@ export function generatePublicKey(secretKey: BLSSecretKey): BLSPubkey { */ export function sign(secretKey: BLSSecretKey, messageHash: Hash, domain: Domain): BLSSignature { const privateKey = PrivateKey.fromBytes(secretKey); - const hash = G2point.hashToG2(messageHash, domain); - return privateKey.sign(hash).toBytesCompressed(); + return privateKey.signMessage(messageHash, domain).toBytesCompressed(); } /** @@ -56,9 +57,16 @@ export function aggregateSignatures(signatures: BLSSignature[]): BLSSignature { */ export function aggregatePubkeys(publicKeys: BLSPubkey[]): BLSPubkey { if(publicKeys.length === 0) { - return new G1point(new ctx.ECP()).toBytesCompressed(); + return Buffer.alloc(PUBLIC_KEY_LENGTH); } - return G1point.aggregate(publicKeys).toBytesCompressed(); + return publicKeys.map(PublicKey.fromBytes).reduce((agg, pubKey) => { + if(agg) { + return agg.add(pubKey); + } else { + return pubKey; + } + } + ).toBytesCompressed(); } /** @@ -70,15 +78,9 @@ export function aggregatePubkeys(publicKeys: BLSPubkey[]): BLSPubkey { */ export function verify(publicKey: BLSPubkey, messageHash: Hash, signature: BLSSignature, domain: Domain): boolean { try { - 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()); - return e1.equals(e2); + return PublicKey + .fromBytes(publicKey) + .verifyMessage(Signature.fromCompressedBytes(signature), messageHash, domain); } catch (e) { return false; } @@ -101,54 +103,13 @@ export function verifyMultiple( return false; } try { - const sig = Signature.fromCompressedBytes(signature).getPoint(); - sig.getPoint().affine(); - - const eCombined = new ctx.FP12(1); - - // @ts-ignore - const reduction = messageHashes.reduce((previous, current, index) => { - // @ts-ignore - if(previous.hash && current.equals(previous.hash)) { - return { - hash: previous.hash, - // @ts-ignore - publicKey: previous.publicKey ? - // @ts-ignore - previous.publicKey.addRaw(publicKeys[index]) - : - G1point.fromBytesCompressed(publicKeys[index]), - }; - } else if(previous.hash) { - // @ts-ignore - const g2 = G2point.hashToG2(previous.hash, domain); - eCombined.mul( - ElipticCurvePairing.pair( - // @ts-ignore - 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); + return Signature + .fromCompressedBytes(signature) + .verifyMultiple( + publicKeys.map((key) => PublicKey.fromBytes(key)), + messageHashes, + domain + ); } catch (e) { return false; } diff --git a/src/keypair.ts b/src/keypair.ts index 953e7ab..6e2505f 100644 --- a/src/keypair.ts +++ b/src/keypair.ts @@ -4,9 +4,9 @@ import {PrivateKey} from "./privateKey"; export class Keypair { - private _publicKey: PublicKey; + private readonly _publicKey: PublicKey; - private _privateKey: PrivateKey; + private readonly _privateKey: PrivateKey; public constructor(privateKey: PrivateKey, publicKey?: PublicKey) { this._privateKey = privateKey; diff --git a/src/privateKey.ts b/src/privateKey.ts index 26cfcda..6afd832 100644 --- a/src/privateKey.ts +++ b/src/privateKey.ts @@ -1,8 +1,11 @@ import {SECRET_KEY_LENGTH} from "./constants"; import assert from "assert"; import {BLSSecretKey, Domain, Hash} from "@chainsafe/eth2.0-types"; -import {SecretKeyType, SignatureType} from "@chainsafe/eth2-bls-wasm"; +import {SecretKeyType} from "@chainsafe/eth2-bls-wasm"; import {getContext} from "./context"; +import {PublicKey} from "./publicKey"; +import {Signature} from "./signature"; +import {padLeft} from "./helpers/utils"; export class PrivateKey { @@ -38,12 +41,17 @@ export class PrivateKey { return this.value; } - public sign(message: Uint8Array): SignatureType { - return this.value.sign(message); + // public sign(message: Uint8Array): Signature { + // return Signature.fromValue(this.value.sign(message)); + // } + + public signMessage(message: Hash, domain: Domain): Signature { + domain = padLeft(domain, 8); + return Signature.fromValue(this.value.signHashWithDomain(Buffer.concat([message, domain]))); } - public signMessage(message: Hash, domain: Domain): SignatureType { - return this.value.signHashWithDomain(Buffer.concat([message, domain])); + public toPublicKey(): PublicKey { + return PublicKey.fromPublicKeyType(this.value.getPublicKey()); } public toBytes(): BLSSecretKey { diff --git a/src/publicKey.ts b/src/publicKey.ts index 8b16a0b..6fef7ca 100644 --- a/src/publicKey.ts +++ b/src/publicKey.ts @@ -1,36 +1,64 @@ -import {G1point} from "./helpers/g1point"; import {PrivateKey} from "./privateKey"; -import {BLSPubkey} from "@chainsafe/eth2.0-types"; +import {BLSPubkey, BLSSignature, Domain, Hash} from "@chainsafe/eth2.0-types"; +import {PublicKeyType} from "@chainsafe/eth2-bls-wasm"; +import {getContext} from "./context"; +import {PUBLIC_KEY_LENGTH} from "./constants"; +import assert from "assert"; +import {Signature} from "./signature"; export class PublicKey { - private point: G1point; + private value: PublicKeyType; - public constructor(point: G1point) { - this.point = point; + protected constructor(value: PublicKeyType) { + this.value = value; } public static fromPrivateKey(privateKey: PrivateKey): PublicKey { + return privateKey.toPublicKey(); + } + + public static fromBytes(bytes: BLSPubkey): PublicKey { + const context = getContext(); + const publicKey = new context.PublicKey(); + publicKey.deserialize(bytes); return new PublicKey( - G1point.generator().mul(privateKey.getValue()) + publicKey ); } - public static fromBytes(publicKey: BLSPubkey): PublicKey { + public static fromHex(value: string): PublicKey { + value = value.replace("0x", ""); + assert(value.length === PUBLIC_KEY_LENGTH * 2); + const context = getContext(); return new PublicKey( - G1point.fromBytesCompressed(publicKey) + context.deserializeHexStrToPublicKey(value) ); } - public getPoint(): G1point { - return this.point; + public static fromPublicKeyType(value: PublicKeyType): PublicKey { + return new PublicKey(value); + } + + public add(other: PublicKey): PublicKey { + const agg = new PublicKey(this.value.clone()); + agg.value.add(other.value); + return agg; + } + + public verifyMessage(signature: Signature, messageHash: Hash, domain: Domain): boolean { + return this.value.verifyHashWithDomain(signature.getValue(), Buffer.concat([messageHash, domain])); } public toBytesCompressed(): BLSPubkey { - return this.point.toBytesCompressed(); + return Buffer.from(this.value.serialize()); } public toHexString(): string { return `0x${this.toBytesCompressed().toString("hex")}`; } + + public getValue(): PublicKeyType { + return this.value; + } } diff --git a/src/signature.ts b/src/signature.ts index 5259a17..5d62ce1 100644 --- a/src/signature.ts +++ b/src/signature.ts @@ -1,35 +1,64 @@ -import {G2point} from "./helpers/g2point"; import assert from "assert"; import {FP_POINT_LENGTH} from "./constants"; -import {BLSSignature} from "@chainsafe/eth2.0-types"; +import {BLSSignature, Domain, Hash} from "@chainsafe/eth2.0-types"; +import {SignatureType} from "@chainsafe/eth2-bls-wasm"; +import {getContext} from "./context"; +import {PublicKey} from "./publicKey"; +import {padLeft} from "./helpers/utils"; export class Signature { - private point: G2point; + private value: SignatureType; - public constructor(point: G2point) { - this.point = point; + protected constructor(value: SignatureType) { + this.value = value; } - public static fromCompressedBytes(signature: BLSSignature): Signature { + public static fromCompressedBytes(value: BLSSignature): Signature { assert( - signature.length === 2 * FP_POINT_LENGTH, + value.length === 2 * FP_POINT_LENGTH, `Signature must have ${2 * FP_POINT_LENGTH} bytes` ); - return new Signature(G2point.fromCompressedBytes(signature)); + const context = getContext(); + const signature = new context.Signature(); + signature.deserialize(value); + return new Signature(signature); + } + + public static fromValue(signature: SignatureType): Signature { + return new Signature(signature); } public add(other: Signature): Signature { + const agg = this.value.clone(); + agg.add(other.value); return new Signature( - this.point.add(other.point) + agg ); } - public getPoint(): G2point { - return this.point; + public getValue(): SignatureType { + return this.value; + } + + public verify(publicKey: PublicKey, message: Hash, domain: Domain): boolean { + domain = padLeft(domain, 8); + return publicKey.verifyMessage(this, message, domain); + } + + public verifyMultiple(publicKeys: PublicKey[], messages: Hash[], domain: Domain): boolean { + domain = padLeft(domain, 8); + return this.value.verifyAggregatedHashWithDomain( + publicKeys.map((key) => key.getValue()), + messages.map((message) => Buffer.concat([message, domain])) + ); } public toBytesCompressed(): BLSSignature { - return this.point.toBytesCompressed(); + return Buffer.from(this.value.serialize()); + } + + public toHex(): string { + return "0x" + this.value.serializeToHexStr(); } } diff --git a/test/benchmarks/index.ts b/test/benchmarks/index.ts index 5b03fa5..a6094e0 100644 --- a/test/benchmarks/index.ts +++ b/test/benchmarks/index.ts @@ -1,11 +1,13 @@ // Import benchmarks import * as suites from "./suites"; import {createReportDir, runSuite} from "@chainsafe/benchmark-utils"; +import {initLibrary} from "../../src"; // Create file const directory: string = createReportDir(); - -// Run benchmarks -Object.values(suites).forEach((suite) => { - runSuite(suite(directory)); +initLibrary().then(() => { + // Run benchmarks + Object.values(suites).forEach((suite) => { + runSuite(suite(directory)); + }); }); \ No newline at end of file diff --git a/test/benchmarks/suites/index.ts b/test/benchmarks/suites/index.ts index 1648eaf..9af379d 100644 --- a/test/benchmarks/suites/index.ts +++ b/test/benchmarks/suites/index.ts @@ -1,5 +1,5 @@ -// export {verifyInValidSignatureBenchmark} from './verifyInValidSignature'; -// export {verifyValidSignatureBenchmark} from './verifyValidSignature'; +export {verifyInValidSignatureBenchmark} from './verifyInValidSignature'; +export {verifyValidSignatureBenchmark} from './verifyValidSignature'; export {verifyValidAggregatedSignature} from './verifyValidAggregatedSignature'; -// export {verifyInvalidAggregatedSignature} from './verifyInvalidAggregatedSignature'; -// export {aggregateSignaturesBenchmark} from './signatureAggregation'; \ No newline at end of file +export {verifyInvalidAggregatedSignature} from './verifyInvalidAggregatedSignature'; +export {aggregateSignaturesBenchmark} from './signatureAggregation'; \ No newline at end of file diff --git a/test/benchmarks/suites/signatureAggregation.ts b/test/benchmarks/suites/signatureAggregation.ts index ad58d51..b7d6e38 100644 --- a/test/benchmarks/suites/signatureAggregation.ts +++ b/test/benchmarks/suites/signatureAggregation.ts @@ -25,6 +25,7 @@ export function aggregateSignaturesBenchmark(dir: string): BenchSuite { }; return { + name: FUNCTION_NAME, testFunctions: [aggregateSignatures], setup: function() { global.signatures = []; diff --git a/test/benchmarks/suites/verifyInValidSignature.ts b/test/benchmarks/suites/verifyInValidSignature.ts index 6cd4106..2091d9f 100644 --- a/test/benchmarks/suites/verifyInValidSignature.ts +++ b/test/benchmarks/suites/verifyInValidSignature.ts @@ -27,6 +27,7 @@ export function verifyInValidSignatureBenchmark(dir: string): BenchSuite { }; return { + name: FUNCTION_NAME, testFunctions: [verifyInValidSignature], setup: function() { const {Keypair} = require("../../../src"); diff --git a/test/benchmarks/suites/verifyInvalidAggregatedSignature.ts b/test/benchmarks/suites/verifyInvalidAggregatedSignature.ts index 32e4a33..5137c39 100644 --- a/test/benchmarks/suites/verifyInvalidAggregatedSignature.ts +++ b/test/benchmarks/suites/verifyInvalidAggregatedSignature.ts @@ -27,6 +27,7 @@ export function verifyInvalidAggregatedSignature(dir: string): BenchSuite { }; return { + name: FUNCTION_NAME, testFunctions: [verifyInvalidAggregatedSignature], setup: function() { const {Keypair, aggregateSignatures} = require("../../../src"); diff --git a/test/benchmarks/suites/verifyValidAggregatedSignature.ts b/test/benchmarks/suites/verifyValidAggregatedSignature.ts index 5bca5f1..d514508 100644 --- a/test/benchmarks/suites/verifyValidAggregatedSignature.ts +++ b/test/benchmarks/suites/verifyValidAggregatedSignature.ts @@ -32,13 +32,13 @@ export function verifyValidAggregatedSignature(dir: string): BenchSuite { const FUNCTION_NAME = "verifyValidAggregatedSignature"; // PLEASE FILL THIS OUT const verifyValidAggregatedSignature = function (): void { - global.verify(global.publicKeys, global.messages, global.signature, global.domain) + global.verify(global.publicKeys, global.messages, global.signature, global.domain); }; return { testFunctions: [verifyValidAggregatedSignature], setup: function() { - const sha256 = require('js-sha256'); + const sha256 = require("js-sha256"); const {aggregateSignatures} = require("../../../src"); const message = Buffer.from(sha256.arrayBuffer(Math.random().toString(36))); const signatures = []; diff --git a/test/benchmarks/suites/verifyValidSignature.ts b/test/benchmarks/suites/verifyValidSignature.ts index a57d766..d77a2bb 100644 --- a/test/benchmarks/suites/verifyValidSignature.ts +++ b/test/benchmarks/suites/verifyValidSignature.ts @@ -29,6 +29,7 @@ export function verifyValidSignatureBenchmark(dir: string): BenchSuite { }; return { + name: FUNCTION_NAME, testFunctions: [verifyValidSignature], setup: function() { const {Keypair} = require("../../../src"); diff --git a/test/spec/aggregate_pubkeys.test.ts b/test/spec/aggregate_pubkeys.test.ts index 753471d..94ee622 100644 --- a/test/spec/aggregate_pubkeys.test.ts +++ b/test/spec/aggregate_pubkeys.test.ts @@ -1,4 +1,4 @@ -import bls from "../../src"; +import bls, {initLibrary} from "../../src"; import {describeDirectorySpecTest, InputType} from "@chainsafe/eth2.0-spec-test-util/lib/single"; import path from "path"; @@ -9,6 +9,10 @@ interface IAggregatePubKeysTestCase { }; } +before(async function f() { + await initLibrary(); +}); + describeDirectorySpecTest( "aggregate pubkeys", path.join( diff --git a/test/spec/aggregate_sigs.test.ts b/test/spec/aggregate_sigs.test.ts index 62c84aa..fb78785 100644 --- a/test/spec/aggregate_sigs.test.ts +++ b/test/spec/aggregate_sigs.test.ts @@ -1,5 +1,5 @@ import path from "path"; -import bls from "../../src"; +import bls, {initLibrary} from "../../src"; import {describeDirectorySpecTest, InputType} from "@chainsafe/eth2.0-spec-test-util/lib/single"; interface AggregateSigsTestCase { @@ -9,6 +9,10 @@ interface AggregateSigsTestCase { }; } +before(async function f() { + await initLibrary(); +}); + describeDirectorySpecTest( "aggregate sigs", path.join( diff --git a/test/spec/msg_hash_compressed.test.ts b/test/spec/msg_hash_compressed.test.ts index ef758dd..1fdd651 100644 --- a/test/spec/msg_hash_compressed.test.ts +++ b/test/spec/msg_hash_compressed.test.ts @@ -1,38 +1,36 @@ -import path from "path"; -import {padLeft} from "../../src/helpers/utils"; -import {G2point} from "../../src/helpers/g2point"; -import {describeDirectorySpecTest, InputType} from "@chainsafe/eth2.0-spec-test-util/lib/single"; - -interface IMsgHHashCOmpressed { - data: { - input: { - message: string; - domain: string; - }; - output: string[]; - }; -} - -describeDirectorySpecTest( - "msg_hash_compressed", - path.join( - __dirname, - "../../../../node_modules/@chainsafe/eth2-spec-tests/tests/general/phase0/bls/msg_hash_compressed/small" - ), - (testCase => { - const domain = padLeft(Buffer.from(testCase.data.input.domain.replace("0x", ""), "hex"), 8); - const input = Buffer.from(testCase.data.input.message.replace("0x", ""), "hex"); - const result = G2point.hashToG2(input, domain); - return `0x${result.toBytesCompressed().toString("hex")}`; - }), - { - inputTypes: { - data: InputType.YAML, - }, - getExpected: (testCase => { - const xReExpected = padLeft(Buffer.from(testCase.data.output[0].replace("0x", ""), "hex"), 48); - const xImExpected = padLeft(Buffer.from(testCase.data.output[1].replace("0x", ""), "hex"), 48); - return "0x" + Buffer.concat([xReExpected, xImExpected]).toString("hex"); - }) - } -); \ No newline at end of file +// import path from "path"; +// import {describeDirectorySpecTest, InputType} from "@chainsafe/eth2.0-spec-test-util/lib/single"; +// +// interface IMsgHHashCOmpressed { +// data: { +// input: { +// message: string; +// domain: string; +// }; +// output: string[]; +// }; +// } +// +// describeDirectorySpecTest( +// "msg_hash_compressed", +// path.join( +// __dirname, +// "../../../../node_modules/@chainsafe/eth2-spec-tests/tests/general/phase0/bls/msg_hash_compressed/small" +// ), +// (testCase => { +// const domain = Buffer.from(testCase.data.input.domain.replace("0x", ""), "hex"); +// const input = Buffer.from(testCase.data.input.message.replace("0x", ""), "hex"); +// const result = G2point.hashToG2(input, domain); +// return `0x${result.toBytesCompressed().toString("hex")}`; +// }), +// { +// inputTypes: { +// data: InputType.YAML, +// }, +// getExpected: (testCase => { +// const xReExpected = padLeft(Buffer.from(testCase.data.output[0].replace("0x", ""), "hex"), 48); +// const xImExpected = padLeft(Buffer.from(testCase.data.output[1].replace("0x", ""), "hex"), 48); +// return "0x" + Buffer.concat([xReExpected, xImExpected]).toString("hex"); +// }) +// } +// ); \ No newline at end of file diff --git a/test/spec/msg_hash_uncompressed.test.ts b/test/spec/msg_hash_uncompressed.test.ts index 4da8e81..08de48f 100644 --- a/test/spec/msg_hash_uncompressed.test.ts +++ b/test/spec/msg_hash_uncompressed.test.ts @@ -1,43 +1,43 @@ -import path from "path"; -import {padLeft} from "../../src/helpers/utils"; -import {G2point} from "../../src/helpers/g2point"; -import {describeDirectorySpecTest, InputType} from "@chainsafe/eth2.0-spec-test-util/lib/single"; - -interface IMsgHHashUnCompressed { - data: { - input: { - message: string; - domain: string; - }; - output: string[][]; - }; -} - -describeDirectorySpecTest( - "msg_hash_uncompressed", - path.join( - __dirname, - "../../../../node_modules/@chainsafe/eth2-spec-tests/tests/general/phase0/bls/msg_hash_uncompressed/small" - ), - (testCase => { - const domain = padLeft(Buffer.from(testCase.data.input.domain.replace("0x", ""), "hex"), 8); - const input = Buffer.from(testCase.data.input.message.replace("0x", ""), "hex"); - const result = G2point.hashToG2(input, domain); - return `0x${result.toBytesCompressed().toString("hex")}`; - }), - { - inputTypes: { - data: InputType.YAML, - }, - getExpected: (testCase => { - return "0x" + G2point.fromUncompressedInput( - Buffer.from(testCase.data.output[0][0].replace("0x", ""), "hex"), - Buffer.from(testCase.data.output[0][1].replace("0x", ""), "hex"), - Buffer.from(testCase.data.output[1][0].replace("0x", ""), "hex"), - Buffer.from(testCase.data.output[1][1].replace("0x", ""), "hex"), - Buffer.from(testCase.data.output[2][0].replace("0x", ""), "hex"), - Buffer.from(testCase.data.output[2][1].replace("0x", ""), "hex"), - ).toBytesCompressed().toString("hex"); - }) - } -); \ No newline at end of file +// import path from "path"; +// import {padLeft} from "../../src/helpers/utils"; +// import {G2point} from "../../src/helpers/g2point"; +// import {describeDirectorySpecTest, InputType} from "@chainsafe/eth2.0-spec-test-util/lib/single"; +// +// interface IMsgHHashUnCompressed { +// data: { +// input: { +// message: string; +// domain: string; +// }; +// output: string[][]; +// }; +// } +// +// describeDirectorySpecTest( +// "msg_hash_uncompressed", +// path.join( +// __dirname, +// "../../../../node_modules/@chainsafe/eth2-spec-tests/tests/general/phase0/bls/msg_hash_uncompressed/small" +// ), +// (testCase => { +// const domain = padLeft(Buffer.from(testCase.data.input.domain.replace("0x", ""), "hex"), 8); +// const input = Buffer.from(testCase.data.input.message.replace("0x", ""), "hex"); +// const result = G2point.hashToG2(input, domain); +// return `0x${result.toBytesCompressed().toString("hex")}`; +// }), +// { +// inputTypes: { +// data: InputType.YAML, +// }, +// getExpected: (testCase => { +// return "0x" + G2point.fromUncompressedInput( +// Buffer.from(testCase.data.output[0][0].replace("0x", ""), "hex"), +// Buffer.from(testCase.data.output[0][1].replace("0x", ""), "hex"), +// Buffer.from(testCase.data.output[1][0].replace("0x", ""), "hex"), +// Buffer.from(testCase.data.output[1][1].replace("0x", ""), "hex"), +// Buffer.from(testCase.data.output[2][0].replace("0x", ""), "hex"), +// Buffer.from(testCase.data.output[2][1].replace("0x", ""), "hex"), +// ).toBytesCompressed().toString("hex"); +// }) +// } +// ); \ No newline at end of file diff --git a/test/spec/priv_to_public.test.ts b/test/spec/priv_to_public.test.ts index dfb3218..9311267 100644 --- a/test/spec/priv_to_public.test.ts +++ b/test/spec/priv_to_public.test.ts @@ -1,4 +1,4 @@ -import bls from "../../src"; +import bls, {initLibrary} from "../../src"; import {describeDirectorySpecTest, InputType} from "@chainsafe/eth2.0-spec-test-util/lib/single"; import path from "path"; @@ -9,6 +9,10 @@ interface IPrivToPubTestCase { }; } +before(async function f() { + await initLibrary(); +}); + describeDirectorySpecTest( "priv_to_pub", path.join( diff --git a/test/spec/sign_message.test.ts b/test/spec/sign_message.test.ts index 3ca0154..484333c 100644 --- a/test/spec/sign_message.test.ts +++ b/test/spec/sign_message.test.ts @@ -1,5 +1,5 @@ import path from "path"; -import bls from "../../src"; +import bls, {initLibrary} from "../../src"; import {padLeft} from "../../src/helpers/utils"; import {describeDirectorySpecTest, InputType} from "@chainsafe/eth2.0-spec-test-util/lib/single"; @@ -14,6 +14,10 @@ interface ISignMessageTestCase { }; } +before(async function f() { + await initLibrary(); +}); + describeDirectorySpecTest( "priv_to_pub", path.join( diff --git a/test/unit/helpers/g1point.test.ts b/test/unit/helpers/g1point.test.ts deleted file mode 100644 index 2ef321f..0000000 --- a/test/unit/helpers/g1point.test.ts +++ /dev/null @@ -1,109 +0,0 @@ -import {G1point} from "../../../src/helpers/g1point"; -import {expect} from "chai"; - -describe('g1point', function() { - - it('should generate different random point', () => { - const g1 = G1point.random(); - const g2 = G1point.random(); - expect(g1.equal(g2)).to.be.false; - }); - - it('should be same', () => { - const g1 = G1point.random(); - expect(g1.equal(g1)).to.be.true; - }); - - it('serialize adn deserialize should produce same result', () => { - const g1 = G1point.random(); - const g2 = G1point.fromBytesCompressed(g1.toBytesCompressed()); - expect(g1.equal(g2)).to.be.true; - }); - - it('deserialize correct point doesn not throw', () => { - expect(() => { - G1point.fromBytesCompressed( - Buffer.from( - '8123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef', - 'hex' - ) - ) - }).to.not.throw; - }); - - it('deserialize incorrect point throws', () => { - expect(() => { - G1point.fromBytesCompressed( - Buffer.from( - '8123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcde0', - 'hex' - ) - ) - }).to.throw('X coordinate is not on the curve.'); - }); - - it('deserialize incorrect point throws 2', () => { - expect(() => { - G1point.fromBytesCompressed( - Buffer.from( - '9a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab', - 'hex' - ) - ) - }).to.throw('X coordinate is too large.'); - }); - - it('deserialize incorrect point throws 3', () => { - expect(() => { - G1point.fromBytesCompressed( - Buffer.from( - '9a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaac', - 'hex' - ) - ) - }).to.throw('X coordinate is too large.'); - }); - - it('deserialize incorrect point throws to few bytes', () => { - expect(() => { - G1point.fromBytesCompressed( - Buffer.from( - '9a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaa', - 'hex' - ) - ) - }).to.throw('Expected g1 compressed input to have 48 bytes'); - }); - - it('deserialize incorrect point throws to many bytes', () => { - expect(() => { - G1point.fromBytesCompressed( - Buffer.from( - '9a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaa900', - 'hex' - ) - ) - }).to.throw('Expected g1 compressed input to have 48 bytes'); - }); - - it('deserialize infinity', () => { - const g1 = G1point.fromBytesCompressed( - Buffer.from( - 'c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', - 'hex' - ) - ); - expect(g1.getPoint().is_infinity()).to.be.true - }); - - it('wrong infinity serialization', () => { - expect(() => { - G1point.fromBytesCompressed( - Buffer.from( - 'e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000', - 'hex' - ) - ) - }).to.throw('The serialised input has B flag set, but A flag is set, or X is non-zero.'); - }); -}); diff --git a/test/unit/helpers/g2point.test.ts b/test/unit/helpers/g2point.test.ts deleted file mode 100644 index 94159af..0000000 --- a/test/unit/helpers/g2point.test.ts +++ /dev/null @@ -1,153 +0,0 @@ -import {G2point} from "../../../src/helpers/g2point"; -import {expect} from "chai"; - -describe('g2point', function() { - - it('should be equals', () => { - const g2 = G2point.random(); - expect(g2.equal(g2)).to.be.true; - }); - - it('should not be equals', () => { - const g2 = G2point.random(); - const g22 = G2point.random(); - expect(g2.equal(g22)).to.be.false; - }); - - it('serialize deserialize should be equal', () => { - const g2 = G2point.random(); - expect(G2point.fromCompressedBytes(g2.toBytesCompressed()).equal(g2)).to.be.true; - }); - - it('should deserialize from compress', () => { - const x = - "8123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" - + "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"; - expect(() => { - G2point.fromCompressedBytes( - Buffer.from( - x, - 'hex' - ) - ) - }).to.not.throw; - }); - - it('should fail to deserialize', () => { - const x = - "800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" - + "1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab"; - expect(() => { - G2point.fromCompressedBytes( - Buffer.from( - x, - 'hex' - ) - ) - }).to.throw('The deserialised X real or imaginary coordinate is too large.'); - }); - - it('should fail to deserialize 2', () => { - const x = - "9a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaab" - + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; - expect(() => { - G2point.fromCompressedBytes( - Buffer.from( - x, - 'hex' - ) - ) - }).to.throw('The deserialised X real or imaginary coordinate is too large.'); - }); - - it('should fail to deserialize 3', () => { - const x = - "800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" - + "1a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaac"; - expect(() => { - G2point.fromCompressedBytes( - Buffer.from( - x, - 'hex' - ) - ) - }).to.throw('The deserialised X real or imaginary coordinate is too large.'); - }); - - it('should fail to deserialize 4', () => { - const x = - "9a0111ea397fe69a4b1ba7b6434bacd764774b84f38512bf6730d2a0f6b0f6241eabfffeb153ffffb9feffffffffaaac" - + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; - expect(() => { - G2point.fromCompressedBytes( - Buffer.from( - x, - 'hex' - ) - ) - }).to.throw('The deserialised X real or imaginary coordinate is too large.'); - }); - - it('should fail to deserialize 5', () => { - const x = - "8123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef" - + "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcde0"; - expect(() => { - G2point.fromCompressedBytes( - Buffer.from( - x, - 'hex' - ) - ) - }).to.throw('X coordinate is not on the curve.'); - }); - - it('should fail to deserialize infinity', () => { - const x = - "800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" - + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; - expect(() => { - G2point.fromCompressedBytes( - Buffer.from( - x, - 'hex' - ) - ) - }).to.throw('X coordinate is not on the curve.'); - }); - - it('should fail to deserialize - too few bytes', () => { - const x = "8123456789abcd"; - expect(() => { - G2point.fromCompressedBytes( - Buffer.from( - x, - 'hex' - ) - ) - }).to.throw('Expected signature of 96 bytes'); - }); - - it('should fail to deserialize - too many bytes', () => { - expect(() => { - G2point.fromCompressedBytes( - Buffer.alloc(100, 1), - ) - }).to.throw('Expected signature of 96 bytes'); - }); - - it('should deserialize infinity', () => { - const x = - "c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" - + "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; - const g2 = G2point.fromCompressedBytes( - Buffer.from( - x, - 'hex' - ) - ); - expect(g2.getPoint().is_infinity()).to.be.true; - }) - -}); diff --git a/test/unit/helpers/utils.test.ts b/test/unit/helpers/utils.test.ts deleted file mode 100644 index 51e6f67..0000000 --- a/test/unit/helpers/utils.test.ts +++ /dev/null @@ -1,27 +0,0 @@ -import {expect} from "chai"; -import {padLeft} from "../../../src/helpers/utils"; -import {G1point} from "../../../src/helpers/g1point"; - -describe('helpers tests', function() { - - describe('padLeft', function() { - - it('throw if source larger than target', () => { - expect( - () => padLeft(Buffer.alloc(2, 0), 1) - ).to.throw; - }); - - it('pad one 0 on left side', () => { - const result = padLeft( - Buffer.alloc(1, 1), - 2 - ); - expect(result.length).to.be.equal(2); - expect(result[0]).to.be.equal(0); - expect(result[1]).to.be.equal(1); - }); - - }); - -}); diff --git a/test/unit/index.test.ts b/test/unit/index.test.ts index 89c5952..b01d2ee 100644 --- a/test/unit/index.test.ts +++ b/test/unit/index.test.ts @@ -1,11 +1,19 @@ -import bls from "../../src"; -import {Keypair} from "../../src/keypair"; +import bls, {aggregatePubkeys, aggregateSignatures, initLibrary, Keypair, verify, verifyMultiple} from "../../src"; import {sha256} from "js-sha256"; -import {G2point} from "../../src/helpers/g2point"; import {expect} from "chai"; +import {destroy} from "../../src/context"; +import {padLeft} from "../../lib/helpers/utils"; describe("test bls", function () { + before(async function() { + await initLibrary(); + }); + + after(function () { + destroy(); + }); + describe("aggregate pubkey", function () { it("should aggregate empty array", function () { expect(bls.aggregatePubkeys([])).to.not.throw; @@ -17,10 +25,11 @@ describe("test bls", function () { const keypair = Keypair.generate(); const messageHash = Buffer.from(sha256.arrayBuffer("Test")); const domain = Buffer.alloc(8, 1); - const signature = keypair.privateKey.sign( - G2point.hashToG2(messageHash, domain) + const signature = keypair.privateKey.signMessage( + messageHash, + domain ); - const result = bls.verify( + const result = verify( keypair.publicKey.toBytesCompressed(), messageHash, signature.toBytesCompressed(), @@ -34,11 +43,12 @@ describe("test bls", function () { const keypair = Keypair.generate(); const messageHash = Buffer.from(sha256.arrayBuffer("Test")); const domain = Buffer.alloc(8, 1); - const signature = keypair.privateKey.sign( - G2point.hashToG2(messageHash, domain) + const signature = keypair.privateKey.signMessage( + messageHash, + domain ); const pubKey = keypair.publicKey.toBytesCompressed(); - bls.verify( + verify( pubKey, messageHash, signature.toBytesCompressed(), @@ -53,7 +63,7 @@ describe("test bls", function () { const messageHash2 = Buffer.from(sha256.arrayBuffer("Test message2")); const domain = Buffer.from("01", "hex"); const signature = Buffer.alloc(96); - const result = bls.verify( + const result = verify( keypair.publicKey.toBytesCompressed(), messageHash2, signature, @@ -66,11 +76,12 @@ describe("test bls", function () { const keypair = Keypair.generate(); const messageHash = Buffer.from(sha256.arrayBuffer("Test message")); const messageHash2 = Buffer.from(sha256.arrayBuffer("Test message2")); - const domain = Buffer.from("01", "hex"); - const signature = keypair.privateKey.sign( - G2point.hashToG2(messageHash, domain) + const domain = padLeft(Buffer.from("01", "hex"), 8); + const signature = keypair.privateKey.signMessage( + messageHash, + domain ); - const result = bls.verify( + const result = verify( keypair.publicKey.toBytesCompressed(), messageHash2, signature.toBytesCompressed(), @@ -82,12 +93,13 @@ describe("test bls", function () { it("should fail verify signature of different domain", () => { const keypair = Keypair.generate(); const messageHash = Buffer.from(sha256.arrayBuffer("Test message")); - const domain = Buffer.from("01", "hex"); - const domain2 = Buffer.from("02", "hex"); - const signature = keypair.privateKey.sign( - G2point.hashToG2(messageHash, domain) + const domain = padLeft(Buffer.from("01", "hex"), 8); + const domain2 = padLeft(Buffer.from("02", "hex"), 8); + const signature = keypair.privateKey.signMessage( + messageHash, + domain ); - const result = bls.verify( + const result = verify( keypair.publicKey.toBytesCompressed(), messageHash, signature.toBytesCompressed(), @@ -101,10 +113,11 @@ describe("test bls", function () { const keypair2 = Keypair.generate(); const messageHash = Buffer.from(sha256.arrayBuffer("Test message")); const domain = Buffer.from("01", "hex"); - const signature = keypair.privateKey.sign( - G2point.hashToG2(messageHash, domain) + const signature = keypair.privateKey.signMessage( + messageHash, + domain ); - const result = bls.verify( + const result = verify( keypair2.publicKey.toBytesCompressed(), messageHash, signature.toBytesCompressed(), @@ -127,32 +140,32 @@ describe("test bls", function () { const keypair3 = Keypair.generate(); const keypair4 = Keypair.generate(); - const message1 = Buffer.from("Test1", "utf-8"); - const message2 = Buffer.from("Test2", "utf-8"); + const message1 = Buffer.from(sha256.arrayBuffer("Test1")); + const message2 = Buffer.from(sha256.arrayBuffer("Test2")); const signature1 = keypair1.privateKey.signMessage(message1, domain); const signature2 = keypair2.privateKey.signMessage(message1, domain); const signature3 = keypair3.privateKey.signMessage(message2, domain); const signature4 = keypair4.privateKey.signMessage(message2, domain); - const aggregatePubKey12 = bls.aggregatePubkeys([ + const aggregatePubKey12 = aggregatePubkeys([ keypair1.publicKey.toBytesCompressed(), keypair2.publicKey.toBytesCompressed(), ]); - const aggregatePubKey34 = bls.aggregatePubkeys([ + const aggregatePubKey34 = aggregatePubkeys([ keypair3.publicKey.toBytesCompressed(), keypair4.publicKey.toBytesCompressed(), ]); - const aggregateSignature = bls.aggregateSignatures([ + const aggregateSignature = aggregateSignatures([ signature1.toBytesCompressed(), signature2.toBytesCompressed(), signature3.toBytesCompressed(), signature4.toBytesCompressed(), ]); - const result = bls.verifyMultiple( + const result = verifyMultiple( [aggregatePubKey12, aggregatePubKey34], [message1, message2], aggregateSignature, @@ -173,21 +186,21 @@ describe("test bls", function () { const keypair3 = Keypair.generate(); const keypair4 = Keypair.generate(); - const message = Buffer.from("Test1", "utf-8"); + const message = Buffer.from(sha256.arrayBuffer("Test1")); 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([ + const aggregateSignature = aggregateSignatures([ signature1.toBytesCompressed(), signature2.toBytesCompressed(), signature3.toBytesCompressed(), signature4.toBytesCompressed(), ]); - const result = bls.verifyMultiple( + const result = verifyMultiple( [ keypair1.publicKey.toBytesCompressed(), keypair2.publicKey.toBytesCompressed(), @@ -212,8 +225,8 @@ describe("test bls", function () { const keypair3 = Keypair.generate(); const keypair4 = Keypair.generate(); - const message1 = Buffer.from("Test1", "utf-8"); - const message2 = Buffer.from("Test2", "utf-8"); + const message1 = Buffer.from(sha256.arrayBuffer("Test1")); + const message2 = Buffer.from(sha256.arrayBuffer("Test2")); const signature1 = keypair1.privateKey.signMessage(message1, domain); const signature2 = keypair2.privateKey.signMessage(message1, domain); @@ -256,8 +269,8 @@ describe("test bls", function () { const keypair3 = Keypair.generate(); const keypair4 = Keypair.generate(); - const message1 = Buffer.from("Test1", "utf-8"); - const message2 = Buffer.from("Test2", "utf-8"); + const message1 = Buffer.from(sha256.arrayBuffer("Test1")); + const message2 = Buffer.from(sha256.arrayBuffer("Test2")); const signature1 = keypair1.privateKey.signMessage(message1, domain); const signature2 = keypair2.privateKey.signMessage(message1, domain); @@ -297,8 +310,8 @@ describe("test bls", function () { const keypair3 = Keypair.generate(); const keypair4 = Keypair.generate(); - const message1 = Buffer.from("Test1", "utf-8"); - const message2 = Buffer.from("Test2", "utf-8"); + const message1 = Buffer.from(sha256.arrayBuffer("Test1")); + const message2 = Buffer.from(sha256.arrayBuffer("Test2")); const signature1 = keypair1.privateKey.signMessage(message1, domain); const signature2 = keypair2.privateKey.signMessage(message1, domain); @@ -335,8 +348,8 @@ describe("test bls", function () { const signature = Buffer.alloc(96); - const message1 = Buffer.from("Test1", "utf-8"); - const message2 = Buffer.from("Test2", "utf-8"); + const message1 = Buffer.from(sha256.arrayBuffer("Test1")); + const message2 = Buffer.from(sha256.arrayBuffer("Test2")); const result = bls.verifyMultiple( [], diff --git a/test/unit/keypair.test.ts b/test/unit/keypair.test.ts index 1b5db88..b5b9d81 100644 --- a/test/unit/keypair.test.ts +++ b/test/unit/keypair.test.ts @@ -2,24 +2,33 @@ import {PrivateKey} from "../../src/privateKey"; import {PublicKey} from "../../src/publicKey"; import {Keypair} from "../../src/keypair"; import {expect} from "chai"; +import {destroy, init} from "../../src/context"; -describe('keypair', function() { +describe("keypair", function() { - it('should create from private and public key', () => { - const secret = PrivateKey.random(); - const secret2 = PrivateKey.random(); - const publicKey = PublicKey.fromBytes(PublicKey.fromPrivateKey(secret2).toBytesCompressed()); - 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); - }); + before(async function () { + await init(); + }); - it('should create from private', () => { - const secret = PrivateKey.random(); - const publicKey = PublicKey.fromPrivateKey(secret); - const keypair = new Keypair(secret); - expect(keypair.publicKey.toBytesCompressed().toString('hex')) - .to.be.equal(publicKey.toBytesCompressed().toString('hex')); - }) + after(function () { + destroy(); + }); + + it("should create from private and public key", () => { + const secret = PrivateKey.random(); + const secret2 = PrivateKey.random(); + const publicKey = PublicKey.fromBytes(PublicKey.fromPrivateKey(secret2).toBytesCompressed()); + 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.random(); + const publicKey = PublicKey.fromPrivateKey(secret); + const keypair = new Keypair(secret); + expect(keypair.publicKey.toBytesCompressed().toString("hex")) + .to.be.equal(publicKey.toBytesCompressed().toString("hex")); + }); }); diff --git a/test/unit/publicKey.test.ts b/test/unit/publicKey.test.ts new file mode 100644 index 0000000..4431557 --- /dev/null +++ b/test/unit/publicKey.test.ts @@ -0,0 +1,33 @@ +import {destroy, init} from "../../src/context"; +import {PublicKey} from "../../src/publicKey"; +import {expect} from "chai"; +import { PrivateKey } from "../../src/privateKey"; + + +describe("public key", function () { + + before(async function f() { + await init(); + }); + + after(function () { + destroy(); + }); + + it("from hex", function () { + const publicKey = + "0xb6f21199594b56d77670564bf422cb331d5281ca2c1f9a45588a56881d8287ef8619efa6456d6cd2ef61306aa5b21311"; + expect(PublicKey.fromHex(publicKey).toHexString()).to.be.equal(publicKey); + }); + + it("from bytes", function () { + const publicKey = + "b6f21199594b56d77670564bf422cb331d5281ca2c1f9a45588a56881d8287ef8619efa6456d6cd2ef61306aa5b21311"; + expect(PublicKey.fromBytes(Buffer.from(publicKey, "hex")).toHexString()).to.be.equal(`0x${publicKey}`); + }); + + it("from private key", function () { + PublicKey.fromPrivateKey(PrivateKey.random()); + }); + +}); \ No newline at end of file