From 9100e8ecc052d4cce582e54581606e6f1a858562 Mon Sep 17 00:00:00 2001 From: Stefan Thomas Date: Sat, 24 May 2014 09:17:38 +0200 Subject: [PATCH] [FEATURE] Add ability to calculate ledger entry keys. --- src/js/ripple/ledger.js | 90 +++++++++++++++++++++++++++++++ src/js/ripple/ledgerspaces.js | 22 ++++++++ src/js/ripple/serializedobject.js | 20 ++++--- test/ledger-test.js | 49 +++++++++++++++++ 4 files changed, 173 insertions(+), 8 deletions(-) create mode 100644 src/js/ripple/ledgerspaces.js diff --git a/src/js/ripple/ledger.js b/src/js/ripple/ledger.js index cacd5991..08a21ce8 100644 --- a/src/js/ripple/ledger.js +++ b/src/js/ripple/ledger.js @@ -5,6 +5,11 @@ var SHAMap = require('./shamap').SHAMap; var SHAMapTreeNode = require('./shamap').SHAMapTreeNode; var SerializedObject = require('./serializedobject').SerializedObject; var stypes = require('./serializedtypes'); +var UInt160 = require('./uint160').UInt160; +var Currency = require('./currency').Currency; +var stypes = require('./serializedtypes'); +var sjcl = require('./utils').sjcl; +var Crypt = require('./crypt').Crypt; function Ledger() { @@ -17,6 +22,91 @@ Ledger.from_json = function (v) { return ledger; }; +Ledger.space = require('./ledgerspaces'); + +/** + * Generate the key for an AccountRoot entry. + * + * @param {String|UInt160} account Ripple Account + * @return {UInt256} + */ +Ledger.calcAccountRootEntryHash = +Ledger.prototype.calcAccountRootEntryHash = function (account) { + account = UInt160.from_json(account); + + var index = new SerializedObject(); + + index.append([0, Ledger.space.account.charCodeAt(0)]); + index.append(account.to_bytes()); + + return index.hash(); +}; + +/** + * Generate the key for an Offer entry. + * + * @param {String|UInt160} account Ripple Account + * @param {Number} sequence Sequence number of the OfferCreate transaction + * that instantiated this offer. + * @return {UInt256} + */ +Ledger.calcOfferEntryHash = +Ledger.prototype.calcOfferEntryHash = function (account, sequence) { + account = UInt160.from_json(account); + sequence = parseInt(sequence); + + var index = new SerializedObject(); + + index.append([0, Ledger.space.offer.charCodeAt(0)]); + index.append(account.to_bytes()); + stypes.Int32.serialize(index, sequence); + + return index.hash(); +}; + +/** + * 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 (account1, account2, currency) { + currency = Currency.from_json(currency); + account1 = UInt160.from_json(account1); + account2 = UInt160.from_json(account2); + + if (!account1.is_valid()) { + throw new Error("Invalid first account"); + } + if (!account2.is_valid()) { + throw new Error("Invalid second account"); + } + if (!currency.is_valid()) { + throw new Error("Invalid currency"); + } + + // The lower ID has to come first + if (account1.to_bn().greaterEquals(account2.to_bn())) { + var tmp = account2; + account2 = account1; + account1 = tmp; + } + + var index = new SerializedObject(); + + index.append([0, Ledger.space.rippleState.charCodeAt(0)]); + index.append(account1.to_bytes()); + index.append(account2.to_bytes()); + index.append(currency.to_bytes()); + + return index.hash(); +}; + Ledger.prototype.parse_json = function (v) { this.ledger_json = v; }; diff --git a/src/js/ripple/ledgerspaces.js b/src/js/ripple/ledgerspaces.js new file mode 100644 index 00000000..57fbc22f --- /dev/null +++ b/src/js/ripple/ledgerspaces.js @@ -0,0 +1,22 @@ +/** + * 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' +}; \ No newline at end of file diff --git a/src/js/ripple/serializedobject.js b/src/js/ripple/serializedobject.js index 42044a46..841a9778 100644 --- a/src/js/ripple/serializedobject.js +++ b/src/js/ripple/serializedobject.js @@ -3,6 +3,7 @@ var extend = require('extend'); var binformat = require('./binformat'); var stypes = require('./serializedtypes'); var UInt256 = require('./uint256').UInt256; +var Crypt = require('./crypt').Crypt; var utils = require('./utils'); var sjcl = utils.sjcl; @@ -247,20 +248,23 @@ SerializedObject.prototype.serialize = function(typedef, obj) { SerializedObject.prototype.hash = function(prefix) { var sign_buffer = new SerializedObject(); - stypes.Int32.serialize(sign_buffer, prefix); + + // Add hashing prefix + if ("undefined" !== typeof prefix) { + stypes.Int32.serialize(sign_buffer, prefix); + } + + // Copy buffer to temporary buffer sign_buffer.append(this.buffer); - return sign_buffer.hash_sha512_half(); + + // XXX We need a proper Buffer class then Crypt could accept that + var bits = sjcl.codec.bytes.toBits(sign_buffer.buffer); + return Crypt.hashSha512Half(bits); }; // DEPRECATED SerializedObject.prototype.signing_hash = SerializedObject.prototype.hash; -SerializedObject.prototype.hash_sha512_half = function() { - var bits = sjcl.codec.bytes.toBits(this.buffer); - var hash = sjcl.bitArray.bitSlice(sjcl.hash.sha512.hash(bits), 0, 256); - return UInt256.from_hex(sjcl.codec.hex.fromBits(hash)); -}; - SerializedObject.prototype.serialize_field = function(spec, obj) { var name = spec[0]; var presence = spec[1]; diff --git a/test/ledger-test.js b/test/ledger-test.js index 9ccc20e6..23206fba 100644 --- a/test/ledger-test.js +++ b/test/ledger-test.js @@ -34,6 +34,55 @@ describe('Ledger', function() { create_ledger_test(38129); // Because, why not. create_ledger_test(40000); + + describe('#calcAccountRootEntryHash', function () { + it('will calculate the AccountRoot entry hash for rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh', function () { + var account = 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'; + var expectedEntryHash = '2B6AC232AA4C4BE41BF49D2459FA4A0347E1B543A4C92FCEE0821C0201E2E9A8'; + var actualEntryHash = Ledger.calcAccountRootEntryHash(account); + + assert.equal(actualEntryHash.to_hex(), expectedEntryHash); + }); + }); + + describe('#calcRippleStateEntryHash', function () { + it('will calculate the RippleState entry hash for rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh and rB5TihdPbKgMrkFqrqUC3yLdE8hhv4BdeY in USD', function () { + var account1 = 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'; + var account2 = 'rB5TihdPbKgMrkFqrqUC3yLdE8hhv4BdeY'; + var currency = 'USD'; + + var expectedEntryHash = 'C683B5BB928F025F1E860D9D69D6C554C2202DE0D45877ADB3077DA4CB9E125C'; + var actualEntryHash1 = Ledger.calcRippleStateEntryHash(account1, account2, currency); + var actualEntryHash2 = Ledger.calcRippleStateEntryHash(account2, account1, currency); + + assert.equal(actualEntryHash1.to_hex(), expectedEntryHash); + assert.equal(actualEntryHash2.to_hex(), expectedEntryHash); + }); + + it('will calculate the RippleState entry hash for r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV and rUAMuQTfVhbfqUDuro7zzy4jj4Wq57MPTj in UAM', function () { + var account1 = 'r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV'; + var account2 = 'rUAMuQTfVhbfqUDuro7zzy4jj4Wq57MPTj'; + var currency = 'UAM'; + + var expectedEntryHash = 'AE9ADDC584358E5847ADFC971834E471436FC3E9DE6EA1773DF49F419DC0F65E'; + var actualEntryHash1 = Ledger.calcRippleStateEntryHash(account1, account2, currency); + var actualEntryHash2 = Ledger.calcRippleStateEntryHash(account2, account1, currency); + + assert.equal(actualEntryHash1.to_hex(), expectedEntryHash); + assert.equal(actualEntryHash2.to_hex(), expectedEntryHash); + }); + }); + + describe('#calcOfferEntryHash', function () { + it('will calculate the Offer entry hash for r32UufnaCGL82HubijgJGDmdE5hac7ZvLw, sequence 137', function () { + var account = 'r32UufnaCGL82HubijgJGDmdE5hac7ZvLw'; + var sequence = 137 + var expectedEntryHash = '03F0AED09DEEE74CEF85CD57A0429D6113507CF759C597BABB4ADB752F734CE3'; + var actualEntryHash = Ledger.calcOfferEntryHash(account, sequence); + + assert.equal(actualEntryHash.to_hex(), expectedEntryHash); + }); + }); }); // vim:sw=2:sts=2:ts=8:et