From 5a15b108e6b4c828db5c780aa8cece1038b1b5c7 Mon Sep 17 00:00:00 2001 From: willclarktech Date: Tue, 20 Apr 2021 13:42:02 +0200 Subject: [PATCH 01/13] proto-signing: Add support for multiple accounts to DirectSecp256k1HdWallet --- .../src/directsecp256k1hdwallet.spec.ts | 7 +- .../src/directsecp256k1hdwallet.ts | 129 ++++++++++-------- 2 files changed, 74 insertions(+), 62 deletions(-) diff --git a/packages/proto-signing/src/directsecp256k1hdwallet.spec.ts b/packages/proto-signing/src/directsecp256k1hdwallet.spec.ts index 3ffb995f..fe580c00 100644 --- a/packages/proto-signing/src/directsecp256k1hdwallet.spec.ts +++ b/packages/proto-signing/src/directsecp256k1hdwallet.spec.ts @@ -23,12 +23,13 @@ describe("DirectSecp256k1HdWallet", () => { it("works with options", async () => { const wallet = await DirectSecp256k1HdWallet.fromMnemonic(defaultMnemonic, { bip39Password: "password123", - hdPath: makeCosmoshubPath(123), + hdPaths: [makeCosmoshubPath(123)], prefix: "yolo", }); expect(wallet.mnemonic).toEqual(defaultMnemonic); - expect((wallet as any).pubkey).not.toEqual(defaultPubkey); - expect((wallet as any).address.slice(0, 4)).toEqual("yolo"); + const [{ pubkey, address }] = await wallet.getAccounts(); + expect(pubkey).not.toEqual(defaultPubkey); + expect(address.slice(0, 4)).toEqual("yolo"); }); }); diff --git a/packages/proto-signing/src/directsecp256k1hdwallet.ts b/packages/proto-signing/src/directsecp256k1hdwallet.ts index f2e3853c..e6c993ab 100644 --- a/packages/proto-signing/src/directsecp256k1hdwallet.ts +++ b/packages/proto-signing/src/directsecp256k1hdwallet.ts @@ -5,6 +5,7 @@ import { HdPath, Random, Secp256k1, + Secp256k1Keypair, sha256, Slip10, Slip10Curve, @@ -15,6 +16,10 @@ import { SignDoc } from "./codec/cosmos/tx/v1beta1/tx"; import { AccountData, DirectSignResponse, OfflineDirectSigner } from "./signer"; import { makeSignBytes } from "./signing"; +interface AccountDataWithPrivkey extends AccountData { + readonly privkey: Uint8Array; +} + /** * Derivation information required to derive a keypair and an address from a mnemonic. */ @@ -26,15 +31,19 @@ interface Secp256k1Derivation { export interface DirectSecp256k1HdWalletOptions { /** The password to use when deriving a BIP39 seed from a mnemonic. */ readonly bip39Password: string; - /** The BIP-32/SLIP-10 derivation path. Defaults to the Cosmos Hub/ATOM path `m/44'/118'/0'/0/0`. */ - readonly hdPath: HdPath; + /** The BIP-32/SLIP-10 derivation paths. Defaults to the Cosmos Hub/ATOM path `m/44'/118'/0'/0/0`. */ + readonly hdPaths: readonly HdPath[]; /** The bech32 address prefix (human readable part). Defaults to "cosmos". */ readonly prefix: string; } +interface DirectSecp256k1HdWalletConstructorOptions extends Partial { + readonly seed: Uint8Array; +} + const defaultOptions: DirectSecp256k1HdWalletOptions = { bip39Password: "", - hdPath: makeCosmoshubPath(0), + hdPaths: [makeCosmoshubPath(0)], prefix: "cosmos", }; @@ -44,103 +53,105 @@ export class DirectSecp256k1HdWallet implements OfflineDirectSigner { * Restores a wallet from the given BIP39 mnemonic. * * @param mnemonic Any valid English mnemonic. - * @param options An optional `DirectSecp256k1HdWalletOptions` object optionally containing a bip39Password, hdPath, and prefix. + * @param options An optional `DirectSecp256k1HdWalletOptions` object optionally containing a bip39Password, hdPaths, and prefix. */ public static async fromMnemonic( mnemonic: string, options: Partial = {}, ): Promise { - const { bip39Password, hdPath, prefix } = { - ...defaultOptions, - ...options, - }; const mnemonicChecked = new EnglishMnemonic(mnemonic); - const seed = await Bip39.mnemonicToSeed(mnemonicChecked, bip39Password); - const { privkey } = Slip10.derivePath(Slip10Curve.Secp256k1, seed, hdPath); - const uncompressed = (await Secp256k1.makeKeypair(privkey)).pubkey; - return new DirectSecp256k1HdWallet( - mnemonicChecked, - hdPath, - privkey, - Secp256k1.compressPubkey(uncompressed), - prefix, - ); + const seed = await Bip39.mnemonicToSeed(mnemonicChecked, options.bip39Password); + return new DirectSecp256k1HdWallet(mnemonicChecked, { + ...options, + seed: seed, + }); } /** * Generates a new wallet with a BIP39 mnemonic of the given length. * * @param length The number of words in the mnemonic (12, 15, 18, 21 or 24). - * @param hdPath The BIP-32/SLIP-10 derivation path. Defaults to the Cosmos Hub/ATOM path `m/44'/118'/0'/0/0`. - * @param prefix The bech32 address prefix (human readable part). Defaults to "cosmos". + * @param options An optional `DirectSecp256k1HdWalletOptions` object optionally containing a bip39Password, hdPaths, and prefix. */ public static async generate( length: 12 | 15 | 18 | 21 | 24 = 12, - hdPath: HdPath = makeCosmoshubPath(0), - prefix = "cosmos", + options: Partial = {}, ): Promise { const entropyLength = 4 * Math.floor((11 * length) / 33); const entropy = Random.getBytes(entropyLength); const mnemonic = Bip39.encode(entropy); - return DirectSecp256k1HdWallet.fromMnemonic(mnemonic.toString(), { hdPath: hdPath, prefix: prefix }); + return DirectSecp256k1HdWallet.fromMnemonic(mnemonic.toString(), options); } /** Base secret */ private readonly secret: EnglishMnemonic; - /** Derivation instruction */ + /** BIP39 seed */ + private readonly seed: Uint8Array; + /** Derivation instructions */ private readonly accounts: readonly Secp256k1Derivation[]; - /** Derived data */ - private readonly pubkey: Uint8Array; - private readonly privkey: Uint8Array; - protected constructor( - mnemonic: EnglishMnemonic, - hdPath: HdPath, - privkey: Uint8Array, - pubkey: Uint8Array, - prefix: string, - ) { + protected constructor(mnemonic: EnglishMnemonic, options: DirectSecp256k1HdWalletConstructorOptions) { + const { seed, hdPaths, prefix } = { ...defaultOptions, ...options }; this.secret = mnemonic; - this.accounts = [ - { - hdPath: hdPath, - prefix: prefix, - }, - ]; - this.privkey = privkey; - this.pubkey = pubkey; + this.seed = seed; + this.accounts = hdPaths.map((hdPath) => ({ + hdPath: hdPath, + prefix: prefix, + })); } public get mnemonic(): string { return this.secret.toString(); } - private get address(): string { - return Bech32.encode(this.accounts[0].prefix, rawSecp256k1PubkeyToRawAddress(this.pubkey)); - } - public async getAccounts(): Promise { - return [ - { - algo: "secp256k1", - address: this.address, - pubkey: this.pubkey, - }, - ]; + const accountsWithPrivkeys = await this.getAccountsWithPrivkeys(); + return accountsWithPrivkeys.map(({ algo, pubkey, address }) => ({ + algo: algo, + pubkey: pubkey, + address: address, + })); } - public async signDirect(address: string, signDoc: SignDoc): Promise { - const signBytes = makeSignBytes(signDoc); - if (address !== this.address) { - throw new Error(`Address ${address} not found in wallet`); + public async signDirect(signerAddress: string, signDoc: SignDoc): Promise { + const accounts = await this.getAccountsWithPrivkeys(); + const account = accounts.find(({ address }) => address === signerAddress); + if (account === undefined) { + throw new Error(`Address ${signerAddress} not found in wallet`); } + const { privkey, pubkey } = account; + const signBytes = makeSignBytes(signDoc); const hashedMessage = sha256(signBytes); - const signature = await Secp256k1.createSignature(hashedMessage, this.privkey); + const signature = await Secp256k1.createSignature(hashedMessage, privkey); const signatureBytes = new Uint8Array([...signature.r(32), ...signature.s(32)]); - const stdSignature = encodeSecp256k1Signature(this.pubkey, signatureBytes); + const stdSignature = encodeSecp256k1Signature(pubkey, signatureBytes); return { signed: signDoc, signature: stdSignature, }; } + + private async getKeyPair(hdPath: HdPath): Promise { + const { privkey } = Slip10.derivePath(Slip10Curve.Secp256k1, this.seed, hdPath); + const { pubkey } = await Secp256k1.makeKeypair(privkey); + return { + privkey: privkey, + pubkey: Secp256k1.compressPubkey(pubkey), + }; + } + + private async getAccountsWithPrivkeys(): Promise { + return Promise.all( + this.accounts.map(async ({ hdPath, prefix }) => { + const { privkey, pubkey } = await this.getKeyPair(hdPath); + const address = Bech32.encode(prefix, rawSecp256k1PubkeyToRawAddress(pubkey)); + return { + algo: "secp256k1" as const, + privkey: privkey, + pubkey: pubkey, + address: address, + }; + }), + ); + } } From 5c39bc9a5a90f5848355b83f3a2aa8ca44db89b4 Mon Sep 17 00:00:00 2001 From: willclarktech Date: Tue, 20 Apr 2021 14:12:41 +0200 Subject: [PATCH 02/13] amino: Add support for multiple accounts to Secp256k1HdWallet --- packages/amino/src/secp256k1hdwallet.spec.ts | 7 +- packages/amino/src/secp256k1hdwallet.ts | 136 +++++++++++-------- 2 files changed, 80 insertions(+), 63 deletions(-) diff --git a/packages/amino/src/secp256k1hdwallet.spec.ts b/packages/amino/src/secp256k1hdwallet.spec.ts index 71344f13..aeb02a39 100644 --- a/packages/amino/src/secp256k1hdwallet.spec.ts +++ b/packages/amino/src/secp256k1hdwallet.spec.ts @@ -25,12 +25,13 @@ describe("Secp256k1HdWallet", () => { it("works with options", async () => { const wallet = await Secp256k1HdWallet.fromMnemonic(defaultMnemonic, { bip39Password: "password123", - hdPath: makeCosmoshubPath(123), + hdPaths: [makeCosmoshubPath(123)], prefix: "yolo", }); expect(wallet.mnemonic).toEqual(defaultMnemonic); - expect((wallet as any).pubkey).not.toEqual(defaultPubkey); - expect((wallet as any).address.slice(0, 4)).toEqual("yolo"); + const [account] = await wallet.getAccounts(); + expect(account.pubkey).not.toEqual(defaultPubkey); + expect(account.address.slice(0, 4)).toEqual("yolo"); }); }); diff --git a/packages/amino/src/secp256k1hdwallet.ts b/packages/amino/src/secp256k1hdwallet.ts index 6bd96854..ba394991 100644 --- a/packages/amino/src/secp256k1hdwallet.ts +++ b/packages/amino/src/secp256k1hdwallet.ts @@ -5,6 +5,7 @@ import { pathToString, Random, Secp256k1, + Secp256k1Keypair, sha256, Slip10, Slip10Curve, @@ -27,6 +28,10 @@ import { supportedAlgorithms, } from "./wallet"; +interface AccountDataWithPrivkey extends AccountData { + readonly privkey: Uint8Array; +} + const serializationTypeV1 = "secp256k1wallet-v1"; /** @@ -110,15 +115,19 @@ interface DerivationInfo { export interface Secp256k1HdWalletOptions { /** The password to use when deriving a BIP39 seed from a mnemonic. */ readonly bip39Password: string; - /** The BIP-32/SLIP-10 derivation path. Defaults to the Cosmos Hub/ATOM path `m/44'/118'/0'/0/0`. */ - readonly hdPath: HdPath; + /** The BIP-32/SLIP-10 derivation paths. Defaults to the Cosmos Hub/ATOM path `m/44'/118'/0'/0/0`. */ + readonly hdPaths: readonly HdPath[]; /** The bech32 address prefix (human readable part). Defaults to "cosmos". */ readonly prefix: string; } +interface Secp256k1HdWalletConstructorOptions extends Partial { + readonly seed: Uint8Array; +} + const defaultOptions: Secp256k1HdWalletOptions = { bip39Password: "", - hdPath: makeCosmoshubPath(0), + hdPaths: [makeCosmoshubPath(0)], prefix: "cosmos", }; @@ -127,45 +136,34 @@ export class Secp256k1HdWallet implements OfflineAminoSigner { * Restores a wallet from the given BIP39 mnemonic. * * @param mnemonic Any valid English mnemonic. - * @param options An optional `Secp256k1HdWalletOptions` object optionally containing a bip39Password, hdPath, and prefix. + * @param options An optional `Secp256k1HdWalletOptions` object optionally containing a bip39Password, hdPaths, and prefix. */ public static async fromMnemonic( mnemonic: string, options: Partial = {}, ): Promise { - const { bip39Password, hdPath, prefix } = { - ...defaultOptions, - ...options, - }; const mnemonicChecked = new EnglishMnemonic(mnemonic); - const seed = await Bip39.mnemonicToSeed(mnemonicChecked, bip39Password); - const { privkey } = Slip10.derivePath(Slip10Curve.Secp256k1, seed, hdPath); - const uncompressed = (await Secp256k1.makeKeypair(privkey)).pubkey; - return new Secp256k1HdWallet( - mnemonicChecked, - hdPath, - privkey, - Secp256k1.compressPubkey(uncompressed), - prefix, - ); + const seed = await Bip39.mnemonicToSeed(mnemonicChecked, options.bip39Password); + return new Secp256k1HdWallet(mnemonicChecked, { + ...options, + seed: seed, + }); } /** * Generates a new wallet with a BIP39 mnemonic of the given length. * * @param length The number of words in the mnemonic (12, 15, 18, 21 or 24). - * @param hdPath The BIP-32/SLIP-10 derivation path. Defaults to the Cosmos Hub/ATOM path `m/44'/118'/0'/0/0`. - * @param prefix The bech32 address prefix (human readable part). Defaults to "cosmos". + * @param options An optional `Secp256k1HdWalletOptions` object optionally containing a bip39Password, hdPaths, and prefix. */ public static async generate( length: 12 | 15 | 18 | 21 | 24 = 12, - hdPath: HdPath = makeCosmoshubPath(0), - prefix = "cosmos", + options: Partial = {}, ): Promise { const entropyLength = 4 * Math.floor((11 * length) / 33); const entropy = Random.getBytes(entropyLength); const mnemonic = Bip39.encode(entropy); - return Secp256k1HdWallet.fromMnemonic(mnemonic.toString(), { hdPath: hdPath, prefix: prefix }); + return Secp256k1HdWallet.fromMnemonic(mnemonic.toString(), options); } /** @@ -212,12 +210,17 @@ export class Secp256k1HdWallet implements OfflineAminoSigner { const { mnemonic, accounts } = decryptedDocument; assert(typeof mnemonic === "string"); if (!Array.isArray(accounts)) throw new Error("Property 'accounts' is not an array"); - if (accounts.length !== 1) throw new Error("Property 'accounts' only supports one entry"); - const account = accounts[0]; - if (!isDerivationJson(account)) throw new Error("Account is not in the correct format."); + if (!accounts.every((account) => isDerivationJson(account))) { + throw new Error("Account is not in the correct format."); + } + const firstPrefix = accounts[0].prefix; + if (!accounts.every(({ prefix }) => prefix === firstPrefix)) { + throw new Error("Accounts do not all have the same prefix"); + } + const hdPaths = accounts.map(({ hdPath }) => stringToPath(hdPath)); return Secp256k1HdWallet.fromMnemonic(mnemonic, { - hdPath: stringToPath(account.hdPath), - prefix: account.prefix, + hdPaths: hdPaths, + prefix: firstPrefix, }); } default: @@ -237,58 +240,47 @@ export class Secp256k1HdWallet implements OfflineAminoSigner { /** Base secret */ private readonly secret: EnglishMnemonic; + /** BIP39 seed */ + private readonly seed: Uint8Array; /** Derivation instruction */ private readonly accounts: readonly DerivationInfo[]; - /** Derived data */ - private readonly pubkey: Uint8Array; - private readonly privkey: Uint8Array; - protected constructor( - mnemonic: EnglishMnemonic, - hdPath: HdPath, - privkey: Uint8Array, - pubkey: Uint8Array, - prefix: string, - ) { + protected constructor(mnemonic: EnglishMnemonic, options: Secp256k1HdWalletConstructorOptions) { + const { seed, hdPaths, prefix } = { ...defaultOptions, ...options }; this.secret = mnemonic; - this.accounts = [ - { - hdPath: hdPath, - prefix: prefix, - }, - ]; - this.privkey = privkey; - this.pubkey = pubkey; + this.seed = seed; + this.accounts = hdPaths.map((hdPath) => ({ + hdPath: hdPath, + prefix: prefix, + })); } public get mnemonic(): string { return this.secret.toString(); } - private get address(): string { - return Bech32.encode(this.accounts[0].prefix, rawSecp256k1PubkeyToRawAddress(this.pubkey)); - } - public async getAccounts(): Promise { - return [ - { - algo: "secp256k1", - address: this.address, - pubkey: this.pubkey, - }, - ]; + const accountsWithPrivkeys = await this.getAccountsWithPrivkeys(); + return accountsWithPrivkeys.map(({ algo, pubkey, address }) => ({ + algo: algo, + pubkey: pubkey, + address: address, + })); } public async signAmino(signerAddress: string, signDoc: StdSignDoc): Promise { - if (signerAddress !== this.address) { + const accounts = await this.getAccountsWithPrivkeys(); + const account = accounts.find(({ address }) => address === signerAddress); + if (account === undefined) { throw new Error(`Address ${signerAddress} not found in wallet`); } + const { privkey, pubkey } = account; const message = sha256(serializeSignDoc(signDoc)); - const signature = await Secp256k1.createSignature(message, this.privkey); + const signature = await Secp256k1.createSignature(message, privkey); const signatureBytes = new Uint8Array([...signature.r(32), ...signature.s(32)]); return { signed: signDoc, - signature: encodeSecp256k1Signature(this.pubkey, signatureBytes), + signature: encodeSecp256k1Signature(pubkey, signatureBytes), }; } @@ -341,4 +333,28 @@ export class Secp256k1HdWallet implements OfflineAminoSigner { }; return JSON.stringify(out); } + + private async getKeyPair(hdPath: HdPath): Promise { + const { privkey } = Slip10.derivePath(Slip10Curve.Secp256k1, this.seed, hdPath); + const { pubkey } = await Secp256k1.makeKeypair(privkey); + return { + privkey: privkey, + pubkey: Secp256k1.compressPubkey(pubkey), + }; + } + + private async getAccountsWithPrivkeys(): Promise { + return Promise.all( + this.accounts.map(async ({ hdPath, prefix }) => { + const { privkey, pubkey } = await this.getKeyPair(hdPath); + const address = Bech32.encode(prefix, rawSecp256k1PubkeyToRawAddress(pubkey)); + return { + algo: "secp256k1" as const, + privkey: privkey, + pubkey: pubkey, + address: address, + }; + }), + ); + } } From 308131f4a840f76496b3060e2e92c691716396b4 Mon Sep 17 00:00:00 2001 From: willclarktech Date: Tue, 20 Apr 2021 15:37:20 +0200 Subject: [PATCH 03/13] Update CHANGELOG for [Direct]Secp256k1HdWallet --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e9be8aa..040c20e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -140,6 +140,16 @@ and this project adheres to can be configured. - @cosmjs/tendermint-rpc: Tendermint v34 `TxData` type now includes `codeSpace`, `gasWanted`, and `gasUsed` properties. +- @cosmjs/amino: `Secp256k1HdWallet.fromMnemonic` now accepts a + `Secp256k1HdWalletOptions` argument which includes an array of `hdPaths` + instead of a single `hdPath`. `Secp256k1HdWallet.generate` now also accepts + options via this interface. This adds support for multiple accounts from the + same mnemonic to `Secp256k1HdWallet`. +- @cosmjs/proto-signing: `DirectSecp256k1HdWallet.fromMnemonic` now accepts a + `DirectSecp256k1HdWalletOptions` argument which includes an array of `hdPaths` + instead of a single `hdPath`. `DirectSecp256k1HdWallet.generate` now also + accepts options via this interface. This adds support for multiple accounts + from the same mnemonic to `DirectSecp256k1HdWallet`. ### Deprecated From 27e8ff45a41408fbef9c65ec68205d2c3529f0c8 Mon Sep 17 00:00:00 2001 From: willclarktech Date: Tue, 20 Apr 2021 15:39:52 +0200 Subject: [PATCH 04/13] launchpad: Update tests for new Secp256k1HdWallet options --- .../launchpad/src/lcdapi/lcdclient.spec.ts | 20 +++++++++---------- .../launchpad/src/signingcosmosclient.spec.ts | 8 ++++++-- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/packages/launchpad/src/lcdapi/lcdclient.spec.ts b/packages/launchpad/src/lcdapi/lcdclient.spec.ts index 994dbd32..c1024991 100644 --- a/packages/launchpad/src/lcdapi/lcdclient.spec.ts +++ b/packages/launchpad/src/lcdapi/lcdclient.spec.ts @@ -548,13 +548,13 @@ describe("LcdClient", () => { it("can't send transaction with additional signatures", async () => { pendingWithoutLaunchpad(); const account1 = await Secp256k1HdWallet.fromMnemonic(faucet.mnemonic, { - hdPath: makeCosmoshubPath(0), + hdPaths: [makeCosmoshubPath(0)], }); const account2 = await Secp256k1HdWallet.fromMnemonic(faucet.mnemonic, { - hdPath: makeCosmoshubPath(1), + hdPaths: [makeCosmoshubPath(1)], }); const account3 = await Secp256k1HdWallet.fromMnemonic(faucet.mnemonic, { - hdPath: makeCosmoshubPath(2), + hdPaths: [makeCosmoshubPath(2)], }); const [address1, address2, address3] = await Promise.all( [account1, account2, account3].map(async (wallet) => { @@ -611,7 +611,7 @@ describe("LcdClient", () => { it("can send multiple messages with one signature", async () => { pendingWithoutLaunchpad(); - const wallet = await Secp256k1HdWallet.fromMnemonic(faucet.mnemonic, { hdPath: makeCosmoshubPath(0) }); + const wallet = await Secp256k1HdWallet.fromMnemonic(faucet.mnemonic); const accounts = await wallet.getAccounts(); const [{ address: walletAddress }] = accounts; @@ -666,10 +666,10 @@ describe("LcdClient", () => { it("can send multiple messages with multiple signatures", async () => { pendingWithoutLaunchpad(); const account1 = await Secp256k1HdWallet.fromMnemonic(faucet.mnemonic, { - hdPath: makeCosmoshubPath(0), + hdPaths: [makeCosmoshubPath(0)], }); const account2 = await Secp256k1HdWallet.fromMnemonic(faucet.mnemonic, { - hdPath: makeCosmoshubPath(1), + hdPaths: [makeCosmoshubPath(1)], }); const [address1, address2] = await Promise.all( [account1, account2].map(async (wallet) => { @@ -741,10 +741,10 @@ describe("LcdClient", () => { it("can't send transaction with wrong signature order (1)", async () => { pendingWithoutLaunchpad(); const account1 = await Secp256k1HdWallet.fromMnemonic(faucet.mnemonic, { - hdPath: makeCosmoshubPath(0), + hdPaths: [makeCosmoshubPath(0)], }); const account2 = await Secp256k1HdWallet.fromMnemonic(faucet.mnemonic, { - hdPath: makeCosmoshubPath(1), + hdPaths: [makeCosmoshubPath(1)], }); const [address1, address2] = await Promise.all( [account1, account2].map(async (wallet) => { @@ -811,10 +811,10 @@ describe("LcdClient", () => { it("can't send transaction with wrong signature order (2)", async () => { pendingWithoutLaunchpad(); const account1 = await Secp256k1HdWallet.fromMnemonic(faucet.mnemonic, { - hdPath: makeCosmoshubPath(0), + hdPaths: [makeCosmoshubPath(0)], }); const account2 = await Secp256k1HdWallet.fromMnemonic(faucet.mnemonic, { - hdPath: makeCosmoshubPath(1), + hdPaths: [makeCosmoshubPath(1)], }); const [address1, address2] = await Promise.all( [account1, account2].map(async (wallet) => { diff --git a/packages/launchpad/src/signingcosmosclient.spec.ts b/packages/launchpad/src/signingcosmosclient.spec.ts index a616c74c..ffe40dda 100644 --- a/packages/launchpad/src/signingcosmosclient.spec.ts +++ b/packages/launchpad/src/signingcosmosclient.spec.ts @@ -225,8 +225,12 @@ describe("SigningCosmosClient", () => { describe("appendSignature", () => { it("works", async () => { pendingWithoutLaunchpad(); - const wallet0 = await Secp256k1HdWallet.fromMnemonic(faucet.mnemonic, { hdPath: makeCosmoshubPath(0) }); - const wallet1 = await Secp256k1HdWallet.fromMnemonic(faucet.mnemonic, { hdPath: makeCosmoshubPath(1) }); + const wallet0 = await Secp256k1HdWallet.fromMnemonic(faucet.mnemonic, { + hdPaths: [makeCosmoshubPath(0)], + }); + const wallet1 = await Secp256k1HdWallet.fromMnemonic(faucet.mnemonic, { + hdPaths: [makeCosmoshubPath(1)], + }); const client0 = new SigningCosmosClient(launchpad.endpoint, faucet.address0, wallet0); const client1 = new SigningCosmosClient(launchpad.endpoint, faucet.address1, wallet1); From 93068298004f3eed9ef0fbdb961f712b1c4476ed Mon Sep 17 00:00:00 2001 From: willclarktech Date: Tue, 20 Apr 2021 15:41:09 +0200 Subject: [PATCH 05/13] cosmwasm-launchpad: Update tests for new Secp256k1HdWallet options --- packages/cosmwasm-launchpad/src/cw3cosmwasmclient.spec.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/cosmwasm-launchpad/src/cw3cosmwasmclient.spec.ts b/packages/cosmwasm-launchpad/src/cw3cosmwasmclient.spec.ts index 0e87d4ba..e5cbecce 100644 --- a/packages/cosmwasm-launchpad/src/cw3cosmwasmclient.spec.ts +++ b/packages/cosmwasm-launchpad/src/cw3cosmwasmclient.spec.ts @@ -248,7 +248,7 @@ describe("Cw3CosmWasmClient", () => { contractAddress, ); const voterWallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic, { - hdPath: makeCosmoshubPath(1), + hdPaths: [makeCosmoshubPath(1)], }); const voter = new Cw3CosmWasmClient(launchpad.endpoint, alice.address1, voterWallet, contractAddress); const toAddress = makeRandomAddress(); @@ -295,11 +295,11 @@ describe("Cw3CosmWasmClient", () => { contractAddress, ); const voter1Wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic, { - hdPath: makeCosmoshubPath(1), + hdPaths: [makeCosmoshubPath(1)], }); const voter1 = new Cw3CosmWasmClient(launchpad.endpoint, alice.address1, voter1Wallet, contractAddress); const voter2Wallet = await Secp256k1HdWallet.fromMnemonic(alice.mnemonic, { - hdPath: makeCosmoshubPath(2), + hdPaths: [makeCosmoshubPath(2)], }); const voter2 = new Cw3CosmWasmClient(launchpad.endpoint, alice.address2, voter2Wallet, contractAddress); const toAddress = makeRandomAddress(); From c233a66bd5f0856d53899ed96f1c8458dc098323 Mon Sep 17 00:00:00 2001 From: willclarktech Date: Tue, 20 Apr 2021 15:49:25 +0200 Subject: [PATCH 06/13] stargate: Update tests for new Secp256k1HdWallet options --- packages/stargate/src/multisignature.spec.ts | 2 +- packages/stargate/src/testutils.spec.ts | 45 +++----------------- 2 files changed, 7 insertions(+), 40 deletions(-) diff --git a/packages/stargate/src/multisignature.spec.ts b/packages/stargate/src/multisignature.spec.ts index 5f65a302..57dad9cf 100644 --- a/packages/stargate/src/multisignature.spec.ts +++ b/packages/stargate/src/multisignature.spec.ts @@ -216,7 +216,7 @@ describe("multisignature", () => { [0, 1, 2, 3, 4].map(async (i) => { // Signing environment const wallet = await Secp256k1HdWallet.fromMnemonic(faucet.mnemonic, { - hdPath: makeCosmoshubPath(i), + hdPaths: [makeCosmoshubPath(i)], }); const pubkey = encodeSecp256k1Pubkey((await wallet.getAccounts())[0].pubkey); const address = (await wallet.getAccounts())[0].address; diff --git a/packages/stargate/src/testutils.spec.ts b/packages/stargate/src/testutils.spec.ts index 8c562d5f..4cb12858 100644 --- a/packages/stargate/src/testutils.spec.ts +++ b/packages/stargate/src/testutils.spec.ts @@ -1,12 +1,6 @@ /* eslint-disable @typescript-eslint/naming-convention */ -import { - AminoSignResponse, - makeCosmoshubPath, - Secp256k1HdWallet, - Secp256k1HdWalletOptions, - StdSignDoc, -} from "@cosmjs/amino"; -import { Bip39, EnglishMnemonic, Random, Secp256k1, Slip10, Slip10Curve } from "@cosmjs/crypto"; +import { AminoSignResponse, Secp256k1HdWallet, Secp256k1HdWalletOptions, StdSignDoc } from "@cosmjs/amino"; +import { Bip39, EnglishMnemonic, Random } from "@cosmjs/crypto"; import { Bech32 } from "@cosmjs/encoding"; import { coins, @@ -155,12 +149,6 @@ export const nonExistentAddress = "cosmos1p79apjaufyphcmsn4g07cynqf0wyjuezqu84hd export const nonNegativeIntegerMatcher = /^[0-9]+$/; export const tendermintIdMatcher = /^[0-9A-F]{64}$/; -const defaultHdWalletOptions = { - bip39Password: "", - hdPath: makeCosmoshubPath(0), - prefix: "cosmos", -}; - /** * A class for testing clients using an Amino signer which modifies the transaction it receives before signing */ @@ -169,21 +157,9 @@ export class ModifyingSecp256k1HdWallet extends Secp256k1HdWallet { mnemonic: string, options: Partial = {}, ): Promise { - const { bip39Password, hdPath, prefix } = { - ...defaultHdWalletOptions, - ...options, - }; const mnemonicChecked = new EnglishMnemonic(mnemonic); - const seed = await Bip39.mnemonicToSeed(mnemonicChecked, bip39Password); - const { privkey } = Slip10.derivePath(Slip10Curve.Secp256k1, seed, hdPath); - const uncompressed = (await Secp256k1.makeKeypair(privkey)).pubkey; - return new ModifyingSecp256k1HdWallet( - mnemonicChecked, - hdPath, - privkey, - Secp256k1.compressPubkey(uncompressed), - prefix, - ); + const seed = await Bip39.mnemonicToSeed(mnemonicChecked, options.bip39Password); + return new ModifyingSecp256k1HdWallet(mnemonicChecked, { ...options, seed: seed }); } public async signAmino(signerAddress: string, signDoc: StdSignDoc): Promise { @@ -207,18 +183,9 @@ export class ModifyingDirectSecp256k1HdWallet extends DirectSecp256k1HdWallet { mnemonic: string, options: Partial = {}, ): Promise { - const { bip39Password, hdPath, prefix } = { ...defaultHdWalletOptions, ...options }; const mnemonicChecked = new EnglishMnemonic(mnemonic); - const seed = await Bip39.mnemonicToSeed(mnemonicChecked, bip39Password); - const { privkey } = Slip10.derivePath(Slip10Curve.Secp256k1, seed, hdPath); - const uncompressed = (await Secp256k1.makeKeypair(privkey)).pubkey; - return new ModifyingDirectSecp256k1HdWallet( - mnemonicChecked, - hdPath, - privkey, - Secp256k1.compressPubkey(uncompressed), - prefix, - ); + const seed = await Bip39.mnemonicToSeed(mnemonicChecked, options.bip39Password); + return new ModifyingDirectSecp256k1HdWallet(mnemonicChecked, { ...options, seed: seed }); } public async signDirect(address: string, signDoc: SignDoc): Promise { From d049f6f8db6fbb393fed5f147ace71078f7a8b3b Mon Sep 17 00:00:00 2001 From: willclarktech Date: Tue, 20 Apr 2021 15:52:48 +0200 Subject: [PATCH 07/13] cosmwasm-stargate: Update tests for new Secp256k1HdWallet options --- .../cosmwasm-stargate/src/testutils.spec.ts | 42 +++---------------- 1 file changed, 6 insertions(+), 36 deletions(-) diff --git a/packages/cosmwasm-stargate/src/testutils.spec.ts b/packages/cosmwasm-stargate/src/testutils.spec.ts index b2b86e3a..a6de5881 100644 --- a/packages/cosmwasm-stargate/src/testutils.spec.ts +++ b/packages/cosmwasm-stargate/src/testutils.spec.ts @@ -1,12 +1,6 @@ /* eslint-disable @typescript-eslint/naming-convention */ -import { - AminoSignResponse, - makeCosmoshubPath, - Secp256k1HdWallet, - Secp256k1HdWalletOptions, - StdSignDoc, -} from "@cosmjs/amino"; -import { Bip39, EnglishMnemonic, Random, Secp256k1, Slip10, Slip10Curve } from "@cosmjs/crypto"; +import { AminoSignResponse, Secp256k1HdWallet, Secp256k1HdWalletOptions, StdSignDoc } from "@cosmjs/amino"; +import { Bip39, EnglishMnemonic, Random } from "@cosmjs/crypto"; import { Bech32, fromBase64 } from "@cosmjs/encoding"; import { DirectSecp256k1HdWallet, @@ -203,12 +197,6 @@ export async function makeWasmClient( return QueryClient.withExtensions(tmClient, setupAuthExtension, setupBankExtension, setupWasmExtension); } -const defaultHdWalletOptions = { - bip39Password: "", - hdPath: makeCosmoshubPath(0), - prefix: "cosmos", -}; - /** * A class for testing clients using an Amino signer which modifies the transaction it receives before signing */ @@ -217,18 +205,9 @@ export class ModifyingSecp256k1HdWallet extends Secp256k1HdWallet { mnemonic: string, options: Partial = {}, ): Promise { - const { bip39Password, hdPath, prefix } = { ...defaultHdWalletOptions, ...options }; const mnemonicChecked = new EnglishMnemonic(mnemonic); - const seed = await Bip39.mnemonicToSeed(mnemonicChecked, bip39Password); - const { privkey } = Slip10.derivePath(Slip10Curve.Secp256k1, seed, hdPath); - const uncompressed = (await Secp256k1.makeKeypair(privkey)).pubkey; - return new ModifyingSecp256k1HdWallet( - mnemonicChecked, - hdPath, - privkey, - Secp256k1.compressPubkey(uncompressed), - prefix, - ); + const seed = await Bip39.mnemonicToSeed(mnemonicChecked, options.bip39Password); + return new ModifyingSecp256k1HdWallet(mnemonicChecked, { ...options, seed: seed }); } public async signAmino(signerAddress: string, signDoc: StdSignDoc): Promise { @@ -252,18 +231,9 @@ export class ModifyingDirectSecp256k1HdWallet extends DirectSecp256k1HdWallet { mnemonic: string, options: Partial = {}, ): Promise { - const { bip39Password, hdPath, prefix } = { ...defaultHdWalletOptions, ...options }; const mnemonicChecked = new EnglishMnemonic(mnemonic); - const seed = await Bip39.mnemonicToSeed(mnemonicChecked, bip39Password); - const { privkey } = Slip10.derivePath(Slip10Curve.Secp256k1, seed, hdPath); - const uncompressed = (await Secp256k1.makeKeypair(privkey)).pubkey; - return new ModifyingDirectSecp256k1HdWallet( - mnemonicChecked, - hdPath, - privkey, - Secp256k1.compressPubkey(uncompressed), - prefix, - ); + const seed = await Bip39.mnemonicToSeed(mnemonicChecked, options.bip39Password); + return new ModifyingDirectSecp256k1HdWallet(mnemonicChecked, { ...options, seed: seed }); } public async signDirect(address: string, signDoc: SignDoc): Promise { From 47568bbe4139a7cdb69deab49b5359676520af13 Mon Sep 17 00:00:00 2001 From: willclarktech Date: Tue, 20 Apr 2021 15:54:18 +0200 Subject: [PATCH 08/13] faucet: Update profile for new Secp256k1HdWallet options --- packages/faucet/src/profile.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/faucet/src/profile.ts b/packages/faucet/src/profile.ts index 05cefa17..eccac4f9 100644 --- a/packages/faucet/src/profile.ts +++ b/packages/faucet/src/profile.ts @@ -19,7 +19,7 @@ export async function createWallets( const numberOfIdentities = 1 + numberOfDistributors; for (let i = 0; i < numberOfIdentities; i++) { const path = makeCosmoshubPath(i); - const wallet = await createWallet(mnemonic, { hdPath: path, prefix: addressPrefix }); + const wallet = await createWallet(mnemonic, { hdPaths: [path], prefix: addressPrefix }); const [{ address }] = await wallet.getAccounts(); if (logging) { const role = i === 0 ? "token holder " : `distributor ${i}`; From 69f996a1f11a9cdd9998b224db966c6539e842dc Mon Sep 17 00:00:00 2001 From: willclarktech Date: Tue, 20 Apr 2021 16:01:50 +0200 Subject: [PATCH 09/13] scripts: Fix wasmd/generate_addresses.js --- scripts/wasmd/generate_addresses.js | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/scripts/wasmd/generate_addresses.js b/scripts/wasmd/generate_addresses.js index 763c82cc..78552260 100755 --- a/scripts/wasmd/generate_addresses.js +++ b/scripts/wasmd/generate_addresses.js @@ -35,18 +35,11 @@ const accountsToCreate = [ async function main() { for (const { mnemonic, accountNumbers } of accountsToCreate) { - const wallets = await Promise.all( - accountNumbers.map((accountNumber) => - Secp256k1HdWallet.fromMnemonic(mnemonic, { - hdPath: makeCosmoshubPath(accountNumber), - prefix: prefix, - }), - ), - ); - const accounts = (await Promise.all(wallets.map((wallet) => wallet.getAccounts()))).map( - (accountsForWallet) => accountsForWallet[0], - ); - + const wallet = await Secp256k1HdWallet.fromMnemonic(mnemonic, { + prefix: prefix, + hdPaths: accountNumbers.map(makeCosmoshubPath), + }); + const accounts = await wallet.getAccounts(); console.info("=".repeat(process.stdout.columns)); console.info("mnemonic:", mnemonic); for (const { address, pubkey } of accounts) { From 26fea0074194f87f12e38216b94608c41cd3912c Mon Sep 17 00:00:00 2001 From: willclarktech Date: Tue, 20 Apr 2021 17:13:29 +0200 Subject: [PATCH 10/13] cli: Update selftest for new Secp256k1HdWallet options --- packages/cli/src/cli.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/src/cli.ts b/packages/cli/src/cli.ts index be5d5fe1..62d07995 100644 --- a/packages/cli/src/cli.ts +++ b/packages/cli/src/cli.ts @@ -111,7 +111,7 @@ export async function main(originalArgs: readonly string[]): Promise { assert(accounts[0].address == "cosmos1kxt5x5q2l57ma2d434pqpafxdm0mgeg9c8cvtx"); const mnemonic = Bip39.encode(Random.getBytes(16)).toString(); - const wallet = await Secp256k1HdWallet.fromMnemonic(mnemonic, { hdPath: makeCosmoshubPath(0) }); + const wallet = await Secp256k1HdWallet.fromMnemonic(mnemonic); const [{ address }] = await wallet.getAccounts(); const data = toAscii("foo bar"); const fee: StdFee = { From b5b15d234b3beefc661a497866f12342275e2c2b Mon Sep 17 00:00:00 2001 From: willclarktech Date: Tue, 20 Apr 2021 17:26:10 +0200 Subject: [PATCH 11/13] amino: Simplify Secp256k1HdWallet serialization --- packages/amino/src/secp256k1hdwallet.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/amino/src/secp256k1hdwallet.ts b/packages/amino/src/secp256k1hdwallet.ts index ba394991..56a65f23 100644 --- a/packages/amino/src/secp256k1hdwallet.ts +++ b/packages/amino/src/secp256k1hdwallet.ts @@ -311,12 +311,10 @@ export class Secp256k1HdWallet implements OfflineAminoSigner { ): Promise { const dataToEncrypt: Secp256k1HdWalletData = { mnemonic: this.mnemonic, - accounts: this.accounts.map( - (account): DerivationInfoJson => ({ - hdPath: pathToString(account.hdPath), - prefix: account.prefix, - }), - ), + accounts: this.accounts.map(({ hdPath, prefix }) => ({ + hdPath: pathToString(hdPath), + prefix: prefix, + })), }; const dataToEncryptRaw = toUtf8(JSON.stringify(dataToEncrypt)); From c0c8061ab1d6dda3d74f4302735a843b8006f8ae Mon Sep 17 00:00:00 2001 From: willclarktech Date: Tue, 20 Apr 2021 17:26:35 +0200 Subject: [PATCH 12/13] amino: Add tests for deserialising wallets with multiple accounts --- packages/amino/src/secp256k1hdwallet.spec.ts | 111 ++++++++++++++++++- 1 file changed, 110 insertions(+), 1 deletion(-) diff --git a/packages/amino/src/secp256k1hdwallet.spec.ts b/packages/amino/src/secp256k1hdwallet.spec.ts index aeb02a39..92aa78a9 100644 --- a/packages/amino/src/secp256k1hdwallet.spec.ts +++ b/packages/amino/src/secp256k1hdwallet.spec.ts @@ -56,8 +56,10 @@ describe("Secp256k1HdWallet", () => { const password = "123"; const serialized = await original.serialize(password); const deserialized = await Secp256k1HdWallet.deserialize(serialized, password); + const accounts = await deserialized.getAccounts(); + expect(deserialized.mnemonic).toEqual(defaultMnemonic); - expect(await deserialized.getAccounts()).toEqual([ + expect(accounts).toEqual([ { algo: "secp256k1", address: defaultAddress, @@ -65,6 +67,52 @@ describe("Secp256k1HdWallet", () => { }, ]); }); + + it("can restore multiple accounts", async () => { + const mnemonic = + "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 prefix = "wasm"; + const accountNumbers = [0, 1, 2, 3, 4]; + const hdPaths = accountNumbers.map(makeCosmoshubPath); + const original = await Secp256k1HdWallet.fromMnemonic(mnemonic, { + hdPaths: hdPaths, + prefix: prefix, + }); + const password = "123"; + const serialized = await original.serialize(password); + const deserialized = await Secp256k1HdWallet.deserialize(serialized, password); + const accounts = await deserialized.getAccounts(); + + expect(deserialized.mnemonic).toEqual(mnemonic); + // These values are taken from the generate_addresses.js script in the scripts/wasmd directory + expect(accounts).toEqual([ + { + algo: "secp256k1", + pubkey: fromBase64("A08EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQ"), + address: "wasm1pkptre7fdkl6gfrzlesjjvhxhlc3r4gm32kke3", + }, + { + algo: "secp256k1", + pubkey: fromBase64("AiDosfIbBi54XJ1QjCeApumcy/FjdtF+YhywPf3DKTx7"), + address: "wasm10dyr9899g6t0pelew4nvf4j5c3jcgv0r5d3a5l", + }, + { + algo: "secp256k1", + pubkey: fromBase64("AzQg33JZqH7vSsm09esZY5bZvmzYwE/SY78cA0iLxpD7"), + address: "wasm1xy4yqngt0nlkdcenxymg8tenrghmek4n3u2lwa", + }, + { + algo: "secp256k1", + pubkey: fromBase64("A3gOAlB6aiRTCPvWMQg2+ZbGYNsLd8qlvV28m8p2UhY2"), + address: "wasm142u9fgcjdlycfcez3lw8x6x5h7rfjlnfaallkd", + }, + { + algo: "secp256k1", + pubkey: fromBase64("Aum2063ub/ErUnIUB36sK55LktGUStgcbSiaAnL1wadu"), + address: "wasm1hsm76p4ahyhl5yh3ve9ur49r5kemhp2r93f89d", + }, + ]); + }); }); describe("deserializeWithEncryptionKey", () => { @@ -99,6 +147,67 @@ describe("Secp256k1HdWallet", () => { ]); } }); + + it("can restore multiple accounts", async () => { + const mnemonic = + "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 prefix = "wasm"; + const password = "123"; + const accountNumbers = [0, 1, 2, 3, 4]; + const hdPaths = accountNumbers.map(makeCosmoshubPath); + let serialized: string; + { + const original = await Secp256k1HdWallet.fromMnemonic(mnemonic, { prefix: prefix, hdPaths: hdPaths }); + const anyKdfParams: KdfConfiguration = { + algorithm: "argon2id", + params: { + outputLength: 32, + opsLimit: 4, + memLimitKib: 3 * 1024, + }, + }; + const encryptionKey = await executeKdf(password, anyKdfParams); + serialized = await original.serializeWithEncryptionKey(encryptionKey, anyKdfParams); + } + + { + const kdfConfiguration = extractKdfConfiguration(serialized); + const encryptionKey = await executeKdf(password, kdfConfiguration); + const deserialized = await Secp256k1HdWallet.deserializeWithEncryptionKey(serialized, encryptionKey); + const accounts = await deserialized.getAccounts(); + + expect(deserialized.mnemonic).toEqual(mnemonic); + expect(deserialized.mnemonic).toEqual(mnemonic); + // These values are taken from the generate_addresses.js script in the scripts/wasmd directory + expect(accounts).toEqual([ + { + algo: "secp256k1", + pubkey: fromBase64("A08EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQ"), + address: "wasm1pkptre7fdkl6gfrzlesjjvhxhlc3r4gm32kke3", + }, + { + algo: "secp256k1", + pubkey: fromBase64("AiDosfIbBi54XJ1QjCeApumcy/FjdtF+YhywPf3DKTx7"), + address: "wasm10dyr9899g6t0pelew4nvf4j5c3jcgv0r5d3a5l", + }, + { + algo: "secp256k1", + pubkey: fromBase64("AzQg33JZqH7vSsm09esZY5bZvmzYwE/SY78cA0iLxpD7"), + address: "wasm1xy4yqngt0nlkdcenxymg8tenrghmek4n3u2lwa", + }, + { + algo: "secp256k1", + pubkey: fromBase64("A3gOAlB6aiRTCPvWMQg2+ZbGYNsLd8qlvV28m8p2UhY2"), + address: "wasm142u9fgcjdlycfcez3lw8x6x5h7rfjlnfaallkd", + }, + { + algo: "secp256k1", + pubkey: fromBase64("Aum2063ub/ErUnIUB36sK55LktGUStgcbSiaAnL1wadu"), + address: "wasm1hsm76p4ahyhl5yh3ve9ur49r5kemhp2r93f89d", + }, + ]); + } + }); }); describe("getAccounts", () => { From 580b417b70a8ea37e466766aaf40dd5674b8410e Mon Sep 17 00:00:00 2001 From: willclarktech Date: Tue, 20 Apr 2021 17:53:33 +0200 Subject: [PATCH 13/13] cli: Update examples for new Secp256k1HdWallet options --- packages/cli/examples/coralnet.ts | 6 +++--- packages/cli/examples/faucet_addresses.ts | 10 ++++++---- packages/cli/examples/stargate.ts | 14 ++++++++------ 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/packages/cli/examples/coralnet.ts b/packages/cli/examples/coralnet.ts index f1fc9cc2..9aaa73de 100644 --- a/packages/cli/examples/coralnet.ts +++ b/packages/cli/examples/coralnet.ts @@ -5,7 +5,7 @@ import { GasPrice, GasLimits, makeCosmoshubPath, Secp256k1HdWallet } from "@cosm interface Options { readonly httpUrl: string; readonly bech32prefix: string; - readonly hdPath: HdPath; + readonly hdPaths: readonly HdPath[]; readonly gasPrice: GasPrice; readonly gasLimits: Partial>; // only set the ones you want to override } @@ -14,13 +14,13 @@ const coralnetOptions: Options = { httpUrl: "https://lcd.coralnet.cosmwasm.com", gasPrice: GasPrice.fromString("0.025ushell"), bech32prefix: "coral", - hdPath: makeCosmoshubPath(0), + hdPaths: [makeCosmoshubPath(0)], gasLimits: { upload: 1500000, }, }; -const wallet = await Secp256k1HdWallet.generate(12, coralnetOptions.hdPath, coralnetOptions.bech32prefix); +const wallet = await Secp256k1HdWallet.generate(12, coralnetOptions); const [{ address }] = await wallet.getAccounts(); const client = new SigningCosmWasmClient( diff --git a/packages/cli/examples/faucet_addresses.ts b/packages/cli/examples/faucet_addresses.ts index af88efb0..57efc874 100644 --- a/packages/cli/examples/faucet_addresses.ts +++ b/packages/cli/examples/faucet_addresses.ts @@ -4,9 +4,11 @@ import { toBase64 } from "@cosmjs/encoding"; const mnemonic = "economy stock theory fatal elder harbor betray wasp final emotion task crumble siren bottom lizard educate guess current outdoor pair theory focus wife stone"; -for (let i of [0, 1, 2, 3, 4]) { - const wallet = await Secp256k1HdWallet.fromMnemonic(mnemonic, { hdPath: makeCosmoshubPath(i) }); - const [{ address, pubkey }] = await wallet.getAccounts(); +const accountNumbers = [0, 1, 2, 3, 4]; +const hdPaths = accountNumbers.map(makeCosmoshubPath); +const wallet = await Secp256k1HdWallet.fromMnemonic(mnemonic, { hdPaths: hdPaths }); +const accounts = await wallet.getAccounts(); +accounts.forEach(({ address, pubkey }, i) => { console.info(`Address ${i}: ${address}`); console.info(`Pubkey ${i}: ${toBase64(pubkey)}`); -} +}); diff --git a/packages/cli/examples/stargate.ts b/packages/cli/examples/stargate.ts index f4577461..129c827a 100644 --- a/packages/cli/examples/stargate.ts +++ b/packages/cli/examples/stargate.ts @@ -1,12 +1,14 @@ -import { makeCosmoshubPath, DirectSecp256k1HdWallet } from "@cosmjs/proto-signing"; +import { makeCosmoshubPath } from "@cosmosjs/amino"; +import { DirectSecp256k1HdWallet } from "@cosmjs/proto-signing"; import { assertIsBroadcastTxSuccess, SigningStargateClient } from "@cosmjs/stargate"; -const mnemonic = "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 mnemonic = + "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 path = makeCosmoshubPath(3); const prefix = "cosmos"; -const wallet = await DirectSecp256k1HdWallet.fromMnemonic(mnemonic, path, prefix); -const [firstAccount] = await wallet.getAccounts(); -console.log("Signer address:", firstAccount.address); +const wallet = await DirectSecp256k1HdWallet.fromMnemonic(mnemonic, { hdPaths: [path], prefix: prefix }); +const [account] = await wallet.getAccounts(); +console.log("Signer address:", account.address); const rpcEndpoint = "ws://localhost:26658"; const client = await SigningStargateClient.connectWithSigner(rpcEndpoint, wallet); @@ -16,7 +18,7 @@ const amount = { denom: "ucosm", amount: "1234567", }; -const result = await client.sendTokens(firstAccount.address, recipient, [amount], "Have fun with your star coins"); +const result = await client.sendTokens(account.address, recipient, [amount], "Have fun with your star coins"); assertIsBroadcastTxSuccess(result); console.log("Successfully broadcasted:", result);