TILLNOTARY · EVIDENCE

Evidence bundles.

GA

An evidence bundle is a portable, self-contained file holding a leaf, its inclusion path, the checkpoint it was proven against, and the witness cosignatures — everything needed to verify the record with zero network access. It is the artifact you hand an auditor, a court, or an air-gapped reviewer, and it keeps working when the reviewer has no account, no token, and no connectivity.

01The artifact

Self-contained, or it isn't evidence

A proof that requires calling our API to check is a proof that depends on us being reachable, truthful, and still in business at verification time. Evidence bundles remove that dependency: the file carries the claim and every cryptographic input needed to check it. The verifier brings exactly one thing of their own — the pinned public keys from a trust file — and that is deliberate: keys must come from somewhere the bundle’s author cannot touch, or the bundle would be vouching for itself.

Together, the checkpoint signature, the witness cosignatures, and the inclusion path make the bundle tamper-evident: any modification to any byte of the leaf, the path, or the checkpoint makes verification fail, deterministically.

02Format

The bundle JSON

json
{
  "tillnotary_evidence": 1,
  "origin": "notary.tilldev.dev/acme-releases",
  "suite": "sig-ed25519-mldsa65-sha256.v1",
  "leaf": {
    "index": 5,
    "value_hex": "c53d57898e94df8d3fd05e41456bb17357992da9c36bab87edda8fe75f9acafb",
    "bytes_b64": null,                     // the attested bytes, for --attest submissions
    "content_class": "hash-only"           // or "public-attestation"
  },
  "inclusion": {
    "tree_size": 8,
    "path_hex": [                          // sibling hashes, leaf → root
      "b153ffc56f36cbfd7bb208f43603fe704c27a0b9424c0424c94fd99f2cc539a3",
      "ed3fcf4624cb77b4775ec5717351c2907a92635984536725bb9056e89b0e2394",
      "a1d55691a4764f1c797f8849de205858215b2113e6f0dca650bc5c85f428a743"
    ]
  },
  "checkpoint": {
    "tree_size": 8,
    "root_hash": "96b66e36e50a3a61fc37f08bf11e12d2b6ea922068183a8d24e1ae613b98402d",
    "timestamp": "2026-09-27T05:44:12.622Z",
    "key_name": "tillnotary-log/acme-releases",
    "signature_b64": "mBhb35vfRMRqJBGq…"
  },
  "cosignatures": [
    { "witness": "tillnotary-witness/w1", "observed_at": 1790487852, "signature_b64": "4LNgxgE5WTXeLCRK…" },
    { "witness": "acme-witness/primary", "observed_at": 1790487853, "signature_b64": "vv0siERecad3wp+q…" }
  ]
}
  • tillnotary_evidence: 1 — format version; verifiers refuse versions they do not understand rather than guess.
  • leaf.value_hex — the hash that was logged. leaf.bytes_b64 carries the raw content only when it was attested at submit time (capped at 4096 bytes) and is null otherwise; content_class is hash-only or public-attestation accordingly.
  • inclusion.path_hex — the Merkle sibling hashes from the leaf up to the root, for the tree of inclusion.tree_size.
  • checkpoint + cosignatures — the signed head the path resolves to, and the cosignatures from the witnesses you pinned, which make it trustable.
03Export

Getting a bundle out

Export from the CLI with proof … --out, or capture one at submit time:

bash
# CLI — verify leaf 5 and export its bundle
$ tilldev notary proof acme-releases 5 --out bundle.json
OK VERIFIED — leaf 5 of notary.tilldev.dev/acme-releases
leaf           c53d57898e94df8d3fd05e41456bb17357992da9c36bab87edda8fe75f9acafb
content class  hash-only
origin         notary.tilldev.dev/acme-releases
tree size      8
root           96b66e36e50a3a61fc37f08bf11e12d2b6ea922068183a8d24e1ae613b98402d
timestamp      2026-09-27T05:44:12.622Z
cosignatures   tillnotary-witness/w1, acme-witness/primary
status         COMPLETE (witness-cosigned)

inclusion path (3 nodes, leaf→root):
  b153ffc56f36cbfd7bb208f43603fe704c27a0b9424c0424c94fd99f2cc539a3
  ed3fcf4624cb77b4775ec5717351c2907a92635984536725bb9056e89b0e2394
  a1d55691a4764f1c797f8849de205858215b2113e6f0dca650bc5c85f428a743
OK Evidence bundle written to bundle.json

# a submit writes its receipt as a bundle in the same step
$ tilldev notary submit acme-releases --file dist/app-v2.4.1.tar.gz --out receipt.json

Or from the Node SDK:

ts
// SDK — same artifact, programmatically
const bundle = await notary.exportEvidence('acme-releases', 5)
await fs.writeFile('bundle.json', JSON.stringify(bundle))

Both paths verify the bundle themselves before writing it. Until witnesses have cosigned a head that includes the leaf, the SDK refuses to export, and the CLI still writes the file but warns that it will not verify offline yet; re-run proof after the witnesses’ next pass.

04Verification

Offline verification: what is checked, what is refused

bash
# no network, no token, no server — only the bundle and your pinned trust file
$ tilldev notary verify bundle.json
OK VERIFIED (offline) — leaf 5 of notary.tilldev.dev/acme-releases
leaf          c53d57898e94df8d3fd05e41456bb17357992da9c36bab87edda8fe75f9acafb
origin        notary.tilldev.dev/acme-releases
tree size     8
root          96b66e36e50a3a61fc37f08bf11e12d2b6ea922068183a8d24e1ae613b98402d
timestamp     2026-09-27T05:44:12.622Z
cosignatures  tillnotary-witness/w1, acme-witness/primary
status        COMPLETE (witness-cosigned)
ts
import { verifyEvidence } from '@tillstack/sdk-notary-node'

// standalone function — needs no client, no baseUrl, no token
const checkpoint = verifyEvidence(bundle, trust)   // throws NotaryVerificationError on any failure

Verification recomputes everything from first principles:

  • the bundle’s origin matches a log pinned in your trust store;
  • hashing the leaf and folding in inclusion.path_hex reproduces exactly checkpoint.root_hash at checkpoint.tree_size;
  • the checkpoint signature verifies under the pinned log key — both halves of the hybrid signature;
  • enough cosignatures verify under pinned witness keys to meet your configured threshold;
  • if bytes_b64 is present, the bytes hash to value_hex under the log’s suite.
RefusedWhy
Wrong originThe bundle names a log that is not in your trust store — an unpinned log cannot be verified at all, only trusted blindly, and the tooling does not do blind.
Tampered leaf or rootAny altered byte breaks the hash chain from leaf to root — the recomputed root will not match the signed one.
Cosignatures below your thresholdThe log alone vouching for itself is not sufficient; your required_cosignatures floor is enforced client-side.
Bytes that don’t hash to the logged leafAttested content must reproduce value_hex exactly — substituted content is detected, not accepted.
05Longevity

Keeping a bundle valid for the long term

A bundle is anchored to the checkpoint inside it, so it never expires and never needs the log to still be online. Two habits make decade-scale evidence stronger:

  • Keep the bundle and its checkpoint together, in your own archive. The checkpoint is your comparison point against the log’s public history — and, if it ever came to it, one half of a non-repudiable equivocation proof.
  • Re-export later to pick up additional cosignatures. Witness sets grow over time; re-running tilldev notary proof against the same leaf yields a bundle carrying every cosignature available today. The old bundle stays valid — the new one is simply harder to argue with.
Which suite will your evidence need?
A bundle inherits its log’s signature suite, and the suite is fixed for the life of the log. If this evidence may ever need CNSA 2.0 algorithm levels, the log must have been created on the government profile — decide that at log creation, not at export time.

The bundle’s anatomy leans on Checkpoints & signatures and Witnesses & refusals. To automate export and verification, see the Node SDK.