# UAT — Trezor Safekeeping (Task #11) **Feature:** Hardware-protected admin release/refund actions via Trezor signing **Branch:** `integrate-main-into-development` **Backend version:** 2.6.59 **Tester role required:** Admin **Browser requirement:** Chromium-based only (Chrome, Edge, Brave) — WebUSB is not supported in Firefox or Safari --- ## Overview Task #11 adds a hardware security layer over two critical admin actions: | Action | Without Trezor | With Trezor | |---|---|---| | Release payment to seller | Admin clicks → tx hash → confirm | Admin clicks → Trezor signs → tx hash → confirm | | Refund payment to buyer | Admin clicks → tx hash → confirm | Admin clicks → Trezor signs → tx hash → confirm | The Trezor requirement is controlled by the backend env var `TREZOR_SAFEKEEPING_REQUIRED`. When `false`, the entire Trezor layer is transparent and the UI behaves exactly as before. An emergency **break-glass** mechanism lets an admin temporarily bypass Trezor for 1 hour with a mandatory Telegram alarm. --- ## Flows Implemented ### Flow A — Trezor Registration (one-time setup) **URL:** `/dashboard/admin/trezor` **When:** First time, or when replacing the registered device. Steps: 1. Admin opens the Trezor Safekeeping page. 2. (Optional) Admin enters a device label in the text field. 3. Admin clicks **Connect Trezor**. 4. Browser shows a USB device picker — admin selects the Trezor. 5. Trezor device shows "Export public key?" — admin confirms on device. 6. Backend issues a registration message tied to the xpub + address. 7. UI moves to **Sign** step — admin clicks **Sign on Trezor**. 8. Trezor device shows the message to sign — admin confirms on device. 9. UI shows "Signature obtained — ready to register." 10. Admin clicks **Register Trezor** → backend stores xpub fingerprint + registration address. 11. Page refreshes and shows the registered account status (address, fingerprint, path, address count). If a Trezor is already registered, the page shows a **Re-register** section instead of the initial registration section. The flow is identical; it overwrites the old record. --- ### Flow B — Release Payment (with Trezor) **URL:** `/dashboard/admin/payments-awaiting-confirmation` **Precondition:** `TREZOR_SAFEKEEPING_REQUIRED=true`, break-glass inactive, Trezor registered. Steps: 1. Admin opens the Payments Awaiting Confirmation list. 2. Clicks the green **Release** button on a payment row. 3. Dialog opens at step 1 — **Build Instruction**. - Shows payment ID and amount. - Admin clicks **Build Instruction**. 4. Backend builds the release instruction (validates payment state). 5. Dialog advances to step 2 — **Sign with Trezor**. - Shows provider and network info from the instruction. - Admin connects Trezor (if not already connected) and clicks **Sign on Trezor**. 6. Trezor device shows the operation message — admin confirms on device. 7. Dialog advances to step 3 — **Enter tx hash**. - Shows "Trezor approved — signer: 0x…" confirmation. - Admin pastes the on-chain transaction hash of the actual transfer. - Admin clicks **Next**. 8. Dialog advances to step 4 — **Confirm**. - Admin clicks **Confirm Release** (green). 9. Backend verifies the Trezor signature against the registered xpub, records the tx hash, and marks the payment released. 10. Dialog closes, list refreshes. --- ### Flow C — Refund Payment (with Trezor) **URL:** `/dashboard/admin/payments-awaiting-confirmation` **Precondition:** Same as Flow B. Identical to Flow B except: - Admin clicks the orange **Refund** button. - Step 1 shows an optional **Refund reason** text field. - Final button is **Confirm Refund** (orange). - `reason` is passed through to the confirm API if provided. --- ### Flow D — Release / Refund without Trezor (break-glass active or safekeeping disabled) **Precondition:** Either `TREZOR_SAFEKEEPING_REQUIRED=false` OR break-glass is active. The dialog runs a shorter 3-step flow: | Step | Label | Action | |---|---|---| | 1 | Build instruction | Same as above | | 2 | Enter tx hash | No Trezor prompt; shows "Break-glass mode active" notice | | 3 | Confirm | Submits without `trezor` field in body | The backend accepts the request because `isTrezorSafekeepingRequired()` returns `false` in both cases. --- ### Flow E — Break-glass Activation **URL:** `/dashboard/admin/trezor` **When:** Emergency — Trezor is unavailable, lost, or broken. Steps: 1. Admin opens the Trezor Safekeeping page. 2. The Break-glass section shows "Trezor safekeeping is active (normal mode)". 3. Admin clicks **Activate Break-glass**. 4. Confirmation dialog appears with warning text. 5. Admin clicks **Activate**. 6. Backend activates break-glass for 1 hour and fires a Telegram alarm to the monitoring bot (`TG_NOTIFY_BOT_TOKEN`). 7. Page shows red alert: "Break-glass is ACTIVE — Activated by: [email] — Expires: [time] — Remaining: ~60 min". 8. The payments-awaiting-confirmation list now passes `skipTrezor={true}` to the dialog. --- ### Flow F — Break-glass Deactivation **URL:** `/dashboard/admin/trezor` Steps: 1. Admin opens the Trezor Safekeeping page while break-glass is active. 2. The section shows the red active alert with remaining time. 3. Admin clicks **Deactivate Break-glass**. 4. Confirmation dialog appears. 5. Admin clicks **Deactivate**. 6. Backend clears break-glass immediately and fires a Telegram "restored" notification. 7. Page shows green "Trezor safekeeping is active (normal mode)". --- ### Flow G — Break-glass auto-expiry No UI action required. - After 1 hour, `isBreakGlassActive()` on the backend returns `false` automatically. - On the next page load or dialog open, `getBreakGlassStatus()` returns `active: false`. - `skipTrezor` reverts to `false` on the payments list. --- ## API Endpoints | Method | Path | Purpose | |---|---|---| | `GET` | `/api/trezor/account` | Get registered Trezor status | | `GET` | `/api/trezor/registration-message` | Get message to sign for registration | | `POST` | `/api/trezor/register` | Submit registration (xpub + proof signature) | | `POST` | `/api/trezor/operation-message` | Get message to sign for a release/refund operation | | `POST` | `/api/payment/:id/release` | Build release instruction | | `POST` | `/api/payment/:id/release/confirm` | Confirm release with tx hash (+ optional trezor sig) | | `POST` | `/api/payment/:id/refund` | Build refund instruction | | `POST` | `/api/payment/:id/refund/confirm` | Confirm refund with tx hash (+ optional trezor sig) | | `GET` | `/api/admin/settings/break-glass` | Get break-glass status | | `POST` | `/api/admin/settings/break-glass` | Activate break-glass | | `DELETE` | `/api/admin/settings/break-glass` | Deactivate break-glass | --- ## Acceptance Tests ### AT-01 — Page loads and shows unregistered state **Precondition:** No Trezor registered in DB. **Steps:** Navigate to `/dashboard/admin/trezor` as admin. **Expected:** - [ ] "No Trezor registered — admin actions are unprotected" warning is shown. - [ ] "Register Trezor" card with device label field and **Connect Trezor** button is visible. - [ ] Break-glass card shows "Trezor safekeeping is active (normal mode)" in green. --- ### AT-02 — Registration flow completes (hardware path) **Precondition:** Trezor Model One or T connected via USB. Chromium browser. **Steps:** Follow Flow A above. **Expected:** - [ ] USB device picker appears after clicking Connect Trezor. - [ ] Trezor device shows public key export prompt. - [ ] After approval, page moves to Sign step and shows the registration message. - [ ] Trezor device shows the message to sign. - [ ] After signing, Submit step shows "Signature obtained". - [ ] After Submit, page shows account status card with green "Trezor registered" alert. - [ ] Chips show correct registration address (matches what was shown on device), fingerprint, path `m/44'/60'/0'`, and address count `0`. --- ### AT-03 — Registration rejected on device **Precondition:** Trezor connected. **Steps:** Click Connect Trezor → reject the export on device. **Expected:** - [ ] Toast shows "Trezor getPublicKey failed: …" or similar error. - [ ] Registration flow resets to idle (Connect button visible again). --- ### AT-04 — Release without Trezor (safekeeping disabled) **Precondition:** Backend `TREZOR_SAFEKEEPING_REQUIRED=false`. **Steps:** 1. Go to `/dashboard/admin/payments-awaiting-confirmation`. 2. Click Release on any payment with status awaiting confirmation. **Expected:** - [ ] Dialog opens with 3-step stepper (Build instruction / Enter tx hash / Confirm). - [ ] Build Instruction succeeds without Trezor prompt. - [ ] "Break-glass mode active — Trezor signature not required." notice shown in step 1. - [ ] Admin enters a dummy tx hash and clicks Next → Confirm. - [ ] `POST /api/payment/:id/release/confirm` called with `txHash` but without `trezor` field. - [ ] Success toast, dialog closes, list refreshes. --- ### AT-05 — Release with Trezor (full hardware path) **Precondition:** `TREZOR_SAFEKEEPING_REQUIRED=true`, Trezor registered, break-glass inactive, Trezor device connected. **Steps:** Follow Flow B above. **Expected:** - [ ] Dialog shows 4-step stepper. - [ ] Sign with Trezor step shows provider/network info from instruction. - [ ] Trezor device prompts to sign the operation message. - [ ] After signing, tx hash step shows "Trezor approved — signer: 0x…". - [ ] Confirm call includes `trezor: { message, signature }` in body. - [ ] Backend returns 200. Toast: "Release confirmed". --- ### AT-06 — Release blocked when Trezor required but not signed **Precondition:** `TREZOR_SAFEKEEPING_REQUIRED=true`, Trezor registered, break-glass inactive. **Steps:** POST directly to `/api/payment/:id/release/confirm` with only `txHash`, no `trezor` field. **Expected:** - [ ] Backend returns 403 or 400 with message indicating Trezor signature is required. --- ### AT-07 — Refund with reason **Precondition:** `TREZOR_SAFEKEEPING_REQUIRED=false` (for UI simplicity). **Steps:** Follow Flow C. Enter "Item not received" in the reason field. **Expected:** - [ ] Reason field visible in Build Instruction step. - [ ] `POST /api/payment/:id/refund/confirm` includes `reason: "Item not received"`. - [ ] Toast: "Refund confirmed". --- ### AT-08 — Break-glass activation fires Telegram alarm **Precondition:** Backend `TG_NOTIFY_BOT_TOKEN` and `TG_NOTIFY_CHAT_ID` set. **Steps:** Follow Flow E. **Expected:** - [ ] Confirmation dialog warns about 1-hour bypass and Telegram alarm. - [ ] After clicking Activate, page shows red break-glass active alert. - [ ] Telegram monitoring chat receives alarm message from the notify bot (NOT the mini app bot). - [ ] `expiresAt` shown is approximately 1 hour from now. - [ ] `remainingMs` shown is approximately 60 min. --- ### AT-09 — Break-glass makes Trezor step disappear in release dialog **Precondition:** `TREZOR_SAFEKEEPING_REQUIRED=true`, break-glass active. **Steps:** 1. Confirm break-glass is active on Trezor page. 2. Go to payments-awaiting-confirmation. 3. Click Release on any payment. **Expected:** - [ ] Dialog shows 3-step stepper (Build instruction / Enter tx hash / Confirm). - [ ] No "Sign with Trezor" step visible. - [ ] Step 1 shows "Break-glass mode active — Trezor signature not required." - [ ] Release completes without Trezor device. --- ### AT-10 — Break-glass deactivation restores Trezor step **Precondition:** Break-glass currently active. **Steps:** 1. Go to Trezor page and click Deactivate Break-glass → confirm. 2. Go to payments-awaiting-confirmation and click Release. **Expected:** - [ ] Trezor page shows green "Trezor safekeeping is active (normal mode)". - [ ] Telegram monitoring chat receives "break-glass restored" notification. - [ ] Release dialog shows 4-step stepper again. --- ### AT-11 — Break-glass expires automatically **Precondition:** Break-glass activated. **Steps:** Wait 1 hour (or manually set `BREAK_GLASS_DURATION_MS` to a short value in `breakGlassRoutes.ts` for testing). **Expected:** - [ ] After expiry, `GET /api/admin/settings/break-glass` returns `active: false`. - [ ] Page refresh shows normal mode. - [ ] Release dialog shows 4-step stepper. --- ### AT-12 — Non-admin cannot access Trezor endpoints **Precondition:** Logged in as buyer or seller. **Steps:** Call `GET /api/trezor/account` and `GET /api/admin/settings/break-glass` with non-admin JWT. **Expected:** - [ ] Both return 403. --- ### AT-13 — Re-registration overwrites old device **Precondition:** Trezor already registered. **Steps:** 1. Note the current registration address on the Trezor page. 2. Click **Register New Device**, complete registration flow with same or different Trezor. **Expected:** - [ ] Page shows updated registration address after re-registration. - [ ] If different device: new fingerprint, new address. - [ ] Previous Trezor's signatures are no longer accepted on subsequent releases. --- ### AT-14 — Cancel at any step resets dialog **Precondition:** Any. **Steps:** Open release dialog, complete Build Instruction step, click Cancel. **Expected:** - [ ] Dialog closes with no action taken. - [ ] Reopening the dialog starts at step 1 with empty state. --- ## Test Environment Setup ### Minimal (no hardware — UI flow only) ```bash # backend/.env TREZOR_SAFEKEEPING_REQUIRED=false ``` Covers: AT-01, AT-04, AT-07, AT-08 to AT-10, AT-12, AT-14 ### With safekeeping enforced (no hardware — break-glass path) ```bash # backend/.env TREZOR_SAFEKEEPING_REQUIRED=true ``` 1. Go to `/dashboard/admin/trezor` 2. Activate break-glass 3. Now release/refund works without Trezor Covers: AT-09, AT-10 ### Full hardware path Requirements: - Trezor Model One or Model T - USB cable - Chromium browser (Chrome / Edge / Brave) - `TREZOR_SAFEKEEPING_REQUIRED=true` - Frontend served on `https://` or `localhost` (WebUSB blocks non-secure production origins) Covers: AT-02, AT-03, AT-05, AT-06, AT-13 --- ## Known Limitations / Out of Scope | Item | Status | |---|---| | Firefox / Safari WebUSB support | Not supported — Trezor registration and signing only works in Chromium | | Multiple Trezor accounts | Only one active registration at a time; re-register to replace | | Break-glass persistence across backend restarts | Intentionally not persisted — restart clears break-glass as a security property | | Payout / sweep actions via Trezor | Backend `assertTrezorSignatureForOperation()` is wired; no UI yet | | Audit log of Trezor operations | Not implemented in this task |