From f93b1f241e0af81da852dea5203e0286e9dbd46c Mon Sep 17 00:00:00 2001 From: Mukul Jangid <49061120+mukulljangid@users.noreply.github.com> Date: Fri, 8 Oct 2021 18:33:31 -0400 Subject: [PATCH] Refactor: Rewrite transaction signing (#1693) * refactor: sign with wallet * refactor: change multisign and rename signTx to sign * refactor: specify multisign as boolean * refactor: support address as well as booleans for multisign * feat: return hash from sign fn and fix tests --- src/sugar/submit.ts | 9 +- src/wallet/index.ts | 28 ++- src/wallet/signer.ts | 18 +- test/integration/integration.ts | 8 +- test/integration/reliableSubmission.ts | 4 +- test/integration/requests/submit.ts | 16 +- .../integration/requests/submitMultisigned.ts | 8 +- test/integration/utils.ts | 3 +- test/wallet/index.ts | 133 ++++++----- test/wallet/signer.ts | 217 ++++++++++-------- 10 files changed, 247 insertions(+), 197 deletions(-) diff --git a/src/sugar/submit.ts b/src/sugar/submit.ts index 96189f98..ea8e6092 100644 --- a/src/sugar/submit.ts +++ b/src/sugar/submit.ts @@ -5,7 +5,6 @@ import { ValidationError, XrplError } from '../errors' import { TxResponse } from '../models/methods' import { Transaction } from '../models/transactions' import { hashes } from '../utils' -import { sign } from '../wallet/signer' // general time for a ledger to close, in milliseconds const LEDGER_CLOSE_TIME = 4000 @@ -35,8 +34,8 @@ async function submit( transaction: Transaction, ): Promise { const tx = await this.autofill(transaction) - const signedTxEncoded = sign(wallet, tx) - return this.submitSigned(signedTxEncoded) + const { tx_blob } = wallet.sign(tx) + return this.submitSigned(tx_blob) } /** @@ -83,8 +82,8 @@ async function submitReliable( transaction: Transaction, ): Promise { const tx = await this.autofill(transaction) - const signedTxEncoded = sign(wallet, tx) - return this.submitSignedReliable(signedTxEncoded) + const { tx_blob } = wallet.sign(tx) + return this.submitSignedReliable(tx_blob) } /** diff --git a/src/wallet/index.ts b/src/wallet/index.ts index f289d9c7..20d3f2e0 100644 --- a/src/wallet/index.ts +++ b/src/wallet/index.ts @@ -23,6 +23,7 @@ import { import ECDSA from '../ecdsa' import { ValidationError } from '../errors' import { Transaction } from '../models/transactions' +import { hashSignedTx } from '../utils/hashes/ledgerHash' const DEFAULT_ALGORITHM: ECDSA = ECDSA.ed25519 const DEFAULT_DERIVATION_PATH = "m/44'/144'/0'/0/0" @@ -30,6 +31,10 @@ const DEFAULT_DERIVATION_PATH = "m/44'/144'/0'/0/0" function hexFromBuffer(buffer: Buffer): string { return buffer.toString('hex').toUpperCase() } +export interface SignedTxBlobHash { + tx_blob: string + hash: string +} /** * A utility for deriving a wallet composed of a keypair (publicKey/privateKey). @@ -171,17 +176,23 @@ class Wallet { * * @param this - Wallet instance. * @param transaction - A transaction to be signed offline. - * @param multisignAddress - Multisign only. An account address corresponding to the multi-signature being added. If this - * wallet represents your [master keypair](https://xrpl.org/cryptographic-keys.html#master-key-pair) you can get your account address - * with the Wallet.getClassicAddress() function. + * @param multisign - Specify true/false to use multisign or actual address (classic/x-address) to make multisign tx request. * @returns A signed transaction. * @throws ValidationError if the transaction is already signed or does not encode/decode to same result. */ - public signTransaction( + // eslint-disable-next-line max-lines-per-function -- introduced more checks to support both string and boolean inputs. + public sign( this: Wallet, transaction: Transaction, - multisignAddress?: string, - ): string { + multisign?: boolean | string, + ): SignedTxBlobHash { + let multisignAddress: boolean | string = false + if (typeof multisign === 'string' && multisign.startsWith('X')) { + multisignAddress = multisign + } else if (multisign) { + multisignAddress = this.getClassicAddress() + } + if (transaction.TxnSignature || transaction.Signers) { throw new ValidationError( 'txJSON must not contain "TxnSignature" or "Signers" properties', @@ -211,7 +222,10 @@ class Wallet { } const serialized = encode(txToSignAndEncode) this.checkTxSerialization(serialized, transaction) - return serialized + return { + tx_blob: serialized, + hash: hashSignedTx(serialized), + } } /** diff --git a/src/wallet/signer.ts b/src/wallet/signer.ts index 9af3d997..f3bc3c58 100644 --- a/src/wallet/signer.ts +++ b/src/wallet/signer.ts @@ -16,22 +16,6 @@ import { validateBaseTransaction } from '../models/transactions/common' import Wallet from '.' -/** - * Uses a wallet to cryptographically sign a transaction which proves the owner of the wallet - * is issuing this transaction. - * - * @param wallet - A Wallet that holds your cryptographic keys. - * @param tx - The Transaction that is being signed. - * @param forMultisign - If true, changes the signature format to encode for multisigning. - * @returns A signed Transaction. - */ -function sign(wallet: Wallet, tx: Transaction, forMultisign = false): string { - return wallet.signTransaction( - tx, - forMultisign ? wallet.getClassicAddress() : '', - ) -} - /** * Takes several transactions with Signer fields (in object or blob form) and creates a * single transaction with all Signers that then gets signed and returned. @@ -183,4 +167,4 @@ function getDecodedTransaction(txOrBlob: Transaction | string): Transaction { return decode(txOrBlob) as unknown as Transaction } -export { sign, authorizeChannel, verifySignature, multisign } +export { authorizeChannel, verifySignature, multisign } diff --git a/test/integration/integration.ts b/test/integration/integration.ts index d1d2093a..72efdf9c 100644 --- a/test/integration/integration.ts +++ b/test/integration/integration.ts @@ -5,7 +5,7 @@ import _ from 'lodash' import { Client } from 'xrpl-local' import { AccountSet, SignerListSet } from 'xrpl-local/models/transactions' import { convertStringToHex } from 'xrpl-local/utils' -import { sign, multisign } from 'xrpl-local/wallet/signer' +import { multisign } from 'xrpl-local/wallet/signer' import serverUrl from './serverUrl' import { setupClient, suiteClientSetup, teardownClient } from './setup' @@ -64,9 +64,9 @@ describe('integration tests', function () { Domain: convertStringToHex('example.com'), } const accountSetTx = await client.autofill(accountSet, 2) - const signed1 = sign(signerWallet1, accountSetTx, true) - const signed2 = sign(signerWallet2, accountSetTx, true) - const multisignedTx = multisign([signed1, signed2]) + const { tx_blob: tx_blob1 } = signerWallet1.sign(accountSetTx, true) + const { tx_blob: tx_blob2 } = signerWallet2.sign(accountSetTx, true) + const multisignedTx = multisign([tx_blob1, tx_blob2]) const submitResponse = await client.submitSigned(multisignedTx) await ledgerAccept(client) assert.strictEqual(submitResponse.result.engine_result, 'tesSUCCESS') diff --git a/test/integration/reliableSubmission.ts b/test/integration/reliableSubmission.ts index adef8b9f..a3cb0e7a 100644 --- a/test/integration/reliableSubmission.ts +++ b/test/integration/reliableSubmission.ts @@ -41,7 +41,7 @@ describe('reliable submission', function () { Account: this.wallet.getClassicAddress(), Domain: convertStringToHex('example.com'), } - const signedAccountSet = this.wallet.signTransaction( + const { tx_blob: signedAccountSet } = this.wallet.sign( await this.client.autofill(accountSet), ) const responsePromise = this.client.submitSignedReliable(signedAccountSet) @@ -60,7 +60,7 @@ describe('reliable submission', function () { Account: this.wallet.getClassicAddress(), Domain: convertStringToHex('example.com'), } - const signedAccountSet = this.wallet.signTransaction( + const { tx_blob: signedAccountSet } = this.wallet.sign( await this.client.autofill(accountSet), ) const responsePromise = this.client.submitSignedReliable(signedAccountSet) diff --git a/test/integration/requests/submit.ts b/test/integration/requests/submit.ts index 93af1693..91a3f3ba 100644 --- a/test/integration/requests/submit.ts +++ b/test/integration/requests/submit.ts @@ -34,15 +34,19 @@ describe('submit', function () { } const autofilledTx = await this.client.autofill(accountSet) - const signedTx = this.wallet.signTransaction(autofilledTx) + const signedTx = this.wallet.sign(autofilledTx) const submitRequest: SubmitRequest = { command: 'submit', - tx_blob: signedTx, + tx_blob: signedTx.tx_blob, } const submitResponse = await this.client.request(submitRequest) await ledgerAccept(this.client) - await verifySubmittedTransaction(this.client, signedTx) + await verifySubmittedTransaction( + this.client, + signedTx.tx_blob, + signedTx.hash, + ) const expectedResponse: SubmitResponse = { id: submitResponse.id, @@ -52,10 +56,10 @@ describe('submit', function () { engine_result_code: 0, engine_result_message: 'The transaction was applied. Only final in a validated ledger.', - tx_blob: signedTx, + tx_blob: signedTx.tx_blob, tx_json: { - ...(decode(signedTx) as unknown as Transaction), - hash: hashSignedTx(signedTx), + ...(decode(signedTx.tx_blob) as unknown as Transaction), + hash: hashSignedTx(signedTx.tx_blob), }, accepted: true, account_sequence_available: diff --git a/test/integration/requests/submitMultisigned.ts b/test/integration/requests/submitMultisigned.ts index b1ff0229..08d8e09e 100644 --- a/test/integration/requests/submitMultisigned.ts +++ b/test/integration/requests/submitMultisigned.ts @@ -12,7 +12,7 @@ import { hashes, } from 'xrpl-local' import { convertStringToHex } from 'xrpl-local/utils' -import { multisign, sign } from 'xrpl-local/wallet/signer' +import { multisign } from 'xrpl-local/wallet/signer' import serverUrl from '../serverUrl' import { setupClient, suiteClientSetup, teardownClient } from '../setup' @@ -68,9 +68,9 @@ describe('submit_multisigned', function () { Domain: convertStringToHex('example.com'), } const accountSetTx = await client.autofill(accountSet, 2) - const signed1 = sign(signerWallet1, accountSetTx, true) - const signed2 = sign(signerWallet2, accountSetTx, true) - const multisigned = multisign([signed1, signed2]) + const signed1 = signerWallet1.sign(accountSetTx, true) + const signed2 = signerWallet2.sign(accountSetTx, true) + const multisigned = multisign([signed1.tx_blob, signed2.tx_blob]) const multisignedRequest: SubmitMultisignedRequest = { command: 'submit_multisigned', tx_json: decode(multisigned) as unknown as Transaction, diff --git a/test/integration/utils.ts b/test/integration/utils.ts index 57063df2..5e1ba314 100644 --- a/test/integration/utils.ts +++ b/test/integration/utils.ts @@ -44,8 +44,9 @@ export async function generateFundedWallet(client: Client): Promise { export async function verifySubmittedTransaction( client: Client, tx: Transaction | string, + hashTx?: string, ): Promise { - const hash = hashSignedTx(tx) + const hash = hashTx ?? hashSignedTx(tx) const data = await client.request({ command: 'tx', transaction: hash, diff --git a/test/wallet/index.ts b/test/wallet/index.ts index 693ec9f3..193c5769 100644 --- a/test/wallet/index.ts +++ b/test/wallet/index.ts @@ -200,10 +200,11 @@ describe('Wallet', function () { }) it('signTransaction successfully', async function () { - const result = wallet.signTransaction( - REQUEST_FIXTURES.normal as Transaction, - ) - assert.deepEqual(result, RESPONSE_FIXTURES.normal.signedTransaction) + const result = wallet.sign(REQUEST_FIXTURES.normal as Transaction) + assert.deepEqual(result, { + tx_blob: RESPONSE_FIXTURES.normal.signedTransaction, + hash: '93F6C6CE73C092AA005103223F3A1F557F4C097A2943D96760F6490F04379917', + }) }) it('signTransaction with lowercase hex data in memo (hex should be case insensitive)', async function () { @@ -230,62 +231,73 @@ describe('Wallet', function () { ], } - const result = Wallet.fromSeed(secret).signTransaction(lowercaseMemoTx) - assert.equal( - result, - '120000228000000023000022B8240000000C2E0000270F201B00D5A36761400000000098968068400000000000000C73210305E09ED602D40AB1AF65646A4007C2DAC17CB6CDACDE301E74FB2D728EA057CF744730450221009C00E8439E017CA622A5A1EE7643E26B4DE9C808DE2ABE45D33479D49A4CEC66022062175BE8733442FA2A4D9A35F85A57D58252AE7B19A66401FE238B36FA28E5A081146C1856D0E36019EA75C56D7E8CBA6E35F9B3F71583147FB49CD110A1C46838788CD12764E3B0F837E0DDF9EA7C1F687474703A2F2F6578616D706C652E636F6D2F6D656D6F2F67656E657269637D0472656E74E1F1', - ) + const result = Wallet.fromSeed(secret).sign(lowercaseMemoTx) + assert.deepEqual(result, { + tx_blob: + '120000228000000023000022B8240000000C2E0000270F201B00D5A36761400000000098968068400000000000000C73210305E09ED602D40AB1AF65646A4007C2DAC17CB6CDACDE301E74FB2D728EA057CF744730450221009C00E8439E017CA622A5A1EE7643E26B4DE9C808DE2ABE45D33479D49A4CEC66022062175BE8733442FA2A4D9A35F85A57D58252AE7B19A66401FE238B36FA28E5A081146C1856D0E36019EA75C56D7E8CBA6E35F9B3F71583147FB49CD110A1C46838788CD12764E3B0F837E0DDF9EA7C1F687474703A2F2F6578616D706C652E636F6D2F6D656D6F2F67656E657269637D0472656E74E1F1', + hash: '41B9CB78D8E18A796CDD4B0BC6FB0EA19F64C4F25FDE23049197852CAB71D10D', + }) }) it('signTransaction with EscrowFinish', async function () { - const result = wallet.signTransaction( - REQUEST_FIXTURES.escrow as Transaction, - ) - assert.deepEqual(result, RESPONSE_FIXTURES.escrow.signedTransaction) + const result = wallet.sign(REQUEST_FIXTURES.escrow as Transaction) + assert.deepEqual(result, { + tx_blob: RESPONSE_FIXTURES.escrow.signedTransaction, + hash: '645B7676DF057E4F5E83F970A18B3751B6813807F1030A8D2F482D02DC885106', + }) }) it('signTransaction with multisignAddress', async function () { - const signature = wallet.signTransaction( + const signature = wallet.sign( REQUEST_FIXTURES.signAs as Transaction, - wallet.getClassicAddress(), + true, ) - assert.deepEqual(signature, RESPONSE_FIXTURES.signAs.signedTransaction) + assert.deepEqual(signature, { + tx_blob: RESPONSE_FIXTURES.signAs.signedTransaction, + hash: 'D8CF5FC93CFE5E131A34599AFB7CE186A5B8D1B9F069E35F4634AD3B27837E35', + }) }) it('signTransaction with X Address and no given tag for multisignAddress', async function () { - const signature = wallet.signTransaction( + const signature = wallet.sign( REQUEST_FIXTURES.signAs as Transaction, wallet.getXAddress(), ) - assert.deepEqual(signature, RESPONSE_FIXTURES.signAs.signedTransaction) + assert.deepEqual(signature, { + tx_blob: RESPONSE_FIXTURES.signAs.signedTransaction, + hash: 'D8CF5FC93CFE5E131A34599AFB7CE186A5B8D1B9F069E35F4634AD3B27837E35', + }) }) it('signTransaction with X Address and tag for multisignAddress', async function () { - const signature = wallet.signTransaction( + const signature = wallet.sign( REQUEST_FIXTURES.signAs as Transaction, wallet.getXAddress(0), ) // Adding a tag changes the classicAddress, which changes the signature from RESPONSE_FIXTURES.signAs - const expectedSignature = - '120000240000000261400000003B9ACA00684000000000000032730081142E244E6F20104E57C0C60BD823CB312BF10928C78314B5F762798A53D543A014CAF8B297CFF8F2F937E8F3E0102300000000732102A8A44DB3D4C73EEEE11DFE54D2029103B776AA8A8D293A91D645977C9DF5F54474473045022100B3F8205578C6A68D3BBD27650F5D2E983718D502C250C5147F07B7EDD8E8583E02207B892818BD58E328C2797F15694A505937861586D527849065B582523E390B128114B3263BD0A9BF9DFDBBBBD07F536355FF477BF0E9E1F1' + const expectedSignature = { + tx_blob: + '120000240000000261400000003B9ACA00684000000000000032730081142E244E6F20104E57C0C60BD823CB312BF10928C78314B5F762798A53D543A014CAF8B297CFF8F2F937E8F3E0102300000000732102A8A44DB3D4C73EEEE11DFE54D2029103B776AA8A8D293A91D645977C9DF5F54474473045022100B3F8205578C6A68D3BBD27650F5D2E983718D502C250C5147F07B7EDD8E8583E02207B892818BD58E328C2797F15694A505937861586D527849065B582523E390B128114B3263BD0A9BF9DFDBBBBD07F536355FF477BF0E9E1F1', + hash: 'D4E6BC7FEA5B9624A09750F8931BE269554BE8C735A83EBD012188B87D4757C8', + } + assert.deepEqual(signature, expectedSignature) }) it('signTransaction throws when given a transaction that is already signed', async function () { - const result = wallet.signTransaction( - REQUEST_FIXTURES.normal as Transaction, - ) + const result = wallet.sign(REQUEST_FIXTURES.normal as Transaction) assert.throws(() => { - const tx = decode(result) as unknown as Transaction - wallet.signTransaction(tx) + const tx = decode(result.tx_blob) as unknown as Transaction + wallet.sign(tx) }, /txJSON must not contain "TxnSignature" or "Signers" properties/u) }) it('signTransaction with an EscrowExecution transaction', async function () { - const result = wallet.signTransaction( - REQUEST_FIXTURES.escrow as Transaction, - ) - assert.deepEqual(result, RESPONSE_FIXTURES.escrow.signedTransaction) + const result = wallet.sign(REQUEST_FIXTURES.escrow as Transaction) + assert.deepEqual(result, { + tx_blob: RESPONSE_FIXTURES.escrow.signedTransaction, + hash: '645B7676DF057E4F5E83F970A18B3751B6813807F1030A8D2F482D02DC885106', + }) }) it('signTransaction succeeds when given a transaction with no flags', async function () { @@ -297,15 +309,19 @@ describe('Wallet', function () { Sequence: 1, Fee: '12', } - const result = wallet.signTransaction(tx) - const expectedResult = - '1200002400000001614000000001312D0068400000000000000C732102A8A44DB3D4C73EEEE11DFE54D2029103B776AA8A8D293A91D645977C9DF5F5447446304402201C0A74EE8ECF5ED83734D7171FB65C01D90D67040DEDCC66414BD546CE302B5802205356843841BFFF60D15F5F5F9FB0AB9D66591778140AB2D137FF576D9DEC44BC8114EE3046A5DDF8422C40DDB93F1D522BB4FE6419158314FDB08D07AAA0EB711793A3027304D688E10C3648' - const decoded = decode(result) + const result = wallet.sign(tx) + const expectedResult = { + tx_blob: + '1200002400000001614000000001312D0068400000000000000C732102A8A44DB3D4C73EEEE11DFE54D2029103B776AA8A8D293A91D645977C9DF5F5447446304402201C0A74EE8ECF5ED83734D7171FB65C01D90D67040DEDCC66414BD546CE302B5802205356843841BFFF60D15F5F5F9FB0AB9D66591778140AB2D137FF576D9DEC44BC8114EE3046A5DDF8422C40DDB93F1D522BB4FE6419158314FDB08D07AAA0EB711793A3027304D688E10C3648', + hash: 'E22186AE9FE477821BF361358174C2B0AC2D3289AA6F7E8C1102B3D270C41204', + } + + const decoded = decode(result.tx_blob) assert( decoded.Flags == null, `Flags = ${JSON.stringify(decoded.Flags)}, should be undefined`, ) - assert.equal(result, expectedResult) + assert.deepEqual(result, expectedResult) }) it('signTransaction succeeds with source.amount/destination.minAmount', async function () { @@ -337,10 +353,13 @@ describe('Wallet', function () { Fee: '12', } - const result = wallet.signTransaction(tx) - const expectedResult = - '12000022800200002400000017201B0086955361EC6386F26FC0FFFF0000000000000000000000005553440000000000DC596C88BCDE4E818D416FCDEEBF2C8656BADC9A68400000000000000C69D4438D7EA4C6800000000000000000000000000047425000000000000C155FFE99C8C91F67083CEFFDB69EBFE76348CA6AD4446F8C5D8A5E0B0000000000000000000000005553440000000000DC596C88BCDE4E818D416FCDEEBF2C8656BADC9A732102A8A44DB3D4C73EEEE11DFE54D2029103B776AA8A8D293A91D645977C9DF5F544744630440220297E0C7670C7DA491E0D649E62C123D988BA93FD7EA1B9141F1D376CDDF902F502205AF1936B22B18BBA7793A88ABEEABADB4CE0E4C3BE583066480F2F476B5ED08E81145E7B112523F68D2F5E879DB4EAC51C6698A6930483149F500E50C2F016CA01945E5A1E5846B61EF2D376' - const decoded = decode(result) + const result = wallet.sign(tx) + const expectedResult = { + tx_blob: + '12000022800200002400000017201B0086955361EC6386F26FC0FFFF0000000000000000000000005553440000000000DC596C88BCDE4E818D416FCDEEBF2C8656BADC9A68400000000000000C69D4438D7EA4C6800000000000000000000000000047425000000000000C155FFE99C8C91F67083CEFFDB69EBFE76348CA6AD4446F8C5D8A5E0B0000000000000000000000005553440000000000DC596C88BCDE4E818D416FCDEEBF2C8656BADC9A732102A8A44DB3D4C73EEEE11DFE54D2029103B776AA8A8D293A91D645977C9DF5F544744630440220297E0C7670C7DA491E0D649E62C123D988BA93FD7EA1B9141F1D376CDDF902F502205AF1936B22B18BBA7793A88ABEEABADB4CE0E4C3BE583066480F2F476B5ED08E81145E7B112523F68D2F5E879DB4EAC51C6698A6930483149F500E50C2F016CA01945E5A1E5846B61EF2D376', + hash: 'FB2813E9E673EF56609070A4BA9640FAD0508DA567320AE9D92FB5A356A03D84', + } + const decoded = decode(result.tx_blob) assert( decoded.Flags === 2147614720, `Flags = ${JSON.stringify(decoded.Flags)}, should be 2147614720`, @@ -362,7 +381,7 @@ describe('Wallet', function () { } assert.throws(() => { - wallet.signTransaction(tx) + wallet.sign(tx) }, /1\.2 is an illegal amount/u) }) @@ -380,15 +399,16 @@ describe('Wallet', function () { } assert.throws(() => { - wallet.signTransaction(tx) + wallet.sign(tx) }, /1123456\.7 is an illegal amount/u) }) it('signTransaction with a ticket transaction', async function () { - const result = wallet.signTransaction( - REQUEST_FIXTURES.ticket as Transaction, - ) - assert.deepEqual(result, RESPONSE_FIXTURES.ticket.signedTransaction) + const result = wallet.sign(REQUEST_FIXTURES.ticket as Transaction) + assert.deepEqual(result, { + tx_blob: RESPONSE_FIXTURES.ticket.signedTransaction, + hash: '0AC60B1E1F063904D9D9D0E9D03F2E9C8D41BC6FC872D5B8BF87E15BBF9669BB', + }) }) it('signTransaction with a Payment transaction with paths', async function () { @@ -416,11 +436,12 @@ describe('Wallet', function () { Sequence: 1, Fee: '12', } - const result = wallet.signTransaction(payment) - assert.deepEqual( - result, - '12000022800200002400000001201B00EF81E661EC6386F26FC0FFFF0000000000000000000000005553440000000000054F6F784A58F9EFB0A9EB90B83464F9D166461968400000000000000C6940000000000000646AD3504529A0465E2E0000000000000000000000005553440000000000054F6F784A58F9EFB0A9EB90B83464F9D1664619732102A8A44DB3D4C73EEEE11DFE54D2029103B776AA8A8D293A91D645977C9DF5F54474463044022049AD75980A5088EBCD768547E06427736BD8C4396B9BD3762CA8C1341BD7A4F9022060C94071C3BDF99FAB4BEB7C0578D6EBEE083157B470699645CCE4738A41D61081145E7B112523F68D2F5E879DB4EAC51C6698A693048314CA6EDC7A28252DAEA6F2045B24F4D7C333E146170112300000000000000000000000005553440000000000054F6F784A58F9EFB0A9EB90B83464F9D166461900', - ) + const result = wallet.sign(payment) + assert.deepEqual(result, { + tx_blob: + '12000022800200002400000001201B00EF81E661EC6386F26FC0FFFF0000000000000000000000005553440000000000054F6F784A58F9EFB0A9EB90B83464F9D166461968400000000000000C6940000000000000646AD3504529A0465E2E0000000000000000000000005553440000000000054F6F784A58F9EFB0A9EB90B83464F9D1664619732102A8A44DB3D4C73EEEE11DFE54D2029103B776AA8A8D293A91D645977C9DF5F54474463044022049AD75980A5088EBCD768547E06427736BD8C4396B9BD3762CA8C1341BD7A4F9022060C94071C3BDF99FAB4BEB7C0578D6EBEE083157B470699645CCE4738A41D61081145E7B112523F68D2F5E879DB4EAC51C6698A693048314CA6EDC7A28252DAEA6F2045B24F4D7C333E146170112300000000000000000000000005553440000000000054F6F784A58F9EFB0A9EB90B83464F9D166461900', + hash: '71D0B4AA13277B32E2C2E751566BB0106764881B0CAA049905A0EDAC73257745', + }) }) it('signTransaction with a prepared payment', async function () { @@ -434,9 +455,13 @@ describe('Wallet', function () { LastLedgerSequence: 8819954, Fee: '12', } - const result = wallet.signTransaction(payment) - const expectedResult = - '12000022800000002400000017201B008694F261400000000000000168400000000000000C732102A8A44DB3D4C73EEEE11DFE54D2029103B776AA8A8D293A91D645977C9DF5F54474473045022100E8929B68B137AB2AAB1AD3A4BB253883B0C8C318DC8BB39579375751B8E54AC502206893B2D61244AFE777DAC9FA3D9DDAC7780A9810AF4B322D629784FD626B8CE481145E7B112523F68D2F5E879DB4EAC51C6698A693048314FDB08D07AAA0EB711793A3027304D688E10C3648' + const result = wallet.sign(payment) + const expectedResult = { + tx_blob: + '12000022800000002400000017201B008694F261400000000000000168400000000000000C732102A8A44DB3D4C73EEEE11DFE54D2029103B776AA8A8D293A91D645977C9DF5F54474473045022100E8929B68B137AB2AAB1AD3A4BB253883B0C8C318DC8BB39579375751B8E54AC502206893B2D61244AFE777DAC9FA3D9DDAC7780A9810AF4B322D629784FD626B8CE481145E7B112523F68D2F5E879DB4EAC51C6698A693048314FDB08D07AAA0EB711793A3027304D688E10C3648', + hash: 'AA1D2BDC59E504AA6C2416E864C615FB18042C1AB4457BEB883F7194D8C452B5', + } + assert.deepEqual(result, expectedResult) }) @@ -452,7 +477,7 @@ describe('Wallet', function () { Fee: '12', } assert.throws(() => { - wallet.signTransaction(payment) + wallet.sign(payment) }, /^1.1234567 is an illegal amount/u) }) }) diff --git a/test/wallet/signer.ts b/test/wallet/signer.ts index 9e231e83..2b2f4552 100644 --- a/test/wallet/signer.ts +++ b/test/wallet/signer.ts @@ -5,12 +5,13 @@ import { JsonObject } from 'ripple-binary-codec/dist/types/serialized-type' import { Transaction, ValidationError } from 'xrpl-local' import Wallet from 'xrpl-local/wallet' import { - sign, authorizeChannel, multisign, verifySignature, } from 'xrpl-local/wallet/signer' +import { SignedTxBlobHash } from '../../src/wallet' + const publicKey = '030E58CDD076E798C84755590AAF6237CA8FAE821070A59F648B517A30DC6F589D' const privateKey = @@ -70,91 +71,98 @@ const multisignTx1: Transaction = { SigningPubKey: '', } -const multisignTxToCombine1: Transaction = { - Account: 'rEuLyBCvcw4CFmzv8RepSiAoNgF8tTGJQC', - Fee: '30000', - Flags: 262144, - LimitAmount: { - currency: 'USD', - issuer: 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', - value: '100', - }, - Sequence: 2, - Signers: [ - { - Signer: { - Account: 'rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW', - SigningPubKey: - '02B3EC4E5DD96029A647CFA20DA07FE1F85296505552CCAC114087E66B46BD77DF', - TxnSignature: - '30450221009C195DBBF7967E223D8626CA19CF02073667F2B22E206727BFE848FF42BEAC8A022048C323B0BED19A988BDBEFA974B6DE8AA9DCAE250AA82BBD1221787032A864E5', - }, - }, - ], - SigningPubKey: '', - TransactionType: 'TrustSet', -} - -const multisignTxToCombine2: Transaction = { - Account: 'rEuLyBCvcw4CFmzv8RepSiAoNgF8tTGJQC', - Fee: '30000', - Flags: 262144, - LimitAmount: { - currency: 'USD', - issuer: 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', - value: '100', - }, - Sequence: 2, - Signers: [ - { - Signer: { - Account: 'rJvuSQhQR37czfxRou4vNWaM97uEhT4ShE', - SigningPubKey: - '02B78EEA571B2633180834CC6E7B4ED84FBF6811D12ECB59410E0C92D13B7726F5', - TxnSignature: - '304502210098009CEFA61EE9843BB7FC29B78CFFAACF28352A4A7CF3AAE79EF12D79BA50910220684F116266E5E4519A7A33F7421631EB8494082BE51A8B03FECCB3E59F77154A', - }, - }, - ], - SigningPubKey: '', - TransactionType: 'TrustSet', -} - -const expectedMultisign: string = encode({ - Account: 'rEuLyBCvcw4CFmzv8RepSiAoNgF8tTGJQC', - Fee: '30000', - Flags: 262144, - LimitAmount: { - currency: 'USD', - issuer: 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', - value: '100', - }, - Sequence: 2, - Signers: [ - { - Signer: { - Account: 'rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW', - SigningPubKey: - '02B3EC4E5DD96029A647CFA20DA07FE1F85296505552CCAC114087E66B46BD77DF', - TxnSignature: - '30450221009C195DBBF7967E223D8626CA19CF02073667F2B22E206727BFE848FF42BEAC8A022048C323B0BED19A988BDBEFA974B6DE8AA9DCAE250AA82BBD1221787032A864E5', - }, - }, - { - Signer: { - Account: 'rJvuSQhQR37czfxRou4vNWaM97uEhT4ShE', - SigningPubKey: - '02B78EEA571B2633180834CC6E7B4ED84FBF6811D12ECB59410E0C92D13B7726F5', - TxnSignature: - '304502210098009CEFA61EE9843BB7FC29B78CFFAACF28352A4A7CF3AAE79EF12D79BA50910220684F116266E5E4519A7A33F7421631EB8494082BE51A8B03FECCB3E59F77154A', - }, - }, - ], - SigningPubKey: '', - TransactionType: 'TrustSet', -}) - describe('Signer', function () { + let multisignTxToCombine1 + let multisignTxToCombine2 + let multisignJSON + let expectedMultisign + + beforeEach(function () { + multisignTxToCombine1 = { + Account: 'rEuLyBCvcw4CFmzv8RepSiAoNgF8tTGJQC', + Fee: '30000', + Flags: 262144, + LimitAmount: { + currency: 'USD', + issuer: 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', + value: '100', + }, + Sequence: 2, + Signers: [ + { + Signer: { + Account: 'rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW', + SigningPubKey: + '02B3EC4E5DD96029A647CFA20DA07FE1F85296505552CCAC114087E66B46BD77DF', + TxnSignature: + '30450221009C195DBBF7967E223D8626CA19CF02073667F2B22E206727BFE848FF42BEAC8A022048C323B0BED19A988BDBEFA974B6DE8AA9DCAE250AA82BBD1221787032A864E5', + }, + }, + ], + SigningPubKey: '', + TransactionType: 'TrustSet', + } as Transaction + + multisignTxToCombine2 = { + Account: 'rEuLyBCvcw4CFmzv8RepSiAoNgF8tTGJQC', + Fee: '30000', + Flags: 262144, + LimitAmount: { + currency: 'USD', + issuer: 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', + value: '100', + }, + Sequence: 2, + Signers: [ + { + Signer: { + Account: 'rJvuSQhQR37czfxRou4vNWaM97uEhT4ShE', + SigningPubKey: + '02B78EEA571B2633180834CC6E7B4ED84FBF6811D12ECB59410E0C92D13B7726F5', + TxnSignature: + '304502210098009CEFA61EE9843BB7FC29B78CFFAACF28352A4A7CF3AAE79EF12D79BA50910220684F116266E5E4519A7A33F7421631EB8494082BE51A8B03FECCB3E59F77154A', + }, + }, + ], + SigningPubKey: '', + TransactionType: 'TrustSet', + } as Transaction + + multisignJSON = { + Account: 'rEuLyBCvcw4CFmzv8RepSiAoNgF8tTGJQC', + Fee: '30000', + Flags: 262144, + LimitAmount: { + currency: 'USD', + issuer: 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', + value: '100', + }, + Sequence: 2, + Signers: [ + { + Signer: { + Account: 'rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW', + SigningPubKey: + '02B3EC4E5DD96029A647CFA20DA07FE1F85296505552CCAC114087E66B46BD77DF', + TxnSignature: + '30450221009C195DBBF7967E223D8626CA19CF02073667F2B22E206727BFE848FF42BEAC8A022048C323B0BED19A988BDBEFA974B6DE8AA9DCAE250AA82BBD1221787032A864E5', + }, + }, + { + Signer: { + Account: 'rJvuSQhQR37czfxRou4vNWaM97uEhT4ShE', + SigningPubKey: + '02B78EEA571B2633180834CC6E7B4ED84FBF6811D12ECB59410E0C92D13B7726F5', + TxnSignature: + '304502210098009CEFA61EE9843BB7FC29B78CFFAACF28352A4A7CF3AAE79EF12D79BA50910220684F116266E5E4519A7A33F7421631EB8494082BE51A8B03FECCB3E59F77154A', + }, + }, + ], + SigningPubKey: '', + TransactionType: 'TrustSet', + } + expectedMultisign = encode(multisignJSON) + }) it('sign', function () { // Test case data generated using this tutorial - https://xrpl.org/send-xrp.html#send-xrp const tx3: Transaction = { @@ -167,19 +175,21 @@ describe('Signer', function () { Fee: '12', Sequence: 20582260, } - const signedTxBlob = - '120000228000000024013A0F74201B013A0FC36140000000014FB18068400000000000000C732102A8A44DB3D4C73EEEE11DFE54D2029103B776AA8A8D293A91D645977C9DF5F544744730450221009ECB5324717E14DD6970126271F05BC2626D2A8FA9F3797555D417F8257C1E6002206BDD74A0F30425F2BA9DB69C90F21B3E27735C190FB4F3A640F066ACBBF06AD98114B3263BD0A9BF9DFDBBBBD07F536355FF477BF0E98314F667B0CA50CC7709A220B0561B85E53A48461FA8' + const signedTxResponse = { + tx_blob: + '120000228000000024013A0F74201B013A0FC36140000000014FB18068400000000000000C732102A8A44DB3D4C73EEEE11DFE54D2029103B776AA8A8D293A91D645977C9DF5F544744730450221009ECB5324717E14DD6970126271F05BC2626D2A8FA9F3797555D417F8257C1E6002206BDD74A0F30425F2BA9DB69C90F21B3E27735C190FB4F3A640F066ACBBF06AD98114B3263BD0A9BF9DFDBBBBD07F536355FF477BF0E98314F667B0CA50CC7709A220B0561B85E53A48461FA8', + hash: 'F73E975C70497A3DA61ADB76A3B39CD971A2DE017419A690BFAD6733B5FD8B3B', + } - const signedTx: string = sign(wallet, tx3) - - assert.equal(signedTx, signedTxBlob) + const signedTx: SignedTxBlobHash = wallet.sign(tx3) + assert.deepEqual(signedTx, signedTxResponse) }) it('sign in multisign format', function () { const multisignWallet = Wallet.fromSeed(unsignedSecret1) assert.deepEqual( - decode(sign(multisignWallet, unsignedTx1, true)), + decode(multisignWallet.sign(unsignedTx1, true).tx_blob), multisignTx1 as unknown as JsonObject, ) }) @@ -190,6 +200,17 @@ describe('Signer', function () { assert.deepEqual(multisign(transactions), expectedMultisign) }) + it('multisign runs successfully with X-address', function () { + multisignTxToCombine1.Account = + 'XVJfK5FpouB7gtk3kaZHqbgV4Bswir4ccz3rsJw9oMf71tc' + multisignTxToCombine2.Account = + 'XVJfK5FpouB7gtk3kaZHqbgV4Bswir4ccz3rsJw9oMf71tc' + + const transactions = [multisignTxToCombine1, multisignTxToCombine2] + + assert.deepEqual(multisign(transactions), expectedMultisign) + }) + it('multisign runs successfully with tx_blobs', function () { const transactions = [multisignTxToCombine1, multisignTxToCombine2] @@ -263,21 +284,23 @@ describe('Signer', function () { }) it('verifySignature succeeds for valid signed transaction blob', function () { - const signedTx = sign(verifyWallet, tx) + const signedTx = verifyWallet.sign(tx) - assert.isTrue(verifySignature(signedTx)) + assert.isTrue(verifySignature(signedTx.tx_blob)) }) it('verify succeeds for valid signed transaction object', function () { - const signedTx = sign(verifyWallet, tx) + const signedTx = verifyWallet.sign(tx) - assert.isTrue(verifySignature(decode(signedTx) as unknown as Transaction)) + assert.isTrue( + verifySignature(decode(signedTx.tx_blob) as unknown as Transaction), + ) }) it('verify throws for invalid signing key', function () { - const signedTx = sign(verifyWallet, tx) + const signedTx = verifyWallet.sign(tx) - const decodedTx = decode(signedTx) as unknown as Transaction + const decodedTx = decode(signedTx.tx_blob) as unknown as Transaction // Use a different key for validation decodedTx.SigningPubKey =