Skip to main content

MOSIP Claim 169 QR Code decoder library

Project description

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_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)

# 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()

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 Support)

For hardware security module (HSM) integration:

def my_hsm_verify(algorithm: str, key_id: bytes | None, data: bytes, signature: bytes):
    """Custom verifier callback for HSM integration.

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

    Raises:
        Exception: If verification fails
    """
    # Delegate to your HSM
    hsm.verify(key_id, data, signature)

result = claim169.decode_with_verifier(qr_text, my_hsm_verify)

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 Support)

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

    Args:
        algorithm: COSE algorithm name (e.g., "A256GCM")
        key_id: Optional key identifier from COSE header
        nonce: IV/nonce from COSE header
        aad: Additional authenticated data (Enc_structure)
        ciphertext: The encrypted data

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

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

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.

Project details


Download files

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

Source Distribution

claim169-0.1.0a0.tar.gz (145.3 kB view details)

Uploaded Source

Built Distributions

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

claim169-0.1.0a0-cp311-cp311-win_amd64.whl (529.4 kB view details)

Uploaded CPython 3.11Windows x86-64

claim169-0.1.0a0-cp311-cp311-macosx_11_0_arm64.whl (615.3 kB view details)

Uploaded CPython 3.11macOS 11.0+ ARM64

claim169-0.1.0a0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (715.1 kB view details)

Uploaded CPython 3.8manylinux: glibc 2.17+ x86-64

File details

Details for the file claim169-0.1.0a0.tar.gz.

File metadata

  • Download URL: claim169-0.1.0a0.tar.gz
  • Upload date:
  • Size: 145.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for claim169-0.1.0a0.tar.gz
Algorithm Hash digest
SHA256 89733eeb43d35ba22e609b36fdd7b2d3ff54ef3c80d2686e786ad1c95fc1ebe1
MD5 787f7102acc5ab5ef58922b0a98c57f1
BLAKE2b-256 82b68aa4a1f0301fa06bc3e79f8bc2d9119e71194bbe547ae1ca4a659b2cb19a

See more details on using hashes here.

Provenance

The following attestation bundles were made for claim169-0.1.0a0.tar.gz:

Publisher: publish-release.yml on jeremi/claim-169

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

File details

Details for the file claim169-0.1.0a0-cp311-cp311-win_amd64.whl.

File metadata

  • Download URL: claim169-0.1.0a0-cp311-cp311-win_amd64.whl
  • Upload date:
  • Size: 529.4 kB
  • Tags: CPython 3.11, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for claim169-0.1.0a0-cp311-cp311-win_amd64.whl
Algorithm Hash digest
SHA256 27874dca2f36706ae5c9862089d2a08296a2ef552744e676a83380f97eab5c32
MD5 993c3fdc84ceaadeccb4ab8d7c014957
BLAKE2b-256 94119343b0982c16b3e5bf0f90436701b6d6fa9695ee7e1cacc13b0ef5df3559

See more details on using hashes here.

Provenance

The following attestation bundles were made for claim169-0.1.0a0-cp311-cp311-win_amd64.whl:

Publisher: publish-release.yml on jeremi/claim-169

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

File details

Details for the file claim169-0.1.0a0-cp311-cp311-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for claim169-0.1.0a0-cp311-cp311-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 f528f7bf57f79be221d0eba9bfab732de3a60d43b800b59fa7204c413949c948
MD5 74555b20af0b254b7549689986280049
BLAKE2b-256 0cb6378f87cf4f85f365347ba6cacb7aef1018a380cd8ebb9894417816b1a066

See more details on using hashes here.

Provenance

The following attestation bundles were made for claim169-0.1.0a0-cp311-cp311-macosx_11_0_arm64.whl:

Publisher: publish-release.yml on jeremi/claim-169

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

File details

Details for the file claim169-0.1.0a0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for claim169-0.1.0a0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 0c532c622a98b7c47212423ee9116d415cc061264986da0d7d732658d44258e5
MD5 df20ffd94fb5a1d983b4bd6c136ff639
BLAKE2b-256 90142e1fc70ee5e893dd98c979bd8a8ade4fdff642afe03076ae3f4d2aeeacc1

See more details on using hashes here.

Provenance

The following attestation bundles were made for claim169-0.1.0a0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:

Publisher: publish-release.yml on jeremi/claim-169

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

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page