Skip to main content

Key proof of possession

When the Wallet Unit asks for a credential, it must prove it controls the private key the credential will be bound to. It does this with a key proof of possession: a JWT of media type openid4vci-proof+jwt (OpenID4VCI v1.0 Appendix F.1), signed with the attested private key.

Two things travel in that proof:

  • The signature is the proof of possession: only the holder of the private key could produce it.
  • The key_attestation header carries the Key Attestation JWT, so proof of possession and key attestation arrive together. The Issuer reads the KA from the header, then checks the proof was signed by one of the keys the KA attests.

The payload binds the proof to a specific Issuer and a specific moment:

  • aud is the Credential Issuer Identifier.
  • nonce echoes the Issuer's c_nonce, which prevents replay.
  • iat must be fresh.

Switch the profile to see the difference. With a Key Attestation, both profiles send a single proof carrying the KA; they differ in which key signs it:

  • OpenID4VCI identifies the signing key with a kid in the proof header; the proof PoPs one of the attested keys, and the KA binds the rest.
  • TS3 v1.5 removed the kid requirement: the proof MUST be signed by attested_keys index 0.

OpenID4VCI's one-proof-per-key (the N-element proofs array) is its path without a Key Attestation.

Generating sample keys and signing tokens…

Illustrative aid

The proof is really signed by a holder key generated in your browser, and its key_attestation header carries the live KA. Decode it to confirm the structure.