Improve parsing, two types of pubkey decoding

This commit is contained in:
Ethan Frey 2020-02-03 18:18:21 +01:00
parent 62b5c88c58
commit f5fbb02ba4
11 changed files with 76 additions and 56 deletions

View File

@ -33,8 +33,8 @@ describe("address", () => {
expect(
decodeCosmosPubkey("cosmospub1addwnpepqd8sgxq7aw348ydctp3n5ajufgxp395hksxjzc6565yfp56scupfqhlgyg5"),
).toEqual({
prefix: "cosmospub",
data: fromBase64("A08EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQ"),
algo: Algorithm.Secp256k1,
});
});
});

View File

@ -1,4 +1,4 @@
import { Address, Algorithm, PubkeyBundle } from "@iov/bcp";
import { Address, Algorithm, PubkeyBundle, PubkeyBytes } from "@iov/bcp";
import { Ripemd160, Secp256k1, Sha256 } from "@iov/crypto";
import { Bech32, Encoding } from "@iov/encoding";
import equal from "fast-deep-equal";
@ -8,7 +8,10 @@ export type CosmosPubkeyBech32Prefix = "cosmospub" | "cosmosvalconspub" | "cosmo
export type CosmosBech32Prefix = CosmosAddressBech32Prefix | CosmosPubkeyBech32Prefix;
// As discussed in https://github.com/binance-chain/javascript-sdk/issues/163
const pubkeyAminoPrefix = Encoding.fromHex("eb5ae98721");
// Prefixes listed here: https://github.com/tendermint/tendermint/blob/d419fffe18531317c28c29a292ad7d253f6cafdf/docs/spec/blockchain/encoding.md#public-key-cryptography
const pubkeyAminoPrefixSecp256k1 = Encoding.fromHex("eb5ae98721");
const pubkeyAminoPrefixEd25519 = Encoding.fromHex("1624de64");
const pubkeyAminoPrefixLength = pubkeyAminoPrefixSecp256k1.length;
function isCosmosAddressBech32Prefix(prefix: string): prefix is CosmosAddressBech32Prefix {
return ["cosmos", "cosmosvalcons", "cosmosvaloper"].includes(prefix);
@ -33,22 +36,27 @@ export function decodeCosmosAddress(
export function decodeCosmosPubkey(
encodedPubkey: string,
): { readonly prefix: CosmosPubkeyBech32Prefix; readonly data: Uint8Array } {
): { readonly algo: Algorithm; readonly data: PubkeyBytes } {
const { prefix, data } = Bech32.decode(encodedPubkey);
if (!isCosmosPubkeyBech32Prefix(prefix)) {
throw new Error(`Invalid bech32 prefix. Must be one of cosmos, cosmosvalcons, or cosmosvaloper.`);
}
if (!equal(data.slice(0, pubkeyAminoPrefix.length), pubkeyAminoPrefix)) {
throw new Error("Pubkey does not have the expected amino prefix " + Encoding.toHex(pubkeyAminoPrefix));
const aminoPrefix = data.slice(0, pubkeyAminoPrefixLength);
const rest = data.slice(pubkeyAminoPrefixLength);
if (equal(aminoPrefix, pubkeyAminoPrefixSecp256k1)) {
if (rest.length !== 33) {
throw new Error("Invalid rest data length. Expected 33 bytes (compressed secp256k1 pubkey).");
}
return { algo: Algorithm.Secp256k1, data: rest as PubkeyBytes };
} else if (equal(aminoPrefix, pubkeyAminoPrefixEd25519)) {
if (rest.length !== 32) {
throw new Error("Invalid rest data length. Expected 32 bytes (ed25519 pubkey).");
}
return { algo: Algorithm.Ed25519, data: rest as PubkeyBytes };
} else {
throw new Error("Unsupported Pubkey type. Amino prefix: " + Encoding.toHex(aminoPrefix));
}
const rest = data.slice(pubkeyAminoPrefix.length);
if (rest.length !== 33) {
throw new Error("Invalid rest data length. Expected 33 bytes (compressed secp256k1 pubkey).");
}
return { prefix: prefix, data: rest };
}
export function isValidAddress(address: string): boolean {

View File

@ -151,13 +151,7 @@ export class CosmWasmConnection implements BlockchainConnection {
this.tokenInfo.find(token => token.denom === denom),
);
const pubkey = !account.public_key
? undefined
: {
algo: Algorithm.Secp256k1,
// amino-js has wrong (outdated) types
data: decodeCosmosPubkey(account.public_key as any).data as PubkeyBytes,
};
const pubkey = !account.public_key ? undefined : decodeCosmosPubkey(account.public_key);
return {
address: address,
balance: supportedCoins.map(coin => decodeAmount(this.tokenInfo, coin)),

View File

@ -1,4 +1,4 @@
import { coinToDecimal, TxsResponse, types } from "@cosmwasm/sdk";
import { coinToDecimal, isAminoStdTx, TxsResponse, types } from "@cosmwasm/sdk";
import {
Address,
Algorithm,
@ -95,7 +95,7 @@ export function parseFee(fee: types.StdFee, tokens: TokenInfos): Fee {
export function parseTx(tx: types.Tx, chainId: ChainId, nonce: Nonce, tokens: TokenInfos): SignedTransaction {
const txValue = tx.value;
if (!types.isAminoStdTx(txValue)) {
if (!isAminoStdTx(txValue)) {
throw new Error("Only Amino StdTx is supported");
}
if (txValue.msg.length !== 1) {

View File

@ -1,4 +1,4 @@
import { Address, PubkeyBundle } from "@iov/bcp";
import { Address, Algorithm, PubkeyBundle, PubkeyBytes } from "@iov/bcp";
export declare type CosmosAddressBech32Prefix = "cosmos" | "cosmosvalcons" | "cosmosvaloper";
export declare type CosmosPubkeyBech32Prefix = "cosmospub" | "cosmosvalconspub" | "cosmosvaloperpub";
export declare type CosmosBech32Prefix = CosmosAddressBech32Prefix | CosmosPubkeyBech32Prefix;
@ -11,8 +11,8 @@ export declare function decodeCosmosAddress(
export declare function decodeCosmosPubkey(
encodedPubkey: string,
): {
readonly prefix: CosmosPubkeyBech32Prefix;
readonly data: Uint8Array;
readonly algo: Algorithm;
readonly data: PubkeyBytes;
};
export declare function isValidAddress(address: string): boolean;
export declare function pubkeyToAddress(pubkey: PubkeyBundle, prefix: CosmosBech32Prefix): Address;

View File

@ -1,5 +1,6 @@
export { coinToDecimal } from "./decoding";
export { decimalToCoin } from "./encoding";
export { RestClient, TxsResponse } from "./restclient";
// export { AminoTx, isAminoStdTx, TokenInfo } from "./types";
export { default as types } from "./types";
// types.X are all the types we re-export
// Note: this doesn't work for functions, just typescript types, so we must explicitly re-export functions
export { default as types, isAminoStdTx } from "./types";

View File

@ -1,7 +1,7 @@
import amino, { unmarshalTx } from "@tendermint/amino-js";
import { unmarshalTx } from "@tendermint/amino-js";
import axios, { AxiosInstance } from "axios";
import { AminoTx } from "./types";
import { AminoTx, BaseAccount } from "./types";
interface NodeInfo {
readonly network: string;
@ -35,7 +35,7 @@ interface BlocksResponse {
interface AuthAccountsResponse {
readonly result: {
readonly value: amino.BaseAccount;
readonly value: BaseAccount;
};
}

View File

@ -1,8 +1,9 @@
// We will move all needed *interfaces* from amino-js here
// This means bcp can just import them from here (if needed at all)
export interface Tx {
type: string;
value: any;
readonly type: string;
// TODO
readonly value: any;
}
export interface StdTx {
@ -13,7 +14,7 @@ export interface StdTx {
}
export interface Msg {
type: string;
readonly type: string;
// TODO: make better union type
readonly value: MsgSend;
}
@ -32,20 +33,30 @@ export interface StdFee {
}
export interface Coin {
denom: string;
amount: string;
readonly denom: string;
readonly amount: string;
}
export interface StdSignature {
pub_key: PubKey;
signature: string;
readonly pub_key: PubKey;
readonly signature: string;
}
export interface PubKey {
/** Amino registered name, e.g. `"tendermint/PubKeySecp256k1"` */
type: string;
/** Base64-encoded key bytes */
value: string;
readonly type: string;
readonly value: string;
}
// AccountPubKey is bech32-encoded amino-binary encoded PubKey interface. oof.
export type AccountPubKey = string;
export interface BaseAccount {
/** Bech32 account address */
readonly address: string;
readonly coins: ReadonlyArray<Coin>;
readonly public_key: AccountPubKey;
readonly account_number: string;
readonly sequence: string;
}
export type AminoTx = Tx & { readonly value: StdTx };

View File

@ -1,4 +1,4 @@
export { coinToDecimal } from "./decoding";
export { decimalToCoin } from "./encoding";
export { RestClient, TxsResponse } from "./restclient";
export { default as types } from "./types";
export { default as types, isAminoStdTx } from "./types";

View File

@ -1,5 +1,4 @@
import amino from "@tendermint/amino-js";
import { AminoTx } from "./types";
import { AminoTx, BaseAccount } from "./types";
interface NodeInfo {
readonly network: string;
}
@ -27,7 +26,7 @@ interface BlocksResponse {
}
interface AuthAccountsResponse {
readonly result: {
readonly value: amino.BaseAccount;
readonly value: BaseAccount;
};
}
export interface TxsResponse {

View File

@ -1,6 +1,6 @@
export interface Tx {
type: string;
value: any;
readonly type: string;
readonly value: any;
}
export interface StdTx {
readonly msg: ReadonlyArray<Msg>;
@ -9,7 +9,7 @@ export interface StdTx {
readonly memo: string | undefined;
}
export interface Msg {
type: string;
readonly type: string;
readonly value: MsgSend;
}
export interface MsgSend {
@ -24,18 +24,25 @@ export interface StdFee {
readonly gas: string;
}
export interface Coin {
denom: string;
amount: string;
readonly denom: string;
readonly amount: string;
}
export interface StdSignature {
pub_key: PubKey;
signature: string;
readonly pub_key: PubKey;
readonly signature: string;
}
export interface PubKey {
/** Amino registered name, e.g. `"tendermint/PubKeySecp256k1"` */
type: string;
/** Base64-encoded key bytes */
value: string;
readonly type: string;
readonly value: string;
}
export declare type AccountPubKey = string;
export interface BaseAccount {
/** Bech32 account address */
readonly address: string;
readonly coins: ReadonlyArray<Coin>;
readonly public_key: AccountPubKey;
readonly account_number: string;
readonly sequence: string;
}
export declare type AminoTx = Tx & {
readonly value: StdTx;