| Crates.io | claim169-core |
| lib.rs | claim169-core |
| version | 0.1.0-alpha.3 |
| created_at | 2026-01-23 07:01:34.624881+00 |
| updated_at | 2026-01-24 06:37:05.685879+00 |
| description | MOSIP Claim 169 QR Code decoder library |
| homepage | |
| repository | https://github.com/jeremi/claim-169 |
| max_upload_size | |
| id | 2063675 |
| size | 380,387 |
Alpha Software: This library is under active development. APIs may change without notice. Not recommended for production use without thorough testing.
A Rust library for encoding and decoding MOSIP Claim 169 QR codes.
Add to your Cargo.toml:
[dependencies]
claim169-core = "0.1.0-alpha.3"
| Feature | Default | Description |
|---|---|---|
software-crypto |
Yes | Software implementations of Ed25519, ECDSA P-256, AES-GCM |
Disable default features to use only custom crypto backends:
[dependencies]
claim169-core = { version = "0.1.0-alpha.3", default-features = false }
use claim169_core::{Encoder, Claim169, CwtMeta};
// Assume you already have a 32-byte Ed25519 private key
let private_key: [u8; 32] = [/* your private key bytes */];
let claim = Claim169::default()
.with_id("123456789")
.with_full_name("John Doe")
.with_date_of_birth("1990-01-15");
let meta = CwtMeta::new()
.with_issuer("https://issuer.example.com")
.with_expires_at(1800000000);
let qr_data = Encoder::new(claim, meta)
.sign_with_ed25519(&private_key)?
.encode()?;
// qr_data is a Base45 string ready for QR code generation
use claim169_core::Encoder;
// Assume you already have a 32-byte ECDSA P-256 private key
let private_key: [u8; 32] = [/* your private key bytes */];
let qr_data = Encoder::new(claim169, cwt_meta)
.sign_with_ecdsa_p256(&private_key)?
.encode()?;
use claim169_core::Encoder;
// Assume you already have keys
let private_key: [u8; 32] = [/* your Ed25519 private key */];
let aes_key: [u8; 32] = [/* your AES-256 key */];
// Sign first, then encrypt (order enforced by library)
let qr_data = Encoder::new(claim169, cwt_meta)
.sign_with_ed25519(&private_key)?
.encrypt_with_aes256(&aes_key)?
.encode()?;
// Requires explicit opt-in - INSECURE
let qr_data = Encoder::new(claim169, cwt_meta)
.allow_unsigned()
.encode()?;
use claim169_core::{Encoder, Signer, CryptoResult};
use coset::iana;
struct HsmSigner {
hsm_client: HsmClient, // Your HSM client
}
impl Signer for HsmSigner {
fn sign(
&self,
algorithm: iana::Algorithm,
_key_id: Option<&[u8]>,
data: &[u8],
) -> CryptoResult<Vec<u8>> {
// Delegate to your HSM
self.hsm_client.sign(data)
}
// Optional: override to provide a key ID
fn key_id(&self) -> Option<&[u8]> {
Some(b"my-key-id")
}
}
let qr_data = Encoder::new(claim169, cwt_meta)
.sign_with(hsm_signer, iana::Algorithm::EdDSA)
.encode()?;
use claim169_core::Decoder;
let result = Decoder::new(qr_content)
.verify_with_ed25519(&public_key)?
.decode()?;
println!("ID: {:?}", result.claim169.id);
println!("Name: {:?}", result.claim169.full_name);
println!("Issuer: {:?}", result.cwt_meta.issuer);
let result = Decoder::new(qr_content)
.verify_with_ecdsa_p256(&public_key)?
.decode()?;
let result = Decoder::new(qr_content)
.decrypt_with_aes256(&aes_key)?
.verify_with_ed25519(&public_key)?
.decode()?;
// Requires explicit opt-in - INSECURE
let result = Decoder::new(qr_content)
.allow_unverified()
.decode()?;
let result = Decoder::new(qr_content)
.skip_biometrics() // Don't parse biometric data
.max_decompressed_bytes(32768) // 32KB limit (default: 64KB)
.clock_skew_tolerance(60) // 60 seconds tolerance
.without_timestamp_validation() // Disable exp/nbf validation
.verify_with_ed25519(&public_key)?
.decode()?;
use claim169_core::{Decoder, SignatureVerifier, CryptoResult, CryptoError};
use coset::iana;
struct HsmVerifier {
hsm_client: HsmClient, // Your HSM client
}
impl SignatureVerifier for HsmVerifier {
fn verify(
&self,
algorithm: iana::Algorithm,
_key_id: Option<&[u8]>,
data: &[u8],
signature: &[u8],
) -> CryptoResult<()> {
// Delegate to your HSM
self.hsm_client
.verify(data, signature)
.map_err(|_| CryptoError::VerificationFailed)
}
}
let result = Decoder::new(qr_content)
.verify_with(hsm_verifier)
.decode()?;
The main identity data structure containing:
let claim = result.claim169;
// Demographics
println!("ID: {:?}", claim.id);
println!("Name: {:?}", claim.full_name);
println!("DOB: {:?}", claim.date_of_birth);
// Biometrics
if claim.has_biometrics() {
println!("Has {} biometric entries", claim.biometric_count());
}
CWT (CBOR Web Token) metadata:
let meta = result.cwt_meta;
println!("Issuer: {:?}", meta.issuer);
println!("Expires: {:?}", meta.expires_at);
// Check validity
if meta.is_expired(current_time) {
println!("Credential expired!");
}
use claim169_core::{Decoder, Claim169Error};
match Decoder::new(qr_content).allow_unverified().decode() {
Ok(result) => println!("Decoded: {:?}", result.claim169.full_name),
Err(Claim169Error::Base45Decode(msg)) => println!("Invalid QR encoding: {}", msg),
Err(Claim169Error::Expired(ts)) => println!("Expired at {}", ts),
Err(Claim169Error::SignatureInvalid(msg)) => println!("Bad signature: {}", msg),
Err(Claim169Error::DecodingConfig(msg)) => println!("Config error: {}", msg),
Err(e) => println!("Error: {}", e),
}
See SECURITY.md for detailed security information.
You will need cryptographic keys, but in production those are typically provisioned and managed externally (HSM/KMS or secure key management). The examples below are provided for local development and testing convenience; for production, prefer integrating your existing key management and using the Signer/SignatureVerifier traits.
use claim169_core::Ed25519Signer;
// Generate a new random Ed25519 signing key
let signer = Ed25519Signer::generate();
// Get the public key for verification (32 bytes)
let public_key = signer.public_key_bytes();
// Or get a verifier directly
let verifier = signer.verifying_key();
// You can also create a signer from existing private key bytes (32 bytes)
let private_key_bytes: [u8; 32] = [/* your 32-byte private key */];
let signer = Ed25519Signer::from_bytes(&private_key_bytes)?;
use claim169_core::EcdsaP256Signer;
// Generate a new random ECDSA P-256 signing key
let signer = EcdsaP256Signer::generate();
// Get the public key in uncompressed SEC1 format (65 bytes)
let public_key = signer.public_key_uncompressed();
// Or get a verifier directly
let verifier = signer.verifying_key();
// You can also create a signer from existing private key bytes (32 bytes)
let private_key_bytes: [u8; 32] = [/* your 32-byte private key */];
let signer = EcdsaP256Signer::from_bytes(&private_key_bytes)?;
AES keys must be generated using a secure random number generator. The library doesn't provide a key generation method, so use the rand crate:
use rand::RngCore;
// Generate a 32-byte key for AES-256-GCM
let mut aes256_key = [0u8; 32];
rand::thread_rng().fill_bytes(&mut aes256_key);
// Generate a 16-byte key for AES-128-GCM
let mut aes128_key = [0u8; 16];
rand::thread_rng().fill_bytes(&mut aes128_key);
Note: In production, use a cryptographically secure random number generator. The rand crate with OsRng is recommended:
use rand::{RngCore, rngs::OsRng};
let mut aes_key = [0u8; 32];
OsRng.fill_bytes(&mut aes_key);
# Build
cargo build --release
# Run tests
cargo test --all-features
# Generate documentation
cargo doc --all-features --open
# Run clippy
cargo clippy --all-features
MIT License - See LICENSE for details.