library impl.srp_client;

import 'dart:typed_data';

import 'package:pointycastle/api.dart';
import 'package:pointycastle/srp/srp6_standard_groups.dart';
import 'package:pointycastle/srp/srp6_util.dart';

class SRP6Client implements SRPClient {
  late BigInt N;
  late BigInt g;

  BigInt? a;
  BigInt? A;

  BigInt? B;

  BigInt? x;
  BigInt? u;
  BigInt? S;

  BigInt? M1;
  BigInt? M2;
  BigInt? Key;

  Digest digest;
  SecureRandom random;
  SRP6GroupParameters group;

  SRP6Client(
      {required this.group, required this.digest, required this.random}) {
    g = group.g;
    N = group.N;
  }

  @override
  BigInt? calculateClientEvidenceMessage() {
    // Verify pre-requirements
    if (A == null || B == null || S == null) {
      throw Exception(
          'Impossible to compute M1: some data are missing from the previous operations (A,B,S)');
    }
    // compute the client evidence message 'M1'
    M1 = SRP6Util.calculateM1(digest, N, A, B, S);
    return M1;
  }

  ///S = (B - kg^x) ^ (a + ux)
  BigInt? calculateS() {
    var k = SRP6Util.calculateK(digest, N, g);
    var exp = (u! * x!) + a!;
    var tmp = g.modPow(x!, N) * (k % N);

    return (B! - (tmp % N)).modPow(exp, N);
  }

  @override
  BigInt? calculateSecret(BigInt serverB) {
    B = SRP6Util.validatePublicValue(N, serverB);
    u = SRP6Util.calculateU(digest, N, A, B);
    S = calculateS();
    return S;
  }

  @override
  BigInt? calculateSessionKey() {
    // Verify pre-requirements (here we enforce a previous calculation of M1 and M2)
    if (S == null || M1 == null || M2 == null) {
      throw Exception(
          'Impossible to compute Key: some data are missing from the previous operations (S,M1,M2)');
    }
    Key = SRP6Util.calculateKey(digest, N, S!);
    return Key;
  }

  @override
  BigInt? generateClientCredentials(
      Uint8List salt, Uint8List identity, Uint8List password) {
    x = SRP6Util.calculateX(digest, N, salt, identity, password);
    a = selectPrivateValue();
    A = g.modPow(a!, N);
    return A;
  }

  BigInt? selectPrivateValue() {
    return SRP6Util.generatePrivateValue(digest, N, g, random);
  }

  @override
  bool verifyServerEvidenceMessage(BigInt serverM2) {
    // Verify pre-requirements
    if (A == null || M1 == null || S == null) {
      throw Exception('Impossible to compute and verify M2: '
          'some data are missing from the previous operations (A,M1,S)');
    }
    // Compute the own server evidence message 'M2'
    var computedM2 = SRP6Util.calculateM2(digest, N, A, M1, S);
    if (computedM2.compareTo(serverM2) == 0) {
      M2 = serverM2;
      return true;
    }
    return false;
  }
}
