Your cart is empty
Browse CatalogYour sell cart is empty
Add cards from the buylist to get started
A working summary — plain English, then the math, then how to verify it yourself. Read top-to-bottom or jump to any section.
Every random-outcome feature — bounty pulls, pack opens, spin wheel, mystery boxes, raffles — requires you to trust that we didn't nudge the result. "Sorry, common pull" is a reasonable answer on a 70%-common tier, but it's indistinguishable from a server that quietly down-weighted rares.
"Trust us, we wouldn't do that" isn't a verification. So we don't ask for trust. Instead, every roll leaves a trail of public data that lets you replay the exact math and confirm the outcome. If we cheated, you'd catch it. If we didn't, the math checks out.
Before rolling, our server generates a random server_seed (32 bytes) and immediately stores both the seed AND its sha256(server_seed)— the "commitment" — in the database. This happens BEFORE the rolling logic runs. The row's committed_at timestamp is locked in at this point.
The roll itself is deterministic:
roll = sha256(server_seed + ':' + client_seed + ':' + nonce)[0..13] / 2^52 rolled_key = pickWeighted(weights, roll)
Then we update the row with the rolled_key and revealed_at. Because the commit was a separate earlier write, you can see that committed_at < revealed_at— the server couldn't have chosen a seed after seeing the outcome it wanted.
Visit any pull's verify page ( /verify/pull/[id] or /verify/draw/[id] ) and four independent checks run in your browser:
server_seed and re-hash it. The output must equal the commitment published at committed_at.pickWeighted must return the same rarity we claim.committed_at <= revealed_at — and these are two separate SQL writes, so the ordering reflects real time.Commit-reveal is safe against external replay, but it's only as good as our DB integrity. A motivated attacker with write access could edit a row post-hoc and fake the entire proof.
So we add a second layer: every few minutes, a cron takes all newly-revealed draws and hashes them into a Merkle tree. The root lands in fairness_digests and each draw is stamped with its merkle_digest_id and merkle_leaf_index. The leaf format is stable:
leaf = sha256(id + '|' + commitment + '|' + server_seed + '|' + revealed_at_iso)
Once a digest root is published, editing any underlying leaf changes the root — detectable to anyone who cached the old root. The public feed at /api/verify/digests lets auditors (or anyone) snapshot the timeline and compare later.
When you verify a draw, the verifier fetches that digest's leaves, re-hashes the pairs bottom-up, and confirms the root matches the one published to the feed. If any leaf in the batch was edited after the fact, this check fails.
| commitment (sha256 of seed) | public | needed to verify — doesn't reveal the seed |
| server_seed | public AFTER reveal | meaningless before the roll; necessary to verify after |
| client_seed | public (userId anonymised) | the user's id portion is hidden; the random suffix shown |
| nonce, weights, outcome | public | required inputs for re-running the math |
| user_id | private | not exposed by the verifier APIs |
| timestamps | public | commit-precedes-reveal is the whole point |
A single dep-free ES module implementing every check on this page lives at /verify/cambridgetcg-verifier.js. Import it into any browser or Node 18+ script and run our claims through your own code:
import * as v from 'https://cambridgetcg.com/verify/cambridgetcg-verifier.js';
const { verdict } = await v.fetchAndVerifyPull('<pull-id>');
console.log(verdict.allMatch ? '✓ verified' : '✗ failed');Covers primitives (sha256, rollFloat, pickWeighted), per-draw verification (verifyDraw), Merkle inclusion (verifyInclusion), and hash-chain integrity (verifyChain). MIT-licensed, ~250 LOC. If the file ever diverges from this page, the page is canonical — please report the bug.
For scripting or third-party auditing, /api/verify/compute accepts the raw inputs and returns pass/fail without a UI:
curl -s -X POST https://cambridgetcg.com/api/verify/compute \
-H 'Content-Type: application/json' \
-d '{
"commitment": "<hex>",
"server_seed": "<hex>",
"client_seed": "<userId>:<suffix>",
"nonce": 123456789,
"rarity_weights": { "common": 0.7, "uncommon": 0.2, "rare": 0.1 },
"claimed_rarity": "common"
}'CORS-wildcarded, no auth. Response shape includes both the pass/fail flags and the recomputed roll + hash so you can see the math, not just the verdict.
Provably-fair is not "provably honest". It proves: given the weights, the seed produces the outcome; given the commitments, the roll wasn't chosen retroactively; given the digests, the history hasn't been rewritten. It does NOT prove that the weights themselves are the weights you'd want — admins can (legitimately) tune tier weights, adjust pack pools, etc, and those changes are captured in each draw'sweights snapshot at roll time.
The aggregate fairness dashboard shows expected vs observed distributions so you can spot drift over time. If the observed always matches the published weights, the weights are what they say they are.
/verify/pull/[id]/verify/draw/[id]/api/rewards/raffles/[id]/proof