Skip to content
qbunk
Field manual · protocol v1

The manual

Why a bunker

Every Solana account is an ed25519 public key, published the moment it receives anything. Shor's algorithm, run on a large enough fault-tolerant quantum computer, turns an elliptic-curve public key into its private key. Nobody knows the date. What is known is that data signed today has to stay trustworthy long after it: a coin's provenance, a creator's identity, a locked dev allocation.

Hash-based signatures do not have that weakness. Their security rests on SHA-256 alone; the best known quantum attack (Grover) only halves the exponent, leaving 2128 work. QBUNK signs every launch with a one-time hash-based key and anchors the identity root on Solana while ed25519 is still unforgeable, so the proof of who launched what keeps verifying after a curve break. The vault goes further: funds in it move only with a hash-based signature, verified on chain. The program stays upgradeable only until its audit, then its upgrade key is burned.

Overview

QBUNK is a pump.fun launchpad with four additions:

  1. every launch is attested by a hash-based, post-quantum identity;
  2. creator fees are split on chain by pump.fun's own fee-sharing program, the creator taking the larger share;
  3. the dev allocation (or anything else) can be locked in a time-locked vault that only a hash-based signature opens;
  4. a scanner shows how much value sits behind plain ed25519 keys.

The TypeScript library (packages/pq) and the on-chain program (programs/qbunk-vault) implement the same byte-level spec and share test vectors. All integers are little-endian unless stated; H is SHA-256.

Hash-based signatures

Shor's algorithm breaks elliptic-curve keys such as ed25519, which every Solana wallet uses. It does not break hash functions. QBUNK signs with WOTS, a Winternitz one-time signature built only from SHA-256, with SPHINCS+-style tweakable hashing: every hash call is keyed by a public seed and a 32-byte address that pins it to one position, so no two calls in the system share an input domain.

ParameterValue
Hash output n32 bytes
Winternitz w16 (4-bit digits, chains of 15 steps)
Chains64 message digits + 3 checksum digits = 67
Signature67 × 32 = 2,144 bytes
TweakT(pubSeed, ADRS, m) = H(pubSeed ‖ ADRS ‖ m)

The address (ADRS) is five big-endian words: family (1 identity, 2 vault), type (chain, public-key compression, tree node, secret PRF), key index, chain or level, and step or node index, followed by 12 zero bytes. A signature reveals, for each chain, the value after d steps where d is that digit of the message; the verifier finishes each chain and compresses the 67 ends into one 32-byte public key.

A WOTS key signs exactly one message. Two signatures under the same key reveal enough intermediate chain values to forge a third. Everything below exists to make sure a key is never asked to sign twice.

The Bunker phrase

Your post-quantum keys come from 32 bytes of fresh entropy, shown as a 24-word BIP-39 mnemonic: the Bunker phrase. It is not derived from your wallet, so an attacker who recovers your ed25519 key learns nothing about it.

skSeed  = HKDF-SHA256(master, salt = "qbunk/v1", info = "sk",  32)
pubSeed = HKDF-SHA256(master, salt = "qbunk/v1", info = "pub", 32)

In the web app the phrase is encrypted at rest in your browser (PBKDF2-SHA256 with 600,000 iterations, then AES-256-GCM). Decrypted seeds live only in memory for the session.

Identity and binding

An identity is a Merkle tree of height 10 over 1,024 one-time WOTS keys (family 1). Leaf i is the compressed public key of key i; inner nodes hash their children with a position-specific tweak. The public identity is (pubSeed, root), displayed as qb1…, the base58 of H("qbunk/id/v1" ‖ pubSeed ‖ root).

An identity signature is leaf index (u32) ‖ WOTS signature ‖ authentication path, 2,468 bytes. Verifying it means completing the WOTS chains, hashing up the path, and comparing with the root: SHA-256 only.

Signed messages are domain-separated digests: H("qbunk/" ‖ domain ‖ "/v1\n" ‖ sorted "key=value" lines).

Binding a wallet

The wallet signs (ed25519) a readable statement naming the identity, its root and public seed; the identity signs the matching register digest with its reserved leaf 0. The root is then anchored on Solana with an SPL Memo qbunk:v1:anchor:<id>:<rootHex> sent by that wallet. Leaf 0 signs one binding only, so an identity binds exactly one wallet.

Signed launches

Each launch burns the next unused leaf (1–1023) and signs:

FieldMeaning
mintthe new coin's mint address
creatorthe launching wallet, pump.fun's creator
name, symbolas in the token metadata
metadataHashhex SHA-256 of the image bytes
leafthe identity leaf used
devVault, devUnlockdev-lock vault and unlock time (empty / 0 when none)
devCommit, vaultProgramthe lock key's commitment and the vault program; verifiers re-derive devVault from them, so the unlock time is signed
feeCreatorBpscreator share of creator fees (7000)

The resulting receipt (identity public key, fields, signature, optional anchor transaction) is stored in the coin's IPFS metadata under "qbunk". Anyone can verify it with SHA-256 alone. Alongside it the app publishes the dev vault's key commitment (qbunkLock), which lets a verifier re-derive the vault address from the unlock time.

The web app burns the leaf and persists that fact before signing, and never reuses a burned leaf, even if the launch later fails.

Vaults

A vault key is WOTS key k of family 2; there is no tree, each vault is one key. Its commitment is pkCommit = H("qbunk/vault-pk/v1" ‖ pubSeed ‖ u32(k) ‖ pkc) and the vault address is the program-derived address with seeds ["qb-vault", [1], pkCommit, i64(unlockTs)]. An unlock time of 0 means unlocked. Because the unlock time is part of the address, a lock can never be shortened.

A vault is a system-owned PDA holding SOL, plus any associated token accounts it owns (SPL Token or Token-2022). Anyone can deposit. Each vault key index is bound to exactly one unlock time: the app allocates key indexes from a persisted counter and never hands one out twice.

Withdrawing

The vault key signs one message:

M = H("qbunk/vault-withdraw/v1" ‖ programId ‖ vault ‖ kind (0 SOL, 1 TOKEN)
      ‖ mint (zeros for SOL) ‖ recipient ‖ u64(amount) ‖ nextVault)

The signature is written to an on-chain buffer in a few transactions, then a withdraw instruction pays amount to the recipient and everything else to nextVault, a fresh vault under a never-used key. The spent address becomes a tombstone that forwards late deposits to nextVault (sweep is permissionless).

The app saves the withdrawal intent before computing the signature. If a withdrawal is interrupted, the vault can only finish that same intent; a buffer whose message the app cannot match is never written to.

The vault program

A native Solana program with no admin and no config. It is deployed upgradeable during the pre-audit period so bugs can be fixed; after the audit the upgrade authority is set to none and the program is frozen for good.

TagInstructionWhat it does
0WriteSignatureAppends signature bytes to the ["qb-sig", vault, payer] buffer. The first write fixes the digest; writes are contiguous.
1WithdrawRebuilds M, checks it equals the buffer digest, recovers the key commitment, checks the vault address and unlock time, pays out, writes the tombstone, closes the buffer.
2CloseBufferReturns buffer rent to its payer. Abandoning a buffer does not make it safe to sign a different message.
3SweepMoves anything that reached a tombstone to its recorded next vault.
CodeError
0InvalidInstruction
1BufferIncomplete
2DigestMismatch
3BadVault
4Locked
5AlreadySpent
6BadTokenAccount
7NotPayer
8BadOffset
9InsufficientFunds
10NotTombstone

Signature schemes

Standard launches can be signed with any of eight schemes: WOTS + Merkle (the default, one leaf per launch), ML-DSA-65 and ML-DSA-87 (FIPS 204), SLH-DSA-128s and SLH-DSA-SHAKE-128f (FIPS 205), Falcon-512 and Falcon-1024 (FIPS 206 draft), and a hybrid ed25519 + ML-DSA-65 where both signatures must verify. A non-WOTS key is derived from your identity seed and certified once: its public key hash is signed by one WOTS leaf, and that certificate travels in every receipt it signs. Quantum launches are locked to WOTS, the same hash-based family that guards the vault.

Derived wallets

Unlimited Solana wallets can be derived from your identity; their keys are never stored. Every transfer is signed twice: by the wallet's ed25519 key, which Solana verifies, and by one of your one-time hash-based keys, whose signature digest is recorded on chain in a memo. The second signature is an attestation; for funds that only a hash-based signature can move, use the vault.

Proof desk

To show you still hold your keys, ask the desk for a nonce and answer it with one fresh one-time key, entirely in your browser. The desk hashes the answer up to the root you filed (67 chains, one leaf, ten levels), with SHA-256 and nothing else, and records that key as spent for good.

Pairs

A coin trades against SOL by default. You can pair it with any quote pump.fun accepts instead: stablecoins, tokenized stocks, majors, or another pump.fun coin. The pair is part of the signed launch fields.

Fee split

The launching wallet is pump.fun's creator. Right after the create, the app sends create_fee_sharing_config and update_fee_shares with the creator at 70% and the QBUNK treasury at 30%. pump.fun's fee program then enforces the split and pays it out permissionlessly with distribute_creator_fees. pump.fun allows the split to be configured once; after that it is locked.

Exposure scanner

Every Solana address is an ed25519 public key, so every balance behind a normal address is exposed to Shor's algorithm. The scanner sums the SOL and token value of an address using Jupiter prices and renders a card to share. Program-derived addresses, such as vaults, have no private key and are shown as protected.

Threat model and limits

What QBUNK protects

  • Provenance. A receipt proves that the holder of a given post-quantum identity attested to a launch's fields, and nobody can forge one by breaking ed25519.
  • Vault funds. A vault has no private key. It opens only with a WOTS signature from your Bunker phrase, only after its unlock time, and only once.
  • Dev locks. The unlock time is part of the vault address, so it cannot be shortened, and the program has no admin who could release funds early.

What it does not protect

  • Tokens in ordinary wallets are still ed25519. Your wallet, your dev tokens before they reach a vault, and every holder's balance remain exposed to a future quantum attacker. So do the pump.fun creator role and the fee recipients, which are ed25519 wallets.
  • Transactions are still paid and signed by ed25519 wallets. A quantum attacker who controls your fee-paying wallet can censor or front-run your vault transactions, but cannot redirect a withdrawal: recipient, amount and next vault are fixed by the hash-based signature.
  • WOTS keys are one-time. Signing two different messages with one key lets anyone forge. The app guards this with persisted counters and intents, but if you use the same phrase on several devices without syncing the safety state, you can still hurt yourself. Export and import it.
  • The program is upgradeable until it is audited. Until then the deployer key could change the vault program; that is what lets us patch bugs. After the audit the upgrade key is burned and nobody, including us, can change it. It has not been audited yet: use small amounts.
  • Your browser is trusted. The phrase is decrypted in the page. A compromised device or malicious extension can steal it.
  • The receipt binds the identity, not a person. It proves which identity signed, and, with the memo anchor, which wallet claimed it. Whether that identity is trustworthy is a social question.