Update bcp address to reduce code dup

This commit is contained in:
Ethan Frey 2020-02-04 20:31:33 +01:00
parent 8807e7532d
commit a2c14bb80b
9 changed files with 47 additions and 118 deletions

View File

@ -1,33 +1,11 @@
import { Address, Algorithm, PubkeyBytes } from "@iov/bcp";
import { Encoding } from "@iov/encoding";
import { decodeCosmosAddress, decodeCosmosPubkey, isValidAddress, pubkeyToAddress } from "./address";
import { decodeCosmosPubkey, isValidAddress, pubkeyToAddress } from "./address";
const { fromBase64, fromHex } = Encoding;
describe("address", () => {
// Bech32 encoding/decoding data generated using https://github.com/bitcoinjs/bech32
describe("decodeCosmosAddress", () => {
it("throws for invalid prefix", () => {
expect(() =>
decodeCosmosAddress("cosmot10q82zkzzmaku5lazhsvxv7hsg4ntpuhd8j5266" as Address),
).toThrowError(/invalid bech32 prefix/i);
});
it("throws for invalid length", () => {
expect(() =>
decodeCosmosAddress("cosmos1alcmj76e030g83fedrnx8lvsythhg70zlct4cwx3" as Address),
).toThrowError(/invalid data length/i);
});
it("decodes valid addresses", () => {
expect(decodeCosmosAddress("cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6" as Address)).toEqual({
prefix: "cosmos",
data: fromHex("0d82b1e7c96dbfa42462fe612932e6bff111d51b"),
});
});
});
describe("decodeCosmosPubkey", () => {
it("works", () => {
expect(

View File

@ -1,88 +1,44 @@
import { CosmosBech32Prefix, decodeBech32Pubkey, encodeAddress, isValidAddress } from "@cosmwasm/sdk";
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";
import { Secp256k1 } from "@iov/crypto";
import { Encoding } from "@iov/encoding";
export type CosmosAddressBech32Prefix = "cosmos" | "cosmosvalcons" | "cosmosvaloper";
export type CosmosPubkeyBech32Prefix = "cosmospub" | "cosmosvalconspub" | "cosmosvaloperpub";
export type CosmosBech32Prefix = CosmosAddressBech32Prefix | CosmosPubkeyBech32Prefix;
export { CosmosBech32Prefix, isValidAddress };
// 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
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);
}
function isCosmosPubkeyBech32Prefix(prefix: string): prefix is CosmosPubkeyBech32Prefix {
return ["cosmospub", "cosmosvalconspub", "cosmosvaloperpub"].includes(prefix);
}
export function decodeCosmosAddress(
address: Address,
): { readonly prefix: CosmosAddressBech32Prefix; readonly data: Uint8Array } {
const { prefix, data } = Bech32.decode(address);
if (!isCosmosAddressBech32Prefix(prefix)) {
throw new Error(`Invalid bech32 prefix. Must be one of cosmos, cosmosvalcons, or cosmosvaloper.`);
}
if (data.length !== 20) {
throw new Error("Invalid data length. Expected 20 bytes.");
}
return { prefix: prefix, data: data };
}
const { fromBase64, toBase64 } = Encoding;
export function decodeCosmosPubkey(
encodedPubkey: string,
): { 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.`);
}
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));
}
}
export function isValidAddress(address: string): boolean {
try {
decodeCosmosAddress(address as Address);
return true;
} catch {
return false;
const sdkPubKey = decodeBech32Pubkey(encodedPubkey);
switch (sdkPubKey.type) {
case "tendermint/PubKeySecp256k1":
return { algo: Algorithm.Secp256k1, data: fromBase64(sdkPubKey.value) as PubkeyBytes };
case "tendermint/PubKeyEd25519":
return { algo: Algorithm.Ed25519, data: fromBase64(sdkPubKey.value) as PubkeyBytes };
default:
throw new Error("Unsupported Pubkey type: " + sdkPubKey.type);
}
}
// See https://github.com/tendermint/tendermint/blob/f2ada0a604b4c0763bda2f64fac53d506d3beca7/docs/spec/blockchain/encoding.md#public-key-cryptography
export function pubkeyToAddress(pubkey: PubkeyBundle, prefix: CosmosBech32Prefix): Address {
const pubkeyData =
pubkey.algo === Algorithm.Secp256k1 ? Secp256k1.compressPubkey(pubkey.data) : pubkey.data;
switch (pubkey.algo) {
case Algorithm.Secp256k1: {
const hash1 = new Sha256(pubkeyData).digest();
const hash2 = new Ripemd160(hash1).digest();
return Bech32.encode(prefix, hash2) as Address;
let pubkeyType: "tendermint/PubKeySecp256k1" | "tendermint/PubKeyEd25519";
let pubkeyData: Uint8Array = pubkey.data;
if (pubkey.algo === Algorithm.Secp256k1) {
pubkeyType = "tendermint/PubKeySecp256k1";
if (pubkeyData.length > 33) {
pubkeyData = Secp256k1.compressPubkey(pubkey.data);
}
case Algorithm.Ed25519: {
const hash = new Sha256(pubkeyData).digest();
return Bech32.encode(prefix, hash.slice(0, 20)) as Address;
}
default:
throw new Error("Unrecognized public key algorithm");
} else if (pubkey.algo === Algorithm.Ed25519) {
pubkeyType = "tendermint/PubKeyEd25519";
} else {
throw new Error(`Unsupported algorithm: ${pubkey.algo}`);
}
const sdkKey = {
type: pubkeyType,
value: toBase64(pubkeyData),
};
return encodeAddress(sdkKey, prefix) as Address;
}

View File

@ -1,5 +1,6 @@
/* eslint-disable @typescript-eslint/camelcase */
import { types } from "@cosmwasm/sdk";
import { StdTx } from "@cosmwasm/sdk/types/types";
import { Address, Algorithm, TokenTicker } from "@iov/bcp";
import { Encoding } from "@iov/encoding";
@ -62,7 +63,7 @@ describe("decode", () => {
describe("decodePubkey", () => {
it("works for secp256k1", () => {
const pubkey = {
const pubkey: types.PubKey = {
type: "tendermint/PubKeySecp256k1",
value: "AtQaCqFnshaZQp6rIkvAPyzThvCvXSDO+9AzbxVErqJP",
};
@ -70,7 +71,7 @@ describe("decode", () => {
});
it("works for ed25519", () => {
const pubkey = {
const pubkey: types.PubKey = {
type: "tendermint/PubKeyEd25519",
value: "s69CnMgLTpuRyEfecjws3mWssBrOICUx8C2O1DkKSto=",
};
@ -82,7 +83,7 @@ describe("decode", () => {
it("throws for unsupported types", () => {
// https://github.com/tendermint/tendermint/blob/v0.33.0/crypto/sr25519/codec.go#L12
const pubkey = {
const pubkey: types.PubKey = {
type: "tendermint/PubKeySr25519",
value: "N4FJNPE5r/Twz55kO1QEIxyaGF5/HTXH6WgLQJWsy1o=",
};
@ -100,7 +101,7 @@ describe("decode", () => {
describe("decodeFullSignature", () => {
it("works", () => {
const fullSignature = {
const fullSignature: types.StdSignature = {
pub_key: {
type: "tendermint/PubKeySecp256k1",
value: "AtQaCqFnshaZQp6rIkvAPyzThvCvXSDO+9AzbxVErqJP",
@ -157,7 +158,7 @@ describe("decode", () => {
describe("parseTx", () => {
it("works", () => {
expect(parseTx(data.tx.value, chainId, nonce, defaultTokens)).toEqual(signedTxJson);
expect(parseTx(data.tx.value as StdTx, chainId, nonce, defaultTokens)).toEqual(signedTxJson);
});
});
@ -168,7 +169,7 @@ describe("decode", () => {
height: "2823",
txhash: txId,
raw_log: '[{"msg_index":0,"success":true,"log":""}]',
tx: data.tx,
tx: data.tx as types.AminoTx,
};
const expected = {
...signedTxJson,

View File

@ -1,18 +1,10 @@
import { CosmosBech32Prefix, isValidAddress } from "@cosmwasm/sdk";
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;
export declare function decodeCosmosAddress(
address: Address,
): {
readonly prefix: CosmosAddressBech32Prefix;
readonly data: Uint8Array;
};
export { CosmosBech32Prefix, isValidAddress };
export declare function decodeCosmosPubkey(
encodedPubkey: string,
): {
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,6 +1,6 @@
import { Encoding } from "@iov/encoding";
import { decodeBech32Pubkey, isValidAddress, pubkeyToAddress } from "./address";
import { decodeBech32Pubkey, encodeAddress, isValidAddress } from "./address";
import { PubKeyEd25519, PubKeySecp256k1 } from "./types";
const { toBase64, fromHex } = Encoding;
@ -34,14 +34,14 @@ describe("address", () => {
});
});
describe("pubkeyToAddress", () => {
describe("encodeAddress", () => {
it("works for Secp256k1 compressed", () => {
const prefix = "cosmos";
const pubkey: PubKeySecp256k1 = {
type: "tendermint/PubKeySecp256k1",
value: "AtQaCqFnshaZQp6rIkvAPyzThvCvXSDO+9AzbxVErqJP",
};
expect(pubkeyToAddress(pubkey, prefix)).toEqual("cosmos1h806c7khnvmjlywdrkdgk2vrayy2mmvf9rxk2r");
expect(encodeAddress(pubkey, prefix)).toEqual("cosmos1h806c7khnvmjlywdrkdgk2vrayy2mmvf9rxk2r");
});
it("works for Ed25519", () => {
@ -50,7 +50,7 @@ describe("address", () => {
type: "tendermint/PubKeyEd25519",
value: toBase64(fromHex("12ee6f581fe55673a1e9e1382a0829e32075a0aa4763c968bc526e1852e78c95")),
};
expect(pubkeyToAddress(pubkey, prefix)).toEqual("cosmos1pfq05em6sfkls66ut4m2257p7qwlk448h8mysz");
expect(encodeAddress(pubkey, prefix)).toEqual("cosmos1pfq05em6sfkls66ut4m2257p7qwlk448h8mysz");
});
});
});

View File

@ -78,7 +78,7 @@ export function isValidAddress(address: string): boolean {
// See https://github.com/tendermint/tendermint/blob/f2ada0a604b4c0763bda2f64fac53d506d3beca7/docs/spec/blockchain/encoding.md#public-key-cryptography
// This assumes we already have a cosmos-compressed pubkey
export function pubkeyToAddress(pubkey: PubKey, prefix: string): string {
export function encodeAddress(pubkey: PubKey, prefix: string): string {
const pubkeyBytes = fromBase64(pubkey.value);
switch (pubkey.type) {
case "tendermint/PubKeySecp256k1": {

View File

@ -1,5 +1,6 @@
import * as types from "./types";
export { CosmosBech32Prefix, decodeBech32Pubkey, encodeAddress, isValidAddress } from "./address";
export { unmarshalTx } from "./decoding";
export { encodeSecp256k1Signature, makeSignBytes, marshalTx } from "./encoding";
export { RestClient, TxsResponse } from "./restclient";

View File

@ -4,4 +4,4 @@ export declare type CosmosPubkeyBech32Prefix = "cosmospub" | "cosmosvalconspub"
export declare type CosmosBech32Prefix = CosmosAddressBech32Prefix | CosmosPubkeyBech32Prefix;
export declare function decodeBech32Pubkey(bech: Bech32PubKey): PubKey;
export declare function isValidAddress(address: string): boolean;
export declare function pubkeyToAddress(pubkey: PubKey, prefix: string): string;
export declare function encodeAddress(pubkey: PubKey, prefix: string): string;

View File

@ -1,4 +1,5 @@
import * as types from "./types";
export { CosmosBech32Prefix, decodeBech32Pubkey, encodeAddress, isValidAddress } from "./address";
export { unmarshalTx } from "./decoding";
export { encodeSecp256k1Signature, makeSignBytes, marshalTx } from "./encoding";
export { RestClient, TxsResponse } from "./restclient";