Miden Arena is a fully peer-to-peer game with no backend. Players stake 10 MIDEN each before a match, and the winner should receive the 20 MIDEN pot. However, combat resolution runs client-side (TypeScript), so there is no on-chain proof of who won. A malicious client could claim victory and withdraw the opponent's stake.
Introduce a lightweight verification service that reads publicly available game moves from the Miden blockchain, replays the deterministic combat engine, and attests the winner to an on-chain escrow account.
Player A ──── 10 MIDEN ────► Escrow Account ◄──── 10 MIDEN ──── Player B
│
│ "Player A won"
│ (signed by verification service)
│
Verification Service
(reads public notes,
replays combat engine)
│
▼
Escrow Account ──── 20 MIDEN ────► Player A (winner)
Before the match, both players send 10 MIDEN to a shared escrow account (a Miden account with custom logic). The escrow holds the pot until the verification service attests the winner.
Players play normally using the existing commit-reveal protocol:
- Commit: each player sends
SHA-256(move + nonce)as a public note - Reveal: after both commits are received, each player reveals
move + nonce
All commit and reveal notes use noteType: "public", so the full move history is recorded on the Miden blockchain. The commit-reveal protocol ensures neither player can peek at the other's move before committing — the hash hides the move, and the random nonce prevents brute-forcing (only ~20 possible moves, but the 256-bit nonce makes hash preimage infeasible).
After the match ends:
- The winning player calls the verification service (via HTTP) with the game ID / escrow note IDs
- The service reads all public commit/reveal notes for that game from the Miden RPC
- The service replays the game using the same deterministic combat engine (
src/engine/combat.ts) - The service verifies the reported winner matches the replayed result
- The service sends a signed payout note to the escrow account, specifying the winner's account ID
The escrow account's custom MASM logic:
- Checks
note.sender == trusted_verification_service_id(hardcoded at deployment) - Reads the winner's account ID from the note inputs
- Sends 20 MIDEN to the winner's session wallet
Since every Miden transaction is STARK-proven, the note.sender field cannot be forged — the network rejects transactions not properly authenticated by the sending account's Falcon-512 key.
- Move secrecy: commit-reveal protocol with SHA-256 + random nonce prevents peeking
- Move integrity: both players verify opponent's reveal matches their committed hash
- Service identity: escrow verifies the payout note sender via Miden's cryptographic proofs (unforgeable
note.sender) - Service correctness: since all game inputs are public on-chain, anyone can independently replay the game and verify the service reported the correct winner
- Service availability: if the service goes down, payouts stall (mitigated by timeout refund — see below)
- Service key security: if the service's Falcon-512 key is compromised, an attacker could send fraudulent payout notes
- Service honesty: the service could theoretically lie about the winner, but this is detectable — anyone can replay the public moves and prove the service lied
| Approach | Trust assumption | Complexity |
|---|---|---|
| Current (no staking) | N/A — no stakes at risk | None |
| Client-side honor system | Loser's client honestly sends funds | Low |
| Mutual attestation | A cheater can force a refund (draw) but never steal | Low-medium |
| Verification service (this) | Service is honest and available (publicly auditable) | Medium |
| On-chain MASM combat engine | Fully trustless (network verifies) | Very high |
The escrow implements a timeout refund: if no valid payout note is received within N blocks (e.g., 500 blocks) after both stakes are deposited, either player can reclaim their 10 MIDEN. A stalled service results in a refund, never a loss.
If a player disconnects during commit-reveal, the opponent's client stops sending moves. After the timeout, both players reclaim stakes from the escrow. The commit-reveal notes are P2IDE (Pay-to-ID with Expiry) with a recall height, so protocol notes are also reclaimable.
Since all moves are public, a dispute is trivially resolvable: replay the game from the on-chain move log. The verification service's output is deterministic and reproducible. A governance mechanism could be added later, but for testnet this is unnecessary.
- Exposes an HTTP endpoint:
POST /verifywith game metadata (player IDs, escrow note IDs) - Reads public notes from the Miden testnet RPC
- Replays combat using the existing TypeScript engine (same
resolveTurn()function) - Sends a payout note to the escrow via the Miden client SDK (WASM)
- Runtime: Node.js with the Miden WASM client SDK (
@miden-sdk/miden-sdk) - Combat engine: import directly from
src/engine/combat.ts(shared code between client and service) - Miden interaction: the service holds its own Falcon-512 keypair and has a Miden account on testnet
- State: stateless — all inputs come from on-chain notes, no database needed
Lightweight enough for free/cheap hosting:
- Fly.io / Railway: always-on container, free tier, deploy from Git
- VPS: any small instance ($5/month), run as a Node.js process
- Self-hosted: fine for testnet/development
POST /verify
Body: { escrowAccountId, playerA, playerB, gameNoteIds[] }
Response: { winner, txHash } or { error }
GET /health
Response: { status: "ok", lastSync: <block_height> }
The escrow is a custom Miden account with minimal logic:
- Accept stakes: receive and hold fungible assets from two specified player accounts
- Accept payout instruction: consume a note from the trusted verification service, read the winner's account ID from note inputs
- Disburse: send the full pot to the winner
- Timeout refund: if no payout received within N blocks, allow either player to reclaim their stake
The escrow only needs to verify note.sender == TRUSTED_SERVICE_ID — it does not need to understand game logic.
- Write the MASM account code (accept stakes, verify service sender, disburse, timeout refund)
- Deploy to Miden testnet
- Test with manual note sends
- Node.js service importing the shared combat engine
- Miden SDK integration for reading notes and sending payout
- HTTP endpoint for game verification requests
- Deploy to Fly.io or similar
- Modify
useStaking.tsto send stakes to the escrow account (not to each other) - After game over, call the verification service endpoint instead of local
withdraw() - Display payout status in the Game Over screen
- Add timeout refund UI for edge cases
- Public dashboard showing all verified games and their on-chain move logs
- Open-source the verification service so anyone can run an independent verifier
- Add a "verify this game" button that replays the match from on-chain data in the browser