HomeOpenID4VCIProof of Possession

Proof of Possession

How wallets prove control of a private key during credential issuance, enabling key binding for holder authentication during presentation.

What is Proof of Possession?

Proof of Possession (PoP) demonstrates that the wallet controls a private key. The issuer binds the corresponding public key to the credential, enabling holder authentication during presentation.

Key Generation

Wallet generates a key pair (typically in secure hardware). Private key never leaves the device.

c_nonce

Server-provided nonce ensures the proof is fresh and wasn't created for a different request.

Signed Proof

Wallet signs a JWT or CWT containing the nonce using its private key.

Key Binding

Issuer embeds the public key in the credential. Holder must use this key during presentation.

JWT Proof Format

The most common proof type. A JWT signed by the wallet's private key, containing the issuer's nonce.

Header

{
  "typ": "openid4vci-proof+jwt",
  "alg": "ES256",
  "kid": "did:key:z6MkiK...#z6MkiK..."
}
typ
Must be "openid4vci-proof+jwt"
alg
Signing algorithm (ES256, EdDSA, etc.)
kid
Key identifier (DID URL or JWK thumbprint)

Payload

{
  "iss": "did:key:z6MkiK...",
  "aud": "https://issuer.example.com",
  "iat": 1704067200,
  "nonce": "tZignsnFbp"
}

JWT Proof Claims

ClaimNameRequiredPurpose
issIssuerRequiredDID or client_id of the wallet. Identifies the key controller.
audAudienceRequiredCredential issuer identifier (from metadata). Prevents token reuse.
iatIssued AtRequiredUnix timestamp when proof was created. Must be recent.
nonceNonceRequiredThe c_nonce from issuer. Proves freshness.
Nonce Freshness
The c_nonce has a limited lifetime (c_nonce_expires_in). If expired, request a fresh nonce from the nonce endpoint or make a new token request.

CWT Proof Format

For ISO mDOC credentials, CBOR Web Token (CWT) proofs may be used. Same claims as JWT but in CBOR encoding with COSE signature.

CWT Proof Structure
COSE_Sign1 = [
  / protected / << {
    / typ / 16: "openid4vci-proof+cwt",
    / alg / 1: -7   / ES256 /
  } >>,
  / unprotected / {
    / kid / 4: h'...'
  },
  / payload / << {
    / iss / 1: "did:key:z6MkiK...",
    / aud / 3: "https://issuer.example.com",
    / iat / 6: 1704067200,
    / nonce / 10: "tZignsnFbp"
  } >>,
  / signature / h'...'
]

When to Use CWT

Use CWT proofs when requesting mDOC credentials or when the issuer specifically supports cwt proof type in metadata.

Claim Numbers

CWT uses numeric claim keys: 1 (iss), 3 (aud), 6 (iat), 10 (nonce). Same semantics as JWT claims.

Key Binding Methods

The issuer binds the wallet's public key to the credential using one of several methods.

did:keyDID Key Method

Public key encoded as a DID. Self-contained, no resolution needed.

did:key:z6MkiKq...
did:jwkDID JWK Method

JWK encoded as a DID. Supports more key types.

did:jwk:eyJrdHkiOiJFQyIsImNydiI6IlAtMjU2Ii...
cnfConfirmation Claim (SD-JWT)

Public key embedded directly in credential as JWK.

"cnf": {
  "jwk": {
    "kty": "EC",
    "crv": "P-256",
    "x": "...",
    "y": "..."
  }
}

Presentation with Key Binding

When presenting the credential, the holder signs a proof (KB-JWT for SD-JWT, deviceAuth for mDOC) using the same private key. The verifier checks that:

  • Signature is valid
  • Signing key matches the bound key in the credential
  • Proof includes verifier's nonce (if challenge-response)

Security Considerations

Proper implementation of proof of possession is critical for credential security.

Nonce Freshness & Replay Prevention

Key Security

Audience Validation
Always verify the 'aud' claim matches the issuer identifier exactly. Accepting proofs with incorrect audiences enables cross-issuer attacks.