Let OfflineSigner.sign take a StdSignDoc instead of an encoded message

This commit is contained in:
Simon Warta 2020-09-23 15:58:34 +02:00
parent 6c116f3d5a
commit bc890bee73
22 changed files with 160 additions and 119 deletions

View File

@ -47,6 +47,8 @@
`BroadcastTxsResponse.logs` to `unknown[]`.
- @cosmjs/launchpad: Export `StdSignDoc` and create helpers to make and
serialize a `StdSignDoc`: `makeStdSignDoc` and `serializeSignDoc`.
- @cosmjs/launchpad: Let `OfflineSigner.sign` take an `StdSignDoc` instead of an
encoded message.
- @cosmjs/launchpad-ledger: Add package supporting Ledger device integration for
Launchpad. Two new classes are provided: `LedgerSigner` (for most use cases)
and `LaunchpadLedger` for more fine-grained access.

View File

@ -6,7 +6,7 @@ import {
isBroadcastTxFailure,
isMsgSend,
LcdClient,
makeSignBytes,
makeStdSignDoc,
MsgSend,
Secp256k1Wallet,
} from "@cosmjs/launchpad";
@ -103,8 +103,8 @@ describe("CosmWasmClient.searchTx", () => {
};
const { accountNumber, sequence } = await client.getSequence();
const chainId = await client.getChainId();
const signBytes = makeSignBytes([sendMsg], fee, chainId, memo, accountNumber, sequence);
const signature = await wallet.sign(alice.address0, signBytes);
const signDoc = makeStdSignDoc([sendMsg], fee, chainId, memo, accountNumber, sequence);
const signature = await wallet.sign(alice.address0, signDoc);
const tx: CosmosSdkTx = {
type: "cosmos-sdk/StdTx",
value: {

View File

@ -3,7 +3,7 @@ import { Sha256 } from "@cosmjs/crypto";
import { Bech32, fromHex, fromUtf8, toAscii, toBase64 } from "@cosmjs/encoding";
import {
assertIsBroadcastTxSuccess,
makeSignBytes,
makeStdSignDoc,
MsgSend,
Secp256k1Wallet,
StdFee,
@ -236,8 +236,8 @@ describe("CosmWasmClient", () => {
const chainId = await client.getChainId();
const { accountNumber, sequence } = await client.getSequence(alice.address0);
const signBytes = makeSignBytes([sendMsg], fee, chainId, memo, accountNumber, sequence);
const signature = await wallet.sign(alice.address0, signBytes);
const signDoc = makeStdSignDoc([sendMsg], fee, chainId, memo, accountNumber, sequence);
const signature = await wallet.sign(alice.address0, signDoc);
const signedTx = {
msg: [sendMsg],
fee: fee,

View File

@ -10,7 +10,7 @@ import {
coin,
coins,
LcdClient,
makeSignBytes,
makeStdSignDoc,
OfflineSigner,
Secp256k1Wallet,
setupAuthExtension,
@ -125,8 +125,8 @@ async function executeContract(
};
const { account_number, sequence } = (await client.auth.account(alice.address0)).result.value;
const signBytes = makeSignBytes([theMsg], fee, wasmd.chainId, memo, account_number, sequence);
const signature = await signer.sign(alice.address0, signBytes);
const signDoc = makeStdSignDoc([theMsg], fee, wasmd.chainId, memo, account_number, sequence);
const signature = await signer.sign(alice.address0, signDoc);
const signedTx = makeSignedTx(theMsg, fee, memo, signature);
return client.broadcastTx(signedTx);
}

View File

@ -11,7 +11,7 @@ import {
GasLimits,
GasPrice,
isBroadcastTxFailure,
makeSignBytes,
makeStdSignDoc,
Msg,
MsgSend,
OfflineSigner,
@ -360,8 +360,8 @@ export class SigningCosmWasmClient extends CosmWasmClient {
public async signAndBroadcast(msgs: readonly Msg[], fee: StdFee, memo = ""): Promise<BroadcastTxResult> {
const { accountNumber, sequence } = await this.getSequence();
const chainId = await this.getChainId();
const signBytes = makeSignBytes(msgs, fee, chainId, memo, accountNumber, sequence);
const signature = await this.signer.sign(this.senderAddress, signBytes);
const signDoc = makeStdSignDoc(msgs, fee, chainId, memo, accountNumber, sequence);
const signature = await this.signer.sign(this.senderAddress, signDoc);
const signedTx: StdTx = {
msg: msgs,
fee: fee,

View File

@ -34,7 +34,7 @@
</div>
<div>
<label>Message</label>
<textarea id="message">
<textarea id="sign-doc">
</textarea>
</div>
<div>

View File

@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { toBase64 } from "@cosmjs/encoding";
import { makeCosmoshubPath, makeSignBytes, StdFee, StdSignature } from "@cosmjs/launchpad";
import { makeCosmoshubPath, makeStdSignDoc, StdFee, StdSignature } from "@cosmjs/launchpad";
import { LedgerSigner } from "../ledgersigner";
@ -48,7 +48,7 @@ export async function sign(
},
},
];
const signBytes = makeSignBytes(
const signDoc = makeStdSignDoc(
msgs,
defaultFee,
defaultChainId,
@ -56,5 +56,5 @@ export async function sign(
accountNumber,
defaultSequence,
);
return signer.sign(fromAddress, signBytes);
return signer.sign(fromAddress, signDoc);
}

View File

@ -1,5 +1,7 @@
import { toBase64, toUtf8 } from "@cosmjs/encoding";
import { AccountData, makeCosmoshubPath } from "@cosmjs/launchpad";
import { toBase64 } from "@cosmjs/encoding";
import { AccountData, makeCosmoshubPath, StdSignDoc } from "@cosmjs/launchpad";
import { Uint53 } from "@cosmjs/math";
import { assert } from "@cosmjs/utils";
import { LedgerSigner } from "../ledgersigner";
@ -8,28 +10,37 @@ declare const document: any;
let accounts: readonly AccountData[] = [];
function createMessage(accountNumber: number, address: string): string {
return `{
"account_number": ${accountNumber},
"chain_id": "testing",
"fee": {
"amount": [{ "amount": 100, "denom": "ucosm" }],
"gas": 250
function createSignDoc(accountNumber: number, address: string): string {
const signDoc: StdSignDoc = {
// eslint-disable-next-line @typescript-eslint/naming-convention
chain_id: "testing",
// eslint-disable-next-line @typescript-eslint/naming-convention
account_number: `${accountNumber}`,
sequence: "0",
fee: {
amount: [{ amount: "100", denom: "ucosm" }],
gas: "250",
},
"memo": "Some memo",
"msgs": [{
"type": "cosmos-sdk/MsgSend",
"value": {
"amount": [{
"amount": "1234567",
"denom": "ucosm"
}],
"from_address": "${address}",
"to_address": "${address}"
}
}],
"sequence": 0
}`;
memo: "Some memo",
msgs: [
{
type: "cosmos-sdk/MsgSend",
value: {
amount: [
{
amount: "1234567",
denom: "ucosm",
},
],
// eslint-disable-next-line @typescript-eslint/naming-convention
from_address: address,
// eslint-disable-next-line @typescript-eslint/naming-convention
to_address: address,
},
},
],
};
return JSON.stringify(signDoc, null, 2);
}
const signer = new LedgerSigner({
@ -37,7 +48,9 @@ const signer = new LedgerSigner({
hdPaths: [makeCosmoshubPath(0), makeCosmoshubPath(1), makeCosmoshubPath(2)],
});
window.updateMessage = (accountNumber: number) => {
window.updateMessage = (accountNumberInput: unknown) => {
assert(typeof accountNumberInput === "string");
const accountNumber = Uint53.fromString(accountNumberInput).toNumber();
const account = accounts[accountNumber];
if (account === undefined) {
return;
@ -46,15 +59,15 @@ window.updateMessage = (accountNumber: number) => {
const address = accounts[accountNumber].address;
const addressInput = document.getElementById("address");
addressInput.value = address;
const messageTextArea = document.getElementById("message");
messageTextArea.textContent = createMessage(accountNumber, address);
const signDocTextArea = document.getElementById("sign-doc");
signDocTextArea.textContent = createSignDoc(accountNumber, address);
};
window.getAccounts = async function getAccounts(): Promise<void> {
const accountNumberInput = document.getElementById("account-number");
const addressInput = document.getElementById("address");
const accountsDiv = document.getElementById("accounts");
const messageTextArea = document.getElementById("message");
const signDocTextArea = document.getElementById("sign-doc");
accountsDiv.textContent = "Loading...";
try {
@ -69,7 +82,7 @@ window.getAccounts = async function getAccounts(): Promise<void> {
accountNumberInput.value = accountNumber;
const address = accounts[0].address;
addressInput.value = address;
messageTextArea.textContent = createMessage(accountNumber, address);
signDocTextArea.textContent = createSignDoc(accountNumber, address);
} catch (error) {
accountsDiv.textContent = error;
}
@ -81,9 +94,9 @@ window.sign = async function sign(): Promise<void> {
try {
const address = document.getElementById("address").value;
const rawMessage = document.getElementById("message").textContent;
const message = JSON.stringify(JSON.parse(rawMessage));
const signature = await signer.sign(address, toUtf8(message));
const signDocJson = document.getElementById("sign-doc").textContent;
const signDoc: StdSignDoc = JSON.parse(signDocJson);
const signature = await signer.sign(address, signDoc);
signatureDiv.textContent = JSON.stringify(signature, null, "\t");
} catch (error) {
signatureDiv.textContent = error;

View File

@ -5,7 +5,9 @@ import {
makeCosmoshubPath,
OfflineSigner,
StdSignature,
StdSignDoc,
} from "@cosmjs/launchpad";
import { serializeSignDoc } from "@cosmjs/launchpad";
import { LaunchpadLedger, LaunchpadLedgerOptions } from "./launchpadledger";
@ -34,14 +36,15 @@ export class LedgerSigner implements OfflineSigner {
return this.accounts;
}
public async sign(address: string, message: Uint8Array): Promise<StdSignature> {
public async sign(signerAddress: string, signDoc: StdSignDoc): Promise<StdSignature> {
const accounts = this.accounts || (await this.getAccounts());
const accountIndex = accounts.findIndex((account) => account.address === address);
const accountIndex = accounts.findIndex((account) => account.address === signerAddress);
if (accountIndex === -1) {
throw new Error(`Address ${address} not found in wallet`);
throw new Error(`Address ${signerAddress} not found in wallet`);
}
const message = serializeSignDoc(signDoc);
const accountForAddress = accounts[accountIndex];
const hdPath = this.hdPaths[accountIndex];
const signature = await this.ledger.sign(message, hdPath);

View File

@ -1,4 +1,4 @@
import { AccountData, OfflineSigner, StdSignature } from "@cosmjs/launchpad";
import { AccountData, OfflineSigner, StdSignature, StdSignDoc } from "@cosmjs/launchpad";
import { LaunchpadLedgerOptions } from "./launchpadledger";
export declare class LedgerSigner implements OfflineSigner {
private readonly ledger;
@ -6,5 +6,5 @@ export declare class LedgerSigner implements OfflineSigner {
private accounts?;
constructor(options?: LaunchpadLedgerOptions);
getAccounts(): Promise<readonly AccountData[]>;
sign(address: string, message: Uint8Array): Promise<StdSignature>;
sign(signerAddress: string, signDoc: StdSignDoc): Promise<StdSignature>;
}

View File

@ -3,7 +3,7 @@ import { assert, sleep } from "@cosmjs/utils";
import { coins } from "./coins";
import { CosmosClient, isBroadcastTxFailure } from "./cosmosclient";
import { makeSignBytes } from "./encoding";
import { makeStdSignDoc } from "./encoding";
import { LcdClient } from "./lcdapi";
import { isMsgSend, MsgSend } from "./msgs";
import { Secp256k1Wallet } from "./secp256k1wallet";
@ -55,8 +55,8 @@ describe("CosmosClient.searchTx", () => {
};
const { accountNumber, sequence } = await client.getSequence();
const chainId = await client.getChainId();
const signBytes = makeSignBytes([sendMsg], fee, chainId, memo, accountNumber, sequence);
const signature = await wallet.sign(walletAddress, signBytes);
const signDoc = makeStdSignDoc([sendMsg], fee, chainId, memo, accountNumber, sequence);
const signature = await wallet.sign(walletAddress, signDoc);
const tx: CosmosSdkTx = {
type: "cosmos-sdk/StdTx",
value: {

View File

@ -3,7 +3,7 @@ import { sleep } from "@cosmjs/utils";
import { ReadonlyDate } from "readonly-date";
import { assertIsBroadcastTxSuccess, CosmosClient, PrivateCosmosClient } from "./cosmosclient";
import { makeSignBytes } from "./encoding";
import { makeStdSignDoc } from "./encoding";
import { findAttribute } from "./logs";
import { MsgSend } from "./msgs";
import { Secp256k1Wallet } from "./secp256k1wallet";
@ -229,8 +229,8 @@ describe("CosmosClient", () => {
const chainId = await client.getChainId();
const { accountNumber, sequence } = await client.getSequence(faucet.address);
const signBytes = makeSignBytes([sendMsg], fee, chainId, memo, accountNumber, sequence);
const signature = await wallet.sign(walletAddress, signBytes);
const signDoc = makeStdSignDoc([sendMsg], fee, chainId, memo, accountNumber, sequence);
const signature = await wallet.sign(walletAddress, signDoc);
const signedTx = {
msg: [sendMsg],
fee: fee,

View File

@ -4,7 +4,7 @@ import { sleep } from "@cosmjs/utils";
import { coin, coins } from "../coins";
import { assertIsBroadcastTxSuccess } from "../cosmosclient";
import { makeSignBytes } from "../encoding";
import { makeStdSignDoc } from "../encoding";
import { MsgDelegate } from "../msgs";
import { Secp256k1Wallet } from "../secp256k1wallet";
import { SigningCosmosClient } from "../signingcosmosclient";
@ -45,8 +45,8 @@ describe("DistributionExtension", () => {
};
const memo = "Test delegation for wasmd";
const { accountNumber, sequence } = await client.getSequence();
const signBytes = makeSignBytes([msg], defaultFee, chainId, memo, accountNumber, sequence);
const signature = await wallet.sign(faucet.address, signBytes);
const signDoc = makeStdSignDoc([msg], defaultFee, chainId, memo, accountNumber, sequence);
const signature = await wallet.sign(faucet.address, signDoc);
const tx = {
msg: [msg],
fee: defaultFee,

View File

@ -3,7 +3,7 @@ import { sleep } from "@cosmjs/utils";
import { coins } from "../coins";
import { assertIsBroadcastTxSuccess } from "../cosmosclient";
import { makeSignBytes } from "../encoding";
import { makeStdSignDoc } from "../encoding";
import { Secp256k1Wallet } from "../secp256k1wallet";
import { SigningCosmosClient } from "../signingcosmosclient";
import {
@ -50,7 +50,7 @@ describe("GovExtension", () => {
};
const proposalMemo = "Test proposal for wasmd";
const { accountNumber: proposalAccountNumber, sequence: proposalSequence } = await client.getSequence();
const proposalSignBytes = makeSignBytes(
const proposalSignDoc = makeStdSignDoc(
[proposalMsg],
defaultFee,
chainId,
@ -58,7 +58,7 @@ describe("GovExtension", () => {
proposalAccountNumber,
proposalSequence,
);
const proposalSignature = await wallet.sign(faucet.address, proposalSignBytes);
const proposalSignature = await wallet.sign(faucet.address, proposalSignDoc);
const proposalTx = {
msg: [proposalMsg],
fee: defaultFee,
@ -82,7 +82,7 @@ describe("GovExtension", () => {
};
const voteMemo = "Test vote for wasmd";
const { accountNumber: voteAccountNumber, sequence: voteSequence } = await client.getSequence();
const voteSignBytes = makeSignBytes(
const voteSignDoc = makeStdSignDoc(
[voteMsg],
defaultFee,
chainId,
@ -90,7 +90,7 @@ describe("GovExtension", () => {
voteAccountNumber,
voteSequence,
);
const voteSignature = await wallet.sign(faucet.address, voteSignBytes);
const voteSignature = await wallet.sign(faucet.address, voteSignDoc);
const voteTx = {
msg: [voteMsg],
fee: defaultFee,

View File

@ -3,7 +3,7 @@ import { assert, sleep } from "@cosmjs/utils";
import { Coin } from "../coins";
import { isBroadcastTxFailure } from "../cosmosclient";
import { makeSignBytes } from "../encoding";
import { makeStdSignDoc } from "../encoding";
import { parseLogs } from "../logs";
import { MsgSend } from "../msgs";
import { Secp256k1Wallet } from "../secp256k1wallet";
@ -239,8 +239,8 @@ describe("LcdClient", () => {
};
const { accountNumber, sequence } = await client.getSequence();
const chainId = await client.getChainId();
const signBytes = makeSignBytes([sendMsg], fee, chainId, memo, accountNumber, sequence);
const signature = await wallet.sign(walletAddress, signBytes);
const signDoc = makeStdSignDoc([sendMsg], fee, chainId, memo, accountNumber, sequence);
const signature = await wallet.sign(walletAddress, signDoc);
const signedTx = {
msg: [sendMsg],
fee: fee,
@ -537,8 +537,8 @@ describe("LcdClient", () => {
const client = LcdClient.withExtensions({ apiUrl: wasmd.endpoint }, setupAuthExtension);
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 wallet.sign(walletAddress, signBytes);
const signDoc = makeStdSignDoc([theMsg], fee, wasmd.chainId, memo, account_number, sequence);
const signature = await wallet.sign(walletAddress, signDoc);
const signedTx = makeSignedTx(theMsg, fee, memo, signature);
const result = await client.broadcastTx(signedTx);
expect(result.code).toBeUndefined();
@ -594,12 +594,12 @@ describe("LcdClient", () => {
const { account_number: an2, sequence: sequence2 } = (await client.auth.account(address2)).result.value;
const { account_number: an3, sequence: sequence3 } = (await client.auth.account(address3)).result.value;
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(address1, signBytes1);
const signature2 = await account2.sign(address2, signBytes2);
const signature3 = await account3.sign(address3, signBytes3);
const signDoc1 = makeStdSignDoc([theMsg], fee, wasmd.chainId, memo, an1, sequence1);
const signDoc2 = makeStdSignDoc([theMsg], fee, wasmd.chainId, memo, an2, sequence2);
const signDoc3 = makeStdSignDoc([theMsg], fee, wasmd.chainId, memo, an3, sequence3);
const signature1 = await account1.sign(address1, signDoc1);
const signature2 = await account2.sign(address2, signDoc2);
const signature3 = await account3.sign(address3, signDoc3);
const signedTx = {
msg: [theMsg],
fee: fee,
@ -658,13 +658,13 @@ describe("LcdClient", () => {
const client = LcdClient.withExtensions({ apiUrl: wasmd.endpoint }, setupAuthExtension);
const { account_number, sequence } = (await client.auth.account(walletAddress)).result.value;
const signBytes = makeSignBytes([msg1, msg2], fee, wasmd.chainId, memo, account_number, sequence);
const signature1 = await wallet.sign(walletAddress, signBytes);
const signDoc = makeStdSignDoc([msg1, msg2], fee, wasmd.chainId, memo, account_number, sequence);
const signature = await wallet.sign(walletAddress, signDoc);
const signedTx = {
msg: [msg1, msg2],
fee: fee,
memo: memo,
signatures: [signature1],
signatures: [signature],
};
const broadcastResult = await client.broadcastTx(signedTx);
expect(broadcastResult.code).toBeUndefined();
@ -722,10 +722,10 @@ describe("LcdClient", () => {
const { account_number: an1, sequence: sequence1 } = (await client.auth.account(address1)).result.value;
const { account_number: an2, sequence: sequence2 } = (await client.auth.account(address2)).result.value;
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(address1, signBytes1);
const signature2 = await account2.sign(address2, signBytes2);
const signDoc1 = makeStdSignDoc([msg2, msg1], fee, wasmd.chainId, memo, an1, sequence1);
const signDoc2 = makeStdSignDoc([msg2, msg1], fee, wasmd.chainId, memo, an2, sequence2);
const signature1 = await account1.sign(address1, signDoc1);
const signature2 = await account2.sign(address2, signDoc2);
const signedTx = {
msg: [msg2, msg1],
fee: fee,
@ -793,10 +793,10 @@ describe("LcdClient", () => {
const { account_number: an1, sequence: sequence1 } = (await client.auth.account(address1)).result.value;
const { account_number: an2, sequence: sequence2 } = (await client.auth.account(address2)).result.value;
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(address1, signBytes1);
const signature2 = await account2.sign(address2, signBytes2);
const signDoc1 = makeStdSignDoc([msg1, msg2], fee, wasmd.chainId, memo, an1, sequence1);
const signDoc2 = makeStdSignDoc([msg1, msg2], fee, wasmd.chainId, memo, an2, sequence2);
const signature1 = await account1.sign(address1, signDoc1);
const signature2 = await account2.sign(address2, signDoc2);
const signedTx = {
msg: [msg1, msg2],
fee: fee,
@ -859,10 +859,10 @@ describe("LcdClient", () => {
const { account_number: an1, sequence: sequence1 } = (await client.auth.account(address1)).result.value;
const { account_number: an2, sequence: sequence2 } = (await client.auth.account(address2)).result.value;
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(address1, signBytes1);
const signature2 = await account2.sign(address2, signBytes2);
const signDoc1 = makeStdSignDoc([msg2, msg1], fee, wasmd.chainId, memo, an1, sequence1);
const signDoc2 = makeStdSignDoc([msg2, msg1], fee, wasmd.chainId, memo, an2, sequence2);
const signature1 = await account1.sign(address1, signDoc1);
const signature2 = await account2.sign(address2, signDoc2);
const signedTx = {
msg: [msg2, msg1],
fee: fee,

View File

@ -3,7 +3,7 @@ import { assert, sleep } from "@cosmjs/utils";
import { coin, coins } from "../coins";
import { assertIsBroadcastTxSuccess } from "../cosmosclient";
import { makeSignBytes } from "../encoding";
import { makeStdSignDoc } from "../encoding";
import { MsgDelegate, MsgUndelegate } from "../msgs";
import { Secp256k1Wallet } from "../secp256k1wallet";
import { SigningCosmosClient } from "../signingcosmosclient";
@ -46,8 +46,8 @@ describe("StakingExtension", () => {
};
const memo = "Test delegation for wasmd";
const { accountNumber, sequence } = await client.getSequence();
const signBytes = makeSignBytes([msg], defaultFee, chainId, memo, accountNumber, sequence);
const signature = await wallet.sign(faucet.address, signBytes);
const signDoc = makeStdSignDoc([msg], defaultFee, chainId, memo, accountNumber, sequence);
const signature = await wallet.sign(faucet.address, signDoc);
const tx = {
msg: [msg],
fee: defaultFee,
@ -69,8 +69,8 @@ describe("StakingExtension", () => {
};
const memo = "Test undelegation for wasmd";
const { accountNumber, sequence } = await client.getSequence();
const signBytes = makeSignBytes([msg], defaultFee, chainId, memo, accountNumber, sequence);
const signature = await wallet.sign(faucet.address, signBytes);
const signDoc = makeStdSignDoc([msg], defaultFee, chainId, memo, accountNumber, sequence);
const signature = await wallet.sign(faucet.address, signDoc);
const tx = {
msg: [msg],
fee: defaultFee,

View File

@ -1,6 +1,8 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { Secp256k1, Secp256k1Signature, Sha256 } from "@cosmjs/crypto";
import { fromBase64, fromHex, toAscii } from "@cosmjs/encoding";
import { fromBase64, fromHex } from "@cosmjs/encoding";
import { serializeSignDoc, StdSignDoc } from "./encoding";
import { extractKdfConfiguration, Secp256k1Wallet } from "./secp256k1wallet";
import { base64Matcher } from "./testutils.spec";
import { executeKdf, KdfConfiguration } from "./wallet";
@ -110,11 +112,18 @@ describe("Secp256k1Wallet", () => {
describe("sign", () => {
it("resolves to valid signature if enabled", async () => {
const wallet = await Secp256k1Wallet.fromMnemonic(defaultMnemonic);
const message = toAscii("foo bar");
const signature = await wallet.sign(defaultAddress, message);
const signDoc: StdSignDoc = {
msgs: [],
fee: { amount: [], gas: "23" },
chain_id: "foochain",
memo: "hello, world",
account_number: "7",
sequence: "54",
};
const signature = await wallet.sign(defaultAddress, signDoc);
const valid = await Secp256k1.verifySignature(
Secp256k1Signature.fromFixedLength(fromBase64(signature.signature)),
new Sha256(message).digest(),
new Sha256(serializeSignDoc(signDoc)).digest(),
defaultPubkey,
);
expect(valid).toEqual(true);

View File

@ -5,6 +5,7 @@ import {
pathToString,
Random,
Secp256k1,
Sha256,
Slip10,
Slip10Curve,
stringToPath,
@ -13,8 +14,9 @@ import { fromBase64, fromUtf8, toBase64, toUtf8 } from "@cosmjs/encoding";
import { assert, isNonNullObject } from "@cosmjs/utils";
import { rawSecp256k1PubkeyToAddress } from "./address";
import { serializeSignDoc, StdSignDoc } from "./encoding";
import { encodeSecp256k1Signature } from "./signature";
import { AccountData, OfflineSigner, PrehashType } from "./signer";
import { AccountData, OfflineSigner } from "./signer";
import { StdSignature } from "./types";
import {
decrypt,
@ -23,7 +25,6 @@ import {
executeKdf,
KdfConfiguration,
makeCosmoshubPath,
prehash,
supportedAlgorithms,
} from "./wallet";
@ -255,16 +256,12 @@ export class Secp256k1Wallet implements OfflineSigner {
];
}
public async sign(
address: string,
message: Uint8Array,
prehashType: PrehashType = "sha256",
): Promise<StdSignature> {
if (address !== this.address) {
throw new Error(`Address ${address} not found in wallet`);
public async sign(signerAddress: string, signDoc: StdSignDoc): Promise<StdSignature> {
if (signerAddress !== this.address) {
throw new Error(`Address ${signerAddress} not found in wallet`);
}
const hashedMessage = prehash(message, prehashType);
const signature = await Secp256k1.createSignature(hashedMessage, this.privkey);
const message = new Sha256(serializeSignDoc(signDoc)).digest();
const signature = await Secp256k1.createSignature(message, this.privkey);
const signatureBytes = new Uint8Array([...signature.r(32), ...signature.s(32)]);
return encodeSecp256k1Signature(this.pubkey, signatureBytes);
}

View File

@ -1,3 +1,4 @@
import { StdSignDoc } from "./encoding";
import { StdSignature } from "./types";
export type PrehashType = "sha256" | "sha512" | null;
@ -19,6 +20,13 @@ export interface OfflineSigner {
/**
* Request signature from whichever key corresponds to provided bech32-encoded address. Rejects if not enabled.
*
* @param signerAddress The address of the account that should sign the transaction
* @param signDoc The content that should be signed
*/
readonly sign: (address: string, message: Uint8Array, prehashType?: PrehashType) => Promise<StdSignature>;
readonly sign: (
signerAddress: string,
signDoc: StdSignDoc,
prehashType?: PrehashType,
) => Promise<StdSignature>;
}

View File

@ -1,7 +1,7 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { Coin } from "./coins";
import { Account, BroadcastTxResult, CosmosClient, GetSequenceResult } from "./cosmosclient";
import { makeSignBytes } from "./encoding";
import { makeStdSignDoc } from "./encoding";
import { buildFeeTable, FeeTable, GasLimits, GasPrice } from "./gas";
import { BroadcastMode } from "./lcdapi";
import { Msg, MsgSend } from "./msgs";
@ -88,8 +88,8 @@ export class SigningCosmosClient extends CosmosClient {
public async signAndBroadcast(msgs: readonly Msg[], fee: StdFee, memo = ""): Promise<BroadcastTxResult> {
const { accountNumber, sequence } = await this.getSequence();
const chainId = await this.getChainId();
const signBytes = makeSignBytes(msgs, fee, chainId, memo, accountNumber, sequence);
const signature = await this.signer.sign(this.senderAddress, signBytes);
const signDoc = makeStdSignDoc(msgs, fee, chainId, memo, accountNumber, sequence);
const signature = await this.signer.sign(this.senderAddress, signDoc);
const signedTx: StdTx = {
msg: msgs,
fee: fee,

View File

@ -1,5 +1,6 @@
import { HdPath } from "@cosmjs/crypto";
import { AccountData, OfflineSigner, PrehashType } from "./signer";
import { StdSignDoc } from "./encoding";
import { AccountData, OfflineSigner } from "./signer";
import { StdSignature } from "./types";
import { EncryptionConfiguration, KdfConfiguration } from "./wallet";
/**
@ -86,7 +87,7 @@ export declare class Secp256k1Wallet implements OfflineSigner {
get mnemonic(): string;
private get address();
getAccounts(): Promise<readonly AccountData[]>;
sign(address: string, message: Uint8Array, prehashType?: PrehashType): Promise<StdSignature>;
sign(signerAddress: string, signDoc: StdSignDoc): Promise<StdSignature>;
/**
* Generates an encrypted serialization of this wallet.
*

View File

@ -1,3 +1,4 @@
import { StdSignDoc } from "./encoding";
import { StdSignature } from "./types";
export declare type PrehashType = "sha256" | "sha512" | null;
export declare type Algo = "secp256k1" | "ed25519" | "sr25519";
@ -14,6 +15,13 @@ export interface OfflineSigner {
readonly getAccounts: () => Promise<readonly AccountData[]>;
/**
* Request signature from whichever key corresponds to provided bech32-encoded address. Rejects if not enabled.
*
* @param signerAddress The address of the account that should sign the transaction
* @param signDoc The content that should be signed
*/
readonly sign: (address: string, message: Uint8Array, prehashType?: PrehashType) => Promise<StdSignature>;
readonly sign: (
signerAddress: string,
signDoc: StdSignDoc,
prehashType?: PrehashType,
) => Promise<StdSignature>;
}