From d5d8284be581dc0e063c8b6354d4734a9ed8c22a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marin=20Petruni=C4=87?= Date: Wed, 27 Nov 2019 19:21:37 +0100 Subject: [PATCH] bls wasm context and private key converted to wasm implementation --- package.json | 1 + src/context.ts | 21 +++++++++++++ src/privateKey.ts | 57 ++++++++++++++++-------------------- test/unit/context.test.ts | 20 +++++++++++++ test/unit/privateKey.test.ts | 44 ++++++++++++++++++++-------- tsconfig.build.json | 10 +++++++ tsconfig.json | 2 +- 7 files changed, 109 insertions(+), 46 deletions(-) create mode 100644 src/context.ts create mode 100644 test/unit/context.test.ts create mode 100644 tsconfig.build.json diff --git a/package.json b/package.json index 3d23df9..0f7cb9c 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "@chainsafe/eth2.0-types": "^0.1.0", "@chainsafe/milagro-crypto-js": "0.1.3", "assert": "^1.4.1", + "bls-wasm": "^0.2.7", "js-sha256": "^0.9.0", "secure-random": "^1.1.1" }, diff --git a/src/context.ts b/src/context.ts new file mode 100644 index 0000000..6b9b5a9 --- /dev/null +++ b/src/context.ts @@ -0,0 +1,21 @@ +// eslint-disable-next-line import/no-extraneous-dependencies +import blsWasmWrapper from "@chainsafe/eth2-bls-wasm"; + +let blsWrapper: typeof blsWasmWrapper | null = null; + +export async function init(): Promise { + await blsWasmWrapper.init(); + blsWrapper = blsWasmWrapper; + return blsWrapper; +} + +export function destroy(): void { + blsWrapper = null; +} + +export function getContext(): typeof blsWasmWrapper{ + if(blsWrapper) { + return blsWrapper; + } + throw new Error("BLS not initialized"); +} \ No newline at end of file diff --git a/src/privateKey.ts b/src/privateKey.ts index 30c7f38..26cfcda 100644 --- a/src/privateKey.ts +++ b/src/privateKey.ts @@ -1,63 +1,56 @@ -import {BIG} from "@chainsafe/milagro-crypto-js/src/big"; -import {FP_POINT_LENGTH, SECRET_KEY_LENGTH} from "./constants"; +import {SECRET_KEY_LENGTH} from "./constants"; import assert from "assert"; -import ctx from "./ctx"; -import {padLeft} from "./helpers/utils"; -import {G2point} from "./helpers/g2point"; -import * as random from "secure-random"; -import {BLSSecretKey, Hash, Domain} from "@chainsafe/eth2.0-types"; +import {BLSSecretKey, Domain, Hash} from "@chainsafe/eth2.0-types"; +import {SecretKeyType, SignatureType} from "@chainsafe/eth2-bls-wasm"; +import {getContext} from "./context"; export class PrivateKey { - private value: BIG; + private value: SecretKeyType; - public constructor(value: BIG) { + protected constructor(value: SecretKeyType) { this.value = value; } public static fromBytes(bytes: Uint8Array): PrivateKey { assert(bytes.length === SECRET_KEY_LENGTH, "Private key should have 32 bytes"); - const value = Buffer.from(bytes); - return new PrivateKey( - ctx.BIG.frombytearray( - padLeft( - value, - 48 - ), - 0 - ) - ); + const context = getContext(); + const secretKey = new context.SecretKey(); + secretKey.deserialize(Buffer.from(bytes)); + return new PrivateKey(secretKey); } public static fromHexString(value: string): PrivateKey { - return PrivateKey.fromBytes( - Buffer.from(value.replace("0x", ""), "hex") - ); + value = value.replace("0x", ""); + assert(value.length === SECRET_KEY_LENGTH * 2, "secret key must have 32 bytes"); + const context = getContext(); + return new PrivateKey(context.deserializeHexStrToSecretKey(value)); } public static random(): PrivateKey { - return PrivateKey.fromBytes(random.randomBuffer(SECRET_KEY_LENGTH)); + const context = getContext(); + const secretKey = new context.SecretKey(); + secretKey.setByCSPRNG(); + return new PrivateKey(secretKey); } - public getValue(): BIG { + public getValue(): SecretKeyType { return this.value; } - public sign(message: G2point): G2point { - return message.mul(this.value); + public sign(message: Uint8Array): SignatureType { + return this.value.sign(message); } - public signMessage(message: Hash, domain: Domain): G2point { - return G2point.hashToG2(message, domain).mul(this.value); + public signMessage(message: Hash, domain: Domain): SignatureType { + return this.value.signHashWithDomain(Buffer.concat([message, domain])); } public toBytes(): BLSSecretKey { - const buffer = Buffer.alloc(FP_POINT_LENGTH, 0); - this.value.tobytearray(buffer, 0); - return buffer.slice(FP_POINT_LENGTH - SECRET_KEY_LENGTH); + return Buffer.from(this.value.serialize()); } public toHexString(): string { - return `0x${this.toBytes().toString("hex")}`; + return `0x${this.value.serializeToHexStr()}`; } } diff --git a/test/unit/context.test.ts b/test/unit/context.test.ts new file mode 100644 index 0000000..2001aed --- /dev/null +++ b/test/unit/context.test.ts @@ -0,0 +1,20 @@ +import {init, getContext, destroy} from "../../src/context"; +import {expect} from "chai"; + +describe("bls wasm constext", function () { + + afterEach(() => { + destroy(); + }); + + it("initializes and works", async function () { + await init(); + expect(getContext().getCurveOrder()) + .to.be.equal("52435875175126190479447740508185965837690552500527637822603658699938581184513"); + }); + + it("throws if context not initialized", async function () { + expect(() => getContext().getCurveOrder()).to.throw(); + }); + +}); \ No newline at end of file diff --git a/test/unit/privateKey.test.ts b/test/unit/privateKey.test.ts index 670b89d..baac9b9 100644 --- a/test/unit/privateKey.test.ts +++ b/test/unit/privateKey.test.ts @@ -1,22 +1,40 @@ import {PrivateKey} from "../../src/privateKey"; import {expect} from "chai"; import {SECRET_KEY_LENGTH} from "../../src/constants"; +import {destroy, init} from "../../src/context"; -describe('privateKey', function() { +describe("privateKey", function() { - it('should generate random private key', function () { - const privateKey1 = PrivateKey.random(); - const privateKey2 = PrivateKey.random(); - expect(privateKey1).to.not.be.equal(privateKey2); - }); + before(async function () { + await init(); + }); - it('should export private key to hex string', function () { - const privateKey = '0x9a88071ff0634f6515c7699c97d069dc4b2fa28455f6b457e92d1c1302f0c6bb'; - expect(PrivateKey.fromHexString(privateKey).toHexString()).to.be.equal(privateKey); - }); + after(function () { + destroy(); + }); - it('should export private key to bytes', function () { - expect(PrivateKey.random().toBytes().length).to.be.equal(SECRET_KEY_LENGTH); - }); + it("should generate random private key", function () { + const privateKey1 = PrivateKey.random(); + const privateKey2 = PrivateKey.random(); + expect(privateKey1.toHexString()).to.not.be.equal(privateKey2.toHexString()); + }); + + it("should export private key to hex string", function () { + const privateKey = "0x07656fd676da43883d163f49566c72b9cbf0a5a294f26808c807700732456da7"; + + expect(PrivateKey.fromHexString(privateKey).toHexString()).to.be.equal(privateKey); + + const privateKey2 = "07656fd676da43883d163f49566c72b9cbf0a5a294f26808c807700732456da7"; + + expect(PrivateKey.fromHexString(privateKey2).toHexString()).to.be.equal(privateKey); + }); + + it("should export private key to bytes", function () { + expect(PrivateKey.random().toBytes().length).to.be.equal(SECRET_KEY_LENGTH); + }); + + it("should not accept too short private key", function () { + expect(() => PrivateKey.fromHexString("0x2123")).to.throw(); + }); }); diff --git a/tsconfig.build.json b/tsconfig.build.json new file mode 100644 index 0000000..10d11e3 --- /dev/null +++ b/tsconfig.build.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig", + "include": ["src"], + "compilerOptions": { + "typeRoots": [ + "src/@types", + "../../node_modules/@types" + ] + } +} diff --git a/tsconfig.json b/tsconfig.json index 10d11e3..12ea84c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,6 @@ { "extends": "../../tsconfig", - "include": ["src"], + "include": ["src", "test"], "compilerOptions": { "typeRoots": [ "src/@types",