Skip to main content

Credential formats

OpenAC does not invent credential formats. It adds a proof layer over existing issuer-signed credentials. Before OpenAC can process a credential, the wallet runs a credential binding: it identifies which values are covered by issuer authentication. Only those values can become OpenAC statement input.

For the full binding model, see Implementation profile §5.

SD-JWT VC (primary path)

Status: Implemented in openac-sdk 0.1.0.

SD-JWT VC is the primary credential family in the PoC, defined by draft-ietf-oauth-sd-jwt-vc.

Binding

The SD-JWT binding identifies:

  • the issuer-signed JWT payload;
  • the disclosures used as OpenAC input ([salt, name, value] triples);
  • the salts and digests needed to check disclosure consistency against _sd values in the payload;
  • the issuer signature and issuer verification material (P-256 / ES256 JWK in the PoC);
  • device binding material from cnf.jwk — required for the device-key ownership proof in Show.

The SDK's Credential.parse implements this binding:

// source: wallet-unit-poc/openac-sdk/src/credential.ts
const cred = Credential.parse(jwt, disclosures);

Each disclosure decodes to [salt, name, value] and a SHA-256 digest aligned with SD-JWT _sd digests.

Issuer-authentication path (Prepare)

Per Paper §3, Prepare runs:

  1. Parse the SD-JWT into messages, salts, hashes, and issuer signature.
  2. Check each hash corresponds to the message and salt (_sd consistency).
  3. Verify the issuer signature under the issuer public key.
  4. Commit to the message vector with a Pedersen commitment.

A claim value may become OpenAC statement input only if it passes this path.

Claim addressing

Claim values are indexed by position in the disclosure array. The SDK's birthday detection illustrates the naming-based addressing before index assignment:

// source: wallet-unit-poc/openac-sdk/src/credential.ts:75-84
findBirthdayClaim(): number | null {
const birthdayKeys = ["roc_birthday", "birthdate", "birthday", "date_of_birth"];

for (const claim of this.claims) {
if (birthdayKeys.includes(claim.name)) {
return claim.index;
}
}

return null;
}

After binding, the claim's index is the stable identifier used in predicate definitions (claimRef in ShowInputOptions).

Device binding requirement

The credential must carry a device binding key:

// source: wallet-unit-poc/openac-sdk/src/credential.ts:87-97
get deviceBindingKey(): EcdsaPublicKey | null {
const cnf = this.payload.cnf as { jwk?: EcdsaPublicKey } | undefined;
if (!cnf?.jwk) return null;
const jwk = cnf.jwk;
if (jwk.kty !== "EC" || jwk.crv !== "P-256" || !jwk.x || !jwk.y) {
return null;
}
return jwk;
}

If cnf.jwk is absent or malformed, precompute fails. The device key is bound into Show via signDeviceNonce — see Show phase.

What does not become statement input

The SD-JWT binding treats the following as non-statement material:

  • JWT header fields (algorithm, key ID, type);
  • iss, iat, exp, nbf, sub, aud — retained for trust and validity checks, not for predicates unless a statement type explicitly uses them;
  • cnf.jwk — retained for device binding, not disclosure;
  • _sd_alg and digest algorithm selection — binding infrastructure.

ISO/IEC 18013-5 (mdoc) — experimental

Status: Experimental circuit target in wallet-unit-poc/circom (yarn compile:mdoc). Not wired to precompute / present in SDK 0.1.0. Do not assume feature parity with the SD-JWT path until versioned.

Binding model

For mdoc, the credential binding identifies:

  • issuer-signed data elements that may become OpenAC statement input, with their namespace and element identifier;
  • MSO (Mobile Security Object) as the issuer-authentication boundary;
  • issuer-authentication material (issuer certificate chain, signature over the MSO);
  • device-signed or session-specific material — separated from issuer-signed input, available for device binding only;
  • validity, status, and trust material for verifier-side checks.

The binding MUST separate issuer-signed data from device-signed or session-specific material. Only issuer-signed data may become statement input.

After binding, each retained value is addressed by its (namespace, element_identifier) pair — these identifiers are preserved through normalization.

Implementation status

The wallet-unit-poc/circom/docs/mdoc-spec.md documents the mdoc circuit specification. The mdoc circuit target is compiled separately from jwt. Full integration with the SDK Prepare/Show pipeline is tracked on the zkID 2026 roadmap.


ePassport (ICAO 9303)

The paper discusses ePassport data groups and hash bindings as a candidate credential family. The in-repo PoC path is ES256 SD-JWT, not raw ICAO LDS. Treat ICAO 9303 as the standards anchor for physical-document credentials; circuit work for dedicated DG parsing may extend the Prepare relation. Track the implementation profile for normative detail.


W3C Verifiable Credentials

Not implemented as a first-class parser in openac-sdk 0.1.0. Presentations that use the same signing and digest story (ES256 + selective disclosure) could be integrated at the Prepare input builder layer in future versions — flagged as roadmap.