// overview
A quantum-safe transaction layer on Solana
QuantumPOS lets anyone with a Phantom wallet hold, send and receive SOL through a Winternitz vault: a program-owned account that only moves funds when it sees a valid one-time hash-based signature. Ed25519, the scheme behind every normal Solana account, can be broken by a large enough quantum computer. A Winternitz signature can’t, because breaking it would mean reversing Keccak-256.
Each user gets one permanent vault. Each spend signs the exact transfer and the hash of the next key, and the program rotates to that key in the same instruction. Recipients are plain Solana addresses. If the recipient also has a vault, the SOL moves vault-to-vault and stays inside the quantum layer.
816 B
signature
34
hash chains
192-bit
classical preimage
96-bit
quantum (Grover)
// threat model
The quantum threat
Shor’s algorithm solves the discrete-log problem behind elliptic curves in polynomial time, so a quantum computer could compute an Ed25519 private key from its public key. A Solana address is its public key, so every wallet that has ever held funds is exposed. That makes “harvest now, decrypt later” a real risk for SOL held for the long term.
Grover’s algorithm is the best known generic quantum attack on a hash function, and it only square-roots the work. A preimage search over 192 bits therefore costs about 2⁹⁶ quantum operations. Hash-based signatures sidestep Shor completely, which is why NIST standardised them (SP 800-208, FIPS 205).
// cryptography
Winternitz one-time signatures (WOTS)
A Winternitz key is a set of hash chains. The secret is the start of each chain and the public key is the end, 255 steps later. To sign, you reveal each chain at the position given by one byte of the message digest. A verifier walks the rest of each chain and checks that the ends hash to the public key. Since nobody can walk a chain backwards, a signature reveals nothing that signs an earlier position.
N = 24 bytes kept per hash (192-bit classical, 96-bit quantum preimage)
MSG_CHUNKS = 32 (one per keccak256 digest byte)
SUM_CHUNKS = 2 (checksum) → CHUNKS = 34, SIG_LEN = 34 × 24 = 816 bytes
secret(seed, i) = keccak256("WNTR:SK" ‖ seed ‖ i)[:24] // a key is one 32-byte seed
step(i, v) = keccak256(i ‖ v)[:24] // chain index is domain-separated
pk_hash = keccak256( ‖ᵢ walk(i, secretᵢ, 255) )
chunks(d) = d[0..32] ‖ be16( Σ (255 − d[j]) ) // checksum
sign(d)ᵢ = walk(i, secretᵢ, chunks(d)ᵢ)
verify = keccak256( ‖ᵢ walk(i, sigᵢ, 255 − chunks(d)ᵢ) ) == pk_hashWhy the checksum matters. Without it, a forger could take a signature, raise one digest byte and walk that chain forward for free. Raising any message byte lowers the checksum, so a forgery would always need some chain walked backwards. The earlier lamports-only vault design (Dean Little) had no checksum. This program does.
One time only. A second signature with the same key reveals two points on each chain, and an attacker can then sign anything in between. That is why the key rotates on every spend.
# shared test vector (program/src/wots.rs ⇄ client/src/check-vector.ts ⇄ our backend/wntr.py) seed = 0x01 × 32, message = "winternitz test vector" pk_hash = fe5020b437c6b20c5e7375bcd9a3c6918b6d7654d8f488ffd8e3975f9fe5ab2a ✓ digest = ee3ac08879e1c9ad49981a20b07abf2bd0171540013cb1b4c77ac074089da713 ✓ sig[0:24] = 4e7850f3be9f49450cdc8a56add99f02c60efdcad1ebc9d4 ✓ sig_hash = 02207cde031055670bd22c656eb8f6b1679600450dd1ab7cb3f1888cb504c6c3 ✓
// on-chain
The vault program
Program 13EtnfYGUH8NaGAnUpDTVgSsXoewNnULp7ESwHzQUANT is built from rabb757/winternitz-vault. It was written from the specification, not forked from the original vault, and its interface is different.
Vault account (106 bytes, owner = program) tag u8 = 1 | bump u8 | nonce u64 LE | vault_id [32] | pk_hash [32] | mint [32] PDA = find_program_address(["vault", vault_id]) vault_id = first pk_hash, so the address is permanent
| tag | instruction | data | accounts |
|---|---|---|---|
| 0 | initialize | pk_hash(32) | payer(s,w) · vault(w) · mint · system |
| 1 | spend: mint | amount u64 · next_pk(32) · sig(816) | vault(w) · mint(w) · dest(w) · token program |
| 2 | spend: transfer | amount u64 · next_pk(32) · sig(816) | vault(w) · source(w) · dest(w) · token program |
| 3 | execute (any CPI) | next_pk(32) · sig(816) · n · flags[n] · cpi_data | vault(w) · target program · …n inner accounts |
transfer digest = keccak256("WNTR:XFER" ‖ vault_id ‖ nonce ‖ source ‖ destination ‖ amount ‖ next_pk_hash)
execute digest = keccak256("WNTR:EXEC" ‖ vault_id ‖ nonce ‖ target ‖ n ‖ (key ‖ flags)* ‖ cpi_data ‖ next_pk_hash)
on success: pk_hash ← next_pk_hash, nonce ← nonce + 1 (written before the CPI)The vault holds data, so the System program can’t debit it. SOL that lands directly on the PDA can never be moved. Funds therefore live as wrapped SOL in the vault’s associated token account, and the vault PDA is the token authority.
// architecture
How the transaction layer uses it
| action | route | transactions (one Phantom approval) |
|---|---|---|
| Open vault | — | initialize + create wSOL ATA (+ optional transfer & SyncNative) |
| Deposit / pay link | — | create ATA (idempotent) · system transfer → ATA · SyncNative |
| Send to vault user | vault | ① ensure recipient ATA ② SyncNative + WOTS transfer(vault ATA → recipient vault ATA) |
| Send to plain wallet | wallet | ① ensure sender wSOL ATA ② WOTS transfer → it ③ CloseAccount (unwrap) + system transfer |
| Withdraw everything | close | SyncNative + WOTS execute(CloseAccount(vault ATA → Phantom)) |
- Same signature on every retry. The signed message has no blockhash, so resuming a stuck transfer resubmits the identical 816 bytes and reveals nothing new about the key.
- Exact confirmation. The server reads the on-chain vault and finalises only when
nonce == n + 1andpk_hash == next_pk. A lagging RPC node can’t trigger a false rotation. - Strict ordering. Steps are signed together but broadcast one at a time. Each is confirmed before the next, so a spend never runs ahead of its destination account.
- Measured cost. The spend transaction is 1,204 bytes plus the priority-fee instruction (limit 1,232). Verifying uses about 590k–660k compute units, and we request 1.4M for the worst case (8,670 keccaks).
- Phantom pays the fees. About 0.00001 SOL per transaction. At current mainnet rent, opening a vault costs about 0.0027 SOL (vault 0.00119 + token account 0.00149). Creating a missing destination token account costs about 0.0015 SOL, and that rent is returned when the account is closed. The app reads live rent from the cluster.
// how it works
From Phantom to a quantum vault and back
Phantom
Pays fees
Your vault
Wrapped SOL, PDA
WOTS spend
816-byte signature
Recipient vault
or native wallet
nonce n → sign(keccak(“WNTR:XFER” | vault | n | src | dst | amt | pkₙ₊₁))
verify: 34 chains walked, hash == pkₙ
rotate: pk ← pkₙ₊₁, nonce ← n+1
Open your vault
We generate a Winternitz key and derive a permanent vault address from its hash. Phantom pays a one-time rent of under 0.003 SOL to create it on mainnet.
Receive into it
Anyone can pay you through your link or QR code. The SOL is wrapped into the vault's token account, and only a Winternitz signature can ever move it.
Sign once
To send, a one-time signature covers the nonce, source, destination, amount and the hash of the next key. Change any byte and the program rejects it.
Key rotates in place
The vault swaps to the next key in the same instruction. Your address never changes, and every old signature stops working for good.
Deliver
Send to another vault and the SOL stays quantum-protected. Send to a plain wallet and it is unwrapped and delivered as native SOL.
// verification
What we verified
- Our Python signer (backend/wntr.py) reproduces all four values of the program's published test vector, byte for byte.
- We pulled the live program bytecode from its programdata account (8fqkcU…MZWu, 117,616 bytes) and ran it locally in LiteSVM.
- 28/28 end-to-end checks passed with the real API against that bytecode: open + deposit, vault-to-vault, native delivery to an empty wallet, partial withdraw, full withdraw, re-deposit after close, retry with the identical signature, the discard warning, and outside-deposit detection.
- Negative tests: a changed amount, a replayed signature and a stale pre-rotation signature were all rejected on-chain with "winternitz signature rejected".
- An earlier build targeted the original Open/Split/Close layout. Mainnet simulation showed this program rejects it (NotEnoughAccountKeys), which is why the layer was rebuilt on the real interface.
// honest scope
Security model & limitations
Server-held keys
One-time seeds are generated and stored on our server, encrypted with Fernet (AES-128-CBC + HMAC). Phantom can't hold Winternitz keys. This trades self-custody for usability: whoever controls the server and its encryption key controls the vaults.
Upgradeable program
The program's upgrade authority (EFQJ3s…aeU) is an ordinary Ed25519 key. Whoever holds it could change the program's rules.
Ed25519 fee payer
Phantom signs and pays fees with Ed25519. A quantum attacker who broke your Phantom key could take the SOL in Phantom, but not the funds behind your vault's Winternitz key.
Native-SOL delivery
Sending to a plain wallet routes the SOL briefly through your own wSOL account before it's unwrapped. At that point it has left the quantum layer, by design.
Discarded transfers
If a signed transfer is discarded, its signature may already be public. Signing a different message with the same key weakens it, so the app asks for explicit confirmation and tells you to move funds.
Don't fund the PDA directly
SOL sent straight to the vault PDA is stuck permanently. Always use the pay link, which deposits into the vault's token account.
Research-grade
The program is new, and the vault account's rent (~0.0012 SOL) can't be reclaimed. Use amounts you can afford to lose.
// research
Research & references
rabb757/winternitz-vault
Source of the mainnet program at 13Etnf…QUANT: Rust program, TypeScript signer, shared test vector.
Program on Solscan
The live upgradeable BPF program this app calls.
deanmlittle/solana-winternitz-vault
Dean Little's original Winternitz vault (Open / Split / Close, lamports-only). This is the prior art.
blueshift-gg/solana-winternitz-vault
Blueshift's maintained version of the original vault.
Blueshift: Quantum-proofing Solana
Background research on opt-in hash-based vaults for Solana.
winternitz.io
Project site and audit receipts for the mainnet program.
R. Merkle, A Certified Digital Signature (1979)
Origin of one-time hash signatures and the Winternitz improvement.
RFC 8391: XMSS
The standardised WOTS+ / XMSS construction and its security analysis.
NIST SP 800-208
NIST recommendation for stateful hash-based signatures (LMS / XMSS).
FIPS 205: SLH-DSA (SPHINCS+)
NIST's stateless hash-based signature standard. It sets the 128-bit level-1 bar this program's 192-bit chains exceed.
P. Shor (1994)
Polynomial-time factoring and discrete logs, the attack on Ed25519.
L. Grover (1996)
Quadratic speed-up for search, the only generic attack on hash preimages.
Solana: Program Derived Addresses
How a vault address is derived from its first key hash.
SPL Token: wrapped SOL
Native mint, SyncNative and CloseAccount, used to hold SOL inside the vault.