Since these are pre-release modules and not listed in the HackerOne scope, I am submitting issues here.
Krunk clue.clsp — Unslashable Premature Reveal (CRITICAL)
In chia-gaming's krunk (Wordle-style) game, the on-chain validator at clue.clsp:70-84 checks whether Alice's word reveal is valid using an if_any_fail block with three conditions. Condition 1 (line 71) checks strlen(MOVE) == 21, condition 2 (line 72) verifies the sha256 commitment binding, and condition 3 (lines 73-80) compares MOVER_SHARE against the expected payout. Inside condition 3, if Bob hasn't guessed the word correctly AND hasn't exhausted all 5 guesses, the code reaches (x "clue reveal before terminal") at line 78 — a CLVM raise intended as a "should be unreachable" assertion. Because the and macro (and.clinc) short-circuits left-to-right, conditions 1 and 2 both evaluate to TRUE for a legitimate premature reveal (correct length, correct commitment), so condition 3 IS evaluated. The raise fires during condition 3's computation, before any comparison can return false, aborting the entire slash spend. Per the referee's security model (referee.clsp:68), a validator that raises makes the cheat permanently unslashable.
The attack sequence:
Alice commits sha256(salt, word) for a valid dictionary word via commit.clsp
Bob makes his first guess (wrong) — state transitions to clue.clsp with bob_guesses = (G1), alice_clues = ()
Alice goes on-chain and submits MOVE = salt||word (21 bytes) via the referee's move path with MOVER_SHARE=0, infohash_c=0 (terminal) — the referee accepts optimistically without validation (referee.clsp:97-108)
Bob attempts to slash with evidence = nil — validator reaches (x "clue reveal before terminal") → raise → slash spend invalid
Bob attempts to slash with 10-byte evidence (dictionary gap) — Alice used a valid dictionary word, so no gap range contains it → unavailable
Bob attempts to slash with 1-byte evidence (wrong-clue index) — alice_clues is empty, so (< evidence (len alice_clues)) at line 64 fails → assert crashes
Bob cannot move — INFOHASH_B=0 blocks the referee's move branch at referee.clsp:99
After timeout, Bob (MOVER) gets MOVER_SHARE=0, Alice (WAITER) collects AMOUNT - 0 = 100% of the pot
Spacepoker end.clsp — Out-of-Range Bitfield Showdown Theft (CRITICAL)
In spacepoker's terminal validator at end.clsp, each player selects 5 of their 7 cards (2 hole + 5 community, built at line 92) using a 1-byte bitfield. The only validation is popcount(mover_bitfield) == 5 at line 31. However, only bits 0–6 correspond to actual cards. The pull_from_bitfield function (lines 61-70) walks the 7-card list and terminates when the list is exhausted — bit 7 (0x80) is silently ignored. A bitfield like 0x8F (bits 0,1,2,3,7) has popcount=5 but selects only 4 cards. When space_hand_eval (space_hand_eval.clinc:39-57) receives 4 distinct-rank cards, group_by_count produces 4 groups, yielding a 9-element hand list. The straight-detection branch at line 49 destructures with (_ _ _ _ _ _ r1 _ _ _ r5) — an 11-position pattern. Accessing position 10 of a 9-element list hits (f nil), which raises "first of non-cons". This raise aborts the slash spend, making the cheat unslashable.
The attack sequence:
The game proceeds normally through betting rounds to the river (N=1)
A call in mid_round.clsp transitions to end.clsp with max_move_size=17
The final revealer (MOVER) submits MOVE = preimage(16 bytes) || bitfield(1 byte) where bitfield = 0x80 | (4 in-range bits) — e.g. 0x8F, which has popcount=5
The referee's move path accepts it: strlen(17) <= MAX_MOVE_SIZE(17), MOVER_SHARE=0 in [0, AMOUNT], infohash_c=0 (terminal) — all pass (referee.clsp:97-108)
Opponent attempts to slash — pull_from_bitfield returns only 4 cards → space_hand_eval builds a 9-element list → 11-position destructure at space_hand_eval.clinc:49 → raise → slash spend invalid
Opponent cannot move — INFOHASH_B=0 blocks further moves
After timeout, the attacker (WAITER after role swap) collects AMOUNT - 0 = 100% of the pot regardless of who actually held the winning hand
Since these are pre-release modules and not listed in the HackerOne scope, I am submitting issues here.
Krunk clue.clsp — Unslashable Premature Reveal (CRITICAL)
In chia-gaming's krunk (Wordle-style) game, the on-chain validator at clue.clsp:70-84 checks whether Alice's word reveal is valid using an if_any_fail block with three conditions. Condition 1 (line 71) checks strlen(MOVE) == 21, condition 2 (line 72) verifies the sha256 commitment binding, and condition 3 (lines 73-80) compares MOVER_SHARE against the expected payout. Inside condition 3, if Bob hasn't guessed the word correctly AND hasn't exhausted all 5 guesses, the code reaches (x "clue reveal before terminal") at line 78 — a CLVM raise intended as a "should be unreachable" assertion. Because the and macro (and.clinc) short-circuits left-to-right, conditions 1 and 2 both evaluate to TRUE for a legitimate premature reveal (correct length, correct commitment), so condition 3 IS evaluated. The raise fires during condition 3's computation, before any comparison can return false, aborting the entire slash spend. Per the referee's security model (referee.clsp:68), a validator that raises makes the cheat permanently unslashable.
The attack sequence:
Alice commits sha256(salt, word) for a valid dictionary word via commit.clsp
Bob makes his first guess (wrong) — state transitions to clue.clsp with bob_guesses = (G1), alice_clues = ()
Alice goes on-chain and submits MOVE = salt||word (21 bytes) via the referee's move path with MOVER_SHARE=0, infohash_c=0 (terminal) — the referee accepts optimistically without validation (referee.clsp:97-108)
Bob attempts to slash with evidence = nil — validator reaches (x "clue reveal before terminal") → raise → slash spend invalid
Bob attempts to slash with 10-byte evidence (dictionary gap) — Alice used a valid dictionary word, so no gap range contains it → unavailable
Bob attempts to slash with 1-byte evidence (wrong-clue index) — alice_clues is empty, so (< evidence (len alice_clues)) at line 64 fails → assert crashes
Bob cannot move — INFOHASH_B=0 blocks the referee's move branch at referee.clsp:99
After timeout, Bob (MOVER) gets MOVER_SHARE=0, Alice (WAITER) collects AMOUNT - 0 = 100% of the pot
Spacepoker end.clsp — Out-of-Range Bitfield Showdown Theft (CRITICAL)
In spacepoker's terminal validator at end.clsp, each player selects 5 of their 7 cards (2 hole + 5 community, built at line 92) using a 1-byte bitfield. The only validation is popcount(mover_bitfield) == 5 at line 31. However, only bits 0–6 correspond to actual cards. The pull_from_bitfield function (lines 61-70) walks the 7-card list and terminates when the list is exhausted — bit 7 (0x80) is silently ignored. A bitfield like 0x8F (bits 0,1,2,3,7) has popcount=5 but selects only 4 cards. When space_hand_eval (space_hand_eval.clinc:39-57) receives 4 distinct-rank cards, group_by_count produces 4 groups, yielding a 9-element hand list. The straight-detection branch at line 49 destructures with (_ _ _ _ _ _ r1 _ _ _ r5) — an 11-position pattern. Accessing position 10 of a 9-element list hits (f nil), which raises "first of non-cons". This raise aborts the slash spend, making the cheat unslashable.
The attack sequence:
The game proceeds normally through betting rounds to the river (N=1)
A call in mid_round.clsp transitions to end.clsp with max_move_size=17
The final revealer (MOVER) submits MOVE = preimage(16 bytes) || bitfield(1 byte) where bitfield = 0x80 | (4 in-range bits) — e.g. 0x8F, which has popcount=5
The referee's move path accepts it: strlen(17) <= MAX_MOVE_SIZE(17), MOVER_SHARE=0 in [0, AMOUNT], infohash_c=0 (terminal) — all pass (referee.clsp:97-108)
Opponent attempts to slash — pull_from_bitfield returns only 4 cards → space_hand_eval builds a 9-element list → 11-position destructure at space_hand_eval.clinc:49 → raise → slash spend invalid
Opponent cannot move — INFOHASH_B=0 blocks further moves
After timeout, the attacker (WAITER after role swap) collects AMOUNT - 0 = 100% of the pot regardless of who actually held the winning hand