// See file LICENSE for more information.

library impl.key_generator.rsa_key_generator;

import 'package:pointycastle/api.dart';
import 'package:pointycastle/asymmetric/api.dart';
import 'package:pointycastle/key_generators/api.dart';
import 'package:pointycastle/src/registry/registry.dart';

bool _testBit(BigInt i, int n) {
  return (i & (BigInt.one << n)) != BigInt.zero;
}

class RSAKeyGenerator implements KeyGenerator {
  static final FactoryConfig factoryConfig =
      StaticFactoryConfig(KeyGenerator, 'RSA', () => RSAKeyGenerator());

  late SecureRandom _random;
  late RSAKeyGeneratorParameters _params;

  @override
  String get algorithmName => 'RSA';

  @override
  void init(CipherParameters params) {
    if (params is ParametersWithRandom) {
      _random = params.random;
      _params = params.parameters as RSAKeyGeneratorParameters;
    } else {
      _random = SecureRandom();
      _params = params as RSAKeyGeneratorParameters;
    }

    if (_params.bitStrength < 12) {
      throw ArgumentError('key bit strength cannot be smaller than 12');
    }

    if (!_testBit(_params.publicExponent, 0)) {
      throw ArgumentError('Public exponent cannot be even');
    }
  }

  @override
  AsymmetricKeyPair<RSAPublicKey, RSAPrivateKey> generateKeyPair() {
    BigInt p, q, n, e;

    // p and q values should have a length of half the strength in bits
    var strength = _params.bitStrength;
    var pbitlength = (strength + 1) ~/ 2;
    var qbitlength = strength - pbitlength;
    var mindiffbits = strength ~/ 3;

    e = _params.publicExponent;

    // TODO Consider generating safe primes for p, q (see DHParametersHelper.generateSafePrimes)
    // (then p-1 and q-1 will not consist of only small factors - see "Pollard's algorithm")

    // generate p, prime and (p-1) relatively prime to e
    while (true) {
      p = generateProbablePrime(pbitlength, 1, _random);

      if (p % e == BigInt.one) {
        continue;
      }

      if (!_isProbablePrime(p, _params.certainty)) {
        continue;
      }

      if (e.gcd(p - BigInt.one) == BigInt.one) {
        break;
      }
    }

    // generate a modulus of the required length
    while (true) {
      // generate q, prime and (q-1) relatively prime to e, and not equal to p
      while (true) {
        q = generateProbablePrime(qbitlength, 1, _random);

        if ((q - p).abs().bitLength < mindiffbits) {
          continue;
        }

        if (q % e == BigInt.one) {
          continue;
        }

        if (!_isProbablePrime(q, _params.certainty)) {
          continue;
        }

        if (e.gcd(q - BigInt.one) == BigInt.one) {
          break;
        }
      }

      // calculate the modulus
      n = p * q;

      if (n.bitLength == _params.bitStrength) {
        break;
      }

      // if we get here our primes aren't big enough, make the largest of the two p and try again
      p = (p.compareTo(q) > 0) ? p : q;
    }

    // Swap p and q if necessary
    if (p < q) {
      var swap = p;
      p = q;
      q = swap;
    }

    // calculate the private exponent
    var pSub1 = p - BigInt.one;
    var qSub1 = q - BigInt.one;
    var phi = pSub1 * qSub1;
    var d = e.modInverse(phi);

    // ignore: deprecated_member_use_from_same_package
    return AsymmetricKeyPair(RSAPublicKey(n, e), RSAPrivateKey(n, d, p, q, e));
  }
}

/// [List] of low primes
final List<BigInt> _lowprimes = [
  BigInt.from(2),
  BigInt.from(3),
  BigInt.from(5),
  BigInt.from(7),
  BigInt.from(11),
  BigInt.from(13),
  BigInt.from(17),
  BigInt.from(19),
  BigInt.from(23),
  BigInt.from(29),
  BigInt.from(31),
  BigInt.from(37),
  BigInt.from(41),
  BigInt.from(43),
  BigInt.from(47),
  BigInt.from(53),
  BigInt.from(59),
  BigInt.from(61),
  BigInt.from(67),
  BigInt.from(71),
  BigInt.from(73),
  BigInt.from(79),
  BigInt.from(83),
  BigInt.from(89),
  BigInt.from(97),
  BigInt.from(101),
  BigInt.from(103),
  BigInt.from(107),
  BigInt.from(109),
  BigInt.from(113),
  BigInt.from(127),
  BigInt.from(131),
  BigInt.from(137),
  BigInt.from(139),
  BigInt.from(149),
  BigInt.from(151),
  BigInt.from(157),
  BigInt.from(163),
  BigInt.from(167),
  BigInt.from(173),
  BigInt.from(179),
  BigInt.from(181),
  BigInt.from(191),
  BigInt.from(193),
  BigInt.from(197),
  BigInt.from(199),
  BigInt.from(211),
  BigInt.from(223),
  BigInt.from(227),
  BigInt.from(229),
  BigInt.from(233),
  BigInt.from(239),
  BigInt.from(241),
  BigInt.from(251),
  BigInt.from(257),
  BigInt.from(263),
  BigInt.from(269),
  BigInt.from(271),
  BigInt.from(277),
  BigInt.from(281),
  BigInt.from(283),
  BigInt.from(293),
  BigInt.from(307),
  BigInt.from(311),
  BigInt.from(313),
  BigInt.from(317),
  BigInt.from(331),
  BigInt.from(337),
  BigInt.from(347),
  BigInt.from(349),
  BigInt.from(353),
  BigInt.from(359),
  BigInt.from(367),
  BigInt.from(373),
  BigInt.from(379),
  BigInt.from(383),
  BigInt.from(389),
  BigInt.from(397),
  BigInt.from(401),
  BigInt.from(409),
  BigInt.from(419),
  BigInt.from(421),
  BigInt.from(431),
  BigInt.from(433),
  BigInt.from(439),
  BigInt.from(443),
  BigInt.from(449),
  BigInt.from(457),
  BigInt.from(461),
  BigInt.from(463),
  BigInt.from(467),
  BigInt.from(479),
  BigInt.from(487),
  BigInt.from(491),
  BigInt.from(499),
  BigInt.from(503),
  BigInt.from(509)
];

final BigInt _lplim = (BigInt.one << 26) ~/ _lowprimes.last;

final BigInt _bigTwo = BigInt.from(2);

/// return index of lowest 1-bit in x, x < 2^31
int _lbit(BigInt x) {
  // Implementation borrowed from bignum.BigIntegerDartvm.
  if (x == BigInt.zero) return -1;
  var r = 0;
  while ((x & BigInt.from(0xffffffff)) == BigInt.zero) {
    x >>= 32;
    r += 32;
  }
  if ((x & BigInt.from(0xffff)) == BigInt.zero) {
    x >>= 16;
    r += 16;
  }
  if ((x & BigInt.from(0xff)) == BigInt.zero) {
    x >>= 8;
    r += 8;
  }
  if ((x & BigInt.from(0xf)) == BigInt.zero) {
    x >>= 4;
    r += 4;
  }
  if ((x & BigInt.from(3)) == BigInt.zero) {
    x >>= 2;
    r += 2;
  }
  if ((x & BigInt.one) == BigInt.zero) ++r;
  return r;
}

/// true if probably prime (HAC 4.24, Miller-Rabin) */
bool _millerRabin(BigInt b, int t) {
  // Implementation borrowed from bignum.BigIntegerDartvm.
  var n1 = b - BigInt.one;
  var k = _lbit(n1);
  if (k <= 0) return false;
  var r = n1 >> k;
  t = (t + 1) >> 1;
  if (t > _lowprimes.length) t = _lowprimes.length;
  BigInt a;
  for (var i = 0; i < t; ++i) {
    a = _lowprimes[i];
    var y = a.modPow(r, b);
    if (y.compareTo(BigInt.one) != 0 && y.compareTo(n1) != 0) {
      var j = 1;
      while (j++ < k && y.compareTo(n1) != 0) {
        y = y.modPow(_bigTwo, b);
        if (y.compareTo(BigInt.one) == 0) return false;
      }
      if (y.compareTo(n1) != 0) return false;
    }
  }
  return true;
}

/// test primality with certainty >= 1-.5^t */
bool _isProbablePrime(BigInt b, int t) {
  // Implementation borrowed from bignum.BigIntegerDartvm.
  int i;
  var x = b.abs();
  if (b <= _lowprimes.last) {
    for (i = 0; i < _lowprimes.length; ++i) {
      if (b == _lowprimes[i]) return true;
    }
    return false;
  }
  if (x.isEven) return false;
  i = 1;
  while (i < _lowprimes.length) {
    var m = _lowprimes[i], j = i + 1;
    while (j < _lowprimes.length && m < _lplim) {
      m *= _lowprimes[j++];
    }
    m = x % m;
    while (i < j) {
      if (m % _lowprimes[i++] == BigInt.zero) {
        return false;
      }
    }
  }
  return _millerRabin(x, t);
}

BigInt generateProbablePrime(int bitLength, int certainty, SecureRandom rnd) {
  if (bitLength < 2) {
    return BigInt.one;
  }

  var candidate = rnd.nextBigInteger(bitLength);

  // force MSB set
  if (!_testBit(candidate, bitLength - 1)) {
    candidate |= BigInt.one << (bitLength - 1);
  }

  // force odd
  if (candidate.isEven) {
    candidate += BigInt.one;
  }

  while (!_isProbablePrime(candidate, certainty)) {
    candidate += _bigTwo;
    if (candidate.bitLength > bitLength) {
      candidate -= BigInt.one << (bitLength - 1);
    }
  }

  return candidate;
}
