diff --git a/packages/sdk/src/address.spec.ts b/packages/sdk/src/address.spec.ts index 7c73ebaa..80df0b7a 100644 --- a/packages/sdk/src/address.spec.ts +++ b/packages/sdk/src/address.spec.ts @@ -1,21 +1,10 @@ import { Encoding } from "@iov/encoding"; -import { decodeBech32Pubkey, encodeAddress, isValidAddress } from "./address"; +import { encodeAddress, isValidAddress } from "./address"; const { toBase64, fromHex } = Encoding; describe("address", () => { - describe("decodeBech32Pubkey", () => { - it("works", () => { - expect( - decodeBech32Pubkey("cosmospub1addwnpepqd8sgxq7aw348ydctp3n5ajufgxp395hksxjzc6565yfp56scupfqhlgyg5"), - ).toEqual({ - type: "tendermint/PubKeySecp256k1", - value: "A08EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQ", - }); - }); - }); - describe("isValidAddress", () => { it("accepts valid addresses", () => { expect(isValidAddress("cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6")).toEqual(true); diff --git a/packages/sdk/src/address.ts b/packages/sdk/src/address.ts index 9f553d5c..85df61c5 100644 --- a/packages/sdk/src/address.ts +++ b/packages/sdk/src/address.ts @@ -1,10 +1,9 @@ import { Ripemd160, Sha256 } from "@iov/crypto"; import { Bech32, Encoding } from "@iov/encoding"; -import equal from "fast-deep-equal"; -import { Bech32PubKey, PubKey, pubkeyType } from "./types"; +import { PubKey, pubkeyType } from "./types"; -const { fromBase64, toBase64 } = Encoding; +const { fromBase64 } = Encoding; // TODO: make this much more configurable export type CosmosAddressBech32Prefix = "cosmos" | "cosmosvalcons" | "cosmosvaloper"; @@ -15,55 +14,6 @@ function isCosmosAddressBech32Prefix(prefix: string): prefix is CosmosAddressBec return ["cosmos", "cosmosvalcons", "cosmosvaloper"].includes(prefix); } -function isCosmosPubkeyBech32Prefix(prefix: string): prefix is CosmosPubkeyBech32Prefix { - return ["cosmospub", "cosmosvalconspub", "cosmosvaloperpub"].includes(prefix); -} - -// As discussed in https://github.com/binance-chain/javascript-sdk/issues/163 -// Prefixes listed here: https://github.com/tendermint/tendermint/blob/d419fffe18531317c28c29a292ad7d253f6cafdf/docs/spec/blockchain/encoding.md#public-key-cryptography -// Last bytes is varint-encoded length prefix -const pubkeyAminoPrefixSecp256k1 = Encoding.fromHex("eb5ae98721"); -const pubkeyAminoPrefixEd25519 = Encoding.fromHex("1624de6420"); -const pubkeyAminoPrefixSr25519 = Encoding.fromHex("0dfb1005"); -const pubkeyAminoPrefixLength = pubkeyAminoPrefixSecp256k1.length; - -export function decodeBech32Pubkey(bech: Bech32PubKey): PubKey { - const { prefix, data } = Bech32.decode(bech); - if (!isCosmosPubkeyBech32Prefix(prefix)) { - throw new Error(`Invalid bech32 prefix. Must be one of cosmos, cosmosvalcons, or cosmosvaloper.`); - } - - 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 { - type: pubkeyType.secp256k1, - value: toBase64(rest), - }; - } else if (equal(aminoPrefix, pubkeyAminoPrefixEd25519)) { - if (rest.length !== 32) { - throw new Error("Invalid rest data length. Expected 32 bytes (Ed25519 pubkey)."); - } - return { - type: pubkeyType.ed25519, - value: toBase64(rest), - }; - } else if (equal(aminoPrefix, pubkeyAminoPrefixSr25519)) { - if (rest.length !== 32) { - throw new Error("Invalid rest data length. Expected 32 bytes (Sr25519 pubkey)."); - } - return { - type: pubkeyType.sr25519, - value: toBase64(rest), - }; - } else { - throw new Error("Unsupported Pubkey type. Amino prefix: " + Encoding.toHex(aminoPrefix)); - } -} - export function isValidAddress(address: string): boolean { try { const { prefix, data } = Bech32.decode(address); diff --git a/packages/sdk/src/index.ts b/packages/sdk/src/index.ts index 1908af35..cbe754e4 100644 --- a/packages/sdk/src/index.ts +++ b/packages/sdk/src/index.ts @@ -2,9 +2,9 @@ import * as logs from "./logs"; import * as types from "./types"; export { logs, types }; -export { CosmosBech32Prefix, decodeBech32Pubkey, encodeAddress, isValidAddress } from "./address"; +export { CosmosBech32Prefix, encodeAddress, isValidAddress } from "./address"; export { unmarshalTx } from "./decoding"; export { encodeSecp256k1Signature, makeSignBytes, marshalTx } from "./encoding"; export { RestClient, TxsResponse } from "./restclient"; export { makeCosmoshubPath, Pen, PrehashType, Secp256k1Pen } from "./pen"; -export { encodeSecp256k1Pubkey } from "./pubkey"; +export { decodeBech32Pubkey, encodeSecp256k1Pubkey } from "./pubkey"; diff --git a/packages/sdk/src/pubkey.spec.ts b/packages/sdk/src/pubkey.spec.ts index 0b700496..2f0c6209 100644 --- a/packages/sdk/src/pubkey.spec.ts +++ b/packages/sdk/src/pubkey.spec.ts @@ -1,6 +1,6 @@ import { Encoding } from "@iov/encoding"; -import { encodeSecp256k1Pubkey } from "./pubkey"; +import { decodeBech32Pubkey, encodeSecp256k1Pubkey } from "./pubkey"; const { fromBase64 } = Encoding; @@ -24,4 +24,15 @@ describe("pubkey", () => { }); }); }); + + describe("decodeBech32Pubkey", () => { + it("works", () => { + expect( + decodeBech32Pubkey("cosmospub1addwnpepqd8sgxq7aw348ydctp3n5ajufgxp395hksxjzc6565yfp56scupfqhlgyg5"), + ).toEqual({ + type: "tendermint/PubKeySecp256k1", + value: "A08EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQ", + }); + }); + }); }); diff --git a/packages/sdk/src/pubkey.ts b/packages/sdk/src/pubkey.ts index 73b10695..23552860 100644 --- a/packages/sdk/src/pubkey.ts +++ b/packages/sdk/src/pubkey.ts @@ -1,7 +1,9 @@ import { Secp256k1 } from "@iov/crypto"; -import { Encoding } from "@iov/encoding"; +import { Bech32, Encoding } from "@iov/encoding"; +import equal from "fast-deep-equal"; -import { PubKey, pubkeyType } from "./types"; +import { CosmosPubkeyBech32Prefix } from "./address"; +import { Bech32PubKey, PubKey, pubkeyType } from "./types"; export function encodeSecp256k1Pubkey(pubkey: Uint8Array): PubKey { return { @@ -9,3 +11,52 @@ export function encodeSecp256k1Pubkey(pubkey: Uint8Array): PubKey { value: Encoding.toBase64(Secp256k1.compressPubkey(pubkey)), }; } + +function isCosmosPubkeyBech32Prefix(prefix: string): prefix is CosmosPubkeyBech32Prefix { + return ["cosmospub", "cosmosvalconspub", "cosmosvaloperpub"].includes(prefix); +} + +// As discussed in https://github.com/binance-chain/javascript-sdk/issues/163 +// Prefixes listed here: https://github.com/tendermint/tendermint/blob/d419fffe18531317c28c29a292ad7d253f6cafdf/docs/spec/blockchain/encoding.md#public-key-cryptography +// Last bytes is varint-encoded length prefix +const pubkeyAminoPrefixSecp256k1 = Encoding.fromHex("eb5ae98721"); +const pubkeyAminoPrefixEd25519 = Encoding.fromHex("1624de6420"); +const pubkeyAminoPrefixSr25519 = Encoding.fromHex("0dfb1005"); +const pubkeyAminoPrefixLength = pubkeyAminoPrefixSecp256k1.length; + +export function decodeBech32Pubkey(bech: Bech32PubKey): PubKey { + const { prefix, data } = Bech32.decode(bech); + if (!isCosmosPubkeyBech32Prefix(prefix)) { + throw new Error(`Invalid bech32 prefix. Must be one of cosmos, cosmosvalcons, or cosmosvaloper.`); + } + + 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 { + type: pubkeyType.secp256k1, + value: Encoding.toBase64(rest), + }; + } else if (equal(aminoPrefix, pubkeyAminoPrefixEd25519)) { + if (rest.length !== 32) { + throw new Error("Invalid rest data length. Expected 32 bytes (Ed25519 pubkey)."); + } + return { + type: pubkeyType.ed25519, + value: Encoding.toBase64(rest), + }; + } else if (equal(aminoPrefix, pubkeyAminoPrefixSr25519)) { + if (rest.length !== 32) { + throw new Error("Invalid rest data length. Expected 32 bytes (Sr25519 pubkey)."); + } + return { + type: pubkeyType.sr25519, + value: Encoding.toBase64(rest), + }; + } else { + throw new Error("Unsupported Pubkey type. Amino prefix: " + Encoding.toHex(aminoPrefix)); + } +} diff --git a/packages/sdk/types/address.d.ts b/packages/sdk/types/address.d.ts index 4fddadd5..3390fc29 100644 --- a/packages/sdk/types/address.d.ts +++ b/packages/sdk/types/address.d.ts @@ -1,7 +1,6 @@ -import { Bech32PubKey, PubKey } from "./types"; +import { PubKey } from "./types"; export declare type CosmosAddressBech32Prefix = "cosmos" | "cosmosvalcons" | "cosmosvaloper"; export declare type CosmosPubkeyBech32Prefix = "cosmospub" | "cosmosvalconspub" | "cosmosvaloperpub"; export declare type CosmosBech32Prefix = CosmosAddressBech32Prefix | CosmosPubkeyBech32Prefix; -export declare function decodeBech32Pubkey(bech: Bech32PubKey): PubKey; export declare function isValidAddress(address: string): boolean; export declare function encodeAddress(pubkey: PubKey, prefix: string): string; diff --git a/packages/sdk/types/index.d.ts b/packages/sdk/types/index.d.ts index abe78f64..0aaeec16 100644 --- a/packages/sdk/types/index.d.ts +++ b/packages/sdk/types/index.d.ts @@ -1,9 +1,9 @@ import * as logs from "./logs"; import * as types from "./types"; export { logs, types }; -export { CosmosBech32Prefix, decodeBech32Pubkey, encodeAddress, isValidAddress } from "./address"; +export { CosmosBech32Prefix, encodeAddress, isValidAddress } from "./address"; export { unmarshalTx } from "./decoding"; export { encodeSecp256k1Signature, makeSignBytes, marshalTx } from "./encoding"; export { RestClient, TxsResponse } from "./restclient"; export { makeCosmoshubPath, Pen, PrehashType, Secp256k1Pen } from "./pen"; -export { encodeSecp256k1Pubkey } from "./pubkey"; +export { decodeBech32Pubkey, encodeSecp256k1Pubkey } from "./pubkey"; diff --git a/packages/sdk/types/pubkey.d.ts b/packages/sdk/types/pubkey.d.ts index 72188ff7..3d7075df 100644 --- a/packages/sdk/types/pubkey.d.ts +++ b/packages/sdk/types/pubkey.d.ts @@ -1,2 +1,3 @@ -import { PubKey } from "./types"; +import { Bech32PubKey, PubKey } from "./types"; export declare function encodeSecp256k1Pubkey(pubkey: Uint8Array): PubKey; +export declare function decodeBech32Pubkey(bech: Bech32PubKey): PubKey;