docs: public BigInteger step2(final BigInteger A, final BigInteger M1) throws SRP6Exception {
  
      // Check arguments
  
      if (A == null)
        throw new IllegalArgumentException("The client public value 'A' must not be null");
  
      this.A = A;
  
      if (M1 == null)
        throw new IllegalArgumentException("The client evidence message 'M1' must not be null");
  
      this.M1 = M1;
  
      // Check current state
      if (state != State.STEP_1)
        throw new IllegalStateException("State violation: Session must be in STEP_1 state");
  
      // Check timeout
      if (hasTimedOut()) throw new SRP6Exception("Session timeout", SRP6Exception.CauseType.TIMEOUT);
  
      // Check A validity
      if (!SRP6Routines.isValidPublicValue(config.N, A))
        throw new SRP6Exception(
            "Bad client public value 'A'", SRP6Exception.CauseType.BAD_PUBLIC_VALUE);
  
      // Check for previous mock step 1
      if (noSuchUserIdentity)
        throw new SRP6Exception("Bad client credentials", SRP6Exception.CauseType.BAD_CREDENTIALS);
  
      if (hashedKeysRoutine != null) {
        URoutineContext hashedKeysContext = new URoutineContext(A, B);
        u = hashedKeysRoutine.computeU(config, hashedKeysContext);
      } else {
        u = SRP6Routines.computeU(digest, config.N, A, B);
        digest.reset();
      }
  
      S = SRP6Routines.computeSessionKey(config.N, v, u, A, b);
  
      // Compute the own client evidence message 'M1'
      BigInteger computedM1;
  
      if (clientEvidenceRoutine != null) {
  
        // With custom routine
        SRP6ClientEvidenceContext ctx = new SRP6ClientEvidenceContext(userID, s, A, B, S);
        computedM1 = clientEvidenceRoutine.computeClientEvidence(config, ctx);
      } else {
        // With default routine
        computedM1 = SRP6Routines.computeClientEvidence(digest, A, B, S);
        digest.reset();
      }
  
      if (!computedM1.equals(M1))
        throw new SRP6Exception("Bad client credentials", SRP6Exception.CauseType.BAD_CREDENTIALS);
  
      state = State.STEP_2;
  
      if (serverEvidenceRoutine != null) {
  
        // With custom routine
        SRP6ServerEvidenceContext ctx = new SRP6ServerEvidenceContext(A, M1, S);
  
        M2 = serverEvidenceRoutine.computeServerEvidence(config, ctx);
      }
  
      updateLastActivityTime();
  
      return M2;
    }