diff --git a/grunt.js b/grunt.js index 152cef351..dab1bcef5 100644 --- a/grunt.js +++ b/grunt.js @@ -32,7 +32,11 @@ module.exports = function(grunt) { "src/js/sjcl/core/convenience.js", "src/js/sjcl/core/bn.js", "src/js/sjcl/core/ecc.js", - "src/js/sjcl/core/srp.js" + "src/js/sjcl/core/srp.js", + "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-ecdsa-der.js" ], dest: 'build/sjcl.js' } diff --git a/src/cpp/ripple/RPCHandler.cpp b/src/cpp/ripple/RPCHandler.cpp index e6a245aad..65bb7996b 100644 --- a/src/cpp/ripple/RPCHandler.cpp +++ b/src/cpp/ripple/RPCHandler.cpp @@ -296,6 +296,11 @@ Json::Value RPCHandler::transactionSign(Json::Value jvRequest, bool bSubmit) return jvResult; } + if (jvRequest.isMember("debug_signing")) { + jvResult["tx_unsigned"] = strHex(stpTrans->getSerializer().peekData()); + jvResult["tx_signing_hash"] = stpTrans->getSigningHash().ToString(); + } + // FIXME: For performance, transactions should not be signed in this code path. stpTrans->sign(naAccountPrivate); diff --git a/src/js/README.md b/src/js/README.md new file mode 100644 index 000000000..7b357e08b --- /dev/null +++ b/src/js/README.md @@ -0,0 +1,3 @@ +# Ripple JavaScript library + +This library lets you connect to a ripple server via websockets. diff --git a/src/js/amount.js b/src/js/amount.js index b24ff0397..7614e4872 100644 --- a/src/js/amount.js +++ b/src/js/amount.js @@ -3,27 +3,18 @@ var sjcl = require('../../build/sjcl'); var bn = sjcl.bn; -var utils = require('./utils.js'); -var jsbn = require('./jsbn.js'); +var utils = require('./utils'); +var jsbn = require('./jsbn'); -var BigInteger = jsbn.BigInteger; -var nbi = jsbn.nbi; +var BigInteger = jsbn.BigInteger; -var alphabets = { - 'ripple' : "rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz", - 'tipple' : "RPShNAF39wBUDnEGHJKLM4pQrsT7VWXYZ2bcdeCg65jkm8ofqi1tuvaxyz", - 'bitcoin' : "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" -}; +var UInt160 = require('./uint160').UInt160, + Seed = require('./seed').Seed, + Currency = require('./currency').Currency; var consts = exports.consts = { - 'address_xns' : "rrrrrrrrrrrrrrrrrrrrrhoLvTp", - 'address_one' : "rrrrrrrrrrrrrrrrrrrrBZbvji", 'currency_xns' : 0, 'currency_one' : 1, - 'uint160_xns' : utils.hexToString("0000000000000000000000000000000000000000"), - 'uint160_one' : utils.hexToString("0000000000000000000000000000000000000001"), - 'hex_xns' : "0000000000000000000000000000000000000000", - 'hex_one' : "0000000000000000000000000000000000000001", 'xns_precision' : 6, // BigInteger values prefixed with bi_. @@ -42,421 +33,8 @@ var consts = exports.consts = { 'cMinOffset' : -96, 'cMaxOffset' : 80, - - 'VER_NONE' : 1, - 'VER_NODE_PUBLIC' : 28, - 'VER_NODE_PRIVATE' : 32, - 'VER_ACCOUNT_ID' : 0, - 'VER_ACCOUNT_PUBLIC' : 35, - 'VER_ACCOUNT_PRIVATE' : 34, - 'VER_FAMILY_GENERATOR' : 41, - 'VER_FAMILY_SEED' : 33, }; -// --> input: big-endian array of bytes. -// <-- string at least as long as input. -var encode_base = function (input, alphabet) { - var alphabet = alphabets[alphabet || 'ripple']; - var bi_base = new BigInteger(String(alphabet.length)); - var bi_q = nbi(); - var bi_r = nbi(); - var bi_value = new BigInteger(input); - var buffer = []; - - while (bi_value.compareTo(BigInteger.ZERO) > 0) - { - bi_value.divRemTo(bi_base, bi_q, bi_r); - bi_q.copyTo(bi_value); - - buffer.push(alphabet[bi_r.intValue()]); - } - - var i; - - for (i = 0; i != input.length && !input[i]; i += 1) { - buffer.push(alphabet[0]); - } - - return buffer.reverse().join(""); -}; - -// --> input: String -// <-- array of bytes or undefined. -var decode_base = function (input, alphabet) { - var alphabet = alphabets[alphabet || 'ripple']; - var bi_base = new BigInteger(String(alphabet.length)); - var bi_value = nbi(); - var i; - - for (i = 0; i != input.length && input[i] === alphabet[0]; i += 1) - ; - - for (; i != input.length; i += 1) { - var v = alphabet.indexOf(input[i]); - - if (v < 0) - return undefined; - - var r = nbi(); - - r.fromInt(v); - - bi_value = bi_value.multiply(bi_base).add(r); - } - - // toByteArray: - // - Returns leading zeros! - // - Returns signed bytes! - var bytes = bi_value.toByteArray().map(function (b) { return b ? b < 0 ? 256+b : b : 0}); - var extra = 0; - - while (extra != bytes.length && !bytes[extra]) - extra += 1; - - if (extra) - bytes = bytes.slice(extra); - - var zeros = 0; - - while (zeros !== input.length && input[zeros] === alphabet[0]) - zeros += 1; - - return [].concat(utils.arraySet(zeros, 0), bytes); -}; - -var sha256 = function (bytes) { - return sjcl.codec.bytes.fromBits(sjcl.hash.sha256.hash(sjcl.codec.bytes.toBits(bytes))); -}; - -var sha256hash = function (bytes) { - return sha256(sha256(bytes)); -}; - -// --> input: Array -// <-- String -var encode_base_check = function (version, input, alphabet) { - var buffer = [].concat(version, input); - var check = sha256(sha256(buffer)).slice(0, 4); - - return encode_base([].concat(buffer, check), alphabet); -} - -// --> input : String -// <-- NaN || BigInteger -var decode_base_check = function (version, input, alphabet) { - var buffer = decode_base(input, alphabet); - - if (!buffer || buffer[0] !== version || buffer.length < 5) - return NaN; - - var computed = sha256hash(buffer.slice(0, -4)).slice(0, 4); - var checksum = buffer.slice(-4); - var i; - - for (i = 0; i != 4; i += 1) - if (computed[i] !== checksum[i]) - return NaN; - - return new BigInteger(buffer.slice(1, -4)); -} - -// -// Seed support -// - -var Seed = function () { - // Internal form: NaN or BigInteger - this._value = NaN; -}; - -Seed.json_rewrite = function (j) { - return Seed.from_json(j).to_json(); -}; - -// Return a new Seed from j. -Seed.from_json = function (j) { - return 'string' === typeof j - ? (new Seed()).parse_json(j) - : j.clone(); -}; - -Seed.is_valid = function (j) { - return Seed.from_json(j).is_valid(); -}; - -Seed.prototype.clone = function () { - return this.copyTo(new Seed()); -}; - -// Returns copy. -Seed.prototype.copyTo = function (d) { - d._value = this._value; - - return d; -}; - -Seed.prototype.equals = function (d) { - return this._value instanceof BigInteger && d._value instanceof BigInteger && this._value.equals(d._value); -}; - -Seed.prototype.is_valid = function () { - return this._value instanceof BigInteger; -}; - -// value = NaN on error. -// One day this will support rfc1751 too. -Seed.prototype.parse_json = function (j) { - if ('string' !== typeof j) { - this._value = NaN; - } - else if (j[0] === "s") { - this._value = decode_base_check(consts.VER_FAMILY_SEED, j); - } - else if (16 === j.length) { - this._value = new BigInteger(utils.stringToArray(j), 128); - } - else { - this._value = NaN; - } - - return this; -}; - -// Convert from internal form. -Seed.prototype.to_json = function () { - if (!(this._value instanceof BigInteger)) - return NaN; - - var bytes = this._value.toByteArray().map(function (b) { return b ? b < 0 ? 256+b : b : 0}); - var target = 20; - - // XXX Make sure only trim off leading zeros. - var array = bytes.length < target - ? bytes.length - ? [].concat(utils.arraySet(target - bytes.length, 0), bytes) - : utils.arraySet(target, 0) - : bytes.slice(target - bytes.length); - var output = encode_base_check(consts.VER_FAMILY_SEED, array); - - return output; -}; - -// -// UInt160 support -// - -var UInt160 = function () { - // Internal form: NaN or BigInteger - this._value = NaN; -}; - -UInt160.json_rewrite = function (j) { - return UInt160.from_json(j).to_json(); -}; - -// Return a new UInt160 from j. -UInt160.from_generic = function (j) { - return 'string' === typeof j - ? (new UInt160()).parse_generic(j) - : j.clone(); -}; - -// Return a new UInt160 from j. -UInt160.from_json = function (j) { - if ('string' === typeof j) { - return (new UInt160()).parse_json(j); - } else if (j instanceof UInt160) { - return j.clone(); - } else { - return new UInt160(); - } -}; - -UInt160.is_valid = function (j) { - return UInt160.from_json(j).is_valid(); -}; - -UInt160.prototype.clone = function () { - return this.copyTo(new UInt160()); -}; - -// Returns copy. -UInt160.prototype.copyTo = function (d) { - d._value = this._value; - - return d; -}; - -UInt160.prototype.equals = function (d) { - return this._value instanceof BigInteger && d._value instanceof BigInteger && this._value.equals(d._value); -}; - -UInt160.prototype.is_valid = function () { - return this._value instanceof BigInteger; -}; - -// value = NaN on error. -UInt160.prototype.parse_generic = function (j) { - // Canonicalize and validate - if (exports.config.accounts && j in exports.config.accounts) - j = exports.config.accounts[j].account; - - switch (j) { - case undefined: - case "0": - case consts.address_xns: - case consts.uint160_xns: - case consts.hex_xns: - this._value = nbi(); - break; - - case "1": - case consts.address_one: - case consts.uint160_one: - case consts.hex_one: - this._value = new BigInteger([1]); - - break; - - default: - if ('string' !== typeof j) { - this._value = NaN; - } - else if (j[0] === "r") { - this._value = decode_base_check(consts.VER_ACCOUNT_ID, j); - } - else if (20 === j.length) { - this._value = new BigInteger(utils.stringToArray(j), 256); - } - else if (40 === j.length) { - // XXX Check char set! - this._value = new BigInteger(j, 16); - } - else { - this._value = NaN; - } - } - - return this; -}; - -// value = NaN on error. -UInt160.prototype.parse_json = function (j) { - // Canonicalize and validate - if (exports.config.accounts && j in exports.config.accounts) - j = exports.config.accounts[j].account; - - if ('string' !== typeof j) { - this._value = NaN; - } - else if (j[0] === "r") { - this._value = decode_base_check(consts.VER_ACCOUNT_ID, j); - } - else { - this._value = NaN; - } - - return this; -}; - -// Convert from internal form. -// XXX Json form should allow 0 and 1, C++ doesn't currently allow it. -UInt160.prototype.to_json = function () { - if (!(this._value instanceof BigInteger)) - return NaN; - - var bytes = this._value.toByteArray().map(function (b) { return b ? b < 0 ? 256+b : b : 0}); - var target = 20; - - // XXX Make sure only trim off leading zeros. - var array = bytes.length < target - ? bytes.length - ? [].concat(utils.arraySet(target - bytes.length, 0), bytes) - : utils.arraySet(target, 0) - : bytes.slice(target - bytes.length); - var output = encode_base_check(consts.VER_ACCOUNT_ID, array); - - return output; -}; - -// -// Currency support -// - -// XXX Internal form should be UInt160. -var Currency = function () { - // Internal form: 0 = XRP. 3 letter-code. - // XXX Internal should be 0 or hex with three letter annotation when valid. - - // Json form: - // '', 'XRP', '0': 0 - // 3-letter code: ... - // XXX Should support hex, C++ doesn't currently allow it. - - this._value = NaN; -} - -// Given "USD" return the json. -Currency.json_rewrite = function (j) { - return Currency.from_json(j).to_json(); -}; - -Currency.from_json = function (j) { - return 'string' === typeof j - ? (new Currency()).parse_json(j) - : j.clone(); -}; - -Currency.is_valid = function (j) { - return currency.from_json(j).is_valid(); -}; - -Currency.prototype.clone = function() { - return this.copyTo(new Currency()); -}; - -// Returns copy. -Currency.prototype.copyTo = function (d) { - d._value = this._value; - - return d; -}; - -Currency.prototype.equals = function (d) { - return ('string' !== typeof this._value && isNaN(this._value)) - || ('string' !== typeof d._value && isNaN(d._value)) ? false : this._value === d._value; -}; - -// this._value = NaN on error. -Currency.prototype.parse_json = function (j) { - if ("" === j || "0" === j || "XRP" === j) { - this._value = 0; - } - else if ('string' != typeof j || 3 !== j.length) { - this._value = NaN; - } - else { - this._value = j; - } - - return this; -}; - -Currency.prototype.is_native = function () { - return !isNaN(this._value) && !this._value; -}; - -Currency.prototype.is_valid = function () { - return !isNaN(this._value); -}; - -Currency.prototype.to_json = function () { - return this._value ? this._value : "XRP"; -}; - -Currency.prototype.to_human = function () { - return this._value ? this._value : "XRP"; -}; // // Amount class in the style of Java's BigInteger class @@ -505,9 +83,9 @@ Amount.is_valid_full = function (j) { Amount.NaN = function () { var result = new Amount(); - + result._value = NaN; - + return result; }; @@ -1305,10 +883,10 @@ Amount.prototype.not_equals_why = function (d) { }; exports.Amount = Amount; + +// DEPRECATED: Include the corresponding files instead. exports.Currency = Currency; exports.Seed = Seed; exports.UInt160 = UInt160; -exports.config = {}; - // vim:sw=2:sts=2:ts=8:et diff --git a/src/js/base.js b/src/js/base.js new file mode 100644 index 000000000..47ef551ed --- /dev/null +++ b/src/js/base.js @@ -0,0 +1,136 @@ + +var sjcl = require('../../build/sjcl'); +var utils = require('./utils'); +var jsbn = require('./jsbn'); +var extend = require('extend'); + +var BigInteger = jsbn.BigInteger; +var nbi = jsbn.nbi; + +var Base = {}; + +var alphabets = Base.alphabets = { + 'ripple' : "rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz", + 'tipple' : "RPShNAF39wBUDnEGHJKLM4pQrsT7VWXYZ2bcdeCg65jkm8ofqi1tuvaxyz", + 'bitcoin' : "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" +}; + +extend(Base, { + 'VER_NONE' : 1, + 'VER_NODE_PUBLIC' : 28, + 'VER_NODE_PRIVATE' : 32, + 'VER_ACCOUNT_ID' : 0, + 'VER_ACCOUNT_PUBLIC' : 35, + 'VER_ACCOUNT_PRIVATE' : 34, + 'VER_FAMILY_GENERATOR' : 41, + 'VER_FAMILY_SEED' : 33 +}); + +var sha256 = function (bytes) { + return sjcl.codec.bytes.fromBits(sjcl.hash.sha256.hash(sjcl.codec.bytes.toBits(bytes))); +}; + +var sha256hash = function (bytes) { + return sha256(sha256(bytes)); +}; + +// --> input: big-endian array of bytes. +// <-- string at least as long as input. +Base.encode = function (input, alpha) { + var alphabet = alphabets[alpha || 'ripple']; + var bi_base = new BigInteger(String(alphabet.length)); + var bi_q = nbi(); + var bi_r = nbi(); + var bi_value = new BigInteger(input); + var buffer = []; + + while (bi_value.compareTo(BigInteger.ZERO) > 0) + { + bi_value.divRemTo(bi_base, bi_q, bi_r); + bi_q.copyTo(bi_value); + + buffer.push(alphabet[bi_r.intValue()]); + } + + var i; + + for (i = 0; i != input.length && !input[i]; i += 1) { + buffer.push(alphabet[0]); + } + + return buffer.reverse().join(""); +}; + +// --> input: String +// <-- array of bytes or undefined. +Base.decode = function (input, alpha) { + var alphabet = alphabets[alpha || 'ripple']; + var bi_base = new BigInteger(String(alphabet.length)); + var bi_value = nbi(); + var i; + + for (i = 0; i != input.length && input[i] === alphabet[0]; i += 1) + ; + + for (; i != input.length; i += 1) { + var v = alphabet.indexOf(input[i]); + + if (v < 0) + return undefined; + + var r = nbi(); + + r.fromInt(v); + + bi_value = bi_value.multiply(bi_base).add(r); + } + + // toByteArray: + // - Returns leading zeros! + // - Returns signed bytes! + var bytes = bi_value.toByteArray().map(function (b) { return b ? b < 0 ? 256+b : b : 0; }); + var extra = 0; + + while (extra != bytes.length && !bytes[extra]) + extra += 1; + + if (extra) + bytes = bytes.slice(extra); + + var zeros = 0; + + while (zeros !== input.length && input[zeros] === alphabet[0]) + zeros += 1; + + return [].concat(utils.arraySet(zeros, 0), bytes); +}; + +// --> input: Array +// <-- String +Base.encode_check = function (version, input, alphabet) { + var buffer = [].concat(version, input); + var check = sha256(sha256(buffer)).slice(0, 4); + + return Base.encode([].concat(buffer, check), alphabet); +} + +// --> input : String +// <-- NaN || BigInteger +Base.decode_check = function (version, input, alphabet) { + var buffer = Base.decode(input, alphabet); + + if (!buffer || buffer[0] !== version || buffer.length < 5) + return NaN; + + var computed = sha256hash(buffer.slice(0, -4)).slice(0, 4); + var checksum = buffer.slice(-4); + var i; + + for (i = 0; i != 4; i += 1) + if (computed[i] !== checksum[i]) + return NaN; + + return new BigInteger(buffer.slice(1, -4)); +} + +exports.Base = Base; diff --git a/src/js/config.js b/src/js/config.js new file mode 100644 index 000000000..372b7d261 --- /dev/null +++ b/src/js/config.js @@ -0,0 +1,3 @@ +// This object serves as a singleton to store config options + +module.exports = {}; diff --git a/src/js/currency.js b/src/js/currency.js new file mode 100644 index 000000000..14e902159 --- /dev/null +++ b/src/js/currency.js @@ -0,0 +1,81 @@ + +// +// Currency support +// + +// XXX Internal form should be UInt160. +var Currency = function () { + // Internal form: 0 = XRP. 3 letter-code. + // XXX Internal should be 0 or hex with three letter annotation when valid. + + // Json form: + // '', 'XRP', '0': 0 + // 3-letter code: ... + // XXX Should support hex, C++ doesn't currently allow it. + + this._value = NaN; +} + +// Given "USD" return the json. +Currency.json_rewrite = function (j) { + return Currency.from_json(j).to_json(); +}; + +Currency.from_json = function (j) { + return 'string' === typeof j + ? (new Currency()).parse_json(j) + : j.clone(); +}; + +Currency.is_valid = function (j) { + return Currency.from_json(j).is_valid(); +}; + +Currency.prototype.clone = function() { + return this.copyTo(new Currency()); +}; + +// Returns copy. +Currency.prototype.copyTo = function (d) { + d._value = this._value; + + return d; +}; + +Currency.prototype.equals = function (d) { + return ('string' !== typeof this._value && isNaN(this._value)) + || ('string' !== typeof d._value && isNaN(d._value)) ? false : this._value === d._value; +}; + +// this._value = NaN on error. +Currency.prototype.parse_json = function (j) { + if ("" === j || "0" === j || "XRP" === j) { + this._value = 0; + } + else if ('string' != typeof j || 3 !== j.length) { + this._value = NaN; + } + else { + this._value = j; + } + + return this; +}; + +Currency.prototype.is_native = function () { + return !isNaN(this._value) && !this._value; +}; + +Currency.prototype.is_valid = function () { + return !isNaN(this._value); +}; + +Currency.prototype.to_json = function () { + return this._value ? this._value : "XRP"; +}; + +Currency.prototype.to_human = function () { + return this._value ? this._value : "XRP"; +}; + +exports.Currency = Currency; diff --git a/src/js/remote.js b/src/js/remote.js index 1b1f67591..23c2296e8 100644 --- a/src/js/remote.js +++ b/src/js/remote.js @@ -22,6 +22,7 @@ var UInt160 = require('./amount').UInt160; var Transaction = require('./transaction').Transaction; var utils = require('./utils'); +var config = require('./config'); // Request events emitted: // 'success' : Request successful. @@ -205,7 +206,7 @@ var Remote = function (opts, trace) { this._testnet = undefined; this._transaction_subs = 0; this.online_target = false; - this.online_state = 'closed'; // 'open', 'closed', 'connecting', 'closing' + this._online_state = 'closed'; // 'open', 'closed', 'connecting', 'closing' this.state = 'offline'; // 'online', 'offline' this.retry_timer = undefined; this.retry = undefined; @@ -245,7 +246,7 @@ var Remote = function (opts, trace) { this.on('newListener', function (type, listener) { if ('transaction' === type) { - if (!self._transaction_subs) + if (!self._transaction_subs && 'open' === self._online_state) { self.request_subscribe([ 'transactions' ]) .request(); @@ -260,7 +261,7 @@ var Remote = function (opts, trace) { { self._transaction_subs -= 1; - if (!self._transaction_subs) + if (!self._transaction_subs && 'open' === self._online_state) { self.request_unsubscribe([ 'transactions' ]) .request(); @@ -272,12 +273,12 @@ var Remote = function (opts, trace) { Remote.prototype = new EventEmitter; Remote.from_config = function (obj, trace) { - var serverConfig = 'string' === typeof obj ? exports.config.servers[obj] : obj; + var serverConfig = 'string' === typeof obj ? config.servers[obj] : obj; var remote = new Remote(serverConfig, trace); - for (var account in exports.config.accounts) { - var accountInfo = exports.config.accounts[account]; + for (var account in config.accounts) { + var accountInfo = config.accounts[account]; if ("object" === typeof accountInfo) { if (accountInfo.secret) { // Index by nickname ... @@ -327,12 +328,12 @@ Remote.prototype._set_state = function (state) { switch (state) { case 'online': - this.online_state = 'open'; + this._online_state = 'open'; this.emit('connected'); break; case 'offline': - this.online_state = 'closed'; + this._online_state = 'closed'; this.emit('disconnected'); break; } @@ -353,7 +354,7 @@ Remote.prototype.connect = function (online) { this.online_target = target; // If we were in a stable state, go dynamic. - switch (this.online_state) { + switch (this._online_state) { case 'open': if (!target) this._connect_stop(); break; @@ -390,9 +391,9 @@ Remote.prototype._connect_retry = function () { // Do not continue trying to connect. this._set_state('offline'); } - else if ('connecting' !== this.online_state) { + else if ('connecting' !== this._online_state) { // New to connecting state. - this.online_state = 'connecting'; + this._online_state = 'connecting'; this.retry = 0; this._set_state('offline'); // Report newly offline. @@ -850,6 +851,17 @@ Remote.prototype.request_wallet_accounts = function (seed) { return request; }; +Remote.prototype.request_sign = function (secret, tx_json) { + utils.assert(this.trusted); // Don't send secrets. + + var request = new Request(this, 'sign'); + + request.message.secret = secret; + request.message.tx_json = tx_json; + + return request; +}; + // Submit a transaction. Remote.prototype.submit = function (transaction) { var self = this; @@ -915,7 +927,12 @@ Remote.prototype.submit = function (transaction) { Remote.prototype._server_subscribe = function () { var self = this; - this.request_subscribe([ 'ledger', 'server' ]) + var feeds = [ 'ledger', 'server' ]; + + if (this._transaction_subs) + feeds.push('transactions'); + + this.request_subscribe(feeds) .on('success', function (message) { self._stand_alone = !!message.stand_alone; self._testnet = !!message.testnet; @@ -1128,7 +1145,9 @@ Remote.prototype.request_ripple_path_find = function (src_account, dst_account, request.message.source_account = UInt160.json_rewrite(src_account); request.message.destination_account = UInt160.json_rewrite(dst_account); request.message.destination_amount = Amount.json_rewrite(dst_amount); - request.message.source_currencies = source_currencies.map(function (ci) { + + if (source_currencies) { + request.message.source_currencies = source_currencies.map(function (ci) { var ci_new = {}; if ('issuer' in ci) @@ -1139,6 +1158,7 @@ Remote.prototype.request_ripple_path_find = function (src_account, dst_account, return ci_new; }); + } return request; }; @@ -1186,7 +1206,6 @@ Remote.prototype.transaction = function () { return new Transaction(this); }; -exports.config = {}; exports.Remote = Remote; // vim:sw=2:sts=2:ts=8:et diff --git a/src/js/seed.js b/src/js/seed.js new file mode 100644 index 000000000..ee519566c --- /dev/null +++ b/src/js/seed.js @@ -0,0 +1,129 @@ +// +// Seed support +// + +var sjcl = require('../../build/sjcl'); +var utils = require('./utils'); +var jsbn = require('./jsbn'); + +var BigInteger = jsbn.BigInteger; + +var Base = require('./base').Base, + UInt256 = require('./uint256').UInt256; + +var Seed = function () { + // Internal form: NaN or BigInteger + this._value = NaN; +}; + +Seed.json_rewrite = function (j) { + return Seed.from_json(j).to_json(); +}; + +// Return a new Seed from j. +Seed.from_json = function (j) { + return (j instanceof Seed) + ? j.clone() + : (new Seed()).parse_json(j); +}; + +Seed.is_valid = function (j) { + return Seed.from_json(j).is_valid(); +}; + +Seed.prototype.clone = function () { + return this.copyTo(new Seed()); +}; + +// Returns copy. +Seed.prototype.copyTo = function (d) { + d._value = this._value; + + return d; +}; + +Seed.prototype.equals = function (d) { + return this._value instanceof BigInteger && d._value instanceof BigInteger && this._value.equals(d._value); +}; + +Seed.prototype.is_valid = function () { + return this._value instanceof BigInteger; +}; + +// value = NaN on error. +// One day this will support rfc1751 too. +Seed.prototype.parse_json = function (j) { + if ('string' !== typeof j) { + 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; +}; + +// Convert from internal form. +Seed.prototype.to_json = function () { + if (!(this._value instanceof BigInteger)) + return NaN; + + var bytes = this._value.toByteArray().map(function (b) { return b ? b < 0 ? 256+b : b : 0; }); + var target = 20; + + // XXX Make sure only trim off leading zeros. + var array = bytes.length < target + ? bytes.length + ? [].concat(utils.arraySet(target - bytes.length, 0), bytes) + : utils.arraySet(target, 0) + : bytes.slice(target - bytes.length); + var output = Base.encode_check(Base.VER_FAMILY_SEED, array); + + return output; +}; + +function append_int(a, i) { + return [].concat(a, i >> 24, (i >> 16) & 0xff, (i >> 8) & 0xff, i & 0xff); +} + +function firstHalfOfSHA512(bytes) { + return sjcl.bitArray.bitSlice( + sjcl.hash.sha512.hash(sjcl.codec.bytes.toBits(bytes)), + 0, 256 + ); +} + +function SHA256_RIPEMD160(bits) { + return sjcl.hash.ripemd160.hash(sjcl.hash.sha256.hash(bits)); +} + +Seed.prototype.generate_private = function (account_id) { + // XXX If account_id is given, should loop over keys until we find the right one + + var seq = 0; + + var private_gen, public_gen, i = 0; + do { + private_gen = sjcl.bn.fromBits(firstHalfOfSHA512(append_int(this.seed, i))); + i++; + } while (!sjcl.ecc.curves.c256.r.greaterEquals(private_gen)); + + public_gen = sjcl.ecc.curves.c256.G.mult(private_gen); + + var sec; + i = 0; + do { + sec = sjcl.bn.fromBits(firstHalfOfSHA512(append_int(append_int(public_gen.toBytesCompressed(), seq), i))); + i++; + } while (!sjcl.ecc.curves.c256.r.greaterEquals(sec)); + + return UInt256.from_bn(sec); +}; + +exports.Seed = Seed; diff --git a/src/js/serializedobject.js b/src/js/serializedobject.js new file mode 100644 index 000000000..e66a2001a --- /dev/null +++ b/src/js/serializedobject.js @@ -0,0 +1,144 @@ +var binformat = require('./binformat'), + sjcl = require('../../build/sjcl'), + extend = require('extend'), + stypes = require('./serializedtypes'); + +var UInt256 = require('./uint256').UInt256; + +var SerializedObject = function () { + this.buffer = []; + this.pointer = 0; +}; + +SerializedObject.from_json = function (obj) { + var typedef; + var so = new SerializedObject(); + + // Create a copy of the object so we don't modify it + obj = extend({}, obj); + + if ("number" === typeof obj.TransactionType) { + obj.TransactionType = SerializedObject.lookup_type_tx(obj.TransactionType); + + if (!obj.TransactionType) { + throw new Error("Transaction type ID is invalid."); + } + } + + if ("string" === typeof obj.TransactionType) { + typedef = binformat.tx[obj.TransactionType].slice(); + + obj.TransactionType = typedef.shift(); + } else if ("undefined" !== typeof obj.LedgerEntryType) { + // XXX: TODO + throw new Error("Ledger entry binary format not yet implemented."); + } else throw new Error("Object to be serialized must contain either " + + "TransactionType or LedgerEntryType."); + + so.serialize(typedef, obj); + + return so; +}; + +SerializedObject.prototype.append = function (bytes) { + this.buffer = this.buffer.concat(bytes); + this.pointer += bytes.length; +}; + +SerializedObject.prototype.to_bits = function () +{ + return sjcl.codec.bytes.toBits(this.buffer); +}; + +SerializedObject.prototype.to_hex = function () { + return sjcl.codec.hex.fromBits(this.to_bits()).toUpperCase(); +}; + +SerializedObject.prototype.serialize = function (typedef, obj) +{ + // Ensure canonical order + typedef = SerializedObject._sort_typedef(typedef.slice()); + + // Serialize fields + for (var i = 0, l = typedef.length; i < l; i++) { + var spec = typedef[i]; + this.serialize_field(spec, obj); + } +}; + +SerializedObject.prototype.signing_hash = function (prefix) +{ + var sign_buffer = new SerializedObject(); + stypes.Int32.serialize(sign_buffer, prefix); + sign_buffer.append(this.buffer); + return sign_buffer.hash_sha512_half(); +}; + +SerializedObject.prototype.hash_sha512_half = function () +{ + var bits = sjcl.codec.bytes.toBits(this.buffer), + 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) +{ + spec = spec.slice(); + + var name = spec.shift(), + presence = spec.shift(), + field_id = spec.shift(), + Type = spec.shift(); + + if ("undefined" !== typeof obj[name]) { + console.log(name, Type.id, field_id); + this.append(SerializedObject.get_field_header(Type.id, field_id)); + + try { + Type.serialize(this, obj[name]); + } catch (e) { + // Add field name to message and rethrow + e.message = "Error serializing '"+name+"': "+e.message; + throw e; + } + } else if (presence === binformat.REQUIRED) { + throw new Error('Missing required field '+name); + } +}; + +SerializedObject.get_field_header = function (type_id, field_id) +{ + var buffer = [0]; + if (type_id > 0xf) buffer.push(type_id & 0xff); + else buffer[0] += (type_id & 0xf) << 4; + + if (field_id > 0xf) buffer.push(field_id & 0xff); + else buffer[0] += field_id & 0xf; + + return buffer; +}; + +function sort_field_compare(a, b) { + // Sort by type id first, then by field id + return a[3].id !== b[3].id ? + a[3].id - b[3].id : + a[2] - b[2]; +}; +SerializedObject._sort_typedef = function (typedef) { + return typedef.sort(sort_field_compare); +}; + +SerializedObject.lookup_type_tx = function (id) { + for (var i in binformat.tx) { + if (!binformat.tx.hasOwnProperty(i)) continue; + + if (binformat.tx[i][0] === id) { + return i; + } + } + + return null; +}; + +exports.SerializedObject = SerializedObject; diff --git a/src/js/serializedtypes.js b/src/js/serializedtypes.js index 11acd294f..2c61b64b3 100644 --- a/src/js/serializedtypes.js +++ b/src/js/serializedtypes.js @@ -1,10 +1,55 @@ -var SerializedType = function () { +/** + * Type definitions for binary format. + * + * This file should not be included directly. Instead, find the format you're + * trying to parse or serialize in binformat.js and pass that to + * SerializedObject.parse() or SerializedObject.serialize(). + */ +var extend = require('extend'), + utils = require('./utils'), + sjcl = require('../../build/sjcl'); + +var amount = require('./amount'), + UInt160 = amount.UInt160, + Amount = amount.Amount; + +// Shortcuts +var hex = sjcl.codec.hex, + bytes = sjcl.codec.bytes; + +var SerializedType = function (methods) { + extend(this, methods); +}; + +SerializedType.prototype.serialize_hex = function (so, hexData) { + var byteData = bytes.fromBits(hex.toBits(hexData)); + this.serialize_varint(so, byteData.length); + so.append(byteData); +}; + +SerializedType.prototype.serialize_varint = function (so, val) { + if (val < 0) { + throw new Error("Variable integers are unsigned."); + } + if (val <= 192) { + so.append([val]); + } else if (val <= 12,480) { + val -= 193; + so.append([193 + (val >>> 8), val & 0xff]); + } else if (val <= 918744) { + val -= 12481; + so.append([ + 241 + (val >>> 16), + val >>> 8 & 0xff, + val & 0xff + ]); + } else throw new Error("Variable integer overflow."); }; exports.Int8 = new SerializedType({ serialize: function (so, val) { - return so.append([val & 0xff]); + so.append([val & 0xff]); }, parse: function (so) { return so.read(1)[0]; @@ -13,7 +58,10 @@ exports.Int8 = new SerializedType({ exports.Int16 = new SerializedType({ serialize: function (so, val) { - // XXX + so.append([ + val >>> 8 & 0xff, + val & 0xff + ]); }, parse: function (so) { // XXX @@ -22,7 +70,12 @@ exports.Int16 = new SerializedType({ exports.Int32 = new SerializedType({ serialize: function (so, val) { - // XXX + so.append([ + val >>> 24 & 0xff, + val >>> 16 & 0xff, + val >>> 8 & 0xff, + val & 0xff + ]); }, parse: function (so) { // XXX @@ -67,7 +120,62 @@ exports.Hash160 = new SerializedType({ exports.Amount = new SerializedType({ serialize: function (so, val) { - // XXX + var amount = Amount.from_json(val); + if (!amount.is_valid()) { + throw new Error("Not a valid Amount object."); + } + + // Amount (64-bit integer) + if (amount.is_native()) { + var valueHex = amount._value.toString(16); + + // Enforce correct length (64 bits) + if (valueHex.length > 16) { + throw new Error('Value out of bounds'); + } + while (valueHex.length < 16) { + valueHex = "0" + valueHex; + } + + var 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!"); + } + + 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.'); + } + + // Issuer (160-bit hash) + // XXX + } }, parse: function (so) { // XXX @@ -76,7 +184,8 @@ exports.Amount = new SerializedType({ exports.VariableLength = new SerializedType({ serialize: function (so, val) { - // XXX + if ("string" === typeof val) this.serialize_hex(so, val); + else throw new Error("Unknown datatype."); }, parse: function (so) { // XXX @@ -85,7 +194,8 @@ exports.VariableLength = new SerializedType({ exports.Account = new SerializedType({ serialize: function (so, val) { - // XXX + var account = UInt160.from_json(val); + this.serialize_hex(so, account.to_hex()); }, parse: function (so) { // XXX diff --git a/src/js/serializer.js b/src/js/serializer.js deleted file mode 100644 index 6a75e84ef..000000000 --- a/src/js/serializer.js +++ /dev/null @@ -1,44 +0,0 @@ -// - -var serializer = {}; - -serializer.addUInt16 = function(value) { - switch (typeof value) { - case 'string': - addUInt16(value.charCodeAt(0)); - break; - - case 'integer': - for (i = 16/8; i; i -=1) { - raw.push(value & 255); - value >>= 8; - } - break; - - default: - throw 'UNEXPECTED_TYPE'; - } -}; - -serializer.addUInt160 = function(value) { - switch (typeof value) { - case 'array': - raw.concat(value); - break; - - case 'integer': - for (i = 160/8; i; i -=1) { - raw.push(value & 255); - value >>= 8; - } - break; - - default: - throw 'UNEXPECTED_TYPE'; - } -}; - -serializer.getSHA512Half = function() { -}; - -// vim:sw=2:sts=2:ts=8:et diff --git a/src/js/sjcl-custom/sjcl-ecdsa-der.js b/src/js/sjcl-custom/sjcl-ecdsa-der.js new file mode 100644 index 000000000..befddbf08 --- /dev/null +++ b/src/js/sjcl-custom/sjcl-ecdsa-der.js @@ -0,0 +1,28 @@ +sjcl.ecc.ecdsa.secretKey.prototype.signDER = function(hash, paranoia) { + return this.encodeDER(this.sign(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(); + + var rb = sjcl.codec.bytes.fromBits(r), + sb = sjcl.codec.bytes.fromBits(s); + + var buffer = [].concat( + 0x30, + 4 + rb.length + sb.length, + 0x02, + rb.length, + rb, + 0x02, + sb.length, + sb + ); + + return sjcl.codec.bytes.toBits(buffer); +}; + diff --git a/src/js/sjcl-custom/sjcl-extramath.js b/src/js/sjcl-custom/sjcl-extramath.js new file mode 100755 index 000000000..4dd5f326a --- /dev/null +++ b/src/js/sjcl-custom/sjcl-extramath.js @@ -0,0 +1,61 @@ +sjcl.bn.ZERO = new sjcl.bn(0); + +/** [ this / that , this % that ] */ +sjcl.bn.prototype.divRem = function (that) { + if (typeof(that) !== "object") { that = new this._class(that); } + var thisa = this.abs(), thata = that.abs(), quot = new this._class(0), + ci = 0; + if (!thisa.greaterEquals(thata)) { + this.initWith(0); + return this; + } else if (thisa.equals(thata)) { + this.initWith(sign); + return this; + } + + for (; thisa.greaterEquals(thata); ci++) { + thata.doubleM(); + } + for (; ci > 0; ci--) { + quot.doubleM(); + thata.halveM(); + if (thisa.greaterEquals(thata)) { + quot.addM(1); + thisa.subM(that).normalize(); + } + } + return [quot, thisa]; +}; + +/** this /= that (rounded to nearest int) */ +sjcl.bn.prototype.divRound = function (that) { + var dr = this.divRem(that), quot = dr[0], rem = dr[1]; + + if (rem.doubleM().greaterEquals(that)) { + quot.addM(1); + } + + return quot; +}; + +/** this /= that (rounded down) */ +sjcl.bn.prototype.div = function (that) { + var dr = this.divRem(that); + return dr[0]; +}; + +sjcl.bn.prototype.sign = function () { + return this.greaterEquals(sjcl.bn.ZERO) ? 1 : -1; + }; + +/** -this */ +sjcl.bn.prototype.neg = function () { + return sjcl.bn.ZERO.sub(this); +}; + +/** |this| */ +sjcl.bn.prototype.abs = function () { + if (this.sign() === -1) { + return this.neg(); + } else return this; +}; diff --git a/src/js/sjcl-custom/sjcl-ripemd160.js b/src/js/sjcl-custom/sjcl-ripemd160.js new file mode 100755 index 000000000..4ee7b9246 --- /dev/null +++ b/src/js/sjcl-custom/sjcl-ripemd160.js @@ -0,0 +1,207 @@ +/** @fileOverview Javascript RIPEMD-160 implementation. + * + * @author Artem S Vybornov + */ +(function() { + +/** + * Context for a RIPEMD-160 operation in progress. + * @constructor + * @class RIPEMD, 160 bits. + */ +sjcl.hash.ripemd160 = function (hash) { + if (hash) { + this._h = hash._h.slice(0); + this._buffer = hash._buffer.slice(0); + this._length = hash._length; + } else { + this.reset(); + } +}; + +/** + * Hash a string or an array of words. + * @static + * @param {bitArray|String} data the data to hash. + * @return {bitArray} The hash value, an array of 5 big-endian words. + */ +sjcl.hash.ripemd160.hash = function (data) { + return (new sjcl.hash.ripemd160()).update(data).finalize(); +}; + +sjcl.hash.ripemd160.prototype = { + /** + * Reset the hash state. + * @return this + */ + reset: function () { + this._h = _h0.slice(0); + this._buffer = []; + this._length = 0; + return this; + }, + + /** + * Reset the hash state. + * @param {bitArray|String} data the data to hash. + * @return this + */ + update: function (data) { + if ( typeof data === "string" ) + data = sjcl.codec.utf8String.toBits(data); + + var i, b = this._buffer = sjcl.bitArray.concat(this._buffer, data), + ol = this._length, + nl = this._length = ol + sjcl.bitArray.bitLength(data); + for (i = 512+ol & -512; i <= nl; i+= 512) { + var words = b.splice(0,16); + for ( var w = 0; w < 16; ++w ) + words[w] = _cvt(words[w]); + + _block.call( this, words ); + } + + return this; + }, + + /** + * Complete hashing and output the hash value. + * @return {bitArray} The hash value, an array of 5 big-endian words. + */ + finalize: function () { + var b = sjcl.bitArray.concat( this._buffer, [ sjcl.bitArray.partial(1,1) ] ), + l = ( this._length + 1 ) % 512, + z = ( l > 448 ? 512 : 448 ) - l % 448, + zp = z % 32; + + if ( zp > 0 ) + b = sjcl.bitArray.concat( b, [ sjcl.bitArray.partial(zp,0) ] ) + for ( ; z >= 32; z -= 32 ) + b.push(0); + + b.push( _cvt( this._length | 0 ) ); + b.push( _cvt( Math.floor(this._length / 0x100000000) ) ); + + while ( b.length ) { + var words = b.splice(0,16); + for ( var w = 0; w < 16; ++w ) + words[w] = _cvt(words[w]); + + _block.call( this, words ); + } + + var h = this._h; + this.reset(); + + for ( var w = 0; w < 5; ++w ) + h[w] = _cvt(h[w]); + + return h; + } +}; + +var _h0 = [ 0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0 ]; + +var _k1 = [ 0x00000000, 0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xa953fd4e ]; +var _k2 = [ 0x50a28be6, 0x5c4dd124, 0x6d703ef3, 0x7a6d76e9, 0x00000000 ]; +for ( var i = 4; i >= 0; --i ) { + for ( var j = 1; j < 16; ++j ) { + _k1.splice(i,0,_k1[i]); + _k2.splice(i,0,_k2[i]); + } +} + +var _r1 = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, + 7, 4, 13, 1, 10, 6, 15, 3, 12, 0, 9, 5, 2, 14, 11, 8, + 3, 10, 14, 4, 9, 15, 8, 1, 2, 7, 0, 6, 13, 11, 5, 12, + 1, 9, 11, 10, 0, 8, 12, 4, 13, 3, 7, 15, 14, 5, 6, 2, + 4, 0, 5, 9, 7, 12, 2, 10, 14, 1, 3, 8, 11, 6, 15, 13 ]; +var _r2 = [ 5, 14, 7, 0, 9, 2, 11, 4, 13, 6, 15, 8, 1, 10, 3, 12, + 6, 11, 3, 7, 0, 13, 5, 10, 14, 15, 8, 12, 4, 9, 1, 2, + 15, 5, 1, 3, 7, 14, 6, 9, 11, 8, 12, 2, 10, 0, 4, 13, + 8, 6, 4, 1, 3, 11, 15, 0, 5, 12, 2, 13, 9, 7, 10, 14, + 12, 15, 10, 4, 1, 5, 8, 7, 6, 2, 13, 14, 0, 3, 9, 11 ]; + +var _s1 = [ 11, 14, 15, 12, 5, 8, 7, 9, 11, 13, 14, 15, 6, 7, 9, 8, + 7, 6, 8, 13, 11, 9, 7, 15, 7, 12, 15, 9, 11, 7, 13, 12, + 11, 13, 6, 7, 14, 9, 13, 15, 14, 8, 13, 6, 5, 12, 7, 5, + 11, 12, 14, 15, 14, 15, 9, 8, 9, 14, 5, 6, 8, 6, 5, 12, + 9, 15, 5, 11, 6, 8, 13, 12, 5, 12, 13, 14, 11, 8, 5, 6 ]; +var _s2 = [ 8, 9, 9, 11, 13, 15, 15, 5, 7, 7, 8, 11, 14, 14, 12, 6, + 9, 13, 15, 7, 12, 8, 9, 11, 7, 7, 12, 7, 6, 15, 13, 11, + 9, 7, 15, 11, 8, 6, 6, 14, 12, 13, 5, 14, 13, 13, 7, 5, + 15, 5, 8, 11, 14, 14, 6, 14, 6, 9, 12, 9, 12, 5, 15, 8, + 8, 5, 12, 9, 12, 5, 14, 6, 8, 13, 6, 5, 15, 13, 11, 11 ]; + +function _f0(x,y,z) { + return x ^ y ^ z; +}; + +function _f1(x,y,z) { + return (x & y) | (~x & z); +}; + +function _f2(x,y,z) { + return (x | ~y) ^ z; +}; + +function _f3(x,y,z) { + return (x & z) | (y & ~z); +}; + +function _f4(x,y,z) { + return x ^ (y | ~z); +}; + +function _rol(n,l) { + return (n << l) | (n >>> (32-l)); +} + +function _cvt(n) { + return ( (n & 0xff << 0) << 24 ) + | ( (n & 0xff << 8) << 8 ) + | ( (n & 0xff << 16) >>> 8 ) + | ( (n & 0xff << 24) >>> 24 ); +} + +function _block(X) { + var A1 = this._h[0], B1 = this._h[1], C1 = this._h[2], D1 = this._h[3], E1 = this._h[4], + A2 = this._h[0], B2 = this._h[1], C2 = this._h[2], D2 = this._h[3], E2 = this._h[4]; + + var j = 0, T; + + for ( ; j < 16; ++j ) { + T = _rol( A1 + _f0(B1,C1,D1) + X[_r1[j]] + _k1[j], _s1[j] ) + E1; + A1 = E1; E1 = D1; D1 = _rol(C1,10); C1 = B1; B1 = T; + T = _rol( A2 + _f4(B2,C2,D2) + X[_r2[j]] + _k2[j], _s2[j] ) + E2; + A2 = E2; E2 = D2; D2 = _rol(C2,10); C2 = B2; B2 = T; } + for ( ; j < 32; ++j ) { + T = _rol( A1 + _f1(B1,C1,D1) + X[_r1[j]] + _k1[j], _s1[j] ) + E1; + A1 = E1; E1 = D1; D1 = _rol(C1,10); C1 = B1; B1 = T; + T = _rol( A2 + _f3(B2,C2,D2) + X[_r2[j]] + _k2[j], _s2[j] ) + E2; + A2 = E2; E2 = D2; D2 = _rol(C2,10); C2 = B2; B2 = T; } + for ( ; j < 48; ++j ) { + T = _rol( A1 + _f2(B1,C1,D1) + X[_r1[j]] + _k1[j], _s1[j] ) + E1; + A1 = E1; E1 = D1; D1 = _rol(C1,10); C1 = B1; B1 = T; + T = _rol( A2 + _f2(B2,C2,D2) + X[_r2[j]] + _k2[j], _s2[j] ) + E2; + A2 = E2; E2 = D2; D2 = _rol(C2,10); C2 = B2; B2 = T; } + for ( ; j < 64; ++j ) { + T = _rol( A1 + _f3(B1,C1,D1) + X[_r1[j]] + _k1[j], _s1[j] ) + E1; + A1 = E1; E1 = D1; D1 = _rol(C1,10); C1 = B1; B1 = T; + T = _rol( A2 + _f1(B2,C2,D2) + X[_r2[j]] + _k2[j], _s2[j] ) + E2; + A2 = E2; E2 = D2; D2 = _rol(C2,10); C2 = B2; B2 = T; } + for ( ; j < 80; ++j ) { + T = _rol( A1 + _f4(B1,C1,D1) + X[_r1[j]] + _k1[j], _s1[j] ) + E1; + A1 = E1; E1 = D1; D1 = _rol(C1,10); C1 = B1; B1 = T; + T = _rol( A2 + _f0(B2,C2,D2) + X[_r2[j]] + _k2[j], _s2[j] ) + E2; + A2 = E2; E2 = D2; D2 = _rol(C2,10); C2 = B2; B2 = T; } + + T = this._h[1] + C1 + D2; + this._h[1] = this._h[2] + D1 + E2; + this._h[2] = this._h[3] + E1 + A2; + this._h[3] = this._h[4] + A1 + B2; + this._h[4] = this._h[0] + B1 + C2; + this._h[0] = T; +} + +})(); diff --git a/src/js/sjcl-custom/sjcl-secp256k1.js b/src/js/sjcl-custom/sjcl-secp256k1.js new file mode 100755 index 000000000..4d34db7f1 --- /dev/null +++ b/src/js/sjcl-custom/sjcl-secp256k1.js @@ -0,0 +1,72 @@ +// ----- for secp256k1 ------ + +// Overwrite NIST-P256 with secp256k1 so we're on even footing +sjcl.ecc.curves.c256 = new sjcl.ecc.curve( + sjcl.bn.pseudoMersennePrime(256, [[0,-1],[4,-1],[6,-1],[7,-1],[8,-1],[9,-1],[32,-1]]), + "0x14551231950b75fc4402da1722fc9baee", + 0, + 7, + "0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + "0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8" +); + +// Replace point addition and doubling algorithms +// NIST-P256 is a=-3, we need algorithms for a=0 +sjcl.ecc.pointJac.prototype.add = function(T) { + var S = this; + if (S.curve !== T.curve) { + throw("sjcl.ecc.add(): Points must be on the same curve to add them!"); + } + + if (S.isIdentity) { + return T.toJac(); + } else if (T.isIdentity) { + return S; + } + + var z1z1 = S.z.square(); + var h = T.x.mul(z1z1).subM(S.x); + var s2 = T.y.mul(S.z).mul(z1z1); + + if (h.equals(0)) { + if (S.y.equals(T.y.mul(z1z1.mul(S.z)))) { + // same point + return S.doubl(); + } else { + // inverses + return new sjcl.ecc.pointJac(S.curve); + } + } + + var hh = h.square(); + var i = hh.copy().doubleM().doubleM(); + var j = h.mul(i); + var r = s2.sub(S.y).doubleM(); + var v = S.x.mul(i); + + var x = r.square().subM(j).subM(v.copy().doubleM()); + var y = r.mul(v.sub(x)).subM(S.y.mul(j).doubleM()); + var z = S.z.add(h).square().subM(z1z1).subM(hh); + + return new sjcl.ecc.pointJac(this.curve,x,y,z); +}; + +sjcl.ecc.pointJac.prototype.doubl = function () { + if (this.isIdentity) { return this; } + + var a = this.x.square(); + var b = this.y.square(); + var c = b.square(); + var d = this.x.add(b).square().subM(a).subM(c).doubleM(); + var e = a.mul(3); + var f = e.square(); + var x = f.sub(d.copy().doubleM()); + var y = e.mul(d.sub(x)).subM(c.doubleM().doubleM().doubleM()); + var z = this.y.mul(this.z).doubleM(); + return new sjcl.ecc.pointJac(this.curve, x, y, z); +}; + +sjcl.ecc.point.prototype.toBytesCompressed = function () { + var header = this.y.mod(2).toString() == "0x0" ? 0x02 : 0x03; + return [header].concat(sjcl.codec.bytes.fromBits(this.x.toBits())) +}; diff --git a/src/js/transaction.js b/src/js/transaction.js index 3fedb3dca..a2ad368a3 100644 --- a/src/js/transaction.js +++ b/src/js/transaction.js @@ -43,10 +43,16 @@ // - may or may not forward. // -var Amount = require('./amount').Amount; -var Currency = require('./amount').Currency; -var UInt160 = require('./amount').UInt160; -var EventEmitter = require('events').EventEmitter; +var sjcl = require('../../build/sjcl'); + +var Amount = require('./amount').Amount; +var Currency = require('./amount').Currency; +var UInt160 = require('./amount').UInt160; +var Seed = require('./seed').Seed; +var EventEmitter = require('events').EventEmitter; +var SerializedObject = require('./serializedobject').SerializedObject; + +var config = require('./config'); var SUBMIT_MISSING = 4; // Report missing. var SUBMIT_LOST = 8; // Give up tracking. @@ -112,6 +118,11 @@ Transaction.flags = { }, }; +Transaction.formats = require('./binformat').tx; + +Transaction.HASH_SIGN = 0x53545800; +Transaction.HASH_SIGN_TESTNET = 0x73747800; + Transaction.prototype.consts = { 'telLOCAL_ERROR' : -399, 'temMALFORMED' : -299, @@ -156,6 +167,30 @@ Transaction.prototype.set_state = function (state) { } }; +Transaction.prototype.serialize = function () { + return SerializedObject.from_json(this.tx_json); +}; + +Transaction.prototype.signing_hash = function () { + var prefix = config.testnet + ? Transaction.HASH_SIGN_TESTNET + : Transaction.HASH_SIGN; + + return SerializedObject.from_json(this.tx_json).signing_hash(prefix); +}; + +Transaction.prototype.sign = function () { + var seed = Seed.from_json(this._secret), + priv = seed.generate_private(this.tx_json.Account), + hash = this.signing_hash(); + + var key = new sjcl.ecc.ecdsa.secretKey(sjcl.ecc.curves['c256'], priv.to_bn()), + sig = key.signDER(hash.to_bits(), 0), + hex = sjcl.codec.hex.fromBits(sig).toUpperCase(); + + this.tx_json.TxnSignature = hex; +}; + // Submit a transaction to the network. // XXX Don't allow a submit without knowing ledger_index. // XXX Have a network canSubmit(), post events for following. @@ -355,21 +390,21 @@ Transaction.prototype.transfer_rate = function (rate) { // --> flags: undefined, _flag_, or [ _flags_ ] Transaction.prototype.set_flags = function (flags) { if (flags) { - var transaction_flags = Transaction.flags[this.tx_json.TransactionType]; + var transaction_flags = Transaction.flags[this.tx_json.TransactionType]; if (undefined == this.tx_json.Flags) // We plan to not define this field on new Transaction. this.tx_json.Flags = 0; - var flag_set = 'object' === typeof flags ? flags : [ flags ]; + var flag_set = 'object' === typeof flags ? flags : [ flags ]; - for (index in flag_set) { - var flag = flag_set[index]; + for (var index in flag_set) { + if (!flag_set.hasOwnProperty(index)) continue; - if (flag in transaction_flags) - { - this.tx_json.Flags += transaction_flags[flag]; - } - else { + var flag = flag_set[index]; + + if (flag in transaction_flags) { + this.tx_json.Flags += transaction_flags[flag]; + } else { // XXX Immediately report an error or mark it. } } diff --git a/src/js/uint.js b/src/js/uint.js new file mode 100644 index 000000000..b989b7e84 --- /dev/null +++ b/src/js/uint.js @@ -0,0 +1,221 @@ + +var sjcl = require('../../build/sjcl'); +var utils = require('./utils'); +var config = require('./config'); +var jsbn = require('./jsbn'); + +var BigInteger = jsbn.BigInteger; +var nbi = jsbn.nbi; + +var Base = require('./base').Base; + +// +// Abstract UInt class +// +// Base class for UInt??? classes +// + +var UInt = function () { + // Internal form: NaN or BigInteger + this._value = NaN; +}; + +UInt.json_rewrite = function (j) { + return this.from_json(j).to_json(); +}; + +// Return a new UInt from j. +UInt.from_generic = function (j) { + if (j instanceof this) { + return j.clone(); + } else { + return (new this()).parse_generic(j); + } +}; + +// Return a new UInt from j. +UInt.from_hex = function (j) { + if (j instanceof this) { + return j.clone(); + } else { + return (new this()).parse_hex(j); + } +}; + +// Return a new UInt from j. +UInt.from_json = function (j) { + if (j instanceof this) { + return j.clone(); + } else { + return (new this()).parse_json(j); + } +}; + +// Return a new UInt from j. +UInt.from_bits = function (j) { + if (j instanceof this) { + return j.clone(); + } else { + return (new this()).parse_bits(j); + } +}; + +// Return a new UInt from j. +UInt.from_bn = function (j) { + if (j instanceof this) { + return j.clone(); + } else { + return (new this()).parse_bn(j); + } +}; + +UInt.is_valid = function (j) { + return this.from_json(j).is_valid(); +}; + +UInt.prototype.clone = function () { + return this.copyTo(new this.constructor()); +}; + +// Returns copy. +UInt.prototype.copyTo = function (d) { + d._value = this._value; + + return d; +}; + +UInt.prototype.equals = function (d) { + return this._value instanceof BigInteger && d._value instanceof BigInteger && this._value.equals(d._value); +}; + +UInt.prototype.is_valid = function () { + return this._value instanceof BigInteger; +}; + +// value = NaN on error. +UInt.prototype.parse_generic = function (j) { + // Canonicalize and validate + if (config.accounts && j in config.accounts) + j = config.accounts[j].account; + + switch (j) { + case undefined: + case "0": + case this.constructor.STR_ZERO: + case this.constructor.ADDRESS_ZERO: + case this.constructor.HEX_ZERO: + this._value = nbi(); + break; + + case "1": + case this.constructor.STR_ONE: + case this.constructor.ADDRESS_ONE: + case this.constructor.HEX_ONE: + this._value = new BigInteger([1]); + + break; + + default: + if ('string' !== typeof j) { + this._value = NaN; + } + else if (j[0] === "r") { + this._value = Base.decode_check(Base.VER_ACCOUNT_ID, j); + } + else if (this.constructor.width === j.length) { + this._value = new BigInteger(utils.stringToArray(j), 256); + } + else if ((this.constructor.width*2) === j.length) { + // XXX Check char set! + this._value = new BigInteger(j, 16); + } + else { + this._value = NaN; + } + } + + return this; +}; + +UInt.prototype.parse_hex = function (j) { + if ('string' === typeof j && + j.length === (this.constructor.width * 2)) { + this._value = new BigInteger(j, 16); + } else { + this._value = NaN; + } + + return this; +}; + +UInt.prototype.parse_bits = function (j) { + if (sjcl.bitArray.bitLength(j) !== this.constructor.width * 8) { + this._value = NaN; + } else { + var bytes = sjcl.codec.bytes.fromBits(j); + this._value = new BigInteger(bytes, 256); + } + + return this; +}; + +UInt.prototype.parse_json = UInt.prototype.parse_hex; + +UInt.prototype.parse_bn = function (j) { + if (j instanceof sjcl.bn && + j.bitLength() <= this.constructor.width * 8) { + var bytes = sjcl.codec.bytes.fromBits(j.toBits()); + this._value = new BigInteger(bytes, 256); + } else { + this._value = NaN; + } + + return this; +}; + +// Convert from internal form. +UInt.prototype.to_bytes = function () { + if (!(this._value instanceof BigInteger)) + return null; + + var bytes = this._value.toByteArray(); + bytes = bytes.map(function (b) { return (b+256) % 256; }); + var target = this.constructor.width; + + // XXX Make sure only trim off leading zeros. + bytes = bytes.slice(-target); + while (bytes.length < target) bytes.unshift(0); + + return bytes; +}; + +UInt.prototype.to_hex = function () { + if (!(this._value instanceof BigInteger)) + return null; + + var bytes = this.to_bytes(); + + return sjcl.codec.hex.fromBits(sjcl.codec.bytes.toBits(bytes)).toUpperCase(); +}; + +UInt.prototype.to_json = UInt.prototype.to_hex; + +UInt.prototype.to_bits = function () { + if (!(this._value instanceof BigInteger)) + return null; + + var bytes = this.to_bytes(); + + return sjcl.codec.bytes.toBits(bytes); +}; + +UInt.prototype.to_bn = function () { + if (!(this._value instanceof BigInteger)) + return null; + + var bits = this.to_bits(); + + return sjcl.bn.fromBits(bits); +}; + +exports.UInt = UInt; diff --git a/src/js/uint160.js b/src/js/uint160.js new file mode 100644 index 000000000..b9165e34c --- /dev/null +++ b/src/js/uint160.js @@ -0,0 +1,63 @@ + +var sjcl = require('../../build/sjcl'); +var utils = require('./utils'); +var config = require('./config'); +var jsbn = require('./jsbn'); +var extend = require('extend'); + +var BigInteger = jsbn.BigInteger; +var nbi = jsbn.nbi; + +var UInt = require('./uint').UInt, + Base = require('./base').Base; + +// +// UInt160 support +// + +var UInt160 = extend(function () { + // Internal form: NaN or BigInteger + this._value = NaN; +}, UInt); + +UInt160.width = 20; +UInt160.prototype = extend({}, UInt.prototype); +UInt160.prototype.constructor = UInt160; + +var ADDRESS_ZERO = UInt160.ADDRESS_ZERO = "rrrrrrrrrrrrrrrrrrrrrhoLvTp"; +var ADDRESS_ONE = UInt160.ADDRESS_ONE = "rrrrrrrrrrrrrrrrrrrrBZbvji"; +var HEX_ZERO = UInt160.HEX_ZERO = "0000000000000000000000000000000000000000"; +var HEX_ONE = UInt160.HEX_ONE = "0000000000000000000000000000000000000001"; +var STR_ZERO = UInt160.STR_ZERO = utils.hexToString(HEX_ZERO); +var STR_ONE = UInt160.STR_ONE = utils.hexToString(HEX_ONE); + +// value = NaN on error. +UInt160.prototype.parse_json = function (j) { + // Canonicalize and validate + if (config.accounts && j in config.accounts) + j = config.accounts[j].account; + + if ('string' !== typeof j) { + this._value = NaN; + } + else if (j[0] === "r") { + this._value = Base.decode_check(Base.VER_ACCOUNT_ID, j); + } + else { + this._value = NaN; + } + + return this; +}; + +// XXX Json form should allow 0 and 1, C++ doesn't currently allow it. +UInt160.prototype.to_json = function () { + if (!(this._value instanceof BigInteger)) + return NaN; + + var output = Base.encode_check(Base.VER_ACCOUNT_ID, this.to_bytes()); + + return output; +}; + +exports.UInt160 = UInt160; diff --git a/src/js/uint256.js b/src/js/uint256.js new file mode 100644 index 000000000..ab5b6f70b --- /dev/null +++ b/src/js/uint256.js @@ -0,0 +1,37 @@ + +var sjcl = require('../../build/sjcl'); +var utils = require('./utils'); +var config = require('./config'); +var jsbn = require('./jsbn'); +var extend = require('extend'); + +var BigInteger = jsbn.BigInteger; +var nbi = jsbn.nbi; + +var UInt = require('./uint').UInt, + Base = require('./base').Base; + +// +// UInt256 support +// + +var UInt256 = extend(function () { + // Internal form: NaN or BigInteger + this._value = NaN; +}, UInt); + +UInt256.width = 32; +UInt256.prototype = extend({}, UInt.prototype); +UInt256.prototype.constructor = UInt256; + +// XXX Generate these constants (or remove them) +var ADDRESS_ZERO = UInt256.ADDRESS_ZERO = "XXX"; +var ADDRESS_ONE = UInt256.ADDRESS_ONE = "XXX"; +var HEX_ZERO = UInt256.HEX_ZERO = "00000000000000000000000000000000" + + "00000000000000000000000000000000"; +var HEX_ONE = UInt256.HEX_ONE = "00000000000000000000000000000000" + + "00000000000000000000000000000001"; +var STR_ZERO = UInt256.STR_ZERO = utils.hexToString(HEX_ZERO); +var STR_ONE = UInt256.STR_ONE = utils.hexToString(HEX_ONE); + +exports.UInt256 = UInt256; diff --git a/src/js/utils.js b/src/js/utils.js index d337763a6..d1779faf9 100644 --- a/src/js/utils.js +++ b/src/js/utils.js @@ -27,11 +27,11 @@ var trace = function(comment, func) { }; var arraySet = function (count, value) { - var a = new Array(count); - var i; + var i, a = new Array(count); - for (i = 0; i != count; i += 1) + for (i = 0; i < count; i++) { a[i] = value; + } return a; }; diff --git a/test/amount-test.js b/test/amount-test.js index 71279cbee..e5bec8e00 100644 --- a/test/amount-test.js +++ b/test/amount-test.js @@ -8,7 +8,8 @@ var amount = require("../src/js/amount.js"); var Amount = require("../src/js/amount.js").Amount; var UInt160 = require("../src/js/amount.js").UInt160; -require("../src/js/amount.js").config = require("./config.js"); +var extend = require('extend'); +extend(require('../src/js/config'), require('./config')); var config = require('./config.js'); @@ -20,16 +21,16 @@ buster.testCase("Amount", { buster.assert.equals(nbi(), UInt160.from_generic("0")._value); }, "Parse 0 export" : function () { - buster.assert.equals(amount.consts.address_xns, UInt160.from_generic("0").to_json()); + buster.assert.equals(UInt160.ADDRESS_ZERO, UInt160.from_generic("0").to_json()); }, "Parse 1" : function () { buster.assert.equals(new BigInteger([1]), UInt160.from_generic("1")._value); }, "Parse rrrrrrrrrrrrrrrrrrrrrhoLvTp export" : function () { - buster.assert.equals(amount.consts.address_xns, UInt160.from_json("rrrrrrrrrrrrrrrrrrrrrhoLvTp").to_json()); + buster.assert.equals(UInt160.ADDRESS_ZERO, UInt160.from_json("rrrrrrrrrrrrrrrrrrrrrhoLvTp").to_json()); }, "Parse rrrrrrrrrrrrrrrrrrrrBZbvji export" : function () { - buster.assert.equals(amount.consts.address_one, UInt160.from_json("rrrrrrrrrrrrrrrrrrrrBZbvji").to_json()); + buster.assert.equals(UInt160.ADDRESS_ONE, UInt160.from_json("rrrrrrrrrrrrrrrrrrrrBZbvji").to_json()); }, "Parse mtgox export" : function () { buster.assert.equals(config.accounts["mtgox"].account, UInt160.from_json("mtgox").to_json()); diff --git a/test/jsonrpc-test.js b/test/jsonrpc-test.js index ffad70ba6..c1569ba5d 100644 --- a/test/jsonrpc-test.js +++ b/test/jsonrpc-test.js @@ -12,8 +12,8 @@ var testutils = require("./testutils.js"); var config = require("./config.js"); -require("../src/js/amount.js").config = require("./config.js"); -require("../src/js/remote.js").config = require("./config.js"); +var extend = require('extend'); +extend(require('../src/js/config'), require('./config')); // How long to wait for server to start. var serverDelay = 1500; diff --git a/test/monitor-test.js b/test/monitor-test.js index daef55a65..861f4c15d 100644 --- a/test/monitor-test.js +++ b/test/monitor-test.js @@ -7,8 +7,8 @@ var Server = require("./server.js").Server; var testutils = require("./testutils.js"); -require("../src/js/amount.js").config = require("./config.js"); -require("../src/js/remote.js").config = require("./config.js"); +var extend = require('extend'); +extend(require('../src/js/config'), require('./config')); buster.testRunner.timeout = 5000; diff --git a/test/offer-test.js b/test/offer-test.js index de87c7d10..07122d1d8 100644 --- a/test/offer-test.js +++ b/test/offer-test.js @@ -9,8 +9,8 @@ var Server = require("./server").Server; var testutils = require("./testutils"); -require("../src/js/amount").config = require("./config"); -require("../src/js/remote").config = require("./config"); +var extend = require('extend'); +extend(require('../src/js/config'), require('./config')); buster.testRunner.timeout = 5000; diff --git a/test/path-test.js b/test/path-test.js index 9cc0d7f82..dbc72ef89 100644 --- a/test/path-test.js +++ b/test/path-test.js @@ -7,8 +7,8 @@ var Server = require("./server.js").Server; var testutils = require("./testutils.js"); -require("../src/js/amount.js").config = require("./config.js"); -require("../src/js/remote.js").config = require("./config.js"); +var extend = require('extend'); +extend(require('../src/js/config'), require('./config')); buster.testRunner.timeout = 5000; diff --git a/test/remote-test.js b/test/remote-test.js index 75ade8be3..e54afde22 100644 --- a/test/remote-test.js +++ b/test/remote-test.js @@ -6,8 +6,8 @@ var Server = require("./server.js").Server; var testutils = require("./testutils.js"); -require("../src/js/amount.js").config = require("./config.js"); -require("../src/js/remote.js").config = require("./config.js"); +var extend = require('extend'); +extend(require('../src/js/config'), require('./config')); // How long to wait for server to start. var serverDelay = 1500; // XXX Not implemented. diff --git a/test/send-test.js b/test/send-test.js index d2fe9c7f3..524841e86 100644 --- a/test/send-test.js +++ b/test/send-test.js @@ -7,8 +7,8 @@ var Server = require("./server.js").Server; var testutils = require("./testutils.js"); -require("../src/js/amount.js").config = require("./config.js"); -require("../src/js/remote.js").config = require("./config.js"); +var extend = require('extend'); +extend(require('../src/js/config'), require('./config')); // How long to wait for server to start. var serverDelay = 1500; diff --git a/test/testutils.js b/test/testutils.js index deddc3009..3ef6d6354 100644 --- a/test/testutils.js +++ b/test/testutils.js @@ -4,8 +4,8 @@ var Amount = require("../src/js/amount.js").Amount; var Remote = require("../src/js/remote.js").Remote; var Server = require("./server.js").Server; -require("../src/js/amount.js").config = require("./config.js"); -require("../src/js/remote.js").config = require("./config.js"); +var extend = require('extend'); +extend(require('../src/js/config'), require('./config')); var config = require("./config.js"); diff --git a/test/websocket-test.js b/test/websocket-test.js index 02d7e59f9..5f15135cc 100644 --- a/test/websocket-test.js +++ b/test/websocket-test.js @@ -4,7 +4,8 @@ var Server = require("./server.js").Server; var Remote = require("../src/js/remote.js").Remote; var config = require("./config.js"); -require("../src/js/remote.js").config = require("./config.js"); +var extend = require('extend'); +extend(require('../src/js/config'), require('./config')); buster.testRunner.timeout = 5000;