From 4f5d919c2c2fc4f1fb935d3f0edad3b750adbc34 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Mon, 22 Mar 2021 16:19:22 +0100 Subject: [PATCH] Create pubkeyToRawAddress and make it work for multisig accounts --- packages/amino/package.json | 1 + packages/amino/src/encoding.spec.ts | 50 ++++++++++++++++++++++++++++- packages/amino/src/encoding.ts | 25 +++++++++++++++ packages/amino/src/index.ts | 1 + packages/launchpad/src/address.ts | 28 +++------------- 5 files changed, 80 insertions(+), 25 deletions(-) diff --git a/packages/amino/package.json b/packages/amino/package.json index bc9cb37d..0df89f2b 100644 --- a/packages/amino/package.json +++ b/packages/amino/package.json @@ -40,6 +40,7 @@ "pack-web": "yarn build-or-skip && webpack --mode development --config webpack.web.config.js" }, "dependencies": { + "@cosmjs/crypto": "^0.25.0-alpha.0", "@cosmjs/encoding": "^0.25.0-alpha.0", "@cosmjs/utils": "^0.25.0-alpha.0" }, diff --git a/packages/amino/src/encoding.spec.ts b/packages/amino/src/encoding.spec.ts index 838ba881..5d915f67 100644 --- a/packages/amino/src/encoding.spec.ts +++ b/packages/amino/src/encoding.spec.ts @@ -1,4 +1,4 @@ -import { Bech32, fromBase64 } from "@cosmjs/encoding"; +import { Bech32, fromBase64, fromHex, toBase64 } from "@cosmjs/encoding"; import { decodeAminoPubkey, @@ -6,6 +6,7 @@ import { encodeAminoPubkey, encodeBech32Pubkey, encodeSecp256k1Pubkey, + pubkeyToRawAddress, } from "./encoding"; import { MultisigThresholdPubkey, Pubkey } from "./pubkeys"; @@ -218,4 +219,51 @@ 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", + // pubkey data: eb5ae98721038cb598ee54130d34f8e0818e7787aa06139a0e2d0026cadb662b55cf16859a67 + // address: wasm1jq59w7y34msq69g4w3zvq6d5h3stcajd8g62xm + // address data: 9028577891aee00d15157444c069b4bc60bc764d + ); + const test2 = decodeBech32Pubkey( + "wasmpub1addwnpepq2gx7x7e29kge5a4ycunytyqr0u8ynql5h583s8r9wdads9m3v8ks6y0nhc", + // pubkey data: eb5ae9872102906f1bd9516c8cd3b52639322c801bf8724c1fa5e878c0e32b9bd6c0bb8b0f68 + // address: wasm146e52j6zphxw8m67cz8860ad5uju892cqmawsg + // address data: aeb3454b420dcce3ef5ec08e7d3fada725c39558 + ); + const test3 = decodeBech32Pubkey( + "wasmpub1addwnpepq0xfx5vavxmgdkn0p6x0l9p3udttghu3qcldd7ql08wa3xy93qq0xuzvtxc", + // pubkey data: eb5ae9872103cc93519d61b686da6f0e8cff9431e356b45f91063ed6f81f79ddd898858800f3 + // address: wasm1a6uxr25mw8qg8zz3l2avsdjsveh4yg9sw7h5np + // address data: eeb861aa9b71c0838851fabac83650666f5220b0 + ); + + expect(pubkeyToRawAddress(test1)).toEqual(fromHex("9028577891aee00d15157444c069b4bc60bc764d")); + expect(pubkeyToRawAddress(test2)).toEqual(fromHex("aeb3454b420dcce3ef5ec08e7d3fada725c39558")); + expect(pubkeyToRawAddress(test3)).toEqual(fromHex("eeb861aa9b71c0838851fabac83650666f5220b0")); + }); + }); }); diff --git a/packages/amino/src/encoding.ts b/packages/amino/src/encoding.ts index a658f53e..571d516b 100644 --- a/packages/amino/src/encoding.ts +++ b/packages/amino/src/encoding.ts @@ -1,3 +1,4 @@ +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"; @@ -116,6 +117,30 @@ export function encodeAminoPubkey(pubkey: Pubkey): Uint8Array { } } +// 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); + if (pubkeyData.length !== 33) { + throw new Error(`Invalid Secp256k1 pubkey length (compressed): ${pubkeyData.length}`); + } + return ripemd160(sha256(pubkeyData)).slice(0, 20); + } 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"); + } +} + /** * 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 a5c0aa4e..803dbe19 100644 --- a/packages/amino/src/index.ts +++ b/packages/amino/src/index.ts @@ -4,6 +4,7 @@ export { encodeAminoPubkey, encodeBech32Pubkey, encodeSecp256k1Pubkey, + pubkeyToRawAddress, } from "./encoding"; export { MultisigThresholdPubkey, diff --git a/packages/launchpad/src/address.ts b/packages/launchpad/src/address.ts index adfc43a0..d31e6d52 100644 --- a/packages/launchpad/src/address.ts +++ b/packages/launchpad/src/address.ts @@ -1,6 +1,7 @@ -import { pubkeyType, SinglePubkey } from "@cosmjs/amino"; +import { SinglePubkey } from "@cosmjs/amino"; +import { pubkeyToRawAddress } from "@cosmjs/amino/build/encoding"; import { ripemd160, sha256 } from "@cosmjs/crypto"; -import { Bech32, fromBase64 } from "@cosmjs/encoding"; +import { Bech32 } from "@cosmjs/encoding"; export function rawSecp256k1PubkeyToAddress(pubkeyRaw: Uint8Array, prefix: string): string { if (pubkeyRaw.length !== 33) { @@ -14,26 +15,5 @@ export function rawSecp256k1PubkeyToAddress(pubkeyRaw: Uint8Array, prefix: strin // 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: SinglePubkey, prefix: string): string { - const pubkeyBytes = fromBase64(pubkey.value); - switch (pubkey.type) { - case pubkeyType.secp256k1: { - return rawSecp256k1PubkeyToAddress(pubkeyBytes, prefix); - } - case pubkeyType.ed25519: { - if (pubkeyBytes.length !== 32) { - throw new Error(`Invalid Ed25519 pubkey length: ${pubkeyBytes.length}`); - } - const hash = sha256(pubkeyBytes); - return Bech32.encode(prefix, hash.slice(0, 20)); - } - case pubkeyType.sr25519: { - if (pubkeyBytes.length !== 32) { - throw new Error(`Invalid Sr25519 pubkey length: ${pubkeyBytes.length}`); - } - const hash = sha256(pubkeyBytes); - return Bech32.encode(prefix, hash.slice(0, 20)); - } - default: - throw new Error("Unrecognized public key algorithm"); - } + return Bech32.encode(prefix, pubkeyToRawAddress(pubkey)); }