Add Transaction.signingData()

This commit is contained in:
Nicholas Dudfield
2015-06-22 19:37:53 +07:00
parent 46121edd62
commit 8d98e443c5
4 changed files with 113 additions and 75 deletions

View File

@@ -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. * Prefix for hashing functions.
* *
@@ -12,14 +19,18 @@
*/ */
// transaction plus signature to give transaction ID // transaction plus signature to give transaction ID
exports.HASH_TX_ID = 0x54584E00; // 'TXN' exports.HASH_TX_ID = 0x54584E00; // 'TXN'
// transaction plus metadata // transaction plus metadata
exports.HASH_TX_NODE = 0x534E4400; // 'TND' exports.HASH_TX_NODE = 0x534E4400; // 'TND'
// inner node in tree // inner node in tree
exports.HASH_INNER_NODE = 0x4D494E00; // 'MIN' exports.HASH_INNER_NODE = 0x4D494E00; // 'MIN'
// leaf node in tree // leaf node in tree
exports.HASH_LEAF_NODE = 0x4D4C4E00; // 'MLN' exports.HASH_LEAF_NODE = 0x4D4C4E00; // 'MLN'
// inner transaction to sign // inner transaction to sign
exports.HASH_TX_SIGN = 0x53545800; // 'STX' exports.HASH_TX_SIGN = 0x53545800; // 'STX'
// inner transaction to sign (TESTNET) // inner transaction to sign (TESTNET)
exports.HASH_TX_SIGN_TESTNET = 0x73747800; // 'stx' exports.HASH_TX_SIGN_TESTNET = 0x73747800; // 'stx'
Object.keys(exports).forEach(function(k) {
exports[k + '_BYTES'] = toBytes(exports[k]);
});

View File

@@ -50,57 +50,9 @@ function SerializedObject(buf) {
this.pointer = 0; this.pointer = 0;
} }
SerializedObject.from_json = function(obj_) { SerializedObject.from_json = function(obj) {
// Create a copy of the object so we don't modify it
const obj = extend(true, {}, obj_);
const so = new SerializedObject(); const so = new SerializedObject();
let typedef; so.parse_json(obj);
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);
return so; return so;
}; };
@@ -154,6 +106,55 @@ SerializedObject.check_fields = function(typedef, obj) {
throw new Error(errorMessage); throw new Error(errorMessage);
}; };
SerializedObject.prototype.parse_json = function(obj_) {
// 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);
this.serialize(typedef, obj);
};
SerializedObject.prototype.append = function(bytes_) { SerializedObject.prototype.append = function(bytes_) {
const bytes = bytes_ instanceof SerializedObject ? bytes_.buffer : bytes_; const bytes = bytes_ instanceof SerializedObject ? bytes_.buffer : bytes_;

View File

@@ -274,7 +274,7 @@ Transaction.prototype.getManager = function(account_) {
return undefined; return undefined;
} }
let account = account_ || this.tx_json.Account; const account = account_ || this.tx_json.Account;
return this.remote.account(account)._transactionManager; return this.remote.account(account)._transactionManager;
}; };
@@ -290,7 +290,7 @@ Transaction.prototype._accountSecret = function(account_) {
return undefined; return undefined;
} }
let account = account_ || this.tx_json.Account; const account = account_ || this.tx_json.Account;
return this.remote.secrets[account]; return this.remote.secrets[account];
}; };
@@ -326,7 +326,7 @@ Transaction.prototype._computeFee = function() {
const fees = [ ]; const fees = [ ];
for (let i = 0; i < servers.length; i++) { for (let i = 0; i < servers.length; i++) {
let server = servers[i]; const server = servers[i];
if (server.isConnected()) { if (server.isConnected()) {
fees.push(Number(server._computeFee(this._getFeeUnits()))); 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'); 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) { Transaction.prototype.hash = function(prefix_, asUINT256, serialized) {
let prefix; let prefix;
@@ -770,7 +777,7 @@ Transaction.prototype.addMemo = function(options_) {
const memo = {}; const memo = {};
const memoRegex = Transaction.MEMO_REGEX; const memoRegex = Transaction.MEMO_REGEX;
let memoType = options.memoType; let memoType = options.memoType;
let memoFormat = options.memoFormat; const memoFormat = options.memoFormat;
let memoData = options.memoData; let memoData = options.memoData;
if (memoType) { if (memoType) {
@@ -918,7 +925,7 @@ Transaction.prototype.transferRate = function(rate) {
// } // }
/* eslint-enable max-len */ /* eslint-enable max-len */
let transferRate = rate; const transferRate = rate;
if (transferRate === 0) { if (transferRate === 0) {
// Clear TransferRate // Clear TransferRate
@@ -1323,7 +1330,7 @@ Transaction.prototype.setExpiration = function(expiration) {
// throw new Error('TransactionType must be OfferCreate to use Expiration'); // throw new Error('TransactionType must be OfferCreate to use Expiration');
// } // }
let timeOffset = expiration instanceof Date const timeOffset = expiration instanceof Date
? expiration.getTime() ? expiration.getTime()
: expiration; : expiration;
@@ -1433,7 +1440,7 @@ Transaction.prototype.abort = function() {
Transaction.prototype.getSummary = Transaction.prototype.getSummary =
Transaction.prototype.summary = function() { Transaction.prototype.summary = function() {
let txSummary = { const txSummary = {
tx_json: this.tx_json, tx_json: this.tx_json,
clientID: this._clientID, clientID: this._clientID,
submittedIDs: this.submittedIDs, submittedIDs: this.submittedIDs,

View File

@@ -553,19 +553,38 @@ describe('Transaction', function() {
done(); done();
}); });
it('Get signing hash', function(done) { describe('signing', function() {
const transaction = new Transaction(); const tx_json = {
transaction._secret = 'sh2pTicynUEG46jjR4EoexHcQEoij'; SigningPubKey: '021FED5FD081CE5C4356431267D04C6E2167E4112C897D5E10335D4E22B4DA49ED',
transaction.tx_json.SigningPubKey = '021FED5FD081CE5C4356431267D04C6E2167E4112C897D5E10335D4E22B4DA49ED'; Account: 'rMWwx3Ma16HnqSd4H6saPisihX9aKpXxHJ',
transaction.tx_json.Account = 'rMWwx3Ma16HnqSd4H6saPisihX9aKpXxHJ'; Flags: 0,
transaction.tx_json.Flags = 0; Fee: 10,
transaction.tx_json.Fee = 10; Sequence: 1,
transaction.tx_json.Sequence = 1; TransactionType: 'AccountSet'
transaction.tx_json.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) { it('Get hash - no prefix', function(done) {
@@ -1959,7 +1978,7 @@ describe('Transaction', function() {
}); });
it('Get min ledger', function() { it('Get min ledger', function() {
let queue = new TransactionQueue(); const queue = new TransactionQueue();
// Randomized submit indexes // Randomized submit indexes
[ [
@@ -1975,7 +1994,7 @@ describe('Transaction', function() {
543305 543305
] ]
.forEach(function(index) { .forEach(function(index) {
let tx = new Transaction(); const tx = new Transaction();
tx.initialSubmitIndex = index; tx.initialSubmitIndex = index;
queue.push(tx); queue.push(tx);
}); });