diff --git a/packages/amino/src/index.ts b/packages/amino/src/index.ts index 21bec81d..cbe92581 100644 --- a/packages/amino/src/index.ts +++ b/packages/amino/src/index.ts @@ -21,7 +21,7 @@ export { } from "./pubkeys"; export { createMultisigThresholdPubkey } from "./multisig"; export { makeCosmoshubPath } from "./paths"; -export { extractKdfConfiguration, Secp256k1HdWallet } from "./secp256k1hdwallet"; +export { extractKdfConfiguration, Secp256k1HdWallet, Secp256k1HdWalletOptions } from "./secp256k1hdwallet"; export { Secp256k1Wallet } from "./secp256k1wallet"; export { decodeSignature, encodeSecp256k1Signature, StdSignature } from "./signature"; export { AminoMsg, makeSignDoc, serializeSignDoc, StdFee, StdSignDoc } from "./signdoc"; diff --git a/packages/amino/src/secp256k1hdwallet.spec.ts b/packages/amino/src/secp256k1hdwallet.spec.ts index 63a5808e..71344f13 100644 --- a/packages/amino/src/secp256k1hdwallet.spec.ts +++ b/packages/amino/src/secp256k1hdwallet.spec.ts @@ -2,6 +2,7 @@ import { Secp256k1, Secp256k1Signature, sha256 } from "@cosmjs/crypto"; import { fromBase64, fromHex } from "@cosmjs/encoding"; +import { makeCosmoshubPath } from "./paths"; import { extractKdfConfiguration, Secp256k1HdWallet } from "./secp256k1hdwallet"; import { serializeSignDoc, StdSignDoc } from "./signdoc"; import { base64Matcher } from "./testutils.spec"; @@ -20,6 +21,17 @@ describe("Secp256k1HdWallet", () => { expect(wallet).toBeTruthy(); expect(wallet.mnemonic).toEqual(defaultMnemonic); }); + + it("works with options", async () => { + const wallet = await Secp256k1HdWallet.fromMnemonic(defaultMnemonic, { + bip39Password: "password123", + hdPath: 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"); + }); }); describe("generate", () => { diff --git a/packages/amino/src/secp256k1hdwallet.ts b/packages/amino/src/secp256k1hdwallet.ts index 1a2b6116..6bd96854 100644 --- a/packages/amino/src/secp256k1hdwallet.ts +++ b/packages/amino/src/secp256k1hdwallet.ts @@ -107,21 +107,38 @@ interface DerivationInfo { readonly prefix: string; } +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 bech32 address prefix (human readable part). Defaults to "cosmos". */ + readonly prefix: string; +} + +const defaultOptions: Secp256k1HdWalletOptions = { + bip39Password: "", + hdPath: makeCosmoshubPath(0), + prefix: "cosmos", +}; + export class Secp256k1HdWallet implements OfflineAminoSigner { /** * Restores a wallet from the given BIP39 mnemonic. * * @param mnemonic Any valid English mnemonic. - * @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, hdPath, and prefix. */ public static async fromMnemonic( mnemonic: string, - hdPath: HdPath = makeCosmoshubPath(0), - prefix = "cosmos", + options: Partial = {}, ): Promise { + const { bip39Password, hdPath, prefix } = { + ...defaultOptions, + ...options, + }; const mnemonicChecked = new EnglishMnemonic(mnemonic); - const seed = await Bip39.mnemonicToSeed(mnemonicChecked); + 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( @@ -148,7 +165,7 @@ export class Secp256k1HdWallet implements OfflineAminoSigner { const entropyLength = 4 * Math.floor((11 * length) / 33); const entropy = Random.getBytes(entropyLength); const mnemonic = Bip39.encode(entropy); - return Secp256k1HdWallet.fromMnemonic(mnemonic.toString(), hdPath, prefix); + return Secp256k1HdWallet.fromMnemonic(mnemonic.toString(), { hdPath: hdPath, prefix: prefix }); } /** @@ -198,7 +215,10 @@ export class Secp256k1HdWallet implements OfflineAminoSigner { 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."); - return Secp256k1HdWallet.fromMnemonic(mnemonic, stringToPath(account.hdPath), account.prefix); + return Secp256k1HdWallet.fromMnemonic(mnemonic, { + hdPath: stringToPath(account.hdPath), + prefix: account.prefix, + }); } default: throw new Error("Unsupported serialization type");