@typedstandards/verify-core
v0.7.0
Published
Portable, browser-safe verification core for Typed Standards evidence packages — the §9.2 check suite, single-sourced so it verifies identically on a server and in the browser.
Maintainers
Readme
@typedstandards/verify-core
The portable, browser-safe verification core for Typed Standards evidence packages — the spec §9.2 check suite, factored so it runs identically on a server (e.g. civicaitools.org's verify route) and in the browser (the typedstandards.org client-side verifier). One implementation, so a tampered package fails the same way wherever it is checked.
Originally extracted from
civic-ai-tools-website(#116 WS2) and published here (#116 WS3) so every consumer depends on one versioned source that cannot drift.
Install
npm install @typedstandards/verify-coreBrowser-safety contract
This package depends on no Node built-ins. There is no node:crypto / fs /
path / process / Buffer anywhere in the source (an upstream ESLint
no-restricted-imports rule enforces it). It runs unchanged in a browser, on the
edge, and in Node.
- Hashing —
@noble/hashes(SHA-256/512). Digests are byte-identical to Node'scrypto.createHash. - Signatures —
@noble/curvesEd25519 / Ed25519ph, with algorithm dispatch (a signature verifies only under the scheme it was made with). Raw key bytes come from a fixed-prefix SPKI slice, notcrypto.createPublicKey. - Canonicalization — RFC 8785 JCS via
canonicalize. - Network is injected — every check that touches the network takes a
FetchLike(defaulting toglobalThis.fetch, read at call time). The server injects itsfetch, the browser injectswindow.fetch, tests inject a stub.
Fetch note. A
FetchLikeshould issue plain GETs with no custom request headers. A custom header triggers a CORS preflight, and a preflightOPTIONShitting civicaitools.org's site-wide 307 to its canonical host can be rejected. A simple GET follows the 307 transparently.
Usage
import { verifyEvidence, type VerifyInput, type TrustRegistry } from '@typedstandards/verify-core';
const input: VerifyInput = {
package: pkg, // the parsed package JSON
packageHash, // the claimed envelope hash
signature, // { signature, publicKey, algorithm?, kid? }
rfc3161Timestamp, // optional
rekorEntryId, // optional
lifecycle, // optional sidecar lifecycle state
};
const result = await verifyEvidence(input, {
registry, // a TrustRegistry (fetched from the package's trustRegistryUrl)
fetch: globalThis.fetch,
});
// result carries the per-check verdicts: hashMatch, signatureValid, keyTrust,
// contentHash, typeResolution, signerIdentity, lifecycle, ...The barrel (.) also exports the individual check functions
(recomputePackageHash, verifySignature, verifyKeyTrust, verifyContentHash,
…), the canonicalization primitives (computeEnvelopeHash,
computeContentHashSha256), and every status vocabulary type — so a consumer can
drive the checks one-at-a-time and render the math as it resolves.
Check depth (v0.6.x)
Every check in the spec §9.2 sequence runs fully client-side, including the three that shipped at reduced depth in v0.1:
- #7 RFC 3161 timestamp — full cryptographic verification: token parsing, message-imprint match, TSA signature, and certificate-chain validation with strict RFC 5280 checks to a pinned root (FreeTSA EC-P384 leaf → RSA-4096 root).
- #8 Rekor transparency log — cryptographic Merkle inclusion-proof verification from the proof carried with the package, against Rekor's pinned public key; no live call to Rekor required.
- #10 lifecycle — independent verification of the signed lifecycle attestation chain, including reachability from the package node, instead of trusting a host-reported state.
Verification is offline-first: from a self-contained commitment bundle the full
sequence runs with zero network access (demonstrated by a hermetic,
network-blocked test in this monorepo). Trust anchors (the TSA certificate
chain and the Rekor log public key) ship pinned in the source with capture
dates and rotation notes. See CHANGELOG.md for how each check reached full
depth across 0.2.0–0.6.0.
License
MIT © Nathan Storey
