@decidio/verify
v0.6.3
Published
Standalone, zero-dependency verifier for Decidio Authority Receipts (W3C VC, Ed25519 did:key). Verify a sealed receipt's signature + content binding + chain order offline — no Decidio dependency. `npx @decidio/verify receipt.json`.
Downloads
4,469
Maintainers
Readme
@decidio/verify
Standalone, zero-dependency verifier for Decidio Authority Receipts. Verify a sealed receipt's signature, content binding, and chain order offline — with no Decidio dependency. A record you can verify only with the vendor's software isn't really owned; this is what makes "the customer owns the record" true rather than rhetorical.
# PIN THE ISSUER. This is the form to use in an audit or a script:
npx @decidio/verify --issuer did:key:z6Mk… receipt.json
npx @decidio/verify --issuer did:key:z6Mk… r1.json r2.json r3.json # each + the chain order
# Without --issuer it REFUSES (exit 2) since 0.5.0 — an unpinned VALID would only prove the file agrees
# with itself. A structural check is a named choice, and its verdict carries the UNPINNED warning:
npx @decidio/verify --allow-unpinned receipt.jsonYour Decidio host's issuer DID is printed by npx @decidio/sdk receipt <id>, shown in the app's
record-verify panel, and published in the open at <api>/api/did (for the hosted sandbox:
https://decidio-api.onrender.com/api/did) — the second channel that turns a DID first seen over one
connection into a checked one. Record it once and pin it forever.
What VALID means (read this before scripting on the exit code)
Verification answers two separate questions, and only one of them is free:
- Is this document internally consistent and unmodified? Answered with
--allow-unpinned. - Was it issued by the party you expect? Answered only with
--issuer.
A receipt signed by a self-generated key is internally consistent, so an unpinned check
reports VALID and exits 0 — correctly, because nothing was asked about who signed it. That is
why, since 0.5.0, the CLI refuses to run unpinned unless you say --allow-unpinned: an external
review forged exactly such a receipt and the exit code said yes. A
forged pair even reports an intact chain: a chain proves the links hold, not the origin. So
decidio-verify receipts/*.json && promote will promote records Decidio never issued unless the
issuer is pinned. Pass --issuer and a wrong signer exits non-zero, naming the issuer as the
problem rather than blaming the content.
import { verifyReceipt, verifyChain } from "@decidio/verify";
const r = verifyReceipt(vc); // { valid, issuer, contentBound, recordId, reason? }What it checks (offline, using only the public DID in the receipt)
- Content binding — re-derives
sha256((prevHash||"") + recordCanonical)and confirms it equals the signedcontentHash, so the signature commits to the actual content (tamper-evident). - Signature — verifies a W3C Data Integrity proof (
DataIntegrityProof, cryptosuiteeddsa-jcs-2022, Ed25519 viadid:key) over the WHOLE receipt: the signing input isSHA-256(JCS(proof options))followed bySHA-256(JCS(receipt without proof)), per Data Integrity EdDSA Cryptosuites v1.0 §3.3. Verification needs only SHA-256, Ed25519, base58 and RFC 8785 — no JSON-LD, no context fetching, no DID resolution, no network. Receipts sealed before 2026-08-04 carry a pre-standard Decidio format under the nameeddsa-2022; those are refused with an explicit reason, not reported as tampered. - Chain (
verifyChain) — each receipt'sprevHashlinks to the priorcontentHash, and every receipt independently verifies.
Signature + content + chain verify offline. Revocation/status is a separate, network-bound check by design — a receipt is tamper-evidence, not a liveness check.
Parity
A Python verifier ships in decidio[verify] (python -m decidio.verify). Both verifiers accept the same real Decidio seals — proven in the cross-language conformance suite. Zero dependencies beyond node:crypto (TS) / cryptography (Python).
