From f5fbb02ba4a18ec57829a763bbcb461c9b44b435 Mon Sep 17 00:00:00 2001 From: Ethan Frey Date: Mon, 3 Feb 2020 18:18:21 +0100 Subject: [PATCH] Improve parsing, two types of pubkey decoding --- packages/bcp/src/address.spec.ts | 2 +- packages/bcp/src/address.ts | 32 +++++++++++++++---------- packages/bcp/src/cosmwasmconnection.ts | 8 +------ packages/bcp/src/decode.ts | 4 ++-- packages/bcp/types/address.d.ts | 6 ++--- packages/sdk/src/index.ts | 5 ++-- packages/sdk/src/restclient.ts | 6 ++--- packages/sdk/src/types.ts | 33 +++++++++++++++++--------- packages/sdk/types/index.d.ts | 2 +- packages/sdk/types/restclient.d.ts | 5 ++-- packages/sdk/types/types.d.ts | 29 +++++++++++++--------- 11 files changed, 76 insertions(+), 56 deletions(-) diff --git a/packages/bcp/src/address.spec.ts b/packages/bcp/src/address.spec.ts index 1d138206..21fcb192 100644 --- a/packages/bcp/src/address.spec.ts +++ b/packages/bcp/src/address.spec.ts @@ -33,8 +33,8 @@ describe("address", () => { expect( decodeCosmosPubkey("cosmospub1addwnpepqd8sgxq7aw348ydctp3n5ajufgxp395hksxjzc6565yfp56scupfqhlgyg5"), ).toEqual({ - prefix: "cosmospub", data: fromBase64("A08EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQ"), + algo: Algorithm.Secp256k1, }); }); }); diff --git a/packages/bcp/src/address.ts b/packages/bcp/src/address.ts index d36a50d4..fa5e7ff2 100644 --- a/packages/bcp/src/address.ts +++ b/packages/bcp/src/address.ts @@ -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 { diff --git a/packages/bcp/src/cosmwasmconnection.ts b/packages/bcp/src/cosmwasmconnection.ts index a87e91a0..d44d143e 100644 --- a/packages/bcp/src/cosmwasmconnection.ts +++ b/packages/bcp/src/cosmwasmconnection.ts @@ -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)), diff --git a/packages/bcp/src/decode.ts b/packages/bcp/src/decode.ts index 6fa4ead7..a1db9910 100644 --- a/packages/bcp/src/decode.ts +++ b/packages/bcp/src/decode.ts @@ -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) { diff --git a/packages/bcp/types/address.d.ts b/packages/bcp/types/address.d.ts index 252add6e..fd3a99c3 100644 --- a/packages/bcp/types/address.d.ts +++ b/packages/bcp/types/address.d.ts @@ -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; diff --git a/packages/sdk/src/index.ts b/packages/sdk/src/index.ts index e664c4a5..21c4f489 100644 --- a/packages/sdk/src/index.ts +++ b/packages/sdk/src/index.ts @@ -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"; diff --git a/packages/sdk/src/restclient.ts b/packages/sdk/src/restclient.ts index 3bd74f70..bd9e4eed 100644 --- a/packages/sdk/src/restclient.ts +++ b/packages/sdk/src/restclient.ts @@ -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; }; } diff --git a/packages/sdk/src/types.ts b/packages/sdk/src/types.ts index 899a6719..b05cd289 100644 --- a/packages/sdk/src/types.ts +++ b/packages/sdk/src/types.ts @@ -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; + readonly public_key: AccountPubKey; + readonly account_number: string; + readonly sequence: string; } export type AminoTx = Tx & { readonly value: StdTx }; diff --git a/packages/sdk/types/index.d.ts b/packages/sdk/types/index.d.ts index d85cdb7b..e96d3116 100644 --- a/packages/sdk/types/index.d.ts +++ b/packages/sdk/types/index.d.ts @@ -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"; diff --git a/packages/sdk/types/restclient.d.ts b/packages/sdk/types/restclient.d.ts index ace2f82b..d952d480 100644 --- a/packages/sdk/types/restclient.d.ts +++ b/packages/sdk/types/restclient.d.ts @@ -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 { diff --git a/packages/sdk/types/types.d.ts b/packages/sdk/types/types.d.ts index ee1adefa..26602e06 100644 --- a/packages/sdk/types/types.d.ts +++ b/packages/sdk/types/types.d.ts @@ -1,6 +1,6 @@ export interface Tx { - type: string; - value: any; + readonly type: string; + readonly value: any; } export interface StdTx { readonly msg: ReadonlyArray; @@ -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; + readonly public_key: AccountPubKey; + readonly account_number: string; + readonly sequence: string; } export declare type AminoTx = Tx & { readonly value: StdTx;