public function verify($otp) {
    $rand = $this->psl['crypt/rand'];

    if($this->clientId === null || $this->clientSecret === null) {
      $this->lastError = 'YUBIKEY_CLIENT_DATA_NEEDED';
      return false;
    }

    if(!self::validOtp($otp)) {
      $this->lastError = 'YUBIKEY_INVALID_OTP';
      return false;
    }
    /* Setup the data needed to make the request. */
    $data['otp']       = $otp;
    $data['id']        = $this->clientId;
    $data['nonce']     = $rand->str(20);
    $data['timestamp'] = 1;
    $data['h']         = $this->sign($data);

    /* Do the request. */
    $response = $this->getResponse($data);
    if($response === false) {
      $this->lastError = 'YUBIKEY_SERVER_ERROR';
      return false;
    }

    /* Check status of response. If not OK return false. */
    if($response['status'] != 'OK') {
      switch($response['status']) {
        case 'REPLAYED_OTP':
          $this->lastError = 'YUBIKEY_SERVER_REPLAYED_OTP';
          break;
        case 'REPLAYED_REQUEST':
          $this->lastError = 'YUBIKEY_SERVER_REPLAYED_REQUEST';
          break;
        case 'BAD_OTP':
          $this->lastError = 'YUBIKEY_SERVER_BAD_OTP';
          break;
        case 'NO_SUCH_CLIENT':
          $this->lastError = 'YUBIKEY_SERVER_NO_SUCH_CLIENT';
          break;
        case 'BAD_SIGNATURE':
          $this->lastError = 'YUBIKEY_SERVER_BAD_SIGNATURE';
          break;
        default:
          $this->lastError = 'YUBIKEY_SERVER_SAYS_NO';
          break;
      }
      return false;
    }

    /* If tokens don't match return false. */
    if($response['otp'] != $otp) {
      $this->lastError = 'YUBIKEY_NO_MATCH';
      return false;
    }

    /* Sign the request to see if it matches signature from server. */
    $signature = $this->sign($response);
    if($signature !== $response['h']) {
      $this->lastError = 'YUBIKEY_BAD_SERVER_SIGNATURE';
      return false;
    }
    return true;
  }