sdk38: Add Wallet interface and implement for Secp256k1Wallet
This commit is contained in:
parent
36b7bdf353
commit
821b666f98
@ -16,7 +16,7 @@ import {
|
||||
wasmdEnabled,
|
||||
} from "./testutils.spec";
|
||||
import { CosmosSdkTx } from "./types";
|
||||
import { Secp256k1Pen } from "./wallet";
|
||||
import { Secp256k1OfflineWallet } from "./wallet";
|
||||
|
||||
interface TestTxSend {
|
||||
readonly sender: string;
|
||||
@ -32,9 +32,9 @@ describe("CosmosClient.searchTx", () => {
|
||||
|
||||
beforeAll(async () => {
|
||||
if (wasmdEnabled()) {
|
||||
const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic);
|
||||
const client = new SigningCosmosClient(wasmd.endpoint, faucet.address, (signBytes) =>
|
||||
pen.sign(signBytes),
|
||||
const wallet = await Secp256k1OfflineWallet.fromMnemonic(faucet.mnemonic);
|
||||
const client = new SigningCosmosClient(wasmd.endpoint, faucet.address, async (signBytes) =>
|
||||
wallet.sign(wallet.address, signBytes),
|
||||
);
|
||||
|
||||
{
|
||||
@ -58,7 +58,7 @@ describe("CosmosClient.searchTx", () => {
|
||||
const { accountNumber, sequence } = await client.getNonce();
|
||||
const chainId = await client.getChainId();
|
||||
const signBytes = makeSignBytes([sendMsg], fee, chainId, memo, accountNumber, sequence);
|
||||
const signature = await pen.sign(signBytes);
|
||||
const signature = await wallet.sign(wallet.address, signBytes);
|
||||
const tx: CosmosSdkTx = {
|
||||
type: "cosmos-sdk/StdTx",
|
||||
value: {
|
||||
|
||||
@ -16,7 +16,7 @@ import {
|
||||
wasmd,
|
||||
} from "./testutils.spec";
|
||||
import { StdFee } from "./types";
|
||||
import { Secp256k1Pen } from "./wallet";
|
||||
import { Secp256k1OfflineWallet } from "./wallet";
|
||||
|
||||
const blockTime = 1_000; // ms
|
||||
|
||||
@ -193,7 +193,7 @@ describe("CosmosClient", () => {
|
||||
describe("postTx", () => {
|
||||
it("works", async () => {
|
||||
pendingWithoutWasmd();
|
||||
const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic);
|
||||
const wallet = await Secp256k1OfflineWallet.fromMnemonic(faucet.mnemonic);
|
||||
const client = new CosmosClient(wasmd.endpoint);
|
||||
|
||||
const memo = "My first contract on chain";
|
||||
@ -224,7 +224,7 @@ describe("CosmosClient", () => {
|
||||
const chainId = await client.getChainId();
|
||||
const { accountNumber, sequence } = await client.getNonce(faucet.address);
|
||||
const signBytes = makeSignBytes([sendMsg], fee, chainId, memo, accountNumber, sequence);
|
||||
const signature = await pen.sign(signBytes);
|
||||
const signature = await wallet.sign(wallet.address, signBytes);
|
||||
const signedTx = {
|
||||
msg: [sendMsg],
|
||||
fee: fee,
|
||||
|
||||
@ -55,4 +55,4 @@ export { findSequenceForSignedTx } from "./sequence";
|
||||
export { encodeSecp256k1Signature, decodeSignature } from "./signature";
|
||||
export { FeeTable, SigningCallback, SigningCosmosClient } from "./signingcosmosclient";
|
||||
export { isStdTx, pubkeyType, CosmosSdkTx, PubKey, StdFee, StdSignature, StdTx } from "./types";
|
||||
export { Pen, Secp256k1Pen, makeCosmoshubPath } from "./wallet";
|
||||
export { OfflineWallet, Secp256k1OfflineWallet, makeCosmoshubPath } from "./wallet";
|
||||
|
||||
@ -20,7 +20,7 @@ import {
|
||||
wasmdEnabled,
|
||||
} from "../testutils.spec";
|
||||
import { StdFee } from "../types";
|
||||
import { makeCosmoshubPath, Secp256k1Pen } from "../wallet";
|
||||
import { makeCosmoshubPath, Secp256k1OfflineWallet } from "../wallet";
|
||||
import { setupAuthExtension } from "./auth";
|
||||
import { TxsResponse } from "./base";
|
||||
import { LcdApiArray, LcdClient, normalizeLcdApiArray } from "./lcdclient";
|
||||
@ -217,9 +217,9 @@ describe("LcdClient", () => {
|
||||
|
||||
beforeAll(async () => {
|
||||
if (wasmdEnabled()) {
|
||||
const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic);
|
||||
const client = new SigningCosmosClient(wasmd.endpoint, faucet.address, (signBytes) =>
|
||||
pen.sign(signBytes),
|
||||
const wallet = await Secp256k1OfflineWallet.fromMnemonic(faucet.mnemonic);
|
||||
const client = new SigningCosmosClient(wasmd.endpoint, faucet.address, async (signBytes) =>
|
||||
wallet.sign(wallet.address, signBytes),
|
||||
);
|
||||
|
||||
{
|
||||
@ -267,7 +267,7 @@ describe("LcdClient", () => {
|
||||
const { accountNumber, sequence } = await client.getNonce();
|
||||
const chainId = await client.getChainId();
|
||||
const signBytes = makeSignBytes([sendMsg], fee, chainId, memo, accountNumber, sequence);
|
||||
const signature = await pen.sign(signBytes);
|
||||
const signature = await wallet.sign(wallet.address, signBytes);
|
||||
const signedTx = {
|
||||
msg: [sendMsg],
|
||||
fee: fee,
|
||||
@ -351,9 +351,9 @@ describe("LcdClient", () => {
|
||||
|
||||
beforeAll(async () => {
|
||||
if (wasmdEnabled()) {
|
||||
const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic);
|
||||
const client = new SigningCosmosClient(wasmd.endpoint, faucet.address, (signBytes) =>
|
||||
pen.sign(signBytes),
|
||||
const wallet = await Secp256k1OfflineWallet.fromMnemonic(faucet.mnemonic);
|
||||
const client = new SigningCosmosClient(wasmd.endpoint, faucet.address, async (signBytes) =>
|
||||
wallet.sign(wallet.address, signBytes),
|
||||
);
|
||||
|
||||
const recipient = makeRandomAddress();
|
||||
@ -534,7 +534,7 @@ describe("LcdClient", () => {
|
||||
describe("postTx", () => {
|
||||
it("can send tokens", async () => {
|
||||
pendingWithoutWasmd();
|
||||
const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic);
|
||||
const wallet = await Secp256k1OfflineWallet.fromMnemonic(faucet.mnemonic);
|
||||
|
||||
const memo = "My first contract on chain";
|
||||
const theMsg: MsgSend = {
|
||||
@ -565,7 +565,7 @@ describe("LcdClient", () => {
|
||||
const { account_number, sequence } = (await client.auth.account(faucet.address)).result.value;
|
||||
|
||||
const signBytes = makeSignBytes([theMsg], fee, wasmd.chainId, memo, account_number, sequence);
|
||||
const signature = await pen.sign(signBytes);
|
||||
const signature = await wallet.sign(wallet.address, signBytes);
|
||||
const signedTx = makeSignedTx(theMsg, fee, memo, signature);
|
||||
const result = await client.postTx(signedTx);
|
||||
expect(result.code).toBeUndefined();
|
||||
@ -582,9 +582,9 @@ describe("LcdClient", () => {
|
||||
|
||||
it("can't send transaction with additional signatures", async () => {
|
||||
pendingWithoutWasmd();
|
||||
const account1 = await Secp256k1Pen.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(0));
|
||||
const account2 = await Secp256k1Pen.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(1));
|
||||
const account3 = await Secp256k1Pen.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(2));
|
||||
const account1 = await Secp256k1OfflineWallet.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(0));
|
||||
const account2 = await Secp256k1OfflineWallet.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(1));
|
||||
const account3 = await Secp256k1OfflineWallet.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(2));
|
||||
const address1 = rawSecp256k1PubkeyToAddress(account1.pubkey, "cosmos");
|
||||
const address2 = rawSecp256k1PubkeyToAddress(account2.pubkey, "cosmos");
|
||||
const address3 = rawSecp256k1PubkeyToAddress(account3.pubkey, "cosmos");
|
||||
@ -622,9 +622,9 @@ describe("LcdClient", () => {
|
||||
const signBytes1 = makeSignBytes([theMsg], fee, wasmd.chainId, memo, an1, sequence1);
|
||||
const signBytes2 = makeSignBytes([theMsg], fee, wasmd.chainId, memo, an2, sequence2);
|
||||
const signBytes3 = makeSignBytes([theMsg], fee, wasmd.chainId, memo, an3, sequence3);
|
||||
const signature1 = await account1.sign(signBytes1);
|
||||
const signature2 = await account2.sign(signBytes2);
|
||||
const signature3 = await account3.sign(signBytes3);
|
||||
const signature1 = await account1.sign(account1.address, signBytes1);
|
||||
const signature2 = await account2.sign(account2.address, signBytes2);
|
||||
const signature3 = await account3.sign(account3.address, signBytes3);
|
||||
const signedTx = {
|
||||
msg: [theMsg],
|
||||
fee: fee,
|
||||
@ -638,7 +638,7 @@ describe("LcdClient", () => {
|
||||
|
||||
it("can send multiple messages with one signature", async () => {
|
||||
pendingWithoutWasmd();
|
||||
const account1 = await Secp256k1Pen.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(0));
|
||||
const account1 = await Secp256k1OfflineWallet.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(0));
|
||||
const address1 = rawSecp256k1PubkeyToAddress(account1.pubkey, "cosmos");
|
||||
|
||||
const memo = "My first contract on chain";
|
||||
@ -683,7 +683,7 @@ describe("LcdClient", () => {
|
||||
const { account_number, sequence } = (await client.auth.account(address1)).result.value;
|
||||
|
||||
const signBytes = makeSignBytes([msg1, msg2], fee, wasmd.chainId, memo, account_number, sequence);
|
||||
const signature1 = await account1.sign(signBytes);
|
||||
const signature1 = await account1.sign(account1.address, signBytes);
|
||||
const signedTx = {
|
||||
msg: [msg1, msg2],
|
||||
fee: fee,
|
||||
@ -696,8 +696,8 @@ describe("LcdClient", () => {
|
||||
|
||||
it("can send multiple messages with multiple signatures", async () => {
|
||||
pendingWithoutWasmd();
|
||||
const account1 = await Secp256k1Pen.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(0));
|
||||
const account2 = await Secp256k1Pen.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(1));
|
||||
const account1 = await Secp256k1OfflineWallet.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(0));
|
||||
const account2 = await Secp256k1OfflineWallet.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(1));
|
||||
const address1 = rawSecp256k1PubkeyToAddress(account1.pubkey, "cosmos");
|
||||
const address2 = rawSecp256k1PubkeyToAddress(account2.pubkey, "cosmos");
|
||||
|
||||
@ -745,8 +745,8 @@ describe("LcdClient", () => {
|
||||
|
||||
const signBytes1 = makeSignBytes([msg2, msg1], fee, wasmd.chainId, memo, an1, sequence1);
|
||||
const signBytes2 = makeSignBytes([msg2, msg1], fee, wasmd.chainId, memo, an2, sequence2);
|
||||
const signature1 = await account1.sign(signBytes1);
|
||||
const signature2 = await account2.sign(signBytes2);
|
||||
const signature1 = await account1.sign(account1.address, signBytes1);
|
||||
const signature2 = await account2.sign(account2.address, signBytes2);
|
||||
const signedTx = {
|
||||
msg: [msg2, msg1],
|
||||
fee: fee,
|
||||
@ -764,8 +764,8 @@ describe("LcdClient", () => {
|
||||
|
||||
it("can't send transaction with wrong signature order (1)", async () => {
|
||||
pendingWithoutWasmd();
|
||||
const account1 = await Secp256k1Pen.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(0));
|
||||
const account2 = await Secp256k1Pen.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(1));
|
||||
const account1 = await Secp256k1OfflineWallet.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(0));
|
||||
const account2 = await Secp256k1OfflineWallet.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(1));
|
||||
const address1 = rawSecp256k1PubkeyToAddress(account1.pubkey, "cosmos");
|
||||
const address2 = rawSecp256k1PubkeyToAddress(account2.pubkey, "cosmos");
|
||||
|
||||
@ -813,8 +813,8 @@ describe("LcdClient", () => {
|
||||
|
||||
const signBytes1 = makeSignBytes([msg1, msg2], fee, wasmd.chainId, memo, an1, sequence1);
|
||||
const signBytes2 = makeSignBytes([msg1, msg2], fee, wasmd.chainId, memo, an2, sequence2);
|
||||
const signature1 = await account1.sign(signBytes1);
|
||||
const signature2 = await account2.sign(signBytes2);
|
||||
const signature1 = await account1.sign(account1.address, signBytes1);
|
||||
const signature2 = await account2.sign(account2.address, signBytes2);
|
||||
const signedTx = {
|
||||
msg: [msg1, msg2],
|
||||
fee: fee,
|
||||
@ -827,8 +827,8 @@ describe("LcdClient", () => {
|
||||
|
||||
it("can't send transaction with wrong signature order (2)", async () => {
|
||||
pendingWithoutWasmd();
|
||||
const account1 = await Secp256k1Pen.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(0));
|
||||
const account2 = await Secp256k1Pen.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(1));
|
||||
const account1 = await Secp256k1OfflineWallet.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(0));
|
||||
const account2 = await Secp256k1OfflineWallet.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(1));
|
||||
const address1 = rawSecp256k1PubkeyToAddress(account1.pubkey, "cosmos");
|
||||
const address2 = rawSecp256k1PubkeyToAddress(account2.pubkey, "cosmos");
|
||||
|
||||
@ -876,8 +876,8 @@ describe("LcdClient", () => {
|
||||
|
||||
const signBytes1 = makeSignBytes([msg2, msg1], fee, wasmd.chainId, memo, an1, sequence1);
|
||||
const signBytes2 = makeSignBytes([msg2, msg1], fee, wasmd.chainId, memo, an2, sequence2);
|
||||
const signature1 = await account1.sign(signBytes1);
|
||||
const signature2 = await account2.sign(signBytes2);
|
||||
const signature1 = await account1.sign(account1.address, signBytes1);
|
||||
const signature2 = await account2.sign(account2.address, signBytes2);
|
||||
const signedTx = {
|
||||
msg: [msg2, msg1],
|
||||
fee: fee,
|
||||
|
||||
@ -4,7 +4,7 @@ import { Coin } from "./coins";
|
||||
import { isPostTxFailure, PrivateCosmWasmClient } from "./cosmosclient";
|
||||
import { SigningCosmosClient } from "./signingcosmosclient";
|
||||
import { makeRandomAddress, pendingWithoutWasmd } from "./testutils.spec";
|
||||
import { Secp256k1Pen } from "./wallet";
|
||||
import { Secp256k1OfflineWallet } from "./wallet";
|
||||
|
||||
const httpUrl = "http://localhost:1317";
|
||||
|
||||
@ -21,8 +21,10 @@ const faucet = {
|
||||
describe("SigningCosmosClient", () => {
|
||||
describe("makeReadOnly", () => {
|
||||
it("can be constructed", async () => {
|
||||
const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic);
|
||||
const client = new SigningCosmosClient(httpUrl, faucet.address, (signBytes) => pen.sign(signBytes));
|
||||
const wallet = await Secp256k1OfflineWallet.fromMnemonic(faucet.mnemonic);
|
||||
const client = new SigningCosmosClient(httpUrl, faucet.address, async (signBytes) =>
|
||||
wallet.sign(wallet.address, signBytes),
|
||||
);
|
||||
expect(client).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@ -30,8 +32,10 @@ describe("SigningCosmosClient", () => {
|
||||
describe("getHeight", () => {
|
||||
it("always uses authAccount implementation", async () => {
|
||||
pendingWithoutWasmd();
|
||||
const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic);
|
||||
const client = new SigningCosmosClient(httpUrl, faucet.address, (signBytes) => pen.sign(signBytes));
|
||||
const wallet = await Secp256k1OfflineWallet.fromMnemonic(faucet.mnemonic);
|
||||
const client = new SigningCosmosClient(httpUrl, faucet.address, async (signBytes) =>
|
||||
wallet.sign(wallet.address, signBytes),
|
||||
);
|
||||
|
||||
const openedClient = (client as unknown) as PrivateCosmWasmClient;
|
||||
const blockLatestSpy = spyOn(openedClient.lcdClient, "blocksLatest").and.callThrough();
|
||||
@ -48,8 +52,10 @@ describe("SigningCosmosClient", () => {
|
||||
describe("sendTokens", () => {
|
||||
it("works", async () => {
|
||||
pendingWithoutWasmd();
|
||||
const pen = await Secp256k1Pen.fromMnemonic(faucet.mnemonic);
|
||||
const client = new SigningCosmosClient(httpUrl, faucet.address, (signBytes) => pen.sign(signBytes));
|
||||
const wallet = await Secp256k1OfflineWallet.fromMnemonic(faucet.mnemonic);
|
||||
const client = new SigningCosmosClient(httpUrl, faucet.address, async (signBytes) =>
|
||||
wallet.sign(wallet.address, signBytes),
|
||||
);
|
||||
|
||||
// instantiate
|
||||
const transferAmount: readonly Coin[] = [
|
||||
|
||||
@ -1,54 +1,74 @@
|
||||
import { Secp256k1, Secp256k1Signature, Sha256 } from "@cosmjs/crypto";
|
||||
import { fromHex, toAscii } from "@cosmjs/encoding";
|
||||
import { fromBase64, fromHex, toAscii } from "@cosmjs/encoding";
|
||||
|
||||
import { decodeSignature } from "./signature";
|
||||
import { Secp256k1Pen } from "./wallet";
|
||||
import { Secp256k1OfflineWallet } from "./wallet";
|
||||
|
||||
describe("Secp256k1OfflineWallet", () => {
|
||||
// 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("Sec256k1Pen", () => {
|
||||
it("can be constructed", async () => {
|
||||
const pen = await Secp256k1Pen.fromMnemonic(
|
||||
"zebra slush diet army arrest purpose hawk source west glimpse custom record",
|
||||
);
|
||||
expect(pen).toBeTruthy();
|
||||
const wallet = await Secp256k1OfflineWallet.fromMnemonic(defaultMnemonic);
|
||||
expect(wallet).toBeTruthy();
|
||||
});
|
||||
|
||||
describe("pubkey", () => {
|
||||
it("returns compressed pubkey", async () => {
|
||||
// special sign fit simple patrol salute grocery chicken wheat radar tonight ceiling
|
||||
// m/44'/118'/0'/0/0
|
||||
// pubkey: 02baa4ef93f2ce84592a49b1d729c074eab640112522a7a89f7d03ebab21ded7b6
|
||||
const pen = await Secp256k1Pen.fromMnemonic(
|
||||
"special sign fit simple patrol salute grocery chicken wheat radar tonight ceiling",
|
||||
);
|
||||
expect(pen.pubkey).toEqual(
|
||||
fromHex("02baa4ef93f2ce84592a49b1d729c074eab640112522a7a89f7d03ebab21ded7b6"),
|
||||
describe("enable", () => {
|
||||
it("resolves to true", async () => {
|
||||
const wallet = await Secp256k1OfflineWallet.fromMnemonic(defaultMnemonic);
|
||||
const enabled = await wallet.enable();
|
||||
expect(enabled).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getAccounts", () => {
|
||||
it("rejects if not enabled", async () => {
|
||||
const wallet = await Secp256k1OfflineWallet.fromMnemonic(defaultMnemonic);
|
||||
await expectAsync(wallet.getAccounts()).toBeRejectedWithError(/wallet not enabled/i);
|
||||
});
|
||||
|
||||
it("resolves to a list of accounts if enabled", async () => {
|
||||
const wallet = await Secp256k1OfflineWallet.fromMnemonic(defaultMnemonic);
|
||||
await wallet.enable();
|
||||
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 Secp256k1OfflineWallet.fromMnemonic(
|
||||
"oyster design unusual machine spread century engine gravity focus cave carry slot",
|
||||
);
|
||||
await wallet.enable();
|
||||
const { address } = (await wallet.getAccounts())[0];
|
||||
expect(address).toEqual("cosmos1cjsxept9rkggzxztslae9ndgpdyt2408lk850u");
|
||||
});
|
||||
});
|
||||
|
||||
describe("sign", () => {
|
||||
it("creates correct signatures", async () => {
|
||||
const pen = await Secp256k1Pen.fromMnemonic(
|
||||
"special sign fit simple patrol salute grocery chicken wheat radar tonight ceiling",
|
||||
);
|
||||
const data = toAscii("foo bar");
|
||||
const { pubkey, signature } = decodeSignature(await pen.sign(data));
|
||||
it("rejects if not enabled", async () => {
|
||||
const wallet = await Secp256k1OfflineWallet.fromMnemonic(defaultMnemonic);
|
||||
const message = toAscii("foo bar");
|
||||
await expectAsync(wallet.sign(defaultAddress, message)).toBeRejectedWithError(/wallet not enabled/i);
|
||||
});
|
||||
|
||||
it("resolves to valid signature if enabled", async () => {
|
||||
const wallet = await Secp256k1OfflineWallet.fromMnemonic(defaultMnemonic);
|
||||
await wallet.enable();
|
||||
const message = toAscii("foo bar");
|
||||
const signature = await wallet.sign(defaultAddress, message);
|
||||
const valid = await Secp256k1.verifySignature(
|
||||
Secp256k1Signature.fromFixedLength(signature),
|
||||
new Sha256(data).digest(),
|
||||
pubkey,
|
||||
Secp256k1Signature.fromFixedLength(fromBase64(signature.signature)),
|
||||
new Sha256(message).digest(),
|
||||
defaultPubkey,
|
||||
);
|
||||
expect(valid).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("address", () => {
|
||||
it("creates same address as Go imlementation", async () => {
|
||||
const pen = await Secp256k1Pen.fromMnemonic(
|
||||
"oyster design unusual machine spread century engine gravity focus cave carry slot",
|
||||
);
|
||||
expect(pen.address("cosmos")).toEqual("cosmos1cjsxept9rkggzxztslae9ndgpdyt2408lk850u");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -15,19 +15,30 @@ import { StdSignature } from "./types";
|
||||
|
||||
export type PrehashType = "sha256" | "sha512" | null;
|
||||
|
||||
/**
|
||||
* A pen is the most basic tool you can think of for signing. It works
|
||||
* everywhere and can be used intuitively by everyone. However, it does not
|
||||
* come with a great amount of features. End of semi suitable metaphor.
|
||||
*
|
||||
* This wraps a single keypair and allows for signing.
|
||||
*
|
||||
* Non-goals of this types are: multi account support, persistency, data migrations,
|
||||
* obfuscation of sensitive data.
|
||||
*/
|
||||
export interface Pen {
|
||||
export type Algo = "secp256k1" | "ed25519" | "sr25519";
|
||||
|
||||
export interface AccountData {
|
||||
// bech32-encoded
|
||||
readonly address: string;
|
||||
readonly algo: Algo;
|
||||
readonly pubkey: Uint8Array;
|
||||
readonly sign: (signBytes: Uint8Array, prehashType?: PrehashType) => Promise<StdSignature>;
|
||||
}
|
||||
|
||||
export interface OfflineWallet {
|
||||
/**
|
||||
* Request access to the user's accounts. Wallet should ask the user to approve or deny access. Returns true if granted access or false if denied.
|
||||
*/
|
||||
readonly enable: () => Promise<boolean>;
|
||||
|
||||
/**
|
||||
* Get AccountData array from wallet. Rejects if not enabled.
|
||||
*/
|
||||
readonly getAccounts: () => Promise<readonly AccountData[]>;
|
||||
|
||||
/**
|
||||
* Request signature from whichever key corresponds to provided bech32-encoded address. Rejects if not enabled.
|
||||
*/
|
||||
readonly sign: (address: string, message: Uint8Array, prehashType?: PrehashType) => Promise<StdSignature>;
|
||||
}
|
||||
|
||||
function prehash(bytes: Uint8Array, type: PrehashType): Uint8Array {
|
||||
@ -57,36 +68,66 @@ export function makeCosmoshubPath(a: number): readonly Slip10RawIndex[] {
|
||||
];
|
||||
}
|
||||
|
||||
export class Secp256k1Pen implements Pen {
|
||||
export class Secp256k1OfflineWallet implements OfflineWallet {
|
||||
public static async fromMnemonic(
|
||||
mnemonic: string,
|
||||
hdPath: readonly Slip10RawIndex[] = makeCosmoshubPath(0),
|
||||
): Promise<Secp256k1Pen> {
|
||||
prefix = "cosmos",
|
||||
): Promise<Secp256k1OfflineWallet> {
|
||||
const seed = await Bip39.mnemonicToSeed(new EnglishMnemonic(mnemonic));
|
||||
const { privkey } = Slip10.derivePath(Slip10Curve.Secp256k1, seed, hdPath);
|
||||
const uncompressed = (await Secp256k1.makeKeypair(privkey)).pubkey;
|
||||
return new Secp256k1Pen(privkey, Secp256k1.compressPubkey(uncompressed));
|
||||
return new Secp256k1OfflineWallet(privkey, Secp256k1.compressPubkey(uncompressed), prefix);
|
||||
}
|
||||
|
||||
public readonly pubkey: Uint8Array;
|
||||
private readonly privkey: Uint8Array;
|
||||
private readonly prefix: string;
|
||||
private readonly algo: Algo = "secp256k1";
|
||||
private enabled = false;
|
||||
|
||||
private constructor(privkey: Uint8Array, pubkey: Uint8Array) {
|
||||
private constructor(privkey: Uint8Array, pubkey: Uint8Array, prefix: string) {
|
||||
this.privkey = privkey;
|
||||
this.pubkey = pubkey;
|
||||
this.prefix = prefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and returns a signature
|
||||
*/
|
||||
public async sign(signBytes: Uint8Array, prehashType: PrehashType = "sha256"): Promise<StdSignature> {
|
||||
const message = prehash(signBytes, prehashType);
|
||||
const signature = await Secp256k1.createSignature(message, this.privkey);
|
||||
const fixedLengthSignature = new Uint8Array([...signature.r(32), ...signature.s(32)]);
|
||||
return encodeSecp256k1Signature(this.pubkey, fixedLengthSignature);
|
||||
public get address(): string {
|
||||
return rawSecp256k1PubkeyToAddress(this.pubkey, this.prefix);
|
||||
}
|
||||
|
||||
public address(prefix: string): string {
|
||||
return rawSecp256k1PubkeyToAddress(this.pubkey, prefix);
|
||||
public async enable(): Promise<boolean> {
|
||||
this.enabled = true;
|
||||
return this.enabled;
|
||||
}
|
||||
|
||||
public async getAccounts(): Promise<readonly AccountData[]> {
|
||||
if (!this.enabled) {
|
||||
throw new Error("Wallet not enabled");
|
||||
}
|
||||
return [
|
||||
{
|
||||
address: this.address,
|
||||
algo: this.algo,
|
||||
pubkey: this.pubkey,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
public async sign(
|
||||
address: string,
|
||||
message: Uint8Array,
|
||||
prehashType: PrehashType = "sha256",
|
||||
): Promise<StdSignature> {
|
||||
if (!this.enabled) {
|
||||
throw new Error("Wallet not enabled");
|
||||
}
|
||||
if (address !== this.address) {
|
||||
throw new Error(`Address ${address} not found in wallet`);
|
||||
}
|
||||
const hashedMessage = prehash(message, prehashType);
|
||||
const signature = await Secp256k1.createSignature(hashedMessage, this.privkey);
|
||||
const signatureBytes = new Uint8Array([...signature.r(32), ...signature.s(32)]);
|
||||
return encodeSecp256k1Signature(this.pubkey, signatureBytes);
|
||||
}
|
||||
}
|
||||
|
||||
2
packages/sdk38/types/index.d.ts
vendored
2
packages/sdk38/types/index.d.ts
vendored
@ -53,4 +53,4 @@ export { findSequenceForSignedTx } from "./sequence";
|
||||
export { encodeSecp256k1Signature, decodeSignature } from "./signature";
|
||||
export { FeeTable, SigningCallback, SigningCosmosClient } from "./signingcosmosclient";
|
||||
export { isStdTx, pubkeyType, CosmosSdkTx, PubKey, StdFee, StdSignature, StdTx } from "./types";
|
||||
export { Pen, Secp256k1Pen, makeCosmoshubPath } from "./wallet";
|
||||
export { OfflineWallet, Secp256k1OfflineWallet, makeCosmoshubPath } from "./wallet";
|
||||
|
||||
50
packages/sdk38/types/wallet.d.ts
vendored
50
packages/sdk38/types/wallet.d.ts
vendored
@ -1,33 +1,45 @@
|
||||
import { Slip10RawIndex } from "@cosmjs/crypto";
|
||||
import { StdSignature } from "./types";
|
||||
export declare type PrehashType = "sha256" | "sha512" | null;
|
||||
/**
|
||||
* A pen is the most basic tool you can think of for signing. It works
|
||||
* everywhere and can be used intuitively by everyone. However, it does not
|
||||
* come with a great amount of features. End of semi suitable metaphor.
|
||||
*
|
||||
* This wraps a single keypair and allows for signing.
|
||||
*
|
||||
* Non-goals of this types are: multi account support, persistency, data migrations,
|
||||
* obfuscation of sensitive data.
|
||||
*/
|
||||
export interface Pen {
|
||||
export declare type Algo = "secp256k1" | "ed25519" | "sr25519";
|
||||
export interface AccountData {
|
||||
readonly address: string;
|
||||
readonly algo: Algo;
|
||||
readonly pubkey: Uint8Array;
|
||||
readonly sign: (signBytes: Uint8Array, prehashType?: PrehashType) => Promise<StdSignature>;
|
||||
}
|
||||
export interface OfflineWallet {
|
||||
/**
|
||||
* Request access to the user's accounts. Wallet should ask the user to approve or deny access. Returns true if granted access or false if denied.
|
||||
*/
|
||||
readonly enable: () => Promise<boolean>;
|
||||
/**
|
||||
* Get AccountData array from wallet. Rejects if not enabled.
|
||||
*/
|
||||
readonly getAccounts: () => Promise<readonly AccountData[]>;
|
||||
/**
|
||||
* Request signature from whichever key corresponds to provided bech32-encoded address. Rejects if not enabled.
|
||||
*/
|
||||
readonly sign: (address: string, message: Uint8Array, prehashType?: PrehashType) => Promise<StdSignature>;
|
||||
}
|
||||
/**
|
||||
* The Cosmoshub derivation path in the form `m/44'/118'/0'/0/a`
|
||||
* with 0-based account index `a`.
|
||||
*/
|
||||
export declare function makeCosmoshubPath(a: number): readonly Slip10RawIndex[];
|
||||
export declare class Secp256k1Pen implements Pen {
|
||||
static fromMnemonic(mnemonic: string, hdPath?: readonly Slip10RawIndex[]): Promise<Secp256k1Pen>;
|
||||
export declare class Secp256k1OfflineWallet implements OfflineWallet {
|
||||
static fromMnemonic(
|
||||
mnemonic: string,
|
||||
hdPath?: readonly Slip10RawIndex[],
|
||||
prefix?: string,
|
||||
): Promise<Secp256k1OfflineWallet>;
|
||||
readonly pubkey: Uint8Array;
|
||||
private readonly privkey;
|
||||
private readonly prefix;
|
||||
private readonly algo;
|
||||
private enabled;
|
||||
private constructor();
|
||||
/**
|
||||
* Creates and returns a signature
|
||||
*/
|
||||
sign(signBytes: Uint8Array, prehashType?: PrehashType): Promise<StdSignature>;
|
||||
address(prefix: string): string;
|
||||
get address(): string;
|
||||
enable(): Promise<boolean>;
|
||||
getAccounts(): Promise<readonly AccountData[]>;
|
||||
sign(address: string, message: Uint8Array, prehashType?: PrehashType): Promise<StdSignature>;
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user