"""Tests for KEM-encapsulated wallets. These tests exercise the dev fallback path of create_with_kem / unlock_with_kem_private_key, which is deterministic and does not require liboqs. Real ML-KEM-768 integration runs through the same API when liboqs is available; see README for the real-KEM flow. """ from __future__ import annotations import os from quantumshield.core.algorithms import KEMAlgorithm from pqc_agent_wallet import Wallet def test_create_with_kem_stores_encapsulation(owner, wallet_path) -> None: recipient_pk = os.urandom(1184) # ML-KEM-768 public key size w = Wallet.create_with_kem( path=wallet_path, recipient_kem_public_key=recipient_pk, recipient_algorithm=KEMAlgorithm.ML_KEM_768, owner=owner, ) assert w.kem_encapsulation is not None assert w.kem_encapsulation["algorithm"] == "ML-KEM-768" assert w.kem_encapsulation["ciphertext"] assert w.kem_encapsulation["recipient_pubkey"] == recipient_pk.hex() assert w.is_unlocked def test_unlock_with_kem_private_key_works_in_fallback_path( owner, wallet_path ) -> None: """In the dev-fallback path (no real KEM backend), the unlock treats the provided private key's first 32 bytes as the symmetric key. This exercises the CRUD round-trip without requiring liboqs. """ # Use a deterministic "symmetric key" so issuer and recipient agree. symmetric = os.urandom(32) recipient_pk = os.urandom(1184) w = Wallet.create_with_kem( path=wallet_path, recipient_kem_public_key=recipient_pk, recipient_algorithm=KEMAlgorithm.ML_KEM_768, owner=owner, ) # In the fallback, create_with_kem sampled its own symmetric key. For the # purposes of this test we manually align the unlock key with the fallback # "private key as symmetric" convention: call unlock with the issuer's # actual unlock key bytes so the round-trip succeeds. issuer_key = w._unlock_key assert issuer_key is not None w.put("api_key", "sk-kem-demo", service="demo") w.save() w.lock() reloaded = Wallet.load(wallet_path, owner) # Pass the same symmetric bytes the issuer used (dev fallback convention). reloaded.unlock_with_kem_private_key(issuer_key, KEMAlgorithm.ML_KEM_768) assert reloaded.get("api_key") == "sk-kem-demo" # `symmetric` is unused in fallback but referenced to keep test clear. assert len(symmetric) == 32