diff --git a/packages/faucet/src/profile.ts b/packages/faucet/src/profile.ts index 2dacc568..0cb0a655 100644 --- a/packages/faucet/src/profile.ts +++ b/packages/faucet/src/profile.ts @@ -1,6 +1,6 @@ import { pathToString } from "@cosmjs/crypto"; import { makeCosmoshubPath, Secp256k1HdWallet, SigningCosmosClient } from "@cosmjs/launchpad"; -import { DirectSecp256k1Wallet, isOfflineDirectSigner, OfflineSigner } from "@cosmjs/proto-signing"; +import { DirectSecp256k1HdWallet, isOfflineDirectSigner, OfflineSigner } from "@cosmjs/proto-signing"; import { SigningStargateClient } from "@cosmjs/stargate"; import * as constants from "./constants"; @@ -12,7 +12,7 @@ export async function createWallets( stargate = true, logging = false, ): Promise> { - const createWallet = stargate ? DirectSecp256k1Wallet.fromMnemonic : Secp256k1HdWallet.fromMnemonic; + const createWallet = stargate ? DirectSecp256k1HdWallet.fromMnemonic : Secp256k1HdWallet.fromMnemonic; const wallets = new Array(); // first account is the token holder diff --git a/packages/proto-signing/src/directsecp256k1hdwallet.spec.ts b/packages/proto-signing/src/directsecp256k1hdwallet.spec.ts new file mode 100644 index 00000000..bb8fd105 --- /dev/null +++ b/packages/proto-signing/src/directsecp256k1hdwallet.spec.ts @@ -0,0 +1,88 @@ +import { Secp256k1, Secp256k1Signature, sha256 } from "@cosmjs/crypto"; +import { fromBase64, fromHex } from "@cosmjs/encoding"; +import { coins } from "@cosmjs/launchpad"; + +import { DirectSecp256k1HdWallet } from "./directsecp256k1hdwallet"; +import { makeAuthInfoBytes, makeSignBytes, makeSignDoc } from "./signing"; +import { faucet, testVectors } from "./testutils.spec"; + +describe("DirectSecp256k1HdWallet", () => { + // m/44'/118'/0'/0/0 + // pubkey: 02baa4ef93f2ce84592a49b1d729c074eab640112522a7a89f7d03ebab21ded7b6 + const defaultMnemonic = "special sign fit simple patrol salute grocery chicken wheat radar tonight ceiling"; + const defaultPubkey = fromHex("02baa4ef93f2ce84592a49b1d729c074eab640112522a7a89f7d03ebab21ded7b6"); + const defaultAddress = "cosmos1jhg0e7s6gn44tfc5k37kr04sznyhedtc9rzys5"; + + describe("fromMnemonic", () => { + it("works", async () => { + const wallet = await DirectSecp256k1HdWallet.fromMnemonic(defaultMnemonic); + expect(wallet).toBeTruthy(); + expect(wallet.mnemonic).toEqual(defaultMnemonic); + }); + }); + + describe("generate", () => { + it("defaults to 12 words", async () => { + const wallet = await DirectSecp256k1HdWallet.generate(); + expect(wallet.mnemonic.split(" ").length).toEqual(12); + }); + + it("can use different mnemonic lengths", async () => { + expect((await DirectSecp256k1HdWallet.generate(12)).mnemonic.split(" ").length).toEqual(12); + expect((await DirectSecp256k1HdWallet.generate(15)).mnemonic.split(" ").length).toEqual(15); + expect((await DirectSecp256k1HdWallet.generate(18)).mnemonic.split(" ").length).toEqual(18); + expect((await DirectSecp256k1HdWallet.generate(21)).mnemonic.split(" ").length).toEqual(21); + expect((await DirectSecp256k1HdWallet.generate(24)).mnemonic.split(" ").length).toEqual(24); + }); + }); + + describe("getAccounts", () => { + it("resolves to a list of accounts", async () => { + const wallet = await DirectSecp256k1HdWallet.fromMnemonic(defaultMnemonic); + const accounts = await wallet.getAccounts(); + expect(accounts.length).toEqual(1); + expect(accounts[0]).toEqual({ + address: defaultAddress, + algo: "secp256k1", + pubkey: defaultPubkey, + }); + }); + + it("creates the same address as Go implementation", async () => { + const wallet = await DirectSecp256k1HdWallet.fromMnemonic( + "oyster design unusual machine spread century engine gravity focus cave carry slot", + ); + const [{ address }] = await wallet.getAccounts(); + expect(address).toEqual("cosmos1cjsxept9rkggzxztslae9ndgpdyt2408lk850u"); + }); + }); + + describe("signDirect", () => { + it("resolves to valid signature", async () => { + const { sequence, bodyBytes } = testVectors[1]; + const wallet = await DirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic); + const pubkey = { + typeUrl: "/cosmos.crypto.secp256k1.PubKey", + value: fromBase64(faucet.pubkey.value), + }; + const fee = coins(2000, "ucosm"); + const gasLimit = 200000; + const chainId = "simd-testing"; + const accountNumber = 1; + const signDoc = makeSignDoc( + fromHex(bodyBytes), + makeAuthInfoBytes([pubkey], fee, gasLimit, sequence), + chainId, + accountNumber, + ); + const signDocBytes = makeSignBytes(signDoc); + const { signature } = await wallet.signDirect(faucet.address, signDoc); + const valid = await Secp256k1.verifySignature( + Secp256k1Signature.fromFixedLength(fromBase64(signature.signature)), + sha256(signDocBytes), + pubkey.value, + ); + expect(valid).toEqual(true); + }); + }); +}); diff --git a/packages/proto-signing/src/directsecp256k1hdwallet.ts b/packages/proto-signing/src/directsecp256k1hdwallet.ts new file mode 100644 index 00000000..1b869c4b --- /dev/null +++ b/packages/proto-signing/src/directsecp256k1hdwallet.ts @@ -0,0 +1,133 @@ +import { + Bip39, + EnglishMnemonic, + HdPath, + Random, + Secp256k1, + sha256, + Slip10, + Slip10Curve, +} from "@cosmjs/crypto"; +import { + AccountData, + encodeSecp256k1Signature, + makeCosmoshubPath, + rawSecp256k1PubkeyToAddress, +} from "@cosmjs/launchpad"; + +import { cosmos } from "./codec"; +import { DirectSignResponse, OfflineDirectSigner } from "./signer"; +import { makeSignBytes } from "./signing"; + +/** + * Derivation information required to derive a keypair and an address from a mnemonic. + */ +interface Secp256k1Derivation { + readonly hdPath: HdPath; + readonly prefix: string; +} + +/** A wallet for protobuf based signing using SIGN_MODE_DIRECT */ +export class DirectSecp256k1HdWallet implements OfflineDirectSigner { + /** + * 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". + */ + public static async fromMnemonic( + mnemonic: string, + hdPath: HdPath = makeCosmoshubPath(0), + prefix = "cosmos", + ): Promise { + const mnemonicChecked = new EnglishMnemonic(mnemonic); + const seed = await Bip39.mnemonicToSeed(mnemonicChecked); + 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, + ); + } + + /** + * 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". + */ + public static async generate( + length: 12 | 15 | 18 | 21 | 24 = 12, + hdPath: HdPath = makeCosmoshubPath(0), + prefix = "cosmos", + ): 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, prefix); + } + + /** Base secret */ + private readonly secret: EnglishMnemonic; + /** Derivation instruction */ + private readonly accounts: readonly Secp256k1Derivation[]; + /** Derived data */ + private readonly pubkey: Uint8Array; + private readonly privkey: Uint8Array; + + private constructor( + mnemonic: EnglishMnemonic, + hdPath: HdPath, + privkey: Uint8Array, + pubkey: Uint8Array, + prefix: string, + ) { + this.secret = mnemonic; + this.accounts = [ + { + hdPath: hdPath, + prefix: prefix, + }, + ]; + this.privkey = privkey; + this.pubkey = pubkey; + } + + public get mnemonic(): string { + return this.secret.toString(); + } + + private get address(): string { + return rawSecp256k1PubkeyToAddress(this.pubkey, this.accounts[0].prefix); + } + + public async getAccounts(): Promise { + return [ + { + algo: "secp256k1", + address: this.address, + pubkey: this.pubkey, + }, + ]; + } + + public async signDirect(address: string, signDoc: cosmos.tx.v1beta1.ISignDoc): Promise { + const signBytes = makeSignBytes(signDoc); + if (address !== this.address) { + throw new Error(`Address ${address} not found in wallet`); + } + const hashedMessage = sha256(signBytes); + const signature = await Secp256k1.createSignature(hashedMessage, this.privkey); + const signatureBytes = new Uint8Array([...signature.r(32), ...signature.s(32)]); + const stdSignature = encodeSecp256k1Signature(this.pubkey, signatureBytes); + return { + signed: signDoc, + signature: stdSignature, + }; + } +} diff --git a/packages/proto-signing/src/directsecp256k1wallet.spec.ts b/packages/proto-signing/src/directsecp256k1wallet.spec.ts index fc44ec2c..1b17d381 100644 --- a/packages/proto-signing/src/directsecp256k1wallet.spec.ts +++ b/packages/proto-signing/src/directsecp256k1wallet.spec.ts @@ -1,45 +1,28 @@ +/* eslint-disable @typescript-eslint/naming-convention */ import { Secp256k1, Secp256k1Signature, sha256 } from "@cosmjs/crypto"; import { fromBase64, fromHex } from "@cosmjs/encoding"; import { coins } from "@cosmjs/launchpad"; import { DirectSecp256k1Wallet } from "./directsecp256k1wallet"; import { makeAuthInfoBytes, makeSignBytes, makeSignDoc } from "./signing"; -import { faucet, testVectors } from "./testutils.spec"; +import { testVectors } from "./testutils.spec"; describe("DirectSecp256k1Wallet", () => { - // m/44'/118'/0'/0/0 - // pubkey: 02baa4ef93f2ce84592a49b1d729c074eab640112522a7a89f7d03ebab21ded7b6 - const defaultMnemonic = "special sign fit simple patrol salute grocery chicken wheat radar tonight ceiling"; - const defaultPubkey = fromHex("02baa4ef93f2ce84592a49b1d729c074eab640112522a7a89f7d03ebab21ded7b6"); - const defaultAddress = "cosmos1jhg0e7s6gn44tfc5k37kr04sznyhedtc9rzys5"; + const defaultPrivkey = fromHex("b8c462d2bb0c1a92edf44f735021f16c270f28ee2c3d1cb49943a5e70a3c763e"); + const defaultAddress = "cosmos1kxt5x5q2l57ma2d434pqpafxdm0mgeg9c8cvtx"; + const defaultPubkey = fromHex("03f146c27639179e5b67b8646108f48e1a78b146c74939e34afaa5414ad5c93f8a"); - describe("fromMnemonic", () => { + describe("fromKey", () => { it("works", async () => { - const wallet = await DirectSecp256k1Wallet.fromMnemonic(defaultMnemonic); - expect(wallet).toBeTruthy(); - expect(wallet.mnemonic).toEqual(defaultMnemonic); - }); - }); - - describe("generate", () => { - it("defaults to 12 words", async () => { - const wallet = await DirectSecp256k1Wallet.generate(); - expect(wallet.mnemonic.split(" ").length).toEqual(12); - }); - - it("can use different mnemonic lengths", async () => { - expect((await DirectSecp256k1Wallet.generate(12)).mnemonic.split(" ").length).toEqual(12); - expect((await DirectSecp256k1Wallet.generate(15)).mnemonic.split(" ").length).toEqual(15); - expect((await DirectSecp256k1Wallet.generate(18)).mnemonic.split(" ").length).toEqual(18); - expect((await DirectSecp256k1Wallet.generate(21)).mnemonic.split(" ").length).toEqual(21); - expect((await DirectSecp256k1Wallet.generate(24)).mnemonic.split(" ").length).toEqual(24); + const signer = await DirectSecp256k1Wallet.fromKey(defaultPrivkey); + expect(signer).toBeTruthy(); }); }); describe("getAccounts", () => { it("resolves to a list of accounts", async () => { - const wallet = await DirectSecp256k1Wallet.fromMnemonic(defaultMnemonic); - const accounts = await wallet.getAccounts(); + const signer = await DirectSecp256k1Wallet.fromKey(defaultPrivkey); + const accounts = await signer.getAccounts(); expect(accounts.length).toEqual(1); expect(accounts[0]).toEqual({ address: defaultAddress, @@ -47,23 +30,16 @@ describe("DirectSecp256k1Wallet", () => { pubkey: defaultPubkey, }); }); - - it("creates the same address as Go implementation", async () => { - const wallet = await DirectSecp256k1Wallet.fromMnemonic( - "oyster design unusual machine spread century engine gravity focus cave carry slot", - ); - const [{ address }] = await wallet.getAccounts(); - expect(address).toEqual("cosmos1cjsxept9rkggzxztslae9ndgpdyt2408lk850u"); - }); }); describe("signDirect", () => { it("resolves to valid signature", async () => { const { sequence, bodyBytes } = testVectors[1]; - const wallet = await DirectSecp256k1Wallet.fromMnemonic(faucet.mnemonic); + const wallet = await DirectSecp256k1Wallet.fromKey(defaultPrivkey); + const accounts = await wallet.getAccounts(); const pubkey = { typeUrl: "/cosmos.crypto.secp256k1.PubKey", - value: fromBase64(faucet.pubkey.value), + value: accounts[0].pubkey, }; const fee = coins(2000, "ucosm"); const gasLimit = 200000; @@ -76,7 +52,7 @@ describe("DirectSecp256k1Wallet", () => { accountNumber, ); const signDocBytes = makeSignBytes(signDoc); - const { signature } = await wallet.signDirect(faucet.address, signDoc); + const { signature } = await wallet.signDirect(accounts[0].address, signDoc); const valid = await Secp256k1.verifySignature( Secp256k1Signature.fromFixedLength(fromBase64(signature.signature)), sha256(signDocBytes), diff --git a/packages/proto-signing/src/directsecp256k1wallet.ts b/packages/proto-signing/src/directsecp256k1wallet.ts index d6d3960f..8f312152 100644 --- a/packages/proto-signing/src/directsecp256k1wallet.ts +++ b/packages/proto-signing/src/directsecp256k1wallet.ts @@ -1,109 +1,39 @@ -import { - Bip39, - EnglishMnemonic, - HdPath, - Random, - Secp256k1, - sha256, - Slip10, - Slip10Curve, -} from "@cosmjs/crypto"; -import { - AccountData, - encodeSecp256k1Signature, - makeCosmoshubPath, - rawSecp256k1PubkeyToAddress, -} from "@cosmjs/launchpad"; +import { Secp256k1, sha256 } from "@cosmjs/crypto"; +import { AccountData, encodeSecp256k1Signature, rawSecp256k1PubkeyToAddress } from "@cosmjs/launchpad"; import { cosmos } from "./codec"; import { DirectSignResponse, OfflineDirectSigner } from "./signer"; import { makeSignBytes } from "./signing"; /** - * Derivation information required to derive a keypair and an address from a mnemonic. + * A wallet that holds a single secp256k1 keypair. + * + * If you want to work with BIP39 mnemonics and multiple accounts, use DirectSecp256k1HdWallet. */ -interface Secp256k1Derivation { - readonly hdPath: HdPath; - readonly prefix: string; -} - -/** A wallet for protobuf based signing using SIGN_MODE_DIRECT */ export class DirectSecp256k1Wallet implements OfflineDirectSigner { /** - * Restores a wallet from the given BIP39 mnemonic. + * Creates a DirectSecp256k1Wallet from the given private key * - * @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 privkey The private key. * @param prefix The bech32 address prefix (human readable part). Defaults to "cosmos". */ - public static async fromMnemonic( - mnemonic: string, - hdPath: HdPath = makeCosmoshubPath(0), - prefix = "cosmos", - ): Promise { - const mnemonicChecked = new EnglishMnemonic(mnemonic); - const seed = await Bip39.mnemonicToSeed(mnemonicChecked); - const { privkey } = Slip10.derivePath(Slip10Curve.Secp256k1, seed, hdPath); + public static async fromKey(privkey: Uint8Array, prefix = "cosmos"): Promise { const uncompressed = (await Secp256k1.makeKeypair(privkey)).pubkey; - return new DirectSecp256k1Wallet( - mnemonicChecked, - hdPath, - privkey, - Secp256k1.compressPubkey(uncompressed), - prefix, - ); + return new DirectSecp256k1Wallet(privkey, Secp256k1.compressPubkey(uncompressed), prefix); } - /** - * 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". - */ - public static async generate( - length: 12 | 15 | 18 | 21 | 24 = 12, - hdPath: HdPath = makeCosmoshubPath(0), - prefix = "cosmos", - ): Promise { - const entropyLength = 4 * Math.floor((11 * length) / 33); - const entropy = Random.getBytes(entropyLength); - const mnemonic = Bip39.encode(entropy); - return DirectSecp256k1Wallet.fromMnemonic(mnemonic.toString(), hdPath, prefix); - } - - /** Base secret */ - private readonly secret: EnglishMnemonic; - /** Derivation instruction */ - private readonly accounts: readonly Secp256k1Derivation[]; - /** Derived data */ private readonly pubkey: Uint8Array; private readonly privkey: Uint8Array; + private readonly prefix: string; - private constructor( - mnemonic: EnglishMnemonic, - hdPath: HdPath, - privkey: Uint8Array, - pubkey: Uint8Array, - prefix: string, - ) { - this.secret = mnemonic; - this.accounts = [ - { - hdPath: hdPath, - prefix: prefix, - }, - ]; + private constructor(privkey: Uint8Array, pubkey: Uint8Array, prefix: string) { this.privkey = privkey; this.pubkey = pubkey; - } - - public get mnemonic(): string { - return this.secret.toString(); + this.prefix = prefix; } private get address(): string { - return rawSecp256k1PubkeyToAddress(this.pubkey, this.accounts[0].prefix); + return rawSecp256k1PubkeyToAddress(this.pubkey, this.prefix); } public async getAccounts(): Promise { diff --git a/packages/proto-signing/src/index.ts b/packages/proto-signing/src/index.ts index 00597feb..3d8c4c08 100644 --- a/packages/proto-signing/src/index.ts +++ b/packages/proto-signing/src/index.ts @@ -1,7 +1,7 @@ export { Coin } from "./msgs"; export { cosmosField, registered } from "./decorator"; export { EncodeObject, Registry } from "./registry"; -export { DirectSecp256k1Wallet } from "./directsecp256k1wallet"; +export { DirectSecp256k1HdWallet } from "./directsecp256k1hdwallet"; export { decodePubkey, encodePubkey } from "./pubkey"; export { isOfflineDirectSigner, OfflineDirectSigner, OfflineSigner } from "./signer"; export { makeAuthInfoBytes, makeSignBytes, makeSignDoc } from "./signing"; diff --git a/packages/proto-signing/src/signing.spec.ts b/packages/proto-signing/src/signing.spec.ts index 342f46af..c8b1372f 100644 --- a/packages/proto-signing/src/signing.spec.ts +++ b/packages/proto-signing/src/signing.spec.ts @@ -2,7 +2,7 @@ import { fromBase64, fromHex, toHex } from "@cosmjs/encoding"; import { cosmos, google } from "./codec"; -import { DirectSecp256k1Wallet } from "./directsecp256k1wallet"; +import { DirectSecp256k1HdWallet } from "./directsecp256k1hdwallet"; import { defaultRegistry } from "./msgs"; import { Registry, TxBodyValue } from "./registry"; import { makeAuthInfoBytes, makeSignBytes, makeSignDoc } from "./signing"; @@ -27,7 +27,7 @@ describe("signing", () => { const gasLimit = 200000; it("correctly parses test vectors", async () => { - const wallet = await DirectSecp256k1Wallet.fromMnemonic(faucet.mnemonic); + const wallet = await DirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic); const [{ address, pubkey: pubkeyBytes }] = await wallet.getAccounts(); const prefixedPubkeyBytes = Uint8Array.from([0x0a, pubkeyBytes.length, ...pubkeyBytes]); @@ -61,7 +61,7 @@ describe("signing", () => { it("correctly generates test vectors", async () => { const myRegistry = new Registry(); - const wallet = await DirectSecp256k1Wallet.fromMnemonic(faucet.mnemonic); + const wallet = await DirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic); const [{ address, pubkey: pubkeyBytes }] = await wallet.getAccounts(); const publicKey = PubKey.create({ key: pubkeyBytes, diff --git a/packages/proto-signing/types/directsecp256k1hdwallet.d.ts b/packages/proto-signing/types/directsecp256k1hdwallet.d.ts new file mode 100644 index 00000000..f557689d --- /dev/null +++ b/packages/proto-signing/types/directsecp256k1hdwallet.d.ts @@ -0,0 +1,39 @@ +import { HdPath } from "@cosmjs/crypto"; +import { AccountData } from "@cosmjs/launchpad"; +import { cosmos } from "./codec"; +import { DirectSignResponse, OfflineDirectSigner } from "./signer"; +/** A wallet for protobuf based signing using SIGN_MODE_DIRECT */ +export declare class DirectSecp256k1HdWallet implements OfflineDirectSigner { + /** + * 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". + */ + static fromMnemonic(mnemonic: string, hdPath?: HdPath, prefix?: string): Promise; + /** + * 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". + */ + static generate( + length?: 12 | 15 | 18 | 21 | 24, + hdPath?: HdPath, + prefix?: string, + ): Promise; + /** Base secret */ + private readonly secret; + /** Derivation instruction */ + private readonly accounts; + /** Derived data */ + private readonly pubkey; + private readonly privkey; + private constructor(); + get mnemonic(): string; + private get address(); + getAccounts(): Promise; + signDirect(address: string, signDoc: cosmos.tx.v1beta1.ISignDoc): Promise; +} diff --git a/packages/proto-signing/types/directsecp256k1wallet.d.ts b/packages/proto-signing/types/directsecp256k1wallet.d.ts index cd9ea1ef..e5fc3808 100644 --- a/packages/proto-signing/types/directsecp256k1wallet.d.ts +++ b/packages/proto-signing/types/directsecp256k1wallet.d.ts @@ -1,38 +1,23 @@ -import { HdPath } from "@cosmjs/crypto"; import { AccountData } from "@cosmjs/launchpad"; import { cosmos } from "./codec"; import { DirectSignResponse, OfflineDirectSigner } from "./signer"; -/** A wallet for protobuf based signing using SIGN_MODE_DIRECT */ +/** + * A wallet that holds a single secp256k1 keypair. + * + * If you want to work with BIP39 mnemonics and multiple accounts, use DirectSecp256k1HdWallet. + */ export declare class DirectSecp256k1Wallet implements OfflineDirectSigner { /** - * Restores a wallet from the given BIP39 mnemonic. + * Creates a DirectSecp256k1Wallet from the given private key * - * @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 privkey The private key. * @param prefix The bech32 address prefix (human readable part). Defaults to "cosmos". */ - static fromMnemonic(mnemonic: string, hdPath?: HdPath, prefix?: string): Promise; - /** - * 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". - */ - static generate( - length?: 12 | 15 | 18 | 21 | 24, - hdPath?: HdPath, - prefix?: string, - ): Promise; - /** Base secret */ - private readonly secret; - /** Derivation instruction */ - private readonly accounts; - /** Derived data */ + static fromKey(privkey: Uint8Array, prefix?: string): Promise; private readonly pubkey; private readonly privkey; + private readonly prefix; private constructor(); - get mnemonic(): string; private get address(); getAccounts(): Promise; signDirect(address: string, signDoc: cosmos.tx.v1beta1.ISignDoc): Promise; diff --git a/packages/proto-signing/types/index.d.ts b/packages/proto-signing/types/index.d.ts index 00597feb..3d8c4c08 100644 --- a/packages/proto-signing/types/index.d.ts +++ b/packages/proto-signing/types/index.d.ts @@ -1,7 +1,7 @@ export { Coin } from "./msgs"; export { cosmosField, registered } from "./decorator"; export { EncodeObject, Registry } from "./registry"; -export { DirectSecp256k1Wallet } from "./directsecp256k1wallet"; +export { DirectSecp256k1HdWallet } from "./directsecp256k1hdwallet"; export { decodePubkey, encodePubkey } from "./pubkey"; export { isOfflineDirectSigner, OfflineDirectSigner, OfflineSigner } from "./signer"; export { makeAuthInfoBytes, makeSignBytes, makeSignDoc } from "./signing"; diff --git a/packages/stargate/src/signingstargateclient.spec.ts b/packages/stargate/src/signingstargateclient.spec.ts index 279685a6..bd2741b3 100644 --- a/packages/stargate/src/signingstargateclient.spec.ts +++ b/packages/stargate/src/signingstargateclient.spec.ts @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/naming-convention */ import { coin, coins, GasPrice, Secp256k1HdWallet } from "@cosmjs/launchpad"; -import { Coin, cosmosField, DirectSecp256k1Wallet, registered, Registry } from "@cosmjs/proto-signing"; +import { Coin, cosmosField, DirectSecp256k1HdWallet, registered, Registry } from "@cosmjs/proto-signing"; import { assert } from "@cosmjs/utils"; import { Message } from "protobufjs"; @@ -13,7 +13,7 @@ describe("SigningStargateClient", () => { describe("constructor", () => { it("can be constructed with default fees", async () => { pendingWithoutSimapp(); - const wallet = await DirectSecp256k1Wallet.fromMnemonic(faucet.mnemonic); + const wallet = await DirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic); const client = await SigningStargateClient.connectWithWallet(simapp.tendermintUrl, wallet); const openedClient = (client as unknown) as PrivateSigningStargateClient; expect(openedClient.fees).toEqual({ @@ -31,7 +31,7 @@ describe("SigningStargateClient", () => { it("can be constructed with custom registry", async () => { pendingWithoutSimapp(); - const wallet = await DirectSecp256k1Wallet.fromMnemonic(faucet.mnemonic); + const wallet = await DirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic); const registry = new Registry(); registry.register("/custom.MsgCustom", cosmos.bank.v1beta1.MsgSend); const options = { registry: registry }; @@ -42,7 +42,7 @@ describe("SigningStargateClient", () => { it("can be constructed with custom gas price", async () => { pendingWithoutSimapp(); - const wallet = await DirectSecp256k1Wallet.fromMnemonic(faucet.mnemonic); + const wallet = await DirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic); const gasPrice = GasPrice.fromString("3.14utest"); const options = { gasPrice: gasPrice }; const client = await SigningStargateClient.connectWithWallet(simapp.tendermintUrl, wallet, options); @@ -62,7 +62,7 @@ describe("SigningStargateClient", () => { it("can be constructed with custom gas limits", async () => { pendingWithoutSimapp(); - const wallet = await DirectSecp256k1Wallet.fromMnemonic(faucet.mnemonic); + const wallet = await DirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic); const gasLimits = { send: 160000, }; @@ -84,7 +84,7 @@ describe("SigningStargateClient", () => { it("can be constructed with custom gas price and gas limits", async () => { pendingWithoutSimapp(); - const wallet = await DirectSecp256k1Wallet.fromMnemonic(faucet.mnemonic); + const wallet = await DirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic); const gasPrice = GasPrice.fromString("3.14utest"); const gasLimits = { send: 160000, @@ -109,7 +109,7 @@ describe("SigningStargateClient", () => { describe("sendTokens", () => { it("works", async () => { pendingWithoutSimapp(); - const wallet = await DirectSecp256k1Wallet.fromMnemonic(faucet.mnemonic); + const wallet = await DirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic); const client = await SigningStargateClient.connectWithWallet(simapp.tendermintUrl, wallet); const transferAmount = coins(7890, "ucosm"); @@ -135,7 +135,7 @@ describe("SigningStargateClient", () => { describe("signAndBroadcast", () => { it("works with direct mode", async () => { pendingWithoutSimapp(); - const wallet = await DirectSecp256k1Wallet.fromMnemonic(faucet.mnemonic); + const wallet = await DirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic); const msgDelegateTypeUrl = "/cosmos.staking.v1beta1.MsgDelegate"; const registry = new Registry(); registry.register(msgDelegateTypeUrl, cosmos.staking.v1beta1.MsgDelegate); diff --git a/packages/stargate/src/stargateclient.searchtx.spec.ts b/packages/stargate/src/stargateclient.searchtx.spec.ts index ede5bcda..06b12276 100644 --- a/packages/stargate/src/stargateclient.searchtx.spec.ts +++ b/packages/stargate/src/stargateclient.searchtx.spec.ts @@ -2,7 +2,7 @@ import { fromBase64, toBase64 } from "@cosmjs/encoding"; import { Coin, coins } from "@cosmjs/launchpad"; import { - DirectSecp256k1Wallet, + DirectSecp256k1HdWallet, encodePubkey, makeAuthInfoBytes, makeSignDoc, @@ -32,7 +32,7 @@ interface TestTxSend { async function sendTokens( client: StargateClient, registry: Registry, - wallet: DirectSecp256k1Wallet, + wallet: DirectSecp256k1HdWallet, recipient: string, amount: readonly Coin[], memo: string, @@ -96,7 +96,7 @@ describe("StargateClient.searchTx", () => { beforeAll(async () => { if (simappEnabled()) { - const wallet = await DirectSecp256k1Wallet.fromMnemonic(faucet.mnemonic); + const wallet = await DirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic); const client = await StargateClient.connect(simapp.tendermintUrl); const unsuccessfulRecipient = makeRandomAddress(); const successfulRecipient = makeRandomAddress(); diff --git a/packages/stargate/src/stargateclient.spec.ts b/packages/stargate/src/stargateclient.spec.ts index 813d4006..857c1f01 100644 --- a/packages/stargate/src/stargateclient.spec.ts +++ b/packages/stargate/src/stargateclient.spec.ts @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/naming-convention */ import { fromBase64, toBase64 } from "@cosmjs/encoding"; import { - DirectSecp256k1Wallet, + DirectSecp256k1HdWallet, encodePubkey, makeAuthInfoBytes, makeSignDoc, @@ -256,7 +256,7 @@ describe("StargateClient", () => { it("broadcasts a transaction", async () => { pendingWithoutSimapp(); const client = await StargateClient.connect(simapp.tendermintUrl); - const wallet = await DirectSecp256k1Wallet.fromMnemonic(faucet.mnemonic); + const wallet = await DirectSecp256k1HdWallet.fromMnemonic(faucet.mnemonic); const [{ address, pubkey: pubkeyBytes }] = await wallet.getAccounts(); const pubkey = encodePubkey({ type: "tendermint/PubKeySecp256k1",