Shadow Draft Card Game

Card Transitional
authzk-circuitsprivacycommit-revealecs

Transitional example: This example uses an older Cougr pattern and is preserved for compatibility reference. For the current recommended approach, see snake.

A Soroban smart-contract example demonstrating:

  • Hidden-hand draft gameplay - players commit to a card choice via a SHA-256 hash before either reveal is visible, preventing last-second strategy adaptation.
  • stellar-zk proof-backed card validation - when a verification key is registered, each card play must include a Groth16 proof that the chosen card is a member of the active allowed set, validated on-chain without revealing the card ID until both players have committed.
  • DAO-governed format rules - any player can propose banning or unbanning a card from the active season format; proposals execute immediately (modelling a passed governance vote).

Architecture

Components

ComponentFieldsPurpose
DeckComponentplayer, active_formatDeck context and active season format
HandCommitmentComponentcommitment, committed, revealed, revealed_cardHidden-hand state per player
BoardStateComponentround, score_one/two, last_played_*, round_stateVisible board after each resolution
FormatProposalComponentproposal_id, ban_card, unban_card, proposer, vote_count, statusDAO governance proposals
GameStatusComponentstatus, phaseTop-level match lifecycle

Systems

SystemTriggerResponsibility
DraftSystemBoth players committedAdvance PHASE_DRAFT → PHASE_PLAY
ProofValidationSystemplay_cardVerify SHA-256 commitment + optional Groth16 proof
CardPlaySystemplay_cardEnforce active format ban list
RoundResolutionSystemBoth players revealedCompare card powers, update scores, reset or end match
FormatGovernanceSystemsubmit_format_proposalApply accepted ban/unban to banned_cards

Game Flow

init_match(p1, p2)
│
└─► Round N
     ├─ PHASE_DRAFT
     │   ├─ p1: submit_choice({ commitment: sha256(card_id || nonce) })
     │   └─ p2: submit_choice({ commitment: sha256(card_id || nonce) })
     │         → DraftSystem advances to PHASE_PLAY
     │
     ├─ PHASE_PLAY
     │   ├─ p1: play_card({ card_id, nonce, proof, public_inputs })
     │   │       ProofValidationSystem: sha256 check + optional Groth16 verify
     │   │       CardPlaySystem: ban-list check
     │   └─ p2: play_card(...)
     │         → RoundResolutionSystem fires
     │
     └─ Resolution
          higher card_power(card_id) wins the round
          first to ROUNDS_TO_WIN (3) wins the match

stellar-zk Integration

The contract uses cougr-core::zk::groth16::verify_groth16 (compatible with stellar-zk / salazarsebas/stellar-zk) for on-chain proof verification.

When a verification key is registered via set_vk, each play_card call must supply a valid Groth16 proof attesting that the revealed card_id belongs to the active allowed set. This allows players to prove card legality in zero-knowledge without leaking which card they chose before the opponent reveals.

Public inputs: [ hash_of_commitment, format_id ]
Private inputs: [ card_id, nonce ]
Circuit: sha256(card_id || nonce) == commitment AND card_id ∈ allowed_set[format_id]

Format Governance

Governance references: governance.script3.io

A minimal DAO surface is provided via submit_format_proposal. In a production deployment this would gate execution on an on-chain quorum vote. For the reference implementation the proposal is auto-accepted upon submission to keep the flow testable without a separate governance contract.

#![allow(unused)]
fn main() {
// Propose a seasonal card ban
contract.submit_format_proposal(
    proposer,
    ProposalInput { ban_card: 8, unban_card: 0 },
);

// Lift a prior ban
contract.submit_format_proposal(
    proposer,
    ProposalInput { ban_card: 0, unban_card: 8 },
);
}

Contract API

#![allow(unused)]
fn main() {
fn init_match(env: Env, player_one: Address, player_two: Address)
fn submit_choice(env: Env, player: Address, choice: ChoiceInput)
fn play_card(env: Env, player: Address, play: PlayInput)
fn submit_format_proposal(env: Env, proposer: Address, proposal: ProposalInput)
fn get_state(env: Env) -> GameState
fn set_vk(env: Env, vk: VerificationKey)
}

Validation Commands

cd examples/shadow_draft_card_game
cargo fmt --check
cargo clippy --all-targets --all-features -- -D warnings
cargo test
stellar contract build