qsafe protocolqsafe protocol
vol. 01 / quantum-safe primitives for solana

when ed25519breaks,keep your story.

Solana signs every transaction with ed25519. The day a cryptographically relevant quantum computer arrives, that assumption becomes a liability. qSAFE is a small Anchor program that binds a post-quantum ML-DSA-65 key to your wallet, so the messages you actually meant to authorize can be proven later by a signature scheme that does not fall to Shor.

02 — the protocol

what happens, step by step

The two columns track what lives off-chain (the user, the keys, the message) and what lives on-chain (the PDAs, their state, the events). A single anchored message takes nine moves.

step01/9
pre-flight

no state yet

No identity, no session. The wallet exists; the post-quantum side does not.

offline · clientuser.local
ed25519 wallet○ live
pubkey…/qsafe-deployer
rolepayer + has_one
ml-dsa-65 keypair— idle
pending
message— idle
payloadtransfer(10 SOL → alice)
signature— idle
pending
on-chain · solanaqsafe.program
PqIdentity PDA— idle
not initialized
Session PDA— idle
no session open
emitted events· stream
IdentityRegistered
SessionOpened
SignatureAnchored
03 — quickstart

four calls.

The SDK hides the chunked upload, the compute budget, and the event parsing. You write the four lines that matter; everything else is plumbing.

01installnpm i @qsafe/sdk @coral-xyz/anchor @solana/web3.js
02envQSAFE_RPC, QSAFE_WALLET
03cost~0.04 SOL per anchored message
04reclaimclose_session refunds the session rent
full example →or use the CLIqsafe keygen / register / sign / anchor
call-qsafe.ts
import { QsafeClient, generatePqKeypair, hashMessage, pqSign } from '@qsafe/sdk';
import { Connection, Keypair } from '@solana/web3.js';

const connection = new Connection(process.env.QSAFE_RPC!);
const wallet = Keypair.fromSecretKey(/* your secret key */);
const client = QsafeClient.forWallet(connection, wallet);

// 1. generate the post-quantum keypair offline
const pq = generatePqKeypair();

// 2. register the public key on-chain (chunked upload, sealed at the end)
await client.register(pq.publicKey);

// 3. sign any message with the pq secret key, off-chain
const message = Buffer.from('transfer 10 SOL to alice');
const sig = pqSign(hashMessage(message), pq.secretKey);

// 4. anchor the signature on-chain (chunked upload, then verify_session)
const result = await client.anchorMessage(message, sig);
console.log('verify tx:', result.anchorSignatures.at(-1));
04 — reference

the surface, briefly.

instructions
01init_identity(algorithm: u8, key_len: u32)allocate PqIdentity PDA
02write_identity_chunk(offset: u32, chunk: bytes)stream public key bytes
03seal_identity()finalize, emit IdentityRegistered
04init_session(session_id, message_hash, sig_len)open verify session
05write_sig_chunk(offset: u32, chunk: bytes)stream signature bytes
06verify_session()run verify, emit SignatureAnchored
07close_session()reclaim session rent
08revoke_identity()mark identity unusable
09close_identity()permanently close, reclaim rent
at a glance
ml-dsa variantml-dsa-65 / dilithium3 (nist fips 204)
public key size1952 bytes
signature size3309 bytes
solana packet limit1232 bytes per tx
default chunk size800 bytes
rent per identity~0.015 sol
rent per session~0.025 sol (refundable via close_session)
program size~280 kb

PqIdentity

account
pda seeds
[b"qsafe-identity", owner.pubkey]
fields
owneralgorithmkey_lensealedrevokedkey_fingerprintpublic_key

Session

account
pda seeds
[b"qsafe-session", identity.pubkey, session_id]
fields
openeridentitysession_idmessage_hashsig_lenverifiedsignature
known limitationml-dsa-65 on sbf

Full on-chain verification of an ML-DSA-65 signature does not fit inside Solana's SBF runtime today. The verifier overflows the per-frame stack limit, and size-optimizing the build pushes seal_identity past the 200K compute-unit meter. The default build therefore runs a deterministic attestation path and emits a SignatureAnchored event with verified=false. External verifiers can recompute the same commitment and run the real ML-DSA verify off-chain. Full on-chain verify is a tracked follow-up.