@visiq/record-verifier
v0.1.3
Published
VisIQ audit-trail verifier for browsers and Node.js — a dependency-light, offline re-check of a decision receipt's payload hash, Ed25519 signatures, Merkle proof, and RFC-3161 timestamp binding; TSA certificate-chain trust stays server-side.
Maintainers
Readme
@visiq/record-verifier
VisIQ audit-trail verifier for browsers and Node.js — a dependency-light, offline re-check of a decision receipt's payload hash, Ed25519 signatures, Merkle proof, and RFC-3161 timestamp binding.
VisIQ records every governance decision as a signed record envelope. This package re-runs that cryptography locally — offline, with no network calls — so you can check a receipt yourself rather than taking our API's word for it.
import { verifyRecordEvidence } from '@visiq/record-verifier';
// Synchronous — no network, no async init.
const result = verifyRecordEvidence(bundle);
if (result.ok) {
// every leg that could be checked, checked out
}
for (const [leg, check] of Object.entries(result.checks)) {
console.log(leg, check.status, check.detail); // "pass" | "fail" | "skipped"
}What it verifies
result.checks has six legs, each pass / fail / skipped:
| Leg | What is re-computed locally |
|---|---|
| integrity | payload_hash recomputes from the source event's canonical JSON |
| leafSignature | the Ed25519 leaf signature verifies over the correct message |
| merkleInclusion | the leaf's proof path reproduces the committed batch root |
| rootSignature | the Ed25519 root signature verifies over root_hash / chain_hash |
| chainLink | chain_hash recomputes (genesis-anchored single-record bundles only) |
| timestamp | the RFC-3161 token's messageImprint binds to that batch root |
This is tamper-evidence, not authenticity — read this
The signatures are checked against keys carried inside the bundle you pass in
(leaf.public_key, batch.root_public_key). This package ships no trust anchor, key
registry, or pinning. So a clean verdict proves the bundle is internally consistent
and unmodified — it does not, on its own, prove VisIQ produced it. Someone who
fabricates a whole bundle with their own keypair gets ok: true.
To get authenticity, pin the expected public key out-of-band and compare it against
bundle.evidence.leaf.public_key / batch.root_public_key before trusting a
clean verdict. That check is yours to make; this package deliberately does not
invent a trust root.
What it does NOT do — read this
A clean verdict is not a claim that every leg was checked. Legs that cannot be
evaluated from the bundle alone return status: "skipped", and skipped legs are
neutral in the overall verdict — so a bundle can come back clean having genuinely
re-verified only some of its legs. Always inspect result.checks rather than
result.ok alone when the distinction matters.
Specifically:
- TSA certificate-chain trust stays server-side. This package binds the
timestamp's message imprint to the batch root and surfaces
genTime; it does not validate the TSA's certificate chain, CMS signature, or OCSP status. - Chain hash is recomputed only for the genesis record (
batch_seq === 1); for every later recordchainLinkisskipped. - Leaf-signature verification is skipped for env/derived-signed leaves when the
bundle carries no source event — a common bundle shape. Note a skipped
leafSignaturealso leaves the overall verdictnot_verified, so it will not masquerade as a pass.
Install
npm install @visiq/record-verifierRuns in browsers and Node. Its only dependencies are @noble/ed25519 and
@noble/hashes.
A zero-dependency verify.mjs CLI is also embedded inside exported proof bundles,
so a recipient can verify one without installing anything at all.
Licence
MIT — see LICENSE.
