Bundle structure
A bundle has four parts:Canonicalization
Before signing,header + subject + evidence are serialized as RFC 8785 (JCS) canonical JSON — keys sorted, no insignificant whitespace, UTF-8 — and hashed with SHA-256. Canonicalization is what makes independent re-verification possible: any conforming implementation reproduces byte-identical input to the signature check.
Dual signatures
Sealing signs the canonical hash with:- Classical: RSA-PSS-SHA256
- Post-quantum (when the PQC stack is available): ML-DSA (FIPS 204), produced by real liboqs lattice cryptography — the service’s container build refuses to ship unless an ML-DSA sign/verify round-trip passes in the runtime image.
Bundles are sealed and signed, not blockchain-anchored: integrity rests on the signatures and the Merkle transparency log below, and QuantumLock does not claim otherwise.
Creating and verifying bundles
tenant_id in the request body is ignored, so no caller can mint evidence attributed to someone else. Reads (GET /api/v1/evidence/bundles/{bundle_id}) are tenant-scoped for the same reason.
The transparency log
Sealed bundles are appended to a Merkle tree over SHA-256 with domain-separated leaf and node hashes.- Checkpoints — signed statements of
(tree_size, root_hash, timestamp), listed atGET /api/v1/evidence/transparency/checkpoints. A checkpoint is stored before it is returned; if it cannot be stored, none is issued. New checkpoints can be forced withPOST /api/v1/evidence/transparency/checkpoint(scopequantumlock:keys). - Inclusion proofs —
GET /api/v1/evidence/transparency/proof/{bundle_id}returns the bundle’s leaf index, the audit path (sibling hashes with left/right positions), the checkpoint’s root hash, and the tree size. Recomputing the path from the bundle’s leaf hash must land exactly on the checkpointed root.
- Append-only — recorded entries cannot be deleted without breaking subsequent proofs.
- Tamper-evident — a modified bundle no longer matches its leaf hash.
- Auditable — anyone holding the bundle, the proof, and a signed checkpoint can verify inclusion.
- Rollback-resistant — signed checkpoints pin tree growth over time.
Verifying independently
You do not have to trust the API’s verification endpoint — you can check everything yourself:1
Fetch the verification keys
GET /api/v1/v2/keys or GET /api/v1/evidence/keys (scope quantumlock:read). The response is JWKS-like; entries carry public key material for asymmetric keys and state whether offline verification is possible. Verification is offline but not anonymous — fetching the keys requires a read-scoped API key.2
Recompute the canonical hash
Canonicalize
header + subject + evidence per RFC 8785 and hash with SHA-256.3
Check the signatures
Verify the RSA-PSS-SHA256 signature; for hybrid bundles, also verify the ML-DSA signature (with liboqs — install
quantumlock[pqc]). Both must pass for a hybrid bundle.4
Check inclusion
Fetch the inclusion proof, recompute the Merkle path from the bundle’s leaf to the root, and compare against a signed checkpoint.
Honest key publication. The keys endpoints never publish placeholder material. If the deployment holds no key with a distributable public half, they answer
503 with the reason — never a 200 with keys that cannot verify anything. Development-only fallback keys are unmistakable by design: key IDs prefixed ql-unsigned-hmac, algorithm literal HMAC-SHA256-NOT-A-SIGNATURE, no public key, and offline_verification: false. Production refuses to start in that state — see Security.