// RSA key generator.

function extended_gcd(a, b, callback)
{
    var x = big_zero;
    var lastx = big_one;
    var y = big_one;
    var lasty = big_zero;
    var tmp;

    ensure_response(loop);

    function loop() {

	if (biCompare(b, big_zero) != 0) {
	    tmp = b;

	    biDivideModulo(a, b,
			   function (qr)
			   {
			       b = qr[1];
			       a = tmp;

			       tmp = x;
			       biMultiply(qr[0], x,
					  function(product)
					  {
					      x = biSubtract(lastx, product);
					      lastx = tmp;

					      tmp = y;
					      biMultiply(qr[0], y,
							 function(product)
							 {
							     y = biSubtract(lasty, product);
							     lasty = tmp;

							     ensure_response(loop);
							 }
							);
					  }
					 );

			   }
			  );
	}
	else {
	    callback(new Array(lastx, lasty, a));
	}
    }
}

// e -- wanted encryption exponent
// null if not needed
// If wanted encryption exponent can't be used (isn't coprime to (p - 1)(q - 1)) automatically generated will be used.
function rsa_keygen(numbits, e, randpool, callback)
{
    var primes_numbits = Math.ceil(numbits / 2);
    var p;
    var q;

    first_prime();

    function first_prime() {
	generate_random_prime(primes_numbits, randpool, function(prime) { p = prime; second_prime(); });
    }

    function second_prime() {
	function callback_fun(prime) {
	    q = prime;
	    if (biCompare(p, q) == 0) {
		second_prime();
	    }
	    else {
		rest();
	    }
	}

	generate_random_prime(primes_numbits, randpool, callback_fun);
    }

    function rest() {
	var euler_func;
	var modulus;
	var decomp;

	function exponents() {

	    function wanted_exponent_check() {
		if (e != null) {
		    if (biCompare(e, big_one) != 0) {
			extended_gcd(e, euler_func,
				     function(result)
				     {
					 if (biCompare(result[2], big_one) != 0) {
					     e = null;
					 }

					 decomp = result;
					 ensure_response(calculations);
				     }
				    );
		    }
		    else {
			e = null;
			ensure_response(calculations);
		    }

		}
		else {
		    ensure_response(calculations);
		}
	    }

	    function finit() {

		if (decomp[0].isNeg == true) {
		    decomp[0] = biAdd(decomp[0], euler_func);
		}

		callback(new Array(e, decomp[0], modulus));
	    }

	    function calculations() {
		if (e == null) {
		    var e_candidate;

		    function loop() {
			e_candidate = random_bigint_below(euler_func, randpool);
			extended_gcd(e_candidate, euler_func,
				     function (result)
				     {
					 if ((biCompare(result[2], big_one) == 0) && (biCompare(e_candidate, big_one) != 0)) {
					     e = e_candidate;
					     decomp = result;
					     ensure_response(finit);
					 }
					 else {
					     ensure_response(loop);
					 }
				     }
				     );
		    }

		    ensure_response(loop);
		}
		else {
		    ensure_response(finit);
		}

	    }

	    ensure_response(wanted_exponent_check);
	}

	biMultiply(biSubtract(p, big_one), biSubtract(q, big_one),
		   function(product)
		   {
		       euler_func = product;
		       biMultiply(p, q,
				  function(product)
				  {
				      modulus = product;
				      ensure_response(exponents);
				  }
				 );

		   }
		  );
    }
}

