Post-quantum
Built to hold after Q-day.
A quantum computer large enough to run Shor’s algorithm recovers an ed25519 key from its public half. Everything Oraqle signs is signed a second time with SLH-DSA, a signature whose security rests on SHA-256 alone, and a Solana program can check both.
This page is the plain account of docs/QUANTUM.md, the specification every name and byte here is pinned to by a test.
Reading Fig. 1
- 01One messageThe domain, the program id and the report, 204 bytes. Both algorithms sign exactly these bytes.
- 02ed25519, on the leftOne check on the curve against the signer’s classical key: small and quick.
- 03FORS, at the footThe digest of the message picks a leaf in each of 14 trees. Their roots compress into one key.
- 04Seven XMSS layersA one-time WOTS+ leaf signs each root, in a tree of height 9, whose own root the layer above signs.
- 05The lockThe last root must equal PK.root, half of the 32 byte key registered for the signer on chain.
Curves fall. Hashes hold.
Shor’s algorithm recovers an ed25519 or X25519 secret key from its public key. Grover’s algorithm halves the strength of a hash, so SHA-256 keeps 128 bits.
| Part | Classical, kept | Post-quantum, added | Standard |
|---|---|---|---|
| Report signatures | ed25519 | SLH-DSA-SHA2-128s, over the same message | FIPS 205 |
| Signer registry on chain | ed25519 key | The signer’s 32 byte SLH-DSA public key, in the same entry | |
| Verification on chain | ed25519 precompile (verify_report) | SLH-DSA verified by the program with SHA-256 alone (verify_report_pq) | FIPS 205 |
| Attestation of managed signers | ed25519 | Binds the SLH-DSA key too | |
| Sealed upstream secrets | X25519 | ML-KEM-768, combined with X25519 | FIPS 203 |
Signed twice, accepted once.
Every report carries both signatures. A reader accepts it when both verify and both keys are the ones registered for that signer on chain.
message = "oraqle/report/v1" || program id (32) || borsh(report) (156)
signature = ed25519(signer, message), base58 (64 bytes)
pq.sig = SLH-DSA-SHA2-128s.sign(pq secret key, message, context = ""),
base64 (7,856 bytes)Pure mode, not pre-hash, with an empty context string: the signed message is M′ = 0x00 ‖ 0x00 ‖ M (FIPS 205, Algorithm 22). Signing is hedged with fresh randomness by default; verification is the same either way.
{
"report": { ...ReportJson },
"signature": "<base58 ed25519 signature>",
"signer": "<base58 ed25519 key>",
"pq": {
"alg": "slh-dsa-sha2-128s",
"key": "0x<64 hex>",
"signature": "<base64, 7856 bytes>"
},
"payload": {},
"encoded": "<base64 borsh>",
"feed": { "slug": "...", "provider": "...", "schema": "..." }
}pq-signature
The post-quantum signature (SLH-DSA) is the signer’s, over this report.
pq-key
The post-quantum key is the one registered for this signer, in the feed’s account.
These two checks join the nine a report already had, eleven in all. An envelope without pq fails the first check, its shape. The SDK runs all eleven against the feed’s account on chain, one of them that the report is for the feed you asked for. This site’s checker runs the other ten: a pasted report names its own feed, and the signer set it is compared with is the one Oraqle copies from the chain.
SLH-DSA-SHA2-128s.
The small parameter set: the signature stays at 7,856 bytes and the check on chain at 340,000 to 410,000 compute units, far under Solana's 1.4 million ceiling. Its slow signing is paid once per observation, not per call.
| Parameter | Value | Meaning |
|---|---|---|
| n | 16 | bytes in every hash output |
| h | 63 | total height of the hypertree |
| d | 7 | layers of XMSS trees |
| h′ | 9 | height of each XMSS tree |
| a | 12 | height of each FORS tree |
| k | 14 | FORS trees |
| lg w | 4 | Winternitz parameter of WOTS+ |
| m | 30 | bytes of message digest |
| What | Bytes | Made of |
|---|---|---|
| Public key | 32 | PK.seed ‖ PK.root |
| Secret key | 64 | SK.seed ‖ SK.prf ‖ PK.seed ‖ PK.root |
| Key generation seed | 48 | SK.seed ‖ SK.prf ‖ PK.seed |
| Signature | 7,856 | R ‖ SIG_FORS ‖ SIG_HT = 16 + 14 × 13 × 16 + 7 × (35 + 9) × 16 |
The one a Solana program can check.
SLH-DSA’s security rests on SHA-256 alone, and Solana has a SHA-256 syscall. So a program can verify it on chain, inside one transaction’s compute budget.
ML-DSA and FN-DSA rest on SHAKE and lattice arithmetic instead.
SLH-DSA is stateless, so Oraqle’s signers are restarted, rotated and replicated freely.
| Property | ed25519 | SLH-DSA-SHA2-128s |
|---|---|---|
| Standard | ed25519 | SLH-DSA-SHA2-128s, FIPS 205 |
| Security rests on | The elliptic curve discrete logarithm | SHA-256 alone |
| A quantum computer | Shor’s algorithm recovers the secret key from the public key | Grover’s algorithm halves a hash’s strength: SHA-256 keeps 128 bits |
| Signature | 64 bytes, base58 | 7,856 bytes, base64 |
| Public key | 32 bytes | 32 bytes |
| Signing | Per observation, by the signer | About a second, once per observation, by the worker |
| Checked on chain by | verify_report | verify_report_pq |
| On-chain cost | The ed25519 precompile: one cheap transaction | A buffer of 8 writes, then 340,000 to 410,000 compute units |
7,856 bytes, one transaction.
verify_report_pq does everything verify_report does, the ed25519 instruction right before it, the signer in the feed’s set with the report’s kind, the decimals, freshness, and then verifies the SLH-DSA signature in the buffer with the key of that signer’s entry.
- 10
- 21,000
- 32,000
- 43,000
- 54,000
- 65,000
- 76,000
- 87,000
- 1
open_pq_buffer(nonce)once; the rent, about 0.056 SOL, comes back on close - 2
write_pq_buffer(offset, bytes) × 8up to 1,000 bytes each; the eight can be sent together - 3
verify_report_pq(report, max_age_ms)after the ed25519 instruction; 340,000 to 410,000 of a 700,000 unit budget
pub struct SignerEntry {
pub key: Pubkey,
pub kind: u8,
pub pq_key: [u8; 32],
}create_feed and add_signer take the SLH-DSA key, which must not be all zeroes, and the attestation of a managed signer binds it. The result is the same verified report, with post_quantum set to true.
The verifier follows FIPS 205 Algorithms 20, 17, 16, 11 and 8, with SHA-256 from the sol_sha256 syscall. It is tested against signatures made by two other implementations and against altered signatures, keys and messages.
A provider’s API key, sealed twice.
A managed feed reads a provider’s API with the provider’s key, sealed to the worker before it is stored, with both key agreements at once.
Classical
ss_x = X25519(eph, worker)
The ephemeral key agreement, kept.
Post-quantum · FIPS 203
ss_m = ML-KEM.encaps(worker)
A 1,088 byte ciphertext to a 1,184 byte public key.
key = HKDF-SHA256(ss_m ‖ ss_x, salt, "oraqle/seal/v2")
One AES-256-GCM key from both secrets.
0x02 ‖ X25519 ephemeral (32) ‖ ML-KEM ciphertext (1088) ‖ iv (12) ‖ tag (16) ‖ AES-256-GCM ciphertext
Both shared secrets go into one key: X25519 and ML-KEM-768 seal it together.
The worker publishes both of its public keys in its heartbeat: the X25519 key, and the ML-KEM-768 key of 1,184 bytes. Its keys are derived from one seed with HKDF-SHA256, under their own labels.
| Key | Held by |
|---|---|
| Managed signer, SLH-DSA | the worker |
| Provider signer, SLH-DSA, from its ed25519 seed | the provider: one secret, the SLH-DSA key follows from it |
| Seal, X25519 and ML-KEM-768 | the worker |