Unreleased documentation. These pages follow the main branch and can change before the next release. For supported guidance, use v0.15.2.
Configure evidence-oid4vci when a deployment needs to expose a holder-bound Evidence Gateway
credential to an OID4VCI client rather than through the Evidence Gateway response contract.
This page documents the adapter’s protocol and deployment contract, not compatibility with a
specific wallet.
When to use evidence-oid4vci
Section titled “When to use evidence-oid4vci”evidence-oid4vci is a supporting service, not a product pattern of its own, in the same sense
Registry Mint is. It exposes Evidence Gateway credentials over OID4VCI 1.0 Final, the
wallet-facing protocol Evidence Gateway deliberately refuses to speak.
Three properties bound the service, and none is negotiable:
- It never signs a credential. Evidence Gateway signs.
evidence-oid4vciholds no Evidence Gateway signing key and has no code path that could use one. - It never holds a holder private key. It receives holder public keys inside wallet-signed proof JWTs and passes them to Evidence Gateway unchanged.
- It adds no Evidence Gateway semantics. It is a protocol adapter. Every authorization decision, every source acquisition, and every signature stays behind the Evidence Gateway runtime contract.
The dependency between the two services runs one way: evidence-oid4vci is a client of Evidence
Gateway and, for its adopter-facing endpoint, a resource server for Registry Mint tokens. No
Evidence Gateway crate depends on evidence-oid4vci at runtime.
Use evidence-oid4vci only for credentials issued under Evidence Gateway’s holder-bound subject
binding mode. A requirement whose subject binding is audience-scoped is not deliverable through
this service.
The repository does not yet carry a real-wallet or external OID4VCI conformance run. Verify the chosen wallet against the deployment before making a compatibility claim.
Before you start
Section titled “Before you start”You need:
- A complete Evidence Gateway deployment with at least one requirement declared
holder-bound, permitted for the SD-JWT VC batch format at bundle and grant scope. - A Registry Mint deployment, or another token issuer, that can grant
evidence-oid4vciits own access token to Evidence Gateway through theprivate_key_jwtclient assertion flow, and this service’s own private JWK for that assertion. - A published
httpsorigin for the credential issuer identifier this service will serve. A wallet compares this origin exactly against theaudof every proof it signs.
Configure the deployment
Section titled “Configure the deployment”evidence-oid4vci reads one YAML document, validated as a whole with deny_unknown_fields: an
unrecognized key fails the load rather than being silently ignored. Every relative path in it
resolves against the document’s own directory.
version: 1validationMode: strictcredentialIssuer: https://wallet.example.orglistener: address: 127.0.0.1 port: 8090 maximumRequestBytes: 16384 requestTimeoutMilliseconds: 5000evidence: baseUrl: https://evidence.example.orgmint: tokenEndpoint: https://mint.example.org/token clientId: evidence-oid4vci privateKeyFile: keys/delivery-client.jwk.json clientAssertionAudience: https://mint.example.org/tokenoffers: issuer: https://mint.example.org jwksUri: https://mint.example.org/.well-known/jwks.json audiences: [evidence-oid4vci] algorithms: [EdDSA] authorizedClients: [adopter-front-end] maximumTokenLifetimeSeconds: 900store: maximumOffers: 4096 offerLifetimeSeconds: 300 accessTokenLifetimeSeconds: 300 nonceLifetimeSeconds: 120 maximumTransactionCodeAttempts: 3credentialIssuer is the credential_issuer identifier this service publishes, and the exact
value a wallet proof must carry as its own aud. A trailing slash is removed as the document
loads, so the identifier the metadata publishes, the identifier an offer names, and the audience a
proof is compared against are one string. listener.maximumRequestBytes is bounded to
1024..=1048576 and listener.requestTimeoutMilliseconds to 1..=30000; a zero body limit or a
zero timeout would leave the service reporting itself ready while every request fails. The timeout
bounds a whole request, from reading its body to the call behind it, and a request that outlives it
is refused with 408 Request Timeout rather than held open. evidence.baseUrl
is the base URL of the Evidence Gateway deployment this service requests credentials from; which
credentials may be requested is the Evidence Gateway bundle’s decision, never restated here.
There is deliberately no configuration member for the credential configurations this service
publishes: every entry is derived from the Evidence Gateway bundle, so published metadata cannot
describe a credential Evidence Gateway would refuse to issue.
mint.privateKeyFile is this service’s own private JWK, read owner-only when the outbound client
to Registry Mint is built, and never logged or rendered. mint.clientAssertionAudience defaults to
mint.tokenEndpoint when absent, which is the usual registration.
offers is the authorization boundary of the adopter-facing POST /offers endpoint, and it is a
required, separate document from mint: the identity this service authenticates to Mint with has
nothing to do with the identities it accepts offer tokens from. offers.issuer and
offers.jwksUri name the authorization server that issues offer tokens, compared exactly against a
token’s iss and read for its keys; both must be https in strict mode. offers.audiences
names the resource identifiers this service answers to, so a token minted for another resource
server cannot be replayed here; at least one is required. offers.algorithms defaults to
[EdDSA] and accepts exactly one signature algorithm family per deployment. offers.authorizedClients
defaults to empty, which accepts any client the issuer vouched for; naming one or more client
identifiers narrows that further. offers.maximumTokenLifetimeSeconds defaults to 900 and is
bounded to 60..=3600 seconds.
validationMode defaults to strict, which requires every published and called origin
(credentialIssuer, evidence.baseUrl, mint.tokenEndpoint, the client assertion audience,
offers.issuer, and offers.jwksUri) to be an absolute https URL with a host and no embedded
credentials, query, or fragment. Set it to supervised-local-development only for a disposable
local developer environment; that mode admits one supervised process group on loopback, requiring
the listener to match the credential issuer’s origin exactly and every other endpoint to be a
canonical http://127.0.0.1:<port> origin.
store is optional and defaults as shown. Every bound is a memory limit or a window on the
in-memory store described in Operational limits: maximumOffers is bounded
to 256..=1048576; offerLifetimeSeconds and accessTokenLifetimeSeconds to 60..=900 seconds
each; nonceLifetimeSeconds to 30..=900 seconds and must not exceed
accessTokenLifetimeSeconds; maximumTransactionCodeAttempts, how many wrong transaction codes an
offer survives before it locks out for the rest of its life, to 1..=10.
Validate the deployment before opening a socket:
evidence-oid4vci check --config /etc/evidence-oid4vci/oid4vci.yamlcheck does everything serve does except bind a socket: it loads and validates the
configuration document and the Mint client key, then exits. check and serve accept
EVIDENCE_OID4VCI_CONFIG in place of --config.
Start evidence-oid4vci
Section titled “Start evidence-oid4vci”evidence-oid4vci serve --config /etc/evidence-oid4vci/oid4vci.yamlEvery field in the configuration document is startup-only; changing the credential issuer, the listener, the Evidence Gateway or Mint endpoints, or the store bounds means restarting the process.
Protocol surface
Section titled “Protocol surface”evidence-oid4vci pins OID4VCI 1.0 Final, one version, with no draft-13 compatibility mode.
| Endpoint | Auth | Notes |
|---|---|---|
GET /.well-known/openid-credential-issuer | none | Publishes the issuer and credential endpoints, authorization server, batch ceiling, and supported credential configurations. The token endpoint is not published here. |
GET /.well-known/oauth-authorization-server | none | Publishes the token endpoint, the pre-authorized-code grant, none for token endpoint authentication, and anonymous pre-authorized access. |
POST /offers | Registry Mint bearer token | Adopter-facing, not wallet-facing. Takes credentialConfigurationId, subjects, and a boolean transactionCode. Returns the offer object, its openid-credential-offer:// URI, and, when one was asked for, the generated transaction code, once. |
POST /token | pre-authorized code, plus the transaction code when the offer set one | grant_type=urn:ietf:params:oauth:grant-type:pre-authorized_code. Returns access_token, token_type, expires_in. No c_nonce. |
POST /nonce | none | Empty body in, {"c_nonce": ...} out, Cache-Control: no-store. The nonce is a keyed freshness challenge and is not bound to an access token. |
POST /credential | bearer access token | Takes credential_configuration_id and proofs.jwt[]. Returns credentials: [{credential: ...}]. |
GET /health, GET /ready | none | Operational probes. Readiness deliberately reports nothing about the Evidence Gateway or Registry Mint deployments behind it, so a probe cannot be used to survey them. |
The transaction code is generated by this service and returned to the adopter once, on the POST /offers response, over the channel that request was authorized on. There is no way to supply one,
which is what keeps a caller from choosing a guessable code.
POST /offers is the real authorization boundary of this service. It accepts selector values and
will cause a credential to be issued for whoever they identify, so protect it at least as strongly
as you would protect a direct call to Evidence Gateway. It is a resource server for Registry
Mint-issued tokens, verified the same way Evidence Gateway’s own authenticator verifies them, and
that verification never shares a code path with this service’s own outbound calls to Evidence
Gateway or Registry Mint.
Only a holder-bound requirement can be named as a credential_configuration_id. A request
naming an audience-scoped requirement is refused, so this service cannot be used to launder an
audience-scoped credential into a wallet.
Each proof uses ES256 and nominates exactly one P-256 public key. It can carry the public JWK
inline, or carry a kid in the self-contained did:jwk:<base64url-public-JWK>#0 form. The service
decodes did:jwk locally and performs no remote DID resolution. aud, iat, and nonce are
required. A proof may also carry exp; an expired proof is refused, and a far-future exp never
widens the five-minute iat freshness window.
Operational limits
Section titled “Operational limits”Everything this service remembers lives in one bounded in-memory store, and none of it survives a restart or a second replica. State these limits plainly to anyone operating the deployment:
- State is in-memory only. A restart invalidates every offer outstanding in a minutes-wide window. There is no database anywhere beneath this service.
- Single-use is enforced per process. A redeemed pre-authorized code and a redeemed access token are each refused on reuse, but only within the one process that redeemed them, which is why a deployment runs exactly one replica. Adding a second replica breaks single-use unless it also adds a shared store, which this service does not have.
- Single-use is mostly a detection property, not a prevention property. It turns a stolen
offer’s silent duplicate issuance into one party getting an error and complaining. The short
offer and access-token lifetimes and the
tx_codeare the primary defenses; single-use marks the attempt after the fact rather than stopping it before.
- Configure Registry Mint to issue the access tokens
evidence-oid4vciuses to authenticate to Evidence Gateway and to protect its ownPOST /offersendpoint. - Configure Evidence Gateway for the deployment this service requests credentials from.