Expand description
Profile PIN: hashing, and the lockout policy that decides what a guess costs.
The policy half is deliberately pure — it takes the stored counter state and the current time, and returns the decision plus the next state. That is what makes “five wrong guesses then a lockout that survives a restart” testable without a database, a clock, or a running app.
What this is not: at-rest protection. The PIN gates switching to a profile; it does not encrypt that profile’s access token, so anyone holding the database and the keyring has every token regardless. That trade is deliberate and its reasoning lives in DR-268 — a wrapped token would leave a locked profile unable to resume its own downloads or drain its own sync queue until somebody walked past and typed the code.
TRACES: UR-083 | DR-268
Structs§
- PinState
- Persisted counter state for one profile’s PIN.
Enums§
- PinDecision
- What the caller should do with an attempt.
Constants§
- BASE_
LOCKOUT_ 🔒SECS - How long the first lockout lasts. Each subsequent failure doubles it.
- MAX_
ATTEMPTS - Wrong guesses allowed before the first lockout.
- MAX_
LOCKOUT_ 🔒SECS - Ceiling on the doubling, so a forgotten PIN never bricks the tile — the password route is always there, and a lockout measured in hours would push people towards not setting a PIN at all.
Functions§
- evaluate
- Decide an attempt and produce the state to persist.
- hash_
pin - Hash a PIN for storage. Returns a PHC string with the salt embedded.
- validate_
pin - A PIN must be 4–8 digits. Rejecting non-digits here rather than in the pad keeps the rule where the rule is enforced.
- verify_
pin - Compare a candidate PIN against a stored PHC string.