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
This commit is contained in:
Mukul Jangid
2021-10-08 18:33:31 -04:00
committed by GitHub
parent 9ad2b28172
commit f93b1f241e
10 changed files with 247 additions and 197 deletions

View File

@@ -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<SubmitResponse> {
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<TxResponse> {
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)
}
/**

View File

@@ -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),
}
}
/**

View File

@@ -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 }

View File

@@ -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')

View File

@@ -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)

View File

@@ -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:

View File

@@ -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,

View File

@@ -44,8 +44,9 @@ export async function generateFundedWallet(client: Client): Promise<Wallet> {
export async function verifySubmittedTransaction(
client: Client,
tx: Transaction | string,
hashTx?: string,
): Promise<void> {
const hash = hashSignedTx(tx)
const hash = hashTx ?? hashSignedTx(tx)
const data = await client.request({
command: 'tx',
transaction: hash,

View File

@@ -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)
})
})

View File

@@ -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 =