Three things, three jobs
Keeping them separate is the whole design.
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.
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.
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:
| Verdict | Meaning |
|---|---|
| VERIFIED | proved here from public inputs — SHA-256 chain, genesis rule, Ed25519 under the supplied public key |
| OPERATOR-ATTESTED | present, but rests on the operator's secret key; this verifier cannot check it and does not pretend to |
| UNVERIFIABLE | could not be checked here, for a stated reason; other tiers still apply |
| ABSENT | the property simply isn't present in this evidence |
| FAILED | checked, and wrong |
The exit code carries the same discipline, so an automated caller can't mistake “attested” for “verified”:
| Exit code | Meaning |
|---|---|
| 0 | cryptographically verified |
| 1 | failed |
| 2 | unreadable bundle |
| 3 | operator-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:
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:
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.
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.
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.
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: 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.