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/cli/prove.js
ADDED
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
// `vh prove <file> --root <repoDir>` — prove a single file belongs to an anchored repo root.
|
|
4
|
+
//
|
|
5
|
+
// The big idea of EPIC-2: you don't have to anchor every file in a repository. You anchor the
|
|
6
|
+
// repo's *Merkle root* once (one hash, one tx) and later prove that any individual file was part
|
|
7
|
+
// of that exact snapshot by producing a Merkle proof — a short list of sibling hashes that folds
|
|
8
|
+
// the file's leaf up to the anchored root. The on-chain `verifyLeaf(root, leaf, proof)` replays
|
|
9
|
+
// that fold with sorted-pair hashing and returns true iff the file really was in the tree.
|
|
10
|
+
//
|
|
11
|
+
// The flow:
|
|
12
|
+
// 1. Hash the repo root directory into its sorted-leaf Merkle root and per-file leaves
|
|
13
|
+
// (cli/hash.js — the exact same construction `vh anchor <dir>` and `verifyLeaf` agree on).
|
|
14
|
+
// 2. Locate <file> within that tree and generate its Merkle proof.
|
|
15
|
+
// 3. Either *print* the proof (`--dry-run`, no key/network) or *check it on-chain*:
|
|
16
|
+
// - read the registry: confirm the root is actually anchored (else there is nothing to
|
|
17
|
+
// prove the file against), then
|
|
18
|
+
// - call `verifyLeaf(root, leaf, proof)` and report ACCEPTED / REJECTED.
|
|
19
|
+
// 4. (Optional) `--anchor` first submits the root via anchor() so a fresh repo can be proven in
|
|
20
|
+
// one shot; this is the only path that needs a signer.
|
|
21
|
+
//
|
|
22
|
+
// Why this is tamper-evident: the leaf is the file's PATH-BOUND digest
|
|
23
|
+
// leaf = keccak256(DIR_LEAF_DOMAIN ++ relPath ++ 0x00 ++ keccak256(file bytes)).
|
|
24
|
+
// Change one byte of the file and keccak256(file bytes) changes; rename the file and relPath
|
|
25
|
+
// changes; either way the leaf changes, so the proof (built for the *original* leaf) no longer
|
|
26
|
+
// folds to the anchored root and `verifyLeaf` returns false. That property is exactly what the
|
|
27
|
+
// test pins down. (The leaf binds the path, so a single file's proof is tied to its location in
|
|
28
|
+
// the repo, not just its bytes.)
|
|
29
|
+
//
|
|
30
|
+
// Split into pure pieces (buildProof) and an on-chain runner (runProve) so the end-to-end test can
|
|
31
|
+
// drive it against a live hardhat node and assert the contract's verifyLeaf verdict directly.
|
|
32
|
+
|
|
33
|
+
const path = require("path");
|
|
34
|
+
const fs = require("fs");
|
|
35
|
+
const { hashDir } = require("./hash");
|
|
36
|
+
const { buildProofArtifact, writeProofArtifact } = require("./proof");
|
|
37
|
+
|
|
38
|
+
const ARTIFACT = require("./core/registryArtifact");
|
|
39
|
+
const ABI = ARTIFACT.abi;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Build (purely, no network) a Merkle proof for `filePath` within the repo at `rootDir`.
|
|
43
|
+
*
|
|
44
|
+
* Returns the directory's anchored root, the file's PATH-BOUND leaf
|
|
45
|
+
* (= keccak256(DIR_LEAF_DOMAIN ++ relPath ++ 0x00 ++ keccak256(file bytes))), and the proof.
|
|
46
|
+
* Replaying `proof` against `leaf` with sorted-pair hashing reproduces `root`, so the proof is
|
|
47
|
+
* exactly what the contract's `verifyLeaf` accepts (it tags the leaf with LEAF_TAG itself). The
|
|
48
|
+
* raw content digest is also returned as `contentHash` for display.
|
|
49
|
+
*
|
|
50
|
+
* @param {object} opts
|
|
51
|
+
* @param {string} opts.file path to a file that must live under rootDir
|
|
52
|
+
* @param {string} opts.rootDir the repository root directory to anchor/prove against
|
|
53
|
+
* @returns {{
|
|
54
|
+
* root: string,
|
|
55
|
+
* leaf: string, // path-bound leaf (what on-chain verifyLeaf consumes)
|
|
56
|
+
* contentHash: string, // bare keccak256(file bytes), for display
|
|
57
|
+
* proof: string[],
|
|
58
|
+
* file: string, // path of the file relative to rootDir (forward slashes)
|
|
59
|
+
* rootDir: string,
|
|
60
|
+
* fileCount: number, // number of files (leaves) in the tree
|
|
61
|
+
* }}
|
|
62
|
+
*/
|
|
63
|
+
function buildProof(opts) {
|
|
64
|
+
const { file, rootDir } = opts;
|
|
65
|
+
if (!file) throw new Error("prove requires a <file>");
|
|
66
|
+
if (!rootDir) {
|
|
67
|
+
throw new Error("no repo root: pass --root <dir> (the repository root to prove against)");
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const rootStat = fs.statSync(rootDir);
|
|
71
|
+
if (!rootStat.isDirectory()) {
|
|
72
|
+
throw new Error(`--root must be a directory (the repo root), got: ${rootDir}`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Resolve the target file to an absolute path and confirm it is a regular file that actually
|
|
76
|
+
// sits *inside* the repo root. Proving a file that isn't in the tree is meaningless, and we want
|
|
77
|
+
// a clear error rather than a confusing "not found in tree" later.
|
|
78
|
+
const absRoot = path.resolve(rootDir);
|
|
79
|
+
const absFile = path.resolve(path.isAbsolute(file) ? file : path.join(absRoot, file));
|
|
80
|
+
const fileStat = fs.statSync(absFile);
|
|
81
|
+
if (!fileStat.isFile()) {
|
|
82
|
+
throw new Error(`not a regular file: ${absFile}`);
|
|
83
|
+
}
|
|
84
|
+
const rel = path.relative(absRoot, absFile);
|
|
85
|
+
if (rel.startsWith("..") || path.isAbsolute(rel)) {
|
|
86
|
+
throw new Error(`file is not inside the repo root: ${absFile} (root: ${absRoot})`);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const { root, leaves, proofFor, leafFor } = hashDir(absRoot);
|
|
90
|
+
// The leaf the contract verifies is the PATH-BOUND digest, not the bare content hash. Look it up
|
|
91
|
+
// (and the proof) by the file's relative path so duplicate-content files still resolve to *this*
|
|
92
|
+
// file's position in the tree. proofFor/leafFor accept a rel path, an absolute path, or a leaf.
|
|
93
|
+
const leaf = leafFor(rel);
|
|
94
|
+
const proof = proofFor(rel);
|
|
95
|
+
// The bare content digest is the matching entry's contentHash; surface it for transparency, and
|
|
96
|
+
// report the file's normalized (forward-slash) relative path.
|
|
97
|
+
const entry = leaves.find((l) => l.leaf === leaf);
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
root,
|
|
101
|
+
leaf,
|
|
102
|
+
contentHash: entry ? entry.contentHash : null,
|
|
103
|
+
proof,
|
|
104
|
+
file: entry ? entry.path : rel,
|
|
105
|
+
rootDir: absRoot,
|
|
106
|
+
fileCount: leaves.length,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Render a built proof as the multi-line block `--dry-run` (and the runner) prints. */
|
|
111
|
+
function formatProof(p, extra) {
|
|
112
|
+
const lines = [
|
|
113
|
+
` repo root dir: ${p.rootDir} (${p.fileCount} files)`,
|
|
114
|
+
` file: ${p.file}`,
|
|
115
|
+
` merkle root: ${p.root}`,
|
|
116
|
+
` content hash: ${p.contentHash}`,
|
|
117
|
+
` leaf (path-bound): ${p.leaf}`,
|
|
118
|
+
` proof (${p.proof.length} sibling${p.proof.length === 1 ? "" : "s"}):`,
|
|
119
|
+
];
|
|
120
|
+
if (p.proof.length === 0) {
|
|
121
|
+
lines.push(" (none — single-file tree: leaf == root)");
|
|
122
|
+
} else {
|
|
123
|
+
for (const h of p.proof) lines.push(` ${h}`);
|
|
124
|
+
}
|
|
125
|
+
if (extra && extra.length) {
|
|
126
|
+
lines.push("");
|
|
127
|
+
for (const l of extra) lines.push(l);
|
|
128
|
+
}
|
|
129
|
+
return lines.join("\n");
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Run the prove command end to end.
|
|
134
|
+
*
|
|
135
|
+
* In `--dry-run` mode it only builds + prints the proof (no key, no network) and returns
|
|
136
|
+
* `{ dryRun: true, ...proof }`.
|
|
137
|
+
*
|
|
138
|
+
* Otherwise it talks to the deployed ContributionRegistry:
|
|
139
|
+
* - If `anchorFirst` is set, it submits the root via anchor() with `signer` (the only path that
|
|
140
|
+
* needs a key), so a brand-new repo can be anchored and proven in one command.
|
|
141
|
+
* - It confirms the root is anchored on-chain (via isAnchored) — there is nothing to prove a
|
|
142
|
+
* file against if the root was never anchored.
|
|
143
|
+
* - It calls `verifyLeaf(root, leaf, proof)` on-chain and reports the contract's verdict.
|
|
144
|
+
*
|
|
145
|
+
* @param {object} opts
|
|
146
|
+
* @param {string} opts.file
|
|
147
|
+
* @param {string} opts.rootDir
|
|
148
|
+
* @param {string} opts.contractAddress
|
|
149
|
+
* @param {object} opts.provider ethers v6 Provider (read-only; required)
|
|
150
|
+
* @param {boolean}[opts.dryRun]
|
|
151
|
+
* @param {boolean}[opts.anchorFirst] anchor the root before proving (needs a signer)
|
|
152
|
+
* @param {object} [opts.signer] ethers Signer (required iff anchorFirst)
|
|
153
|
+
* @param {boolean}[opts.iUnderstandMainnet] forwarded to anchor()'s chainId guard
|
|
154
|
+
* @param {string} [opts.out] write a portable, self-contained proof artifact here
|
|
155
|
+
* (caller-chosen path — never silently the cwd). Works on
|
|
156
|
+
* the no-key `--dry-run`/build path; `vh verify-proof <p>`
|
|
157
|
+
* independently verifies it offline + on-chain.
|
|
158
|
+
* @param {object} [opts.ethers] ethers v6 module
|
|
159
|
+
* @param {(s:string)=>void}[opts.log] sink for human output (defaults to process.stdout)
|
|
160
|
+
* @returns {Promise<object>} result describing what happened
|
|
161
|
+
*/
|
|
162
|
+
async function runProve(opts) {
|
|
163
|
+
const ethersLib = opts.ethers || require("ethers");
|
|
164
|
+
const log = opts.log || ((s) => process.stdout.write(s));
|
|
165
|
+
|
|
166
|
+
const built = buildProof({ file: opts.file, rootDir: opts.rootDir });
|
|
167
|
+
|
|
168
|
+
// The contract rejects a zero hash, and a zero leaf/root means an empty or degenerate tree.
|
|
169
|
+
if (/^0x0{64}$/i.test(built.root)) {
|
|
170
|
+
throw new Error("refusing to prove against the zero root (contract rejects it)");
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (opts.dryRun) {
|
|
174
|
+
// The build path needs no key and no network. If asked, write the portable proof artifact here so
|
|
175
|
+
// a fresh repo's proof can be exported and handed to a third party who only has an RPC URL.
|
|
176
|
+
let artifactPath = null;
|
|
177
|
+
if (opts.out) {
|
|
178
|
+
artifactPath = _writeArtifact(built, opts);
|
|
179
|
+
log(`Wrote portable proof artifact: ${artifactPath}\n`);
|
|
180
|
+
}
|
|
181
|
+
log(formatProof(built) + "\n");
|
|
182
|
+
return { dryRun: true, ...built, out: artifactPath };
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const { contractAddress, provider } = opts;
|
|
186
|
+
if (!contractAddress) {
|
|
187
|
+
throw new Error(
|
|
188
|
+
"no contract address: pass --contract <address> or set VH_CONTRACT in the environment"
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
if (!ethersLib.isAddress(contractAddress)) {
|
|
192
|
+
throw new Error(`invalid contract address: ${contractAddress}`);
|
|
193
|
+
}
|
|
194
|
+
if (!provider) {
|
|
195
|
+
throw new Error("no provider: pass --rpc <url> or set VH_RPC_URL / AMOY_RPC_URL");
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const address = ethersLib.getAddress(contractAddress);
|
|
199
|
+
const readContract = new ethersLib.Contract(address, ABI, provider);
|
|
200
|
+
|
|
201
|
+
// Optionally anchor the root first so a fresh repo can be proven in one shot.
|
|
202
|
+
let anchored = null;
|
|
203
|
+
if (opts.anchorFirst) {
|
|
204
|
+
if (!opts.signer) {
|
|
205
|
+
throw new Error("--anchor needs a signer (set PRIVATE_KEY) to submit the root");
|
|
206
|
+
}
|
|
207
|
+
// Reuse the audited anchor flow (chainId guard, event parsing) instead of re-implementing it.
|
|
208
|
+
const { runAnchor } = require("./anchor");
|
|
209
|
+
const anchorRes = await runAnchor({
|
|
210
|
+
path: built.rootDir,
|
|
211
|
+
contractAddress: address,
|
|
212
|
+
provider,
|
|
213
|
+
signer: opts.signer,
|
|
214
|
+
iUnderstandMainnet: opts.iUnderstandMainnet,
|
|
215
|
+
ethers: ethersLib,
|
|
216
|
+
log: () => {}, // keep prove's own output clean; we summarize below
|
|
217
|
+
});
|
|
218
|
+
anchored = anchorRes.anchored;
|
|
219
|
+
log(`Anchored repo root ${built.root} (${built.fileCount} files).\n`);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// There is nothing to prove a file against if the root was never anchored.
|
|
223
|
+
const rootIsAnchored = await readContract.isAnchored(built.root);
|
|
224
|
+
if (!rootIsAnchored) {
|
|
225
|
+
throw new Error(
|
|
226
|
+
`repo root ${built.root} is not anchored on-chain; ` +
|
|
227
|
+
"anchor it first (`vh anchor <repoDir>` or pass --anchor) before proving a file against it."
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// The acceptance criterion: the *on-chain* verifyLeaf must accept a genuine file's proof.
|
|
232
|
+
const accepted = await readContract.verifyLeaf(built.root, built.leaf, built.proof);
|
|
233
|
+
|
|
234
|
+
// If asked, export the portable proof artifact here too — recording the resolved contract address
|
|
235
|
+
// and chainId so the artifact is fully self-describing for `vh verify-proof`.
|
|
236
|
+
let artifactPath = null;
|
|
237
|
+
if (opts.out) {
|
|
238
|
+
let chainId;
|
|
239
|
+
try {
|
|
240
|
+
const net = await provider.getNetwork();
|
|
241
|
+
chainId = net.chainId;
|
|
242
|
+
} catch (_) {
|
|
243
|
+
chainId = undefined; // chainId is an optional hint; never fail the prove over it
|
|
244
|
+
}
|
|
245
|
+
artifactPath = _writeArtifact(built, opts, { contractAddress: address, chainId });
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const extra = [
|
|
249
|
+
` root anchored: yes`,
|
|
250
|
+
` verifyLeaf: ${accepted ? "ACCEPTED" : "REJECTED"}`,
|
|
251
|
+
];
|
|
252
|
+
if (artifactPath) {
|
|
253
|
+
extra.push(` proof artifact: ${artifactPath} (verify with \`vh verify-proof\`)`);
|
|
254
|
+
}
|
|
255
|
+
if (!accepted) {
|
|
256
|
+
extra.push(
|
|
257
|
+
" The on-chain verifyLeaf rejected this proof: the file does not match the anchored",
|
|
258
|
+
" repo root (it was modified/tampered, or it was not part of the anchored snapshot)."
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
log(formatProof(built, extra) + "\n");
|
|
262
|
+
|
|
263
|
+
return {
|
|
264
|
+
dryRun: false,
|
|
265
|
+
...built,
|
|
266
|
+
contractAddress: address,
|
|
267
|
+
rootIsAnchored: true,
|
|
268
|
+
accepted,
|
|
269
|
+
anchored,
|
|
270
|
+
out: artifactPath,
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Build and write the portable proof artifact for `built` to `opts.out`. Validates the destination
|
|
276
|
+
* path is a non-empty string (an `--out` with no/empty value hard-errors, parser parity with the
|
|
277
|
+
* other commands) and resolves it to an absolute path so the caller always learns the exact file
|
|
278
|
+
* written — never silently dropping it into an ambiguous cwd.
|
|
279
|
+
*
|
|
280
|
+
* @param {object} built a buildProof() result
|
|
281
|
+
* @param {object} opts the runProve opts (carries `out`)
|
|
282
|
+
* @param {object} [ctx] optional on-chain context { contractAddress, chainId } to record
|
|
283
|
+
* @returns {string} the absolute path written
|
|
284
|
+
*/
|
|
285
|
+
function _writeArtifact(built, opts, ctx = {}) {
|
|
286
|
+
if (typeof opts.out !== "string" || opts.out.trim() === "") {
|
|
287
|
+
throw new Error("--out requires a destination file path");
|
|
288
|
+
}
|
|
289
|
+
const outPath = path.resolve(opts.out);
|
|
290
|
+
const artifact = buildProofArtifact(built, ctx);
|
|
291
|
+
writeProofArtifact(artifact, outPath);
|
|
292
|
+
return outPath;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
module.exports = {
|
|
296
|
+
buildProof,
|
|
297
|
+
runProve,
|
|
298
|
+
formatProof,
|
|
299
|
+
ABI,
|
|
300
|
+
};
|