JSPM

@makerchecker/proof-verifier

1.2.0
  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 247
  • Score
    100M100P100Q55779F
  • License Apache-2.0

Standalone, zero-dependency, zero-trust verifier for Agent Action Proof Receipt bundles. Verifies a MakerChecker audit bundle offline with no database or server access.

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.

npm License Zero dependencies

[!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 - --json

Exit 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:

  1. Signature — Ed25519 over the RFC 8785 canonical signing string.
  2. Count — events match the signed count.
  3. Hash-set digest — the exact event set and order are bound into the signature.
  4. Per-event hashes — every event's SHA-256 recomputes (any altered field fails here).
  5. Linkage — full bundles verify complete genesis-rooted prev_hash linkage; run bundles verify every event is bound to the signed run_id.
  6. Head & bounds — head hash and first/last seq match the manifest.
  7. 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.