From 4c60f883fa7cfdd2b5c56c6aff547743f0b174c5 Mon Sep 17 00:00:00 2001 From: Simon Warta Date: Mon, 21 Dec 2020 14:59:42 +0100 Subject: [PATCH] Add SigningCosmosClient.appendSignature --- CHANGELOG.md | 2 + .../launchpad/src/signingcosmosclient.spec.ts | 62 +++++++++++++++++++ packages/launchpad/src/signingcosmosclient.ts | 23 +++++++ packages/launchpad/src/testutils.spec.ts | 16 +++++ .../launchpad/types/signingcosmosclient.d.ts | 5 ++ 5 files changed, 108 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b2bdfe2ad6..eb56543582 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,8 @@ remove such functionality from `CosmosClient.searchTx`. - @cosmjs/launchpad: Add `SigningCosmosClient.sign` method for signing without broadcasting. +- @cosmjs/launchpad: Add `SigningCosmosClient.appendSignature` method creating + transactions with multiple signatures. - @cosmjs/launchpad: Add support for undefined memo in `makeSignDoc`. - @cosmjs/launchpad-ledger: `LedgerSigner.sign` method renamed `signAmino`. - @cosmjs/proto-signing: Add new package for handling transaction signing with diff --git a/packages/launchpad/src/signingcosmosclient.spec.ts b/packages/launchpad/src/signingcosmosclient.spec.ts index 18064387ba..b71364de33 100644 --- a/packages/launchpad/src/signingcosmosclient.spec.ts +++ b/packages/launchpad/src/signingcosmosclient.spec.ts @@ -14,6 +14,7 @@ import { makeRandomAddress, pendingWithoutLaunchpad, } from "./testutils.spec"; +import { makeCosmoshubPath } from "./wallet"; describe("SigningCosmosClient", () => { describe("makeReadOnly", () => { @@ -222,4 +223,65 @@ describe("SigningCosmosClient", () => { assertIsBroadcastTxSuccess(broadcastResult); }); }); + + describe("appendSignature", () => { + it("works", async () => { + pendingWithoutLaunchpad(); + const wallet0 = await Secp256k1HdWallet.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(0)); + const wallet1 = await Secp256k1HdWallet.fromMnemonic(faucet.mnemonic, makeCosmoshubPath(1)); + const client0 = new SigningCosmosClient(launchpad.endpoint, faucet.address0, wallet0); + const client1 = new SigningCosmosClient(launchpad.endpoint, faucet.address1, wallet1); + + const msg1: MsgSend = { + type: "cosmos-sdk/MsgSend", + value: { + from_address: faucet.address0, + to_address: makeRandomAddress(), + amount: coins(1234567, "ucosm"), + }, + }; + const msg2: MsgSend = { + type: "cosmos-sdk/MsgSend", + value: { + from_address: faucet.address1, + to_address: makeRandomAddress(), + amount: coins(1234567, "ucosm"), + }, + }; + const fee = { + amount: coins(2000, "ucosm"), + gas: "160000", // 2*80k + }; + const memo = "This must be authorized by the two of us"; + + const signed = await client0.sign([msg1, msg2], fee, memo); + expect(signed.msg).toEqual([msg1, msg2]); + expect(signed.fee).toEqual(fee); + expect(signed.memo).toEqual(memo); + expect(signed.signatures).toEqual([ + { + pub_key: faucet.pubkey0, + signature: jasmine.stringMatching(base64Matcher), + }, + ]); + + const cosigned = await client1.appendSignature(signed); + expect(cosigned.msg).toEqual([msg1, msg2]); + expect(cosigned.fee).toEqual(fee); + expect(cosigned.memo).toEqual(memo); + expect(cosigned.signatures).toEqual([ + { + pub_key: faucet.pubkey0, + signature: jasmine.stringMatching(base64Matcher), + }, + { + pub_key: faucet.pubkey1, + signature: jasmine.stringMatching(base64Matcher), + }, + ]); + + const broadcastResult = await client0.broadcastTx(cosigned); + assertIsBroadcastTxSuccess(broadcastResult); + }); + }); }); diff --git a/packages/launchpad/src/signingcosmosclient.ts b/packages/launchpad/src/signingcosmosclient.ts index bd1a33a6e1..ed7049ab4d 100644 --- a/packages/launchpad/src/signingcosmosclient.ts +++ b/packages/launchpad/src/signingcosmosclient.ts @@ -1,4 +1,6 @@ /* eslint-disable @typescript-eslint/naming-convention */ +import equals from "fast-deep-equal"; + import { Coin } from "./coins"; import { Account, BroadcastTxResult, CosmosClient, GetSequenceResult } from "./cosmosclient"; import { makeSignDoc } from "./encoding"; @@ -102,4 +104,25 @@ export class SigningCosmosClient extends CosmosClient { const { signed, signature } = await this.signer.signAmino(this.senderAddress, signDoc); return makeStdTx(signed, signature); } + + /** + * Gets account number and sequence from the API, creates a sign doc, + * creates a single signature and appends it to the existing signatures. + */ + public async appendSignature(signedTx: StdTx): Promise { + const { msg: msgs, fee, memo } = signedTx; + const { accountNumber, sequence } = await this.getSequence(); + const chainId = await this.getChainId(); + const signDoc = makeSignDoc(msgs, fee, chainId, memo, accountNumber, sequence); + const { signed, signature: additionalSignature } = await this.signer.signAmino( + this.senderAddress, + signDoc, + ); + if (!equals(signDoc, signed)) { + throw new Error( + "The signed document differs from the one of the original transaction. This is not allowed since the resulting transaction will be invalid.", + ); + } + return makeStdTx(signed, [...signedTx.signatures, additionalSignature]); + } } diff --git a/packages/launchpad/src/testutils.spec.ts b/packages/launchpad/src/testutils.spec.ts index 2b36e537b1..57bea7d16b 100644 --- a/packages/launchpad/src/testutils.spec.ts +++ b/packages/launchpad/src/testutils.spec.ts @@ -42,6 +42,22 @@ export const faucet = { type: "tendermint/PubKeySecp256k1", value: "A08EGB7ro1ORuFhjOnZcSgwYlpe0DSFjVNUIkNNQxwKQ", }, + pubkey1: { + type: "tendermint/PubKeySecp256k1", + value: "AiDosfIbBi54XJ1QjCeApumcy/FjdtF+YhywPf3DKTx7", + }, + pubkey2: { + type: "tendermint/PubKeySecp256k1", + value: "AzQg33JZqH7vSsm09esZY5bZvmzYwE/SY78cA0iLxpD7", + }, + pubkey3: { + type: "tendermint/PubKeySecp256k1", + value: "A3gOAlB6aiRTCPvWMQg2+ZbGYNsLd8qlvV28m8p2UhY2", + }, + pubkey4: { + type: "tendermint/PubKeySecp256k1", + value: "Aum2063ub/ErUnIUB36sK55LktGUStgcbSiaAnL1wadu", + }, address0: "cosmos1pkptre7fdkl6gfrzlesjjvhxhlc3r4gmmk8rs6", address1: "cosmos10dyr9899g6t0pelew4nvf4j5c3jcgv0r73qga5", address2: "cosmos1xy4yqngt0nlkdcenxymg8tenrghmek4nmqm28k", diff --git a/packages/launchpad/types/signingcosmosclient.d.ts b/packages/launchpad/types/signingcosmosclient.d.ts index 36e881811e..fabefef012 100644 --- a/packages/launchpad/types/signingcosmosclient.d.ts +++ b/packages/launchpad/types/signingcosmosclient.d.ts @@ -58,4 +58,9 @@ export declare class SigningCosmosClient extends CosmosClient { * creates a single signature and assembles the signed transaction. */ sign(msgs: readonly Msg[], fee: StdFee, memo?: string): Promise; + /** + * Gets account number and sequence from the API, creates a sign doc, + * creates a single signature and appends it to the existing signatures. + */ + appendSignature(signedTx: StdTx): Promise; }