From 5b8d3637cee5bf0a2b155113ded3d844e721c5f7 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Wed, 24 Mar 2021 10:36:41 +0100 Subject: [PATCH] Move makeMultisignedTx into separate module --- packages/stargate/src/multisignature.spec.ts | 102 ++++++++++++ packages/stargate/src/multisignature.ts | 63 +++++++ .../signingstargateclient.multisig.spec.ts | 157 ------------------ 3 files changed, 165 insertions(+), 157 deletions(-) create mode 100644 packages/stargate/src/multisignature.spec.ts create mode 100644 packages/stargate/src/multisignature.ts delete mode 100644 packages/stargate/src/signingstargateclient.multisig.spec.ts diff --git a/packages/stargate/src/multisignature.spec.ts b/packages/stargate/src/multisignature.spec.ts new file mode 100644 index 00000000..5805a95b --- /dev/null +++ b/packages/stargate/src/multisignature.spec.ts @@ -0,0 +1,102 @@ +import { createMultisigThresholdPubkey, encodeSecp256k1Pubkey, pubkeyToAddress } from "@cosmjs/amino"; +import { coins, makeCosmoshubPath, Secp256k1HdWallet } from "@cosmjs/launchpad"; +import { assert } from "@cosmjs/utils"; + +import { MsgSend } from "./codec/cosmos/bank/v1beta1/tx"; +import { TxRaw } from "./codec/cosmos/tx/v1beta1/tx"; +import { makeMultisignedTx } from "./multisignature"; +import { SignerData, SigningStargateClient } from "./signingstargateclient"; +import { assertIsBroadcastTxSuccess } from "./stargateclient"; +import { faucet, pendingWithoutSimapp, simapp } from "./testutils.spec"; + +describe("multisignature", () => { + describe("makeMultisignedTx", () => { + it("works", async () => { + pendingWithoutSimapp(); + const wallet0 = await Secp256k1HdWallet.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(0)); + const wallet1 = await Secp256k1HdWallet.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(1)); + const wallet2 = await Secp256k1HdWallet.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(2)); + const wallet3 = await Secp256k1HdWallet.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(3)); + const wallet4 = await Secp256k1HdWallet.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(4)); + const pubkey0 = encodeSecp256k1Pubkey((await wallet0.getAccounts())[0].pubkey); + const pubkey1 = encodeSecp256k1Pubkey((await wallet1.getAccounts())[0].pubkey); + const pubkey2 = encodeSecp256k1Pubkey((await wallet2.getAccounts())[0].pubkey); + const pubkey3 = encodeSecp256k1Pubkey((await wallet3.getAccounts())[0].pubkey); + const pubkey4 = encodeSecp256k1Pubkey((await wallet4.getAccounts())[0].pubkey); + const address0 = (await wallet0.getAccounts())[0].address; + const address1 = (await wallet1.getAccounts())[0].address; + const address2 = (await wallet2.getAccounts())[0].address; + const address3 = (await wallet3.getAccounts())[0].address; + const address4 = (await wallet4.getAccounts())[0].address; + const multisigPubkey = createMultisigThresholdPubkey([pubkey0, pubkey1, pubkey2, pubkey3, pubkey4], 2); + const multisigAddress = pubkeyToAddress(multisigPubkey, "cosmos"); + expect(multisigAddress).toEqual("cosmos1h90ml36rcu7yegwduzgzderj2jmq49hcpfclw9"); + + const client0 = await SigningStargateClient.connectWithSigner(simapp.tendermintUrl, wallet0); + const client1 = await SigningStargateClient.connectWithSigner(simapp.tendermintUrl, wallet1); + const client2 = await SigningStargateClient.connectWithSigner(simapp.tendermintUrl, wallet2); + const client3 = await SigningStargateClient.connectWithSigner(simapp.tendermintUrl, wallet3); + const client4 = await SigningStargateClient.connectWithSigner(simapp.tendermintUrl, wallet4); + + const msgSend: MsgSend = { + fromAddress: multisigAddress, + toAddress: "cosmos19rvl6ja9h0erq9dc2xxfdzypc739ej8k5esnhg", + amount: coins(1234, "ucosm"), + }; + const msg = { + typeUrl: "/cosmos.bank.v1beta1.MsgSend", + value: msgSend, + }; + const gasLimit = 200000; + const fee = { + amount: coins(2000, "ucosm"), + gas: gasLimit.toString(), + }; + const memo = "Use your tokens wisely"; + + const multisigAccount = await client0.getAccount(multisigAddress); + assert(multisigAccount, "Account does not exist on chain"); + const signerData: SignerData = { + accountNumber: multisigAccount.accountNumber, + sequence: multisigAccount.sequence, + chainId: await client0.getChainId(), + }; + + const { + bodyBytes, + signatures: [signature0], + } = await client0.sign(faucet.address0, [msg], fee, memo, signerData); + const { + signatures: [signature1], + } = await client1.sign(faucet.address1, [msg], fee, memo, signerData); + const { + signatures: [signature2], + } = await client2.sign(faucet.address2, [msg], fee, memo, signerData); + const { + signatures: [signature3], + } = await client3.sign(faucet.address3, [msg], fee, memo, signerData); + const { + signatures: [signature4], + } = await client4.sign(faucet.address4, [msg], fee, memo, signerData); + + const signatures = new Map([ + [address0, signature0], + [address1, signature1], + [address2, signature2], + [address3, signature3], + [address4, signature4], + ]); + const signedTx = makeMultisignedTx( + multisigPubkey, + multisigAccount.sequence, + fee, + bodyBytes, + signatures, + ); + + // ensure signature is valid + const result = await client0.broadcastTx(Uint8Array.from(TxRaw.encode(signedTx).finish())); + assertIsBroadcastTxSuccess(result); + }); + }); +}); diff --git a/packages/stargate/src/multisignature.ts b/packages/stargate/src/multisignature.ts new file mode 100644 index 00000000..de98e563 --- /dev/null +++ b/packages/stargate/src/multisignature.ts @@ -0,0 +1,63 @@ +import { Bech32 } from "@cosmjs/encoding"; +import { encodePubkey } from "@cosmjs/proto-signing"; +import Long from "long"; + +import { MultisigThresholdPubkey, pubkeyToAddress } from "../../amino/build"; +import { CompactBitArray, MultiSignature } from "./codec/cosmos/crypto/multisig/v1beta1/multisig"; +import { SignMode } from "./codec/cosmos/tx/signing/v1beta1/signing"; +import { AuthInfo, SignerInfo } from "./codec/cosmos/tx/v1beta1/tx"; +import { TxRaw } from "./codec/cosmos/tx/v1beta1/tx"; +import { StdFee } from "./fee"; + +export function makeMultisignedTx( + multisigPubkey: MultisigThresholdPubkey, + sequence: number, + fee: StdFee, + bodyBytes: Uint8Array, + signatures: Map, +): TxRaw { + const addresses = Array.from(signatures.keys()); + const prefix = Bech32.decode(addresses[0]).prefix; + + let bits = 0; + const signaturesList = new Array(); + for (let i = 0; i < multisigPubkey.value.pubkeys.length; i++) { + const signerAddress = pubkeyToAddress(multisigPubkey.value.pubkeys[i], prefix); + const signature = signatures.get(signerAddress); + if (signature) { + // eslint-disable-next-line no-bitwise + bits |= 0b1 << (8 - 1 - i); + signaturesList.push(signature); + } + } + + const signerInfo: SignerInfo = { + publicKey: encodePubkey(multisigPubkey), + modeInfo: { + multi: { + bitarray: CompactBitArray.fromPartial({ + elems: new Uint8Array([bits]), + extraBitsStored: multisigPubkey.value.pubkeys.length, + }), + modeInfos: signaturesList.map((_) => ({ single: { mode: SignMode.SIGN_MODE_LEGACY_AMINO_JSON } })), + }, + }, + sequence: Long.fromNumber(sequence), + }; + + const authInfo = AuthInfo.fromPartial({ + signerInfos: [signerInfo], + fee: { + amount: [...fee.amount], + gasLimit: Long.fromString(fee.gas), + }, + }); + + const authInfoBytes = AuthInfo.encode(authInfo).finish(); + const signedTx = TxRaw.fromPartial({ + bodyBytes: bodyBytes, + authInfoBytes: authInfoBytes, + signatures: [MultiSignature.encode(MultiSignature.fromPartial({ signatures: signaturesList })).finish()], + }); + return signedTx; +} diff --git a/packages/stargate/src/signingstargateclient.multisig.spec.ts b/packages/stargate/src/signingstargateclient.multisig.spec.ts deleted file mode 100644 index 117ea680..00000000 --- a/packages/stargate/src/signingstargateclient.multisig.spec.ts +++ /dev/null @@ -1,157 +0,0 @@ -import { - createMultisigThresholdPubkey, - encodeSecp256k1Pubkey, - MultisigThresholdPubkey, - pubkeyToAddress, -} from "@cosmjs/amino"; -import { Bech32 } from "@cosmjs/encoding"; -import { coins, makeCosmoshubPath, Secp256k1HdWallet } from "@cosmjs/launchpad"; -import { encodePubkey } from "@cosmjs/proto-signing"; -import { MultiSignature } from "@cosmjs/proto-signing/build/codec/cosmos/crypto/multisig/v1beta1/multisig"; -import { assert } from "@cosmjs/utils"; -import Long from "long"; - -import { MsgSend } from "./codec/cosmos/bank/v1beta1/tx"; -import { CompactBitArray } from "./codec/cosmos/crypto/multisig/v1beta1/multisig"; -import { SignMode } from "./codec/cosmos/tx/signing/v1beta1/signing"; -import { AuthInfo, SignerInfo, TxRaw } from "./codec/cosmos/tx/v1beta1/tx"; -import { StdFee } from "./fee"; -import { SignerData, SigningStargateClient } from "./signingstargateclient"; -import { assertIsBroadcastTxSuccess } from "./stargateclient"; -import { faucet, simapp } from "./testutils.spec"; - -function makeMultisignedTx( - multisigPubkey: MultisigThresholdPubkey, - sequence: number, - fee: StdFee, - bodyBytes: Uint8Array, - signatures: Map, -): TxRaw { - const addresses = Array.from(signatures.keys()); - const prefix = Bech32.decode(addresses[0]).prefix; - - let bits = 0; - const signaturesList = new Array(); - for (let i = 0; i < multisigPubkey.value.pubkeys.length; i++) { - const signerAddress = pubkeyToAddress(multisigPubkey.value.pubkeys[i], prefix); - const signature = signatures.get(signerAddress); - if (signature) { - // eslint-disable-next-line no-bitwise - bits |= 0b1 << (8 - 1 - i); - signaturesList.push(signature); - } - } - - const signerInfo: SignerInfo = { - publicKey: encodePubkey(multisigPubkey), - modeInfo: { - multi: { - bitarray: CompactBitArray.fromPartial({ - elems: new Uint8Array([bits]), - extraBitsStored: multisigPubkey.value.pubkeys.length, - }), - modeInfos: signaturesList.map((_) => ({ single: { mode: SignMode.SIGN_MODE_LEGACY_AMINO_JSON } })), - }, - }, - sequence: Long.fromNumber(sequence), - }; - - const authInfo = AuthInfo.fromPartial({ - signerInfos: [signerInfo], - fee: { - amount: [...fee.amount], - gasLimit: Long.fromString(fee.gas), - }, - }); - - const authInfoBytes = AuthInfo.encode(authInfo).finish(); - const signedTx = TxRaw.fromPartial({ - bodyBytes: bodyBytes, - authInfoBytes: authInfoBytes, - signatures: [MultiSignature.encode(MultiSignature.fromPartial({ signatures: signaturesList })).finish()], - }); - return signedTx; -} - -describe("SigningStargateClient multisig", () => { - it("works", async () => { - const wallet0 = await Secp256k1HdWallet.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(0)); - const wallet1 = await Secp256k1HdWallet.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(1)); - const wallet2 = await Secp256k1HdWallet.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(2)); - const wallet3 = await Secp256k1HdWallet.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(3)); - const wallet4 = await Secp256k1HdWallet.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(4)); - const pubkey0 = encodeSecp256k1Pubkey((await wallet0.getAccounts())[0].pubkey); - const pubkey1 = encodeSecp256k1Pubkey((await wallet1.getAccounts())[0].pubkey); - const pubkey2 = encodeSecp256k1Pubkey((await wallet2.getAccounts())[0].pubkey); - const pubkey3 = encodeSecp256k1Pubkey((await wallet3.getAccounts())[0].pubkey); - const pubkey4 = encodeSecp256k1Pubkey((await wallet4.getAccounts())[0].pubkey); - const address0 = (await wallet0.getAccounts())[0].address; - const address1 = (await wallet1.getAccounts())[0].address; - const address2 = (await wallet2.getAccounts())[0].address; - const address3 = (await wallet3.getAccounts())[0].address; - const address4 = (await wallet4.getAccounts())[0].address; - const multisigPubkey = createMultisigThresholdPubkey([pubkey0, pubkey1, pubkey2, pubkey3, pubkey4], 2); - const multisigAddress = pubkeyToAddress(multisigPubkey, "cosmos"); - expect(multisigAddress).toEqual("cosmos1h90ml36rcu7yegwduzgzderj2jmq49hcpfclw9"); - - const client0 = await SigningStargateClient.connectWithSigner(simapp.tendermintUrl, wallet0); - const client1 = await SigningStargateClient.connectWithSigner(simapp.tendermintUrl, wallet1); - const client2 = await SigningStargateClient.connectWithSigner(simapp.tendermintUrl, wallet2); - const client3 = await SigningStargateClient.connectWithSigner(simapp.tendermintUrl, wallet3); - const client4 = await SigningStargateClient.connectWithSigner(simapp.tendermintUrl, wallet4); - - const msgSend: MsgSend = { - fromAddress: multisigAddress, - toAddress: "cosmos19rvl6ja9h0erq9dc2xxfdzypc739ej8k5esnhg", - amount: coins(1234, "ucosm"), - }; - const msg = { - typeUrl: "/cosmos.bank.v1beta1.MsgSend", - value: msgSend, - }; - const gasLimit = 200000; - const fee = { - amount: coins(2000, "ucosm"), - gas: gasLimit.toString(), - }; - const memo = "Use your tokens wisely"; - - const multisigAccount = await client0.getAccount(multisigAddress); - assert(multisigAccount, "Account does not exist on chain"); - const signerData: SignerData = { - accountNumber: multisigAccount.accountNumber, - sequence: multisigAccount.sequence, - chainId: await client0.getChainId(), - }; - - const { - bodyBytes, - signatures: [signature0], - } = await client0.sign(faucet.address0, [msg], fee, memo, signerData); - const { - signatures: [signature1], - } = await client1.sign(faucet.address1, [msg], fee, memo, signerData); - const { - signatures: [signature2], - } = await client2.sign(faucet.address2, [msg], fee, memo, signerData); - const { - signatures: [signature3], - } = await client3.sign(faucet.address3, [msg], fee, memo, signerData); - const { - signatures: [signature4], - } = await client4.sign(faucet.address4, [msg], fee, memo, signerData); - - const signatures = new Map([ - [address0, signature0], - [address1, signature1], - [address2, signature2], - [address3, signature3], - [address4, signature4], - ]); - const signedTx = makeMultisignedTx(multisigPubkey, multisigAccount.sequence, fee, bodyBytes, signatures); - - // ensure signature is valid - const result04 = await client0.broadcastTx(Uint8Array.from(TxRaw.encode(signedTx).finish())); - assertIsBroadcastTxSuccess(result04); - }); -});