Make Pen construction async and pubkey synchonous

This avoids a lot of computation, since the HD derivation only happens
once.
This commit is contained in:
Simon Warta 2020-02-07 09:19:31 +01:00
parent 6ed7305f8a
commit 999f0f3830
4 changed files with 44 additions and 55 deletions

View File

@ -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);
});

View File

@ -22,7 +22,7 @@ export type PrehashType = "sha256" | "sha512" | null;
* obfuscation of sensitive data.
*/
export interface Pen {
readonly getPubkey: () => Promise<Uint8Array>;
readonly pubkey: Uint8Array;
readonly createSignature: (signBytes: Uint8Array, prehashType?: PrehashType) => Promise<Uint8Array>;
}
@ -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<Secp256k1Pen> {
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<Uint8Array> {
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<Uint8Array> {
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<Uint8Array> {
const seed = await Bip39.mnemonicToSeed(this.mnemonic);
return Slip10.derivePath(Slip10Curve.Secp256k1, seed, this.hdPath).privkey;
}
}

View File

@ -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<PostTxsResp
const account = (await client.authAccounts(faucetAddress)).result.value;
const signBytes = makeSignBytes([theMsg], fee, defaultNetworkId, memo, account);
const signature = encodeSecp256k1Signature(await pen.getPubkey(), await pen.createSignature(signBytes));
const signature = encodeSecp256k1Signature(pen.pubkey, await pen.createSignature(signBytes));
const signedTx = makeSignedTx(theMsg, fee, memo, signature);
return client.postTx(marshalTx(signedTx));
}
@ -155,7 +154,7 @@ async function instantiateContract(
const account = (await client.authAccounts(faucetAddress)).result.value;
const signBytes = makeSignBytes([theMsg], fee, defaultNetworkId, memo, account);
const signature = encodeSecp256k1Signature(await pen.getPubkey(), await pen.createSignature(signBytes));
const signature = encodeSecp256k1Signature(pen.pubkey, await pen.createSignature(signBytes));
const signedTx = makeSignedTx(theMsg, fee, memo, signature);
return client.postTx(marshalTx(signedTx));
}
@ -187,7 +186,7 @@ async function executeContract(
const account = (await client.authAccounts(faucetAddress)).result.value;
const signBytes = makeSignBytes([theMsg], fee, defaultNetworkId, memo, account);
const signature = encodeSecp256k1Signature(await pen.getPubkey(), await pen.createSignature(signBytes));
const signature = encodeSecp256k1Signature(pen.pubkey, await pen.createSignature(signBytes));
const signedTx = makeSignedTx(theMsg, fee, memo, signature);
return client.postTx(marshalTx(signedTx));
}
@ -230,6 +229,7 @@ describe("RestClient", () => {
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");

View File

@ -11,7 +11,7 @@ export declare type PrehashType = "sha256" | "sha512" | null;
* obfuscation of sensitive data.
*/
export interface Pen {
readonly getPubkey: () => Promise<Uint8Array>;
readonly pubkey: Uint8Array;
readonly createSignature: (signBytes: Uint8Array, prehashType?: PrehashType) => Promise<Uint8Array>;
}
/**
@ -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<Uint8Array>;
static fromMnemonic(mnemonic: string, hdPath?: readonly Slip10RawIndex[]): Promise<Secp256k1Pen>;
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<Uint8Array>;
private getPrivkey;
}