Provenance

How QMesh records a run so that its result can be checked later.

When a circuit job completes, QMesh builds a provenance bundle: a record of what was submitted, which plan ran and what came back, committed with a hash. Anyone holding the bundle can recompute the hash and see whether anything changed.

How a run is recorded and verifiedA finished run is written into a bundle and hashed. Many bundle hashes are folded into one Merkle root. Optionally the bundle is pinned to IPFS and the root is anchored on a test network.THE RUNBUNDLEMERKLE ROOTIPFSIPFSANCHORRESULTSHA-256PINROOT01 RECORDA finished run is written into a bundle02 HASHIts contents are committed with SHA-25603 MERKLE ROOTMany bundles share a single root04 PUBLISHOptional: pin the bundle to IPFS05 ANCHOROptional: write the root to a test network

What a bundle proves

A bundle proves that a recorded result has not changed since it was recorded. It does not prove that the work ran on quantum hardware (it did not: see Scope), that the answer is scientifically correct, that a peer was honest, or that stored data stays available. Treat an anchor receipt as evidence to check, not as a verification result.

What goes in

The bundle commits to the input, the plan id, the ordered fragment results and the final result. It stores a hash of the result rather than a second copy of the potentially large statevector. It includes the run and owner ids and event records, and it does not include the circuit text or the counts.

A bundle is created for circuit jobs. Finance jobs have their own provenance inside their results and are not covered by the bundle pipeline yet.

If a new circuit run cannot persist its provenance, the run fails instead of reporting completion.

How it is hashed

  1. The bundle is turned into canonical JSON: keys sorted, no insignificant whitespace, UTF-8. This is a Python JSON profile, not RFC 8785, so another language must reproduce the same bytes rather than rely on its default serializer.
  2. The commitment is the SHA-256 of an envelope {"profile":"qmesh-provenance-sha256-v1","bundle":{…}}. The storage and anchors fields are excluded, so attaching a storage location or an anchor later does not change the hash.
  3. Many bundles can be combined under one Merkle root. Leaves are SHA256(0x00 || digest) and parents are SHA256(0x01 || left || right). An odd last node at each level is promoted unchanged. This follows the tree shape of RFC 6962, and it is not compatible with OpenZeppelin's MerkleProof.

Verify a run

curl http://localhost:8081/api/v1/provenance/<bundle_id>/verify

Only the run's owner or an administrator can verify; anything else is a 404. The response lists checks, each of kind hash, storage or anchor, with one of four statuses:

StatusMeaning
passedThe stated check succeeded.
failedThe evidence contradicts the recorded commitment.
unavailableAn adapter, a historical commitment or an external service is missing.
not_applicableNo attachment of that type was recorded.

The overall result is unverified when a check failed or none passed, partially_verified when some passed but the evidence is incomplete, and verified only when every applicable check passed and at least one storage or anchor check succeeded. Local database hashes alone never reach verified.

Publish to IPFS

If KUBO_RPC_SECRET is set, the coordinator publishes each completed bundle to the platform's Kubo node and pins it, after the run is already saved. Publishing is best effort: a failure never changes or fails the run, and it is not retried. Bundles up to 256 KiB are stored as a single block.

Pinned content is announced on the public IPFS network. Bundles carry run and owner ids and hashes, not the circuit, but publish them only where that is acceptable.

Anchor on a test network

Anchoring writes the Merkle root of many bundles to Ethereum Sepolia, so anyone can later check that a bundle existed at that time. It arrived in #113 (checking anchors) and #114 (anchoring on a schedule). It is off until you configure it.

  • With QB2_ANCHOR_NETWORK and QB2_ANCHOR_SENDER set, the backend can verify anchors.
  • With QB2_ANCHOR_PRIVATE_KEY also set, it anchors circuit-job bundles on a schedule. Finance bundles are not anchored yet. Use a dedicated, low-balance key that is never a user, platform or deployer wallet.
  • An anchor is a zero-value transaction from the anchor account to itself. Its 45-byte calldata holds the marker QMSHANC1, the Merkle root, the batch size and a profile byte. No contract is deployed.
  • A run happens every QB2_ANCHOR_INTERVAL_SECONDS (default hourly). One batch is one transaction of about 22,000 gas, whatever its size (up to QB2_ANCHOR_MAX_BATCH, default 10,000).
  • A batch survives crashes: it moves prepared, signed, submitted, anchored (or failed), the signed transaction is stored before it is broadcast, a stuck transaction is replaced with a higher fee up to QB2_ANCHOR_MAX_FEE_GWEI, and anchors are recorded only after the block is finalized. Only one replica runs the scheduler at a time.
  • To rotate the key, set the new key and sender and list the old address in QB2_ANCHOR_PAST_SENDERS, so older anchors keep verifying.

Only test networks are allowed.

Anyone can check a downloaded bundle against its anchor without the database:

cd backend
uv run --frozen python scripts/verify_anchor.py bundle.json --sender 0xANCHOR_ACCOUNT

The script exits 0 if every anchor passed, 1 if any check failed, and 2 if none failed but a transaction was unavailable.

On this page