BUNKER.MARKETS MANUAL
REV 1 · SHA-256 ONLYHOW A COIN GETS A
POST-QUANTUM RECEIPT.
> This manual covers the key vault, the mint attestation, the audit challenge, and the exact bytes that get hashed at every step. Everything here ships in src/lib/pq and runs the same in the browser and on the server. If a sentence here and the code ever disagree, the code wins.
PG 01README
#readmeA vault is an XMSS-style tree of 256 Winternitz one-time keys, grown from one deterministic wallet signature. The tree root is the public key; a short hash of it is the operator's pq1… address.
A launch spends one leaf. That leaf signs a digest of the coin's public fields and, for WOTS launches, also owns the mint keypair. The receipt is pinned into the coin's IPFS metadata, so provenance can be checked with nothing but a hash function, long after elliptic curves stop being trustworthy.
- 01derivewallet signs a fixed message → seeds → 256 keys → root
- 02enrolroot registered, leaf 0 burned as the genesis receipt
- 03anchorroot timestamped on Solana in an SPL memo
- 04mintleaf i signs the coin digest, coin goes live on pump.fun
- 05auditanyone recomputes the root from the receipt
PG 02THREAT
#threatThe adversary we plan for can eventually forge ed25519 signatures: a cryptographically relevant quantum computer running Shor's algorithm, or a classical break of elliptic-curve discrete log. On Solana an address is an ed25519 public key, so there is no hash shield at all.
| asset | today | after the break |
|---|---|---|
| funds in ed25519 accounts | safe | exposed; needs an on-chain PQ vault |
| who launched a coin | wallet signature | WOTS receipt + anchor |
| login / ownership | wallet signature | WOTS challenge-response |
| vault, passphrase-hardened | safe | safe, given passphrase entropy |
| vault, wallet-only | safe | re-derivable by whoever holds the wallet key |
PG 03HASH CHAINS
#chainsEvery hash is SHA-256, n = 32 bytes. Security reduces to second-preimage resistance; Grover lowers that to roughly 2128 quantum work, which stays out of reach.
tweak
As in SPHINCS+, every call is domain-separated by a public seed and a 32-byte address ADRS that pins it to one position in the structure. A preimage search cannot be amortised across chains or across operators.
digits & checksum
Winternitz parameter w = 16. The 32-byte digest becomes 64 base-16 digits d₀…d₆₃; each selects a position on a 15-step hash chain. A checksum over the remaining distance of every chain adds three more digits:
σᵢ = Fdᵢ(skᵢ) · verify: F15−dᵢ(σᵢ) ≟ pkᵢ
Raising a message digit is free (hash forward from a public σᵢ) but necessarily lowers C, and walking a checksum chain backwards needs a preimage. Signing costs Σdᵢ hashes, verifying Σ(15−dᵢ): together exactly 1,005.
PG 04KEY TREE
#treeThe 67 chain ends of leaf i compress into one node; 256 leaves form a binary tree of height 8. Root plus pk.seedis the 64-byte public key.
nh,j = SHA-256(pk.seed ‖ ADRS(2, h, j) ‖ nh−1,2j ‖ nh−1,2j+1)
A full receipt is (leaf, σ, a₀…a₇): 4 + 67·32 + 8·32 = 2,404 bytes. Verification rebuilds the leaf from σ and climbs eight levels. Valid iff the result equals the enrolled root.
PG 05DERIVE
#deriveNo new seed phrase. The wallet signs a fixed message; RFC 8032 ed25519 is deterministic, so one wallet always yields the same 64 bytes. An optional passphrase is stretched with scrypt and mixed in, adding entropy the curve never sees.
bunker.markets — Post-Quantum Identity Derivation v1 Signing this message derives your hash-based (WOTS/XMSS) keys locally in your browser. It is not a transaction and costs nothing. Never sign this exact message on any other site. Wallet: 7xKX…AsU Domain: bunker.markets Version: 1
Growing the tree is 256 × 1,072 ≈ 274k SHA-256 calls, about half a second in a modern browser. Secrets live in memory for the tab's lifetime and nowhere else.
PG 06ENROL
#enrolEnrolment binds wallet and root both ways. One statement is signed twice, by the wallet (ed25519) and by leaf 0 (WOTS). The server checks both before storing the public key; leaf 0 is burned as the genesis receipt.
bunker.markets — Register Post-Quantum Identity Wallet: 7xKX…AsU PQ address: pq1Hn3…W8k Root: 3f9a1c…e07b Public seed: b41d02…9c55 Tree height: 8 Scheme: WOTS(w=16,SHA-256)+Merkle
PG 07ANCHOR
#anchorA row in our database is only as trustworthy as our database. The anchor writes the root to Solana in an SPL Memo signed by the wallet: a public timestamp from the era when ed25519 was still unforgeable. After the break, any claim about who owns a root is checked against the earliest anchor.
bunker.markets:v1:anchor:pq1Hn3…W8k:3f9a1c…e07b
PG 08MINT ATTESTATION
#mintLeaf i signs a domain-separated digest over the coin's public fields, sorted by key. The same leaf deterministically owns the mint keypair, so the mint address is itself a commitment to the vault.
Create plus optional dev buy is one v0 transaction compiled against pump.fun's address lookup table; without the ALT the ~40 accounts exceed Solana's 1,232-byte packet. Before broadcasting, the server checks fee payer, mint and program, so it cannot be used to relay an arbitrary transaction.
metadata
The pinned JSON carries the full receipt, so an audit needs neither our API nor our database:
{
"name": "…", "symbol": "…", "image": "ipfs://…",
"website": "https://bunker.markets/coin/<mint>",
"bunker": {
"version": 1,
"scheme": "WOTS(w=16,SHA-256)+Merkle(h=8)",
"pqAddress": "pq1…", "root": "…", "pubSeed": "…", "height": 8,
"messageHash": "…",
"signature": { "leaf": 4, "wots": "<4288 hex>", "auth": ["…" ×8] }
}
}fees
pump.fun fixes a coin's fee recipient at mint time via the creator field of create_v2. Every coin here sets it to the platform treasury; the launching wallet signs, pays, and is recorded as creator in the receipt.
PG 09SCHEMES
#schemesOperators choose how a launch is signed. WOTS + Merkle is the vault's root of trust and burns one leaf per launch. The NIST schemes are many-time keys grown from the same seed; each is certified once by a WOTS leaf signing a binding digest, then signs any number of launches.
| scheme | standard | assumption | sig | pk |
|---|---|---|---|---|
| WOTS + Merkle | RFC 8391 style | SHA-256 2nd preimage | 2,404 B | 64 B |
| ML-DSA-65 | FIPS 204 | Module-LWE / SIS | 3,309 B | 1,952 B |
| SLH-DSA-SHA2-128s | FIPS 205 | SHA-256, stateless | 7,856 B | 32 B |
| Falcon-512 | FIPS 206 draft | NTRU lattices | ≤ 666 B | 897 B |
| ML-DSA-87 | FIPS 204 · L5 | Module-LWE / SIS | 4,627 B | 2,592 B |
| SLH-DSA-SHAKE-128f | FIPS 205 | SHA-3 / SHAKE256 | 17,088 B | 32 B |
| Falcon-1024 | FIPS 206 draft · L5 | NTRU lattices | ≤ 1,280 B | 1,793 B |
| ed25519 + ML-DSA-65 | IETF composite draft | either must hold | 3,373 B | 1,984 B |
certs = WOTS.sign(H("bind" ‖ pq-address ‖ s ‖ H(pks)), leaf)
d = H("launch" ‖ creator ‖ image ‖ mint ‖ name ‖ scheme ‖ symbol)
The hybrid is a composite: one seed expands (SHAKE256) into an ed25519 key and an ML-DSA-65 key, and the signature is both concatenated. It verifies only if both verify. SLH-DSA-SHAKE swaps SHA-2 for SHA-3, so the two SPHINCS+ options rest on different hash designs.
A scheme receipt carries its signature, the scheme public key and the WOTS certificate, so an audit needs only the root: check the certificate against the root, then the signature against the certified key. The digest commits to the scheme id, so nothing replays under a different scheme. Implementations: @noble/post-quantum.
PG 10CHALLENGE
#challengeA login that never consults ed25519. The server issues a 32-byte nonce valid for five minutes; the client signsSHA-256("bunker.markets/proof/v1\n" ‖ leaf ‖ nonce ‖ wallet) with its next unused leaf.
| check | on failure |
|---|---|
| challenge exists · matches wallet · unused · unexpired | 404 / 409 / 410 |
| XMSS.verify(digest, σ, enrolled root) | verified: false |
| INSERT (vault, leaf) into leaf ledger | 409 leaf already used |
The ledger's primary key (identity_id, leaf_index) makes one-time use a database invariant. The challenge is consumed even on failure, so a nonce cannot be retried.
PG 11RISK REGISTER
#risk- R01 forgery
- Reduces to SHA-256 second-preimage resistance in the tweakable-hash model. ~2¹²⁸ quantum work.
- R02 leaf reuse
- Blocked server-side by the ledger. A malicious server could accept reuse; the client never signs twice with one leaf and always fetches the next free index.
- R03 wallet loss
- Wallet-only vaults are re-derivable by anyone holding the wallet key, including a post-break attacker. Hardened vaults also need the passphrase.
- R04 server loss
- Cannot forge receipts: no secrets held. Can censor or lie about enrolments, which is why the anchor and the IPFS copy exist.
- R05 capacity
- 256 signatures per vault. Rotation to a fresh tree, signed by the old tree's next leaf, is on the roadmap.
PG 12PARAMS
#params| item | value |
|---|---|
| hash | SHA-256 |
| WOTS | w = 16 · 67 chains · 2,144 B |
| tree | height 8 · 256 leaves |
| receipt | 2,404 B |
| public key | root ‖ pk.seed · 64 B |
| address | pq1 ‖ base58(SHA-256(dom ‖ pk.seed ‖ root)[0..20]) |
| passphrase KDF | scrypt N = 2¹⁵, r = 8, p = 1 |
| seed KDF | HKDF-SHA256 |
| mint KDF | HKDF-SHA256(sk.seed, 'bunker.markets/mint/v1', u32be(leaf)) |
PG 13DIY VERIFY
#diyEvery coin page has an Audit button that runs this in your browser. The same thing in code:
import { launchDigest } from "@/lib/pq/messages";
import { verify } from "@/lib/pq/xmss";
const meta = await fetch(metadataUri).then((r) => r.json());
const d = launchDigest({
mint, creator, name: meta.name, symbol: meta.symbol,
image: meta.image, leaf: meta.bunker.signature.leaf,
});
const { valid, computedRoot } = verify(d, meta.bunker.signature, {
root: meta.bunker.root, pubSeed: meta.bunker.pubSeed, height: meta.bunker.height,
});