Skip to content
oraqle

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.

Quantum-safeSLH-DSA + ed25519

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

  1. 01One messageThe domain, the program id and the report, 204 bytes. Both algorithms sign exactly these bytes.
  2. 02ed25519, on the leftOne check on the curve against the signer’s classical key: small and quick.
  3. 03FORS, at the footThe digest of the message picks a leaf in each of 14 trees. Their roots compress into one key.
  4. 04Seven XMSS layersA one-time WOTS+ leaf signs each root, in a tree of height 9, whose own root the layer above signs.
  5. 05The lockThe last root must equal PK.root, half of the 32 byte key registered for the signer on chain.
Fig. 1 · Hybrid verification
Fig. 1One report, signed twice. The same message is signed with ed25519 and with SLH-DSA-SHA2-128s. To check the second, the verifier hashes its way up a FORS layer and seven XMSS trees; the root it reaches must equal the 32 byte key registered for the signer on chain. A report is accepted only when both signatures verify.

01 Why two signatures

Shor · Grover

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.

What Oraqle keeps from the classical design and what it adds
PartClassical, keptPost-quantum, addedStandard
Report signaturesed25519SLH-DSA-SHA2-128s, over the same messageFIPS 205
Signer registry on chained25519 keyThe signer’s 32 byte SLH-DSA public key, in the same entry
Verification on chained25519 precompile (verify_report)SLH-DSA verified by the program with SHA-256 alone (verify_report_pq)FIPS 205
Attestation of managed signersed25519Binds the SLH-DSA key too
Sealed upstream secretsX25519ML-KEM-768, combined with X25519FIPS 203

02 The hybrid signature

Same message, two algorithms

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
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.

envelope.json
{
  "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.

03 The parameter set

FIPS 205, Table 2

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.

The parameters of SLH-DSA-SHA2-128s
ParameterValueMeaning
n16bytes in every hash output
h63total height of the hypertree
d7layers of XMSS trees
h′9height of each XMSS tree
a12height of each FORS tree
k14FORS trees
lg w4Winternitz parameter of WOTS+
m30bytes of message digest
The sizes of SLH-DSA-SHA2-128s keys and signatures
WhatBytesMade of
Public key32PK.seed ‖ PK.root
Secret key64SK.seed ‖ SK.prf ‖ PK.seed ‖ PK.root
Key generation seed48SK.seed ‖ SK.prf ‖ PK.seed
Signature7,856R ‖ SIG_FORS ‖ SIG_HT = 16 + 14 × 13 × 16 + 7 × (35 + 9) × 16

04 Why SLH-DSA

and not ML-DSA

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.

ed25519 and SLH-DSA compared
Propertyed25519SLH-DSA-SHA2-128s
Standarded25519SLH-DSA-SHA2-128s, FIPS 205
Security rests onThe elliptic curve discrete logarithmSHA-256 alone
A quantum computerShor’s algorithm recovers the secret key from the public keyGrover’s algorithm halves a hash’s strength: SHA-256 keeps 128 bits
Signature64 bytes, base587,856 bytes, base64
Public key32 bytes32 bytes
SigningPer observation, by the signerAbout a second, once per observation, by the worker
Checked on chain byverify_reportverify_report_pq
On-chain costThe ed25519 precompile: one cheap transactionA buffer of 8 writes, then 340,000 to 410,000 compute units

05 On chain

verify_report_pq

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.

PqBuffer · data [7856]seeds: "pq-buffer", authority, nonce
  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
offset 07,856 bytes
  1. 1open_pq_buffer(nonce)once; the rent, about 0.056 SOL, comes back on close
  2. 2write_pq_buffer(offset, bytes) × 8up to 1,000 bytes each; the eight can be sent together
  3. 3verify_report_pq(report, max_age_ms)after the ed25519 instruction; 340,000 to 410,000 of a 700,000 unit budget
Fig. 2A transaction holds 1,232 bytes; an SLH-DSA signature is 7,856. So the signature is written into a buffer account first, in eight writes, and the program verifies it from there. A buffer is reusable: write the next signature over the last and verify again.
state.rs
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.

The steps, in the docs

06 Sealed secrets

ML-KEM-768 + X25519

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

Fig. 3How a provider’s API key is sealed to Oraqle’s worker, with X25519 and ML-KEM-768 together.

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.

How each key is derived
KeyHeld by
Managed signer, SLH-DSAthe worker
Provider signer, SLH-DSA, from its ed25519 seedthe provider: one secret, the SLH-DSA key follows from it
Seal, X25519 and ML-KEM-768the worker