Provably fair spot assignment for live card breaks & giveaways
"A camera proves a result was shown. A receipt proves it was the only possible result."
| # | Spot | Participant |
|---|
Anyone can reproduce this assignment offline using only public information. The commitment was submitted before the entropy existed; the derivation is fully deterministic. No other assignment was possible given this entropy and these lists.
spots and participants
arrays in their original entered order. Compute:response.value β the VDF epoch output.
seed as 4 Γ uint32 (big-endian).
Seed the sfc32 PRNG. Run Fisher-Yates on the participants array.
Assign shuffled[i] β spots[i].
// ββ FairBreak v1 derivation βββββββββββββββββββββββββββββββββββββββββββββ
// derivation: "sha256(entropy|rule_hash) β sfc32 β fisher-yates"
// sfc32 PRNG β Small Fast Counting, 128-bit state
// Algorithm by Chris Doty-Humphrey (Public Domain)
// All bit ops operate on signed 32-bit int; >>> 0 converts to uint32.
function sfc32(a, b, c, d) {
return function next() {
let t = (a + b | 0) + d | 0;
d = d + 1 | 0;
a = b ^ (b >>> 9);
b = c + (c << 3) | 0;
c = (c << 21 | c >>> 11);
c = c + t | 0;
return (t >>> 0) / 4294967296; // returns float in [0, 1)
};
}
// Parse first 16 bytes (= first 32 hex chars) of a SHA-256 hex string
// as 4 Γ uint32 big-endian for sfc32 seeds [a, b, c, d].
function hexToSfc32Seeds(hex) {
return [0, 1, 2, 3].map(i => parseInt(hex.slice(i * 8, i * 8 + 8), 16) >>> 0);
}
// Standard Knuth/Durstenfeld Fisher-Yates in-place shuffle.
function fisherYates(arr, rng) {
for (let i = arr.length - 1; i > 0; i--) {
const j = Math.floor(rng() * (i + 1));
[arr[i], arr[j]] = [arr[j], arr[i]];
}
}
// SHA-256 via Web Crypto API (browser) β or node:crypto.subtle (Node β₯ 19)
async function sha256hex(text) {
const buf = await crypto.subtle.digest(
'SHA-256',
new TextEncoder().encode(text)
);
return Array.from(new Uint8Array(buf))
.map(b => b.toString(16).padStart(2, '0'))
.join('');
}
// Full FairBreak v1 derivation.
// entropyHex : response.value from GET /v1/rng/reveal/{commitment_id}
// ruleHash : SHA-256 of the canonical JSON (step 1 above)
// participants: deduped array in original entered order
async function derive(entropyHex, ruleHash, participants) {
// seed = SHA-256(entropy_hex + "|" + rule_hash)
const seed = await sha256hex(entropyHex + '|' + ruleHash);
const [a, b, c, d] = hexToSfc32Seeds(seed);
const rng = sfc32(a, b, c, d);
const shuffled = [...participants];
fisherYates(shuffled, rng);
return { seed, shuffled };
// Assign: spots[i] β shuffled[i] (remaining spots unassigned if len(participants) < len(spots))
}
Paste a FairBreak receipt JSON to re-run the full derivation and confirm the assignment independently β without trusting this server.