Files
nick-doc/04 - Flows/Trezor Safekeeping Flow.md
2026-05-24 11:12:17 +04:00

3.9 KiB

Trezor Safekeeping Flow

This flow adds hardware-backed custody controls without replacing the current payment model. The backend never stores private keys. Trezor support starts as a single hardware signer and is designed to upgrade to multisig later.

Default mode: optional. Existing release/refund flows do not require Trezor proof unless TREZOR_SAFEKEEPING_REQUIRED=true.

Goals

  • Generate a fresh receive address per user/payment from a registered Trezor xpub.
  • Require a Trezor-produced signature before release/refund confirmation when safekeeping enforcement is enabled.
  • Keep SHKeeper and Request Network optional provider paths intact.
  • Preserve the existing Payment model and orchestration surface.

Registration

  1. User connects a Trezor in the frontend and exports an Ethereum account xpub, for example m/44'/60'/0'.
  2. Backend builds a registration challenge:
    • GET /api/trezor/registration-message?xpub=...&registrationAddress=...
  3. The registration address must be the first derived address from the xpub:
    • m/44'/60'/0'/0/0
  4. User signs the challenge with that Trezor address.
  5. Frontend submits:
    • POST /api/trezor/register
    • xpub
    • registrationAddress
    • proofMessage
    • proofSignature
    • optional basePath, deviceLabel
  6. Backend verifies:
    • xpub is public, not private.
    • registration address matches xpub-derived index 0.
    • signature recovers the registration address.
  7. Backend stores only:
    • userId
    • xpub fingerprint
    • xpub
    • base derivation path
    • registration address
    • next address index
    • issued address records

Address Generation

To issue the next payment address:

POST /api/trezor/addresses/next
{
  "purpose": "deposit",
  "paymentId": "..."
}

The backend derives non-hardened receive addresses from the registered xpub:

m/44'/60'/0'/0/{index}

If a paymentId already has an address, the endpoint returns the same address instead of incrementing the index.

Transaction Approval

Before a release/refund confirmation, the admin asks the backend for the exact operation message:

POST /api/trezor/operation-message
{
  "operation": "release",
  "paymentId": "...",
  "transactionHash": "0x...",
  "amount": 100,
  "currency": "USDT",
  "provider": "request.network"
}

The Trezor signs that message. Release/refund confirmation then includes:

{
  "txHash": "0x...",
  "trezor": {
    "message": "Amanat escrow Trezor transaction approval\n...",
    "signature": "0x..."
  }
}

When TREZOR_SAFEKEEPING_REQUIRED=true, confirmReleaseRefundInstruction verifies the signature before calling the payment adapter confirmation path.

Enforcement Flag

TREZOR_SAFEKEEPING_REQUIRED=false

Default is permissive so existing SHKeeper and Request Network flows continue to work. Set it to true only after registering the operating admin's Trezor account and testing the signing path. Any value other than the literal string true is treated as disabled.

Safety Rules

  • Never store Trezor seed words, private keys, or xprv/tprv values.
  • Reject private extended keys at registration.
  • Verify every signature locally before accepting it.
  • Use exact transaction-intent messages; do not accept free-form signatures.
  • Treat generated deposit addresses as public routing metadata, not as proof of payment.
  • Keep ledger availability checks enabled for release/refund accounting.

Upgrade Path To Multisig

The current design stores a single trezor-eoa signer. Later, replace the signer policy with:

  • addressType: safe-multisig
  • a Safe address per tenant/admin group
  • threshold policy, such as 2-of-3
  • Trezor owners as Safe signers
  • release/refund flow creates a Safe transaction and records collected signatures before execution

The payment orchestration API should stay the same: build instruction, collect hardware-backed approval, confirm release/refund, append ledger entry.