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_attestationheader 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:
audis the Credential Issuer Identifier.nonceechoes the Issuer'sc_nonce, which prevents replay.iatmust 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
kidin the proof header; the proof PoPs one of the attested keys, and the KA binds the rest. - TS3 v1.5 removed the
kidrequirement: the proof MUST be signed byattested_keysindex 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…
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.