/**
   * Call to save the public key (value B in the docs) when received from the server
   *
   * @param publicKey_B B
   * @throws SRPAuthenticationFailedException if B is invalid
   */
  public void setServerPublicKey_B(BigInteger publicKey_B) throws SRPAuthenticationFailedException {
    if (fPublicKey_A == null) {
      throw new IllegalStateException("setSalt_s() has not been called yet.");
    }

    if (publicKey_B.mod(fConstants.largePrime_N).equals(BigInteger.ZERO)) {
      throw new SRPAuthenticationFailedException("B%N == 0");
    }

    BigInteger SRP6_u = SRPUtils.calc_u(fPublicKey_A, publicKey_B);
    if (SRP6_u.mod(fConstants.largePrime_N).equals(BigInteger.ZERO)) {
      throw new SRPAuthenticationFailedException("u%N == 0");
    }

    // S = (B - 3(g^x))^(a + ux)
    BigInteger three_g_pow_x =
        fConstants.srp6Multiplier_k.multiply(
            fConstants.primitiveRoot_g.modPow(fPrivateKey_x, fConstants.largePrime_N));
    BigInteger B_minus_g_pow_x = publicKey_B.subtract(three_g_pow_x);
    BigInteger ux = SRP6_u.multiply(fPrivateKey_x);
    fCommonValue_S =
        B_minus_g_pow_x.modPow(fRandom_a.add(ux), fConstants.largePrime_N)
            .mod(fConstants.largePrime_N);
    fEvidenceValue_M1 = SRPUtils.calcM1(fPublicKey_A, publicKey_B, fCommonValue_S);

    // the MD5 output is the same as the AES key length
    fSessionKey_K = SRPUtils.hashToBytesMD5(fCommonValue_S);
  }
  /**
   * Once the server sends the salt (value s in the docs), call this method to save the value
   *
   * @param salt salt from the server
   */
  public void setSalt_s(BigInteger salt) {
    fPrivateKey_x = SRPUtils.makePrivateKey(fPassword, salt);
    fRandom_a = SRPUtils.random(fConstants);
    fCommonValue_S = null;
    fEvidenceValue_M1 = null;
    fSessionKey_K = null;

    // A = g^a
    fPublicKey_A = fConstants.primitiveRoot_g.modPow(fRandom_a, fConstants.largePrime_N);
  }
  /** Call to calculate the common session key (S/K in the docs) */
  public void computeCommonValue_S() {
    if (fPublicKey_A == null) {
      throw new IllegalStateException("setClientPublicKey_A() has not been called yet.");
    }

    fCommonValue_S =
        fPublicKey_A
            .multiply(fVerifier.verifier_v.modPow(fSRP6_u, fConstants.largePrime_N))
            .modPow(fRandom_b, fConstants.largePrime_N);
    fEvidenceValue_M1 = SRPUtils.calcM1(fPublicKey_A, fPublicKey_B, fCommonValue_S);

    // the MD5 output is the same as the AES key length
    fSessionKey_K = SRPUtils.hashToBytesMD5(fCommonValue_S);
  }
  /**
   * Return the value M(2) that should be sent to the client
   *
   * @return M(2)
   */
  public BigInteger getEvidenceValue_M2() {
    if (fEvidenceValue_M1 == null) {
      throw new IllegalStateException("computeCommonValue_S() has not been called yet.");
    }

    return SRPUtils.calcM2(fPublicKey_A, fEvidenceValue_M1, fCommonValue_S);
  }
  /**
   * When the client sends the public key (value A in the docs) call this method to store the value
   *
   * @param publicKey_A A
   * @throws SRPAuthenticationFailedException if A is invalid
   */
  public void setClientPublicKey_A(BigInteger publicKey_A) throws SRPAuthenticationFailedException {
    if (publicKey_A.mod(fConstants.largePrime_N).equals(BigInteger.ZERO)) {
      throw new SRPAuthenticationFailedException("A%N == 0");
    }

    fPublicKey_A = publicKey_A;
    fSRP6_u = SRPUtils.calc_u(fPublicKey_A, fPublicKey_B);
    if (fSRP6_u.mod(fConstants.largePrime_N).equals(BigInteger.ZERO)) {
      throw new SRPAuthenticationFailedException("u%N == 0");
    }
  }
  /**
   * When the server sends M(2), call this method to validate the number.
   *
   * @param evidenceValueFromServer_M2 M(2) from the server.
   * @throws SRPAuthenticationFailedException if M(2) is incorrect
   */
  public void validateServerEvidenceValue_M2(BigInteger evidenceValueFromServer_M2)
      throws SRPAuthenticationFailedException {
    if (fEvidenceValue_M1 == null) {
      throw new IllegalStateException("computeCommonValue_S() has not been called yet.");
    }

    BigInteger M2 = SRPUtils.calcM2(fPublicKey_A, fEvidenceValue_M1, fCommonValue_S);
    if (!evidenceValueFromServer_M2.equals(M2)) {
      throw new SRPAuthenticationFailedException("M(2) is incorrect");
    }
  }
  /**
   * @param constants constants to use
   * @param verifier the verifier as returned from {@link SRPFactory#makeVerifier(byte[])}
   */
  public SRPServerSession(SRPConstants constants, SRPVerifier verifier) {
    fConstants = constants;
    fVerifier = verifier;
    fRandom_b = SRPUtils.random(fConstants);
    fSRP6_u = null;
    fPublicKey_A = null;
    fCommonValue_S = null;
    fEvidenceValue_M1 = null;
    fSessionKey_K = null;

    // B = 3v + g^b
    fPublicKey_B =
        fVerifier
            .verifier_v
            .multiply(constants.srp6Multiplier_k)
            .add(fConstants.primitiveRoot_g.modPow(fRandom_b, fConstants.largePrime_N));
  }