An evidence bundle is a cryptographically sealed record of a license fact — its status, a validation receipt, an execution attestation — built so that a third party can verify it later without trusting the API’s word. Sealed bundles are additionally recorded in an append-only transparency log, so their existence at a point in time can be proven and silent modification or deletion can be detected.

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:
  1. Classical: RSA-PSS-SHA256
  2. 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.
For a hybrid-sealed bundle, verification requires both signatures to be valid — compromise of a single algorithm does not forge a bundle.
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

Bundles are always attributed to the authenticated tenant — a 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 at GET /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 with POST /api/v1/evidence/transparency/checkpoint (scope quantumlock:keys).
  • Inclusion proofsGET /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.
Properties this gives you:
  1. Append-only — recorded entries cannot be deleted without breaking subsequent proofs.
  2. Tamper-evident — a modified bundle no longer matches its leaf hash.
  3. Auditable — anyone holding the bundle, the proof, and a signed checkpoint can verify inclusion.
  4. 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.