From 1af930fbd164dd0c0391f2693709f4534fe6ceb4 Mon Sep 17 00:00:00 2001 From: Stefan Thomas Date: Fri, 8 Feb 2013 02:37:38 +0100 Subject: [PATCH 1/7] JS: Override SJCL ECDSA with a standards-compliant version. --- grunt.js | 1 + src/js/sjcl-custom/sjcl-validecc.js | 30 +++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 src/js/sjcl-custom/sjcl-validecc.js diff --git a/grunt.js b/grunt.js index dab1bcef57..a306fab645 100644 --- a/grunt.js +++ b/grunt.js @@ -36,6 +36,7 @@ module.exports = function(grunt) { "src/js/sjcl-custom/sjcl-secp256k1.js", "src/js/sjcl-custom/sjcl-ripemd160.js", "src/js/sjcl-custom/sjcl-extramath.js", + "src/js/sjcl-custom/sjcl-validecc.js", "src/js/sjcl-custom/sjcl-ecdsa-der.js" ], dest: 'build/sjcl.js' diff --git a/src/js/sjcl-custom/sjcl-validecc.js b/src/js/sjcl-custom/sjcl-validecc.js new file mode 100644 index 0000000000..99ecde393d --- /dev/null +++ b/src/js/sjcl-custom/sjcl-validecc.js @@ -0,0 +1,30 @@ +sjcl.ecc.ecdsa.secretKey.prototype = { + sign: function(hash, paranoia) { + var R = this._curve.r, + l = R.bitLength(), + k = sjcl.bn.random(R.sub(1), paranoia).add(1), + r = this._curve.G.mult(k).x.mod(R), + s = sjcl.bn.fromBits(hash).add(r.mul(this._exponent)).mul(k.inverseMod(R)).mod(R); + + return sjcl.bitArray.concat(r.toBits(l), s.toBits(l)); + } +}; + +sjcl.ecc.ecdsa.publicKey.prototype = { + verify: function(hash, rs) { + var w = sjcl.bitArray, + R = this._curve.r, + l = R.bitLength(), + r = sjcl.bn.fromBits(w.bitSlice(rs,0,l)), + s = sjcl.bn.fromBits(w.bitSlice(rs,l,2*l)), + sInv = s.modInverse(R), + hG = sjcl.bn.fromBits(hash).mul(sInv).mod(R), + hA = r.mul(sInv).mod(R), + r2 = this._curve.G.mult2(hG, hA, this._point).x; + + if (r.equals(0) || s.equals(0) || r.greaterEquals(R) || s.greaterEquals(R) || !r2.equals(r)) { + throw (new sjcl.exception.corrupt("signature didn't check out")); + } + return true; + } +}; From 8e81146f2e33d1d06aec190ada1c32bb7235d476 Mon Sep 17 00:00:00 2001 From: Stefan Thomas Date: Fri, 8 Feb 2013 02:38:15 +0100 Subject: [PATCH 2/7] JS: Crypto: Fix ECDSA DER signature encoding. --- src/js/sjcl-custom/sjcl-ecdsa-der.js | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/js/sjcl-custom/sjcl-ecdsa-der.js b/src/js/sjcl-custom/sjcl-ecdsa-der.js index befddbf084..30fe439d87 100644 --- a/src/js/sjcl-custom/sjcl-ecdsa-der.js +++ b/src/js/sjcl-custom/sjcl-ecdsa-der.js @@ -5,12 +5,18 @@ sjcl.ecc.ecdsa.secretKey.prototype.signDER = function(hash, paranoia) { sjcl.ecc.ecdsa.secretKey.prototype.encodeDER = function(rs) { var w = sjcl.bitArray, R = this._curve.r, - l = R.bitLength(), - r = sjcl.bn.fromBits(w.bitSlice(rs,0,l)).toBits(), - s = sjcl.bn.fromBits(w.bitSlice(rs,l,2*l)).toBits(); + l = R.bitLength(); - var rb = sjcl.codec.bytes.fromBits(r), - sb = sjcl.codec.bytes.fromBits(s); + var rb = sjcl.codec.bytes.fromBits(w.bitSlice(rs,0,l)), + sb = sjcl.codec.bytes.fromBits(w.bitSlice(rs,l,2*l)); + + // Drop empty leading bytes + while (!rb[0] && rb.length) rb.shift(); + while (!sb[0] && sb.length) sb.shift(); + + // If high bit is set, prepend an extra zero byte (DER signed integer) + if (rb[0] & 0x80) rb.unshift(0); + if (sb[0] & 0x80) sb.unshift(0); var buffer = [].concat( 0x30, From 1e3fbcb641ef5e1369f0fcb3a8fd58d02e2011ca Mon Sep 17 00:00:00 2001 From: Stefan Thomas Date: Fri, 8 Feb 2013 03:14:49 +0100 Subject: [PATCH 3/7] JS: Seed from passphrase support. --- src/js/seed.js | 41 +++++++++++++++++++++++++++++++---------- 1 file changed, 31 insertions(+), 10 deletions(-) diff --git a/src/js/seed.js b/src/js/seed.js index fcd332ea86..9dbe2fb0d1 100644 --- a/src/js/seed.js +++ b/src/js/seed.js @@ -27,19 +27,37 @@ Seed.prototype.constructor = Seed; // value = NaN on error. // One day this will support rfc1751 too. Seed.prototype.parse_json = function (j) { - if ('string' !== typeof j) { - this._value = NaN; + if ('string' === typeof j) { + if (!j.length) { + this._value = NaN; + // XXX Should actually always try and continue if it failed. + } else if (j[0] === "s") { + this._value = Base.decode_check(Base.VER_FAMILY_SEED, j); + } else if (j.length === 32) { + this._value = this.parse_hex(j); + // XXX Should also try 1751 + } else { + this.parse_passphrase(j); + } + } else if (Array.isArray(j) && 16 === j.length) { + this._value = new BigInteger(utils.stringToArray(j), 128); + } else { + this._value = NaN; } - else if (j[0] === "s") { - this._value = Base.decode_check(Base.VER_FAMILY_SEED, j); - } - else if (16 === j.length) { - this._value = new BigInteger(utils.stringToArray(j), 128); - } - else { - this._value = NaN; + + return this; +}; + +Seed.prototype.parse_passphrase = function (j) { + if ("string" !== typeof j) { + throw new Error("Passphrase must be a string"); } + var hash = sjcl.hash.sha512.hash(sjcl.codec.utf8String.toBits(j)); + var bits = sjcl.bitArray.bitSlice(hash, 0, 128); + + this.parse_bits(bits); + return this; }; @@ -68,6 +86,9 @@ function SHA256_RIPEMD160(bits) { } Seed.prototype.get_key = function (account_id) { + if (!this.is_valid()) { + throw new Error("Cannot generate keys from invalid seed!"); + } // XXX Should loop over keys until we find the right one var curve = this._curve; From 937f61434595343722895ca5df0f2903d996c1de Mon Sep 17 00:00:00 2001 From: Stefan Thomas Date: Fri, 8 Feb 2013 03:15:43 +0100 Subject: [PATCH 4/7] JS: Correctly construct submit request when using local_signing. --- src/js/remote.js | 6 ++++++ src/js/transaction.js | 9 +++++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/js/remote.js b/src/js/remote.js index af05c2931c..8c2beb1e57 100644 --- a/src/js/remote.js +++ b/src/js/remote.js @@ -138,6 +138,12 @@ Request.prototype.tx_json = function (j) { return this; }; +Request.prototype.tx_blob = function (j) { + this.message.tx_blob = j; + + return this; +}; + Request.prototype.ripple_state = function (account, issuer, currency) { this.message.ripple_state = { 'accounts' : [ diff --git a/src/js/transaction.js b/src/js/transaction.js index e9d7e8c665..a6e0621ed9 100644 --- a/src/js/transaction.js +++ b/src/js/transaction.js @@ -356,6 +356,7 @@ Transaction.prototype.submit = function (callback) { return this; } else if (this.remote.local_signing) { this.sign(); + request.tx_blob(this.serialize().to_hex()); } else { if (!this.remote.trusted) { this.emit('error', { @@ -363,13 +364,13 @@ Transaction.prototype.submit = function (callback) { 'result_message' : "Attempt to give a secret to an untrusted server." }); return this; - } else { - request.secret(this._secret); } + + request.secret(this._secret); + request.build_path(this._build_path); + request.tx_json(this.tx_json); } - request.build_path(this._build_path); - request.tx_json(this.tx_json); request.request(); return this; From 7faea9c1b4a9fc1f4c6e912ce946f4e5e5473786 Mon Sep 17 00:00:00 2001 From: Stefan Thomas Date: Fri, 8 Feb 2013 03:16:42 +0100 Subject: [PATCH 5/7] JS: Support for more types in serializedtypes.js. --- src/js/serializedobject.js | 2 +- src/js/serializedtypes.js | 156 +++++++++++++++++++++++++++---------- 2 files changed, 116 insertions(+), 42 deletions(-) diff --git a/src/js/serializedobject.js b/src/js/serializedobject.js index e66a2001ac..f346f69524 100644 --- a/src/js/serializedobject.js +++ b/src/js/serializedobject.js @@ -92,7 +92,7 @@ SerializedObject.prototype.serialize_field = function (spec, obj) Type = spec.shift(); if ("undefined" !== typeof obj[name]) { - console.log(name, Type.id, field_id); + //console.log(name, Type.id, field_id); this.append(SerializedObject.get_field_header(Type.id, field_id)); try { diff --git a/src/js/serializedtypes.js b/src/js/serializedtypes.js index 2c61b64b36..93009a9fb7 100644 --- a/src/js/serializedtypes.js +++ b/src/js/serializedtypes.js @@ -12,7 +12,8 @@ var extend = require('extend'), var amount = require('./amount'), UInt160 = amount.UInt160, - Amount = amount.Amount; + Amount = amount.Amount, + Currency= amount.Currency; // Shortcuts var hex = sjcl.codec.hex, @@ -47,7 +48,7 @@ SerializedType.prototype.serialize_varint = function (so, val) { } else throw new Error("Variable integer overflow."); }; -exports.Int8 = new SerializedType({ +var STInt8 = exports.Int8 = new SerializedType({ serialize: function (so, val) { so.append([val & 0xff]); }, @@ -56,7 +57,7 @@ exports.Int8 = new SerializedType({ } }); -exports.Int16 = new SerializedType({ +var STInt16 = exports.Int16 = new SerializedType({ serialize: function (so, val) { so.append([ val >>> 8 & 0xff, @@ -65,10 +66,11 @@ exports.Int16 = new SerializedType({ }, parse: function (so) { // XXX + throw new Error("Parsing Int16 not implemented"); } }); -exports.Int32 = new SerializedType({ +var STInt32 = exports.Int32 = new SerializedType({ serialize: function (so, val) { so.append([ val >>> 24 & 0xff, @@ -79,46 +81,82 @@ exports.Int32 = new SerializedType({ }, parse: function (so) { // XXX + throw new Error("Parsing Int32 not implemented"); } }); -exports.Int64 = new SerializedType({ +var STInt64 = exports.Int64 = new SerializedType({ serialize: function (so, val) { // XXX + throw new Error("Serializing Int64 not implemented"); }, parse: function (so) { // XXX + throw new Error("Parsing Int64 not implemented"); } }); -exports.Hash128 = new SerializedType({ +var STHash128 = exports.Hash128 = new SerializedType({ serialize: function (so, val) { // XXX + throw new Error("Serializing Hash128 not implemented"); }, parse: function (so) { // XXX + throw new Error("Parsing Hash128 not implemented"); } }); -exports.Hash256 = new SerializedType({ +var STHash256 = exports.Hash256 = new SerializedType({ serialize: function (so, val) { // XXX + throw new Error("Serializing Hash256 not implemented"); }, parse: function (so) { // XXX + throw new Error("Parsing Hash256 not implemented"); } }); -exports.Hash160 = new SerializedType({ +var STHash160 = exports.Hash160 = new SerializedType({ serialize: function (so, val) { // XXX + throw new Error("Serializing Hash160 not implemented"); }, parse: function (so) { // XXX + throw new Error("Parsing Hash160 not implemented"); } }); -exports.Amount = new SerializedType({ +// Internal +var STCurrency = new SerializedType({ + serialize: function (so, val) { + var currency = val.to_json(); + if ("string" === typeof currency && currency.length === 3) { + var currencyCode = currency.toUpperCase(), + currencyData = utils.arraySet(20, 0); + + if (!/^[A-Z]{3}$/.test(currencyCode)) { + throw new Error('Invalid currency code'); + } + + currencyData[12] = currencyCode.charCodeAt(0) & 0xff; + currencyData[13] = currencyCode.charCodeAt(1) & 0xff; + currencyData[14] = currencyCode.charCodeAt(2) & 0xff; + + so.append(currencyData); + } else { + throw new Error('Tried to serialize invalid/unimplemented currency type.'); + } + }, + parse: function (so) { + // XXX + throw new Error("Parsing Currency not implemented"); + } +}); + +var STAmount = exports.Amount = new SerializedType({ serialize: function (so, val) { var amount = Amount.from_json(val); if (!amount.is_valid()) { @@ -126,6 +164,7 @@ exports.Amount = new SerializedType({ } // Amount (64-bit integer) + var valueBytes = utils.arraySet(8, 0); if (amount.is_native()) { var valueHex = amount._value.toString(16); @@ -137,103 +176,138 @@ exports.Amount = new SerializedType({ valueHex = "0" + valueHex; } - var valueBytes = bytes.fromBits(hex.toBits(valueHex)); + valueBytes = bytes.fromBits(hex.toBits(valueHex)); // Clear most significant two bits - these bits should already be 0 if // Amount enforces the range correctly, but we'll clear them anyway just // so this code can make certain guarantees about the encoded value. valueBytes[0] &= 0x3f; if (!amount.is_negative()) valueBytes[0] |= 0x40; - - so.append(valueBytes); } else { - // XXX - throw new Error("Non-native amounts not implemented!"); + var hi = 0, lo = 0; + + // First bit: non-native + hi |= 1 << 31; + + if (!amount.is_zero()) { + // Second bit: non-negative? + if (!amount.is_negative()) hi |= 1 << 30; + + // Next eight bits: offset/exponent + hi |= ((97 + amount._offset) & 0xff) << 22; + + // Remaining 52 bits: mantissa + hi |= amount._value.shiftRight(32).intValue() & 0x3fffff; + lo = amount._value.intValue() & 0xffffffff; + } + + valueBytes = sjcl.codec.bytes.fromBits([hi, lo]); } + so.append(valueBytes); + if (!amount.is_native()) { // Currency (160-bit hash) - var currency = amount.currency().to_json(); - if ("string" === typeof currency && currency.length === 3) { - var currencyCode = currency.toUpperCase(), - currencyData = utils.arraySet(20, 0); - - if (!/^[A-Z]{3}$/.test(currencyCode)) { - throw new Error('Invalid currency code'); - } - - currencyData[12] = currencyCode.charCodeAt(0) & 0xff; - currencyData[13] = currencyCode.charCodeAt(1) & 0xff; - currencyData[14] = currencyCode.charCodeAt(2) & 0xff; - - var currencyBits = bytes.toBits(currencyData), - currencyHash = sjcl.hash.ripemd160.hash(currencyBits); - - so.append(bytes.fromBits(currencyHash)); - } else { - throw new Error('Tried to serialize invalid/unimplemented currency type.'); - } + var currency = amount.currency(); + STCurrency.serialize(so, currency); // Issuer (160-bit hash) - // XXX + so.append(amount.issuer().to_bytes()); } }, parse: function (so) { // XXX + throw new Error("Parsing Amount not implemented"); } }); -exports.VariableLength = new SerializedType({ +var STVL = exports.VariableLength = new SerializedType({ serialize: function (so, val) { if ("string" === typeof val) this.serialize_hex(so, val); else throw new Error("Unknown datatype."); }, parse: function (so) { // XXX + throw new Error("Parsing VL not implemented"); } }); -exports.Account = new SerializedType({ +var STAccount = exports.Account = new SerializedType({ serialize: function (so, val) { var account = UInt160.from_json(val); this.serialize_hex(so, account.to_hex()); }, parse: function (so) { // XXX + throw new Error("Parsing Account not implemented"); } }); -exports.PathSet = new SerializedType({ +var STPathSet = exports.PathSet = new SerializedType({ serialize: function (so, val) { // XXX + for (var i = 0, l = val.length; i < l; i++) { + for (var j = 0, l2 = val[i].length; j < l2; j++) { + var entry = val[i][j]; + + var type = 0; + + if (entry.account) type |= 0x01; + if (entry.currency) type |= 0x10; + if (entry.issuer) type |= 0x20; + + STInt8.serialize(so, type); + + if (entry.account) { + so.append(UInt160.from_json(entry.account).to_bytes()); + } + if (entry.currency) { + var currency = Currency.from_json(entry.currency); + STCurrency.serialize(so, currency); + } + if (entry.issuer) { + so.append(UInt160.from_json(entry.issuer).to_bytes()); + } + } + + if (j < l2) STInt8.serialize(so, 0xff); + } + STInt8.serialize(so, 0x00); }, parse: function (so) { // XXX + throw new Error("Parsing PathSet not implemented"); } }); -exports.Vector256 = new SerializedType({ +var STVector256 = exports.Vector256 = new SerializedType({ serialize: function (so, val) { // XXX + throw new Error("Serializing Vector256 not implemented"); }, parse: function (so) { // XXX + throw new Error("Parsing Vector256 not implemented"); } }); -exports.Object = new SerializedType({ +var STObject = exports.Object = new SerializedType({ serialize: function (so, val) { // XXX + throw new Error("Serializing Object not implemented"); }, parse: function (so) { // XXX + throw new Error("Parsing Object not implemented"); } }); -exports.Array = new SerializedType({ +var STArray = exports.Array = new SerializedType({ serialize: function (so, val) { // XXX + throw new Error("Serializing Array not implemented"); }, parse: function (so) { // XXX + throw new Error("Parsing Array not implemented"); } }); From e44fa621e4e059ba02b79b13326b9a58ee47c874 Mon Sep 17 00:00:00 2001 From: Arthur Britto Date: Thu, 7 Feb 2013 18:39:21 -0800 Subject: [PATCH 6/7] UT: Throw if nodejs version is less than 0.8.18. --- test/server.js | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/test/server.js b/test/server.js index 5a5d79cc24..5b9e639586 100644 --- a/test/server.js +++ b/test/server.js @@ -32,6 +32,28 @@ var Server = function (name, config, verbose) { this.started = false; this.quiet = !verbose; this.stopping = false; + + var nodejs_version = process.version.match(/^v(\d+)+\.(\d+)\.(\d+)$/).slice(1,4); + var wanted_version = [ 0, 8, 18 ]; + + while (wanted_version.length && nodejs_version.length && nodejs_version[0] == wanted_version[0]) + { + nodejs_version.shift(); + wanted_version.shift(); + } + + var sgn = !nodejs_version.length && !wanted_version.length + ? 0 + : nodejs_version.length + ? nodejs_version[0] - wanted_version[0] + : -1; + + if (sgn < 0) + { + console.log("\n*** Node.js version is too low."); + + throw "Nodejs version is too low."; + } }; Server.prototype = new EventEmitter; From 6918528d234c5dab8b7efe6457a394194472bd5f Mon Sep 17 00:00:00 2001 From: Stefan Thomas Date: Fri, 8 Feb 2013 03:56:44 +0100 Subject: [PATCH 7/7] JS: Remove broken Seed parse_json string interpretation. --- src/js/seed.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/js/seed.js b/src/js/seed.js index 9dbe2fb0d1..11d1f226fd 100644 --- a/src/js/seed.js +++ b/src/js/seed.js @@ -39,8 +39,6 @@ Seed.prototype.parse_json = function (j) { } else { this.parse_passphrase(j); } - } else if (Array.isArray(j) && 16 === j.length) { - this._value = new BigInteger(utils.stringToArray(j), 128); } else { this._value = NaN; }