Merge branch 'licy183-ecelgamal' into master
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5da7a58..92a2cbc 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,10 @@
 Changelog
 =========
 
+#### Version 3.3.0 (2021-08-12)
+
+* ECElGamal Encryptor and Decryptor
+
 #### Version 3.2.0 (2021-07-29)
 
 * Better ASN1 Dump
diff --git a/CONTRIBUTORS b/CONTRIBUTORS
index 6bbac2b..1d4cc8e 100644
--- a/CONTRIBUTORS
+++ b/CONTRIBUTORS
@@ -17,4 +17,5 @@
 Yurii Baryshev <https://github.com/YuriiBaryshev>
 Graciliano M. Passos <https://github.com/gmpassos>
 Netthaw <https://github.com/Netthaw>
+Uchiha Kakashi <https://github.com/licy183>
 
diff --git a/lib/asymmetric/ec_elgamal.dart b/lib/asymmetric/ec_elgamal.dart
new file mode 100644
index 0000000..2412f3f
--- /dev/null
+++ b/lib/asymmetric/ec_elgamal.dart
@@ -0,0 +1,81 @@
+// See file LICENSE for more information.
+
+library impl.asymmetric.ecc.elgamal;
+
+import 'package:pointycastle/api.dart';
+import 'package:pointycastle/export.dart';
+import 'package:pointycastle/src/platform_check/platform_check.dart';
+
+BigInt _generateK(BigInt n, SecureRandom random) {
+  var nBitLength = n.bitLength;
+  BigInt k;
+  do {
+    k = random.nextBigInteger(nBitLength);
+  } while ((k == BigInt.zero) || (k.compareTo(n) >= 0));
+  return k;
+}
+
+SecureRandom _newSecureRandom() => FortunaRandom()
+  ..seed(KeyParameter(Platform.instance.platformEntropySource().getBytes(32)));
+
+/// The basic ElGamal encryptor using Elliptic Curve
+class ECElGamalEncryptor implements ECEncryptor {
+  ECPublicKey? _key;
+  late SecureRandom _random;
+
+  /// Process a single EC [point] using the basic ElGamal algorithm.
+  @override
+  ECPair encrypt(ECPoint point) {
+    if (_key == null) {
+      throw StateError('ECElGamalEncryptor is not initialised');
+    }
+    var key = _key!;
+    var ec = key.parameters!;
+    var k = _generateK(ec.n, _random);
+    return ECPair(
+      (ec.G * k)!,
+      ((key.Q! * k)! + point)!,
+    );
+  }
+
+  @override
+  void init(CipherParameters params) {
+    AsymmetricKeyParameter akparams;
+    if (params is ParametersWithRandom) {
+      akparams = params.parameters as AsymmetricKeyParameter<AsymmetricKey>;
+      _random = params.random;
+    } else {
+      akparams = params as AsymmetricKeyParameter<AsymmetricKey>;
+      _random = _newSecureRandom();
+    }
+    var k = akparams.key as ECAsymmetricKey;
+    if (!(k is ECPublicKey)) {
+      throw ArgumentError('ECPublicKey is required for encryption.');
+    }
+    _key = k;
+  }
+}
+
+/// The basic ElGamal decryptor using Elliptic Curve
+class ECElGamalDecryptor implements ECDecryptor {
+  ECPrivateKey? _key;
+
+  /// Decrypt an EC [pair] producing the original [ECPoint].
+  @override
+  ECPoint decrypt(ECPair pair) {
+    if (_key == null) {
+      throw StateError('ECElGamalEncryptor is not initialised');
+    }
+    return (pair.y - (pair.x * _key!.d)!)!;
+  }
+
+  @override
+  void init(CipherParameters params) {
+    var akparams = params as AsymmetricKeyParameter<AsymmetricKey>;
+    var k = akparams.key as ECAsymmetricKey;
+    if (!(k is ECPrivateKey)) {
+      throw ArgumentError('ECPrivateKey is required for decryption.');
+    }
+    _key = k;
+  }
+}
diff --git a/lib/ecc/api.dart b/lib/ecc/api.dart
index 8ff895d..d16db16 100644
--- a/lib/ecc/api.dart
+++ b/lib/ecc/api.dart
@@ -193,3 +193,36 @@
     return r.hashCode + s.hashCode;
   }
 }
+
+/// A pair of [ECPoint]s.
+class ECPair {
+  final ECPoint x;
+  final ECPoint y;
+
+  const ECPair(this.x, this.y);
+
+  @override
+  bool operator ==(other) {
+    if (other is! ECPair) return false;
+    return (other.x == x) && (other.y == y);
+  }
+
+  @override
+  int get hashCode => x.hashCode + y.hashCode * 37;
+}
+
+/// The encryptor using Elliptic Curve
+abstract class ECEncryptor {
+  ECPair encrypt(ECPoint point);
+
+  /// Initialize the encryptor.
+  void init(CipherParameters params);
+}
+
+/// The decryptor using Elliptic Curve
+abstract class ECDecryptor {
+  ECPoint decrypt(ECPair pair);
+
+  /// Initialize the decryptor.
+  void init(CipherParameters params);
+}
diff --git a/lib/ecc/ecc_fp.dart b/lib/ecc/ecc_fp.dart
index 7a8ab8e..ced7404 100644
--- a/lib/ecc/ecc_fp.dart
+++ b/lib/ecc/ecc_fp.dart
@@ -197,7 +197,7 @@
 
 /// Elliptic curve points over Fp
 class ECPoint extends ecc.ECPointBase {
-  /// Create a point that encodes with or without point compresion.
+  /// Create a point that encodes with or without point compression.
   ///
   /// @param curve the curve to use
   /// @param x affine x co-ordinate
diff --git a/test/asymmetric/ec_elgamal_test.dart b/test/asymmetric/ec_elgamal_test.dart
new file mode 100644
index 0000000..7218bb3
--- /dev/null
+++ b/test/asymmetric/ec_elgamal_test.dart
@@ -0,0 +1,83 @@
+// See file LICENSE for more information.
+
+library test.asymmetric.ec_elgamal_test;
+
+import 'package:pointycastle/asymmetric/ec_elgamal.dart';
+import 'package:pointycastle/ecc/ecc_fp.dart' as fp;
+import 'package:pointycastle/export.dart';
+import 'package:pointycastle/src/platform_check/platform_check.dart';
+import 'package:test/test.dart';
+import 'package:pointycastle/pointycastle.dart';
+
+import '../test/src/helpers.dart';
+
+SecureRandom _newSecureRandom() => FortunaRandom()
+  ..seed(KeyParameter(Platform.instance.platformEntropySource().getBytes(32)));
+
+void main() {
+  var n = BigInt.parse(
+      '6277101735386680763835789423176059013767194773182842284081');
+  var q = BigInt.parse(
+      '6277101735386680763835789423207666416083908700390324961279');
+  var a = BigInt.parse(
+    'fffffffffffffffffffffffffffffffefffffffffffffffc',
+    radix: 16,
+  );
+  var b = BigInt.parse(
+    '64210519e59c80e70fa7e9ab72243049feb8deecc146b9b1',
+    radix: 16,
+  );
+  var curve = fp.ECCurve(q, a, b);
+  var params = ECDomainParametersImpl(
+    'test_elgamal',
+    curve,
+    curve.decodePoint(
+      createUint8ListFromHexString(
+        '03188da80eb03090f67cbf20eb43a18800f4ff0afd82ff1012',
+      ),
+    )!, // G
+    n,
+  );
+  var pubKey = ECPublicKey(
+    curve.decodePoint(
+      createUint8ListFromHexString(
+          '0262b12d60690cdcf330babab6e69763b471f994dd702d16a5'),
+    ), // Q
+    params,
+  );
+  var priKey = ECPrivateKey(
+    BigInt.parse(
+        '651056770906015076056810763456358567190100156695615665659'), // d
+    params,
+  );
+  var secureRandom = _newSecureRandom();
+  var pRandom = ParametersWithRandom<PublicKeyParameter>(
+    PublicKeyParameter(pubKey),
+    secureRandom,
+  );
+
+  test('ECElgamal encrypt and decrypt test: first', () {
+    var value = BigInt.from(123);
+    var data = (priKey.parameters!.G * value)!;
+    var encryptor = ECElGamalEncryptor();
+    encryptor.init(pRandom);
+    var pair = encryptor.encrypt(data);
+    var decryptor = ECElGamalDecryptor();
+    decryptor.init(PrivateKeyParameter(priKey));
+    var result = decryptor.decrypt(pair);
+    expect(data, equals(result));
+  });
+
+  test('ECElgamal encrypt and decrypt test: second', () {
+    var value =
+        _newSecureRandom().nextBigInteger(pubKey.parameters!.n.bitLength - 1);
+    var data = (priKey.parameters!.G * value)!;
+    var encryptor = ECElGamalEncryptor();
+    encryptor.init(pRandom);
+    var pair = encryptor.encrypt(data);
+    var decryptor = ECElGamalDecryptor();
+    decryptor.init(PrivateKeyParameter(priKey));
+    var result = decryptor.decrypt(pair);
+    expect(data, equals(result));
+  });
+}