From bb918107ff62dc611de2ce5c274e01aa68a5fec4 Mon Sep 17 00:00:00 2001 From: Nicholas Dudfield Date: Mon, 22 Jun 2015 19:37:53 +0700 Subject: [PATCH] Add Transaction.signingData --- src/core/hashprefixes.js | 21 +++++-- src/core/serializedobject.js | 104 ++++++++++++++++++----------------- src/core/transaction.js | 21 ++++--- test/transaction-test.js | 45 ++++++++++----- 4 files changed, 115 insertions(+), 76 deletions(-) diff --git a/src/core/hashprefixes.js b/src/core/hashprefixes.js index b4ce7405c1..e1dfa4d6a8 100644 --- a/src/core/hashprefixes.js +++ b/src/core/hashprefixes.js @@ -1,3 +1,10 @@ +'use strict'; + +// TODO: move in helpers from serializedtypes to utils +function toBytes(n) { + return [n >>> 24, (n >>> 16) & 0xff, (n >>> 8) & 0xff, n & 0xff]; +} + /** * Prefix for hashing functions. * @@ -12,14 +19,18 @@ */ // transaction plus signature to give transaction ID -exports.HASH_TX_ID = 0x54584E00; // 'TXN' +exports.HASH_TX_ID = 0x54584E00; // 'TXN' // transaction plus metadata -exports.HASH_TX_NODE = 0x534E4400; // 'TND' +exports.HASH_TX_NODE = 0x534E4400; // 'TND' // inner node in tree -exports.HASH_INNER_NODE = 0x4D494E00; // 'MIN' +exports.HASH_INNER_NODE = 0x4D494E00; // 'MIN' // leaf node in tree -exports.HASH_LEAF_NODE = 0x4D4C4E00; // 'MLN' +exports.HASH_LEAF_NODE = 0x4D4C4E00; // 'MLN' // inner transaction to sign -exports.HASH_TX_SIGN = 0x53545800; // 'STX' +exports.HASH_TX_SIGN = 0x53545800; // 'STX' // inner transaction to sign (TESTNET) exports.HASH_TX_SIGN_TESTNET = 0x73747800; // 'stx' + +Object.keys(exports).forEach(function(k) { + exports[k + '_BYTES'] = toBytes(exports[k]); +}); diff --git a/src/core/serializedobject.js b/src/core/serializedobject.js index b6c3b1bb6f..5475d0fafc 100644 --- a/src/core/serializedobject.js +++ b/src/core/serializedobject.js @@ -49,58 +49,10 @@ function SerializedObject(buf) { } this.pointer = 0; } - -SerializedObject.from_json = function(obj_) { - // Create a copy of the object so we don't modify it - const obj = extend(true, {}, obj_); - + +SerializedObject.from_json = function(obj) { const so = new SerializedObject(); - let typedef; - - if (typeof obj.TransactionType === 'number') { - obj.TransactionType = SerializedObject.lookup_type_tx(obj.TransactionType); - if (!obj.TransactionType) { - throw new Error('Transaction type ID is invalid.'); - } - } - - if (typeof obj.LedgerEntryType === 'number') { - obj.LedgerEntryType = SerializedObject.lookup_type_le(obj.LedgerEntryType); - - if (!obj.LedgerEntryType) { - throw new Error('LedgerEntryType ID is invalid.'); - } - } - - if (typeof obj.TransactionType === 'string') { - typedef = binformat.tx[obj.TransactionType]; - if (!Array.isArray(typedef)) { - throw new Error('Transaction type is invalid'); - } - - typedef = typedef.slice(); - obj.TransactionType = typedef.shift(); - } else if (typeof obj.LedgerEntryType === 'string') { - typedef = binformat.ledger[obj.LedgerEntryType]; - - if (!Array.isArray(typedef)) { - throw new Error('LedgerEntryType is invalid'); - } - - typedef = typedef.slice(); - obj.LedgerEntryType = typedef.shift(); - - } else if (typeof obj.AffectedNodes === 'object') { - typedef = binformat.metadata; - } else { - throw new Error('Object to be serialized must contain either' + - ' TransactionType, LedgerEntryType or AffectedNodes.'); - } - - // ND: This from_*json* seems a reasonable place to put validation of `json` - SerializedObject.check_fields(typedef, obj); - so.serialize(typedef, obj); - + so.parse_json(obj); return so; }; @@ -154,6 +106,56 @@ SerializedObject.check_fields = function(typedef, obj) { throw new Error(errorMessage); }; +SerializedObject.prototype.parse_json = function(obj_) { + const so = this; + // Create a copy of the object so we don't modify it + const obj = extend(true, {}, obj_); + let typedef; + + if (typeof obj.TransactionType === 'number') { + obj.TransactionType = SerializedObject.lookup_type_tx(obj.TransactionType); + if (!obj.TransactionType) { + throw new Error('Transaction type ID is invalid.'); + } + } + + if (typeof obj.LedgerEntryType === 'number') { + obj.LedgerEntryType = SerializedObject.lookup_type_le(obj.LedgerEntryType); + + if (!obj.LedgerEntryType) { + throw new Error('LedgerEntryType ID is invalid.'); + } + } + + if (typeof obj.TransactionType === 'string') { + typedef = binformat.tx[obj.TransactionType]; + if (!Array.isArray(typedef)) { + throw new Error('Transaction type is invalid'); + } + + typedef = typedef.slice(); + obj.TransactionType = typedef.shift(); + } else if (typeof obj.LedgerEntryType === 'string') { + typedef = binformat.ledger[obj.LedgerEntryType]; + + if (!Array.isArray(typedef)) { + throw new Error('LedgerEntryType is invalid'); + } + + typedef = typedef.slice(); + obj.LedgerEntryType = typedef.shift(); + + } else if (typeof obj.AffectedNodes === 'object') { + typedef = binformat.metadata; + } else { + throw new Error('Object to be serialized must contain either' + + ' TransactionType, LedgerEntryType or AffectedNodes.'); + } + + SerializedObject.check_fields(typedef, obj); + so.serialize(typedef, obj); +}; + SerializedObject.prototype.append = function(bytes_) { const bytes = bytes_ instanceof SerializedObject ? bytes_.buffer : bytes_; diff --git a/src/core/transaction.js b/src/core/transaction.js index f36ce9fed3..f9285cac33 100644 --- a/src/core/transaction.js +++ b/src/core/transaction.js @@ -274,7 +274,7 @@ Transaction.prototype.getManager = function(account_) { return undefined; } - let account = account_ || this.tx_json.Account; + const account = account_ || this.tx_json.Account; return this.remote.account(account)._transactionManager; }; @@ -290,7 +290,7 @@ Transaction.prototype._accountSecret = function(account_) { return undefined; } - let account = account_ || this.tx_json.Account; + const account = account_ || this.tx_json.Account; return this.remote.secrets[account]; }; @@ -326,7 +326,7 @@ Transaction.prototype._computeFee = function() { const fees = [ ]; for (let i = 0; i < servers.length; i++) { - let server = servers[i]; + const server = servers[i]; if (server.isConnected()) { fees.push(Number(server._computeFee(this._getFeeUnits()))); } @@ -435,6 +435,13 @@ Transaction.prototype.signingHash = function(testnet) { return this.hash(testnet ? 'HASH_TX_SIGN_TESTNET' : 'HASH_TX_SIGN'); }; +Transaction.prototype.signingData = function() { + const so = new SerializedObject(); + so.append(hashprefixes.HASH_TX_SIGN_BYTES); + so.parse_json(this.tx_json); + return so; +}; + Transaction.prototype.hash = function(prefix_, asUINT256, serialized) { let prefix; @@ -770,7 +777,7 @@ Transaction.prototype.addMemo = function(options_) { const memo = {}; const memoRegex = Transaction.MEMO_REGEX; let memoType = options.memoType; - let memoFormat = options.memoFormat; + const memoFormat = options.memoFormat; let memoData = options.memoData; if (memoType) { @@ -918,7 +925,7 @@ Transaction.prototype.transferRate = function(rate) { // } /* eslint-enable max-len */ - let transferRate = rate; + const transferRate = rate; if (transferRate === 0) { // Clear TransferRate @@ -1323,7 +1330,7 @@ Transaction.prototype.setExpiration = function(expiration) { // throw new Error('TransactionType must be OfferCreate to use Expiration'); // } - let timeOffset = expiration instanceof Date + const timeOffset = expiration instanceof Date ? expiration.getTime() : expiration; @@ -1433,7 +1440,7 @@ Transaction.prototype.abort = function() { Transaction.prototype.getSummary = Transaction.prototype.summary = function() { - let txSummary = { + const txSummary = { tx_json: this.tx_json, clientID: this._clientID, submittedIDs: this.submittedIDs, diff --git a/test/transaction-test.js b/test/transaction-test.js index 8ed048e31e..35ea73eabc 100644 --- a/test/transaction-test.js +++ b/test/transaction-test.js @@ -553,19 +553,38 @@ describe('Transaction', function() { done(); }); - it('Get signing hash', function(done) { - const transaction = new Transaction(); - transaction._secret = 'sh2pTicynUEG46jjR4EoexHcQEoij'; - transaction.tx_json.SigningPubKey = '021FED5FD081CE5C4356431267D04C6E2167E4112C897D5E10335D4E22B4DA49ED'; - transaction.tx_json.Account = 'rMWwx3Ma16HnqSd4H6saPisihX9aKpXxHJ'; - transaction.tx_json.Flags = 0; - transaction.tx_json.Fee = 10; - transaction.tx_json.Sequence = 1; - transaction.tx_json.TransactionType = 'AccountSet'; + describe('signing', function() { + const tx_json = { + SigningPubKey: '021FED5FD081CE5C4356431267D04C6E2167E4112C897D5E10335D4E22B4DA49ED', + Account: 'rMWwx3Ma16HnqSd4H6saPisihX9aKpXxHJ', + Flags: 0, + Fee: 10, + Sequence: 1, + TransactionType: 'AccountSet' + }; - assert.strictEqual(transaction.signingHash(), 'D1C15200CF532175F1890B6440AD223D3676140522BC11D2784E56760AE3B4FE'); + const expectedSigningHash = + 'D1C15200CF532175F1890B6440AD223D3676140522BC11D2784E56760AE3B4FE'; - done(); + it('Get signing hash', function() { + const transaction = Transaction.from_json(tx_json); + transaction._secret = 'sh2pTicynUEG46jjR4EoexHcQEoij'; + assert.strictEqual(transaction.signingHash(), expectedSigningHash); + }); + + it('Get signing data', function() { + const tx = Transaction.from_json(tx_json); + const data = tx.signingData(); + + assert.strictEqual(data.hash().to_json(), + expectedSigningHash); + + assert.strictEqual(data.to_hex(), + ('535458001200032200000000240000000168400000000000000' + + 'A7321021FED5FD081CE5C4356431267D04C6E2167E4112C897D' + + '5E10335D4E22B4DA49ED8114E0E6E281CA324AEE034B2BB8AC9' + + '7BA1ACA95A068')); + }); }); it('Get hash - no prefix', function(done) { @@ -1959,7 +1978,7 @@ describe('Transaction', function() { }); it('Get min ledger', function() { - let queue = new TransactionQueue(); + const queue = new TransactionQueue(); // Randomized submit indexes [ @@ -1975,7 +1994,7 @@ describe('Transaction', function() { 543305 ] .forEach(function(index) { - let tx = new Transaction(); + const tx = new Transaction(); tx.initialSubmitIndex = index; queue.push(tx); });