Credentials & trust
Trust artifacts, offline verification, the two verifiers, and did:key vs did:web — how a credential is trusted without calling home.
In plain terms. How someone with no internet connection can still tell that a credential is genuine and has not been cancelled. The short answer: they downloaded the issuer's public key and its cancellation list earlier, and check against those.
How does a border post with no internet decide a residency credential is genuine and still valid? By checking it against trust artifacts it cached earlier.
Trust artifacts
A trust artifact is issuer-published data a verifier needs to evaluate a credential — separate from the credential itself. There are two:
| Artifact | Answers | Published at |
|---|---|---|
| Issuer public keys (DID document) | "Is this signature really from the issuer I trust?" | GET /.well-known/did.json |
| Revocation status list | "Has this credential been revoked since issuance?" | GET /.well-known/status/<cc>.json |
The verifier fetches these once while online and caches them; afterwards it can verify any
number of credentials offline. The credential comes to the field (QR, wallet); the trust
artifacts are already on the device. This is the whole offline-verification design, served by
WellKnownController.
Trust artifacts must themselves be trustworthy
A cached artifact you rely on must be tamper-evident. That's why the status list is signed
(a Data Integrity proof), not bare JSON — an unsigned status list a verifier caches would be
trusting TLS at fetch time and nothing after. See
StatusListPublisher.
The two verifiers
VcVerifier | VpVerifier | |
|---|---|---|
| Verifies | a credential | a presentation (a credential being shown) |
| Answers | "Is this genuine, unexpired, unrevoked?" | "Is the presenter its holder, to me, now?" |
| Checks | signature · expiry · revocation | + holder binding · nonce · audience |
| Runs | offline, against cached artifacts | online / interactive (login) |
VpVerifier wraps VcVerifier: it verifies the envelope (holder binding, freshness,
audience) and delegates the credential inside to VcVerifier. On its own, a credential is a
bearer token — the presentation checks are what bind it to the right person, presented to the
right verifier, once. (See Verifiable Credentials.)
Verifiers are not authenticated to OpenResidency — verification is a pure cryptographic check
against public data, so there's no account, guard, or session for them. The logic exists
(VcVerifier/VpVerifier, embeddable from the framework-free core); the principal does not, by
design — requiring auth would break permissionless offline verification.
did:key vs did:web
The issuer is named by a DID (Decentralized Identifier), which resolves to its public keys.
did:key— the public key is encoded into the identifier (did:key:z6Mk…). Self- resolving, offline, no network — but it can't rotate (a new key is a new DID). Used in tests and fully-offline demos.did:web— the identifier is a domain (did:web:id.katsina.gov.ng), resolved by fetching/.well-known/did.json. Rotatable (publish a new key under the same DID; old credentials still verify) and human-meaningful. Used in production.
The decisive difference is rotation: a residency issuer is long-lived and must rotate its
signing key without invalidating years of issued credentials — which did:web allows and
did:key does not. The trust list holds a list of keys (current first, retired after) for
exactly this reason.
Credential formats
Two, sharing one status-list entry (so revoking a resident revokes both):
- VC-JWT — compact; fits in a printed QR, verifies with one signature check. The offline path.
ldp_vc(JSON-LD + Data Integrity proof) — what wallets like Inji accept. Issued viaLdpIssuer.
Architecture
The core / delivery / ports split — a hexagonal design where the framework-free core is the real asset and the NestJS delivery layer is disposable.
Offline verification
How a credential is checked with no network — a signature check against the cached issuer public key, plus a revocation check against the cached status list — and why offline is permissive while online is strict.