Verifiability¶
Clout Cards implements a cryptographically verifiable audit trail that allows anyone to prove game integrity—even though all data is stored in a traditional PostgreSQL database. The key insight is that the data is signed by tamper-proof code running inside a TEE, making it impossible for anyone (including the operator) to modify historical events without detection.
Build on EigenCloud
The verification architecture described here is powered by EigenCloud. Build your own verifiable applications with TEE-backed cryptographic guarantees. Learn more at the EigenCloud Developer Docs.
The Trust Model¶
Traditional online poker requires trusting the operator not to cheat. Clout Cards inverts this model:
| Traditional Poker | Clout Cards |
|---|---|
| Trust the operator | Trust math and hardware |
| Audit by regulation | Audit by cryptography |
| Opaque game logic | Verifiable signatures |
| Database is source of truth | Database is verifiable cache |
Event Chain Architecture¶
Every state-changing operation in Clout Cards creates an immutable, signed event:
┌─────────────────────────────────────────────────────────────────┐
│ TEE (Trusted Execution Environment) │
│ │
│ RPC Call ──► Process Logic ──► Create Event ──► Sign Event │
│ │ │ │
│ ▼ ▼ │
│ Update Cache Tables EIP-712 Signature │
│ │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ PostgreSQL Database │
│ │
│ Events Table (Source of Truth): │
│ - event_id, kind, payload_json │
│ - digest, sig_r, sig_s, sig_v │
│ - tee_pubkey, tee_version │
│ │
│ Cache Tables (Derived State): │
│ - player_escrow_balances, hands, pots, etc. │
│ │
└─────────────────────────────────────────────────────────────────┘
The Events Table is the cryptographic source of truth. All other tables are caches that can be reconstructed by replaying the event log.
How Digests Are Computed¶
Every event is signed using EIP-712 typed data signatures, the same standard used for signing Ethereum transactions. This provides:
- Structured data - Not just raw bytes, but typed fields
- Domain separation - Signatures are bound to a specific chain and application
- Human readability - Wallets can display what's being signed
EIP-712 Domain¶
All Clout Cards events share this domain:
{
name: 'CloutCardsEvents',
version: '1',
chainId: <network_chain_id>,
verifyingContract: '0x0000000000000000000000000000000000000000'
}
Payload Structure¶
Events are signed as RPCPayload typed data:
const PAYLOAD_TYPES = {
RPCPayload: [
{ name: 'kind', type: 'string' }, // Event type
{ name: 'payload', type: 'string' }, // Canonical JSON
{ name: 'nonce', type: 'uint256' }, // For replay protection
],
};
Digest Computation¶
The digest (hash) is computed using the standard EIP-712 algorithm:
This produces a 32-byte hash that uniquely represents the event payload within the domain context.
How Signatures Are Verified¶
Each event stored in the database contains:
| Field | Description |
|---|---|
payloadJson |
The canonical JSON of the game action |
digest |
The EIP-712 hash of the payload |
sigR, sigS, sigV |
ECDSA signature components |
teePubkey |
The TEE's Ethereum address |
Verification Steps¶
To verify any event:
- Recompute the digest from
payloadJsonusing EIP-712 - Verify digest matches the stored
digestfield - Recover the signer from the signature using ECDSA recovery
- Compare addresses - recovered address must match
teePubkey
// Client-side verification (simplified)
const message = {
kind: event.kind,
payload: event.payloadJson,
nonce: BigInt(0),
};
// Recompute digest
const computedDigest = ethers.TypedDataEncoder.hash(domain, PAYLOAD_TYPES, message);
// Recover signer from signature
const recoveredAddress = ethers.recoverAddress(computedDigest, {
r: event.sigR,
s: event.sigS,
v: event.sigV,
});
// Verify signer matches TEE
const isValid = recoveredAddress.toLowerCase() === event.teePubkey.toLowerCase();
If verification succeeds, you have mathematical proof that: - The payload has not been modified since signing - The signature was created by the holder of the TEE private key - The TEE private key never leaves the secure enclave
Deck Commitment Verification¶
For poker, the deck shuffle must be provably fair. Clout Cards uses a commit-reveal scheme:
At Hand Start¶
- TEE generates a random 256-bit nonce
- TEE shuffles the deck using secure randomness
- TEE computes commitment:
hash = keccak256(JSON.stringify(deck) + nonce) - Only the
shuffleSeedHashis published in thehand_startevent
During the Hand¶
- Players cannot reverse-engineer the deck from the hash
- The nonce makes brute-forcing computationally infeasible
- Cards are dealt according to the pre-committed deck order
At Hand End¶
- TEE reveals the full
deckarray and thenonce - Anyone can verify:
keccak256(deck + nonce) === shuffleSeedHash - If they match, the deck was committed at the start and never modified
// Deck verification
const deckJson = JSON.stringify(deck);
const computedHash = ethers.keccak256(ethers.toUtf8Bytes(deckJson + nonce));
const isValid = computedHash === shuffleSeedHash;
This proves that the TEE could not have changed the deck during the hand—for example, to deal favorable cards to a particular player.
The History Explorer¶
Clout Cards includes a built-in History Explorer that makes verification accessible to all users. It's available through the "Hand History" panel at any table.
Hand History List¶
The panel shows all completed hands with:
- Hand number and timestamp
- Community cards dealt
- Total pot size
- Winner(s)
Hand Detail View¶
Clicking a hand opens the detailed verification view:
┌─────────────────────────────────────────────────────────────────┐
│ ← Back Hand #142 ✕ │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ 🛡️ TEE Signature Verification ✓ All Valid │ │
│ │ │ │
│ │ 5/5 events verified │ │
│ │ ▼ Show events │ │
│ │ │ │
│ │ Event #234: hand_start ✓ Valid │ │
│ │ Event #235: hand_action ✓ Valid │ │
│ │ Event #236: community_cards ✓ Valid │ │
│ │ Event #237: hand_action ✓ Valid │ │
│ │ Event #238: hand_end ✓ Valid │ │
│ │ │ │
│ │ TEE Address: 0x7B2e...F3a9 │ │
│ └───────────────────────────────────────────────────────────┘ │
│ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ 🔒 Deck Commitment ✓ Verified │ │
│ │ │ │
│ │ Committed Hash: 0x8f3a...2e1b │ │
│ │ Computed Hash: 0x8f3a...2e1b │ │
│ │ │ │
│ │ 👁️ View Full Deck │ │
│ └───────────────────────────────────────────────────────────┘ │
│ │
│ Community Cards: [A♠] [K♠] [Q♠] [J♠] [10♠] │
│ │
│ Pot: 0.05 ETH │
│ Winner: Seat 3 (Royal Flush) │
│ │
└─────────────────────────────────────────────────────────────────┘
What Gets Verified¶
| Check | What It Proves |
|---|---|
| Signature verification | Event was created by the TEE and not modified |
| Digest match | Stored hash matches recomputed hash from payload |
| TEE address match | Signature recovers to the expected TEE address |
| Deck commitment | Cards dealt match the committed deck at hand start |
Expandable Event Details¶
Each event can be expanded to show:
- Full JSON payload
- EIP-712 digest
- Signature components (r, s, v)
- TEE version that signed it
Why Centralized Storage Doesn't Matter¶
A common objection: "If the database is centralized, can't the operator just change the data?"
The answer is no, because of how cryptographic signatures work:
What the Operator Cannot Do¶
| Attack | Why It Fails |
|---|---|
| Modify an event payload | Signature verification fails |
| Change player balances | Events that set balances are signed |
| Alter deck after commit | Hash verification fails |
| Forge new events | Would need TEE private key |
| Sign with different key | TEE pubkey is verifiable via attestation |
What the Operator Could Do (But It's Obvious)¶
| Attack | Detection |
|---|---|
| Delete events | Event ID gaps are visible |
| Withhold events | Players notice missing hands |
| Stop the service | Players can withdraw via smart contract |
The Database as a Verifiable Cache¶
Think of the database as a signed append-only log:
Event 1: { action: "deposit", amount: "0.1 ETH", sig: "..." }
Event 2: { action: "hand_start", players: [...], sig: "..." }
Event 3: { action: "hand_action", type: "RAISE", sig: "..." }
Event 4: { action: "hand_end", winners: [...], sig: "..." }
...
- Any modification invalidates signatures
- Any deletion creates visible gaps
- The TEE private key is the only way to create valid signatures
- The TEE private key cannot be extracted (hardware guarantee)
Verification Without Trust¶
The verification system enables multiple levels of assurance:
Level 1: UI Verification¶
Users can verify hands directly in the app through the History Explorer. Green checkmarks indicate valid signatures and deck commitments.
Level 2: Manual Verification¶
Developers can export event data and verify signatures using standard Ethereum libraries:
# Export events
psql -c "SELECT * FROM events WHERE kind = 'hand_end'" > events.json
# Verify with ethers.js, web3.js, or any EIP-712 library
Level 3: Full Audit¶
For complete verification:
- Get the TEE attestation from EigenCloud
- Verify the attestation proves specific code is running
- Verify the TEE public key matches the attestation
- Verify all event signatures against that public key
- Replay events to reconstruct all derived state
- Compare derived state with database state
Level 4: Open Source¶
The entire codebase is open source on GitHub:
- Backend game logic
- TEE signing code
- Frontend verification utilities
- Smart contracts
Anyone can audit the code that runs inside the TEE.
Summary¶
Clout Cards achieves trustless verification through:
| Component | Purpose |
|---|---|
| TEE | Ensures code integrity and key isolation |
| EIP-712 signatures | Cryptographically binds events to TEE |
| Deck commitments | Proves fair dealing without revealing cards |
| Event chain | Creates immutable audit trail |
| History Explorer | Makes verification accessible to all users |
The result is a poker game where:
- ✅ You don't need to trust the operator
- ✅ Every hand can be verified independently
- ✅ Cheating is mathematically impossible (with correct TEE)
- ✅ The database is just a signed cache, not the source of truth
- ✅ Even the operator cannot forge valid signatures
This architecture proves that centralized infrastructure can provide decentralized guarantees when combined with trusted execution and cryptographic verification.