From 91a64137fe051a60a89eae462321da61badb67cd Mon Sep 17 00:00:00 2001 From: Chris Clark Date: Tue, 6 Oct 2015 12:45:05 -0700 Subject: [PATCH] BREAKING CHANGE: Use ripple-binary-codec --- npm-shrinkwrap.json | 36 +++++++++++++++++++ package.json | 1 + src/api/transaction/sign.js | 25 +++++++------ src/core/index.js | 1 + src/core/ledger.js | 2 +- src/core/orderbookutils.js | 16 ++------- src/core/transaction.js | 28 ++++++--------- src/core/transactionmanager.js | 4 +-- src/core/utils.js | 15 ++++++-- .../compute-ledger-hash-transactions.json | 14 ++++---- test/orderbook-autobridge-test.js | 11 ++++++ test/transaction-test.js | 22 ++++++------ 12 files changed, 111 insertions(+), 64 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 31e32b00..cab896f8 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -132,6 +132,42 @@ } } }, + "ripple-binary-codec": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/ripple-binary-codec/-/ripple-binary-codec-0.0.5.tgz", + "dependencies": { + "bn.js": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-3.2.0.tgz" + }, + "create-hash": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.1.2.tgz", + "dependencies": { + "cipher-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.1.tgz" + }, + "ripemd160": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-1.0.1.tgz" + }, + "sha.js": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.4.tgz" + } + } + }, + "decimal.js": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-4.0.3.tgz" + }, + "inherits": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz" + } + } + }, "ripple-keypairs": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/ripple-keypairs/-/ripple-keypairs-0.9.0.tgz", diff --git a/package.json b/package.json index 8b5bbdd0..7d1adeb6 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "lodash": "^3.1.0", "lru-cache": "~2.5.0", "ripple-address-codec": "^2.0.1", + "ripple-binary-codec": "^0.0.5", "ripple-keypairs": "^0.9.0", "ripple-lib-transactionparser": "^0.5.1", "ripple-lib-value": "0.1.0", diff --git a/src/api/transaction/sign.js b/src/api/transaction/sign.js index e5c7ee2f..ff86601e 100644 --- a/src/api/transaction/sign.js +++ b/src/api/transaction/sign.js @@ -2,7 +2,8 @@ 'use strict'; const utils = require('./utils'); const keypairs = require('ripple-keypairs'); -const core = utils.common.core; +const binary = require('ripple-binary-codec'); +const sha512 = require('hash.js').sha512; const validate = utils.common.validate; /** @@ -17,20 +18,22 @@ const validate = utils.common.validate; */ const HASH_TX_ID = 0x54584E00; // 'TXN' -function serialize(txJSON) { - return core.SerializedObject.from_json(txJSON); +// For a hash function, rippled uses SHA-512 and then truncates the result +// to the first 256 bytes. This algorithm, informally called SHA-512Half, +// provides an output that has comparable security to SHA-256, but runs +// faster on 64-bit processors. +function sha512half(buffer) { + return sha512().update(buffer).digest('hex').toUpperCase().slice(0, 64); } function hashSerialization(serialized, prefix) { - return serialized.hash(prefix || HASH_TX_ID).to_hex(); -} - -function signingData(txJSON) { - return core.Transaction.from_json(txJSON).signingData().buffer; + const hexPrefix = prefix.toString(16).toUpperCase(); + return sha512half(new Buffer(hexPrefix + serialized, 'hex')); } function computeSignature(txJSON, privateKey) { - return keypairs.sign(signingData(txJSON), privateKey); + const signingData = binary.encodeForSigning(txJSON); + return keypairs.sign(new Buffer(signingData, 'hex'), privateKey); } function sign(txJSON: string, secret: string @@ -44,9 +47,9 @@ function sign(txJSON: string, secret: string tx.SigningPubKey = keypair.publicKey; } tx.TxnSignature = computeSignature(tx, keypair.privateKey); - const serialized = serialize(tx); + const serialized = binary.encode(tx); return { - signedTransaction: serialized.to_hex(), + signedTransaction: serialized, id: hashSerialization(serialized, HASH_TX_ID) }; } diff --git a/src/core/index.js b/src/core/index.js index a109387c..a8d946f9 100644 --- a/src/core/index.js +++ b/src/core/index.js @@ -25,6 +25,7 @@ exports._test = { UInt128: require('./uint128').UInt128, UInt160: require('./uint160').UInt160, UInt256: require('./uint256').UInt256, + OrderbookUtils: require('./orderbookutils'), constants: require('./constants') }; diff --git a/src/core/ledger.js b/src/core/ledger.js index b1a773ca..f2d9df13 100644 --- a/src/core/ledger.js +++ b/src/core/ledger.js @@ -109,7 +109,7 @@ Ledger.prototype.calc_tx_hash = function() { const meta = SerializedObject.from_json(tx_json.metaData); const data = new SerializedObject(); - stypes.VariableLength.serialize(data, tx.serialize().to_hex()); + stypes.VariableLength.serialize(data, tx.serialize()); stypes.VariableLength.serialize(data, meta.to_hex()); tx_map.add_item(tx.hash(), data, SHAMapTreeNode.TYPE_TRANSACTION_MD); }); diff --git a/src/core/orderbookutils.js b/src/core/orderbookutils.js index eae49353..164188af 100644 --- a/src/core/orderbookutils.js +++ b/src/core/orderbookutils.js @@ -3,11 +3,10 @@ const _ = require('lodash'); const assert = require('assert'); const constants = require('./constants'); -const SerializedObject = require('./serializedobject').SerializedObject; -const Types = require('./serializedtypes'); const Amount = require('./amount').Amount; const Currency = require('./currency').Currency; const {IOUValue} = require('ripple-lib-value'); +const binary = require('ripple-binary-codec'); const OrderBookUtils = {}; function assertValidNumber(number, message) { @@ -143,11 +142,7 @@ OrderBookUtils.getOfferQuality = function(offer, currencyGets, currency_, OrderBookUtils.convertOfferQualityToHex = function(quality) { assert(quality instanceof Amount, 'Quality is not an amount'); - - const so = new SerializedObject(); - Types.Quality.serialize(so, quality.to_text()); - - return so.to_hex(); + return OrderBookUtils.convertOfferQualityToHex(quality.to_text()); }; /** @@ -160,14 +155,9 @@ OrderBookUtils.convertOfferQualityToHex = function(quality) { */ OrderBookUtils.convertOfferQualityToHexFromText = function(quality) { - - const so = new SerializedObject(); - Types.Quality.serialize(so, quality); - - return so.to_hex(); + return binary.encodeQuality(quality); }; - OrderBookUtils.CURRENCY_ONE = Currency.from_json(1); OrderBookUtils.ISSUER_ONE = constants.ACCOUNT_ONE; diff --git a/src/core/transaction.js b/src/core/transaction.js index 130ba1e7..8dbb366e 100644 --- a/src/core/transaction.js +++ b/src/core/transaction.js @@ -9,11 +9,11 @@ const utils = require('./utils'); const sjclcodec = require('sjcl-codec'); const Amount = require('./amount').Amount; const Currency = require('./currency').Currency; -const SerializedObject = require('./serializedobject').SerializedObject; const RippleError = require('./rippleerror').RippleError; const hashprefixes = require('./hashprefixes'); const log = require('./log').internal.sub('transaction'); const {isValidAddress, decodeAddress} = require('ripple-address-codec'); +const binary = require('ripple-binary-codec'); /** * @constructor Transaction @@ -451,7 +451,7 @@ Transaction.prototype.setCanonicalFlag = function() { }; Transaction.prototype.serialize = function() { - return SerializedObject.from_json(this.tx_json); + return binary.encode(this.tx_json); }; Transaction.prototype.signingHash = function(testnet) { @@ -459,22 +459,16 @@ Transaction.prototype.signingHash = function(testnet) { }; Transaction.prototype.signingData = function() { - const so = new SerializedObject(); - so.append(hashprefixes.HASH_TX_SIGN_BYTES); - so.parse_json(this.tx_json); - return so; + return binary.encodeForSigning(this.tx_json); }; Transaction.prototype.multiSigningData = function(account) { - const so = new SerializedObject(); - so.append(hashprefixes.HASH_TX_MULTISIGN_BYTES); - so.parse_json(this.tx_json); - so.append(decodeAddress(account)); - return so; + return binary.encodeForMultisigning(this.tx_json, account); }; -Transaction.prototype.hash = function(prefix_, asUINT256, serialized) { +Transaction.prototype.hash = function(prefix_, serialized_) { let prefix; + assert(serialized_ === undefined || _.isString(serialized_)); if (typeof prefix_ !== 'string') { prefix = hashprefixes.HASH_TX_ID; @@ -484,9 +478,9 @@ Transaction.prototype.hash = function(prefix_, asUINT256, serialized) { prefix = hashprefixes[prefix_]; } - const hash = (serialized || this.serialize()).hash(prefix); - - return asUINT256 ? hash : hash.to_hex(); + const hexPrefix = prefix.toString(16).toUpperCase(); + const serialized = serialized_ || this.serialize(); + return utils.sha512half(new Buffer(hexPrefix + serialized, 'hex')); }; Transaction.prototype.sign = function(secret) { @@ -505,7 +499,7 @@ Transaction.prototype.sign = function(secret) { } const keypair = deriveKeypair(secret || this._secret); - this.tx_json.TxnSignature = sign(this.signingData().buffer, + this.tx_json.TxnSignature = sign(new Buffer(this.signingData(), 'hex'), keypair.privateKey); this.previousSigningHash = hash; @@ -1654,7 +1648,7 @@ Transaction.prototype.multiSign = function(account, secret) { const signer = { Account: account, - TxnSignature: sign(signingData.buffer, keypair.privateKey), + TxnSignature: sign(new Buffer(signingData, 'hex'), keypair.privateKey), SigningPubKey: keypair.publicKey }; diff --git a/src/core/transactionmanager.js b/src/core/transactionmanager.js index c741bf26..a19b9fc0 100644 --- a/src/core/transactionmanager.js +++ b/src/core/transactionmanager.js @@ -524,9 +524,9 @@ TransactionManager.prototype._prepareRequest = function(tx) { tx.sign(); const serialized = tx.serialize(); - submitRequest.txBlob(serialized.to_hex()); + submitRequest.txBlob(serialized); - const hash = tx.hash(null, null, serialized); + const hash = tx.hash(null, serialized); tx.addId(hash); } else { if (tx.hasMultiSigners()) { diff --git a/src/core/utils.js b/src/core/utils.js index a1ebd8cb..c99264a7 100644 --- a/src/core/utils.js +++ b/src/core/utils.js @@ -1,4 +1,13 @@ 'use strict'; +const sha512 = require('hash.js').sha512; + +// For a hash function, rippled uses SHA-512 and then truncates the result +// to the first 256 bytes. This algorithm, informally called SHA-512Half, +// provides an output that has comparable security to SHA-256, but runs +// faster on 64-bit processors. +function sha512half(buffer) { + return sha512().update(buffer).digest('hex').toUpperCase().slice(0, 64); +} // returns the mantissa from the passed in string, // adding zeros until it has 16 sd @@ -21,7 +30,7 @@ function getMantissaDecimalString(bignum) { function trace(comment, func) { return function() { - console.log('%s: %s', trace, arguments.toString); + console.log('%s: %s', comment, arguments.toString); func(arguments); }; } @@ -112,7 +121,8 @@ function assert(assertion, msg) { * @return {Array} unique values (for string representation of value) in `arr` */ function arrayUnique(arr) { - const u = {}, a = []; + const u = {}; + const a = []; for (let i = 0, l = arr.length; i < l; i++) { const k = arr[i]; @@ -151,6 +161,7 @@ exports.time = { toRipple: fromTimestamp }; +exports.sha512half = sha512half; exports.trace = trace; exports.arraySet = arraySet; exports.hexToString = hexToString; diff --git a/test/fixtures/api/requests/compute-ledger-hash-transactions.json b/test/fixtures/api/requests/compute-ledger-hash-transactions.json index 2cad11d2..09668312 100644 --- a/test/fixtures/api/requests/compute-ledger-hash-transactions.json +++ b/test/fixtures/api/requests/compute-ledger-hash-transactions.json @@ -1,6 +1,6 @@ [ { - "hash": "f8f337dee5d5b238a10af4a4d56926ba26c83ee7af5a5a6474340c56f9252df3", + "hash": "F8F337DEE5D5B238A10AF4A4D56926BA26C83EE7AF5A5A6474340C56F9252DF3", "date": "2015-08-12T01:01:10+00:00", "ledger_index": 15202439, "tx": { @@ -60,7 +60,7 @@ } }, { - "hash": "f8d5de632b1d8b64e577c46912cce483d6df4fd4e2cf4a3d586a099de3b27021", + "hash": "F8D5DE632B1D8B64E577C46912CCE483D6DF4FD4E2CF4A3D586A099DE3B27021", "date": "2015-08-12T01:01:10+00:00", "ledger_index": 15202439, "tx": { @@ -120,7 +120,7 @@ } }, { - "hash": "e9004490a92413e92dacd621ac73fd434a8950c350f7572ffeaf4d6aaf8fc288", + "hash": "E9004490A92413E92DACD621AC73FD434A8950C350F7572FFEAF4D6AAF8FC288", "date": "2015-08-12T01:01:10+00:00", "ledger_index": 15202439, "tx": { @@ -180,7 +180,7 @@ } }, { - "hash": "d44bff924d23211b82b8f604af6d92f260f8dd13103a96f03e48825c4a978fd6", + "hash": "D44BFF924D23211B82B8F604AF6D92F260F8DD13103A96F03E48825C4A978FD6", "date": "2015-08-12T01:01:10+00:00", "ledger_index": 15202439, "tx": { @@ -240,7 +240,7 @@ } }, { - "hash": "c978d915bfb17687335cbfc4b207d9e7213bcee35b468c2eee016cdce4edb6e4", + "hash": "C978D915BFB17687335CBFC4B207D9E7213BCEE35B468C2EEE016CDCE4EDB6E4", "date": "2015-08-12T01:01:10+00:00", "ledger_index": 15202439, "tx": { @@ -372,7 +372,7 @@ } }, { - "hash": "31b34fd7c90cdc6cf680a814debc6f616c69275c0e99711f904de088a8ed4b28", + "hash": "31B34FD7C90CDC6CF680A814DEBC6F616C69275C0E99711F904DE088A8ED4B28", "date": "2015-08-12T01:01:10+00:00", "ledger_index": 15202439, "tx": { @@ -412,7 +412,7 @@ } }, { - "hash": "260bc2964ffe6d81cb25c152f8054ffb2ce6ed04ff89d8d0d0559bc14bef0e46", + "hash": "260BC2964FFE6D81CB25C152F8054FFB2CE6ED04FF89D8D0D0559BC14BEF0E46", "date": "2015-08-12T01:01:10+00:00", "ledger_index": 15202439, "tx": { diff --git a/test/orderbook-autobridge-test.js b/test/orderbook-autobridge-test.js index 2345d8af..09e6d0b3 100644 --- a/test/orderbook-autobridge-test.js +++ b/test/orderbook-autobridge-test.js @@ -6,6 +6,7 @@ const _ = require('lodash'); const assert = require('assert-diff'); const Remote = require('ripple-lib').Remote; const Currency = require('ripple-lib').Currency; +const OrderbookUtils = require('ripple-lib')._test.OrderbookUtils; const addresses = require('./fixtures/addresses'); const fixtures = require('./fixtures/orderbook'); const IOUValue = require('ripple-lib-value').IOUValue; @@ -843,4 +844,14 @@ describe('OrderBook Autobridging', function() { }); }); + + it('convertOfferQualityToHexFromText', function() { + const bookDirectory = + '4627DFFCFF8B5A265EDBD8AE8C14A52325DBFEDAF4F5C32E5D06F4C3362FE1D0'; + const quality = '195796912.5171664'; + assert.strictEqual( + OrderbookUtils.convertOfferQualityToHexFromText(quality), + bookDirectory.slice(-16) + ); + }); }); diff --git a/test/transaction-test.js b/test/transaction-test.js index 50b90fcc..31cb7fdf 100644 --- a/test/transaction-test.js +++ b/test/transaction-test.js @@ -345,7 +345,7 @@ describe('Transaction', function() { remote.setSecret(src, addresses.SECRET); assert(transaction.complete()); - const json = transaction.serialize().to_json(); + const json = transaction.serialize(); assert.notStrictEqual(json.Fee, '66500000', 'Fee == 66500000, i.e. 66.5 XRP!'); }); @@ -584,7 +584,7 @@ describe('Transaction', function() { SigningPubKey: '021FED5FD081CE5C4356431267D04C6E2167E4112C897D5E10335D4E22B4DA49ED', Account: 'rMWwx3Ma16HnqSd4H6saPisihX9aKpXxHJ', Flags: 0, - Fee: 10, + Fee: '10', Sequence: 1, TransactionType: 'AccountSet' }; @@ -602,10 +602,9 @@ describe('Transaction', function() { const tx = Transaction.from_json(tx_json); const data = tx.signingData(); - assert.strictEqual(data.hash().to_json(), - expectedSigningHash); + assert.strictEqual(tx.signingHash(), expectedSigningHash); - assert.strictEqual(data.to_hex(), + assert.strictEqual(data, ('535458001200032200000000240000000168400000000000000' + 'A7321021FED5FD081CE5C4356431267D04C6E2167E4112C897D' + '5E10335D4E22B4DA49ED8114E0E6E281CA324AEE034B2BB8AC9' + @@ -619,7 +618,7 @@ describe('Transaction', function() { transaction.tx_json.SigningPubKey = '021FED5FD081CE5C4356431267D04C6E2167E4112C897D5E10335D4E22B4DA49ED'; transaction.tx_json.Account = 'rMWwx3Ma16HnqSd4H6saPisihX9aKpXxHJ'; transaction.tx_json.Flags = 0; - transaction.tx_json.Fee = 10; + transaction.tx_json.Fee = '10'; transaction.tx_json.Sequence = 1; transaction.tx_json.TransactionType = 'AccountSet'; @@ -634,7 +633,7 @@ describe('Transaction', function() { transaction.tx_json.SigningPubKey = '021FED5FD081CE5C4356431267D04C6E2167E4112C897D5E10335D4E22B4DA49ED'; transaction.tx_json.Account = 'rMWwx3Ma16HnqSd4H6saPisihX9aKpXxHJ'; transaction.tx_json.Flags = 0; - transaction.tx_json.Fee = 10; + transaction.tx_json.Fee = '10'; transaction.tx_json.Sequence = 1; transaction.tx_json.TransactionType = 'AccountSet'; @@ -650,7 +649,7 @@ describe('Transaction', function() { transaction.tx_json.SigningPubKey = '021FED5FD081CE5C4356431267D04C6E2167E4112C897D5E10335D4E22B4DA49ED'; transaction.tx_json.Account = 'rMWwx3Ma16HnqSd4H6saPisihX9aKpXxHJ'; transaction.tx_json.Flags = 0; - transaction.tx_json.Fee = 10; + transaction.tx_json.Fee = '10'; transaction.tx_json.Sequence = 1; transaction.tx_json.TransactionType = 'AccountSet'; @@ -840,7 +839,7 @@ describe('Transaction', function() { const transaction = Transaction.from_json(input_json); - assert.deepEqual(transaction.serialize().to_hex(), expected_hex); + assert.deepEqual(transaction.serialize(), expected_hex); }); it('Sign transaction', function(done) { @@ -849,7 +848,7 @@ describe('Transaction', function() { transaction.tx_json.SigningPubKey = '021FED5FD081CE5C4356431267D04C6E2167E4112C897D5E10335D4E22B4DA49ED'; transaction.tx_json.Account = 'rMWwx3Ma16HnqSd4H6saPisihX9aKpXxHJ'; transaction.tx_json.Flags = 0; - transaction.tx_json.Fee = 10; + transaction.tx_json.Fee = '10'; transaction.tx_json.Sequence = 1; transaction.tx_json.TransactionType = 'AccountSet'; @@ -2261,7 +2260,8 @@ describe('Transaction', function() { const abytes = decodeAddress(a1); const prefix = require('ripple-lib')._test.HashPrefixes.HASH_TX_MULTISIGN_BYTES; - assert.deepEqual(d1.buffer, prefix.concat(tbytes, abytes)); + assert.deepEqual(new Buffer(d1, 'hex'), + new Buffer(prefix.concat(tbytes, abytes))); }); it('Multisign', function() {