Post-Quantum Security
A sufficiently large quantum computer running Shor’s algorithm breaks the discrete-log and integer-factorization problems behind Ed25519, ECDSA, and RSA in polynomial time. The audit-defensible posture is to ship NIST-standard post-quantum primitives before the cryptographically-relevant quantum era — not after — because customer audit trails are long-lived and adversaries can harvest now, decrypt later.
What is in use
Section titled “What is in use”Smart Engines uses the @noble/post-quantum library (pinned at 0.4.1), a TypeScript implementation of the NIST PQC standards. Two FIPS standards are in scope:
| Standard | Algorithm | Role | Status in platform |
|---|---|---|---|
| FIPS 204 | ML-DSA-87 (Dilithium5) | Digital signatures — NIST Level 5 | In use, partly. Validators mint quorum certificates for a few internal record types today; most attested surfaces are wired but do not yet mint them (see below). A certificate that is present but fails verification is rejected; a missing certificate is logged and the artifact is still accepted. |
| FIPS 203 | ML-KEM-768 / ML-KEM-1024 | Key encapsulation for encryption | In use for eight internal surfaces, each sealed with ML-KEM plus AES-256-GCM. New writes in the older, pre-PQC format are refused; older envelopes can still be read during a compatibility window. |
Why the top tier (ML-DSA-87 / Level 5)? The platform uses one signature level for every certificate rather than mixing tiers, and at the platform’s quorum sizes the extra cost of the highest level is small.
What is protected — the attestation
Section titled “What is protected — the attestation”The platform’s attestation is a quorum certificate in the PqcCertV1 shape. The validator that initiates an attestation collects ML-DSA-87 signatures from a validator quorum over the cluster’s internal messaging and assembles one certificate. A validator co-signs only content it has admitted itself, and only events signed with an enrolled validator key can be witnessed.
Today certificates reach a quorum for a few internal record types, such as validator membership events and smart-app runtime status. For the other attested surfaces — on-chain shadow certificates, BaaS receipts, license envelopes, marketplace listings and DKG transcripts among them — the attestation path is built, but the event that would trigger it is not yet signed with an enrolled key, so no certificate is minted. A verifier asked about one of those artifacts sees a missing certificate.
export type PqcCertV1 = { readonly pqcCertVersion: 1; readonly algorithm: 'ml-dsa-87'; readonly consumer: string; // e.g. 'license-envelope', 'baas-storage', 'onchain-hedera' readonly contentHash: string; // hex sha256 of canonical artifact payload readonly payloadHash?: string; // optional; must equal contentHash when present readonly threshold: number; // quorum threshold (k of n) readonly signers: readonly { readonly nodeId: string; readonly dilithium5PublicKey: string; readonly signature: string; }[]; readonly ts: number; // unix-ms of cert assembly readonly topicMessageId?: string; // HCS coordinate; UNSIGNED, see policy.expectedTopicMessageId};It is a quorum cert, not a single signature: each signers[] entry is one validator’s ML-DSA-87 signature, and the cert is full only when the verified-signer count meets threshold (status: 'partial' is returned if fewer than threshold sign before timeout).
%%{init: {'sequence':{'showSequenceNumbers':false}}}%%
sequenceDiagram
autonumber
participant Art as Platform artifact
participant Pub as Initiating validator
participant Peers as Validator peers
participant Cust as Customer (offline)
Art->>Pub: contentHash = sha256(canonical payload)
Pub->>Peers: request signatures over the content hash
Peers->>Peers: each signs hash with its ML-DSA-87 key
Peers-->>Pub: ML-DSA-87 signature + pubkey
Pub->>Pub: assemble PqcCertV1 when ≥ threshold collected
Note over Pub: cert ships inline or is published to an HCS attestation topic
Cust->>Cust: verifyPqcAttestation(cert, payload, registry, { network, maxAgeMs }) — offline after one Mirror read
Note over Cust: schema → content-hash → anchored registry is mandatory → per-signer key binding → reject duplicates → quorum floor
Offline verification
Section titled “Offline verification”Customers verify with a pure, no-network SDK primitive — verifyPqcAttestation(cert, payload, registry, { network, maxAgeMs }) from @hsuite/smart-engines-sdk/pqc-verify, where registry = await fetchRegistrySnapshotFromMirror({ network }) is the one network read. It performs these checks, all offline:
- Schema —
pqcCertVersion === 1,algorithm === 'ml-dsa-87'; if apayloadHashis present it must equalcontentHash(payload-hash-mismatch). - Content-hash match —
sha256(canonical(payload)) === cert.contentHash. - Anchored registry — mandatory for every consumer. The registry is the validators’ own signed HCS rows: each validator publishes its entry to the public validator-registry topic under its Ed25519 key, and the entry carries a SHA-256 commitment to its ML-DSA-87 key.
fetchRegistrySnapshotFromMirror({ network })reads those rows from any public Mirror node (rows from every answering mirror are unioned), and the verifier authenticates each one against the Ed25519 pins and topic id the SDK ships for that network — or against ananchorthe caller supplies, which the result reports asanchor.source: 'caller'. A Mirror can withhold rows (that validator is not counted) or serve old ones (each signer’s own binding must be younger thanmaxRegistryAgeMs, default 72 h), but it cannot forge one. Omit the registry and the result isregistry-snapshot-required; pass a served{snapshotTs, validators}snapshot and it isregistry-snapshot-unanchored, because whoever serves a snapshot chooses its keys.hsuite verifyreads the Mirror under the--networkpreset by default;--registry <file>verifies saved rows and--registry-anchor <file>replaces the SDK’s pins. No discovery seed, gateway or host is a trust input. When no mirror answers, the fetch throwsRegistryUnavailableError, so “registry unreachable” can never degrade into “verified against the attacker’s keys.” Known limit: the binding between a validator and its ML-DSA-87 key is authenticated by a classical Ed25519 signature on the registry row. Optional pins on the ML-DSA identities themselves exist in the anchor format but are not yet filled. - Per-signer key binding — a signer no pin names (
registry-unknown-signer), or whose key’s sha256 is not the commitment its anchored entry binds (registry-pubkey-mismatch), refuses the cert. A signer whose newest entry is a leave, whose binding is stale, or whose binding is missing, duplicated or equivocated (registry-inactive-signer,registry-evidence-stale,registry-unkeyed-signer) does not count. Each counted signature verifies under the now-bound key over the domain-separated digest of(consumer, contentHash, ts). - No duplicate signers — the same
nodeId, or the same public key under twonodeIds, is rejected outright (duplicate-signer), never silently de-duplicated. A repeat is never a legitimate quorum. - Quorum floor — verified-signer count
>=the effective threshold:⌈2N/3⌉of the anchor’sNpinned validators (a Mirror’s omissions and a hostile leave never lowerN), never below 2 (attestationQuorumThreshold), raised further by a highercert.thresholdorpolicy.minSigners; a cert cannot self-declare its way below it (threshold-not-met). The platform’s own verifier and emitter use the same function. - Freshness — a decision every caller makes. A quorum cert never expires on its own, so
policy.maxAgeMsis required: a finite bound rejects stale (stale-attestation) and future-dated (future-dated-attestation) certs, andmaxAgeMs: Infinityverifies a historical record (an archived artifact) deliberately, with no time bound. Omit it and the result is{ valid: false, reason: 'freshness-policy-required' };hsuite verifylikewise requires--max-age <ms|infinity>.
There is also a hsuite verify CLI subcommand, and the host tier can serve certificates for lookup. It serves certificates only, never the validator key set they are checked against.
What is not protected (yet)
Section titled “What is not protected (yet)”This is the honest boundary — it matters for audits.
Chain-adapter migration path
Section titled “Chain-adapter migration path”The on-chain bytes a blockchain actually stores stay on that chain’s native curve (Ed25519 on the live chains, Hedera and XRPL), because the chain’s own consensus must verify them. The design pairs each on-chain transaction with a shadow cert: an ML-DSA-87 quorum certificate that rides alongside the native signature, off chain, and stays soft-on. The chain adapters include a shadow-cert emit step, but its event is not yet signed with an enrolled key, so no shadow cert is minted today. Labels are also reserved for Stellar, Polkadot, Solana and Bitcoin, which are coming next.
%%{init: {'flowchart':{'htmlLabels':true}}}%%
flowchart LR
Tx["Transaction"] --> Native["Native chain signature<br/><i>Ed25519<br/>(NOT yet quantum-safe on chain)</i>"]
Tx --> Shadow["Shadow PQC cert<br/><i>ML-DSA-87, off-chain<br/>built, not yet minted</i>"]
Native --> Ledger["On-chain"]
Shadow --> Audit["PQ-safe audit trail"]
Consumer enforcement status
Section titled “Consumer enforcement status”- Signature certificates (ML-DSA-87) — every consumer runs
soft-ontoday: a present certificate that fails verification is rejected, while a missing certificate is logged and the artifact accepted. Moving a consumer tohard-on(reject when missing) is a per-consumer step, planned once certificate coverage is complete. Where an internal consumer’s PQC verifier is not wired at all, the consumer refuses the event rather than skipping the check. - BaaS receipts (
baas-*) and on-chain shadow certs (onchain-*) aresoft-onby design: the customer SDK, not the validator, decides whether to enforce them. None of them is minted today, for the reason above. - Encryption (ML-KEM) — enforced on its eight internal surfaces for new writes, with the cluster-key caveat above. Per-app threshold keys are built, not yet enabled.
Publication coordinate
Section titled “Publication coordinate”topicMessageId (<topicId>@<consensusTimestamp>) is outside the signed digest in pqcCertVersion: 1: it is assigned after consensus, when the quorum has already signed. The verifier therefore refuses a cert that carries one (topic-message-id-unauthenticated) unless the caller passes the same value as policy.expectedTopicMessageId, read from a Mirror node; a different value is topic-message-id-mismatch. Certs the platform emits carry none. Binding the coordinate into the signatures would need pqcCertVersion: 2 with a post-publication signing round.
Cert format stability
Section titled “Cert format stability”pqcCertVersion: 1 is the locked customer API contract. Additive changes stay v1; breaking changes bump pqcCertVersion and ship a co-released dual-accepting SDK. A cryptographic-algorithm change (post-Dilithium5) is a breaking change.
Next steps
Section titled “Next steps”- Post-Quantum Security concept — the intuition, for readers new to lattice crypto.
- TSS / MPC — the quorum that signs each cert.
- Threat Model — where the quantum adversary fits in the overall model.
- Glossary — ML-DSA, ML-KEM, lattice, KEM, attestation defined.