0) {
+ if(p < this.DB && (d = this[i]>>p) > 0) { m = true; r = int2char(d); }
+ while(i >= 0) {
+ if(p < k) {
+ d = (this[i]&((1<>(p+=this.DB-k);
+ }
+ else {
+ d = (this[i]>>(p-=k))&km;
+ if(p <= 0) { p += this.DB; --i; }
+ }
+ if(d > 0) m = true;
+ if(m) r += int2char(d);
+ }
+ }
+ return m?r:"0";
+ }
+
+ // (public) -this
+ function bnNegate() { var r = nbi(); BigInteger.ZERO.subTo(this,r); return r; }
+
+ // (public) |this|
+ function bnAbs() { return (this.s<0)?this.negate():this; }
+
+ // (public) return + if this > a, - if this < a, 0 if equal
+ function bnCompareTo(a) {
+ var r = this.s-a.s;
+ if(r != 0) return r;
+ var i = this.t;
+ r = i-a.t;
+ if(r != 0) return (this.s<0)?-r:r;
+ while(--i >= 0) if((r=this[i]-a[i]) != 0) return r;
+ return 0;
+ }
+
+ // returns bit length of the integer x
+ function nbits(x) {
+ var r = 1, t;
+ if((t=x>>>16) != 0) { x = t; r += 16; }
+ if((t=x>>8) != 0) { x = t; r += 8; }
+ if((t=x>>4) != 0) { x = t; r += 4; }
+ if((t=x>>2) != 0) { x = t; r += 2; }
+ if((t=x>>1) != 0) { x = t; r += 1; }
+ return r;
+ }
+
+ // (public) return the number of bits in "this"
+ function bnBitLength() {
+ if(this.t <= 0) return 0;
+ return this.DB*(this.t-1)+nbits(this[this.t-1]^(this.s&this.DM));
+ }
+
+ // (protected) r = this << n*DB
+ function bnpDLShiftTo(n,r) {
+ var i;
+ for(i = this.t-1; i >= 0; --i) r[i+n] = this[i];
+ for(i = n-1; i >= 0; --i) r[i] = 0;
+ r.t = this.t+n;
+ r.s = this.s;
+ }
+
+ // (protected) r = this >> n*DB
+ function bnpDRShiftTo(n,r) {
+ for(var i = n; i < this.t; ++i) r[i-n] = this[i];
+ r.t = Math.max(this.t-n,0);
+ r.s = this.s;
+ }
+
+ // (protected) r = this << n
+ function bnpLShiftTo(n,r) {
+ var bs = n%this.DB;
+ var cbs = this.DB-bs;
+ var bm = (1<= 0; --i) {
+ r[i+ds+1] = (this[i]>>cbs)|c;
+ c = (this[i]&bm)<= 0; --i) r[i] = 0;
+ r[ds] = c;
+ r.t = this.t+ds+1;
+ r.s = this.s;
+ r.clamp();
+ }
+
+ // (protected) r = this >> n
+ function bnpRShiftTo(n,r) {
+ r.s = this.s;
+ var ds = Math.floor(n/this.DB);
+ if(ds >= this.t) { r.t = 0; return; }
+ var bs = n%this.DB;
+ var cbs = this.DB-bs;
+ var bm = (1<>bs;
+ for(var i = ds+1; i < this.t; ++i) {
+ r[i-ds-1] |= (this[i]&bm)<>bs;
+ }
+ if(bs > 0) r[this.t-ds-1] |= (this.s&bm)<>= this.DB;
+ }
+ if(a.t < this.t) {
+ c -= a.s;
+ while(i < this.t) {
+ c += this[i];
+ r[i++] = c&this.DM;
+ c >>= this.DB;
+ }
+ c += this.s;
+ }
+ else {
+ c += this.s;
+ while(i < a.t) {
+ c -= a[i];
+ r[i++] = c&this.DM;
+ c >>= this.DB;
+ }
+ c -= a.s;
+ }
+ r.s = (c<0)?-1:0;
+ if(c < -1) r[i++] = this.DV+c;
+ else if(c > 0) r[i++] = c;
+ r.t = i;
+ r.clamp();
+ }
+
+ // (protected) r = this * a, r != this,a (HAC 14.12)
+ // "this" should be the larger one if appropriate.
+ function bnpMultiplyTo(a,r) {
+ var x = this.abs(), y = a.abs();
+ var i = x.t;
+ r.t = i+y.t;
+ while(--i >= 0) r[i] = 0;
+ for(i = 0; i < y.t; ++i) r[i+x.t] = x.am(0,y[i],r,i,0,x.t);
+ r.s = 0;
+ r.clamp();
+ if(this.s != a.s) BigInteger.ZERO.subTo(r,r);
+ }
+
+ // (protected) r = this^2, r != this (HAC 14.16)
+ function bnpSquareTo(r) {
+ var x = this.abs();
+ var i = r.t = 2*x.t;
+ while(--i >= 0) r[i] = 0;
+ for(i = 0; i < x.t-1; ++i) {
+ var c = x.am(i,x[i],r,2*i,0,1);
+ if((r[i+x.t]+=x.am(i+1,2*x[i],r,2*i+1,c,x.t-i-1)) >= x.DV) {
+ r[i+x.t] -= x.DV;
+ r[i+x.t+1] = 1;
+ }
+ }
+ if(r.t > 0) r[r.t-1] += x.am(i,x[i],r,2*i,0,1);
+ r.s = 0;
+ r.clamp();
+ }
+
+ // (protected) divide this by m, quotient and remainder to q, r (HAC 14.20)
+ // r != q, this != m. q or r may be null.
+ function bnpDivRemTo(m,q,r) {
+ var pm = m.abs();
+ if(pm.t <= 0) return;
+ var pt = this.abs();
+ if(pt.t < pm.t) {
+ if(q != null) q.fromInt(0);
+ if(r != null) this.copyTo(r);
+ return;
+ }
+ if(r == null) r = nbi();
+ var y = nbi(), ts = this.s, ms = m.s;
+ var nsh = this.DB-nbits(pm[pm.t-1]); // normalize modulus
+ if(nsh > 0) { pm.lShiftTo(nsh,y); pt.lShiftTo(nsh,r); }
+ else { pm.copyTo(y); pt.copyTo(r); }
+ var ys = y.t;
+ var y0 = y[ys-1];
+ if(y0 == 0) return;
+ var yt = y0*(1<1)?y[ys-2]>>this.F2:0);
+ var d1 = this.FV/yt, d2 = (1<= 0) {
+ r[r.t++] = 1;
+ r.subTo(t,r);
+ }
+ BigInteger.ONE.dlShiftTo(ys,t);
+ t.subTo(y,y); // "negative" y so we can replace sub with am later
+ while(y.t < ys) y[y.t++] = 0;
+ while(--j >= 0) {
+ // Estimate quotient digit
+ var qd = (r[--i]==y0)?this.DM:Math.floor(r[i]*d1+(r[i-1]+e)*d2);
+ if((r[i]+=y.am(0,qd,r,j,0,ys)) < qd) { // Try it out
+ y.dlShiftTo(j,t);
+ r.subTo(t,r);
+ while(r[i] < --qd) r.subTo(t,r);
+ }
+ }
+ if(q != null) {
+ r.drShiftTo(ys,q);
+ if(ts != ms) BigInteger.ZERO.subTo(q,q);
+ }
+ r.t = ys;
+ r.clamp();
+ if(nsh > 0) r.rShiftTo(nsh,r); // Denormalize remainder
+ if(ts < 0) BigInteger.ZERO.subTo(r,r);
+ }
+
+ // (public) this mod a
+ function bnMod(a) {
+ var r = nbi();
+ this.abs().divRemTo(a,null,r);
+ if(this.s < 0 && r.compareTo(BigInteger.ZERO) > 0) a.subTo(r,r);
+ return r;
+ }
+
+ // Modular reduction using "classic" algorithm
+ function Classic(m) { this.m = m; }
+ function cConvert(x) {
+ if(x.s < 0 || x.compareTo(this.m) >= 0) return x.mod(this.m);
+ else return x;
+ }
+ function cRevert(x) { return x; }
+ function cReduce(x) { x.divRemTo(this.m,null,x); }
+ function cMulTo(x,y,r) { x.multiplyTo(y,r); this.reduce(r); }
+ function cSqrTo(x,r) { x.squareTo(r); this.reduce(r); }
+
+ Classic.prototype.convert = cConvert;
+ Classic.prototype.revert = cRevert;
+ Classic.prototype.reduce = cReduce;
+ Classic.prototype.mulTo = cMulTo;
+ Classic.prototype.sqrTo = cSqrTo;
+
+ // (protected) return "-1/this % 2^DB"; useful for Mont. reduction
+ // justification:
+ // xy == 1 (mod m)
+ // xy = 1+km
+ // xy(2-xy) = (1+km)(1-km)
+ // x[y(2-xy)] = 1-k^2m^2
+ // x[y(2-xy)] == 1 (mod m^2)
+ // if y is 1/x mod m, then y(2-xy) is 1/x mod m^2
+ // should reduce x and y(2-xy) by m^2 at each step to keep size bounded.
+ // JS multiply "overflows" differently from C/C++, so care is needed here.
+ function bnpInvDigit() {
+ if(this.t < 1) return 0;
+ var x = this[0];
+ if((x&1) == 0) return 0;
+ var y = x&3; // y == 1/x mod 2^2
+ y = (y*(2-(x&0xf)*y))&0xf; // y == 1/x mod 2^4
+ y = (y*(2-(x&0xff)*y))&0xff; // y == 1/x mod 2^8
+ y = (y*(2-(((x&0xffff)*y)&0xffff)))&0xffff; // y == 1/x mod 2^16
+ // last step - calculate inverse mod DV directly;
+ // assumes 16 < DB <= 32 and assumes ability to handle 48-bit ints
+ y = (y*(2-x*y%this.DV))%this.DV; // y == 1/x mod 2^dbits
+ // we really want the negative inverse, and -DV < y < DV
+ return (y>0)?this.DV-y:-y;
+ }
+
+ // Montgomery reduction
+ function Montgomery(m) {
+ this.m = m;
+ this.mp = m.invDigit();
+ this.mpl = this.mp&0x7fff;
+ this.mph = this.mp>>15;
+ this.um = (1<<(m.DB-15))-1;
+ this.mt2 = 2*m.t;
+ }
+
+ // xR mod m
+ function montConvert(x) {
+ var r = nbi();
+ x.abs().dlShiftTo(this.m.t,r);
+ r.divRemTo(this.m,null,r);
+ if(x.s < 0 && r.compareTo(BigInteger.ZERO) > 0) this.m.subTo(r,r);
+ return r;
+ }
+
+ // x/R mod m
+ function montRevert(x) {
+ var r = nbi();
+ x.copyTo(r);
+ this.reduce(r);
+ return r;
+ }
+
+ // x = x/R mod m (HAC 14.32)
+ function montReduce(x) {
+ while(x.t <= this.mt2) // pad x so am has enough room later
+ x[x.t++] = 0;
+ for(var i = 0; i < this.m.t; ++i) {
+ // faster way of calculating u0 = x[i]*mp mod DV
+ var j = x[i]&0x7fff;
+ var u0 = (j*this.mpl+(((j*this.mph+(x[i]>>15)*this.mpl)&this.um)<<15))&x.DM;
+ // use am to combine the multiply-shift-add into one call
+ j = i+this.m.t;
+ x[j] += this.m.am(0,u0,x,i,0,this.m.t);
+ // propagate carry
+ while(x[j] >= x.DV) { x[j] -= x.DV; x[++j]++; }
+ }
+ x.clamp();
+ x.drShiftTo(this.m.t,x);
+ if(x.compareTo(this.m) >= 0) x.subTo(this.m,x);
+ }
+
+ // r = "x^2/R mod m"; x != r
+ function montSqrTo(x,r) { x.squareTo(r); this.reduce(r); }
+
+ // r = "xy/R mod m"; x,y != r
+ function montMulTo(x,y,r) { x.multiplyTo(y,r); this.reduce(r); }
+
+ Montgomery.prototype.convert = montConvert;
+ Montgomery.prototype.revert = montRevert;
+ Montgomery.prototype.reduce = montReduce;
+ Montgomery.prototype.mulTo = montMulTo;
+ Montgomery.prototype.sqrTo = montSqrTo;
+
+ // (protected) true iff this is even
+ function bnpIsEven() { return ((this.t>0)?(this[0]&1):this.s) == 0; }
+
+ // (protected) this^e, e < 2^32, doing sqr and mul with "r" (HAC 14.79)
+ function bnpExp(e,z) {
+ if(e > 0xffffffff || e < 1) return BigInteger.ONE;
+ var r = nbi(), r2 = nbi(), g = z.convert(this), i = nbits(e)-1;
+ g.copyTo(r);
+ while(--i >= 0) {
+ z.sqrTo(r,r2);
+ if((e&(1< 0) z.mulTo(r2,g,r);
+ else { var t = r; r = r2; r2 = t; }
+ }
+ return z.revert(r);
+ }
+
+ // (public) this^e % m, 0 <= e < 2^32
+ function bnModPowInt(e,m) {
+ var z;
+ if(e < 256 || m.isEven()) z = new Classic(m); else z = new Montgomery(m);
+ return this.exp(e,z);
+ }
+
+ // protected
+ BigInteger.prototype.copyTo = bnpCopyTo;
+ BigInteger.prototype.fromInt = bnpFromInt;
+ BigInteger.prototype.fromString = bnpFromString;
+ BigInteger.prototype.clamp = bnpClamp;
+ BigInteger.prototype.dlShiftTo = bnpDLShiftTo;
+ BigInteger.prototype.drShiftTo = bnpDRShiftTo;
+ BigInteger.prototype.lShiftTo = bnpLShiftTo;
+ BigInteger.prototype.rShiftTo = bnpRShiftTo;
+ BigInteger.prototype.subTo = bnpSubTo;
+ BigInteger.prototype.multiplyTo = bnpMultiplyTo;
+ BigInteger.prototype.squareTo = bnpSquareTo;
+ BigInteger.prototype.divRemTo = bnpDivRemTo;
+ BigInteger.prototype.invDigit = bnpInvDigit;
+ BigInteger.prototype.isEven = bnpIsEven;
+ BigInteger.prototype.exp = bnpExp;
+
+ // public
+ BigInteger.prototype.toString = bnToString;
+ BigInteger.prototype.negate = bnNegate;
+ BigInteger.prototype.abs = bnAbs;
+ BigInteger.prototype.compareTo = bnCompareTo;
+ BigInteger.prototype.bitLength = bnBitLength;
+ BigInteger.prototype.mod = bnMod;
+ BigInteger.prototype.modPowInt = bnModPowInt;
+
+ // "constants"
+ BigInteger.ZERO = nbv(0);
+ BigInteger.ONE = nbv(1);
+
+ // jsbn2 stuff
+
+ // (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) 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) 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];
+ }
+ }
+
+ // (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;
+ }
+
+ // (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))<>= 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();
+ }
+
+ BigInteger.prototype.fromRadix = bnpFromRadix;
+ BigInteger.prototype.chunkSize = bnpChunkSize;
+ BigInteger.prototype.signum = bnSigNum;
+ BigInteger.prototype.dMultiply = bnpDMultiply;
+ BigInteger.prototype.dAddOffset = bnpDAddOffset;
+ BigInteger.prototype.toRadix = bnpToRadix;
+ BigInteger.prototype.intValue = bnIntValue;
+ BigInteger.prototype.addTo = bnpAddTo;
+
+ //======= end jsbn =======
+
+ // Emscripten wrapper
+ var Wrapper = {
+ abs: function(l, h) {
+ var x = new goog.math.Long(l, h);
+ var ret;
+ if (x.isNegative()) {
+ ret = x.negate();
+ } else {
+ ret = x;
+ }
+ HEAP32[tempDoublePtr>>2] = ret.low_;
+ HEAP32[tempDoublePtr+4>>2] = ret.high_;
+ },
+ ensureTemps: function() {
+ if (Wrapper.ensuredTemps) return;
+ Wrapper.ensuredTemps = true;
+ Wrapper.two32 = new BigInteger();
+ Wrapper.two32.fromString('4294967296', 10);
+ Wrapper.two64 = new BigInteger();
+ Wrapper.two64.fromString('18446744073709551616', 10);
+ Wrapper.temp1 = new BigInteger();
+ Wrapper.temp2 = new BigInteger();
+ },
+ lh2bignum: function(l, h) {
+ var a = new BigInteger();
+ a.fromString(h.toString(), 10);
+ var b = new BigInteger();
+ a.multiplyTo(Wrapper.two32, b);
+ var c = new BigInteger();
+ c.fromString(l.toString(), 10);
+ var d = new BigInteger();
+ c.addTo(b, d);
+ return d;
+ },
+ stringify: function(l, h, unsigned) {
+ var ret = new goog.math.Long(l, h).toString();
+ if (unsigned && ret[0] == '-') {
+ // unsign slowly using jsbn bignums
+ Wrapper.ensureTemps();
+ var bignum = new BigInteger();
+ bignum.fromString(ret, 10);
+ ret = new BigInteger();
+ Wrapper.two64.addTo(bignum, ret);
+ ret = ret.toString(10);
+ }
+ return ret;
+ },
+ fromString: function(str, base, min, max, unsigned) {
+ Wrapper.ensureTemps();
+ var bignum = new BigInteger();
+ bignum.fromString(str, base);
+ var bigmin = new BigInteger();
+ bigmin.fromString(min, 10);
+ var bigmax = new BigInteger();
+ bigmax.fromString(max, 10);
+ if (unsigned && bignum.compareTo(BigInteger.ZERO) < 0) {
+ var temp = new BigInteger();
+ bignum.addTo(Wrapper.two64, temp);
+ bignum = temp;
+ }
+ var error = false;
+ if (bignum.compareTo(bigmin) < 0) {
+ bignum = bigmin;
+ error = true;
+ } else if (bignum.compareTo(bigmax) > 0) {
+ bignum = bigmax;
+ error = true;
+ }
+ var ret = goog.math.Long.fromString(bignum.toString()); // min-max checks should have clamped this to a range goog.math.Long can handle well
+ HEAP32[tempDoublePtr>>2] = ret.low_;
+ HEAP32[tempDoublePtr+4>>2] = ret.high_;
+ if (error) throw 'range error';
+ }
+ };
+ return Wrapper;
+})();
+
+//======= end closure i64 code =======
+
+
+
+// === Auto-generated postamble setup entry stuff ===
+
+if (memoryInitializer) {
+ if (typeof Module['locateFile'] === 'function') {
+ memoryInitializer = Module['locateFile'](memoryInitializer);
+ } else if (Module['memoryInitializerPrefixURL']) {
+ memoryInitializer = Module['memoryInitializerPrefixURL'] + memoryInitializer;
+ }
+ if (ENVIRONMENT_IS_NODE || ENVIRONMENT_IS_SHELL) {
+ var data = Module['readBinary'](memoryInitializer);
+ HEAPU8.set(data, STATIC_BASE);
+ } else {
+ addRunDependency('memory initializer');
+ Browser.asyncLoad(memoryInitializer, function(data) {
+ HEAPU8.set(data, STATIC_BASE);
+ removeRunDependency('memory initializer');
+ }, function(data) {
+ throw 'could not load memory initializer ' + memoryInitializer;
+ });
+ }
+}
+
+function ExitStatus(status) {
+ this.name = "ExitStatus";
+ this.message = "Program terminated with exit(" + status + ")";
+ this.status = status;
+};
+ExitStatus.prototype = new Error();
+ExitStatus.prototype.constructor = ExitStatus;
+
+var initialStackTop;
+var preloadStartTime = null;
+var calledMain = false;
+
+dependenciesFulfilled = function runCaller() {
+ // If run has never been called, and we should call run (INVOKE_RUN is true, and Module.noInitialRun is not false)
+ if (!Module['calledRun'] && shouldRunNow) run();
+ if (!Module['calledRun']) dependenciesFulfilled = runCaller; // try this again later, after new deps are fulfilled
+}
+
+Module['callMain'] = Module.callMain = function callMain(args) {
+ assert(runDependencies == 0, 'cannot call main when async dependencies remain! (listen on __ATMAIN__)');
+ assert(__ATPRERUN__.length == 0, 'cannot call main when preRun functions remain to be called');
+
+ args = args || [];
+
+ ensureInitRuntime();
+
+ var argc = args.length+1;
+ function pad() {
+ for (var i = 0; i < 4-1; i++) {
+ argv.push(0);
+ }
+ }
+ var argv = [allocate(intArrayFromString(Module['thisProgram']), 'i8', ALLOC_NORMAL) ];
+ pad();
+ for (var i = 0; i < argc-1; i = i + 1) {
+ argv.push(allocate(intArrayFromString(args[i]), 'i8', ALLOC_NORMAL));
+ pad();
+ }
+ argv.push(0);
+ argv = allocate(argv, 'i32', ALLOC_NORMAL);
+
+ initialStackTop = STACKTOP;
+
+ try {
+
+ var ret = Module['_main'](argc, argv, 0);
+
+
+ // if we're not running an evented main loop, it's time to exit
+ exit(ret);
+ }
+ catch(e) {
+ if (e instanceof ExitStatus) {
+ // exit() throws this once it's done to make sure execution
+ // has been stopped completely
+ return;
+ } else if (e == 'SimulateInfiniteLoop') {
+ // running an evented main loop, don't immediately exit
+ Module['noExitRuntime'] = true;
+ return;
+ } else {
+ if (e && typeof e === 'object' && e.stack) Module.printErr('exception thrown: ' + [e, e.stack]);
+ throw e;
+ }
+ } finally {
+ calledMain = true;
+ }
+}
+
+
+
+
+function run(args) {
+ args = args || Module['arguments'];
+
+ if (preloadStartTime === null) preloadStartTime = Date.now();
+
+ if (runDependencies > 0) {
+ return;
+ }
+
+ preRun();
+
+ if (runDependencies > 0) return; // a preRun added a dependency, run will be called later
+ if (Module['calledRun']) return; // run may have just been called through dependencies being fulfilled just in this very frame
+
+ function doRun() {
+ if (Module['calledRun']) return; // run may have just been called while the async setStatus time below was happening
+ Module['calledRun'] = true;
+
+ if (ABORT) return;
+
+ ensureInitRuntime();
+
+ preMain();
+
+ if (ENVIRONMENT_IS_WEB && preloadStartTime !== null) {
+ Module.printErr('pre-main prep time: ' + (Date.now() - preloadStartTime) + ' ms');
+ }
+
+ if (Module['onRuntimeInitialized']) Module['onRuntimeInitialized']();
+
+ if (Module['_main'] && shouldRunNow) Module['callMain'](args);
+
+ postRun();
+ }
+
+ if (Module['setStatus']) {
+ Module['setStatus']('Running...');
+ setTimeout(function() {
+ setTimeout(function() {
+ Module['setStatus']('');
+ }, 1);
+ doRun();
+ }, 1);
+ } else {
+ doRun();
+ }
+}
+Module['run'] = Module.run = run;
+
+function exit(status) {
+ if (Module['noExitRuntime']) {
+ return;
+ }
+
+ ABORT = true;
+ EXITSTATUS = status;
+ STACKTOP = initialStackTop;
+
+ // exit the runtime
+ exitRuntime();
+
+ if (ENVIRONMENT_IS_NODE) {
+ // Work around a node.js bug where stdout buffer is not flushed at process exit:
+ // Instead of process.exit() directly, wait for stdout flush event.
+ // See https://github.com/joyent/node/issues/1669 and https://github.com/kripken/emscripten/issues/2582
+ // Workaround is based on https://github.com/RReverser/acorn/commit/50ab143cecc9ed71a2d66f78b4aec3bb2e9844f6
+ process['stdout']['once']('drain', function () {
+ process['exit'](status);
+ });
+ console.log(' '); // Make sure to print something to force the drain event to occur, in case the stdout buffer was empty.
+ // Work around another node bug where sometimes 'drain' is never fired - make another effort
+ // to emit the exit status, after a significant delay (if node hasn't fired drain by then, give up)
+ setTimeout(function() {
+ process['exit'](status);
+ }, 500);
+ } else
+ if (ENVIRONMENT_IS_SHELL && typeof quit === 'function') {
+ quit(status);
+ }
+ // if we reach here, we must throw an exception to halt the current execution
+ throw new ExitStatus(status);
+}
+Module['exit'] = Module.exit = exit;
+
+function abort(text) {
+ if (text) {
+ Module.print(text);
+ Module.printErr(text);
+ }
+
+ ABORT = true;
+ EXITSTATUS = 1;
+
+ var extra = '\nIf this abort() is unexpected, build with -s ASSERTIONS=1 which can give more information.';
+
+ throw 'abort() at ' + stackTrace() + extra;
+}
+Module['abort'] = Module.abort = abort;
+
+// {{PRE_RUN_ADDITIONS}}
+
+if (Module['preInit']) {
+ if (typeof Module['preInit'] == 'function') Module['preInit'] = [Module['preInit']];
+ while (Module['preInit'].length > 0) {
+ Module['preInit'].pop()();
+ }
+}
+
+// shouldRunNow refers to calling main(), not run().
+var shouldRunNow = true;
+if (Module['noInitialRun']) {
+ shouldRunNow = false;
+}
+
+
+run();
+
+// {{POST_RUN_ADDITIONS}}
+
+
+
+
+
+
+// {{MODULE_ADDITIONS}}
+
+
+
+
+/* vim: ts=4:sw=4:expandtab
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+var axolotlInternal = axolotlInternal || {};
+
+axolotlInternal.curve25519 = function() {
+ 'use strict';
+
+ // Insert some bytes into the emscripten memory and return a pointer
+ function _allocate(bytes) {
+ var address = Module._malloc(bytes.length);
+ Module.HEAPU8.set(bytes, address);
+
+ return address;
+ }
+
+ function _readBytes(address, length, array) {
+ array.set(Module.HEAPU8.subarray(address, address + length));
+ }
+
+ var basepoint = new Uint8Array(32);
+ basepoint[0] = 9;
+
+ return {
+ keyPair: function(privKey) {
+ return new Promise(function(resolve) {
+ var priv = new Uint8Array(privKey);
+ priv[0] &= 248;
+ priv[31] &= 127;
+ priv[31] |= 64
+
+ // Where to store the result
+ var publicKey_ptr = Module._malloc(32);
+
+ // Get a pointer to the private key
+ var privateKey_ptr = _allocate(priv);
+
+ // The basepoint for generating public keys
+ var basepoint_ptr = _allocate(basepoint);
+
+ // The return value is just 0, the operation is done in place
+ var err = Module._curve25519_donna(publicKey_ptr,
+ privateKey_ptr,
+ basepoint_ptr);
+
+ var res = new Uint8Array(32);
+ _readBytes(publicKey_ptr, 32, res);
+
+ Module._free(publicKey_ptr);
+ Module._free(privateKey_ptr);
+ Module._free(basepoint_ptr);
+
+ resolve({ pubKey: res.buffer, privKey: privKey });
+ });
+
+ },
+ sharedSecret: function(pubKey, privKey) {
+ // Where to store the result
+ var sharedKey_ptr = Module._malloc(32);
+
+ // Get a pointer to our private key
+ var privateKey_ptr = _allocate(new Uint8Array(privKey));
+
+ // Get a pointer to their public key, the basepoint when you're
+ // generating a shared secret
+ var basepoint_ptr = _allocate(new Uint8Array(pubKey));
+
+ // Return value is 0 here too of course
+ var err = Module._curve25519_donna(sharedKey_ptr,
+ privateKey_ptr,
+ basepoint_ptr);
+
+ var res = new Uint8Array(32);
+ _readBytes(sharedKey_ptr, 32, res);
+
+ Module._free(sharedKey_ptr);
+ Module._free(privateKey_ptr);
+ Module._free(basepoint_ptr);
+
+ return Promise.resolve(res.buffer);
+ },
+ sign: function(privKey, message) {
+ // Where to store the result
+ var signature_ptr = Module._malloc(64);
+
+ // Get a pointer to our private key
+ var privateKey_ptr = _allocate(new Uint8Array(privKey));
+
+ // Get a pointer to the message
+ var message_ptr = _allocate(new Uint8Array(message));
+
+ var err = Module._curve25519_sign(signature_ptr,
+ privateKey_ptr,
+ message_ptr,
+ message.byteLength);
+
+ var res = new Uint8Array(64);
+ _readBytes(signature_ptr, 64, res);
+
+ Module._free(signature_ptr);
+ Module._free(privateKey_ptr);
+ Module._free(message_ptr);
+
+ return Promise.resolve(res.buffer);
+ },
+ verify: function(pubKey, message, sig) {
+ // Get a pointer to their public key
+ var publicKey_ptr = _allocate(new Uint8Array(pubKey));
+
+ // Get a pointer to the signature
+ var signature_ptr = _allocate(new Uint8Array(sig));
+
+ // Get a pointer to the message
+ var message_ptr = _allocate(new Uint8Array(message));
+
+ var res = Module._curve25519_verify(signature_ptr,
+ publicKey_ptr,
+ message_ptr,
+ message.byteLength);
+
+ Module._free(publicKey_ptr);
+ Module._free(signature_ptr);
+ Module._free(message_ptr);
+
+ return new Promise(function(resolve, reject) {
+ if (res !== 0) {
+ reject(new Error("Invalid signature"));
+ } else {
+ resolve();
+ }
+ });
+ }
+ };
+}();
+
+var axolotlInternal = axolotlInternal || {};
+// I am the worker
+this.onmessage = function(e) {
+ axolotlInternal.curve25519[e.data.methodName].apply(null, e.data.args).then(function(result) {
+ postMessage({ id: e.data.id, result: result });
+ }).catch(function(error) {
+ postMessage({ id: e.data.id, error: error.message });
+ });
+};
+
+})();
\ No newline at end of file
diff --git a/libtextsecure/libaxolotl.js b/libtextsecure/libaxolotl.js
index 0478aa3d..4adbf3a5 100644
--- a/libtextsecure/libaxolotl.js
+++ b/libtextsecure/libaxolotl.js
@@ -25168,33 +25168,36 @@ axolotlInternal.curve25519 = function() {
return {
keyPair: function(privKey) {
- var priv = new Uint8Array(privKey);
- priv[0] &= 248;
- priv[31] &= 127;
- priv[31] |= 64
+ return new Promise(function(resolve) {
+ var priv = new Uint8Array(privKey);
+ priv[0] &= 248;
+ priv[31] &= 127;
+ priv[31] |= 64
- // Where to store the result
- var publicKey_ptr = Module._malloc(32);
+ // Where to store the result
+ var publicKey_ptr = Module._malloc(32);
- // Get a pointer to the private key
- var privateKey_ptr = _allocate(priv);
+ // Get a pointer to the private key
+ var privateKey_ptr = _allocate(priv);
- // The basepoint for generating public keys
- var basepoint_ptr = _allocate(basepoint);
+ // The basepoint for generating public keys
+ var basepoint_ptr = _allocate(basepoint);
- // The return value is just 0, the operation is done in place
- var err = Module._curve25519_donna(publicKey_ptr,
- privateKey_ptr,
- basepoint_ptr);
+ // The return value is just 0, the operation is done in place
+ var err = Module._curve25519_donna(publicKey_ptr,
+ privateKey_ptr,
+ basepoint_ptr);
- var res = new Uint8Array(32);
- _readBytes(publicKey_ptr, 32, res);
+ var res = new Uint8Array(32);
+ _readBytes(publicKey_ptr, 32, res);
- Module._free(publicKey_ptr);
- Module._free(privateKey_ptr);
- Module._free(basepoint_ptr);
+ Module._free(publicKey_ptr);
+ Module._free(privateKey_ptr);
+ Module._free(basepoint_ptr);
+
+ resolve({ pubKey: res.buffer, privKey: privKey });
+ });
- return Promise.resolve({ pubKey: res.buffer, privKey: privKey });
},
sharedSecret: function(pubKey, privKey) {
// Where to store the result
@@ -25275,6 +25278,61 @@ axolotlInternal.curve25519 = function() {
};
}();
+var axolotlInternal = axolotlInternal || {};
+
+// I am the...workee?
+var origCurve25519 = axolotlInternal.curve25519;
+
+axolotlInternal.startWorker = function(url) {
+ axolotlInternal.stopWorker(); // there can be only one
+ axolotlInternal.curve25519 = new Curve25519Worker(url);
+};
+axolotlInternal.stopWorker = function() {
+ if (axolotlInternal.curve25519 instanceof Curve25519Worker) {
+ var worker = axolotlInternal.curve25519.worker;
+ axolotlInternal.curve25519 = origCurve25519;
+ worker.terminate();
+ }
+};
+
+function Curve25519Worker(url) {
+ this.jobs = {};
+ this.jobId = 0;
+ this.worker = new Worker(url);
+ this.worker.onmessage = function(e) {
+ var job = this.jobs[e.data.id];
+ if (e.data.error && typeof job.onerror === 'function') {
+ job.onerror(new Error(e.data.error));
+ } else if (typeof job.onsuccess === 'function') {
+ job.onsuccess(e.data.result);
+ }
+ delete this.jobs[e.data.id];
+ }.bind(this);
+}
+
+Curve25519Worker.prototype = {
+ constructor: Curve25519Worker,
+ postMessage: function(methodName, args, onsuccess, onerror) {
+ return new Promise(function(resolve, reject) {
+ this.jobs[this.jobId] = { onsuccess: resolve, onerror: reject };
+ this.worker.postMessage({ id: this.jobId, methodName: methodName, args: args });
+ this.jobId++;
+ }.bind(this));
+ },
+ keyPair: function(privKey) {
+ return this.postMessage('keyPair', [privKey]);
+ },
+ sharedSecret: function(pubKey, privKey) {
+ return this.postMessage('sharedSecret', [pubKey, privKey]);
+ },
+ sign: function(privKey, message) {
+ return this.postMessage('sign', [privKey, message]);
+ },
+ verify: function(pubKey, message, sig) {
+ return this.postMessage('verify', [pubKey, message, sig]);
+ }
+};
+
;(function(){
/**
* CryptoJS core components.
@@ -36738,7 +36796,57 @@ axolotlInternal.utils = function() {
'use strict';
window.axolotl = window.axolotl || {};
-window.axolotl.protocol = function(storage_interface, updateKeysCallback) {
+function isNonNegativeInteger(n) {
+ return (typeof n === 'number' && (n % 1) === 0 && n >= 0);
+}
+
+window.axolotl.util = {
+ generateIdentityKeyPair: function() {
+ return axolotlInternal.crypto.createKeyPair();
+ },
+
+ generateRegistrationId: function() {
+ var registrationId = new Uint16Array(axolotlInternal.crypto.getRandomBytes(2))[0];
+ return registrationId & 0x3fff;
+ },
+
+ generateSignedPreKey: function (identityKeyPair, signedKeyId) {
+ if (!(identityKeyPair.privKey instanceof ArrayBuffer) ||
+ identityKeyPair.privKey.byteLength != 32 ||
+ !(identityKeyPair.pubKey instanceof ArrayBuffer) ||
+ identityKeyPair.pubKey.byteLength != 33) {
+ throw new TypeError('Invalid argument for identityKeyPair');
+ }
+ if (!isNonNegativeInteger(signedKeyId)) {
+ throw new TypeError(
+ 'Invalid argument for signedKeyId: ' + signedKeyId
+ );
+ }
+
+ return axolotlInternal.crypto.createKeyPair().then(function(keyPair) {
+ return axolotlInternal.crypto.Ed25519Sign(identityKeyPair.privKey, keyPair.pubKey).then(function(sig) {
+ return {
+ keyId : signedKeyId,
+ keyPair : keyPair,
+ signature : sig
+ };
+ });
+ });
+ },
+
+ generatePreKey: function(keyId) {
+ if (!isNonNegativeInteger(keyId)) {
+ throw new TypeError('Invalid argument for keyId: ' + keyId);
+ }
+
+ return axolotlInternal.crypto.createKeyPair().then(function(keyPair) {
+ return { keyId: keyId, keyPair: keyPair };
+ });
+ },
+
+};
+
+window.axolotl.protocol = function(storage_interface) {
var self = {};
/******************************
@@ -36762,150 +36870,139 @@ window.axolotl.protocol = function(storage_interface, updateKeysCallback) {
***************************/
var crypto_storage = {};
- crypto_storage.putKeyPair = function(keyName, keyPair) {
- storage_interface.put("25519Key" + keyName, keyPair);
- }
-
- crypto_storage.getNewStoredKeyPair = function(keyName) {
- return axolotlInternal.crypto.createKeyPair().then(function(keyPair) {
- crypto_storage.putKeyPair(keyName, keyPair);
- return keyPair;
+ function getRecord(encodedNumber) {
+ return storage_interface.getSession(encodedNumber).then(function(serialized) {
+ if (serialized === undefined) {
+ return undefined;
+ }
+ return axolotlInternal.RecipientRecord.deserialize(serialized);
});
}
- crypto_storage.getStoredKeyPair = function(keyName) {
- var res = storage_interface.get("25519Key" + keyName);
- if (res === undefined)
- return undefined;
- return { pubKey: axolotlInternal.utils.convertToArrayBuffer(res.pubKey), privKey: axolotlInternal.utils.convertToArrayBuffer(res.privKey) };
- }
-
- crypto_storage.removeStoredKeyPair = function(keyName) {
- storage_interface.remove("25519Key" + keyName);
- }
-
- crypto_storage.getIdentityKey = function() {
- return this.getStoredKeyPair("identityKey");
- }
-
crypto_storage.saveSession = function(encodedNumber, session, registrationId) {
- var record = storage_interface.sessions.get(encodedNumber);
- if (record === undefined) {
- if (registrationId === undefined)
- throw new Error("Tried to save a session for an existing device that didn't exist");
- else
- record = new axolotl.sessions.RecipientRecord(session.indexInfo.remoteIdentityKey, registrationId);
- }
+ return getRecord(encodedNumber).then(function(record) {
+ if (record === undefined) {
+ if (registrationId === undefined)
+ throw new Error("Tried to save a session for an existing device that didn't exist");
+ else
+ record = new axolotlInternal.RecipientRecord(session.indexInfo.remoteIdentityKey, registrationId);
+ }
+ var sessions = record._sessions;
- var sessions = record._sessions;
+ if (record.identityKey === null)
+ record.identityKey = session.indexInfo.remoteIdentityKey;
+ if (axolotlInternal.utils.convertToString(record.identityKey) !== axolotlInternal.utils.convertToString(session.indexInfo.remoteIdentityKey))
+ throw new Error("Identity key changed at session save time");
- if (record.identityKey === null)
- record.identityKey = session.indexInfo.remoteIdentityKey;
- if (axolotlInternal.utils.convertToString(record.identityKey) !== axolotlInternal.utils.convertToString(session.indexInfo.remoteIdentityKey))
- throw new Error("Identity key changed at session save time");
+ var doDeleteSession = false;
+ if (session.indexInfo.closed != -1) {
+ doDeleteSession = (session.indexInfo.closed < (Date.now() - MESSAGE_LOST_THRESHOLD_MS));
- var doDeleteSession = false;
- if (session.indexInfo.closed != -1) {
- doDeleteSession = (session.indexInfo.closed < (new Date().getTime() - MESSAGE_LOST_THRESHOLD_MS));
-
- if (!doDeleteSession) {
- var keysLeft = false;
- for (var key in session) {
- if (key != "indexInfo" && key != "oldRatchetList" && key != "currentRatchet") {
- keysLeft = true;
- break;
+ if (!doDeleteSession) {
+ var keysLeft = false;
+ for (var key in session) {
+ if (key != "indexInfo" && key != "oldRatchetList" && key != "currentRatchet") {
+ keysLeft = true;
+ break;
+ }
}
- }
- doDeleteSession = !keysLeft;
- console.log((doDeleteSession ? "Deleting " : "Not deleting ") + "closed session which has not yet timed out");
- } else
- console.log("Deleting closed session due to timeout (created at " + session.indexInfo.closed + ")");
- }
+ doDeleteSession = !keysLeft;
+ console.log((doDeleteSession ? "Deleting " : "Not deleting ") + "closed session which has not yet timed out");
+ } else
+ console.log("Deleting closed session due to timeout (created at " + session.indexInfo.closed + ")");
+ }
- if (doDeleteSession)
- delete sessions[axolotlInternal.utils.convertToString(session.indexInfo.baseKey)];
- else
- sessions[axolotlInternal.utils.convertToString(session.indexInfo.baseKey)] = session;
+ if (doDeleteSession)
+ delete sessions[axolotlInternal.utils.convertToString(session.indexInfo.baseKey)];
+ else
+ sessions[axolotlInternal.utils.convertToString(session.indexInfo.baseKey)] = session;
- var openSessionRemaining = false;
- for (var key in sessions)
- if (sessions[key].indexInfo.closed == -1)
- openSessionRemaining = true;
- if (!openSessionRemaining) // Used as a flag to get new pre keys for the next session
- record.registrationId = null;
- else if (record.registrationId === null && registrationId !== undefined)
- record.registrationId = registrationId;
- else if (record.registrationId === null)
- throw new Error("Had open sessions on a record that had no registrationId set");
+ var openSessionRemaining = false;
+ for (var key in sessions)
+ if (sessions[key].indexInfo.closed == -1)
+ openSessionRemaining = true;
+ if (!openSessionRemaining) // Used as a flag to get new pre keys for the next session
+ record.registrationId = null;
+ else if (record.registrationId === null && registrationId !== undefined)
+ record.registrationId = registrationId;
+ else if (record.registrationId === null)
+ throw new Error("Had open sessions on a record that had no registrationId set");
- var identityKey = storage_interface.identityKeys.get(encodedNumber);
- if (identityKey === undefined)
- storage_interface.identityKeys.put(encodedNumber, record.identityKey);
- else if (axolotlInternal.utils.convertToString(identityKey) !== axolotlInternal.utils.convertToString(record.identityKey))
- throw new Error("Tried to change identity key at save time");
+ return storage_interface.getIdentityKey(encodedNumber).then(function(identityKey) {
+ if (identityKey !== undefined && axolotlInternal.utils.convertToString(identityKey) !== axolotlInternal.utils.convertToString(record.identityKey))
+ throw new Error("Tried to change identity key at save time");
- storage_interface.sessions.put(encodedNumber, record);
+ return storage_interface.putIdentityKey(encodedNumber, record.identityKey).then(function() {
+ return storage_interface.putSession(encodedNumber, record.serialize());
+ });
+ });
+ });
}
var getSessions = function(encodedNumber) {
- var record = storage_interface.sessions.get(encodedNumber);
- if (record === undefined)
- return undefined;
- return record._sessions;
- }
+ return getRecord(encodedNumber).then(function(record) {
+ if (record === undefined)
+ return undefined;
+ return record._sessions;
+ });
+ };
crypto_storage.getOpenSession = function(encodedNumber) {
- var sessions = getSessions(encodedNumber);
- if (sessions === undefined)
- return undefined;
+ return getSessions(encodedNumber).then(function(sessions) {
+ if (sessions === undefined)
+ return undefined;
- for (var key in sessions)
- if (sessions[key].indexInfo.closed == -1)
- return sessions[key];
- return undefined;
- }
+ for (var key in sessions)
+ if (sessions[key].indexInfo.closed == -1)
+ return sessions[key];
+ return undefined;
+ });
+ };
crypto_storage.getSessionByRemoteEphemeralKey = function(encodedNumber, remoteEphemeralKey) {
- var sessions = getSessions(encodedNumber);
- if (sessions === undefined)
- return undefined;
+ return getSessions(encodedNumber).then(function(sessions) {
+ if (sessions === undefined)
+ return undefined;
- var searchKey = axolotlInternal.utils.convertToString(remoteEphemeralKey);
+ var searchKey = axolotlInternal.utils.convertToString(remoteEphemeralKey);
- var openSession = undefined;
- for (var key in sessions) {
- if (sessions[key].indexInfo.closed == -1) {
- if (openSession !== undefined)
- throw new Error("Datastore inconsistensy: multiple open sessions for " + encodedNumber);
- openSession = sessions[key];
+ var openSession = undefined;
+ for (var key in sessions) {
+ if (sessions[key].indexInfo.closed == -1) {
+ if (openSession !== undefined)
+ throw new Error("Datastore inconsistensy: multiple open sessions for " + encodedNumber);
+ openSession = sessions[key];
+ }
+ if (sessions[key][searchKey] !== undefined)
+ return sessions[key];
}
- if (sessions[key][searchKey] !== undefined)
- return sessions[key];
- }
- if (openSession !== undefined)
- return openSession;
+ if (openSession !== undefined)
+ return openSession;
- return undefined;
+ return undefined;
+ });
}
crypto_storage.getSessionOrIdentityKeyByBaseKey = function(encodedNumber, baseKey) {
- var record = storage_interface.sessions.get(encodedNumber);
- if (record === undefined) {
- var identityKey = storage_interface.identityKeys.get(encodedNumber);
- if (identityKey === undefined)
- return undefined;
- return { indexInfo: { remoteIdentityKey: identityKey } };
- }
- var sessions = record._sessions;
+ return getRecord(encodedNumber).then(function(record) {
+ if (record === undefined) {
+ return storage_interface.getIdentityKey(encodedNumber).then(function(identityKey) {
+ if (identityKey === undefined)
+ return undefined;
+ return { indexInfo: { remoteIdentityKey: identityKey } };
+ });
+ }
+ var sessions = record._sessions;
- var preferredSession = record._sessions[axolotlInternal.utils.convertToString(baseKey)];
- if (preferredSession !== undefined)
- return preferredSession;
+ var preferredSession = record._sessions[axolotlInternal.utils.convertToString(baseKey)];
+ if (preferredSession !== undefined)
+ return preferredSession;
- if (record.identityKey !== undefined)
- return { indexInfo: { remoteIdentityKey: record.identityKey } };
+ if (record.identityKey !== undefined)
+ return { indexInfo: { remoteIdentityKey: record.identityKey } };
- throw new Error("Datastore inconsistency: device was stored without identity key");
+ throw new Error("Datastore inconsistency: device was stored without identity key");
+ });
}
/*****************************
@@ -36948,7 +37045,7 @@ window.axolotl.protocol = function(storage_interface, updateKeysCallback) {
}
var initSession = function(isInitiator, ourEphemeralKey, ourSignedKey, encodedNumber, theirIdentityPubKey, theirEphemeralPubKey, theirSignedPubKey) {
- var ourIdentityKey = crypto_storage.getIdentityKey();
+ var ourIdentityKey = storage_interface.getMyIdentityKey();
if (isInitiator) {
if (ourSignedKey !== undefined)
@@ -37034,7 +37131,7 @@ window.axolotl.protocol = function(storage_interface, updateKeysCallback) {
var ratchet = axolotlInternal.utils.convertToString(entry.ephemeralKey);
console.log("Checking old chain with added time " + (entry.added/1000));
if ((!objectContainsKeys(session[ratchet].messageKeys) && (session[ratchet].chainKey === undefined || session[ratchet].chainKey.key === undefined))
- || entry.added < new Date().getTime() - MESSAGE_LOST_THRESHOLD_MS) {
+ || entry.added < Date.now() - MESSAGE_LOST_THRESHOLD_MS) {
delete session[ratchet];
console.log("...deleted");
} else
@@ -37057,7 +37154,7 @@ window.axolotl.protocol = function(storage_interface, updateKeysCallback) {
for (var i in session) {
if (session[i].chainKey !== undefined && session[i].chainKey.key !== undefined) {
if (!sessionClosedByRemote)
- session.oldRatchetList[session.oldRatchetList.length] = { added: new Date().getTime(), ephemeralKey: i };
+ session.oldRatchetList[session.oldRatchetList.length] = { added: Date.now(), ephemeralKey: i };
else
delete session[i].chainKey.key;
}
@@ -37065,61 +37162,63 @@ window.axolotl.protocol = function(storage_interface, updateKeysCallback) {
// Delete current root key and our ephemeral key pair to disallow ratchet stepping
delete session.currentRatchet['rootKey'];
delete session.currentRatchet['ephemeralKeyPair'];
- session.indexInfo.closed = new Date().getTime();
+ session.indexInfo.closed = Date.now();
removeOldChains(session);
}
self.closeOpenSessionForDevice = function(encodedNumber) {
- var session = crypto_storage.getOpenSession(encodedNumber);
- if (session === undefined)
- return;
+ return crypto_storage.getOpenSession(encodedNumber).then(function(session) {
+ if (session === undefined)
+ return;
- closeSession(session);
- crypto_storage.saveSession(encodedNumber, session);
+ closeSession(session);
+ return crypto_storage.saveSession(encodedNumber, session);
+ });
}
- var refreshPreKeys;
var initSessionFromPreKeyWhisperMessage = function(encodedNumber, message) {
- var preKeyPair = crypto_storage.getStoredKeyPair("preKey" + message.preKeyId);
- var signedPreKeyPair = crypto_storage.getStoredKeyPair("signedKey" + message.signedPreKeyId);
+ return storage_interface.getPreKey(message.preKeyId).then(function(preKeyPair) {
+ return storage_interface.getSignedPreKey(message.signedPreKeyId).then(function(signedPreKeyPair) {
+ return crypto_storage.getSessionOrIdentityKeyByBaseKey(encodedNumber, axolotlInternal.utils.convertToArrayBuffer(message.baseKey)).then(function(session) {
+ return crypto_storage.getOpenSession(encodedNumber).then(function(open_session) {
+ if (signedPreKeyPair === undefined) {
+ // Session may or may not be the right one, but if its not, we can't do anything about it
+ // ...fall through and let decryptWhisperMessage handle that case
+ if (session !== undefined && session.currentRatchet !== undefined)
+ return Promise.resolve([session, undefined]);
+ else
+ throw new Error("Missing Signed PreKey for PreKeyWhisperMessage");
+ }
+ if (session !== undefined) {
+ // Duplicate PreKeyMessage for session:
+ if (isEqual(session.indexInfo.baseKey, message.baseKey, false))
+ return Promise.resolve([session, undefined]);
- //TODO: Call refreshPreKeys when it looks like all our prekeys are used up?
-
- var session = crypto_storage.getSessionOrIdentityKeyByBaseKey(encodedNumber, axolotlInternal.utils.convertToArrayBuffer(message.baseKey));
- var open_session = crypto_storage.getOpenSession(encodedNumber);
- if (signedPreKeyPair === undefined) {
- // Session may or may not be the right one, but if its not, we can't do anything about it
- // ...fall through and let decryptWhisperMessage handle that case
- if (session !== undefined && session.currentRatchet !== undefined)
- return Promise.resolve([session, undefined]);
- else
- throw new Error("Missing Signed PreKey for PreKeyWhisperMessage");
- }
- if (session !== undefined) {
- // Duplicate PreKeyMessage for session:
- if (isEqual(session.indexInfo.baseKey, message.baseKey, false))
- return Promise.resolve([session, undefined]);
-
- // We already had a session/known identity key:
- if (isEqual(session.indexInfo.remoteIdentityKey, message.identityKey, false)) {
- // If the identity key matches the previous one, close the previous one and use the new one
- if (open_session !== undefined)
- closeSession(open_session); // To be returned and saved later
- } else {
- // ...otherwise create an error that the UI will pick up and ask the user if they want to re-negotiate
- throw new textsecure.IncomingIdentityKeyError(encodedNumber, axolotlInternal.utils.convertToString(message.encode()));
- }
- }
- return initSession(false, preKeyPair, signedPreKeyPair, encodedNumber, axolotlInternal.utils.convertToArrayBuffer(message.identityKey), axolotlInternal.utils.convertToArrayBuffer(message.baseKey), undefined)
- .then(function(new_session) {
- // Note that the session is not actually saved until the very end of decryptWhisperMessage
- // ... to ensure that the sender actually holds the private keys for all reported pubkeys
- return [new_session, function() {
- if (open_session !== undefined)
- crypto_storage.saveSession(encodedNumber, open_session);
- crypto_storage.removeStoredKeyPair("preKey" + message.preKeyId);
- }];
- });;
+ // We already had a session/known identity key:
+ if (isEqual(session.indexInfo.remoteIdentityKey, message.identityKey, false)) {
+ // If the identity key matches the previous one, close the previous one and use the new one
+ if (open_session !== undefined)
+ closeSession(open_session); // To be returned and saved later
+ } else {
+ // ...otherwise create an error that the UI will pick up and ask the user if they want to re-negotiate
+ throw new textsecure.IncomingIdentityKeyError(encodedNumber, axolotlInternal.utils.convertToString(message.encode()));
+ }
+ }
+ return initSession(false, preKeyPair, signedPreKeyPair, encodedNumber, axolotlInternal.utils.convertToArrayBuffer(message.identityKey), axolotlInternal.utils.convertToArrayBuffer(message.baseKey), undefined)
+ .then(function(new_session) {
+ // Note that the session is not actually saved until the very end of decryptWhisperMessage
+ // ... to ensure that the sender actually holds the private keys for all reported pubkeys
+ return [new_session, function() {
+ return storage_interface.removePreKey(message.preKeyId).then(function() {
+ if (open_session !== undefined)
+ return crypto_storage.saveSession(encodedNumber, open_session);
+ });
+ }];
+ });
+ });
+ });
+ });
+ });
}
var fillMessageKeys = function(chain, counter) {
@@ -37177,7 +37276,7 @@ window.axolotl.protocol = function(storage_interface, updateKeysCallback) {
if (!objectContainsKeys(previousRatchet.messageKeys))
delete session[axolotlInternal.utils.convertToString(ratchet.lastRemoteEphemeralKey)];
else
- session.oldRatchetList[session.oldRatchetList.length] = { added: new Date().getTime(), ephemeralKey: ratchet.lastRemoteEphemeralKey };
+ session.oldRatchetList[session.oldRatchetList.length] = { added: Date.now(), ephemeralKey: ratchet.lastRemoteEphemeralKey };
}).then(finish);
} else
return finish();
@@ -37193,50 +37292,58 @@ window.axolotl.protocol = function(storage_interface, updateKeysCallback) {
var message = axolotlInternal.protobuf.WhisperMessage.decode(messageProto, 'binary');
var remoteEphemeralKey = axolotlInternal.utils.convertToArrayBuffer(message.ephemeralKey);
+ var promise;
if (session === undefined) {
- var session = crypto_storage.getSessionByRemoteEphemeralKey(encodedNumber, remoteEphemeralKey);
- if (session === undefined)
- throw new Error("No session found to decrypt message from " + encodedNumber);
+ promise = crypto_storage.getSessionByRemoteEphemeralKey(encodedNumber, remoteEphemeralKey).then(function(session) {
+ if (session === undefined)
+ throw new Error("No session found to decrypt message from " + encodedNumber);
+ return session;
+ });
+ } else {
+ promise = Promise.resolve(session);
}
- return maybeStepRatchet(session, remoteEphemeralKey, message.previousCounter).then(function() {
- var chain = session[axolotlInternal.utils.convertToString(message.ephemeralKey)];
+ return promise.then(function(session) {
+ return maybeStepRatchet(session, remoteEphemeralKey, message.previousCounter).then(function() {
+ var chain = session[axolotlInternal.utils.convertToString(message.ephemeralKey)];
- return fillMessageKeys(chain, message.counter).then(function() {
- return HKDF(axolotlInternal.utils.convertToArrayBuffer(chain.messageKeys[message.counter]), '', "WhisperMessageKeys").then(function(keys) {
- delete chain.messageKeys[message.counter];
+ return fillMessageKeys(chain, message.counter).then(function() {
+ return HKDF(axolotlInternal.utils.convertToArrayBuffer(chain.messageKeys[message.counter]), '', "WhisperMessageKeys").then(function(keys) {
+ delete chain.messageKeys[message.counter];
- var messageProtoArray = axolotlInternal.utils.convertToArrayBuffer(messageProto);
- var macInput = new Uint8Array(messageProtoArray.byteLength + 33*2 + 1);
- macInput.set(new Uint8Array(axolotlInternal.utils.convertToArrayBuffer(session.indexInfo.remoteIdentityKey)));
- macInput.set(new Uint8Array(axolotlInternal.utils.convertToArrayBuffer(crypto_storage.getIdentityKey().pubKey)), 33);
- macInput[33*2] = (3 << 4) | 3;
- macInput.set(new Uint8Array(messageProtoArray), 33*2 + 1);
+ var messageProtoArray = axolotlInternal.utils.convertToArrayBuffer(messageProto);
+ var macInput = new Uint8Array(messageProtoArray.byteLength + 33*2 + 1);
+ macInput.set(new Uint8Array(axolotlInternal.utils.convertToArrayBuffer(session.indexInfo.remoteIdentityKey)));
+ macInput.set(new Uint8Array(axolotlInternal.utils.convertToArrayBuffer(storage_interface.getMyIdentityKey().pubKey)), 33);
+ macInput[33*2] = (3 << 4) | 3;
+ macInput.set(new Uint8Array(messageProtoArray), 33*2 + 1);
- return verifyMAC(macInput.buffer, keys[1], mac).then(function() {
- return axolotlInternal.crypto.decrypt(keys[0], axolotlInternal.utils.convertToArrayBuffer(message.ciphertext), keys[2].slice(0, 16))
- .then(function(paddedPlaintext) {
+ return verifyMAC(macInput.buffer, keys[1], mac).then(function() {
+ return axolotlInternal.crypto.decrypt(keys[0], axolotlInternal.utils.convertToArrayBuffer(message.ciphertext), keys[2].slice(0, 16))
+ .then(function(paddedPlaintext) {
- paddedPlaintext = new Uint8Array(paddedPlaintext);
- var plaintext;
- for (var i = paddedPlaintext.length - 1; i >= 0; i--) {
- if (paddedPlaintext[i] == 0x80) {
- plaintext = new Uint8Array(i);
- plaintext.set(paddedPlaintext.subarray(0, i));
- plaintext = plaintext.buffer;
- break;
- } else if (paddedPlaintext[i] != 0x00)
- throw new Error('Invalid padding');
- }
+ paddedPlaintext = new Uint8Array(paddedPlaintext);
+ var plaintext;
+ for (var i = paddedPlaintext.length - 1; i >= 0; i--) {
+ if (paddedPlaintext[i] == 0x80) {
+ plaintext = new Uint8Array(i);
+ plaintext.set(paddedPlaintext.subarray(0, i));
+ plaintext = plaintext.buffer;
+ break;
+ } else if (paddedPlaintext[i] != 0x00)
+ throw new Error('Invalid padding');
+ }
- delete session['pendingPreKey'];
- removeOldChains(session);
- crypto_storage.saveSession(encodedNumber, session, registrationId);
- return [plaintext, function() {
- closeSession(session, true);
+ delete session['pendingPreKey'];
removeOldChains(session);
- crypto_storage.saveSession(encodedNumber, session);
- }];
+ return crypto_storage.saveSession(encodedNumber, session, registrationId).then(function() {
+ return [plaintext, function() {
+ closeSession(session, true);
+ removeOldChains(session);
+ return crypto_storage.saveSession(encodedNumber, session);
+ }];
+ });
+ });
});
});
});
@@ -37260,7 +37367,7 @@ window.axolotl.protocol = function(storage_interface, updateKeysCallback) {
return initSessionFromPreKeyWhisperMessage(from, preKeyProto).then(function(sessions) {
return doDecryptWhisperMessage(from, axolotlInternal.utils.convertToString(preKeyProto.message), sessions[0], preKeyProto.registrationId).then(function(result) {
if (sessions[1] !== undefined)
- sessions[1]();
+ return sessions[1]().then(function() { return result; });
return result;
});
});
@@ -37268,154 +37375,95 @@ window.axolotl.protocol = function(storage_interface, updateKeysCallback) {
// return Promise(encoded [PreKey]WhisperMessage)
self.encryptMessageFor = function(deviceObject, pushMessageContent) {
- var session = crypto_storage.getOpenSession(deviceObject.encodedNumber);
- var hadSession = session !== undefined;
+ return crypto_storage.getOpenSession(deviceObject.encodedNumber).then(function(session) {
+ var hadSession = session !== undefined;
- var doEncryptPushMessageContent = function() {
- var msg = new axolotlInternal.protobuf.WhisperMessage();
- var plaintext = axolotlInternal.utils.convertToArrayBuffer(pushMessageContent.encode());
+ var doEncryptPushMessageContent = function() {
+ var msg = new axolotlInternal.protobuf.WhisperMessage();
+ var plaintext = axolotlInternal.utils.convertToArrayBuffer(pushMessageContent.encode());
- var paddedPlaintext = new Uint8Array(Math.ceil((plaintext.byteLength + 1) / 160.0) * 160 - 1);
- paddedPlaintext.set(new Uint8Array(plaintext));
- paddedPlaintext[plaintext.byteLength] = 0x80;
+ var paddedPlaintext = new Uint8Array(Math.ceil((plaintext.byteLength + 1) / 160.0) * 160 - 1);
+ paddedPlaintext.set(new Uint8Array(plaintext));
+ paddedPlaintext[plaintext.byteLength] = 0x80;
- msg.ephemeralKey = axolotlInternal.utils.convertToArrayBuffer(session.currentRatchet.ephemeralKeyPair.pubKey);
- var chain = session[axolotlInternal.utils.convertToString(msg.ephemeralKey)];
+ msg.ephemeralKey = axolotlInternal.utils.convertToArrayBuffer(session.currentRatchet.ephemeralKeyPair.pubKey);
+ var chain = session[axolotlInternal.utils.convertToString(msg.ephemeralKey)];
- return fillMessageKeys(chain, chain.chainKey.counter + 1).then(function() {
- return HKDF(axolotlInternal.utils.convertToArrayBuffer(chain.messageKeys[chain.chainKey.counter]), '', "WhisperMessageKeys").then(function(keys) {
- delete chain.messageKeys[chain.chainKey.counter];
- msg.counter = chain.chainKey.counter;
- msg.previousCounter = session.currentRatchet.previousCounter;
+ return fillMessageKeys(chain, chain.chainKey.counter + 1).then(function() {
+ return HKDF(axolotlInternal.utils.convertToArrayBuffer(chain.messageKeys[chain.chainKey.counter]), '', "WhisperMessageKeys").then(function(keys) {
+ delete chain.messageKeys[chain.chainKey.counter];
+ msg.counter = chain.chainKey.counter;
+ msg.previousCounter = session.currentRatchet.previousCounter;
- return axolotlInternal.crypto.encrypt(keys[0], paddedPlaintext.buffer, keys[2].slice(0, 16)).then(function(ciphertext) {
- msg.ciphertext = ciphertext;
- var encodedMsg = axolotlInternal.utils.convertToArrayBuffer(msg.encode());
+ return axolotlInternal.crypto.encrypt(keys[0], paddedPlaintext.buffer, keys[2].slice(0, 16)).then(function(ciphertext) {
+ msg.ciphertext = ciphertext;
+ var encodedMsg = axolotlInternal.utils.convertToArrayBuffer(msg.encode());
- var macInput = new Uint8Array(encodedMsg.byteLength + 33*2 + 1);
- macInput.set(new Uint8Array(axolotlInternal.utils.convertToArrayBuffer(crypto_storage.getIdentityKey().pubKey)));
- macInput.set(new Uint8Array(axolotlInternal.utils.convertToArrayBuffer(session.indexInfo.remoteIdentityKey)), 33);
- macInput[33*2] = (3 << 4) | 3;
- macInput.set(new Uint8Array(encodedMsg), 33*2 + 1);
+ var macInput = new Uint8Array(encodedMsg.byteLength + 33*2 + 1);
+ macInput.set(new Uint8Array(axolotlInternal.utils.convertToArrayBuffer(storage_interface.getMyIdentityKey().pubKey)));
+ macInput.set(new Uint8Array(axolotlInternal.utils.convertToArrayBuffer(session.indexInfo.remoteIdentityKey)), 33);
+ macInput[33*2] = (3 << 4) | 3;
+ macInput.set(new Uint8Array(encodedMsg), 33*2 + 1);
- return axolotlInternal.crypto.sign(keys[1], macInput.buffer).then(function(mac) {
- var result = new Uint8Array(encodedMsg.byteLength + 9);
- result[0] = (3 << 4) | 3;
- result.set(new Uint8Array(encodedMsg), 1);
- result.set(new Uint8Array(mac, 0, 8), encodedMsg.byteLength + 1);
+ return axolotlInternal.crypto.sign(keys[1], macInput.buffer).then(function(mac) {
+ var result = new Uint8Array(encodedMsg.byteLength + 9);
+ result[0] = (3 << 4) | 3;
+ result.set(new Uint8Array(encodedMsg), 1);
+ result.set(new Uint8Array(mac, 0, 8), encodedMsg.byteLength + 1);
- removeOldChains(session);
+ removeOldChains(session);
- crypto_storage.saveSession(deviceObject.encodedNumber, session, !hadSession ? deviceObject.registrationId : undefined);
- return result;
+ return crypto_storage.saveSession(deviceObject.encodedNumber, session, !hadSession ? deviceObject.registrationId : undefined).then(function() {
+ return result;
+ });
+ });
});
});
});
- });
- }
+ }
- var preKeyMsg = new axolotlInternal.protobuf.PreKeyWhisperMessage();
- preKeyMsg.identityKey = axolotlInternal.utils.convertToArrayBuffer(crypto_storage.getIdentityKey().pubKey);
- preKeyMsg.registrationId = storage_interface.getMyRegistrationId();
+ var preKeyMsg = new axolotlInternal.protobuf.PreKeyWhisperMessage();
+ preKeyMsg.identityKey = axolotlInternal.utils.convertToArrayBuffer(storage_interface.getMyIdentityKey().pubKey);
+ preKeyMsg.registrationId = storage_interface.getMyRegistrationId();
- if (session === undefined) {
- var deviceIdentityKey = axolotlInternal.utils.convertToArrayBuffer(deviceObject.identityKey);
- var deviceSignedKey = axolotlInternal.utils.convertToArrayBuffer(deviceObject.signedKey);
- return axolotlInternal.crypto.Ed25519Verify(deviceIdentityKey, deviceSignedKey, axolotlInternal.utils.convertToArrayBuffer(deviceObject.signedKeySignature)).then(function() {
- return axolotlInternal.crypto.createKeyPair().then(function(baseKey) {
- preKeyMsg.preKeyId = deviceObject.preKeyId;
- preKeyMsg.signedPreKeyId = deviceObject.signedKeyId;
- preKeyMsg.baseKey = axolotlInternal.utils.convertToArrayBuffer(baseKey.pubKey);
- return initSession(true, baseKey, undefined, deviceObject.encodedNumber,
- deviceIdentityKey, axolotlInternal.utils.convertToArrayBuffer(deviceObject.preKey), deviceSignedKey)
- .then(function(new_session) {
- session = new_session;
- session.pendingPreKey = { preKeyId: deviceObject.preKeyId, signedKeyId: deviceObject.signedKeyId, baseKey: baseKey.pubKey };
- return doEncryptPushMessageContent().then(function(message) {
- preKeyMsg.message = message;
- var result = String.fromCharCode((3 << 4) | 3) + axolotlInternal.utils.convertToString(preKeyMsg.encode());
- return {type: 3, body: result};
+ if (session === undefined) {
+ var deviceIdentityKey = axolotlInternal.utils.convertToArrayBuffer(deviceObject.identityKey);
+ var deviceSignedKey = axolotlInternal.utils.convertToArrayBuffer(deviceObject.signedKey);
+ return axolotlInternal.crypto.Ed25519Verify(deviceIdentityKey, deviceSignedKey, axolotlInternal.utils.convertToArrayBuffer(deviceObject.signedKeySignature)).then(function() {
+ return axolotlInternal.crypto.createKeyPair().then(function(baseKey) {
+ preKeyMsg.preKeyId = deviceObject.preKeyId;
+ preKeyMsg.signedPreKeyId = deviceObject.signedKeyId;
+ preKeyMsg.baseKey = axolotlInternal.utils.convertToArrayBuffer(baseKey.pubKey);
+ return initSession(true, baseKey, undefined,
+ deviceObject.encodedNumber, deviceIdentityKey,
+ axolotlInternal.utils.convertToArrayBuffer(deviceObject.preKey),
+ deviceSignedKey).then(function(new_session) {
+ session = new_session;
+ session.pendingPreKey = { preKeyId: deviceObject.preKeyId, signedKeyId: deviceObject.signedKeyId, baseKey: baseKey.pubKey };
+ return doEncryptPushMessageContent().then(function(message) {
+ preKeyMsg.message = message;
+ var result = String.fromCharCode((3 << 4) | 3) + axolotlInternal.utils.convertToString(preKeyMsg.encode());
+ return {type: 3, body: result};
+ });
});
});
});
- });
- } else
- return doEncryptPushMessageContent().then(function(message) {
- if (session.pendingPreKey !== undefined) {
- preKeyMsg.baseKey = axolotlInternal.utils.convertToArrayBuffer(session.pendingPreKey.baseKey);
- preKeyMsg.preKeyId = session.pendingPreKey.preKeyId;
- preKeyMsg.signedPreKeyId = session.pendingPreKey.signedKeyId;
- preKeyMsg.message = message;
+ } else
+ return doEncryptPushMessageContent().then(function(message) {
+ if (session.pendingPreKey !== undefined) {
+ preKeyMsg.baseKey = axolotlInternal.utils.convertToArrayBuffer(session.pendingPreKey.baseKey);
+ preKeyMsg.preKeyId = session.pendingPreKey.preKeyId;
+ preKeyMsg.signedPreKeyId = session.pendingPreKey.signedKeyId;
+ preKeyMsg.message = message;
- var result = String.fromCharCode((3 << 4) | 3) + axolotlInternal.utils.convertToString(preKeyMsg.encode());
- return {type: 3, body: result};
- } else
- return {type: 1, body: axolotlInternal.utils.convertToString(message)};
- });
- }
-
- var GENERATE_KEYS_KEYS_GENERATED = 100;
- self.generateKeys = function() {
- var identityKeyPair = crypto_storage.getIdentityKey();
- var identityKeyCalculated = function(identityKeyPair) {
- var firstPreKeyId = storage_interface.get("maxPreKeyId", 0);
- storage_interface.put("maxPreKeyId", firstPreKeyId + GENERATE_KEYS_KEYS_GENERATED);
-
- var signedKeyId = storage_interface.get("signedKeyId", 0);
- storage_interface.put("signedKeyId", signedKeyId + 1);
-
- var keys = {};
- keys.identityKey = identityKeyPair.pubKey;
- keys.preKeys = [];
-
- var generateKey = function(keyId) {
- return crypto_storage.getNewStoredKeyPair("preKey" + keyId, false).then(function(keyPair) {
- keys.preKeys[keyId] = {keyId: keyId, publicKey: keyPair.pubKey};
+ var result = String.fromCharCode((3 << 4) | 3) + axolotlInternal.utils.convertToString(preKeyMsg.encode());
+ return {type: 3, body: result};
+ } else
+ return {type: 1, body: axolotlInternal.utils.convertToString(message)};
});
- };
-
- var promises = [];
- for (var i = firstPreKeyId; i < firstPreKeyId + GENERATE_KEYS_KEYS_GENERATED; i++)
- promises[i] = generateKey(i);
-
- promises[firstPreKeyId + GENERATE_KEYS_KEYS_GENERATED] = crypto_storage.getNewStoredKeyPair("signedKey" + signedKeyId).then(function(keyPair) {
- return axolotlInternal.crypto.Ed25519Sign(identityKeyPair.privKey, keyPair.pubKey).then(function(sig) {
- keys.signedPreKey = {keyId: signedKeyId, publicKey: keyPair.pubKey, signature: sig};
- });
- });
-
- //TODO: Process by date added and agressively call generateKeys when we get near maxPreKeyId in a message
- crypto_storage.removeStoredKeyPair("signedKey" + (signedKeyId - 2));
-
- return Promise.all(promises).then(function() {
- storage_interface.put("lastPreKeyUpdate", Date.now());
- return keys;
- });
- }
- if (identityKeyPair === undefined)
- return crypto_storage.getNewStoredKeyPair("identityKey").then(function(keyPair) { return identityKeyCalculated(keyPair); });
- else
- return identityKeyCalculated(identityKeyPair);
- }
-
- //TODO: Replace this stuff
- refreshPreKeys = function() {
- self.generateKeys().then(function(keys) {
- console.log("Pre Keys updated!");
- return updateKeysCallback(keys);
- }).catch(function(e) {
- //TODO: Notify the user somehow???
- console.error(e);
});
}
- if (updateKeysCallback)
- window.setInterval(function() {
- // Note that this will not ever run until generateKeys has been called at least once
- if (storage_interface.get("lastPreKeyUpdate", Date.now()) < Date.now() - MESSAGE_LOST_THRESHOLD_MS)
- refreshPreKeys();
- }, 60 * 1000);
-
self.createIdentityKeyRecvSocket = function() {
var socketInfo = {};
var keyPair;
@@ -37436,16 +37484,16 @@ window.axolotl.protocol = function(storage_interface, updateKeysCallback) {
return verifyMAC(ivAndCiphertext, keys[1], mac).then(function() {
return axolotlInternal.crypto.decrypt(keys[0], ciphertext, iv).then(function(plaintext) {
- var identityKeyMsg = axolotlInternal.protobuf.ProvisionMessage.decode(plaintext);
+ var provisionMessage = axolotlInternal.protobuf.ProvisionMessage.decode(plaintext);
- return axolotlInternal.crypto.createKeyPair(axolotlInternal.utils.convertToArrayBuffer(identityKeyMsg.identityKeyPrivate)).then(function(identityKeyPair) {
- if (crypto_storage.getStoredKeyPair("identityKey") !== undefined)
- throw new Error("Tried to overwrite identity key");
-
- crypto_storage.putKeyPair("identityKey", identityKeyPair);
- identityKeyMsg.identityKeyPrivate = null;
-
- return identityKeyMsg;
+ return axolotlInternal.crypto.createKeyPair(
+ provisionMessage.identityKeyPrivate.toArrayBuffer()
+ ).then(function(identityKeyPair) {
+ return {
+ identityKeyPair : identityKeyPair,
+ number : provisionMessage.number,
+ provisioningCode : provisionMessage.provisioningCode
+ };
});
});
});
@@ -37460,6 +37508,32 @@ window.axolotl.protocol = function(storage_interface, updateKeysCallback) {
});
}
+
+ self.getRegistrationId = function(encodedNumber) {
+ return getRecord(encodedNumber).then(function(record) {
+ if (record === undefined) {
+ return undefined;
+ }
+ return record.registrationId;
+ });
+ };
+
+ self.hasOpenSession = function(encodedNumber) {
+ return getRecord(encodedNumber).then(function(record) {
+ if (record === undefined) {
+ return false;
+ }
+ return record.haveOpenSession();
+ });
+ };
+
+ self.startWorker = function(url) {
+ axolotlInternal.startWorker(url);
+ };
+ self.stopWorker = function() {
+ axolotlInternal.stopWorker();
+ };
+
return self;
};
@@ -37582,43 +37656,41 @@ axolotlInternal.protobuf = function() {
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
-;(function() {
-'use strict';
-window.axolotl = window.axolotl || {};
+var axolotlInternal = axolotlInternal || {};
-var RecipientRecord = function(identityKey, registrationId) {
- this._sessions = {};
- this.identityKey = identityKey !== undefined ? axolotlInternal.utils.convertToString(identityKey) : null;
- this.registrationId = registrationId;
+axolotlInternal.RecipientRecord = function() {
+ 'use strict';
- if (this.registrationId === undefined || typeof this.registrationId !== "number")
- this.registrationId = null;
-};
+ var RecipientRecord = function(identityKey, registrationId) {
+ this._sessions = {};
+ this.identityKey = identityKey !== undefined ? axolotlInternal.utils.convertToString(identityKey) : null;
+ this.registrationId = registrationId;
-RecipientRecord.prototype.serialize = function() {
- return axolotlInternal.utils.jsonThing({sessions: this._sessions, registrationId: this.registrationId, identityKey: this.identityKey});
-}
+ if (this.registrationId === undefined || typeof this.registrationId !== "number")
+ this.registrationId = null;
+ };
-RecipientRecord.prototype.deserialize = function(serialized) {
- var data = JSON.parse(serialized);
- this._sessions = data.sessions;
- if (this._sessions === undefined || this._sessions === null || typeof this._sessions !== "object" || Array.isArray(this._sessions))
- throw new Error("Error deserializing RecipientRecord");
- this.identityKey = data.identityKey;
- this.registrationId = data.registrationId;
- if (this.identityKey === undefined || this.registrationId === undefined)
- throw new Error("Error deserializing RecipientRecord");
-}
+ RecipientRecord.prototype.serialize = function() {
+ return axolotlInternal.utils.jsonThing({sessions: this._sessions, registrationId: this.registrationId, identityKey: this.identityKey});
+ }
-RecipientRecord.prototype.haveOpenSession = function() {
- return this.registrationId !== null;
-}
+ RecipientRecord.deserialize = function(serialized) {
+ var data = JSON.parse(serialized);
+ var record = new RecipientRecord(data.identityKey, data.registrationId);
+ record._sessions = data.sessions;
+ if (record._sessions === undefined || record._sessions === null || typeof record._sessions !== "object" || Array.isArray(record._sessions))
+ throw new Error("Error deserializing RecipientRecord");
+ if (record.identityKey === undefined || record.registrationId === undefined)
+ throw new Error("Error deserializing RecipientRecord");
+ return record;
+ }
-window.axolotl.sessions = {
- RecipientRecord: RecipientRecord,
-};
+ RecipientRecord.prototype.haveOpenSession = function() {
+ return this.registrationId !== null;
+ }
-})();
+ return RecipientRecord;
+}();
})();
\ No newline at end of file