Releases: nhowardtli/virp-verify
Release list
virp-verify v0.1.3
virp-verify v0.1.3
Built from commit c38c4f8 of nhowardtli/virp-verify. Report format docket-report/0.9.
Binaries: virp-verify-x86_64 and virp-verify-aarch64, both static musl. Hashes in SHA256SUMS.
New: cross-session capture continuity. When a bundle carries consecutive sessions for the same camera, the BOUNDARY block now reports each outage across the session boundary with its duration and gap reason. Per-session grades, overall verdicts and exit codes are unchanged; the object is omitted when no camera has two sessions.
Keys: adds the Reolink RLC-810A producer public key (4727a5b9). Chain, Axis producer and witness keys unchanged.
Samples: bundle-blip-20260907 (6 sessions, two cameras, Sep 5-7 2026, including a 28.4 h and a 10.5 h accounted outage). Expected: exit 5 unpinned, exit 0 with the chain key pinned and both producer keys supplied; witness ABSENT because this export did not carry receipts (exporter fix pending, verifier unchanged). axis-20260904-v6 is kept for the witness VERIFIED and referenced-citation properties this sample cannot show.
Verify: sha256sum -c SHA256SUMS, then minisign -Vm SHA256SUMS -p <seal-2026-09.pub>. SHA256SUMS is signed under seal key AD48B20F5D11CED6 (2026-09).
virp-verify 0.1.2 — the binary and the tag name the same commit
A provenance fix. This release's binary was built at this release's tag, and
that is now a rule with no exceptions.
A reviewer caught what 0.1.1 got wrong: its envelope — tag, notes, trusted
comment — named commit 236e683, while the binary inside reported 0.1.0 (commit 01ce2e2, clean, release), because 0.1.1 deliberately shipped the 0.1.0
binary unchanged. Every statement in that release was true and the notes said
which tag reproduced the hash, but a reader checking the first line of a report
against the release it came from found two commits and no way to tell, from the
release alone, which was the mistake. A provenance chain that needs a paragraph
to survive its own first look is not doing its job. The rule is written down in
docs/VERIFIER-RELEASE.md.
Requirements: Linux on x86_64 — native, WSL2, or an x86_64 container
(docker run --rm -it debian:stable-slim) — with curl, jq, minisign,
tar and sha256sum installed. The binary below is x86_64 Linux only; on
Apple Silicon or arm Linux it will not run, and Build it yourself is your
path.
Everything, in one block
keys=https://raw.githubusercontent.com/nhowardtli/virp-verify/411d008a04eb9a52b3ade9a4afe58ae4b358099b/keys
mkdir -p virp-verify-0.1.2 && cd virp-verify-0.1.2
# Every release asset except the keys — those come from the commit above, which
# is dated and cannot be replaced, unlike a file attached to a release page.
curl -fsSL https://api.github.com/repos/nhowardtli/virp-verify/releases/tags/virp-verify-v0.1.2 \
| jq -r '.assets[].browser_download_url' | grep -vE '\.(hex|keys\.json)$' \
| xargs -n1 curl -fsSLO
for k in $(grep -oE '[^ ]+\.(hex|keys\.json)$' SHA256SUMS) seal-virp-ad48b20f-2026-09-05.pub; do
curl -fsSL "$keys/$k" -o "$k"
done
sha256sum -c SHA256SUMS
minisign -Vm SHA256SUMS -p seal-virp-ad48b20f-2026-09-05.pub
tar -xzf axis-20260904-v6.tar.gz
chmod +x virp-verify
show='^virp-verify |^ (witness {16}|referenced_artifact_binding )[A-Z]|^OVERALL VERDICT'
./virp-verify axis-20260904-v6 | grep -E "$show"
./virp-verify \
--pin chain-313-c1104805-2026-08-28.hex \
--producer-key producer-axis-m3085v-fae0d249-2026-09-03.hex \
--witness-key witness-virp-systems-2a771e12-2026-09-03.hex \
axis-20260904-v6 | grep -E "$show"sha256sum -c prints OK seven times, minisign prints Signature and comment signature verified, and the two runs print four lines each. The first of those
lines reads virp-verify 0.1.2 (commit 1519d57, clean, release) — the same
commit this release is tagged at, which is the point of the release.
What you just saw
OVERALL VERDICT: CRYPTOGRAPHICALLY-CONSISTENT (signer trust not established)
OVERALL VERDICT: CRYPTOGRAPHICALLY-VERIFIED
The same bundle, twice. Nothing about the evidence changed between those two
lines; every hash, link and signature verified in both runs. What changed is
where the verifying key came from — inside the bundle in the first run,
from a commit you chose in the second. A bundle that vouches for itself proves
internal consistency and nothing about who produced it.
The witness line flips the same way: UNVERIFIABLE ("no --witness-key was
supplied") to VERIFIED (leaf 476 of tree 571). UNVERIFIABLE is not a
failure and not a pass. referenced_artifact_binding does not flip, because
it needs no key: 51 cited artifacts recomputed against the fields that cite
them.
Everything above ran offline. After the downloads, neither run touched the
network: the witness result comes from an inclusion proof and a signed tree
head carried inside the bundle and recomputed here, and --witness-url — which
re-checks the carried tree against the log serving it now — is the only flag
that would go out and ask anyone anything.
CRYPTOGRAPHICALLY-VERIFIED does not mean the entries are true, that the
video shows what anyone says it shows, or that any particular physical device
produced it — source_device_established grades NO in this very bundle.
README.md is the full version of all of this, and it is one of the assets.
Build it yourself
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
git clone https://github.com/nhowardtli/virp-verify && cd virp-verify
git checkout virp-verify-v0.1.2
rustup target add x86_64-unknown-linux-musl # or aarch64-unknown-linux-musl
tools/release/build-verifier.shBuilt from two separate clean clones at two unrelated paths, checked out at tag
virp-verify-v0.1.2 (commit 1519d57), byte-identical both times:
| target | sha256 |
|---|---|
x86_64-unknown-linux-musl |
bf5188681d107ecff09ac91c68449aa74f85b2e978f3766c93043d8508b26985 |
aarch64-unknown-linux-musl |
a45d67847b9344017339c42e06dd72c93a1f266ad7603017288b6285e598bfee |
The x86_64 line is the binary attached here and the virp-verify line of the
signed SHA256SUMS. Check yours against the manifest you downloaded, not
against this page: this page is controlled by whoever produced the file it
describes, and the manifest is signed.
Check out the tag, not main. The commit is compiled into the binary, so a
build from any other commit is a different binary with a different hash by
design — the binary cannot misreport where it came from.
The arm build, and why its hash is not the x86_64 hash
No aarch64 binary is attached here. It is a build path for people on arm,
and the hash above is published so an arm builder has something to compare
notes about, not something to check a download against. It is different machine
code, and it is also built by different machinery because it has to be — GNU
strip does not recognise the format and the host cc cannot link for another
architecture — so it links with the rust-lld that ships with the pinned
toolchain and has rustc strip at link time. Two targets never produce one
hash. It has been built but not executed: this release was prepared on x86_64
with no arm hardware and no emulation available.
Supersedes 0.1.1 and 0.1.0
Both are left in place for anyone holding a copy of their bytes, and both are
still exactly what their notes say they are. Use this one: it is the first
release whose binary and whose envelope name the same commit.
The seal key
SHA256SUMS.minisig is signed with AD48B20F5D11CED6, generated
2026-09-05:
minisign -Vm SHA256SUMS -P RWTWzhFdD7JIrUh5gRyPshZDiGwwwHhL8QUaiNX/RLjcaNxa0JCoR611
The previous seal key, 4F0B72D4DA341448 (2026-08-23), is retained, not
revoked: it still verifies what it signed, notably seal-2026-08.json, and
it verifies nothing in this release. Trying the old key against this signature
fails with a key-id mismatch, which is the correct outcome.
Both public halves are committed under
keys/ in commit
411d008a04eb9a52b3ade9a4afe58ae4b358099b, whose message says which key is
which, when each was generated and which host holds each private half, and
which carries an OpenTimestamps proof of its own date. A release asset can be
replaced silently and a dated commit cannot, which is why the block above
fetches every key from that commit rather than from this page. The four bundle
keys are attached here too, under the bare filenames SHA256SUMS names, so
downloading every asset into one directory and running the check passes with
nothing to arrange; the manifest covers them, so writing the commit's copies
over the downloaded ones and re-running sha256sum -c is what proves the two
are byte-identical. The commit is the canonical source; the assets are a
convenience the hashes keep honest.
virp-verify 0.1.1 — same binary, a README you can paste
Superseded by virp-verify 0.1.2.
The binary here was built at tagvirp-verify-v0.1.0and reports
virp-verify 0.1.0 (commit 01ce2e2, clean, release), while this release is
tagged at236e683. Everything below says so accurately, but a reader
checking the first line of a report against the release it came from finds
two commits and no way to tell which is the mistake. 0.1.2 ships a binary
built at its own tag; that is now a rule with no exceptions.This release is left in place unchanged, for anyone holding a copy of its
bytes.
Same binary as 0.1.0, byte for byte. What changed is everything around it: a
README a stranger can paste, the keys attached as release assets, and a second
build target for arm.
Requirements: Linux on x86_64 — native, WSL2, or an x86_64 container
(docker run --rm -it debian:stable-slim) — with curl, jq, minisign,
tar and sha256sum installed. The binary below is x86_64 Linux only; on
Apple Silicon or arm Linux it will not run, and Build it yourself is your
path.
Everything, in one block
keys=https://raw.githubusercontent.com/nhowardtli/virp-verify/411d008a04eb9a52b3ade9a4afe58ae4b358099b/keys
mkdir -p virp-verify-0.1.1 && cd virp-verify-0.1.1
# Every release asset except the keys — those come from the commit above, which
# is dated and cannot be replaced, unlike a file attached to a release page.
curl -fsSL https://api.github.com/repos/nhowardtli/virp-verify/releases/tags/virp-verify-v0.1.1 \
| jq -r '.assets[].browser_download_url' | grep -vE '\.(hex|keys\.json)$' \
| xargs -n1 curl -fsSLO
for k in $(grep -oE '[^ ]+\.(hex|keys\.json)$' SHA256SUMS) seal-virp-ad48b20f-2026-09-05.pub; do
curl -fsSL "$keys/$k" -o "$k"
done
sha256sum -c SHA256SUMS
minisign -Vm SHA256SUMS -p seal-virp-ad48b20f-2026-09-05.pub
tar -xzf axis-20260904-v6.tar.gz
chmod +x virp-verify
show='^virp-verify |^ (witness {16}|referenced_artifact_binding )[A-Z]|^OVERALL VERDICT'
./virp-verify axis-20260904-v6 | grep -E "$show"
./virp-verify \
--pin chain-313-c1104805-2026-08-28.hex \
--producer-key producer-axis-m3085v-fae0d249-2026-09-03.hex \
--witness-key witness-virp-systems-2a771e12-2026-09-03.hex \
axis-20260904-v6 | grep -E "$show"sha256sum -c prints OK seven times, minisign prints Signature and comment signature verified, and the two runs print four lines each.
What you just saw
OVERALL VERDICT: CRYPTOGRAPHICALLY-CONSISTENT (signer trust not established)
OVERALL VERDICT: CRYPTOGRAPHICALLY-VERIFIED
The same bundle, twice. Nothing about the evidence changed between those two
lines; every hash, link and signature verified in both runs. What changed is
where the verifying key came from — inside the bundle in the first run,
from a commit you chose in the second. A bundle that vouches for itself proves
internal consistency and nothing about who produced it.
The witness line flips the same way: UNVERIFIABLE ("no --witness-key was
supplied") to VERIFIED (leaf 476 of tree 571). UNVERIFIABLE is not a
failure and not a pass. referenced_artifact_binding does not flip, because
it needs no key: 51 cited artifacts recomputed against the fields that cite
them.
Everything above ran offline. After the downloads, neither run touched the
network: the witness result comes from an inclusion proof and a signed tree
head carried inside the bundle and recomputed here, and --witness-url — which
re-checks the carried tree against the log serving it now — is the only flag
that would go out and ask anyone anything.
CRYPTOGRAPHICALLY-VERIFIED does not mean the entries are true, that the
video shows what anyone says it shows, or that any particular physical device
produced it — source_device_established grades NO in this very bundle.
README.md is the full version of all of this, and it is one of the assets.
Fixed since 0.1.0
The binary is x86_64 Linux only, and nothing said so. It failed on arm and
on Apple Silicon with no explanation. The requirement is now the second thing
on the page, and tools/release/build-verifier.sh takes a second target,
aarch64-unknown-linux-musl, so a reader on arm can build the verifier rather
than go find another machine.
SHA256SUMS covered the keys/ files, which were not release assets. The
first command in the 0.1.0 README printed four FAILED lines to anyone who
followed it. The four bundle keys are now attached here as well, and
SHA256SUMS names every file by bare filename, so downloading every asset into
one directory and running the check passes with nothing to arrange. The block
above still takes the keys from commit
411d008
rather than from this page: a release asset can be replaced silently and a
dated commit cannot, and that commit carries an OpenTimestamps proof of its
own date. The key assets are byte-identical to that commit's copies, and
SHA256SUMS covers them, so the block writing the commit's copies over the
downloaded ones and re-running sha256sum -c is what proves it. The commit is
the canonical source; these assets are a convenience the hashes keep honest.
Build it yourself
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
git clone https://github.com/nhowardtli/virp-verify && cd virp-verify
rustup target add x86_64-unknown-linux-musl # or aarch64-unknown-linux-musl
tools/release/build-verifier.shThe published hash belongs to exactly one target and one commit:
3de194fd31a7544e7260f04415e8865957403a78690609b0378f277f5277b304
x86_64-unknown-linux-musl, tag virp-verify-v0.1.0 (commit 01ce2e2)
To reproduce it, git checkout virp-verify-v0.1.0 — that is where this
release's binary comes from, unchanged, and it still reports virp-verify 0.1.0 (commit 01ce2e2, clean, release). The commit is compiled in, so a build from
the 0.1.1 tag is a different binary with a different hash by design.
The arm build, and why its hash is not the x86_64 hash
Building both targets at tag virp-verify-v0.1.1 (commit 236e683) gives:
| target | sha256 |
|---|---|
x86_64-unknown-linux-musl |
0cdef1126e41be999c500b072fe21361bbb62a7fe9a3c92812b8a08ac68cb048 |
aarch64-unknown-linux-musl |
37fd8ed38069d575ebc04fef9984be0ce8128e24b3b6323dbfb690bad99342c0 |
Neither is 3de194fd…, and they are not each other. The x86_64 figure differs
from the released binary only because the commit string compiled into it says
236e683 instead of 01ce2e2 — the code is identical. The aarch64 figure
differs for a larger reason: it is different machine code. It is also built
by different machinery, because it has to be — GNU strip does not recognise
the format and the host cc cannot link for another architecture — so it links
with the rust-lld that ships with the pinned toolchain and has rustc strip
at link time. Two targets never produce one hash, and there is no published
aarch64 hash to check a download against: no aarch64 binary is attached
here. It is a build path for people on arm, and it has been built but not
executed — this release was prepared on x86_64 with no arm hardware and no
emulation available.
The seal key rotated
SHA256SUMS.minisig is signed with AD48B20F5D11CED6, generated
2026-09-05:
minisign -Vm SHA256SUMS -P RWTWzhFdD7JIrUh5gRyPshZDiGwwwHhL8QUaiNX/RLjcaNxa0JCoR611
The previous seal key, 4F0B72D4DA341448 (2026-08-23), is retained, not
revoked: it still verifies what it signed, notably seal-2026-08.json, and
it verifies nothing in this release. Trying the old key against this signature
fails with a key-id mismatch, which is the correct outcome.
Both public halves are committed under
keys/ in commit
411d008a04eb9a52b3ade9a4afe58ae4b358099b, whose message says which key is
which, when each was generated and which host holds each private half. Prefer
that copy over the key string on this page — this page is controlled by whoever
produced the file it describes, which is the whole reason the block above
fetches from the commit.
virp-verify 0.1.0 — reproducible, signed, source public
Superseded by virp-verify 0.1.2.
Use that one: it ships a binary built at its own tag, which this release
does not — the binary here is 0.1.0 built at01ce2e2, and 0.1.1 shipped
it again under a different tag. 0.1.2 fixes that and writes the rule down.This release is left in place unchanged, for anyone holding a copy of its
bytes.
A standalone verifier for VIRP evidence bundles, and one real bundle to run it
on. One static x86_64 Linux file: no runtime, no libc, no installer.
README.md below is the full instructions. This is the short version.
What it verifies
It reads a Docket evidence bundle, recomputes the hashes, the chain links and
the Ed25519 signatures, checks the RFC 9162 witness inclusion and consistency
proofs, and prints which properties hold and which it could not establish.
It never signs, never holds a private key, and never executes anything from
the bundle. It is a pure reader — that is what makes it publishable.
Exit codes are deliberately not collapsed into pass/fail; --help lists them.
With no keys the sample grades exit 5, CRYPTOGRAPHICALLY-CONSISTENT —
signer trust not established, because the only key available came from inside
the bundle. With the three out-of-band keys it grades exit 0,
CRYPTOGRAPHICALLY-VERIFIED. Drop --witness-key and the witness property
grades UNVERIFIABLE, which is neither a pass nor a failure.
What it does not verify
CRYPTOGRAPHICALLY-VERIFIED does not mean the entries are true, that the
video shows what anyone says it shows, or that any particular physical device
produced it. No frame is decoded and no scene is judged. The verifier prints
those limits at the bottom of every report.
The keys in this release travel beside the evidence, and they were handed to
you by the same party that handed you the evidence. That proves internal
consistency and nothing about who produced it. The README says so at length.
Build it yourself and compare
git clone https://github.com/nhowardtli/virp-verify
cd virp-verify
git checkout virp-verify-v0.1.0
rustup target add x86_64-unknown-linux-musl
tools/release/build-verifier.sh
sha256 3de194fd31a7544e7260f04415e8865957403a78690609b0378f277f5277b304
That is the hash of virp-verify in this release and the hash in
SHA256SUMS. The toolchain is pinned in rust-toolchain.toml (Rust 1.98.0).
The build is path-independent: CARGO_INCREMENTAL=0 plus
--remap-path-prefix, so the same source on the same toolchain gives the same
bytes wherever the tree sits on disk. Confirmed from three separate clean
clones, one of them a fresh clone of this repository at this tag.
Check out the tag. virp-verify --version reports the commit it was built
from and that string is compiled in, so a build from main is a different
binary with a different hash by design.
The seal key has rotated
SHA256SUMS.minisig is signed with AD48B20F5D11CED6, generated
2026-09-05:
minisign -Vm SHA256SUMS -P RWTWzhFdD7JIrUh5gRyPshZDiGwwwHhL8QUaiNX/RLjcaNxa0JCoR611
This is a rotation. The previous seal key, 4F0B72D4DA341448 (generated
2026-08-23), is retained, not revoked: it still verifies what it signed,
notably seal-2026-08.json, and it verifies nothing in this release. Trying
the old key against this signature fails with a key-id mismatch, and that is
the correct outcome.
Both public halves are committed at
keys/, in commit
411d008a04eb9a52b3ade9a4afe58ae4b358099b, whose message says which key is
which, when each was generated and which host holds each private half. A
release asset can be replaced silently; a commit cannot. Prefer the committed
copy over the key string on this page — this page is controlled by whoever
produced the file it describes.
The keys are not release assets
SHA256SUMS covers seven files, but only five are attached here. The four
bundle keys come from that same keys/ commit. Fetch them into a keys/
subdirectory beside the downloads and sha256sum -c SHA256SUMS then passes
7/7 — which also proves the committed keys are byte-identical to the ones the
signed manifest covers:
mkdir -p keys && cd keys
for k in chain-313-c1104805-2026-08-28.hex \
chain-313-c1104805-2026-08-28.keys.json \
producer-axis-m3085v-fae0d249-2026-09-03.hex \
witness-virp-systems-2a771e12-2026-09-03.hex; do
curl -fsSLO "https://raw.githubusercontent.com/nhowardtli/virp-verify/411d008a04eb9a52b3ade9a4afe58ae4b358099b/keys/$k"
done
Supersedes the 2026-08-29 verifier release
This release supersedes
v0.1.0-verifier
on nhowardtli/virp (published 2026-08-30). That one shipped a binary whose
own notes state it "is not reproducibly built and not signed" — accurate then
and still accurate: it carries no signature under any key, including the
retained 2026-08 seal key. It also predates the public source split, so its
binary could not be rebuilt from anything a reader could clone.
This release fixes all three: the source is public, the binary reproduces from
a tag you can check out, and SHA256SUMS is signed. If you landed on the old
release, use this one.
nhowardtli/virp-verify is the verifier's home going forward.