Merge pull request #432 from CosmWasm/flexible-offline-signer

Allow OfflineSigner implementation to override fields
This commit is contained in:
Simon Warta 2020-09-24 17:52:09 +02:00 committed by GitHub
commit 75b7a7d2bf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
51 changed files with 516 additions and 445 deletions

View File

@ -45,6 +45,20 @@
`isSearchBySentFromOrToQuery` and `isSearchByTagsQuery`.
- @cosmjs/launchpad: Change type of `TxsResponse.logs` and
`BroadcastTxsResponse.logs` to `unknown[]`.
- @cosmjs/launchpad: Export `StdSignDoc` and create helpers to make and
serialize a `StdSignDoc`: `makeSignDoc` and `serializeSignDoc`.
- @cosmjs/launchpad: Let `OfflineSigner.sign` take an `StdSignDoc` instead of an
encoded message and return a `SignResponse` that includes the document which
was signed.
- @cosmjs/launchpad: Remove `PrehashType` and the prehash type argument in
`OfflineSigner.sign` because the signer now needs to know how to serialize an
`StdSignDoc`.
- @cosmjs/launchpad: Remove `makeSignBytes` in favour of `makeSignDoc` and
`serializeSignDoc`.
- @cosmjs/launchpad: Create `WrappedTx`, `WrappedStdTx` and `isWrappedStdTx` to
better represent the Amino tx interface. Deprecate `CosmosSdkTx`, which is an
alias for `WrappedStdTx`.
- @cosmjs/launchpad: Add `makeStdTx` to create an `StdTx`.
- @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

@ -66,7 +66,7 @@ const sendTokensMsg: MsgSend = {
},
};
const signBytes = makeSignBytes(
const signDoc = makeSignDoc(
[sendTokensMsg],
defaultFee,
defaultNetworkId,
@ -74,13 +74,8 @@ const signBytes = makeSignBytes(
account_number,
sequence,
);
const signature = await pen.sign(signBytes);
const signedTx: StdTx = {
msg: [sendTokensMsg],
fee: defaultFee,
memo: memo,
signatures: [signature],
};
const { signed, signature } = await wallet.sign(faucetAddress, signDoc);
const signedTx = makeStdTx(signed, signature);
const broadcastResult = await client.broadcastTx(signedTx);
```

View File

@ -27,14 +27,9 @@ console.log("Connected to chain:", chainId);
const { accountNumber, sequence } = await client.getSequence(senderAddress);
console.log("Account/sequence:", accountNumber, sequence);
const signBytes = makeSignBytes([msg], fee, chainId, memo, accountNumber, sequence);
const signature = await wallet.sign(senderAddress, signBytes);
const signedTx: StdTx = {
msg: [msg],
fee: fee,
memo: memo,
signatures: [signature],
};
const signDoc = makeSignDoc([msg], fee, chainId, memo, accountNumber, sequence);
const { signed, signature } = await wallet.sign(senderAddress, signDoc);
const signedTx = makeStdTx(signed, signature);
const result = await client.broadcastTx(signedTx);
console.log("Broadcast result:", result);

View File

@ -98,7 +98,8 @@ export async function main(originalArgs: readonly string[]): Promise<void> {
"encodeSecp256k1Signature",
"logs",
"makeCosmoshubPath",
"makeSignBytes",
"makeSignDoc",
"makeStdTx",
"IndexedTx",
"BroadcastTxResult",
"Coin",
@ -115,6 +116,7 @@ export async function main(originalArgs: readonly string[]): Promise<void> {
"Secp256k1Wallet",
"SigningCosmosClient",
"StdFee",
"StdSignDoc",
"StdTx",
],
],
@ -161,7 +163,13 @@ export async function main(originalArgs: readonly string[]): Promise<void> {
const wallet = await Secp256k1Wallet.fromMnemonic(mnemonic, makeCosmoshubPath(0));
const [{ address }] = await wallet.getAccounts();
const data = toAscii("foo bar");
const signature = await wallet.sign(address, data);
const fee: StdFee = {
amount: coins(5000000, "ucosm"),
gas: "89000000",
};
const signDoc = makeSignDoc([], fee, "chain-xyz", "hello, world", 1, 2);
const { signed, signature } = await wallet.sign(address, signDoc);
assert(signed.memo === "hello, world");
const bechPubkey = "coralvalconspub1zcjduepqvxg72ccnl9r65fv0wn3amlk4sfzqfe2k36l073kjx2qyaf6sk23qw7j8wq";
assert(encodeBech32Pubkey(decodeBech32Pubkey(bechPubkey), "coralvalconspub") == bechPubkey);

View File

@ -2,13 +2,14 @@
import {
Coin,
coins,
CosmosSdkTx,
isBroadcastTxFailure,
isMsgSend,
LcdClient,
makeSignBytes,
makeSignDoc,
makeStdTx,
MsgSend,
Secp256k1Wallet,
WrappedStdTx,
} from "@cosmjs/launchpad";
import { assert, sleep } from "@cosmjs/utils";
@ -30,7 +31,7 @@ interface TestTxSend {
readonly recipient: string;
readonly hash: string;
readonly height: number;
readonly tx: CosmosSdkTx;
readonly tx: WrappedStdTx;
}
interface TestTxExecute {
@ -38,7 +39,7 @@ interface TestTxExecute {
readonly contract: string;
readonly hash: string;
readonly height: number;
readonly tx: CosmosSdkTx;
readonly tx: WrappedStdTx;
}
describe("CosmWasmClient.searchTx", () => {
@ -103,16 +104,11 @@ 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 tx: CosmosSdkTx = {
const signDoc = makeSignDoc([sendMsg], fee, chainId, memo, accountNumber, sequence);
const { signed, signature } = await wallet.sign(alice.address0, signDoc);
const tx: WrappedStdTx = {
type: "cosmos-sdk/StdTx",
value: {
msg: [sendMsg],
fee: fee,
memo: memo,
signatures: [signature],
},
value: makeStdTx(signed, signature),
};
const transactionId = await client.getIdentifier(tx);
const result = await client.broadcastTx(tx.value);

View File

@ -3,7 +3,9 @@ import { Sha256 } from "@cosmjs/crypto";
import { Bech32, fromHex, fromUtf8, toAscii, toBase64 } from "@cosmjs/encoding";
import {
assertIsBroadcastTxSuccess,
makeSignBytes,
isWrappedStdTx,
makeSignDoc,
makeStdTx,
MsgSend,
Secp256k1Wallet,
StdFee,
@ -199,6 +201,7 @@ describe("CosmWasmClient", () => {
it("works", async () => {
pendingWithoutWasmd();
const client = new CosmWasmClient(wasmd.endpoint);
assert(isWrappedStdTx(cosmoshub.tx));
expect(await client.getIdentifier(cosmoshub.tx)).toEqual(cosmoshub.id);
});
});
@ -236,14 +239,9 @@ 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 signedTx = {
msg: [sendMsg],
fee: fee,
memo: memo,
signatures: [signature],
};
const signDoc = makeSignDoc([sendMsg], fee, chainId, memo, accountNumber, sequence);
const { signed, signature } = await wallet.sign(alice.address0, signDoc);
const signedTx = makeStdTx(signed, signature);
const result = await client.broadcastTx(signedTx);
assertIsBroadcastTxSuccess(result);
const { logs, transactionHash } = result;

View File

@ -5,7 +5,6 @@ import {
BroadcastMode,
BroadcastTxResult,
Coin,
CosmosSdkTx,
IndexedTx,
LcdClient,
normalizePubkey,
@ -13,6 +12,7 @@ import {
setupAuthExtension,
StdTx,
uint64ToNumber,
WrappedStdTx,
} from "@cosmjs/launchpad";
import { Uint53 } from "@cosmjs/math";
@ -199,7 +199,7 @@ export class CosmWasmClient {
/**
* Returns a 32 byte upper-case hex transaction hash (typically used as the transaction ID)
*/
public async getIdentifier(tx: CosmosSdkTx): Promise<string> {
public async getIdentifier(tx: WrappedStdTx): Promise<string> {
// We consult the REST API because we don't have a local amino encoder
const response = await this.lcdClient.encodeTx(tx);
const hash = new Sha256(fromBase64(response.tx)).digest();

View File

@ -10,7 +10,8 @@ import {
coin,
coins,
LcdClient,
makeSignBytes,
makeSignDoc,
makeStdTx,
OfflineSigner,
Secp256k1Wallet,
setupAuthExtension,
@ -36,7 +37,6 @@ import {
fromOneElementArray,
getHackatom,
makeRandomAddress,
makeSignedTx,
pendingWithoutWasmd,
wasmd,
wasmdEnabled,
@ -125,9 +125,9 @@ 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 signedTx = makeSignedTx(theMsg, fee, memo, signature);
const signDoc = makeSignDoc([theMsg], fee, wasmd.chainId, memo, account_number, sequence);
const { signed, signature } = await signer.sign(alice.address0, signDoc);
const signedTx = makeStdTx(signed, signature);
return client.broadcastTx(signedTx);
}

View File

@ -11,12 +11,12 @@ import {
GasLimits,
GasPrice,
isBroadcastTxFailure,
makeSignBytes,
makeSignDoc,
makeStdTx,
Msg,
MsgSend,
OfflineSigner,
StdFee,
StdTx,
} from "@cosmjs/launchpad";
import { Uint53 } from "@cosmjs/math";
import pako from "pako";
@ -360,14 +360,9 @@ 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 signedTx: StdTx = {
msg: msgs,
fee: fee,
memo: memo,
signatures: [signature],
};
const signDoc = makeSignDoc(msgs, fee, chainId, memo, accountNumber, sequence);
const { signed, signature } = await this.signer.sign(this.senderAddress, signDoc);
const signedTx = makeStdTx(signed, signature);
return this.broadcastTx(signedTx);
}
}

View File

@ -1,6 +1,5 @@
import { Random } from "@cosmjs/crypto";
import { Bech32, fromBase64 } from "@cosmjs/encoding";
import { Msg, StdFee, StdSignature, StdTx } from "@cosmjs/launchpad";
import hackatom from "./testdata/contract.json";
@ -89,12 +88,3 @@ export function fromOneElementArray<T>(elements: ArrayLike<T>): T {
if (elements.length !== 1) throw new Error(`Expected exactly one element but got ${elements.length}`);
return elements[0];
}
export function makeSignedTx(firstMsg: Msg, fee: StdFee, memo: string, firstSignature: StdSignature): StdTx {
return {
msg: [firstMsg],
fee: fee,
memo: memo,
signatures: [firstSignature],
};
}

View File

@ -3,11 +3,11 @@ import {
BroadcastMode,
BroadcastTxResult,
Coin,
CosmosSdkTx,
IndexedTx,
LcdClient,
PubKey,
StdTx,
WrappedStdTx,
} from "@cosmjs/launchpad";
import { WasmExtension } from "./lcdapi/wasm";
import { JsonObject } from "./types";
@ -132,7 +132,7 @@ export declare class CosmWasmClient {
/**
* Returns a 32 byte upper-case hex transaction hash (typically used as the transaction ID)
*/
getIdentifier(tx: CosmosSdkTx): Promise<string>;
getIdentifier(tx: WrappedStdTx): Promise<string>;
/**
* Returns account number and sequence.
*

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, makeSignDoc, StdFee, StdSignature } from "@cosmjs/launchpad";
import { LedgerSigner } from "../ledgersigner";
@ -48,13 +48,7 @@ export async function sign(
},
},
];
const signBytes = makeSignBytes(
msgs,
defaultFee,
defaultChainId,
defaultMemo,
accountNumber,
defaultSequence,
);
return signer.sign(fromAddress, signBytes);
const signDoc = makeSignDoc(msgs, defaultFee, defaultChainId, defaultMemo, accountNumber, defaultSequence);
const { signature } = await signer.sign(fromAddress, signDoc);
return signature;
}

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

@ -4,8 +4,9 @@ import {
encodeSecp256k1Signature,
makeCosmoshubPath,
OfflineSigner,
StdSignature,
StdSignDoc,
} from "@cosmjs/launchpad";
import { serializeSignDoc, SignResponse } from "@cosmjs/launchpad";
import { LaunchpadLedger, LaunchpadLedgerOptions } from "./launchpadledger";
@ -34,17 +35,21 @@ 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<SignResponse> {
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);
return encodeSecp256k1Signature(accountForAddress.pubkey, signature);
return {
signed: signDoc,
signature: encodeSecp256k1Signature(accountForAddress.pubkey, signature),
};
}
}

View File

@ -1,4 +1,5 @@
import { AccountData, OfflineSigner, StdSignature } from "@cosmjs/launchpad";
import { AccountData, OfflineSigner, StdSignDoc } from "@cosmjs/launchpad";
import { SignResponse } from "@cosmjs/launchpad";
import { LaunchpadLedgerOptions } from "./launchpadledger";
export declare class LedgerSigner implements OfflineSigner {
private readonly ledger;
@ -6,5 +7,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<SignResponse>;
}

View File

@ -173,7 +173,7 @@ import { MsgExecuteContract, setupWasmExtension } from "@cosmjs/cosmwasm";
import {
assertIsPostTxSuccess,
LcdClient,
makeSignBytes,
makeSignDoc,
setupAuthExtension,
StdFee,
StdTx,
@ -205,21 +205,9 @@ const memo = "Time for action";
const { account_number, sequence } = (
await client.auth.account(myAddress)
).result.value;
const signBytes = makeSignBytes(
[msg],
fee,
apiUrl,
memo,
account_number,
sequence,
);
const signature = await signer.sign(myAddress, signBytes);
const signedTx: StdTx = {
msg: [msg],
fee: fee,
memo: memo,
signatures: [signature],
};
const signDoc = makeSignDoc([msg], fee, apiUrl, memo, account_number, sequence);
const { signed, signature } = await signer.sign(myAddress, signDoc);
const signedTx = makeStdTx(signed, signature);
const result = await client.postTx(signedTx);
assertIsPostTxSuccess(result);
```

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 { makeSignDoc } from "./encoding";
import { LcdClient } from "./lcdapi";
import { isMsgSend, MsgSend } from "./msgs";
import { Secp256k1Wallet } from "./secp256k1wallet";
@ -16,14 +16,14 @@ import {
wasmd,
wasmdEnabled,
} from "./testutils.spec";
import { CosmosSdkTx } from "./types";
import { makeStdTx, WrappedStdTx } from "./tx";
interface TestTxSend {
readonly sender: string;
readonly recipient: string;
readonly hash: string;
readonly height: number;
readonly tx: CosmosSdkTx;
readonly tx: WrappedStdTx;
}
describe("CosmosClient.searchTx", () => {
@ -55,16 +55,11 @@ 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 tx: CosmosSdkTx = {
const signDoc = makeSignDoc([sendMsg], fee, chainId, memo, accountNumber, sequence);
const { signed, signature } = await wallet.sign(walletAddress, signDoc);
const tx: WrappedStdTx = {
type: "cosmos-sdk/StdTx",
value: {
msg: [sendMsg],
fee: fee,
memo: memo,
signatures: [signature],
},
value: makeStdTx(signed, signature),
};
const transactionId = await client.getIdentifier(tx);
const result = await client.broadcastTx(tx.value);

View File

@ -1,9 +1,9 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { sleep } from "@cosmjs/utils";
import { assert, sleep } from "@cosmjs/utils";
import { ReadonlyDate } from "readonly-date";
import { assertIsBroadcastTxSuccess, CosmosClient, PrivateCosmosClient } from "./cosmosclient";
import { makeSignBytes } from "./encoding";
import { makeSignDoc } from "./encoding";
import { findAttribute } from "./logs";
import { MsgSend } from "./msgs";
import { Secp256k1Wallet } from "./secp256k1wallet";
@ -16,6 +16,7 @@ import {
unused,
wasmd,
} from "./testutils.spec";
import { isWrappedStdTx, makeStdTx } from "./tx";
import { StdFee } from "./types";
const blockTime = 1_000; // ms
@ -190,6 +191,7 @@ describe("CosmosClient", () => {
it("works", async () => {
pendingWithoutWasmd();
const client = new CosmosClient(wasmd.endpoint);
assert(isWrappedStdTx(cosmoshub.tx));
expect(await client.getIdentifier(cosmoshub.tx)).toEqual(cosmoshub.id);
});
});
@ -229,14 +231,9 @@ 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 signedTx = {
msg: [sendMsg],
fee: fee,
memo: memo,
signatures: [signature],
};
const signDoc = makeSignDoc([sendMsg], fee, chainId, memo, accountNumber, sequence);
const { signed, signature } = await wallet.sign(walletAddress, signDoc);
const signedTx = makeStdTx(signed, signature);
const txResult = await client.broadcastTx(signedTx);
assertIsBroadcastTxSuccess(txResult);
const { logs, transactionHash } = txResult;

View File

@ -12,7 +12,8 @@ import {
uint64ToNumber,
} from "./lcdapi";
import { Log, parseLogs } from "./logs";
import { CosmosSdkTx, PubKey, StdTx } from "./types";
import { StdTx, WrappedStdTx } from "./tx";
import { PubKey } from "./types";
export interface GetSequenceResult {
readonly accountNumber: number;
@ -121,7 +122,7 @@ export interface IndexedTx {
readonly code: number;
readonly rawLog: string;
readonly logs: readonly Log[];
readonly tx: CosmosSdkTx;
readonly tx: WrappedStdTx;
/** The gas limit as set by the user */
readonly gasWanted?: number;
/** The gas used by the execution */
@ -203,7 +204,7 @@ export class CosmosClient {
/**
* Returns a 32 byte upper-case hex transaction hash (typically used as the transaction ID)
*/
public async getIdentifier(tx: CosmosSdkTx): Promise<string> {
public async getIdentifier(tx: WrappedStdTx): Promise<string> {
// We consult the REST API because we don't have a local amino encoder
const response = await this.lcdClient.encodeTx(tx);
const hash = new Sha256(fromBase64(response.tx)).digest();

View File

@ -1,7 +1,7 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { toUtf8 } from "@cosmjs/encoding";
import { Uint53 } from "@cosmjs/math";
import { uint64ToString } from "./lcdapi";
import { Msg } from "./msgs";
import { StdFee } from "./types";
@ -29,30 +29,33 @@ function sortJson(json: any): any {
* @see https://docs.cosmos.network/master/modules/auth/03_types.html#stdsigndoc
*/
export interface StdSignDoc {
readonly account_number: string;
readonly chain_id: string;
readonly fee: StdFee;
readonly memo: string;
readonly msgs: readonly Msg[];
readonly account_number: string;
readonly sequence: string;
readonly fee: StdFee;
readonly msgs: readonly Msg[];
readonly memo: string;
}
export function makeSignBytes(
export function makeSignDoc(
msgs: readonly Msg[],
fee: StdFee,
chainId: string,
memo: string,
accountNumber: number | string,
sequence: number | string,
): Uint8Array {
const signDoc: StdSignDoc = {
account_number: uint64ToString(accountNumber),
): StdSignDoc {
return {
chain_id: chainId,
account_number: Uint53.fromString(accountNumber.toString()).toString(),
sequence: Uint53.fromString(sequence.toString()).toString(),
fee: fee,
memo: memo,
msgs: msgs,
sequence: uint64ToString(sequence),
memo: memo,
};
}
export function serializeSignDoc(signDoc: StdSignDoc): Uint8Array {
const sortedSignDoc = sortJson(signDoc);
return toUtf8(JSON.stringify(sortedSignDoc));
}

View File

@ -28,7 +28,7 @@ export {
isSearchBySentFromOrToQuery,
isSearchByTagsQuery,
} from "./cosmosclient";
export { makeSignBytes, StdSignDoc } from "./encoding";
export { makeSignDoc, serializeSignDoc, StdSignDoc } from "./encoding";
export { buildFeeTable, FeeTable, GasLimits, GasPrice } from "./gas";
export {
AuthAccountsResponse,
@ -98,15 +98,9 @@ export {
} from "./pubkey";
export { findSequenceForSignedTx } from "./sequence";
export { encodeSecp256k1Signature, decodeSignature } from "./signature";
export { AccountData, Algo, OfflineSigner, SignResponse } from "./signer";
export { CosmosFeeTable, SigningCosmosClient } from "./signingcosmosclient";
export { isStdTx, pubkeyType, CosmosSdkTx, PubKey, StdFee, StdSignature, StdTx } from "./types";
export {
AccountData,
Algo,
PrehashType,
OfflineSigner,
makeCosmoshubPath,
executeKdf,
KdfConfiguration,
} from "./wallet";
export { isStdTx, isWrappedStdTx, makeStdTx, CosmosSdkTx, StdTx, WrappedStdTx, WrappedTx } from "./tx";
export { pubkeyType, PubKey, StdFee, StdSignature } from "./types";
export { makeCosmoshubPath, executeKdf, KdfConfiguration } from "./wallet";
export { extractKdfConfiguration, Secp256k1Wallet } from "./secp256k1wallet";

View File

@ -1,5 +1,5 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { CosmosSdkTx } from "../types";
import { WrappedStdTx } from "../tx";
/**
* The mode used to send transaction
@ -109,7 +109,7 @@ export interface TxsResponse {
readonly code?: number;
readonly raw_log: string;
readonly logs?: unknown[];
readonly tx: CosmosSdkTx;
readonly tx: WrappedStdTx;
/** The gas limit as set by the user */
readonly gas_wanted?: string;
/** The gas used by the execution */

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 { makeSignDoc } from "../encoding";
import { MsgDelegate } from "../msgs";
import { Secp256k1Wallet } from "../secp256k1wallet";
import { SigningCosmosClient } from "../signingcosmosclient";
@ -16,6 +16,7 @@ import {
wasmd,
wasmdEnabled,
} from "../testutils.spec";
import { makeStdTx } from "../tx";
import { DistributionExtension, setupDistributionExtension } from "./distribution";
import { LcdClient } from "./lcdclient";
@ -45,16 +46,11 @@ 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 tx = {
msg: [msg],
fee: defaultFee,
memo: memo,
signatures: [signature],
};
const signDoc = makeSignDoc([msg], defaultFee, chainId, memo, accountNumber, sequence);
const { signed, signature } = await wallet.sign(faucet.address, signDoc);
const signedTx = makeStdTx(signed, signature);
const result = await client.broadcastTx(tx);
const result = await client.broadcastTx(signedTx);
assertIsBroadcastTxSuccess(result);
await sleep(75); // wait until transactions are indexed

View File

@ -3,7 +3,7 @@ import { sleep } from "@cosmjs/utils";
import { coins } from "../coins";
import { assertIsBroadcastTxSuccess } from "../cosmosclient";
import { makeSignBytes } from "../encoding";
import { makeSignDoc } 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 = makeSignDoc(
[proposalMsg],
defaultFee,
chainId,
@ -58,7 +58,7 @@ describe("GovExtension", () => {
proposalAccountNumber,
proposalSequence,
);
const proposalSignature = await wallet.sign(faucet.address, proposalSignBytes);
const { signature: 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 = makeSignDoc(
[voteMsg],
defaultFee,
chainId,
@ -90,7 +90,7 @@ describe("GovExtension", () => {
voteAccountNumber,
voteSequence,
);
const voteSignature = await wallet.sign(faucet.address, voteSignBytes);
const { signature: 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 { makeSignDoc } from "../encoding";
import { parseLogs } from "../logs";
import { MsgSend } from "../msgs";
import { Secp256k1Wallet } from "../secp256k1wallet";
@ -12,7 +12,6 @@ import cosmoshub from "../testdata/cosmoshub.json";
import {
faucet,
makeRandomAddress,
makeSignedTx,
nonNegativeIntegerMatcher,
pendingWithoutWasmd,
tendermintIdMatcher,
@ -20,6 +19,7 @@ import {
wasmd,
wasmdEnabled,
} from "../testutils.spec";
import { isWrappedStdTx, makeStdTx, StdTx } from "../tx";
import { StdFee } from "../types";
import { makeCosmoshubPath } from "../wallet";
import { setupAuthExtension } from "./auth";
@ -239,14 +239,9 @@ 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 signedTx = {
msg: [sendMsg],
fee: fee,
memo: memo,
signatures: [signature],
};
const signDoc = makeSignDoc([sendMsg], fee, chainId, memo, accountNumber, sequence);
const { signed, signature } = await wallet.sign(walletAddress, signDoc);
const signedTx = makeStdTx(signed, signature);
const transactionId = await client.getIdentifier({ type: "cosmos-sdk/StdTx", value: signedTx });
const result = await client.broadcastTx(signedTx);
assert(isBroadcastTxFailure(result));
@ -493,6 +488,7 @@ describe("LcdClient", () => {
it("works for cosmoshub example", async () => {
pendingWithoutWasmd();
const client = new LcdClient(wasmd.endpoint);
assert(isWrappedStdTx(cosmoshub.tx));
const response = await client.encodeTx(cosmoshub.tx);
expect(response).toEqual(
jasmine.objectContaining({
@ -537,9 +533,9 @@ 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 signedTx = makeSignedTx(theMsg, fee, memo, signature);
const signDoc = makeSignDoc([theMsg], fee, wasmd.chainId, memo, account_number, sequence);
const { signed, signature } = await wallet.sign(walletAddress, signDoc);
const signedTx = makeStdTx(signed, signature);
const result = await client.broadcastTx(signedTx);
expect(result.code).toBeUndefined();
expect(result).toEqual({
@ -594,13 +590,13 @@ 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 signedTx = {
const signDoc1 = makeSignDoc([theMsg], fee, wasmd.chainId, memo, an1, sequence1);
const signDoc2 = makeSignDoc([theMsg], fee, wasmd.chainId, memo, an2, sequence2);
const signDoc3 = makeSignDoc([theMsg], fee, wasmd.chainId, memo, an3, sequence3);
const { signature: signature1 } = await account1.sign(address1, signDoc1);
const { signature: signature2 } = await account2.sign(address2, signDoc2);
const { signature: signature3 } = await account3.sign(address3, signDoc3);
const signedTx: StdTx = {
msg: [theMsg],
fee: fee,
memo: memo,
@ -658,14 +654,9 @@ 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 signedTx = {
msg: [msg1, msg2],
fee: fee,
memo: memo,
signatures: [signature1],
};
const signDoc = makeSignDoc([msg1, msg2], fee, wasmd.chainId, memo, account_number, sequence);
const { signed, signature } = await wallet.sign(walletAddress, signDoc);
const signedTx = makeStdTx(signed, signature);
const broadcastResult = await client.broadcastTx(signedTx);
expect(broadcastResult.code).toBeUndefined();
});
@ -722,11 +713,11 @@ 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 signedTx = {
const signDoc1 = makeSignDoc([msg2, msg1], fee, wasmd.chainId, memo, an1, sequence1);
const signDoc2 = makeSignDoc([msg2, msg1], fee, wasmd.chainId, memo, an2, sequence2);
const { signature: signature1 } = await account1.sign(address1, signDoc1);
const { signature: signature2 } = await account2.sign(address2, signDoc2);
const signedTx: StdTx = {
msg: [msg2, msg1],
fee: fee,
memo: memo,
@ -793,11 +784,11 @@ 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 signedTx = {
const signDoc1 = makeSignDoc([msg1, msg2], fee, wasmd.chainId, memo, an1, sequence1);
const signDoc2 = makeSignDoc([msg1, msg2], fee, wasmd.chainId, memo, an2, sequence2);
const { signature: signature1 } = await account1.sign(address1, signDoc1);
const { signature: signature2 } = await account2.sign(address2, signDoc2);
const signedTx: StdTx = {
msg: [msg1, msg2],
fee: fee,
memo: memo,
@ -859,11 +850,11 @@ 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 signedTx = {
const signDoc1 = makeSignDoc([msg2, msg1], fee, wasmd.chainId, memo, an1, sequence1);
const signDoc2 = makeSignDoc([msg2, msg1], fee, wasmd.chainId, memo, an2, sequence2);
const { signature: signature1 } = await account1.sign(address1, signDoc1);
const { signature: signature2 } = await account2.sign(address2, signDoc2);
const signedTx: StdTx = {
msg: [msg2, msg1],
fee: fee,
memo: memo,

View File

@ -2,7 +2,7 @@
import { assert, isNonNullObject } from "@cosmjs/utils";
import axios, { AxiosError, AxiosInstance } from "axios";
import { CosmosSdkTx, StdTx } from "../types";
import { StdTx, WrappedStdTx } from "../tx";
import {
BlockResponse,
BroadcastMode,
@ -284,7 +284,7 @@ export class LcdClient {
}
/** returns the amino-encoding of the transaction performed by the server */
public async encodeTx(tx: CosmosSdkTx): Promise<EncodeTxResponse> {
public async encodeTx(tx: WrappedStdTx): Promise<EncodeTxResponse> {
const responseData = await this.post("/txs/encode", tx);
if (!responseData.tx) {
throw new Error("Unexpected response data format");

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 { makeSignDoc } from "../encoding";
import { MsgDelegate, MsgUndelegate } from "../msgs";
import { Secp256k1Wallet } from "../secp256k1wallet";
import { SigningCosmosClient } from "../signingcosmosclient";
@ -16,6 +16,7 @@ import {
wasmd,
wasmdEnabled,
} from "../testutils.spec";
import { makeStdTx } from "../tx";
import { LcdClient } from "./lcdclient";
import { BondStatus, setupStakingExtension, StakingExtension } from "./staking";
@ -46,16 +47,11 @@ 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 tx = {
msg: [msg],
fee: defaultFee,
memo: memo,
signatures: [signature],
};
const signDoc = makeSignDoc([msg], defaultFee, chainId, memo, accountNumber, sequence);
const { signed, signature } = await wallet.sign(faucet.address, signDoc);
const signedTx = makeStdTx(signed, signature);
const result = await client.broadcastTx(tx);
const result = await client.broadcastTx(signedTx);
assertIsBroadcastTxSuccess(result);
}
{
@ -69,16 +65,11 @@ 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 tx = {
msg: [msg],
fee: defaultFee,
memo: memo,
signatures: [signature],
};
const signDoc = makeSignDoc([msg], defaultFee, chainId, memo, accountNumber, sequence);
const { signed, signature } = await wallet.sign(faucet.address, signDoc);
const signedTx = makeStdTx(signed, signature);
const result = await client.broadcastTx(tx);
const result = await client.broadcastTx(signedTx);
assertIsBroadcastTxSuccess(result);
}

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,19 @@ 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 { signed, signature } = await wallet.sign(defaultAddress, signDoc);
expect(signed).toEqual(signDoc);
const valid = await Secp256k1.verifySignature(
Secp256k1Signature.fromFixedLength(fromBase64(signature.signature)),
new Sha256(message).digest(),
new Sha256(serializeSignDoc(signed)).digest(),
defaultPubkey,
);
expect(valid).toEqual(true);

View File

@ -5,6 +5,7 @@ import {
pathToString,
Random,
Secp256k1,
Sha256,
Slip10,
Slip10Curve,
stringToPath,
@ -13,19 +14,16 @@ 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 { StdSignature } from "./types";
import { AccountData, OfflineSigner, SignResponse } from "./signer";
import {
AccountData,
decrypt,
encrypt,
EncryptionConfiguration,
executeKdf,
KdfConfiguration,
makeCosmoshubPath,
OfflineSigner,
prehash,
PrehashType,
supportedAlgorithms,
} from "./wallet";
@ -257,18 +255,17 @@ 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<SignResponse> {
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);
return {
signed: signDoc,
signature: encodeSecp256k1Signature(this.pubkey, signatureBytes),
};
}
/**

View File

@ -1,7 +1,10 @@
import { assert } from "@cosmjs/utils";
import { findSequenceForSignedTx } from "./sequence";
import response1 from "./testdata/txresponse1.json";
import response2 from "./testdata/txresponse2.json";
import response3 from "./testdata/txresponse3.json";
import { isWrappedStdTx } from "./tx";
// Those values must match ./testdata/txresponse*.json
const chainId = "testing";
@ -10,6 +13,10 @@ const accountNumber = 4;
describe("sequence", () => {
describe("findSequenceForSignedTx", () => {
it("works", async () => {
assert(isWrappedStdTx(response1.tx));
assert(isWrappedStdTx(response2.tx));
assert(isWrappedStdTx(response3.tx));
const current = 100; // what we get from GET /auth/accounts/{address}
expect(await findSequenceForSignedTx(response1.tx, chainId, accountNumber, current)).toEqual(10);
// We know response3.height > response1.height, so the sequence must be at least 10+1
@ -19,6 +26,10 @@ describe("sequence", () => {
});
it("returns undefined when sequence is not in range", async () => {
assert(isWrappedStdTx(response1.tx));
assert(isWrappedStdTx(response2.tx));
assert(isWrappedStdTx(response3.tx));
expect(await findSequenceForSignedTx(response1.tx, chainId, accountNumber, 5)).toBeUndefined();
expect(await findSequenceForSignedTx(response1.tx, chainId, accountNumber, 20, 11)).toBeUndefined();
expect(await findSequenceForSignedTx(response1.tx, chainId, accountNumber, 20, 50)).toBeUndefined();

View File

@ -1,8 +1,8 @@
import { Secp256k1, Secp256k1Signature, Sha256 } from "@cosmjs/crypto";
import { makeSignBytes } from "./encoding";
import { makeSignDoc, serializeSignDoc } from "./encoding";
import { decodeSignature } from "./signature";
import { CosmosSdkTx } from "./types";
import { WrappedStdTx } from "./tx";
/**
* Serach for sequence s with `min` <= `s` < `upperBound` to find the sequence that was used to sign the transaction
@ -16,7 +16,7 @@ import { CosmosSdkTx } from "./types";
* @returns the sequence if a match was found and undefined otherwise
*/
export async function findSequenceForSignedTx(
tx: CosmosSdkTx,
tx: WrappedStdTx,
chainId: string,
accountNumber: number,
upperBound: number,
@ -30,13 +30,8 @@ export async function findSequenceForSignedTx(
for (let s = min; s < upperBound; s++) {
// console.log(`Trying sequence ${s}`);
const signBytes = makeSignBytes(
tx.value.msg,
tx.value.fee,
chainId,
tx.value.memo || "",
accountNumber,
s,
const signBytes = serializeSignDoc(
makeSignDoc(tx.value.msg, tx.value.fee, chainId, tx.value.memo || "", accountNumber, s),
);
const prehashed = new Sha256(signBytes).digest();
const valid = await Secp256k1.verifySignature(secp256keSignature, prehashed, pubkey);

View File

@ -0,0 +1,38 @@
import { StdSignDoc } from "./encoding";
import { StdSignature } from "./types";
export type Algo = "secp256k1" | "ed25519" | "sr25519";
export interface AccountData {
/** A printable address (typically bech32 encoded) */
readonly address: string;
readonly algo: Algo;
readonly pubkey: Uint8Array;
}
export interface SignResponse {
/**
* The sign doc that was signed.
* This may be different from the input signDoc when the signer modifies it as part of the signing process.
*/
readonly signed: StdSignDoc;
readonly signature: StdSignature;
}
export interface OfflineSigner {
/**
* 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.
*
* The signer implementation may offer the user the ability to override parts of the signDoc. It must
* return the doc that was signed in the response.
*
* @param signerAddress The address of the account that should sign the transaction
* @param signDoc The content that should be signed
*/
readonly sign: (signerAddress: string, signDoc: StdSignDoc) => Promise<SignResponse>;
}

View File

@ -1,12 +1,13 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { Coin } from "./coins";
import { Account, BroadcastTxResult, CosmosClient, GetSequenceResult } from "./cosmosclient";
import { makeSignBytes } from "./encoding";
import { makeSignDoc } from "./encoding";
import { buildFeeTable, FeeTable, GasLimits, GasPrice } from "./gas";
import { BroadcastMode } from "./lcdapi";
import { Msg, MsgSend } from "./msgs";
import { StdFee, StdTx } from "./types";
import { OfflineSigner } from "./wallet";
import { OfflineSigner } from "./signer";
import { makeStdTx } from "./tx";
import { StdFee } from "./types";
/**
* These fees are used by the higher level methods of SigningCosmosClient
@ -88,14 +89,9 @@ 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 signedTx: StdTx = {
msg: msgs,
fee: fee,
memo: memo,
signatures: [signature],
};
const signDoc = makeSignDoc(msgs, fee, chainId, memo, accountNumber, sequence);
const { signed, signature } = await this.signer.sign(this.senderAddress, signDoc);
const signedTx = makeStdTx(signed, signature);
return this.broadcastTx(signedTx);
}
}

View File

@ -1,9 +1,6 @@
import { Random } from "@cosmjs/crypto";
import { Bech32 } from "@cosmjs/encoding";
import { Msg } from "./msgs";
import { StdFee, StdSignature, StdTx } from "./types";
export function makeRandomAddress(): string {
return Bech32.encode("cosmos", Random.getBytes(20));
}
@ -74,12 +71,3 @@ export function fromOneElementArray<T>(elements: ArrayLike<T>): T {
if (elements.length !== 1) throw new Error(`Expected exactly one element but got ${elements.length}`);
return elements[0];
}
export function makeSignedTx(firstMsg: Msg, fee: StdFee, memo: string, firstSignature: StdSignature): StdTx {
return {
msg: [firstMsg],
fee: fee,
memo: memo,
signatures: [firstSignature],
};
}

View File

@ -0,0 +1,54 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { coins } from "./coins";
import { makeSignDoc } from "./encoding";
import { makeStdTx } from "./tx";
import { StdFee, StdSignature } from "./types";
describe("tx", () => {
describe("makeStdTx", () => {
it("can make an StdTx from a SignDoc and one signature", () => {
const fee: StdFee = { amount: coins(123, "ucosm"), gas: "22" };
const signDoc = makeSignDoc([], fee, "chain-xy", "hello", 3, 4);
const signature: StdSignature = {
pub_key: {
type: "tendermint/PubKeySecp256k1",
value: "AtQaCqFnshaZQp6rIkvAPyzThvCvXSDO+9AzbxVErqJP",
},
signature: "1nUcIH0CLT0/nQ0mBTDrT6kMG20NY/PsH7P2gc4bpYNGLEYjBmdWevXUJouSE/9A/60QG9cYeqyTe5kFDeIPxQ==",
};
const signedTx = makeStdTx(signDoc, signature);
expect(signedTx).toEqual({
msg: [],
memo: "hello",
fee: fee,
signatures: [signature],
});
});
it("can make an StdTx from a SignDoc and multiple signatures", () => {
const fee: StdFee = { amount: coins(123, "ucosm"), gas: "22" };
const signDoc = makeSignDoc([], fee, "chain-xy", "hello", 3, 4);
const signature1: StdSignature = {
pub_key: {
type: "tendermint/PubKeySecp256k1",
value: "AtQaCqFnshaZQp6rIkvAPyzThvCvXSDO+9AzbxVErqJP",
},
signature: "1nUcIH0CLT0/nQ0mBTDrT6kMG20NY/PsH7P2gc4bpYNGLEYjBmdWevXUJouSE/9A/60QG9cYeqyTe5kFDeIPxQ==",
};
const signature2: StdSignature = {
pub_key: {
type: "tendermint/PubKeySecp256k1",
value: "A5qFcJBJvEK/fOmEAY0DHNWwSRZ9TEfNZyH8VoVvDtAq",
},
signature: "NK1Oy4EUGAsoC03c1wi9GG03JC/39LEdautC5Jk643oIbEPqeXHMwaqbdvO/Jws0X/NAXaN8SAy2KNY5Qml+5Q==",
};
const signedTx = makeStdTx(signDoc, [signature1, signature2]);
expect(signedTx).toEqual({
msg: [],
memo: "hello",
fee: fee,
signatures: [signature1, signature2],
});
});
});
});

View File

@ -0,0 +1,57 @@
import { StdSignDoc } from "./encoding";
import { Msg } from "./msgs";
import { StdFee, StdSignature } from "./types";
/**
* A Cosmos SDK StdTx
*
* @see https://docs.cosmos.network/master/modules/auth/03_types.html#stdtx
*/
export interface StdTx {
readonly msg: readonly Msg[];
readonly fee: StdFee;
readonly signatures: readonly StdSignature[];
readonly memo: string | undefined;
}
export function isStdTx(txValue: unknown): txValue is StdTx {
const { memo, msg, fee, signatures } = txValue as StdTx;
return (
typeof memo === "string" && Array.isArray(msg) && typeof fee === "object" && Array.isArray(signatures)
);
}
export function makeStdTx(
content: Pick<StdSignDoc, "msgs" | "fee" | "memo">,
signatures: StdSignature | readonly StdSignature[],
): StdTx {
return {
msg: content.msgs,
fee: content.fee,
memo: content.memo,
signatures: Array.isArray(signatures) ? signatures : [signatures],
};
}
/**
* An Amino JSON wrapper around the Tx interface
*/
export interface WrappedTx {
readonly type: string;
readonly value: any;
}
/**
* An Amino JSON wrapper around StdTx
*/
export interface WrappedStdTx extends WrappedTx {
readonly type: "cosmos-sdk/StdTx";
readonly value: StdTx;
}
export function isWrappedStdTx(wrapped: WrappedTx): wrapped is WrappedStdTx {
return (wrapped as WrappedStdTx).type === "cosmos-sdk/StdTx" && isStdTx(wrapped.value);
}
/** @deprecated use WrappedStdTx */
export type CosmosSdkTx = WrappedStdTx;

View File

@ -1,30 +1,5 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { Coin } from "./coins";
import { Msg } from "./msgs";
/**
* A Cosmos SDK StdTx
*
* @see https://docs.cosmos.network/master/modules/auth/03_types.html#stdtx
*/
export interface StdTx {
readonly msg: readonly Msg[];
readonly fee: StdFee;
readonly signatures: readonly StdSignature[];
readonly memo: string | undefined;
}
export function isStdTx(txValue: unknown): txValue is StdTx {
const { memo, msg, fee, signatures } = txValue as StdTx;
return (
typeof memo === "string" && Array.isArray(msg) && typeof fee === "object" && Array.isArray(signatures)
);
}
export interface CosmosSdkTx {
readonly type: string;
readonly value: StdTx;
}
export interface StdFee {
readonly amount: readonly Coin[];

View File

@ -3,52 +3,12 @@ import {
HdPath,
isArgon2idOptions,
Random,
Sha256,
Sha512,
Slip10RawIndex,
xchacha20NonceLength,
Xchacha20poly1305Ietf,
} from "@cosmjs/crypto";
import { toAscii } from "@cosmjs/encoding";
import { StdSignature } from "./types";
export type PrehashType = "sha256" | "sha512" | null;
export type Algo = "secp256k1" | "ed25519" | "sr25519";
export interface AccountData {
// bech32-encoded
readonly address: string;
readonly algo: Algo;
readonly pubkey: Uint8Array;
}
export interface OfflineSigner {
/**
* 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>;
}
export function prehash(bytes: Uint8Array, type: PrehashType): Uint8Array {
switch (type) {
case null:
return new Uint8Array([...bytes]);
case "sha256":
return new Sha256(bytes).digest();
case "sha512":
return new Sha512(bytes).digest();
default:
throw new Error("Unknown prehash type");
}
}
/**
* The Cosmoshub derivation path in the form `m/44'/118'/0'/0/a`
* with 0-based account index `a`.

View File

@ -1,7 +1,8 @@
import { Coin } from "./coins";
import { AuthExtension, BroadcastMode, LcdClient } from "./lcdapi";
import { Log } from "./logs";
import { CosmosSdkTx, PubKey, StdTx } from "./types";
import { StdTx, WrappedStdTx } from "./tx";
import { PubKey } from "./types";
export interface GetSequenceResult {
readonly accountNumber: number;
readonly sequence: number;
@ -78,7 +79,7 @@ export interface IndexedTx {
readonly code: number;
readonly rawLog: string;
readonly logs: readonly Log[];
readonly tx: CosmosSdkTx;
readonly tx: WrappedStdTx;
/** The gas limit as set by the user */
readonly gasWanted?: number;
/** The gas used by the execution */
@ -127,7 +128,7 @@ export declare class CosmosClient {
/**
* Returns a 32 byte upper-case hex transaction hash (typically used as the transaction ID)
*/
getIdentifier(tx: CosmosSdkTx): Promise<string>;
getIdentifier(tx: WrappedStdTx): Promise<string>;
/**
* Returns account number and sequence.
*

View File

@ -6,18 +6,19 @@ import { StdFee } from "./types";
* @see https://docs.cosmos.network/master/modules/auth/03_types.html#stdsigndoc
*/
export interface StdSignDoc {
readonly account_number: string;
readonly chain_id: string;
readonly fee: StdFee;
readonly memo: string;
readonly msgs: readonly Msg[];
readonly account_number: string;
readonly sequence: string;
readonly fee: StdFee;
readonly msgs: readonly Msg[];
readonly memo: string;
}
export declare function makeSignBytes(
export declare function makeSignDoc(
msgs: readonly Msg[],
fee: StdFee,
chainId: string,
memo: string,
accountNumber: number | string,
sequence: number | string,
): Uint8Array;
): StdSignDoc;
export declare function serializeSignDoc(signDoc: StdSignDoc): Uint8Array;

View File

@ -26,7 +26,7 @@ export {
isSearchBySentFromOrToQuery,
isSearchByTagsQuery,
} from "./cosmosclient";
export { makeSignBytes, StdSignDoc } from "./encoding";
export { makeSignDoc, serializeSignDoc, StdSignDoc } from "./encoding";
export { buildFeeTable, FeeTable, GasLimits, GasPrice } from "./gas";
export {
AuthAccountsResponse,
@ -96,15 +96,9 @@ export {
} from "./pubkey";
export { findSequenceForSignedTx } from "./sequence";
export { encodeSecp256k1Signature, decodeSignature } from "./signature";
export { AccountData, Algo, OfflineSigner, SignResponse } from "./signer";
export { CosmosFeeTable, SigningCosmosClient } from "./signingcosmosclient";
export { isStdTx, pubkeyType, CosmosSdkTx, PubKey, StdFee, StdSignature, StdTx } from "./types";
export {
AccountData,
Algo,
PrehashType,
OfflineSigner,
makeCosmoshubPath,
executeKdf,
KdfConfiguration,
} from "./wallet";
export { isStdTx, isWrappedStdTx, makeStdTx, CosmosSdkTx, StdTx, WrappedStdTx, WrappedTx } from "./tx";
export { pubkeyType, PubKey, StdFee, StdSignature } from "./types";
export { makeCosmoshubPath, executeKdf, KdfConfiguration } from "./wallet";
export { extractKdfConfiguration, Secp256k1Wallet } from "./secp256k1wallet";

View File

@ -1,4 +1,4 @@
import { CosmosSdkTx } from "../types";
import { WrappedStdTx } from "../tx";
/**
* The mode used to send transaction
*
@ -93,7 +93,7 @@ export interface TxsResponse {
readonly code?: number;
readonly raw_log: string;
readonly logs?: unknown[];
readonly tx: CosmosSdkTx;
readonly tx: WrappedStdTx;
/** The gas limit as set by the user */
readonly gas_wanted?: string;
/** The gas used by the execution */

View File

@ -1,4 +1,4 @@
import { CosmosSdkTx, StdTx } from "../types";
import { StdTx, WrappedStdTx } from "../tx";
import {
BlockResponse,
BroadcastMode,
@ -151,7 +151,7 @@ export declare class LcdClient {
txById(id: string): Promise<TxsResponse>;
txsQuery(query: string): Promise<SearchTxsResponse>;
/** returns the amino-encoding of the transaction performed by the server */
encodeTx(tx: CosmosSdkTx): Promise<EncodeTxResponse>;
encodeTx(tx: WrappedStdTx): Promise<EncodeTxResponse>;
/**
* Broadcasts a signed transaction to the transaction pool.
* Depending on the client's broadcast mode, this might or might

View File

@ -1,6 +1,7 @@
import { HdPath } from "@cosmjs/crypto";
import { StdSignature } from "./types";
import { AccountData, EncryptionConfiguration, KdfConfiguration, OfflineSigner, PrehashType } from "./wallet";
import { StdSignDoc } from "./encoding";
import { AccountData, OfflineSigner, SignResponse } from "./signer";
import { EncryptionConfiguration, KdfConfiguration } from "./wallet";
/**
* This interface describes a JSON object holding the encrypted wallet and the meta data.
* All fields in here must be JSON types.
@ -85,7 +86,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<SignResponse>;
/**
* Generates an encrypted serialization of this wallet.
*

View File

@ -1,4 +1,4 @@
import { CosmosSdkTx } from "./types";
import { WrappedStdTx } from "./tx";
/**
* Serach for sequence s with `min` <= `s` < `upperBound` to find the sequence that was used to sign the transaction
*
@ -11,7 +11,7 @@ import { CosmosSdkTx } from "./types";
* @returns the sequence if a match was found and undefined otherwise
*/
export declare function findSequenceForSignedTx(
tx: CosmosSdkTx,
tx: WrappedStdTx,
chainId: string,
accountNumber: number,
upperBound: number,

33
packages/launchpad/types/signer.d.ts vendored Normal file
View File

@ -0,0 +1,33 @@
import { StdSignDoc } from "./encoding";
import { StdSignature } from "./types";
export declare type Algo = "secp256k1" | "ed25519" | "sr25519";
export interface AccountData {
/** A printable address (typically bech32 encoded) */
readonly address: string;
readonly algo: Algo;
readonly pubkey: Uint8Array;
}
export interface SignResponse {
/**
* The sign doc that was signed.
* This may be different from the input signDoc when the signer modifies it as part of the signing process.
*/
readonly signed: StdSignDoc;
readonly signature: StdSignature;
}
export interface OfflineSigner {
/**
* 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.
*
* The signer implementation may offer the user the ability to override parts of the signDoc. It must
* return the doc that was signed in the response.
*
* @param signerAddress The address of the account that should sign the transaction
* @param signDoc The content that should be signed
*/
readonly sign: (signerAddress: string, signDoc: StdSignDoc) => Promise<SignResponse>;
}

View File

@ -3,8 +3,8 @@ import { Account, BroadcastTxResult, CosmosClient, GetSequenceResult } from "./c
import { FeeTable, GasLimits, GasPrice } from "./gas";
import { BroadcastMode } from "./lcdapi";
import { Msg } from "./msgs";
import { OfflineSigner } from "./signer";
import { StdFee } from "./types";
import { OfflineSigner } from "./wallet";
/**
* These fees are used by the higher level methods of SigningCosmosClient
*/

36
packages/launchpad/types/tx.d.ts vendored Normal file
View File

@ -0,0 +1,36 @@
import { StdSignDoc } from "./encoding";
import { Msg } from "./msgs";
import { StdFee, StdSignature } from "./types";
/**
* A Cosmos SDK StdTx
*
* @see https://docs.cosmos.network/master/modules/auth/03_types.html#stdtx
*/
export interface StdTx {
readonly msg: readonly Msg[];
readonly fee: StdFee;
readonly signatures: readonly StdSignature[];
readonly memo: string | undefined;
}
export declare function isStdTx(txValue: unknown): txValue is StdTx;
export declare function makeStdTx(
content: Pick<StdSignDoc, "msgs" | "fee" | "memo">,
signatures: StdSignature | readonly StdSignature[],
): StdTx;
/**
* An Amino JSON wrapper around the Tx interface
*/
export interface WrappedTx {
readonly type: string;
readonly value: any;
}
/**
* An Amino JSON wrapper around StdTx
*/
export interface WrappedStdTx extends WrappedTx {
readonly type: "cosmos-sdk/StdTx";
readonly value: StdTx;
}
export declare function isWrappedStdTx(wrapped: WrappedTx): wrapped is WrappedStdTx;
/** @deprecated use WrappedStdTx */
export declare type CosmosSdkTx = WrappedStdTx;

View File

@ -1,21 +1,4 @@
import { Coin } from "./coins";
import { Msg } from "./msgs";
/**
* A Cosmos SDK StdTx
*
* @see https://docs.cosmos.network/master/modules/auth/03_types.html#stdtx
*/
export interface StdTx {
readonly msg: readonly Msg[];
readonly fee: StdFee;
readonly signatures: readonly StdSignature[];
readonly memo: string | undefined;
}
export declare function isStdTx(txValue: unknown): txValue is StdTx;
export interface CosmosSdkTx {
readonly type: string;
readonly value: StdTx;
}
export interface StdFee {
readonly amount: readonly Coin[];
readonly gas: string;

View File

@ -1,23 +1,4 @@
import { HdPath } from "@cosmjs/crypto";
import { StdSignature } from "./types";
export declare type PrehashType = "sha256" | "sha512" | null;
export declare type Algo = "secp256k1" | "ed25519" | "sr25519";
export interface AccountData {
readonly address: string;
readonly algo: Algo;
readonly pubkey: Uint8Array;
}
export interface OfflineSigner {
/**
* 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>;
}
export declare function prehash(bytes: Uint8Array, type: PrehashType): Uint8Array;
/**
* The Cosmoshub derivation path in the form `m/44'/118'/0'/0/a`
* with 0-based account index `a`.