The trust boundary is the product
WardWallet's whole design is one line the server can never cross. Here is exactly where each secret lives, and why a breach of our infrastructure cannot move a player's funds.
A fully compromised server can refuse a transaction. It cannot authorize one more. Not by policy — by construction.
Generated on the device, never sent
secp256k1, created in the browser or the Unreal plugin, sealed in the OS keystore (DPAPI on Windows, Keychain on macOS/iOS) under AES-256-GCM. The server only ever receives transactions that are already signed.
The key that sets the rules stays with you
The policy is signed with an Ed25519 key that never leaves the studio's browser. The server holds the public key, verifies, and enforces. It cannot widen what you signed.
Verified on both sides of the line
Rules are checked at prepare (server), before signing (device), and at broadcast (server). The player can confirm the call matches the published policy — they don't have to trust us.
A code only the player holds
120-bit code → PBKDF2-HMAC-SHA256, 600,000 iterations, 16-byte salt → AES-256-GCM. The hosted backup is an opaque blob; against a database dump alone an attacker faces AES over a 120-bit secret.
Outbound URLs are fenced
Every webhook and JWKS URL is HTTPS-only, no credentials in the URL, public IPs only, IP re-validated at dial time, redirects refused — the “webhook URL” field can't become an internal network scanner.
Serialized, capped, from your wallet
Top-ups are serialized with an in-process lock plus a Postgres advisory lock on (chain, address); a regression test fires twenty concurrent requests and exactly two pass — the cap. The transaction still comes from the player's own address.
Where every secret lives
| Player key | secp256k1, on the device, in the OS keystore (AES-256-GCM) |
| Policy signing key | Ed25519, in the studio's browser — never on the server |
| Wallet backup | PBKDF2 600k → AES-256-GCM under a 120-bit code the player alone holds |
| Sign-in codes | argon2id, 64 MiB per call, never stored in the clear |
| App secrets | Derived by HKDF-SHA-256 from one master key held outside the database |
Operational hardening
- Master-key rotation is a single command that re-encrypts every blob in one transaction; the ciphertext format is versioned.
- Data minimization: a periodic purge drops expired sign-in codes, sessions, delivered webhooks and old Stripe events on configurable retentions.
- GDPR erasure is built in — a player's personal data goes, the financial record stays, scrubbed and pseudonymous.
- No-store on the authenticated dashboard; strict CSP (
script-src 'self'); production refuses the well-known dev key, a missing SMTP, and a disabled fee ceiling.