Package Exports
- @makerchecker/proof-verifier
- @makerchecker/proof-verifier/core
- @makerchecker/proof-verifier/node
Readme
✅ @makerchecker/proof-verifier
Prove a MakerChecker audit bundle is intact and who signed it — offline, trusting nothing but the bytes.
@makerchecker/proof-verifier re-implements verification from the public audit specification alone — it does not import the server, touch a database, or make a network call. Hand it a signed bundle and it tells you whether the hash chain is intact and who signed it. Zero dependencies, fully offline.
[!TIP] Verify a bundle now — no install, no server, nothing leaves your machine:
npx @makerchecker/proof-verifier verify bundle.json
An auditor trusts this verifier because it is not the vendor's server. The code is Apache-2.0 and reproducible; the trust is in the independence.
Why it exists
A MakerChecker deployment exports a signed, hash-chained record of every agent
action (audit export). The value of that record is that anyone can check it
without trusting us. This package is the reference independent checker: a CLI,
an importable isomorphic core, and a single-file web page that runs entirely in
the browser with nothing uploaded.
CLI
npx @makerchecker/proof-verifier verify bundle.json
# pin the instance public key (obtained out of band) so a re-signed bundle is rejected:
npx @makerchecker/proof-verifier verify bundle.json --key instance-pubkey.pem
# pipe it, or get machine-readable output:
cat bundle.json | npx @makerchecker/proof-verifier verify - --jsonExit code 0 = verified, 1 = failed, 2 = usage error.
PASS 8 events, full chain (genesis-rooted)
head 4c5e1a8a2c226d3…b68e02d4474d46c
key sha256:bb48c332…7fc889d (public-key fingerprint, abbreviated)Browser
Open verifier.html in a recent browser, or save it and open
it offline. Drop a bundle in; all hashing and Ed25519 verification happen
locally via WebCrypto. This is the file you can hand a regulator: it keeps
working with no server, no account, and no internet, even after a contract ends.
Library
import { verifyBundle, nodeCrypto } from "@makerchecker/proof-verifier";
const result = await verifyBundle(bundle, nodeCrypto, {
expectedPublicKeyPem, // optional out-of-band key pinning
});
// { ok: true, count, bundleKind, headHash, keyFingerprint }
// | { ok: false, reason, reasonCode?, failedSeq?, path? }The core (@makerchecker/proof-verifier/core) is isomorphic: all cryptography
is injected through a provider, so the same logic runs under Node and in the
browser.
What it checks
Per the audit spec:
- Signature — Ed25519 over the RFC 8785 canonical signing string.
- Count — events match the signed count.
- Hash-set digest — the exact event set and order are bound into the signature.
- Per-event hashes — every event's SHA-256 recomputes (any altered field fails here).
- Linkage — full bundles verify complete genesis-rooted
prev_hashlinkage; run bundles verify every event is bound to the signedrun_id. - Head & bounds — head hash and first/last seq match the manifest.
- Key pinning (optional) — rejects a bundle re-signed with any key but the pinned one.
Spec-violation verdicts (distinct from tamper)
The spec requires hashed input to be I-JSON (RFC 7493): every string —
object key or value — must be well-formed Unicode. RFC 8785 presumes I-JSON
and defines no interoperable byte sequence for an unpaired surrogate
(implementations disagree: ES2019 JSON.stringify emits a \ud800 escape,
Python/Go RFC 8785 libraries throw or emit different bytes), so a hash over
such a string can never cross-verify. When a bundle contains one, the verifier
returns a machine-readable spec-violation reject, deliberately distinct
from a tamper verdict:
{
ok: false,
reasonCode: "ill_formed_string", // machine-readable verdict class
failedSeq: "9", // the offending event (absent for a manifest string)
path: "$.payload.note", // JSON path of the ill-formed string
reason: "event seq 9 contains an ill-formed string (unpaired surrogate) at $.payload.note: ..."
}ill_formed_string proves nothing was altered — it says the bundle violates
the spec's I-JSON requirement, its hashes were never well-defined, and it must
not be accepted. Producers running the fixed pipeline can no longer emit such
bundles (ill-formed strings are rejected at API ingress with HTTP 400 and by
the serializer itself, fail closed). No Unicode normalization is ever applied:
NFC and NFD strings are distinct bytes, and characters outside JSON's
mandatory escape set (including astral characters like U+1F600) are hashed and
emitted literally — the unicode-literal vector proves both properties.
Conformance vectors
vectors/ is a public corpus: valid full and run bundles (one
with astral + NFC/NFD strings proving literal, unnormalized Unicode handling)
plus adversarial variants (tampered payload, corrupted signature, truncation,
reordering, a re-signed foreign-run splice, a wrong-key forgery, and an
ill-formed-string bundle that must be rejected as an I-JSON spec violation),
each with the verdict a conformant verifier must return in
vectors/index.json. Any implementation in any language
can run these and self-certify.
The corpus is deterministic and frozen: external implementers can pin the
committed vector files by hash. Regeneration is byte-reproducible — every
timestamp and id is a fixed constant, event UUIDs are derived from SHA-256 of
a fixed namespace string, and the bundles are signed with the committed
vectors/test-fixture-signing-key.pem,
a deliberately public test fixture (never a secret, never used outside this
corpus; see the header of
scripts/build-vectors.mjs). npm test
regenerates into a temp directory and fails on any byte diff against the
committed files, so the corpus cannot drift silently.
npm test # byte-compares a fresh regeneration, then asserts every verdict
npm run build:vectors # regenerate in place (must be a no-op diff)The reimplementation here is cross-checked to be byte-identical to the
producer's own primitives (@makerchecker/shared), so "verify from the spec
alone" is a fact, not a slogan.
License
Apache-2.0.