-
Notifications
You must be signed in to change notification settings - Fork 0
Presentations
A presentation is what a holder sends a verifier: one or more
credentials, bound to that verifier (aud / client_id) and to a one-time
challenge (nonce), signed with the holder's key so it cannot be replayed
elsewhere. openvc verifies three shapes: VP-JWT, OpenID4VP 1.0
vp_token responses (including HAIP-encrypted ones), and Data Integrity
presentations with challenge / domain.
The holder wraps issued credentials in a signed JWT; verify checks the
holder signature, the aud + nonce binding, and cascade-verifies every
embedded credential through the pipeline:
from cryptography.hazmat.primitives.asymmetric import ec, ed25519
from openvc.keys import Ed25519SigningKey, P256SigningKey
from openvc.multibase import encode_multibase
from openvc.proof.vc_jwt import VcJwtProofSuite
from openvc.proof.vp_jwt import VpJwtProofSuite
# An issuer and a holder, each addressed by did:key so the flow runs offline.
ipriv = ec.generate_private_key(ec.SECP256R1())
iraw = P256SigningKey(ipriv, kid="_").public_key_raw(compressed=True)
imb = encode_multibase(bytes([0x80, 0x24]) + iraw) # multicodec p256-pub
issuer, issuer_did = P256SigningKey(ipriv, kid=f"did:key:{imb}#{imb}"), f"did:key:{imb}"
hpriv = ed25519.Ed25519PrivateKey.generate()
hraw = Ed25519SigningKey(hpriv, kid="_").public_key_raw()
hmb = encode_multibase(bytes([0xED, 0x01]) + hraw) # multicodec ed25519-pub
holder, holder_did = Ed25519SigningKey(hpriv, kid=f"did:key:{hmb}#{hmb}"), f"did:key:{hmb}"
# The issuer issues a credential ABOUT the holder.
vc = VcJwtProofSuite().sign({
"@context": ["https://www.w3.org/ns/credentials/v2"],
"type": ["VerifiableCredential"], "issuer": issuer_did,
"credentialSubject": {"id": holder_did, "role": "member"},
}, signing_key=issuer)
# The holder presents it, bound to this verifier and a one-time nonce.
vp = VpJwtProofSuite().sign(
[vc], holder_key=holder, audience="https://verifier.example", nonce="chal-42")
# require_holder_binding also asserts the credential was issued TO this holder.
result = VpJwtProofSuite().verify(
vp, audience="https://verifier.example", nonce="chal-42",
require_holder_binding=True)
print(result.holder, len(result.credentials), result.credentials[0].subject)For an OpenID4VP
verifier, verify_vp_token checks a wallet's response statelessly: you
pass the nonce and client_id your Authorization Request used and the DCQL
query it carried; openvc validates the response shape against the query and
the holder binding of each presentation:
from cryptography.hazmat.primitives.asymmetric import ec
from openvc import verify_vp_token
from openvc.keys import P256SigningKey
from openvc.multibase import encode_multibase
from openvc.proof.sd_jwt import SdJwtVcProofSuite
NONCE = "n-0S6_WzA2Mj"
CLIENT_ID = "x509_san_dns:verifier.example" # the full, prefixed Client Identifier
VCT = "https://credentials.example.com/identity_credential"
def did_key_p256():
priv = ec.generate_private_key(ec.SECP256R1())
raw = P256SigningKey(priv, kid="_").public_key_raw(compressed=True)
mb = encode_multibase(bytes([0x80, 0x24]) + raw)
return P256SigningKey(priv, kid=f"did:key:{mb}#{mb}"), f"did:key:{mb}"
issuer, issuer_did = did_key_p256()
holder, holder_did = did_key_p256()
# Issuer -> holder: an SD-JWT VC bound to the holder key (cnf) …
issued = SdJwtVcProofSuite().issue(
{"iss": issuer_did, "given_name": "Ada", "sub": holder_did},
signing_key=issuer, vct=VCT, disclosable=["given_name"],
holder_jwk=holder.public_jwk())
# … then holder -> verifier: a KB-JWT bound to this verifier's nonce + client_id.
presentation = SdJwtVcProofSuite().create_presentation(
issued, holder_key=holder, audience=CLIENT_ID, nonce=NONCE)
# The OpenID4VP response: an object keyed by DCQL Credential Query id.
vp_token = {"my_credential": [presentation]}
dcql_query = {"credentials": [
{"id": "my_credential", "format": "dc+sd-jwt", "meta": {"vct_values": [VCT]}}]}
result = verify_vp_token(vp_token, dcql_query=dcql_query,
nonce=NONCE, client_id=CLIENT_ID)
(p,) = result.for_query("my_credential")
print(p.format, p.holder, p.raw.claims["given_name"])Under the High Assurance Interoperability Profile the wallet encrypts the
whole response object into a JWE against the verifier's key-agreement key.
verify_encrypted_vp_response decrypts (ECDH-ES) and verifies in one call:
from openvc import verify_encrypted_vp_response
from openvc.keys import P256KeyAgreementKey
# The verifier's key-agreement key; publish public_jwk() (use: "enc") to wallets.
verifier_key = P256KeyAgreementKey.generate(kid="verifier#enc")
result = verify_encrypted_vp_response(
response_jwe, # the wallet's direct_post.jwt body
key=verifier_key, dcql_query=dcql_query, nonce=NONCE, client_id=CLIENT_ID)The full round-trip — including a stand-in wallet encryptor — is runnable in
examples/09_haip_encrypted_response.py.
openvc only decrypts (a verifier act); producing JWEs for wallets is out
of scope.
A Data Integrity presentation binds with challenge / domain instead of
nonce / aud; the pipeline enforces both plus cascade verification of the
embedded credentials. See the
API reference for the exact call surface.
aud/domain pins the presentation to your verifier; nonce/challenge
pins it to one run of your protocol. Always generate the nonce server-side,
one per authorization request, and reject reuse — openvc checks the binding,
but only you can guarantee freshness.
📖 Published automatically from the wiki/ directory of the main repository — edits made directly on the wiki are overwritten by the next sync. To change a page, open a PR against wiki/. Every python block on these pages is executed by CI. · API reference · LGPL-3.0-or-later
Start
Proof formats
Verifying in practice
- Presentations & OpenID4VP
- Resolving issuer keys
- Relying-party certificates
- Credential schemas
- Status lists
- Trust anchors: EU TL & EBSI
- Async verification
Walkthroughs
Operating
Reference