From 999f0f3830888bf2a81e8729b9d9c3c90b018706 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Fri, 7 Feb 2020 09:19:31 +0100 Subject: [PATCH] Make Pen construction async and pubkey synchonous This avoids a lot of computation, since the HD derivation only happens once. --- packages/sdk/src/pen.spec.ts | 17 ++++++------- packages/sdk/src/pen.ts | 33 ++++++++++++------------- packages/sdk/src/restclient.spec.ts | 38 ++++++++++++----------------- packages/sdk/types/pen.d.ts | 11 ++++----- 4 files changed, 44 insertions(+), 55 deletions(-) diff --git a/packages/sdk/src/pen.spec.ts b/packages/sdk/src/pen.spec.ts index 8a7fbadb..7ea39cee 100644 --- a/packages/sdk/src/pen.spec.ts +++ b/packages/sdk/src/pen.spec.ts @@ -6,22 +6,22 @@ import { Secp256k1Pen } from "./pen"; const { fromHex } = Encoding; describe("Sec256k1Pen", () => { - it("can be constructed", () => { - const pen = new Secp256k1Pen( + it("can be constructed", async () => { + const pen = await Secp256k1Pen.fromMnemonic( "zebra slush diet army arrest purpose hawk source west glimpse custom record", ); expect(pen).toBeTruthy(); }); - describe("getPubkey", () => { + describe("pubkey", () => { it("returns compressed pubkey", async () => { // special sign fit simple patrol salute grocery chicken wheat radar tonight ceiling // m/44'/118'/0'/0/0 // pubkey: 02baa4ef93f2ce84592a49b1d729c074eab640112522a7a89f7d03ebab21ded7b6 - const pen = new Secp256k1Pen( + const pen = await Secp256k1Pen.fromMnemonic( "special sign fit simple patrol salute grocery chicken wheat radar tonight ceiling", ); - expect(await pen.getPubkey()).toEqual( + expect(pen.pubkey).toEqual( fromHex("02baa4ef93f2ce84592a49b1d729c074eab640112522a7a89f7d03ebab21ded7b6"), ); }); @@ -29,10 +29,7 @@ describe("Sec256k1Pen", () => { describe("createSignature", () => { it("creates correct signatures", async () => { - // special sign fit simple patrol salute grocery chicken wheat radar tonight ceiling - // m/44'/118'/0'/0/0 - // pubkey: 02baa4ef93f2ce84592a49b1d729c074eab640112522a7a89f7d03ebab21ded7b6 - const pen = new Secp256k1Pen( + const pen = await Secp256k1Pen.fromMnemonic( "special sign fit simple patrol salute grocery chicken wheat radar tonight ceiling", ); const data = Encoding.toAscii("foo bar"); @@ -41,7 +38,7 @@ describe("Sec256k1Pen", () => { const valid = await Secp256k1.verifySignature( new Secp256k1Signature(signature.slice(0, 32), signature.slice(32, 64)), new Sha256(data).digest(), - fromHex("02baa4ef93f2ce84592a49b1d729c074eab640112522a7a89f7d03ebab21ded7b6"), + pen.pubkey, ); expect(valid).toEqual(true); }); diff --git a/packages/sdk/src/pen.ts b/packages/sdk/src/pen.ts index 59e04108..f50d2465 100644 --- a/packages/sdk/src/pen.ts +++ b/packages/sdk/src/pen.ts @@ -22,7 +22,7 @@ export type PrehashType = "sha256" | "sha512" | null; * obfuscation of sensitive data. */ export interface Pen { - readonly getPubkey: () => Promise; + readonly pubkey: Uint8Array; readonly createSignature: (signBytes: Uint8Array, prehashType?: PrehashType) => Promise; } @@ -54,18 +54,22 @@ export function makeCosmoshubPath(a: number): readonly Slip10RawIndex[] { } export class Secp256k1Pen implements Pen { - private readonly mnemonic: EnglishMnemonic; - private readonly hdPath: readonly Slip10RawIndex[]; - - public constructor(mnemonic: string, hdPath: readonly Slip10RawIndex[] = makeCosmoshubPath(0)) { - this.mnemonic = new EnglishMnemonic(mnemonic); - this.hdPath = hdPath; + public static async fromMnemonic( + mnemonic: string, + hdPath: readonly Slip10RawIndex[] = makeCosmoshubPath(0), + ): Promise { + const seed = await Bip39.mnemonicToSeed(new EnglishMnemonic(mnemonic)); + const { privkey } = Slip10.derivePath(Slip10Curve.Secp256k1, seed, hdPath); + const uncompressed = (await Secp256k1.makeKeypair(privkey)).pubkey; + return new Secp256k1Pen(privkey, Secp256k1.compressPubkey(uncompressed)); } - public async getPubkey(): Promise { - const privkey = await this.getPrivkey(); - const uncompressed = (await Secp256k1.makeKeypair(privkey)).pubkey; - return Secp256k1.compressPubkey(uncompressed); + public readonly pubkey: Uint8Array; + private readonly privkey: Uint8Array; + + private constructor(privkey: Uint8Array, pubkey: Uint8Array) { + this.privkey = privkey; + this.pubkey = pubkey; } /** @@ -76,12 +80,7 @@ export class Secp256k1Pen implements Pen { prehashType: PrehashType = "sha256", ): Promise { const message = prehash(signBytes, prehashType); - const signature = await Secp256k1.createSignature(message, await this.getPrivkey()); + const signature = await Secp256k1.createSignature(message, this.privkey); return new Uint8Array([...signature.r(32), ...signature.s(32)]); } - - private async getPrivkey(): Promise { - const seed = await Bip39.mnemonicToSeed(this.mnemonic); - return Slip10.derivePath(Slip10Curve.Secp256k1, seed, this.hdPath).privkey; - } } diff --git a/packages/sdk/src/restclient.spec.ts b/packages/sdk/src/restclient.spec.ts index e64b3110..e3f9541b 100644 --- a/packages/sdk/src/restclient.spec.ts +++ b/packages/sdk/src/restclient.spec.ts @@ -25,9 +25,8 @@ const { fromBase64, fromHex, toAscii, toBase64, toHex } = Encoding; const httpUrl = "http://localhost:1317"; const defaultNetworkId = "testing"; -const faucetPen = new Secp256k1Pen( - "economy stock theory fatal elder harbor betray wasp final emotion task crumble siren bottom lizard educate guess current outdoor pair theory focus wife stone", -); +const faucetMnemonic = + "economy stock theory fatal elder harbor betray wasp final emotion task crumble siren bottom lizard educate guess current outdoor pair theory focus wife stone"; const faucetAddress = "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6"; const emptyAddress = "cosmos1ltkhnmdcqemmd2tkhnx7qx66tq7e0wykw2j85k"; @@ -118,7 +117,7 @@ async function uploadContract(client: RestClient, pen: Pen): Promise { describe("post", () => { it("can send tokens", async () => { pendingWithoutCosmos(); + const pen = await Secp256k1Pen.fromMnemonic(faucetMnemonic); const memo = "My first contract on chain"; const theMsg: MsgSend = { @@ -260,10 +260,7 @@ describe("RestClient", () => { const account = (await client.authAccounts(faucetAddress)).result.value; const signBytes = makeSignBytes([theMsg], fee, defaultNetworkId, memo, account); - const signature = encodeSecp256k1Signature( - await faucetPen.getPubkey(), - await faucetPen.createSignature(signBytes), - ); + const signature = encodeSecp256k1Signature(pen.pubkey, await pen.createSignature(signBytes)); const signedTx = makeSignedTx(theMsg, fee, memo, signature); const result = await client.postTx(marshalTx(signedTx)); // console.log("Raw log:", result.raw_log); @@ -272,6 +269,7 @@ describe("RestClient", () => { it("can upload, instantiate and execute wasm", async () => { pendingWithoutCosmos(); + const pen = await Secp256k1Pen.fromMnemonic(faucetMnemonic); const client = new RestClient(httpUrl); const transferAmount: readonly Coin[] = [ @@ -291,7 +289,7 @@ describe("RestClient", () => { // upload { // console.log("Raw log:", result.raw_log); - const result = await uploadContract(client, faucetPen); + const result = await uploadContract(client, pen); expect(result.code).toBeFalsy(); const logs = parseSuccess(result.raw_log); const codeIdAttr = findAttribute(logs, "message", "code_id"); @@ -304,13 +302,7 @@ describe("RestClient", () => { // instantiate { - const result = await instantiateContract( - client, - faucetPen, - codeId, - beneficiaryAddress, - transferAmount, - ); + const result = await instantiateContract(client, pen, codeId, beneficiaryAddress, transferAmount); expect(result.code).toBeFalsy(); // console.log("Raw log:", result.raw_log); const logs = parseSuccess(result.raw_log); @@ -325,7 +317,7 @@ describe("RestClient", () => { // execute { - const result = await executeContract(client, faucetPen, contractAddress); + const result = await executeContract(client, pen, contractAddress); expect(result.code).toBeFalsy(); // console.log("Raw log:", result.raw_log); const [firstLog] = parseSuccess(result.raw_log); @@ -343,6 +335,7 @@ describe("RestClient", () => { describe("query", () => { it("can list upload code", async () => { pendingWithoutCosmos(); + const pen = await Secp256k1Pen.fromMnemonic(faucetMnemonic); const client = new RestClient(httpUrl); // check with contracts were here first to compare @@ -351,7 +344,7 @@ describe("RestClient", () => { const numExisting = existingInfos.length; // upload data - const result = await uploadContract(client, faucetPen); + const result = await uploadContract(client, pen); expect(result.code).toBeFalsy(); const logs = parseSuccess(result.raw_log); const codeIdAttr = findAttribute(logs, "message", "code_id"); @@ -372,6 +365,7 @@ describe("RestClient", () => { it("can list contracts and get info", async () => { pendingWithoutCosmos(); + const pen = await Secp256k1Pen.fromMnemonic(faucetMnemonic); const client = new RestClient(httpUrl); const beneficiaryAddress = makeRandomAddress(); const transferAmount: readonly Coin[] = [ @@ -387,7 +381,7 @@ describe("RestClient", () => { if (existingInfos.length > 0) { codeId = existingInfos[existingInfos.length - 1].id; } else { - const uploaded = await uploadContract(client, faucetPen); + const uploaded = await uploadContract(client, pen); expect(uploaded.code).toBeFalsy(); const uploadLogs = parseSuccess(uploaded.raw_log); const codeIdAttr = findAttribute(uploadLogs, "message", "code_id"); @@ -397,7 +391,7 @@ describe("RestClient", () => { // create new instance and compare before and after const existingContracts = await client.listContractAddresses(); - const result = await instantiateContract(client, faucetPen, codeId, beneficiaryAddress, transferAmount); + const result = await instantiateContract(client, pen, codeId, beneficiaryAddress, transferAmount); expect(result.code).toBeFalsy(); const logs = parseSuccess(result.raw_log); const contractAddressAttr = findAttribute(logs, "message", "contract_address"); diff --git a/packages/sdk/types/pen.d.ts b/packages/sdk/types/pen.d.ts index 728b933a..08a3f8d8 100644 --- a/packages/sdk/types/pen.d.ts +++ b/packages/sdk/types/pen.d.ts @@ -11,7 +11,7 @@ export declare type PrehashType = "sha256" | "sha512" | null; * obfuscation of sensitive data. */ export interface Pen { - readonly getPubkey: () => Promise; + readonly pubkey: Uint8Array; readonly createSignature: (signBytes: Uint8Array, prehashType?: PrehashType) => Promise; } /** @@ -20,13 +20,12 @@ export interface Pen { */ export declare function makeCosmoshubPath(a: number): readonly Slip10RawIndex[]; export declare class Secp256k1Pen implements Pen { - private readonly mnemonic; - private readonly hdPath; - constructor(mnemonic: string, hdPath?: readonly Slip10RawIndex[]); - getPubkey(): Promise; + static fromMnemonic(mnemonic: string, hdPath?: readonly Slip10RawIndex[]): Promise; + readonly pubkey: Uint8Array; + private readonly privkey; + private constructor(); /** * Creates a fixed length encoding of the signature parameters r (32 bytes) and s (32 bytes). */ createSignature(signBytes: Uint8Array, prehashType?: PrehashType): Promise; - private getPrivkey; }