# `otp`

The `otp` library provides predicates for generating and verifying HOTP and TOTP one-time passwords following:

https://www.rfc-editor.org/rfc/rfc4226

https://www.rfc-editor.org/rfc/rfc6238

The implementation is built on top of the `hmac` and `base32` libraries.

## API documentation

Open the ../../apis/library_index.html#otp link in a web browser.

## Loading

To load all entities in this library, load the `loader.lgt` utility file:

    | ?- logtalk_load(otp(loader)).

## Testing

To test this library predicates, load the `tester.lgt` file:

    | ?- logtalk_load(otp(tester)).

## Public predicates

The library currently provides the following public predicates:

- `otp::hotp/5`

- `otp::totp/5`

- `otp::hotp_verify/7`

- `otp::totp_verify/7`

The arguments are:

- `hotp(Hash, Secret, Counter, Digits, OTP)`

- `totp(Hash, Secret, UnixTime, Digits, OTP)`

- `hotp_verify(Hash, Secret, Counter, Window, Digits, OTP, MatchedCounter)`

- `totp_verify(Hash, Secret, UnixTime, Window, Digits, OTP, MatchedTimeStep)`

The `totp/5` and `totp_verify/7` predicates take an explicit Unix time in seconds. The current implementation uses the standard 30-second time step and the Unix epoch `T0 = 0`.

## Secrets

The `Secret` argument can be given either as a raw byte list or using one of the following Base32 wrapper terms:

- `base32(atom(Atom))`

- `base32(chars(Chars))`

- `base32(codes(Codes))`

For example:

    | ?- otp::hotp(sha1, base32(atom('GEZDGNBVGY3TQOJQGEZDGNBVGY3TQOJQ')), 0, 6, OTP).
    OTP = '755224'
    yes

    | ?- atom_codes('12345678901234567890', Secret),
         otp::hotp(sha1, Secret, 1, 6, OTP).
    OTP = '287082'
    Secret = [49,50,51,52,53,54,55,56,57,48,49,50,51,52,53,54,55,56,57,48]
    yes

## Generation

HOTP generation uses the supplied moving counter directly:

    | ?- atom_codes('12345678901234567890', Secret),
         otp::hotp(sha1, Secret, 9, 6, OTP).
    OTP = '520489'
    yes

TOTP generation first converts the explicit Unix time to the corresponding 30-second time step and then computes an HOTP value for that step:

    | ?- atom_codes('12345678901234567890', Secret),
         otp::totp(sha1, Secret, 59, 8, OTP).
    OTP = '94287082'
    yes

Generated OTP values are returned as zero-padded atoms so that leading zeros are preserved.

## Verification

The `otp::hotp_verify/7` predicate searches forward from the supplied counter through a bounded counter window and returns the matching counter on success.

    | ?- atom_codes('12345678901234567890', Secret),
         otp::hotp_verify(sha1, Secret, 0, 9, 6, '520489', MatchedCounter).
    MatchedCounter = 9
    Secret = [49,50,51,52,53,54,55,56,57,48,49,50,51,52,53,54,55,56,57,48]
    yes

The `otp::totp_verify/7` predicate searches within a symmetric time-step window around the current time step and returns the matching time step on success.

    | ?- atom_codes('12345678901234567890', Secret),
         otp::totp_verify(sha1, Secret, 29, 1, 8, '94287082', MatchedTimeStep).
    MatchedTimeStep = 1
    Secret = [49,50,51,52,53,54,55,56,57,48,49,50,51,52,53,54,55,56,57,48]
    yes

## Hash support

The `Hash` argument must identify a currently loaded hash object that provides `digest/2`, `digest_size/1`, and `block_size/1`, and whose digest size is at least 20 bytes.

In practice, the standard RFC examples in this library use `sha1`, `sha256`, and `sha512` on unbounded-integer backends. Hashes such as `md5` are rejected because their digest size is too short for this library’s validation rules.

## Bounded-backend limitation

The current repository hash implementation only defines `sha1`, `sha256`, and `sha512` on unbounded-integer backends. On bounded-integer backends, the `hashes` library loads `hash_32.lgt`, which does not provide those digest objects.

As a consequence:

- the `otp` library itself still loads on bounded backends

- standard HOTP/TOTP generation with `sha1`, `sha256`, or `sha512` is not available there

- calls such as `otp::hotp(sha1, Secret, Counter, Digits, OTP)` fail with `domain_error(otp_hash, sha1)` on bounded-integer backends

This is a limitation of the currently available hash objects in the repository, not of the OTP control-flow logic itself.