SLAB UPGRADE FAIRNESS · SPIN #1906

Prove. Commit. Verify.

The operator cannot bias whether this spin wins. 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 AND verify everything — the binding, the ECVRF proof, the roll, the outcome — right here in your browser.

Chance 50.00%API outcome WONSlot 435636724
FOUR STEPS · ONE VIEWPORT
Prove > Chance > Commit > Roll
  1. 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 ‖ spinId). The public key is registered + frozen on-chain, so the operator cannot swap keys after the fact.
    beta = VRF(sk, sha256(slotHash ‖ spinId))
  2. Chance
    Your win odds are fixed at spin time
    When you spin, the win chance (chanceBps) is recorded alongside the stake + target. It is the threshold the roll is compared against — it cannot change after the fact.
    win iff roll < chanceBps / 10000
  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 outcome could not be precomputed before the slot resolved.
    on-chain: sha256(proof) @ commit PDA
  4. Roll
    The roll IS the VRF output
    roll = beta[0:32] (the first 32 bytes of the 64-byte VRF output, big-endian). Map it into [0,1); below chanceBps/10000 you WON. Recompute the binding in your browser and verify the proof on Collector Crypt.
    roll = beta[0:32]
SECTION 02

Recompute the roll. Right here. In your browser.

The widget recomputes the VRF input alpha = sha256(slotHash ‖ spinId), verifies the ECVRF proof itself (verifyVRF, RFC 9381) against the operator key, confirms the published roll IS beta[0:32], and decides WON/LOST locally — 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
SPIN #1906
LIVE API
VRF authority
121e5Pn2vsGRAztBfvEin5yqsytx8X1KcJbkQYufi28w
Slot
435636724
Slot hash
73sWnKtqXQFUEteMxWqC1bmDrSyWag4AMnaMdoXApMzG
alpha (published)
cefc26d3909161c94f8e4f1377d89a86968228f1995ecbd27e0e5753149f5795
alpha (recomputed)
cefc26d3909161c94f8e4f1377d89a86968228f1995ecbd27e0e5753149f5795
Alpha binds slot
alpha === sha256(slotHash ‖ spinId)
Operator VRF key
e75d1717431b1c691084425d0a0d96f094c7e72fb9d35a49b7052326d8f48f0e
ECVRF proof valid
verifyVRF(operatorKey, alpha, proof) === true (in-browser)
beta (VRF output)
6751ffbe6621f0d1acae929041740a3e05e02a3e6fb9d0c5c5406d94bca2a8495512315057d8d8eaf1aa2d8bc4cca6e6fe1e489d2e5dcc6fc4d6b8b6560695cd
Published roll
0x6751ffbe6621f0d1acae929041740a3e05e02a3e6fb9d0c5c5406d94bca2a849
Roll is beta[0:32]
rollHex === beta[0:32]
Win threshold
roll < 0.5 (50.00% · 5000 bps)
Roll value
0.403594955429
Local outcome
WON
API outcome
WON
Outcome agrees
local === API outcome
Stake
$15.79 USDC
Target mint
4zmSAs…6y5Q1d
On-chain commit
14owwH1CtzFgSEANx3eDMFnVr2zj3Uk3jrpZuZQ8wyhi
Commit tx
5BnUxS28DeugfzDzwet8djt7DWqSquVo2yPmiMCyAdtQPw3eu38cwiCpardrmSimhBiR5yoAx6YzxPxabqTj5wDt
Owner (verify input)
8jFytfeNKfebmkjTgvWVUExN4ZiogiTUJnYmDRPL5PFo
Label (verify input)
upgrade
Memo (verify input)
pg-upgrade-1906
Proof (verify input)
85b9dcb5e412e496228845cf1c2e37a8443fe44370dd71b80540bf8cbf44d54012641f74aa94c8b218477e4b513cceef64e0606a949ee077efba6c32d07dac7f8bfec9bae3267d7a477b206e3b5d3a06
$15.79$15.79Staked$15.79USDC · Cash stake
Outcome
WON
Win chance
50%
Stake
$15.79USDC cash
Prize
$30Pokemon Japanese SWSH 25th Anniversary Full Art Flying Pikachu VMAX #24 · CGC 10
REFERENCE IMPLEMENTATION

Copy-paste. Run it locally. Same answer.

upgrade-vrf-roll.tsTypeScript
import { sha256 } from '@noble/hashes/sha256';

// alpha = sha256(slotHash(32B) || utf8(spinId)) — the public VRF input.
export function vrfAlphaHex(slotHashHex: string, spinId: number): string {
  const slot = Buffer.from(slotHashHex.replace(/^0x/, ''), 'hex');
  const id = new TextEncoder().encode(String(spinId));
  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);
}

// Same [0,1) map + chance compare as the worker.
export function isWin(rollHex: string, chanceBps: number): boolean {
  const n = BigInt(rollHex);
  const unit = Number((n * 1_000_000_000_000n) / 2n ** 256n) / 1e12;
  return unit < chanceBps / 10_000;
}

// Verify the ECVRF proof itself in-browser (no server, no CC dependency):
//   import { verifyVRF, hexToBytes } from '@collectorcrypt/ecvrf';
//   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.