Skip to content

Commit

Permalink
build(sdk-coin-xrp): update ripple libs
Browse files Browse the repository at this point in the history
BREAKING CHANGE: explainTransaction for XRP transaction now computes
a different transaction hash than previous versions. This is because
xrpl 2.x uses different hash prefixes for computing signed and
unsigned transaction hashes

Ticket: WIN-3541
  • Loading branch information
hitansh-bitgo committed Oct 3, 2024
1 parent a8319bf commit 58d17f7
Show file tree
Hide file tree
Showing 5 changed files with 211 additions and 372 deletions.
8 changes: 4 additions & 4 deletions modules/sdk-coin-xrp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,10 @@
"@bitgo/utxo-lib": "^10.3.0",
"bignumber.js": "^9.0.0",
"lodash": "^4.17.14",
"ripple-address-codec": "~4.1.3",
"ripple-binary-codec": "~0.2.4",
"ripple-keypairs": "^0.11.0",
"ripple-lib": "~1.4.1"
"ripple-address-codec": "^5.0.0",
"ripple-binary-codec": "^2.1.0",
"ripple-keypairs": "^2.0.0",
"xrpl": "^4.0.0"
},
"devDependencies": {
"@bitgo/sdk-api": "^1.54.3",
Expand Down
11 changes: 3 additions & 8 deletions modules/sdk-coin-xrp/src/ripple.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,10 @@
/**
*/
import * as rippleKeypairs from 'ripple-keypairs';
import * as ripple from 'ripple-lib';
import * as xrpl from 'xrpl';
import { ECPair } from '@bitgo/utxo-lib';

import * as binary from 'ripple-binary-codec';
import { computeBinaryTransactionHash } from 'ripple-lib/dist/npm/common/hashes';

function computeSignature(tx, privateKey, signAs) {
const signingData = signAs ? binary.encodeForMultisigning(tx, signAs) : binary.encodeForSigning(tx);
Expand Down Expand Up @@ -67,12 +66,8 @@ const signWithPrivateKey = function (txHex, privateKey, options) {
const serialized = binary.encode(tx);
return {
signedTransaction: serialized,
id: computeBinaryTransactionHash(serialized),
id: xrpl.hashes.hashSignedTx(serialized),
};
};

export = (params): ripple.RippleAPI => {
const rippleLib = new ripple.RippleAPI(params);
(rippleLib as any).signWithPrivateKey = signWithPrivateKey;
return rippleLib;
};
export = { ...xrpl, signWithPrivateKey };
34 changes: 20 additions & 14 deletions modules/sdk-coin-xrp/src/xrp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ import * as _ from 'lodash';
import * as url from 'url';
import * as querystring from 'querystring';

import * as xrpl from 'xrpl';
import * as rippleAddressCodec from 'ripple-address-codec';
import * as rippleBinaryCodec from 'ripple-binary-codec';
import { computeBinaryTransactionHash } from 'ripple-lib/dist/npm/common/hashes';
import * as rippleKeypairs from 'ripple-keypairs';
import {
BaseCoin,
Expand All @@ -31,7 +31,7 @@ import {
VerifyTransactionOptions,
} from '@bitgo/sdk-core';

const ripple = require('./ripple');
import ripple from './ripple';

interface Address {
address: string;
Expand Down Expand Up @@ -262,9 +262,7 @@ export class Xrp extends BaseCoin {
}
const userAddress = rippleKeypairs.deriveAddress(userKey.publicKey.toString('hex'));

const rippleLib = ripple();

const tx = rippleLib.signWithPrivateKey(txPrebuild.txHex, userPrivateKey.toString('hex'), {
const tx = ripple.signWithPrivateKey(txPrebuild.txHex, userPrivateKey.toString('hex'), {
signAs: userAddress,
});

Expand Down Expand Up @@ -319,7 +317,14 @@ export class Xrp extends BaseCoin {
throw new Error('txHex needs to be either hex or JSON string for XRP');
}
}
const id = computeBinaryTransactionHash(txHex as string);
let id: string;
// hashes ids are different for signed and unsigned tx
// first we try to get the hash id as if it is signed, will throw if its not
try {
id = xrpl.hashes.hashSignedTx(txHex);
} catch (e) {
id = xrpl.hashes.hashTx(txHex);
}

if (transaction.TransactionType == 'AccountSet') {
return {
Expand Down Expand Up @@ -587,30 +592,31 @@ export class Xrp extends BaseCoin {
coin: this.getChain(),
};
}
const rippleLib = ripple();

if (!keys[0].privateKey) {
throw new Error(`userKey is not a private key`);
}
const userKey = keys[0].privateKey.toString('hex');
const userSignature = rippleLib.signWithPrivateKey(txJSON, userKey, { signAs: userAddress });
const userSignature = ripple.signWithPrivateKey(txJSON, userKey, { signAs: userAddress });

let signedTransaction;
let signedTransaction: string;

if (isKrsRecovery) {
signedTransaction = userSignature;
signedTransaction = userSignature.signedTransaction;
} else {
if (!keys[1].privateKey) {
throw new Error(`backupKey is not a private key`);
}
const backupKey = keys[1].privateKey.toString('hex');
const backupSignature = rippleLib.signWithPrivateKey(txJSON, backupKey, { signAs: backupAddress });
signedTransaction = rippleLib.combine([userSignature.signedTransaction, backupSignature.signedTransaction]);
const backupSignature = ripple.signWithPrivateKey(txJSON, backupKey, { signAs: backupAddress });
signedTransaction = ripple.multisign([userSignature.signedTransaction, backupSignature.signedTransaction]);
}

const transactionExplanation: RecoveryInfo = (await this.explainTransaction({
txHex: signedTransaction.signedTransaction,
txHex: signedTransaction,
})) as any;
transactionExplanation.txHex = signedTransaction.signedTransaction;

transactionExplanation.txHex = signedTransaction;

if (isKrsRecovery) {
transactionExplanation.backupKey = params.backupKey;
Expand Down
27 changes: 14 additions & 13 deletions modules/sdk-coin-xrp/test/unit/xrp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import 'should';
import { TestBitGo, TestBitGoAPI } from '@bitgo/sdk-test';
import { BitGoAPI } from '@bitgo/sdk-api';
import { Txrp } from '../../src/txrp';
const ripple = require('../../src/ripple');
import ripple from '../../src/ripple';

import * as nock from 'nock';
import assert from 'assert';
Expand Down Expand Up @@ -91,7 +91,7 @@ describe('XRP:', function () {
txHex:
'{"TransactionType":"Payment","Account":"rBSpCz8PafXTJHppDcNnex7dYnbe3tSuFG","Destination":"rfjub8A4dpSD5nnszUFTsLprxu1W398jwc","DestinationTag":0,"Amount":"253481","Flags":2147483648,"LastLedgerSequence":1626225,"Fee":"45","Sequence":7}',
});
unsignedExplanation.id.should.equal('CB36F366F1AC25FCDB38A19F17384ED3509D9B7F063520034597852FB10A1B45');
unsignedExplanation.id.should.equal('37486621138DFB0C55FEF45FD275B565254464651A04CB02EE371F8C4A84D8CA');
signedExplanation.id.should.equal('D52681436CC5B94E9D00BC8172047B1A6F3C028D2D0A5CDFB81680039C48ADFD');
unsignedExplanation.outputAmount.should.equal('253481');
signedExplanation.outputAmount.should.equal('253481');
Expand Down Expand Up @@ -122,7 +122,7 @@ describe('XRP:', function () {
txHex:
'{"TransactionType":"AccountSet","Account":"r95xbEHFzDfc9XfmXHaDnj6dHNntT9RNcy","Fee":"45","Sequence":15070378,"LastLedgerSequence":15320391,"MessageKey":"02000000000000000000000000415F8315C9948AD91E2CCE5B8583A36DA431FB61"}',
});
unsignedExplanation.id.should.equal('69E8A046124F15749BF75554D82F19282C1FECAA9785444FCC21107528741EDD');
unsignedExplanation.id.should.equal('A0F2AF7A3E0936BCFEE0D047789502D01518D9A4F1287D50568D66474475B3E7');
unsignedExplanation.accountSet.messageKey.should.equal(
'02000000000000000000000000415F8315C9948AD91E2CCE5B8583A36DA431FB61'
);
Expand All @@ -141,17 +141,19 @@ describe('XRP:', function () {
xrpAddress: 'rJBWFy35Ya3qDZD89DuWBwm8oBbYmqb3H9',
};

const rippleLib = ripple();
const fullySigned = rippleLib.signWithPrivateKey(halfSignedTxHex, signer.rawPrv, {
const fullySigned = ripple.signWithPrivateKey(halfSignedTxHex, signer.rawPrv, {
signAs: signer.xrpAddress,
});

const signedTransaction = rippleBinaryCodec.decode(fullySigned.signedTransaction);
signedTransaction.TransactionType.should.equal('Payment');
signedTransaction.Amount.should.equal('14999970');
signedTransaction.Account.should.equal('rBfhJ6HopLW69xK83nyShdNxC3uggjs46K');
signedTransaction.Destination.should.equal('rKuDJCu188nbLDs2zfaT2RNScS6aa63PLC');
signedTransaction.Signers.length.should.equal(2);
signedTransaction.should.containDeep({
TransactionType: 'Payment',
Amount: '14999970',
Account: 'rBfhJ6HopLW69xK83nyShdNxC3uggjs46K',
Destination: 'rKuDJCu188nbLDs2zfaT2RNScS6aa63PLC',
});
assert(Array.isArray(signedTransaction.Signers));
(signedTransaction.Signers as Array<string>).length.should.equal(2);
});

it('should be able to cosign XRP transaction in any form', function () {
Expand All @@ -168,11 +170,10 @@ describe('XRP:', function () {
xrpAddress: 'rJBWFy35Ya3qDZD89DuWBwm8oBbYmqb3H9',
};

const rippleLib = ripple();
const coSignedHexTransaction = rippleLib.signWithPrivateKey(unsignedTxHex, signer.rawPrv, {
const coSignedHexTransaction = ripple.signWithPrivateKey(unsignedTxHex, signer.rawPrv, {
signAs: signer.xrpAddress,
});
const coSignedJsonTransaction = rippleLib.signWithPrivateKey(unsignedTxJson, signer.rawPrv, {
const coSignedJsonTransaction = ripple.signWithPrivateKey(unsignedTxJson, signer.rawPrv, {
signAs: signer.xrpAddress,
});
coSignedHexTransaction.signedTransaction.should.equal(coSignedJsonTransaction.signedTransaction);
Expand Down
Loading

0 comments on commit 58d17f7

Please sign in to comment.