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,96 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# journal.generic.sh — a portable, copy-paste CI CONTINUOUS-INTEGRITY gate built on the `vh journal`
|
|
3
|
+
# append-only, hash-chained integrity journal (T-60.3).
|
|
4
|
+
#
|
|
5
|
+
# WHAT THIS IS
|
|
6
|
+
# Drop this into ANY CI that can run a shell step (GitLab CI, CircleCI, Jenkins, a Makefile recipe, a
|
|
7
|
+
# git pre-push hook, a bare cron box). On every run it APPENDS this build's verify verdict to a STANDING
|
|
8
|
+
# append-only journal, then VERIFIES the whole chain and FAILS THE BUILD (non-zero exit) unless the chain
|
|
9
|
+
# is UNBROKEN and every recorded observation was ACCEPTED. Unlike a one-shot verify (does it match RIGHT
|
|
10
|
+
# NOW?), a green pipeline here MEANS "the artifact has verified CONTINUOUSLY across every recorded run, and
|
|
11
|
+
# the record itself is tamper-evident" — the standing, re-runnable integrity trail a one-shot verify cannot
|
|
12
|
+
# produce.
|
|
13
|
+
#
|
|
14
|
+
# WHY APPEND-THEN-VERIFY (the continuous-integrity shape)
|
|
15
|
+
# `vh journal append` records THIS run's verdict as ONE new hash-chained line — STRICTLY ADDITIVELY (prior
|
|
16
|
+
# lines are never rewritten). Recording a REJECTED verdict is a SUCCESSFUL append (the journal's job is to
|
|
17
|
+
# faithfully record what it saw); the drift then surfaces at `vh journal verify` time. So the gate's verdict
|
|
18
|
+
# is the VERIFY exit code: a tampered artifact records a REJECT this run, and verify reports the chain as
|
|
19
|
+
# DRIFTED (exit 3) — blocking the merge. A deleted / reordered / inserted / hand-edited PAST line BREAKS
|
|
20
|
+
# the chain and verify LOCALIZES the first break (also exit 3).
|
|
21
|
+
#
|
|
22
|
+
# TRUST BOUNDARY (this gate will not let you overclaim)
|
|
23
|
+
# A PASS proves ORDERING + CONTINUITY of the verifier's OWN observations + tamper-evidence of the record.
|
|
24
|
+
# The `ts` on each entry is SELF-ASSERTED (the verifier's own wall clock), NOT a trusted timestamp — so the
|
|
25
|
+
# journal NEVER claims "unaltered since date T" on its own; that claim needs a trust-root that signs/
|
|
26
|
+
# timestamps the `ts` (STRATEGY.md P-3). See docs/INTEGRITY-JOURNAL.md.
|
|
27
|
+
#
|
|
28
|
+
# PERSIST THE JOURNAL BETWEEN RUNS
|
|
29
|
+
# The continuous value comes from the SAME journal file GROWING across runs. Persist VH_JOURNAL between CI
|
|
30
|
+
# runs (a cache, a committed file, or a stored build artifact) so the chain accumulates; a fresh journal
|
|
31
|
+
# each run only ever proves a single observation. `append` creates the file on the first run.
|
|
32
|
+
#
|
|
33
|
+
# DEPENDENCIES
|
|
34
|
+
# The full verifyhash package (from `npm i verifyhash` / `npx vh`, which brings in ethers as a runtime
|
|
35
|
+
# dependency) plus a POSIX shell — NO ADDITIONAL dependencies, no hardhat (a devDep, never installed for
|
|
36
|
+
# consumers), and no network (append/verify are pure-local file ops).
|
|
37
|
+
# INDEPENDENCE NOTE: unlike the ZERO-DEPENDENCY standalone `verify-vh` bundle in verifier/ (which vendors its
|
|
38
|
+
# own keccak and needs no ethers), the `vh journal` gate runs the PRODUCER package. Journal verification is
|
|
39
|
+
# not YET available in the standalone independent verifier, so re-verifying a journal today requires the
|
|
40
|
+
# producer stack. See docs/INTEGRITY-JOURNAL.md ("Independence scope").
|
|
41
|
+
#
|
|
42
|
+
# CONFIGURE VIA ENVIRONMENT (so this file is literal copy-paste; no in-file editing required):
|
|
43
|
+
# VH_BIN the `vh` command (default: vh — on PATH via npm; or a path)
|
|
44
|
+
# VH_JOURNAL path to the append-only journal file (REQUIRED; created on first append)
|
|
45
|
+
# VH_ARTIFACT a *.vhevidence.json seal / signed container to record this run (optional; when unset the
|
|
46
|
+
# gate ONLY re-verifies the standing chain without appending)
|
|
47
|
+
# VH_DIR dir holding the seal's referenced files (optional; defaults to the artifact's own dir)
|
|
48
|
+
# VH_TS a self-asserted timestamp for the entry (optional; defaults to the vh wall clock, ISO)
|
|
49
|
+
#
|
|
50
|
+
# EXIT CODES (passed straight through from `vh journal verify`, so the job status is meaningful):
|
|
51
|
+
# 0 PASS — unbroken chain, every recorded observation ACCEPTED; allow the merge.
|
|
52
|
+
# 3 BROKEN/DRIFTED — the chain was tampered OR a recorded observation was REJECTED; BLOCK the merge.
|
|
53
|
+
# 2 USAGE — misconfiguration (missing VH_JOURNAL / bad flag); never reported as "passed".
|
|
54
|
+
# 1 IO — a file could not be read/written; never reported as "passed".
|
|
55
|
+
#
|
|
56
|
+
# Usage:
|
|
57
|
+
# VH_JOURNAL=./integrity.jsonl VH_ARTIFACT=./dist/release.vhevidence.json ./journal.generic.sh
|
|
58
|
+
set -euo pipefail
|
|
59
|
+
|
|
60
|
+
VH_BIN="${VH_BIN:-vh}"
|
|
61
|
+
|
|
62
|
+
if [ -z "${VH_JOURNAL:-}" ]; then
|
|
63
|
+
echo "journal CI gate: set VH_JOURNAL to the path of the append-only journal file." >&2
|
|
64
|
+
exit 2
|
|
65
|
+
fi
|
|
66
|
+
|
|
67
|
+
# 1) APPEND this run's observation (the normal path). Skipped only when VH_ARTIFACT is unset (re-verify the
|
|
68
|
+
# standing chain without recording a new entry). Recording a REJECT is a SUCCESSFUL append (exit 0) — the
|
|
69
|
+
# drift surfaces at verify time below. A usage/IO failure of append (exit 2/1) is a real gate failure
|
|
70
|
+
# (we could not even record), passed straight through.
|
|
71
|
+
if [ -n "${VH_ARTIFACT:-}" ]; then
|
|
72
|
+
set -- journal append "$VH_ARTIFACT" --to "$VH_JOURNAL"
|
|
73
|
+
if [ -n "${VH_DIR:-}" ]; then set -- "$@" --dir "$VH_DIR"; fi
|
|
74
|
+
if [ -n "${VH_TS:-}" ]; then set -- "$@" --ts "$VH_TS"; fi
|
|
75
|
+
|
|
76
|
+
set +e
|
|
77
|
+
"$VH_BIN" "$@"
|
|
78
|
+
append_code=$?
|
|
79
|
+
set -e
|
|
80
|
+
if [ "$append_code" -ne 0 ]; then
|
|
81
|
+
echo "journal CI gate: append FAILED (exit $append_code) — could not record this run's observation." >&2
|
|
82
|
+
exit "$append_code"
|
|
83
|
+
fi
|
|
84
|
+
fi
|
|
85
|
+
|
|
86
|
+
# 2) VERIFY the whole standing chain. This is the gate's verdict — the SHARED 0/3 verify contract, passed
|
|
87
|
+
# straight through so the CI job status is meaningful.
|
|
88
|
+
set +e
|
|
89
|
+
"$VH_BIN" journal verify "$VH_JOURNAL"
|
|
90
|
+
code=$?
|
|
91
|
+
set -e
|
|
92
|
+
|
|
93
|
+
if [ "$code" -ne 0 ]; then
|
|
94
|
+
echo "journal CI gate: FAILED (exit $code) — the integrity journal is BROKEN or recorded a DRIFT; blocking the merge." >&2
|
|
95
|
+
fi
|
|
96
|
+
exit "$code"
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# journal.github-actions.yml — copy-paste GitHub Actions CONTINUOUS-INTEGRITY gate built on the `vh journal`
|
|
2
|
+
# append-only, hash-chained integrity journal (T-60.3).
|
|
3
|
+
#
|
|
4
|
+
# WHAT THIS IS
|
|
5
|
+
# Drop this file at `.github/workflows/integrity-journal.yml` in the repo that produces sealed verifyhash
|
|
6
|
+
# artifacts. On every push / pull request it APPENDS this build's verify verdict to a STANDING append-only
|
|
7
|
+
# journal, then VERIFIES the whole chain and FAILS the job unless the chain is UNBROKEN and every recorded
|
|
8
|
+
# observation was ACCEPTED. A green check now MEANS "the artifact has verified CONTINUOUSLY across every
|
|
9
|
+
# recorded run, and the record itself is tamper-evident."
|
|
10
|
+
#
|
|
11
|
+
# WHY APPEND-THEN-VERIFY (the continuous-integrity shape, not a one-shot verify)
|
|
12
|
+
# `vh journal append` records THIS run's verdict as ONE new hash-chained line — STRICTLY ADDITIVELY (prior
|
|
13
|
+
# lines are never rewritten). Recording a REJECTED verdict is a SUCCESSFUL append; the drift then surfaces
|
|
14
|
+
# at `vh journal verify` time. So a tampered artifact records a REJECT this run and `vh journal verify`
|
|
15
|
+
# reports the chain as DRIFTED (exit 3) — failing the job. A deleted / reordered / inserted / hand-edited
|
|
16
|
+
# PAST line BREAKS the chain and verify LOCALIZES the first break (also exit 3).
|
|
17
|
+
#
|
|
18
|
+
# PERSIST THE JOURNAL BETWEEN RUNS (where the continuous value lives)
|
|
19
|
+
# The chain only GROWS if the SAME journal file survives across runs. The step below persists it with
|
|
20
|
+
# actions/cache using a ROLLING key (`…-${{ github.run_id }}`) + a `restore-keys` prefix fallback: each run
|
|
21
|
+
# RESTORES the most-recent prior journal and SAVES a fresh immutable cache under its own key, so the chain
|
|
22
|
+
# truly accumulates. A STATIC key would be a trap — GitHub caches are immutable, so a static-key HIT after
|
|
23
|
+
# run 1 SKIPS the post-job save and the journal would silently never grow past its first entry while the job
|
|
24
|
+
# stayed green. Alternatively commit the journal file to the repo or upload/download it as a build artifact.
|
|
25
|
+
# A fresh journal each run only ever proves a single observation. `append` creates the file on the first run.
|
|
26
|
+
#
|
|
27
|
+
# TRUST BOUNDARY: a PASS proves ORDERING + CONTINUITY of the verifier's OWN observations + tamper-evidence of
|
|
28
|
+
# the record. The `ts` on each entry is SELF-ASSERTED (the verifier's own wall clock), NOT a trusted
|
|
29
|
+
# timestamp — the journal NEVER claims "unaltered since date T" on its own; that claim needs a trust-root
|
|
30
|
+
# that signs/timestamps the `ts` (STRATEGY.md P-3). See docs/INTEGRITY-JOURNAL.md.
|
|
31
|
+
#
|
|
32
|
+
# WHAT YOU EDIT (near the top of the run steps):
|
|
33
|
+
# - how the seal ARTIFACT is produced for your release (the SDK's buildSeal/serializeSeal, or your tooling).
|
|
34
|
+
# The example below writes a tiny in-line one. Replace with your real artifacts + their referenced files.
|
|
35
|
+
#
|
|
36
|
+
# NOTE: the loop NEVER runs this file. It is a shipped EXAMPLE. The exact gate (append then verify, exit-code
|
|
37
|
+
# passthrough) is the SAME logic verifier/ci/journal.generic.sh ships, and a test drives that gate against the
|
|
38
|
+
# real `vh journal` command to prove it exits 0 on an unbroken chain and non-zero (3) after a tampered artifact
|
|
39
|
+
# appends a REJECT.
|
|
40
|
+
|
|
41
|
+
name: integrity-journal continuous-integrity gate
|
|
42
|
+
|
|
43
|
+
on:
|
|
44
|
+
push:
|
|
45
|
+
pull_request:
|
|
46
|
+
|
|
47
|
+
jobs:
|
|
48
|
+
integrity-journal:
|
|
49
|
+
runs-on: ubuntu-latest
|
|
50
|
+
steps:
|
|
51
|
+
# 1. Your repo (containing the sealed artifacts + the verifyhash package, or add a download/install
|
|
52
|
+
# step for however your release publishes them).
|
|
53
|
+
- uses: actions/checkout@v4
|
|
54
|
+
|
|
55
|
+
- uses: actions/setup-node@v4
|
|
56
|
+
with:
|
|
57
|
+
node-version: "20"
|
|
58
|
+
|
|
59
|
+
# 2. Install verifyhash (provides the `vh journal` command + the SDK to build the seal artifact).
|
|
60
|
+
- name: Install verifyhash
|
|
61
|
+
run: npm ci || npm install
|
|
62
|
+
|
|
63
|
+
# 3. PERSIST the standing journal across runs so the hash-chain GROWS. Without this the chain restarts
|
|
64
|
+
# every build and can only ever prove a single observation. (Commit the file or use an artifact
|
|
65
|
+
# upload/download instead, if a cache does not fit your flow.)
|
|
66
|
+
# The key MUST rotate per run: `key` embeds ${{ github.run_id }} so every run is a cache MISS and
|
|
67
|
+
# therefore SAVES a new immutable cache; `restore-keys` (a prefix) then RESTORES the most-recent
|
|
68
|
+
# prior journal so the chain accumulates. A static key would HIT every run after the first and,
|
|
69
|
+
# caches being immutable, SKIP the save — leaving the journal frozen at one entry while staying green.
|
|
70
|
+
- name: Restore the standing integrity journal
|
|
71
|
+
uses: actions/cache@v4
|
|
72
|
+
with:
|
|
73
|
+
path: integrity.jsonl
|
|
74
|
+
key: vh-integrity-journal-${{ github.ref_name }}-${{ github.run_id }}
|
|
75
|
+
restore-keys: |
|
|
76
|
+
vh-integrity-journal-${{ github.ref_name }}-
|
|
77
|
+
|
|
78
|
+
# 4. Produce THIS build's seal artifact. Here we build a tiny seal with the SDK and write its referenced
|
|
79
|
+
# file to disk (append RE-DERIVES the root from these bytes). Replace with your real release artifacts.
|
|
80
|
+
- name: Build the seal artifact for this run
|
|
81
|
+
run: |
|
|
82
|
+
mkdir -p dist
|
|
83
|
+
node -e '
|
|
84
|
+
const vh = require("verifyhash");
|
|
85
|
+
const fs = require("fs");
|
|
86
|
+
const path = require("path");
|
|
87
|
+
const entries = [{ relPath: "dist/app.js", bytes: Buffer.from("console.log(1)\n") }];
|
|
88
|
+
for (const e of entries) { fs.mkdirSync(path.dirname(e.relPath), { recursive: true }); fs.writeFileSync(e.relPath, e.bytes); }
|
|
89
|
+
fs.writeFileSync("dist/release.vhevidence.json", vh.serializeSeal(vh.buildSeal(entries)));
|
|
90
|
+
'
|
|
91
|
+
|
|
92
|
+
# 5. THE GATE. APPEND this run's verdict to the standing journal (strictly additive), then VERIFY the
|
|
93
|
+
# whole chain. Exit 0 PASS (unbroken + every observation ACCEPTED) -> the job passes; exit 3 (broken
|
|
94
|
+
# chain OR a recorded REJECT) -> the job FAILS -> the merge is blocked. This is the SAME append-then-
|
|
95
|
+
# verify logic verifier/ci/journal.generic.sh ships; the last command's exit code is the gate verdict.
|
|
96
|
+
- name: Record + verify continuous integrity (fails the build on a broken chain or a recorded drift)
|
|
97
|
+
run: |
|
|
98
|
+
npx vh journal append dist/release.vhevidence.json --to integrity.jsonl --dir .
|
|
99
|
+
npx vh journal verify integrity.jsonl
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# reproduce-vh.generic.sh — a portable, copy-paste CI gate that answers "WHO VERIFIES THE VERIFIER?"
|
|
3
|
+
# on EVERY build, by reproducing the standalone verifier byte-for-byte from the source you audited.
|
|
4
|
+
#
|
|
5
|
+
# WHAT THIS IS (and why it is different from verify-vh.generic.sh)
|
|
6
|
+
# verify-vh.generic.sh gates your SEALS — it fails the build when a sealed artifact is tampered.
|
|
7
|
+
# THIS gate watches the VERIFIER ITSELF. A security/procurement reviewer who audited the standalone
|
|
8
|
+
# verifier once does not want to re-audit it by hand on every release; they want a pinned, automatable
|
|
9
|
+
# control that re-confirms — offline, with no network, trusting no checksum we ship — that the
|
|
10
|
+
# committed `verify-vh-standalone.js` / `seal-vh-standalone.js` bundles, their `.sha256` sidecars, the
|
|
11
|
+
# build-provenance manifest, AND every inlined `lib/*.js` source file STILL reproduce byte-for-byte
|
|
12
|
+
# from the in-tree source they read. The instant a supply-chain swap, a stale bundle, or a one-byte
|
|
13
|
+
# source edit slips in, `node build-standalone.js --check` exits non-zero and THIS gate FAILS THE BUILD.
|
|
14
|
+
#
|
|
15
|
+
# This turns the §0b "reproduce-from-source" answer from a one-time read into a RENEWING dependency:
|
|
16
|
+
# a green pipeline now MEANS "the verifier we depend on is still the source we audited."
|
|
17
|
+
#
|
|
18
|
+
# WHY A SHELL SNIPPET (not just the node call)
|
|
19
|
+
# `set -e` + an explicit exit-code passthrough is the difference between a gate that BLOCKS a merge
|
|
20
|
+
# and a step that prints red but lets the pipeline go green. Any non-zero verdict from `--check`
|
|
21
|
+
# becomes a non-zero exit of this script, which every CI treats as a failed job.
|
|
22
|
+
#
|
|
23
|
+
# DEPENDENCIES
|
|
24
|
+
# Node >= 18 and the in-tree `verifier/` source (build-standalone.js + lib/ + dist/). NOTHING ELSE —
|
|
25
|
+
# no `npm install`, no ethers, no hardhat, no network. `--check` is read-only: it writes nothing.
|
|
26
|
+
#
|
|
27
|
+
# CONFIGURE VIA ENVIRONMENT (so this file is literal copy-paste; no in-file editing required):
|
|
28
|
+
# BUILD_STANDALONE path to build-standalone.js (default: ./verifier/build-standalone.js)
|
|
29
|
+
#
|
|
30
|
+
# EXIT CODES (passed straight through from `--check`, so the job status is meaningful):
|
|
31
|
+
# 0 ALL MATCH — every bundle, sidecar, manifest AND inlined source reproduces; the verifier is the
|
|
32
|
+
# source you audited. Allow the merge.
|
|
33
|
+
# 1 MISMATCH — something does NOT reproduce (the report NAMES the offending bundle/sidecar/manifest
|
|
34
|
+
# or the exact lib/*.js source file); BLOCK the merge and distrust this checkout.
|
|
35
|
+
#
|
|
36
|
+
# Usage:
|
|
37
|
+
# ./reproduce-vh.generic.sh
|
|
38
|
+
# BUILD_STANDALONE=path/to/verifier/build-standalone.js ./reproduce-vh.generic.sh
|
|
39
|
+
set -euo pipefail
|
|
40
|
+
|
|
41
|
+
BUILD_STANDALONE="${BUILD_STANDALONE:-./verifier/build-standalone.js}"
|
|
42
|
+
|
|
43
|
+
if [ ! -f "$BUILD_STANDALONE" ]; then
|
|
44
|
+
echo "reproduce-vh CI gate: build-standalone.js not found at '$BUILD_STANDALONE'." >&2
|
|
45
|
+
echo " Set BUILD_STANDALONE to the path of verifier/build-standalone.js in your checkout." >&2
|
|
46
|
+
exit 1
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
# Run the read-only reproduce-and-attest pass. `set -e` would abort on the non-zero exit before we could
|
|
50
|
+
# echo a clear message, so we capture the code explicitly and pass it through verbatim.
|
|
51
|
+
set +e
|
|
52
|
+
node "$BUILD_STANDALONE" --check
|
|
53
|
+
code=$?
|
|
54
|
+
set -e
|
|
55
|
+
|
|
56
|
+
if [ "$code" -ne 0 ]; then
|
|
57
|
+
echo "reproduce-vh CI gate: FAILED (exit $code) — the verifier does NOT reproduce from source; blocking the merge." >&2
|
|
58
|
+
fi
|
|
59
|
+
exit "$code"
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# reproduce-vh.github-actions.yml — copy-paste GitHub Actions gate that answers "WHO VERIFIES THE
|
|
2
|
+
# VERIFIER?" on every build, by reproducing the standalone verifier byte-for-byte from source.
|
|
3
|
+
#
|
|
4
|
+
# WHAT THIS IS
|
|
5
|
+
# Drop this file at `.github/workflows/reproduce-vh.yml` in the repo that VENDORS the verifyhash
|
|
6
|
+
# `verifier/` tree. On every push / pull request it runs `node verifier/build-standalone.js --check`,
|
|
7
|
+
# which RE-COMPILES each standalone bundle from the in-tree source, recomputes its published checksum
|
|
8
|
+
# and the build-provenance manifest, and cross-checks every inlined lib/*.js source file against the
|
|
9
|
+
# manifest-pinned sha256 — offline, no network, trusting no checksum we ship. If ANY bundle, sidecar,
|
|
10
|
+
# manifest, or source file no longer reproduces (a supply-chain swap, a stale bundle, a one-byte
|
|
11
|
+
# source edit), `--check` exits non-zero and the job FAILS — blocking the merge. A green check now
|
|
12
|
+
# MEANS "the verifier we depend on is still the exact source we audited."
|
|
13
|
+
#
|
|
14
|
+
# This is the verifier-integrity twin of verify-vh.github-actions.yml: that one gates your SEALS;
|
|
15
|
+
# THIS one gates the VERIFIER ITSELF, turning the §0b reproduce-from-source answer into a renewing
|
|
16
|
+
# control instead of a one-time read.
|
|
17
|
+
#
|
|
18
|
+
# WHAT YOU EDIT: nothing — `--check` is self-contained and reads only the committed verifier/ tree.
|
|
19
|
+
# (Needs no `npm install`: it depends on nothing but Node core.)
|
|
20
|
+
#
|
|
21
|
+
# NOTE: the loop NEVER runs this file. It is a shipped EXAMPLE. The exact gate command in the `run:`
|
|
22
|
+
# block below is the SAME command verifier/ci/reproduce-vh.generic.sh runs, and a test extracts and
|
|
23
|
+
# executes it to prove it passes on a clean tree and fails (exit 1) when a source byte is flipped.
|
|
24
|
+
|
|
25
|
+
name: reproduce-vh — who verifies the verifier
|
|
26
|
+
|
|
27
|
+
on:
|
|
28
|
+
push:
|
|
29
|
+
pull_request:
|
|
30
|
+
|
|
31
|
+
jobs:
|
|
32
|
+
reproduce-vh:
|
|
33
|
+
runs-on: ubuntu-latest
|
|
34
|
+
steps:
|
|
35
|
+
# 1. Your repo, containing the vendored `verifier/` tree (build-standalone.js + lib/ + dist/).
|
|
36
|
+
- uses: actions/checkout@v4
|
|
37
|
+
|
|
38
|
+
- uses: actions/setup-node@v4
|
|
39
|
+
with:
|
|
40
|
+
node-version: "20"
|
|
41
|
+
|
|
42
|
+
# 2. THE GATE. No install step: `--check` depends on nothing but Node core. It re-compiles every
|
|
43
|
+
# bundle from source, recomputes every checksum + the manifest, and cross-checks every inlined
|
|
44
|
+
# source file's hash. Exit 0 = ALL MATCH (the verifier is the source you audited); exit 1 =
|
|
45
|
+
# MISMATCH (the report names the offending bundle/sidecar/manifest/source). `node` propagates
|
|
46
|
+
# that exit code, and GitHub Actions fails the job on any non-zero — blocking the merge.
|
|
47
|
+
- name: Reproduce the standalone verifier from source (fails the build on any drift)
|
|
48
|
+
run: |
|
|
49
|
+
node verifier/build-standalone.js --check
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# verify-service.generic.sh — a portable, copy-paste CI MERGE GATE that calls the `vh serve-verify` HTTP
|
|
3
|
+
# endpoint (T-59.3).
|
|
4
|
+
#
|
|
5
|
+
# WHAT THIS IS
|
|
6
|
+
# Drop this into ANY CI that can run a shell step (GitLab CI, CircleCI, Jenkins, a Makefile recipe, a
|
|
7
|
+
# git pre-push hook). Instead of shelling out to the `vh` binary per-artifact, it POSTs a prepared verify
|
|
8
|
+
# REQUEST BODY to a BOOTED `vh serve-verify` service and FAILS THE BUILD (non-zero exit) unless the
|
|
9
|
+
# service answers 200 ACCEPTED. A tampered / forged / unknown seal answers 422 / 400 / 413 and the gate
|
|
10
|
+
# exits non-zero — so the merge is blocked. A green pipeline now MEANS "the verify service ACCEPTED every
|
|
11
|
+
# sealed artifact."
|
|
12
|
+
#
|
|
13
|
+
# WHY A SHELL SNIPPET (not just the curl call)
|
|
14
|
+
# `set -e` + an explicit HTTP-status passthrough is the difference between a gate that BLOCKS a merge and
|
|
15
|
+
# a step that prints red but still lets the pipeline go green. This wrapper makes the failure path the
|
|
16
|
+
# DEFAULT: anything other than 200 ACCEPTED from the service becomes a non-zero exit of this script, which
|
|
17
|
+
# every CI treats as a failed job.
|
|
18
|
+
#
|
|
19
|
+
# HOW THE REQUEST BODY IS PRODUCED (out of band — this gate does not build it)
|
|
20
|
+
# The request body is the exact JSON the service expects (see docs/VERIFY-SERVICE.md), e.g.
|
|
21
|
+
# { "kind": "verify-seal", "seal": <seal-object-or-json-string>,
|
|
22
|
+
# "entries": [ { "relPath": "dist/app.js", "content": "<base64>", "encoding": "base64" } ] }
|
|
23
|
+
# Produce it however your release flow does (the SDK's buildSeal/serializeSeal, or your own tooling) and
|
|
24
|
+
# hand its PATH to this gate. This keeps the gate a pure, dependency-light HTTP client.
|
|
25
|
+
#
|
|
26
|
+
# DEPENDENCIES
|
|
27
|
+
# `curl` and a POSIX shell. NOTHING ELSE — no node, no ethers, no hardhat in the gate itself (the SERVICE
|
|
28
|
+
# is booted separately; see verify-service.github-actions.yml for a full boot+gate recipe).
|
|
29
|
+
#
|
|
30
|
+
# CONFIGURE VIA ENVIRONMENT (so this file is literal copy-paste; no in-file editing required):
|
|
31
|
+
# VH_VERIFY_URL base URL of the booted service (default: http://127.0.0.1:4180)
|
|
32
|
+
# VH_REQUEST path to the verify request-body JSON file (REQUIRED)
|
|
33
|
+
#
|
|
34
|
+
# EXIT CODES (so the job status is meaningful):
|
|
35
|
+
# 0 ACCEPTED — HTTP 200; the service verified the seal; allow the merge.
|
|
36
|
+
# 3 REJECTED — HTTP 422; a well-formed request that did NOT verify (tamper/forge/wrong signer); BLOCK.
|
|
37
|
+
# 2 BAD_REQ — HTTP 400/413; the request itself was malformed/unknown/too large; BLOCK (never a pass).
|
|
38
|
+
# 1 IO — the service was unreachable or the request file was missing; never reported as "passed".
|
|
39
|
+
#
|
|
40
|
+
# Usage:
|
|
41
|
+
# VH_VERIFY_URL=http://127.0.0.1:4180 VH_REQUEST=./verify-request.json ./verify-service.generic.sh
|
|
42
|
+
set -euo pipefail
|
|
43
|
+
|
|
44
|
+
VH_VERIFY_URL="${VH_VERIFY_URL:-http://127.0.0.1:4180}"
|
|
45
|
+
|
|
46
|
+
if [ -z "${VH_REQUEST:-}" ]; then
|
|
47
|
+
echo "verify-service CI gate: set VH_REQUEST to the path of the verify request-body JSON file." >&2
|
|
48
|
+
exit 2
|
|
49
|
+
fi
|
|
50
|
+
if [ ! -f "$VH_REQUEST" ]; then
|
|
51
|
+
echo "verify-service CI gate: request file not found: $VH_REQUEST" >&2
|
|
52
|
+
exit 1
|
|
53
|
+
fi
|
|
54
|
+
|
|
55
|
+
# POST the request body to /verify. `-w` appends the HTTP status on its own trailing line so we can read it
|
|
56
|
+
# WITHOUT a JSON parser; `-s -S` stays quiet but still prints transport errors; `--fail` is NOT used (we want
|
|
57
|
+
# the body + status even on a 4xx so we can report the verdict). A connection failure makes curl exit
|
|
58
|
+
# non-zero, which we map to the IO(1) class rather than a silent pass.
|
|
59
|
+
set +e
|
|
60
|
+
RESPONSE="$(curl -s -S -o - -w $'\n%{http_code}' \
|
|
61
|
+
-H 'content-type: application/json' \
|
|
62
|
+
--data-binary "@${VH_REQUEST}" \
|
|
63
|
+
"${VH_VERIFY_URL%/}/verify")"
|
|
64
|
+
curl_code=$?
|
|
65
|
+
set -e
|
|
66
|
+
|
|
67
|
+
if [ "$curl_code" -ne 0 ]; then
|
|
68
|
+
echo "verify-service CI gate: could not reach ${VH_VERIFY_URL} (curl exit ${curl_code})." >&2
|
|
69
|
+
exit 1
|
|
70
|
+
fi
|
|
71
|
+
|
|
72
|
+
# The last line is the HTTP status; everything before it is the JSON verdict body.
|
|
73
|
+
http_status="$(printf '%s\n' "$RESPONSE" | tail -n1)"
|
|
74
|
+
body="$(printf '%s\n' "$RESPONSE" | sed '$d')"
|
|
75
|
+
|
|
76
|
+
echo "verify-service CI gate: ${VH_VERIFY_URL%/}/verify -> HTTP ${http_status}"
|
|
77
|
+
echo "$body"
|
|
78
|
+
|
|
79
|
+
case "$http_status" in
|
|
80
|
+
200)
|
|
81
|
+
echo "verify-service CI gate: ACCEPTED — allowing the merge." >&2
|
|
82
|
+
exit 0
|
|
83
|
+
;;
|
|
84
|
+
422)
|
|
85
|
+
echo "verify-service CI gate: REJECTED (HTTP 422) — blocking the merge." >&2
|
|
86
|
+
exit 3
|
|
87
|
+
;;
|
|
88
|
+
400 | 413)
|
|
89
|
+
echo "verify-service CI gate: BAD REQUEST (HTTP ${http_status}) — blocking the merge." >&2
|
|
90
|
+
exit 2
|
|
91
|
+
;;
|
|
92
|
+
*)
|
|
93
|
+
echo "verify-service CI gate: unexpected HTTP ${http_status} — blocking the merge." >&2
|
|
94
|
+
exit 1
|
|
95
|
+
;;
|
|
96
|
+
esac
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# verify-service.github-actions.yml — copy-paste GitHub Actions MERGE GATE that calls the `vh serve-verify`
|
|
2
|
+
# HTTP endpoint (T-59.3).
|
|
3
|
+
#
|
|
4
|
+
# WHAT THIS IS
|
|
5
|
+
# Drop this file at `.github/workflows/verify-service.yml` in the repo that produces sealed verifyhash
|
|
6
|
+
# artifacts. On every push / pull request it BOOTS the `vh serve-verify` service (loopback-only, on a
|
|
7
|
+
# fixed port), POSTs a prepared verify REQUEST BODY to it, and FAILS the job unless the service answers
|
|
8
|
+
# 200 ACCEPTED. If the sealed bytes were tampered/forged, the service answers 422 / 400 and the gate
|
|
9
|
+
# exits non-zero — so the merge is blocked. A green check now MEANS "the verify service ACCEPTED the
|
|
10
|
+
# sealed artifact."
|
|
11
|
+
#
|
|
12
|
+
# WHY BOOT-THEN-POST (the "CI plugin that imports rather than shells out")
|
|
13
|
+
# Unlike verify-vh.generic.sh (which runs the standalone binary per artifact), this recipe stands the
|
|
14
|
+
# verifier UP as a SERVICE once and gates by HTTP. That is the drop-in shape another microservice or a
|
|
15
|
+
# fan-out CI matrix consumes: boot once, POST many seals, read ACCEPT/REJECT — no per-artifact process
|
|
16
|
+
# spawn. The service is VERIFY-ONLY (it never signs, holds no key, writes nothing) and binds LOOPBACK.
|
|
17
|
+
#
|
|
18
|
+
# WHAT YOU EDIT (near the top of the run steps):
|
|
19
|
+
# - how the verify REQUEST BODY (verify-request.json) is produced for your release (the SDK's
|
|
20
|
+
# buildSeal/serializeSeal, or your own tooling). The example below writes a tiny in-line one.
|
|
21
|
+
# - the artifacts / checkout for your release flow.
|
|
22
|
+
#
|
|
23
|
+
# NOTE: the loop NEVER runs this file. It is a shipped EXAMPLE. The exact gate command in the `run:` block
|
|
24
|
+
# below is the SAME command verifier/ci/verify-service.generic.sh runs (curl POST -> HTTP-status exit code),
|
|
25
|
+
# and a test boots the real service and executes that gate to prove it passes on a clean seal and fails
|
|
26
|
+
# (exit 3) on a tampered one.
|
|
27
|
+
|
|
28
|
+
name: verify-service merge gate
|
|
29
|
+
|
|
30
|
+
on:
|
|
31
|
+
push:
|
|
32
|
+
pull_request:
|
|
33
|
+
|
|
34
|
+
jobs:
|
|
35
|
+
verify-service:
|
|
36
|
+
runs-on: ubuntu-latest
|
|
37
|
+
steps:
|
|
38
|
+
# 1. Your repo (containing the sealed artifacts + the verifyhash package, or add a download/install
|
|
39
|
+
# step for however your release publishes them).
|
|
40
|
+
- uses: actions/checkout@v4
|
|
41
|
+
|
|
42
|
+
- uses: actions/setup-node@v4
|
|
43
|
+
with:
|
|
44
|
+
node-version: "20"
|
|
45
|
+
|
|
46
|
+
# 2. Install verifyhash (provides the `vh serve-verify` service + the SDK to build the request body).
|
|
47
|
+
- name: Install verifyhash
|
|
48
|
+
run: npm ci || npm install
|
|
49
|
+
|
|
50
|
+
# 3. BOOT the verify service on loopback (a fixed port), in the background. It is verify-only, holds
|
|
51
|
+
# no key, and writes nothing. We wait for /healthz to answer before gating.
|
|
52
|
+
- name: Boot vh serve-verify (loopback-only)
|
|
53
|
+
run: |
|
|
54
|
+
npx vh serve-verify --port 4180 --host 127.0.0.1 &
|
|
55
|
+
for i in $(seq 1 50); do
|
|
56
|
+
if curl -sf http://127.0.0.1:4180/healthz >/dev/null; then break; fi
|
|
57
|
+
sleep 0.2
|
|
58
|
+
done
|
|
59
|
+
curl -sf http://127.0.0.1:4180/healthz
|
|
60
|
+
|
|
61
|
+
# 4. Produce the verify REQUEST BODY for your release. Here we build a tiny seal with the SDK and write
|
|
62
|
+
# the request body the service expects (see docs/VERIFY-SERVICE.md). Replace with your real artifacts.
|
|
63
|
+
- name: Build the verify request body
|
|
64
|
+
run: |
|
|
65
|
+
node -e '
|
|
66
|
+
const vh = require("verifyhash");
|
|
67
|
+
const fs = require("fs");
|
|
68
|
+
const entries = [{ relPath: "dist/app.js", bytes: Buffer.from("console.log(1)\n") }];
|
|
69
|
+
const seal = vh.serializeSeal(vh.buildSeal(entries));
|
|
70
|
+
const wire = entries.map(e => ({ relPath: e.relPath, content: Buffer.from(e.bytes).toString("base64"), encoding: "base64" }));
|
|
71
|
+
fs.writeFileSync("verify-request.json", JSON.stringify({ kind: "verify-seal", seal, entries: wire }));
|
|
72
|
+
'
|
|
73
|
+
|
|
74
|
+
# 5. THE GATE. POST the request body to the booted service; map the HTTP status to an exit code. 200
|
|
75
|
+
# ACCEPTED -> pass; 422 REJECTED / 400 bad request -> non-zero -> the job FAILS -> the merge is
|
|
76
|
+
# blocked. This is the SAME logic verifier/ci/verify-service.generic.sh ships.
|
|
77
|
+
- name: Verify the sealed artifact via the service (fails the build on a bad seal)
|
|
78
|
+
env:
|
|
79
|
+
VH_VERIFY_URL: "http://127.0.0.1:4180"
|
|
80
|
+
VH_REQUEST: "verify-request.json"
|
|
81
|
+
run: |
|
|
82
|
+
status=$(curl -s -o /tmp/vh-verify-body.json -w '%{http_code}' \
|
|
83
|
+
-H 'content-type: application/json' \
|
|
84
|
+
--data-binary "@${VH_REQUEST}" \
|
|
85
|
+
"${VH_VERIFY_URL%/}/verify")
|
|
86
|
+
echo "verify-service gate: HTTP ${status}"
|
|
87
|
+
cat /tmp/vh-verify-body.json
|
|
88
|
+
test "${status}" = "200"
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# verify-vh.generic.sh — a portable, copy-paste CI MERGE GATE for verifyhash artifacts.
|
|
3
|
+
#
|
|
4
|
+
# WHAT THIS IS
|
|
5
|
+
# Drop this into ANY CI that can run a shell step (GitLab CI, CircleCI, Jenkins, a Makefile recipe,
|
|
6
|
+
# a git pre-push hook, a bare cron box). It runs the STANDALONE, OFFLINE `verify-vh` verifier over a
|
|
7
|
+
# release's artifact(s) and FAILS THE BUILD (non-zero exit) the instant any artifact is tampered,
|
|
8
|
+
# forged, or signed by the wrong key. A green pipeline now MEANS "every sealed artifact still matches
|
|
9
|
+
# the bytes the producer signed."
|
|
10
|
+
#
|
|
11
|
+
# WHY A SHELL SNIPPET (not just the node call)
|
|
12
|
+
# `set -e` + an explicit exit-code passthrough is the difference between a gate that BLOCKS a merge
|
|
13
|
+
# and a step that prints red but still lets the pipeline go green. This wrapper makes the failure
|
|
14
|
+
# path the DEFAULT: any non-zero verdict from verify-vh becomes a non-zero exit of this script, which
|
|
15
|
+
# every CI treats as a failed job.
|
|
16
|
+
#
|
|
17
|
+
# DEPENDENCIES
|
|
18
|
+
# Node >= 18 and the standalone `verifier/` tree (verify-vh.js + lib/ + js-sha3). NOTHING ELSE — no
|
|
19
|
+
# ethers, no hardhat, no network. See verifier/README.md to vendor or `npm install` just this tree.
|
|
20
|
+
#
|
|
21
|
+
# CONFIGURE VIA ENVIRONMENT (so this file is literal copy-paste; no in-file editing required):
|
|
22
|
+
# VERIFY_VH path to verify-vh.js (default: ./verifier/verify-vh.js)
|
|
23
|
+
# VH_VENDOR producer signer address 0x..(20B) (REQUIRED — pin who is allowed to have signed)
|
|
24
|
+
# VH_MANIFEST a release manifest file (gate EVERY artifact in one shot; optional)
|
|
25
|
+
# VH_ARTIFACTS space-separated artifact paths (used when VH_MANIFEST is unset)
|
|
26
|
+
# VH_DIR dir holding the referenced files (optional; defaults to each artifact's own dir)
|
|
27
|
+
#
|
|
28
|
+
# EXIT CODES (passed straight through from verify-vh, so the job status is meaningful):
|
|
29
|
+
# 0 OK — every artifact verified; allow the merge.
|
|
30
|
+
# 3 REJECTED — an artifact was tampered/forged/wrong-issuer; BLOCK the merge (report names which).
|
|
31
|
+
# 2 USAGE — misconfiguration (bad flag / bad address / empty manifest).
|
|
32
|
+
# 1 IO — an artifact or the manifest could not be read; never reported as "passed".
|
|
33
|
+
#
|
|
34
|
+
# Usage:
|
|
35
|
+
# VH_VENDOR=0xabc... VH_ARTIFACTS="dist/a.vhevidence.json" ./verify-vh.generic.sh
|
|
36
|
+
# VH_VENDOR=0xabc... VH_MANIFEST=release.manifest ./verify-vh.generic.sh
|
|
37
|
+
set -euo pipefail
|
|
38
|
+
|
|
39
|
+
VERIFY_VH="${VERIFY_VH:-./verifier/verify-vh.js}"
|
|
40
|
+
|
|
41
|
+
if [ -z "${VH_VENDOR:-}" ]; then
|
|
42
|
+
echo "verify-vh CI gate: set VH_VENDOR to the producer's signer address (0x + 20 bytes)." >&2
|
|
43
|
+
exit 2
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
# Build the verify-vh argument list. A MANIFEST gates the whole release in one invocation; otherwise we
|
|
47
|
+
# pass the artifact list positionally. Either way it is ONE call -> ONE exit code the CI gates on.
|
|
48
|
+
set -- # reset positional params we will hand to verify-vh
|
|
49
|
+
if [ -n "${VH_MANIFEST:-}" ]; then
|
|
50
|
+
set -- --manifest "$VH_MANIFEST"
|
|
51
|
+
else
|
|
52
|
+
if [ -z "${VH_ARTIFACTS:-}" ]; then
|
|
53
|
+
echo "verify-vh CI gate: set VH_MANIFEST or VH_ARTIFACTS (the artifact(s) to verify)." >&2
|
|
54
|
+
exit 2
|
|
55
|
+
fi
|
|
56
|
+
# shellcheck disable=SC2086 # word-splitting VH_ARTIFACTS into separate args is intentional.
|
|
57
|
+
set -- $VH_ARTIFACTS
|
|
58
|
+
fi
|
|
59
|
+
|
|
60
|
+
set -- "$@" --vendor "$VH_VENDOR"
|
|
61
|
+
if [ -n "${VH_DIR:-}" ]; then
|
|
62
|
+
set -- "$@" --dir "$VH_DIR"
|
|
63
|
+
fi
|
|
64
|
+
|
|
65
|
+
# Run the gate. `set -e` would abort on the non-zero exit before we could echo a clear message, so we
|
|
66
|
+
# capture the code explicitly and pass it through verbatim — preserving the 0/3/2/1 contract for CI.
|
|
67
|
+
set +e
|
|
68
|
+
node "$VERIFY_VH" "$@"
|
|
69
|
+
code=$?
|
|
70
|
+
set -e
|
|
71
|
+
|
|
72
|
+
if [ "$code" -ne 0 ]; then
|
|
73
|
+
echo "verify-vh CI gate: FAILED (exit $code) — blocking the merge." >&2
|
|
74
|
+
fi
|
|
75
|
+
exit "$code"
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# verify-vh.github-actions.yml — copy-paste GitHub Actions MERGE GATE for verifyhash artifacts.
|
|
2
|
+
#
|
|
3
|
+
# WHAT THIS IS
|
|
4
|
+
# Drop this file at `.github/workflows/verify-vh.yml` in the repo that RECEIVES sealed verifyhash
|
|
5
|
+
# artifacts. On every push / pull request it installs ONLY the standalone, offline `verify-vh`
|
|
6
|
+
# verifier (no ethers, no hardhat, no network) and runs it over your release artifact(s). If any
|
|
7
|
+
# artifact is tampered, forged, or signed by the wrong key, `verify-vh` exits non-zero and the job
|
|
8
|
+
# FAILS — so the merge is blocked. A green check now MEANS "every sealed artifact still matches the
|
|
9
|
+
# bytes the producer signed."
|
|
10
|
+
#
|
|
11
|
+
# WHAT YOU EDIT (three things, all near the top of the run step):
|
|
12
|
+
# - VH_VENDOR : the producer's signer address (0x + 20 bytes), obtained out-of-band.
|
|
13
|
+
# - VH_MANIFEST : a release manifest listing every artifact (gate the whole release in one shot),
|
|
14
|
+
# OR set VH_ARTIFACTS to a space-separated list of artifact paths instead.
|
|
15
|
+
# - the checkout / paths to your artifacts (this example assumes they are committed in the repo;
|
|
16
|
+
# adapt the `actions/checkout` or add an artifact-download step for your release flow).
|
|
17
|
+
#
|
|
18
|
+
# NOTE: the loop NEVER runs this file. It is a shipped EXAMPLE. The exact gate command in the `run:`
|
|
19
|
+
# block below is the SAME command verifier/ci/verify-vh.generic.sh runs, and a test extracts and
|
|
20
|
+
# executes it to prove it passes on good input and fails (exit 3) on tampered input.
|
|
21
|
+
|
|
22
|
+
name: verify-vh merge gate
|
|
23
|
+
|
|
24
|
+
on:
|
|
25
|
+
push:
|
|
26
|
+
pull_request:
|
|
27
|
+
|
|
28
|
+
jobs:
|
|
29
|
+
verify-vh:
|
|
30
|
+
runs-on: ubuntu-latest
|
|
31
|
+
steps:
|
|
32
|
+
# 1. Your repo (containing the sealed artifacts + the vendored `verifier/` tree, or add a
|
|
33
|
+
# download step for however your release publishes them).
|
|
34
|
+
- uses: actions/checkout@v4
|
|
35
|
+
|
|
36
|
+
- uses: actions/setup-node@v4
|
|
37
|
+
with:
|
|
38
|
+
node-version: "20"
|
|
39
|
+
|
|
40
|
+
# 2. Install ONLY the standalone verifier. This pulls js-sha3 and NOTHING else — no ethers,
|
|
41
|
+
# no hardhat, no producer stack. (If you vendor `verifier/` into your repo, you can drop
|
|
42
|
+
# this step entirely; it is self-contained.)
|
|
43
|
+
- name: Install standalone verify-vh (js-sha3 only)
|
|
44
|
+
working-directory: verifier
|
|
45
|
+
run: npm ci --omit=dev || npm install --omit=dev
|
|
46
|
+
|
|
47
|
+
# 3. THE GATE. One invocation, one exit code. verify-vh exits 0 only if EVERY artifact verifies;
|
|
48
|
+
# 3 if any is tampered/forged/wrong-issuer; 2 on misconfig; 1 on IO. `node` propagates that
|
|
49
|
+
# exit code, and GitHub Actions fails the job on any non-zero — blocking the merge.
|
|
50
|
+
- name: Verify sealed artifacts (fails the build on a bad seal)
|
|
51
|
+
env:
|
|
52
|
+
# EDIT THESE for your release:
|
|
53
|
+
VH_VENDOR: "0x0000000000000000000000000000000000000000" # producer signer address (out-of-band)
|
|
54
|
+
VH_MANIFEST: "release.manifest" # or set VH_ARTIFACTS instead
|
|
55
|
+
run: |
|
|
56
|
+
node verifier/verify-vh.js --manifest "$VH_MANIFEST" --vendor "$VH_VENDOR"
|