> ## Documentation Index
> Fetch the complete documentation index at: https://docs.zerokeyusb.com/llms.txt
> Use this file to discover all available pages before exploring further.

# PIN Verification

> Unlock flow, Counter0 hardware rate-limiting, SHA-256 hash comparison, and adaptive delays for the Master PIN.

ZeroKeyUSB uses a Master PIN (1–16 digits) to authenticate the user. The verification process combines a **software constant-time hash comparison** with a **hardware monotonic counter** in the ATECC608A to block brute-force attempts at both software and hardware levels.

***

## How the PIN is stored

The PIN is **never stored in plaintext**. At PIN setup time (`storeSignature()`):

```mermaid theme={null}
flowchart LR
    PIN["PIN digits<br/>pinArray[16]"] --> CONCAT["Concatenate"]
    SERIAL["Chip serial<br/>9 bytes from ATECC"] --> CONCAT
    CONCAT --> SHA["SHA-256"]
    SHA --> HASH["32-byte hash"]
    HASH --> EEP["EEPROM<br/>@ 0x0048"]
    HASH --> SLOT["ATECC Slot 9<br/>(for future CheckMac)"]
    
    style SHA fill:#dbeafe,stroke:#2563eb,color:#000
    style EEP fill:#dcfce7,stroke:#16a34a,color:#000
    style SLOT fill:#fef3c7,stroke:#d97706,color:#000
```

1. The user's PIN digits are read from `pinArray[16]` (each byte holds a digit value 0–9).
2. `derivePinKey()` computes: `SHA-256(pinArray[16] ∥ chip_serial[9])`.
   * `chip_serial` is the 9-byte unique serial read from the ATECC608A Config Zone.
3. The resulting 32-byte hash is written to EEPROM at `0x0048–0x0067`.
4. The same 32-byte hash is also written to **ATECC slot 9** (for potential future CheckMac use).

The chip serial acts as a hardware salt: the same numeric PIN on a different device produces a completely different 32-byte hash.

***

## Unlock sequence

Each unlock attempt executes the following steps in `verifySignature()`:

```
1. zerokeyAtecc.counterIncrement(Counter0) → new_counter
2. Read threshold from EEPROM [0x0020]
3. If new_counter >= threshold → eraseAll() + halt (hard lockout)
4. derivePinKey(pinArray, derived):
       serial = ATECC608A.readSerial()
       derived = SHA-256(pinArray[16] || serial[9])
5. Read stored hash from EEPROM [0x0048] → stored[32]
6. diff = 0; for i in 0..31: diff |= stored[i] ^ derived[i]  // constant-time
7. If diff == 0:
       writeThreshold(new_counter + 50)   // reset budget
       writeFailedAttemptsCounter(0)
       → ACCESS GRANTED
8. Else:
       incrementFailedAttemptsCounter()
       waitFromEeprom()                   // exponential backoff
       → ACCESS DENIED
```

***

## Hardware counter — hard limit

**Counter0 in the ATECC608A** is a monotonic hardware counter. Its key properties:

* Increments by 1 on every `counterIncrement()` call — once per PIN attempt.
* **Cannot be decremented, reset, or rolled back** by any software, power cycle, or hardware reset.
* Persists across power loss inside the chip's non-volatile memory.
* The threshold stored at EEPROM `0x0020` is a uint32 (little-endian) representing the counter value at which the next wipe occurs.

| Event               | Counter effect | Threshold effect         |
| ------------------- | -------------- | ------------------------ |
| Wrong PIN attempt   | +1             | unchanged                |
| Correct PIN         | +1             | reset to `Counter0 + 50` |
| Counter ≥ threshold | —              | `eraseAll()` triggered   |

With an uninterrupted stream of wrong PINs the counter catches up to the threshold in exactly **50 attempts** and triggers a full credential wipe.

***

## Adaptive delays — soft backoff

Stored at EEPROM `0x0002`. This UX-layer counter is reset on a correct PIN:

| Failed attempts | Wait time          |
| --------------- | ------------------ |
| 0               | none               |
| 1               | 5 s                |
| 2               | 10 s               |
| 3               | 20 s               |
| 4               | 40 s               |
| 5               | 80 s               |
| 6               | 160 s              |
| 7               | 320 s              |
| 8               | 640 s              |
| 9               | 1 280 s            |
| ≥ 10            | 2 560 s (≈ 43 min) |

Formula: `wait = 5 × 2^(min(attempts, 10) − 1)` seconds, capped at 2 560 s.

During the delay the OLED shows a progress bar and countdown. The device does not accept new input until the timer expires.

***

## Secure input handling

* Digits are buffered in `pinArray[16]` in SRAM and cleared after verification.
* Touch events are ignored during the lockout wait (`waitFromEeprom()`).
* SerialUSB **cannot** inject PIN digits — only physical capacitive-touch input is accepted.
* The PIN comparison uses a constant-time XOR accumulator (`diff |= stored[i] ^ derived[i]`) to avoid timing side-channels.

***

## Changing the PIN

Initiated via **Menu → Change PIN** → `storeSignature()`:

1. 3-second on-screen countdown (allows safe abort).
2. `derivePinKey(pinArray, derived)` computes the new hash.
3. New 32-byte hash written to ATECC slot 9.
4. New 32-byte hash written to EEPROM `0x0048`.
5. Current Counter0 is read; threshold is set to `Counter0 + 50`.
6. ATECC ping confirms the chip is still alive. The AES key in slot 8 is **not touched** by PIN setup — it is provisioned once at first boot and is irrevocable.
7. IV is loaded or generated.
8. Setup config flag is written (`0x42`).
9. All credential slots are silently re-initialised with encrypted blanks.

<Note>
  Changing the PIN does **not** change the AES master key or re-encrypt existing credentials. The AES key lives inside ATECC slot 8 and is generated once per device; it cannot be rotated. Existing ciphertext is decryptable with the same chip as long as it is not destroyed.
</Note>

***

## Forgotten PIN

ZeroKeyUSB has no PIN recovery mechanism. The only option is a **factory reset** (`eraseAll()`), which:

1. Shows a 3-second countdown.
2. Loads the device IV.
3. Overwrites all 62 credential slots × 4 pages with encrypted blanks.
4. Clears TOTP metadata.

After reset the device halts with a "LOCKED — reflash" error. The bootloader must be used to flash new firmware and re-provision the device from scratch.

Previously stored credentials are unrecoverable unless you have a plaintext backup exported before the reset.

<Warning>
  Choose a PIN you can remember but others cannot guess. A PIN of 4 digits or fewer is vulnerable to offline SHA-256 dictionary attacks if an adversary gains I²C access to the device.
</Warning>
