diff --git a/packages/stargate/src/multisignature.spec.ts b/packages/stargate/src/multisignature.spec.ts index 1d4a090f..42695155 100644 --- a/packages/stargate/src/multisignature.spec.ts +++ b/packages/stargate/src/multisignature.spec.ts @@ -6,7 +6,7 @@ import { MsgSend } from "./codec/cosmos/bank/v1beta1/tx"; import { TxRaw } from "./codec/cosmos/tx/v1beta1/tx"; import { makeCompactBitArray, makeMultisignedTx } from "./multisignature"; import { SignerData, SigningStargateClient } from "./signingstargateclient"; -import { assertIsBroadcastTxSuccess } from "./stargateclient"; +import { assertIsBroadcastTxSuccess, StargateClient } from "./stargateclient"; import { faucet, pendingWithoutSimapp, simapp } from "./testutils.spec"; describe("multisignature", () => { @@ -165,90 +165,178 @@ 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 multisigAccountAddress = "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); + // On the composer's machine signing instructions are created. + // The composer does not need to be one of the signers. + const signingInstruction = await (async () => { + const client = await StargateClient.connect(simapp.tendermintUrl); + const accountOnChain = await client.getAccount(multisigAccountAddress); + assert(accountOnChain, "Account does not exist on chain"); - 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 msgSend: MsgSend = { + fromAddress: multisigAccountAddress, + 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 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(), - }; + return { + accountNumber: accountOnChain.accountNumber, + sequence: accountOnChain.sequence, + chainId: await client.getChainId(), + msgs: [msg], + fee: fee, + memo: "Use your tokens wisely", + }; + })(); - 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); + // Signing environment 0 + const [pubkey0, signature0, bodyBytes] = await (async () => { + const wallet = await Secp256k1HdWallet.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(0)); + const pubkey = encodeSecp256k1Pubkey((await wallet.getAccounts())[0].pubkey); + const address = (await wallet.getAccounts())[0].address; + const signingClient = await SigningStargateClient.offline(wallet); + const signerData: SignerData = { + accountNumber: signingInstruction.accountNumber, + sequence: signingInstruction.sequence, + chainId: signingInstruction.chainId, + }; + const { bodyBytes: bb, signatures } = await signingClient.sign( + address, + signingInstruction.msgs, + signingInstruction.fee, + signingInstruction.memo, + signerData, + ); + return [pubkey, signatures[0], bb] as const; + })(); - const signatures = new Map([ - [address0, signature0], - [address1, signature1], - [address2, signature2], - [address3, signature3], - [address4, signature4], - ]); - const signedTx = makeMultisignedTx( - multisigPubkey, - multisigAccount.sequence, - fee, - bodyBytes, - signatures, - ); + // Signing environment 1 + const [pubkey1, signature1] = await (async () => { + const wallet = await Secp256k1HdWallet.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(1)); + const pubkey = encodeSecp256k1Pubkey((await wallet.getAccounts())[0].pubkey); + const address = (await wallet.getAccounts())[0].address; + const signingClient = await SigningStargateClient.offline(wallet); + const signerData: SignerData = { + accountNumber: signingInstruction.accountNumber, + sequence: signingInstruction.sequence, + chainId: signingInstruction.chainId, + }; + const { signatures } = await signingClient.sign( + address, + signingInstruction.msgs, + signingInstruction.fee, + signingInstruction.memo, + signerData, + ); + return [pubkey, signatures[0]] as const; + })(); - // ensure signature is valid - const result = await client0.broadcastTx(Uint8Array.from(TxRaw.encode(signedTx).finish())); - assertIsBroadcastTxSuccess(result); + // Signing environment 2 + const [pubkey2, signature2] = await (async () => { + const wallet = await Secp256k1HdWallet.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(2)); + const pubkey = encodeSecp256k1Pubkey((await wallet.getAccounts())[0].pubkey); + const address = (await wallet.getAccounts())[0].address; + const signingClient = await SigningStargateClient.offline(wallet); + const signerData: SignerData = { + accountNumber: signingInstruction.accountNumber, + sequence: signingInstruction.sequence, + chainId: signingInstruction.chainId, + }; + const { signatures } = await signingClient.sign( + address, + signingInstruction.msgs, + signingInstruction.fee, + signingInstruction.memo, + signerData, + ); + return [pubkey, signatures[0]] as const; + })(); + + // Signing environment 3 + const [pubkey3, signature3] = await (async () => { + const wallet = await Secp256k1HdWallet.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(3)); + const pubkey = encodeSecp256k1Pubkey((await wallet.getAccounts())[0].pubkey); + const address = (await wallet.getAccounts())[0].address; + const signingClient = await SigningStargateClient.offline(wallet); + const signerData: SignerData = { + accountNumber: signingInstruction.accountNumber, + sequence: signingInstruction.sequence, + chainId: signingInstruction.chainId, + }; + const { signatures } = await signingClient.sign( + address, + signingInstruction.msgs, + signingInstruction.fee, + signingInstruction.memo, + signerData, + ); + return [pubkey, signatures[0]] as const; + })(); + + // Signing environment 4 + const [pubkey4, signature4] = await (async () => { + const wallet = await Secp256k1HdWallet.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(4)); + const pubkey = encodeSecp256k1Pubkey((await wallet.getAccounts())[0].pubkey); + const address = (await wallet.getAccounts())[0].address; + const signingClient = await SigningStargateClient.offline(wallet); + const signerData: SignerData = { + accountNumber: signingInstruction.accountNumber, + sequence: signingInstruction.sequence, + chainId: signingInstruction.chainId, + }; + const { signatures } = await signingClient.sign( + address, + signingInstruction.msgs, + signingInstruction.fee, + signingInstruction.memo, + signerData, + ); + return [pubkey, signatures[0]] as const; + })(); + + // From here on, no private keys are required anymore. Any anonymous entity + // can collect, assemble and broadcast. + { + const multisigPubkey = createMultisigThresholdPubkey( + [pubkey0, pubkey1, pubkey2, pubkey3, pubkey4], + 2, + ); + expect(pubkeyToAddress(multisigPubkey, "cosmos")).toEqual(multisigAccountAddress); + + const address0 = pubkeyToAddress(pubkey0, "cosmos"); + const address1 = pubkeyToAddress(pubkey1, "cosmos"); + const address2 = pubkeyToAddress(pubkey2, "cosmos"); + const address3 = pubkeyToAddress(pubkey3, "cosmos"); + const address4 = pubkeyToAddress(pubkey4, "cosmos"); + + const broadcaster = await StargateClient.connect(simapp.tendermintUrl); + const signedTx = makeMultisignedTx( + multisigPubkey, + signingInstruction.sequence, + signingInstruction.fee, + bodyBytes, + new Map([ + [address0, signature0], + [address1, signature1], + [address2, signature2], + [address3, signature3], + [address4, signature4], + ]), + ); + // ensure signature is valid + const result = await broadcaster.broadcastTx(Uint8Array.from(TxRaw.encode(signedTx).finish())); + assertIsBroadcastTxSuccess(result); + } }); }); });