Skip to content
INSURED SHIPPING · VERIFIED AUTHENTICATION · SECURE CHECKOUTINSURED SHIPPING · SECURE CHECKOUT
POLICIES · VAULT PACKS

How a pack draw is checked

A Vault Pack draws one card from a published pool. The seed that picks it is fixed when the pack is sold, and the pool’s hash covers every card, its value and its odds. Once the pack opens, its record carries everything needed to repeat the draw, and the checker on this page repeats it in your browser.

What is fixed, and when

When the pack is sold

The server makes a 32-byte seed from its cryptographic random source and keeps it sealed. From that moment the pack shows the seed’s SHA-256, its seed commit; Your packs prints it while the pack is sealed. The pack also records the pool it was sold against, with that pool’s hash.

When the pack opens

The draw runs on the pool as it stands when the pack opens: a card sold or added since the sale changes it, and the odds are worked out again over what is there. That pool is hashed, every card with its value and its probability, and the seed picks the card from it. The seed is revealed with the card, and the pack’s record keeps both pools, the one at the sale and the one at the opening, so you can compare them.

What a check shows, and what it does not

It shows that the revealed seed is the one committed at the sale, that the pool hash covers the exact cards, values and probabilities the draw used, and that this seed and this pool give the card you received. It does not show more than that. There is no seed of yours in the draw today, so the seed is the shop’s alone; and the pool is the shop’s at the moment of opening, which you can set beside the one published at the sale.

The formula

commit     = SHA-256(seed)
line       = key + " " + value in cents + " " + probability
pool text  = every line, in draw order, joined by "\n"
pool hash  = SHA-256(pool text)                 64 hex characters
roll       = first 13 hex digits of SHA-256(seed + ":" + pool hash),
             read as an integer, divided by 2^52
card       = the first line whose running total of probabilities
             is above the roll
  • Every hash is taken over text, in UTF-8, and written in lowercase hex. The seed is used as its 64 characters, never as the bytes they spell.
  • A probability is written as the shortest decimal that reads back to the same 64-bit number, as JavaScript’s String(n) writes it: 0.00001, 1e-7, 0.30000000000000004. You never write one yourself: the record serves the lines, and every language reads them back to the same number.
  • A key with a space, a % or a character outside plain ASCII has each such byte written as %XX. Certificate numbers have none.
  • Probabilities are added in the order of the lines. A roll exactly on a running total belongs to the next card, and when rounding leaves the total just short of the roll, the last card is drawn.
  • Packs sold before October 15, 2026 carry formula 1, a 16-character pool hash: SHA-256 of key:value for every card, joined by |, cut to its first 16 characters. It covers the cards and their values but not their probabilities, so for those packs the checker repeats the walk and says it is not a check of the card.

Repeat it yourself

With the pool’s lines saved in lines.txt, nothing but Python’s standard library:

import hashlib
seed, pool_hash = "…", "…"
lines = open("lines.txt", encoding="utf-8").read().rstrip("\n").split("\n")
assert hashlib.sha256("\n".join(lines).encode()).hexdigest() == pool_hash
roll = int(hashlib.sha256((seed + ":" + pool_hash).encode()).hexdigest()[:13], 16) / 2**52
total = 0.0
for number, line in enumerate(lines, 1):
    total += float(line.split(" ")[2])
    if roll < total:
        break
print(number, line)

Where a proof comes from

  • Your packs: every opened pack has Check this draw, which reads its proof here.
  • The app: the reveal says what the phone checked, the seed and the card, each on its own line.
  • The API: GET /api/packs/<pack id>/proof, signed in, for your own packs. The answer to opening a pack carries the same proof.