Metadata-Version: 2.4
Name: vcb-verify
Version: 1.0.0
Summary: Verifiable Credential Barcode (VCB) verification for DL/ID PDF417 barcodes
Author: Subfile, LLC
License: BSD-3-Clause
Project-URL: Homepage, https://github.com/subfile-llc/vcb-verify
Project-URL: Repository, https://github.com/subfile-llc/vcb-verify
Project-URL: Issues, https://github.com/subfile-llc/vcb-verify/issues
Keywords: verifiable-credentials,vcb,pdf417,aamva,did,data-integrity,w3c
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: BSD License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Security :: Cryptography
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.12
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: cborld>=0.1.0
Requires-Dist: PyLD>=2.0.0
Requires-Dist: rdfcanon>=1.0.0
Requires-Dist: rdflib>=7.0.0
Requires-Dist: base58>=2.1.1
Requires-Dist: cryptography>=42.0.0
Requires-Dist: httpx>=0.27
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == "dev"
Requires-Dist: pytest-asyncio>=0.24; extra == "dev"
Requires-Dist: ruff>=0.8; extra == "dev"
Dynamic: license-file

# VCB Verify

[![CI](https://github.com/subfile-llc/vcb-verify/actions/workflows/ci.yml/badge.svg)](https://github.com/subfile-llc/vcb-verify/actions/workflows/ci.yml)
[![PyPI](https://img.shields.io/pypi/v/vcb-verify)](https://pypi.org/project/vcb-verify/)
[![Python](https://img.shields.io/pypi/pyversions/vcb-verify)](https://pypi.org/project/vcb-verify/)

> Verify [Verifiable Credential Barcodes][VCB] on DL/ID PDF417 barcodes.

## Background

[Verifiable Credential Barcodes][VCB] embed digitally signed credentials
inside PDF417 barcodes on government-issued drivers licenses and identification
cards. This library verifies those credentials: it parses the barcode,
checks the cryptographic proof, and optionally checks revocation status.

The library is jurisdiction-agnostic. Any issuer that follows the
[W3C VCB specification][VCB] can be supported by adding an `IssuerProfile`.
Built-in profiles are included for California DMV and the W3C Utopia test
vectors. Each profile bundles DID documents for offline use, but online
resolution is recommended for production to pick up key rotations. Host
allowlists on each profile restrict which remote resources can be fetched
during verification.

The API is async (`asyncio`). Python 3.12+.

## Install

```bash
pip install vcb-verify
```

Development:

```bash
git clone https://github.com/subfile-llc/vcb-verify.git
cd vcb-verify
pip install -e ".[dev]"
```

## Usage

### Basic verification

```python
import asyncio
from vcb_verify import verify
from vcb_verify.issuers import ca_dmv_prod_profile

async def main():
    scanned_data = ...  # PDF417 bytes or string from barcode scanner
    result = await verify(data=scanned_data, profile=ca_dmv_prod_profile())

    if result.valid:
        print("Valid credential")
    else:
        print(f"Invalid: {result.error}")

asyncio.run(main())
```

By default, barcodes without VCB data are returned as not-valid rather than
raising an error. Pass `require_vcb=True` to treat missing VCB data as a
verification failure, or use `profile.vcb_required_after` to require it
based on the credential's issued date.

### Check revocation status

Revocation checking requires a network request to the issuer's status list
endpoint:

```python
result = await verify(
    data=scanned_data,
    profile=ca_dmv_prod_profile(),
    verify_status=True,
)
```

### Online key resolution

To fetch DID documents live from the issuer instead of using bundled copies:

```python
profile = ca_dmv_prod_profile()
result = await verify(
    data=scanned_data,
    profile=profile,
    document_loader=profile.get_loader(online=True),
)
```

### Debug output

```python
result = await verify(
    data=scanned_data,
    profile=ca_dmv_prod_profile(),
    debug=True,
)
if result.debug:
    print(result.debug["credential"])
    print(result.debug["aamva_hash"].hex())
```

## Issuer Profiles

An `IssuerProfile` configures verification for a specific jurisdiction.

### Built-in profiles

| Profile | Function | Description |
|---------|----------|-------------|
| CA DMV Production | `ca_dmv_prod_profile()` | California production credentials |
| CA DMV UAT | `ca_dmv_uat_profile()` | California test/UAT credentials |
| W3C Utopia | `utopia_test_profile()` | W3C spec test vectors (offline only) |

```python
from vcb_verify.issuers import ca_dmv_prod_profile, ca_dmv_uat_profile, utopia_test_profile
```

### Custom profiles

```python
from vcb_verify import IssuerProfile

profile = IssuerProfile(
    name="my-state",
    issuer_identification_number="123456",
    issued_state_value="XX",
    vcb_subfile="ZC",
    vcb_field="ZCE",
    type_table={ ... },           # CBOR-LD compression table for this issuer
    allowed_hosts={"did.example.com"},
    static_documents={ ... },     # Bundled DID documents for offline mode
)
```

See the [CA DMV profile](src/vcb_verify/issuers/ca_dmv/profiles.py) for a
complete example.

## API

### `verify(data, *, profile, ...)`

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `data` | `bytes \| str` | *(required)* | PDF417 barcode data |
| `profile` | `IssuerProfile` | *(required)* | Issuer configuration |
| `require_vcb` | `bool` | `False` | Require VCB data to be present |
| `verify_status` | `bool` | `False` | Check revocation status online |
| `document_loader` | `DocumentLoader` | `None` | Override the default document loader |
| `debug` | `bool` | `False` | Include debug details in result |

**Returns:** `VerifyResult`

| Field | Type | Description |
|-------|------|-------------|
| `valid` | `bool` | `True` if the credential is valid |
| `error` | `Exception \| None` | Error details if invalid |
| `debug` | `dict \| None` | Debug details if requested |

### `IssuerProfile.get_loader(*, online=False)`

Returns a document loader for this profile. When `online` is `False`
(default), DID documents are resolved from bundled static copies.
Set `online=True` to fetch live documents from the issuer's server.

## Contribute

```bash
pip install -e ".[dev]"
pytest
ruff check src tests
```

## License

[BSD-3-Clause](LICENSE) © [Subfile, LLC](https://github.com/subfile-llc/vcb-verify)

[VCB]: https://www.w3.org/TR/vc-barcodes-1.0/
