Deduplicate makeSignBytes

This commit is contained in:
Simon Warta 2020-02-04 13:34:51 +01:00
parent 44182e96b7
commit 2d14efa4cd
10 changed files with 66 additions and 58 deletions

View File

@ -1,5 +1,5 @@
/* eslint-disable @typescript-eslint/camelcase */ /* eslint-disable @typescript-eslint/camelcase */
import { marshalTx, sortJson, unmarshalTx } from "@cosmwasm/sdk"; import { makeSignBytes, marshalTx, types, unmarshalTx } from "@cosmwasm/sdk";
import { import {
Address, Address,
ChainId, ChainId,
@ -14,7 +14,6 @@ import {
TxCodec, TxCodec,
UnsignedTransaction, UnsignedTransaction,
} from "@iov/bcp"; } from "@iov/bcp";
import { Encoding } from "@iov/encoding";
import { CosmosBech32Prefix, isValidAddress, pubkeyToAddress } from "./address"; import { CosmosBech32Prefix, isValidAddress, pubkeyToAddress } from "./address";
import { Caip5 } from "./caip5"; import { Caip5 } from "./caip5";
@ -22,8 +21,6 @@ import { parseTx } from "./decode";
import { buildSignedTx, buildUnsignedTx } from "./encode"; import { buildSignedTx, buildUnsignedTx } from "./encode";
import { nonceToAccountNumber, nonceToSequence, TokenInfos } from "./types"; import { nonceToAccountNumber, nonceToSequence, TokenInfos } from "./types";
const { toUtf8 } = Encoding;
export class CosmWasmCodec implements TxCodec { export class CosmWasmCodec implements TxCodec {
private readonly prefix: CosmosBech32Prefix; private readonly prefix: CosmosBech32Prefix;
private readonly tokens: TokenInfos; private readonly tokens: TokenInfos;
@ -34,18 +31,19 @@ export class CosmWasmCodec implements TxCodec {
} }
public bytesToSign(unsigned: UnsignedTransaction, nonce: Nonce): SigningJob { public bytesToSign(unsigned: UnsignedTransaction, nonce: Nonce): SigningJob {
const memo = (unsigned as any).memo;
const built = buildUnsignedTx(unsigned, this.tokens); const built = buildUnsignedTx(unsigned, this.tokens);
const signMsg = sortJson({ const nonceInfo: types.NonceInfo = {
account_number: nonceToAccountNumber(nonce).toString(), account_number: nonceToAccountNumber(nonce),
chain_id: Caip5.decode(unsigned.chainId), sequence: nonceToSequence(nonce),
fee: (built.value as any).fee, };
memo: memo, const signBytes = makeSignBytes(
msgs: (built.value as any).msg, built.value.msg[0],
sequence: nonceToSequence(nonce).toString(), built.value.fee,
}); Caip5.decode(unsigned.chainId),
const signBytes = toUtf8(JSON.stringify(signMsg)); built.value.memo || "",
nonceInfo,
);
return { return {
bytes: signBytes as SignableBytes, bytes: signBytes as SignableBytes,

View File

@ -23,13 +23,9 @@ const maxAcct = 1 << 23;
// tslint:disable-next-line:no-bitwise // tslint:disable-next-line:no-bitwise
const maxSeq = 1 << 20; const maxSeq = 1 << 20;
// NonceInfo is the data we need from account to create a nonce
// Use this so no confusion about order of arguments
export type NonceInfo = Pick<types.BaseAccount, "account_number" | "sequence">;
// this (lossily) encodes the two pieces of info (uint64) needed to sign into // this (lossily) encodes the two pieces of info (uint64) needed to sign into
// one (53-bit) number. Cross your fingers. // one (53-bit) number. Cross your fingers.
export function accountToNonce({ account_number: account, sequence }: NonceInfo): Nonce { export function accountToNonce({ account_number: account, sequence }: types.NonceInfo): Nonce {
// we allow 23 bits (8 million) for accounts, and 20 bits (1 million) for tx/account // we allow 23 bits (8 million) for accounts, and 20 bits (1 million) for tx/account
// let's fix this soon // let's fix this soon
if (account > maxAcct) { if (account > maxAcct) {

View File

@ -15,7 +15,6 @@ export interface TokenInfo {
readonly fractionalDigits: number; readonly fractionalDigits: number;
} }
export declare type TokenInfos = ReadonlyArray<TokenInfo>; export declare type TokenInfos = ReadonlyArray<TokenInfo>;
export declare type NonceInfo = Pick<types.BaseAccount, "account_number" | "sequence">; export declare function accountToNonce({ account_number: account, sequence }: types.NonceInfo): Nonce;
export declare function accountToNonce({ account_number: account, sequence }: NonceInfo): Nonce;
export declare function nonceToAccountNumber(nonce: Nonce): number; export declare function nonceToAccountNumber(nonce: Nonce): number;
export declare function nonceToSequence(nonce: Nonce): number; export declare function nonceToSequence(nonce: Nonce): number;

View File

@ -1,11 +1,11 @@
import { Secp256k1 } from "@iov/crypto"; import { Secp256k1 } from "@iov/crypto";
import { Encoding } from "@iov/encoding"; import { Encoding } from "@iov/encoding";
import { StdSignature, StdTx } from "./types"; import { Msg, NonceInfo, StdFee, StdSignature, StdTx } from "./types";
const { toBase64 } = Encoding; const { toBase64, toUtf8 } = Encoding;
export function sortJson(json: any): any { function sortJson(json: any): any {
if (typeof json !== "object" || json === null) { if (typeof json !== "object" || json === null) {
return json; return json;
} }
@ -28,6 +28,26 @@ export function marshalTx(tx: StdTx): Uint8Array {
return Encoding.toUtf8(json); return Encoding.toUtf8(json);
} }
export function makeSignBytes(
msg: Msg,
fee: StdFee,
chainId: string,
memo: string,
account: NonceInfo,
): Uint8Array {
const signMsg = sortJson({
// eslint-disable-next-line @typescript-eslint/camelcase
account_number: account.account_number.toString(),
// eslint-disable-next-line @typescript-eslint/camelcase
chain_id: chainId,
fee: fee,
memo: memo,
msgs: msg,
sequence: account.sequence.toString(),
});
return toUtf8(JSON.stringify(signMsg));
}
export function encodeSecp256k1Signature(pubkey: Uint8Array, signature: Uint8Array): StdSignature { export function encodeSecp256k1Signature(pubkey: Uint8Array, signature: Uint8Array): StdSignature {
return { return {
// eslint-disable-next-line @typescript-eslint/camelcase // eslint-disable-next-line @typescript-eslint/camelcase

View File

@ -1,6 +1,6 @@
import * as types from "./types"; import * as types from "./types";
export { unmarshalTx } from "./decoding"; export { unmarshalTx } from "./decoding";
export { encodeSecp256k1Signature, marshalTx, sortJson } from "./encoding"; export { encodeSecp256k1Signature, makeSignBytes, marshalTx } from "./encoding";
export { RestClient, TxsResponse } from "./restclient"; export { RestClient, TxsResponse } from "./restclient";
export { types }; export { types };

View File

@ -3,13 +3,13 @@ import { ChainId, PrehashType, SignableBytes } from "@iov/bcp";
import { Encoding } from "@iov/encoding"; import { Encoding } from "@iov/encoding";
import { HdPaths, Secp256k1HdWallet } from "@iov/keycontrol"; import { HdPaths, Secp256k1HdWallet } from "@iov/keycontrol";
import { encodeSecp256k1Signature, marshalTx, sortJson } from "./encoding"; import { encodeSecp256k1Signature, makeSignBytes, marshalTx } from "./encoding";
import { RestClient } from "./restclient"; import { RestClient } from "./restclient";
import contract from "./testdata/contract.json"; import contract from "./testdata/contract.json";
import data from "./testdata/cosmoshub.json"; import data from "./testdata/cosmoshub.json";
import { MsgStoreCode, StdTx } from "./types"; import { MsgStoreCode, StdFee, StdTx } from "./types";
const { fromBase64, toUtf8 } = Encoding; const { fromBase64 } = Encoding;
const httpUrl = "http://localhost:1317"; const httpUrl = "http://localhost:1317";
const defaultNetworkId = "testing"; const defaultNetworkId = "testing";
@ -75,41 +75,25 @@ describe("RestClient", () => {
builder: "v0.0.1", builder: "v0.0.1",
}, },
}; };
const fee: StdFee = {
const unsigned: StdTx = { amount: [
msg: [theMsg], {
memo: memo, amount: "5000",
signatures: [], denom: "ucosm",
fee: { },
amount: [ ],
{ gas: "89000000",
amount: "5000",
denom: "ucosm",
},
],
gas: "89000000",
},
}; };
const client = new RestClient(httpUrl); const client = new RestClient(httpUrl);
const account = (await client.authAccounts(faucetAddress)).result.value; const account = (await client.authAccounts(faucetAddress)).result.value;
const signBytes = makeSignBytes(theMsg, fee, defaultNetworkId, memo, account) as SignableBytes;
const signMsg = sortJson({
account_number: account.account_number.toString(),
chain_id: defaultNetworkId,
fee: unsigned.fee,
memo: memo,
msgs: unsigned.msg,
sequence: account.sequence.toString(),
});
const signBytes = toUtf8(JSON.stringify(signMsg)) as SignableBytes;
const rawSignature = await wallet.createTransactionSignature(signer, signBytes, PrehashType.Sha256); const rawSignature = await wallet.createTransactionSignature(signer, signBytes, PrehashType.Sha256);
const signature = encodeSecp256k1Signature(signer.pubkey.data, rawSignature); const signature = encodeSecp256k1Signature(signer.pubkey.data, rawSignature);
const tx: StdTx = { const tx: StdTx = {
msg: unsigned.msg, msg: [theMsg],
fee: unsigned.fee, fee: fee,
memo: memo, memo: memo,
signatures: [signature], signatures: [signature],
}; };

View File

@ -97,3 +97,6 @@ export interface BaseAccount {
readonly account_number: number; readonly account_number: number;
readonly sequence: number; readonly sequence: number;
} }
/** The data we need from BaseAccount to create a nonce */
export type NonceInfo = Pick<BaseAccount, "account_number" | "sequence">;

View File

@ -1,4 +1,10 @@
import { StdSignature, StdTx } from "./types"; import { Msg, NonceInfo, StdFee, StdSignature, StdTx } from "./types";
export declare function sortJson(json: any): any;
export declare function marshalTx(tx: StdTx): Uint8Array; export declare function marshalTx(tx: StdTx): Uint8Array;
export declare function makeSignBytes(
msg: Msg,
fee: StdFee,
chainId: string,
memo: string,
account: NonceInfo,
): Uint8Array;
export declare function encodeSecp256k1Signature(pubkey: Uint8Array, signature: Uint8Array): StdSignature; export declare function encodeSecp256k1Signature(pubkey: Uint8Array, signature: Uint8Array): StdSignature;

View File

@ -1,5 +1,5 @@
import * as types from "./types"; import * as types from "./types";
export { unmarshalTx } from "./decoding"; export { unmarshalTx } from "./decoding";
export { encodeSecp256k1Signature, marshalTx, sortJson } from "./encoding"; export { encodeSecp256k1Signature, makeSignBytes, marshalTx } from "./encoding";
export { RestClient, TxsResponse } from "./restclient"; export { RestClient, TxsResponse } from "./restclient";
export { types }; export { types };

View File

@ -69,4 +69,6 @@ export interface BaseAccount {
readonly account_number: number; readonly account_number: number;
readonly sequence: number; readonly sequence: number;
} }
/** The data we need from BaseAccount to create a nonce */
export declare type NonceInfo = Pick<BaseAccount, "account_number" | "sequence">;
export {}; export {};