diff --git a/build/sjcl.js b/build/sjcl.js index 5b4baf99..6eb17447 100644 --- a/build/sjcl.js +++ b/build/sjcl.js @@ -3497,10 +3497,10 @@ sjcl.ecc.point.prototype.isOnCurve = function() { var component_b = self.curve.b; var field_modulus = self.curve.field.modulus; - var y_squared_mod_field_order = self.y.mul(self.y).mod(field_modulus); - var x_cubed_plus_ax_plus_b = self.x.mul(self.x).mul(self.x).add(component_a.mul(self.x)).add(component_b).mod(field_modulus); + var left_hand_side = self.y.mul(self.y).mod(field_modulus); + var right_hand_side = self.x.mul(self.x).mul(self.x).add(component_a.mul(self.x)).add(component_b).mod(field_modulus); - return y_squared_mod_field_order.equals(x_cubed_plus_ax_plus_b); + return left_hand_side.equals(right_hand_side); }; diff --git a/scripts/verify_ledger_json.js b/scripts/verify_ledger_json.js index 81dba743..f942a10f 100644 --- a/scripts/verify_ledger_json.js +++ b/scripts/verify_ledger_json.js @@ -9,6 +9,8 @@ if (process.argc < 1) { var json = fs.readFileSync(process.argv[2], 'utf-8'); var ledger = Ledger.from_json(JSON.parse(json)); -console.log("Calculated transaction hash: "+ledger.calc_tx_hash().to_hex()) console.log("Transaction hash in header: "+ledger.ledger_json.transaction_hash); +console.log("Calculated transaction hash: "+ledger.calc_tx_hash().to_hex()); +console.log("Account state hash in header: "+ledger.ledger_json.account_hash); +console.log("Calculated account state hash: "+ledger.calc_account_hash().to_hex()); diff --git a/src/js/ripple/binformat.js b/src/js/ripple/binformat.js index 65229b6c..4c85daf7 100644 --- a/src/js/ripple/binformat.js +++ b/src/js/ripple/binformat.js @@ -208,6 +208,7 @@ var base = [ [ 'TransactionType' , REQUIRED ], [ 'Flags' , OPTIONAL ], [ 'SourceTag' , OPTIONAL ], + [ 'LastLedgerSequence' , OPTIONAL ], [ 'Account' , REQUIRED ], [ 'Sequence' , REQUIRED ], [ 'Fee' , REQUIRED ], @@ -274,133 +275,105 @@ exports.tx = { ]) }; -exports.ledger = { - AccountRoot: [97], - Contract: [99], - DirectoryNode: [100], - Features: [102], - GeneratorMap: [103], - LedgerHashes: [104], - Nickname: [110], - Offer: [111], - RippleState: [114], - FeeSettings: [115] -}; -/* -TODO: -Need `base` factored out -AccountRoot needs AccountTxnID +var sleBase = [ + ['LedgerIndex', OPTIONAL], + ['LedgerEntryType', REQUIRED], + ['Flags', REQUIRED] +]; -{ - 'AccountRoot': [97, - ['LedgerEntryType', REQUIRED], - ['Flags', REQUIRED], - ['Sequence', REQUIRED], - ['PreviousTxnLgrSeq', REQUIRED], - ['TransferRate', OPTIONAL], - ['WalletSize', OPTIONAL], - ['OwnerCount', REQUIRED], - ['EmailHash', OPTIONAL], - ['PreviousTxnID', REQUIRED], - ['LedgerIndex', OPTIONAL], - ['WalletLocator', OPTIONAL], - ['Balance', REQUIRED], - ['MessageKey', OPTIONAL,], - ['Domain', OPTIONAL,], - ['Account', REQUIRED], - ['RegularKey', OPTIONAL]], - 'Contract': [99, - ['LedgerEntryType', REQUIRED], - ['Flags', REQUIRED], - ['PreviousTxnLgrSeq', REQUIRED], - ['Expiration', REQUIRED], - ['BondAmount', REQUIRED], - ['PreviousTxnID', REQUIRED], - ['LedgerIndex', OPTIONAL], - ['Balance', REQUIRED], - ['FundCode', OPTIONAL], - ['RemoveCode', OPTIONAL], - ['ExpireCode', OPTIONAL], - ['CreateCode', OPTIONAL], - ['Account', REQUIRED], - ['Owner', REQUIRED], - ['Issuer', REQUIRED]], - 'DirectoryNode': [100, - ['LedgerEntryType', REQUIRED], - ['Flags', REQUIRED], - ['IndexNext', OPTIONAL], - ['IndexPrevious', OPTIONAL], - ['ExchangeRate', OPTIONAL], - ['LedgerIndex', OPTIONAL], - ['RootIndex', REQUIRED], - ['Owner', OPTIONAL], - ['TakerPaysCurrency', OPTIONAL], - ['TakerPaysIssuer', OPTIONAL], - ['TakerGetsCurrency', OPTIONAL], - ['TakerGetsIssuer', OPTIONAL], - ['Indexes', REQUIRED]], - 'EnabledFeatures': [102, - ['LedgerEntryType', REQUIRED], - ['Flags', REQUIRED], - ['LedgerIndex', OPTIONAL], - ['Features', REQUIRED]], - 'FeeSettings': [115, - ['LedgerEntryType', REQUIRED], - ['Flags', REQUIRED], - ['ReferenceFeeUnits', REQUIRED], - ['ReserveBase', REQUIRED], - ['ReserveIncrement', REQUIRED], - ['BaseFee', REQUIRED], - ['LedgerIndex', OPTIONAL]], - 'GeneratorMap': [103, - ['LedgerEntryType', REQUIRED], - ['Flags', REQUIRED], - ['LedgerIndex', OPTIONAL], - ['Generator', REQUIRED,]], - 'LedgerHashes': [104, - ['LedgerEntryType', REQUIRED], - ['Flags', REQUIRED], - ['FirstLedgerSequence', OPTIONAL], - ['LastLedgerSequence', OPTIONAL], - ['LedgerIndex', OPTIONAL], - ['Hashes', REQUIRED]], - 'Nickname': [110, - ['LedgerEntryType', REQUIRED], - ['Flags', REQUIRED], - ['LedgerIndex', OPTIONAL], - ['MinimumOffer', OPTIONAL], - ['Account', REQUIRED]], - 'Offer': [111, - ['LedgerEntryType', REQUIRED], - ['Flags', REQUIRED], - ['Sequence', REQUIRED], - ['PreviousTxnLgrSeq', REQUIRED], - ['Expiration', OPTIONAL], - ['BookNode', REQUIRED], - ['OwnerNode', REQUIRED], - ['PreviousTxnID', REQUIRED], - ['LedgerIndex', OPTIONAL], - ['BookDirectory', REQUIRED], - ['TakerPays', REQUIRED], - ['TakerGets', REQUIRED], - ['Account', REQUIRED]], - 'RippleState': [114, - ['LedgerEntryType', REQUIRED], - ['Flags', REQUIRED], - ['PreviousTxnLgrSeq', REQUIRED], - ['HighQualityIn', OPTIONAL], - ['HighQualityOut', OPTIONAL], - ['LowQualityIn', OPTIONAL], - ['LowQualityOut', OPTIONAL], - ['LowNode', OPTIONAL], - ['HighNode', OPTIONAL], - ['PreviousTxnID', REQUIRED], - ['LedgerIndex', OPTIONAL], - ['Balance', REQUIRED], - ['LowLimit', REQUIRED], - ['HighLimit', REQUIRED]] +exports.ledger = { + AccountRoot: [97].concat(sleBase,[ + ['Sequence', REQUIRED], + ['PreviousTxnLgrSeq', REQUIRED], + ['TransferRate', OPTIONAL], + ['WalletSize', OPTIONAL], + ['OwnerCount', REQUIRED], + ['EmailHash', OPTIONAL], + ['PreviousTxnID', REQUIRED], + ['AccountTxnID', OPTIONAL], + ['WalletLocator', OPTIONAL], + ['Balance', REQUIRED], + ['MessageKey', OPTIONAL], + ['Domain', OPTIONAL], + ['Account', REQUIRED], + ['RegularKey', OPTIONAL]]), + Contract: [99].concat(sleBase,[ + ['PreviousTxnLgrSeq', REQUIRED], + ['Expiration', REQUIRED], + ['BondAmount', REQUIRED], + ['PreviousTxnID', REQUIRED], + ['Balance', REQUIRED], + ['FundCode', OPTIONAL], + ['RemoveCode', OPTIONAL], + ['ExpireCode', OPTIONAL], + ['CreateCode', OPTIONAL], + ['Account', REQUIRED], + ['Owner', REQUIRED], + ['Issuer', REQUIRED]]), + DirectoryNode: [100].concat(sleBase,[ + ['IndexNext', OPTIONAL], + ['IndexPrevious', OPTIONAL], + ['ExchangeRate', OPTIONAL], + ['RootIndex', REQUIRED], + ['Owner', OPTIONAL], + ['TakerPaysCurrency', OPTIONAL], + ['TakerPaysIssuer', OPTIONAL], + ['TakerGetsCurrency', OPTIONAL], + ['TakerGetsIssuer', OPTIONAL], + ['Indexes', REQUIRED]]), + EnabledFeatures: [102].concat(sleBase,[ + ['Features', REQUIRED]]), + FeeSettings: [115].concat(sleBase,[ + ['ReferenceFeeUnits', REQUIRED], + ['ReserveBase', REQUIRED], + ['ReserveIncrement', REQUIRED], + ['BaseFee', REQUIRED], + ['LedgerIndex', OPTIONAL]]), + GeneratorMap: [103].concat(sleBase,[ + ['Generator', REQUIRED]]), + LedgerHashes: [104].concat(sleBase,[ + ['LedgerEntryType', REQUIRED], + ['Flags', REQUIRED], + ['FirstLedgerSequence', OPTIONAL], + ['LastLedgerSequence', OPTIONAL], + ['LedgerIndex', OPTIONAL], + ['Hashes', REQUIRED]]), + Nickname: [110].concat(sleBase,[ + ['LedgerEntryType', REQUIRED], + ['Flags', REQUIRED], + ['LedgerIndex', OPTIONAL], + ['MinimumOffer', OPTIONAL], + ['Account', REQUIRED]]), + Offer: [111].concat(sleBase,[ + ['LedgerEntryType', REQUIRED], + ['Flags', REQUIRED], + ['Sequence', REQUIRED], + ['PreviousTxnLgrSeq', REQUIRED], + ['Expiration', OPTIONAL], + ['BookNode', REQUIRED], + ['OwnerNode', REQUIRED], + ['PreviousTxnID', REQUIRED], + ['LedgerIndex', OPTIONAL], + ['BookDirectory', REQUIRED], + ['TakerPays', REQUIRED], + ['TakerGets', REQUIRED], + ['Account', REQUIRED]]), + RippleState: [114].concat(sleBase,[ + ['LedgerEntryType', REQUIRED], + ['Flags', REQUIRED], + ['PreviousTxnLgrSeq', REQUIRED], + ['HighQualityIn', OPTIONAL], + ['HighQualityOut', OPTIONAL], + ['LowQualityIn', OPTIONAL], + ['LowQualityOut', OPTIONAL], + ['LowNode', OPTIONAL], + ['HighNode', OPTIONAL], + ['PreviousTxnID', REQUIRED], + ['LedgerIndex', OPTIONAL], + ['Balance', REQUIRED], + ['LowLimit', REQUIRED], + ['HighLimit', REQUIRED]]) } -*/ exports.metadata = [ [ 'TransactionIndex' , REQUIRED ], diff --git a/src/js/ripple/hashprefixes.js b/src/js/ripple/hashprefixes.js index 11d8c101..b4ce7405 100644 --- a/src/js/ripple/hashprefixes.js +++ b/src/js/ripple/hashprefixes.js @@ -17,6 +17,8 @@ exports.HASH_TX_ID = 0x54584E00; // 'TXN' 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) diff --git a/src/js/ripple/ledger.js b/src/js/ripple/ledger.js index 9acf0462..b45d5cab 100644 --- a/src/js/ripple/ledger.js +++ b/src/js/ripple/ledger.js @@ -37,4 +37,16 @@ Ledger.prototype.calc_tx_hash = function () { return tx_map.hash(); }; +Ledger.prototype.calc_account_hash = function () { + var account_map = new SHAMap(); + + this.ledger_json.accountState.forEach(function (le) { + var data = SerializedObject.from_json(le); + + account_map.add_item(le.index, data, SHAMapTreeNode.TYPE_ACCOUNT_STATE); + }); + + return account_map.hash(); +}; + exports.Ledger = Ledger; diff --git a/src/js/ripple/serializedobject.js b/src/js/ripple/serializedobject.js index d7589425..6f4dbe7f 100644 --- a/src/js/ripple/serializedobject.js +++ b/src/js/ripple/serializedobject.js @@ -53,6 +53,14 @@ SerializedObject.from_json = function (obj) { } } + if ("number" === typeof obj.TransactionType) { + obj.LedgerEntryType = SerializedObject.lookup_type_le(obj.LedgerEntryType); + + if (!obj.LedgerEntryType) { + throw new Error('LedgerEntryType ID is invalid.'); + } + } + if ("string" === typeof obj.TransactionType) { typedef = binformat.tx[obj.TransactionType]; @@ -62,9 +70,16 @@ SerializedObject.from_json = function (obj) { typedef = typedef.slice(); obj.TransactionType = typedef.shift(); - } else if ("undefined" !== typeof obj.LedgerEntryType) { - // XXX: TODO - throw new Error('Ledger entry binary format not yet implemented.'); + } else if ("string" === typeof obj.LedgerEntryType) { + typedef = binformat.ledger[obj.LedgerEntryType]; + + if (!Array.isArray(typedef)) { + throw new Error('LedgerEntryType is invalid'); + } + + typedef = typedef.slice(); + obj.LedgerEntryType = typedef.shift(); + } else if ("object" === typeof obj.AffectedNodes) { typedef = binformat.metadata; } else { @@ -81,7 +96,7 @@ SerializedObject.from_json = function (obj) { SerializedObject.check_no_missing_fields = function (typedef, obj) { var missing_fields = []; - + for (var i = typedef.length - 1; i >= 0; i--) { var spec = typedef[i]; var field = spec[0] @@ -91,17 +106,18 @@ SerializedObject.check_no_missing_fields = function (typedef, obj) { missing_fields.push(field); }; }; - + if (missing_fields.length > 0) { var object_name; + if (obj.TransactionType != null) { object_name = SerializedObject.lookup_type_tx(obj.TransactionType); + } else if (obj.LedgerEntryType != null){ + object_name = SerializedObject.lookup_type_le(obj.LedgerEntryType); } else { object_name = "TransactionMetaData"; - } /*else { - TODO: LedgerEntryType ... - }*/ - throw new Error(object_name + " is missing fields: " + + } + throw new Error(object_name + " is missing fields: " + JSON.stringify(missing_fields)); }; } @@ -296,4 +312,9 @@ SerializedObject.lookup_type_tx = function (id) { return TRANSACTION_TYPES[id]; }; +SerializedObject.lookup_type_le = function (id) { + assert(typeof id === 'number'); + return LEDGER_ENTRY_TYPES[id]; +}; + exports.SerializedObject = SerializedObject; diff --git a/src/js/ripple/shamap.js b/src/js/ripple/shamap.js index bc2823ab..4b061d2f 100644 --- a/src/js/ripple/shamap.js +++ b/src/js/ripple/shamap.js @@ -44,13 +44,16 @@ SHAMapTreeNode.prototype.hash = function () { /** * Inner (non-leaf) node in a SHAMap tree. */ -function SHAMapTreeNodeInner() { +function SHAMapTreeNodeInner(depth) { SHAMapTreeNode.call(this); this.leaves = {}; this.type = SHAMapTreeNode.INNER; + // TODO + this.depth == depth == null ? 0 : depth; + this.empty = true; } @@ -156,6 +159,10 @@ SHAMapTreeNodeLeaf.prototype.set_segment = function (segment) { SHAMapTreeNodeLeaf.prototype.hash = function () { var buffer = new SerializedObject(); switch (this.type) { + case SHAMapTreeNode.TYPE_ACCOUNT_STATE: + buffer.append(this.node); + buffer.append(this.tag.to_bytes()); + return buffer.hash(hashprefixes.HASH_LEAF_NODE); case SHAMapTreeNode.TYPE_TRANSACTION_NM: return this.tag; case SHAMapTreeNode.TYPE_TRANSACTION_MD: