SHOWDOWN FAIRNESS · LOBBY #69

Prove. Commit. Verify.

The operator cannot bias which depositor wins this Slab Showdown lobby. The roll is an RFC 9381 ECVRF output keyed by our on-chain frozen key, with its input bound to the resolved slot hash so it cannot be precomputed. sha256(proof) is committed on-chain. Recompute the binding, verify the ECVRF proof, confirm the roll, and re-derive the weighted winner — right here in your browser.

State paidWinner 8Hpvf2kbsDPTTarget slot 435168796
FOUR STEPS · ONE VIEWPORT
Deposit root > Prove > Commit > Winner
  1. Deposit root
    Every deposited card is recorded at commit
    When the lobby commits we record a Merkle root of every deposit (mint, depositor, valueUsd). The verifier reads back the same per-depositor USD list to re-derive the weighted shares.
    depositMerkleRoot = MerkleRoot(deposits)
  2. Prove
    ECVRF output keyed by our on-chain frozen key
    Once the target slot lands, the operator computes beta = VRF(sk, alpha) where alpha = sha256(slotHash ‖ lobbyId). The public key is registered + frozen on-chain, so the operator cannot swap keys after the fact.
    beta = VRF(sk, sha256(slotHash ‖ lobbyId))
  3. Commit
    sha256(proof) is written on-chain
    The proof commitment lands on-chain via the cc-vrf program (a Light Protocol compressed PDA). Because alpha binds the FUTURE slot hash, the winner could not be precomputed before the slot resolved.
    on-chain: sha256(proof) @ commit PDA
  4. Winner
    The roll IS the VRF output; winner is USD-weighted
    roll = beta[0:32] (the first 32 bytes of the 64-byte VRF output, big-endian). The depositor whose prefix-sum slice of total USD value covers roll mod totalUsd wins. Recompute the binding + the winner in your browser; verify the proof on Collector Crypt.
    winner = weightedPick(roll, deposits)
SECTION 02

Recompute the winner. Right here. In your browser.

The widget recomputes the VRF input alpha = sha256(slotHash ‖ lobbyId), verifies the ECVRF proof itself (verifyVRF, RFC 9381) against the operator key, confirms the published roll IS beta[0:32], then re-derives the USD-weighted winner from that roll — the entire chain, in your browser. Collector Crypt is an independent cross-check that the operator key is the one frozen on-chain.

RECOMPUTED IN BROWSER
ECVRF (RFC 9381) · Collector Crypt VRF
✓ VERIFIED
LOBBY #69
LIVE API
VRF authority
12En38jGJWeF3FBPcYnKrR3DL9ysE71efJcJAt2D6MMs
Slot
435168796
Slot hash
FU5f63RDqec38rNdFszAcMxRdd5Gqi9RumLKbQr8mrUE
alpha (published)
6ffd06048147ddb58d61689043c708fa7ab51ad88ddec7567973f99761790bf4
alpha (recomputed)
6ffd06048147ddb58d61689043c708fa7ab51ad88ddec7567973f99761790bf4
Alpha binds slot
alpha === sha256(slotHash ‖ lobbyId)
Operator VRF key
e75d1717431b1c691084425d0a0d96f094c7e72fb9d35a49b7052326d8f48f0e
ECVRF proof valid
verifyVRF(operatorKey, alpha, proof) === true (in-browser)
beta (VRF output)
65a5d63735bd0a0d23d1d8c57404b88c91477e3d3f185c8b3c15489bf50a32310b520b9afac96093a3bf53724204497a9ae28d8a0aeed871c3d4b00e10ed0484
Published roll
0x65a5d63735bd0a0d23d1d8c57404b88c91477e3d3f185c8b3c15489bf50a3231
Roll is beta[0:32]
rollHex === beta[0:32]
Depositors
3 cards · USD-weighted
Excluded (service)
7Kmu8C…xL3qCS
Local winner
8Hpvf2PK83EVBKSKEYmKWXJaZGQx8zpZE72yv1kbsDPT
API winner
8Hpvf2PK83EVBKSKEYmKWXJaZGQx8zpZE72yv1kbsDPT
Winner agrees
local weighted pick === API winner
On-chain commit
1fm69YST3FC3fsm8TbhGBX3xoarU1VrCa7Aha4jgtaG
Commit tx
3cNY5HvKoNe5xeYpm2y711XiL5tfKv4xHJsNrEXYYXjrgSVW3hHTGYPpN6fUrRBKFGvCSgZHSPRsqP2tAkAu8b5V
Owner (verify input)
7Kmu8CbSpoe8LErKz9HojBPeCVz6WXtPGDzh11xL3qCS
Label (verify input)
showdown
Memo (verify input)
pg-showdown-69
Proof (verify input)
087c18905785c1095983f0a20d6fd2a6518624d136f6b352b68dcf22d74d8577e3dacdc27dfbdbcb2e5bd17be87fc0b6ccd31f0fcd870145d9f7a9a570b47bc629cc729b36d58efeb30cbf9fda8b0a02

Verified in your browser: alpha binds the public slot, the ECVRF proof checks out (verifyVRF, RFC 9381) under the published operator key, the roll IS beta[0:32], and the USD-weighted winner pick lands on the published winner. The one link not provable from this page alone — that the published key is the one we froze on-chain before the lobby — you confirm on Collector Crypt with the owner / label / memo / alpha / proof above.

3 slabs worth $51 from 2 players — 8Hpv…sDPT won the pot with a 61% share.

The payoff · winner takes the pot
8Hpv…sDPT
Beat 1 other player
Took home
$51
Win odds
60.8%
Their stake
$31
Pot
$51
Rake to burns
$0
Deposits · odds = stake / pot · click a slab to zoom
8Hpv…sDPTWinner$31 · 60.8% · 2 slabs
5tiw…PJV6$20 · 39.2% · 1 slab
REFERENCE IMPLEMENTATION

Copy-paste. Run it locally. Same answer.

showdown-vrf-winner.tsTypeScript
import { sha256 } from '@noble/hashes/sha256';
import { verifyVRF, hexToBytes } from '@collectorcrypt/ecvrf';

// alpha = sha256(slotHash(32B) || utf8(lobbyId)) — the public VRF input.
export function vrfAlphaHex(slotHashHex: string, lobbyId: number): string {
  const slot = Buffer.from(slotHashHex.replace(/^0x/, ''), 'hex');
  const id = new TextEncoder().encode(String(lobbyId));
  const msg = new Uint8Array(slot.length + id.length);
  msg.set(slot, 0); msg.set(id, slot.length);
  return Buffer.from(sha256(msg)).toString('hex');
}

// The published roll IS the VRF output beta[0:32].
export function rollFromBeta(betaHex: string): string {
  return '0x' + betaHex.replace(/^0x/, '').slice(0, 64);
}

// USD-weighted winner pick — mirror of pickRoyaleWinnerFromRoll.
// Weight each depositor by round(valueUsd * 1e6) micro-USD, drop the
// service addresses (royale wallet + cold rake-burn), then walk the
// prefix-sum of weights with roll mod totalWeight.
export function weightedWinner(
  rollHex: string,
  cards: { depositor: string; valueUsd: number }[],
  exclude: string[],
): string {
  const ex = new Set(exclude);
  const w = new Map<string, bigint>();
  for (const c of cards) {
    if (ex.has(c.depositor)) continue;
    const micro = BigInt(Math.round(c.valueUsd * 1_000_000));
    w.set(c.depositor, (w.get(c.depositor) ?? 0n) + micro);
  }
  const rows = [...w.entries()].map(([address, balance]) => ({ address, balance }))
    .sort((a, b) => (a.address < b.address ? -1 : a.address > b.address ? 1 : 0));
  let total = 0n; const cum: bigint[] = [];
  for (const r of rows) { total += r.balance; cum.push(total); }
  const off = ((BigInt(rollHex) % total) + total) % total;
  let lo = 0, hi = cum.length;
  while (lo < hi) { const m = (lo + hi) >> 1; if (cum[m] <= off) lo = m + 1; else hi = m; }
  return rows[lo].address;
}

// Verify the ECVRF proof itself in-browser (no server, no CC dependency):
//   verifyVRF(hexToBytes(operatorKey), hexToBytes(alpha), hexToBytes(proof)) === true
// Cross-check operatorKey is the on-chain frozen one at
// https://vrf.collectorcrypt.com/#/verify.

Node ≥ 20 · no dependencies · same output as the browser widget.