mirror of
https://github.com/Xahau/xahau.js.git
synced 2025-11-20 12:15:51 +00:00
Use ripple-hashes
This commit is contained in:
28
npm-shrinkwrap.json
generated
28
npm-shrinkwrap.json
generated
@@ -168,6 +168,34 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"ripple-hashes": {
|
||||||
|
"version": "0.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ripple-hashes/-/ripple-hashes-0.0.1.tgz",
|
||||||
|
"dependencies": {
|
||||||
|
"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"
|
||||||
|
},
|
||||||
|
"inherits": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"ripple-keypairs": {
|
"ripple-keypairs": {
|
||||||
"version": "0.10.0",
|
"version": "0.10.0",
|
||||||
"resolved": "https://registry.npmjs.org/ripple-keypairs/-/ripple-keypairs-0.10.0.tgz",
|
"resolved": "https://registry.npmjs.org/ripple-keypairs/-/ripple-keypairs-0.10.0.tgz",
|
||||||
|
|||||||
@@ -28,6 +28,7 @@
|
|||||||
"lru-cache": "~2.5.0",
|
"lru-cache": "~2.5.0",
|
||||||
"ripple-address-codec": "^2.0.1",
|
"ripple-address-codec": "^2.0.1",
|
||||||
"ripple-binary-codec": "^0.0.6",
|
"ripple-binary-codec": "^0.0.6",
|
||||||
|
"ripple-hashes": "^0.0.1",
|
||||||
"ripple-keypairs": "^0.10.0",
|
"ripple-keypairs": "^0.10.0",
|
||||||
"ripple-lib-transactionparser": "^0.5.1",
|
"ripple-lib-transactionparser": "^0.5.1",
|
||||||
"ripple-lib-value": "0.1.0",
|
"ripple-lib-value": "0.1.0",
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const common = require('../common');
|
const common = require('../common');
|
||||||
|
const hashes = require('ripple-hashes');
|
||||||
|
|
||||||
function convertLedgerHeader(header) {
|
function convertLedgerHeader(header) {
|
||||||
return {
|
return {
|
||||||
@@ -25,7 +26,7 @@ function convertLedgerHeader(header) {
|
|||||||
|
|
||||||
function hashLedgerHeader(ledgerHeader) {
|
function hashLedgerHeader(ledgerHeader) {
|
||||||
const header = convertLedgerHeader(ledgerHeader);
|
const header = convertLedgerHeader(ledgerHeader);
|
||||||
return common.core.Ledger.calculateLedgerHash(header);
|
return hashes.computeLedgerHash(header);
|
||||||
}
|
}
|
||||||
|
|
||||||
function computeTransactionHash(ledger) {
|
function computeTransactionHash(ledger) {
|
||||||
@@ -39,8 +40,7 @@ function computeTransactionHash(ledger) {
|
|||||||
tx.meta ? {metaData: tx.meta} : {});
|
tx.meta ? {metaData: tx.meta} : {});
|
||||||
return renameMeta;
|
return renameMeta;
|
||||||
});
|
});
|
||||||
const ledgerObject = common.core.Ledger.from_json({transactions: txs});
|
const transactionHash = hashes.computeTransactionTreeHash(txs);
|
||||||
const transactionHash = ledgerObject.calc_tx_hash();
|
|
||||||
if (ledger.transactionHash !== undefined
|
if (ledger.transactionHash !== undefined
|
||||||
&& ledger.transactionHash !== transactionHash) {
|
&& ledger.transactionHash !== transactionHash) {
|
||||||
throw new common.errors.ValidationError('transactionHash in header'
|
throw new common.errors.ValidationError('transactionHash in header'
|
||||||
@@ -54,8 +54,7 @@ function computeStateHash(ledger) {
|
|||||||
return ledger.stateHash;
|
return ledger.stateHash;
|
||||||
}
|
}
|
||||||
const state = JSON.parse(ledger.rawState);
|
const state = JSON.parse(ledger.rawState);
|
||||||
const ledgerObject = common.core.Ledger.from_json({accountState: state});
|
const stateHash = hashes.computeStateTreeHash(state);
|
||||||
const stateHash = ledgerObject.calc_account_hash();
|
|
||||||
if (ledger.stateHash !== undefined && ledger.stateHash !== stateHash) {
|
if (ledger.stateHash !== undefined && ledger.stateHash !== stateHash) {
|
||||||
throw new common.errors.ValidationError('stateHash in header'
|
throw new common.errors.ValidationError('stateHash in header'
|
||||||
+ ' does not match computed hash of state');
|
+ ' does not match computed hash of state');
|
||||||
@@ -64,11 +63,11 @@ function computeStateHash(ledger) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function computeLedgerHash(ledger: Object): string {
|
function computeLedgerHash(ledger: Object): string {
|
||||||
const hashes = {
|
const subhashes = {
|
||||||
transactionHash: computeTransactionHash(ledger),
|
transactionHash: computeTransactionHash(ledger),
|
||||||
stateHash: computeStateHash(ledger)
|
stateHash: computeStateHash(ledger)
|
||||||
};
|
};
|
||||||
return hashLedgerHeader(_.assign({}, ledger, hashes));
|
return hashLedgerHeader(_.assign({}, ledger, subhashes));
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = computeLedgerHash;
|
module.exports = computeLedgerHash;
|
||||||
|
|||||||
@@ -1,38 +0,0 @@
|
|||||||
'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.
|
|
||||||
*
|
|
||||||
* These prefixes are inserted before the source material used to
|
|
||||||
* generate various hashes. This is done to put each hash in its own
|
|
||||||
* "space." This way, two different types of objects with the
|
|
||||||
* same binary data will produce different hashes.
|
|
||||||
*
|
|
||||||
* Each prefix is a 4-byte value with the last byte set to zero
|
|
||||||
* and the first three bytes formed from the ASCII equivalent of
|
|
||||||
* some arbitrary string. For example "TXN".
|
|
||||||
*/
|
|
||||||
|
|
||||||
// transaction plus signature to give transaction ID
|
|
||||||
exports.HASH_TX_ID = 0x54584E00; // 'TXN'
|
|
||||||
// transaction plus metadata
|
|
||||||
exports.HASH_TX_NODE = 0x534E4400; // 'TND'
|
|
||||||
// inner node in tree
|
|
||||||
exports.HASH_INNER_NODE = 0x4D494E00; // 'MIN'
|
|
||||||
// leaf node in tree
|
|
||||||
exports.HASH_LEAF_NODE = 0x4D4C4E00; // 'MLN'
|
|
||||||
// inner transaction to sign
|
|
||||||
exports.HASH_TX_SIGN = 0x53545800; // 'STX'
|
|
||||||
// inner transaction to sign (TESTNET)
|
|
||||||
exports.HASH_TX_SIGN_TESTNET = 0x73747800; // 'stx'
|
|
||||||
// inner transaction to multisign
|
|
||||||
exports.HASH_TX_MULTISIGN = 0x534D5400; // 'SMT'
|
|
||||||
|
|
||||||
Object.keys(exports).forEach(function(k) {
|
|
||||||
exports[k + '_BYTES'] = toBytes(exports[k]);
|
|
||||||
});
|
|
||||||
@@ -9,7 +9,6 @@ exports.Meta = require('./meta').Meta;
|
|||||||
exports.RippleError = require('./rippleerror').RippleError;
|
exports.RippleError = require('./rippleerror').RippleError;
|
||||||
exports.utils = require('./utils');
|
exports.utils = require('./utils');
|
||||||
exports.Server = require('./server').Server;
|
exports.Server = require('./server').Server;
|
||||||
exports.Ledger = require('./ledger').Ledger;
|
|
||||||
|
|
||||||
exports._test = {
|
exports._test = {
|
||||||
Log: require('./log'),
|
Log: require('./log'),
|
||||||
@@ -17,7 +16,6 @@ exports._test = {
|
|||||||
TransactionManager: require('./transactionmanager').TransactionManager,
|
TransactionManager: require('./transactionmanager').TransactionManager,
|
||||||
TransactionQueue: require('./transactionqueue').TransactionQueue,
|
TransactionQueue: require('./transactionqueue').TransactionQueue,
|
||||||
RangeSet: require('./rangeset').RangeSet,
|
RangeSet: require('./rangeset').RangeSet,
|
||||||
HashPrefixes: require('./hashprefixes'),
|
|
||||||
OrderbookUtils: require('./orderbookutils'),
|
OrderbookUtils: require('./orderbookutils'),
|
||||||
constants: require('./constants')
|
constants: require('./constants')
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,186 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
const sha512half = require('./utils').sha512half;
|
|
||||||
const BigNumber = require('bignumber.js');
|
|
||||||
const hashprefixes = require('./hashprefixes');
|
|
||||||
const SHAMap = require('./shamap').SHAMap;
|
|
||||||
const SHAMapTreeNode = require('./shamap').SHAMapTreeNode;
|
|
||||||
const {decodeAddress} = require('ripple-address-codec');
|
|
||||||
const binary = require('ripple-binary-codec');
|
|
||||||
|
|
||||||
function Ledger() {
|
|
||||||
this.ledger_json = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
Ledger.from_json = function(v) {
|
|
||||||
const ledger = new Ledger();
|
|
||||||
ledger.parse_json(v);
|
|
||||||
return ledger;
|
|
||||||
};
|
|
||||||
|
|
||||||
Ledger.space = require('./ledgerspaces');
|
|
||||||
|
|
||||||
function hash(hex) {
|
|
||||||
return sha512half(new Buffer(hex, 'hex'));
|
|
||||||
}
|
|
||||||
|
|
||||||
function hashTransaction(txBlobHex) {
|
|
||||||
const prefix = hashprefixes.HASH_TX_ID.toString(16).toUpperCase();
|
|
||||||
return hash(prefix + txBlobHex);
|
|
||||||
}
|
|
||||||
|
|
||||||
function padLeftZero(string, length) {
|
|
||||||
return Array(length - string.length + 1).join('0') + string;
|
|
||||||
}
|
|
||||||
|
|
||||||
function intToHex(integer, byteLength) {
|
|
||||||
return padLeftZero(Number(integer).toString(16), byteLength * 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
function bytesToHex(bytes) {
|
|
||||||
return (new Buffer(bytes)).toString('hex');
|
|
||||||
}
|
|
||||||
|
|
||||||
function bigintToHex(integerString, byteLength) {
|
|
||||||
const hex = (new BigNumber(integerString)).toString(16);
|
|
||||||
return padLeftZero(hex, byteLength * 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
function addressToHex(address) {
|
|
||||||
return (new Buffer(decodeAddress(address))).toString('hex');
|
|
||||||
}
|
|
||||||
|
|
||||||
function currencyToHex(currency) {
|
|
||||||
if (currency.length === 3) {
|
|
||||||
const bytes = new Array(20 + 1).join('0').split('').map(parseFloat);
|
|
||||||
bytes[12] = currency.charCodeAt(0) & 0xff;
|
|
||||||
bytes[13] = currency.charCodeAt(1) & 0xff;
|
|
||||||
bytes[14] = currency.charCodeAt(2) & 0xff;
|
|
||||||
return bytesToHex(bytes);
|
|
||||||
}
|
|
||||||
return currency;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate the key for an AccountRoot entry.
|
|
||||||
*
|
|
||||||
* @param {String|UInt160} accountArg - Ripple Account
|
|
||||||
* @return {UInt256}
|
|
||||||
*/
|
|
||||||
Ledger.calcAccountRootEntryHash =
|
|
||||||
Ledger.prototype.calcAccountRootEntryHash = function(address) {
|
|
||||||
const prefix = '00' + intToHex(Ledger.space.account.charCodeAt(0), 1);
|
|
||||||
return hash(prefix + addressToHex(address));
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate the key for an Offer entry.
|
|
||||||
*
|
|
||||||
* @param {String|UInt160} accountArg - Ripple Account
|
|
||||||
* @param {Number} sequence - Sequence number of the OfferCreate transaction
|
|
||||||
* that instantiated this offer.
|
|
||||||
* @return {UInt256}
|
|
||||||
*/
|
|
||||||
Ledger.calcOfferEntryHash =
|
|
||||||
Ledger.prototype.calcOfferEntryHash = function(address, sequence) {
|
|
||||||
const prefix = '00' + intToHex(Ledger.space.offer.charCodeAt(0), 1);
|
|
||||||
return hash(prefix + addressToHex(address) + intToHex(sequence, 4));
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generate the key for a RippleState entry.
|
|
||||||
*
|
|
||||||
* The ordering of the two account parameters does not matter.
|
|
||||||
*
|
|
||||||
* @param {String|UInt160} _account1 - First Ripple Account
|
|
||||||
* @param {String|UInt160} _account2 - Second Ripple Account
|
|
||||||
* @param {String|Currency} _currency - The currency code
|
|
||||||
* @return {UInt256}
|
|
||||||
*/
|
|
||||||
Ledger.calcRippleStateEntryHash =
|
|
||||||
Ledger.prototype.calcRippleStateEntryHash = function(
|
|
||||||
address1, address2, currency) {
|
|
||||||
const address1Hex = addressToHex(address1);
|
|
||||||
const address2Hex = addressToHex(address2);
|
|
||||||
|
|
||||||
const swap = (new BigNumber(address1Hex, 16)).greaterThan(
|
|
||||||
new BigNumber(address2Hex, 16));
|
|
||||||
const lowAddressHex = swap ? address2Hex : address1Hex;
|
|
||||||
const highAddressHex = swap ? address1Hex : address2Hex;
|
|
||||||
|
|
||||||
const prefix = '00' + intToHex(Ledger.space.rippleState.charCodeAt(0), 1);
|
|
||||||
return hash(prefix + lowAddressHex + highAddressHex +
|
|
||||||
currencyToHex(currency));
|
|
||||||
};
|
|
||||||
|
|
||||||
Ledger.prototype.parse_json = function(v) {
|
|
||||||
this.ledger_json = v;
|
|
||||||
};
|
|
||||||
|
|
||||||
function addLengthPrefix(hex) {
|
|
||||||
const length = hex.length / 2;
|
|
||||||
if (length <= 192) {
|
|
||||||
return bytesToHex([length]) + hex;
|
|
||||||
} else if (length <= 12480) {
|
|
||||||
const x = length - 193;
|
|
||||||
return bytesToHex([193 + (x >>> 8), x & 0xff]) + hex;
|
|
||||||
} else if (length <= 918744) {
|
|
||||||
const x = length - 12481;
|
|
||||||
return bytesToHex([241 + (x >>> 16), x >>> 8 & 0xff, x & 0xff]) + hex;
|
|
||||||
}
|
|
||||||
throw new Error('Variable integer overflow.');
|
|
||||||
}
|
|
||||||
|
|
||||||
Ledger.prototype.calc_tx_hash = function() {
|
|
||||||
const tx_map = new SHAMap();
|
|
||||||
|
|
||||||
this.ledger_json.transactions.forEach(function(tx_json) {
|
|
||||||
const txBlobHex = binary.encode(tx_json);
|
|
||||||
const metaHex = binary.encode(tx_json.metaData);
|
|
||||||
const txHash = hashTransaction(txBlobHex);
|
|
||||||
const data = addLengthPrefix(txBlobHex) + addLengthPrefix(metaHex);
|
|
||||||
tx_map.add_item(txHash, data, SHAMapTreeNode.TYPE_TRANSACTION_MD);
|
|
||||||
});
|
|
||||||
|
|
||||||
return tx_map.hash();
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {Object} options - object
|
|
||||||
*
|
|
||||||
* @param {Boolean} [options.sanity_test=false] - If `true`, will serialize each
|
|
||||||
* accountState item to binary and then back to json before finally
|
|
||||||
* serializing for hashing. This is mostly to expose any issues with
|
|
||||||
* ripple-lib's binary <--> json codecs.
|
|
||||||
*
|
|
||||||
* @return {UInt256} - hash of shamap
|
|
||||||
*/
|
|
||||||
Ledger.prototype.calc_account_hash = function() {
|
|
||||||
const account_map = new SHAMap();
|
|
||||||
|
|
||||||
this.ledger_json.accountState.forEach(function(ledgerEntry) {
|
|
||||||
const data = binary.encode(ledgerEntry);
|
|
||||||
account_map.add_item(ledgerEntry.index, data,
|
|
||||||
SHAMapTreeNode.TYPE_ACCOUNT_STATE);
|
|
||||||
});
|
|
||||||
|
|
||||||
return account_map.hash();
|
|
||||||
};
|
|
||||||
|
|
||||||
// see rippled Ledger::updateHash()
|
|
||||||
Ledger.calculateLedgerHash =
|
|
||||||
Ledger.prototype.calculateLedgerHash = function(ledgerHeader) {
|
|
||||||
const prefix = '4C575200';
|
|
||||||
return hash(prefix +
|
|
||||||
intToHex(ledgerHeader.ledger_index, 4) +
|
|
||||||
bigintToHex(ledgerHeader.total_coins, 8) +
|
|
||||||
ledgerHeader.parent_hash +
|
|
||||||
ledgerHeader.transaction_hash +
|
|
||||||
ledgerHeader.account_hash +
|
|
||||||
intToHex(ledgerHeader.parent_close_time, 4) +
|
|
||||||
intToHex(ledgerHeader.close_time, 4) +
|
|
||||||
intToHex(ledgerHeader.close_time_resolution, 1) +
|
|
||||||
intToHex(ledgerHeader.close_flags, 1)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.Ledger = Ledger;
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
/**
|
|
||||||
* Ripple ledger namespace prefixes.
|
|
||||||
*
|
|
||||||
* The Ripple ledger is a key-value store. In order to avoid name collisions,
|
|
||||||
* names are partitioned into namespaces.
|
|
||||||
*
|
|
||||||
* Each namespace is just a single character prefix.
|
|
||||||
*/
|
|
||||||
module.exports = {
|
|
||||||
account : 'a',
|
|
||||||
dirNode : 'd',
|
|
||||||
generatorMap : 'g',
|
|
||||||
nickname : 'n',
|
|
||||||
rippleState : 'r',
|
|
||||||
offer : 'o', // Entry for an offer.
|
|
||||||
ownerDir : 'O', // Directory of things owned by an account.
|
|
||||||
bookDir : 'B', // Directory of order books.
|
|
||||||
contract : 'c',
|
|
||||||
skipList : 's',
|
|
||||||
amendment : 'f',
|
|
||||||
feeSettings : 'e'
|
|
||||||
};
|
|
||||||
@@ -1,175 +0,0 @@
|
|||||||
'use strict';
|
|
||||||
|
|
||||||
const util = require('util');
|
|
||||||
const hashprefixes = require('./hashprefixes');
|
|
||||||
const sha512half = require('./utils').sha512half;
|
|
||||||
const HEX_ZERO = '00000000000000000000000000000000' +
|
|
||||||
'00000000000000000000000000000000';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Abstract class representing a node in a SHAMap tree.
|
|
||||||
*
|
|
||||||
* Can be either SHAMapTreeNodeInner or SHAMapTreeNodeLeaf.
|
|
||||||
*
|
|
||||||
* @class
|
|
||||||
*/
|
|
||||||
function SHAMapTreeNode() { }
|
|
||||||
|
|
||||||
SHAMapTreeNode.TYPE_INNER = 1;
|
|
||||||
SHAMapTreeNode.TYPE_TRANSACTION_NM = 2;
|
|
||||||
SHAMapTreeNode.TYPE_TRANSACTION_MD = 3;
|
|
||||||
SHAMapTreeNode.TYPE_ACCOUNT_STATE = 4;
|
|
||||||
|
|
||||||
function hash(hex) {
|
|
||||||
return sha512half(new Buffer(hex, 'hex'));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {String} tag (64 hexadecimal characters)
|
|
||||||
* @param {SHAMapTreeNode} node
|
|
||||||
* @return {void}
|
|
||||||
* @virtual
|
|
||||||
*/
|
|
||||||
/* eslint-disable no-unused-vars*/
|
|
||||||
SHAMapTreeNode.prototype.add_item = function(tag, node) {
|
|
||||||
throw new Error(
|
|
||||||
'Called unimplemented virtual method SHAMapTreeNode#add_item.');
|
|
||||||
};
|
|
||||||
/* eslint-enable no-unused-vars*/
|
|
||||||
|
|
||||||
SHAMapTreeNode.prototype.hash = function() {
|
|
||||||
throw new Error('Called unimplemented virtual method SHAMapTreeNode#hash.');
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inner (non-leaf) node in a SHAMap tree.
|
|
||||||
* @param {Number} depth (i.e. how many parent inner nodes)
|
|
||||||
* @class
|
|
||||||
*/
|
|
||||||
function SHAMapTreeNodeInner(depth) {
|
|
||||||
SHAMapTreeNode.call(this);
|
|
||||||
this.leaves = {};
|
|
||||||
this.type = SHAMapTreeNode.INNER;
|
|
||||||
this.depth = depth === undefined ? 0 : depth;
|
|
||||||
this.empty = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
util.inherits(SHAMapTreeNodeInner, SHAMapTreeNode);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {String} tag (equates to a ledger entry `index`)
|
|
||||||
* @param {SHAMapTreeNode} node (to add)
|
|
||||||
* @return {void}
|
|
||||||
*/
|
|
||||||
SHAMapTreeNodeInner.prototype.add_item = function(tag, node) {
|
|
||||||
const depth = this.depth;
|
|
||||||
const existing_node = this.get_node(tag[depth]);
|
|
||||||
|
|
||||||
if (existing_node) {
|
|
||||||
// A node already exists in this slot
|
|
||||||
if (existing_node instanceof SHAMapTreeNodeInner) {
|
|
||||||
// There is an inner node, so we need to go deeper
|
|
||||||
existing_node.add_item(tag, node);
|
|
||||||
} else if (existing_node.tag === tag) {
|
|
||||||
// Collision
|
|
||||||
throw new Error(
|
|
||||||
'Tried to add a node to a SHAMap that was already in there.');
|
|
||||||
} else {
|
|
||||||
// Turn it into an inner node
|
|
||||||
const new_inner_node = new SHAMapTreeNodeInner(depth + 1);
|
|
||||||
|
|
||||||
// Parent new and existing node
|
|
||||||
new_inner_node.add_item(existing_node.tag, existing_node);
|
|
||||||
new_inner_node.add_item(tag, node);
|
|
||||||
|
|
||||||
// And place the newly created inner node in the slot
|
|
||||||
this.set_node(tag[depth], new_inner_node);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Neat, we have a nice open spot for the new node
|
|
||||||
this.set_node(tag[depth], node);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Overwrite the node that is currently in a given slot.
|
|
||||||
* @param {String} slot (a character 0-F)
|
|
||||||
* @param {SHAMapTreeNode} node (to place)
|
|
||||||
* @return {void}
|
|
||||||
*/
|
|
||||||
SHAMapTreeNodeInner.prototype.set_node = function(slot, node) {
|
|
||||||
this.leaves[slot] = node;
|
|
||||||
this.empty = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
SHAMapTreeNodeInner.prototype.get_node = function(slot) {
|
|
||||||
return this.leaves[slot];
|
|
||||||
};
|
|
||||||
|
|
||||||
SHAMapTreeNodeInner.prototype.hash = function() {
|
|
||||||
if (this.empty) {
|
|
||||||
return HEX_ZERO;
|
|
||||||
}
|
|
||||||
|
|
||||||
let hex = '';
|
|
||||||
for (let i = 0; i < 16; i++) {
|
|
||||||
const slot = i.toString(16).toUpperCase();
|
|
||||||
hex += this.leaves[slot] ? this.leaves[slot].hash() : HEX_ZERO;
|
|
||||||
}
|
|
||||||
|
|
||||||
const prefix = hashprefixes.HASH_INNER_NODE.toString(16);
|
|
||||||
return hash(prefix + hex);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Leaf node in a SHAMap tree.
|
|
||||||
* @param {String} tag (equates to a ledger entry `index`)
|
|
||||||
* @param {String} data (hex of account state, transaction etc)
|
|
||||||
* @param {Number} type (one of TYPE_ACCOUNT_STATE, TYPE_TRANSACTION_MD etc)
|
|
||||||
* @class
|
|
||||||
*/
|
|
||||||
function SHAMapTreeNodeLeaf(tag, data, type) {
|
|
||||||
SHAMapTreeNode.call(this);
|
|
||||||
|
|
||||||
if (typeof tag !== 'string') {
|
|
||||||
throw new Error('Tag is unexpected type.');
|
|
||||||
}
|
|
||||||
|
|
||||||
this.tag = tag;
|
|
||||||
this.type = type;
|
|
||||||
this.data = data;
|
|
||||||
}
|
|
||||||
|
|
||||||
util.inherits(SHAMapTreeNodeLeaf, SHAMapTreeNode);
|
|
||||||
|
|
||||||
SHAMapTreeNodeLeaf.prototype.hash = function() {
|
|
||||||
switch (this.type) {
|
|
||||||
case SHAMapTreeNode.TYPE_ACCOUNT_STATE:
|
|
||||||
const leafPrefix = hashprefixes.HASH_LEAF_NODE.toString(16);
|
|
||||||
return hash(leafPrefix + this.data + this.tag);
|
|
||||||
case SHAMapTreeNode.TYPE_TRANSACTION_NM:
|
|
||||||
return this.tag;
|
|
||||||
case SHAMapTreeNode.TYPE_TRANSACTION_MD:
|
|
||||||
const txPrefix = hashprefixes.HASH_TX_NODE.toString(16);
|
|
||||||
return hash(txPrefix + this.data + this.tag);
|
|
||||||
default:
|
|
||||||
throw new Error('Tried to hash a SHAMap node of unknown type.');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function SHAMap() {
|
|
||||||
this.root = new SHAMapTreeNodeInner(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
SHAMap.prototype.add_item = function(tag, data, type) {
|
|
||||||
this.root.add_item(tag, new SHAMapTreeNodeLeaf(tag, data, type));
|
|
||||||
};
|
|
||||||
|
|
||||||
SHAMap.prototype.hash = function() {
|
|
||||||
return this.root.hash();
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.SHAMap = SHAMap;
|
|
||||||
exports.SHAMapTreeNode = SHAMapTreeNode;
|
|
||||||
exports.SHAMapTreeNodeInner = SHAMapTreeNodeInner;
|
|
||||||
exports.SHAMapTreeNodeLeaf = SHAMapTreeNodeLeaf;
|
|
||||||
@@ -10,10 +10,11 @@ const sjclcodec = require('sjcl-codec');
|
|||||||
const Amount = require('./amount').Amount;
|
const Amount = require('./amount').Amount;
|
||||||
const Currency = require('./currency').Currency;
|
const Currency = require('./currency').Currency;
|
||||||
const RippleError = require('./rippleerror').RippleError;
|
const RippleError = require('./rippleerror').RippleError;
|
||||||
const hashprefixes = require('./hashprefixes');
|
|
||||||
const log = require('./log').internal.sub('transaction');
|
const log = require('./log').internal.sub('transaction');
|
||||||
const {isValidAddress, decodeAddress} = require('ripple-address-codec');
|
const {isValidAddress, decodeAddress} = require('ripple-address-codec');
|
||||||
const binary = require('ripple-binary-codec');
|
const binary = require('ripple-binary-codec');
|
||||||
|
const {computeTransactionHash, computeTransactionSigningHash}
|
||||||
|
= require('ripple-hashes');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @constructor Transaction
|
* @constructor Transaction
|
||||||
@@ -462,8 +463,8 @@ Transaction.prototype.serialize = function() {
|
|||||||
return binary.encode(this.tx_json);
|
return binary.encode(this.tx_json);
|
||||||
};
|
};
|
||||||
|
|
||||||
Transaction.prototype.signingHash = function(testnet) {
|
Transaction.prototype.signingHash = function() {
|
||||||
return this.hash(testnet ? 'HASH_TX_SIGN_TESTNET' : 'HASH_TX_SIGN');
|
return computeTransactionSigningHash(this.tx_json);
|
||||||
};
|
};
|
||||||
|
|
||||||
Transaction.prototype.signingData = function() {
|
Transaction.prototype.signingData = function() {
|
||||||
@@ -474,21 +475,8 @@ Transaction.prototype.multiSigningData = function(account) {
|
|||||||
return binary.encodeForMultisigning(this.tx_json, account);
|
return binary.encodeForMultisigning(this.tx_json, account);
|
||||||
};
|
};
|
||||||
|
|
||||||
Transaction.prototype.hash = function(prefix_, serialized_) {
|
Transaction.prototype.hash = function() {
|
||||||
let prefix;
|
return computeTransactionHash(this.tx_json);
|
||||||
assert(serialized_ === undefined || _.isString(serialized_));
|
|
||||||
|
|
||||||
if (typeof prefix_ !== 'string') {
|
|
||||||
prefix = hashprefixes.HASH_TX_ID;
|
|
||||||
} else if (!hashprefixes.hasOwnProperty(prefix_)) {
|
|
||||||
throw new Error('Unknown hashing prefix requested: ' + prefix_);
|
|
||||||
} else {
|
|
||||||
prefix = hashprefixes[prefix_];
|
|
||||||
}
|
|
||||||
|
|
||||||
const hexPrefix = prefix.toString(16).toUpperCase();
|
|
||||||
const serialized = serialized_ || this.serialize();
|
|
||||||
return utils.sha512half(new Buffer(hexPrefix + serialized, 'hex'));
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Transaction.prototype.sign = function(secret) {
|
Transaction.prototype.sign = function(secret) {
|
||||||
|
|||||||
@@ -1,93 +0,0 @@
|
|||||||
/* eslint-disable max-len, valid-jsdoc */
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const assert = require('assert');
|
|
||||||
const fs = require('fs');
|
|
||||||
|
|
||||||
const Ledger = require('ripple-lib').Ledger;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param ledger_index {Number}
|
|
||||||
* Expects a corresponding ledger dump in $repo/test/fixtures/ folder
|
|
||||||
*/
|
|
||||||
function create_ledger_test(ledger_index) {
|
|
||||||
describe(String(ledger_index), function() {
|
|
||||||
const path = __dirname + '/fixtures/ledger-full-' + ledger_index + '.json';
|
|
||||||
|
|
||||||
const ledger_raw = fs.readFileSync(path);
|
|
||||||
const ledger_json = JSON.parse(ledger_raw);
|
|
||||||
const ledger = Ledger.from_json(ledger_json);
|
|
||||||
|
|
||||||
const hasAccounts = Array.isArray(ledger_json.accountState)
|
|
||||||
&& ledger_json.accountState.length > 0;
|
|
||||||
|
|
||||||
if (hasAccounts) {
|
|
||||||
it('has account_hash of ' + ledger_json.account_hash, function() {
|
|
||||||
assert.equal(ledger_json.account_hash, ledger.calc_account_hash());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
it('has transaction_hash of ' + ledger_json.transaction_hash, function() {
|
|
||||||
assert.equal(ledger_json.transaction_hash,
|
|
||||||
ledger.calc_tx_hash());
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
describe('Ledger', function() {
|
|
||||||
// This is the first recorded ledger with a non empty transaction set
|
|
||||||
create_ledger_test(38129);
|
|
||||||
// Because, why not.
|
|
||||||
create_ledger_test(40000);
|
|
||||||
// 1311 AffectedNodes, no accounts
|
|
||||||
create_ledger_test(7501326);
|
|
||||||
|
|
||||||
describe('#calcAccountRootEntryHash', function() {
|
|
||||||
it('will calculate the AccountRoot entry hash for rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', function() {
|
|
||||||
const account = 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh';
|
|
||||||
const expectedEntryHash = '2B6AC232AA4C4BE41BF49D2459FA4A0347E1B543A4C92FCEE0821C0201E2E9A8';
|
|
||||||
const actualEntryHash = Ledger.calcAccountRootEntryHash(account);
|
|
||||||
|
|
||||||
assert.equal(actualEntryHash, expectedEntryHash);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('#calcRippleStateEntryHash', function() {
|
|
||||||
it('will calculate the RippleState entry hash for rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh and rB5TihdPbKgMrkFqrqUC3yLdE8hhv4BdeY in USD', function() {
|
|
||||||
const account1 = 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh';
|
|
||||||
const account2 = 'rB5TihdPbKgMrkFqrqUC3yLdE8hhv4BdeY';
|
|
||||||
const currency = 'USD';
|
|
||||||
|
|
||||||
const expectedEntryHash = 'C683B5BB928F025F1E860D9D69D6C554C2202DE0D45877ADB3077DA4CB9E125C';
|
|
||||||
const actualEntryHash1 = Ledger.calcRippleStateEntryHash(account1, account2, currency);
|
|
||||||
const actualEntryHash2 = Ledger.calcRippleStateEntryHash(account2, account1, currency);
|
|
||||||
|
|
||||||
assert.equal(actualEntryHash1, expectedEntryHash);
|
|
||||||
assert.equal(actualEntryHash2, expectedEntryHash);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('will calculate the RippleState entry hash for r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV and rUAMuQTfVhbfqUDuro7zzy4jj4Wq57MPTj in UAM', function() {
|
|
||||||
const account1 = 'r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV';
|
|
||||||
const account2 = 'rUAMuQTfVhbfqUDuro7zzy4jj4Wq57MPTj';
|
|
||||||
const currency = 'UAM';
|
|
||||||
|
|
||||||
const expectedEntryHash = 'AE9ADDC584358E5847ADFC971834E471436FC3E9DE6EA1773DF49F419DC0F65E';
|
|
||||||
const actualEntryHash1 = Ledger.calcRippleStateEntryHash(account1, account2, currency);
|
|
||||||
const actualEntryHash2 = Ledger.calcRippleStateEntryHash(account2, account1, currency);
|
|
||||||
|
|
||||||
assert.equal(actualEntryHash1, expectedEntryHash);
|
|
||||||
assert.equal(actualEntryHash2, expectedEntryHash);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('#calcOfferEntryHash', function() {
|
|
||||||
it('will calculate the Offer entry hash for r32UufnaCGL82HubijgJGDmdE5hac7ZvLw, sequence 137', function() {
|
|
||||||
const account = 'r32UufnaCGL82HubijgJGDmdE5hac7ZvLw';
|
|
||||||
const sequence = 137;
|
|
||||||
const expectedEntryHash = '03F0AED09DEEE74CEF85CD57A0429D6113507CF759C597BABB4ADB752F734CE3';
|
|
||||||
const actualEntryHash = Ledger.calcOfferEntryHash(account, sequence);
|
|
||||||
|
|
||||||
assert.equal(actualEntryHash, expectedEntryHash);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// vim:sw=2:sts=2:ts=8:et
|
|
||||||
@@ -637,25 +637,8 @@ describe('Transaction', function() {
|
|||||||
transaction.tx_json.Sequence = 1;
|
transaction.tx_json.Sequence = 1;
|
||||||
transaction.tx_json.TransactionType = 'AccountSet';
|
transaction.tx_json.TransactionType = 'AccountSet';
|
||||||
|
|
||||||
assert.strictEqual(transaction.hash('HASH_TX_SIGN'), 'D1C15200CF532175F1890B6440AD223D3676140522BC11D2784E56760AE3B4FE');
|
assert.strictEqual(transaction.signingHash(),
|
||||||
assert.strictEqual(transaction.hash('HASH_TX_SIGN_TESTNET'), '9FE7D27FC5B9891076B66591F99A683E01E0912986A629235459A3BD1961F341');
|
'D1C15200CF532175F1890B6440AD223D3676140522BC11D2784E56760AE3B4FE');
|
||||||
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('Get hash - invalid prefix', 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';
|
|
||||||
|
|
||||||
assert.throws(function() {
|
|
||||||
transaction.hash('HASH_TX_SIGNZ');
|
|
||||||
});
|
|
||||||
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
@@ -2258,10 +2241,11 @@ describe('Transaction', function() {
|
|||||||
const txHex = binary.encode(
|
const txHex = binary.encode(
|
||||||
lodash.merge(transaction.tx_json, {SigningPubKey: ''}));
|
lodash.merge(transaction.tx_json, {SigningPubKey: ''}));
|
||||||
const abytes = decodeAddress(a1);
|
const abytes = decodeAddress(a1);
|
||||||
const prefix = require('ripple-lib')._test.HashPrefixes.HASH_TX_MULTISIGN_BYTES;
|
const prefix = 0x534D5400;
|
||||||
|
const prefixHex = prefix.toString(16);
|
||||||
|
|
||||||
assert.deepEqual(new Buffer(d1, 'hex'),
|
assert.deepEqual(new Buffer(d1, 'hex'),
|
||||||
Buffer.concat([new Buffer(prefix), new Buffer(txHex, 'hex'),
|
Buffer.concat([new Buffer(prefixHex, 'hex'), new Buffer(txHex, 'hex'),
|
||||||
new Buffer(abytes)]));
|
new Buffer(abytes)]));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user