From 0509d5af373c6f542f569979d11fc3ef034294e6 Mon Sep 17 00:00:00 2001 From: Arthur Britto Date: Thu, 11 Oct 2012 16:19:24 -0700 Subject: [PATCH 01/10] JS: Split node utils into their own file. --- js/nodeutils.js | 88 ++++++++++++++++++ js/utils.js | 87 ------------------ test/server.js | 196 ++++++++++++++++++++-------------------- test/standalone-test.js | 3 +- 4 files changed, 187 insertions(+), 187 deletions(-) create mode 100644 js/nodeutils.js diff --git a/js/nodeutils.js b/js/nodeutils.js new file mode 100644 index 0000000000..befdb4abca --- /dev/null +++ b/js/nodeutils.js @@ -0,0 +1,88 @@ +var fs = require("fs"); +var path = require("path"); + +var utils = require("./utils.js"); + +// Empty a directory. +var emptyPath = function(dirPath, done) { + fs.readdir(dirPath, function(err, files) { + if (err) { + done(err); + } + else { + utils.mapOr(rmPath, files.map(function(f) { return path.join(dirPath, f); }), done); + } + }); +}; + +// Make a directory and sub-directories. +var mkPath = function(dirPath, mode, done) { + fs.mkdir(dirPath, typeof mode === "string" ? parseInt(mode, 8) : mode, function(e) { + if (!e || e.code === "EEXIST") { + // Created or already exists, done. + done(); + } + else if (e.code === "ENOENT") { + // Missing sub dir. + + mkPath(path.dirname(dirPath), mode, function(e) { + if (e) { + throw e; + } + else { + mkPath(dirPath, mode, done); + } + }); + } + else { + throw e; + } + }); +}; + +// Create directory if needed and empty if needed. +var resetPath = function(dirPath, mode, done) { + mkPath(dirPath, mode, function(e) { + if (e) { + done(e); + } + else { + emptyPath(dirPath, done); + } + }); +}; + +// Remove path recursively. +var rmPath = function(dirPath, done) { +// console.log("rmPath: %s", dirPath); + + fs.lstat(dirPath, function(err, stats) { + if (err && err.code == "ENOENT") { + done(); + } + if (err) { + done(err); + } + else if (stats.isDirectory()) { + emptyPath(dirPath, function(e) { + if (e) { + done(e); + } + else { +// console.log("rmdir: %s", dirPath); done(); + fs.rmdir(dirPath, done); + } + }); + } + else { +// console.log("unlink: %s", dirPath); done(); + fs.unlink(dirPath, done); + } + }); +}; + +exports.mkPath = mkPath; +exports.resetPath = resetPath; +exports.rmPath = rmPath; + +// vim:sw=2:sts=2:ts=8 diff --git a/js/utils.js b/js/utils.js index 950da3e416..37f78736b9 100644 --- a/js/utils.js +++ b/js/utils.js @@ -1,8 +1,3 @@ -// YYY Should probably have two versions: node vs browser - -var fs = require("fs"); -var path = require("path"); - Function.prototype.method = function(name,func) { this.prototype[name] = func; @@ -42,84 +37,6 @@ var mapOr = function(func, array, done) { } }; -// Make a directory and sub-directories. -var mkPath = function(dirPath, mode, done) { - fs.mkdir(dirPath, typeof mode === "string" ? parseInt(mode, 8) : mode, function(e) { - if (!e || e.code === "EEXIST") { - // Created or already exists, done. - done(); - } - else if (e.code === "ENOENT") { - // Missing sub dir. - - mkPath(path.dirname(dirPath), mode, function(e) { - if (e) { - throw e; - } - else { - mkPath(dirPath, mode, done); - } - }); - } - else { - throw e; - } - }); -}; - -// Empty a directory. -var emptyPath = function(dirPath, done) { - fs.readdir(dirPath, function(err, files) { - if (err) { - done(err); - } - else { - mapOr(rmPath, files.map(function(f) { return path.join(dirPath, f); }), done); - } - }); -}; - -// Remove path recursively. -var rmPath = function(dirPath, done) { -// console.log("rmPath: %s", dirPath); - - fs.lstat(dirPath, function(err, stats) { - if (err && err.code == "ENOENT") { - done(); - } - if (err) { - done(err); - } - else if (stats.isDirectory()) { - emptyPath(dirPath, function(e) { - if (e) { - done(e); - } - else { -// console.log("rmdir: %s", dirPath); done(); - fs.rmdir(dirPath, done); - } - }); - } - else { -// console.log("unlink: %s", dirPath); done(); - fs.unlink(dirPath, done); - } - }); -}; - -// Create directory if needed and empty if needed. -var resetPath = function(dirPath, mode, done) { - mkPath(dirPath, mode, function(e) { - if (e) { - done(e); - } - else { - emptyPath(dirPath, done); - } - }); -}; - var trace = function(comment, func) { return function() { console.log("%s: %s", trace, arguments.toString); @@ -151,11 +68,7 @@ var stringToHex = function (s) { }).join(""); }; -exports.emptyPath = emptyPath; exports.mapOr = mapOr; -exports.mkPath = mkPath; -exports.resetPath = resetPath; -exports.rmPath = rmPath; exports.trace = trace; exports.hexToString = hexToString; exports.stringToHex = stringToHex; diff --git a/test/server.js b/test/server.js index 4095b2a356..96d8d89c3e 100644 --- a/test/server.js +++ b/test/server.js @@ -8,148 +8,148 @@ // Servers are created in tmp/server/$server // -var config = require("./config.js"); -var utils = require("../js/utils.js"); +var config = require("./config.js"); +var nodeutils = require("../js/nodeutils.js"); -var fs = require("fs"); -var path = require("path"); -var util = require("util"); -var child = require("child_process"); +var fs = require("fs"); +var path = require("path"); +var util = require("util"); +var child = require("child_process"); var servers = {}; // Create a server object var Server = function(name) { - this.name = name; + this.name = name; }; // Return a server's newcoind.cfg as string. -Server.method('configContent', function() { - var cfg = config.servers[this.name]; +Server.prototype.configContent = function() { + var cfg = config.servers[this.name]; - return Object.keys(cfg).map(function(o) { - return util.format("[%s]\n%s\n", o, cfg[o]); - }).join(""); -}); + return Object.keys(cfg).map(function(o) { + return util.format("[%s]\n%s\n", o, cfg[o]); + }).join(""); +}; -Server.method('serverPath', function() { +Server.prototype.serverPath = function() { return "tmp/server/" + this.name; -}); +}; -Server.method('configPath', function() { - return path.join(this.serverPath(), "newcoind.cfg"); -}); +Server.prototype.configPath = function() { + return path.join(this.serverPath(), "newcoind.cfg"); +}; // Write a server's newcoind.cfg. -Server.method('writeConfig', function(done) { - fs.writeFile(this.configPath(), this.configContent(), 'utf8', done); -}); +Server.prototype.writeConfig = function(done) { + fs.writeFile(this.configPath(), this.configContent(), 'utf8', done); +}; // Spawn the server. -Server.method('serverSpawnSync', function() { - // Spawn in standalone mode for now. - this.child = child.spawn( - config.newcoind, - [ - "-a", - "-v", - "--conf=newcoind.cfg" - ], - { - cwd: this.serverPath(), - env: process.env, - stdio: 'inherit' - }); +Server.prototype.serverSpawnSync = function() { + // Spawn in standalone mode for now. + this.child = child.spawn( + config.newcoind, + [ + "-a", + "-v", + "--conf=newcoind.cfg" + ], + { + cwd: this.serverPath(), + env: process.env, + stdio: 'inherit' + }); - console.log("server: start %s: %s -a --conf=%s", this.child.pid, config.newcoind, this.configPath()); + console.log("server: start %s: %s -a --conf=%s", this.child.pid, config.newcoind, this.configPath()); - // By default, just log exits. - this.child.on('exit', function(code, signal) { - // If could not exec: code=127, signal=null - // If regular exit: code=0, signal=null - console.log("server: spawn: server exited code=%s: signal=%s", code, signal); - }); + // By default, just log exits. + this.child.on('exit', function(code, signal) { + // If could not exec: code=127, signal=null + // If regular exit: code=0, signal=null + console.log("server: spawn: server exited code=%s: signal=%s", code, signal); + }); -}); +}; // Prepare server's working directory. -Server.method('makeBase', function(done) { +Server.prototype.makeBase = function(done) { var path = this.serverPath(); - var self = this; + var self = this; // Reset the server directory, build it if needed. - utils.resetPath(path, '0777', function(e) { - if (e) { - throw e; - } - else { - self.writeConfig(done); - } - }); -}); + nodeutils.resetPath(path, '0777', function(e) { + if (e) { + throw e; + } + else { + self.writeConfig(done); + } + }); +}; // Create a standalone server. // Prepare the working directory and spawn the server. -Server.method('start', function(done) { - var self = this; +Server.prototype.start = function(done) { + var self = this; this.makeBase(function(e) { - if (e) { - throw e; - } - else { - self.serverSpawnSync(); - done(); - } - }); -}); + if (e) { + throw e; + } + else { + self.serverSpawnSync(); + done(); + } + }); +}; // Stop a standalone server. -Server.method('stop', function(done) { - if (this.child) { - // Update the on exit to invoke done. - this.child.on('exit', function(code, signal) { - console.log("server: stop: server exited"); - done(); - }); - this.child.kill(); - } - else - { - console.log("server: stop: no such server"); - done(); - } -}); +Server.prototype.stop = function(done) { + if (this.child) { + // Update the on exit to invoke done. + this.child.on('exit', function(code, signal) { + console.log("server: stop: server exited"); + done(); + }); + this.child.kill(); + } + else + { + console.log("server: stop: no such server"); + done('noSuchServer'); + } +}; // Start the named server. exports.start = function(name, done) { - if (servers[name]) - { - console.log("server: start: server already started."); - } - else - { - var server = new Server(name); + if (servers[name]) + { + console.log("server: start: server already started."); + } + else + { + var server = new Server(name); - servers[name] = server; + servers[name] = server; - console.log("server: start: %s", JSON.stringify(server)); + console.log("server: start: %s", JSON.stringify(server)); - server.start(done); - } + server.start(done); + } }; // Delete the named server. exports.stop = function(name, done) { - console.log("server: stop: %s of %s", name, Object.keys(servers).toString()); + console.log("server: stop: %s of %s", name, Object.keys(servers).toString()); - var server = servers[name]; - if (server) { - server.stop(done); - delete servers[name]; - } + var server = servers[name]; + if (server) { + server.stop(done); + delete servers[name]; + } }; exports.Server = Server; -// vim:ts=4 +// vim:sw=2:sts=2:ts=8 diff --git a/test/standalone-test.js b/test/standalone-test.js index 68d86b7843..f8b8985956 100644 --- a/test/standalone-test.js +++ b/test/standalone-test.js @@ -1,9 +1,8 @@ -var fs = require("fs"); var buster = require("buster"); +var config = require("./config.js"); var server = require("./server.js"); var remote = require("../js/remote.js"); -var config = require("./config.js"); // How long to wait for server to start. var serverDelay = 1500; From 37da7aa8bc2f9fdf551d37c52d5b9df90d90a9cd Mon Sep 17 00:00:00 2001 From: Arthur Britto Date: Thu, 11 Oct 2012 16:20:37 -0700 Subject: [PATCH 02/10] Make jsbn a single node style file. --- js/{jsbn/jsbn2.js => jsbn.js} | 565 +++++++++++++++++++++++++++++++++- js/jsbn/jsbn.js | 559 --------------------------------- 2 files changed, 564 insertions(+), 560 deletions(-) rename js/{jsbn/jsbn2.js => jsbn.js} (54%) delete mode 100644 js/jsbn/jsbn.js diff --git a/js/jsbn/jsbn2.js b/js/jsbn.js similarity index 54% rename from js/jsbn/jsbn2.js rename to js/jsbn.js index 5b2b725c46..61325fcbd1 100644 --- a/js/jsbn/jsbn2.js +++ b/js/jsbn.js @@ -1,12 +1,541 @@ +// Derived from Tom Wu's jsbn code. +// +// Changes made for clean up and to package as a node.js module. + // Copyright (c) 2005-2009 Tom Wu // All Rights Reserved. // See "LICENSE" for details. +// Basic JavaScript BN library - subset useful for RSA encryption. // Extended JavaScript BN functions, required for RSA private ops. - // Version 1.1: new BigInteger("0", 10) returns "proper" zero // Version 1.2: square() API, isProbablePrime fix +// Bits per digit +var dbits; + +// JavaScript engine analysis +var canary = 0xdeadbeefcafe; +var j_lm = ((canary&0xffffff)==0xefcafe); + +// (public) Constructor +var BigInteger = function BigInteger(a,b,c) { + if(a != null) + if("number" == typeof a) this.fromNumber(a,b,c); + else if(b == null && "string" != typeof a) this.fromString(a,256); + else this.fromString(a,b); +}; + +// return new, unset BigInteger +var nbi = function nbi() { return new BigInteger(null); }; + +// am: Compute w_j += (x*this_i), propagate carries, +// c is initial carry, returns final carry. +// c < 3*dvalue, x < 2*dvalue, this_i < dvalue +// We need to select the fastest one that works in this environment. + +// am1: use a single mult and divide to get the high bits, +// max digit bits should be 26 because +// max internal value = 2*dvalue^2-2*dvalue (< 2^53) +function am1(i,x,w,j,c,n) { + while(--n >= 0) { + var v = x*this[i++]+w[j]+c; + c = Math.floor(v/0x4000000); + w[j++] = v&0x3ffffff; + } + return c; +} +// am2 avoids a big mult-and-extract completely. +// Max digit bits should be <= 30 because we do bitwise ops +// on values up to 2*hdvalue^2-hdvalue-1 (< 2^31) +function am2(i,x,w,j,c,n) { + var xl = x&0x7fff, xh = x>>15; + while(--n >= 0) { + var l = this[i]&0x7fff; + var h = this[i++]>>15; + var m = xh*l+h*xl; + l = xl*l+((m&0x7fff)<<15)+w[j]+(c&0x3fffffff); + c = (l>>>30)+(m>>>15)+xh*h+(c>>>30); + w[j++] = l&0x3fffffff; + } + return c; +} +// Alternately, set max digit bits to 28 since some +// browsers slow down when dealing with 32-bit numbers. +function am3(i,x,w,j,c,n) { + var xl = x&0x3fff, xh = x>>14; + while(--n >= 0) { + var l = this[i]&0x3fff; + var h = this[i++]>>14; + var m = xh*l+h*xl; + l = xl*l+((m&0x3fff)<<14)+w[j]+c; + c = (l>>28)+(m>>14)+xh*h; + w[j++] = l&0xfffffff; + } + return c; +} + +if(j_lm && 'undefined' !== typeof navigator && (navigator.appName == "Microsoft Internet Explorer")) { + BigInteger.prototype.am = am2; + dbits = 30; +} +else if(j_lm && 'undefined' !== typeof navigator && (navigator.appName != "Netscape")) { + BigInteger.prototype.am = am1; + dbits = 26; +} +else { // Mozilla/Netscape seems to prefer am3 + BigInteger.prototype.am = am3; + dbits = 28; +} + +BigInteger.prototype.DB = dbits; +BigInteger.prototype.DM = ((1<= 0; --i) r[i] = this[i]; + r.t = this.t; + r.s = this.s; +} + +// (protected) set from integer value x, -DV <= x < DV +function bnpFromInt(x) { + this.t = 1; + this.s = (x<0)?-1:0; + if(x > 0) this[0] = x; + else if(x < -1) this[0] = x+DV; + else this.t = 0; +} + +// return bigint initialized to value +function nbv(i) { var r = nbi(); r.fromInt(i); return r; } + +// (protected) set from string and radix +function bnpFromString(s,b) { + var k; + if(b == 16) k = 4; + else if(b == 8) k = 3; + else if(b == 256) k = 8; // byte array + else if(b == 2) k = 1; + else if(b == 32) k = 5; + else if(b == 4) k = 2; + else { this.fromRadix(s,b); return; } + this.t = 0; + this.s = 0; + var i = s.length, mi = false, sh = 0; + while(--i >= 0) { + var x = (k==8)?s[i]&0xff:intAt(s,i); + if(x < 0) { + if(s.charAt(i) == "-") mi = true; + continue; + } + mi = false; + if(sh == 0) + this[this.t++] = x; + else if(sh+k > this.DB) { + this[this.t-1] |= (x&((1<<(this.DB-sh))-1))<>(this.DB-sh)); + } + else + this[this.t-1] |= x<= this.DB) sh -= this.DB; + } + if(k == 8 && (s[0]&0x80) != 0) { + this.s = -1; + if(sh > 0) this[this.t-1] |= ((1<<(this.DB-sh))-1)< 0 && this[this.t-1] == c) --this.t; +} + +// (public) return string representation in given radix +function bnToString(b) { + if(this.s < 0) return "-"+this.negate().toString(b); + var k; + if(b == 16) k = 4; + else if(b == 8) k = 3; + else if(b == 2) k = 1; + else if(b == 32) k = 5; + else if(b == 4) k = 2; + else return this.toRadix(b); + var km = (1< 0) { + if(p < this.DB && (d = this[i]>>p) > 0) { m = true; r = int2char(d); } + while(i >= 0) { + if(p < k) { + d = (this[i]&((1<>(p+=this.DB-k); + } + else { + d = (this[i]>>(p-=k))&km; + if(p <= 0) { p += this.DB; --i; } + } + if(d > 0) m = true; + if(m) r += int2char(d); + } + } + return m?r:"0"; +} + +// (public) -this +function bnNegate() { var r = nbi(); BigInteger.ZERO.subTo(this,r); return r; } + +// (public) |this| +function bnAbs() { return (this.s<0)?this.negate():this; } + +// (public) return + if this > a, - if this < a, 0 if equal +function bnCompareTo(a) { + var r = this.s-a.s; + if(r != 0) return r; + var i = this.t; + r = i-a.t; + if(r != 0) return (this.s<0)?-r:r; + while(--i >= 0) if((r=this[i]-a[i]) != 0) return r; + return 0; +} + +// returns bit length of the integer x +function nbits(x) { + var r = 1, t; + if((t=x>>>16) != 0) { x = t; r += 16; } + if((t=x>>8) != 0) { x = t; r += 8; } + if((t=x>>4) != 0) { x = t; r += 4; } + if((t=x>>2) != 0) { x = t; r += 2; } + if((t=x>>1) != 0) { x = t; r += 1; } + return r; +} + +// (public) return the number of bits in "this" +function bnBitLength() { + if(this.t <= 0) return 0; + return this.DB*(this.t-1)+nbits(this[this.t-1]^(this.s&this.DM)); +} + +// (protected) r = this << n*DB +function bnpDLShiftTo(n,r) { + var i; + for(i = this.t-1; i >= 0; --i) r[i+n] = this[i]; + for(i = n-1; i >= 0; --i) r[i] = 0; + r.t = this.t+n; + r.s = this.s; +} + +// (protected) r = this >> n*DB +function bnpDRShiftTo(n,r) { + for(var i = n; i < this.t; ++i) r[i-n] = this[i]; + r.t = Math.max(this.t-n,0); + r.s = this.s; +} + +// (protected) r = this << n +function bnpLShiftTo(n,r) { + var bs = n%this.DB; + var cbs = this.DB-bs; + var bm = (1<= 0; --i) { + r[i+ds+1] = (this[i]>>cbs)|c; + c = (this[i]&bm)<= 0; --i) r[i] = 0; + r[ds] = c; + r.t = this.t+ds+1; + r.s = this.s; + r.clamp(); +} + +// (protected) r = this >> n +function bnpRShiftTo(n,r) { + r.s = this.s; + var ds = Math.floor(n/this.DB); + if(ds >= this.t) { r.t = 0; return; } + var bs = n%this.DB; + var cbs = this.DB-bs; + var bm = (1<>bs; + for(var i = ds+1; i < this.t; ++i) { + r[i-ds-1] |= (this[i]&bm)<>bs; + } + if(bs > 0) r[this.t-ds-1] |= (this.s&bm)<>= this.DB; + } + if(a.t < this.t) { + c -= a.s; + while(i < this.t) { + c += this[i]; + r[i++] = c&this.DM; + c >>= this.DB; + } + c += this.s; + } + else { + c += this.s; + while(i < a.t) { + c -= a[i]; + r[i++] = c&this.DM; + c >>= this.DB; + } + c -= a.s; + } + r.s = (c<0)?-1:0; + if(c < -1) r[i++] = this.DV+c; + else if(c > 0) r[i++] = c; + r.t = i; + r.clamp(); +} + +// (protected) r = this * a, r != this,a (HAC 14.12) +// "this" should be the larger one if appropriate. +function bnpMultiplyTo(a,r) { + var x = this.abs(), y = a.abs(); + var i = x.t; + r.t = i+y.t; + while(--i >= 0) r[i] = 0; + for(i = 0; i < y.t; ++i) r[i+x.t] = x.am(0,y[i],r,i,0,x.t); + r.s = 0; + r.clamp(); + if(this.s != a.s) BigInteger.ZERO.subTo(r,r); +} + +// (protected) r = this^2, r != this (HAC 14.16) +function bnpSquareTo(r) { + var x = this.abs(); + var i = r.t = 2*x.t; + while(--i >= 0) r[i] = 0; + for(i = 0; i < x.t-1; ++i) { + var c = x.am(i,x[i],r,2*i,0,1); + if((r[i+x.t]+=x.am(i+1,2*x[i],r,2*i+1,c,x.t-i-1)) >= x.DV) { + r[i+x.t] -= x.DV; + r[i+x.t+1] = 1; + } + } + if(r.t > 0) r[r.t-1] += x.am(i,x[i],r,2*i,0,1); + r.s = 0; + r.clamp(); +} + +// (protected) divide this by m, quotient and remainder to q, r (HAC 14.20) +// r != q, this != m. q or r may be null. +function bnpDivRemTo(m,q,r) { + var pm = m.abs(); + if(pm.t <= 0) return; + var pt = this.abs(); + if(pt.t < pm.t) { + if(q != null) q.fromInt(0); + if(r != null) this.copyTo(r); + return; + } + if(r == null) r = nbi(); + var y = nbi(), ts = this.s, ms = m.s; + var nsh = this.DB-nbits(pm[pm.t-1]); // normalize modulus + if(nsh > 0) { pm.lShiftTo(nsh,y); pt.lShiftTo(nsh,r); } + else { pm.copyTo(y); pt.copyTo(r); } + var ys = y.t; + var y0 = y[ys-1]; + if(y0 == 0) return; + var yt = y0*(1<1)?y[ys-2]>>this.F2:0); + var d1 = this.FV/yt, d2 = (1<= 0) { + r[r.t++] = 1; + r.subTo(t,r); + } + BigInteger.ONE.dlShiftTo(ys,t); + t.subTo(y,y); // "negative" y so we can replace sub with am later + while(y.t < ys) y[y.t++] = 0; + while(--j >= 0) { + // Estimate quotient digit + var qd = (r[--i]==y0)?this.DM:Math.floor(r[i]*d1+(r[i-1]+e)*d2); + if((r[i]+=y.am(0,qd,r,j,0,ys)) < qd) { // Try it out + y.dlShiftTo(j,t); + r.subTo(t,r); + while(r[i] < --qd) r.subTo(t,r); + } + } + if(q != null) { + r.drShiftTo(ys,q); + if(ts != ms) BigInteger.ZERO.subTo(q,q); + } + r.t = ys; + r.clamp(); + if(nsh > 0) r.rShiftTo(nsh,r); // Denormalize remainder + if(ts < 0) BigInteger.ZERO.subTo(r,r); +} + +// (public) this mod a +function bnMod(a) { + var r = nbi(); + this.abs().divRemTo(a,null,r); + if(this.s < 0 && r.compareTo(BigInteger.ZERO) > 0) a.subTo(r,r); + return r; +} + +// Modular reduction using "classic" algorithm +function Classic(m) { this.m = m; } +function cConvert(x) { + if(x.s < 0 || x.compareTo(this.m) >= 0) return x.mod(this.m); + else return x; +} +function cRevert(x) { return x; } +function cReduce(x) { x.divRemTo(this.m,null,x); } +function cMulTo(x,y,r) { x.multiplyTo(y,r); this.reduce(r); } +function cSqrTo(x,r) { x.squareTo(r); this.reduce(r); } + +Classic.prototype.convert = cConvert; +Classic.prototype.revert = cRevert; +Classic.prototype.reduce = cReduce; +Classic.prototype.mulTo = cMulTo; +Classic.prototype.sqrTo = cSqrTo; + +// (protected) return "-1/this % 2^DB"; useful for Mont. reduction +// justification: +// xy == 1 (mod m) +// xy = 1+km +// xy(2-xy) = (1+km)(1-km) +// x[y(2-xy)] = 1-k^2m^2 +// x[y(2-xy)] == 1 (mod m^2) +// if y is 1/x mod m, then y(2-xy) is 1/x mod m^2 +// should reduce x and y(2-xy) by m^2 at each step to keep size bounded. +// JS multiply "overflows" differently from C/C++, so care is needed here. +function bnpInvDigit() { + if(this.t < 1) return 0; + var x = this[0]; + if((x&1) == 0) return 0; + var y = x&3; // y == 1/x mod 2^2 + y = (y*(2-(x&0xf)*y))&0xf; // y == 1/x mod 2^4 + y = (y*(2-(x&0xff)*y))&0xff; // y == 1/x mod 2^8 + y = (y*(2-(((x&0xffff)*y)&0xffff)))&0xffff; // y == 1/x mod 2^16 + // last step - calculate inverse mod DV directly; + // assumes 16 < DB <= 32 and assumes ability to handle 48-bit ints + y = (y*(2-x*y%this.DV))%this.DV; // y == 1/x mod 2^dbits + // we really want the negative inverse, and -DV < y < DV + return (y>0)?this.DV-y:-y; +} + +// Montgomery reduction +function Montgomery(m) { + this.m = m; + this.mp = m.invDigit(); + this.mpl = this.mp&0x7fff; + this.mph = this.mp>>15; + this.um = (1<<(m.DB-15))-1; + this.mt2 = 2*m.t; +} + +// xR mod m +function montConvert(x) { + var r = nbi(); + x.abs().dlShiftTo(this.m.t,r); + r.divRemTo(this.m,null,r); + if(x.s < 0 && r.compareTo(BigInteger.ZERO) > 0) this.m.subTo(r,r); + return r; +} + +// x/R mod m +function montRevert(x) { + var r = nbi(); + x.copyTo(r); + this.reduce(r); + return r; +} + +// x = x/R mod m (HAC 14.32) +function montReduce(x) { + while(x.t <= this.mt2) // pad x so am has enough room later + x[x.t++] = 0; + for(var i = 0; i < this.m.t; ++i) { + // faster way of calculating u0 = x[i]*mp mod DV + var j = x[i]&0x7fff; + var u0 = (j*this.mpl+(((j*this.mph+(x[i]>>15)*this.mpl)&this.um)<<15))&x.DM; + // use am to combine the multiply-shift-add into one call + j = i+this.m.t; + x[j] += this.m.am(0,u0,x,i,0,this.m.t); + // propagate carry + while(x[j] >= x.DV) { x[j] -= x.DV; x[++j]++; } + } + x.clamp(); + x.drShiftTo(this.m.t,x); + if(x.compareTo(this.m) >= 0) x.subTo(this.m,x); +} + +// r = "x^2/R mod m"; x != r +function montSqrTo(x,r) { x.squareTo(r); this.reduce(r); } + +// r = "xy/R mod m"; x,y != r +function montMulTo(x,y,r) { x.multiplyTo(y,r); this.reduce(r); } + +Montgomery.prototype.convert = montConvert; +Montgomery.prototype.revert = montRevert; +Montgomery.prototype.reduce = montReduce; +Montgomery.prototype.mulTo = montMulTo; +Montgomery.prototype.sqrTo = montSqrTo; + +// (protected) true iff this is even +function bnpIsEven() { return ((this.t>0)?(this[0]&1):this.s) == 0; } + +// (protected) this^e, e < 2^32, doing sqr and mul with "r" (HAC 14.79) +function bnpExp(e,z) { + if(e > 0xffffffff || e < 1) return BigInteger.ONE; + var r = nbi(), r2 = nbi(), g = z.convert(this), i = nbits(e)-1; + g.copyTo(r); + while(--i >= 0) { + z.sqrTo(r,r2); + if((e&(1< 0) z.mulTo(r2,g,r); + else { var t = r; r = r2; r2 = t; } + } + return z.revert(r); +} + +// (public) this^e % m, 0 <= e < 2^32 +function bnModPowInt(e,m) { + var z; + if(e < 256 || m.isEven()) z = new Classic(m); else z = new Montgomery(m); + return this.exp(e,z); +} + // (public) function bnClone() { var r = nbi(); this.copyTo(r); return r; } @@ -654,3 +1183,37 @@ BigInteger.prototype.square = bnSquare; // int hashCode() // long longValue() // static BigInteger valueOf(long val) +// protected +BigInteger.prototype.copyTo = bnpCopyTo; +BigInteger.prototype.fromInt = bnpFromInt; +BigInteger.prototype.fromString = bnpFromString; +BigInteger.prototype.clamp = bnpClamp; +BigInteger.prototype.dlShiftTo = bnpDLShiftTo; +BigInteger.prototype.drShiftTo = bnpDRShiftTo; +BigInteger.prototype.lShiftTo = bnpLShiftTo; +BigInteger.prototype.rShiftTo = bnpRShiftTo; +BigInteger.prototype.subTo = bnpSubTo; +BigInteger.prototype.multiplyTo = bnpMultiplyTo; +BigInteger.prototype.squareTo = bnpSquareTo; +BigInteger.prototype.divRemTo = bnpDivRemTo; +BigInteger.prototype.invDigit = bnpInvDigit; +BigInteger.prototype.isEven = bnpIsEven; +BigInteger.prototype.exp = bnpExp; + +// public +BigInteger.prototype.toString = bnToString; +BigInteger.prototype.negate = bnNegate; +BigInteger.prototype.abs = bnAbs; +BigInteger.prototype.compareTo = bnCompareTo; +BigInteger.prototype.bitLength = bnBitLength; +BigInteger.prototype.mod = bnMod; +BigInteger.prototype.modPowInt = bnModPowInt; + +// "constants" +BigInteger.ZERO = nbv(0); +BigInteger.ONE = nbv(1); + +exports.nbi = nbi; +exports.BigInteger = BigInteger; + +// vim:sw=2:sts=2:ts=8 diff --git a/js/jsbn/jsbn.js b/js/jsbn/jsbn.js deleted file mode 100644 index 40bb9e2b9c..0000000000 --- a/js/jsbn/jsbn.js +++ /dev/null @@ -1,559 +0,0 @@ -// Copyright (c) 2005 Tom Wu -// All Rights Reserved. -// See "LICENSE" for details. - -// Basic JavaScript BN library - subset useful for RSA encryption. - -// Bits per digit -var dbits; - -// JavaScript engine analysis -var canary = 0xdeadbeefcafe; -var j_lm = ((canary&0xffffff)==0xefcafe); - -// (public) Constructor -function BigInteger(a,b,c) { - if(a != null) - if("number" == typeof a) this.fromNumber(a,b,c); - else if(b == null && "string" != typeof a) this.fromString(a,256); - else this.fromString(a,b); -} - -// return new, unset BigInteger -function nbi() { return new BigInteger(null); } - -// am: Compute w_j += (x*this_i), propagate carries, -// c is initial carry, returns final carry. -// c < 3*dvalue, x < 2*dvalue, this_i < dvalue -// We need to select the fastest one that works in this environment. - -// am1: use a single mult and divide to get the high bits, -// max digit bits should be 26 because -// max internal value = 2*dvalue^2-2*dvalue (< 2^53) -function am1(i,x,w,j,c,n) { - while(--n >= 0) { - var v = x*this[i++]+w[j]+c; - c = Math.floor(v/0x4000000); - w[j++] = v&0x3ffffff; - } - return c; -} -// am2 avoids a big mult-and-extract completely. -// Max digit bits should be <= 30 because we do bitwise ops -// on values up to 2*hdvalue^2-hdvalue-1 (< 2^31) -function am2(i,x,w,j,c,n) { - var xl = x&0x7fff, xh = x>>15; - while(--n >= 0) { - var l = this[i]&0x7fff; - var h = this[i++]>>15; - var m = xh*l+h*xl; - l = xl*l+((m&0x7fff)<<15)+w[j]+(c&0x3fffffff); - c = (l>>>30)+(m>>>15)+xh*h+(c>>>30); - w[j++] = l&0x3fffffff; - } - return c; -} -// Alternately, set max digit bits to 28 since some -// browsers slow down when dealing with 32-bit numbers. -function am3(i,x,w,j,c,n) { - var xl = x&0x3fff, xh = x>>14; - while(--n >= 0) { - var l = this[i]&0x3fff; - var h = this[i++]>>14; - var m = xh*l+h*xl; - l = xl*l+((m&0x3fff)<<14)+w[j]+c; - c = (l>>28)+(m>>14)+xh*h; - w[j++] = l&0xfffffff; - } - return c; -} -if(j_lm && (navigator.appName == "Microsoft Internet Explorer")) { - BigInteger.prototype.am = am2; - dbits = 30; -} -else if(j_lm && (navigator.appName != "Netscape")) { - BigInteger.prototype.am = am1; - dbits = 26; -} -else { // Mozilla/Netscape seems to prefer am3 - BigInteger.prototype.am = am3; - dbits = 28; -} - -BigInteger.prototype.DB = dbits; -BigInteger.prototype.DM = ((1<= 0; --i) r[i] = this[i]; - r.t = this.t; - r.s = this.s; -} - -// (protected) set from integer value x, -DV <= x < DV -function bnpFromInt(x) { - this.t = 1; - this.s = (x<0)?-1:0; - if(x > 0) this[0] = x; - else if(x < -1) this[0] = x+DV; - else this.t = 0; -} - -// return bigint initialized to value -function nbv(i) { var r = nbi(); r.fromInt(i); return r; } - -// (protected) set from string and radix -function bnpFromString(s,b) { - var k; - if(b == 16) k = 4; - else if(b == 8) k = 3; - else if(b == 256) k = 8; // byte array - else if(b == 2) k = 1; - else if(b == 32) k = 5; - else if(b == 4) k = 2; - else { this.fromRadix(s,b); return; } - this.t = 0; - this.s = 0; - var i = s.length, mi = false, sh = 0; - while(--i >= 0) { - var x = (k==8)?s[i]&0xff:intAt(s,i); - if(x < 0) { - if(s.charAt(i) == "-") mi = true; - continue; - } - mi = false; - if(sh == 0) - this[this.t++] = x; - else if(sh+k > this.DB) { - this[this.t-1] |= (x&((1<<(this.DB-sh))-1))<>(this.DB-sh)); - } - else - this[this.t-1] |= x<= this.DB) sh -= this.DB; - } - if(k == 8 && (s[0]&0x80) != 0) { - this.s = -1; - if(sh > 0) this[this.t-1] |= ((1<<(this.DB-sh))-1)< 0 && this[this.t-1] == c) --this.t; -} - -// (public) return string representation in given radix -function bnToString(b) { - if(this.s < 0) return "-"+this.negate().toString(b); - var k; - if(b == 16) k = 4; - else if(b == 8) k = 3; - else if(b == 2) k = 1; - else if(b == 32) k = 5; - else if(b == 4) k = 2; - else return this.toRadix(b); - var km = (1< 0) { - if(p < this.DB && (d = this[i]>>p) > 0) { m = true; r = int2char(d); } - while(i >= 0) { - if(p < k) { - d = (this[i]&((1<>(p+=this.DB-k); - } - else { - d = (this[i]>>(p-=k))&km; - if(p <= 0) { p += this.DB; --i; } - } - if(d > 0) m = true; - if(m) r += int2char(d); - } - } - return m?r:"0"; -} - -// (public) -this -function bnNegate() { var r = nbi(); BigInteger.ZERO.subTo(this,r); return r; } - -// (public) |this| -function bnAbs() { return (this.s<0)?this.negate():this; } - -// (public) return + if this > a, - if this < a, 0 if equal -function bnCompareTo(a) { - var r = this.s-a.s; - if(r != 0) return r; - var i = this.t; - r = i-a.t; - if(r != 0) return (this.s<0)?-r:r; - while(--i >= 0) if((r=this[i]-a[i]) != 0) return r; - return 0; -} - -// returns bit length of the integer x -function nbits(x) { - var r = 1, t; - if((t=x>>>16) != 0) { x = t; r += 16; } - if((t=x>>8) != 0) { x = t; r += 8; } - if((t=x>>4) != 0) { x = t; r += 4; } - if((t=x>>2) != 0) { x = t; r += 2; } - if((t=x>>1) != 0) { x = t; r += 1; } - return r; -} - -// (public) return the number of bits in "this" -function bnBitLength() { - if(this.t <= 0) return 0; - return this.DB*(this.t-1)+nbits(this[this.t-1]^(this.s&this.DM)); -} - -// (protected) r = this << n*DB -function bnpDLShiftTo(n,r) { - var i; - for(i = this.t-1; i >= 0; --i) r[i+n] = this[i]; - for(i = n-1; i >= 0; --i) r[i] = 0; - r.t = this.t+n; - r.s = this.s; -} - -// (protected) r = this >> n*DB -function bnpDRShiftTo(n,r) { - for(var i = n; i < this.t; ++i) r[i-n] = this[i]; - r.t = Math.max(this.t-n,0); - r.s = this.s; -} - -// (protected) r = this << n -function bnpLShiftTo(n,r) { - var bs = n%this.DB; - var cbs = this.DB-bs; - var bm = (1<= 0; --i) { - r[i+ds+1] = (this[i]>>cbs)|c; - c = (this[i]&bm)<= 0; --i) r[i] = 0; - r[ds] = c; - r.t = this.t+ds+1; - r.s = this.s; - r.clamp(); -} - -// (protected) r = this >> n -function bnpRShiftTo(n,r) { - r.s = this.s; - var ds = Math.floor(n/this.DB); - if(ds >= this.t) { r.t = 0; return; } - var bs = n%this.DB; - var cbs = this.DB-bs; - var bm = (1<>bs; - for(var i = ds+1; i < this.t; ++i) { - r[i-ds-1] |= (this[i]&bm)<>bs; - } - if(bs > 0) r[this.t-ds-1] |= (this.s&bm)<>= this.DB; - } - if(a.t < this.t) { - c -= a.s; - while(i < this.t) { - c += this[i]; - r[i++] = c&this.DM; - c >>= this.DB; - } - c += this.s; - } - else { - c += this.s; - while(i < a.t) { - c -= a[i]; - r[i++] = c&this.DM; - c >>= this.DB; - } - c -= a.s; - } - r.s = (c<0)?-1:0; - if(c < -1) r[i++] = this.DV+c; - else if(c > 0) r[i++] = c; - r.t = i; - r.clamp(); -} - -// (protected) r = this * a, r != this,a (HAC 14.12) -// "this" should be the larger one if appropriate. -function bnpMultiplyTo(a,r) { - var x = this.abs(), y = a.abs(); - var i = x.t; - r.t = i+y.t; - while(--i >= 0) r[i] = 0; - for(i = 0; i < y.t; ++i) r[i+x.t] = x.am(0,y[i],r,i,0,x.t); - r.s = 0; - r.clamp(); - if(this.s != a.s) BigInteger.ZERO.subTo(r,r); -} - -// (protected) r = this^2, r != this (HAC 14.16) -function bnpSquareTo(r) { - var x = this.abs(); - var i = r.t = 2*x.t; - while(--i >= 0) r[i] = 0; - for(i = 0; i < x.t-1; ++i) { - var c = x.am(i,x[i],r,2*i,0,1); - if((r[i+x.t]+=x.am(i+1,2*x[i],r,2*i+1,c,x.t-i-1)) >= x.DV) { - r[i+x.t] -= x.DV; - r[i+x.t+1] = 1; - } - } - if(r.t > 0) r[r.t-1] += x.am(i,x[i],r,2*i,0,1); - r.s = 0; - r.clamp(); -} - -// (protected) divide this by m, quotient and remainder to q, r (HAC 14.20) -// r != q, this != m. q or r may be null. -function bnpDivRemTo(m,q,r) { - var pm = m.abs(); - if(pm.t <= 0) return; - var pt = this.abs(); - if(pt.t < pm.t) { - if(q != null) q.fromInt(0); - if(r != null) this.copyTo(r); - return; - } - if(r == null) r = nbi(); - var y = nbi(), ts = this.s, ms = m.s; - var nsh = this.DB-nbits(pm[pm.t-1]); // normalize modulus - if(nsh > 0) { pm.lShiftTo(nsh,y); pt.lShiftTo(nsh,r); } - else { pm.copyTo(y); pt.copyTo(r); } - var ys = y.t; - var y0 = y[ys-1]; - if(y0 == 0) return; - var yt = y0*(1<1)?y[ys-2]>>this.F2:0); - var d1 = this.FV/yt, d2 = (1<= 0) { - r[r.t++] = 1; - r.subTo(t,r); - } - BigInteger.ONE.dlShiftTo(ys,t); - t.subTo(y,y); // "negative" y so we can replace sub with am later - while(y.t < ys) y[y.t++] = 0; - while(--j >= 0) { - // Estimate quotient digit - var qd = (r[--i]==y0)?this.DM:Math.floor(r[i]*d1+(r[i-1]+e)*d2); - if((r[i]+=y.am(0,qd,r,j,0,ys)) < qd) { // Try it out - y.dlShiftTo(j,t); - r.subTo(t,r); - while(r[i] < --qd) r.subTo(t,r); - } - } - if(q != null) { - r.drShiftTo(ys,q); - if(ts != ms) BigInteger.ZERO.subTo(q,q); - } - r.t = ys; - r.clamp(); - if(nsh > 0) r.rShiftTo(nsh,r); // Denormalize remainder - if(ts < 0) BigInteger.ZERO.subTo(r,r); -} - -// (public) this mod a -function bnMod(a) { - var r = nbi(); - this.abs().divRemTo(a,null,r); - if(this.s < 0 && r.compareTo(BigInteger.ZERO) > 0) a.subTo(r,r); - return r; -} - -// Modular reduction using "classic" algorithm -function Classic(m) { this.m = m; } -function cConvert(x) { - if(x.s < 0 || x.compareTo(this.m) >= 0) return x.mod(this.m); - else return x; -} -function cRevert(x) { return x; } -function cReduce(x) { x.divRemTo(this.m,null,x); } -function cMulTo(x,y,r) { x.multiplyTo(y,r); this.reduce(r); } -function cSqrTo(x,r) { x.squareTo(r); this.reduce(r); } - -Classic.prototype.convert = cConvert; -Classic.prototype.revert = cRevert; -Classic.prototype.reduce = cReduce; -Classic.prototype.mulTo = cMulTo; -Classic.prototype.sqrTo = cSqrTo; - -// (protected) return "-1/this % 2^DB"; useful for Mont. reduction -// justification: -// xy == 1 (mod m) -// xy = 1+km -// xy(2-xy) = (1+km)(1-km) -// x[y(2-xy)] = 1-k^2m^2 -// x[y(2-xy)] == 1 (mod m^2) -// if y is 1/x mod m, then y(2-xy) is 1/x mod m^2 -// should reduce x and y(2-xy) by m^2 at each step to keep size bounded. -// JS multiply "overflows" differently from C/C++, so care is needed here. -function bnpInvDigit() { - if(this.t < 1) return 0; - var x = this[0]; - if((x&1) == 0) return 0; - var y = x&3; // y == 1/x mod 2^2 - y = (y*(2-(x&0xf)*y))&0xf; // y == 1/x mod 2^4 - y = (y*(2-(x&0xff)*y))&0xff; // y == 1/x mod 2^8 - y = (y*(2-(((x&0xffff)*y)&0xffff)))&0xffff; // y == 1/x mod 2^16 - // last step - calculate inverse mod DV directly; - // assumes 16 < DB <= 32 and assumes ability to handle 48-bit ints - y = (y*(2-x*y%this.DV))%this.DV; // y == 1/x mod 2^dbits - // we really want the negative inverse, and -DV < y < DV - return (y>0)?this.DV-y:-y; -} - -// Montgomery reduction -function Montgomery(m) { - this.m = m; - this.mp = m.invDigit(); - this.mpl = this.mp&0x7fff; - this.mph = this.mp>>15; - this.um = (1<<(m.DB-15))-1; - this.mt2 = 2*m.t; -} - -// xR mod m -function montConvert(x) { - var r = nbi(); - x.abs().dlShiftTo(this.m.t,r); - r.divRemTo(this.m,null,r); - if(x.s < 0 && r.compareTo(BigInteger.ZERO) > 0) this.m.subTo(r,r); - return r; -} - -// x/R mod m -function montRevert(x) { - var r = nbi(); - x.copyTo(r); - this.reduce(r); - return r; -} - -// x = x/R mod m (HAC 14.32) -function montReduce(x) { - while(x.t <= this.mt2) // pad x so am has enough room later - x[x.t++] = 0; - for(var i = 0; i < this.m.t; ++i) { - // faster way of calculating u0 = x[i]*mp mod DV - var j = x[i]&0x7fff; - var u0 = (j*this.mpl+(((j*this.mph+(x[i]>>15)*this.mpl)&this.um)<<15))&x.DM; - // use am to combine the multiply-shift-add into one call - j = i+this.m.t; - x[j] += this.m.am(0,u0,x,i,0,this.m.t); - // propagate carry - while(x[j] >= x.DV) { x[j] -= x.DV; x[++j]++; } - } - x.clamp(); - x.drShiftTo(this.m.t,x); - if(x.compareTo(this.m) >= 0) x.subTo(this.m,x); -} - -// r = "x^2/R mod m"; x != r -function montSqrTo(x,r) { x.squareTo(r); this.reduce(r); } - -// r = "xy/R mod m"; x,y != r -function montMulTo(x,y,r) { x.multiplyTo(y,r); this.reduce(r); } - -Montgomery.prototype.convert = montConvert; -Montgomery.prototype.revert = montRevert; -Montgomery.prototype.reduce = montReduce; -Montgomery.prototype.mulTo = montMulTo; -Montgomery.prototype.sqrTo = montSqrTo; - -// (protected) true iff this is even -function bnpIsEven() { return ((this.t>0)?(this[0]&1):this.s) == 0; } - -// (protected) this^e, e < 2^32, doing sqr and mul with "r" (HAC 14.79) -function bnpExp(e,z) { - if(e > 0xffffffff || e < 1) return BigInteger.ONE; - var r = nbi(), r2 = nbi(), g = z.convert(this), i = nbits(e)-1; - g.copyTo(r); - while(--i >= 0) { - z.sqrTo(r,r2); - if((e&(1< 0) z.mulTo(r2,g,r); - else { var t = r; r = r2; r2 = t; } - } - return z.revert(r); -} - -// (public) this^e % m, 0 <= e < 2^32 -function bnModPowInt(e,m) { - var z; - if(e < 256 || m.isEven()) z = new Classic(m); else z = new Montgomery(m); - return this.exp(e,z); -} - -// protected -BigInteger.prototype.copyTo = bnpCopyTo; -BigInteger.prototype.fromInt = bnpFromInt; -BigInteger.prototype.fromString = bnpFromString; -BigInteger.prototype.clamp = bnpClamp; -BigInteger.prototype.dlShiftTo = bnpDLShiftTo; -BigInteger.prototype.drShiftTo = bnpDRShiftTo; -BigInteger.prototype.lShiftTo = bnpLShiftTo; -BigInteger.prototype.rShiftTo = bnpRShiftTo; -BigInteger.prototype.subTo = bnpSubTo; -BigInteger.prototype.multiplyTo = bnpMultiplyTo; -BigInteger.prototype.squareTo = bnpSquareTo; -BigInteger.prototype.divRemTo = bnpDivRemTo; -BigInteger.prototype.invDigit = bnpInvDigit; -BigInteger.prototype.isEven = bnpIsEven; -BigInteger.prototype.exp = bnpExp; - -// public -BigInteger.prototype.toString = bnToString; -BigInteger.prototype.negate = bnNegate; -BigInteger.prototype.abs = bnAbs; -BigInteger.prototype.compareTo = bnCompareTo; -BigInteger.prototype.bitLength = bnBitLength; -BigInteger.prototype.mod = bnMod; -BigInteger.prototype.modPowInt = bnModPowInt; - -// "constants" -BigInteger.ZERO = nbv(0); -BigInteger.ONE = nbv(1); From 773f9263dce8fd8e647253b18632b0045bbf2703 Mon Sep 17 00:00:00 2001 From: Arthur Britto Date: Thu, 11 Oct 2012 16:22:07 -0700 Subject: [PATCH 03/10] JS: Add BigInteger support to Amount. --- js/amount.js | 177 ++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 148 insertions(+), 29 deletions(-) diff --git a/js/amount.js b/js/amount.js index ead5aa74db..07a5780476 100644 --- a/js/amount.js +++ b/js/amount.js @@ -1,7 +1,10 @@ // Represent Newcoin amounts and currencies. +// - Numbers in hex are big-endian. +var utils = require('./utils.js'); +var jsbn = require('./jsbn.js'); -var utils = require("./utils.js"); +var BigInteger = jsbn.BigInteger; var UInt160 = function () { // Internal form: @@ -9,7 +12,13 @@ var UInt160 = function () { // XXX Should standardize on 'i' format or 20 format. }; -// Returns NaN on error. +UInt160.prototype.from_json = function (j) { + var u = new UInt160(); + + return u.parse_json(j); +}; + +// value === NaN on error. UInt160.prototype.parse_json = function (j) { // Canonicalize and validate @@ -49,7 +58,7 @@ UInt160.prototype.parse_json = function (j) { } } - return this.value; + return this; }; // Convert from internal form. @@ -109,19 +118,84 @@ var Amount = function () { // integer : XNS // { 'value' : ..., 'currency' : ..., 'issuer' : ...} - this.value = 0; - this.offset = 0; - this.is_native = false; - this.is_negative = false; + this.value = new BigInteger(); // NaN for bad value. Always positive for non-XNS. + this.offset = undefined; // For non-XNS. + this.is_native = true; // Default to XNS. Only valid if value is not NaN. + this.is_negative = undefined; // Undefined for XNS. this.currency = new Currency(); this.issuer = new UInt160(); }; -// Convert only value to JSON text. -Amount.prototype.to_text = function() { - // XXX Needs to work for native and non-native. - return this.is_negative ? -this.value : this.value; // XXX Use biginteger. +// YYY Might also check range. +Amount.prototype.is_valid = function() { + return NaN !== this.value; +} + +// Convert only value to JSON wire format. +Amount.prototype.to_text = function(allow_nan) { + if (NaN === this.value) { + // Never should happen. + return allow_nan ? NaN : "0"; + } + else if (this.is_native) { + if (this.value.compareTo(exports.consts.xns_max) > 0 || this.value.compareTo(exports.consts.xns_min) < 0) + { + // Never should happen. + return allow_nan ? NaN : "0"; + } + else + { + return this.value.toString(); + } + } + else if (this.value.equals(BigInteger.ZERO)) + { + return "0"; + } + else if (this.offset < -25 || mOffset > -5) + { + // Use e notation. + // XXX Clamp output. + + return (this.is_negative ? "-" : "") + this.value.toString() + "e" + this.offset; + } + else + { + var val = "000000000000000000000000000" + this.value.toString() + "00000000000000000000000"; + var pre = val.substring(0, this.offset + 43); + var post = val.substring(this.offset + 43); + var s_pre = val.match(/[1-9].*$/); // Everything but leading zeros. + var s_post = val.match(/0+$/); // Trailing zeros. + + + return (this.is_negative ? "-" : "") + + (null == s_pre ? "0" : s_pre[0]) + + "." + + post.substring(post.length - s_post.length); + } +}; + +Amount.prototype.canonicalize = function() { + if (NaN === this.value || !this.currency) { + // nothing + } + else if (this.value.equals(BigInteger.ZERO)) { + this.offset = -100; + this.is_negative = false; + } + else + { + while (this.value.compareTo(exports.consts.bi_man_min_value)) { + this.value.multiply(exports.consts.bi_10); + this.offset -= 1; + } + + while (this.value.compareTo(exports.consts.bi_man_max_value)) { + this.value.divide(exports.consts.bi_10); + this.offset += 1; + } + } }; Amount.prototype.to_json = function() { @@ -138,21 +212,35 @@ Amount.prototype.to_json = function() { } }; -// Parse a native value. +// Parse a XNS value from untrusted input. +// XXX Improvements: disallow leading zeros. Amount.prototype.parse_native = function(j) { - if ('integer' === typeof j) { - // XNS - this.value = j >= 0 ? j : -j; // XXX Use biginteger. - this.offset = 0; + var m; + + if ('string' === typeof j) + m = j.match(/^(\d+)(\.\d{1,6})?$/); + + if ('integer' === typeof j || null !== m) { + if ('integer' === typeof j || ("" === e[2])) { + this.value = new BigInteger(j); + } + else + { + // Decimal notation + var int_part = (new BigInteger(e[1])).multiply(exports.consts.xns_unit); + var fraction_part = (new BigInteger(e[2])).multiply(new BigInteger(Math.pow(10, exports.consts.xns_unit-e[2].length))); + + this.value = int_part.add(fraction_part); + } this.is_native = true; - this.is_negative = j < 0; + this.offset = undefined; + this.is_negative = undefined; + + if (this.value.compareTo(exports.consts.xns_max) > 0 || this.value.compareTo(exports.consts.xns_min) < 0) + { + this.value = NaN; + } } - else if ('string' === typeof j) { - this.value = j >= 0 ? j : -j; // XXX Use biginteger. - this.offset = 0; - this.is_native = true; - this.is_negative = j < 0; - } else { this.value = NaN; } @@ -161,16 +249,41 @@ Amount.prototype.parse_native = function(j) { // Parse a non-native value. Amount.prototype.parse_value = function(j) { if ('integer' === typeof j) { - this.value = j >= 0 ? j : -j; // XXX Use biginteger. + this.value = new BigInteger(j); this.offset = 0; this.is_native = false; this.is_negative = j < 0; + + this.canonicalize(); } else if ('string' === typeof j) { - this.value = j >= 0 ? j : -j; // XXX Use biginteger. - this.offset = 0; + var e = j.match(/^(-?\d+)e(\d+)/); + var d = j.match(/^(-?\d+)\.(\d+)/); + + if (null !== e) { + // e notation + + this.value = new BigInteger(e[1]); + this.offset = parseInt(e[2]); + } + else if (null !== d) { + // float notation + + this.value = (new BigInteger(e[1])).multiply((new BigInteger(exports.consts.bi_10)).pow(e[2].length)).add(new BigInteger(e[2])); + this.offset = -e[2].length; + } + else + { + // integer notation + + this.value = new BigInteger(j); + this.offset = 0; + } + this.is_native = false; - this.is_negative = j < 0; + this.is_negative = undefined; + + this.canonicalize(); } else { this.value = NaN; @@ -201,10 +314,16 @@ exports.consts = { 'address_one' : "iiiiiiiiiiiiiiiiiiiiBZbvjr", 'currency_xns' : 0, 'currency_one' : 1, - 'uint160_xns' : hexToString("0000000000000000000000000000000000000000"), - 'uint160_one' : hexToString("0000000000000000000000000000000000000001"), + 'uint160_xns' : utils.hexToString("0000000000000000000000000000000000000000"), + 'uint160_one' : utils.hexToString("0000000000000000000000000000000000000001"), 'hex_xns' : "0000000000000000000000000000000000000000", 'hex_one' : "0000000000000000000000000000000000000001", + 'xns_max' : new BigInteger("9000000000000000000"), // Json wire limit. + 'xns_min' : new BigInteger("-9000000000000000000"), // Json wire limit. + 'xns_unit' : new BigInteger('1000000'), + 'bi_man_min_value' : new BigInteger('1000000000000000'), + 'bi_man_max_value' : new BigInteger('9999999999999999'), + 'bi_10' : new BigInteger('10'), }; // vim:sw=2:sts=2:ts=8 From 1a5de6bd3d04a9f007f5ff5041f91290a82fff8a Mon Sep 17 00:00:00 2001 From: JoelKatz Date: Thu, 11 Oct 2012 16:22:29 -0700 Subject: [PATCH 04/10] operator<< must return a value --- src/SHAMapNodes.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/SHAMapNodes.cpp b/src/SHAMapNodes.cpp index ad1e27749f..859b47382e 100644 --- a/src/SHAMapNodes.cpp +++ b/src/SHAMapNodes.cpp @@ -518,6 +518,7 @@ std::ostream& operator<<(std::ostream& out, const SHAMapMissingNode& mn) out << "Missing/STA(" << mn.getNodeID() << ")"; else out << "Missing/" << mn.getNodeID(); + return out; } From 8ebceaa53e089eeefc4696ad99d30f5d16b4d1b6 Mon Sep 17 00:00:00 2001 From: Arthur Britto Date: Thu, 11 Oct 2012 16:25:01 -0700 Subject: [PATCH 05/10] UT: Initial check in unit tests for amount.js. --- js/amount.js | 2 +- test/amount-test.js | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 test/amount-test.js diff --git a/js/amount.js b/js/amount.js index 07a5780476..49a60c0739 100644 --- a/js/amount.js +++ b/js/amount.js @@ -12,7 +12,7 @@ var UInt160 = function () { // XXX Should standardize on 'i' format or 20 format. }; -UInt160.prototype.from_json = function (j) { +UInt160.from_json = function (j) { var u = new UInt160(); return u.parse_json(j); diff --git a/test/amount-test.js b/test/amount-test.js new file mode 100644 index 0000000000..3a7e656a74 --- /dev/null +++ b/test/amount-test.js @@ -0,0 +1,13 @@ +var buster = require("buster"); + +var amount = require("../js/amount.js"); + +buster.testCase("Amount", { + "UInt160" : { + "Parse 0" : function () { + buster.assert.equals(0, amount.UInt160.from_json("0").value); + }, + } +}); + +// vim:sw=2:sts=2:ts=8 From 7fc13e35288eab9ad20a989a0f74ee6214bc1125 Mon Sep 17 00:00:00 2001 From: Arthur Britto Date: Thu, 11 Oct 2012 20:07:41 -0700 Subject: [PATCH 06/10] Clean up getJson() for STAmount. --- src/Amount.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/Amount.cpp b/src/Amount.cpp index 78cde08d5f..672d67505f 100644 --- a/src/Amount.cpp +++ b/src/Amount.cpp @@ -1131,15 +1131,13 @@ Json::Value STAmount::getJson(int) const { Json::Value elem(Json::objectValue); - // This is a hack, many places don't specify a currency. STAmount is used just as a value. if (!mIsNative) { + // It is an error for currency or issuer not to be specified for valid json. + elem["value"] = getText(); elem["currency"] = getHumanCurrency(); - - if (!mIssuer.isZero()) - elem["issuer"] = NewcoinAddress::createHumanAccountID(mIssuer); - + elem["issuer"] = NewcoinAddress::createHumanAccountID(mIssuer); } else { From f419dcc77b4e55b0383bb3d57c0eb8916d2ba6ad Mon Sep 17 00:00:00 2001 From: Arthur Britto Date: Thu, 11 Oct 2012 20:08:08 -0700 Subject: [PATCH 07/10] JS: fixes and tests from amounts. --- js/amount.js | 118 +++++++++++++++++++++++++++++--------------- test/amount-test.js | 21 ++++++++ 2 files changed, 100 insertions(+), 39 deletions(-) diff --git a/js/amount.js b/js/amount.js index 49a60c0739..d450d0fe35 100644 --- a/js/amount.js +++ b/js/amount.js @@ -13,9 +13,7 @@ var UInt160 = function () { }; UInt160.from_json = function (j) { - var u = new UInt160(); - - return u.parse_json(j); + return (new UInt160()).parse_json(j); }; // value === NaN on error. @@ -71,7 +69,7 @@ UInt160.prototype.to_json = function () { { return exports.consts.hex_one; } - else if (20 === this.value.length) { + else if ('string' === typeof this.value && 20 === this.value.length) { return utils.stringToHex(this.value); } else @@ -90,7 +88,11 @@ var Currency = function () { // XXX Should support hex, C++ doesn't currently allow it. } -// Returns NaN on error. +Currency.from_json = function (j) { + return (new Currency()).parse_json(j); +}; + +// this.value === NaN on error. Currency.prototype.parse_json = function (j) { if ("" === j || "0" === j || "XNS" === j) { this.value = 0; @@ -102,15 +104,15 @@ Currency.prototype.parse_json = function (j) { this.value = j; } - return this.value; + return this; }; Currency.prototype.to_json = function () { - return this.value ? this.value : 'XNS'; + return this.value ? this.value : "XNS"; }; Currency.prototype.to_human = function() { - return this.value ? this.value : 'XNS'; + return this.value ? this.value : "XNS"; }; var Amount = function () { @@ -127,6 +129,10 @@ var Amount = function () { this.issuer = new UInt160(); }; +Amount.from_json = function (j) { + return (new Amount()).parse_json(j); +}; + // YYY Might also check range. Amount.prototype.is_valid = function() { return NaN !== this.value; @@ -153,7 +159,7 @@ Amount.prototype.to_text = function(allow_nan) { { return "0"; } - else if (this.offset < -25 || mOffset > -5) + else if (this.offset < -25 || this.offset > -5) { // Use e notation. // XXX Clamp output. @@ -165,14 +171,12 @@ Amount.prototype.to_text = function(allow_nan) { var val = "000000000000000000000000000" + this.value.toString() + "00000000000000000000000"; var pre = val.substring(0, this.offset + 43); var post = val.substring(this.offset + 43); - var s_pre = val.match(/[1-9].*$/); // Everything but leading zeros. - var s_post = val.match(/0+$/); // Trailing zeros. - + var s_pre = pre.match(/[1-9].*$/); // Everything but leading zeros. + var s_post = post.match(/[1-9]0*$/); // Last non-zero plus trailing zeros. return (this.is_negative ? "-" : "") - + (null == s_pre ? "0" : s_pre[0]) - + "." - + post.substring(post.length - s_post.length); + + (s_pre ? s_pre[0] : "0") + + (s_post ? "." + post.substring(0, 1+post.length-s_post[0].length) : ""); } }; @@ -186,13 +190,13 @@ Amount.prototype.canonicalize = function() { } else { - while (this.value.compareTo(exports.consts.bi_man_min_value)) { - this.value.multiply(exports.consts.bi_10); + while (this.value.compareTo(exports.consts.bi_man_min_value) < 0) { + this.value = this.value.multiply(exports.consts.bi_10); this.offset -= 1; } - while (this.value.compareTo(exports.consts.bi_man_max_value)) { - this.value.divide(exports.consts.bi_10); + while (this.value.compareTo(exports.consts.bi_man_max_value) > 0) { + this.value = this.value.divide(exports.consts.bi_10); this.offset += 1; } } @@ -212,7 +216,17 @@ Amount.prototype.to_json = function() { } }; +Amount.prototype.to_text_full = function() { + return this.value === NaN + ? NaN + : this.is_native + ? this.to_text() + "/XNS" + : this.to_text() + "/" + this.currency.to_json() + "/" + this.issuer.to_json(); +}; + // Parse a XNS value from untrusted input. +// - integer = raw units +// - float = with precision 6 // XXX Improvements: disallow leading zeros. Amount.prototype.parse_native = function(j) { var m; @@ -220,18 +234,21 @@ Amount.prototype.parse_native = function(j) { if ('string' === typeof j) m = j.match(/^(\d+)(\.\d{1,6})?$/); - if ('integer' === typeof j || null !== m) { - if ('integer' === typeof j || ("" === e[2])) { - this.value = new BigInteger(j); - } - else - { - // Decimal notation - var int_part = (new BigInteger(e[1])).multiply(exports.consts.xns_unit); - var fraction_part = (new BigInteger(e[2])).multiply(new BigInteger(Math.pow(10, exports.consts.xns_unit-e[2].length))); + if (null !== m) { + if (undefined === m[2]) { + // Integer notation - this.value = int_part.add(fraction_part); + this.value = new BigInteger(m[1]); } + else { + // Decimal notation + + var int_part = (new BigInteger(m[1])).multiply(exports.consts.xns_unit); + var fraction_part = (new BigInteger(m[2])).multiply(new BigInteger(String(Math.pow(10, 1+exports.consts.xns_precision-m[2].length)))); + + this.value = int_part.add(fraction_part); + } + this.is_native = true; this.offset = undefined; this.is_negative = undefined; @@ -244,11 +261,13 @@ Amount.prototype.parse_native = function(j) { else { this.value = NaN; } + + return this; }; // Parse a non-native value. Amount.prototype.parse_value = function(j) { - if ('integer' === typeof j) { + if ('number' === typeof j) { this.value = new BigInteger(j); this.offset = 0; this.is_native = false; @@ -260,17 +279,19 @@ Amount.prototype.parse_value = function(j) { var e = j.match(/^(-?\d+)e(\d+)/); var d = j.match(/^(-?\d+)\.(\d+)/); - if (null !== e) { + if (e) { // e notation this.value = new BigInteger(e[1]); this.offset = parseInt(e[2]); } - else if (null !== d) { + else if (d) { // float notation - this.value = (new BigInteger(e[1])).multiply((new BigInteger(exports.consts.bi_10)).pow(e[2].length)).add(new BigInteger(e[2])); - this.offset = -e[2].length; + var integer = new BigInteger(d[1]); + var fraction = new BigInteger(d[2]); + this.value = integer.multiply(exports.consts.bi_10.clone().pow(d[2].length)).add(fraction); + this.offset = -d[2].length; } else { @@ -288,21 +309,39 @@ Amount.prototype.parse_value = function(j) { else { this.value = NaN; } + + return this; }; // <-> j Amount.prototype.parse_json = function(j) { - if ('object' === typeof j && j.currency) { + if ('string' === typeof j) { + // .../.../... notation is not a wire format. But allowed for easier testing. + var m = j.match(/^(.+)\/(...)\/(.+)$/); - this.parse_value(j); + if (m) { + this.parse_value(m[1]); + this.currency = Currency.from_json(m[2]); + this.issuer = UInt160.from_json(m[3]); + } + else { + this.parse_native(j); + this.currency = new Currency(); + this.issuer = new UInt160(); + } + } + else if ('object' === typeof j && j.currency) { + // Never XNS. + + this.parse_value(j.value); this.currency.parse_json(j.currency); this.issuer.parse_json(j.issuer); } else { - this.parse_native(j); - this.currency = 0; - this.issuer = 0; + this.value = NaN; } + + return this; }; exports.Amount = Amount; @@ -321,6 +360,7 @@ exports.consts = { 'xns_max' : new BigInteger("9000000000000000000"), // Json wire limit. 'xns_min' : new BigInteger("-9000000000000000000"), // Json wire limit. 'xns_unit' : new BigInteger('1000000'), + 'xns_precision' : 6, 'bi_man_min_value' : new BigInteger('1000000000000000'), 'bi_man_max_value' : new BigInteger('9999999999999999'), 'bi_10' : new BigInteger('10'), diff --git a/test/amount-test.js b/test/amount-test.js index 3a7e656a74..b9addaac10 100644 --- a/test/amount-test.js +++ b/test/amount-test.js @@ -7,6 +7,27 @@ buster.testCase("Amount", { "Parse 0" : function () { buster.assert.equals(0, amount.UInt160.from_json("0").value); }, + "Parse 0 export" : function () { + buster.assert.equals(amount.consts.hex_xns, amount.UInt160.from_json("0").to_json()); + }, + "Parse native 123" : function () { + buster.assert.equals("123/XNS", amount.Amount.from_json("123").to_text_full()); + }, + "Parse native 12.3" : function () { + buster.assert.equals("12300000/XNS", amount.Amount.from_json("12.3").to_text_full()); + }, + "Parse 123./USD/iHb9CJAWyB4ij91VRWn96DkukG4bwdtyTh" : function () { + buster.assert.equals("123/USD/iHb9CJAWyB4ij91VRWn96DkukG4bwdtyTh", amount.Amount.from_json("123./USD/iHb9CJAWyB4ij91VRWn96DkukG4bwdtyTh").to_text_full()); + }, + "Parse 12300/USD/iHb9CJAWyB4ij91VRWn96DkukG4bwdtyTh" : function () { + buster.assert.equals("12300/USD/iHb9CJAWyB4ij91VRWn96DkukG4bwdtyTh", amount.Amount.from_json("12300/USD/iHb9CJAWyB4ij91VRWn96DkukG4bwdtyTh").to_text_full()); + }, + "Parse 12.3/USD/iHb9CJAWyB4ij91VRWn96DkukG4bwdtyTh" : function () { + buster.assert.equals("12.3/USD/iHb9CJAWyB4ij91VRWn96DkukG4bwdtyTh", amount.Amount.from_json("12.3/USD/iHb9CJAWyB4ij91VRWn96DkukG4bwdtyTh").to_text_full()); + }, + "Parse 1.2300/USD/iHb9CJAWyB4ij91VRWn96DkukG4bwdtyTh" : function () { + buster.assert.equals("1.23/USD/iHb9CJAWyB4ij91VRWn96DkukG4bwdtyTh", amount.Amount.from_json("1.2300/USD/iHb9CJAWyB4ij91VRWn96DkukG4bwdtyTh").to_text_full()); + }, } }); From bb83bcf0d12bcb2441d2ba42fa67014558475a15 Mon Sep 17 00:00:00 2001 From: Arthur Britto Date: Thu, 11 Oct 2012 21:41:07 -0700 Subject: [PATCH 08/10] Revise remote.js and unit tests to use new amount.js. --- js/remote.js | 90 ++++++++++++++++++++++++++++++----------- test/standalone-test.js | 21 +++++----- 2 files changed, 79 insertions(+), 32 deletions(-) diff --git a/js/remote.js b/js/remote.js index 3bf421d697..719bc73470 100644 --- a/js/remote.js +++ b/js/remote.js @@ -7,10 +7,15 @@ // YYY A better model might be to allow requesting a target state: keep connected or not. // -var util = require('util'); +// Node +var util = require('util'); +// npm var WebSocket = require('ws'); +var amount = require('./amount.js'); +var Amount = amount.Amount; + // --> trusted: truthy, if remote is trusted var Remote = function (trusted, websocket_ip, websocket_port, config, trace) { this.trusted = trusted; @@ -57,10 +62,10 @@ var flags = { // XXX This needs to be determined from the network. var fees = { - 'default' : 100, - 'account_create' : 1000, - 'nickname_create' : 1000, - 'offer' : 100, + 'default' : Amount.from_json("100"), + 'account_create' : Amount.from_json("1000"), + 'nickname_create' : Amount.from_json("1000"), + 'offer' : Amount.from_json("100"), }; Remote.prototype.connect_helper = function () { @@ -383,40 +388,79 @@ Remote.prototype.dirty_account_root = function (account) { // Transactions // -Remote.prototype.ripple_line_set = function (secret, src, dst, amount, onDone) { +Remote.prototype.offer_create = function (secret, src, taker_pays, taker_gets, expiration, onDone) { var secret = this.config.accounts[src] ? this.config.accounts[src].secret : secret; var src_account = this.config.accounts[src] ? this.config.accounts[src].account : src; - var dst_account = this.config.accounts[dst] ? this.config.accounts[dst].account : dst; + + var transaction = { + 'TransactionType' : 'OfferCreate', + 'Account' : src_account, + 'Fee' : fees.offer.to_json(), + 'TakerPays' : taker_pays.to_json(), + 'TakerGets' : taker_gets.to_json(), + }; + + if (expiration) + transaction.Expiration = expiration; this.submit_seq( { - 'transaction' : { - 'TransactionType' : 'CreditSet', - 'Account' : src_account, - 'Destination' : dst_account, - 'Fee' : create ? fees.account_create : fees['default'], - 'Amount' : amount, - }, + 'transaction' : transaction, 'secret' : secret, }, function () { }, onDone); }; -Remote.prototype.send_xns = function (secret, src, dst, amount, create, onDone) { +Remote.prototype.ripple_line_set = function (secret, src, limit, quaility_in, quality_out, onDone) { + var secret = this.config.accounts[src] ? this.config.accounts[src].secret : secret; + var src_account = this.config.accounts[src] ? this.config.accounts[src].account : src; + + var transaction = { + 'TransactionType' : 'CreditSet', + 'Account' : src_account, + 'Fee' : fees['default'].to_json(), + }; + + if (limit) + transaction.LimitAmount = limit.to_json(); + + if (quaility_in) + transaction.QualityIn = quaility_in; + + if (quaility_out) + transaction.QualityOut = quaility_out; + + this.submit_seq( + { + 'transaction' : transaction, + 'secret' : secret, + }, function () { + }, onDone); +}; + +// --> create: is only valid if destination gets XNS. +Remote.prototype.send = function (secret, src, dst, deliver_amount, send_max, create, onDone) { var secret = this.config.accounts[src] ? this.config.accounts[src].secret : secret; var src_account = this.config.accounts[src] ? this.config.accounts[src].account : src; var dst_account = this.config.accounts[dst] ? this.config.accounts[dst].account : dst; + var transaction = { + 'TransactionType' : 'Payment', + 'Account' : src_account, + 'Fee' : (create ? fees.account_create : fees['default']).to_json(), + 'Destination' : dst_account, + 'Amount' : deliver_amount.to_json(), + }; + + if (create) + transaction.Flags = flags.tfCreateAccount; + + if (send_max) + transaction.SendMax = send_max.to_json(); + this.submit_seq( { - 'transaction' : { - 'TransactionType' : 'Payment', - 'Account' : src_account, - 'Destination' : dst_account, - 'Fee' : create ? fees.account_create : fees['default'], - 'Flags' : create ? flags.tfCreateAccount : 0, - 'Amount' : amount, - }, + 'transaction' : transaction, 'secret' : secret, }, function () { }, onDone); diff --git a/test/standalone-test.js b/test/standalone-test.js index f8b8985956..1cf0d2a6da 100644 --- a/test/standalone-test.js +++ b/test/standalone-test.js @@ -2,8 +2,11 @@ var buster = require("buster"); var config = require("./config.js"); var server = require("./server.js"); +var amount = require("../js/amount.js"); var remote = require("../js/remote.js"); +var Amount = amount.Amount; + // How long to wait for server to start. var serverDelay = 1500; @@ -113,8 +116,8 @@ buster.testCase("Websocket commands", { alpha.request_ledger_entry({ 'ledger_closed' : r.ledger_closed, - 'type' : 'account_root', - 'account_root' : 'iHb9CJAWyB4ij91VRWn96DkukG4bwdtyTh' + 'type' : 'account_root', + 'account_root' : 'iHb9CJAWyB4ij91VRWn96DkukG4bwdtyTh' } , function (r) { // console.log("account_root: %s", JSON.stringify(r)); @@ -133,8 +136,8 @@ buster.testCase("Websocket commands", { alpha.request_ledger_entry({ 'ledger_closed' : r.ledger_closed, - 'type' : 'account_root', - 'account_root' : 'foobar' + 'type' : 'account_root', + 'account_root' : 'foobar' } , function (r) { // console.log("account_root: %s", JSON.stringify(r)); @@ -153,8 +156,8 @@ buster.testCase("Websocket commands", { alpha.request_ledger_entry({ 'ledger_closed' : r.ledger_closed, - 'type' : 'account_root', - 'account_root' : 'iG1QQv2nh2gi7RCZ1P8YYcBUKCCN633jCn' + 'type' : 'account_root', + 'account_root' : 'iG1QQv2nh2gi7RCZ1P8YYcBUKCCN633jCn' }, function (r) { console.log("account_root: %s", JSON.stringify(r)); @@ -173,8 +176,8 @@ buster.testCase("Websocket commands", { alpha.request_ledger_entry({ 'ledger_closed' : r.ledger_closed, - 'type' : 'account_root', - 'index' : "2B6AC232AA4C4BE41BF49D2459FA4A0347E1B543A4C92FCEE0821C0201E2E9A8", + 'type' : 'account_root', + 'index' : "2B6AC232AA4C4BE41BF49D2459FA4A0347E1B543A4C92FCEE0821C0201E2E9A8", } , function (r) { console.log("node: %s", JSON.stringify(r)); @@ -186,7 +189,7 @@ buster.testCase("Websocket commands", { 'create account' : function (done) { - alpha.send_xns(undefined, 'root', 'alice', 10000, true, function (r) { + alpha.send(undefined, 'root', 'alice', Amount.from_json("10000"), undefined, 'CREATE', function (r) { console.log(r); buster.refute(r.error); From 51c5de61d93022d964b3ff9b9ebd88ee5143cee8 Mon Sep 17 00:00:00 2001 From: JoelKatz Date: Fri, 12 Oct 2012 03:59:25 -0700 Subject: [PATCH 09/10] TMGetObjectByHash design changes. --- src/Peer.cpp | 38 +++++++++++++++++++++----------------- src/Peer.h | 2 -- src/newcoin.proto | 44 ++++++++++++++++++++------------------------ 3 files changed, 41 insertions(+), 43 deletions(-) diff --git a/src/Peer.cpp b/src/Peer.cpp index 585b720d7f..45ffc4b375 100644 --- a/src/Peer.cpp +++ b/src/Peer.cpp @@ -540,7 +540,7 @@ void Peer::processReadBuffer() break; #endif - case newcoin::mtGET_OBJECT: + case newcoin::mtGET_OBJECTS: { newcoin::TMGetObjectByHash msg; if (msg.ParseFromArray(&mReadbuf[HEADER_SIZE], mReadbuf.size() - HEADER_SIZE)) @@ -549,15 +549,6 @@ void Peer::processReadBuffer() } break; - case newcoin::mtOBJECT: - { - newcoin::TMObjectByHash msg; - if (msg.ParseFromArray(&mReadbuf[HEADER_SIZE], mReadbuf.size() - HEADER_SIZE)) - recvObjectByHash(msg); - else std::cerr << "parse error: " << type << std::endl; - } - break; - default: std::cerr << "Unknown Msg: " << type << std::endl; std::cerr << strHex(&mReadbuf[0], mReadbuf.size()); @@ -872,16 +863,29 @@ void Peer::recvPeers(newcoin::TMPeers& packet) } } -void Peer::recvIndexedObject(newcoin::TMIndexedObject& packet) -{ -} - void Peer::recvGetObjectByHash(newcoin::TMGetObjectByHash& packet) { -} + if (packet.query()) + { // this is a query + newcoin::TMGetObjectByHash reply; -void Peer::recvObjectByHash(newcoin::TMObjectByHash& packet) -{ + reply.clear_query(); + if (packet.has_seq()) + reply.set_seq(packet.seq()); + reply.set_type(packet.type()); + if (packet.has_ledgerhash()) + reply.set_ledgerhash(packet.ledgerhash()); + + for (unsigned i = 0; i < packet.objects_size(); ++i) + { + const newcoin::TMIndexedObject& obj = packet.objects(i); + // WRITEME + } + } + else + { // this is a reply + // WRITEME + } } void Peer::recvPing(newcoin::TMPing& packet) diff --git a/src/Peer.h b/src/Peer.h index 0b28a1dc9b..301089caec 100644 --- a/src/Peer.h +++ b/src/Peer.h @@ -102,9 +102,7 @@ protected: void recvGetContacts(newcoin::TMGetContacts& packet); void recvGetPeers(newcoin::TMGetPeers& packet); void recvPeers(newcoin::TMPeers& packet); - void recvIndexedObject(newcoin::TMIndexedObject& packet); void recvGetObjectByHash(newcoin::TMGetObjectByHash& packet); - void recvObjectByHash(newcoin::TMObjectByHash& packet); void recvPing(newcoin::TMPing& packet); void recvErrorMessage(newcoin::TMErrorMsg& packet); void recvSearchTransaction(newcoin::TMSearchTransaction& packet); diff --git a/src/newcoin.proto b/src/newcoin.proto index cd33de9f0f..743ab55eaa 100644 --- a/src/newcoin.proto +++ b/src/newcoin.proto @@ -28,8 +28,7 @@ enum MessageType { // data replication and synchronization mtGET_VALIDATIONS = 40; mtVALIDATION = 41; - mtGET_OBJECT = 42; - mtOBJECT = 43; + mtGET_OBJECTS = 42; } @@ -193,34 +192,31 @@ message TMAccount{ message TMIndexedObject { - enum ObjectType { - otTRANSACTION = 1; - otTRANSACTION_NODE = 2; // a node in a transaction tree - otTRANSACTION_LEAF = 3; // a leaf in a transaction tree - otACCOUNT = 4; // a single account state (with balance/sequence) - otACCOUNT_NODE = 5; // a node in an account state tree - otACCOUNT_LEAF = 6; // a leaf in an account state tree - otLEDGER = 7; - } - - required bytes hash = 1; - required ObjectType type = 2; + optional bytes hash = 1; + optional bytes nodeID = 2; + optional bytes index = 3; + optional bytes data = 4; } - - message TMGetObjectByHash { - required TMIndexedObject object = 1; - optional uint32 seq = 2; // used to match replies to queries + enum ObjectType { + otUNKNOWN = 0; + otLEDGER = 1; + otTRANSACTION = 2; + otTRANSACTION_NODE = 3; + otSTATE_NODE = 4; + otCAS_OBJECT = 5; + } + + required ObjectType type = 1; + required bool query = 2; // is this a query or a reply? + optional uint32 seq = 3; // used to match replies to queries + optional bytes ledgerHash = 4; // the hash of the ledger these queries are for + optional bool fat = 5; // return related nodes + repeated TMIndexedObject objects = 6; // the specific objects requested } -message TMObjectByHash -{ - optional TMIndexedObject object = 1; // present unless no object found - optional bytes data = 2; // present unless no object found - optional uint32 seq = 3; // matches seq from query -} message TMLedgerNode { required bytes nodedata = 1; From 92f004c47f36851284b42547ef2483271797c1f3 Mon Sep 17 00:00:00 2001 From: JoelKatz Date: Fri, 12 Oct 2012 06:35:16 -0700 Subject: [PATCH 10/10] A minimal, but serviceable, implemention of replying to TMGetObjectByHash queries. --- src/Peer.cpp | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/Peer.cpp b/src/Peer.cpp index 45ffc4b375..5e6875c807 100644 --- a/src/Peer.cpp +++ b/src/Peer.cpp @@ -876,11 +876,27 @@ void Peer::recvGetObjectByHash(newcoin::TMGetObjectByHash& packet) if (packet.has_ledgerhash()) reply.set_ledgerhash(packet.ledgerhash()); + // This is a very minimal implementation for (unsigned i = 0; i < packet.objects_size(); ++i) { + uint256 hash; const newcoin::TMIndexedObject& obj = packet.objects(i); - // WRITEME + if (obj.has_hash() && (obj.hash().size() == (256/8))) + { + memcpy(hash.begin(), obj.hash().data(), 256 / 8); + HashedObject::pointer hObj = theApp->getHashedObjectStore().retrieve(hash); + if (hObj) + { + newcoin::TMIndexedObject& newObj = *reply.add_objects(); + newObj.set_hash(hash.begin(), hash.size()); + newObj.set_data(&hObj->getData().front(), hObj->getData().size()); + if (obj.has_nodeid()) + newObj.set_index(obj.nodeid()); + } + } } + cLog(lsDEBUG) << "GetObjByHash query: had " << reply.objects_size() << " of " << packet.objects_size(); + sendPacket(boost::make_shared(packet, newcoin::mtGET_OBJECTS)); } else { // this is a reply