Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

polaris-oid4vp

An OpenID4VP 1.0 verifier under the High Assurance Interoperability Profile. It checks a presentation from a wallet that has never heard of Polaris: an SD-JWT VC signed with ES256, with holder key binding, delivered over direct_post.jwt.

OpenID Certified

OpenID Certified™ by Egor Khaklin to the OpenID4VP 1.0 + HAIP 1.0 Verifier profile. polaris-oid4vp 1.0.0rc7 finished all eleven modules of the Foundation's hosted oid4vp-1final-verifier-haip-test-plan (sd_jwt_vc, direct_post.jwt) without failure, and the OpenID Foundation lists the certification (2026-09-24). It covers that version in that role: not an endorsement, not an audit, and not other versions.

Status: 1.0.0rc9, a release candidate, not certified: it carries the fixes made since 1.0.0rc7. Outside results: the Foundation's hosted suite (0.1.0, then 1.0.0rc7 for certification) and four unmodified external wallets: walt.id Wallet API v2 (last against 1.0.0-rc.3), Credo 0.6.3 (against 1.0.0rc7, 2026-09-27), eudi-dev v2.3.7 and OID4VCgo 0.12.0 (2026-09-28). No operator other than the author has run it and no independent security review exists. Details: the scoreboard.

Try it in ten minutes, without cloning anything: the stranger's path takes a clean machine with Docker, this package and an unmodified walt.id wallet to an accepted presentation. If it fails for you, please open an issue.

Install and run

pip install --pre polaris-oid4vp
polaris-oid4vp keygen --out ./pki --host verifier.example
polaris-oid4vp serve  --pki ./pki --port 9443 --issuer-jwks issuers.json
  • keygen makes a CA, a leaf it signs and a listener certificate, and prints the client_id and the anchor a counterparty registers. The profile rejects a self-signed leaf and a trust anchor inside the chain; keygen avoids both. Its keys are for testing.
  • serve --issuer-trust-anchor ca.pem trusts an issuer that signs with its certificate in x5c, as HAIP issuers do (repeatable; each file may hold several PEM certificates). The leaf must chain to an anchor in one link, be inside its validity period, and, when it states a key usage, include digitalSignature; one that states extended key usages must name one an issuer may hold. A leaf that states no key usage is not restricted by one. An iss must be a name the leaf gives: a URI subjectAltName exactly, or, for a leaf naming only DNS hosts, an https URL on one. Without iss, the certificate's subject is the issuer.
  • --issuer-jwks trusts every key it lists for every iss; list one issuer's keys per verifier.
  • serve with neither --issuer-jwks nor --issuer-trust-anchor refuses every presentation (issuer_key) and says so on stderr.
  • The CLI is a test harness. A deployment embeds Verifier (it needs your status policy; see Revocation).

This is a separate package from polaris-verify because it listens on a socket and depends on cryptography; polaris-verify does neither.

Verifying a presentation

from polaris_oid4vp.sdjwt import verify_presentation

verdict = verify_presentation(
    presentation,                     # the ~-separated string the wallet sent
    expected_nonce=request_nonce,     # what YOUR request asked for
    expected_audience=client_id,
    trust_anchors=[anchor],           # or issuer_jwks=[...]
    expected_vct="urn:eudi:pid:1",    # the credential type you asked for
)
verdict.authentic, verdict.code, verdict.reason, verdict.claims

expected_nonce and expected_audience are required: reading them from the presentation would make every presentation replayable. sdjwt.py touches no socket and reads no configuration.

Refusal code Refuses
issuer_signature an issuer signature that does not verify
sd_hash a key-binding JWT not bound to this presentation
kb_signature a key-binding signature that does not verify
nonce, audience a key-binding JWT for another request or verifier
kb_freshness a key-binding iat outside the window
credential_validity a credential past its exp or before its nbf
vct a credential of a type the query did not ask for
disclosure a disclosure of a claim that must be signed (iss, exp, cnf, ...), a colliding claim, or nesting past the depth cap
issuer_key an untrusted key, an x5c leaf outside its validity or not marked for signing, or one that does not name the credential's iss
malformed over 256 KiB, JSON over 64 KiB or 64 levels, or non-finite numbers

Recursive disclosures (SD-JWT 4.2.4.1) are supported.

Refusal codes are for the operator and never go on the wire. Every refused presentation gets one constant body, so the verifier is not a per-check oracle:

{"error": "invalid_request", "error_description": "the presentation was not accepted"}

Open: response timing still differs by which check refused; this is not measured.

The response JWE (jwe.py) accepts only ECDH-ES over P-256 with A128GCM or A256GCM, with a fresh encryption key per request. The listener (verifier.py, serve.py) is standard library only.

Revocation

verdict.revocation is one of seven states:

State Meaning
no_status_claim the credential names no status list
not_evaluated it names one and no resolver was supplied
unsupported_status its status claim is in a form this verifier cannot read
checked a resolver answered; status carries the issuer's value
unreachable a resolver was asked and got no answer
no_authority no key is stated as entitled to publish this issuer's status, so nothing was fetched
list_refused a status list was obtained and is not usable as evidence; code says why
  • Opt-in: pass status_resolver= to Verifier(...) or verify_presentation. Without one nothing is fetched and the state is not_evaluated.
  • status.py decides a Token Status List (draft-ietf-oauth-status-list) without opening a socket; decide_by_fetching takes your fetch callable.
  • Who may publish status for whom is stated, not inferred: StatedAuthority records that a named key may publish for a named issuer at a named URI. Anything else is no_authority, and no request is sent to a URI an unvetted credential names.

Conformance run

git clone --depth 1 https://gitlab.com/openid/conformance-suite.git
cd conformance-suite && docker compose -f docker-compose-prebuilt.yml up -d
python3 scripts/polaris-oid4vp-conformance-drill.py

Seven negative modules PASS; four positive modules end in REVIEW (the suite wants a screenshot of a displayed result). The drill carries two negative controls: a verifier that refuses everything must fail the positive modules, and one that accepts everything must fail all seven negative ones.

Tests

cd packages/polaris-oid4vp && python3 -m unittest test_sdjwt test_jwe test_verifier \
    test_serve test_cli test_conformance_capture test_status
  • 333 tests in seven files. test_conformance_capture replays a real direct_post.jwt response built by the OpenID Foundation suite's wallet (Nimbus JOSE), so this package's ECDH-ES, KDF, AAD binding and digests are checked against an independent implementation.
  • scripts/polaris-oid4vp-mutation-drill.py makes each of the 103 refusals accept and requires a test to fail: 97 are caught, and the 6 survivors are declared with their reasons.
  • Held-out boundary mutations (off-by-one windows, header binding, listener routing) each have a dedicated test class.

Metadata

Release files for polaris-oid4vp 1.0.0rc9

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for polaris-oid4vp 1.0.0rc9
File Size Uploaded
polaris_oid4vp-1.0.0rc9.tar.gz 57.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for polaris-oid4vp 1.0.0rc9
File Interpreter ABI Platform
polaris_oid4vp-1.0.0rc9-py3-none-any.whl Python 3 none any Details

Total release size: 118.0 kB

Release files / polaris_oid4vp-1.0.0rc9.tar.gz

Download URL polaris_oid4vp-1.0.0rc9.tar.gz
Size 57.7 kB
Tags Source
SHA-256 checksum
How to use checksums
19ac649498924fc6d2b93d646e066bcda4979dfc195a3d501bd9a1d7025a93b5
BLAKE2b-256 checksum
How to use checksums
cd48af732ed452784341841d984d43b6593f0e48cab6705d40e991e9d55047e7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 30, 2026.

Transparency log

Release files / polaris_oid4vp-1.0.0rc9-py3-none-any.whl

Download URL polaris_oid4vp-1.0.0rc9-py3-none-any.whl
Size 60.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
d25b1f7d27380bd3dbe7387f0938e6c3badc36a878cbbcd0ab09a81511e7ce79
BLAKE2b-256 checksum
How to use checksums
7d03ed6db68a15b91468d9646d998ddba7b67d17faaa2059d252cf97c4ee1e7b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 30, 2026.

Transparency log
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