This repository provides Python bindings for the ISO 18013-5 Mobile Driver's License (mDL) library using UniFFI, allowing interaction with mDL documents from Python applications.
The ISO MDL UniFFI library provides a Python interface to the Rust-based isomdl library, enabling:
- Holder functionality: Create presentation sessions for mDL documents
- Reader functionality: Verify and read mDL documents from holders
- Document management: Create, manage, and present mobile driver's licenses
- Cross-platform support: Works on macOS, Linux, and Windows
This project uses UniFFI to generate Python bindings from Rust code:
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ Python App │ ←→ │ UniFFI Bindings │ ←→ │ Rust Library │
└─────────────────┘ └──────────────────┘ └─────────────────┘
│
┌───────▼───────┐
│ isomdl crate │
└───────────────┘
- Rust: Latest stable version (install via rustup)
- Python: 3.9 or later
- UV: Python package manager (recommended, install via
pip install uv) - Cross-compilation tools (for building binaries for multiple platforms)
- Xcode command line tools:
xcode-select --install
- Build essentials:
sudo apt update && sudo apt install build-essential
- MinGW-w64 toolchain
git clone <repository-url>
cd isomdl-uniffi
git checkout python-bindingsFor contributors, set up pre-commit hooks to ensure code quality:
# Install pre-commit
pip install pre-commit
# Install git hook scripts
pre-commit install
# (Optional) Run against all files
pre-commit run --all-filesSee python/precommit/README.md for more details.
This project includes comprehensive GitHub Actions workflows:
- Quick validation for PRs with essential checks
- Runs on every PR to
mainanddevelopbranches - Validates Rust formatting, compilation, and tests
- Builds Python bindings and runs test suite
- Verifies selective disclosure functionality
- Checks Python code quality (ruff)
- Comprehensive testing across multiple platforms and Python versions
- Matrix builds: Ubuntu, macOS, Windows × Python 3.9-3.12
- Security auditing with
cargo audit - Integration tests including selective disclosure validation
- Automated releases on version tags (
v*) - Builds Python wheels for multiple platforms
- Publishes Rust crate to crates.io
- Creates GitHub releases with binaries
- Automated dependency updates for Rust crates
- Weekly updates for GitHub Actions
- Properly labeled and organized PRs
All workflows ensure that:
- ✅ Rust code compiles and passes tests
- ✅ Python bindings build successfully
- ✅ Complete test suite passes (including selective disclosure tests)
- ✅ Code meets formatting and quality standards
- ✅ Security vulnerabilities are detected early
- ✅ All commits are properly signed with DCO (Developer Certificate of Origin)
Using UV (recommended if you have UV installed):
cd python
uv sync --extra dev
uv run python build.pyUsing standard Python:
cd python
python3 build.pyDirect build script:
./python/precommit/build-bindings.shThis will:
- Build the Rust library in release mode with UniFFI symbol preservation
- Generate Python bindings using UniFFI
- Copy the bindings to the Python package directory
# Run the comprehensive test suite
./python/test-bindings.py# Create virtual environment (optional but recommended)
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
# Install the generated package
cd rust/out/python
pip install -e .
# Test the installation
python -c "import isomdl_uniffi; print('Success!')"import isomdl_uniffi as mdl
import uuid
# Create an mDL document from CBOR data
mdoc = mdl.Mdoc.from_cbor(cbor_data, "device_key_alias")
# Start a presentation session
session_uuid = uuid.uuid4()
session = mdl.MdlPresentationSession.new(mdoc, session_uuid)
print(f"QR Code URI: {session.qr_code_uri}")
print(f"BLE Identifier: {session.ble_ident.hex()}")import isomdl_uniffi as mdl
# Define what data elements to request
requested_items = {
"org.iso.18013.5.1": {
"family_name": True,
"given_name": True,
"birth_date": True
}
}
# Establish reader session
session_data = mdl.establish_session(
uri="mdoc://example-uri",
requested_items=requested_items,
trust_anchor_registry=["-----BEGIN CERTIFICATE-----\n..."]
)
print(f"Session UUID: {session_data.uuid}")Represents an ISO 18013-5 mobile document.
Methods:
from_cbor(data: bytes, key_alias: str) -> Mdoc: Create from CBOR datato_cbor() -> bytes: Export to CBOR formatnamespaces() -> list[str]: Get available namespaceselements_for_namespace(namespace: str) -> list[Element]: Get elements in namespace
Manages the holder's presentation session.
Methods:
new(mdoc: Mdoc, uuid: UUID) -> MdlPresentationSession: Create new sessionqr_code_uri: str: QR code for reader scanningble_ident: bytes: Bluetooth Low Energy identifier
Handles reader-side session management.
Methods:
establish_session(uri: str, requested_items: dict, trust_anchors: list[str]) -> MDLReaderSessionData
class Element:
identifier: str # Element name
value: Optional[str] # JSON representation of valueisomdl-uniffi/
├── rust/ # Rust source code
│ ├── src/
│ │ ├── lib.rs # Main library entry point
│ │ └── mdl/ # MDL-specific modules
│ │ ├── mod.rs # Module definitions
│ │ ├── holder.rs # Holder functionality
│ │ ├── reader.rs # Reader functionality
│ │ ├── mdoc.rs # Document management
│ │ └── util.rs # Utility functions
│ ├── Cargo.toml # Rust dependencies
│ ├── uniffi-bindgen.rs # UniFFI binding generator
│ └── out/ # Generated bindings (gitignored)
├── kotlin/ # Kotlin bindings (separate)
└── out/ # Generated bindings output
└── python/ # Python package output
- Setup development environment:
# Install development dependencies
cargo install uniffi-bindgen
rustup target add x86_64-apple-darwin aarch64-apple-darwin x86_64-unknown-linux-gnu- Build and test:
# Build library
cargo build --release
# Run Rust tests
cargo test
# Generate Python bindings
cargo run --bin uniffi-bindgen generate --library target/release/libisomdl_uniffi.dylib --language python --out-dir out/python
# Test Python bindings
cd out/python
uv venv test-env
source test-env/bin/activate
uv pip install -e .
python -c "import isomdl_uniffi; print('Import successful!')"The build scripts build binaries for the current platform only. For production deployments requiring multiple platforms, you can:
- Use GitHub Actions or CI/CD to build on different runners
- Use cross-compilation tools like cross-rs:
# Install cross-compilation tool
cargo install cross --git https://github.com/cross-rs/cross
# Add target platforms
rustup target add x86_64-apple-darwin aarch64-apple-darwin x86_64-unknown-linux-gnu x86_64-pc-windows-gnu
# Build for specific targets
cross build --release --target x86_64-unknown-linux-gnu --lib
cargo build --release --target x86_64-apple-darwin --lib
# ... etc for other platforms- Create universal macOS binaries using lipo:
# Build both architectures
cargo build --release --target x86_64-apple-darwin --lib
cargo build --release --target aarch64-apple-darwin --lib
# Merge into universal binary
lipo -create \
target/x86_64-apple-darwin/release/libisomdl_uniffi.dylib \
target/aarch64-apple-darwin/release/libisomdl_uniffi.dylib \
-output out/python/libisomdl_uniffi.dylibThis library is designed to be used within ACA-Py (Aries Cloud Agent Python) plugins. Example integration:
# In your ACA-Py plugin
from aries_cloudagent.core.profile import Profile
import isomdl_uniffi as mdl
class MDLHandler:
def __init__(self, profile: Profile):
self.profile = profile
async def create_presentation(self, mdoc_data: bytes):
# Create mDL document
mdoc = mdl.Mdoc.from_cbor(mdoc_data, "key_alias")
# Start presentation session
session = mdl.MdlPresentationSession.new(mdoc, uuid.uuid4())
return {
"qr_code": session.qr_code_uri,
"ble_ident": session.ble_ident.hex()
}uniffi: UniFFI framework for generating bindingsisomdl: Core ISO 18013-5 implementationserde: Serialization frameworkuuid: UUID generation and parsingbase64: Base64 encoding/decodingp256: ECDSA P-256 cryptographyanyhow: Error handling
Generated Python package has minimal dependencies, as most functionality is provided by the compiled Rust library.
We welcome contributions to this project! For detailed guidelines on how to contribute, including:
- Development environment setup
- Code standards and formatting
- Testing requirements
- Submission process
- DCO (Developer Certificate of Origin) requirements
Please see CONTRIBUTING.md for complete contribution guidelines.
see the LICENSE file for details.
This project incorporates code from the following open-source projects:
- isomdl by Spruce Systems, Inc. - https://github.com/spruceid/isomdl
- sprucekit-mobile by Spruce Systems, Inc. - https://github.com/spruceid/sprucekit-mobile
Both projects are dual-licensed under Apache 2.0 and MIT licenses.
See THIRD_PARTY_LICENSES.md for complete license information and attributions.
-
Build fails with "target not found"
- Ensure all required targets are installed:
rustup target add <target>
- Ensure all required targets are installed:
-
Python import fails
- Verify the dynamic library is in the correct location
- Check that Python can find the generated module
-
Cross-compilation issues
- Install
cross:cargo install cross - Use Docker for consistent cross-compilation environment
- Install
- Check the UniFFI documentation
- Review the isomdl library documentation
- Open an issue in this repository
isomdl-uniffi/
├── README.md # Main documentation
├── rust/ # Rust source code
│ ├── src/ # Rust library source
│ ├── Cargo.toml # Rust dependencies
│ ├── uniffi-bindgen.rs # UniFFI binding generator
│ └── out/ # Generated bindings (gitignored)
│ └── python/ # Python bindings output
│ ├── isomdl_uniffi.py # Generated Python module
│ ├── libisomdl_uniffi.dylib # Compiled library
│ ├── setup.py # Package setup
│ └── pyproject.toml # Package config
└── kotlin/ # Kotlin bindings (separate)
- Add comprehensive test suite
- Improve error handling and error types
- Add more usage examples
- Performance optimizations
- Enhanced documentation
- CI/CD pipeline for automated builds
- Cross-platform binary distribution