sdk38: Add Wallet interface and implement for Secp256k1Wallet

This commit is contained in:
willclarktech 2020-07-08 16:34:58 +02:00
parent 36b7bdf353
commit 821b666f98
No known key found for this signature in database
GPG Key ID: 551A86E2E398ADF7
9 changed files with 207 additions and 128 deletions

View File

@ -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: {

View File

@ -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,

View File

@ -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";

View File

@ -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,

View File

@ -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[] = [

View File

@ -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");
});
});
});

View File

@ -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);
}
}

View File

@ -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";

View File

@ -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>;
}