verifyhash 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -3
- package/cli/agent-hook.js +431 -0
- package/docs/ADOPT.md +15 -5
- package/docs/AGENT-HOOK.md +111 -0
- package/docs/ANCHORING.md +43 -22
- package/docs/PUBLISH-VERIFY-VH.md +45 -0
- package/examples/README.md +185 -0
- package/examples/policy.lenient.json +5 -0
- package/examples/policy.strict.json +6 -0
- package/examples/run.js +366 -0
- package/examples/sample-dataset/README.txt +10 -0
- package/examples/sample-dataset/corpus/cc-by-poem.txt +8 -0
- package/examples/sample-dataset/corpus/mit-notes.txt +4 -0
- package/examples/sample-dataset/data/unlabeled.txt +5 -0
- package/examples/sample-dataset/vendored/gpl-snippet.txt +5 -0
- package/examples/sample-dataset.hints.json +7 -0
- package/examples/sample-parcel/data/manifest-of-contents.txt +7 -0
- package/examples/sample-parcel/data/records.csv +4 -0
- package/examples/sample-parcel/delivery-note.txt +9 -0
- package/package.json +26 -3
- package/verifier/README.md +584 -0
- package/verifier/action/README.md +87 -0
- package/verifier/action/action.yml +146 -0
- package/verifier/build-standalone-html.js +1287 -0
- package/verifier/build-standalone.js +989 -0
- package/verifier/ci/journal.generic.sh +96 -0
- package/verifier/ci/journal.github-actions.yml +99 -0
- package/verifier/ci/reproduce-vh.generic.sh +59 -0
- package/verifier/ci/reproduce-vh.github-actions.yml +49 -0
- package/verifier/ci/verify-service.generic.sh +96 -0
- package/verifier/ci/verify-service.github-actions.yml +88 -0
- package/verifier/ci/verify-vh.generic.sh +75 -0
- package/verifier/ci/verify-vh.github-actions.yml +56 -0
- package/verifier/dist/BUILD-PROVENANCE.json +210 -0
- package/verifier/dist/seal-vh-standalone.js +876 -0
- package/verifier/dist/seal-vh-standalone.js.sha256 +1 -0
- package/verifier/dist/verify-vh-standalone.html +3373 -0
- package/verifier/dist/verify-vh-standalone.html.sha256 +1 -0
- package/verifier/dist/verify-vh-standalone.js +5123 -0
- package/verifier/dist/verify-vh-standalone.js.sha256 +1 -0
- package/verifier/lib/canonical.js +141 -0
- package/verifier/lib/keccak.js +30 -0
- package/verifier/lib/keccak256-vendored.js +206 -0
- package/verifier/lib/merkle.js +145 -0
- package/verifier/lib/revocation-core.js +606 -0
- package/verifier/lib/revocation.js +200 -0
- package/verifier/lib/seal-cli.js +374 -0
- package/verifier/lib/seal-evidence.js +237 -0
- package/verifier/lib/secp256k1-recover.js +249 -0
- package/verifier/package.json +39 -0
- package/verifier/verify-vh.js +3376 -0
- package/docs/ADOPTION.json +0 -11
- package/docs/AUDIT.md +0 -55
- package/docs/DECIDE.md +0 -47
- package/docs/DECISIONS-PENDING.md +0 -27
- package/docs/DEPLOY-PUBLIC-SITE.md +0 -301
- package/docs/ENGINE-LEDGER.json +0 -12
- package/docs/LOOP-AUDIT-2026-07-03.json +0 -580
- package/docs/LOOP-HARDENING-PLAN.md +0 -44
- package/docs/METRICS.jsonl +0 -31
- package/docs/MORNING.md +0 -204
- package/docs/STRATEGY-ARCHIVE.md +0 -5055
- package/docs/SUPERVISOR-RUNBOOK.md +0 -52
- package/docs/USAGE-BUDGET.json +0 -121
|
@@ -0,0 +1,584 @@
|
|
|
1
|
+
# `verify-vh` — the independent, offline verifier
|
|
2
|
+
|
|
3
|
+
**You received a sealed verifyhash artifact and you are NOT a verifyhash customer.** This directory
|
|
4
|
+
is everything you need to check it yourself: a single command, near-zero dependencies, **no network,
|
|
5
|
+
and no back-edge into the producer's stack**. You do not need our `ethers`/`hardhat` toolchain, an
|
|
6
|
+
account, a license, or our permission. Read this file, `npm install`, run one command, and decide.
|
|
7
|
+
|
|
8
|
+
`verify-vh` is **free**. There is no paid tier for verification — the producer pays to *seal*; anyone
|
|
9
|
+
may *verify* forever, offline, at zero cost. That is deliberate: a proof a counterparty cannot
|
|
10
|
+
independently check is not a proof.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## 0. Get it in 10 seconds (zero-install — start here)
|
|
15
|
+
|
|
16
|
+
The fastest way to check a seal needs **no clone, no `npm install`, no `node_modules`, no account**:
|
|
17
|
+
save ONE self-contained file — [`dist/verify-vh-standalone.js`](dist/verify-vh-standalone.js) — and run
|
|
18
|
+
it with `node`. It depends on **nothing but Node core** (the keccak provider is a vendored pure-JS one,
|
|
19
|
+
cross-checked against `js-sha3` and `ethers`):
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# 1. Save the single file dist/verify-vh-standalone.js next to the packet you were handed.
|
|
23
|
+
|
|
24
|
+
# 2. (Optional, recommended) check its PUBLISHED checksum so you know the file wasn't swapped in transit.
|
|
25
|
+
# We ship it beside the bundle as dist/verify-vh-standalone.js.sha256 (standard `sha256sum` format):
|
|
26
|
+
sha256sum -c verify-vh-standalone.js.sha256 # -> "verify-vh-standalone.js: OK"
|
|
27
|
+
# (macOS: shasum -a 256 -c verify-vh-standalone.js.sha256)
|
|
28
|
+
|
|
29
|
+
# 3. Run it — no install:
|
|
30
|
+
node verify-vh-standalone.js <packet> --vendor 0xPRODUCER_ADDRESS
|
|
31
|
+
# exit 0 = verifies; exit 3 = REJECTED (names the changed file / wrong signer).
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
That one file is **byte-for-byte the same verifier** described in the rest of this README — it is built
|
|
35
|
+
deterministically from these sources, and a stale bundle FAILS CI
|
|
36
|
+
(`../test/verifier.standalone.test.js`). The split-source path below (`npm install` the `verifier/` tree
|
|
37
|
+
and run `verify-vh.js`) stays for auditors who want to read each `lib/*` file on its own; **both compute
|
|
38
|
+
the identical verdict and exit code.** The checksum is a transport-integrity check pinned to a hex you
|
|
39
|
+
get out-of-band from the producer — like `--vendor`; the real trust anchor is the source audit in §6.
|
|
40
|
+
**Don't want to trust our checksum either? Reproduce the bundle from source yourself — see §0b.**
|
|
41
|
+
|
|
42
|
+
**The easier path changes nothing about what is proven:** whether you run the one-file bundle or the
|
|
43
|
+
split tree, the seal proves **tamper-evidence + signer-pin**, NOT a trusted "sealed at T" (that still
|
|
44
|
+
requires **P-3** — see §4). The convenience is in the *install*, never in the *claim*.
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## 0y. No Node at all? Verify (and try to fool it) in your browser — one offline page
|
|
49
|
+
|
|
50
|
+
Everything in §0 still assumes `node` on a PATH. If you — or the counterparty you are convincing —
|
|
51
|
+
have **no terminal at all**, the same verifier ships as **one committed, fully offline HTML file**:
|
|
52
|
+
[`dist/verify-vh-standalone.html`](dist/verify-vh-standalone.html) (integrity sidecar:
|
|
53
|
+
[`dist/verify-vh-standalone.html.sha256`](dist/verify-vh-standalone.html.sha256)). Save it and
|
|
54
|
+
double-click it; the page opens with the **60-second challenge built in**: click **"Load the sample
|
|
55
|
+
packet & verify"** (ACCEPT), then change ONE character of the editable sample file and re-verify
|
|
56
|
+
(**REJECT** — the page names the file you changed) — then drag a REAL packet + its files in and read
|
|
57
|
+
the same verdict + per-file localization this README describes (optional vendor pin and revocations
|
|
58
|
+
drop included). The page also carries a built-in **agent-session demo** (§2c): a sample
|
|
59
|
+
`*.vhagent.json` packet with one tool_call payload already REDACTED behind its hash commitment —
|
|
60
|
+
load it (ACCEPT — redaction is not tamper), tamper one payload byte in the page, and watch the
|
|
61
|
+
REJECT name the offending event `seq`. The page contains **NO network API at all** (no `fetch`, no `XMLHttpRequest`, no
|
|
62
|
+
WebSocket), so your packet bytes never leave your machine — check the browser **devtools Network tab**:
|
|
63
|
+
it stays empty. Like the node bundle, it is built deterministically from these same sources
|
|
64
|
+
(`node build-standalone-html.js --check` reproduces it byte-for-byte, pinned in
|
|
65
|
+
[`dist/BUILD-PROVENANCE.json`](dist/BUILD-PROVENANCE.json)).
|
|
66
|
+
|
|
67
|
+
The boundary on the page is the same one this README carries, verbatim: **ACCEPT is tamper-evidence
|
|
68
|
+
that these exact bytes match the seal — and, for a signed seal, WHO vouched (signer recovery + optional
|
|
69
|
+
vendor pin). It is NOT a trusted timestamp and NOT proof of WHEN without the P-3 trust-root. For
|
|
70
|
+
CI/production gating use the node standalone (`verify-vh-standalone.js`).** The browser page is the
|
|
71
|
+
first-contact convenience; your pipeline gates on the node standalone (§2b).
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## 0z. The 5-second proof — one command, no flags, no key (`demo`)
|
|
76
|
+
|
|
77
|
+
**Never run this tool before? Start here.** Before you have a packet, an address, or any idea what a "seal"
|
|
78
|
+
is, run the **zero-config demo** — it takes a brand-new user from *nothing* to a *verified packet* in one
|
|
79
|
+
command, with **no flags, no `--vendor` to paste, and no key knowledge**:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
node verify-vh-standalone.js demo # (or, from the split tree: node verify-vh.js demo)
|
|
83
|
+
# or, with nothing checked out at all: npx --yes <package> demo
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
It ships a tiny, **genuinely-signed** evidence packet baked into the file, plays it through the **exact same
|
|
87
|
+
verify path** every real check uses, and prints the honest verdict:
|
|
88
|
+
|
|
89
|
+
```
|
|
90
|
+
STEP 1 — verify the genuine packet (signer recovered from the bytes, then pinned):
|
|
91
|
+
ACCEPT — the artifact verifies. signer: 0x70997970c51812dc3a010c7d01b50e0d17dc79c8
|
|
92
|
+
...
|
|
93
|
+
STEP 2 — tamper ONE byte of a referenced file, then re-verify the SAME packet:
|
|
94
|
+
REJECT (CHANGED) — the tampered copy is caught:
|
|
95
|
+
CHANGED model-card.md: sealed 0x1aeca0… != on-disk 0xb71fba…
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
A genuine packet is **ACCEPTED and its signer named**; a one-byte change is **REJECTED**. The demo's signature
|
|
99
|
+
is a real EIP-191 signature by a **fixed, well-known TEST-ONLY key** (hardhat account #1 — never a real key,
|
|
100
|
+
never real funds); the address above is genuinely *recovered* from the bytes by the same pure-JS secp256k1
|
|
101
|
+
routine a real verify uses, not echoed. The demo writes only a throwaway temp dir it deletes, opens **no
|
|
102
|
+
network**, and exits `0`. It proves exactly what §4 says — **tamper-evidence + signer-pin**, NOT a trusted
|
|
103
|
+
"sealed at T" — and nothing more.
|
|
104
|
+
|
|
105
|
+
**Want to poke at it with your own hands?** The bare `demo` runs in a throwaway dir and is gone when it exits —
|
|
106
|
+
you can *watch* it but not *touch* it. Add a directory name and it **writes the same genuinely-signed packet
|
|
107
|
+
into a folder you keep**, then prints the exact copy-paste commands to verify, tamper, and restore it yourself:
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
node verify-vh-standalone.js demo ./vh-demo # writes ./vh-demo/{demo-packet.vhevidence.json, model-card.md, weights.txt}
|
|
111
|
+
# It then prints, ready to paste:
|
|
112
|
+
node verify-vh-standalone.js ./vh-demo/demo-packet.vhevidence.json --vendor 0x7099...79C8 # exit 0 = ACCEPT
|
|
113
|
+
printf 'X' >> ./vh-demo/model-card.md # tamper one byte
|
|
114
|
+
node verify-vh-standalone.js ./vh-demo/demo-packet.vhevidence.json --vendor 0x7099...79C8 # exit 3 = REJECT (CHANGED)
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
That is the working on-ramp from *watched a demo* to *verified my own bytes on disk* — the packet it writes is
|
|
118
|
+
the same real artifact a producer would hand you (`mechanically tested in ../test/verifier.demo.test.js`), not a
|
|
119
|
+
toy. Once it clicks, point the tool at a **real** packet you were handed
|
|
120
|
+
(`node verify-vh.js <packet> --vendor 0xPRODUCER_ADDRESS`); and when you want a counterparty to be able to pin
|
|
121
|
+
**you**, that is the paid producer side — **sign your own files** with `vh evidence seal --sign` (see §0a).
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## 0b. "Who verifies the verifier?" — reproduce the bundle from source yourself (zero-trust bootstrap)
|
|
126
|
+
|
|
127
|
+
The published checksum in §0 proves the file survived transport — but it comes **from the same place as
|
|
128
|
+
the bundle**, so on its own it cannot prove the bundle is the source you can read here (if our
|
|
129
|
+
distribution were compromised, both would swap together). The answer to *"who verifies the verifier?"* is
|
|
130
|
+
to **reproduce the bundle from the in-tree source** and confirm the published checksum is exactly what
|
|
131
|
+
that source compiles to. It is offline, Node-core-only (no `npm install`, no `hardhat`), and writes
|
|
132
|
+
nothing:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
# From the verifier/ tree you can READ end to end (the builder + every lib/*.js it inlines):
|
|
136
|
+
node build-standalone.js --check
|
|
137
|
+
# -> per-target MATCH/MISMATCH for each bundle, its .sha256 sidecar, AND every inlined source file.
|
|
138
|
+
# exit 0 = every committed bundle, sidecar, and the build-provenance manifest reproduce byte-for-byte
|
|
139
|
+
# from source, and every source file hashes to its manifest-pinned sha256.
|
|
140
|
+
# exit 1 = something does not reproduce — the line NAMES the offending file (bundle, sidecar, or a
|
|
141
|
+
# specific lib/*.js source).
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
The build is **deterministic** (no timestamp, no randomness, a hand-fixed module list), so the bundle
|
|
145
|
+
bytes are a pure function of the committed sources. `--check` recompiles both bundles in memory, recomputes
|
|
146
|
+
their checksums, and compares against the committed files — and cross-checks each inlined source against the
|
|
147
|
+
committed **build-provenance manifest**, [`dist/BUILD-PROVENANCE.json`](dist/BUILD-PROVENANCE.json). That
|
|
148
|
+
manifest maps each published bundle's sha256 to the **ordered, individually-hashed** `lib/*.js` files it
|
|
149
|
+
inlines — so you can `sha256` the exact files you audited and find their hashes there, then see they compose
|
|
150
|
+
(in that order) the bundle whose hash is in the `.sha256` sidecar. Trust roots in **reading source**, not in
|
|
151
|
+
trusting our hex.
|
|
152
|
+
|
|
153
|
+
This proves **build integrity** — the bundle faithfully reproduces the audited source. It is NOT a claim
|
|
154
|
+
that the source's *logic* is correct (read it, and run the conformance corpus, for that), and NOT a trusted
|
|
155
|
+
timestamp/identity (that is **P-3**). `--check` opens **no network** and writes nothing under the tree
|
|
156
|
+
(proven by `../test/verifier.reproduce.test.js`).
|
|
157
|
+
|
|
158
|
+
Reproducing the bundle changes **nothing** about the trust boundary in §4: whether you run the one-file
|
|
159
|
+
bundle or the split tree, the seal proves **tamper-evidence + signer-pin**, NOT a trusted "sealed at T"
|
|
160
|
+
(that still requires **P-3** — see §4). The reproduce step moves trust from *our hex* to *the source you
|
|
161
|
+
read*; it does not widen the *claim*.
|
|
162
|
+
|
|
163
|
+
**Make it a RENEWING control, not a one-time read — wire `--check` into your own CI.** Auditing the
|
|
164
|
+
verifier once is good; re-confirming it on *every* build is better, because a supply-chain swap of the
|
|
165
|
+
verifier itself (a stale bundle, a one-byte source edit) then **fails your pipeline** instead of slipping
|
|
166
|
+
past. Two shipped, copy-paste snippets do exactly that — they run `--check` and pass its exit code
|
|
167
|
+
straight through, so any drift blocks the merge:
|
|
168
|
+
|
|
169
|
+
- **[`ci/reproduce-vh.generic.sh`](ci/reproduce-vh.generic.sh)** — a portable `set -e` shell gate for
|
|
170
|
+
GitLab CI, CircleCI, Jenkins, a Makefile recipe, or a git hook. No config, no install: `./verifier/ci/reproduce-vh.generic.sh`.
|
|
171
|
+
- **[`ci/reproduce-vh.github-actions.yml`](ci/reproduce-vh.github-actions.yml)** — a GitHub Actions
|
|
172
|
+
workflow you drop at `.github/workflows/reproduce-vh.yml`; a green check then *means* "the verifier we
|
|
173
|
+
depend on is still the exact source we audited."
|
|
174
|
+
|
|
175
|
+
These are the verifier-integrity twins of the seal-gate snippets in §2b (that gate your *seals*; these
|
|
176
|
+
gate the *verifier*). They are **examples the loop never runs**, but their exact gate command is
|
|
177
|
+
mechanically tested (`../test/verifier.reproduce-ci-snippet.test.js`): it must exit `0` on a clean
|
|
178
|
+
checkout and **non-zero, naming the offending source file,** when one byte of an inlined `lib/*.js`
|
|
179
|
+
changes — so the snippet you copy is known-good, not aspirational. Wiring the gate widens **nothing**
|
|
180
|
+
about the trust boundary in §4; it just makes the §0b reproduce answer *renew* on every build.
|
|
181
|
+
|
|
182
|
+
---
|
|
183
|
+
|
|
184
|
+
## 0a. Produce your OWN seal in 10 seconds, then hand it off (the free self-service round-trip)
|
|
185
|
+
|
|
186
|
+
§0 is the FREE **verify** side. There is a matching FREE **produce** side, so you can run the *whole*
|
|
187
|
+
loop yourself — seal your own files, hand the result to a counterparty, watch them verify it — with **no
|
|
188
|
+
clone, no `npm install`, no account, no key**, on either side. Save ONE file —
|
|
189
|
+
[`dist/seal-vh-standalone.js`](dist/seal-vh-standalone.js) — and run it with `node`. Like the verifier,
|
|
190
|
+
it depends on **nothing but Node core** (the keccak provider is the same vendored pure-JS one):
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
# 1. Save the single file dist/seal-vh-standalone.js (optionally check dist/seal-vh-standalone.js.sha256
|
|
194
|
+
# the same way as the verifier in §0).
|
|
195
|
+
|
|
196
|
+
# 2. Seal up to 25 of YOUR OWN files into one tamper-evident packet — no install, no key, no account:
|
|
197
|
+
node seal-vh-standalone.js <your-folder> -o packet.vhevidence.json # exit 0 = sealed
|
|
198
|
+
|
|
199
|
+
# 3. Hand packet.vhevidence.json + your folder to a counterparty. They run the FREE verifier from §0:
|
|
200
|
+
node verify-vh-standalone.js packet.vhevidence.json --dir <your-folder> # exit 0 = verifies; 3 = REJECTED
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
That is the entire organic adoption loop, self-service and free on both ends, before any sales call: one
|
|
204
|
+
file to **seal**, one file to **verify**, and the `.vhevidence.json` is the only thing that has to change
|
|
205
|
+
hands. The standalone sealer is built deterministically from these sources and a stale bundle FAILS CI
|
|
206
|
+
(`../test/freeseal.standalone.test.js`); its seal bytes are byte-for-byte identical to the producer's own
|
|
207
|
+
`cli/evidence.js` seal over the same folder, so a free seal is the *same* artifact the paid tool wraps —
|
|
208
|
+
never a toy.
|
|
209
|
+
|
|
210
|
+
**The honest scope boundary is exactly the same as §0 — and the free seal is *narrower* still.** A
|
|
211
|
+
standalone seal proves **tamper-evidence + offline-recompute** — the referenced files are byte-for-byte
|
|
212
|
+
the ones sealed, independently re-derivable by anyone — and **NOT** a trusted "sealed at T" without
|
|
213
|
+
**P-3** (see §4). On top of that, the FREE seal is **UNSIGNED** (no signer to pin — there is no
|
|
214
|
+
`--sign`/`--license`/`--key` flag here at all) and **capped at 25 files** (a folder of more than 25
|
|
215
|
+
hard-errors and writes nothing). **SIGNING** (an EIP-191 signer-pin so a counterparty can pin you with
|
|
216
|
+
`--vendor`) and **UNLIMITED** sealing are the PAID upgrade — `vh evidence seal --sign` / the
|
|
217
|
+
`evidence_unlimited` entitlement (`--license`), routed through the full producer CLI. The free loop is
|
|
218
|
+
the funnel; the paid upgrade adds *who signed it* and *no file cap*.
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
## 1. What you have, in one minute
|
|
223
|
+
|
|
224
|
+
A counterparty (the "producer") ran a paid verifyhash tool over some files and handed you:
|
|
225
|
+
|
|
226
|
+
1. **The artifact** — a small JSON file (`*.vhevidence.json`, `*.vhseal`, `*.vhdataset.json`, or a
|
|
227
|
+
proof bundle). It lists, for each file, a `relPath` and a keccak-256 `contentHash`, folds those
|
|
228
|
+
into one keccak Merkle **root**, and (if signed) carries a 65-byte secp256k1 signature over the
|
|
229
|
+
canonical bytes of that root.
|
|
230
|
+
2. **The referenced files** themselves (e.g. `model-card.md`, `weights.bin`). By default they sit
|
|
231
|
+
next to the artifact; otherwise point `--dir` at them.
|
|
232
|
+
3. **The producer's signer address** (`0x…`, 20 bytes) — out-of-band: a contract, an email
|
|
233
|
+
signature, a website. You pin it with `--vendor` so a *different* key cannot impersonate them.
|
|
234
|
+
|
|
235
|
+
`verify-vh` recomputes the root from **the bytes you actually hold**, recovers **who signed it**, and
|
|
236
|
+
tells you in one line whether both match.
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
## 2. Install & run
|
|
241
|
+
|
|
242
|
+
```bash
|
|
243
|
+
cd verifier
|
|
244
|
+
npm install # pulls ONE runtime dependency: js-sha3 (keccak). Nothing else.
|
|
245
|
+
node verify-vh.js <artifact> [--vendor 0xADDR] [--dir <files-dir>] [--json]
|
|
246
|
+
# or, after `npm link` / global install:
|
|
247
|
+
verify-vh <artifact> --vendor 0xADDR
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
Requires Node ≥ 18. No build step, no native modules, no compiler.
|
|
251
|
+
|
|
252
|
+
**Exit codes** (so you can gate CI on them):
|
|
253
|
+
|
|
254
|
+
| code | meaning |
|
|
255
|
+
|------|---------|
|
|
256
|
+
| `0` | **OK** — every referenced byte matches the seal, signature valid, signer == `--vendor` |
|
|
257
|
+
| `3` | **REJECTED** — a clean, expected NO verdict (file changed/missing, bad signature, wrong issuer) |
|
|
258
|
+
| `2` | usage error (bad flags) |
|
|
259
|
+
| `1` | I/O error (artifact unreadable) |
|
|
260
|
+
|
|
261
|
+
---
|
|
262
|
+
|
|
263
|
+
## 2a. Gate a whole release in one command — batch / manifest mode
|
|
264
|
+
|
|
265
|
+
A release produces *many* artifacts (an evidence packet per dataset, a reconciliation seal per report, a
|
|
266
|
+
proof bundle per claim). You should not have to call the verifier once per file and `&&` the exit codes
|
|
267
|
+
by hand. Pass several artifacts — or a **manifest** listing them — and get **ONE** verdict and **ONE** CI
|
|
268
|
+
exit code:
|
|
269
|
+
|
|
270
|
+
```bash
|
|
271
|
+
# Repeated artifacts (each inherits the one --vendor/--dir you pass):
|
|
272
|
+
verify-vh a.vhevidence.json b.vhseal c.vhevidence.json --vendor 0xADDR --dir ./out
|
|
273
|
+
|
|
274
|
+
# A manifest file (newline list OR JSON array), each entry with its OWN optional --vendor/--dir:
|
|
275
|
+
verify-vh --manifest release.manifest --json
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
**The aggregate exit contract** — the same four codes, now over the *whole set*:
|
|
279
|
+
|
|
280
|
+
| code | meaning |
|
|
281
|
+
|------|---------|
|
|
282
|
+
| `0` | **OK** — and only if — **every** artifact in the batch verifies |
|
|
283
|
+
| `3` | **REJECTED** — **any** artifact is rejected; the report names **which** artifact failed and why |
|
|
284
|
+
| `2` | usage error (bad flag, malformed per-entry `--vendor`, empty manifest, `--manifest` + a positional) |
|
|
285
|
+
| `1` | I/O error (the manifest, or any listed artifact, is unreadable) — the batch never "passes" while an artifact could not be evaluated |
|
|
286
|
+
|
|
287
|
+
**Manifest format.** Either a **newline list** (one entry per line; blank lines and `#` comments are
|
|
288
|
+
skipped) or a **JSON array**. Each entry is an artifact path with an optional per-entry `--vendor` /
|
|
289
|
+
`--dir`. Paths resolve relative to the **manifest file's own directory** (a release ships its manifest
|
|
290
|
+
next to its artifacts); a top-level `--vendor`/`--dir` is a **default** each entry may override.
|
|
291
|
+
|
|
292
|
+
```text
|
|
293
|
+
# release.manifest (newline form)
|
|
294
|
+
datasets/march.vhevidence.json --vendor 0xb463…3221 --dir datasets/march
|
|
295
|
+
recon/q2.vhseal --vendor 0xb463…3221
|
|
296
|
+
proofs/claim-7.vhproof.json
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
```json
|
|
300
|
+
[
|
|
301
|
+
"proofs/claim-7.vhproof.json",
|
|
302
|
+
{ "artifact": "recon/q2.vhseal", "vendor": "0xb463…3221" },
|
|
303
|
+
{ "artifact": "datasets/march.vhevidence.json", "vendor": "0xb463…3221", "dir": "datasets/march" }
|
|
304
|
+
]
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
`--json` emits a **stable aggregate**:
|
|
308
|
+
|
|
309
|
+
```json
|
|
310
|
+
{ "ok": false, "total": 3, "passed": 2, "failed": 1,
|
|
311
|
+
"results": [ /* …one entry PER artifact, each the SAME shape the single-artifact --json emits… */ ] }
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
Each `results[]` entry is byte-identical in shape to the single-artifact `--json` object (the same core
|
|
315
|
+
verifies every entry — no divergence). Gate your release CI on `ok` (or the process exit code). The
|
|
316
|
+
batch path adds **no new crypto and no new artifact kind**, and every entry keeps the same per-entry
|
|
317
|
+
**path-escape / no-network** guarantees as a lone verify. The **single-artifact** invocation
|
|
318
|
+
(`verify-vh <artifact>`) is unchanged — a lone positional still emits the single-artifact object, not an
|
|
319
|
+
aggregate.
|
|
320
|
+
|
|
321
|
+
---
|
|
322
|
+
|
|
323
|
+
## 2b. Wire it into your pipeline — a copy-paste CI merge gate
|
|
324
|
+
|
|
325
|
+
A pilot becomes a renewal when the gate is *wired in*: the build fails the moment a sealed artifact is
|
|
326
|
+
tampered, forged, or signed by the wrong key. Two shipped snippets make that one paste:
|
|
327
|
+
|
|
328
|
+
- **[`ci/verify-vh.generic.sh`](ci/verify-vh.generic.sh)** — a portable `set -e` shell gate for **GitLab
|
|
329
|
+
CI, CircleCI, Jenkins, a Makefile recipe, or a git hook**. It is configured entirely by environment
|
|
330
|
+
variables (no in-file editing), runs the standalone verifier in single-artifact *or* manifest mode, and
|
|
331
|
+
passes the `0/3/2/1` exit code straight through so any non-zero verdict **fails the job**:
|
|
332
|
+
|
|
333
|
+
```bash
|
|
334
|
+
# gate one artifact:
|
|
335
|
+
VH_VENDOR=0xPRODUCER VH_ARTIFACTS="dist/packet.vhevidence.json" ./verifier/ci/verify-vh.generic.sh
|
|
336
|
+
# gate a WHOLE release in one invocation:
|
|
337
|
+
VH_VENDOR=0xPRODUCER VH_MANIFEST=release.manifest ./verifier/ci/verify-vh.generic.sh
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
| env | meaning |
|
|
341
|
+
|-----|---------|
|
|
342
|
+
| `VH_VENDOR` | **required** — the producer's signer address (`0x` + 20 bytes), pinned out-of-band |
|
|
343
|
+
| `VH_MANIFEST` | a release manifest (gate every artifact at once) |
|
|
344
|
+
| `VH_ARTIFACTS` | space-separated artifact paths (when no manifest) |
|
|
345
|
+
| `VH_DIR` | optional dir holding the referenced files |
|
|
346
|
+
| `VERIFY_VH` | path to `verify-vh.js` (default `./verifier/verify-vh.js`) |
|
|
347
|
+
|
|
348
|
+
- **[`ci/verify-vh.github-actions.yml`](ci/verify-vh.github-actions.yml)** — a GitHub Actions workflow you
|
|
349
|
+
drop at `.github/workflows/verify-vh.yml`. It installs **only** the standalone verifier (`js-sha3`, no
|
|
350
|
+
ethers/hardhat) and runs the gate on every push / pull request; a green check then *means* every sealed
|
|
351
|
+
artifact still matches the bytes the producer signed.
|
|
352
|
+
|
|
353
|
+
Both ship as **examples the loop never runs**, but their exact gate command is mechanically tested
|
|
354
|
+
(`../test/verifier.ci-snippet.test.js`): it must exit `0` on a good release and `3` on a tampered one, so
|
|
355
|
+
the snippet you copy is known-good, not aspirational.
|
|
356
|
+
|
|
357
|
+
**The boundary holds in CI too: verification is FREE, sealing is PAID.** Running this gate — like every
|
|
358
|
+
`verify-vh` call — costs nothing, needs no licence, and opens no network. The licence gates only the
|
|
359
|
+
**producer's** paid sealing surface; your pipeline gates on the proofs for free. A green gate is a
|
|
360
|
+
*renewing* dependency precisely because checking the producer's seal never costs you anything, while
|
|
361
|
+
producing a valid one is what the producer pays for.
|
|
362
|
+
|
|
363
|
+
---
|
|
364
|
+
|
|
365
|
+
## 2c. Verify an AGENT-SESSION packet (`*.vhagent.json`) — AgentTrace, free
|
|
366
|
+
|
|
367
|
+
The producer's `vh agent seal` turns an ordered AI-agent session log (prompts, completions, tool
|
|
368
|
+
calls/results, notes) into ONE tamper-evident, selectively-REDACTABLE packet. `verify-vh`
|
|
369
|
+
auto-detects it like every other artifact kind — same command, same exit codes, zero install via the
|
|
370
|
+
standalone bundle or the offline browser page (§0y has a built-in agent demo):
|
|
371
|
+
|
|
372
|
+
```bash
|
|
373
|
+
node verify-vh.js session.vhagent.json # unsigned packet (the FREE surface)
|
|
374
|
+
node verify-vh.js session.vhagent.json --vendor 0xPRODUCER # signed packet, signer pinned
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
What is INDEPENDENTLY re-derived (this verifier imports **nothing** from the producer stack — the
|
|
378
|
+
whole convention is re-implemented against the verifier's own keccak):
|
|
379
|
+
|
|
380
|
+
- **Every event leaf.** For a FULL event the payload's keccak-256 hash commitment is recomputed from
|
|
381
|
+
the payload bytes (and cross-checked against the carried commitment); for a REDACTED event the
|
|
382
|
+
well-formed carried commitment is what the tree binds. A one-byte payload edit — or a **forged
|
|
383
|
+
commitment on a redacted event** — is a REJECT that **names the offending event `seq`**. The
|
|
384
|
+
payload's UTF-8 encoding matches the producer **byte-for-byte** (a lone low surrogate encodes to its
|
|
385
|
+
literal 3-byte form; only a lone HIGH surrogate — which has no UTF-8 encoding — is rejected), so a
|
|
386
|
+
genuine packet the producer sealed is never falsely rejected here.
|
|
387
|
+
- **The ordered head.** An RFC-6962-style, position-bound Merkle root (leaf `0x00` / node `0x01`
|
|
388
|
+
domain separation, children in tree order — NEVER sorted) over the event leaves. Reordering,
|
|
389
|
+
dropping, or inserting events changes the root: `root_mismatch`.
|
|
390
|
+
- **The head signature, when present.** A signed packet carries a detached EIP-191 attestation over
|
|
391
|
+
the HEAD `{ size, root }` (so ONE signature stays valid for every redacted copy). The signer is
|
|
392
|
+
recovered with the same vendored secp256k1 routine and pinned to `--vendor`; a signature pasted
|
|
393
|
+
from a different session is `head_not_bound`, a forged one `bad_signature`, and a `--vendor` pin
|
|
394
|
+
on an UNSIGNED packet is a clean REJECT (`unsigned_cannot_pin_vendor`) — a stripped signature
|
|
395
|
+
never passes a pinned verify.
|
|
396
|
+
|
|
397
|
+
The packet is SELF-CONTAINED (no sibling files, so `--dir` is irrelevant), and REDACTION IS NOT
|
|
398
|
+
TAMPER: a packet whose payloads were withheld behind their commitments still verifies with the
|
|
399
|
+
IDENTICAL head — the verdict lists exactly which seqs are withheld. The same honest boundary as
|
|
400
|
+
everything else here: ACCEPT proves the LOG is unaltered since seal — **not** that the log
|
|
401
|
+
faithfully records what the agent actually did, not a trusted timestamp, and `ts` fields are
|
|
402
|
+
self-asserted (the packet's own in-band trust note says the same).
|
|
403
|
+
|
|
404
|
+
---
|
|
405
|
+
|
|
406
|
+
## 2d. Verify an ANCHORED RECEIPT's binding (`vh-anchored-receipt@1`) — zero producer stack (T-70.4)
|
|
407
|
+
|
|
408
|
+
The producer's `vh anchor-artifact` binds a sealed artifact's ONE canonical digest into an on-chain
|
|
409
|
+
registry record and emits a canonical **`vh-anchored-receipt@1`** container. The receipt's OFFLINE
|
|
410
|
+
**binding leg** verifies here — same zero-install posture, no `ethers`, no producer code:
|
|
411
|
+
|
|
412
|
+
```bash
|
|
413
|
+
node verify-vh.js receipt.vhanchored.json --anchored-artifact packet.vhevidence.json
|
|
414
|
+
# same flags on the single-file bundle: node verify-vh-standalone.js <receipt> --anchored-artifact <sealed-file>
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
The receipt is validated STRICTLY (unknown/missing fields, malformed chain facts, or an edited
|
|
418
|
+
in-band trust note are each a named `bad-receipt`), and the sealed artifact's digest is RECOMPUTED
|
|
419
|
+
through the SAME closed six-kind table the producer core uses (evidence seal, agent-session packet,
|
|
420
|
+
journal tree head, TrustLedger reconciliation seal, dataset/parcel attestation — each re-validated
|
|
421
|
+
through a strict, dependency-free port of its shipped validator first). ACCEPT is exit `0`; any
|
|
422
|
+
deviation is the specific named reject — `digest-mismatch` / `kind-mismatch` / `how-mismatch` /
|
|
423
|
+
`bad-receipt` / the artifact's own named reject — exit `3`, matching the producer cli's verdicts on
|
|
424
|
+
the same inputs. On ACCEPT the verdict also **classifies the chain the receipt claims** — `chainClass`
|
|
425
|
+
(`local-dev` / `public-testnet` / `unknown`) and a `publiclyMeaningful` boolean in `--json`, plus a
|
|
426
|
+
leading `WARNING`/`ADVISORY` line — so a receipt from a **local dev chain** (worth nothing publicly,
|
|
427
|
+
STRATEGY.md P-2) is never mistaken for a public proof. **The honest boundary:** this is the OFFLINE
|
|
428
|
+
binding leg ONLY — the receipt's `chain` facts remain the *anchorer's claim* until re-checked against
|
|
429
|
+
the chain, which needs a chain endpoint by definition and stays with the producer cli
|
|
430
|
+
(`vh verify-anchored --rpc --contract`). See
|
|
431
|
+
[`docs/ANCHORING.md`](../docs/ANCHORING.md) for what an anchored receipt proves and does NOT.
|
|
432
|
+
|
|
433
|
+
---
|
|
434
|
+
|
|
435
|
+
## 3. The exact bytes verified, and the scheme
|
|
436
|
+
|
|
437
|
+
Nothing here is magic; it is two standard primitives you can re-implement in an afternoon.
|
|
438
|
+
|
|
439
|
+
### 3a. Per-file content hash
|
|
440
|
+
For each referenced file, `contentHash = keccak256(file_bytes)`, the raw file bytes with no framing,
|
|
441
|
+
no normalization, no encoding step. Change one byte → a different hash. The verifier reports that file
|
|
442
|
+
as `CHANGED` and prints both the sealed and the on-disk hash.
|
|
443
|
+
|
|
444
|
+
### 3b. The keccak Merkle root
|
|
445
|
+
The per-file `(relPath, contentHash)` leaves (plus, for reconciliation seals, a synthetic
|
|
446
|
+
`verdict`/role header leaf so a verdict edit also moves the root) are folded into one **keccak-256
|
|
447
|
+
Merkle root**. The verifier re-derives this root from the files on disk and compares it, byte-for-byte,
|
|
448
|
+
to the `root` embedded in the artifact. (See `lib/merkle.js` for the exact leaf encoding and pairing
|
|
449
|
+
order — it is short and dependency-free.)
|
|
450
|
+
|
|
451
|
+
### 3c. The signature: EIP-191 `personal_sign` over keccak
|
|
452
|
+
A signed artifact carries a 65-byte `r(32) || s(32) || v(1)` secp256k1 signature. The signed message
|
|
453
|
+
is the **canonical UTF-8 bytes** of the artifact's unsigned payload (the same bytes the verifier
|
|
454
|
+
re-derives in `lib/canonical.js` — it does NOT trust a "signature" field that just echoes a hash). The
|
|
455
|
+
digest is the standard EIP-191 personal-sign pre-image:
|
|
456
|
+
|
|
457
|
+
```
|
|
458
|
+
keccak256( "\x19Ethereum Signed Message:\n" + <decimal byte length> + <canonical message bytes> )
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
`verify-vh` recovers the signer **address** from `(message, signature)` using a tiny vendored
|
|
462
|
+
secp256k1 public-key recovery (SEC 1 §4.1.6) over `js-sha3` keccak — **no `ethers`**. The address is
|
|
463
|
+
`"0x" + last-20-bytes( keccak256( X32 || Y32 ) )`, lowercased. If you pass `--vendor 0xADDR`, the
|
|
464
|
+
recovered address must equal it (compared as 20 raw bytes; checksum casing is ignored), or the verdict
|
|
465
|
+
is `wrong_issuer`.
|
|
466
|
+
|
|
467
|
+
---
|
|
468
|
+
|
|
469
|
+
## 4. The trust boundary — read this before you rely on it
|
|
470
|
+
|
|
471
|
+
`verify-vh` is honest about what a recomputation can and cannot prove. It proves, **purely from the
|
|
472
|
+
bytes in your hands**:
|
|
473
|
+
|
|
474
|
+
- ✅ **Tamper-evidence** — the referenced files are byte-for-byte the ones the producer sealed (if any
|
|
475
|
+
file changed, you see exactly which one, sealed-hash vs on-disk-hash).
|
|
476
|
+
- ✅ **Offline recompute** — the root is independently re-derivable; you are not trusting our software,
|
|
477
|
+
our servers, or a "trust us, it matched" claim. No network call happens (proven mechanically — see
|
|
478
|
+
§6 and `test/verifier.isolation.test.js`).
|
|
479
|
+
- ✅ **Signer-pin** — *which key* vouched for this artifact, pinned to an address you supply
|
|
480
|
+
out-of-band, so a different key cannot impersonate the producer.
|
|
481
|
+
- ✅ **Revocation-aware (opt-in)** — with `--revocations <file-or-dir> [--as-of <ISO>]` `verify-vh`
|
|
482
|
+
consults the producer's signed key revocations and **downgrades** an otherwise-ACCEPTED artifact to
|
|
483
|
+
**REVOKED** (exit 3) when the signing key was revoked **at or before** the as-of instant (default:
|
|
484
|
+
now). A revocation dated *after* the as-of leaves it ACCEPTED with an informational later-revoked note;
|
|
485
|
+
a forged / tampered / third-party revocation is **ignored** with a warning (a revocation only ever
|
|
486
|
+
*removes* trust, never adds it — a key revokes itself). This reaches the **same** downgrade the
|
|
487
|
+
producer-stack `vh ... verify-signed --revocations <f> --as-of <T>` reaches on the identical inputs —
|
|
488
|
+
fully OFFLINE, no producer stack, no network, no key (see
|
|
489
|
+
[`../docs/KEY-LIFECYCLE.md`](../docs/KEY-LIFECYCLE.md)). A directory is read as a flat pool of
|
|
490
|
+
revocation files; a single file may be one revocation or a JSON array.
|
|
491
|
+
|
|
492
|
+
It deliberately does **NOT** prove:
|
|
493
|
+
|
|
494
|
+
- ❌ **A trusted "sealed at time T".** The signature says *this key vouched for these bytes*, not *on
|
|
495
|
+
this date*. Any `timestamp`/`sealedAt` field inside an artifact is producer-asserted and rides the
|
|
496
|
+
human-owned signing/timestamp trust-root (proposal **P-3** in `../STRATEGY.md`). For an *independent*
|
|
497
|
+
time anchor, the family offers a separate **RFC-3161** timestamp path (`vh … verify-timestamp`,
|
|
498
|
+
also offline) — that is a different deliverable, not something `verify-vh` asserts.
|
|
499
|
+
- ❌ **A legal or accounting opinion.** A green verdict means the bytes and the signer check out. It is
|
|
500
|
+
not an attestation that the underlying claim (a reconciliation, a model's provenance) is *correct* —
|
|
501
|
+
that judgement belongs to the producer and their reviewers.
|
|
502
|
+
|
|
503
|
+
In one sentence: **`verify-vh` tells you the bytes are unchanged and which key signed them — not when,
|
|
504
|
+
and not whether the producer's conclusion is true.**
|
|
505
|
+
|
|
506
|
+
---
|
|
507
|
+
|
|
508
|
+
## 5. Worked example: producer seals → hands over packet → you run `verify-vh`
|
|
509
|
+
|
|
510
|
+
This is a real, end-to-end run (test-only ephemeral keys; never a real key or real funds).
|
|
511
|
+
|
|
512
|
+
**Step 1 — the producer seals** a directory of files into a signed evidence packet with their paid
|
|
513
|
+
tool, then publishes their signer address `0xb463…3221` somewhere you trust:
|
|
514
|
+
|
|
515
|
+
```
|
|
516
|
+
data/
|
|
517
|
+
model-card.md
|
|
518
|
+
weights.bin
|
|
519
|
+
packet.vhevidence.json ← the signed seal the producer hands you, alongside the two files
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
**Step 2 — you, the counterparty, verify** (you did NOT install the producer's stack):
|
|
523
|
+
|
|
524
|
+
```bash
|
|
525
|
+
cd verifier && npm install
|
|
526
|
+
node verify-vh.js ../data/packet.vhevidence.json --vendor 0xb463f30cf53d1e0365130363ae9b9867998c3221
|
|
527
|
+
```
|
|
528
|
+
|
|
529
|
+
Output (exit `0`):
|
|
530
|
+
|
|
531
|
+
```
|
|
532
|
+
# verify-vh — .../data/packet.vhevidence.json
|
|
533
|
+
kind: vh.evidence-seal-signed
|
|
534
|
+
embedded kind: vh.evidence-seal
|
|
535
|
+
signed: yes
|
|
536
|
+
recovered signer: 0xb463f30cf53d1e0365130363ae9b9867998c3221
|
|
537
|
+
claimed signer: 0xb463f30cf53d1e0365130363ae9b9867998c3221
|
|
538
|
+
pinned --vendor: 0xb463f30cf53d1e0365130363ae9b9867998c3221
|
|
539
|
+
signer matches vendor: yes
|
|
540
|
+
sealed root: 0x51004f29ea5b0081be2943d377b2c1572b0543af4bfea724642fa73db3589dd5
|
|
541
|
+
recomputed root: 0x51004f29ea5b0081be2943d377b2c1572b0543af4bfea724642fa73db3589dd5
|
|
542
|
+
root matches: yes
|
|
543
|
+
files: 2 matched, 0 changed, 0 missing, 0 rejected, 0 unexpected
|
|
544
|
+
|
|
545
|
+
OK — the artifact verifies.
|
|
546
|
+
```
|
|
547
|
+
|
|
548
|
+
**Step 3 — tamper detection.** Suppose `model-card.md` was altered by one byte in transit. Re-running
|
|
549
|
+
exits `3` and names the file:
|
|
550
|
+
|
|
551
|
+
```
|
|
552
|
+
recomputed root: 0xb2dd6f94… (≠ sealed root)
|
|
553
|
+
root matches: NO
|
|
554
|
+
REJECTED (CHANGED):
|
|
555
|
+
CHANGED model-card.md: sealed 0x59396c16… != on-disk 0xd241bee9…
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
A wrong `--vendor` yields `wrong_issuer`; a corrupted signature yields `bad_signature` — both clean
|
|
559
|
+
exit `3` verdicts, never a crash. Add `--json` for a stable machine verdict object
|
|
560
|
+
(`{ verdict, reason, accepted, rootMatches, signerMatchesVendor, counts, … }`) to gate CI.
|
|
561
|
+
|
|
562
|
+
---
|
|
563
|
+
|
|
564
|
+
## 6. Why you can trust *this verifier* itself
|
|
565
|
+
|
|
566
|
+
Independence is **mechanically enforced**, not just promised:
|
|
567
|
+
|
|
568
|
+
- **No producer stack.** Every `require(` in this whole tree (`verify-vh.js` + `lib/*`) is grepped by
|
|
569
|
+
`../test/verifier.isolation.test.js`; it must never pull `ethers`, `hardhat`, `@nomicfoundation/*`,
|
|
570
|
+
or anything under `../cli/` or `../trustledger/`. The only runtime dependency is `js-sha3`.
|
|
571
|
+
- **No network, no back-edge.** The same test runs a real verify and asserts the process opens **no
|
|
572
|
+
socket and no network handle** — `verify-vh` never `require`s `http`/`https`/`net`/`dns`. It cannot
|
|
573
|
+
phone home, because it has nothing to phone home *with*.
|
|
574
|
+
- **Read-only.** It holds no key, writes nothing, and leaves your working tree byte-for-byte untouched.
|
|
575
|
+
- **Cross-checked crypto.** Its secp256k1 recovery is independently re-implemented and continuously
|
|
576
|
+
cross-checked against the production path (`../test/verifier.crypto.test.js`) so the two can never
|
|
577
|
+
silently drift.
|
|
578
|
+
|
|
579
|
+
See [`../docs/INDEPENDENT-VERIFICATION.md`](../docs/INDEPENDENT-VERIFICATION.md) for the full
|
|
580
|
+
counterparty-facing specification.
|
|
581
|
+
|
|
582
|
+
|
|
583
|
+
---
|
|
584
|
+
<sub>© 2026 verifyhash.com · Licensed under Apache-2.0 (SPDX-License-Identifier: Apache-2.0) — see the [LICENSE](https://verifyhash.com/LICENSE) and [NOTICE](https://verifyhash.com/NOTICE) served with this file.</sub>
|