text: /**
  * @class SRACryptoWorker Asynchronous Web Worker implementation of the SRA cryptosystem.
  * @name SRACryptoWorker
  * @version 0.2.0
  */
  self.importScripts("BigInteger.min.js"); //very important!
  var useCrypto = false; //use browser's crypto interface?
  var useNative = false; //use native BigInt (https://caniuse.com/#search=BigInt -or- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt#Browser_compatibility)
  try {
     //sometimes crypto is defined but getRandomValues isn't
     if (typeof(crypto.getRandomValues) == "function") {
        useCrypto = true;
     }
  } catch (err) {
  }
  try {
     //sometimes crypto is defined but getRandomValues isn't
     if (typeof(BigInt) == "function") {
        useNative = true;
     }
  } catch (err) {
  }
  
  /**
  * Handles all externally received messages from the host.
  *
  * External-to-internal function map:<br/>
  * <br/>
  * "randomPrime" => {@link generateRandomPrime}<br/>
  * "checkPrime" => {@link checkPrime}<br/>
  * "randomKeypair" => {@link generateRandomKeypair}<br/>
  * "randomQuadResidues" => {@link generateRandomQuadResidues}<br/>
  * "checkResidues" => {@link checkResidues}<br/>
  * "encrypt" => {@link encrypt}<br/>
  * "decrypt" => {@link decrypt}
  * @private
  * @param {Object} event A standard Worker "message" event.
  */
  function handleMessage (event) {
     var request = event.data;
     var result = {};
     switch (event.data.method) {
        case "randomPrime":
           var bitLength = event.data.params.bitLength;
           var radix = event.data.params.radix;
           if (isNaN(radix)) {
              radix = 16; //default to hex
           }
           result = generateRandomPrime(bitLength, radix);
           break;
        case "checkPrime":
           var primeVal = event.data.params.prime.trim();
           if (primeVal.startsWith("0x")) {
              primeVal = primeVal.substring(2);
              radix = 16;
           } else {
              radix = 10;
           }
           result = checkPrime(primeVal, radix);
           break;
        case "randomKeypair":
           primeVal = event.data.params.prime.trim();
           if (primeVal.startsWith("0x")) {
              primeVal = primeVal.substring(2);
              radix = 16;
           } else {
              radix = 10;
           }
           result = generateRandomKeypair(primeVal, radix);
           break;
        case "randomQuadResidues":
           primeVal = event.data.params.prime.trim();
           var numValues = event.data.params.numValues;
           if (primeVal.startsWith("0x")) {
              primeVal = primeVal.substring(2);
              radix = 16;
           } else {
              radix = 10;
           }
           result = generateRandomQuadResidues(primeVal, numValues, radix);
           break;
        case "checkResidues":
           var residues = event.data.params.residues;
           primeVal = event.data.params.prime.trim();
           if (primeVal.startsWith("0x")) {
              primeVal = primeVal.substring(2);
              var primeRadix = 16;
           } else {
              primeRadix = 10;
           }
           var residueObjects = new Array();
           for (var count=0; count < residues.length; count++) {
              var currentValue = residues[count].trim();
              if (currentValue.startsWith("0x")) {
                 currentValue = currentValue.substring(2);
                 radix = 16;
              } else {
                 radix = 10;
              }
              residueObjects.push({"value":currentValue, "radix":radix});
           }
           result = checkResidues(residueObjects, primeVal, primeRadix);
           break;
        case "encrypt":
           var keypair = event.data.params.keypair;
           if (keypair.encKey.startsWith("0x")) {
              keypair.encKey = keypair.encKey.substring(2);
              keypair.prime = keypair.prime.substring(2);
              var keyRadix = 16;
           } else {
              keyRadix = 10;
           }
           var value = event.data.params.value;
           if (value.startsWith("0x")) {
              var encValue = value.substring(2);
              var valueRadix = 16;
           } else {
              encValue = value;
              valueRadix = 10;
           }
           result = encrypt (encValue, valueRadix, keypair, keyRadix);
           break;
        case "decrypt":
              keypair = event.data.params.keypair;
              if (keypair.decKey.startsWith("0x")) {
                 keypair.decKey = keypair.decKey.substring(2);
                 keypair.prime = keypair.prime.substring(2);
                 var keyRadix = 16;
              } else {
                 keyRadix = 10;
              }
              var value = event.data.params.value;
              if (value.startsWith("0x")) {
                 var decValue = value.substring(2);
                 var valueRadix = 16;
              } else {
                 decValue = value;
                 valueRadix = 10;
              }
              result = decrypt (decValue, valueRadix, keypair, keyRadix);
              break;
        default:
           break;
      }
      postMessage({"result": result, "requestID": request.requestID});
  }
  
  /**
  * Generates a pseudo-random positive integer using the most secure method available
  * (the second option can probably be improved).
  *
  * @param {Number} maxValue The returned random integer will be between 0 and
  * this parameter.
  * @return {Number|Integer} A pseudo-random positive integer value in the
  * range 0 to maxValue.
  * @private
  */
  function randomInteger(maxValue) {
     if (useCrypto) {
        //use safer crypto interface if available
        var arr = new Uint32Array(2);
        crypto.getRandomValues(arr);
        var multiplier = 0;
        (arr[0] > arr[1]) ? multiplier = arr[1] / arr[0] :  multiplier = arr[0] / arr[1];
        return (Math.round(multiplier * maxValue));
     } else {
        //the second option
        return (Math.round(Math.random() * maxValue));
     }
  }
  
  /**
  * Generates a pseudo-random bit string of a given length.
  *
  * @param {Number} bitLength The number of bits / length of the result.
  * @return {String} A pseudo-random bit string of length bitLength.
  * @private
  */
  function randomBitStr(bitLength) {
     return (Array.from({length:bitLength}, (v, i) => {return ((i==0) ? 1 : randomInteger(1))}).join(""));
  }
  
  /**
  * Generates a random n-bit prime number and returns it in a given radix.
  *
  * @param {Number} bitLength The number of bits to use for the generated prime
  * value.
  * @param {Number} radix=16|10 The radix of the returned value string. Must be
  * either 16 or 10
  * @return {String} A bitLength-length prime number value represented as a string,
  * either a hexadecimal value (if radix is 16), or a decimal value (if radix is 10).
  * @private
  */
  function generateRandomPrime(bitLength, radix) {
     var bitStr = randomBitStr(bitLength);
     var bi_prime = bigInt(bitStr, 2);
     while (bi_prime.isPrime() == false) {
        bi_prime = bi_prime.minus(bigInt.one);
     }
     if (radix == 16) {
        return ("0x" + bi_prime.toString(radix));
     } else {
        return (bi_prime.toString(radix));
     }
  }
  
  /**
  * Checks whether the input prime value string is actually a prime value.
  *
  * @param {String} primeVal The string representation of the prime value to check.
  * @param {Number} radix The expected radix of primeVal, either 16 (hex) or
  * 10 (dec).
  * @return {Boolean} True if the primeVal string represents a prime number value.
  * @private
  */
  function checkPrime(primeVal, radix) {
     var bi_prime = bigInt(primeVal, radix);
     return (bi_prime.isPrime());
  }
  
  /**
  * Generates a random keypair from a given prime number.
  *
  * @param {String} primeVal The prime number value representation to use to
  * generate the keypair.
  * @param {Number} radix The assumed radix of the primeVal value and for the
  * generated keypair.
  * @return {keypair} The generated keypair+prime value object in the specified
  * radix.
  * @private
  */
  function generateRandomKeypair(primeVal, radix) {
     var bi_prime = bigInt(primeVal, radix);
     var bi_phi_prime = bi_prime.minus(1); //assume this is a prime number
     var encKey = bigInt(randomBitStr(bi_phi_prime.bitLength()), 2);
     var done = false;
     while (!done) {
        try {
           var decKey = encKey.modInv(bi_phi_prime);
           done = true;
        } catch (err) {
           encKey = encKey.minus(1);
           done = false;
        }
     }
     if (radix == 16) {
        return ({"encKey":"0x"+encKey.toString(radix), "decKey":"0x"+decKey.toString(radix), "prime":"0x"+bi_prime.toString(16)});
     } else {
        return ({"encKey":encKey.toString(radix), "decKey":decKey.toString(radix), "prime":bi_prime.toString(10)});
     }
  }
  
  /**
  * Generates a fixed series of sequential quadratic residues modulo a given prime
  * value. This is done by halving the prime minus 1 and using that as the
  * starting value of the lowest possible quadratic residue.
  * In other words, the starting point for the quadratic residue series is:
  * <code>(primeVal-1)/2</code>
  *
  * @param {String} primeVal The representation of the prime number to use to
  * generate the quadratic residues.
  * @param {Number} numValues The number of sequential quadratic residues to
  * generate.
  * @param {Number} radix The assumed radix of primeVal and of the generated
  * quadratic residues.
  * @return {Array} The sequentially generated quadratic residues as
  * string representations in the given radix.
  * @private
  */
  function generateRandomQuadResidues(primeVal, numValues, radix) {
     var residues = new Array();
     var bi_prime = bigInt(primeVal, radix);
     var bi_phi_prime = bi_prime.minus(1); //assume this is a prime number
     var bi_exp = bi_phi_prime.divide(2);
     var currentValue = bi_exp.minus(1); //start with one less than half the prime
     while (residues.length < numValues) {
        if (currentValue.modPow(bi_exp, bi_prime).equals(1)) {
           if (radix == 16) {
              residues.push("0x"+currentValue.toString(16));
           } else {
              residues.push(currentValue.toString(10));
           }
        }
        currentValue = currentValue.plus(1);
     }
     return (residues);
  }
  
  /**
  * Calculates the quadratic residues / non-residues of a series of numbers
  * modulo a given prime value.
  *
  * @param {Array} residueObjects Objects containing:
  * @param {String} residueObjects.value The representation of the value to calculate
  * quadratic residue / non-residue for.
  * @param {Number} residueObjects.radix The assumed radix of the associated value.
  * @param {Number} radix The assumed radix of primeVal.
  * @return {Array} The calculated quadratic residues (1) or non-residues (primeVal-1)
  * of the residueObjects array with the indexes of the input array matching the
  * indexes of the results.
  * @private
  */
  function checkResidues(residueObjects, primeVal, primeRadix) {
     var residues = new Array();
     var bi_prime = bigInt(primeVal, primeRadix);
     var bi_phi_prime = bi_prime.minus(1); //assume this is a prime number
     var bi_exp = bi_phi_prime.divide(2);
     var currentValue = bi_exp.minus(1); //start with one less than half the prime
     var returnValues = new Array();
     for (var count=0; count < residueObjects.length; count++) {
        var currentResObject = residueObjects[count];
        var currentValue = bigInt(currentResObject.value, currentResObject.radix);
        returnValues.push(currentValue.modPow(bi_exp, bi_prime).toString(currentResObject.radix));
     }
     return (returnValues);
  }
  
  /**
  * Encrypts a value with a keypair object.
  *
  * @param {String} encValue The representation of the value to encrypt.
  * @param {Number} valueRadix The assumed radix of encValue.
  * @param {keypair} keypair A keypair to use for the operation.
  * @param {Number} keyRadix The assumed radix of all of the values in the keypair.
  * @return {String} The encrypted value as a string representation in the valueRadix.
  * @private
  */
  function encrypt (encValue, valueRadix, keypair, keyRadix) {
     var value = bigInt(encValue, valueRadix);
     var key = bigInt(keypair.encKey, keyRadix);
     var prime = bigInt(keypair.prime, keyRadix);
     var result = value.modPow(key, prime);
     if (valueRadix == 16) {
        return ("0x"+result.toString(valueRadix));
     } else {
        return (result.toString(valueRadix));
     }
  }
  
  
  /**
  * Decrypts a value with a keypair object.
  *
  * @param {String} decValue The representation of the value to decrypt.
  * @param {Number} valueRadix The assumed radix of decValue.
  * @param {keypair} keypair A keypair to use for the operation.
  * @param {Number} keyRadix The assumed radix of all of the values in the keypair.
  * @return {String} The decrypted value as a string representation in the valueRadix.
  * @private
  */
  function decrypt (decValue, valueRadix, keypair, keyRadix) {
     var value = bigInt(decValue, valueRadix);
     var key = bigInt(keypair.decKey, keyRadix);
     var prime = bigInt(keypair.prime, keyRadix);
     var result = value.modPow(key, prime);
     if (valueRadix == 16) {
        return ("0x"+result.toString(valueRadix));
     } else {
        return (result.toString(valueRadix));
     }
  }
  
  // Native BigInt functons (not yet implemented above)
  
  /*
  
  function isPrimeNative(n) {
     n = BigInt(n)
     if (n % 1n || n < 2n) return false;
     if (n==leastFactor(n)) return true;
     return false;
  }
  
  
  
  function leastFactor (n){
     n = BigInt(n)
   if (n==0n) return 0n;
   if (n % 1n || n*n < 2n) return 1n;
   if (n%2n==0n) return 2n;
   if (n%3n==0n) return 3n;
   if (n%5n==0n) return 5n;
   var m = sqrt(n);
   for (var i=7n;i<=m;i+=30n) {
    if (n%i==0n)      return i;
    if (n%(i+4n)==0n)  return i+4n;
    if (n%(i+6n)==0n)  return i+6n;
    if (n%(i+10n)==0n) return i+10n;
    if (n%(i+12n)==0n) return i+12n;
    if (n%(i+16n)==0n) return i+16n;
    if (n%(i+22n)==0n) return i+22n;
    if (n%(i+24n)==0n) return i+24n;
   }
   return n;
  }
  
  function sqrt(value) {
  		if (value < 0n) {
  			throw 'square root of negative numbers is not supported'
  		}
  
  		if (value < 2n) {
  			return value;
  		}
  
  		function newtonIteration(n, x0) {
  			const x1 = ((n / x0) + x0) >> 1n;
  			if (x0 === x1 || x0 === (x1 - 1n)) {
  				return x0;
  			}
  			return newtonIteration(n, x1);
  		}
  
  		return newtonIteration(value, 1n);
  	}
  
  function modInvNative(x, m) {
     var inverse = xgcdNative(BigInt(x), BigInt(m))[0];
     if (inverse < BigInt(0)) {
        inverse = m + inverse;
     }
     return (inverse);
  }
  
  function xgcdNative(a, m) {
    if (m == BigInt(0)) {
      return ([BigInt(1), BigInt(0), a]);
    }
    temp = xgcdNative(m, a % m);
    var x = temp[BigInt(0)];
    var y = temp[BigInt(1)];
    var d = temp[BigInt(2)];
     return ([y, x-y*(a/m), d]);
  }
  
  function generateRandomPrimeNative(startValue, radix) {
     var prime = BigInt(startValue);
     while (isPrimeNative(prime)== false) {
        prime = prime - 1n;
     }
     if (radix == 16) {
        return ("0x" + prime.toString(radix));
     } else {
        return (prime.toString(radix));
     }
  }
  
  function generateRandomPrimeNative(bitLength, radix) {
     var bitStr = randomBitStr(bitLength);
     var prime = BigInt("0b"+bitStr);
     while (isPrimeNative(prime)== false) {
        prime = prime - 1n;
     }
     if (radix == 16) {
        return ("0x" + prime.toString(radix));
     } else {
        return (prime.toString(radix));
     }
  }
  */
  //setup message handler
  onmessage = handleMessage;
  
  //signal host that worker is ready to accept requests
  postMessage({"ready":true});
  