A minimum-viable card-minting system with no blockchain, no database, no ledger. Players mine a short code via Proof-of-Work; a stateless signing service validates and signs a certificate that judges can verify offline using the season’s public key.
- Miner (player client): randomly generates codes and checks viability by counting
0characters insha256(code + ":viability")(hex string). - Signer (server): stateless HTTP service that validates code viability, deterministically derives properties from suffix-hashed code, signs a canonical digest, and returns a certificate. Keeps only a private key locally.
- Judge/Verifier: offline app/tool that validates a certificate and signature and recomputes viability and properties.
- Publisher: posts a Season Config JSON that all parties trust.
A single JSON file published at season start (static for the season). The verifier requires cert.v === config.schemaVersion and cert.seasonId === config.seasonId.
{
"schemaVersion": 1,
"seasonId": 1,
"name": "Season 1: Minty Fresh",
"mining": {
"mode": "BLIND",
"hash": "SHA-256",
"gene": { "alphabet": "abcd", "length": 12 },
"viability": { "type": "zeros-in-hex", "suffix": ":viability", "threshold": 16 }
},
"propertiesFromCode": {
"version": 1,
"rules": [
{ "key": "h", "suffix": ":health", "method": "zeros-in-hex-div2-cap10" },
{ "key": "a", "suffix": ":attack", "method": "zeros-in-hex-div2-cap10" }
]
},
"signing": { "algo": "Ed25519", "publicKeyBase64": "…" }
}- Blind mining: pick a gene string; a code is legal iff
count('0' in hex(sha256(code + ":viability")))meets the threshold. - Properties: derived by hashing the same code with property-specific suffixes (e.g.,
":health",":attack") and counting zeros. - The Season Config itself SHOULD be signed (detached signature) and versioned.
code is an ASCII string of length gene.length using only characters from gene.alphabet.
- Mode:
BLINDin v0 (no targeted minting). - The code is the sole mined input in v0.
seed = SHA256(code) (32 raw bytes).
- Viability hash (hex):
v = hex(SHA256(code || ":viability"))where the suffix comes from config. - Property hash (hex) for key with suffix
s:d = hex(SHA256(code || s)).
Let threshold = mining.viability.threshold. Require zeros(v) ≥ threshold, where zeros(x) counts '0' characters in the hex string.
For each property rule { key, suffix }:
- Compute
d = sha256(code + suffix)as a hex string (64 chars). - Let
z = count('0' in d). - Property value =
min(floor(z / 2), 10), lower-bounded at 0.
Concretely (v0):
- Health (
h):suffix=":health" → h = min(floor(zeros/2), 10) - Attack (
a):suffix=":attack" → a = min(floor(zeros/2), 10)
The signer builds a deterministic preimage and signs SHA256(preimage) with Ed25519.
preimage =
"OMT-CERT\0" // ASCII tag
|| u8(version) // certificate version
|| u32be(seasonId)
|| "BLIND" // mining.mode as ASCII
|| u8(geneLen) // code length from config
|| UTF-8(code) // ASCII in practice
|| u8(viability.threshold)
|| u8(viability.zeros)
|| seed(32 bytes)
|| propertyBytes
propertyBytes =
u8(count) || repeated ( u8(keyLen) || UTF-8(key) || u16be(value) )
Notes:
- Property entries are sorted by descending key (lexicographic,
b[0].localeCompare(a[0])) before encoding. - The final
digest = SHA256(preimage)is what gets signed.
{
"v": 1,
"seasonId": 1,
"mining": { "mode": "BLIND", "viabilityZeros": 18, "threshold": 16 },
"code": "bcdddbabdcaa",
"seedHex": "…64-hex…",
"properties": { "h": 3, "a": 4 },
"signatureB64": "…"
}POST /sign
Request:
{ "version": 1, "seasonId": 1, "code": "…" }Server behavior:
- Validate code charset/length against
mining.gene. - Compute
v = sha256_hex(code + viability.suffix); ensurezeros(v) ≥ threshold. seed = sha256(code); derivepropertiesvia the deterministic rule above.- Build canonical digest, sign with Ed25519, return the Certificate JSON.
Response codes (v0 implementation):
200 OKwith certificate on success.400 Bad Requestfor invalid inputs or non-viable code.410 GoneifseasonIddoes not match the active config.
The service is stateless; there is no persistence or duplicate tracking.
Input: Certificate JSON and the Season Config.
Checks:
- Validate
cert.v === cfg.schemaVersionandcert.seasonId === cfg.seasonId. - Validate code charset/length per
cfg.mining.gene. - Recompute
v = sha256_hex(cert.code + cfg.mining.viability.suffix); enforcezeros(v) ≥ cfg.mining.viability.thresholdand== cert.mining.viabilityZeros. - Recompute
seed = sha256(cert.code)and ensurehex(seed) === cert.seedHex. - Recompute
propertiesFromCode(cert.code, cfg.propertiesFromCode)and ensure equality withcert.propertiesfor all keys. - Rebuild canonical digest and verify
Ed25519(signature, digest)usingcfg.signing.publicKeyBase64.
No network required.
- Authenticity: Only the signer’s private key can produce valid certificates.
- Determinism: Anyone can recompute viability & properties → easy counterfeit detection.
- No global auditability: Authority could over-issue; v0 accepts this.
- Duplicates: Not prevented in v0 (server is stateless). Events may locally reject repeated
cardIdif they choose to track. - Key hygiene: Rotate signer key between seasons; keep old pubkeys in verifier for legacy cards.
Optional hardening later: rate limits, signed manifests, revocation lists—none required for v0.
- hash: SHA-256
- code: alphabet =
"abcd", length = 12 - viability: threshold = 16 (zeros-in-hex)
- properties: cap values to 0–10 via
min(floor(z/2), 10) - signatures: Ed25519 (deterministic, widely supported)
- QR: JSON, max payload ≤ ~1.5 KiB (CBOR optional)
Miner
cfg = load_season_config()
for code in random_code_stream(alphabet=cfg.mining.gene.alphabet, length=cfg.mining.gene.length):
v = sha256_hex(code + cfg.mining.viability.suffix)
if count_zeros(v) >= cfg.mining.viability.threshold:
send_to_signer({version=1, seasonId=cfg.seasonId, code})
break
Signer (handler)
assert valid_code(req.code, cfg.mining.gene)
v = sha256_hex(req.code + cfg.mining.viability.suffix)
assert count_zeros(v) >= cfg.mining.viability.threshold
seed = sha256(req.code)
props = properties_from_code(req.code, cfg.propertiesFromCode)
digest = build_digest_blind(version, seasonId, mode="BLIND", code=req.code, geneLen=cfg.mining.gene.length, threshold=cfg.mining.viability.threshold, zeros=count_zeros(v), seed, props)
sig = ed25519_sign(sk, digest)
return cert_json_blind(code=req.code, viabilityZeros=count_zeros(v), threshold=cfg.mining.viability.threshold, seed, props, sig)
Verifier
assert cert.v == cfg.schemaVersion && cert.seasonId == cfg.seasonId
assert valid_code(cert.code, cfg.mining.gene)
v = sha256_hex(cert.code + cfg.mining.viability.suffix)
assert count_zeros(v) >= cfg.mining.viability.threshold && count_zeros(v) == cert.mining.viabilityZeros
seed = sha256(cert.code)
assert hex(seed) == cert.seedHex
assert properties_from_code(cert.code, cfg.propertiesFromCode) == cert.properties
digest = build_digest_blind(...)
assert ed25519_verify(cfg.signing.publicKeyBase64, digest, cert.signatureB64)