SHOWDOWN FAIRNESS · LOBBY #70

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 Do5L1vf1yHBATarget slot 435575690
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 #70
LIVE API
VRF authority
12En38jGJWeF3FBPcYnKrR3DL9ysE71efJcJAt2D6MMs
Slot
435575690
Slot hash
B4hwBE2y1TDz4jH52o5mfZuiakbmJqA9rupCyNbZV6gV
alpha (published)
d2394d12cd0d73aa638fe1652e0287b7d1c6cc360661d4384957e177de3ddb84
alpha (recomputed)
d2394d12cd0d73aa638fe1652e0287b7d1c6cc360661d4384957e177de3ddb84
Alpha binds slot
alpha === sha256(slotHash ‖ lobbyId)
Operator VRF key
e75d1717431b1c691084425d0a0d96f094c7e72fb9d35a49b7052326d8f48f0e
ECVRF proof valid
verifyVRF(operatorKey, alpha, proof) === true (in-browser)
beta (VRF output)
570e57db5245551154bd44e700438279fe127e729ce3f323b33ad4078fe53abb239cf9e2bd5b745c31b6c06254883def0046cceeeb234ff488c219e0c25d21f1
Published roll
0x570e57db5245551154bd44e700438279fe127e729ce3f323b33ad4078fe53abb
Roll is beta[0:32]
rollHex === beta[0:32]
Depositors
3 cards · USD-weighted
Excluded (service)
7Kmu8C…xL3qCS
Local winner
Do5L1vz86ps7boZ9hwR8CLnA9ywjXnaZ2yK2HMf1yHBA
API winner
Do5L1vz86ps7boZ9hwR8CLnA9ywjXnaZ2yK2HMf1yHBA
Winner agrees
local weighted pick === API winner
On-chain commit
1T8fnF9tcGSHSX2h4pqH7FujKasvqPcArUswd11ApU9
Commit tx
2Xssu5yBVenoau2YKFvZRLyXWzAuwyM9DdDZP1un28gXrU8Mmg9dJJCUxJr8iAbXoVt7U1yPjiWmXvAURMk1F8qb
Owner (verify input)
7Kmu8CbSpoe8LErKz9HojBPeCVz6WXtPGDzh11xL3qCS
Label (verify input)
showdown
Memo (verify input)
pg-showdown-70
Proof (verify input)
b106a089ae682323f77c52e4425a6af34f66f47261aa51cbdea9029e8daaf5b23857081e2c7d4a40dbaf3b72dacb270b29ce1fe25dfef0ff682c107e2d44d24778d4656dc65639eff7862636d8541806

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 $83 from 2 players — Do5L…yHBA won the pot with a 83% share.

The payoff · winner takes the pot
Do5L…yHBA
Beat 1 other player
Took home
$83
Win odds
83.1%
Their stake
$69
Pot
$83
Rake to burns
$0
Deposits · odds = stake / pot · click a slab to zoom
Do5L…yHBAWinner$69 · 83.1% · 2 slabs
8Hpv…sDPT$14 · 16.9% · 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.