From d025b4a0c3a98a6de27a1bee9573c85347bcd66b Mon Sep 17 00:00:00 2001 From: Chris Clark Date: Thu, 8 Jan 2015 17:33:37 -0800 Subject: [PATCH] [TASK] Refactor to use bignumber.js --- npm-shrinkwrap.json | 5 + package.json | 1 + src/js/jsbn/jsbn.js | 1210 ----------------------------- src/js/ripple/amount.js | 752 ++++++------------ src/js/ripple/base.js | 186 +++-- src/js/ripple/bignumber.js | 33 + src/js/ripple/currency.js | 2 +- src/js/ripple/index.js | 1 - src/js/ripple/seed.js | 5 +- src/js/ripple/serializedobject.js | 38 +- src/js/ripple/serializedtypes.js | 167 ++-- src/js/ripple/uint.js | 72 +- src/js/ripple/uint128.js | 1 - src/js/ripple/uint160.js | 7 +- src/js/ripple/uint256.js | 1 - src/js/ripple/utils.js | 22 +- test/amount-test.js | 17 +- test/orderbook-test.js | 4 +- test/serializedtypes-test.js | 14 +- test/uint-test.js | 4 +- 20 files changed, 562 insertions(+), 1980 deletions(-) delete mode 100644 src/js/jsbn/jsbn.js create mode 100644 src/js/ripple/bignumber.js diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 8d0de13b75..6563b4a6be 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -7,6 +7,11 @@ "from": "async@>=0.8.0 <0.9.0", "resolved": "https://registry.npmjs.org/async/-/async-0.8.0.tgz" }, + "bignumber.js": { + "version": "2.0.0", + "from": "bignumber.js@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-2.0.0.tgz" + }, "extend": { "version": "1.2.1", "from": "extend@>=1.2.1 <1.3.0", diff --git a/package.json b/package.json index 3ea87c526a..bd540e18c1 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ }, "dependencies": { "async": "~0.8.0", + "bignumber.js": "^2.0.0", "extend": "~1.2.1", "lodash": "^2.4.1", "lru-cache": "~2.5.0", diff --git a/src/js/jsbn/jsbn.js b/src/js/jsbn/jsbn.js deleted file mode 100644 index 98b80fc3cc..0000000000 --- a/src/js/jsbn/jsbn.js +++ /dev/null @@ -1,1210 +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 && '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+this.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; } - -// (public) return value as integer -function bnIntValue() { - if(this.s < 0) { - if(this.t == 1) return this[0]-this.DV; - else if(this.t == 0) return -1; - } - else if(this.t == 1) return this[0]; - else if(this.t == 0) return 0; - // assumes 16 < DB < 32 - return ((this[1]&((1<<(32-this.DB))-1))<>24; } - -// (public) return value as short (assumes DB>=16) -function bnShortValue() { return (this.t==0)?this.s:(this[0]<<16)>>16; } - -// (protected) return x s.t. r^x < DV -function bnpChunkSize(r) { return Math.floor(Math.LN2*this.DB/Math.log(r)); } - -// (public) 0 if this == 0, 1 if this > 0 -function bnSigNum() { - if(this.s < 0) return -1; - else if(this.t <= 0 || (this.t == 1 && this[0] <= 0)) return 0; - else return 1; -} - -// (protected) convert to radix string -function bnpToRadix(b) { - if(b == null) b = 10; - if(this.signum() == 0 || b < 2 || b > 36) return "0"; - var cs = this.chunkSize(b); - var a = Math.pow(b,cs); - var d = nbv(a), y = nbi(), z = nbi(), r = ""; - this.divRemTo(d,y,z); - while(y.signum() > 0) { - r = (a+z.intValue()).toString(b).substr(1) + r; - y.divRemTo(d,y,z); - } - return z.intValue().toString(b) + r; -} - -// (protected) convert from radix string -function bnpFromRadix(s,b) { - this.fromInt(0); - if(b == null) b = 10; - var cs = this.chunkSize(b); - var d = Math.pow(b,cs), mi = false, j = 0, w = 0; - for(var i = 0; i < s.length; ++i) { - var x = intAt(s,i); - if(x < 0) { - if(s.charAt(i) == "-" && this.signum() == 0) mi = true; - continue; - } - w = b*w+x; - if(++j >= cs) { - this.dMultiply(d); - this.dAddOffset(w,0); - j = 0; - w = 0; - } - } - if(j > 0) { - this.dMultiply(Math.pow(b,j)); - this.dAddOffset(w,0); - } - if(mi) BigInteger.ZERO.subTo(this,this); -} - -// (protected) alternate constructor -function bnpFromNumber(a,b,c) { - if("number" == typeof b) { - // new BigInteger(int,int,RNG) - if(a < 2) this.fromInt(1); - else { - this.fromNumber(a,c); - if(!this.testBit(a-1)) // force MSB set - this.bitwiseTo(BigInteger.ONE.shiftLeft(a-1),op_or,this); - if(this.isEven()) this.dAddOffset(1,0); // force odd - while(!this.isProbablePrime(b)) { - this.dAddOffset(2,0); - if(this.bitLength() > a) this.subTo(BigInteger.ONE.shiftLeft(a-1),this); - } - } - } - else { - // new BigInteger(int,RNG) - var x = new Array(), t = a&7; - x.length = (a>>3)+1; - b.nextBytes(x); - if(t > 0) x[0] &= ((1< 0) { - if(p < this.DB && (d = this[i]>>p) != (this.s&this.DM)>>p) - r[k++] = d|(this.s<<(this.DB-p)); - while(i >= 0) { - if(p < 8) { - d = (this[i]&((1<>(p+=this.DB-8); - } - else { - d = (this[i]>>(p-=8))&0xff; - if(p <= 0) { p += this.DB; --i; } - } - if((d&0x80) != 0) d |= -256; - if(k == 0 && (this.s&0x80) != (d&0x80)) ++k; - if(k > 0 || d != this.s) r[k++] = d; - } - } - return r; -} - -function bnEquals(a) { return(this.compareTo(a)==0); } -function bnMin(a) { return(this.compareTo(a)<0)?this:a; } -function bnMax(a) { return(this.compareTo(a)>0)?this:a; } - -// (protected) r = this op a (bitwise) -function bnpBitwiseTo(a,op,r) { - var i, f, m = Math.min(a.t,this.t); - for(i = 0; i < m; ++i) r[i] = op(this[i],a[i]); - if(a.t < this.t) { - f = a.s&this.DM; - for(i = m; i < this.t; ++i) r[i] = op(this[i],f); - r.t = this.t; - } - else { - f = this.s&this.DM; - for(i = m; i < a.t; ++i) r[i] = op(f,a[i]); - r.t = a.t; - } - r.s = op(this.s,a.s); - r.clamp(); -} - -// (public) this & a -function op_and(x,y) { return x&y; } -function bnAnd(a) { var r = nbi(); this.bitwiseTo(a,op_and,r); return r; } - -// (public) this | a -function op_or(x,y) { return x|y; } -function bnOr(a) { var r = nbi(); this.bitwiseTo(a,op_or,r); return r; } - -// (public) this ^ a -function op_xor(x,y) { return x^y; } -function bnXor(a) { var r = nbi(); this.bitwiseTo(a,op_xor,r); return r; } - -// (public) this & ~a -function op_andnot(x,y) { return x&~y; } -function bnAndNot(a) { var r = nbi(); this.bitwiseTo(a,op_andnot,r); return r; } - -// (public) ~this -function bnNot() { - var r = nbi(); - for(var i = 0; i < this.t; ++i) r[i] = this.DM&~this[i]; - r.t = this.t; - r.s = ~this.s; - return r; -} - -// (public) this << n -function bnShiftLeft(n) { - var r = nbi(); - if(n < 0) this.rShiftTo(-n,r); else this.lShiftTo(n,r); - return r; -} - -// (public) this >> n -function bnShiftRight(n) { - var r = nbi(); - if(n < 0) this.lShiftTo(-n,r); else this.rShiftTo(n,r); - return r; -} - -// return index of lowest 1-bit in x, x < 2^31 -function lbit(x) { - if(x == 0) return -1; - var r = 0; - if((x&0xffff) == 0) { x >>= 16; r += 16; } - if((x&0xff) == 0) { x >>= 8; r += 8; } - if((x&0xf) == 0) { x >>= 4; r += 4; } - if((x&3) == 0) { x >>= 2; r += 2; } - if((x&1) == 0) ++r; - return r; -} - -// (public) returns index of lowest 1-bit (or -1 if none) -function bnGetLowestSetBit() { - for(var i = 0; i < this.t; ++i) - if(this[i] != 0) return i*this.DB+lbit(this[i]); - if(this.s < 0) return this.t*this.DB; - return -1; -} - -// return number of 1 bits in x -function cbit(x) { - var r = 0; - while(x != 0) { x &= x-1; ++r; } - return r; -} - -// (public) return number of set bits -function bnBitCount() { - var r = 0, x = this.s&this.DM; - for(var i = 0; i < this.t; ++i) r += cbit(this[i]^x); - return r; -} - -// (public) true iff nth bit is set -function bnTestBit(n) { - var j = Math.floor(n/this.DB); - if(j >= this.t) return(this.s!=0); - return((this[j]&(1<<(n%this.DB)))!=0); -} - -// (protected) this op (1<>= 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 > 0) r[i++] = c; - else if(c < -1) r[i++] = this.DV+c; - r.t = i; - r.clamp(); -} - -// (public) this + a -function bnAdd(a) { var r = nbi(); this.addTo(a,r); return r; } - -// (public) this - a -function bnSubtract(a) { var r = nbi(); this.subTo(a,r); return r; } - -// (public) this * a -function bnMultiply(a) { var r = nbi(); this.multiplyTo(a,r); return r; } - -// (public) this^2 -function bnSquare() { var r = nbi(); this.squareTo(r); return r; } - -// (public) this / a -function bnDivide(a) { var r = nbi(); this.divRemTo(a,r,null); return r; } - -// (public) this % a -function bnRemainder(a) { var r = nbi(); this.divRemTo(a,null,r); return r; } - -// (public) [this/a,this%a] -function bnDivideAndRemainder(a) { - var q = nbi(), r = nbi(); - this.divRemTo(a,q,r); - return new Array(q,r); -} - -// (protected) this *= n, this >= 0, 1 < n < DV -function bnpDMultiply(n) { - this[this.t] = this.am(0,n-1,this,0,0,this.t); - ++this.t; - this.clamp(); -} - -// (protected) this += n << w words, this >= 0 -function bnpDAddOffset(n,w) { - if(n == 0) return; - while(this.t <= w) this[this.t++] = 0; - this[w] += n; - while(this[w] >= this.DV) { - this[w] -= this.DV; - if(++w >= this.t) this[this.t++] = 0; - ++this[w]; - } -} - -// A "null" reducer -function NullExp() {} -function nNop(x) { return x; } -function nMulTo(x,y,r) { x.multiplyTo(y,r); } -function nSqrTo(x,r) { x.squareTo(r); } - -NullExp.prototype.convert = nNop; -NullExp.prototype.revert = nNop; -NullExp.prototype.mulTo = nMulTo; -NullExp.prototype.sqrTo = nSqrTo; - -// (public) this^e -function bnPow(e) { return this.exp(e,new NullExp()); } - -// (protected) r = lower n words of "this * a", a.t <= n -// "this" should be the larger one if appropriate. -function bnpMultiplyLowerTo(a,n,r) { - var i = Math.min(this.t+a.t,n); - r.s = 0; // assumes a,this >= 0 - r.t = i; - while(i > 0) r[--i] = 0; - var j; - for(j = r.t-this.t; i < j; ++i) r[i+this.t] = this.am(0,a[i],r,i,0,this.t); - for(j = Math.min(a.t,n); i < j; ++i) this.am(0,a[i],r,i,0,n-i); - r.clamp(); -} - -// (protected) r = "this * a" without lower n words, n > 0 -// "this" should be the larger one if appropriate. -function bnpMultiplyUpperTo(a,n,r) { - --n; - var i = r.t = this.t+a.t-n; - r.s = 0; // assumes a,this >= 0 - while(--i >= 0) r[i] = 0; - for(i = Math.max(n-this.t,0); i < a.t; ++i) - r[this.t+i-n] = this.am(n-i,a[i],r,0,0,this.t+i-n); - r.clamp(); - r.drShiftTo(1,r); -} - -// Barrett modular reduction -function Barrett(m) { - // setup Barrett - this.r2 = nbi(); - this.q3 = nbi(); - BigInteger.ONE.dlShiftTo(2*m.t,this.r2); - this.mu = this.r2.divide(m); - this.m = m; -} - -function barrettConvert(x) { - if(x.s < 0 || x.t > 2*this.m.t) return x.mod(this.m); - else if(x.compareTo(this.m) < 0) return x; - else { var r = nbi(); x.copyTo(r); this.reduce(r); return r; } -} - -function barrettRevert(x) { return x; } - -// x = x mod m (HAC 14.42) -function barrettReduce(x) { - x.drShiftTo(this.m.t-1,this.r2); - if(x.t > this.m.t+1) { x.t = this.m.t+1; x.clamp(); } - this.mu.multiplyUpperTo(this.r2,this.m.t+1,this.q3); - this.m.multiplyLowerTo(this.q3,this.m.t+1,this.r2); - while(x.compareTo(this.r2) < 0) x.dAddOffset(1,this.m.t+1); - x.subTo(this.r2,x); - while(x.compareTo(this.m) >= 0) x.subTo(this.m,x); -} - -// r = x^2 mod m; x != r -function barrettSqrTo(x,r) { x.squareTo(r); this.reduce(r); } - -// r = x*y mod m; x,y != r -function barrettMulTo(x,y,r) { x.multiplyTo(y,r); this.reduce(r); } - -Barrett.prototype.convert = barrettConvert; -Barrett.prototype.revert = barrettRevert; -Barrett.prototype.reduce = barrettReduce; -Barrett.prototype.mulTo = barrettMulTo; -Barrett.prototype.sqrTo = barrettSqrTo; - -// (public) this^e % m (HAC 14.85) -function bnModPow(e,m) { - var i = e.bitLength(), k, r = nbv(1), z; - if(i <= 0) return r; - else if(i < 18) k = 1; - else if(i < 48) k = 3; - else if(i < 144) k = 4; - else if(i < 768) k = 5; - else k = 6; - if(i < 8) - z = new Classic(m); - else if(m.isEven()) - z = new Barrett(m); - else - z = new Montgomery(m); - - // precomputation - var g = new Array(), n = 3, k1 = k-1, km = (1< 1) { - var g2 = nbi(); - z.sqrTo(g[1],g2); - while(n <= km) { - g[n] = nbi(); - z.mulTo(g2,g[n-2],g[n]); - n += 2; - } - } - - var j = e.t-1, w, is1 = true, r2 = nbi(), t; - i = nbits(e[j])-1; - while(j >= 0) { - if(i >= k1) w = (e[j]>>(i-k1))&km; - else { - w = (e[j]&((1<<(i+1))-1))<<(k1-i); - if(j > 0) w |= e[j-1]>>(this.DB+i-k1); - } - - n = k; - while((w&1) == 0) { w >>= 1; --n; } - if((i -= n) < 0) { i += this.DB; --j; } - if(is1) { // ret == 1, don't bother squaring or multiplying it - g[w].copyTo(r); - is1 = false; - } - else { - while(n > 1) { z.sqrTo(r,r2); z.sqrTo(r2,r); n -= 2; } - if(n > 0) z.sqrTo(r,r2); else { t = r; r = r2; r2 = t; } - z.mulTo(r2,g[w],r); - } - - while(j >= 0 && (e[j]&(1< 0) { - x.rShiftTo(g,x); - y.rShiftTo(g,y); - } - while(x.signum() > 0) { - if((i = x.getLowestSetBit()) > 0) x.rShiftTo(i,x); - if((i = y.getLowestSetBit()) > 0) y.rShiftTo(i,y); - if(x.compareTo(y) >= 0) { - x.subTo(y,x); - x.rShiftTo(1,x); - } - else { - y.subTo(x,y); - y.rShiftTo(1,y); - } - } - if(g > 0) y.lShiftTo(g,y); - return y; -} - -// (protected) this % n, n < 2^26 -function bnpModInt(n) { - if(n <= 0) return 0; - var d = this.DV%n, r = (this.s<0)?n-1:0; - if(this.t > 0) - if(d == 0) r = this[0]%n; - else for(var i = this.t-1; i >= 0; --i) r = (d*r+this[i])%n; - return r; -} - -// (public) 1/this % m (HAC 14.61) -function bnModInverse(m) { - var ac = m.isEven(); - if((this.isEven() && ac) || m.signum() == 0) return BigInteger.ZERO; - var u = m.clone(), v = this.clone(); - var a = nbv(1), b = nbv(0), c = nbv(0), d = nbv(1); - while(u.signum() != 0) { - while(u.isEven()) { - u.rShiftTo(1,u); - if(ac) { - if(!a.isEven() || !b.isEven()) { a.addTo(this,a); b.subTo(m,b); } - a.rShiftTo(1,a); - } - else if(!b.isEven()) b.subTo(m,b); - b.rShiftTo(1,b); - } - while(v.isEven()) { - v.rShiftTo(1,v); - if(ac) { - if(!c.isEven() || !d.isEven()) { c.addTo(this,c); d.subTo(m,d); } - c.rShiftTo(1,c); - } - else if(!d.isEven()) d.subTo(m,d); - d.rShiftTo(1,d); - } - if(u.compareTo(v) >= 0) { - u.subTo(v,u); - if(ac) a.subTo(c,a); - b.subTo(d,b); - } - else { - v.subTo(u,v); - if(ac) c.subTo(a,c); - d.subTo(b,d); - } - } - if(v.compareTo(BigInteger.ONE) != 0) return BigInteger.ZERO; - if(d.compareTo(m) >= 0) return d.subtract(m); - if(d.signum() < 0) d.addTo(m,d); else return d; - if(d.signum() < 0) return d.add(m); else return d; -} - -var lowprimes = [2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101,103,107,109,113,127,131,137,139,149,151,157,163,167,173,179,181,191,193,197,199,211,223,227,229,233,239,241,251,257,263,269,271,277,281,283,293,307,311,313,317,331,337,347,349,353,359,367,373,379,383,389,397,401,409,419,421,431,433,439,443,449,457,461,463,467,479,487,491,499,503,509,521,523,541,547,557,563,569,571,577,587,593,599,601,607,613,617,619,631,641,643,647,653,659,661,673,677,683,691,701,709,719,727,733,739,743,751,757,761,769,773,787,797,809,811,821,823,827,829,839,853,857,859,863,877,881,883,887,907,911,919,929,937,941,947,953,967,971,977,983,991,997]; -var lplim = (1<<26)/lowprimes[lowprimes.length-1]; - -// (public) test primality with certainty >= 1-.5^t -function bnIsProbablePrime(t) { - var i, x = this.abs(); - if(x.t == 1 && x[0] <= lowprimes[lowprimes.length-1]) { - for(i = 0; i < lowprimes.length; ++i) - if(x[0] == lowprimes[i]) return true; - return false; - } - if(x.isEven()) return false; - i = 1; - while(i < lowprimes.length) { - var m = lowprimes[i], j = i+1; - while(j < lowprimes.length && m < lplim) m *= lowprimes[j++]; - m = x.modInt(m); - while(i < j) if(m%lowprimes[i++] == 0) return false; - } - return x.millerRabin(t); -} - -// (protected) true if probably prime (HAC 4.24, Miller-Rabin) -function bnpMillerRabin(t) { - var n1 = this.subtract(BigInteger.ONE); - var k = n1.getLowestSetBit(); - if(k <= 0) return false; - var r = n1.shiftRight(k); - t = (t+1)>>1; - if(t > lowprimes.length) t = lowprimes.length; - var a = nbi(); - for(var i = 0; i < t; ++i) { - //Pick bases at random, instead of starting at 2 - a.fromInt(lowprimes[Math.floor(Math.random()*lowprimes.length)]); - var y = a.modPow(r,this); - if(y.compareTo(BigInteger.ONE) != 0 && y.compareTo(n1) != 0) { - var j = 1; - while(j++ < k && y.compareTo(n1) != 0) { - y = y.modPowInt(2,this); - if(y.compareTo(BigInteger.ONE) == 0) return false; - } - if(y.compareTo(n1) != 0) return false; - } - } - return true; -} - - -// protected -BigInteger.prototype.chunkSize = bnpChunkSize; -BigInteger.prototype.toRadix = bnpToRadix; -BigInteger.prototype.fromRadix = bnpFromRadix; -BigInteger.prototype.fromNumber = bnpFromNumber; -BigInteger.prototype.bitwiseTo = bnpBitwiseTo; -BigInteger.prototype.changeBit = bnpChangeBit; -BigInteger.prototype.addTo = bnpAddTo; -BigInteger.prototype.dMultiply = bnpDMultiply; -BigInteger.prototype.dAddOffset = bnpDAddOffset; -BigInteger.prototype.multiplyLowerTo = bnpMultiplyLowerTo; -BigInteger.prototype.multiplyUpperTo = bnpMultiplyUpperTo; -BigInteger.prototype.modInt = bnpModInt; -BigInteger.prototype.millerRabin = bnpMillerRabin; - -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; - -BigInteger.prototype.clone = bnClone; -BigInteger.prototype.intValue = bnIntValue; -BigInteger.prototype.byteValue = bnByteValue; -BigInteger.prototype.shortValue = bnShortValue; -BigInteger.prototype.signum = bnSigNum; -BigInteger.prototype.toByteArray = bnToByteArray; -BigInteger.prototype.equals = bnEquals; -BigInteger.prototype.min = bnMin; -BigInteger.prototype.max = bnMax; -BigInteger.prototype.and = bnAnd; -BigInteger.prototype.or = bnOr; -BigInteger.prototype.xor = bnXor; -BigInteger.prototype.andNot = bnAndNot; -BigInteger.prototype.not = bnNot; -BigInteger.prototype.shiftLeft = bnShiftLeft; -BigInteger.prototype.shiftRight = bnShiftRight; -BigInteger.prototype.getLowestSetBit = bnGetLowestSetBit; -BigInteger.prototype.bitCount = bnBitCount; -BigInteger.prototype.testBit = bnTestBit; -BigInteger.prototype.setBit = bnSetBit; -BigInteger.prototype.clearBit = bnClearBit; -BigInteger.prototype.flipBit = bnFlipBit; -BigInteger.prototype.add = bnAdd; -BigInteger.prototype.subtract = bnSubtract; -BigInteger.prototype.multiply = bnMultiply; -BigInteger.prototype.divide = bnDivide; -BigInteger.prototype.remainder = bnRemainder; -BigInteger.prototype.divideAndRemainder = bnDivideAndRemainder; -BigInteger.prototype.modPow = bnModPow; -BigInteger.prototype.modInverse = bnModInverse; -BigInteger.prototype.pow = bnPow; -BigInteger.prototype.gcd = bnGCD; -BigInteger.prototype.isProbablePrime = bnIsProbablePrime; - -// JSBN-specific extension -BigInteger.prototype.square = bnSquare; - -// "constants" -BigInteger.ZERO = nbv(0); -BigInteger.ONE = nbv(1); - -// BigInteger interfaces not implemented in jsbn: - -// BigInteger(int signum, byte[] magnitude) -// double doubleValue() -// float floatValue() -// int hashCode() -// long longValue() -// static BigInteger valueOf(long val) - -BigInteger.valueOf = nbi; - -exports.BigInteger = BigInteger; diff --git a/src/js/ripple/amount.js b/src/js/ripple/amount.js index 852fd6bbff..ac137e37a9 100644 --- a/src/js/ripple/amount.js +++ b/src/js/ripple/amount.js @@ -1,53 +1,45 @@ // Represent Ripple amounts and currencies. // - Numbers in hex are big-endian. -var extend = require('extend'); -var utils = require('./utils'); -var sjcl = utils.sjcl; -var bn = sjcl.bn; - -var BigInteger = utils.jsbn.BigInteger; - -var UInt160 = require('./uint160').UInt160; -var Seed = require('./seed').Seed; -var Currency = require('./currency').Currency; - -// -// Amount class in the style of Java's BigInteger class -// http://docs.oracle.com/javase/1.3/docs/api/java/math/BigInteger.html -// +var assert = require('assert'); +var extend = require('extend'); +var utils = require('./utils'); +var UInt160 = require('./uint160').UInt160; +var Seed = require('./seed').Seed; +var Currency = require('./currency').Currency; +var BigNumber = require('./bignumber'); function Amount() { // Json format: // integer : XRP // { 'value' : ..., 'currency' : ..., 'issuer' : ...} - this._value = new BigInteger(); // NaN for bad value. Always positive. - this._offset = 0; // Always 0 for XRP. + this._value = new BigNumber(NaN); this._is_native = true; // Default to XRP. Only valid if value is not NaN. - this._is_negative = false; this._currency = new Currency(); this._issuer = new UInt160(); -}; +} var consts = { currency_xns: 0, currency_one: 1, xns_precision: 6, - // BigInteger values prefixed with bi_. - bi_5: new BigInteger('5'), - bi_7: new BigInteger('7'), - bi_10: new BigInteger('10'), - bi_1e14: new BigInteger(String(1e14)), - bi_1e16: new BigInteger(String(1e16)), - bi_1e17: new BigInteger(String(1e17)), - bi_1e32: new BigInteger('100000000000000000000000000000000'), - bi_man_max_value: new BigInteger('9999999999999999'), - bi_man_min_value: new BigInteger('1000000000000000'), - bi_xns_max: new BigInteger('9000000000000000000'), // Json wire limit. - bi_xns_min: new BigInteger('-9000000000000000000'),// Json wire limit. - bi_xns_unit: new BigInteger('1000000'), + // bi_ prefix refers to "big integer" + bi_5: new BigNumber('5'), + bi_7: new BigNumber('7'), + bi_10: new BigNumber('10'), + bi_1e14: new BigNumber(String(1e14)), + bi_1e16: new BigNumber(String(1e16)), + bi_1e17: new BigNumber(String(1e17)), + bi_1e32: new BigNumber('100000000000000000000000000000000'), + bi_man_max_value: new BigNumber('9999999999999999'), + bi_man_min_value: new BigNumber('1000000000000000'), + bi_xns_max: new BigNumber('9000000000000000000'), // Json wire limit. + bi_xns_min: new BigNumber('-9000000000000000000'),// Json wire limit. + bi_xrp_max: new BigNumber('9000000000000'), + bi_xrp_min: new BigNumber('-9000000000000'), + bi_xns_unit: new BigNumber('1000000'), cMinOffset: -96, cMaxOffset: 80, @@ -101,8 +93,16 @@ Amount.is_valid_full = function(j) { Amount.NaN = function() { var result = new Amount(); - result._value = NaN; - return result; + result._value = new BigNumber(NaN); // should have no effect + return result; // but let's be careful +}; + +// be sure that _is_native is set properly BEFORE calling _set_value +Amount.prototype._set_value = function(value, roundingMode) { + assert(value instanceof BigNumber); + this._value = value.isZero() && value.isNegative() ? value.negated() : value; + this.canonicalize(roundingMode); + this._check_limits(); }; // Returns a new value which is the absolute value of this. @@ -110,170 +110,47 @@ Amount.prototype.abs = function() { return this.clone(this.is_negative()); }; -// Result in terms of this' currency and issuer. -Amount.prototype.add = function(v) { - var result; - - v = Amount.from_json(v); - - if (!this.is_comparable(v)) { - result = Amount.NaN(); - } else if (v.is_zero()) { - result = this; - } else if (this.is_zero()) { - result = v.clone(); - result._is_native = this._is_native; - result._currency = this._currency; - result._issuer = this._issuer; - } else if (this._is_native) { - result = new Amount(); - - var v1 = this._is_negative ? this._value.negate() : this._value; - var v2 = v._is_negative ? v._value.negate() : v._value; - var s = v1.add(v2); - - result._is_negative = s.compareTo(BigInteger.ZERO) < 0; - result._value = result._is_negative ? s.negate() : s; - result._currency = this._currency; - result._issuer = this._issuer; - } else { - var v1 = this._is_negative ? this._value.negate() : this._value; - var o1 = this._offset; - var v2 = v._is_negative ? v._value.negate() : v._value; - var o2 = v._offset; - - while (o1 < o2) { - v1 = v1.divide(Amount.bi_10); - o1 += 1; - } - - while (o2 < o1) { - v2 = v2.divide(Amount.bi_10); - o2 += 1; - } - - result = new Amount(); - result._is_native = false; - result._offset = o1; - result._value = v1.add(v2); - result._is_negative = result._value.compareTo(BigInteger.ZERO) < 0; - - if (result._is_negative) { - result._value = result._value.negate(); - } - - result._currency = this._currency; - result._issuer = this._issuer; +Amount.prototype.add = function(addend) { + var addendAmount = Amount.from_json(addend); - result.canonicalize(); + if (!this.is_comparable(addendAmount)) { + return Amount.NaN(); } - return result; + return this._copy(this._value.plus(addendAmount._value)); }; -// Result in terms of this currency and issuer. -Amount.prototype.subtract = function(v) { +Amount.prototype.subtract = function(subtrahend) { // Correctness over speed, less code has less bugs, reuse add code. - return this.add(Amount.from_json(v).negate()); + return this.add(Amount.from_json(subtrahend).negate()); }; -// Result in terms of this' currency and issuer. // XXX Diverges from cpp. -Amount.prototype.multiply = function(v) { - var result; - - v = Amount.from_json(v); - - if (this.is_zero()) { - result = this; - } else if (v.is_zero()) { - result = this.clone(); - result._value = BigInteger.ZERO; - } else { - var v1 = this._value; - var o1 = this._offset; - var v2 = v._value; - var o2 = v._offset; - - if (this.is_native()) { - while (v1.compareTo(Amount.bi_man_min_value) < 0) { - v1 = v1.multiply(Amount.bi_10); - o1 -= 1; - } - } - - if (v.is_native()) { - while (v2.compareTo(Amount.bi_man_min_value) < 0) { - v2 = v2.multiply(Amount.bi_10); - o2 -= 1; - } - } - - result = new Amount(); - result._offset = o1 + o2 + 14; - result._value = v1.multiply(v2).divide(Amount.bi_1e14).add(Amount.bi_7); - result._is_native = this._is_native; - result._is_negative = this._is_negative !== v._is_negative; - result._currency = this._currency; - result._issuer = this._issuer; - - result.canonicalize(); - } - - return result; +Amount.prototype.multiply = function(multiplicand) { + var multiplicandAmount = Amount.from_json(multiplicand); + // TODO: probably should just multiply by multiplicandAmount._value + var multiplyBy = multiplicandAmount.is_native() ? + multiplicandAmount._value.times(Amount.bi_xns_unit) + : multiplicandAmount._value; + return this._copy(this._value.times(multiplyBy)); }; -// Result in terms of this' currency and issuer. -Amount.prototype.divide = function(d) { - var result; - - d = Amount.from_json(d); - - if (d.is_zero()) { - throw new Error('divide by zero'); - } - - if (this.is_zero()) { - result = this; - } else if (!this.is_valid()) { +Amount.prototype.divide = function(divisor) { + var divisorAmount = Amount.from_json(divisor); + if (!this.is_valid()) { throw new Error('Invalid dividend'); - } else if (!d.is_valid()) { + } + if (!divisorAmount.is_valid()) { throw new Error('Invalid divisor'); - } else { - var _n = this; - - if (_n.is_native()) { - _n = _n.clone(); - - while (_n._value.compareTo(Amount.bi_man_min_value) < 0) { - _n._value = _n._value.multiply(Amount.bi_10); - _n._offset -= 1; - } - } - - var _d = d; - - if (_d.is_native()) { - _d = _d.clone(); - - while (_d._value.compareTo(Amount.bi_man_min_value) < 0) { - _d._value = _d._value.multiply(Amount.bi_10); - _d._offset -= 1; - } - } - - result = new Amount(); - result._offset = _n._offset - _d._offset - 17; - result._value = _n._value.multiply(Amount.bi_1e17).divide(_d._value).add(Amount.bi_5); - result._is_native = _n._is_native; - result._is_negative = _n._is_negative !== _d._is_negative; - result._currency = _n._currency; - result._issuer = _n._issuer; - - result.canonicalize(); } - - return result; + if (divisorAmount.is_zero()) { + throw new Error('divide by zero'); + } + // TODO: probably should just divide by divisorAmount._value + var divideBy = divisorAmount.is_native() ? + divisorAmount._value.times(Amount.bi_xns_unit) + : divisorAmount._value; + return this._copy(this._value.dividedBy(divideBy)); }; /** @@ -307,8 +184,6 @@ Amount.prototype.ratio_human = function(denominator, opts) { denominator = Amount.from_json(denominator); } - denominator = Amount.from_json(denominator); - // If either operand is NaN, the result is NaN. if (!numerator.is_valid() || !denominator.is_valid()) { return Amount.NaN(); @@ -337,9 +212,7 @@ Amount.prototype.ratio_human = function(denominator, opts) { // // To compensate, we multiply the numerator by 10^xns_precision. if (denominator._is_native) { - numerator = numerator.clone(); - numerator._value = numerator._value.multiply(Amount.bi_xns_unit); - numerator.canonicalize(); + numerator._set_value(numerator._value.times(Amount.bi_xns_unit)); } return numerator.divide(denominator); @@ -396,8 +269,7 @@ Amount.prototype.product_human = function(factor, opts) { // // See also Amount#ratio_human. if (factor._is_native) { - product._value = product._value.divide(Amount.bi_xns_unit); - product.canonicalize(); + product._set_value(product._value.dividedBy(Amount.bi_xns_unit)); } return product; @@ -409,10 +281,7 @@ Amount.prototype.product_human = function(factor, opts) { * @private */ Amount.prototype._invert = function() { - this._value = Amount.bi_1e32.divide(this._value); - this._offset = -32 - this._offset; - this.canonicalize(); - + this._set_value((new BigNumber(1)).dividedBy(this._value)); return this; }; @@ -423,7 +292,7 @@ Amount.prototype._invert = function() { * inverse of the value. */ Amount.prototype.invert = function() { - return this.copy()._invert(); + return this.clone()._invert(); }; /** @@ -453,56 +322,35 @@ Amount.prototype.invert = function() { * @returns {Amount} * @throws {Error} if offset exceeds legal ranges, meaning the amount value is bigger than supported */ -Amount.prototype.canonicalize = function() { - if (!(this._value instanceof BigInteger)) { - // NaN. - // nothing - } else if (this._is_native) { - // Native. - if (this._value.equals(BigInteger.ZERO)) { - this._offset = 0; - this._is_negative = false; - } else { - // Normalize _offset to 0. - - while (this._offset < 0) { - this._value = this._value.divide(Amount.bi_10); - this._offset += 1; - } - - while (this._offset > 0) { - this._value = this._value.multiply(Amount.bi_10); - this._offset -= 1; - } - } - } else if (this.is_zero()) { - this._offset = Amount.cMinOffset; - this._is_negative = false; +Amount.prototype.canonicalize = function(roundingMode) { + if (this._is_native) { + this._value = this._value.round(6, BigNumber.ROUND_DOWN); } else { - // Normalize mantissa to valid range. - - while (this._value.compareTo(Amount.bi_man_min_value) < 0) { - this._value = this._value.multiply(Amount.bi_10); - this._offset -= 1; - } - - while (this._value.compareTo(Amount.bi_man_max_value) > 0) { - this._value = this._value.divide(Amount.bi_10); - this._offset += 1; + if (roundingMode) { + var value = this._value; + this._value = BigNumber.withRoundingMode(roundingMode, function() { + return new BigNumber(value.toPrecision(16)); + }); + } else { + this._value = new BigNumber(this._value.toPrecision(16)); } } +}; - // Make sure not bigger than supported. Throw if so. - if (this.is_negative() && this._offset < Amount.cMinOffset) { - throw new Error('Exceeding min value of ' + Amount.min_value); +Amount.prototype._check_limits = function() { + if (this._value.isNaN() || this._value.isZero()) { + return this; } - - // Make sure not smaller than supported. Throw if so. - if (!this.is_negative() && this._offset > Amount.cMaxOffset) { - throw new Error('Exceeding max value of ' + Amount.max_value); + if (!this._is_native) { + var absval = this._value.absoluteValue(); + if (absval.lessThan((new BigNumber(Amount.min_value)).absoluteValue())) { + throw new Error('Exceeding min value of ' + Amount.min_value); + } + if (absval.greaterThan(new BigNumber(Amount.max_value))) { + throw new Error('Exceeding max value of ' + Amount.max_value); + } } - return this; }; @@ -510,61 +358,27 @@ Amount.prototype.clone = function(negate) { return this.copyTo(new Amount(), negate); }; -Amount.prototype.compareTo = function(v) { - var result; +Amount.prototype._copy = function(value) { + var copy = this.clone(); + copy._set_value(value); + return copy; +}; - v = Amount.from_json(v); - - if (!this.is_comparable(v)) { - result = Amount.NaN(); - } else if (this._is_negative !== v._is_negative) { - // Different sign. - result = this._is_negative ? -1 : 1; - } else if (this._value.equals(BigInteger.ZERO)) { - // Same sign: positive. - result = v._value.equals(BigInteger.ZERO) ? 0 : -1; - } else if (v._value.equals(BigInteger.ZERO)) { - // Same sign: positive. - result = 1; - } else if (!this._is_native && this._offset > v._offset) { - result = this._is_negative ? -1 : 1; - } else if (!this._is_native && this._offset < v._offset) { - result = this._is_negative ? 1 : -1; - } else { - result = this._value.compareTo(v._value); - if (result > 0) { - result = this._is_negative ? -1 : 1; - } else if (result < 0) { - result = this._is_negative ? 1 : -1; - } +Amount.prototype.compareTo = function(to) { + var toAmount = Amount.from_json(to); + if (!this.is_comparable(toAmount)) { + return Amount.NaN(); } - - return result; + return this._value.comparedTo(toAmount._value); }; // Make d a copy of this. Returns d. // Modification of objects internally refered to is not allowed. Amount.prototype.copyTo = function(d, negate) { - if (typeof this._value === 'object') { - this._value.copyTo(d._value); - } else { - d._value = this._value; - } - - d._offset = this._offset; + d._value = negate ? this._value.negated() : this._value; d._is_native = this._is_native; - d._is_negative = negate - ? !this._is_negative // Negating. - : this._is_negative; // Just copying. - - d._currency = this._currency; - d._issuer = this._issuer; - - // Prevent negative zero - if (d.is_zero()) { - d._is_negative = false; - } - + d._currency = this._currency; + d._issuer = this._issuer; return d; }; @@ -577,20 +391,16 @@ Amount.prototype.equals = function(d, ignore_issuer) { return this.equals(Amount.from_json(d)); } - var result = !((!this.is_valid() || !d.is_valid()) - || (this._is_native !== d._is_native) - || (!this._value.equals(d._value) || this._offset !== d._offset) - || (this._is_negative !== d._is_negative) - || (!this._is_native && (!this._currency.equals(d._currency) || !ignore_issuer && !this._issuer.equals(d._issuer)))); - - return result; + return this.is_valid() && d.is_valid() + && this._is_native === d._is_native + && this._value.equals(d._value) + && (this._is_native || (this._currency.equals(d._currency) + && (ignore_issuer || this._issuer.equals(d._issuer)))); }; // True if Amounts are valid and both native or non-native. Amount.prototype.is_comparable = function(v) { - return this._value instanceof BigInteger - && v._value instanceof BigInteger - && this._is_native === v._is_native; + return this.is_valid() && v.is_valid() && this._is_native === v._is_native; }; Amount.prototype.is_native = function() { @@ -598,9 +408,7 @@ Amount.prototype.is_native = function() { }; Amount.prototype.is_negative = function() { - return this._value instanceof BigInteger - ? this._is_negative - : false; // NaN is not negative + return this._value.isNegative(); }; Amount.prototype.is_positive = function() { @@ -609,7 +417,7 @@ Amount.prototype.is_positive = function() { // Only checks the value. Not the currency and issuer. Amount.prototype.is_valid = function() { - return this._value instanceof BigInteger; + return !this._value.isNaN(); }; Amount.prototype.is_valid_full = function() { @@ -617,7 +425,7 @@ Amount.prototype.is_valid_full = function() { }; Amount.prototype.is_zero = function() { - return this._value instanceof BigInteger ? this._value.equals(BigInteger.ZERO) : false; + return this._value.isZero(); }; Amount.prototype.issuer = function() { @@ -629,23 +437,6 @@ Amount.prototype.negate = function() { return this.clone('NEGATE'); }; -/** - * Invert this amount and return the new value. - * - * Creates a new Amount object as a copy of the current one (including the same - * unit (currency & issuer), inverts it (1/x) and returns the result. - */ -Amount.prototype.invert = function() { - var one = this.clone(); - one._value = BigInteger.ONE; - one._offset = 0; - one._is_negative = false; - - one.canonicalize(); - - return one.ratio_human(this); -}; - /** * Tries to correctly interpret an amount as entered by a user. * @@ -659,96 +450,71 @@ Amount.prototype.invert = function() { * * The regular expression below matches above cases, broken down for better understanding: * - * ^\s* // start with any amount of whitespace * ([A-z]{3}|[0-9]{3}) // either 3 letter alphabetic currency-code or 3 digit numeric currency-code. See ISO 4217 - * \s* // any amount of whitespace - * (-)? // optional dash - * (\d+) // 1 or more digits - * (?:\.(\d*))? // optional . character with any amount of digits - * \s* // any amount of whitespace - * ([A-z]{3}|[0-9]{3})? // either 3 letter alphabetic currency-code or 3 digit numeric currency-code. See ISO 4217 - * \s* // any amount of whitespace * $ // end of string * */ -Amount.human_RE_hex = /^\s*(-)?(\d+)(?:\.(\d*))?\s*([a-fA-F0-9]{40})\s*$/; -Amount.human_RE = /^\s*([A-z]{3}|[0-9]{3})?\s*(-)?(\d+)(?:\.(\d*))?\s*([A-z]{3}|[0-9]{3})?\s*$/; Amount.prototype.parse_human = function(j, opts) { opts = opts || {}; - var integer; - var fraction; - var currency; - var precision = null; - - // first check if it's a hex formatted currency - var matches = String(j).match(Amount.human_RE_hex); - if (matches && matches.length === 5 && matches[4]) { - integer = matches[2]; - fraction = matches[3] || ''; - currency = matches[4]; - this._is_negative = Boolean(matches[1]); - } + var hex_RE = /^[a-fA-F0-9]{40}$/; + var currency_RE = /^([a-zA-Z]{3}|[0-9]{3})$/; - if (integer === void(0) && currency === void(0)) { - var m = String(j).match(Amount.human_RE); - if (m) { - currency = m[5] || m[1] || 'XRP'; - integer = m[5] && m[1] ? m[1] + '' + m[3] : (m[3] || '0'); - fraction = m[4] || ''; - this._is_negative = Boolean(m[2]); - } - } - - if (integer) { - currency = currency.toUpperCase(); + var value; + var currency; - this._value = new BigInteger(integer); - this.set_currency(currency); + var words = j.split(' ').filter(function(word) { return word !== ''; }); - // XRP have exactly six digits of precision - if (currency === 'XRP') { - fraction = fraction.slice(0, 6); - while (fraction.length < 6) { - fraction += '0'; + if (words.length === 1) { + if (isFinite(words[0])) { + value = words[0]; + currency = 'XRP'; + } else { + value = words[0].slice(0, -3); + currency = words[0].slice(-3); + if (!(isFinite(value) && currency.match(currency_RE))) { + return Amount.NaN(); } - this._is_native = true; - this._value = this._value.multiply(Amount.bi_xns_unit).add(new BigInteger(fraction)); + } + } else if (words.length === 2) { + if (isFinite(words[0]) && words[1].match(hex_RE)) { + value = words[0]; + currency = words[1]; + } else if (words[0].match(currency_RE) && isFinite(words[1])) { + value = words[1]; + currency = words[0]; + } else if (isFinite(words[0]) && words[1].match(currency_RE)) { + value = words[0]; + currency = words[1]; } else { - // Other currencies have arbitrary precision - fraction = fraction.replace(/0+$/, ''); - precision = fraction.length; + return Amount.NaN(); + } + } else { + return Amount.NaN(); + } - this._is_native = false; - var multiplier = Amount.bi_10.clone().pow(precision); - this._value = this._value.multiply(multiplier).add(new BigInteger(fraction)); - this._offset = -precision; + currency = currency.toUpperCase(); + this.set_currency(currency); + this._is_native = (currency === 'XRP'); + this._set_value(new BigNumber(value)); - this.canonicalize(); - } + // Apply interest/demurrage + if (opts.reference_date && this._currency.has_interest()) { + var interest = this._currency.get_interest_at(opts.reference_date); - // Apply interest/demurrage - if (opts.reference_date && this._currency.has_interest()) { - var interest = this._currency.get_interest_at(opts.reference_date); - - // XXX Because the Amount parsing routines don't support some of the things - // that JavaScript can output when casting a float to a string, the - // following call sometimes does not produce a valid Amount. - // - // The correct way to solve this is probably to switch to a proper - // BigDecimal for our internal representation and then use that across - // the board instead of instantiating these dummy Amount objects. - var interestTempAmount = Amount.from_json(''+interest+'/1/1'); - - if (interestTempAmount.is_valid()) { - var ref = this.divide(interestTempAmount); - this._value = ref._value; - this._offset = ref._offset; - } + // XXX Because the Amount parsing routines don't support some of the things + // that JavaScript can output when casting a float to a string, the + // following call sometimes does not produce a valid Amount. + // + // The correct way to solve this is probably to switch to a proper + // BigDecimal for our internal representation and then use that across + // the board instead of instantiating these dummy Amount objects. + var interestTempAmount = Amount.from_json(''+interest+'/1/1'); + + if (interestTempAmount.is_valid()) { + this._set_value(this.divide(interestTempAmount)._value); } - } else { - this._value = NaN; } return this; @@ -756,7 +522,6 @@ Amount.prototype.parse_human = function(j, opts) { Amount.prototype.parse_issuer = function(issuer) { this._issuer = UInt160.from_json(issuer); - return this; }; @@ -798,29 +563,41 @@ Amount.prototype.parse_quality = function(quality, counterCurrency, counterIssue var baseCurrency = Currency.from_json(opts.base_currency); - this._is_negative = false; - this._value = new BigInteger(quality.substring(quality.length-14), 16); - this._offset = parseInt(quality.substring(quality.length-16, quality.length-14), 16)-100; + var mantissa_hex = quality.substring(quality.length-14); + var offset_hex = quality.substring(quality.length-16, quality.length-14); + var mantissa = new BigNumber(mantissa_hex, 16); + var offset = parseInt(offset_hex, 16) - 100; + + var value = new BigNumber(mantissa.toString() + 'e' + offset.toString()); + this._currency = Currency.from_json(counterCurrency); this._issuer = UInt160.from_json(counterIssuer); this._is_native = this._currency.is_native(); + var power = 0; + if (this._is_native) { + if (opts.inverse) { + power += 1; + } else { + power -= 1; + } + } + // Correct offset if xrp_as_drops option is not set and base currency is XRP if (!opts.xrp_as_drops && baseCurrency.is_valid() && baseCurrency.is_native()) { if (opts.inverse) { - this._offset -= 6; + power -= 1; } else { - this._offset += 6; + power += 1; } } - if (opts.inverse) { - this._invert(); - } - - this.canonicalize(); + var one = new BigNumber(1); + var adjusted = value.times(Amount.bi_xns_unit.toPower(power)); + var newValue = opts.inverse ? one.dividedBy(adjusted) : adjusted; + this._set_value(newValue); if (opts.reference_date && baseCurrency.is_valid() && baseCurrency.has_interest()) { var interest = baseCurrency.get_interest_at(opts.reference_date); @@ -829,9 +606,7 @@ Amount.prototype.parse_quality = function(quality, counterCurrency, counterIssue var interestTempAmount = Amount.from_json(''+interest+'/1/1'); if (interestTempAmount.is_valid()) { - var v = this.divide(interestTempAmount); - this._value = v._value; - this._offset = v._offset; + this._set_value(this.divide(interestTempAmount)._value); } } @@ -842,12 +617,7 @@ Amount.prototype.parse_number = function(n) { this._is_native = false; this._currency = Currency.from_json(1); this._issuer = UInt160.from_json(1); - this._is_negative = n < 0 ? true : false; - this._value = new BigInteger(String(this._is_negative ? -n : n)); - this._offset = 0; - - this.canonicalize(); - + this._set_value(new BigNumber(n)); return this; }; @@ -897,7 +667,7 @@ Amount.prototype.parse_json = function(j) { break; default: - this._value = NaN; + this._set_value(new BigNumber(NaN)); } return this; @@ -908,33 +678,20 @@ Amount.prototype.parse_json = function(j) { // - float = with precision 6 // XXX Improvements: disallow leading zeros. Amount.prototype.parse_native = function(j) { - var m; - - if (typeof j === 'string') { - m = j.match(/^(-?)(\d*)(\.\d{0,6})?$/); - } - - if (m) { - if (m[3] === void(0)) { - // Integer notation - this._value = new BigInteger(m[2]); + if (typeof j === 'string' && j.match(/^-?\d*(\.\d{0,6})?$/)) { + var value = new BigNumber(j); + this._is_native = true; + if (j.indexOf('.') >= 0) { + this._set_value(value); } else { - // Float notation : values multiplied by 1,000,000. - var int_part = (new BigInteger(m[2])).multiply(Amount.bi_xns_unit); - var fraction_part = (new BigInteger(m[3])).multiply(new BigInteger(String(Math.pow(10, 1+Amount.xns_precision-m[3].length)))); - - this._value = int_part.add(fraction_part); + this._set_value(value.dividedBy(Amount.bi_xns_unit)); } - - this._is_native = true; - this._offset = 0; - this._is_negative = !!m[1] && this._value.compareTo(BigInteger.ZERO) !== 0; - - if (this._value.compareTo(Amount.bi_xns_max) > 0) { - this._value = NaN; + // TODO: move this overflow check to canonicalize + if (this._value.abs().greaterThan(Amount.bi_xrp_max)) { + this._set_value(new BigNumber(NaN)); } } else { - this._value = NaN; + this._set_value(new BigNumber(NaN)); } return this; @@ -943,63 +700,14 @@ Amount.prototype.parse_native = function(j) { // Parse a non-native value for the json wire format. // Requires _currency to be set! Amount.prototype.parse_value = function(j) { - this._is_native = false; - - switch (typeof j) { - case 'number': - this._is_negative = j < 0; - this._value = new BigInteger(Math.abs(j)); - this._offset = 0; - - this.canonicalize(); - break; - - case 'string': - var i = j.match(/^(-?)(\d+)$/); - var d = !i && j.match(/^(-?)(\d*)\.(\d*)$/); - var e = !e && j.match(/^(-?)(\d*)e(-?\d+)$/); - - if (e) { - // e notation - this._value = new BigInteger(e[2]); - this._offset = parseInt(e[3]); - this._is_negative = !!e[1]; - - this.canonicalize(); - } else if (d) { - // float notation - var integer = new BigInteger(d[2]); - var fraction = new BigInteger(d[3]); - var precision = d[3].length; - - this._value = integer.multiply(Amount.bi_10.clone().pow(precision)).add(fraction); - this._offset = -precision; - this._is_negative = !!d[1]; - - this.canonicalize(); - } else if (i) { - // integer notation - this._value = new BigInteger(i[2]); - this._offset = 0; - this._is_negative = !!i[1]; - - this.canonicalize(); - } else { - this._value = NaN; - } - break; - - default: - this._value = j instanceof BigInteger ? j : NaN; - } - + this._is_native = false; + this._set_value(new BigNumber(j), BigNumber.ROUND_DOWN); return this; }; Amount.prototype.set_currency = function(c) { this._currency = Currency.from_json(c); this._is_native = this._currency.is_native(); - return this; }; @@ -1020,37 +728,43 @@ Amount.prototype.to_number = function(allow_nan) { // Convert only value to JSON wire format. Amount.prototype.to_text = function(allow_nan) { - var result = NaN; + if (this._is_native && this._value.abs().greaterThan(Amount.bi_xrp_max)) { + return '0'; + } + if (this._value.isNaN() && !allow_nan) { + return '0'; + } else if (this._value.isNaN()) { + return NaN; // TODO: why does to_text return NaN? return 'NaN'? + } if (this._is_native) { - if (this.is_valid() && this._value.compareTo(Amount.bi_xns_max) <= 0){ - result = this._value.toString(); + if (this.is_valid() && this._value.lessThanOrEqualTo(Amount.bi_xns_max)){ + return this._value.times(Amount.bi_xns_unit).toString(); + } else { + return NaN; // TODO: why does to_text return NaN? return 'NaN'? } - } else if (this.is_zero()) { - result = '0'; - } else if (this._offset && (this._offset < -25 || this._offset > -4)) { + } + + // not native + var offset = this._value.e - 15; + var sign = this._value.isNegative() ? '-' : ''; + var mantissa = utils.getMantissaDecimalString(this._value.absoluteValue()); + if (offset !== 0 && (offset < -25 || offset > -4)) { // Use e notation. // XXX Clamp output. - result = this._value.toString() + 'e' + this._offset; + return sign + mantissa.toString() + 'e' + offset.toString(); } else { - var val = '000000000000000000000000000' + this._value.toString() + '00000000000000000000000'; - var pre = val.substring(0, this._offset + 43); - var post = val.substring(this._offset + 43); + var val = '000000000000000000000000000' + mantissa.toString() + + '00000000000000000000000'; + var pre = val.substring(0, offset + 43); + var post = val.substring(offset + 43); 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. - result = '' - + (s_pre ? s_pre[0] : '0') + return sign + (s_pre ? s_pre[0] : '0') + (s_post ? '.' + post.substring(0, 1 + post.length - s_post[0].length) : ''); } - if (!allow_nan && typeof result === 'number' && isNaN(result)) { - result = '0'; - } else if (this._is_negative) { - result = '-' + result; - } - - return result; }; /** @@ -1131,15 +845,11 @@ Amount.prototype.to_human = function(opts) { ref = this.applyInterest(opts.reference_date); } - var order = ref._is_native ? Amount.xns_precision : -ref._offset; - var denominator = Amount.bi_10.clone().pow(order); - var int_part = ref._value.divide(denominator).toString(); - var fraction_part = ref._value.mod(denominator).toString(); - - // Add leading zeros to fraction - while (fraction_part.length < order) { - fraction_part = '0' + fraction_part; - } + var isNegative = ref._value.isNegative(); + var valueString = ref._value.abs().toString(); + var parts = valueString.split('.'); + var int_part = parts[0]; + var fraction_part = parts.length === 2 ? parts[1] : ''; int_part = int_part.replace(/^0*/, ''); fraction_part = fraction_part.replace(/0*$/, ''); @@ -1208,7 +918,7 @@ Amount.prototype.to_human = function(opts) { } var formatted = ''; - if (opts.signed && this._is_negative) { + if (opts.signed && isNegative) { if (typeof opts.signed !== 'string') { opts.signed = '-'; } @@ -1250,11 +960,9 @@ Amount.prototype.to_json = function() { }; Amount.prototype.to_text_full = function(opts) { - return this._value instanceof BigInteger - ? this._is_native + return this._is_native ? this.to_human() + '/XRP' - : this.to_text() + '/' + this._currency.to_json() + '/' + this._issuer.to_json(opts) - : NaN; + : this.to_text() + '/' + this._currency.to_json() + '/' + this._issuer.to_json(opts); }; // For debugging. @@ -1276,10 +984,10 @@ Amount.prototype.not_equals_why = function(d, ignore_issuer) { } else { var type = this._is_native ? 'XRP' : 'Non-XRP'; - if (!this._value.equals(d._value) || this._offset !== d._offset) { - result = type + ' value differs.'; - } else if (this._is_negative !== d._is_negative) { + if (!this._value.isZero() && this._value.negated().equals(d._value)) { result = type + ' sign differs.'; + } else if (!this._value.equals(d._value)) { + result = type + ' value differs.'; } else if (!this._is_native) { if (!this._currency.equals(d._currency)) { result = 'Non-XRP currency differs.'; diff --git a/src/js/ripple/base.js b/src/js/ripple/base.js index ef6a5fb67f..76715be039 100644 --- a/src/js/ripple/base.js +++ b/src/js/ripple/base.js @@ -2,8 +2,6 @@ var sjcl = require('./utils').sjcl; var utils = require('./utils'); var extend = require('extend'); -var BigInteger = utils.jsbn.BigInteger; - var Base = {}; var alphabets = Base.alphabets = { @@ -25,83 +23,156 @@ extend(Base, { function sha256(bytes) { return sjcl.codec.bytes.fromBits(sjcl.hash.sha256.hash(sjcl.codec.bytes.toBits(bytes))); -}; +} function sha256hash(bytes) { return sha256(sha256(bytes)); -}; +} + +function divmod58(number, startAt) { + var remainder = 0; + for (var i = startAt; i < number.length; i++) { + var digit256 = number[i] & 0xFF; + var temp = remainder * 256 + digit256; + number[i] = (temp / 58); + remainder = temp % 58; + } + return remainder; +} + +function divmod256(number58, startAt) { + var remainder = 0; + for (var i = startAt; i < number58.length; i++) { + var digit58 = number58[i] & 0xFF; + var temp = remainder * 58 + digit58; + number58[i] = (temp / 256); + remainder = temp % 256; + } + return remainder; +} -// --> 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 = new BigInteger(); - var bi_r = new BigInteger(); - 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()]); +function encodeString (alphabet, input) { + if (input.length == 0) { + return []; } - for (var i=0; i !== input.length && !input[i]; i += 1) { - buffer.push(alphabet[0]); - } + // we need to copy the buffer for calc + scratch = input.slice(); - return buffer.reverse().join(''); -}; + // Count leading zeroes. + var zeroCount = 0; + while (zeroCount < scratch.length && + scratch[zeroCount] == 0) + ++zeroCount; -// --> input: String -// <-- array of bytes or undefined. -Base.decode = function(input, alpha) { - if (typeof input !== 'string') { - return void(0); + // The actual encoding. + var out = new Array(scratch.length * 2); + var j = out.length; + var startAt = zeroCount; + + while (startAt < scratch.length) { + var mod = divmod58(scratch, startAt); + if (scratch[startAt] == 0) { + ++startAt; + } + out[--j] = alphabet[mod]; } - var alphabet = alphabets[alpha || 'ripple']; - var bi_base = new BigInteger(String(alphabet.length)); - var bi_value = new BigInteger(); - var i; + // Strip extra 'r' if there are some after decoding. + while (j < out.length && out[j] == alphabet[0]) ++j; + // Add as many leading 'r' as there were leading zeros. + while (--zeroCount >= 0) out[--j] = alphabet[0]; + while(j--) out.shift(); + + return out.join(''); +} - for (i = 0; i !== input.length && input[i] === alphabet[0]; i += 1) { +function decodeString(indexes, input) { + var isString = typeof input === 'string'; + + if (input.length == 0) { + return []; } - for (; i !== input.length; i += 1) { - var v = alphabet.indexOf(input[i]); + input58 = new Array(input.length); + + // Transform the String to a base58 byte sequence + for (var i = 0; i < input.length; ++i) { + if (isString) { + var c = input.charCodeAt(i); + } - if (v < 0) { - return void(0); + var digit58 = -1; + if (c >= 0 && c < 128) { + digit58 = indexes[c]; + } + if (digit58 < 0) { + throw new Error("Illegal character " + c + " at " + i); } - var r = new BigInteger(); - r.fromInt(v); - bi_value = bi_value.multiply(bi_base).add(r); + input58[i] = digit58; + } + // Count leading zeroes + var zeroCount = 0; + while (zeroCount < input58.length && input58[zeroCount] == 0) { + ++zeroCount; + } + // The encoding + out = utils.arraySet(input.length, 0); + var j = out.length; + + var startAt = zeroCount; + while (startAt < input58.length) { + var mod = divmod256(input58, startAt); + if (input58[startAt] == 0) { + ++startAt; + } + out[--j] = mod; } - // 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; + // Do no add extra leading zeroes, move j to first non null byte. + while (j < out.length && (out[j] == 0)) ++j; - while (extra !== bytes.length && !bytes[extra]) { - extra += 1; - } + j -= zeroCount; + while(j--) out.shift(); + + return out; +} - if (extra) { - bytes = bytes.slice(extra); +function Base58(alphabet) { + var indexes = utils.arraySet(128, -1); + for (var i = 0; i < alphabet.length; i++) { + indexes[alphabet.charCodeAt(i)] = i; } + return { + decode: decodeString.bind(null, indexes), + encode: encodeString.bind(null, alphabet) + }; +} + +Base.encoders = {}; +Object.keys(alphabets).forEach(function(alphabet){ + Base.encoders[alphabet] = Base58(alphabets[alphabet]); +}); - var zeros = 0; +// --> input: big-endian array of bytes. +// <-- string at least as long as input. +Base.encode = function(input, alpha) { + return this.encoders[alpha || 'ripple'].encode(input); +}; - while (zeros !== input.length && input[zeros] === alphabet[0]) { - zeros += 1; +// --> input: String +// <-- array of bytes or undefined. +Base.decode = function(input, alpha) { + if (typeof input !== 'string') { + return void(0); + } + try { + return this.encoders[alpha || 'ripple'].decode(input); + } + catch(e) { + return (void 0); } - - return [].concat(utils.arraySet(zeros, 0), bytes); }; Base.verify_checksum = function(bytes) { @@ -129,7 +200,7 @@ Base.encode_check = function(version, input, alphabet) { }; // --> input : String -// <-- NaN || BigInteger +// <-- NaN || sjcl.bn Base.decode_check = function(version, input, alphabet) { var buffer = Base.decode(input, alphabet); @@ -163,7 +234,8 @@ Base.decode_check = function(version, input, alphabet) { // intrepret the value as a negative number buffer[0] = 0; - return new BigInteger(buffer.slice(0, -4), 256); + return sjcl.bn.fromBits ( + sjcl.codec.bytes.toBits(buffer.slice(0, -4))); }; exports.Base = Base; diff --git a/src/js/ripple/bignumber.js b/src/js/ripple/bignumber.js new file mode 100644 index 0000000000..86cbbb2006 --- /dev/null +++ b/src/js/ripple/bignumber.js @@ -0,0 +1,33 @@ +var BigNumber = require('bignumber.js'); +var extend = require('extend'); + +function BigNumberWrapper(value, base) { + // reset config every time a BigNumber is instantiated so that + // these global settings won't be overridden if another file tries + // to set them at require-time. + BigNumber.config({ ROUNDING_MODE: BigNumber.ROUND_HALF_UP, + DECIMAL_PLACES: 40 }); + BigNumber.call(this, value, base); +} + +extend(BigNumberWrapper, BigNumber); // copy class static properties +BigNumberWrapper.prototype = BigNumber.prototype; + +BigNumberWrapper.config = function() { + throw new Error('BigNumber.config may only be called from bignumber.js'); +}; + +BigNumberWrapper.withRoundingMode = function(roundingMode, func) { + var config = BigNumber.config(); + var oldRoundingMode = config.ROUNDING_MODE; + config.ROUNDING_MODE = roundingMode; + BigNumber.config(config); + try { + return func(); + } finally { + config.ROUNDING_MODE = oldRoundingMode; + BigNumber.config(config); + } +}; + +module.exports = BigNumberWrapper; diff --git a/src/js/ripple/currency.js b/src/js/ripple/currency.js index 859ee140d6..cd11d313cf 100644 --- a/src/js/ripple/currency.js +++ b/src/js/ripple/currency.js @@ -308,7 +308,7 @@ Currency.prototype.get_interest_percentage_at = function(referenceDate, decimals // currency data, since there are some values that are invalid. // //Currency.prototype.is_valid = function() { -// return this._value instanceof BigInteger && ...; +// return UInt.prototype.is_valid() && ...; //}; Currency.prototype.to_json = function(opts) { diff --git a/src/js/ripple/index.js b/src/js/ripple/index.js index 2d3269577d..c2fc8816dc 100644 --- a/src/js/ripple/index.js +++ b/src/js/ripple/index.js @@ -29,7 +29,6 @@ exports.RangeSet = require('./rangeset').RangeSet; // the official client, it makes sense to expose the SJCL instance so we don't // have to include it twice. exports.sjcl = require('./utils').sjcl; -exports.jsbn = require('./utils').jsbn; exports.types = require('./serializedtypes'); exports.config = require('./config'); diff --git a/src/js/ripple/seed.js b/src/js/ripple/seed.js index e36cce84c9..322f047cf4 100644 --- a/src/js/ripple/seed.js +++ b/src/js/ripple/seed.js @@ -6,8 +6,6 @@ var extend = require('extend'); var utils = require('./utils'); var sjcl = utils.sjcl; -var BigInteger = utils.jsbn.BigInteger; - var Base = require('./base').Base; var UInt = require('./uint').UInt; var UInt256 = require('./uint256').UInt256; @@ -15,7 +13,6 @@ var UInt160 = require('./uint160').UInt160; var KeyPair = require('./keypair').KeyPair; var Seed = extend(function () { - // Internal form: NaN or BigInteger this._curve = sjcl.ecc.curves.k256; this._value = NaN; }, UInt); @@ -60,7 +57,7 @@ Seed.prototype.parse_passphrase = function (j) { }; Seed.prototype.to_json = function () { - if (!(this._value instanceof BigInteger)) { + if (!(this.is_valid())) { return NaN; } diff --git a/src/js/ripple/serializedobject.js b/src/js/ripple/serializedobject.js index 1aa55b879e..6298ea1570 100644 --- a/src/js/ripple/serializedobject.js +++ b/src/js/ripple/serializedobject.js @@ -1,13 +1,12 @@ +var _ = require('lodash'); var assert = require('assert'); var extend = require('extend'); var binformat = require('./binformat'); var stypes = require('./serializedtypes'); -var UInt256 = require('./uint256').UInt256; var Crypt = require('./crypt').Crypt; var utils = require('./utils'); var sjcl = utils.sjcl; -var BigInteger = utils.jsbn.BigInteger; var TRANSACTION_TYPES = { }; @@ -27,6 +26,13 @@ Object.keys(binformat.ter).forEach(function(key) { TRANSACTION_RESULTS[binformat.ter[key]] = key; }); +function normalize_sjcl_bn_hex(string) { + var hex = string.slice(2); // remove '0x' prefix + // now strip leading zeros + var i = _.findIndex(hex, function(c) { return c !== '0'; }); + return i >= 0 ? hex.slice(i) : '0'; +} + function SerializedObject(buf) { if (Array.isArray(buf) || (Buffer && Buffer.isBuffer(buf)) ) { this.buffer = buf; @@ -38,11 +44,11 @@ function SerializedObject(buf) { throw new Error('Invalid buffer passed.'); } this.pointer = 0; -}; +} SerializedObject.from_json = function(obj) { // Create a copy of the object so we don't modify it - var obj = extend(true, {}, obj); + obj = extend(true, {}, obj); var so = new SerializedObject(); var typedef; @@ -103,8 +109,8 @@ SerializedObject.check_no_missing_fields = function(typedef, obj) { if (binformat.REQUIRED === requirement && obj[field] === void(0)) { missing_fields.push(field); - }; - }; + } + } if (missing_fields.length > 0) { var object_name; @@ -114,12 +120,12 @@ SerializedObject.check_no_missing_fields = function(typedef, obj) { } else if (obj.LedgerEntryType != null){ object_name = SerializedObject.lookup_type_le(obj.LedgerEntryType); } else { - object_name = "TransactionMetaData"; + object_name = 'TransactionMetaData'; } - throw new Error(object_name + " is missing fields: " + + throw new Error(object_name + ' is missing fields: ' + JSON.stringify(missing_fields)); - }; + } }; SerializedObject.prototype.append = function(bytes) { @@ -152,7 +158,7 @@ function readOrPeek(advance) { return result; }; -}; +} SerializedObject.prototype.read = readOrPeek(true); @@ -209,8 +215,8 @@ SerializedObject.jsonify_structure = function(structure, field_name) { if (typeof structure.to_json === 'function') { output = structure.to_json(); - } else if (structure instanceof BigInteger) { - output = ('0000000000000000' + structure.toString(16).toUpperCase()).slice(-16); + } else if (structure instanceof sjcl.bn) { + output = ('0000000000000000' + normalize_sjcl_bn_hex(structure.toString()).toUpperCase()).slice(-16); } else { //new Array or Object output = new structure.constructor(); @@ -248,15 +254,15 @@ SerializedObject.prototype.serialize = function(typedef, obj) { SerializedObject.prototype.hash = function(prefix) { var sign_buffer = new SerializedObject(); - + // Add hashing prefix - if ("undefined" !== typeof prefix) { + if ('undefined' !== typeof prefix) { stypes.Int32.serialize(sign_buffer, prefix); } // Copy buffer to temporary buffer sign_buffer.append(this.buffer); - + // XXX We need a proper Buffer class then Crypt could accept that var bits = sjcl.codec.bytes.toBits(sign_buffer.buffer); return Crypt.hashSha512Half(bits); @@ -312,7 +318,7 @@ SerializedObject.sort_typedef = function(typedef) { function sort_field_compare(a, b) { // Sort by type id first, then by field id return a[3] !== b[3] ? stypes[a[3]].id - stypes[b[3]].id : a[2] - b[2]; - }; + } return typedef.sort(sort_field_compare); }; diff --git a/src/js/ripple/serializedtypes.js b/src/js/ripple/serializedtypes.js index e064b2a28b..9c8150de35 100644 --- a/src/js/ripple/serializedtypes.js +++ b/src/js/ripple/serializedtypes.js @@ -11,6 +11,7 @@ var extend = require('extend'); var binformat = require('./binformat'); var utils = require('./utils'); var sjcl = utils.sjcl; +var BigNumber = require('./bignumber'); var UInt128 = require('./uint128').UInt128; var UInt160 = require('./uint160').UInt160; @@ -21,59 +22,48 @@ var amount = require('./amount'); var Amount = amount.Amount; var Currency = amount.Currency; -// Shortcuts -var hex = sjcl.codec.hex; -var bytes = sjcl.codec.bytes; -var utf8 = sjcl.codec.utf8String; - -var BigInteger = utils.jsbn.BigInteger; - - var SerializedType = function (methods) { extend(this, methods); }; function isNumber(val) { return typeof val === 'number' && isFinite(val); -}; +} function isString(val) { return typeof val === 'string'; -}; +} function isHexInt64String(val) { return isString(val) && /^[0-9A-F]{0,16}$/i.test(val); -}; - -function isCurrencyString(val) { - return isString(val) && /^[A-Z0-9]{3}$/.test(val); -}; - -function isBigInteger(val) { - return val instanceof BigInteger; -}; +} -function serializeHex(so, hexData, noLength) { - var byteData = bytes.fromBits(hex.toBits(hexData)); +function serializeBits(so, bits, noLength) { + var byteData = sjcl.codec.bytes.fromBits(bits); if (!noLength) { SerializedType.serialize_varint(so, byteData.length); } so.append(byteData); -}; +} + +function serializeHex(so, hexData, noLength) { + serializeBits(so, sjcl.codec.hex.toBits(hexData), noLength); +} /** * parses bytes as hex */ function convertByteArrayToHex (byte_array) { return sjcl.codec.hex.fromBits(sjcl.codec.bytes.toBits(byte_array)).toUpperCase(); -}; +} function convertStringToHex(string) { - return hex.fromBits(utf8.toBits(string)).toUpperCase(); + var utf8String = sjcl.codec.utf8String.toBits(string); + return sjcl.codec.hex.fromBits(utf8String).toUpperCase(); } function convertHexToString(hexString) { - return utf8.fromBits(hex.toBits(hexString)); + return sjcl.codec.utf8String.fromBits(sjcl.codec.hex.toBits(hexString)); } SerializedType.serialize_varint = function (so, val) { @@ -130,7 +120,7 @@ function convertIntegerToByteArray(val, bytes) { } if (val < 0 || val >= Math.pow(256, bytes)) { - throw new Error('Value out of bounds'); + throw new Error('Value out of bounds '); } var newBytes = [ ]; @@ -140,7 +130,7 @@ function convertIntegerToByteArray(val, bytes) { } return newBytes; -}; +} // Convert a certain number of bytes from the serialized object ('so') into an integer. function readAndSum(so, bytes) { @@ -157,7 +147,7 @@ function readAndSum(so, bytes) { // Convert to unsigned integer return sum >>> 0; -}; +} var STInt8 = exports.Int8 = new SerializedType({ serialize: function (so, val) { @@ -201,41 +191,25 @@ var STInt64 = exports.Int64 = new SerializedType({ if (val < 0) { throw new Error('Negative value for unsigned Int64 is invalid.'); } - bigNumObject = new BigInteger(String(val), 10); + bigNumObject = new sjcl.bn(val, 10); } else if (isString(val)) { if (!isHexInt64String(val)) { throw new Error('Not a valid hex Int64.'); } - bigNumObject = new BigInteger(val, 16); - } else if (isBigInteger(val)) { - if (val.compareTo(BigInteger.ZERO) < 0) { + bigNumObject = new sjcl.bn(val, 16); + } else if (val instanceof sjcl.bn) { + if (!val.greaterEquals(0)) { throw new Error('Negative value for unsigned Int64 is invalid.'); } bigNumObject = val; } else { throw new Error('Invalid type for Int64'); } - - var hex = bigNumObject.toString(16); - - if (hex.length > 16) { - throw new Error('Int64 is too large'); - } - - while (hex.length < 16) { - hex = '0' + hex; - } - - serializeHex(so, hex, true); //noLength = true + serializeBits(so, bigNumObject.toBits(64), true); //noLength = true }, parse: function (so) { var bytes = so.read(8); - // We need to add a 0, so if the high bit is set it won't think it's a - // pessimistic numeric fraek. What doth lief? - var result = new BigInteger([0].concat(bytes), 256); - assert(result instanceof BigInteger); - - return result; + return sjcl.bn.fromBits(sjcl.codec.bytes.toBits(bytes)); } }); @@ -247,7 +221,7 @@ var STHash128 = exports.Hash128 = new SerializedType({ if (!hash.is_valid()) { throw new Error('Invalid Hash128'); } - serializeHex(so, hash.to_hex(), true); //noLength = true + serializeBits(so, hash.to_bits(), true); //noLength = true }, parse: function (so) { return UInt128.from_bytes(so.read(16)); @@ -262,7 +236,7 @@ var STHash256 = exports.Hash256 = new SerializedType({ if (!hash.is_valid()) { throw new Error('Invalid Hash256'); } - serializeHex(so, hash.to_hex(), true); //noLength = true + serializeBits(so, hash.to_bits(), true); //noLength = true }, parse: function (so) { return UInt256.from_bytes(so.read(32)); @@ -277,7 +251,7 @@ var STHash160 = exports.Hash160 = new SerializedType({ if (!hash.is_valid()) { throw new Error('Invalid Hash160'); } - serializeHex(so, hash.to_hex(), true); //noLength = true + serializeBits(so, hash.to_bits(), true); //noLength = true }, parse: function (so) { return UInt160.from_bytes(so.read(20)); @@ -288,7 +262,7 @@ STHash160.id = 17; // Internal var STCurrency = new SerializedType({ - serialize: function (so, val, xrp_as_ascii) { + serialize: function (so, val) { var currencyData = val.to_bytes(); if (!currencyData) { @@ -317,11 +291,14 @@ var STAmount = exports.Amount = new SerializedType({ throw new Error('Not a valid Amount object.'); } + var value = new BigNumber(amount.to_text()); + var offset = value.e - 15; + // Amount (64-bit integer) var valueBytes = utils.arraySet(8, 0); if (amount.is_native()) { - var valueHex = amount._value.toString(16); + var valueHex = value.absoluteValue().toString(16); // Enforce correct length (64 bits) if (valueHex.length > 16) { @@ -332,7 +309,7 @@ var STAmount = exports.Amount = new SerializedType({ valueHex = '0' + valueHex; } - valueBytes = bytes.fromBits(hex.toBits(valueHex)); + valueBytes = sjcl.codec.bytes.fromBits(sjcl.codec.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. @@ -354,10 +331,16 @@ var STAmount = exports.Amount = new SerializedType({ } // Next eight bits: offset/exponent - hi |= ((97 + amount._offset) & 0xff) << 22; + hi |= ((97 + offset) & 0xff) << 22; + // Remaining 54 bits: mantissa - hi |= amount._value.shiftRight(32).intValue() & 0x3fffff; - lo = amount._value.intValue() & 0xffffffff; + var mantissaDecimal = utils.getMantissaDecimalString(value.abs()); + var mantissaHex = (new BigNumber(mantissaDecimal)).toString(16); + assert(mantissaHex.length <= 16, + 'Mantissa hex representation ' + mantissaHex + + ' exceeds the maximum length of 16'); + hi |= parseInt(mantissaHex.slice(0, -8), 16) & 0x3fffff; + lo = parseInt(mantissaHex.slice(-8), 16); } valueBytes = sjcl.codec.bytes.fromBits([hi, lo]); @@ -375,7 +358,6 @@ var STAmount = exports.Amount = new SerializedType({ } }, parse: function (so) { - var amount = new Amount(); var value_bytes = so.read(8); var is_zero = !(value_bytes[0] & 0x7f); @@ -383,6 +365,8 @@ var STAmount = exports.Amount = new SerializedType({ is_zero = is_zero && !value_bytes[i]; } + var is_negative = !is_zero && !(value_bytes[0] & 0x40); + if (value_bytes[0] & 0x80) { //non-native var currency = STCurrency.parse(so); @@ -392,26 +376,23 @@ var STAmount = exports.Amount = new SerializedType({ var offset = ((value_bytes[0] & 0x3f) << 2) + (value_bytes[1] >>> 6) - 97; var mantissa_bytes = value_bytes.slice(1); mantissa_bytes[0] &= 0x3f; - var value = new BigInteger(mantissa_bytes, 256); - - if (value.equals(BigInteger.ZERO) && !is_zero ) { - throw new Error('Invalid zero representation'); - } - - amount._value = value; - amount._offset = offset; - amount._currency = currency; - amount._issuer = issuer; - amount._is_native = false; + var mantissa = new BigNumber(utils.arrayToHex(mantissa_bytes), 16); + var sign = is_negative ? '-' : ''; + var valueString = sign + mantissa.toString() + 'e' + offset.toString(); + + return Amount.from_json({ + currency: currency, + issuer: issuer.to_json(), + value: valueString + }); } else { //native var integer_bytes = value_bytes.slice(); integer_bytes[0] &= 0x3f; - amount._value = new BigInteger(integer_bytes, 256); - amount._is_native = true; + var integer_hex = utils.arrayToHex(integer_bytes); + var value = new BigNumber(integer_hex, 16); + return Amount.from_json((is_negative ? '-' : '') + value.toString()); } - amount._is_negative = !is_zero && !(value_bytes[0] & 0x40); - return amount; } }); @@ -440,7 +421,7 @@ var STAccount = exports.Account = new SerializedType({ if (!account.is_valid()) { throw new Error('Invalid account!'); } - serializeHex(so, account.to_hex()); + serializeBits(so, account.to_bits()); }, parse: function (so) { var len = this.parse_varint(so); @@ -493,7 +474,7 @@ var STPathSet = exports.PathSet = new SerializedType({ STInt8.serialize(so, type); if (entry.account) { - so.append(UInt160.from_json(entry.account).to_bytes()); + STHash160.serialize(so, entry.account); } if (entry.currency) { @@ -502,7 +483,7 @@ var STPathSet = exports.PathSet = new SerializedType({ } if (entry.issuer) { - so.append(UInt160.from_json(entry.issuer).to_bytes()); + STHash160.serialize(so, entry.issuer); } } } @@ -572,7 +553,7 @@ var STPathSet = exports.PathSet = new SerializedType({ if (entry.account || entry.currency || entry.issuer) { entry.type = type; - entry.type_hex = ("000000000000000" + type.toString(16)).slice(-16); + entry.type_hex = ('000000000000000' + type.toString(16)).slice(-16); current_path.push(entry); } else { @@ -593,7 +574,7 @@ STPathSet.id = 18; var STVector256 = exports.Vector256 = new SerializedType({ serialize: function (so, val) { //Assume val is an array of STHash256 objects. - var length_as_varint = SerializedType.serialize_varint(so, val.length * 32); + SerializedType.serialize_varint(so, val.length * 32); for (var i=0, l=val.length; i= 0) { - this._value = new BigInteger(String(j)); + this._value = new sjcl.bn(j); } this._update(); @@ -243,51 +243,31 @@ UInt.prototype.parse_number = function(j) { // Convert from internal form. UInt.prototype.to_bytes = function() { - if (!(this._value instanceof BigInteger)) { + if (!this.is_valid()) { 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; + return sjcl.codec.bytes.fromBits(this.to_bits()); }; UInt.prototype.to_hex = function() { - if (!(this._value instanceof BigInteger)) { + if (!this.is_valid()) { return null; } - - var bytes = this.to_bytes(); - return sjcl.codec.hex.fromBits(sjcl.codec.bytes.toBits(bytes)).toUpperCase(); + return sjcl.codec.hex.fromBits(this.to_bits()).toUpperCase(); }; UInt.prototype.to_json = UInt.prototype.to_hex; UInt.prototype.to_bits = function() { - if (!(this._value instanceof BigInteger)) { + if (!this.is_valid()) { return null; } - var bytes = this.to_bytes(); - - return sjcl.codec.bytes.toBits(bytes); + return this._value.toBits(this.constructor.width * 8); }; UInt.prototype.to_bn = function() { - if (!(this._value instanceof BigInteger)) { + if (!this.is_valid()) { return null; } diff --git a/src/js/ripple/uint128.js b/src/js/ripple/uint128.js index fbf875dd45..5f5f45f53a 100644 --- a/src/js/ripple/uint128.js +++ b/src/js/ripple/uint128.js @@ -7,7 +7,6 @@ var UInt = require('./uint').UInt; // var UInt128 = extend(function () { - // Internal form: NaN or BigInteger this._value = NaN; }, UInt); diff --git a/src/js/ripple/uint160.js b/src/js/ripple/uint160.js index a3d02c7b22..ffe90c4cb5 100644 --- a/src/js/ripple/uint160.js +++ b/src/js/ripple/uint160.js @@ -2,8 +2,6 @@ var utils = require('./utils'); var config = require('./config'); var extend = require('extend'); -var BigInteger = utils.jsbn.BigInteger; - var UInt = require('./uint').UInt; var Base = require('./base').Base; @@ -12,7 +10,6 @@ var Base = require('./base').Base; // var UInt160 = extend(function() { - // Internal form: NaN or BigInteger this._value = NaN; this._version_byte = void(0); this._update(); @@ -49,7 +46,7 @@ UInt160.prototype.parse_json = function(j) { // Allow raw numbers - DEPRECATED // This is used mostly by the test suite and is supported // as a legacy feature only. DO NOT RELY ON THIS BEHAVIOR. - this._value = new BigInteger(String(j)); + this.parse_number(j); this._version_byte = Base.VER_ACCOUNT_ID; } else if (typeof j !== 'string') { this._value = NaN; @@ -83,7 +80,7 @@ UInt160.prototype.parse_generic = function(j) { UInt160.prototype.to_json = function(opts) { opts = opts || {}; - if (this._value instanceof BigInteger) { + if (this.is_valid()) { // If this value has a type, return a Base58 encoded string. if (typeof this._version_byte === 'number') { var output = Base.encode_check(this._version_byte, this.to_bytes()); diff --git a/src/js/ripple/uint256.js b/src/js/ripple/uint256.js index e179765482..72b1e5a010 100644 --- a/src/js/ripple/uint256.js +++ b/src/js/ripple/uint256.js @@ -7,7 +7,6 @@ var UInt = require('./uint').UInt; // var UInt256 = extend(function() { - // Internal form: NaN or BigInteger this._value = NaN; }, UInt); diff --git a/src/js/ripple/utils.js b/src/js/ripple/utils.js index 393fca318d..3f30af9066 100644 --- a/src/js/ripple/utils.js +++ b/src/js/ripple/utils.js @@ -1,3 +1,15 @@ + +function getMantissaDecimalString(bignum) { + var mantissa = bignum.toPrecision(16) + .replace(/\./, '') // remove decimal point + .replace(/e.*/, '') // remove scientific notation + .replace(/^0*/, ''); // remove leading zeroes + while (mantissa.length < 16) { + mantissa += '0'; // add trailing zeroes until length is 16 + } + return mantissa; +} + function filterErr(code, done) { return function(e) { done(e.code !== code ? e : void(0)); @@ -69,6 +81,13 @@ function hexToArray(h) { return stringToArray(hexToString(h)); }; +function arrayToHex(a) { + return a.map(function(byteValue) { + var hex = byteValue.toString(16); + return hex.length > 1 ? hex : '0' + hex; + }).join(''); +} + function chunkString(str, n, leftAlign) { var ret = []; var i=0, len=str.length; @@ -144,15 +163,16 @@ exports.hexToString = hexToString; exports.hexToArray = hexToArray; exports.stringToArray = stringToArray; exports.stringToHex = stringToHex; +exports.arrayToHex = arrayToHex; exports.chunkString = chunkString; exports.assert = assert; exports.arrayUnique = arrayUnique; exports.toTimestamp = toTimestamp; exports.fromTimestamp = fromTimestamp; +exports.getMantissaDecimalString = getMantissaDecimalString; // Going up three levels is needed to escape the src-cov folder used for the // test coverage stuff. exports.sjcl = require('../../../build/sjcl'); -exports.jsbn = require('../../../src/js/jsbn/jsbn'); // vim:sw=2:sts=2:ts=8:et diff --git a/test/amount-test.js b/test/amount-test.js index 1e04829bd6..c078e2d29d 100644 --- a/test/amount-test.js +++ b/test/amount-test.js @@ -1,5 +1,4 @@ var assert = require('assert'); -var BigInteger = require('ripple-lib').jsbn.BigInteger; var Amount = require('ripple-lib').Amount; var UInt160 = require('ripple-lib').UInt160; var load_config = require('ripple-lib').config.load; @@ -127,6 +126,9 @@ describe('Amount', function() { it('1 XRP human', function() { assert.strictEqual(Amount.from_human("1 XRP").to_human_full(), '1/XRP'); }); + it('1XRP human', function() { + assert.strictEqual(Amount.from_human('1XRP').to_human_full(), '1/XRP'); + }); it('0.1 XRP', function() { assert.strictEqual(Amount.from_human("0.1 XRP").to_text_full(), '0.1/XRP'); }); @@ -281,14 +283,11 @@ describe('Amount', function() { }); }); describe('UInt160', function() { - it('Parse 0', function () { - assert.deepEqual(new BigInteger(), UInt160.from_generic('0')._value); - }); it('Parse 0 export', function () { assert.strictEqual(UInt160.ACCOUNT_ZERO, UInt160.from_generic('0').set_version(0).to_json()); }); it('Parse 1', function () { - assert.deepEqual(new BigInteger([1]), UInt160.from_generic('1')._value); + assert.deepEqual(UInt160.ACCOUNT_ONE, UInt160.from_generic('1').set_version(0).to_json()); }); it('Parse rrrrrrrrrrrrrrrrrrrrrhoLvTp export', function () { assert.strictEqual(UInt160.ACCOUNT_ZERO, UInt160.from_json('rrrrrrrrrrrrrrrrrrrrrhoLvTp').to_json()); @@ -973,7 +972,7 @@ describe('Amount', function() { assert.strictEqual(Amount.from_json('10000000').product_human(Amount.from_json('10')).to_text_full(), '0.0001/XRP'); }); it('Multiply USD with XAU (dem)', function () { - assert.strictEqual(Amount.from_json('2000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').product_human(Amount.from_json('10/015841551A748AD2C1F76FF6ECB0CCCD00000000/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'), {reference_date: 443845330 + 31535000}).to_text_full(), '19900.00316303882/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'); + assert.strictEqual(Amount.from_json('2000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').product_human(Amount.from_json('10/015841551A748AD2C1F76FF6ECB0CCCD00000000/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'), {reference_date: 443845330 + 31535000}).to_text_full(), '19900.00316303883/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'); }); it('Multiply 0 XRP with 0 XRP human', function () { assert.strictEqual('0/XRP', Amount.from_json('0').product_human(Amount.from_json('0')).to_human_full()); @@ -1045,7 +1044,7 @@ describe('Amount', function() { assert.strictEqual(Amount.from_json('10000000').product_human(Amount.from_json('10')).to_human_full(), '0.0001/XRP'); }); it('Multiply USD with XAU (dem) human', function () { - assert.strictEqual(Amount.from_json('2000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').product_human(Amount.from_json('10/015841551A748AD2C1F76FF6ECB0CCCD00000000/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'), {reference_date: 443845330 + 31535000}).to_human_full(), '19,900.00316303882/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'); + assert.strictEqual(Amount.from_json('2000/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh').product_human(Amount.from_json('10/015841551A748AD2C1F76FF6ECB0CCCD00000000/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'), {reference_date: 443845330 + 31535000}).to_human_full(), '19,900.00316303883/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'); }); }); @@ -1220,7 +1219,6 @@ describe('Amount', function() { it ('from_json minimum IOU', function() { var amt = Amount.from_json('-1e-81/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'); - assert.strictEqual(amt._value.toString(), Amount.bi_man_min_value.toString()); assert.strictEqual(amt.to_text(), '-1000000000000000e-96'); assert.strictEqual(amt.to_text(), Amount.min_value); }); @@ -1233,7 +1231,6 @@ describe('Amount', function() { it ('from_json maximum IOU', function() { var amt = Amount.from_json('9999999999999999e80/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'); - assert.strictEqual(amt._value.toString(), Amount.bi_man_max_value.toString()); assert.strictEqual(amt.to_text(), '9999999999999999e80'); }); @@ -1245,13 +1242,11 @@ describe('Amount', function() { it ('from_json normalize mantissa to valid max range, lost significant digits', function() { var amt = Amount.from_json('99999999999999999999999999999999/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'); - assert.strictEqual(amt._value.toString(), Amount.bi_man_max_value.toString()); assert.strictEqual(amt.to_text(), '9999999999999999e16'); }); it ('from_json normalize mantissa to min valid range, lost significant digits', function() { var amt = Amount.from_json('-0.0000000000000000000000001/USD/rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'); - assert.strictEqual(amt._value.toString(), Amount.bi_man_min_value.toString()); assert.strictEqual(amt.to_text(), '-1000000000000000e-40'); }); }); diff --git a/test/orderbook-test.js b/test/orderbook-test.js index 026e8bde50..80a34117fa 100644 --- a/test/orderbook-test.js +++ b/test/orderbook-test.js @@ -1566,8 +1566,8 @@ describe('OrderBook', function() { }, index: 'B6BC3B0F87976370EE11F5575593FE63AA5DC1D602830DC96F04B2D597F044BF', owner_funds: '0.1129267125000245', - taker_gets_funded: '0.1127013098802639', - taker_pays_funded: '55.95620035555102', + taker_gets_funded: '0.112701309880264', + taker_pays_funded: '55.95620035555106', is_fully_funded: false }, { Account: 'raudnGKfTK23YKfnS7ixejHrqGERTYNFXk', diff --git a/test/serializedtypes-test.js b/test/serializedtypes-test.js index 372dd2d540..4c22143e68 100644 --- a/test/serializedtypes-test.js +++ b/test/serializedtypes-test.js @@ -2,7 +2,7 @@ var assert = require('assert'); var SerializedObject = require('ripple-lib').SerializedObject; var types = require('ripple-lib').types; var Amount = require('ripple-lib').Amount; -var BigInteger = require('ripple-lib').jsbn.BigInteger; +var sjcl = require('ripple-lib').sjcl; describe('Serialized types', function() { describe('Int8', function() { @@ -287,7 +287,7 @@ describe('Serialized types', function() { var so = new SerializedObject("8B2386F26F8E232B"); var num = types.Int64.parse(so); // We get a positive number - assert.strictEqual(num.toString(16), '8b2386f26f8e232b'); + assert.strictEqual(num.toString(), '0x8b2386f26f8e232b'); }); it('Serialize "0123456789ABCDEF"', function () { var so = new SerializedObject(); @@ -299,15 +299,15 @@ describe('Serialized types', function() { types.Int64.serialize(so, 'F0E1D2C3B4A59687'); assert.strictEqual(so.to_hex(), 'F0E1D2C3B4A59687'); }); - it('Serialize BigInteger("FFEEDDCCBBAA9988")', function () { + it('Serialize bn("FFEEDDCCBBAA9988")', function () { var so = new SerializedObject(); - types.Int64.serialize(so, new BigInteger('FFEEDDCCBBAA9988', 16)); + types.Int64.serialize(so, new sjcl.bn('FFEEDDCCBBAA9988', 16)); assert.strictEqual(so.to_hex(), 'FFEEDDCCBBAA9988'); }); - it('Fail to serialize BigInteger("-1")', function () { + it('Fail to serialize BigNumber("-1")', function () { var so = new SerializedObject(); assert.throws(function () { - types.Int64.serialize(so, new BigInteger('-1', 10)); + types.Int64.serialize(so, new BigNumber('-1', 10)); }); }); it('Fail to serialize "10000000000000000"', function () { @@ -343,7 +343,7 @@ describe('Serialized types', function() { it('Parse "0123456789ABCDEF"', function () { var so = new SerializedObject("0123456789ABCDEF"); var num = types.Int64.parse(so); - assert.strictEqual(num.toString(10), '81985529216486895'); + assert.strictEqual(num.toString(), '0x123456789abcdef'); }); }); diff --git a/test/uint-test.js b/test/uint-test.js index d1e1ec3f84..29026bf05a 100644 --- a/test/uint-test.js +++ b/test/uint-test.js @@ -9,8 +9,8 @@ describe('UInt', function() { assert.strictEqual(val.to_hex(), '00000000000000000000000000000000'); }); it('should create 00000000000000000000000000000001 when called with 1', function () { - var val = UInt128.from_number(0); - assert.strictEqual(val.to_hex(), '00000000000000000000000000000000'); + var val = UInt128.from_number(1); + assert.strictEqual(val.to_hex(), '00000000000000000000000000000001'); }); it('should create 000000000000000000000000FFFFFFFF when called with 0xFFFFFFFF', function () { var val = UInt128.from_number(0xFFFFFFFF);