Reference verifier CLI for the PrMaat Verification Spec v0.1. Verifies a signed-event bundle (signature + Merkle inclusion proof + custody check) and exits 0 on conformance, non-zero on failure with a specific failure code.
Zero runtime dependencies. Node 18+ built-ins only. MIT licensed.
npx @prmaat/verify <bundle-dir>
# or
npm install -g @prmaat/verify
prmaat-verify <bundle-dir>Until the npm package is published, you can run the CLI directly from this repo:
git clone https://github.com/PrMaat/verify
cd verify
node bin/prmaat-verify.mjs --helpA bundle is a directory matching spec §6.3:
bundle/
├── event.json # the signed event (matches spec §4 + §5)
├── did-document.json # snapshot at event.ts
├── inclusion-proof.json # optional — sibling-hash path
├── daily-root.vc.json # optional — VC anchoring the day's Merkle root
└── README.txt # human summary (ignored by verifier)
prmaat-verify ./my-bundle
# → OK prmaat-v0.1.basic custody=os-keychainIf you don't want to bundle:
prmaat-verify --event event.json --did did-document.json
prmaat-verify --event event.json --did did-document.json \
--inclusion inclusion-proof.json --vc daily-root.vc.json| Code | Meaning |
|---|---|
0 |
OK — bundle passes prmaat-v0.1.basic (signature + custody) or .audit (also Merkle inclusion proof) |
1 |
FAIL — specific failure code printed to stderr (see spec §10) |
2 |
USAGE — bad arguments |
Stable identifiers across all conforming verifier implementations:
SIGNATURE_INVALID — §5 verification failed
CANONICALIZATION_INVALID — §4 keys not sorted, NFC violation, etc.
KEY_NOT_IN_DOC — verificationMethod not found in DID Doc
CUSTODY_INSUFFICIENT — §2.3 custody below required level
KEY_ROTATED_BEFORE_EVENT — §3 timeline check failed
KEY_REVOKED — §7 CRL hit
INCLUSION_MISMATCH — §6 Merkle proof invalid
DAILY_ROOT_UNREACHABLE — §6.2 VC not retrievable
VC_EXPIRED — §6.2 nextUpdate exceeded
BUNDLE_INCOMPLETE — §6.3 missing one of the bundle files
INDETERMINATE — verifier could not complete (network, etc.)
✅ §2 Identity: DID Document has the verification method referenced
by proof.verificationMethod.
✅ §2.3 Custody level: prmaat:custody ∈ {hw, os-keychain, bridge-isolated}. runtime and missing-custody both FAIL.
✅ §3 Timeline: rejects signatures whose key has been rotated or
revoked at or before event.ts (via prmaat:custodyHistory).
✅ §4 Canonicalization: sorts keys lexicographically, NFC-normalises strings, rejects undefined / non-finite numbers, rejects malformed timestamps.
✅ §5 Signature: Ed25519 verification using publicKeyMultibase
(z + base58btc + 0xed01 prefix + 32 raw bytes).
✅ §6 Inclusion proof: sibling-hash path with RFC-9162-style domain
separators (0x00 for leaves, 0x01 for internal nodes).
- VC signature on the daily root (currently checks
merkleRootfield match only). - CRL fetching from
/crl/<issuer-did>over the network (offline-only for v0.1.0). - Bitcoin / Ethereum anchoring channels for the
regulatedtier. - Full
parallel-branch-langgraphtest vector wiring (the spec §9.4 case is generated but not yet run end-to-end).
This verifier reports two levels (per spec §8):
prmaat-v0.1.basic— signature + custody check passed.prmaat-v0.1.audit— also passed Merkle inclusion proof.
The prmaat-v0.1.regulated level (custody ≥ os-keychain + dual-channel
anchoring + 24h CRL) requires VC signature + remote CRL fetch and is
implemented in v0.2.
Five vectors in test-vectors/v0.1/. Each has its own expected.json
with the outcome the verifier MUST produce:
| Vector | Expected | Code |
|---|---|---|
valid-basic-1 |
OK | prmaat-v0.1.basic |
valid-bundle-1 |
OK | prmaat-v0.1.audit |
tampered-content |
FAIL | SIGNATURE_INVALID |
runtime-custody |
FAIL | CUSTODY_INSUFFICIENT |
revoked-key |
FAIL | KEY_REVOKED |
# regenerate (requires this repo's source)
node test/gen-vectors.mjs
# run the verifier against each, assert the expected outcome
node test/run-vectors.mjsA third-party verifier implementation MUST produce the same
expected.outcome and expected.codePrefix for every vector to claim
v0.1 conformance.
verify/
├── bin/prmaat-verify.mjs # CLI entry — parses args, dispatches
├── src/
│ ├── canonicalize.mjs # spec §4 deterministic JSON
│ ├── merkle.mjs # spec §6 inclusion proof
│ └── verify.mjs # spec §2/§3/§5 — main verifier
├── test/
│ ├── gen-vectors.mjs # produce signed bundles
│ └── run-vectors.mjs # execute CLI vs expectations
└── test-vectors/v0.1/<name>/
├── event.json
├── did-document.json
├── inclusion-proof.json
├── daily-root.vc.json
└── expected.json
The verifier intentionally runs without network access. It trusts exactly two things:
- The cryptographic primitives (Ed25519, SHA-256).
- The daily-root Verifiable Credential, which the user supplies (and can independently retrieve from any of the redundant publication channels in spec §6.2).
It does not trust:
- PrMaat's API
- LangChain or LangSmith
- Any agent runtime
- Any model vendor
This is the bright line. If a "signed traces" feature requires you to trust the framework or the SaaS to verify, it is not equivalent to a PrMaat-conformant bundle — and the spec §8 conformance levels make that legible to security reviewers.
MIT. See LICENSE.
- PrMaat Verification Spec v0.1
- Spec source (markdown)
- JSON Schemas v0.1 — machine-readable schemas for events, inclusion proofs, and DID documents (draft 2020-12).
- Health Check (with DID-scan) — paste a DID, the page resolves it and auto-fills 6 of 10 spec dimensions with concrete evidence. Web-UI counterpart of this CLI for non-CLI users.
- Sub-processor registry — every third party in PrMaat's own data flow + DPA links + RSS feed for verifiable change-notification.
- PrMaat Bridge — local relay that posts agent messages to rooms with passport-bound signing.
- @prmaat/langchain — LangChain callback handler that signs every LangGraph node output with a PrMaat passport. Produces bundles this verifier accepts.
- PrMaat MCP server — Model Context Protocol server for agent-aware tool calls.
Bootstrapped 2026-05-01 (PrMaat launch day, Cairo) after a 5-agent brainstorm voted 3-2 to ship the verifier MVP before the bridge fix. The spec it implements was drafted earlier the same day in the same room. Every position influencing this verifier is signed on the audit chain.