From 9e4a79f6706349709ccec829710f797a64e26f27 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Fri, 7 Feb 2020 00:02:08 +0100 Subject: [PATCH 1/7] Add Pen --- packages/sdk/src/index.ts | 1 + packages/sdk/src/pen.spec.ts | 49 ++++++++++++++++++++ packages/sdk/src/pen.ts | 87 +++++++++++++++++++++++++++++++++++ packages/sdk/types/index.d.ts | 1 + packages/sdk/types/pen.d.ts | 32 +++++++++++++ 5 files changed, 170 insertions(+) create mode 100644 packages/sdk/src/pen.spec.ts create mode 100644 packages/sdk/src/pen.ts create mode 100644 packages/sdk/types/pen.d.ts diff --git a/packages/sdk/src/index.ts b/packages/sdk/src/index.ts index 2b01f294..8ac4d6f7 100644 --- a/packages/sdk/src/index.ts +++ b/packages/sdk/src/index.ts @@ -4,4 +4,5 @@ export { CosmosBech32Prefix, decodeBech32Pubkey, encodeAddress, isValidAddress } export { unmarshalTx } from "./decoding"; export { encodeSecp256k1Signature, makeSignBytes, marshalTx } from "./encoding"; export { RestClient, TxsResponse } from "./restclient"; +export { makeCosmoshubPath, Pen, PrehashType, Secp256k1Pen } from "./pen"; export { types }; diff --git a/packages/sdk/src/pen.spec.ts b/packages/sdk/src/pen.spec.ts new file mode 100644 index 00000000..8a7fbadb --- /dev/null +++ b/packages/sdk/src/pen.spec.ts @@ -0,0 +1,49 @@ +import { Secp256k1, Secp256k1Signature, Sha256 } from "@iov/crypto"; +import { Encoding } from "@iov/encoding"; + +import { Secp256k1Pen } from "./pen"; + +const { fromHex } = Encoding; + +describe("Sec256k1Pen", () => { + it("can be constructed", () => { + const pen = new Secp256k1Pen( + "zebra slush diet army arrest purpose hawk source west glimpse custom record", + ); + expect(pen).toBeTruthy(); + }); + + describe("getPubkey", () => { + 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( + "special sign fit simple patrol salute grocery chicken wheat radar tonight ceiling", + ); + expect(await pen.getPubkey()).toEqual( + fromHex("02baa4ef93f2ce84592a49b1d729c074eab640112522a7a89f7d03ebab21ded7b6"), + ); + }); + }); + + 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( + "special sign fit simple patrol salute grocery chicken wheat radar tonight ceiling", + ); + const data = Encoding.toAscii("foo bar"); + const signature = await pen.createSignature(data); + + const valid = await Secp256k1.verifySignature( + new Secp256k1Signature(signature.slice(0, 32), signature.slice(32, 64)), + new Sha256(data).digest(), + fromHex("02baa4ef93f2ce84592a49b1d729c074eab640112522a7a89f7d03ebab21ded7b6"), + ); + expect(valid).toEqual(true); + }); + }); +}); diff --git a/packages/sdk/src/pen.ts b/packages/sdk/src/pen.ts new file mode 100644 index 00000000..59e04108 --- /dev/null +++ b/packages/sdk/src/pen.ts @@ -0,0 +1,87 @@ +import { + Bip39, + EnglishMnemonic, + Secp256k1, + Sha256, + Sha512, + Slip10, + Slip10Curve, + Slip10RawIndex, +} from "@iov/crypto"; + +export type PrehashType = "sha256" | "sha512" | null; + +/** + * A pen is the most basic tool you can think of for signing. It works + * everywhere and can be used intuitively by everyone. However, it does not + * come with a great amount of features. End of semi suitable metaphor. + * + * This wraps a single keypair and allows for signing. + * + * Non-goals of this types are: multi account support, persistency, data migrations, + * obfuscation of sensitive data. + */ +export interface Pen { + readonly getPubkey: () => Promise; + readonly createSignature: (signBytes: Uint8Array, prehashType?: PrehashType) => Promise; +} + +function prehash(bytes: Uint8Array, type: PrehashType): Uint8Array { + switch (type) { + case null: + return new Uint8Array([...bytes]); + case "sha256": + return new Sha256(bytes).digest(); + case "sha512": + return new Sha512(bytes).digest(); + default: + throw new Error("Unknown prehash type"); + } +} + +/** + * The Cosmoshub derivation path in the form `m/44'/118'/0'/0/a` + * with 0-based account index `a`. + */ +export function makeCosmoshubPath(a: number): readonly Slip10RawIndex[] { + return [ + Slip10RawIndex.hardened(44), + Slip10RawIndex.hardened(118), + Slip10RawIndex.hardened(0), + Slip10RawIndex.normal(0), + Slip10RawIndex.normal(a), + ]; +} + +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 async getPubkey(): Promise { + const privkey = await this.getPrivkey(); + const uncompressed = (await Secp256k1.makeKeypair(privkey)).pubkey; + return Secp256k1.compressPubkey(uncompressed); + } + + /** + * Creates a fixed length encoding of the signature parameters r (32 bytes) and s (32 bytes). + */ + public async createSignature( + signBytes: Uint8Array, + prehashType: PrehashType = "sha256", + ): Promise { + const message = prehash(signBytes, prehashType); + const signature = await Secp256k1.createSignature(message, await this.getPrivkey()); + 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/types/index.d.ts b/packages/sdk/types/index.d.ts index 18324a0a..a341b70c 100644 --- a/packages/sdk/types/index.d.ts +++ b/packages/sdk/types/index.d.ts @@ -3,4 +3,5 @@ export { CosmosBech32Prefix, decodeBech32Pubkey, encodeAddress, isValidAddress } export { unmarshalTx } from "./decoding"; export { encodeSecp256k1Signature, makeSignBytes, marshalTx } from "./encoding"; export { RestClient, TxsResponse } from "./restclient"; +export { makeCosmoshubPath, Pen, PrehashType, Secp256k1Pen } from "./pen"; export { types }; diff --git a/packages/sdk/types/pen.d.ts b/packages/sdk/types/pen.d.ts new file mode 100644 index 00000000..728b933a --- /dev/null +++ b/packages/sdk/types/pen.d.ts @@ -0,0 +1,32 @@ +import { Slip10RawIndex } from "@iov/crypto"; +export declare type PrehashType = "sha256" | "sha512" | null; +/** + * A pen is the most basic tool you can think of for signing. It works + * everywhere and can be used intuitively by everyone. However, it does not + * come with a great amount of features. End of semi suitable metaphor. + * + * This wraps a single keypair and allows for signing. + * + * Non-goals of this types are: multi account support, persistency, data migrations, + * obfuscation of sensitive data. + */ +export interface Pen { + readonly getPubkey: () => Promise; + readonly createSignature: (signBytes: Uint8Array, prehashType?: PrehashType) => Promise; +} +/** + * The Cosmoshub derivation path in the form `m/44'/118'/0'/0/a` + * with 0-based account index `a`. + */ +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; + /** + * Creates a fixed length encoding of the signature parameters r (32 bytes) and s (32 bytes). + */ + createSignature(signBytes: Uint8Array, prehashType?: PrehashType): Promise; + private getPrivkey; +} From 3d2fe5fd03e78ae93893c2041786a12a31787104 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Fri, 7 Feb 2020 00:32:21 +0100 Subject: [PATCH 2/7] Use Pen in RestClient tests --- packages/sdk/package.json | 2 - packages/sdk/src/restclient.spec.ts | 72 ++++++++++------------------- 2 files changed, 24 insertions(+), 50 deletions(-) diff --git a/packages/sdk/package.json b/packages/sdk/package.json index a05d2ce8..55a04c7f 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -43,7 +43,5 @@ "axios": "^0.19.0" }, "devDependencies": { - "@iov/bcp": "^2.0.0-alpha.7", - "@iov/keycontrol": "^2.0.0-alpha.7" } } diff --git a/packages/sdk/src/restclient.spec.ts b/packages/sdk/src/restclient.spec.ts index 405b780e..e64b3110 100644 --- a/packages/sdk/src/restclient.spec.ts +++ b/packages/sdk/src/restclient.spec.ts @@ -1,12 +1,11 @@ /* eslint-disable @typescript-eslint/camelcase */ -import { ChainId, Identity, PrehashType, SignableBytes } from "@iov/bcp"; import { Random } from "@iov/crypto"; import { Bech32, Encoding } from "@iov/encoding"; -import { HdPaths, Secp256k1HdWallet } from "@iov/keycontrol"; import { encodeSecp256k1Signature, makeSignBytes, marshalTx } from "./encoding"; import { leb128Encode } from "./leb128.spec"; import { Attribute, Log, parseLogs } from "./logs"; +import { Pen, Secp256k1Pen } from "./pen"; import { PostTxsResponse, RestClient } from "./restclient"; import contract from "./testdata/contract.json"; import cosmoshub from "./testdata/cosmoshub.json"; @@ -26,9 +25,9 @@ const { fromBase64, fromHex, toAscii, toBase64, toHex } = Encoding; const httpUrl = "http://localhost:1317"; const defaultNetworkId = "testing"; -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 faucetPath = HdPaths.cosmos(0); +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 faucetAddress = "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6"; const emptyAddress = "cosmos1ltkhnmdcqemmd2tkhnx7qx66tq7e0wykw2j85k"; @@ -96,11 +95,7 @@ function findAttribute(logs: readonly Log[], eventType: "message" | "transfer", return out; } -async function uploadContract( - client: RestClient, - wallet: Secp256k1HdWallet, - signer: Identity, -): Promise { +async function uploadContract(client: RestClient, pen: Pen): Promise { const memo = "My first contract on chain"; const theMsg: MsgStoreCode = { type: "wasm/store-code", @@ -122,17 +117,15 @@ async function uploadContract( }; const account = (await client.authAccounts(faucetAddress)).result.value; - const signBytes = makeSignBytes([theMsg], fee, defaultNetworkId, memo, account) as SignableBytes; - const rawSignature = await wallet.createTransactionSignature(signer, signBytes, PrehashType.Sha256); - const signature = encodeSecp256k1Signature(signer.pubkey.data, rawSignature); + const signBytes = makeSignBytes([theMsg], fee, defaultNetworkId, memo, account); + const signature = encodeSecp256k1Signature(await pen.getPubkey(), await pen.createSignature(signBytes)); const signedTx = makeSignedTx(theMsg, fee, memo, signature); return client.postTx(marshalTx(signedTx)); } async function instantiateContract( client: RestClient, - wallet: Secp256k1HdWallet, - signer: Identity, + pen: Pen, codeId: number, beneficiaryAddress: string, transferAmount: readonly Coin[], @@ -161,17 +154,15 @@ async function instantiateContract( }; const account = (await client.authAccounts(faucetAddress)).result.value; - const signBytes = makeSignBytes([theMsg], fee, defaultNetworkId, memo, account) as SignableBytes; - const rawSignature = await wallet.createTransactionSignature(signer, signBytes, PrehashType.Sha256); - const signature = encodeSecp256k1Signature(signer.pubkey.data, rawSignature); + const signBytes = makeSignBytes([theMsg], fee, defaultNetworkId, memo, account); + const signature = encodeSecp256k1Signature(await pen.getPubkey(), await pen.createSignature(signBytes)); const signedTx = makeSignedTx(theMsg, fee, memo, signature); return client.postTx(marshalTx(signedTx)); } async function executeContract( client: RestClient, - wallet: Secp256k1HdWallet, - signer: Identity, + pen: Pen, contractAddress: string, ): Promise { const memo = "Time for action"; @@ -195,9 +186,8 @@ async function executeContract( }; const account = (await client.authAccounts(faucetAddress)).result.value; - const signBytes = makeSignBytes([theMsg], fee, defaultNetworkId, memo, account) as SignableBytes; - const rawSignature = await wallet.createTransactionSignature(signer, signBytes, PrehashType.Sha256); - const signature = encodeSecp256k1Signature(signer.pubkey.data, rawSignature); + const signBytes = makeSignBytes([theMsg], fee, defaultNetworkId, memo, account); + const signature = encodeSecp256k1Signature(await pen.getPubkey(), await pen.createSignature(signBytes)); const signedTx = makeSignedTx(theMsg, fee, memo, signature); return client.postTx(marshalTx(signedTx)); } @@ -240,8 +230,6 @@ describe("RestClient", () => { describe("post", () => { it("can send tokens", async () => { pendingWithoutCosmos(); - const wallet = Secp256k1HdWallet.fromMnemonic(faucetMnemonic); - const signer = await wallet.createIdentity("abc" as ChainId, faucetPath); const memo = "My first contract on chain"; const theMsg: MsgSend = { @@ -271,9 +259,11 @@ describe("RestClient", () => { const client = new RestClient(httpUrl); const account = (await client.authAccounts(faucetAddress)).result.value; - const signBytes = makeSignBytes([theMsg], fee, defaultNetworkId, memo, account) as SignableBytes; - const rawSignature = await wallet.createTransactionSignature(signer, signBytes, PrehashType.Sha256); - const signature = encodeSecp256k1Signature(signer.pubkey.data, rawSignature); + const signBytes = makeSignBytes([theMsg], fee, defaultNetworkId, memo, account); + const signature = encodeSecp256k1Signature( + await faucetPen.getPubkey(), + await faucetPen.createSignature(signBytes), + ); const signedTx = makeSignedTx(theMsg, fee, memo, signature); const result = await client.postTx(marshalTx(signedTx)); // console.log("Raw log:", result.raw_log); @@ -282,8 +272,6 @@ describe("RestClient", () => { it("can upload, instantiate and execute wasm", async () => { pendingWithoutCosmos(); - const wallet = Secp256k1HdWallet.fromMnemonic(faucetMnemonic); - const signer = await wallet.createIdentity("abc" as ChainId, faucetPath); const client = new RestClient(httpUrl); const transferAmount: readonly Coin[] = [ @@ -303,7 +291,7 @@ describe("RestClient", () => { // upload { // console.log("Raw log:", result.raw_log); - const result = await uploadContract(client, wallet, signer); + const result = await uploadContract(client, faucetPen); expect(result.code).toBeFalsy(); const logs = parseSuccess(result.raw_log); const codeIdAttr = findAttribute(logs, "message", "code_id"); @@ -318,8 +306,7 @@ describe("RestClient", () => { { const result = await instantiateContract( client, - wallet, - signer, + faucetPen, codeId, beneficiaryAddress, transferAmount, @@ -338,7 +325,7 @@ describe("RestClient", () => { // execute { - const result = await executeContract(client, wallet, signer, contractAddress); + const result = await executeContract(client, faucetPen, contractAddress); expect(result.code).toBeFalsy(); // console.log("Raw log:", result.raw_log); const [firstLog] = parseSuccess(result.raw_log); @@ -356,8 +343,6 @@ describe("RestClient", () => { describe("query", () => { it("can list upload code", async () => { pendingWithoutCosmos(); - const wallet = Secp256k1HdWallet.fromMnemonic(faucetMnemonic); - const signer = await wallet.createIdentity("abc" as ChainId, faucetPath); const client = new RestClient(httpUrl); // check with contracts were here first to compare @@ -366,7 +351,7 @@ describe("RestClient", () => { const numExisting = existingInfos.length; // upload data - const result = await uploadContract(client, wallet, signer); + const result = await uploadContract(client, faucetPen); expect(result.code).toBeFalsy(); const logs = parseSuccess(result.raw_log); const codeIdAttr = findAttribute(logs, "message", "code_id"); @@ -387,8 +372,6 @@ describe("RestClient", () => { it("can list contracts and get info", async () => { pendingWithoutCosmos(); - const wallet = Secp256k1HdWallet.fromMnemonic(faucetMnemonic); - const signer = await wallet.createIdentity("abc" as ChainId, faucetPath); const client = new RestClient(httpUrl); const beneficiaryAddress = makeRandomAddress(); const transferAmount: readonly Coin[] = [ @@ -404,7 +387,7 @@ describe("RestClient", () => { if (existingInfos.length > 0) { codeId = existingInfos[existingInfos.length - 1].id; } else { - const uploaded = await uploadContract(client, wallet, signer); + const uploaded = await uploadContract(client, faucetPen); expect(uploaded.code).toBeFalsy(); const uploadLogs = parseSuccess(uploaded.raw_log); const codeIdAttr = findAttribute(uploadLogs, "message", "code_id"); @@ -414,14 +397,7 @@ describe("RestClient", () => { // create new instance and compare before and after const existingContracts = await client.listContractAddresses(); - const result = await instantiateContract( - client, - wallet, - signer, - codeId, - beneficiaryAddress, - transferAmount, - ); + const result = await instantiateContract(client, faucetPen, codeId, beneficiaryAddress, transferAmount); expect(result.code).toBeFalsy(); const logs = parseSuccess(result.raw_log); const contractAddressAttr = findAttribute(logs, "message", "contract_address"); From 6ed7305f8acf08a6253c8aac5e3ea4c4b0ca1591 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Fri, 7 Feb 2020 09:03:14 +0100 Subject: [PATCH 3/7] Enable await-promise linter rule --- tslint.json | 1 + 1 file changed, 1 insertion(+) diff --git a/tslint.json b/tslint.json index f2c54d8c..1e301e42 100644 --- a/tslint.json +++ b/tslint.json @@ -4,6 +4,7 @@ "jsRules": {}, "rules": { "array-type": [true, "array"], + "await-promise": true, "callable-types": false, "comment-format": [true, "check-space"], "curly": false, From 999f0f3830888bf2a81e8729b9d9c3c90b018706 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Fri, 7 Feb 2020 09:19:31 +0100 Subject: [PATCH 4/7] 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; } From d7cf91eb4c087030e738528bfad7cafa56e2e4ce Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Fri, 7 Feb 2020 09:25:43 +0100 Subject: [PATCH 5/7] Add Pen to CLI imports --- packages/cli/src/cli.ts | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/packages/cli/src/cli.ts b/packages/cli/src/cli.ts index 57ecd587..e884299a 100644 --- a/packages/cli/src/cli.ts +++ b/packages/cli/src/cli.ts @@ -38,7 +38,18 @@ export function main(originalArgs: readonly string[]): void { } const imports = new Map([ - ["@cosmwasm/sdk", ["encodeSecp256k1Signature", "makeSignBytes", "marshalTx", "types", "RestClient"]], + [ + "@cosmwasm/sdk", + [ + "encodeSecp256k1Signature", + "makeSignBytes", + "marshalTx", + "Pen", + "RestClient", + "Secp256k1Pen", + "types", + ], + ], [ "@iov/bcp", [ @@ -151,6 +162,12 @@ export function main(originalArgs: readonly string[]): void { const hexHash = toHex(hash); export class NewDummyClass {}; + const pen = await Secp256k1Pen.fromMnemonic( + "zebra slush diet army arrest purpose hawk source west glimpse custom record", + ); + const data = Encoding.toAscii("foo bar"); + const signature = await pen.createSignature(data); + const profile = new UserProfile(); const wallet = profile.addWallet(Ed25519HdWallet.fromMnemonic("degree tackle suggest window test behind mesh extra cover prepare oak script")); const db = levelup(leveldown('./selftest_userprofile_db')); From d46b59db4fcd1e21b2c8ac5c6c0f8cac9facd688 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Fri, 7 Feb 2020 09:29:20 +0100 Subject: [PATCH 6/7] Remove levelup/leveldown functionality from CLI --- packages/cli/.gitignore | 2 -- packages/cli/package.json | 1 - packages/cli/src/cli.ts | 7 ------- yarn.lock | 19 ------------------- 4 files changed, 29 deletions(-) diff --git a/packages/cli/.gitignore b/packages/cli/.gitignore index 2f7cb8a7..68bf3735 100644 --- a/packages/cli/.gitignore +++ b/packages/cli/.gitignore @@ -1,5 +1,3 @@ build/ dist/ docs/ - -selftest_userprofile_db/ diff --git a/packages/cli/package.json b/packages/cli/package.json index 826a6326..44ed3548 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -43,7 +43,6 @@ "babylon": "^6.18.0", "colors": "^1.3.3", "diff": "^4", - "leveldown": "^5.0.0", "recast": "^0.18.0", "ts-node": "^8", "typescript": "~3.7" diff --git a/packages/cli/src/cli.ts b/packages/cli/src/cli.ts index e884299a..5631d517 100644 --- a/packages/cli/src/cli.ts +++ b/packages/cli/src/cli.ts @@ -122,8 +122,6 @@ export function main(originalArgs: readonly string[]): void { console.info(colors.yellow("Available imports:")); console.info(colors.yellow(" * http")); console.info(colors.yellow(" * https")); - console.info(colors.yellow(" * leveldown")); - console.info(colors.yellow(" * levelup")); console.info(colors.yellow(" * from long")); console.info(colors.yellow(" - Long")); for (const moduleName of imports.keys()) { @@ -139,8 +137,6 @@ export function main(originalArgs: readonly string[]): void { console.info(colors.yellow(" - toHex")); let init = ` - import leveldown = require('leveldown'); - import levelup from "levelup"; import * as http from 'http'; import * as https from 'https'; import Long from "long"; @@ -170,9 +166,6 @@ export function main(originalArgs: readonly string[]): void { const profile = new UserProfile(); const wallet = profile.addWallet(Ed25519HdWallet.fromMnemonic("degree tackle suggest window test behind mesh extra cover prepare oak script")); - const db = levelup(leveldown('./selftest_userprofile_db')); - await profile.storeIn(db, "secret passwd"); - const profileFromDb = await UserProfile.loadFrom(db, "secret passwd"); console.info("Done testing, will exit now."); process.exit(0); diff --git a/yarn.lock b/yarn.lock index b93ac1be..634f4e2f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4898,15 +4898,6 @@ level-supports@~1.0.0: dependencies: xtend "^4.0.2" -leveldown@^5.0.0: - version "5.4.1" - resolved "https://registry.yarnpkg.com/leveldown/-/leveldown-5.4.1.tgz#83a8fdd9bb52b1ed69be2ef59822b6cdfcdb51ec" - integrity sha512-3lMPc7eU3yj5g+qF1qlALInzIYnkySIosR1AsUKFjL9D8fYbTLuENBAeDRZXIG4qeWOAyqRItOoLu2v2avWiMA== - dependencies: - abstract-leveldown "~6.2.1" - napi-macros "~2.0.0" - node-gyp-build "~4.1.0" - levelup@^4.0.0: version "4.3.2" resolved "https://registry.yarnpkg.com/levelup/-/levelup-4.3.2.tgz#31c5b1b29f146d1d35d692e01a6da4d28fa55ebd" @@ -5517,11 +5508,6 @@ nanomatch@^1.2.9: snapdragon "^0.8.1" to-regex "^3.0.1" -napi-macros@~2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/napi-macros/-/napi-macros-2.0.0.tgz#2b6bae421e7b96eb687aa6c77a7858640670001b" - integrity sha512-A0xLykHtARfueITVDernsAWdtIMbOJgKgcluwENp3AlsKN/PloyO10HtmoqnFAQAcxPkgZN7wdfPfEd0zNGxbg== - natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" @@ -5556,11 +5542,6 @@ node-fetch@^2.3.0, node-fetch@^2.5.0: resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd" integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA== -node-gyp-build@~4.1.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.1.1.tgz#d7270b5d86717068d114cc57fff352f96d745feb" - integrity sha512-dSq1xmcPDKPZ2EED2S6zw/b9NKsqzXRE6dVr8TVQnI3FJOTteUMuqF3Qqs6LZg+mLGYJWqQzMbIjMtJqTv87nQ== - node-gyp@^5.0.2: version "5.0.7" resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-5.0.7.tgz#dd4225e735e840cf2870e4037c2ed9c28a31719e" From 11d369b4a17381c232a26bd4aeb0f1e78df695e8 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Fri, 7 Feb 2020 09:35:24 +0100 Subject: [PATCH 7/7] Convert CLI examples to pen --- packages/cli/README.md | 5 ++-- packages/cli/examples/local_faucet.ts | 5 +--- packages/cli/package.json | 2 -- packages/cli/src/cli.ts | 40 --------------------------- 4 files changed, 3 insertions(+), 49 deletions(-) diff --git a/packages/cli/README.md b/packages/cli/README.md index 40da3cca..c40c677a 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -64,9 +64,8 @@ const sendTokensMsg: types.MsgSend = { }, }; -const signBytes = makeSignBytes([sendTokensMsg], defaultFee, defaultNetworkId, memo, account) as SignableBytes; -const rawSignature = await wallet.createTransactionSignature(signer, signBytes, PrehashType.Sha256); -const signature = encodeSecp256k1Signature(signer.pubkey.data, rawSignature); +const signBytes = makeSignBytes([sendTokensMsg], defaultFee, defaultNetworkId, memo, account); +const signature = encodeSecp256k1Signature(pen.pubkey, await pen.createSignature(signBytes)); const signedTx: types.StdTx = { msg: [sendTokensMsg], fee: defaultFee, diff --git a/packages/cli/examples/local_faucet.ts b/packages/cli/examples/local_faucet.ts index 2b6c6522..21199c9c 100644 --- a/packages/cli/examples/local_faucet.ts +++ b/packages/cli/examples/local_faucet.ts @@ -12,10 +12,7 @@ const defaultFee: types.StdFee = { 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 faucetPath = HdPaths.cosmos(0); const faucetAddress = "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6"; -const wallet = Secp256k1HdWallet.fromMnemonic(faucetMnemonic); -const signer = await wallet.createIdentity("unused_value" as ChainId, faucetPath); - +const pen = await Secp256k1Pen.fromMnemonic(faucetMnemonic); const client = new RestClient(defaultHttpUrl); diff --git a/packages/cli/package.json b/packages/cli/package.json index 44ed3548..156d8d11 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -34,10 +34,8 @@ ], "dependencies": { "@cosmwasm/sdk": "^0.0.3", - "@iov/bcp": "^2.0.0-alpha.7", "@iov/crypto": "^2.0.0-alpha.7", "@iov/encoding": "^2.0.0-alpha.7", - "@iov/keycontrol": "^2.0.0-alpha.7", "@iov/utils": "^2.0.0-alpha.7", "argparse": "^1.0.10", "babylon": "^6.18.0", diff --git a/packages/cli/src/cli.ts b/packages/cli/src/cli.ts index 5631d517..d39287a9 100644 --- a/packages/cli/src/cli.ts +++ b/packages/cli/src/cli.ts @@ -50,29 +50,6 @@ export function main(originalArgs: readonly string[]): void { "types", ], ], - [ - "@iov/bcp", - [ - "Address", - "Algorithm", - "ChainId", - "Nonce", - "PrehashType", - "PubkeyBytes", - "SendTransaction", - "SignableBytes", - "TokenTicker", - "TransactionId", - // block info - "BlockInfoPending", - "BlockInfoSucceeded", - "BlockInfoFailed", - "BlockInfo", - "isBlockInfoPending", - "isBlockInfoSucceeded", - "isBlockInfoFailed", - ], - ], [ "@iov/crypto", [ @@ -101,20 +78,6 @@ export function main(originalArgs: readonly string[]): void { "Uint64", ], ], - [ - "@iov/keycontrol", - [ - "Ed25519HdWallet", - "HdPaths", - "Keyring", - "Secp256k1HdWallet", - "UserProfile", - "Wallet", - "WalletId", - "WalletImplementationIdString", - "WalletSerializationString", - ], - ], ["@iov/utils", ["sleep"]], ]); @@ -164,9 +127,6 @@ export function main(originalArgs: readonly string[]): void { const data = Encoding.toAscii("foo bar"); const signature = await pen.createSignature(data); - const profile = new UserProfile(); - const wallet = profile.addWallet(Ed25519HdWallet.fromMnemonic("degree tackle suggest window test behind mesh extra cover prepare oak script")); - console.info("Done testing, will exit now."); process.exit(0); `;