Skip to main content

Quantum-safe settlement receipts

A settlement receipt is a portable, self-contained proof that a rollup's settlement batch was anchored to the Main Chain under a post-quantum signature. It binds a specific batch to the on-chain anchor that committed the rollup's state at that height, and it can be verified fully offline — no node, no trust in the verifier's network path.

The anchor signature is ML-DSA-87 (Dilithium-5, FIPS-204), the same post-quantum scheme the Main Chain uses, so a receipt inherits the base chain's quantum-safe integrity.

The canonical anchor message

Verification checks a Dilithium-5 signature over a canonical message built from the anchor fields, concatenated in this exact order:

layer_id || layer_height (8-byte big-endian) || state_root || validator_set_hash

anchorSignBytes(...) produces these bytes; the verifier reconstructs them from the receipt and checks the signature against the layer creator's registered ML-DSA-87 key.

Build and verify (TypeScript)

import {
createRdkClient,
buildSettlementReceipt,
verifySettlementReceipt,
} from "@qorechain/rdk";

// The public qore.host endpoints are baked into the presets (RDK ≥ 0.4.2);
// pass `endpoints` only to target your own node.
const rdk = createRdkClient({ network: "testnet" });

// Build a portable receipt for one batch.
const receipt = await buildSettlementReceipt(rdk, "my-roll", 7);

// Persist it, ship it, hand it to a counterparty — it is self-contained JSON.

// Verify fully offline. With no client, you must supply the creator's key.
const result = await verifySettlementReceipt(receipt, {
creatorPublicKey: "<layer creator ML-DSA-87 public key>",
});

console.log(result.valid); // true when the signature and the batch↔anchor binding both hold

If you pass a client instead of (or alongside) creatorPublicKey, verification fetches the layer creator's registered ML-DSA-87 key from the chain (getPqcAccount(address)). Verification then checks two things:

  1. the Dilithium-5 signature over the canonical anchor message, and
  2. the batch ↔ anchor state-root binding — that the batch you hold is the one the anchor committed.
// Online verification: fetch the creator's PQC key from the chain.
const online = await verifySettlementReceipt(receipt, { client: rdk });

Reading anchors

Receipts are built from the on-chain x/multilayer Anchor query, available over both gRPC and REST as of chain version v3.1.80 (see REST / gRPC Endpoints). The reads:

  • getAnchor(layerId) — the anchor for a layer.
  • getLatestAnchor() — the most recent anchor.
  • getAnchors(layerId) — the anchor history for a layer.
  • getPqcAccount(address) — a registered post-quantum account (its ML-DSA-87 key), used to verify the creator's signature.

CLI

# Build a receipt and print it.
qorollup receipt my-roll 7

# Build, then verify it inline.
qorollup receipt my-roll 7 --verify

# Build and write it to a file.
qorollup receipt my-roll 7 --out receipt.json

See Deploying a Rollup for the full qorollup operator CLI.

Other languages

The Python, Go, Rust, and Java (JVM) clients expose the same build/verify surface. They perform the ML-DSA-87 verification through the qorechain-pqc library rather than a bundled JavaScript implementation; install it alongside the RDK client for your language.