verifyhash 0.1.0

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.
Files changed (154) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +883 -0
  3. package/cli/abi/ContributionRegistry.json +881 -0
  4. package/cli/agent.js +2173 -0
  5. package/cli/anchor-artifact.js +853 -0
  6. package/cli/anchor.js +400 -0
  7. package/cli/claim.js +881 -0
  8. package/cli/core/agent-commit.js +448 -0
  9. package/cli/core/agent-session.js +598 -0
  10. package/cli/core/anchor-binding.js +663 -0
  11. package/cli/core/attestation.js +580 -0
  12. package/cli/core/evidence-plans.js +495 -0
  13. package/cli/core/fixtures/evidence-plans/baseline.json +19 -0
  14. package/cli/core/fulfill-intake.js +1082 -0
  15. package/cli/core/go-live-preflight.js +481 -0
  16. package/cli/core/license.js +534 -0
  17. package/cli/core/manifest.js +243 -0
  18. package/cli/core/packetseal.js +591 -0
  19. package/cli/core/registryArtifact.js +49 -0
  20. package/cli/core/revocation.js +539 -0
  21. package/cli/core/rfc3161.js +389 -0
  22. package/cli/core/timestamp.js +482 -0
  23. package/cli/core/trust-asof.js +479 -0
  24. package/cli/dataset.js +2950 -0
  25. package/cli/evidence.js +2227 -0
  26. package/cli/fulfill-webhook-http.js +438 -0
  27. package/cli/git.js +220 -0
  28. package/cli/hash.js +550 -0
  29. package/cli/identity.js +1072 -0
  30. package/cli/journal-cli.js +1110 -0
  31. package/cli/journal-log.js +454 -0
  32. package/cli/journal.js +334 -0
  33. package/cli/lineage.js +447 -0
  34. package/cli/list.js +287 -0
  35. package/cli/parcel.js +1509 -0
  36. package/cli/proof.js +578 -0
  37. package/cli/prove.js +300 -0
  38. package/cli/receipt.js +631 -0
  39. package/cli/registry.js +331 -0
  40. package/cli/reputation.js +344 -0
  41. package/cli/revocation.js +495 -0
  42. package/cli/serve-verify-http.js +298 -0
  43. package/cli/serve-verify.js +333 -0
  44. package/cli/show.js +339 -0
  45. package/cli/verify.js +383 -0
  46. package/cli/vh.js +3927 -0
  47. package/docs/ADOPT.md +183 -0
  48. package/docs/ADOPTION.json +11 -0
  49. package/docs/AGENTTRACE.md +247 -0
  50. package/docs/ANCHORING.md +167 -0
  51. package/docs/AUDIT.md +55 -0
  52. package/docs/CONFORMANCE.md +107 -0
  53. package/docs/DATALEDGER.md +638 -0
  54. package/docs/DECIDE.md +47 -0
  55. package/docs/DECISIONS-PENDING.md +27 -0
  56. package/docs/DEPLOY-PUBLIC-SITE.md +301 -0
  57. package/docs/ENGINE-LEDGER.json +12 -0
  58. package/docs/EVIDENCE.md +519 -0
  59. package/docs/GO-LIVE.md +66 -0
  60. package/docs/IDENTITY.md +123 -0
  61. package/docs/INDEPENDENT-VERIFICATION.md +377 -0
  62. package/docs/INTEGRITY-JOURNAL.md +337 -0
  63. package/docs/KEY-LIFECYCLE.md +179 -0
  64. package/docs/LICENSING.md +46 -0
  65. package/docs/LINEAGE.md +307 -0
  66. package/docs/LOOP-AUDIT-2026-07-03.json +580 -0
  67. package/docs/LOOP-HARDENING-PLAN.md +44 -0
  68. package/docs/MERKLE-LEAVES.md +113 -0
  69. package/docs/METRICS.jsonl +31 -0
  70. package/docs/MORNING.md +204 -0
  71. package/docs/PILOT.md +444 -0
  72. package/docs/PROOFPARCEL.md +227 -0
  73. package/docs/PROOFS.md +262 -0
  74. package/docs/RECEIPTS.md +341 -0
  75. package/docs/REPUTATION.md +158 -0
  76. package/docs/SDK.md +301 -0
  77. package/docs/STRATEGY-ARCHIVE.md +5055 -0
  78. package/docs/SUPERVISOR-RUNBOOK.md +52 -0
  79. package/docs/TRUST-BOUNDARIES.md +335 -0
  80. package/docs/TRUSTLEDGER.md +1976 -0
  81. package/docs/USAGE-BUDGET.json +121 -0
  82. package/docs/VERIFY-SERVICE.md +168 -0
  83. package/index.js +160 -0
  84. package/package.json +41 -0
  85. package/trustledger/build-standalone.js +796 -0
  86. package/trustledger/cli.js +3179 -0
  87. package/trustledger/close.js +391 -0
  88. package/trustledger/corpus.js +159 -0
  89. package/trustledger/dist/BUILD-PROVENANCE.json +99 -0
  90. package/trustledger/dist/trustledger-standalone.html +6197 -0
  91. package/trustledger/dist/trustledger-standalone.html.sha256 +1 -0
  92. package/trustledger/door-core.js +442 -0
  93. package/trustledger/fixtures/bank.csv +7 -0
  94. package/trustledger/fixtures/bank.malformed.csv +3 -0
  95. package/trustledger/fixtures/bank.noalias.csv +5 -0
  96. package/trustledger/fixtures/bank.ofx +34 -0
  97. package/trustledger/fixtures/bank.real.csv +5 -0
  98. package/trustledger/fixtures/corpus/_shared/prior-close.json +22 -0
  99. package/trustledger/fixtures/corpus/bank-book-mismatch--benign-twin/inputs.json +14 -0
  100. package/trustledger/fixtures/corpus/bank-book-mismatch--benign-twin/meta.json +7 -0
  101. package/trustledger/fixtures/corpus/bank-book-mismatch--out-of-trust/inputs.json +14 -0
  102. package/trustledger/fixtures/corpus/bank-book-mismatch--out-of-trust/meta.json +7 -0
  103. package/trustledger/fixtures/corpus/continuity-break--benign-twin/inputs.json +15 -0
  104. package/trustledger/fixtures/corpus/continuity-break--benign-twin/meta.json +7 -0
  105. package/trustledger/fixtures/corpus/continuity-break--out-of-trust/inputs.json +15 -0
  106. package/trustledger/fixtures/corpus/continuity-break--out-of-trust/meta.json +7 -0
  107. package/trustledger/fixtures/corpus/negative-tenant-ledger--benign-twin/inputs.json +13 -0
  108. package/trustledger/fixtures/corpus/negative-tenant-ledger--benign-twin/meta.json +7 -0
  109. package/trustledger/fixtures/corpus/negative-tenant-ledger--out-of-trust/inputs.json +13 -0
  110. package/trustledger/fixtures/corpus/negative-tenant-ledger--out-of-trust/meta.json +7 -0
  111. package/trustledger/fixtures/corpus/owner-overdraw--benign-twin/inputs.json +15 -0
  112. package/trustledger/fixtures/corpus/owner-overdraw--benign-twin/meta.json +7 -0
  113. package/trustledger/fixtures/corpus/owner-overdraw--out-of-trust/inputs.json +15 -0
  114. package/trustledger/fixtures/corpus/owner-overdraw--out-of-trust/meta.json +7 -0
  115. package/trustledger/fixtures/corpus/security-deposit-segregation--benign-twin/inputs.json +16 -0
  116. package/trustledger/fixtures/corpus/security-deposit-segregation--benign-twin/meta.json +7 -0
  117. package/trustledger/fixtures/corpus/security-deposit-segregation--out-of-trust/inputs.json +13 -0
  118. package/trustledger/fixtures/corpus/security-deposit-segregation--out-of-trust/meta.json +7 -0
  119. package/trustledger/fixtures/corpus/subledger-out-of-balance--benign-twin/inputs.json +13 -0
  120. package/trustledger/fixtures/corpus/subledger-out-of-balance--benign-twin/meta.json +7 -0
  121. package/trustledger/fixtures/corpus/subledger-out-of-balance--out-of-trust/inputs.json +13 -0
  122. package/trustledger/fixtures/corpus/subledger-out-of-balance--out-of-trust/meta.json +7 -0
  123. package/trustledger/fixtures/e2e/bank.aliased.csv +4 -0
  124. package/trustledger/fixtures/e2e/bank.csv +4 -0
  125. package/trustledger/fixtures/e2e/bank.nsf.csv +4 -0
  126. package/trustledger/fixtures/e2e/quickbooks.csv +6 -0
  127. package/trustledger/fixtures/e2e/quickbooks.nsf.csv +8 -0
  128. package/trustledger/fixtures/e2e/rentroll.csv +6 -0
  129. package/trustledger/fixtures/e2e/rentroll.nsf.csv +8 -0
  130. package/trustledger/fixtures/e2e/rentroll.short.csv +5 -0
  131. package/trustledger/fixtures/plans/baseline.json +25 -0
  132. package/trustledger/fixtures/plans/price-binding.example.json +27 -0
  133. package/trustledger/fixtures/policy/ambiguous-deposit-example.json +12 -0
  134. package/trustledger/fixtures/policy/baseline.json +19 -0
  135. package/trustledger/fixtures/policy/ca-example.json +12 -0
  136. package/trustledger/fixtures/policy/negative-tenant-ledger-example.json +12 -0
  137. package/trustledger/fixtures/policy/owner-overdraw-example.json +12 -0
  138. package/trustledger/fixtures/quickbooks.csv +7 -0
  139. package/trustledger/fixtures/quickbooks.real.csv +5 -0
  140. package/trustledger/fixtures/rentroll.csv +6 -0
  141. package/trustledger/fixtures/rentroll.real.csv +4 -0
  142. package/trustledger/ingest.js +1163 -0
  143. package/trustledger/lib/policy-bundled-loader.js +44 -0
  144. package/trustledger/lib/sha256-vendored.js +227 -0
  145. package/trustledger/license.js +563 -0
  146. package/trustledger/match.js +551 -0
  147. package/trustledger/plans.js +551 -0
  148. package/trustledger/policy.js +398 -0
  149. package/trustledger/public/index.html +512 -0
  150. package/trustledger/reconcile.js +1486 -0
  151. package/trustledger/report.js +887 -0
  152. package/trustledger/seal.js +854 -0
  153. package/trustledger/server.js +391 -0
  154. package/trustledger/valueproof.js +350 -0
@@ -0,0 +1,123 @@
1
+ # Producer identity card (`vh identity`)
2
+
3
+ A **signed, offline-verifiable "who is this vendor, and what exactly do they attest?"** card. A producer
4
+ SIGNS — with the **same key** that signs their evidence packets / signed licenses / dataset attestations —
5
+ a small, self-describing container that binds their **`vendorAddress`** to a bounded `claims[]` set (what
6
+ they attest) and an honest `nonClaims[]` set (what they explicitly do **NOT**). A recipient — or a **cold
7
+ prospect** who has never met you — recovers the signer from the card, confirms it **equals the card's own
8
+ `vendorAddress`** (the key controls the address it claims), and OPTIONALLY pins it to an address they
9
+ learned out of band. All of it is **OFFLINE, key-free, network-free, I/O-free**.
10
+
11
+ It is the **recipient's / cold prospect's pin-point**: every other artifact this family mints (an evidence
12
+ seal, a signed license, a dataset attestation) pins its producer by a vendor **address** the recipient must
13
+ learn out of band — an email, a slide, a README line. The identity card is the **one** artifact whose
14
+ whole job is to answer, verifiably, "**does this 0x-address really belong to THIS vendor, and what exactly
15
+ do they attest — and, just as load-bearing, what do they explicitly NOT?**" before the recipient trusts a
16
+ single packet.
17
+
18
+ **Trust boundary (the output leads with this, verbatim — the standing `IDENTITY_CARD_TRUST_NOTE`):**
19
+
20
+ ```
21
+ This is a verifyhash producer IDENTITY CARD: the holder of `vendorAddress`'s key SIGNED it, binding that address to the `claims` it attests and the `nonClaims` it explicitly does NOT. verify RE-DERIVES the signer from these exact bytes and REQUIRES it to equal `vendorAddress` — it never trusts the file's own claims. It proves IDENTITY + the claim SET ONLY: it does NOT prove any specific sealed/signed packet is true (each packet carries its own proof), it is NOT a trusted TIMESTAMP ("published since T" rides the human-owned signing/timestamp trust-root, STRATEGY.md P-3), and it is NOT a legal opinion.
22
+ ```
23
+
24
+ > A card proves **IDENTITY + the claim SET**, **NOT packet truth** (each sealed/signed packet carries its
25
+ > own proof — `vh evidence verify` / `verify-signed`), **NOT a trusted timestamp** (P-3), and **NOT a legal
26
+ > opinion**. See [`docs/TRUST-BOUNDARIES.md`](TRUST-BOUNDARIES.md).
27
+
28
+ ## Commands
29
+
30
+ ```
31
+ vh identity publish --address <0xaddr> --product-line <line> --claim <text> [--claim ...] --non-claim <text> [--non-claim ...] [--published-at <ISO>] (--key-env <VAR> | --key-file <path>) [--out <p>] [--json]
32
+ vh identity verify <card> [--signer <0xaddr>] [--json]
33
+ ```
34
+
35
+ ### `vh identity publish` — mint the card
36
+
37
+ `publish` MINTS a signed `*.vhidentity.json` card binding `--address` to the `--claim` set it attests + the
38
+ `--non-claim` set it explicitly does NOT. It signs with a **HUMAN-provisioned key** (EXACTLY ONE of
39
+ `--key-env` / `--key-file`, **read-used-discarded** via the shared `loadSigningWallet` — the loop **NEVER**
40
+ generates, persists, or logs a key, and the key never appears in any output).
41
+
42
+ - **The load-bearing mint invariant — the key controls the address it claims.** `publish` mints **ONLY**
43
+ when the provisioned key's address **EQUALS** `--address`. A key that does **NOT** control `--address`
44
+ **hard-errors (exit 2) BEFORE writing anything** — so a card can never assert an identity the key cannot
45
+ back, and a published card **always** round-trips to ACCEPTED by construction.
46
+ - **Filesystem hygiene.** Default **prints the card + writes NOTHING**; `--out <p>` writes ONLY to the
47
+ caller-chosen path — **never silently to cwd**.
48
+ - `--product-line` is one of the closed set `["dataledger", "evidence", "trustledger"]` (an out-of-set line
49
+ is a usage error); `claims` and `nonClaims` are each a **non-empty** list (a card that attests nothing, or
50
+ that drops the honest boundary, is refused).
51
+ - The output **LEADS with the trust line**; `--json` carries the PUBLIC card summary (vendorAddress, signer,
52
+ productLine, claims, nonClaims, publishedAt) + the artifact — and **never the key**.
53
+ - **Exit:** **0** ok / **2** usage (missing/invalid field, key-source error, key does not control
54
+ `--address`) / **1** IO (`--out` write).
55
+
56
+ ### `vh identity verify` — check + pin the card
57
+
58
+ `verify <card>` is the **OFFLINE / key-free / network-free** read path. It RECOVERS the signer from the
59
+ embedded canonical card bytes + signature and:
60
+
61
+ 1. confirms the signature backs the container's claimed `signer` (**ALWAYS**);
62
+ 2. confirms the recovered signer **IS** the card's own `vendorAddress` — the load-bearing "the key controls
63
+ the address it claims" check (**ALWAYS**);
64
+ 3. OPTIONALLY pins the recovered signer to an expected **`--signer <0xaddr>`** you learned out of band.
65
+
66
+ The verdict is **ACCEPTED** only when **every requested check passes**; a **forged / tampered / wrong-vendor
67
+ card, or a wrong `--signer`, is a clean REJECTED — NEVER a silent pass**. It **LEADS with the trust line**,
68
+ prints the `claims` + `nonClaims` + per-check **PASS / FAIL / [skip]**, and writes **NOTHING**.
69
+
70
+ - **Exit:** **0** ACCEPTED / **3** REJECTED / **2** usage / **1** IO — the same read contract as
71
+ `vh evidence verify-signed` / `vh dataset verify-attest`.
72
+
73
+ ## Pin once, trust across handoffs
74
+
75
+ The card exists so a recipient does the **address-to-vendor** trust step **ONCE**, then reuses that pin
76
+ across **every later handoff** — without re-establishing trust out of band each time.
77
+
78
+ 1. **Pin once.** The vendor publishes their card once (`vh identity publish … --out vendor.vhidentity.json`)
79
+ and the recipient confirms it once: `vh identity verify vendor.vhidentity.json --signer <addr-you-were-given>`.
80
+ That single ACCEPTED verdict is the recipient's durable answer to "this 0x-address really is this vendor,
81
+ and here is exactly what they attest / do NOT." The `--signer` pin binds the card to the address the
82
+ recipient learned out of band (the email/slide/contract) — so the out-of-band step happens **once**.
83
+ 2. **Trust across handoffs.** Every subsequent evidence packet, signed license, or dataset attestation the
84
+ vendor hands over is signed by the **same** key. The recipient verifies each artifact's signer
85
+ (`vh evidence verify-signed <p> --signer <addr>`, etc.) against the **same pinned address** — no new
86
+ out-of-band step, no re-pinning. The card's `vendorAddress` IS the address every later `--signer` pin
87
+ reuses, so one verified card amortizes the trust cost across an unbounded stream of handoffs.
88
+
89
+ This is why the card is the **cold prospect's first stop**: it converts a single out-of-band "is this
90
+ address really them?" into a verifiable, reusable pin — closing the gap between "interesting claim" and "I
91
+ trust this vendor enough to run a pilot."
92
+
93
+ ## What the card does and does NOT prove
94
+
95
+ - **It DOES prove IDENTITY + the claim SET.** The key that controls `vendorAddress` signed a bounded,
96
+ self-describing list of what the vendor attests (`claims`) and what they explicitly do **NOT** (`nonClaims`).
97
+ verify re-derives the signer from the exact bytes and requires it to equal `vendorAddress` — it never
98
+ trusts the file's own claims.
99
+ - **It does NOT prove any specific packet's contents are true.** Each sealed/signed packet carries its OWN
100
+ proof — re-derive it (`vh evidence verify`) and check the signer (`vh evidence verify-signed`). The card
101
+ vouches for **who** the vendor is and **what** they claim to attest, not for the truth of any one packet.
102
+ - **It is NOT a trusted timestamp.** A `publishedAt` is the vendor's self-asserted instant; "published
103
+ since T" rides the human-owned signing/timestamp trust-root (`needs-human`, **P-3** in
104
+ [`STRATEGY.md`](../STRATEGY.md)), exactly like every other dated artifact in this family.
105
+ - **It is NOT a legal opinion.** The card makes no compliance/legal claim; it is tamper-evidence + a signed
106
+ identity binding, nothing more.
107
+
108
+ ## Where it fits
109
+
110
+ The card is **one more product on the shared signed-attestation envelope** — no new crypto, no new
111
+ dependency, no new scheme. It defines its own KIND (`vh-identity-card` / `vh-identity-card-signed`) + a
112
+ closed field set, then hands the canonical payload to [`cli/core/attestation.js`](../cli/core/attestation.js),
113
+ which does all the crypto: it embeds the EXACT canonical payload bytes, attaches the detached **EIP-191**
114
+ signature, and later re-derives the signer — byte-for-byte the same shared paths the evidence seal and the
115
+ signed license use. The publish/verify core lives in [`cli/identity.js`](../cli/identity.js).
116
+
117
+ Provisioning the real vendor key, setting the price, and landing the first design partner remain **human
118
+ steps** — the producer publishes their card with the same key they provision for signed evidence/licenses
119
+ (STRATEGY.md › **P-7 step 1** / **P-6 step 1**).
120
+
121
+
122
+ ---
123
+ <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>
@@ -0,0 +1,377 @@
1
+ # Independent verification — the buyer/counterparty deliverable
2
+
3
+ > **Audience: a NON-customer.** You were handed a sealed verifyhash artifact by a counterparty (a
4
+ > "producer") and you need to decide whether to believe it — without trusting the producer's software,
5
+ > their servers, or us. This document is the contract that makes that possible. The runnable tool and a
6
+ > quickstart live in [`../verifier/README.md`](../verifier/README.md); this is the deeper specification.
7
+
8
+ The whole point of a provenance seal is that **the party relying on it is not the party who made it.**
9
+ A proof you can only check with the producer's own toolchain is not a proof — it is a request to trust
10
+ them. `verify-vh` exists so the relying party can recompute everything themselves, offline, for free.
11
+
12
+ ---
13
+
14
+ ## 0. Get it in 10 seconds (zero-install — start here)
15
+
16
+ You do not need to clone this repo, run `npm install`, create an account, or install our toolchain to
17
+ check a seal. Verification is **one self-contained file**,
18
+ [`../verifier/dist/verify-vh-standalone.js`](../verifier/dist/verify-vh-standalone.js), that depends on
19
+ **nothing but Node core**. The fastest honest path:
20
+
21
+ ```bash
22
+ # 1. Save ONE file — verifier/dist/verify-vh-standalone.js — next to the packet you were handed.
23
+ # (Copy it from the repo, or your counterparty sends it to you. No clone, no install.)
24
+
25
+ # 2. (Optional, recommended) Check its PUBLISHED checksum so you know the file itself wasn't swapped.
26
+ # The producer publishes the SHA-256 out-of-band; we ship it next to the file as
27
+ # verify-vh-standalone.js.sha256 (standard `sha256sum` line format):
28
+ sha256sum -c verify-vh-standalone.js.sha256 # -> "verify-vh-standalone.js: OK"
29
+ # (macOS: `shasum -a 256 -c verify-vh-standalone.js.sha256`. Or eyeball it:
30
+ # `sha256sum verify-vh-standalone.js` and compare to the published hex.)
31
+
32
+ # 2b. (Even better — needs NO second file.) The bundle is SELF-DESCRIBING: it carries its own
33
+ # build-provenance and can check its own bytes. From the ONE file alone:
34
+ node verify-vh-standalone.js --self-attest # -> "[MATCH] ... this file is intact"; exit 1 if edited
35
+ node verify-vh-standalone.js --provenance # -> the ordered source modules + sha256 it was built from
36
+ # `--provenance` lists the exact `verifier/lib/*` files (each pinned by its own sha256) the bundle inlines;
37
+ # those same hashes are in verifier/dist/BUILD-PROVENANCE.json, which
38
+ # `node verifier/build-standalone.js --check` reproduces end-to-end from source.
39
+
40
+ # 3. Run it on the packet — no clone, no `npm install`, no node_modules, no account:
41
+ node verify-vh-standalone.js <packet>.vhevidence.json --vendor 0xPRODUCER_ADDRESS
42
+ # exit 0 = verifies; exit 3 = REJECTED (and it names the file that changed / the wrong signer).
43
+ ```
44
+
45
+ That single file is **byte-for-byte the same verifier** documented below — it is built deterministically
46
+ from the `verifier/` sources (the keccak provider is swapped for a vendored pure-JS one, cross-checked
47
+ against `js-sha3` and `ethers`), and a stale bundle FAILS CI
48
+ ([`../test/verifier.standalone.test.js`](../test/verifier.standalone.test.js)). The split-source,
49
+ `npm install`-the-`verifier/`-tree path in [`../verifier/README.md`](../verifier/README.md) and §4 below
50
+ remains for auditors who want to read each lib file on its own; both paths compute the **identical**
51
+ verdict and exit code.
52
+
53
+ **The easier path does not change what is proven.** Whether you run the one-file bundle or the split tree,
54
+ the seal proves **tamper-evidence + signer-pin**, NOT a trusted "sealed at T" (that still requires
55
+ **P-3** — see §3). The convenience is in the *install*, never in the *claim*.
56
+
57
+ > **New here? Start with the guided 60-second challenge.** If you were handed a seal cold and want the
58
+ > *fastest* "prove it to me" path — verify a real pre-sealed sample packet, then tamper one byte and watch
59
+ > it get rejected and the changed file named — run [`../challenge/`](../challenge/) (one command, zero
60
+ > install, zero account). It is the cold-prospect entry point to everything specified below, on the
61
+ > **FREE, UNSIGNED** path (so there is no signer to pin in the sample), and it points free-verify →
62
+ > free-produce → **paid-produce**. See [`../challenge/README.md`](../challenge/README.md).
63
+
64
+ > **No Node on the machine? Run that challenge in your browser.** The same engine also ships as ONE
65
+ > committed, fully offline HTML page —
66
+ > [`../verifier/dist/verify-vh-standalone.html`](../verifier/dist/verify-vh-standalone.html): open it,
67
+ > click the built-in sample packet (**ACCEPT**), change one character of the sample file on the page and
68
+ > re-verify (**REJECT**, naming the file), then drag a real packet + its files in for the same verdict +
69
+ > per-file localization specified below. The file contains **no network API at all** — check the browser
70
+ > **devtools Network tab**: it stays empty, so your bytes never leave your machine. The boundary is the
71
+ > one in §3, unchanged; for CI/production gating use the node standalone (`verify-vh-standalone.js`).
72
+
73
+ > **On the checksum's trust model — read this so step 2 isn't oversold.** `sha256sum -c` proves the file
74
+ > you saved is bit-for-bit the file whose hash was published; it does **not**, by itself, prove *who*
75
+ > published that hash. The hash is a **transport-integrity** check (did the bundle arrive intact / un-swapped?),
76
+ > pinned to a value you obtain **out-of-band** from the producer — exactly like the `--vendor` signer
77
+ > address. The real root of trust is still the cross-checked, dependency-grepped, network-poisoned audit of
78
+ > the source in §5: the checksum only saves you from re-reading 80 KB every time once you have audited it once.
79
+
80
+ ---
81
+
82
+ ## 0a. Produce your own seal in 10 seconds, then verify it (the free round-trip)
83
+
84
+ §0 shows the **verify** half. There is a matching FREE **produce** half, so you can drive the *entire*
85
+ loop yourself before you ever talk to us — **seal your own files, hand them off, verify** — with **no
86
+ clone, no `npm install`, no account, no key** on either side. The producer half is also **one
87
+ self-contained file**, [`../verifier/dist/seal-vh-standalone.js`](../verifier/dist/seal-vh-standalone.js),
88
+ depending on **nothing but Node core** (the same vendored keccak the verifier uses):
89
+
90
+ ```bash
91
+ # 1. Save ONE file — verifier/dist/seal-vh-standalone.js (optionally check its published
92
+ # seal-vh-standalone.js.sha256 the same way as the verifier in §0).
93
+
94
+ # 2. Seal up to 25 of YOUR OWN files into one tamper-evident packet — no install, no key, no account:
95
+ node seal-vh-standalone.js <your-folder> -o packet.vhevidence.json # exit 0 = sealed
96
+
97
+ # 3. Hand packet.vhevidence.json + your folder to a counterparty; they run the FREE verifier from §0:
98
+ node verify-vh-standalone.js packet.vhevidence.json --dir <your-folder> # exit 0 = verifies; 3 = REJECTED
99
+ ```
100
+
101
+ That is the whole self-service adoption loop, free on both ends: one file to **seal**, one file to
102
+ **verify**, and the `.vhevidence.json` is all that changes hands. The standalone sealer is built
103
+ deterministically from the `verifier/` sources and a stale bundle FAILS CI
104
+ ([`../test/freeseal.standalone.test.js`](../test/freeseal.standalone.test.js)); its seal bytes are
105
+ byte-for-byte identical to the producer's own `cli/evidence.js` seal over the same folder, so a free seal
106
+ is the **same** artifact the paid tool wraps — not a toy.
107
+
108
+ **The honest boundary is identical to §0, and the free seal is narrower still.** A standalone seal proves
109
+ **tamper-evidence + offline-recompute** — these are exactly those files, independently re-derivable by
110
+ anyone — and **NOT** a trusted "sealed at T" without **P-3** (see §3). On top of that the FREE seal is
111
+ **UNSIGNED** (no signer to pin; there is no `--sign`/`--license`/`--key` flag here at all) and **capped at
112
+ 25 files** (a folder of more than 25 hard-errors and writes nothing). **SIGNING** (an EIP-191 signer-pin
113
+ you can then check with `--vendor`) and **UNLIMITED** sealing are the PAID upgrade —
114
+ `vh evidence seal --sign` / the `evidence_unlimited` entitlement (`--license`) through the full producer
115
+ CLI. The free round-trip is the funnel; the paid upgrade adds *who signed it* and *no file cap*.
116
+
117
+ ---
118
+
119
+ ## 1. The deliverable, precisely
120
+
121
+ `verify-vh` is a **standalone, read-only, OFFLINE** verifier that, given an artifact plus the files it
122
+ references and the producer's signer address:
123
+
124
+ 1. **re-derives** the keccak-256 Merkle root from the bytes you hold,
125
+ 2. **compares** it to the root embedded (and signed) in the artifact,
126
+ 3. **recovers** the secp256k1 signer of the signature, and
127
+ 4. **pins** that signer to an address you supply out-of-band.
128
+
129
+ It emits a deterministic verdict and a CI-gateable exit code. It is **free** — verification is never a
130
+ paid tier (the producer pays to seal; anyone may verify forever). It ships with near-zero dependencies
131
+ (`js-sha3` + a tiny vendored secp256k1 routine), so a third party can `npm install` it alone and audit
132
+ the entire thing in an afternoon.
133
+
134
+ ---
135
+
136
+ ## 2. The exact bytes that are verified
137
+
138
+ ### 2.1 Content hash (per file)
139
+ ```
140
+ contentHash(file) = keccak256( raw file bytes )
141
+ ```
142
+ No normalization, no encoding, no line-ending fixups — the literal bytes on disk. A single changed byte
143
+ produces a different hash, and the verifier reports that file as `CHANGED` with both the sealed and the
144
+ on-disk hash so the diff is attributable.
145
+
146
+ ### 2.2 Merkle root (per artifact)
147
+ Each referenced file contributes a leaf binding its `relPath` to its `contentHash`. Reconciliation
148
+ seals additionally fold in a synthetic **header leaf** computed from the seal's own `verdict` + input
149
+ role bindings, so editing the verdict (which lives in the seal, not in any file) still changes the
150
+ recomputed root. All leaves are folded into one keccak-256 **root**. The exact leaf encoding and
151
+ pairing order are in [`../verifier/lib/merkle.js`](../verifier/lib/merkle.js) — short and
152
+ dependency-free by design, so you can re-implement it.
153
+
154
+ ### 2.3 Signature (EIP-191 `personal_sign` / keccak)
155
+ A signed artifact carries a 65-byte `r(32) || s(32) || v(1)` secp256k1 signature whose **message is the
156
+ canonical UTF-8 bytes of the artifact's unsigned payload** — re-derived by the verifier in
157
+ [`../verifier/lib/canonical.js`](../verifier/lib/canonical.js), **not** read back from a self-asserting
158
+ field. The digest is the standard EIP-191 personal-sign pre-image:
159
+
160
+ ```
161
+ digest = keccak256( "\x19Ethereum Signed Message:\n" + decimal(byteLength(message)) + message )
162
+ ```
163
+
164
+ The signer address is recovered via standard secp256k1 public-key recovery (SEC 1 §4.1.6) and rendered
165
+ as `"0x" + lastBytes20( keccak256( X32 || Y32 ) )`, lowercased. With `--vendor 0xADDR` the recovered
166
+ address must equal it (20 raw bytes; checksum casing ignored) or the verdict is `wrong_issuer`. This is
167
+ a second, independent implementation of the family's recovery, continuously cross-checked against the
168
+ production `ethers` path so the two cannot silently diverge
169
+ ([`../test/verifier.crypto.test.js`](../test/verifier.crypto.test.js)).
170
+
171
+ ---
172
+
173
+ ## 3. The trust boundary
174
+
175
+ `verify-vh` makes exactly three claims, all derivable from the bytes in your hands, and explicitly
176
+ disclaims everything else.
177
+
178
+ ### What it DOES prove
179
+ - **Tamper-evidence.** The referenced files are byte-for-byte the ones sealed; any change is localized
180
+ and attributable (which file, sealed-hash vs on-disk-hash).
181
+ - **Offline recompute.** The root is re-derivable by you alone — no trusted server, no "it matched on
182
+ our end." No network access occurs (proven mechanically; see §5).
183
+ - **Signer-pin.** *Which key* vouched, pinned to an address you obtained out-of-band, so a different
184
+ key cannot impersonate the producer.
185
+ - **Revocation-aware (opt-in).** With `--revocations <file-or-dir> [--as-of <ISO>]` `verify-vh`
186
+ consults the producer's signed key revocations and **downgrades** an otherwise-ACCEPTED artifact to
187
+ **REVOKED** (exit 3) when the signing key was revoked **at or before** the as-of instant (default:
188
+ now). A revocation dated *after* the as-of leaves the verdict ACCEPTED with an informational
189
+ later-revoked note; a forged / tampered / third-party revocation is **ignored** with a warning (a
190
+ revocation only ever *removes* trust, never adds it — a key revokes itself, so a third party cannot
191
+ grief you into rejecting a good artifact). This reaches the **same** downgrade the producer-stack
192
+ `vh ... verify-signed --revocations <f> --as-of <T>` reaches on the identical inputs — fully OFFLINE,
193
+ no producer stack, no network, no key (see [`KEY-LIFECYCLE.md`](KEY-LIFECYCLE.md)). A directory is
194
+ read as a flat pool of revocation files; a single file may be one revocation or a JSON array.
195
+
196
+ ### What it does NOT prove
197
+ - **NOT a trusted "sealed at time T."** A signature attests *this key vouched for these bytes* — not
198
+ *when*. Any `timestamp`/`sealedAt`/`reportDate` field inside an artifact is **producer-asserted** and
199
+ rides the human-owned signing/timestamp trust-root (proposal **P-3** in
200
+ [`../STRATEGY.md`](../STRATEGY.md)). For an *independent* time anchor the family ships a separate,
201
+ also-offline **RFC-3161** timestamp path (`vh dataset/parcel verify-timestamp`, P-3 Option B) — that
202
+ is a different deliverable, and `verify-vh` does not assert it.
203
+ - **NOT a legal/accounting opinion.** A green verdict means the bytes and the signer check out — not
204
+ that the producer's underlying conclusion (a reconciliation result, a dataset's lawful provenance) is
205
+ correct. That judgement stays with the producer and their reviewers.
206
+
207
+ > **One sentence:** `verify-vh` tells you the bytes are unchanged and which key signed them — **not
208
+ > when, and not whether the producer's conclusion is true.**
209
+
210
+ ---
211
+
212
+ ## 4. Worked example: `producer seals → hands over packet → counterparty runs verify-vh`
213
+
214
+ A real end-to-end run (test-only ephemeral keys — never a real key or real funds; the same path the
215
+ acceptance suite [`../test/verifier.cli.test.js`](../test/verifier.cli.test.js) exercises against the
216
+ REAL producer code).
217
+
218
+ **(a) Producer side.** With their paid evidence tool, the producer seals a directory and publishes
219
+ their signer address (`0xb463…3221`) somewhere the counterparty trusts (contract, site, email sig):
220
+
221
+ ```
222
+ data/
223
+ model-card.md
224
+ weights.bin
225
+ packet.vhevidence.json # the signed seal, handed to the counterparty alongside the files
226
+ ```
227
+
228
+ **(b) Hand-over.** The counterparty receives the three files. No producer software, account, or license
229
+ is involved.
230
+
231
+ **(c) Counterparty verifies** (one runtime dependency, no network):
232
+
233
+ ```bash
234
+ cd verifier && npm install
235
+ node verify-vh.js ../data/packet.vhevidence.json \
236
+ --vendor 0xb463f30cf53d1e0365130363ae9b9867998c3221
237
+ ```
238
+
239
+ Accepted output (exit `0`):
240
+
241
+ ```
242
+ kind: vh.evidence-seal-signed
243
+ embedded kind: vh.evidence-seal
244
+ signed: yes
245
+ recovered signer: 0xb463f30cf53d1e0365130363ae9b9867998c3221
246
+ claimed signer: 0xb463f30cf53d1e0365130363ae9b9867998c3221
247
+ pinned --vendor: 0xb463f30cf53d1e0365130363ae9b9867998c3221
248
+ signer matches vendor: yes
249
+ sealed root: 0x51004f29ea5b0081be2943d377b2c1572b0543af4bfea724642fa73db3589dd5
250
+ recomputed root: 0x51004f29ea5b0081be2943d377b2c1572b0543af4bfea724642fa73db3589dd5
251
+ root matches: yes
252
+ files: 2 matched, 0 changed, 0 missing, 0 rejected, 0 unexpected
253
+
254
+ OK — the artifact verifies.
255
+ ```
256
+
257
+ `--json` returns a stable verdict object — e.g.
258
+ `{"verdict":"OK","reason":"OK","accepted":true,"rootMatches":true,"signerMatchesVendor":true,"counts":{"matched":2,"changed":0,"missing":0,"escaped":0,"unexpected":0}}`
259
+ — so a counterparty's CI can gate on `accepted`.
260
+
261
+ **(d) The rejections you should be able to reproduce** (each a clean exit `3`, never a crash):
262
+
263
+ | you change | verdict | exit |
264
+ |------------|---------|------|
265
+ | any sealed byte (`echo x >> model-card.md`) | `CHANGED` (names the file, sealed vs on-disk hash) | 3 |
266
+ | a sealed file is absent | `MISSING` | 3 |
267
+ | pass a `--vendor` that is not the signer | `wrong_issuer` | 3 |
268
+ | corrupt the embedded signature | `bad_signature` | 3 |
269
+
270
+ ---
271
+
272
+ ## 4a. Batch / manifest mode — gate a whole release in one invocation
273
+
274
+ A release is rarely one artifact. To make `verify-vh` a wired-in CI **merge gate** rather than a one-off
275
+ demo, a single invocation can verify **every** artifact a release produces and return **one** exit code.
276
+
277
+ **Two ways to name the set:**
278
+
279
+ ```bash
280
+ # (i) repeated positionals — each inherits the one top-level --vendor/--dir:
281
+ verify-vh a.vhevidence.json b.vhseal --vendor 0xADDR --dir ./out
282
+
283
+ # (ii) a manifest file — each entry carries its OWN optional --vendor/--dir:
284
+ verify-vh --manifest release.manifest [--vendor 0xADDR] [--dir <d>] [--json]
285
+ ```
286
+
287
+ **Manifest format.** A manifest is EITHER a **newline list** OR a **JSON array**:
288
+
289
+ - *Newline form* — one entry per line; blank lines and lines beginning with `#` are skipped. A line is an
290
+ artifact path followed by optional `--vendor <0xaddr>` / `--dir <d>` tokens:
291
+ ```text
292
+ # 2026-Q2 release
293
+ datasets/march.vhevidence.json --vendor 0xb463…3221 --dir datasets/march
294
+ recon/q2.vhseal --vendor 0xb463…3221
295
+ proofs/claim-7.vhproof.json
296
+ ```
297
+ - *JSON form* — an array of strings and/or `{ "artifact": "...", "vendor"?: "0x...", "dir"?: "..." }`
298
+ objects.
299
+
300
+ Artifact paths resolve relative to the **manifest file's own directory** (a release ships its manifest
301
+ alongside its artifacts). A per-entry `--dir` likewise resolves against the manifest directory and
302
+ localizes where THAT artifact's sibling files are read. A **top-level** `--vendor`/`--dir` is a
303
+ **default** every entry inherits unless the entry overrides it. The manifest is parsed in-process; it
304
+ introduces **no new crypto and no network** — it is a list, and nothing more.
305
+
306
+ **Aggregate exit contract** (the existing `0/3/2/1` codes, now over the whole set):
307
+
308
+ | exit | when |
309
+ |------|------|
310
+ | `0` (OK) | **and only if** EVERY artifact verifies (each `accepted`) |
311
+ | `3` (REJECTED) | ANY artifact is rejected — the report names WHICH artifact failed and why (per-entry `reason`) |
312
+ | `2` (USAGE) | a bad flag, a malformed per-entry `--vendor`, an empty manifest, or `--manifest` passed together with a positional artifact |
313
+ | `1` (IO) | the manifest itself, or any listed artifact, is unreadable / not the expected shape |
314
+
315
+ Usage and IO faults are evaluated per entry and **short-circuit** the whole run with the matching code:
316
+ a release gate must never report `ok` while one of its artifacts could not even be read or parsed.
317
+
318
+ **Stable `--json` aggregate.** With `--json`, batch mode emits one object:
319
+
320
+ ```json
321
+ { "ok": false, "total": 3, "passed": 2, "failed": 1, "results": [ /* …per-artifact… */ ] }
322
+ ```
323
+
324
+ Each element of `results[]` is **byte-identical in shape** to the single-artifact `--json` verdict object
325
+ (§4) — the SAME core (`verifyArtifact`) verifies every entry, so the per-artifact body cannot drift from
326
+ the single path. Gate your CI on `ok` (or the exit code). Every entry preserves the same per-entry
327
+ path-escape / no-network guarantees (§3, §5) as a lone verify. The **single-artifact** invocation
328
+ (`verify-vh <artifact>`) is a strict subset and is unchanged: a lone positional emits the single-artifact
329
+ object, not an aggregate.
330
+
331
+ ---
332
+
333
+ ## 4b. A copy-paste CI merge gate — wire it into the partner's pipeline
334
+
335
+ Batch mode answers the most common B2B adoption question — *"how do I make my pipeline AUTOMATICALLY
336
+ reject a tampered/forged artifact on every merge?"* — only once it is actually **wired into CI**. Two
337
+ shipped snippets do that with a single paste, and both install **only** the standalone verifier
338
+ (`js-sha3`, never the producer's ethers/hardhat stack):
339
+
340
+ - **[`../verifier/ci/verify-vh.generic.sh`](../verifier/ci/verify-vh.generic.sh)** — a portable `set -e`
341
+ shell gate for GitLab CI / CircleCI / Jenkins / a Makefile / a git hook. Configured purely by env vars
342
+ (`VH_VENDOR`, plus `VH_MANIFEST` *or* `VH_ARTIFACTS`, optional `VH_DIR`), it runs `verify-vh` over the
343
+ release and passes the `0/3/2/1` exit code straight through, so any non-zero verdict **fails the job**.
344
+ - **[`../verifier/ci/verify-vh.github-actions.yml`](../verifier/ci/verify-vh.github-actions.yml)** — a
345
+ GitHub Actions workflow dropped at `.github/workflows/verify-vh.yml` that gates every push / pull
346
+ request.
347
+
348
+ These are shipped **examples the loop never executes**. To prevent doc-rot, their exact gate command is
349
+ mechanically extracted and run by [`../test/verifier.ci-snippet.test.js`](../test/verifier.ci-snippet.test.js):
350
+ the shipped command MUST exit `0` on a good release and `3` on a tampered one. A snippet a partner copies
351
+ is therefore known-good, not aspirational — the gate that converts a one-off pilot into a wired-in
352
+ renewal.
353
+
354
+ ---
355
+
356
+ ## 5. Why you can trust the verifier itself (proven, not promised)
357
+
358
+ Independence is enforced by [`../test/verifier.isolation.test.js`](../test/verifier.isolation.test.js),
359
+ which is part of the standard `npx hardhat test` suite:
360
+
361
+ - **No producer stack / no back-edge.** It statically greps **every** `require(` across the whole
362
+ `verifier/` tree and asserts none resolves to `ethers`, `hardhat`, `@nomicfoundation/*`, or anything
363
+ under `cli/` or `trustledger/`. The only runtime dependency is `js-sha3`.
364
+ - **No network.** It runs a real verify and asserts the process opens **no socket or network handle**,
365
+ and that the source never `require`s `http`/`https`/`net`/`dns`/`tls`. The tool cannot phone home —
366
+ it has nothing to phone home with.
367
+ - **Read-only.** Holds no key, writes nothing, leaves the working tree untouched
368
+ ([`../test/verifier.cli.test.js`](../test/verifier.cli.test.js)).
369
+ - **No silent crypto drift.** The vendored secp256k1 recovery is cross-checked against the production
370
+ `ethers` recovery ([`../test/verifier.crypto.test.js`](../test/verifier.crypto.test.js)).
371
+
372
+ This is what lets a counterparty audit the verifier in an afternoon and then rely on its verdict
373
+ without relying on us.
374
+
375
+
376
+ ---
377
+ <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>