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.
- package/LICENSE +201 -0
- package/README.md +883 -0
- package/cli/abi/ContributionRegistry.json +881 -0
- package/cli/agent.js +2173 -0
- package/cli/anchor-artifact.js +853 -0
- package/cli/anchor.js +400 -0
- package/cli/claim.js +881 -0
- package/cli/core/agent-commit.js +448 -0
- package/cli/core/agent-session.js +598 -0
- package/cli/core/anchor-binding.js +663 -0
- package/cli/core/attestation.js +580 -0
- package/cli/core/evidence-plans.js +495 -0
- package/cli/core/fixtures/evidence-plans/baseline.json +19 -0
- package/cli/core/fulfill-intake.js +1082 -0
- package/cli/core/go-live-preflight.js +481 -0
- package/cli/core/license.js +534 -0
- package/cli/core/manifest.js +243 -0
- package/cli/core/packetseal.js +591 -0
- package/cli/core/registryArtifact.js +49 -0
- package/cli/core/revocation.js +539 -0
- package/cli/core/rfc3161.js +389 -0
- package/cli/core/timestamp.js +482 -0
- package/cli/core/trust-asof.js +479 -0
- package/cli/dataset.js +2950 -0
- package/cli/evidence.js +2227 -0
- package/cli/fulfill-webhook-http.js +438 -0
- package/cli/git.js +220 -0
- package/cli/hash.js +550 -0
- package/cli/identity.js +1072 -0
- package/cli/journal-cli.js +1110 -0
- package/cli/journal-log.js +454 -0
- package/cli/journal.js +334 -0
- package/cli/lineage.js +447 -0
- package/cli/list.js +287 -0
- package/cli/parcel.js +1509 -0
- package/cli/proof.js +578 -0
- package/cli/prove.js +300 -0
- package/cli/receipt.js +631 -0
- package/cli/registry.js +331 -0
- package/cli/reputation.js +344 -0
- package/cli/revocation.js +495 -0
- package/cli/serve-verify-http.js +298 -0
- package/cli/serve-verify.js +333 -0
- package/cli/show.js +339 -0
- package/cli/verify.js +383 -0
- package/cli/vh.js +3927 -0
- package/docs/ADOPT.md +183 -0
- package/docs/ADOPTION.json +11 -0
- package/docs/AGENTTRACE.md +247 -0
- package/docs/ANCHORING.md +167 -0
- package/docs/AUDIT.md +55 -0
- package/docs/CONFORMANCE.md +107 -0
- package/docs/DATALEDGER.md +638 -0
- package/docs/DECIDE.md +47 -0
- package/docs/DECISIONS-PENDING.md +27 -0
- package/docs/DEPLOY-PUBLIC-SITE.md +301 -0
- package/docs/ENGINE-LEDGER.json +12 -0
- package/docs/EVIDENCE.md +519 -0
- package/docs/GO-LIVE.md +66 -0
- package/docs/IDENTITY.md +123 -0
- package/docs/INDEPENDENT-VERIFICATION.md +377 -0
- package/docs/INTEGRITY-JOURNAL.md +337 -0
- package/docs/KEY-LIFECYCLE.md +179 -0
- package/docs/LICENSING.md +46 -0
- package/docs/LINEAGE.md +307 -0
- package/docs/LOOP-AUDIT-2026-07-03.json +580 -0
- package/docs/LOOP-HARDENING-PLAN.md +44 -0
- package/docs/MERKLE-LEAVES.md +113 -0
- package/docs/METRICS.jsonl +31 -0
- package/docs/MORNING.md +204 -0
- package/docs/PILOT.md +444 -0
- package/docs/PROOFPARCEL.md +227 -0
- package/docs/PROOFS.md +262 -0
- package/docs/RECEIPTS.md +341 -0
- package/docs/REPUTATION.md +158 -0
- package/docs/SDK.md +301 -0
- package/docs/STRATEGY-ARCHIVE.md +5055 -0
- package/docs/SUPERVISOR-RUNBOOK.md +52 -0
- package/docs/TRUST-BOUNDARIES.md +335 -0
- package/docs/TRUSTLEDGER.md +1976 -0
- package/docs/USAGE-BUDGET.json +121 -0
- package/docs/VERIFY-SERVICE.md +168 -0
- package/index.js +160 -0
- package/package.json +41 -0
- package/trustledger/build-standalone.js +796 -0
- package/trustledger/cli.js +3179 -0
- package/trustledger/close.js +391 -0
- package/trustledger/corpus.js +159 -0
- package/trustledger/dist/BUILD-PROVENANCE.json +99 -0
- package/trustledger/dist/trustledger-standalone.html +6197 -0
- package/trustledger/dist/trustledger-standalone.html.sha256 +1 -0
- package/trustledger/door-core.js +442 -0
- package/trustledger/fixtures/bank.csv +7 -0
- package/trustledger/fixtures/bank.malformed.csv +3 -0
- package/trustledger/fixtures/bank.noalias.csv +5 -0
- package/trustledger/fixtures/bank.ofx +34 -0
- package/trustledger/fixtures/bank.real.csv +5 -0
- package/trustledger/fixtures/corpus/_shared/prior-close.json +22 -0
- package/trustledger/fixtures/corpus/bank-book-mismatch--benign-twin/inputs.json +14 -0
- package/trustledger/fixtures/corpus/bank-book-mismatch--benign-twin/meta.json +7 -0
- package/trustledger/fixtures/corpus/bank-book-mismatch--out-of-trust/inputs.json +14 -0
- package/trustledger/fixtures/corpus/bank-book-mismatch--out-of-trust/meta.json +7 -0
- package/trustledger/fixtures/corpus/continuity-break--benign-twin/inputs.json +15 -0
- package/trustledger/fixtures/corpus/continuity-break--benign-twin/meta.json +7 -0
- package/trustledger/fixtures/corpus/continuity-break--out-of-trust/inputs.json +15 -0
- package/trustledger/fixtures/corpus/continuity-break--out-of-trust/meta.json +7 -0
- package/trustledger/fixtures/corpus/negative-tenant-ledger--benign-twin/inputs.json +13 -0
- package/trustledger/fixtures/corpus/negative-tenant-ledger--benign-twin/meta.json +7 -0
- package/trustledger/fixtures/corpus/negative-tenant-ledger--out-of-trust/inputs.json +13 -0
- package/trustledger/fixtures/corpus/negative-tenant-ledger--out-of-trust/meta.json +7 -0
- package/trustledger/fixtures/corpus/owner-overdraw--benign-twin/inputs.json +15 -0
- package/trustledger/fixtures/corpus/owner-overdraw--benign-twin/meta.json +7 -0
- package/trustledger/fixtures/corpus/owner-overdraw--out-of-trust/inputs.json +15 -0
- package/trustledger/fixtures/corpus/owner-overdraw--out-of-trust/meta.json +7 -0
- package/trustledger/fixtures/corpus/security-deposit-segregation--benign-twin/inputs.json +16 -0
- package/trustledger/fixtures/corpus/security-deposit-segregation--benign-twin/meta.json +7 -0
- package/trustledger/fixtures/corpus/security-deposit-segregation--out-of-trust/inputs.json +13 -0
- package/trustledger/fixtures/corpus/security-deposit-segregation--out-of-trust/meta.json +7 -0
- package/trustledger/fixtures/corpus/subledger-out-of-balance--benign-twin/inputs.json +13 -0
- package/trustledger/fixtures/corpus/subledger-out-of-balance--benign-twin/meta.json +7 -0
- package/trustledger/fixtures/corpus/subledger-out-of-balance--out-of-trust/inputs.json +13 -0
- package/trustledger/fixtures/corpus/subledger-out-of-balance--out-of-trust/meta.json +7 -0
- package/trustledger/fixtures/e2e/bank.aliased.csv +4 -0
- package/trustledger/fixtures/e2e/bank.csv +4 -0
- package/trustledger/fixtures/e2e/bank.nsf.csv +4 -0
- package/trustledger/fixtures/e2e/quickbooks.csv +6 -0
- package/trustledger/fixtures/e2e/quickbooks.nsf.csv +8 -0
- package/trustledger/fixtures/e2e/rentroll.csv +6 -0
- package/trustledger/fixtures/e2e/rentroll.nsf.csv +8 -0
- package/trustledger/fixtures/e2e/rentroll.short.csv +5 -0
- package/trustledger/fixtures/plans/baseline.json +25 -0
- package/trustledger/fixtures/plans/price-binding.example.json +27 -0
- package/trustledger/fixtures/policy/ambiguous-deposit-example.json +12 -0
- package/trustledger/fixtures/policy/baseline.json +19 -0
- package/trustledger/fixtures/policy/ca-example.json +12 -0
- package/trustledger/fixtures/policy/negative-tenant-ledger-example.json +12 -0
- package/trustledger/fixtures/policy/owner-overdraw-example.json +12 -0
- package/trustledger/fixtures/quickbooks.csv +7 -0
- package/trustledger/fixtures/quickbooks.real.csv +5 -0
- package/trustledger/fixtures/rentroll.csv +6 -0
- package/trustledger/fixtures/rentroll.real.csv +4 -0
- package/trustledger/ingest.js +1163 -0
- package/trustledger/lib/policy-bundled-loader.js +44 -0
- package/trustledger/lib/sha256-vendored.js +227 -0
- package/trustledger/license.js +563 -0
- package/trustledger/match.js +551 -0
- package/trustledger/plans.js +551 -0
- package/trustledger/policy.js +398 -0
- package/trustledger/public/index.html +512 -0
- package/trustledger/reconcile.js +1486 -0
- package/trustledger/report.js +887 -0
- package/trustledger/seal.js +854 -0
- package/trustledger/server.js +391 -0
- package/trustledger/valueproof.js +350 -0
package/docs/AUDIT.md
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Security audit — ContributionRegistry (run wmkm6kzoj, 2026-06-23)
|
|
2
|
+
|
|
3
|
+
8-lens adversarial audit + 3-judge verification panel. **21 raw → 10 confirmed, 13 dismissed.**
|
|
4
|
+
Full machine output: `tasks/wmkm6kzoj.output`.
|
|
5
|
+
|
|
6
|
+
## Staged Epic-0 fix-tasks (fold into BACKLOG.md when no driver run is active)
|
|
7
|
+
|
|
8
|
+
Append these under `## EPIC-0` in BACKLOG.md, then relaunch the driver.
|
|
9
|
+
|
|
10
|
+
- **T-0.1** `TODO` Domain-separate Merkle leaves from internal nodes (verifyLeaf + CLI). deps: none. files: contracts/ContributionRegistry.sol, cli/hash.js, test/
|
|
11
|
+
- Acceptance: leaves are domain-separated so a crafted internal node can NOT be verified as a leaf
|
|
12
|
+
(double-hash leaves per OZ StandardMerkleTree, or tag the hash); `cli/hash.js` and the JS Merkle
|
|
13
|
+
helper in tests use the identical convention; a test constructs a second-preimage forgery attempt and
|
|
14
|
+
asserts `verifyLeaf` rejects it; full suite green. *(Covers F15, F11, F13. → Builder, `contract` persona.)*
|
|
15
|
+
|
|
16
|
+
- **T-0.2** `TODO` Bind file paths into repo Merkle leaves. deps: T-0.1. files: cli/hash.js, test/
|
|
17
|
+
- Acceptance: directory leaf = `keccak256(domainPrefix ‖ relPath ‖ 0x00 ‖ keccak256(content))`; the root
|
|
18
|
+
commits to names+content so renaming/moving a file changes the root; docs state exactly what the root
|
|
19
|
+
commits to; a test proves a rename is detected; suite green. *(Covers C2. → Builder, `cli` persona.)*
|
|
20
|
+
|
|
21
|
+
- **T-0.3** `TODO needs-decision` Resolve attribution front-running (this IS decision D-1). deps: none. files: contracts/, cli/, test/
|
|
22
|
+
- Engineering fork for the Decider. Options: (a) **commit–reveal** — `commit(keccak256(contentHash, msg.sender, salt))`
|
|
23
|
+
then `reveal(contentHash, salt)` after N blocks (audit's recommendation; defeats both theft and griefing);
|
|
24
|
+
(b) **per-author namespacing** — key records by `keccak256(addr, contentHash)`; (c) **accept + document** —
|
|
25
|
+
redefine `contributor` as "first anchorer", drop authorship claims from spec/NatSpec.
|
|
26
|
+
- Acceptance (after the Decider chooses): chosen scheme implemented; a test proves a front-runner who copies
|
|
27
|
+
the mempool value canNOT become the recorded author (or, for option c, NatSpec/README/spec updated to drop
|
|
28
|
+
authorship claims + a test asserting only the weaker "existed by block N" guarantee); suite green.
|
|
29
|
+
*(Covers F4, F14, F2, F5. → Decider, then Builder.)*
|
|
30
|
+
|
|
31
|
+
- **T-0.4** `TODO` Document trust boundaries: uri + timestamp. deps: none. files: contracts/ (NatSpec), README/docs
|
|
32
|
+
- Acceptance: NatSpec + docs state plainly that `uri` is an untrusted hint (consumers must re-derive and
|
|
33
|
+
re-hash content) and that `timestamp`/`blockNumber` prove on-chain ordering and an upper bound on existence
|
|
34
|
+
time, NOT authorship time and not validator-proof. *(Covers F17, C3. → Builder, docs.)*
|
|
35
|
+
|
|
36
|
+
## Confirmed findings (record)
|
|
37
|
+
|
|
38
|
+
| ID | Sev | Panel | Title |
|
|
39
|
+
|----|-----|-------|-------|
|
|
40
|
+
| F4 | High | 3/3 | anchor() front-runnable — permanent mis-attribution |
|
|
41
|
+
| F14 | High | 3/3 | attribution = first broadcaster, not author |
|
|
42
|
+
| F15 | High | 3/3 | verifyLeaf accepts internal nodes as leaves (second-preimage) |
|
|
43
|
+
| F2 | Med | 3/3 | no msg.sender↔content binding → authorship squatting |
|
|
44
|
+
| F5 | Med | 3/3 | griefing pre-emption with poisoned uri |
|
|
45
|
+
| F11 | Med | 2/3 | verifyLeaf no leaf/node domain separation |
|
|
46
|
+
| F17 | Med | 3/3 | uri unauthenticated, unbound, fixed by first anchorer |
|
|
47
|
+
| C2 | Med | 3/3 | Merkle root doesn't commit to file names/paths |
|
|
48
|
+
| C3 | Low | 2/3 | timestamp/blockNumber validator-influenced, not trustworthy time |
|
|
49
|
+
| F13 | Info | 2/3 | sorted-pair odd-node promotion enables ambiguous tree shapes |
|
|
50
|
+
|
|
51
|
+
## Dismissed (13 — panel refuted as non-issues / accepted risk)
|
|
52
|
+
|
|
53
|
+
No reentrancy surface · ownerless-immutable remediation gap · verifyLeaf no MEV (pure) · unbounded proof loop
|
|
54
|
+
(self-paid) · unbounded uri (self-paid) · uint64 truncation · `unchecked total+1` sound · empty-proof leaf==root
|
|
55
|
+
(×2) · Anchored event omits blockNumber (×2) · evmVersion not pinned · incomplete NatSpec.
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# The adversarial conformance corpus — what "REJECT" actually proves
|
|
2
|
+
|
|
3
|
+
> **Audience: a buyer's security/procurement reviewer asking "how do I know your verifier won't say
|
|
4
|
+
> ACCEPT on something it shouldn't?"** This page answers that mechanically, with a runnable gate and a
|
|
5
|
+
> precise statement of what it proves — and what it does **not**. It trusts nothing but `node` and the
|
|
6
|
+
> bytes committed in this repo.
|
|
7
|
+
|
|
8
|
+
The 60-second [`../challenge/`](../challenge/) kit proves a verifier catches **one** byte-edit on **one**
|
|
9
|
+
packet. The conformance corpus raises that to the real question: *does the verifier catch the OTHER ways
|
|
10
|
+
an artifact gets poisoned, across the kinds of packets a business actually seals?* It commits one CLEAN
|
|
11
|
+
business packet per vertical plus exactly one POISONED packet per tamper class, and drives every shipped
|
|
12
|
+
evidence-seal verifier of the UNSIGNED content-integrity seal against every poisoned artifact in one
|
|
13
|
+
read-only command.
|
|
14
|
+
|
|
15
|
+
## Run it
|
|
16
|
+
|
|
17
|
+
```sh
|
|
18
|
+
node challenge/corpus/run-corpus.js # human report; exit 0 = PASS, 1 = FAIL
|
|
19
|
+
node challenge/corpus/run-corpus.js --json # stable, machine-readable result on stdout
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
[`challenge/corpus/run-corpus.js`](../challenge/corpus/run-corpus.js) is the **self-auditing conformance
|
|
23
|
+
runner**: it drives every shipped verifier of the unsigned content-integrity seal — the producer's own
|
|
24
|
+
`vh evidence verify` **and** the two INDEPENDENT offline verifiers a counterparty actually runs (the
|
|
25
|
+
single-file standalone bundle
|
|
26
|
+
[`../verifier/dist/verify-vh-standalone.js`](../verifier/dist/verify-vh-standalone.js) and the split-tree
|
|
27
|
+
[`../verifier/verify-vh.js`](../verifier/verify-vh.js)) — against EVERY poisoned artifact in the corpus,
|
|
28
|
+
and asserts the one load-bearing safety invariant the whole product rests on:
|
|
29
|
+
|
|
30
|
+
> **NO verifier EVER returns ACCEPT (exit 0) on a poisoned input.**
|
|
31
|
+
|
|
32
|
+
It is a permanent **regression floor**: if any future refactor ever opens a false-ACCEPT hole in ANY
|
|
33
|
+
verifier, this runner goes RED. The gate is proven to have teeth — its companion test injects a synthetic
|
|
34
|
+
verifier that wrongly ACCEPTS a poisoned input and proves the runner then exits 1, naming the offending
|
|
35
|
+
class + verifier (a gate that cannot fail proves nothing).
|
|
36
|
+
|
|
37
|
+
## The honest boundary — what a green run proves, and what it does NOT
|
|
38
|
+
|
|
39
|
+
Be precise about what an all-REJECT run buys you:
|
|
40
|
+
|
|
41
|
+
- **It proves the verifier REJECTs every ENUMERATED tamper class** in the corpus (each clean packet plus
|
|
42
|
+
exactly one documented mutation), **by re-deriving the keccak-256 Merkle root from the bytes you hold**
|
|
43
|
+
— never trusting the seal's own stored hashes. That is mechanical, runnable in seconds, and trusts
|
|
44
|
+
nothing but `node` and the committed bytes.
|
|
45
|
+
- **It does NOT prove the absence of unknown tamper classes.** The corpus is a finite, enumerated
|
|
46
|
+
taxonomy; passing it means the verifier rejects *these* poisonings, not *every conceivable* one. A
|
|
47
|
+
green run is evidence of conformance to the published classes, never a proof that no other attack
|
|
48
|
+
exists.
|
|
49
|
+
- **A REJECT is tamper-evidence, NOT a trusted timestamp.** The verifier re-derives "these are exactly
|
|
50
|
+
those bytes"; it asserts nothing about *when*. A trustworthy "sealed at T" still requires **P-3** (the
|
|
51
|
+
human-owned trust-root — a self-managed signing key, an RFC-3161 timestamp authority, or an on-chain
|
|
52
|
+
anchor; see [`INDEPENDENT-VERIFICATION.md`](INDEPENDENT-VERIFICATION.md) §3 and **P-3** in
|
|
53
|
+
[`../STRATEGY.md`](../STRATEGY.md)). The corpus is the **FREE, UNSIGNED** path, so there is no signer to
|
|
54
|
+
pin here either; a green verdict is not a legal or accounting opinion.
|
|
55
|
+
- **It covers the UNSIGNED content-integrity surface ONLY — it does NOT red-team the signer-pin
|
|
56
|
+
(`--vendor`) path.** Every fixture in the corpus is unsigned, and the taxonomy contains **no
|
|
57
|
+
signature-corruption / signer-substitution / attestation-edit class**. So this corpus exercises only the
|
|
58
|
+
three verifiers of the unsigned content-integrity seal (the standalone bundle, the split-tree
|
|
59
|
+
`verify-vh.js`, and the producer's `vh evidence verify`); it does **not** exercise the signed-verifier
|
|
60
|
+
surface — the standalone verifier's `--vendor` signer-pin, `vh evidence verify-signed`, or the
|
|
61
|
+
attestation verifiers (`vh dataset/parcel verify-attest`, `vh revocation/identity verify`). Signer-pin
|
|
62
|
+
is the page's named **PAID** upgrade; a green corpus run is **not** evidence that the signer-pin path was
|
|
63
|
+
adversarially tested.
|
|
64
|
+
|
|
65
|
+
This is the same trust boundary every verifyhash seal carries — stated once, so the conformance story
|
|
66
|
+
never over-promises.
|
|
67
|
+
|
|
68
|
+
## The enumerated tamper classes
|
|
69
|
+
|
|
70
|
+
The taxonomy is published in [`../challenge/corpus/manifest.json`](../challenge/corpus/manifest.json) and
|
|
71
|
+
regenerated deterministically by [`../challenge/corpus/generate.js`](../challenge/corpus/generate.js).
|
|
72
|
+
Each class is one clean business packet plus exactly one documented mutation the verifier must REJECT.
|
|
73
|
+
`expectedExit` is the standalone verifier's own contract: **3** = REJECTED (tamper: CHANGED / MISSING /
|
|
74
|
+
forged root), **2** = usage (unrecognized seal kind).
|
|
75
|
+
|
|
76
|
+
| class id | vertical | mutation | expected exit |
|
|
77
|
+
|----------|----------|----------|---------------|
|
|
78
|
+
| `finance-amount-edited` | finance | Flip one digit in a ledger credit amount. | 3 |
|
|
79
|
+
| `finance-tie-out-dropped` | finance | Delete the bank tie-out file the seal still references. | 3 |
|
|
80
|
+
| `ai-data-sample-swapped` | ai-data | Relabel one training sample in `samples.jsonl`. | 3 |
|
|
81
|
+
| `ai-data-license-stripped` | ai-data | Truncate `LICENSE.txt` to empty (strip the provenance license). | 3 |
|
|
82
|
+
| `ai-data-file-renamed` | ai-data | Rename a sealed file so the sealed path is now MISSING. | 3 |
|
|
83
|
+
| `software-sbom-injected` | software | Inject an undeclared dependency line into `sbom.json`. | 3 |
|
|
84
|
+
| `software-checksum-edited` | software | Alter one published artifact checksum digit in `checksums.txt`. | 3 |
|
|
85
|
+
| `legal-clause-altered` | legal | Alter the fee amount in `agreement.txt`. | 3 |
|
|
86
|
+
| `legal-signature-page-dropped` | legal | Delete the executed signature page the seal references. | 3 |
|
|
87
|
+
| `seal-root-forged` | finance | Forge the seal's Merkle root (packet bytes untouched); the verifier RE-DERIVES the root and rejects. | 3 |
|
|
88
|
+
| `seal-kind-corrupted` | software | Corrupt the seal's `kind` to an unrecognized value (usage error). | 2 |
|
|
89
|
+
|
|
90
|
+
Adding a new tamper class to the manifest WITHOUT also documenting it here FAILS the build: the docs-rot
|
|
91
|
+
guard [`../test/challenge.corpus.docs.test.js`](../test/challenge.corpus.docs.test.js) cross-checks this
|
|
92
|
+
list against `manifest.json` (every manifest class id appears here, and no id here is stale), so the
|
|
93
|
+
buyer-facing trust story can never silently drift from the taxonomy it claims to cover.
|
|
94
|
+
|
|
95
|
+
## Where this fits
|
|
96
|
+
|
|
97
|
+
- The corpus is reachable from the cold-prospect kit: [`../challenge/README.md`](../challenge/README.md)
|
|
98
|
+
links this conformance step after the one-byte tamper walkthrough.
|
|
99
|
+
- The deeper specification of the verifiers it drives — the exact bytes verified, the no-network posture,
|
|
100
|
+
and how their independence is proven mechanically — is in
|
|
101
|
+
[`INDEPENDENT-VERIFICATION.md`](INDEPENDENT-VERIFICATION.md).
|
|
102
|
+
- The buyer pilot runbook [`PILOT.md`](PILOT.md) points at this corpus as the procurement-grade
|
|
103
|
+
conformance check a security reviewer runs before relying on the verifier.
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
<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>
|