Layer 6 · Verification, provenance & anchoring
Canonical reference:
digstore-core::merkle+dig-client-wasm/dig-node/ the DIG Browser C++ (dig_crypto.cc). The trusted root comes only from the chain (anchored-root pinning), never from the serving origin.
The four ordered integrity gates
Applied in this order on a content read:
Gate 1 — per-resource merkle inclusion proof (always-on, fail-closed)
resource_leaf(served_ciphertext) == proof.leaf AND proof.verify() folds to proof.root. See Merkle inclusion proofs. The fullest fail-closed spec is the native VerifyInclusion (dig_crypto.cc:585-677): base64-decode + bounds-check → computed_leaf == proof.leaf → fold with NODE_TAG → root check.
Gate 2 — AES-256-GCM-SIV authenticated decryption (fail-closed)
The content key decrypts each chunk; a tag failure (tamper / wrong key / decoy) yields no plaintext. chunk_lens MUST sum to ciphertext.len() (a hard framing check) — dig_crypto.cc:679-737.
Gate 3 — anchored-root pinning against the CHIP-0035 singleton
The chain, not the serving origin, is the authority for a store's latest authorized root. Resolution never trusts the rpc-served "latest".
dig.getAnchoredRoot(dig-node/src/lib.rs:721-743) walks the CHIP-0035 DataStore singleton lineage on coinset.org viasync_datastoreand returnsmetadata.root_hash— the trusted root a browser pins a rootlesschia://URN against.- CLI clone/pull additionally requires the served root to equal the singleton's current on-chain root, read via the launcher id in the module's ChainState section; it fails closed on mismatch OR an unreachable chain (never a silent fallback).
- Caveat: a module with no embedded ChainState makes the chain-root gate a no-op — the authenticated head signature is then the only authority.
Gate 4 — risc0 execution proofs (wired, mock backend by default)
The execution proof proves a node faithfully executed the serving computation. verify_response requires response.roothash ∈ trusted_roots AND bound_public_output(roothash, output) == proof.public_output; verify_node_attested requires the node pubkey ∈ the module's trusted attestation set before the sig check (prover.rs:36-110).
The default backend is MockProver, whose receipts are not cryptographically binding. Real risc0 proofs require the risc0 cargo feature plus the RISC0 toolchain and supplying CoinsetChainSource + SystemClock + Risc0Prover. The enforced integrity guarantees today are gates 1–3 (merkle inclusion, authenticated decryption, anchored-root pinning); rely on those, not on gate 4, for trust.
The freshness contract
When real execution proofs are enabled, public_input = client_nonce(32) || ChiaBlockRef(44) (= 76 bytes), and CoinsetChainSource fetches the Chia peak/block records from coinset.org for the §13.7/§16 freshness gate. The host attestation freshness window is 300s (attestation).
The rpc.dig.net serve path uses serve_blind with default deps (MockProver + MockChainSource + FixedClock), so the §12/§13 attestation-freshness gate is not active there. Reads remain safe because the reader enforces gates 1–3 against the chain-anchored root client-side — the host is never the trust anchor.
Provenance UX surfacing
The native loader records a process-global ledger keyed by capsule (storeId:rootHash); the chia://shields WebUI restates per-resource verdicts (inclusionProofPassed, catalogued DIG_ERR_* codes), fail-closed: any entry without inclusionProofPassed === true counts as FAILED. A chia:// page that rendered earns a "Verified on Chia" omnibox chip (+ " · local" when served from this device's cache). Only the chia:// content scheme earns the chip; dig:// internal pages do not. The shields page exposes document-level data-dig-* attributes for agents.
Related
- Merkle inclusion proofs — gate 1
- Cryptography — gate 2
- On-chain anchoring — the singleton gate 3 pins against
- Inclusion vs execution proofs — gate 4, in depth
- The blind host model — why the host is never the trust anchor