diff scripts/minifier/otr/otr.js @ 12:1596660ddf72

Add minifier script for otr.js and its dependencies
author souliane <souliane@mailoo.org>
date Wed, 03 Sep 2014 19:38:05 +0200
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/scripts/minifier/otr/otr.js	Wed Sep 03 19:38:05 2014 +0200
@@ -0,0 +1,2601 @@
+/*!
+
+  otr.js v0.2.12 - 2014-04-15
+  (c) 2014 - Arlo Breault <arlolra@gmail.com>
+  Freely distributed under the MPL v2.0 license.
+
+  This file is concatenated for the browser.
+  Please see: https://github.com/arlolra/otr
+
+*/
+
+;(function (root, factory) {
+
+  if (typeof define === 'function' && define.amd) {
+    define([
+        "bigint"
+      , "crypto"
+      , "eventemitter"
+    ], function (BigInt, CryptoJS, EventEmitter) {
+      var root = {
+          BigInt: BigInt
+        , CryptoJS: CryptoJS
+        , EventEmitter: EventEmitter
+        , OTR: {}
+        , DSA: {}
+      }
+      return factory.call(root)
+    })
+  } else {
+    root.OTR = {}
+    root.DSA = {}
+    factory.call(root)
+  }
+
+}(this, function () {
+
+;(function () {
+  "use strict";
+
+  var root = this
+
+  var CONST = {
+
+    // diffie-heilman
+      N : 'FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B139B22514A08798E3404DDEF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7EDEE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3DC2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F83655D23DCA3AD961C62F356208552BB9ED529077096966D670C354E4ABC9804F1746C08CA237327FFFFFFFFFFFFFFFF'
+    , G : '2'
+
+    // otr message states
+    , MSGSTATE_PLAINTEXT : 0
+    , MSGSTATE_ENCRYPTED : 1
+    , MSGSTATE_FINISHED  : 2
+
+    // otr auth states
+    , AUTHSTATE_NONE               : 0
+    , AUTHSTATE_AWAITING_DHKEY     : 1
+    , AUTHSTATE_AWAITING_REVEALSIG : 2
+    , AUTHSTATE_AWAITING_SIG       : 3
+
+    // whitespace tags
+    , WHITESPACE_TAG    : '\x20\x09\x20\x20\x09\x09\x09\x09\x20\x09\x20\x09\x20\x09\x20\x20'
+    , WHITESPACE_TAG_V2 : '\x20\x20\x09\x09\x20\x20\x09\x20'
+    , WHITESPACE_TAG_V3 : '\x20\x20\x09\x09\x20\x20\x09\x09'
+
+    // otr tags
+    , OTR_TAG       : '?OTR'
+    , OTR_VERSION_1 : '\x00\x01'
+    , OTR_VERSION_2 : '\x00\x02'
+    , OTR_VERSION_3 : '\x00\x03'
+
+    // smp machine states
+    , SMPSTATE_EXPECT0 : 0
+    , SMPSTATE_EXPECT1 : 1
+    , SMPSTATE_EXPECT2 : 2
+    , SMPSTATE_EXPECT3 : 3
+    , SMPSTATE_EXPECT4 : 4
+
+    // unstandard status codes
+    , STATUS_SEND_QUERY  : 0
+    , STATUS_AKE_INIT    : 1
+    , STATUS_AKE_SUCCESS : 2
+    , STATUS_END_OTR     : 3
+
+  }
+
+  if (typeof module !== 'undefined' && module.exports) {
+    module.exports = CONST
+  } else {
+    root.OTR.CONST = CONST
+  }
+
+}).call(this)
+;(function () {
+  "use strict";
+
+  var root = this
+
+  var HLP = {}, CryptoJS, BigInt
+  if (typeof module !== 'undefined' && module.exports) {
+    module.exports = HLP = {}
+    CryptoJS = require('../vendor/crypto.js')
+    BigInt = require('../vendor/bigint.js')
+  } else {
+    if (root.OTR) root.OTR.HLP = HLP
+    if (root.DSA) root.DSA.HLP = HLP
+    CryptoJS = root.CryptoJS
+    BigInt = root.BigInt
+  }
+
+  // data types (byte lengths)
+  var DTS = {
+      BYTE  : 1
+    , SHORT : 2
+    , INT   : 4
+    , CTR   : 8
+    , MAC   : 20
+    , SIG   : 40
+  }
+
+  // otr message wrapper begin and end
+  var WRAPPER_BEGIN = "?OTR"
+    , WRAPPER_END   = "."
+
+  var TWO = BigInt.str2bigInt('2', 10)
+
+  HLP.debug = function (msg) {
+    // used as HLP.debug.call(ctx, msg)
+    if ( this.debug &&
+         typeof this.debug !== 'function' &&
+         typeof console !== 'undefined'
+    ) console.log(msg)
+  }
+
+  HLP.extend = function (child, parent) {
+    for (var key in parent) {
+      if (Object.hasOwnProperty.call(parent, key))
+        child[key] = parent[key]
+    }
+    function Ctor() { this.constructor = child }
+    Ctor.prototype = parent.prototype
+    child.prototype = new Ctor()
+    child.__super__ = parent.prototype
+  }
+
+  // constant-time string comparison
+  HLP.compare = function (str1, str2) {
+    if (str1.length !== str2.length)
+      return false
+    var i = 0, result = 0
+    for (; i < str1.length; i++)
+      result |= str1[i].charCodeAt(0) ^ str2[i].charCodeAt(0)
+    return result === 0
+  }
+
+  HLP.randomExponent = function () {
+    return BigInt.randBigInt(1536)
+  }
+
+  HLP.smpHash = function (version, fmpi, smpi) {
+    var sha256 = CryptoJS.algo.SHA256.create()
+    sha256.update(CryptoJS.enc.Latin1.parse(HLP.packBytes(version, DTS.BYTE)))
+    sha256.update(CryptoJS.enc.Latin1.parse(HLP.packMPI(fmpi)))
+    if (smpi) sha256.update(CryptoJS.enc.Latin1.parse(HLP.packMPI(smpi)))
+    var hash = sha256.finalize()
+    return HLP.bits2bigInt(hash.toString(CryptoJS.enc.Latin1))
+  }
+
+  HLP.makeMac = function (aesctr, m) {
+    var pass = CryptoJS.enc.Latin1.parse(m)
+    var mac = CryptoJS.HmacSHA256(CryptoJS.enc.Latin1.parse(aesctr), pass)
+    return HLP.mask(mac.toString(CryptoJS.enc.Latin1), 0, 160)
+  }
+
+  HLP.make1Mac = function (aesctr, m) {
+    var pass = CryptoJS.enc.Latin1.parse(m)
+    var mac = CryptoJS.HmacSHA1(CryptoJS.enc.Latin1.parse(aesctr), pass)
+    return mac.toString(CryptoJS.enc.Latin1)
+  }
+
+  HLP.encryptAes = function (msg, c, iv) {
+    var opts = {
+        mode: CryptoJS.mode.CTR
+      , iv: CryptoJS.enc.Latin1.parse(iv)
+      , padding: CryptoJS.pad.NoPadding
+    }
+    var aesctr = CryptoJS.AES.encrypt(
+        msg
+      , CryptoJS.enc.Latin1.parse(c)
+      , opts
+    )
+    var aesctr_decoded = CryptoJS.enc.Base64.parse(aesctr.toString())
+    return CryptoJS.enc.Latin1.stringify(aesctr_decoded)
+  }
+
+  HLP.decryptAes = function (msg, c, iv) {
+    msg = CryptoJS.enc.Latin1.parse(msg)
+    var opts = {
+        mode: CryptoJS.mode.CTR
+      , iv: CryptoJS.enc.Latin1.parse(iv)
+      , padding: CryptoJS.pad.NoPadding
+    }
+    return CryptoJS.AES.decrypt(
+        CryptoJS.enc.Base64.stringify(msg)
+      , CryptoJS.enc.Latin1.parse(c)
+      , opts
+    )
+  }
+
+  HLP.multPowMod = function (a, b, c, d, e) {
+    return BigInt.multMod(BigInt.powMod(a, b, e), BigInt.powMod(c, d, e), e)
+  }
+
+  HLP.ZKP = function (v, c, d, e) {
+    return BigInt.equals(c, HLP.smpHash(v, d, e))
+  }
+
+  // greater than, or equal
+  HLP.GTOE = function (a, b) {
+    return (BigInt.equals(a, b) || BigInt.greater(a, b))
+  }
+
+  HLP.between = function (x, a, b) {
+    return (BigInt.greater(x, a) && BigInt.greater(b, x))
+  }
+
+  HLP.checkGroup = function (g, N_MINUS_2) {
+    return HLP.GTOE(g, TWO) && HLP.GTOE(N_MINUS_2, g)
+  }
+
+  HLP.h1 = function (b, secbytes) {
+    var sha1 = CryptoJS.algo.SHA1.create()
+    sha1.update(CryptoJS.enc.Latin1.parse(b))
+    sha1.update(CryptoJS.enc.Latin1.parse(secbytes))
+    return (sha1.finalize()).toString(CryptoJS.enc.Latin1)
+  }
+
+  HLP.h2 = function (b, secbytes) {
+    var sha256 = CryptoJS.algo.SHA256.create()
+    sha256.update(CryptoJS.enc.Latin1.parse(b))
+    sha256.update(CryptoJS.enc.Latin1.parse(secbytes))
+    return (sha256.finalize()).toString(CryptoJS.enc.Latin1)
+  }
+
+  HLP.mask = function (bytes, start, n) {
+    return bytes.substr(start / 8, n / 8)
+  }
+
+  var _toString = String.fromCharCode;
+  HLP.packBytes = function (val, bytes) {
+    val = val.toString(16)
+    var nex, res = ''  // big-endian, unsigned long
+    for (; bytes > 0; bytes--) {
+      nex = val.length ? val.substr(-2, 2) : '0'
+      val = val.substr(0, val.length - 2)
+      res = _toString(parseInt(nex, 16)) + res
+    }
+    return res
+  }
+
+  HLP.packINT = function (d) {
+    return HLP.packBytes(d, DTS.INT)
+  }
+
+  HLP.packCtr = function (d) {
+    return HLP.padCtr(HLP.packBytes(d, DTS.CTR))
+  }
+
+  HLP.padCtr = function (ctr) {
+    return ctr + '\x00\x00\x00\x00\x00\x00\x00\x00'
+  }
+
+  HLP.unpackCtr = function (d) {
+    d = HLP.toByteArray(d.substring(0, 8))
+    return HLP.unpack(d)
+  }
+
+  HLP.unpack = function (arr) {
+    var val = 0, i = 0, len = arr.length
+    for (; i < len; i++) {
+      val = (val * 256) + arr[i]
+    }
+    return val
+  }
+
+  HLP.packData = function (d) {
+    return HLP.packINT(d.length) + d
+  }
+
+  HLP.bits2bigInt = function (bits) {
+    bits = HLP.toByteArray(bits)
+    return BigInt.ba2bigInt(bits)
+  }
+
+  HLP.packMPI = function (mpi) {
+    return HLP.packData(BigInt.bigInt2bits(BigInt.trim(mpi, 0)))
+  }
+
+  HLP.packSHORT = function (short) {
+    return HLP.packBytes(short, DTS.SHORT)
+  }
+
+  HLP.unpackSHORT = function (short) {
+    short = HLP.toByteArray(short)
+    return HLP.unpack(short)
+  }
+
+  HLP.packTLV = function (type, value) {
+    return HLP.packSHORT(type) + HLP.packSHORT(value.length) + value
+  }
+
+  HLP.readLen = function (msg) {
+    msg = HLP.toByteArray(msg.substring(0, 4))
+    return HLP.unpack(msg)
+  }
+
+  HLP.readData = function (data) {
+    var n = HLP.unpack(data.splice(0, 4))
+    return [n, data]
+  }
+
+  HLP.readMPI = function (data) {
+    data = HLP.toByteArray(data)
+    data = HLP.readData(data)
+    return BigInt.ba2bigInt(data[1])
+  }
+
+  HLP.packMPIs = function (arr) {
+    return arr.reduce(function (prv, cur) {
+      return prv + HLP.packMPI(cur)
+    }, '')
+  }
+
+  HLP.unpackMPIs = function (num, mpis) {
+    var i = 0, arr = []
+    for (; i < num; i++) arr.push('MPI')
+    return (HLP.splitype(arr, mpis)).map(function (m) {
+      return HLP.readMPI(m)
+    })
+  }
+
+  HLP.wrapMsg = function (msg, fs, v3, our_it, their_it) {
+    msg = CryptoJS.enc.Base64.stringify(CryptoJS.enc.Latin1.parse(msg))
+    msg = WRAPPER_BEGIN + ":" + msg + WRAPPER_END
+
+    var its
+    if (v3) {
+      its = '|'
+      its += (HLP.readLen(our_it)).toString(16)
+      its += '|'
+      its += (HLP.readLen(their_it)).toString(16)
+    }
+
+    if (!fs) return [null, msg]
+
+    var n = Math.ceil(msg.length / fs)
+    if (n > 65535) return ['Too many fragments']
+    if (n == 1) return [null, msg]
+
+    var k, bi, ei, frag, mf, mfs = []
+    for (k = 1; k <= n; k++) {
+      bi = (k - 1) * fs
+      ei = k * fs
+      frag = msg.slice(bi, ei)
+      mf = WRAPPER_BEGIN
+      if (v3) mf += its
+      mf += ',' + k + ','
+      mf += n + ','
+      mf += frag + ','
+      mfs.push(mf)
+    }
+
+    return [null, mfs]
+  }
+
+  HLP.splitype = function splitype(arr, msg) {
+    var data = []
+    arr.forEach(function (a) {
+      var str
+      switch (a) {
+        case 'PUBKEY':
+          str = splitype(['SHORT', 'MPI', 'MPI', 'MPI', 'MPI'], msg).join('')
+          break
+        case 'DATA':  // falls through
+        case 'MPI':
+          str = msg.substring(0, HLP.readLen(msg) + 4)
+          break
+        default:
+          str = msg.substring(0, DTS[a])
+      }
+      data.push(str)
+      msg = msg.substring(str.length)
+    })
+    return data
+  }
+
+  // https://github.com/msgpack/msgpack-javascript/blob/master/msgpack.js
+
+  var _bin2num = (function () {
+    var i = 0, _bin2num = {}
+    for (; i < 0x100; ++i) {
+      _bin2num[String.fromCharCode(i)] = i  // "\00" -> 0x00
+    }
+    for (i = 0x80; i < 0x100; ++i) {  // [Webkit][Gecko]
+      _bin2num[String.fromCharCode(0xf700 + i)] = i  // "\f780" -> 0x80
+    }
+    return _bin2num
+  }())
+
+  HLP.toByteArray = function (data) {
+    var rv = []
+      , ary = data.split("")
+      , i = -1
+      , iz = ary.length
+      , remain = iz % 8
+
+    while (remain--) {
+      ++i
+      rv[i] = _bin2num[ary[i]]
+    }
+    remain = iz >> 3
+    while (remain--) {
+      rv.push(_bin2num[ary[++i]], _bin2num[ary[++i]],
+              _bin2num[ary[++i]], _bin2num[ary[++i]],
+              _bin2num[ary[++i]], _bin2num[ary[++i]],
+              _bin2num[ary[++i]], _bin2num[ary[++i]])
+    }
+    return rv
+  }
+
+}).call(this)
+;(function () {
+  "use strict";
+
+  var root = this
+
+  var CryptoJS, BigInt, Worker, WWPath, HLP
+  if (typeof module !== 'undefined' && module.exports) {
+    module.exports = DSA
+    CryptoJS = require('../vendor/crypto.js')
+    BigInt = require('../vendor/bigint.js')
+    WWPath = require('path').join(__dirname, '/dsa-webworker.js')
+    HLP = require('./helpers.js')
+  } else {
+    // copy over and expose internals
+    Object.keys(root.DSA).forEach(function (k) {
+      DSA[k] = root.DSA[k]
+    })
+    root.DSA = DSA
+    CryptoJS = root.CryptoJS
+    BigInt = root.BigInt
+    Worker = root.Worker
+    WWPath = 'dsa-webworker.js'
+    HLP = DSA.HLP
+  }
+
+  var ZERO = BigInt.str2bigInt('0', 10)
+    , ONE = BigInt.str2bigInt('1', 10)
+    , TWO = BigInt.str2bigInt('2', 10)
+    , KEY_TYPE = '\x00\x00'
+
+  var DEBUG = false
+  function timer() {
+    var start = (new Date()).getTime()
+    return function (s) {
+      if (!DEBUG || typeof console === 'undefined') return
+      var t = (new Date()).getTime()
+      console.log(s + ': ' + (t - start))
+      start = t
+    }
+  }
+
+  function makeRandom(min, max) {
+    var c = BigInt.randBigInt(BigInt.bitSize(max))
+    if (!HLP.between(c, min, max)) return makeRandom(min, max)
+    return c
+  }
+
+  // altered BigInt.randProbPrime()
+  // n rounds of Miller Rabin (after trial division with small primes)
+  var rpprb = []
+  function isProbPrime(k, n) {
+    var i, B = 30000, l = BigInt.bitSize(k)
+    var primes = BigInt.primes
+
+    if (primes.length === 0)
+      primes = BigInt.findPrimes(B)
+
+    if (rpprb.length != k.length)
+      rpprb = BigInt.dup(k)
+
+    // check ans for divisibility by small primes up to B
+    for (i = 0; (i < primes.length) && (primes[i] <= B); i++)
+      if (BigInt.modInt(k, primes[i]) === 0 && !BigInt.equalsInt(k, primes[i]))
+        return 0
+
+    // do n rounds of Miller Rabin, with random bases less than k
+    for (i = 0; i < n; i++) {
+      BigInt.randBigInt_(rpprb, l, 0)
+      while(!BigInt.greater(k, rpprb))  // pick a random rpprb that's < k
+        BigInt.randBigInt_(rpprb, l, 0)
+      if (!BigInt.millerRabin(k, rpprb))
+        return 0
+    }
+
+    return 1
+  }
+
+  var bit_lengths = {
+      '1024': { N: 160, repeat: 40 }  // 40x should give 2^-80 confidence
+    , '2048': { N: 224, repeat: 56 }
+  }
+
+  var primes = {}
+
+  // follows go lang http://golang.org/src/pkg/crypto/dsa/dsa.go
+  // fips version was removed in 0c99af0df3e7
+  function generatePrimes(bit_length) {
+
+    var t = timer()  // for debugging
+
+    // number of MR tests to perform
+    var repeat = bit_lengths[bit_length].repeat
+
+    var N = bit_lengths[bit_length].N
+
+    var LM1 = BigInt.twoToThe(bit_length - 1)
+    var bl4 = 4 * bit_length
+    var brk = false
+
+    var q, p, rem, counter
+    for (;;) {
+
+      q = BigInt.randBigInt(N, 1)
+      q[0] |= 1
+
+      if (!isProbPrime(q, repeat)) continue
+      t('q')
+
+      for (counter = 0; counter < bl4; counter++) {
+        p = BigInt.randBigInt(bit_length, 1)
+        p[0] |= 1
+
+        rem = BigInt.mod(p, q)
+        rem = BigInt.sub(rem, ONE)
+        p = BigInt.sub(p, rem)
+
+        if (BigInt.greater(LM1, p)) continue
+        if (!isProbPrime(p, repeat)) continue
+
+        t('p')
+        primes[bit_length] = { p: p, q: q }
+        brk = true
+        break
+      }
+
+      if (brk) break
+    }
+
+    var h = BigInt.dup(TWO)
+    var pm1 = BigInt.sub(p, ONE)
+    var e = BigInt.multMod(pm1, BigInt.inverseMod(q, p), p)
+
+    var g
+    for (;;) {
+      g = BigInt.powMod(h, e, p)
+      if (BigInt.equals(g, ONE)) {
+        h = BigInt.add(h, ONE)
+        continue
+      }
+      primes[bit_length].g = g
+      t('g')
+      return
+    }
+
+    throw new Error('Unreachable!')
+  }
+
+  function DSA(obj, opts) {
+    if (!(this instanceof DSA)) return new DSA(obj, opts)
+
+    // options
+    opts = opts || {}
+
+    // inherit
+    if (obj) {
+      var self = this
+      ;['p', 'q', 'g', 'y', 'x'].forEach(function (prop) {
+        self[prop] = obj[prop]
+      })
+      this.type = obj.type || KEY_TYPE
+      return
+    }
+
+    // default to 1024
+    var bit_length = parseInt(opts.bit_length ? opts.bit_length : 1024, 10)
+
+    if (!bit_lengths[bit_length])
+      throw new Error('Unsupported bit length.')
+
+    // set primes
+    if (!primes[bit_length])
+      generatePrimes(bit_length)
+
+    this.p = primes[bit_length].p
+    this.q = primes[bit_length].q
+    this.g = primes[bit_length].g
+
+    // key type
+    this.type = KEY_TYPE
+
+    // private key
+    this.x = makeRandom(ZERO, this.q)
+
+    // public keys (p, q, g, y)
+    this.y = BigInt.powMod(this.g, this.x, this.p)
+
+    // nocache?
+    if (opts.nocache) primes[bit_length] = null
+  }
+
+  DSA.prototype = {
+
+    constructor: DSA,
+
+    packPublic: function () {
+      var str = this.type
+      str += HLP.packMPI(this.p)
+      str += HLP.packMPI(this.q)
+      str += HLP.packMPI(this.g)
+      str += HLP.packMPI(this.y)
+      return str
+    },
+
+    packPrivate: function () {
+      var str = this.packPublic() + HLP.packMPI(this.x)
+      str = CryptoJS.enc.Latin1.parse(str)
+      return str.toString(CryptoJS.enc.Base64)
+    },
+
+    // http://www.imperialviolet.org/2013/06/15/suddendeathentropy.html
+    generateNonce: function (m) {
+      var priv = BigInt.bigInt2bits(BigInt.trim(this.x, 0))
+      var rand = BigInt.bigInt2bits(BigInt.randBigInt(256))
+
+      var sha256 = CryptoJS.algo.SHA256.create()
+      sha256.update(CryptoJS.enc.Latin1.parse(priv))
+      sha256.update(m)
+      sha256.update(CryptoJS.enc.Latin1.parse(rand))
+
+      var hash = sha256.finalize()
+      hash = HLP.bits2bigInt(hash.toString(CryptoJS.enc.Latin1))
+      BigInt.rightShift_(hash, 256 - BigInt.bitSize(this.q))
+
+      return HLP.between(hash, ZERO, this.q) ? hash : this.generateNonce(m)
+    },
+
+    sign: function (m) {
+      m = CryptoJS.enc.Latin1.parse(m)
+      var b = BigInt.str2bigInt(m.toString(CryptoJS.enc.Hex), 16)
+      var k, r = ZERO, s = ZERO
+      while (BigInt.isZero(s) || BigInt.isZero(r)) {
+        k = this.generateNonce(m)
+        r = BigInt.mod(BigInt.powMod(this.g, k, this.p), this.q)
+        if (BigInt.isZero(r)) continue
+        s = BigInt.inverseMod(k, this.q)
+        s = BigInt.mult(s, BigInt.add(b, BigInt.mult(this.x, r)))
+        s = BigInt.mod(s, this.q)
+      }
+      return [r, s]
+    },
+
+    fingerprint: function () {
+      var pk = this.packPublic()
+      if (this.type === KEY_TYPE) pk = pk.substring(2)
+      pk = CryptoJS.enc.Latin1.parse(pk)
+      return CryptoJS.SHA1(pk).toString(CryptoJS.enc.Hex)
+    }
+
+  }
+
+  DSA.parsePublic = function (str, priv) {
+    var fields = ['SHORT', 'MPI', 'MPI', 'MPI', 'MPI']
+    if (priv) fields.push('MPI')
+    str = HLP.splitype(fields, str)
+    var obj = {
+        type: str[0]
+      , p: HLP.readMPI(str[1])
+      , q: HLP.readMPI(str[2])
+      , g: HLP.readMPI(str[3])
+      , y: HLP.readMPI(str[4])
+    }
+    if (priv) obj.x = HLP.readMPI(str[5])
+    return new DSA(obj)
+  }
+
+  function tokenizeStr(str) {
+    var start, end
+
+    start = str.indexOf("(")
+    end = str.lastIndexOf(")")
+
+    if (start < 0 || end < 0)
+      throw new Error("Malformed S-Expression")
+
+    str = str.substring(start + 1, end)
+
+    var splt = str.search(/\s/)
+    var obj = {
+        type: str.substring(0, splt)
+      , val: []
+    }
+
+    str = str.substring(splt + 1, end)
+    start = str.indexOf("(")
+
+    if (start < 0) obj.val.push(str)
+    else {
+
+      var i, len, ss, es
+      while (start > -1) {
+        i = start + 1
+        len = str.length
+        for (ss = 1, es = 0; i < len && es < ss; i++) {
+          if (str[i] === "(") ss++
+          if (str[i] === ")") es++
+        }
+        obj.val.push(tokenizeStr(str.substring(start, ++i)))
+        str = str.substring(++i)
+        start = str.indexOf("(")
+      }
+
+    }
+    return obj
+  }
+
+  function parseLibotr(obj) {
+    if (!obj.type) throw new Error("Parse error.")
+
+    var o, val
+    if (obj.type === "privkeys") {
+      o = []
+      obj.val.forEach(function (i) {
+        o.push(parseLibotr(i))
+      })
+      return o
+    }
+
+    o = {}
+    obj.val.forEach(function (i) {
+
+      val = i.val[0]
+      if (typeof val === "string") {
+
+        if (val.indexOf("#") === 0) {
+          val = val.substring(1, val.lastIndexOf("#"))
+          val = BigInt.str2bigInt(val, 16)
+        }
+
+      } else {
+        val = parseLibotr(i)
+      }
+
+      o[i.type] = val
+    })
+
+    return o
+  }
+
+  DSA.parsePrivate = function (str, libotr) {
+    if (!libotr) {
+      str = CryptoJS.enc.Base64.parse(str)
+      str = str.toString(CryptoJS.enc.Latin1)
+      return DSA.parsePublic(str, true)
+    }
+    // only returning the first key found
+    return parseLibotr(tokenizeStr(str))[0]["private-key"].dsa
+  }
+
+  DSA.verify = function (key, m, r, s) {
+    if (!HLP.between(r, ZERO, key.q) || !HLP.between(s, ZERO, key.q))
+      return false
+
+    var hm = CryptoJS.enc.Latin1.parse(m)  // CryptoJS.SHA1(m)
+    hm = BigInt.str2bigInt(hm.toString(CryptoJS.enc.Hex), 16)
+
+    var w = BigInt.inverseMod(s, key.q)
+    var u1 = BigInt.multMod(hm, w, key.q)
+    var u2 = BigInt.multMod(r, w, key.q)
+
+    u1 = BigInt.powMod(key.g, u1, key.p)
+    u2 = BigInt.powMod(key.y, u2, key.p)
+
+    var v = BigInt.mod(BigInt.multMod(u1, u2, key.p), key.q)
+
+    return BigInt.equals(v, r)
+  }
+
+  DSA.createInWebWorker = function (options, cb) {
+    var opts = {
+        path: WWPath
+      , seed: BigInt.getSeed
+    }
+    if (options && typeof options === 'object')
+      Object.keys(options).forEach(function (k) {
+        opts[k] = options[k]
+      })
+
+    // load optional dep. in node
+    if (typeof module !== 'undefined' && module.exports)
+      Worker = require('webworker-threads').Worker
+
+    var worker = new Worker(opts.path)
+    worker.onmessage = function (e) {
+      var data = e.data
+      switch (data.type) {
+        case "debug":
+          if (!DEBUG || typeof console === 'undefined') return
+          console.log(data.val)
+          break;
+        case "data":
+          worker.terminate()
+          cb(DSA.parsePrivate(data.val))
+          break;
+        default:
+          throw new Error("Unrecognized type.")
+      }
+    }
+    worker.postMessage({
+        seed: opts.seed()
+      , imports: opts.imports
+      , debug: DEBUG
+    })
+  }
+
+}).call(this)
+;(function () {
+  "use strict";
+
+  var root = this
+
+  var Parse = {}, CryptoJS, CONST, HLP
+  if (typeof module !== 'undefined' && module.exports) {
+    module.exports = Parse
+    CryptoJS = require('../vendor/crypto.js')
+    CONST = require('./const.js')
+    HLP = require('./helpers.js')
+  } else {
+    root.OTR.Parse = Parse
+    CryptoJS = root.CryptoJS
+    CONST = root.OTR.CONST
+    HLP = root.OTR.HLP
+  }
+
+  // whitespace tags
+  var tags = {}
+  tags[CONST.WHITESPACE_TAG_V2] = CONST.OTR_VERSION_2
+  tags[CONST.WHITESPACE_TAG_V3] = CONST.OTR_VERSION_3
+
+  Parse.parseMsg = function (otr, msg) {
+
+    var ver = []
+
+    // is this otr?
+    var start = msg.indexOf(CONST.OTR_TAG)
+    if (!~start) {
+
+      // restart fragments
+      this.initFragment(otr)
+
+      // whitespace tags
+      ind = msg.indexOf(CONST.WHITESPACE_TAG)
+
+      if (~ind) {
+
+        msg = msg.split('')
+        msg.splice(ind, 16)
+
+        var tag, len = msg.length
+        for (; ind < len;) {
+          tag = msg.slice(ind, ind + 8).join('')
+          if (Object.hasOwnProperty.call(tags, tag)) {
+            msg.splice(ind, 8)
+            ver.push(tags[tag])
+            continue
+          }
+          ind += 8
+        }
+
+        msg = msg.join('')
+
+      }
+
+      return { msg: msg, ver: ver }
+    }
+
+    var ind = start + CONST.OTR_TAG.length
+    var com = msg[ind]
+
+    // message fragment
+    if (com === ',' || com === '|') {
+      return this.msgFragment(otr, msg.substring(ind + 1), (com === '|'))
+    }
+
+    this.initFragment(otr)
+
+    // query message
+    if (~['?', 'v'].indexOf(com)) {
+
+      // version 1
+      if (msg[ind] === '?') {
+        ver.push(CONST.OTR_VERSION_1)
+        ind += 1
+      }
+
+      // other versions
+      var vers = {
+          '2': CONST.OTR_VERSION_2
+        , '3': CONST.OTR_VERSION_3
+      }
+      var qs = msg.substring(ind + 1)
+      var qi = qs.indexOf('?')
+
+      if (qi >= 1) {
+        qs = qs.substring(0, qi).split('')
+        if (msg[ind] === 'v') {
+          qs.forEach(function (q) {
+            if (Object.hasOwnProperty.call(vers, q)) ver.push(vers[q])
+          })
+        }
+      }
+
+      return { cls: 'query', ver: ver }
+    }
+
+    // otr message
+    if (com === ':') {
+
+      ind += 1
+
+      var info = msg.substring(ind, ind + 4)
+      if (info.length < 4) return { msg: msg }
+      info = CryptoJS.enc.Base64.parse(info).toString(CryptoJS.enc.Latin1)
+
+      var version = info.substring(0, 2)
+      var type = info.substring(2)
+
+      // supporting otr versions 2 and 3
+      if (!otr['ALLOW_V' + HLP.unpackSHORT(version)]) return { msg: msg }
+
+      ind += 4
+
+      var end = msg.substring(ind).indexOf('.')
+      if (!~end) return { msg: msg }
+
+      msg = CryptoJS.enc.Base64.parse(msg.substring(ind, ind + end))
+      msg = CryptoJS.enc.Latin1.stringify(msg)
+
+      // instance tags
+      var instance_tags
+      if (version === CONST.OTR_VERSION_3) {
+        instance_tags = msg.substring(0, 8)
+        msg = msg.substring(8)
+      }
+
+      var cls
+      if (~['\x02', '\x0a', '\x11', '\x12'].indexOf(type)) {
+        cls = 'ake'
+      } else if (type === '\x03') {
+        cls = 'data'
+      }
+
+      return {
+          version: version
+        , type: type
+        , msg: msg
+        , cls: cls
+        , instance_tags: instance_tags
+      }
+    }
+
+    // error message
+    if (msg.substring(ind, ind + 7) === ' Error:') {
+      if (otr.ERROR_START_AKE) {
+        otr.sendQueryMsg()
+      }
+      return { msg: msg.substring(ind + 7), cls: 'error' }
+    }
+
+    return { msg: msg }
+  }
+
+  Parse.initFragment = function (otr) {
+    otr.fragment = { s: '', j: 0, k: 0 }
+  }
+
+  Parse.msgFragment = function (otr, msg, v3) {
+
+    msg = msg.split(',')
+
+    // instance tags
+    if (v3) {
+      var its = msg.shift().split('|')
+      var their_it = HLP.packINT(parseInt(its[0], 16))
+      var our_it = HLP.packINT(parseInt(its[1], 16))
+      if (otr.checkInstanceTags(their_it + our_it)) return  // ignore
+    }
+
+    if (msg.length < 4 ||
+      isNaN(parseInt(msg[0], 10)) ||
+      isNaN(parseInt(msg[1], 10))
+    ) return
+
+    var k = parseInt(msg[0], 10)
+    var n = parseInt(msg[1], 10)
+    msg = msg[2]
+
+    if (n < k || n === 0 || k === 0) {
+      this.initFragment(otr)
+      return
+    }
+
+    if (k === 1) {
+      this.initFragment(otr)
+      otr.fragment = { k: 1, n: n, s: msg }
+    } else if (n === otr.fragment.n && k === (otr.fragment.k + 1)) {
+      otr.fragment.s += msg
+      otr.fragment.k += 1
+    } else {
+      this.initFragment(otr)
+    }
+
+    if (n === k) {
+      msg = otr.fragment.s
+      this.initFragment(otr)
+      return this.parseMsg(otr, msg)
+    }
+
+    return
+  }
+
+}).call(this)
+;(function () {
+  "use strict";
+
+  var root = this
+
+  var CryptoJS, BigInt, CONST, HLP, DSA
+  if (typeof module !== 'undefined' && module.exports) {
+    module.exports = AKE
+    CryptoJS = require('../vendor/crypto.js')
+    BigInt = require('../vendor/bigint.js')
+    CONST = require('./const.js')
+    HLP = require('./helpers.js')
+    DSA = require('./dsa.js')
+  } else {
+    root.OTR.AKE = AKE
+    CryptoJS = root.CryptoJS
+    BigInt = root.BigInt
+    CONST = root.OTR.CONST
+    HLP = root.OTR.HLP
+    DSA = root.DSA
+  }
+
+  // diffie-hellman modulus
+  // see group 5, RFC 3526
+  var N = BigInt.str2bigInt(CONST.N, 16)
+  var N_MINUS_2 = BigInt.sub(N, BigInt.str2bigInt('2', 10))
+
+  function hMac(gx, gy, pk, kid, m) {
+    var pass = CryptoJS.enc.Latin1.parse(m)
+    var hmac = CryptoJS.algo.HMAC.create(CryptoJS.algo.SHA256, pass)
+    hmac.update(CryptoJS.enc.Latin1.parse(HLP.packMPI(gx)))
+    hmac.update(CryptoJS.enc.Latin1.parse(HLP.packMPI(gy)))
+    hmac.update(CryptoJS.enc.Latin1.parse(pk))
+    hmac.update(CryptoJS.enc.Latin1.parse(kid))
+    return (hmac.finalize()).toString(CryptoJS.enc.Latin1)
+  }
+
+  // AKE constructor
+  function AKE(otr) {
+    if (!(this instanceof AKE)) return new AKE(otr)
+
+    // otr instance
+    this.otr = otr
+
+    // our keys
+    this.our_dh = otr.our_old_dh
+    this.our_keyid = otr.our_keyid - 1
+
+    // their keys
+    this.their_y = null
+    this.their_keyid = null
+    this.their_priv_pk = null
+
+    // state
+    this.ssid = null
+    this.transmittedRS = false
+    this.r = null
+
+    // bind methods
+    var self = this
+    ;['sendMsg'].forEach(function (meth) {
+      self[meth] = self[meth].bind(self)
+    })
+  }
+
+  AKE.prototype = {
+
+    constructor: AKE,
+
+    createKeys: function(g) {
+      var s = BigInt.powMod(g, this.our_dh.privateKey, N)
+      var secbytes = HLP.packMPI(s)
+      this.ssid = HLP.mask(HLP.h2('\x00', secbytes), 0, 64)  // first 64-bits
+      var tmp = HLP.h2('\x01', secbytes)
+      this.c = HLP.mask(tmp, 0, 128)  // first 128-bits
+      this.c_prime = HLP.mask(tmp, 128, 128)  // second 128-bits
+      this.m1 = HLP.h2('\x02', secbytes)
+      this.m2 = HLP.h2('\x03', secbytes)
+      this.m1_prime = HLP.h2('\x04', secbytes)
+      this.m2_prime = HLP.h2('\x05', secbytes)
+    },
+
+    verifySignMac: function (mac, aesctr, m2, c, their_y, our_dh_pk, m1, ctr) {
+      // verify mac
+      var vmac = HLP.makeMac(aesctr, m2)
+      if (!HLP.compare(mac, vmac))
+        return ['MACs do not match.']
+
+      // decrypt x
+      var x = HLP.decryptAes(aesctr.substring(4), c, ctr)
+      x = HLP.splitype(['PUBKEY', 'INT', 'SIG'], x.toString(CryptoJS.enc.Latin1))
+
+      var m = hMac(their_y, our_dh_pk, x[0], x[1], m1)
+      var pub = DSA.parsePublic(x[0])
+
+      var r = HLP.bits2bigInt(x[2].substring(0, 20))
+      var s = HLP.bits2bigInt(x[2].substring(20))
+
+      // verify sign m
+      if (!DSA.verify(pub, m, r, s)) return ['Cannot verify signature of m.']
+
+      return [null, HLP.readLen(x[1]), pub]
+    },
+
+    makeM: function (their_y, m1, c, m2) {
+      var pk = this.otr.priv.packPublic()
+      var kid = HLP.packINT(this.our_keyid)
+      var m = hMac(this.our_dh.publicKey, their_y, pk, kid, m1)
+      m = this.otr.priv.sign(m)
+      var msg = pk + kid
+      msg += BigInt.bigInt2bits(m[0], 20)  // pad to 20 bytes
+      msg += BigInt.bigInt2bits(m[1], 20)
+      msg = CryptoJS.enc.Latin1.parse(msg)
+      var aesctr = HLP.packData(HLP.encryptAes(msg, c, HLP.packCtr(0)))
+      var mac = HLP.makeMac(aesctr, m2)
+      return aesctr + mac
+    },
+
+    akeSuccess: function (version) {
+      HLP.debug.call(this.otr, 'success')
+
+      if (BigInt.equals(this.their_y, this.our_dh.publicKey))
+        return this.otr.error('equal keys - we have a problem.', true)
+
+      this.otr.our_old_dh = this.our_dh
+      this.otr.their_priv_pk = this.their_priv_pk
+
+      if (!(
+        (this.their_keyid === this.otr.their_keyid &&
+         BigInt.equals(this.their_y, this.otr.their_y)) ||
+        (this.their_keyid === (this.otr.their_keyid - 1) &&
+         BigInt.equals(this.their_y, this.otr.their_old_y))
+      )) {
+
+        this.otr.their_y = this.their_y
+        this.otr.their_old_y = null
+        this.otr.their_keyid = this.their_keyid
+
+        // rotate keys
+        this.otr.sessKeys[0] = [ new this.otr.DHSession(
+            this.otr.our_dh
+          , this.otr.their_y
+        ), null ]
+        this.otr.sessKeys[1] = [ new this.otr.DHSession(
+            this.otr.our_old_dh
+          , this.otr.their_y
+        ), null ]
+
+      }
+
+      // ake info
+      this.otr.ssid = this.ssid
+      this.otr.transmittedRS = this.transmittedRS
+      this.otr_version = version
+
+      // go encrypted
+      this.otr.authstate = CONST.AUTHSTATE_NONE
+      this.otr.msgstate = CONST.MSGSTATE_ENCRYPTED
+
+      // null out values
+      this.r = null
+      this.myhashed = null
+      this.dhcommit = null
+      this.encrypted = null
+      this.hashed = null
+
+      this.otr.trigger('status', [CONST.STATUS_AKE_SUCCESS])
+
+      // send stored msgs
+      this.otr.sendStored()
+    },
+
+    handleAKE: function (msg) {
+      var send, vsm, type
+      var version = msg.version
+
+      switch (msg.type) {
+
+        case '\x02':
+          HLP.debug.call(this.otr, 'd-h key message')
+
+          msg = HLP.splitype(['DATA', 'DATA'], msg.msg)
+
+          if (this.otr.authstate === CONST.AUTHSTATE_AWAITING_DHKEY) {
+            var ourHash = HLP.readMPI(this.myhashed)
+            var theirHash = HLP.readMPI(msg[1])
+            if (BigInt.greater(ourHash, theirHash)) {
+              type = '\x02'
+              send = this.dhcommit
+              break  // ignore
+            } else {
+              // forget
+              this.our_dh = this.otr.dh()
+              this.otr.authstate = CONST.AUTHSTATE_NONE
+              this.r = null
+              this.myhashed = null
+            }
+          } else if (
+            this.otr.authstate === CONST.AUTHSTATE_AWAITING_SIG
+          ) this.our_dh = this.otr.dh()
+
+          this.otr.authstate = CONST.AUTHSTATE_AWAITING_REVEALSIG
+
+          this.encrypted = msg[0].substring(4)
+          this.hashed = msg[1].substring(4)
+
+          type = '\x0a'
+          send = HLP.packMPI(this.our_dh.publicKey)
+          break
+
+        case '\x0a':
+          HLP.debug.call(this.otr, 'reveal signature message')
+
+          msg = HLP.splitype(['MPI'], msg.msg)
+
+          if (this.otr.authstate !== CONST.AUTHSTATE_AWAITING_DHKEY) {
+            if (this.otr.authstate === CONST.AUTHSTATE_AWAITING_SIG) {
+              if (!BigInt.equals(this.their_y, HLP.readMPI(msg[0]))) return
+            } else {
+              return  // ignore
+            }
+          }
+
+          this.otr.authstate = CONST.AUTHSTATE_AWAITING_SIG
+
+          this.their_y = HLP.readMPI(msg[0])
+
+          // verify gy is legal 2 <= gy <= N-2
+          if (!HLP.checkGroup(this.their_y, N_MINUS_2))
+            return this.otr.error('Illegal g^y.', true)
+
+          this.createKeys(this.their_y)
+
+          type = '\x11'
+          send = HLP.packMPI(this.r)
+          send += this.makeM(this.their_y, this.m1, this.c, this.m2)
+
+          this.m1 = null
+          this.m2 = null
+          this.c = null
+          break
+
+        case '\x11':
+          HLP.debug.call(this.otr, 'signature message')
+
+          if (this.otr.authstate !== CONST.AUTHSTATE_AWAITING_REVEALSIG)
+            return  // ignore
+
+          msg = HLP.splitype(['DATA', 'DATA', 'MAC'], msg.msg)
+
+          this.r = HLP.readMPI(msg[0])
+
+          // decrypt their_y
+          var key = CryptoJS.enc.Hex.parse(BigInt.bigInt2str(this.r, 16))
+          key = CryptoJS.enc.Latin1.stringify(key)
+
+          var gxmpi = HLP.decryptAes(this.encrypted, key, HLP.packCtr(0))
+          gxmpi = gxmpi.toString(CryptoJS.enc.Latin1)
+
+          this.their_y = HLP.readMPI(gxmpi)
+
+          // verify hash
+          var hash = CryptoJS.SHA256(CryptoJS.enc.Latin1.parse(gxmpi))
+
+          if (!HLP.compare(this.hashed, hash.toString(CryptoJS.enc.Latin1)))
+            return this.otr.error('Hashed g^x does not match.', true)
+
+          // verify gx is legal 2 <= g^x <= N-2
+          if (!HLP.checkGroup(this.their_y, N_MINUS_2))
+            return this.otr.error('Illegal g^x.', true)
+
+          this.createKeys(this.their_y)
+
+          vsm = this.verifySignMac(
+              msg[2]
+            , msg[1]
+            , this.m2
+            , this.c
+            , this.their_y
+            , this.our_dh.publicKey
+            , this.m1
+            , HLP.packCtr(0)
+          )
+          if (vsm[0]) return this.otr.error(vsm[0], true)
+
+          // store their key
+          this.their_keyid = vsm[1]
+          this.their_priv_pk = vsm[2]
+
+          send = this.makeM(
+              this.their_y
+            , this.m1_prime
+            , this.c_prime
+            , this.m2_prime
+          )
+
+          this.m1 = null
+          this.m2 = null
+          this.m1_prime = null
+          this.m2_prime = null
+          this.c = null
+          this.c_prime = null
+
+          this.sendMsg(version, '\x12', send)
+          this.akeSuccess(version)
+          return
+
+        case '\x12':
+          HLP.debug.call(this.otr, 'data message')
+
+          if (this.otr.authstate !== CONST.AUTHSTATE_AWAITING_SIG)
+            return  // ignore
+
+          msg = HLP.splitype(['DATA', 'MAC'], msg.msg)
+
+          vsm = this.verifySignMac(
+              msg[1]
+            , msg[0]
+            , this.m2_prime
+            , this.c_prime
+            , this.their_y
+            , this.our_dh.publicKey
+            , this.m1_prime
+            , HLP.packCtr(0)
+          )
+          if (vsm[0]) return this.otr.error(vsm[0], true)
+
+          // store their key
+          this.their_keyid = vsm[1]
+          this.their_priv_pk = vsm[2]
+
+          this.m1_prime = null
+          this.m2_prime = null
+          this.c_prime = null
+
+          this.transmittedRS = true
+          this.akeSuccess(version)
+          return
+
+        default:
+          return  // ignore
+
+      }
+
+      this.sendMsg(version, type, send)
+    },
+
+    sendMsg: function (version, type, msg) {
+      var send = version + type
+      var v3 = (version === CONST.OTR_VERSION_3)
+
+      // instance tags for v3
+      if (v3) {
+        HLP.debug.call(this.otr, 'instance tags')
+        send += this.otr.our_instance_tag
+        send += this.otr.their_instance_tag
+      }
+
+      send += msg
+
+      // fragment message if necessary
+      send = HLP.wrapMsg(
+          send
+        , this.otr.fragment_size
+        , v3
+        , this.otr.our_instance_tag
+        , this.otr.their_instance_tag
+      )
+      if (send[0]) return this.otr.error(send[0])
+
+      this.otr.io(send[1])
+    },
+
+    initiateAKE: function (version) {
+      HLP.debug.call(this.otr, 'd-h commit message')
+
+      this.otr.trigger('status', [CONST.STATUS_AKE_INIT])
+
+      this.otr.authstate = CONST.AUTHSTATE_AWAITING_DHKEY
+
+      var gxmpi = HLP.packMPI(this.our_dh.publicKey)
+      gxmpi = CryptoJS.enc.Latin1.parse(gxmpi)
+
+      this.r = BigInt.randBigInt(128)
+      var key = CryptoJS.enc.Hex.parse(BigInt.bigInt2str(this.r, 16))
+      key = CryptoJS.enc.Latin1.stringify(key)
+
+      this.myhashed = CryptoJS.SHA256(gxmpi)
+      this.myhashed = HLP.packData(this.myhashed.toString(CryptoJS.enc.Latin1))
+
+      this.dhcommit = HLP.packData(HLP.encryptAes(gxmpi, key, HLP.packCtr(0)))
+      this.dhcommit += this.myhashed
+
+      this.sendMsg(version, '\x02', this.dhcommit)
+    }
+
+  }
+
+}).call(this)
+;(function () {
+  "use strict";
+
+  var root = this
+
+  var CryptoJS, BigInt,  EventEmitter, CONST, HLP
+  if (typeof module !== 'undefined' && module.exports) {
+    module.exports = SM
+    CryptoJS = require('../vendor/crypto.js')
+    BigInt = require('../vendor/bigint.js')
+    EventEmitter = require('../vendor/eventemitter.js')
+    CONST = require('./const.js')
+    HLP = require('./helpers.js')
+  } else {
+    root.OTR.SM = SM
+    CryptoJS = root.CryptoJS
+    BigInt = root.BigInt
+    EventEmitter = root.EventEmitter
+    CONST = root.OTR.CONST
+    HLP = root.OTR.HLP
+  }
+
+  // diffie-hellman modulus and generator
+  // see group 5, RFC 3526
+  var G = BigInt.str2bigInt(CONST.G, 10)
+  var N = BigInt.str2bigInt(CONST.N, 16)
+  var N_MINUS_2 = BigInt.sub(N, BigInt.str2bigInt('2', 10))
+
+  // to calculate D's for zero-knowledge proofs
+  var Q = BigInt.sub(N, BigInt.str2bigInt('1', 10))
+  BigInt.divInt_(Q, 2)  // meh
+
+  function SM(reqs) {
+    if (!(this instanceof SM)) return new SM(reqs)
+
+    this.version = 1
+
+    this.our_fp = reqs.our_fp
+    this.their_fp = reqs.their_fp
+    this.ssid = reqs.ssid
+
+    this.debug = !!reqs.debug
+
+    // initial state
+    this.init()
+  }
+
+  // inherit from EE
+  HLP.extend(SM, EventEmitter)
+
+  // set the initial values
+  // also used when aborting
+  SM.prototype.init = function () {
+    this.smpstate = CONST.SMPSTATE_EXPECT1
+    this.secret = null
+  }
+
+  SM.prototype.makeSecret = function (our, secret) {
+    var sha256 = CryptoJS.algo.SHA256.create()
+    sha256.update(CryptoJS.enc.Latin1.parse(HLP.packBytes(this.version, 1)))
+    sha256.update(CryptoJS.enc.Hex.parse(our ? this.our_fp : this.their_fp))
+    sha256.update(CryptoJS.enc.Hex.parse(our ? this.their_fp : this.our_fp))
+    sha256.update(CryptoJS.enc.Latin1.parse(this.ssid))
+    sha256.update(CryptoJS.enc.Latin1.parse(secret))
+    var hash = sha256.finalize()
+    this.secret = HLP.bits2bigInt(hash.toString(CryptoJS.enc.Latin1))
+  }
+
+  SM.prototype.makeG2s = function () {
+    this.a2 = HLP.randomExponent()
+    this.a3 = HLP.randomExponent()
+    this.g2a = BigInt.powMod(G, this.a2, N)
+    this.g3a = BigInt.powMod(G, this.a3, N)
+    if ( !HLP.checkGroup(this.g2a, N_MINUS_2) ||
+         !HLP.checkGroup(this.g3a, N_MINUS_2)
+    ) this.makeG2s()
+  }
+
+  SM.prototype.computeGs = function (g2a, g3a) {
+    this.g2 = BigInt.powMod(g2a, this.a2, N)
+    this.g3 = BigInt.powMod(g3a, this.a3, N)
+  }
+
+  SM.prototype.computePQ = function (r) {
+    this.p = BigInt.powMod(this.g3, r, N)
+    this.q = HLP.multPowMod(G, r, this.g2, this.secret, N)
+  }
+
+  SM.prototype.computeR = function () {
+    this.r = BigInt.powMod(this.QoQ, this.a3, N)
+  }
+
+  SM.prototype.computeRab = function (r) {
+    return BigInt.powMod(r, this.a3, N)
+  }
+
+  SM.prototype.computeC = function (v, r) {
+    return HLP.smpHash(v, BigInt.powMod(G, r, N))
+  }
+
+  SM.prototype.computeD = function (r, a, c) {
+    return BigInt.subMod(r, BigInt.multMod(a, c, Q), Q)
+  }
+
+  // the bulk of the work
+  SM.prototype.handleSM = function (msg) {
+    var send, r2, r3, r7, t1, t2, t3, t4, rab, tmp2, cR, d7, ms, trust
+
+    var expectStates = {
+        2: CONST.SMPSTATE_EXPECT1
+      , 3: CONST.SMPSTATE_EXPECT2
+      , 4: CONST.SMPSTATE_EXPECT3
+      , 5: CONST.SMPSTATE_EXPECT4
+      , 7: CONST.SMPSTATE_EXPECT1
+    }
+
+    if (msg.type === 6) {
+      this.init()
+      this.trigger('abort')
+      return
+    }
+
+    // abort! there was an error
+    if (this.smpstate !== expectStates[msg.type])
+      return this.abort()
+
+    switch (this.smpstate) {
+
+      case CONST.SMPSTATE_EXPECT1:
+        HLP.debug.call(this, 'smp tlv 2')
+
+        // user specified question
+        var ind, question
+        if (msg.type === 7) {
+          ind = msg.msg.indexOf('\x00')
+          question = msg.msg.substring(0, ind)
+          msg.msg = msg.msg.substring(ind + 1)
+        }
+
+        // 0:g2a, 1:c2, 2:d2, 3:g3a, 4:c3, 5:d3
+        ms = HLP.readLen(msg.msg.substr(0, 4))
+        if (ms !== 6) return this.abort()
+        msg = HLP.unpackMPIs(6, msg.msg.substring(4))
+
+        if ( !HLP.checkGroup(msg[0], N_MINUS_2) ||
+             !HLP.checkGroup(msg[3], N_MINUS_2)
+        ) return this.abort()
+
+        // verify znp's
+        if (!HLP.ZKP(1, msg[1], HLP.multPowMod(G, msg[2], msg[0], msg[1], N)))
+          return this.abort()
+
+        if (!HLP.ZKP(2, msg[4], HLP.multPowMod(G, msg[5], msg[3], msg[4], N)))
+          return this.abort()
+
+        this.g3ao = msg[3]  // save for later
+
+        this.makeG2s()
+
+        // zero-knowledge proof that the exponents
+        // associated with g2a & g3a are known
+        r2 = HLP.randomExponent()
+        r3 = HLP.randomExponent()
+        this.c2 = this.computeC(3, r2)
+        this.c3 = this.computeC(4, r3)
+        this.d2 = this.computeD(r2, this.a2, this.c2)
+        this.d3 = this.computeD(r3, this.a3, this.c3)
+
+        this.computeGs(msg[0], msg[3])
+
+        this.smpstate = CONST.SMPSTATE_EXPECT0
+
+        // assume utf8 question
+        question = CryptoJS.enc.Latin1
+          .parse(question)
+          .toString(CryptoJS.enc.Utf8)
+
+        // invoke question
+        this.trigger('question', [question])
+        return
+
+      case CONST.SMPSTATE_EXPECT2:
+        HLP.debug.call(this, 'smp tlv 3')
+
+        // 0:g2a, 1:c2, 2:d2, 3:g3a, 4:c3, 5:d3, 6:p, 7:q, 8:cP, 9:d5, 10:d6
+        ms = HLP.readLen(msg.msg.substr(0, 4))
+        if (ms !== 11) return this.abort()
+        msg = HLP.unpackMPIs(11, msg.msg.substring(4))
+
+        if ( !HLP.checkGroup(msg[0], N_MINUS_2) ||
+             !HLP.checkGroup(msg[3], N_MINUS_2) ||
+             !HLP.checkGroup(msg[6], N_MINUS_2) ||
+             !HLP.checkGroup(msg[7], N_MINUS_2)
+        ) return this.abort()
+
+        // verify znp of c3 / c3
+        if (!HLP.ZKP(3, msg[1], HLP.multPowMod(G, msg[2], msg[0], msg[1], N)))
+          return this.abort()
+
+        if (!HLP.ZKP(4, msg[4], HLP.multPowMod(G, msg[5], msg[3], msg[4], N)))
+          return this.abort()
+
+        this.g3ao = msg[3]  // save for later
+
+        this.computeGs(msg[0], msg[3])
+
+        // verify znp of cP
+        t1 = HLP.multPowMod(this.g3, msg[9], msg[6], msg[8], N)
+        t2 = HLP.multPowMod(G, msg[9], this.g2, msg[10], N)
+        t2 = BigInt.multMod(t2, BigInt.powMod(msg[7], msg[8], N), N)
+
+        if (!HLP.ZKP(5, msg[8], t1, t2))
+          return this.abort()
+
+        var r4 = HLP.randomExponent()
+        this.computePQ(r4)
+
+        // zero-knowledge proof that P & Q
+        // were generated according to the protocol
+        var r5 = HLP.randomExponent()
+        var r6 = HLP.randomExponent()
+        var tmp = HLP.multPowMod(G, r5, this.g2, r6, N)
+        var cP = HLP.smpHash(6, BigInt.powMod(this.g3, r5, N), tmp)
+        var d5 = this.computeD(r5, r4, cP)
+        var d6 = this.computeD(r6, this.secret, cP)
+
+        // store these
+        this.QoQ = BigInt.divMod(this.q, msg[7], N)
+        this.PoP = BigInt.divMod(this.p, msg[6], N)
+
+        this.computeR()
+
+        // zero-knowledge proof that R
+        // was generated according to the protocol
+        r7 = HLP.randomExponent()
+        tmp2 = BigInt.powMod(this.QoQ, r7, N)
+        cR = HLP.smpHash(7, BigInt.powMod(G, r7, N), tmp2)
+        d7 = this.computeD(r7, this.a3, cR)
+
+        this.smpstate = CONST.SMPSTATE_EXPECT4
+
+        send = HLP.packINT(8) + HLP.packMPIs([
+            this.p
+          , this.q
+          , cP
+          , d5
+          , d6
+          , this.r
+          , cR
+          , d7
+        ])
+
+        // TLV
+        send = HLP.packTLV(4, send)
+        break
+
+      case CONST.SMPSTATE_EXPECT3:
+        HLP.debug.call(this, 'smp tlv 4')
+
+        // 0:p, 1:q, 2:cP, 3:d5, 4:d6, 5:r, 6:cR, 7:d7
+        ms = HLP.readLen(msg.msg.substr(0, 4))
+        if (ms !== 8) return this.abort()
+        msg = HLP.unpackMPIs(8, msg.msg.substring(4))
+
+        if ( !HLP.checkGroup(msg[0], N_MINUS_2) ||
+             !HLP.checkGroup(msg[1], N_MINUS_2) ||
+             !HLP.checkGroup(msg[5], N_MINUS_2)
+        ) return this.abort()
+
+        // verify znp of cP
+        t1 = HLP.multPowMod(this.g3, msg[3], msg[0], msg[2], N)
+        t2 = HLP.multPowMod(G, msg[3], this.g2, msg[4], N)
+        t2 = BigInt.multMod(t2, BigInt.powMod(msg[1], msg[2], N), N)
+
+        if (!HLP.ZKP(6, msg[2], t1, t2))
+          return this.abort()
+
+        // verify znp of cR
+        t3 = HLP.multPowMod(G, msg[7], this.g3ao, msg[6], N)
+        this.QoQ = BigInt.divMod(msg[1], this.q, N)  // save Q over Q
+        t4 = HLP.multPowMod(this.QoQ, msg[7], msg[5], msg[6], N)
+
+        if (!HLP.ZKP(7, msg[6], t3, t4))
+          return this.abort()
+
+        this.computeR()
+
+        // zero-knowledge proof that R
+        // was generated according to the protocol
+        r7 = HLP.randomExponent()
+        tmp2 = BigInt.powMod(this.QoQ, r7, N)
+        cR = HLP.smpHash(8, BigInt.powMod(G, r7, N), tmp2)
+        d7 = this.computeD(r7, this.a3, cR)
+
+        send = HLP.packINT(3) + HLP.packMPIs([ this.r, cR, d7 ])
+        send = HLP.packTLV(5, send)
+
+        rab = this.computeRab(msg[5])
+        trust = !!BigInt.equals(rab, BigInt.divMod(msg[0], this.p, N))
+
+        this.trigger('trust', [trust, 'answered'])
+        this.init()
+        break
+
+      case CONST.SMPSTATE_EXPECT4:
+        HLP.debug.call(this, 'smp tlv 5')
+
+        // 0:r, 1:cR, 2:d7
+        ms = HLP.readLen(msg.msg.substr(0, 4))
+        if (ms !== 3) return this.abort()
+        msg = HLP.unpackMPIs(3, msg.msg.substring(4))
+
+        if (!HLP.checkGroup(msg[0], N_MINUS_2)) return this.abort()
+
+        // verify znp of cR
+        t3 = HLP.multPowMod(G, msg[2], this.g3ao, msg[1], N)
+        t4 = HLP.multPowMod(this.QoQ, msg[2], msg[0], msg[1], N)
+        if (!HLP.ZKP(8, msg[1], t3, t4))
+          return this.abort()
+
+        rab = this.computeRab(msg[0])
+        trust = !!BigInt.equals(rab, this.PoP)
+
+        this.trigger('trust', [trust, 'asked'])
+        this.init()
+        return
+
+    }
+
+    this.sendMsg(send)
+  }
+
+  // send a message
+  SM.prototype.sendMsg = function (send) {
+    this.trigger('send', [this.ssid, '\x00' + send])
+  }
+
+  SM.prototype.rcvSecret = function (secret, question) {
+    HLP.debug.call(this, 'receive secret')
+
+    var fn, our = false
+    if (this.smpstate === CONST.SMPSTATE_EXPECT0) {
+      fn = this.answer
+    } else {
+      fn = this.initiate
+      our = true
+    }
+
+    this.makeSecret(our, secret)
+    fn.call(this, question)
+  }
+
+  SM.prototype.answer = function () {
+    HLP.debug.call(this, 'smp answer')
+
+    var r4 = HLP.randomExponent()
+    this.computePQ(r4)
+
+    // zero-knowledge proof that P & Q
+    // were generated according to the protocol
+    var r5 = HLP.randomExponent()
+    var r6 = HLP.randomExponent()
+    var tmp = HLP.multPowMod(G, r5, this.g2, r6, N)
+    var cP = HLP.smpHash(5, BigInt.powMod(this.g3, r5, N), tmp)
+    var d5 = this.computeD(r5, r4, cP)
+    var d6 = this.computeD(r6, this.secret, cP)
+
+    this.smpstate = CONST.SMPSTATE_EXPECT3
+
+    var send = HLP.packINT(11) + HLP.packMPIs([
+        this.g2a
+      , this.c2
+      , this.d2
+      , this.g3a
+      , this.c3
+      , this.d3
+      , this.p
+      , this.q
+      , cP
+      , d5
+      , d6
+    ])
+
+    this.sendMsg(HLP.packTLV(3, send))
+  }
+
+  SM.prototype.initiate = function (question) {
+    HLP.debug.call(this, 'smp initiate')
+
+    if (this.smpstate !== CONST.SMPSTATE_EXPECT1)
+      this.abort()  // abort + restart
+
+    this.makeG2s()
+
+    // zero-knowledge proof that the exponents
+    // associated with g2a & g3a are known
+    var r2 = HLP.randomExponent()
+    var r3 = HLP.randomExponent()
+    this.c2 = this.computeC(1, r2)
+    this.c3 = this.computeC(2, r3)
+    this.d2 = this.computeD(r2, this.a2, this.c2)
+    this.d3 = this.computeD(r3, this.a3, this.c3)
+
+    // set the next expected state
+    this.smpstate = CONST.SMPSTATE_EXPECT2
+
+    var send = ''
+    var type = 2
+
+    if (question) {
+      send += question
+      send += '\x00'
+      type = 7
+    }
+
+    send += HLP.packINT(6) + HLP.packMPIs([
+        this.g2a
+      , this.c2
+      , this.d2
+      , this.g3a
+      , this.c3
+      , this.d3
+    ])
+
+    this.sendMsg(HLP.packTLV(type, send))
+  }
+
+  SM.prototype.abort = function () {
+    this.init()
+    this.sendMsg(HLP.packTLV(6, ''))
+    this.trigger('abort')
+  }
+
+}).call(this)
+;(function () {
+  "use strict";
+
+  var root = this
+
+  var CryptoJS, BigInt, EventEmitter, Worker, SMWPath
+    , CONST, HLP, Parse, AKE, SM, DSA
+  if (typeof module !== 'undefined' && module.exports) {
+    module.exports = OTR
+    CryptoJS = require('../vendor/crypto.js')
+    BigInt = require('../vendor/bigint.js')
+    EventEmitter = require('../vendor/eventemitter.js')
+    SMWPath = require('path').join(__dirname, '/sm-webworker.js')
+    CONST = require('./const.js')
+    HLP = require('./helpers.js')
+    Parse = require('./parse.js')
+    AKE = require('./ake.js')
+    SM = require('./sm.js')
+    DSA = require('./dsa.js')
+    // expose CONST for consistency with docs
+    OTR.CONST = CONST
+  } else {
+    // copy over and expose internals
+    Object.keys(root.OTR).forEach(function (k) {
+      OTR[k] = root.OTR[k]
+    })
+    root.OTR = OTR
+    CryptoJS = root.CryptoJS
+    BigInt = root.BigInt
+    EventEmitter = root.EventEmitter
+    Worker = root.Worker
+    SMWPath = 'sm-webworker.js'
+    CONST = OTR.CONST
+    HLP = OTR.HLP
+    Parse = OTR.Parse
+    AKE = OTR.AKE
+    SM = OTR.SM
+    DSA = root.DSA
+  }
+
+  // diffie-hellman modulus and generator
+  // see group 5, RFC 3526
+  var G = BigInt.str2bigInt(CONST.G, 10)
+  var N = BigInt.str2bigInt(CONST.N, 16)
+
+  // JavaScript integers
+  var MAX_INT = Math.pow(2, 53) - 1  // doubles
+  var MAX_UINT = Math.pow(2, 31) - 1  // bitwise operators
+
+  // OTR contructor
+  function OTR(options) {
+    if (!(this instanceof OTR)) return new OTR(options)
+
+    // options
+    options = options || {}
+
+    // private keys
+    if (options.priv && !(options.priv instanceof DSA))
+      throw new Error('Requires long-lived DSA key.')
+
+    this.priv = options.priv ? options.priv : new DSA()
+
+    this.fragment_size = options.fragment_size || 0
+    if (this.fragment_size < 0)
+      throw new Error('Fragment size must be a positive integer.')
+
+    this.send_interval = options.send_interval || 0
+    if (this.send_interval < 0)
+      throw new Error('Send interval must be a positive integer.')
+
+    this.outgoing = []
+
+    // instance tag
+    this.our_instance_tag = options.instance_tag || OTR.makeInstanceTag()
+
+    // debug
+    this.debug = !!options.debug
+
+    // smp in webworker options
+    // this is still experimental and undocumented
+    this.smw = options.smw
+
+    // init vals
+    this.init()
+
+    // bind methods
+    var self = this
+    ;['sendMsg', 'receiveMsg'].forEach(function (meth) {
+      self[meth] = self[meth].bind(self)
+    })
+
+    EventEmitter.call(this)
+  }
+
+  // inherit from EE
+  HLP.extend(OTR, EventEmitter)
+
+  // add to prototype
+  OTR.prototype.init = function () {
+
+    this.msgstate = CONST.MSGSTATE_PLAINTEXT
+    this.authstate = CONST.AUTHSTATE_NONE
+
+    this.ALLOW_V2 = true
+    this.ALLOW_V3 = true
+
+    this.REQUIRE_ENCRYPTION = false
+    this.SEND_WHITESPACE_TAG = false
+    this.WHITESPACE_START_AKE = false
+    this.ERROR_START_AKE = false
+
+    Parse.initFragment(this)
+
+    // their keys
+    this.their_y = null
+    this.their_old_y = null
+    this.their_keyid = 0
+    this.their_priv_pk = null
+    this.their_instance_tag = '\x00\x00\x00\x00'
+
+    // our keys
+    this.our_dh = this.dh()
+    this.our_old_dh = this.dh()
+    this.our_keyid = 2
+
+    // session keys
+    this.sessKeys = [ new Array(2), new Array(2) ]
+
+    // saved
+    this.storedMgs = []
+    this.oldMacKeys = []
+
+    // smp
+    this.sm = null  // initialized after AKE
+
+    // when ake is complete
+    // save their keys and the session
+    this._akeInit()
+
+    // receive plaintext message since switching to plaintext
+    // used to decide when to stop sending pt tags when SEND_WHITESPACE_TAG
+    this.receivedPlaintext = false
+
+  }
+
+  OTR.prototype._akeInit = function () {
+    this.ake = new AKE(this)
+    this.transmittedRS = false
+    this.ssid = null
+  }
+
+  // smp over webworker
+  OTR.prototype._SMW = function (otr, reqs) {
+    this.otr = otr
+    var opts = {
+        path: SMWPath
+      , seed: BigInt.getSeed
+    }
+    if (typeof otr.smw === 'object')
+      Object.keys(otr.smw).forEach(function (k) {
+        opts[k] = otr.smw[k]
+      })
+
+    // load optional dep. in node
+    if (typeof module !== 'undefined' && module.exports)
+      Worker = require('webworker-threads').Worker
+
+    this.worker = new Worker(opts.path)
+    var self = this
+    this.worker.onmessage = function (e) {
+      var d = e.data
+      if (!d) return
+      self.trigger(d.method, d.args)
+    }
+    this.worker.postMessage({
+        type: 'seed'
+      , seed: opts.seed()
+      , imports: opts.imports
+    })
+    this.worker.postMessage({
+        type: 'init'
+      , reqs: reqs
+    })
+  }
+
+  // inherit from EE
+  HLP.extend(OTR.prototype._SMW, EventEmitter)
+
+  // shim sm methods
+  ;['handleSM', 'rcvSecret', 'abort'].forEach(function (m) {
+    OTR.prototype._SMW.prototype[m] = function () {
+      this.worker.postMessage({
+          type: 'method'
+        , method: m
+        , args: Array.prototype.slice.call(arguments, 0)
+      })
+    }
+  })
+
+  OTR.prototype._smInit = function () {
+    var reqs = {
+        ssid: this.ssid
+      , our_fp: this.priv.fingerprint()
+      , their_fp: this.their_priv_pk.fingerprint()
+      , debug: this.debug
+    }
+    if (this.smw) {
+      if (this.sm) this.sm.worker.terminate()  // destroy prev webworker
+      this.sm = new this._SMW(this, reqs)
+    } else {
+      this.sm = new SM(reqs)
+    }
+    var self = this
+    ;['trust', 'abort', 'question'].forEach(function (e) {
+      self.sm.on(e, function () {
+        self.trigger('smp', [e].concat(Array.prototype.slice.call(arguments)))
+      })
+    })
+    this.sm.on('send', function (ssid, send) {
+      if (self.ssid === ssid) {
+        send = self.prepareMsg(send)
+        self.io(send)
+      }
+    })
+  }
+
+  OTR.prototype.io = function (msg, meta) {
+
+    // buffer
+    msg = ([].concat(msg)).map(function(m){
+       return { msg: m, meta: meta }
+    })
+    this.outgoing = this.outgoing.concat(msg)
+
+    var self = this
+    ;(function send(first) {
+      if (!first) {
+        if (!self.outgoing.length) return
+        var elem = self.outgoing.shift()
+        self.trigger('io', [elem.msg, elem.meta])
+      }
+      setTimeout(send, first ? 0 : self.send_interval)
+    }(true))
+
+  }
+
+  OTR.prototype.dh = function dh() {
+    var keys = { privateKey: BigInt.randBigInt(320) }
+    keys.publicKey = BigInt.powMod(G, keys.privateKey, N)
+    return keys
+  }
+
+  // session constructor
+  OTR.prototype.DHSession = function DHSession(our_dh, their_y) {
+    if (!(this instanceof DHSession)) return new DHSession(our_dh, their_y)
+
+    // shared secret
+    var s = BigInt.powMod(their_y, our_dh.privateKey, N)
+    var secbytes = HLP.packMPI(s)
+
+    // session id
+    this.id = HLP.mask(HLP.h2('\x00', secbytes), 0, 64)  // first 64-bits
+
+    // are we the high or low end of the connection?
+    var sq = BigInt.greater(our_dh.publicKey, their_y)
+    var sendbyte = sq ? '\x01' : '\x02'
+    var rcvbyte  = sq ? '\x02' : '\x01'
+
+    // sending and receiving keys
+    this.sendenc = HLP.mask(HLP.h1(sendbyte, secbytes), 0, 128)  // f16 bytes
+    this.sendmac = CryptoJS.SHA1(CryptoJS.enc.Latin1.parse(this.sendenc))
+    this.sendmac = this.sendmac.toString(CryptoJS.enc.Latin1)
+
+    this.rcvenc = HLP.mask(HLP.h1(rcvbyte, secbytes), 0, 128)
+    this.rcvmac = CryptoJS.SHA1(CryptoJS.enc.Latin1.parse(this.rcvenc))
+    this.rcvmac = this.rcvmac.toString(CryptoJS.enc.Latin1)
+    this.rcvmacused = false
+
+    // extra symmetric key
+    this.extra_symkey = HLP.h2('\xff', secbytes)
+
+    // counters
+    this.send_counter = 0
+    this.rcv_counter = 0
+  }
+
+  OTR.prototype.rotateOurKeys = function () {
+
+    // reveal old mac keys
+    var self = this
+    this.sessKeys[1].forEach(function (sk) {
+      if (sk && sk.rcvmacused) self.oldMacKeys.push(sk.rcvmac)
+    })
+
+    // rotate our keys
+    this.our_old_dh = this.our_dh
+    this.our_dh = this.dh()
+    this.our_keyid += 1
+
+    this.sessKeys[1][0] = this.sessKeys[0][0]
+    this.sessKeys[1][1] = this.sessKeys[0][1]
+    this.sessKeys[0] = [
+        this.their_y ?
+            new this.DHSession(this.our_dh, this.their_y) : null
+      , this.their_old_y ?
+            new this.DHSession(this.our_dh, this.their_old_y) : null
+    ]
+
+  }
+
+  OTR.prototype.rotateTheirKeys = function (their_y) {
+
+    // increment their keyid
+    this.their_keyid += 1
+
+    // reveal old mac keys
+    var self = this
+    this.sessKeys.forEach(function (sk) {
+      if (sk[1] && sk[1].rcvmacused) self.oldMacKeys.push(sk[1].rcvmac)
+    })
+
+    // rotate their keys / session
+    this.their_old_y = this.their_y
+    this.sessKeys[0][1] = this.sessKeys[0][0]
+    this.sessKeys[1][1] = this.sessKeys[1][0]
+
+    // new keys / sessions
+    this.their_y = their_y
+    this.sessKeys[0][0] = new this.DHSession(this.our_dh, this.their_y)
+    this.sessKeys[1][0] = new this.DHSession(this.our_old_dh, this.their_y)
+
+  }
+
+  OTR.prototype.prepareMsg = function (msg, esk) {
+    if (this.msgstate !== CONST.MSGSTATE_ENCRYPTED || this.their_keyid === 0)
+      return this.error('Not ready to encrypt.')
+
+    var sessKeys = this.sessKeys[1][0]
+
+    if (sessKeys.send_counter >= MAX_INT)
+      return this.error('Should have rekeyed by now.')
+
+    sessKeys.send_counter += 1
+
+    var ctr = HLP.packCtr(sessKeys.send_counter)
+
+    var send = this.ake.otr_version + '\x03'  // version and type
+    var v3 = (this.ake.otr_version === CONST.OTR_VERSION_3)
+
+    if (v3) {
+      send += this.our_instance_tag
+      send += this.their_instance_tag
+    }
+
+    send += '\x00'  // flag
+    send += HLP.packINT(this.our_keyid - 1)
+    send += HLP.packINT(this.their_keyid)
+    send += HLP.packMPI(this.our_dh.publicKey)
+    send += ctr.substring(0, 8)
+
+    if (Math.ceil(msg.length / 8) >= MAX_UINT)  // * 16 / 128
+      return this.error('Message is too long.')
+
+    var aes = HLP.encryptAes(
+        CryptoJS.enc.Latin1.parse(msg)
+      , sessKeys.sendenc
+      , ctr
+    )
+
+    send += HLP.packData(aes)
+    send += HLP.make1Mac(send, sessKeys.sendmac)
+    send += HLP.packData(this.oldMacKeys.splice(0).join(''))
+
+    send = HLP.wrapMsg(
+        send
+      , this.fragment_size
+      , v3
+      , this.our_instance_tag
+      , this.their_instance_tag
+    )
+    if (send[0]) return this.error(send[0])
+
+    // emit extra symmetric key
+    if (esk) this.trigger('file', ['send', sessKeys.extra_symkey, esk])
+
+    return send[1]
+  }
+
+  OTR.prototype.handleDataMsg = function (msg) {
+    var vt = msg.version + msg.type
+
+    if (this.ake.otr_version === CONST.OTR_VERSION_3)
+      vt += msg.instance_tags
+
+    var types = ['BYTE', 'INT', 'INT', 'MPI', 'CTR', 'DATA', 'MAC', 'DATA']
+    msg = HLP.splitype(types, msg.msg)
+
+    // ignore flag
+    var ign = (msg[0] === '\x01')
+
+    if (this.msgstate !== CONST.MSGSTATE_ENCRYPTED || msg.length !== 8) {
+      if (!ign) this.error('Received an unreadable encrypted message.', true)
+      return
+    }
+
+    var our_keyid = this.our_keyid - HLP.readLen(msg[2])
+    var their_keyid = this.their_keyid - HLP.readLen(msg[1])
+
+    if (our_keyid < 0 || our_keyid > 1) {
+      if (!ign) this.error('Not of our latest keys.', true)
+      return
+    }
+
+    if (their_keyid < 0 || their_keyid > 1) {
+      if (!ign) this.error('Not of your latest keys.', true)
+      return
+    }
+
+    var their_y = their_keyid ? this.their_old_y : this.their_y
+
+    if (their_keyid === 1 && !their_y) {
+      if (!ign) this.error('Do not have that key.')
+      return
+    }
+
+    var sessKeys = this.sessKeys[our_keyid][their_keyid]
+
+    var ctr = HLP.unpackCtr(msg[4])
+    if (ctr <= sessKeys.rcv_counter) {
+      if (!ign) this.error('Counter in message is not larger.')
+      return
+    }
+    sessKeys.rcv_counter = ctr
+
+    // verify mac
+    vt += msg.slice(0, 6).join('')
+    var vmac = HLP.make1Mac(vt, sessKeys.rcvmac)
+
+    if (!HLP.compare(msg[6], vmac)) {
+      if (!ign) this.error('MACs do not match.')
+      return
+    }
+    sessKeys.rcvmacused = true
+
+    var out = HLP.decryptAes(
+        msg[5].substring(4)
+      , sessKeys.rcvenc
+      , HLP.padCtr(msg[4])
+    )
+    out = out.toString(CryptoJS.enc.Latin1)
+
+    if (!our_keyid) this.rotateOurKeys()
+    if (!their_keyid) this.rotateTheirKeys(HLP.readMPI(msg[3]))
+
+    // parse TLVs
+    var ind = out.indexOf('\x00')
+    if (~ind) {
+      this.handleTLVs(out.substring(ind + 1), sessKeys)
+      out = out.substring(0, ind)
+    }
+
+    out = CryptoJS.enc.Latin1.parse(out)
+    return out.toString(CryptoJS.enc.Utf8)
+  }
+
+  OTR.prototype.handleTLVs = function (tlvs, sessKeys) {
+    var type, len, msg
+    for (; tlvs.length; ) {
+      type = HLP.unpackSHORT(tlvs.substr(0, 2))
+      len = HLP.unpackSHORT(tlvs.substr(2, 2))
+
+      msg = tlvs.substr(4, len)
+
+      // TODO: handle pathological cases better
+      if (msg.length < len) break
+
+      switch (type) {
+        case 1:
+          // Disconnected
+          this.msgstate = CONST.MSGSTATE_FINISHED
+          this.trigger('status', [CONST.STATUS_END_OTR])
+          break
+        case 2: case 3: case 4:
+        case 5: case 6: case 7:
+          // SMP
+          if (this.msgstate !== CONST.MSGSTATE_ENCRYPTED) {
+            if (this.sm) this.sm.abort()
+            return
+          }
+          if (!this.sm) this._smInit()
+          this.sm.handleSM({ msg: msg, type: type })
+          break
+        case 8:
+          // utf8 filenames
+          msg = msg.substring(4) // remove 4-byte indication
+          msg = CryptoJS.enc.Latin1.parse(msg)
+          msg = msg.toString(CryptoJS.enc.Utf8)
+
+          // Extra Symkey
+          this.trigger('file', ['receive', sessKeys.extra_symkey, msg])
+          break
+      }
+
+      tlvs = tlvs.substring(4 + len)
+    }
+  }
+
+  OTR.prototype.smpSecret = function (secret, question) {
+    if (this.msgstate !== CONST.MSGSTATE_ENCRYPTED)
+      return this.error('Must be encrypted for SMP.')
+
+    if (typeof secret !== 'string' || secret.length < 1)
+      return this.error('Secret is required.')
+
+    if (!this.sm) this._smInit()
+
+    // utf8 inputs
+    secret = CryptoJS.enc.Utf8.parse(secret).toString(CryptoJS.enc.Latin1)
+    question = CryptoJS.enc.Utf8.parse(question).toString(CryptoJS.enc.Latin1)
+
+    this.sm.rcvSecret(secret, question)
+  }
+
+  OTR.prototype.sendQueryMsg = function () {
+    var versions = {}
+      , msg = CONST.OTR_TAG
+
+    if (this.ALLOW_V2) versions['2'] = true
+    if (this.ALLOW_V3) versions['3'] = true
+
+    // but we don't allow v1
+    // if (versions['1']) msg += '?'
+
+    var vs = Object.keys(versions)
+    if (vs.length) {
+      msg += 'v'
+      vs.forEach(function (v) {
+        if (v !== '1') msg += v
+      })
+      msg += '?'
+    }
+
+    this.io(msg)
+    this.trigger('status', [CONST.STATUS_SEND_QUERY])
+  }
+
+  OTR.prototype.sendMsg = function (msg, meta) {
+    if ( this.REQUIRE_ENCRYPTION ||
+         this.msgstate !== CONST.MSGSTATE_PLAINTEXT
+    ) {
+      msg = CryptoJS.enc.Utf8.parse(msg)
+      msg = msg.toString(CryptoJS.enc.Latin1)
+    }
+
+    switch (this.msgstate) {
+      case CONST.MSGSTATE_PLAINTEXT:
+        if (this.REQUIRE_ENCRYPTION) {
+          this.storedMgs.push({msg: msg, meta: meta})
+          this.sendQueryMsg()
+          return
+        }
+        if (this.SEND_WHITESPACE_TAG && !this.receivedPlaintext) {
+          msg += CONST.WHITESPACE_TAG  // 16 byte tag
+          if (this.ALLOW_V3) msg += CONST.WHITESPACE_TAG_V3
+          if (this.ALLOW_V2) msg += CONST.WHITESPACE_TAG_V2
+        }
+        break
+      case CONST.MSGSTATE_FINISHED:
+        this.storedMgs.push({msg: msg, meta: meta})
+        this.error('Message cannot be sent at this time.')
+        return
+      case CONST.MSGSTATE_ENCRYPTED:
+        msg = this.prepareMsg(msg)
+        break
+      default:
+        throw new Error('Unknown message state.')
+    }
+
+    if (msg) this.io(msg, meta)
+  }
+
+  OTR.prototype.receiveMsg = function (msg) {
+
+    // parse type
+    msg = Parse.parseMsg(this, msg)
+
+    if (!msg) return
+
+    switch (msg.cls) {
+      case 'error':
+        this.error(msg.msg)
+        return
+      case 'ake':
+        if ( msg.version === CONST.OTR_VERSION_3 &&
+          this.checkInstanceTags(msg.instance_tags)
+        ) return  // ignore
+        this.ake.handleAKE(msg)
+        return
+      case 'data':
+        if ( msg.version === CONST.OTR_VERSION_3 &&
+          this.checkInstanceTags(msg.instance_tags)
+        ) return  // ignore
+        msg.msg = this.handleDataMsg(msg)
+        msg.encrypted = true
+        break
+      case 'query':
+        if (this.msgstate === CONST.MSGSTATE_ENCRYPTED) this._akeInit()
+        this.doAKE(msg)
+        break
+      default:
+        // check for encrypted
+        if ( this.REQUIRE_ENCRYPTION ||
+             this.msgstate !== CONST.MSGSTATE_PLAINTEXT
+        ) this.error('Received an unencrypted message.')
+
+        // received a plaintext message
+        // stop sending the whitespace tag
+        this.receivedPlaintext = true
+
+        // received a whitespace tag
+        if (this.WHITESPACE_START_AKE && msg.ver.length > 0)
+          this.doAKE(msg)
+    }
+
+    if (msg.msg) this.trigger('ui', [msg.msg, !!msg.encrypted])
+  }
+
+  OTR.prototype.checkInstanceTags = function (it) {
+    var their_it = HLP.readLen(it.substr(0, 4))
+    var our_it = HLP.readLen(it.substr(4, 4))
+
+    if (our_it && our_it !== HLP.readLen(this.our_instance_tag))
+      return true
+
+    if (HLP.readLen(this.their_instance_tag)) {
+      if (HLP.readLen(this.their_instance_tag) !== their_it) return true
+    } else {
+      if (their_it < 100) return true
+      this.their_instance_tag = HLP.packINT(their_it)
+    }
+  }
+
+  OTR.prototype.doAKE = function (msg) {
+    if (this.ALLOW_V3 && ~msg.ver.indexOf(CONST.OTR_VERSION_3)) {
+      this.ake.initiateAKE(CONST.OTR_VERSION_3)
+    } else if (this.ALLOW_V2 && ~msg.ver.indexOf(CONST.OTR_VERSION_2)) {
+      this.ake.initiateAKE(CONST.OTR_VERSION_2)
+    } else {
+      // is this an error?
+      this.error('OTR conversation requested, ' +
+        'but no compatible protocol version found.')
+    }
+  }
+
+  OTR.prototype.error = function (err, send) {
+    if (send) {
+      if (!this.debug) err = "An OTR error has occurred."
+      err = '?OTR Error:' + err
+      this.io(err)
+      return
+    }
+    this.trigger('error', [err])
+  }
+
+  OTR.prototype.sendStored = function () {
+    var self = this
+    ;(this.storedMgs.splice(0)).forEach(function (elem) {
+      var msg = self.prepareMsg(elem.msg)
+      self.io(msg, elem.meta)
+    })
+  }
+
+  OTR.prototype.sendFile = function (filename) {
+    if (this.msgstate !== CONST.MSGSTATE_ENCRYPTED)
+      return this.error('Not ready to encrypt.')
+
+    if (this.ake.otr_version !== CONST.OTR_VERSION_3)
+      return this.error('Protocol v3 required.')
+
+    if (!filename) return this.error('Please specify a filename.')
+
+    // utf8 filenames
+    var l1name = CryptoJS.enc.Utf8.parse(filename)
+    l1name = l1name.toString(CryptoJS.enc.Latin1)
+
+    if (l1name.length >= 65532) return this.error('filename is too long.')
+
+    var msg = '\x00'  // null byte
+    msg += '\x00\x08'  // type 8 tlv
+    msg += HLP.packSHORT(4 + l1name.length)  // length of value
+    msg += '\x00\x00\x00\x01'  // four bytes indicating file
+    msg += l1name
+
+    msg = this.prepareMsg(msg, filename)
+    this.io(msg)
+  }
+
+  OTR.prototype.endOtr = function () {
+    if (this.msgstate === CONST.MSGSTATE_ENCRYPTED) {
+      this.sendMsg('\x00\x00\x01\x00\x00')
+      if (this.sm) {
+        if (this.smw) this.sm.worker.terminate()  // destroy webworker
+        this.sm = null
+      }
+    }
+    this.msgstate = CONST.MSGSTATE_PLAINTEXT
+    this.receivedPlaintext = false
+    this.trigger('status', [CONST.STATUS_END_OTR])
+  }
+
+  // attach methods
+
+  OTR.makeInstanceTag = function () {
+    var num = BigInt.randBigInt(32)
+    if (BigInt.greater(BigInt.str2bigInt('100', 16), num))
+      return OTR.makeInstanceTag()
+    return HLP.packINT(parseInt(BigInt.bigInt2str(num, 10), 10))
+  }
+
+}).call(this)
+
+
+  return {
+      OTR: this.OTR
+    , DSA: this.DSA
+  }
+
+}))
\ No newline at end of file