Security & Threat Model
Last updated 2026-10-05 • Contact: paymentsonwink@gmail.com (subject: Security) • Chain id 4217 Tempo mainnet (not 42431 testnet)
Current model — honest (what we ship today)
Wink Wallet keys are encrypted on our server so same email restores same address. We can decrypt them.
This is custodial-adjacent, not fully non-custodial. Connected wallets (MetaMask, Phantom, Tonkeeper) are actually non-custodial — we only get address via EIP-1193, never private key. Until Q1 2027, default for new users should be connect MetaMask/Phantom, with Wink Wallet labeled recoverable by us.
How it works today:
1. Key generated in browser via crypto.getRandomValues (CSPRNG), stored in localStorage
2. Browser sends private key in plaintext to /api/wallet/primary/set over HTTPS → server encrypts with ENCRYPTION_KEY (server-side) → stores in user_primary_wallets.encryptedPrivateKey
3. Login on new device with same email → server decrypts with ENCRYPTION_KEY → returns same address, signs via /api/wallet/primary
4. Lose email + no exported seed = funds gone. Stolen inbox = stolen balance (attacker can login and we will decrypt for them).
What we store: Ciphertext + iv in DB. We hold ENCRYPTION_KEY to decrypt. Key travels to server before encryption.
What we cannot do today: We cannot prevent email takeover. We can decrypt if we have DB + ENCRYPTION_KEY.
What happens if DB leaks: Attacker gets ciphertext, needs ENCRYPTION_KEY to decrypt. If they get both DB + key, they can decrypt.
What happens if API logs / proxy / compromised route leaks: Key is sent to server before encryption — a server log, a proxy, or a compromised /api/wallet/primary/set route is a key leak, not just a DB leak. We mitigate with no plaintext logs, HTTPS only, short-lived memory, but the risk is real until client-side encryption ships. That's why we say we can decrypt and why default should be MetaMask/Phantom.
Mitigation Q1 2027: Client-side scrypt where server never sees key or passphrase — browser encrypts, uploads only salt+iv+ciphertext, we cannot decrypt even with DB + key.
Transport: Plaintext private key → /api/wallet/primary/set over HTTPS (current risk) → server encrypts
Storage: localStorage + encrypted blob in DB (user_primary_wallets.encryptedPrivateKey)
Encryption: Server ENCRYPTION_KEY (AES) — server can decrypt (current)
Sync: Same email = same address, auto-restore
Threat: DB + ENCRYPTION_KEY leak = decrypt possible. Server log/proxy/compromised route = key leak (key sent before encryption). Email takeover = balance takeover.
Connected wallets: Only address, never key — actually non-custodial, recommended default
Roadmap Q1 2027: Client-side scrypt, server never sees key, we cannot decrypt
1. What we can see vs what we cannot (current)
- Handle → Tempo address mapping (on-chain, public)
- Payment links, amount, chain of origin (DB for dashboard)
- Tempo tx hashes (public explorer 4217)
- Wink Wallet encrypted blob + we hold ENCRYPTION_KEY to decrypt (current) — custodial-adjacent
- Bridge quotes via Relay API
- Connected wallet private keys (MetaMask/Phantom/Tonkeeper) — only address via EIP-1193
- Your email password — only OTP via Resend
- Future client-encrypted backup passphrase — server will never receive it (Q1 2027 roadmap)
2. Roadmap Q1 2027 — client-encrypted backup (you own password)
For PC + phone auto-restore without us being able to decrypt, we will ship client-side scrypt — NOT shipped yet, so do not claim "you own password" today.
1. Key generated in browser (CSPRNG)
2. User sets backup passphrase (you alone know, no email reset)
3. Browser: scrypt(passphrase, random salt N=32768 r=8 p=1) → key → AES-GCM-256 encrypt(privateKey, iv)
4. Upload ONLY: salt + iv + ciphertext + KDF params — never passphrase, never plaintext
5. New device: download ciphertext, enter same passphrase in browser, decrypt locally — server never sees passphrase
6. DB leak = only ciphertext, needs brute-force of your passphrase. No email reset possible.
Copy will THEN be: "Key encrypted in your browser with passphrase we cannot reset. We store only ciphertext."
Current server-side ENCRYPTION_KEY model is being replaced in Q1 2027. Until then, current model above is truth.
3. Handle registry — the real custody risk
@handle → Tempo address mapping on chain 4217 (not 42431 testnet). If we can change @handle's address, we can redirect payments. We lock it:
- Registry contract on Tempo 4217 mainnet (address to be published, verified source on explorer)
- Only current owner key can update mapping — requires signature from owner private key
- 24h timelock on updates — users can see pending change and cancel
- We cannot redirect without owner signature. Documented in architecture note.
4. What is audited?
| Component | Audited? | Notes |
|---|---|---|
| Tempo L1 + pathUSD 0x20c0…0001 (4217) | Yes | Tempo team, mainnet 18 Mar 2026 |
| OUSD 0x20c0…874be | Yes | OpenStandard, $400M |
| Relay Protocol (13 chains) | Yes | Trail of Bits — we use 13 direct routes, not 54 |
| WinkPay custom contract holding funds | N/A | We have none — solver fills, non-custodial bridge |
| Handle registry + payroll | Planned Q1 2027 | OtterSec/Zellic at $10k volume |
| Wink Wallet encryption (current server-side) | No | Custodial-adjacent, moving to client scrypt Q1 2027 |
5. User-facing copy — use this everywhere (send, claim, wallet, homepage badge, docs)
Use this exact sentence in /send, /claim, /wallet, homepage badge, /docs. Do not say "keys never leave browser" or "you own password" until Q1 2027 client-side model ships. Do not say "non-custodial" for Wink Wallet today — say "encrypted backup, custodial-adjacent, see /security".