diff --git a/packages/amino/src/addresses.spec.ts b/packages/amino/src/addresses.spec.ts new file mode 100644 index 00000000..de90b7b9 --- /dev/null +++ b/packages/amino/src/addresses.spec.ts @@ -0,0 +1,91 @@ +import { Bech32, fromHex, toBase64 } from "@cosmjs/encoding"; + +import { pubkeyToAddress, pubkeyToRawAddress } from "./addresses"; +import { decodeBech32Pubkey } from "./encoding"; +import { MultisigThresholdPubkey } from "./pubkeys"; + +describe("addresses", () => { + describe("pubkeyToRawAddress", () => { + it("works for Secp256k1", () => { + const pubkey = { + type: "tendermint/PubKeySecp256k1", + value: "AtQaCqFnshaZQp6rIkvAPyzThvCvXSDO+9AzbxVErqJP", + }; + expect(pubkeyToRawAddress(pubkey)).toEqual( + Bech32.decode("cosmos1h806c7khnvmjlywdrkdgk2vrayy2mmvf9rxk2r").data, + ); + }); + + it("works for Ed25519", () => { + const pubkey = { + type: "tendermint/PubKeyEd25519", + value: toBase64(fromHex("12ee6f581fe55673a1e9e1382a0829e32075a0aa4763c968bc526e1852e78c95")), + }; + expect(pubkeyToRawAddress(pubkey)).toEqual( + Bech32.decode("cosmos1pfq05em6sfkls66ut4m2257p7qwlk448h8mysz").data, + ); + }); + + it("works for multisig", () => { + const test1 = decodeBech32Pubkey( + "wasmpub1addwnpepqwxttx8w2sfs6d8cuzqcuau84grp8xsw95qzdjkmvc44tnckskdxw3zw2km", + ); + const test2 = decodeBech32Pubkey( + "wasmpub1addwnpepq2gx7x7e29kge5a4ycunytyqr0u8ynql5h583s8r9wdads9m3v8ks6y0nhc", + ); + const test3 = decodeBech32Pubkey( + "wasmpub1addwnpepq0xfx5vavxmgdkn0p6x0l9p3udttghu3qcldd7ql08wa3xy93qq0xuzvtxc", + ); + + const testgroup1: MultisigThresholdPubkey = { + type: "tendermint/PubKeyMultisigThreshold", + value: { + threshold: "2", + pubkeys: [test1, test2, test3], + }, + }; + expect(pubkeyToRawAddress(testgroup1)).toEqual(fromHex("0892a77fab2fa7e192c3b7b2741e6682f3abb72f")); + }); + }); + + describe("pubkeyToAddress", () => { + it("works for Secp256k1", () => { + const prefix = "cosmos"; + const pubkey = { + type: "tendermint/PubKeySecp256k1", + value: "AtQaCqFnshaZQp6rIkvAPyzThvCvXSDO+9AzbxVErqJP", + }; + expect(pubkeyToAddress(pubkey, prefix)).toEqual("cosmos1h806c7khnvmjlywdrkdgk2vrayy2mmvf9rxk2r"); + }); + + it("works for Ed25519", () => { + const prefix = "cosmos"; + const pubkey = { + type: "tendermint/PubKeyEd25519", + value: toBase64(fromHex("12ee6f581fe55673a1e9e1382a0829e32075a0aa4763c968bc526e1852e78c95")), + }; + expect(pubkeyToAddress(pubkey, prefix)).toEqual("cosmos1pfq05em6sfkls66ut4m2257p7qwlk448h8mysz"); + }); + + it("works for multisig", () => { + const test1 = decodeBech32Pubkey( + "wasmpub1addwnpepqwxttx8w2sfs6d8cuzqcuau84grp8xsw95qzdjkmvc44tnckskdxw3zw2km", + ); + const test2 = decodeBech32Pubkey( + "wasmpub1addwnpepq2gx7x7e29kge5a4ycunytyqr0u8ynql5h583s8r9wdads9m3v8ks6y0nhc", + ); + const test3 = decodeBech32Pubkey( + "wasmpub1addwnpepq0xfx5vavxmgdkn0p6x0l9p3udttghu3qcldd7ql08wa3xy93qq0xuzvtxc", + ); + + const testgroup1: MultisigThresholdPubkey = { + type: "tendermint/PubKeyMultisigThreshold", + value: { + threshold: "2", + pubkeys: [test1, test2, test3], + }, + }; + expect(pubkeyToAddress(testgroup1, "wasm")).toEqual("wasm1pzf2wlat97n7rykrk7e8g8nxste6hde0r8jqsy"); + }); + }); +}); diff --git a/packages/amino/src/addresses.ts b/packages/amino/src/addresses.ts new file mode 100644 index 00000000..133f8f8b --- /dev/null +++ b/packages/amino/src/addresses.ts @@ -0,0 +1,38 @@ +// See https://github.com/tendermint/tendermint/blob/f2ada0a604b4c0763bda2f64fac53d506d3beca7/docs/spec/blockchain/encoding.md#public-key-cryptography + +import { ripemd160, sha256 } from "@cosmjs/crypto"; +import { Bech32, fromBase64 } from "@cosmjs/encoding"; + +import { encodeAminoPubkey } from "./encoding"; +import { isEd25519Pubkey, isMultisigThresholdPubkey, isSecp256k1Pubkey, Pubkey } from "./pubkeys"; + +export function rawSecp256k1PubkeyToRawAddress(pubkeyData: Uint8Array): Uint8Array { + if (pubkeyData.length !== 33) { + throw new Error(`Invalid Secp256k1 pubkey length (compressed): ${pubkeyData.length}`); + } + return ripemd160(sha256(pubkeyData)); +} + +// For secp256k1 this assumes we already have a compressed pubkey. +export function pubkeyToRawAddress(pubkey: Pubkey): Uint8Array { + if (isSecp256k1Pubkey(pubkey)) { + const pubkeyData = fromBase64(pubkey.value); + return rawSecp256k1PubkeyToRawAddress(pubkeyData); + } else if (isEd25519Pubkey(pubkey)) { + const pubkeyData = fromBase64(pubkey.value); + if (pubkeyData.length !== 32) { + throw new Error(`Invalid Ed25519 pubkey length: ${pubkeyData.length}`); + } + return sha256(pubkeyData).slice(0, 20); + } else if (isMultisigThresholdPubkey(pubkey)) { + // https://github.com/tendermint/tendermint/blob/38b401657e4ad7a7eeb3c30a3cbf512037df3740/crypto/multisig/threshold_pubkey.go#L71-L74 + const pubkeyData = encodeAminoPubkey(pubkey); + return sha256(pubkeyData).slice(0, 20); + } else { + throw new Error("Unsupported public key type"); + } +} + +export function pubkeyToAddress(pubkey: Pubkey, prefix: string): string { + return Bech32.encode(prefix, pubkeyToRawAddress(pubkey)); +} diff --git a/packages/amino/src/encoding.spec.ts b/packages/amino/src/encoding.spec.ts index 1389aea1..838ba881 100644 --- a/packages/amino/src/encoding.spec.ts +++ b/packages/amino/src/encoding.spec.ts @@ -1,4 +1,4 @@ -import { Bech32, fromBase64, fromHex, toBase64 } from "@cosmjs/encoding"; +import { Bech32, fromBase64 } from "@cosmjs/encoding"; import { decodeAminoPubkey, @@ -6,8 +6,6 @@ import { encodeAminoPubkey, encodeBech32Pubkey, encodeSecp256k1Pubkey, - pubkeyToAddress, - pubkeyToRawAddress, } from "./encoding"; import { MultisigThresholdPubkey, Pubkey } from "./pubkeys"; @@ -220,88 +218,4 @@ describe("encoding", () => { expect(encodeAminoPubkey(testgroup4)).toEqual(expected4); }); }); - - describe("pubkeyToRawAddress", () => { - it("works for Secp256k1", () => { - const pubkey = { - type: "tendermint/PubKeySecp256k1", - value: "AtQaCqFnshaZQp6rIkvAPyzThvCvXSDO+9AzbxVErqJP", - }; - expect(pubkeyToRawAddress(pubkey)).toEqual( - Bech32.decode("cosmos1h806c7khnvmjlywdrkdgk2vrayy2mmvf9rxk2r").data, - ); - }); - - it("works for Ed25519", () => { - const pubkey = { - type: "tendermint/PubKeyEd25519", - value: toBase64(fromHex("12ee6f581fe55673a1e9e1382a0829e32075a0aa4763c968bc526e1852e78c95")), - }; - expect(pubkeyToRawAddress(pubkey)).toEqual( - Bech32.decode("cosmos1pfq05em6sfkls66ut4m2257p7qwlk448h8mysz").data, - ); - }); - - it("works for multisig", () => { - const test1 = decodeBech32Pubkey( - "wasmpub1addwnpepqwxttx8w2sfs6d8cuzqcuau84grp8xsw95qzdjkmvc44tnckskdxw3zw2km", - ); - const test2 = decodeBech32Pubkey( - "wasmpub1addwnpepq2gx7x7e29kge5a4ycunytyqr0u8ynql5h583s8r9wdads9m3v8ks6y0nhc", - ); - const test3 = decodeBech32Pubkey( - "wasmpub1addwnpepq0xfx5vavxmgdkn0p6x0l9p3udttghu3qcldd7ql08wa3xy93qq0xuzvtxc", - ); - - const testgroup1: MultisigThresholdPubkey = { - type: "tendermint/PubKeyMultisigThreshold", - value: { - threshold: "2", - pubkeys: [test1, test2, test3], - }, - }; - expect(pubkeyToRawAddress(testgroup1)).toEqual(fromHex("0892a77fab2fa7e192c3b7b2741e6682f3abb72f")); - }); - }); - - describe("pubkeyToAddress", () => { - it("works for Secp256k1", () => { - const prefix = "cosmos"; - const pubkey = { - type: "tendermint/PubKeySecp256k1", - value: "AtQaCqFnshaZQp6rIkvAPyzThvCvXSDO+9AzbxVErqJP", - }; - expect(pubkeyToAddress(pubkey, prefix)).toEqual("cosmos1h806c7khnvmjlywdrkdgk2vrayy2mmvf9rxk2r"); - }); - - it("works for Ed25519", () => { - const prefix = "cosmos"; - const pubkey = { - type: "tendermint/PubKeyEd25519", - value: toBase64(fromHex("12ee6f581fe55673a1e9e1382a0829e32075a0aa4763c968bc526e1852e78c95")), - }; - expect(pubkeyToAddress(pubkey, prefix)).toEqual("cosmos1pfq05em6sfkls66ut4m2257p7qwlk448h8mysz"); - }); - - it("works for multisig", () => { - const test1 = decodeBech32Pubkey( - "wasmpub1addwnpepqwxttx8w2sfs6d8cuzqcuau84grp8xsw95qzdjkmvc44tnckskdxw3zw2km", - ); - const test2 = decodeBech32Pubkey( - "wasmpub1addwnpepq2gx7x7e29kge5a4ycunytyqr0u8ynql5h583s8r9wdads9m3v8ks6y0nhc", - ); - const test3 = decodeBech32Pubkey( - "wasmpub1addwnpepq0xfx5vavxmgdkn0p6x0l9p3udttghu3qcldd7ql08wa3xy93qq0xuzvtxc", - ); - - const testgroup1: MultisigThresholdPubkey = { - type: "tendermint/PubKeyMultisigThreshold", - value: { - threshold: "2", - pubkeys: [test1, test2, test3], - }, - }; - expect(pubkeyToAddress(testgroup1, "wasm")).toEqual("wasm1pzf2wlat97n7rykrk7e8g8nxste6hde0r8jqsy"); - }); - }); }); diff --git a/packages/amino/src/encoding.ts b/packages/amino/src/encoding.ts index be04dfec..a658f53e 100644 --- a/packages/amino/src/encoding.ts +++ b/packages/amino/src/encoding.ts @@ -1,4 +1,3 @@ -import { ripemd160, sha256 } from "@cosmjs/crypto"; import { Bech32, fromBase64, fromHex, toBase64, toHex } from "@cosmjs/encoding"; import { Uint53 } from "@cosmjs/math"; import { arrayContentStartsWith } from "@cosmjs/utils"; @@ -117,38 +116,6 @@ export function encodeAminoPubkey(pubkey: Pubkey): Uint8Array { } } -export function rawSecp256k1PubkeyToRawAddress(pubkeyData: Uint8Array): Uint8Array { - if (pubkeyData.length !== 33) { - throw new Error(`Invalid Secp256k1 pubkey length (compressed): ${pubkeyData.length}`); - } - return ripemd160(sha256(pubkeyData)); -} - -// See https://github.com/tendermint/tendermint/blob/f2ada0a604b4c0763bda2f64fac53d506d3beca7/docs/spec/blockchain/encoding.md#public-key-cryptography -// For secp256k1 this assumes we already have a compressed pubkey. -export function pubkeyToRawAddress(pubkey: Pubkey): Uint8Array { - if (isSecp256k1Pubkey(pubkey)) { - const pubkeyData = fromBase64(pubkey.value); - return rawSecp256k1PubkeyToRawAddress(pubkeyData); - } else if (isEd25519Pubkey(pubkey)) { - const pubkeyData = fromBase64(pubkey.value); - if (pubkeyData.length !== 32) { - throw new Error(`Invalid Ed25519 pubkey length: ${pubkeyData.length}`); - } - return sha256(pubkeyData).slice(0, 20); - } else if (isMultisigThresholdPubkey(pubkey)) { - // https://github.com/tendermint/tendermint/blob/38b401657e4ad7a7eeb3c30a3cbf512037df3740/crypto/multisig/threshold_pubkey.go#L71-L74 - const pubkeyData = encodeAminoPubkey(pubkey); - return sha256(pubkeyData).slice(0, 20); - } else { - throw new Error("Unsupported public key type"); - } -} - -export function pubkeyToAddress(pubkey: Pubkey, prefix: string): string { - return Bech32.encode(prefix, pubkeyToRawAddress(pubkey)); -} - /** * Encodes a public key to binary Amino and then to bech32. * diff --git a/packages/amino/src/index.ts b/packages/amino/src/index.ts index 1508bc1e..cb2ae3bf 100644 --- a/packages/amino/src/index.ts +++ b/packages/amino/src/index.ts @@ -1,12 +1,10 @@ +export { pubkeyToAddress, pubkeyToRawAddress, rawSecp256k1PubkeyToRawAddress } from "./addresses"; export { decodeAminoPubkey, decodeBech32Pubkey, encodeAminoPubkey, encodeBech32Pubkey, encodeSecp256k1Pubkey, - pubkeyToAddress, - pubkeyToRawAddress, - rawSecp256k1PubkeyToRawAddress, } from "./encoding"; export { MultisigThresholdPubkey,