What it is
A third party with no access to a running Parmana instance at all, no API key, no network access to this deployment, should still be able to verify that a signed trust record, receipt, or refusal record is genuine and unmodified, given only the artifact itself and the relevant public key. This chapter covers the offline verification path, how a public key is discovered in the first place, and an honest assessment of two packages that sound like they belong here by name but turned out not to both be wired the way their names suggest.Why it was built
Every signature this codebase produces is meant to be checkable without trusting Parmana’s own server to tell you it’s valid, that would defeat the purpose of signing anything. Offline verification exists so the check can be performed with zero network calls, zero disk reads beyond the artifact and a public key you already have, and zero environment configuration.How it works
Offline verification
verifyExecutionTrustRecordOffline() (packages/crypto/src/OfflineVerifier.ts) is explicitly
built to have zero external dependencies at call time: the caller supplies the exact public key
material for every keyId a record references, and everything else, canonical serialization,
the exact field mapping the signature actually covers, the Ed25519/ML-DSA-65 signature
implementations, is the same code the real, online VerificationCrypto uses.
ExecutionTrustRecordCanonicalView.ts is shared between the offline and online paths
specifically so there is exactly one definition of “which fields participate in the signature,”
not two implementations that could silently drift apart from each other.
It checks three things, and reports each independently rather than collapsing them into one
opaque boolean: hashValid (does the record’s own trustRecordHash match a fresh hash of its
canonical content), legacySignatureValid (does the always-present legacy signature field
verify), and hybridSignaturesValid (only present when the record actually carries a
signatures array, a hybrid record needs at least two distinct-algorithm entries, and a
duplicate algorithm in that array is treated as invalid). The function’s own comment is explicit
about a real limitation: the hash check recomputes SHA-256 unconditionally, because that’s the
only hash algorithm this codebase actually implements today (other config-declared algorithms
like sha3-512/blake3 have no real provider), a record hashed under a genuinely different
algorithm could not be distinguished from a tampered one by this check alone, since the
algorithm used isn’t itself recorded on the artifact separately from the hash value.
The comment also names a real relationship worth knowing: this module is the reference a
separately published, independently maintained package,
@parmana/sign (github.com/pavancharak/parmana-sign, see docs/CLAIMS.md §3.12), is built
on, that external package ships the lower-level primitives (canonical serialization, sign/verify)
this module also uses, but does not yet know the Execution-Trust-Record-specific canonical field
mapping or the hybrid envelope shape. Syncing that into the external package is separate work,
not something this repository’s own build performs.
Public-key discovery
createKeysRouter() (packages/api/src/routes/keys.ts) exposes GET /keys/:keyId and
GET /.well-known/jwks.json. Both are deliberately mounted before this app’s caller-auth
middleware, alongside /refusal/verify and /audit/verify, a third party cannot be required to
already hold a Parmana-issued credential just to fetch the key it needs to check a signature
that credential has nothing to do with. Every response includes a PEM-encoded SPKI public key
(RFC 7468), which is what OfflineVerifier.ts and its Python counterpart both consume directly,
plus a jwk field when the running Node version’s KeyObject.export({format:"jwk"}) supports
the key’s algorithm, the comment notes ML-DSA-65 exports as JOSE/COSE key type "AKP"
(from the IETF draft-ietf-jose-fully-specified-algorithms track, not an identifier this codebase
invented) and Ed25519 as the long-standardized "OKP". The JWKS endpoint is explicitly not a
standards-pure RFC 7517 JWK Set, since this codebase’s PEM-first shape doesn’t fit that spec’s
structure exactly, it’s a superset any consumer that only wants the .jwk field per entry can
filter down to.
An honest assessment of @parmana/replay and @parmana/receipt
This is worth stating precisely rather than by reputation. Checking actual imports (not package
existence, actual usage inside packages/api/src):
@parmana/replayis a real package (packages/replay/), with a realReplayEngine,ReplayExecutor,ReplayBuilder,ReplayVerifier, and a real test suite including an integration test and a determinism test. It has zero imports anywhere insidepackages/api/src. It is real, tested, tutorial-capable code with no production wiring , say this plainly rather than implying it’s load-bearing because its name suggests it should be.@parmana/receipt, as a standalone package, does not exist in this repository at all , there is nopackages/receipt/directory and nopackage.jsonanywhere named@parmana/receipt. Receipt functionality is real and genuinely wired into production, but it lives inside@parmana/crypto(ReceiptCrypto.ts,ReceiptHasher.ts) and theReceipttype in@parmana/shared, used directly by the runtime’s trust-record construction, confirmed by real signed receipts appearing in realExecutionTrustRecordoutput. If you have read or been told that “@parmana/receiptand@parmana/replayare both unwired,” that claim is now only half true: receipts are real and live; replay is the one that remains genuinely unwired.
How it enables things, with a concrete example
examples/tutorials/05-verification/run.ts and examples/tutorials/12-envelope-verification/run.ts
exercise the online verification path. packages/crypto/tests/unit/ (search for
OfflineVerifier or offline-verifier) covers the offline path directly with real, generated
key material and a deliberately tampered record to confirm detection. python/parmana/crypto/offline_verifier.py
is the Python-side equivalent of the same capability, for a consumer with no Node.js runtime at
all.
How to validate this yourself
packages/crypto/src/OfflineVerifier.ts,ExecutionTrustRecordCanonicalView.tspackages/api/src/routes/keys.tspython/parmana/crypto/offline_verifier.pypackages/replay/src/andpackages/replay/tests/(real, but check its own package.json’s consumers, or the lack of any insidepackages/api/src, to confirm the unwired claim yourself)packages/crypto/src/ReceiptCrypto.ts,ReceiptHasher.ts(confirm receipts are real by tracing their usage frompackages/runtime/src/into an actual trust record)
Integration requirements
None for offline verification itself, that is the entire point, zero network calls, zero environment variables. To exerciseGET /keys/:keyId or the JWKS endpoint against a real
deployment, only PARMANA_KEY_DIR (or the configured KEY_PROVIDER) needs to be set correctly
server-side; the caller needs nothing.