view scripts/minifier/otr/otr.js @ 103:e69883c1ec30

docker (libervia_cont): added a "status" command: - if libervia container is not running, it exits with error code 1 - if libervia container is running but no server is launched, it exits with error code 2 - if libervia container is running and server is launcher, it exits with error code 0 (success) server detection is done by doing a simple grep on logs, that's not perfectly reliable (ports can be changed in configuration, even if that doesn't really make sense in Docker context) but should be good enough for this purpose.
author Goffi <goffi@goffi.org>
date Sat, 27 Feb 2016 00:45:40 +0100
parents 1596660ddf72
children
line wrap: on
line source

/*!

  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
  }

}))