Skip to main content

claim169

Alpha Software: This library is under active development. APIs may change without notice. Not recommended for production use without thorough testing.

PyPI Python License: MIT

A Python library for encoding and decoding MOSIP Claim 169 QR codes. Built on Rust for performance and security.

Installation

pip install claim169

Overview

MOSIP Claim 169 defines a standard for encoding identity data in QR codes using:

  • CBOR encoding with numeric keys for compactness
  • CWT (CBOR Web Token) for standard claims
  • COSE_Sign1 for digital signatures
  • COSE_Encrypt0 for optional encryption
  • zlib compression + Base45 encoding for QR-friendly output

Quick Start

import claim169

# Decode a QR code (recommended: with signature verification)
qr_text = "6BF5YZB2..."  # Base45-encoded QR content
result = claim169.decode(qr_text, verify_with_ed25519=public_key_bytes)

# Access identity data
print(f"ID: {result.claim169.id}")
print(f"Name: {result.claim169.full_name}")
print(f"DOB: {result.claim169.date_of_birth}")

# Access CWT metadata
print(f"Issuer: {result.cwt_meta.issuer}")
print(f"Expires: {result.cwt_meta.expires_at}")

Encoding

Create Claim 169 QR code data with various signing and encryption options. In production, keys are typically provisioned and managed externally (HSM/KMS or secure key management). The examples below assume you already have key bytes.

from claim169 import Claim169Input, CwtMetaInput, encode_with_ed25519, encode_signed_encrypted

# Create identity data
claim = Claim169Input(id="123456", full_name="John Doe")
claim.date_of_birth = "1990-01-15"
claim.email = "john@example.com"

# Create CWT metadata
meta = CwtMetaInput(issuer="https://issuer.example.com", expires_at=1800000000)

# Encode with Ed25519 signature (32-byte private key)
qr_data = encode_with_ed25519(claim, meta, private_key_bytes)

# Encode with signature and AES-256 encryption
qr_data = encode_signed_encrypted(claim, meta, sign_key_bytes, encrypt_key_bytes)

Encoding Functions

from claim169 import (
    encode_with_ed25519,           # Ed25519 signed
    encode_with_ecdsa_p256,        # ECDSA P-256 signed
    encode_signed_encrypted,       # Signed + AES-256-GCM encrypted
    encode_signed_encrypted_aes128,# Signed + AES-128-GCM encrypted
    encode_with_signer,            # Custom signer (HSM, cloud KMS, etc.)
    encode_with_signer_and_encryptor,  # Custom signer + encryptor
    encode_unsigned,               # Unsigned (testing only)
    generate_nonce,                # Generate random 12-byte nonce
)

# Ed25519 signed (recommended)
qr_data = encode_with_ed25519(claim, cwt_meta, private_key)  # 32-byte key

# ECDSA P-256 signed
qr_data = encode_with_ecdsa_p256(claim, cwt_meta, private_key)  # 32-byte key

# Signed and encrypted (AES-256-GCM)
qr_data = encode_signed_encrypted(claim, cwt_meta, sign_key, encrypt_key)

# Signed and encrypted (AES-128-GCM)
qr_data = encode_signed_encrypted_aes128(claim, cwt_meta, sign_key, encrypt_key)

# Unsigned (for testing only - not recommended for production)
qr_data = encode_unsigned(claim, cwt_meta)

# Generate a random 12-byte nonce for encryption
nonce = generate_nonce()

Custom Signer (HSM/Cloud KMS)

For integrating with external crypto providers like HSMs, cloud KMS (AWS KMS, Google Cloud KMS, Azure Key Vault), smart cards, TPMs, or remote signing services:

from claim169 import encode_with_signer, encode_with_signer_and_encryptor

def my_signer(algorithm: str, key_id: bytes | None, data: bytes) -> bytes:
    """Custom signer callback.

    Args:
        algorithm: COSE algorithm name ("EdDSA" or "ES256")
        key_id: Optional key identifier
        data: The data to sign (COSE Sig_structure)

    Returns:
        Signature bytes (64 bytes for EdDSA, 64 bytes for ES256)
    """
    return my_hsm.sign(key_id, data)

# Sign with custom signer
qr_data = encode_with_signer(claim, meta, my_signer, "EdDSA")

# Sign with custom signer and key_id
qr_data = encode_with_signer(claim, meta, my_signer, "EdDSA", key_id=b"my-key-123")

# Sign with custom signer (ES256)
qr_data = encode_with_signer(claim, meta, my_signer, "ES256")

Custom Encryptor (HSM/Cloud KMS)

def my_encryptor(algorithm: str, key_id: bytes | None, nonce: bytes, aad: bytes, plaintext: bytes) -> bytes:
    """Custom encryptor callback.

    Args:
        algorithm: COSE algorithm name ("A256GCM" or "A128GCM")
        key_id: Optional key identifier
        nonce: 12-byte IV for AES-GCM
        aad: Additional authenticated data
        plaintext: Data to encrypt

    Returns:
        Ciphertext with authentication tag appended
    """
    return my_hsm.encrypt(key_id, nonce, aad, plaintext)

# Sign with custom signer and encrypt with custom encryptor
qr_data = encode_with_signer_and_encryptor(
    claim, meta,
    my_signer, "EdDSA",
    my_encryptor, "A256GCM"
)

Signature Verification

Ed25519 Verification (Recommended)

# Decode with Ed25519 signature verification
public_key = bytes.fromhex("d75a980182b10ab7...")  # 32 bytes
result = claim169.decode_with_ed25519(qr_text, public_key)

if result.is_verified():
    print("Signature is valid!")

ECDSA P-256 Verification

# Decode with ECDSA P-256 signature verification
public_key = bytes.fromhex("04...")  # SEC1-encoded (33 or 65 bytes)
result = claim169.decode_with_ecdsa_p256(qr_text, public_key)

Custom Verifier (HSM/Cloud KMS)

For integrating with external crypto providers like HSMs, cloud KMS (AWS KMS, Google Cloud KMS, Azure Key Vault), smart cards, or TPMs:

def my_verifier(algorithm: str, key_id: bytes | None, data: bytes, signature: bytes):
    """Custom verifier callback.

    Args:
        algorithm: COSE algorithm name ("EdDSA" or "ES256")
        key_id: Optional key identifier from COSE header
        data: The signed data (COSE Sig_structure)
        signature: The signature bytes

    Raises:
        Exception: If verification fails
    """
    # Delegate to your crypto provider
    my_hsm.verify(key_id, data, signature)

result = claim169.decode_with_verifier(qr_text, my_verifier)

Encrypted Payloads

AES-GCM Decryption

# Decrypt with AES-256-GCM key
aes_key = bytes.fromhex("000102030405...")  # 32 bytes for AES-256
result = claim169.decode_encrypted_aes(qr_text, aes_key, allow_unverified=True)  # testing only

With Nested Signature Verification

def verify_callback(algorithm, key_id, data, signature):
    public_key.verify(signature, data)

result = claim169.decode_encrypted_aes(
    qr_text,
    aes_key,
    verifier=verify_callback
)

Custom Decryptor (HSM/Cloud KMS)

def my_decryptor(algorithm: str, key_id: bytes | None, nonce: bytes, aad: bytes, ciphertext: bytes) -> bytes:
    """Custom decryptor callback.

    Args:
        algorithm: COSE algorithm name ("A256GCM" or "A128GCM")
        key_id: Optional key identifier from COSE header
        nonce: 12-byte IV from COSE header
        aad: Additional authenticated data (COSE Enc_structure)
        ciphertext: The encrypted data with auth tag

    Returns:
        Decrypted plaintext bytes
    """
    return my_hsm.decrypt(key_id, nonce, aad, ciphertext)

# Provide a verifier for the inner COSE_Sign1 (recommended)
result = claim169.decode_with_decryptor(qr_text, my_decryptor, verifier=my_verifier)

Decode Options

# Skip biometric data for faster parsing
result = claim169.decode(
    qr_text,
    verify_with_ed25519=public_key_bytes,
    skip_biometrics=True,
)

# Limit decompressed size (default: 64KB)
result = claim169.decode(
    qr_text,
    verify_with_ed25519=public_key_bytes,
    max_decompressed_bytes=32768,
)

Data Model

DecodeResult

result.claim169           # Claim169 - Identity data
result.cwt_meta           # CwtMeta - Token metadata
result.verification_status  # "verified", "skipped", or "failed"

# Helper methods
result.is_verified()      # True if signature was verified

Claim169Input

Input class for encoding identity data into QR codes.

from claim169 import Claim169Input

claim = Claim169Input(id="123456", full_name="John Doe")

# Set optional fields
claim.first_name = "John"
claim.last_name = "Doe"
claim.date_of_birth = "1990-01-15"
claim.gender = 1  # 1=Male, 2=Female, 3=Other
claim.email = "john@example.com"
claim.phone = "+1234567890"
claim.address = "123 Main St"
claim.nationality = "US"
claim.marital_status = 1

CwtMetaInput

Input class for encoding CWT (CBOR Web Token) metadata.

from claim169 import CwtMetaInput

meta = CwtMetaInput(issuer="https://issuer.example.com", expires_at=1800000000)

# Set optional fields
meta.subject = "user-123"
meta.not_before = 1700000000
meta.issued_at = 1700000000

Claim169

claim = result.claim169

# Demographics
claim.id                  # Unique identifier
claim.full_name           # Full name
claim.first_name          # First name
claim.middle_name         # Middle name
claim.last_name           # Last name
claim.date_of_birth       # ISO 8601 format
claim.gender              # 1=Male, 2=Female, 3=Other
claim.address             # Address
claim.email               # Email address
claim.phone               # Phone number
claim.nationality         # Nationality code
claim.marital_status      # Marital status code

# Biometrics (when present)
claim.face                # List of face biometrics
claim.right_thumb         # Right thumb fingerprint
# ... (all finger/iris/palm biometrics)

# Helper methods
claim.has_biometrics()    # True if any biometric data present
claim.to_dict()           # Convert to dictionary

CwtMeta

meta = result.cwt_meta

meta.issuer               # Token issuer
meta.subject              # Token subject
meta.expires_at           # Expiration timestamp (Unix seconds)
meta.not_before           # Not-before timestamp
meta.issued_at            # Issued-at timestamp

# Helper methods
meta.is_valid_now()       # True if token is currently valid
meta.is_expired()         # True if token has expired

Biometric

bio = claim.face[0]

bio.data                  # Raw biometric data bytes
bio.format                # Biometric format code
bio.sub_format            # Sub-format code (optional)
bio.issuer                # Issuer identifier (optional)

Exception Types

from claim169 import (
    Claim169Exception,       # Base exception
    Base45DecodeError,       # Invalid Base45 encoding
    DecompressError,         # zlib decompression failed
    CoseParseError,          # Invalid COSE structure
    CwtParseError,           # Invalid CWT structure
    Claim169NotFoundError,   # Missing claim 169
    SignatureError,          # Signature verification failed
    DecryptionError,         # Decryption failed
)

Development

Building from Source

# Install maturin
pip install maturin

# Build and install in development mode
cd core/claim169-python
maturin develop

Running Tests

cd core/claim169-python
uv run pytest tests/ -v

License

MIT License - See LICENSE file for details.

Release files for claim169 0.3.0

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

Source distribution (sdist)

Source distribution for claim169 0.3.0
File Size Uploaded
claim169-0.3.0.tar.gz 184.3 kB Details

Built distributions (wheels)

Table of built distributions (wheels) for claim169 0.3.0
File Interpreter ABI Platform
claim169-0.3.0-cp38-abi3-win_amd64.whl CPython 3.8 abi3 Windows x86-64 Details
claim169-0.3.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl CPython 3.8 abi3 Linux glibc 2.17+ x86-64 Details
claim169-0.3.0-cp38-abi3-macosx_11_0_arm64.whl CPython 3.8 abi3 macOS 11.0+ ARM64 Details

Total release size: 2.4 MB

Release files / claim169-0.3.0.tar.gz

Download URL claim169-0.3.0.tar.gz
Size 184.3 kB
Tags Source
SHA-256 checksum
How to use checksums
4c53bd7596a1c6483c9aa73fe10a9ed4c0187ea1ae4e2e33f06a77c69f63805e
BLAKE2b-256 checksum
How to use checksums
c55741964700bee148671ca49c0fbd749517027603d4a1b199faafa808cc3584
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

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 Mar 10, 2026.

Transparency log

Release files / claim169-0.3.0-cp38-abi3-win_amd64.whl

Download URL claim169-0.3.0-cp38-abi3-win_amd64.whl
Size 646.0 kB
Tags CPython 3.8 Windows x86-64 abi3
SHA-256 checksum
How to use checksums
56322e8e34747a25db1c9243fb36243a05c3eee6662ad87f55573bf00fb49dca
BLAKE2b-256 checksum
How to use checksums
1eea09f585e8bc42383084822681f1bada9cae32ec8b1c407302ab7ed73e5c54
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

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 Mar 10, 2026.

Transparency log

Release files / claim169-0.3.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL claim169-0.3.0-cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 836.1 kB
Tags CPython 3.8 Linux glibc 2.17+ x86-64 abi3
SHA-256 checksum
How to use checksums
9378cf0b912dca2323d83021ca200e05dfc7a39d54eda61928982c88385cfcc9
BLAKE2b-256 checksum
How to use checksums
c704b65d8a7c5e1eaebda027bfe533d7f705700c1f97a34e31abafe4fcb2e4d8
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

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 Mar 10, 2026.

Transparency log

Release files / claim169-0.3.0-cp38-abi3-macosx_11_0_arm64.whl

Download URL claim169-0.3.0-cp38-abi3-macosx_11_0_arm64.whl
Size 730.9 kB
Tags CPython 3.8 abi3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
1819c8d5d923ad7e6afb1761444e4b6225d56e2499af80c5d375668e0a96186e
BLAKE2b-256 checksum
How to use checksums
bf0b441ec9587f9911d5bb4c22d96889c58b834db19ae68bfaf2ddeb32bf4dd7
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

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 Mar 10, 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