Skip to content

protocol v1 / account layout version 1

How Hashlock works

A sealed coin's creator fees and dev bag sit on an address no wallet key controls. The only thing that moves them is a hash-based one-time signature that a Solana program checks with SHA-256. This is the specification: every formula below is what the program and the browser run, and every claim can be checked on chain.

vault program
reading
who can change it
reading
creator fees
reading
platform share
reading

These four lines are read when the page loads. The rest of the page is constant for protocol v1.

Contents0%

1Overview

Hashlock is a launchpad for pump.fun coins. On Solana an ordinary address is an Ed25519 public key, so anything a wallet holds is only as safe as that curve. A sealed launch moves the two things a creator controls, the creator fees and the dev buy, onto an address that has no private key at all. It belongs to a vault, and a vault is opened by hash-based keys.

A vault's public key is one 32-byte Merkle root over 1,024 one-time signing keys. The keys come from a 24-word phrase that your browser generates and never sends anywhere. To move anything out, you sign the exact action with the next unused one-time key, and the vault program recomputes the root from your signature on chain. If it matches the root stored in the vault, the move happens. No Ed25519 signature can stand in for that check.

Solana itself signs every transaction with Ed25519, and pump.fun's programs are classical. The threat model lists what each of those controls.

  • Verified on chain. The signature is checked by the program that holds the funds, not written into a file for someone to check later.
  • Independent of your wallet. By default the vault key is fresh randomness, so breaking your wallet's key says nothing about the vault's.
  • Derived from the chain. "Sealed" is computed from three on-chain accounts. You can recompute it at /verify with any RPC and without our server.

2Threat model

The adversary can forge an Ed25519 signature for any public key it has seen. That could be a large quantum computer running Shor's algorithm, or a classical break of the curve. It cannot find second preimages of SHA-256. Grover's algorithm leaves about 2128 work for a 256-bit second preimage.

Because a Solana address is the public key itself, there is no hash in front of it to hide behind. Every ordinary wallet is exposed from the day such an adversary exists.

AssetBefore a curve breakAfter a curve break
SOL and tokens in an ordinary walletSafe while the key stays secret.Exposed. The address is the public key.
Creator fees of a sealed coin, once paid to the vaultOnly a vault signature moves them.Unchanged. No Ed25519 key can authorize the move.
Creator fees still inside pump.funHeld by pump.fun's programs. The locked split names the vault, and anyone can trigger the payout.The split cannot be edited by a forged creator signature, because its admin is revoked. pump.fun's own keys are classical: see the row below.
The dev bagIn the vault's token account. Only a vault signature moves it.Unchanged.
Anything else deposited in a vault made from a 24-word phraseOnly a vault signature moves it.Unchanged.
A vault derived from a wallet signatureSafe while the wallet key stays secret.Exposed. Whoever can forge that wallet's signature rebuilds the vault key. Worth exactly as much as the wallet.
The fee payer of a vault transactionPays network fees and stages the signature bytes. It can decline to send. It cannot change what the signature authorizes.The same powers in an attacker's hands: pay, or refuse. Recipient, amount and mint are all inside the signed message.
pump.fun's own programsUpgradeable and administered by pump.fun.Their keys are Ed25519. An upgrade or admin action there could redirect creator fees before they reach a vault. This holds for every pump.fun coin, sealed or not.
The vault programIts upgrade status is read from chain: see Program upgrades. While an upgrade authority exists it is an Ed25519 path around every vault.
Hashlock's own share of feesPaid to one ordinary platform wallet and spent by the flywheel.Exposed like any wallet. It holds fees on their way to a burn, never a creator's or holder's funds.
Solana consensusValidators sign with Ed25519.Outside any program's control. A chain that cannot agree on blocks protects nothing.

3Primitives

One hash function is used everywhere: SHA-256, with n = 32 bytes. The one-time signature and the tree follow RFC 8391 (XMSS), SHA2_256 parameter set, as a single tree (layer 0, tree 0). toByte(x, 32) is x as 32 big-endian bytes; it separates the three functions.

F(KEY, M)   = SHA256(toByte(0,32) || KEY || M)
              M is 32 bytes
H(KEY, M)   = SHA256(toByte(1,32) || KEY || M)
              M is 64 bytes
PRF(KEY, M) = SHA256(toByte(3,32) || KEY || M)
              KEY = pub_seed, M = 32-byte ADRS

The address, ADRS

Every hash call is tied to one position in the structure by a 32-byte address: eight big-endian 32-bit words.

  • layerword 0, always 0
  • treewords 1 and 2, always 0
  • typeword 3: 0, 1 or 2
  • word 4leaf index, or 0
  • word 5chain index or tree height
  • word 6hash index or tree index
  • keyAndMaskword 7: 0, 1 or 2
typeword 4word 5word 6
0 OTSleaf indexchain indexhash index
1 L-treeleaf indextree heighttree index
2 hash tree0tree heighttree index

Chains and node hashes

chain(X, i, s):
  for j in i .. i+s-1:
    KEY = PRF(pub_seed, ADRS{hash=j, km=0})
    BM  = PRF(pub_seed, ADRS{hash=j, km=1})
    X   = F(KEY, X xor BM)

RAND_HASH(L, R):
  KEY = PRF(pub_seed, ADRS{km=0})
  BM0 = PRF(pub_seed, ADRS{km=1})
  BM1 = PRF(pub_seed, ADRS{km=2})
  return H(KEY, (L xor BM0) || (R xor BM1))

Every call is keyed and masked by the pair (pub_seed, ADRS). An attacker cannot reuse one preimage search across many chains, many leaves or many vaults, because each position hashes differently. That is why security rests on second-preimage resistance and not on collision resistance of these functions.

4One-time signatures (WOTS+)

A one-time key is 67 hash chains. With Winternitz parameter w = 16, each chain has 16 positions, 0 to 15, and each base-16 digit of the message picks a position on one chain.

symbolvaluemeaning
n32hash output, bytes
w16positions per chain
len164message digits (32 bytes, 4 bits each)
len23checksum digits
len67chains per one-time key
signature2,144 bytes67 values of 32 bytes

Message digits and checksum

The 32-byte message is read as 64 base-16 digits, high nibble first. A checksum is appended so that nobody can turn one signature into another by hashing forward.

d[0..63]  = base-16 digits of msg,
            high nibble first
C         = sum(15 - d[i]), i in 0..63
            at most 960
C         = C << 4
            written as 2 big-endian bytes
d[64..66] = first 3 base-16 digits of C

Raising any message digit lowers the checksum, and lowering a checksum digit means walking a chain backwards, which is a SHA-256 preimage.

Sign and verify

ADRS  = {type 0, word4 = leaf, word5 = i}
sig_i = chain(sk_i, 0, d[i])
        the signer walks d[i] steps
pk_i  = chain(sig_i, d[i], 15 - d[i])
        the verifier walks the rest
Three message chains and the three checksum chains, with example digits. The other 61 chains work the same way.

The verifier runs sum(15 - d[i]) chain steps, so the cost depends on the message. The signer runs the complement, and the two always add up to 67 x 15 = 1,005 steps. Each step is two PRF calls and one F.

A signature reveals a position on every chain. Two signatures from the same key, over different messages, reveal enough to forge a third. Each key signs one message, once. The program enforces that on chain: see one-time key enforcement.

5The key tree

A vault needs more than one signature, so it holds a tree of one-time keys, in the style of XMSS. The site uses height 10, which is 1,024 keys. The program also accepts heights 8 and 12.

From a one-time key to a leaf

The 67 public chain ends of a one-time key are compressed into one 32-byte leaf by an L-tree: pair them with RAND_HASH (ADRS type 1, word 4 = the key's index), level by level, promoting an odd node unchanged. 67 values take 66 hashes.

From leaves to the root

node(0, i)   = leaf i
node(k+1, j) = RAND_HASH(node(k, 2j),
                         node(k, 2j+1))
               ADRS{type 2, height = k,
                    index = j}
root         = node(10, 0)

The vault's whole public key is the root, the public seed and the height: 65 bytes. The vault's address is derived from all three (section 7).

The authentication path

A signature carries, besides the 67 chain values, the 10 sibling hashes on the way from its leaf to the root, bottom up. At level k the verifier hashes the node it has with the sibling it was given. Bit k of the leaf index says which goes on the left.

Height 3 for the drawing: leaf 5 with its path a0, a1, a2. The real tree has 10 levels, so 10 siblings and 1,024 leaves.
signature blob
  = 2,144 + 32 x height bytes
  = 2,464 bytes at height 10
  = 67 chain values, then a0 .. a9

The signature is valid when the recomputed root equals the root stored in the vault account, byte for byte.

6Key derivation

Keys are made in the browser. The program never sees a secret, and neither does our server.

Default: a 24-word phrase

entropy  = crypto.getRandomValues(32 bytes)
mnemonic = BIP39 English, 24 words
           256 bits + 8-bit SHA-256 checksum
bip39    = PBKDF2-HMAC-SHA512(
             NFKD(mnemonic),
             "mnemonic" || NFKD(passphrase),
             2048 rounds, 64 bytes)
seed     = SHA256(
    "entangle/pqvault/v1/bip39" || bip39)

sk_seed  = SHA256(
    "entangle/pqvault/v1/sk_seed" || seed)
pub_seed = SHA256(
    "entangle/pqvault/v1/pub_seed" || seed)
sk[k][i] = SHA256(
    toByte(4,32) || sk_seed ||
    ADRS{type 0, word4 = k, word5 = i})
           chain i of one-time key k

The phrase is standard BIP39, checked against the reference test vectors, so you can validate your words with any BIP39 tool. An optional passphrase acts as a 25th word. Every step is a hash, so the key keeps about 128 bits of security against Grover.

Why the default does not use your wallet. A key derived from a wallet signature can be rebuilt by anyone who can produce that signature. After a curve break, that is anyone. A phrase made from fresh randomness has no link to any Ed25519 key, so nothing an attacker learns about your wallet helps with your vault.

stays in page memorypublic, on chain
entropy, the 24 words, the passphraseroot
bip39, seed, sk_seedpub_seed
every sk[k][i]height, and the vault address they derive

Building the height-10 tree takes about one second in a browser (947 ms measured in a Worker, so the page does not freeze). The words are shown once and never stored. Our server receives the root, the public seed, the height and the vault address, all of which are public on chain anyway.

Fingerprint. The first 8 characters of base58(root). The site shows it when you type a phrase, so you can see which vault the words open. It is a display aid of about 47 bits, enough to tell vaults apart, not proof against someone grinding a look-alike.

The strings read entangle/pqvault/v1 because Entangle was this product's name when the protocol was written. They are hashed into every root, address and signature. Changing one byte would give every phrase a different vault, so they stay.

Optional: derive from a wallet signature

seed = SHA256(signature)
       the wallet's Ed25519 signature
       over this exact text:

"Hashlock PQ key v1\nThis signature derives
your vault key. Only sign on Hashlock."

One string, set on two lines here. Its only
real line break is the \n; the other is a space.

This mode is not post-quantum. The vault is exactly as strong as the wallet that signed. The app labels it "Wallet-derived, not post-quantum" and such a vault should not be described as sealed against a curve break.

7The vault program

pqvault is a native Solana program, written without a framework. Program id: not published on this network yet. All of its accounts are program derived addresses with fixed little-endian layouts. Byte 0 is the account kind and byte 1 is the layout version, which is 1.

Vault, 83 bytes

Seeds ["vault", root, pub_seed, [height]].

offsetfieldtype
0kind = 1u8
1version = 1u8
2root[32]
34pub_seed[32]
66heightu8
67next_leafu32
71created_slotu64
79successor_countu16
81bump, owner_bumpu8, u8

The address commits to the full public key. With only the root in the seeds, someone could create your vault first with the right root and a wrong public seed, leaving an address nobody can sign for. With all three in the seeds, a stranger can still pay to create your vault, but only the correct one.

Owner PDA

Seeds ["owner", vault]. A plain system account with no data and no private key. It holds the vault's SOL and owns the vault's token accounts. This is the address pump.fun pays a sealed coin's creator fees to. Deposits are ordinary transfers and need no instruction. Only the program can sign for it, and it does so only after a valid vault signature.

Signature buffer, 83 bytes plus the signature

Seeds ["sig", vault, payer, nonce u64]. A temporary account that holds one signature blob while it is uploaded.

offsetfieldtype
0kind = 2, version = 1u8, u8
2vault[32]
34payer[32]
66nonceu64
74leaf_indexu32
78total_lenu32
82bumpu8
83data: chain values, then the path[total_len]

total_len must equal 2,144 + 32 x height.

Launch record, 139 bytes

Seeds ["launch", mint]. One per coin, written once.

offsetfieldtype
0kind = 3, version = 1u8, u8
2mint[32]
34vault[32]
66fee_config[32]
98launched_slotu64
106successor_mint[32]
138bumpu8

Instructions

instructiondataaccounts
0 init_vaultroot[32] pub_seed[32] height u8payer (signer), vault, system program
1 open_buffernonce u64, leaf_index u32, total_len u32payer (signer), vault, buffer, system program
2 write_bufferoffset u32, bytespayer (signer), buffer
3 close_buffernonepayer (signer), buffer
4 executeleaf_index u32, action_bytespayer (signer), vault, buffer, owner PDA, system program, then the action's accounts

Only execute touches value, and it accepts four actions. Each is a tag byte followed by little-endian fields, and each ends with an expiry slot.

actionfieldswhat the program checks
0 WithdrawSollamports u64, to[32], expiry u64The destination account must be the signed to. A system transfer signed by the owner PDA.
1 WithdrawTokenamount u64, mint[32], to_token_account[32], token_program[32], expiry u64The token program must be the signed one, and SPL Token or Token-2022. Mint and destination must match the signed values. Mint and source must be owned by that token program. Transfer-hook mints are not supported.
2 RegisterLaunchmint[32], fee_config[32], expiry u64The bonding curve must be pump.fun's own account for the mint, and its creator must be the fee payer or the owner PDA. That stops anyone else from claiming a coin's Launch record.
3 SetSuccessormint[32], successor_mint[32], expiry u64The Launch record must belong to this vault. A successor can be set once and never changed.

The signed message

A one-time key signs a 32-byte digest of exactly this, 127 bytes in order:

  • "entangle/pqvault/v1"19 bytes, ASCII
  • program_id32 bytes
  • vault32 bytes
  • leaf_index4 bytes, u32 LE
  • SHA256(action_bytes)32 bytes
  • expiry_slot8 bytes, u64 LE

msg = SHA256 of the six fields above

The domain string, program id and vault address tie a signature to one deployment and one vault. The leaf index is in the message and also inside every WOTS+ address. The expiry slot is in the message and in the action, and the program requires the current slot to be at most the expiry. The action bytes travel in the instruction itself, so what is signed is exactly what runs.

What execute does, in order

  1. Checks the payer is a writable signer, the vault is a program-owned Vault whose address re-derives from its stored fields, and the owner PDA re-derives.
  2. Requires leaf_index < 2^height, leaf_index >= next_leaf and slot <= expiry_slot.
  3. Checks the buffer is program-owned and bound to this vault, this payer and this leaf, with the length the height implies.
  4. Recomputes the message, then the 67 chain ends, the leaf and the root. If the root is not the vault's root, it fails with BadSignature.
  5. Sets next_leaf = leaf_index + 1 and writes it before calling any other program.
  6. Performs the action.
  7. Closes the buffer and returns its rent to the payer.

One-time key enforcement

The vault account stores next_leaf. A signature for any leaf below it is refused with LeafAlreadyUsed, and every accepted signature moves it past the leaf just spent. The counter only goes up. Skipping leaves is allowed, going back is not. Replay is closed three ways: the rising counter, the program id and vault inside the message, and the expiry slot.

The chain cannot see a signature that was uploaded and never executed. Those bytes are public from the moment they land, even if the buffer is later closed. So the client never reuses a leaf after any failed or abandoned attempt: it takes the highest of the vault's next_leaf, every live buffer's leaf plus one, and a per-vault mark the site keeps on the server. That mark is a leaf number, not a secret.

Three transactions, and why

A Solana transaction is at most 1,232 bytes and the signature blob alone is 2,464. So every execute is three transactions, sent in order, each confirmed before the next.

transaction 1

open_buffer

Creates the buffer for this vault, payer and leaf, and writes the first chunk of the signature.

transaction 2

write_buffer

Writes the middle of the signature.

transaction 3

execute

Sets the compute budget, writes the last chunk, verifies, acts, and closes the buffer.

The verification itself is not split. It fits one transaction with room to spare, so there is no half-verified state on chain.

Measured cost

Real transactions on a local validator at height 10, measured 2026-10-09. The client computes the exact number of chain steps from the message and requests that plus 15% headroom.

executeCU usedCU requested
WithdrawSol359,429446,344
WithdrawToken, SPL Token356,964434,039
WithdrawToken, Token-2022357,451443,440
RegisterLaunch383,737496,197
SetSuccessor343,515427,542

Each chain step costs about 534 compute units. The worst possible message needs 990 steps, about 607,000 units at height 10, well under Solana's 1.4 million limit.

accountbytesrent, 2026-10-09
Vault830.00107 SOL, once
Launch record1390.00136 SOL, once per coin
Signature buffer, height 102,5470.0136 SOL, returned when execute closes it
Program130,016about 0.661 SOL, paid at deploy

8Sealed launch

A sealed launch is two wallet prompts after the vault exists. Every transaction is signed by your own wallet. Our server builds them and holds no key.

  1. stepyour walletvault programpump.fun
  2. Create the vault, once

    The browser makes the 24 words and builds the tree. init_vault stores the root, public seed and height in a new Vault account. One vault serves every coin you launch.

    your walletvault programpump.fun
  3. Create the coin and make the dev buy

    pump.fun's create and buy, with your wallet as the coin's creator. The dev buy lands in your wallet for now.

    prompt 1 your walletvault programpump.fun
  4. Register the launch with a vault signature

    Signed in the same prompt, sent once the coin exists. Your next one-time key signs RegisterLaunch(mint, fee_config). The program verifies it against your vault's root, checks that pump.fun's bonding curve names the fee payer as creator, writes the Launch record and advances next_leaf.

    prompt 1, three transactions your walletvault programpump.fun, read only
  5. Lock the fee split

    Any creator fees already earned are swept first. Then pump.fun's fee-sharing config is created with the creator share to the vault's owner PDA and the platform share to Hashlock's platform wallet, and its admin is revoked. From here nobody can edit the split, including you and us.

    prompt 2 your walletvault programpump.fun
  6. Move the dev bag into the vault

    The same prompt transfers your whole dev-buy balance to the owner PDA's token account. It is an ordinary token transfer in. Getting it out again takes a vault signature.

    prompt 2 your walletowner PDA receivespump.fun
  7. The chain now says sealed

    The site reads the Launch record and the fee-sharing config and shows the Sealed badge. Nothing is switched on by hand.

    your walletLaunch recordlocked split

What sealed means, exactly

A coin is sealed when all three hold on chain:

  1. the Launch record at ["launch", mint] exists, is owned by the vault program and names the mint;
  2. pump.fun's fee-sharing config at ["sharing-config", mint] has its admin revoked;
  3. in that config, the vault's owner PDA holds at least 5,000 of the 10,000 basis points.

A Launch record alone is not enough. A creator could register a launch and then lock the fees to an ordinary wallet, so the split is checked directly against the vault the record names. The program stores the fee_config address it was given but does not inspect it, which is why the rule reads pump.fun's account itself.

The coin page and the fee pages read those accounts from chain. The board lists many coins at once, so it uses a cache of what the chain said, refreshed in the background, and a coin is never listed as sealed on the strength of our database alone. The dev bag is reported separately: it is sealed while it sits in the vault's token account, and the vault page shows the balance.

Between steps 2 and 5 the coin is not sealed. Fees earned in that window go to your wallet's creator balance until the split locks, and the dev buy is in your wallet until it is moved. If you stop halfway, every page reminds you to finish, and the coin carries no Sealed badge until the chain shows all three conditions.

A coin run by the agent is not sealed. The agent's wallet moves that coin's fees automatically, which a vault signature cannot do. Each coin is one or the other and its badge says which.

The successor record

A vault can name a successor mint for a coin it launched, once, with SetSuccessor. If Solana one day moves to post-quantum accounts, holders can see in advance, on chain, where the creator says the coin continues. Claiming a successor is not built yet. The record can be made today so that it predates any break.

9Fees and the flywheel

Reading the live split from this site's configuration.

The split lives in pump.fun's fee-sharing config for each coin and is locked at launch. A coin keeps the split it was launched with: changing the site's setting later affects new launches only. pump.fun's own trading fees are separate and are set by pump.fun.

The platform wallet is an ordinary Solana wallet, signed through Privy's server wallets. No private key for it exists in our code, environment or database. It is not sealed. It holds only Hashlock's own share on its way to a burn.

Paying out is permissionless. Anyone can trigger pump.fun's sweep and distribute for a coin, and it pays every recipient in the config at once, never the caller. The site does it on a timer, and a creator can do it from the vault page for about 0.00001 SOL in network fees.

10Pairing

A coin can be quoted in anything pump.fun accepts and nothing else: SOL, USDC, the tokens on pump.fun's quote list such as tokenized stocks and large memecoins, and pump coins paired with one of those. A mint pump.fun refuses would fail on chain, so the launcher checks first.

The dev buy is always paid in SOL. For a pair that is not SOL, the launcher adds a swap from SOL into the quote token and your wallet signs it in the same prompt as the launch. Before your wallet sees it, the server simulates the swap and refuses it unless the only thing your wallet can lose is the dev buy amount plus fees.

Sealing works the same for every pair. Creator fees in a token quote arrive in the owner PDA's token account for that token and leave by WithdrawToken. Both SPL Token and Token-2022 quotes are supported. A token with a transfer hook is not, because the program forwards no extra accounts, so the site will not let you seal one.

11Program upgrades

Whoever can upgrade the vault program can replace the code that checks signatures. That is a classical path around every vault, so it is the most important thing to know about a deployment. The site does not state an intention. It reads the program's ProgramData account and reports what the chain says.

Right now: reading the chain status from this site.
final

No upgrade authority

The program can never change. Your vault depends only on SHA-256 and on Solana running. A bug could not be fixed either.

multisig

Several signers and a delay

An upgrade needs a threshold of separate keys and waits a public delay on chain. You can see a pending change and withdraw before it runs. The signers are still Ed25519 keys.

single key

One address

One key can replace the program with no notice. Treat a vault under such a program as protected by that key, not by hashes.

A multisig counts as a multisig only when it can be proven: the upgrade authority must be a vault address of a Squads multisig, and the threshold and delay are read from that multisig's account. A multisig that one signer can pass, or whose settings one authority can rewrite, is reported as a single key.

/verify reads the same ProgramData account from your own RPC, so you do not have to take this section's word for it.

12Recovery without Hashlock

A vault does not depend on this site. Everything needed to move its funds is the 24 words, the program on chain, a Solana RPC and any wallet to pay the network fee. The program id is not published on this network yet; keep it with your words. The offline file asks for it if it was saved before the program was published.

  • /recover is a stand-alone page. It has no sign-in, calls no Hashlock API, and talks only to the RPC you type. It rebuilds your keys from the phrase, picks an unused one-time key and sends a withdrawal.
  • The offline file is the same page as one HTML file with all of its code inside. Save it now. It opens from disk and works if this domain is gone.

Without the site there is no server-side leaf mark, so the recovery page reads next_leaf and scans for live signature buffers itself. If your RPC does not allow that scan, it tells you.

13Security analysis

Forgery

To move value without the phrase, an attacker has to make execute accept a signature the owner never made, for a leaf that is not yet spent. There are three ways to try, and each is a SHA-256 problem.

  • Walk a chain backwards. For an unused leaf nothing has been revealed, so every chain needs a preimage from its public end. For a leaf with one known signature, the checksum forces at least one chain to go backwards.
  • Reach the root another way. A different leaf or a different path that hashes to the same root is a second preimage of a keyed, masked hash.
  • Reuse a signature for another action. The message is a SHA-256 digest that fixes the program, vault, leaf, action and expiry. A different action with the same digest is a second preimage.

A second preimage of SHA-256 costs about 2256 classically and about 2128 with Grover.

One place rests on collision resistance instead. The message digest is plain SHA-256 with no per-signature randomness. Someone who chooses part of an action you then sign, such as a destination address, could in principle prepare two actions with the same digest. That is a SHA-256 collision: about 2128 work classically. The known quantum collision algorithms need about 285 steps on paper and impractical amounts of memory. Sign only actions you built yourself.

Key reuse

This is the main operational risk of any one-time scheme. The program refuses a spent leaf, but a signature is public as soon as its bytes are uploaded, before execute runs. If an upload is abandoned and the same leaf later signs a different message, both signatures are on chain and an attacker can combine them. The client's rule is simple: a retry with the identical action and expiry is safe because signing is deterministic, and anything else moves to a new leaf. A skipped leaf costs one of 1,024 keys. A reused one can cost the vault.

Losing or leaking the phrase

The 24 words are the key. Anyone who reads them, or reads page memory while a vault is open, controls the vault. If you lose them, nothing can move the funds: there is no reset, no support override and no second key, by design. A locked fee split keeps paying a vault whose phrase is lost, and those fees stay there.

If our server is compromised

it cannotit can
Sign for a vault. It never has the phrase, the seed or any one-time key.Serve altered page code that copies the phrase when you type it. This is the serious one. The offline recovery file, saved in advance, does not load code from us.
Move anything a vault holds, or change a Launch record.Show a wrong Sealed badge. /verify and the code in section 15 read the chain without us.
Edit a locked fee split.Build a launch that does not do what the page says. Your wallet shows each transaction, and the result is checkable on chain before you promote the coin.
Make the program accept a spent leaf.Report too low a leaf mark. The client also reads next_leaf and live buffers from chain, so this only matters for a signature that was uploaded, abandoned and closed, on a browser that lost its own record.
Stop you withdrawing. The recovery page needs only an RPC.Go offline, or refuse to relay your transactions.

What is still Ed25519

  • The fee payer. Any wallet can pay for a vault transaction. It can refuse to send or try to send first. Someone who copies an uploaded signature and executes it gets the identical result and pays the fee.
  • pump.fun. The bonding curve, the pool and the fee-sharing program are pump.fun's, upgradeable by pump.fun. Fees are theirs to route until they land in the owner PDA.
  • The vault program's upgrade authority, while one exists. See section 11.
  • Solana's validators and transaction signatures.
  • Your wallet, for everything outside the vault, including the SOL that pays fees.

Capacity and rotation

A height-10 vault signs 1,024 times in its life. Every withdrawal, launch registration, successor record and skipped leaf spends one. When next_leaf reaches 1,024 the vault can make no further moves and whatever it holds stays there.

A locked fee split cannot be pointed at a new vault. A sealed coin keeps paying the same owner PDA for as long as it trades, so a creator has at most 1,024 moves across every coin in that vault. Withdraw in batches, not after every trade. The vault page shows the keys left, and the site registers one vault per account today.

Scope

Sealing determines who can move a coin's creator fees and dev bag. A creator with the phrase can withdraw and sell with a valid vault signature. Tokens that holders keep in ordinary wallets are outside the vault; any holder can open a vault and deposit.

14Parameters

parametervalue
Hash functionSHA-256, n = 32 bytes
SchemeWOTS+ and an XMSS-style tree, RFC 8391, SHA2_256
Winternitz w16
Chains per key (len1 + len2)67 (64 + 3)
Tree height10 on the site; 8, 10 or 12 accepted
One-time keys per vault1,024
One-time signature2,144 bytes
Authentication path320 bytes
Signature blob2,464 bytes
Public key (root, public seed, height)65 bytes
Phrase24 words, 256 bits, BIP39 English
Phrase stretchingPBKDF2-HMAC-SHA512, 2048 rounds
Message domainentangle/pqvault/v1
Signed message preimage127 bytes
Vault / Launch / buffer header83 / 139 / 83 bytes
Transactions per vault move3
Verifier chain steps, worst case990
Second preimage work, classical / Grover2^256 / about 2^128
Sealed: owner PDA share, minimum5,000 of 10,000 bps, locked
Platform share of creator feesread at load
Part of the platform share that burnsread at load
Vault program idnot published on this network yet

15Verify it yourself

/verify runs six checks on any mint, straight from an RPC you choose: the Launch record, the vault, the fee split with every recipient, the dev bag, the program's upgrade authority, and the keys used and left. It shows each raw address with a link to Solscan.

Or skip our pages. This is the whole sealed rule in one function, using the program's client modules and nothing from our API. It throws if a record is missing.

import { launchPda, ownerPda, decodeLaunch, decodeVault }
  from '/pq/client/ix.js';
import { Connection, PublicKey } from '/pq/client/web3.js';
// the page needs the import map at /pq/client/importmap.json

const PROGRAM = new PublicKey('PASTE_THE_VAULT_PROGRAM_ID');
const PUMP_FEES = new PublicKey(
  'pfeeUxB6jkeY1Hxd7CsFCAjcbHA9rWtchMGdZ6VojVZ');

export async function checkCoin(mint, rpcUrl) {
  const rpc = new Connection(rpcUrl, 'confirmed');
  const read = async (address, program) => {
    const a = await rpc.getAccountInfo(address);
    if (!a || !a.owner.equals(program))
      throw new Error('missing: ' + address.toBase58());
    return a.data;
  };

  // 1. the Launch record names the vault
  const launch = decodeLaunch(
    await read(launchPda(PROGRAM, mint), PROGRAM));
  const vault = decodeVault(await read(launch.vault, PROGRAM));
  const owner = ownerPda(PROGRAM, launch.vault);

  // 2. pump.fun's split: locked, and paying the owner PDA
  const [config] = PublicKey.findProgramAddressSync(
    [new TextEncoder().encode('sharing-config'),
     new PublicKey(mint).toBytes()], PUMP_FEES);
  const d = await read(config, PUMP_FEES);
  const view = new DataView(d.buffer, d.byteOffset, d.length);
  const locked = d[75] === 1;
  let ownerBps = 0;
  for (let i = 0, o = 80; i < view.getUint32(76, true); i++, o += 34)
    if (owner.equals(new PublicKey(d.subarray(o, o + 32))))
      ownerBps += view.getUint16(o + 32, true);

  return {
    sealed: locked && ownerBps >= 5000,
    vault: launch.vault.toBase58(),
    owner: owner.toBase58(),
    ownerBps,
    keysLeft: 2 ** vault.height - vault.nextLeaf,
  };
}

The client modules are served from this site at /pq/client and are the same files the app and the recovery page use. To check a signature rather than an account, verify.js in the same folder re-implements the on-chain check in about twenty lines.