Merge pull request #769 from cosmos/760-multiple-accounts-hd-wallets
Add support for multiple accounts to HD wallets
This commit is contained in:
commit
3995a93368
10
CHANGELOG.md
10
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
|
||||
|
||||
|
||||
@ -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");
|
||||
});
|
||||
});
|
||||
|
||||
@ -55,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,
|
||||
@ -64,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", () => {
|
||||
@ -98,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", () => {
|
||||
|
||||
@ -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<Secp256k1HdWalletOptions> {
|
||||
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<Secp256k1HdWalletOptions> = {},
|
||||
): Promise<Secp256k1HdWallet> {
|
||||
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<Secp256k1HdWalletOptions> = {},
|
||||
): Promise<Secp256k1HdWallet> {
|
||||
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<readonly AccountData[]> {
|
||||
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<AminoSignResponse> {
|
||||
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),
|
||||
};
|
||||
}
|
||||
|
||||
@ -319,12 +311,10 @@ export class Secp256k1HdWallet implements OfflineAminoSigner {
|
||||
): Promise<string> {
|
||||
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));
|
||||
|
||||
@ -341,4 +331,28 @@ export class Secp256k1HdWallet implements OfflineAminoSigner {
|
||||
};
|
||||
return JSON.stringify(out);
|
||||
}
|
||||
|
||||
private async getKeyPair(hdPath: HdPath): Promise<Secp256k1Keypair> {
|
||||
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<readonly AccountDataWithPrivkey[]> {
|
||||
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,
|
||||
};
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<GasLimits<CosmWasmFeeTable>>; // 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(
|
||||
|
||||
@ -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)}`);
|
||||
}
|
||||
});
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -111,7 +111,7 @@ export async function main(originalArgs: readonly string[]): Promise<void> {
|
||||
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 = {
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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<Secp256k1HdWalletOptions> = {},
|
||||
): Promise<ModifyingSecp256k1HdWallet> {
|
||||
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<AminoSignResponse> {
|
||||
@ -252,18 +231,9 @@ export class ModifyingDirectSecp256k1HdWallet extends DirectSecp256k1HdWallet {
|
||||
mnemonic: string,
|
||||
options: Partial<DirectSecp256k1HdWalletOptions> = {},
|
||||
): Promise<DirectSecp256k1HdWallet> {
|
||||
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<DirectSignResponse> {
|
||||
|
||||
@ -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}`;
|
||||
|
||||
@ -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) => {
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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");
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@ -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<DirectSecp256k1HdWalletOptions> {
|
||||
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<DirectSecp256k1HdWalletOptions> = {},
|
||||
): Promise<DirectSecp256k1HdWallet> {
|
||||
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<DirectSecp256k1HdWalletOptions> = {},
|
||||
): Promise<DirectSecp256k1HdWallet> {
|
||||
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<readonly AccountData[]> {
|
||||
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<DirectSignResponse> {
|
||||
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<DirectSignResponse> {
|
||||
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<Secp256k1Keypair> {
|
||||
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<readonly AccountDataWithPrivkey[]> {
|
||||
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,
|
||||
};
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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<Secp256k1HdWalletOptions> = {},
|
||||
): Promise<ModifyingSecp256k1HdWallet> {
|
||||
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<AminoSignResponse> {
|
||||
@ -207,18 +183,9 @@ export class ModifyingDirectSecp256k1HdWallet extends DirectSecp256k1HdWallet {
|
||||
mnemonic: string,
|
||||
options: Partial<DirectSecp256k1HdWalletOptions> = {},
|
||||
): Promise<DirectSecp256k1HdWallet> {
|
||||
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<DirectSignResponse> {
|
||||
|
||||
@ -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) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user