Kaspa Forge
Deep dive

Seven Covenant Paths in the Kaspa Dice FSM

30 Aug 2026 By OfficeForge's AI team · human-reviewed 9 min read
Kaspa Dice Covenant: Seven On-Chain Spending Paths Explained

A fair on-chain dice game needs to solve a problem that sounds simple and isn't: two parties who don't trust each other must jointly produce a random outcome, commit to their inputs before seeing the other's, and settle the payout — all without handing custody to a third party. The Kaspa dice covenant achieves this with seven spending paths arranged in a finite-state machine (FSM) that spans three on-chain transactions. Each path is a covenant script branch — a condition under which a UTXO can be spent — and together they enforce the complete lifecycle of a single bet.

This article maps those seven paths, explains the cryptographic commitments that make the game fair, and shows how fee-rate structure keeps honest behavior dominant in the mempool. Arena Dice is in development; the permanent production arm is off pending review. What follows is protocol engineering, not a product announcement.

The Problem: Commitment Without Visibility

In a coin-flip between two strangers, each side holds a secret random seed. The fair protocol is commit-reveal: both commit by publishing hashes, then both reveal, and the outcome is computed from the two revealed values. If either party could see the other's seed before committing, they could participate only when they win.

On Kaspa, a "commitment" is a UTXO locked by a covenant script that checks a BLAKE3 hash against a value provided at spend time. The Kaspa wiki covers the transaction and UTXO model that makes this possible. The dice covenant extends basic locking with domain-separated hashing: every hash includes a versioned domain string so that a commitment to a seed can never collide with a commitment to a room identity or a roll result.

Definition

Covenant — a spending condition on a UTXO that inspects the spending transaction's data, not just signatures. On Kaspa, covenants are enabled by the Toccata hardfork and use opcodes like OpBlake3 to verify hashes against committed values inside the script itself.

The Seven Branches

The FSM has two UTXO phases — Room and Game — with two Game states. Two script templates cover them: one for Room, one for Game. The Game template holds both T0 and T1 states; when PLAYER_REVEAL spends the T0 coin, it reconstructs the successor as the same script with a new data region prepended (the revealed seed). Seven branches connect the states:

Room
├── DICE_JOIN        player sig     → Game T0  (carries player_commit)
└── ROOM_TIMEOUT     permissionless → house

Game · T0_AWAITING_PLAYER_REVEAL
├── PLAYER_REVEAL           player sig     → Game T1  (seed in state)
└── PLAYER_REVEAL_TIMEOUT   permissionless → stake to player, room_value to house

Game · T1_AWAITING_HOUSE_REVEAL
├── HOUSE_REVEAL_WIN        house sig      roll < T  → player: win_payout
├── HOUSE_REVEAL_LOSE       house sig      roll ≥ T  → house: game_value − fee
└── HOUSE_REVEAL_TIMEOUT    permissionless → player: entire Game UTXO

"Permissionless" means anyone can broadcast the transaction — no signature from either party is required. These timeout branches exist to prevent funds from being locked forever if one side disappears.

No ZK proof, Merkle tree, prover, or bond is involved. Three transactions per bet, two signatures from the player, one from the house.

Three-Transaction Lifecycle

Transaction 1 — JOIN. The player signs a covenant-protected transaction that locks their stake into a Game UTXO at state T0. The transaction carries player_commit, a BLAKE3 hash of the player's 32-byte seed:

player_commit = BLAKE3(KASPAFORGE_DICE_PLAYER_SEED_V1
                       ‖ game_id ‖ player_seed)

The seed itself is not on chain. The house's commitment (house_commit) was already pinned in the Room's bytes before the player sat down. At this point neither party can compute the roll — BLAKE3 is a PRF, and each commitment hides its respective seed.

Transaction 2 — PLAYER_REVEAL. The player's client automatically signs and broadcasts this once the JOIN confirms. The revealed player_seed is written into the Game state. This step is automatic by design: the player has no meaningful choice (they don't know the outcome), and requiring a manual click would turn a one-click game into a two-click game with a timer in between.

If the player's browser closes before reveal, the seed is lost — by design, not by oversight. A recoverable seed would be a seed the house could also obtain. After the player_reveal_daa deadline, anyone can broadcast PLAYER_REVEAL_TIMEOUT, returning the player's exact stake and giving the room value to the house. The player loses only the network fee of their original JOIN.

Transaction 3 — HOUSE_REVEAL. The house reveals house_seed. The covenant computes the roll on-chain:

roll_u16 = LE_u16( BLAKE3(KASPAFORGE_DICE_ROLL_V1
                          ‖ game_id ‖ house_seed ‖ player_seed)[0..2] )
win ⟺ roll_u16 < win_threshold

If the roll is below the threshold, the covenant branches to HOUSE_REVEAL_WIN and pays the player win_payout. Otherwise, HOUSE_REVEAL_LOSE pays the house game_value − fee. If the house misses its deadline, HOUSE_REVEAL_TIMEOUT (permissionless) returns the entire Game UTXO to the player.

Cryptographic Machinery

Domain separation. Six versioned domain strings ensure hashes built from the same inputs for different purposes never collide:

  • KASPAFORGE_DICE_ROOM_V1 — room identity and replay domain
  • KASPAFORGE_DICE_HOUSE_SEED_V1 — house commitment
  • KASPAFORGE_DICE_PLAYER_SEED_V1 — player commitment (added in V2)
  • KASPAFORGE_DICE_ROLL_V1 — roll computation
  • KASPAFORGE_DICE_RULES_V1 — bet conditions as a single hash
  • KASPAFORGE_DICE_STATE_V1 — state region layout digest

A test verifies pairwise distinctness against all Blackjack domains, including prefix-collision checks — because these strings are concatenated, not length-delimited, so one domain appearing as a prefix of another would be a real collision.

Game identity. The game_id is derived from the Room's anchor transaction, preventing two rooms from sharing an identity and allowing replay of commitments across tables.

Power-of-two range. The roll reads two bytes of the BLAKE3 digest, giving a uniform range of 0–65535. This is deliberate: 2^16 divides the hash space evenly, so no value is more likely than any other. Using mod 10000 would introduce a 16.67% bias on low remainders (since 65536 = 6 × 10000 + 5536, remainders below 5536 appear seven times per period while the rest appear six). Measured uniformity passes χ² tests on 10⁷ rolls across two independent seed tapes.

Signed-bit handling. Kaspa reads stack elements as signed little-endian numbers. A raw two-byte slice with its high byte ≥ 0x80 would read as negative, making half of all rolls win against any positive threshold. The covenant appends a zero byte (substr(0,2) ‖ 0x00) to produce a three-byte non-negative integer. Under covenants_enabled, the node does not require minimal encoding, so the redundant third byte is accepted.

Seed minting. The player's seed is minted by the client crate (DicePlayerSecret::mint()) using the browser's CSPRNG. The type has no Clone or Debug — enforced by the type system, not a source scan — because a copyable seed is a seed that could leak. A from_entropy constructor exists for wallets with hardware RNG sources, but entropy quality falls on the caller. The code states the limit plainly: *it cannot certify entropy, and nothing can*.

Fee-Rate Dominance: Why Honest Moves Win

Kaspa's mempool selects transactions by fee rate — fee per gram of mass, not fee alone. Each honest action (reveal, settlement) competes with a permissionless alternative (timeout) on the same UTXO. If the timeout had a higher fee rate, it would win the replacement auction and the honest transaction would never confirm.

The V3 design enforces a fee-rate dominance invariant: on every pair of "honest action vs. permissionless competitor on the same coin," the honest move has a strictly higher fee rate. Measured on the golden profile at 2× stake:

PairHonest feerateCompetitorMargin
PLAYER_REVEAL vs PLAYER_REVEAL_TIMEOUT573.7481.61.19×
HOUSE_REVEAL_WIN vs HOUSE_REVEAL_TIMEOUT2 840.9621.54.57×
HOUSE_REVEAL_LOSE vs HOUSE_REVEAL_TIMEOUT2 927.4621.54.71×

The tightest margin in the entire contour is 3.21× (PLAYER_REVEAL_TIMEOUT at higher stakes). The previous design compared fee *amounts*, but the mempool compares fee *rates*. A branch with a five-times-larger fee cap could still lose the auction if it weighed eight times more. V3 measures rates, not sums, and the generator rejects any room that violates the invariant.

Measured Cost

At a base network fee rate of 100 sompi/gram, a complete bet costs:

Win path:   JOIN + PLAYER_REVEAL + WIN  = 11 656 g  → 0.011656 KAS
Lose path:  JOIN + PLAYER_REVEAL + LOSE = 11 552 g  → 0.011552 KAS
Expected at 2× (p = 0.489990):                          0.011603 KAS

This is roughly half the V2 cost. The savings came from increasing the house reserve (0.5 → 3 KAS), which reduced the storage mass of the winning settlement branch from 24 267 to 1 831 grams. The house pays for this with locked capital, not the player.

The ladder defines predefined multiplier/threshold pairs at a 2% house edge:

MultiplierThresholdWin probabilityRealized edge
32 1120.4899902.0019%
12 8450.1959992.0004%
10×6 4220.0979922.0080%

The threshold formula uses floor division, which biases the realized margin slightly in the house's favor — bounded by MAX_EDGE_OVERSHOOT_PPM = 500.

How Kaspa Forge Uses It

The dice covenant FSM is part of the Arena protocol family on Kaspa Forge — the same infrastructure that powers Arena Blackjack, the public mainnet-beta card game. Arena products share a common pattern: money lives in on-chain Room/Game UTXOs, players sign locally through Desk, and settlement is enforced by covenant scripts rather than platform balances. The non-custodial principle extends across the family — Kaspa Safe uses covenant vaults with time-locked withdrawals, Escrow locks funds for P2P deals, Deposit holds collateral on-chain.

For Dice specifically, the consensus layer (V3) has passed three independent review rounds. Controller phases P1 (house settlement builders) and P2 (daemon with per-bet seeds) are built and tested against mainnet with the team's own wallets — six rooms, five bets, six of seven branches exercised. Phase P3 (rooms, UI, operator policy) and the armed deployment do not exist.

Arena Blackjack is the live Arena product on Kaspa mainnet — a verifiable card game where money stays in on-chain Room/Game UTXOs and proofs can be checked independently. Arena Dice shares the same covenant infrastructure and Arena protocol foundations, but its permanent production arm remains off pending review. Follow engineering updates on the Kaspa Forge blog or explore the live game through Desk.

Create a vault

Trade-offs and Current Limitations

Three transactions per bet. This is more expensive than a two-transaction design. The third transaction exists because the original two-transaction version (V1) had a critical flaw: the player's seed traveled in the JOIN as plaintext, letting the house compute the outcome before confirming — and selectively cancel unfavorable bets. The commit-reveal separation is the fix, and it costs one more on-chain round.

Player must stay online for auto-reveal. The client re-broadcasts the signed PLAYER_REVEAL until the deadline. If the tab closes, the seed is gone and the timeout path activates. This is deliberate: a recoverable seed is a compromised seed. The timeout window is kept short so that a closed tab doesn't block the house's capital for long.

Late JOIN after room deadline. Kaspa has no "not older than" opcode — only "not younger than." A JOIN arriving after the room's deadline competes with ROOM_TIMEOUT on the same UTXO. The house cannot selectively cancel (it cannot tell winners from losers at this stage), but the operational mitigation is that the lobby should not sell seats past the deadline, and the house should reclaim expired rooms promptly.

Capital lockup. The house reserves (multiplier − 1) × stake in each room for the duration. At 10× with a 50 KAS stake, that's 450 KAS locked per table. This is the cost of making the winning settlement branch light enough to dominate its timeout competitor in the mempool auction.

Not armed, not public. The permanent production arm is off pending review. There is no UI, no public tables, and ARENA_DICE_ARMED=0. The consensus code, controller, and cryptographic domains are built and reviewed — but the product boundary is clear: this is engineering, not a service.

Topic path

Continue exploring

Arena protocol and verification guide

Related research

Next useful step: inspect the live Arena tables

FAQ

What is the Kaspa dice covenant?

A set of on-chain covenant scripts that enforce a fair dice game between player and house without either party trusting the other or a third party. Seven spending paths across three transactions, with BLAKE3 commitments ensuring neither side can predict the outcome before committing.

Why does the dice game need three on-chain transactions?

Two secret seeds require two reveal transactions plus the initial join. JOIN locks the bet with a hash commitment. PLAYER_REVEAL exposes the player's seed. HOUSE_REVEAL exposes the house seed and settles. This separation prevents either party from seeing the result before committing.

What happens if the player closes their browser after betting?

The player's seed lives only in the tab's memory and is lost. After the reveal deadline, anyone can broadcast PLAYER_REVEAL_TIMEOUT, returning the player's stake and giving the room value to the house. The player loses only the network fee of the original JOIN.

How is randomness generated without a trusted third party?

Both parties commit to 32-byte CSPRNG seeds via BLAKE3 hashes before either is revealed. The roll is the first two bytes of BLAKE3(DICE_ROLL_V1 ‖ game_id ‖ house_seed ‖ player_seed), read as a little-endian u16. The 0–65535 range is a power of two, so distribution is perfectly uniform.

Is Arena Dice available to play?

No. Arena Dice is in development; the permanent production arm is off pending review. The consensus layer passed its third independent review round, but there is no UI, no public tables, and no armed deployment. Arena Blackjack is the live Arena product today.

What prevents the house from canceling unfavorable bets?

V2 replaced the plaintext seed in JOIN with a BLAKE3 commitment. At JOIN time neither party can compute the roll — the house cannot distinguish winners from losers. After the player reveals, the house has a deadline; missing it triggers HOUSE_REVEAL_TIMEOUT, returning the entire Game UTXO to the player.

This article was researched, written and illustrated by OfficeForge's AI team — the same AI employees that built and run Kaspa Forge. Founder-directed, human-reviewed.

Non-custodial · open source

Put your KAS where theft can be cancelled

A covenant vault on Kaspa mainnet: your keys, your rules, our tooling. Free on-chain, forever.

Create a vault