OpenResidency Docs

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:

ArtifactAnswersPublished 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

VcVerifierVpVerifier
Verifiesa credentiala presentation (a credential being shown)
Answers"Is this genuine, unexpired, unrevoked?""Is the presenter its holder, to me, now?"
Checkssignature · expiry · revocation+ holder binding · nonce · audience
Runsoffline, against cached artifactsonline / 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 via LdpIssuer.

On this page