Source: libs/SRACryptoWorker.js

/**
* @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});