# Pass-OTP compatibility

`crates/storage` owns OTP key URI parsing, code generation, entry discovery and
mutation, HOTP counter updates, and presentation payloads. The CLI only reads
input, asks for typed confirmations, invokes the Rust service, and presents the
returned zeroizing bytes. Runtime code never launches `pass`, `gpg`,
`oathtool`, `otptool`, `qrencode`, or a shell.

## Key URIs and entry layout

IronStorage reads and writes the standard `otpauth://totp/...` and
`otpauth://hotp/...` lines used by `pass-otp`. URI validation covers a Base32
secret, decoded issuer and account label, SHA-1/SHA-256/SHA-512 algorithm,
six- or eight-digit output, positive TOTP period, and required HOTP counter.
Defaults are SHA-1, six digits, and a 30-second TOTP period. Duplicate known
parameters, mismatched label/query issuers, invalid type-specific parameters,
and malformed percent or Base32 encoding are typed failures.

An explicitly supplied URI is preserved byte-for-byte. `otp insert --secret`
constructs the same default TOTP URI shape as upstream and percent-encodes its
issuer and account. Without an explicit entry path, the decoded issuer and
account produce `issuer/account`; an account without an issuer produces the
account path. The frontend must confirm that derived path before storage
changes anything.

Within a multiline password entry, the URI must begin at the start of its own
line. `otp append` adds a line when none exists or replaces the existing line
while preserving every other byte. Multiple URI lines are rejected as
ambiguous instead of guessing which token to use. Validation, confirmation,
recipient resolution, encryption, the atomic repository write, and the
embedded Git commit form one storage-owned operation with rollback on commit
failure.

## Codes and counters

HOTP implements RFC 4226 dynamic truncation. TOTP implements RFC 6238 by using
the selected Unix-time step as the HOTP counter. The HMAC digest and formatted
code are zeroized after use, and tests cover the published SHA-1, SHA-256, and
SHA-512 vectors, alternate periods, six/eight digits, and counters.

For `pass-otp` compatibility, a stored HOTP counter records the last-used
counter. Code generation checks for concurrent entry changes, increments the
counter, rewrites only its URI value, atomically encrypts the updated entry,
and commits `Increment HOTP counter for <entry>.` before returning the code. A
validation, encryption, concurrency, or Git failure therefore never exposes a
code whose counter update was not committed.

OTP codes support terminal or secret-safe clipboard presentation. URI output
supports terminal, clipboard, and the shared storage-owned QR matrix renderer.
Clipboard and QR requests never print the underlying code or URI as plaintext.

Every generated code carries an `OtpCodeValidity` value from `crates/storage`.
`Timed { valid_until, period }` identifies the exclusive Unix-time boundary and
complete display interval for TOTP; frontends call its `remaining_at` method to
present a countdown and request a replacement at zero. The `CounterBased`
variant identifies the HOTP counter whose increment was committed and must be
described as counter-based rather than time-limited. Frontends must not recover
periods from OTP URIs, decrement an assumed interval, or infer the kind from
formatted text. This same contract is intended for the terminal, desktop,
Apple, AutoFill, and watch interfaces.

The CLI preserves code-only standard output for pass-compatible pipelines and
reports the non-secret validity description on standard error. Clipboard
lifecycle feedback remains separate, and clipboard-only presentation does not
echo the code. The TUI observes the system clock during its normal repaint loop,
asks the storage validity value for the remaining seconds, and refreshes at the
exact boundary without treating repainting as user activity.
