Skip to main content

openvc

PyPI Python versions CI License: LGPL-3.0-or-later

A dependency-light, HSM-friendly Verifiable Credentials core for Python: sign and verify W3C VCs in the three mainstream proof formats, resolve issuer keys, check revocation, and verify wallet presentations — fail-closed by default, with private keys that never have to enter the process.

Capability What is covered Spec
Sign & verify VC-JWT (ES256 / ES384 / EdDSA) VC-JOSE-COSE
SD-JWT VC — selective disclosure, Key Binding, Type Metadata SD-JWT VC
Data Integrity — eddsa-rdfc-2022, ecdsa-rdfc-2019, eddsa-jcs-2022 / ecdsa-jcs-2019 (stdlib JCS, no pyld), and selective-disclosure ecdsa-sd-2023 vc-di-eddsa / vc-di-ecdsa
Verify presentations VP-JWT, Data Integrity challenge/domain, and stateless OpenID4VP 1.0 vp_token — incl. HAIP direct_post.jwt JWE-encrypted responses and the W3C Digital Credentials API (origin-bound) OpenID4VP / HAIP / DC API
EUDI relying-party access certificate (WRPAC) — the X.509 identity of the requester, validated to ACA anchors ETSI TS 119 411-8
EUDI relying-party registration certificate (WRPRC) — the signed JWT/CWT carrying the requester's registered entitlements, cross-checked against its WRPAC and against what the request actually asks for ETSI TS 119 475
Issuer side OpenID4VCI 1.0 key-proof verification — verify the openid4vci-proof+jwt a wallet sends to your Credential Endpoint and get back the key to bind the credential to; discovery parsers for untrusted Credential Offers and Issuer Metadata. Stateless: no endpoint, no Authorization Server, nonce single-use injected as a required callable OpenID4VCI
Resolve issuer keys did:key, did:jwk, did:web, did:webvh (verifiable-history log) (+ did:ebsi via plugin), /.well-known/jwt-vc-issuer, X.509 x5c chains with SAN issuer binding DID
Revocation Bitstring Status List and Token Status List — check and issue W3C / IETF
Trust anchors Caller-pinned X.509 anchors, EU Trusted Lists (XML LOTL → national TL, and the JSON Lists of Trusted Entities with the EU WRPAC/WRPRC provider profiles), EBSI Trusted Issuers Registry (read-only plugin) ETSI TS 119 612 / TS 119 602 / EBSI
Keys The SigningKey protocol — an HSM / KMS / Vault backend is a drop-in; ES256 signatures are raw JOSE R‖S, never DER

Install

The PyPI distribution is openvc-core (the import package stays openvc):

pip install openvc-core

The core needs only cryptography and pyjwt. Everything heavier is an extra:

Extra Adds Pulls in
openvc-core[data-integrity] RDF-canonicalized suites (eddsa-rdfc-2022, ecdsa-rdfc-2019, ecdsa-sd-2023) pyld
openvc-core[ebsi] the EBSI registry client httpx
openvc-core[schema] credentialSchema (W3C VC JSON Schema) validation jsonschema
openvc-core[trustlist] XAdES signature verification for EU Trusted Lists signxml
openvc-core[pq] experimental ML-DSA (RFC 9964) signing + verification cryptography>=48
openvc-core[all] ebsi + data-integrity + schema + trustlist + pq + the dev tools

Quick start

Issue a VC-JWT and verify it with the one-call pipeline. verify_credential detects the format (VC-JWT / SD-JWT VC / Data Integrity / enveloped), resolves the issuer key, verifies the proof, and applies policy — types, audience, and fail-closed status:

from cryptography.hazmat.primitives.asymmetric import ed25519

from openvc import VerificationPolicy, verify_credential
from openvc.keys import Ed25519SigningKey
from openvc.multibase import encode_multibase
from openvc.proof.vc_jwt import VcJwtProofSuite

# An issuer key addressed by did:key, so the whole flow runs offline.
private_key = ed25519.Ed25519PrivateKey.generate()
public_raw = Ed25519SigningKey(private_key, kid="_").public_key_raw()
mb = encode_multibase(bytes([0xED, 0x01]) + public_raw)   # multicodec ed25519-pub
issuer = Ed25519SigningKey(private_key, kid=f"did:key:{mb}#{mb}")

token = VcJwtProofSuite().sign({
    "@context": ["https://www.w3.org/ns/credentials/v2"],
    "id": "urn:uuid:2f3a-example",
    "type": ["VerifiableCredential", "ExampleCredential"],
    "issuer": f"did:key:{mb}",
    "credentialSubject": {"id": "did:example:alice", "name": "Ada Lovelace"},
}, signing_key=issuer)

result = verify_credential(
    token, policy=VerificationPolicy(expected_types=["ExampleCredential"]))
print(result.format, result.issuer, result.subject)

Selective disclosure with SD-JWT VC — issue, present with a Key Binding JWT, verify; the holder proves possession of the cnf key and the verifier sees only what was disclosed:

from openvc.keys import Ed25519SigningKey
from openvc.proof.sd_jwt import SdJwtVcProofSuite

issuer = Ed25519SigningKey.generate(kid="https://issuer.example#key-1")
holder = Ed25519SigningKey.generate(kid="holder-key-1")
suite = SdJwtVcProofSuite()

sd_jwt = suite.issue(
    {"iss": "https://issuer.example", "given_name": "Ada", "age": 36},
    signing_key=issuer, disclosable=["given_name", "age"],
    holder_jwk=holder.public_jwk(), vct="https://credentials.example/identity")

presentation = suite.create_presentation(
    sd_jwt, holder_key=holder, audience="https://verifier.example", nonce="n-123")

result = suite.verify(
    presentation, public_key_jwk=issuer.public_jwk(),
    audience="https://verifier.example", nonce="n-123", require_key_binding=True,
    expected_vct="https://credentials.example/identity")
print(result.claims["given_name"], result.key_bound)

Every flow — Data Integrity proofs, VP-JWT and OpenID4VP presentations, status lists, remote HSM signing, EU Trusted Lists, EBSI — has a guide in the wiki and a runnable script in examples/.

Why openvc

  • HSM-first. Signing goes through the SigningKey protocol (alg / kid / sign), so a PKCS#11, AWS KMS, or Vault Transit backend drops in and the private key never enters the process. ES256 signatures are the correct raw JOSE R‖S form — the classic reason a locally-produced token fails elsewhere.
  • Fail-closed by construction. The {ES256, ES384, EdDSA, Ed25519} allow-list runs before any crypto (alg:none, RS*, HS* never reach a verifier); a declared credential status without a resolver rejects; an unparseable timestamp rejects; the JWT envelope is reconciled with the embedded credential.
  • Post-quantum ready (experimental). ML-DSA (RFC 9964, ML-DSA-44/65/87) signs and verifies VC-JWT / SD-JWT VC behind an explicit opt-in (allow_pq=True) and the [pq] extra — first-mover space; no maintained Python VC library signs ML-DSA today. Never a default trust path; the allow-list above is unchanged unless you opt in.
  • SSRF-guarded network. did:web and /.well-known/jwt-vc-issuer go through an https-only fetch that blocks non-globally-routable addresses, refuses redirects, and pins the connection to the validated IP (no DNS rebinding). Status-list and credentialSchema fetches use the same guard via the blessed defaults in openvc.resolvers; a custom resolver opts out.
  • Dependency-light. The core imports cryptography and pyjwt, nothing else; JSON canonicalization (RFC 8785) and the ecdsa-sd-2023 CBOR codec are hand-rolled on the stdlib, and pyld / httpx stay behind extras.
  • Conformance pinned by real vectors. eddsa-rdfc-2022 reproduces the official W3C test vector byte-for-byte; ecdsa-rdfc-2019 / ecdsa-sd-2023 verify the official vc-di-ecdsa vectors and match their intermediates; ISO 18013-5 mso_mdoc verifies the Annex D reference DeviceResponse; the OpenID4VCI key-proof verifier verifies the spec's own App. F.1 example against Issuer Metadata and Credential Offers recorded from the EU reference issuer; the EBSI client is verified against recorded pilot responses. Golden fixtures are the drift alarm. Beyond them, a test-only VC-API shim runs openvc through the official W3C suites (vc-data-model-2.0, vc-di-eddsa, vc-di-ecdsa, bitstring-status-list) for third-party conformance reports.

Documentation

  • Manual (wiki) — installation, a guide per proof format, presentations & OpenID4VP, issuer-key resolution, status lists, trust (EU Trusted Lists, EBSI), HSM integration, the security model, and the versioning contract.
  • API reference — generated from the docstrings, per module.
  • examples/ — thirteen runnable, offline scripts covering every flow (they run in CI, so they cannot rot).

Scope

openvc is the generic VC machinery a badge issuer, an EBSI verifier, or a EUDI wallet backend builds on — intentionally not an Open Badges library, a wallet, or a node operator. EBSI support is read-only (resolve did:ebsi, read the trust registries); onboarding/writing is out of scope. The openvc_ebsi plugin depends on openvc, never the reverse.

Three invariants bound everything above: no state, no transport, dependency-light. openvc signs and verifies credentials and validates what a wallet sends, but it runs no HTTP endpoint, holds no session, and persists nothing — where a protocol needs state, the store is injected and openvc keeps none of it. That, not "verify-only", is the boundary; see ADR-0007.

Project

pip install -e ".[all]"       # from a checkout
pytest                        # offline: deterministic, no network
OPENVC_EBSI_LIVE=1 pytest     # + the opt-in live EBSI smoke test

License

LGPL-3.0-or-later. Copyright © 2026 Luis González Fernández. See COPYING.LESSER and COPYING.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

openvc_core-1.25.0.tar.gz (417.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

openvc_core-1.25.0-py3-none-any.whl (271.4 kB view details)

Uploaded Python 3

File details

Details for the file openvc_core-1.25.0.tar.gz.

File metadata

  • Download URL: openvc_core-1.25.0.tar.gz
  • Upload date:
  • Size: 417.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for openvc_core-1.25.0.tar.gz
Algorithm Hash digest
SHA256 7d003d9079bd47b0a3eea3e32655dba63aebeb9818bea4e2f3aedbf14c040cce
MD5 921e48ed796bf0c6cd85383463d9ba56
BLAKE2b-256 f9bda0fd17bd6bc48435401324fa6a157d0d6d2ae032a43222c1b63088eba102

See more details on using hashes here.

Provenance

The following attestation bundles were made for openvc_core-1.25.0.tar.gz:

Publisher: ci.yml on luisgf/openvc

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file openvc_core-1.25.0-py3-none-any.whl.

File metadata

  • Download URL: openvc_core-1.25.0-py3-none-any.whl
  • Upload date:
  • Size: 271.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for openvc_core-1.25.0-py3-none-any.whl
Algorithm Hash digest
SHA256 5bada698c360ba50df45af83d135cdace619356aa0249ce0ce7547e0c1692209
MD5 5f934c6a000bd580d5ee6f1d3a7adab2
BLAKE2b-256 eb697cf4357d87dd2212ddd6fb940e3585301e684ec324986a4d131171ca0c34

See more details on using hashes here.

Provenance

The following attestation bundles were made for openvc_core-1.25.0-py3-none-any.whl:

Publisher: ci.yml on luisgf/openvc

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

1.26.0

2 files

This release

1.25.0 This release

2 files

1.24.0

2 files

1.23.1

2 files

1.23.0

2 files

1.22.0

2 files

1.21.0

2 files

1.20.4

2 files

1.20.3

2 files

1.20.2

2 files

1.20.1

2 files

1.20.0

2 files

1.19.3

2 files

1.19.1

2 files

1.19.0

2 files

1.18.0

2 files

1.17.0

2 files

1.16.0

2 files

1.15.0

2 files

1.14.0

2 files

1.13.1

2 files

1.13.0

2 files

1.12.0

2 files

1.11.1

2 files

1.11.0

2 files

1.10.0

2 files

1.9.0

2 files

1.8.0

2 files

1.7.0

2 files

1.6.0

2 files

1.5.0

2 files

1.4.0

2 files

1.3.0

2 files

1.2.0

2 files

1.1.0

2 files

1.0.1

2 files

1.0.0

2 files

0.9.0

2 files

0.8.1

2 files

0.8.0

2 files

0.7.1

2 files

0.7.0

2 files

0.6.0

2 files

0.5.0

2 files

0.4.0

2 files

0.3.1

2 files

0.3.0

2 files

0.2.1

2 files

0.2.0

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page