From 99e1ac36b85385d7c7fbd88ab39bc7698dd723f5 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Mon, 22 Mar 2021 13:02:29 +0100 Subject: [PATCH] Add support for multisig in encodeAminoPubkey --- packages/amino/src/encoding.spec.ts | 47 ++++++++++++++++++++++++++++- packages/amino/src/encoding.ts | 23 +++++++++++++- packages/amino/src/pubkeys.ts | 1 + 3 files changed, 69 insertions(+), 2 deletions(-) diff --git a/packages/amino/src/encoding.spec.ts b/packages/amino/src/encoding.spec.ts index 5fd43919..7fa4a203 100644 --- a/packages/amino/src/encoding.spec.ts +++ b/packages/amino/src/encoding.spec.ts @@ -7,7 +7,7 @@ import { encodeBech32Pubkey, encodeSecp256k1Pubkey, } from "./encoding"; -import { Pubkey } from "./pubkeys"; +import { MultisigThresholdPubkey, Pubkey } from "./pubkeys"; describe("encoding", () => { describe("encodeSecp256k1Pubkey", () => { @@ -135,5 +135,50 @@ describe("encoding", () => { "coralvalconspub1zcjduepqvxg72ccnl9r65fv0wn3amlk4sfzqfe2k36l073kjx2qyaf6sk23qw7j8wq", ); }); + + it("works for multisig", () => { + // ./build/wasmd keys add test1 + // ./build/wasmd keys add test2 + // ./build/wasmd keys add test3 + // ./build/wasmd keys add testgroup1 --multisig=test1,test2,test3 --multisig-threshold 2 + // ./build/wasmd keys add testgroup2 --multisig=test1,test2,test3 --multisig-threshold 1 + // ./build/wasmd keys add testgroup3 --multisig=test3,test1 --multisig-threshold 2 + + const test1 = decodeBech32Pubkey( + "wasmpub1addwnpepqwxttx8w2sfs6d8cuzqcuau84grp8xsw95qzdjkmvc44tnckskdxw3zw2km", + ); + const test2 = decodeBech32Pubkey( + "wasmpub1addwnpepq2gx7x7e29kge5a4ycunytyqr0u8ynql5h583s8r9wdads9m3v8ks6y0nhc", + ); + const test3 = decodeBech32Pubkey( + "wasmpub1addwnpepq0xfx5vavxmgdkn0p6x0l9p3udttghu3qcldd7ql08wa3xy93qq0xuzvtxc", + ); + + // 2/3 multisig + const testgroup1: MultisigThresholdPubkey = { + type: "tendermint/PubKeyMultisigThreshold", + value: { + threshold: "2", + pubkeys: [test1, test2, test3], + }, + }; + const expected1 = Bech32.decode( + "wasmpub1ytql0csgqgfzd666axrjzquvkkvwu4qnp5603cyp3emc02sxzwdqutgqym9dke3t2h83dpv6vufzd666axrjzq5sdudaj5tv3nfm2f3exgkgqxlcwfxplf0g0rqwx2um6mqthzc0dqfzd666axrjzq7vjdge6cdksmdx7r5vl72rrc6kk30ezp376mup77wamzvgtzqq7v7aysdd", + ).data; + expect(encodeAminoPubkey(testgroup1)).toEqual(expected1); + + // 1/3 multisig + const testgroup2: MultisigThresholdPubkey = { + type: "tendermint/PubKeyMultisigThreshold", + value: { + threshold: "1", + pubkeys: [test1, test2, test3], + }, + }; + const expected2 = Bech32.decode( + "wasmpub1ytql0csgqyfzd666axrjzquvkkvwu4qnp5603cyp3emc02sxzwdqutgqym9dke3t2h83dpv6vufzd666axrjzq5sdudaj5tv3nfm2f3exgkgqxlcwfxplf0g0rqwx2um6mqthzc0dqfzd666axrjzq7vjdge6cdksmdx7r5vl72rrc6kk30ezp376mup77wamzvgtzqq7vc4ejke", + ).data; + expect(encodeAminoPubkey(testgroup2)).toEqual(expected2); + }); }); }); diff --git a/packages/amino/src/encoding.ts b/packages/amino/src/encoding.ts index 5acbc8f9..689a67cd 100644 --- a/packages/amino/src/encoding.ts +++ b/packages/amino/src/encoding.ts @@ -1,7 +1,8 @@ import { Bech32, fromBase64, fromHex, toBase64, toHex } from "@cosmjs/encoding"; +import { Uint53 } from "@cosmjs/math"; import { arrayContentStartsWith } from "@cosmjs/utils"; -import { Pubkey, pubkeyType, Secp256k1Pubkey } from "./pubkeys"; +import { isMultisigThresholdPubkey, Pubkey, pubkeyType, Secp256k1Pubkey } from "./pubkeys"; export function encodeSecp256k1Pubkey(pubkey: Uint8Array): Secp256k1Pubkey { if (pubkey.length !== 33 || (pubkey[0] !== 0x02 && pubkey[0] !== 0x03)) { @@ -19,6 +20,8 @@ export function encodeSecp256k1Pubkey(pubkey: Uint8Array): Secp256k1Pubkey { const pubkeyAminoPrefixSecp256k1 = fromHex("eb5ae987" + "21" /* fixed length */); const pubkeyAminoPrefixEd25519 = fromHex("1624de64" + "20" /* fixed length */); const pubkeyAminoPrefixSr25519 = fromHex("0dfb1005" + "20" /* fixed length */); +/** See https://github.com/tendermint/tendermint/commit/38b401657e4ad7a7eeb3c30a3cbf512037df3740 */ +const pubkeyAminoPrefixMultisigThreshold = fromHex("22c1f7e2" /* variable length not included */); /** * Decodes a pubkey in the Amino binary format to a type/value object. @@ -67,10 +70,28 @@ export function decodeBech32Pubkey(bechEncoded: string): Pubkey { return decodeAminoPubkey(data); } +function encodeSmallUint(value: number | string): number[] { + const checked = Uint53.fromString(value.toString()).toNumber(); + if (checked > 127) throw new Error("Encoding numbers > 127 is not supported here."); + return [checked]; +} + /** * Encodes a public key to binary Amino. */ export function encodeAminoPubkey(pubkey: Pubkey): Uint8Array { + if (isMultisigThresholdPubkey(pubkey)) { + const out = Array.from(pubkeyAminoPrefixMultisigThreshold); + out.push(8); // TODO: What is this? + out.push(...encodeSmallUint(pubkey.value.threshold)); + for (const pubkeyData of pubkey.value.pubkeys.map((p) => encodeAminoPubkey(p))) { + out.push(18); // TODO: What is this? + out.push(...encodeSmallUint(pubkeyData.length)); + out.push(...pubkeyData); + } + return new Uint8Array(out); + } + let aminoPrefix: Uint8Array; switch (pubkey.type) { // Note: please don't add cases here without writing additional unit tests diff --git a/packages/amino/src/pubkeys.ts b/packages/amino/src/pubkeys.ts index 90da0a79..215d0016 100644 --- a/packages/amino/src/pubkeys.ts +++ b/packages/amino/src/pubkeys.ts @@ -18,6 +18,7 @@ export const pubkeyType = { ed25519: "tendermint/PubKeyEd25519" as const, /** @see https://github.com/tendermint/tendermint/blob/v0.33.0/crypto/sr25519/codec.go#L12 */ sr25519: "tendermint/PubKeySr25519" as const, + multisigThreshold: "tendermint/PubKeyMultisigThreshold" as const, }; /**