// See file LICENSE for more information.

library api.ecc;

import 'dart:typed_data';

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

export 'ecdh.dart';

/// Standard ECC curve description
abstract class ECDomainParameters {
  /// Get this domain's standard name.
  String get domainName;

  ECCurve get curve;

  List<int>? get seed;

  ECPoint get G;

  BigInt get n;

  /// Create a curve description from its standard name
  factory ECDomainParameters(String domainName) =>
      registry.create<ECDomainParameters>(domainName);
}

/// Type for coordinates of an [ECPoint]
abstract class ECFieldElement {
  BigInt? toBigInteger();

  String get fieldName;

  int get fieldSize;

  int get byteLength;

  ECFieldElement operator +(ECFieldElement b);

  ECFieldElement operator -(ECFieldElement b);

  ECFieldElement operator *(ECFieldElement b);

  ECFieldElement operator /(ECFieldElement b);

  ECFieldElement operator -();

  ECFieldElement invert();

  ECFieldElement square();

  ECFieldElement? sqrt();
}

/// An elliptic curve point
abstract class ECPoint {
  ECCurve get curve;

  ECFieldElement? get x;

  ECFieldElement? get y;

  bool get isCompressed;

  bool get isInfinity;

  Uint8List getEncoded([bool compressed = true]);

  ECPoint? operator +(ECPoint? b);

  ECPoint? operator -(ECPoint b);

  ECPoint operator -();

  ECPoint? twice();

  /// Multiply this point by the given number [k].
  ECPoint? operator *(BigInt? k);
}

/// An elliptic curve
abstract class ECCurve {
  ECFieldElement? get a;

  ECFieldElement? get b;

  int get fieldSize;

  ECPoint? get infinity;

  /// Create an [ECFieldElement] on this curve from its big integer value.
  ECFieldElement fromBigInteger(BigInt x);

  /// Create an [ECPoint] on its curve from its coordinates
  ECPoint createPoint(BigInt x, BigInt y, [bool withCompression = false]);

  ECPoint decompressPoint(int yTilde, BigInt x1);

  /// Decode a point on this curve from its ASN.1 encoding. The different encodings are taken account of, including point
  /// compression for Fp (X9.62 s 4.2.1 pg 17).
  ECPoint? decodePoint(List<int> encoded);
}

/// Base class for asymmetric keys in ECC
abstract class ECAsymmetricKey implements AsymmetricKey {
  /// The domain parameters of this key
  final ECDomainParameters? parameters;

  /// Create an asymmetric key for the given domain parameters
  ECAsymmetricKey(this.parameters);
}

/// Private keys in ECC
class ECPrivateKey extends ECAsymmetricKey implements PrivateKey {
  /// ECC's d private parameter
  final BigInt? d;

  /// Create an ECC private key for the given d and domain parameters.
  ECPrivateKey(this.d, ECDomainParameters? parameters) : super(parameters);
  @override
  bool operator ==(Object other) {
    if (other is! ECPrivateKey) return false;
    return (other.parameters == parameters) && (other.d == d);
  }

  @override
  int get hashCode {
    return parameters.hashCode + d.hashCode;
  }
}

/// Public keys in ECC
class ECPublicKey extends ECAsymmetricKey implements PublicKey {
  /// ECC's Q public parameter
  final ECPoint? Q;

  /// Create an ECC public key for the given Q and domain parameters.
  ECPublicKey(this.Q, ECDomainParameters? parameters) : super(parameters);
  @override
  bool operator ==(Object other) {
    if (other is! ECPublicKey) return false;
    return (other.parameters == parameters) && (other.Q == Q);
  }

  @override
  int get hashCode {
    return parameters.hashCode + Q.hashCode;
  }
}

/// A [Signature] created with ECC.
class ECSignature implements Signature {
  final BigInt r;
  final BigInt s;

  ECSignature(this.r, this.s);

  /// Returns true if s is in lower-s form, false otherwise.
  bool isNormalized(ECDomainParameters curveParams) {
    return !(s.compareTo(curveParams.n >> 1) > 0);
  }

  ///
  /// 'normalize' this signature by converting its s to lower-s form if necessary
  /// This is required to validate this signature with some libraries such as libsecp256k1
  /// which enforce lower-s form for all signatures to combat ecdsa signature malleability
  ///
  /// Returns this if the signature was already normalized, or a copy if it is changed.
  ///
  ECSignature normalize(ECDomainParameters curveParams) {
    if (isNormalized(curveParams)) {
      return this;
    }
    return ECSignature(r, curveParams.n - s);
  }

  @override
  String toString() => '(${r.toString()},${s.toString()})';
  @override
  bool operator ==(Object other) {
    if (other is! ECSignature) return false;
    return (other.r == r) && (other.s == s);
  }

  @override
  int get hashCode {
    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 ==(Object 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);
}

abstract class ECDHAgreement {
  /// initialise the agreement engine.
  void init(ECPrivateKey param);

  /// return the field size for the agreement algorithm in bytes.
  int getFieldSize();

  /// given a public key from a given party calculate the next
  /// message in the agreement sequence.
  BigInt calculateAgreement(ECPublicKey pubKey);
}
