4.1 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 the Request Network payment adapter and legacy provider abstractions intact while adding custody controls.
- Preserve the existing
Paymentmodel and orchestration surface.
Registration
- User connects a Trezor in the frontend and exports an Ethereum account xpub, for example
m/44'/60'/0'. - Backend builds a registration challenge:
GET /api/trezor/registration-message?xpub=...®istrationAddress=...
- The registration address must be the first derived address from the xpub:
m/44'/60'/0'/0/0
- User signs the challenge with that Trezor address.
- Frontend submits:
POST /api/trezor/registerxpubregistrationAddressproofMessageproofSignature- optional
basePath,deviceLabel
- Backend verifies:
- xpub is public, not private.
- registration address matches xpub-derived index
0. - signature recovers the registration address.
- 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 Request Network release/refund 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. The recommended production path is to 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. See PRD - Decentralized Custody and Smart-Contract Escrow Roadmap for the staged Safe-first path before any custom escrow contract.