Three things, three jobs

Keeping them separate is the whole design.

VIRP produces the record

The open protocol

Apache 2.0, public IETF drafts, public hostile-review history. VIRP signs infrastructure observations at the moment of capture and hash-links them into a chain. Cited, not sold — the full detail is on the Engineering Details page.

Docket presents the record

The evidence book

Docket assembles a portable bundle and, later, the human-readable exception report. It sits deliberately outside the trust path: it never signs, never holds a private key, never touches infrastructure.

virp-verify challenges the record

The checker

A standalone, dependency-light binary that recomputes every checkable property from public inputs and hands down a verdict. Intended to be free and open on release — the trust lives in anyone being able to run it.

The vocabulary — why there's no “PASS”

virp-verify never collapses its findings into a green checkmark. Each property gets one of five honest words:

VerdictMeaning
VERIFIEDproved here from public inputs — SHA-256 chain, genesis rule, Ed25519 under the supplied public key
OPERATOR-ATTESTEDpresent, but rests on the operator's secret key; this verifier cannot check it and does not pretend to
UNVERIFIABLEcould not be checked here, for a stated reason; other tiers still apply
ABSENTthe property simply isn't present in this evidence
FAILEDchecked, and wrong

The exit code carries the same discipline, so an automated caller can't mistake “attested” for “verified”:

Exit codeMeaning
0cryptographically verified
1failed
2unreadable bundle
3operator-attested

For a bundle with several sessions, the process exit code reflects the least independently established verdict: unreadability or failure takes precedence, then operator-attested, then cryptographically verified. A single failed session fails the bundle; a single unverifiable-but-attested session holds the whole bundle at attested even if others verified cleanly. The green case is only reported when nothing drags it down.

Act I — a clean verdict

We point virp-verify at a bundle exported from a live VIRP node. It contains two signed sessions and one older, pre-signing session, plus the node's public key.

The signed sessions come back fully proven — every hash recomputed, every link checked, every Ed25519 signature verified under the node's public key, and the session-key-binding rule confirming every entry was signed under the same key the head commits to:

$ virp-verify ./bundle-313
session burnin-reenabled:2026-08-23  (3 entries)  signed under key_id c1104805…
  entry_hashes           VERIFIED     3 entries recomputed (SHA-256 over canonical bytes)
  contiguity             VERIFIED     sequences 0..2 contiguous
  genesis                VERIFIED     genesis rule reproduces
  links                  VERIFIED     2 links checked against recomputed hashes
  head_commitment        VERIFIED     head commits to the final entry hash
  entry_hmacs            OPERATOR-ATTESTED (unverifiable here)
  head_signature         VERIFIED     ed25519-detached-v1, domain tag VIRP-CHAIN-HEAD-SIG-v1
  session_key_binding    VERIFIED     every entry signed under the head's key_id
  entry_signatures       VERIFIED     3 entries, ed25519-detached-v1
  verdict: CRYPTOGRAPHICALLY-VERIFIED

The honesty that sells it: notice entry_hmacs reads OPERATOR-ATTESTED, not VERIFIED. Those HMAC values rest on a secret key the operator holds and the verifier deliberately does not. Rather than wave them through, the tool says plainly: present, but I can't independently check these, and I won't pretend I can. The properties it marks VERIFIED are the ones it actually proved from the public key alone.

And the older session, from before signing was deployed, is graded for exactly what it is — no signatures to check, so they're ABSENT, and the record rests on attestation:

$ virp-verify ./bundle-313 (continued)
session virp-cli:pve-lab  (4 entries)
  entry_hashes           VERIFIED
  head_signature         ABSENT       unsigned / pre-signing session
  entry_signatures       ABSENT
  verdict: OPERATOR-ATTESTED (unverifiable by this verifier)

One tool, one bundle, three independently graded sessions — and no pressure to give unlike evidence the same verdict. That refusal to average is the product.

Act II — now try to fool it

A verdict is only worth what it costs to forge. So we tamper with copies of the verified bundle and watch what happens. Four different attacks; four different correct refusals.

1. Alter the evidence

Change a single field in one signed entry — one word in one record.

$ virp-verify ./tamper-body
  entry_hashes     FAILED   entry hash mismatch at sequence 0
  links            FAILED   previous hash mismatch at sequence 1
  head_commitment  FAILED   head does not match final verified entry
  entry_signatures FAILED   Ed25519 signature verification failed at sequence 0
  verdict: FAILED

One flipped field trips four independent alarms, each naming its own location. To forge the record undetected, an attacker would have to defeat the hash chain and the head commitment and the Ed25519 signature at once — and the last of those requires the signing key, which never enters Docket, never ships in the bundle, and is not available to the verifier. Meanwhile the other signed session in the same bundle stayed CRYPTOGRAPHICALLY-VERIFIED: the tool localizes the damage instead of throwing up its hands.

2. Strip a signature

Delete one entry's signature, leaving everything else intact.

$ virp-verify ./tamper-strip
  session_key_binding  FAILED   missing Ed25519 signature at sequence 1 in a head-signed session (stripped signature)
  entry_signatures     FAILED   session key binding failed; entry signatures cannot be trusted
  verdict: FAILED

You can't quietly drop a signature. The head committed to a fully-signed session, so a missing signature is a detected violation, not a silent gap.

3. Corrupt a signature, leave the body untouched

Flip one byte of a signature. The entry data is unchanged.

$ virp-verify ./tamper-sig
  entry_hashes         VERIFIED   (body genuinely unchanged)
  session_key_binding  VERIFIED   (key id still correct)
  entry_signatures     FAILED     Ed25519 signature verification failed at sequence 0
  verdict: FAILED

This is the most important one. The hash still verifies and the key id is still right — and the signature still FAILS. The checks are independent: a valid hash cannot launder a broken signature, and a correct key id can't either. Each cryptographic property is proved on its own terms.

4. Relabel the key

Point keys.json at a different key id than the key bytes actually derive.

$ virp-verify ./tamper-key
  virp-verify: cannot read bundle: keys.json claims key_id dead4805…
               but the key bytes derive c1104805…
  verdict: UNREADABLE (nothing was verified)

The verifier re-derives the key id from the key bytes and refuses the bundle outright. You can't relabel a key to make signatures appear to come from a different identity — the tool won't even begin grading evidence whose framing it can't trust.

What Docket proves — and what it doesn't

Stated as plainly as the tool states it, because the boundary is the product:

It proves

The record is an unbroken, unaltered, sequential chain; signed entries were signed by the holder of a specific key; and a signed head binds the final entry, the key identity, and the recorded timestamp together.

When a bundle also includes a VIRP seal, it additionally proves that a given session head belongs to that seal — and where the seal's root has been anchored in an independent public record (a commit in the public VIRP repository plus an OpenTimestamps proof), that membership becomes a dated, publicly witnessed commitment rather than a bare self-signed timestamp. The bundle in this walkthrough is unsealed, so it demonstrates signing and binding, not anchoring.

It does not prove

Who owns a signing key — that a signature attributes to a key is one fact; whose key it is, is a separate trust decision. Nor that HMAC-only history is authentic — that rests on the operator's secret: attested, never verified here.

And — the deepest boundary — it does not prove that the device actually did the thing. That boundary matters enough to get its own box below.

The capture boundary. VIRP records what the capture point observed. If the capture point is fooled, the chain faithfully records the fooling. Docket proves the record is intact; it doesn't turn the record into ground truth. That last sentence is not fine print — it's the reason to believe everything above it.

Run the verifier yourself

virp-verify is intended to be free and open source on release — its openness is part of the trust argument, since a checker you can't run yourself is a checker you have to take on faith.

virp-verify publishes shortly — ask to be notified

Where it fits

Docket is for the moment someone who wasn't there asks “how do you know it did what it says?” — an examiner, a cyber-liability insurer, a client's counsel, a change-advisory board. Its natural home is infrastructure touched by automated or autonomous systems you're accountable for, where “the tool's own log says so” isn't an answer you can stand behind.

  • VIRP is the open protocol underneath — cited, not sold.
  • virp-verify is intended to be free and open — the trust lives in anyone being able to run it.
  • Docket is the evidence book on top — the part that turns a signed chain into something a human can read, drill into, and hand to an auditor.