ellite/Wallos · error · InvalidArgumentException

Invalid "counter" parameter.

Error message

Invalid "counter" parameter.

What it means

HOTP::getCounter reads the 'counter' parameter and throws InvalidArgumentException unless it is an int >= 0. The library enforces that the stored HOTP counter is a non-negative integer before use in URI generation or verification.

Solutions

  1. Set a valid counter before use: $hotp->setParameter('counter', 0); or pass counter in HOTP::create().
  2. Cast values when loading from external storage: (int) $row['counter'] before storing.
  3. When parsing a provisioning URI, ensure it includes counter=<int> in the query string.
  4. Guard with is_int($counter) && $counter >= 0 before calling verify()/getProvisioningUri().

Example fix

// before
$hotp = HOTP::createFromSecret($secret); // counter never set
$uri = $hotp->getProvisioningUri(); // throws
// after
$hotp = HOTP::create($secret, 0, 'sha1', 6); // counter provided
$uri = $hotp->getProvisioningUri();
Defensive patterns

Strategy: validation

Validate before calling

$counter = $hotp->getParameter('counter');
if (!is_int($counter) || $counter < 0) {
    $hotp->setParameter('counter', 0);
}

Type guard

function hasValidCounter(object $hotp): bool {
    $c = $hotp->getParameter('counter');
    return is_int($c) && $c >= 0;
}

Try / catch

try {
    $uri = $hotp->getProvisioningUri();
} catch (InvalidArgumentException $e) {
    $hotp->setParameter('counter', 0);
    $uri = $hotp->getProvisioningUri();
}

Prevention

When it happens

Trigger: Calling getCounter() (directly or via getProvisioningUri()/verify()) when the 'counter' parameter was never set, was set to a non-int, or was stored as a numeric string from parsing a provisioning URI query parameter.

Common situations: Creating an HOTP object without calling setParameter('counter', ...), loading a provisioning URI lacking a counter= query parameter, or counter arriving as string from JSON/DB storage.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of ellite/Wallos@52820e87ca (2026-09-13). Data as JSON: /api/errors/b9f09c40102fd815. Report an issue: GitHub.

Appendix: source

Thrown at libs/OTPHP/HOTP.php:55

        $htop->setCounter(self::DEFAULT_COUNTER);
        $htop->setDigest(self::DEFAULT_DIGEST);
        $htop->setDigits(self::DEFAULT_DIGITS);

        return $htop;
    }

    public static function generate(): self
    {
        return self::createFromSecret(self::generateSecret());
    }

    /**
     * @return 0|positive-int
     */
    public function getCounter(): int
    {
        $value = $this->getParameter('counter');
        (is_int($value) && $value >= 0) || throw new InvalidArgumentException('Invalid "counter" parameter.');

        return $value;
    }

    public function getProvisioningUri(): string
    {
        return $this->generateURI('hotp', [
            'counter' => $this->getCounter(),
        ]);
    }

    /**
     * If the counter is not provided, the OTP is verified at the actual counter.
     *
     * @param null|0|positive-int $counter
     */
    public function verify(string $otp, null|int $counter = null, null|int $window = null): bool
    {

View on GitHub (pinned to 52820e87ca)