Metadata-Version: 2.5
Name: alea-q
Version: 0.2.1
Summary: SDK Python pour l'API ALEA-Q / CERTROPI — génération d'entiers aléatoires certifiés Device-Independent
Project-URL: Homepage, https://alea-q.com
Project-URL: Documentation, https://docs.alea-q.com
Project-URL: Repository, https://gitlab.com/certropi/alea-q-python
Project-URL: Bug Tracker, https://gitlab.com/certropi/alea-q-python/-/issues
Author-email: CERTROPI <sdk@certropi.com>
License: Proprietary
Keywords: alea-q,certropi,cryptography,device-independent,qrng,quantum,random
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Scientific/Engineering :: Physics
Classifier: Topic :: Security :: Cryptography
Requires-Python: >=3.10
Requires-Dist: httpx>=0.27.0
Provides-Extra: dev
Requires-Dist: cryptography>=47.0.0; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
Requires-Dist: pytest>=8.0.0; extra == 'dev'
Requires-Dist: respx>=0.21.0; extra == 'dev'
Requires-Dist: ruff>=0.4.0; extra == 'dev'
Requires-Dist: websockets>=12.0; extra == 'dev'
Provides-Extra: stream
Requires-Dist: websockets>=12.0; extra == 'stream'
Provides-Extra: verify
Requires-Dist: cryptography>=47.0.0; extra == 'verify'
Description-Content-Type: text/markdown

# alea-q

SDK Python officiel pour l'API **ALEA-Q / CERTROPI** — génération d'entiers aléatoires certifiés Device-Independent depuis des ordinateurs quantiques réels.

## Installation

```bash
pip install alea-q

# Avec vérification de certificats d'attestation
pip install "alea-q[verify]"

# Avec streaming Silver en temps réel
pip install "alea-q[stream]"
```

## Démarrage rapide

```python
from alea_q import AleaQClient

# Clé API dans le constructeur ou variable d'env ALEA_Q_API_KEY
client = AleaQClient(api_key="sk_plat_xxx")

# Platinum — entier DI-certifié depuis un ordinateur quantique réel
result = client.platinum.generate(n_bits=256)
print(result.as_hex())        # 0x3f2a...
print(result.certificate)     # dict d'attestation SHA-3/ECDSA

# Silver — DRBG seedé QPU
result = client.silver.generate(n_bits=128)

# Ivory — batch haute vitesse
batch = client.ivory.batch(size=10000, n_bits=32)
print(batch.values[:5])
```

## Authentification

```python
# Option 1 — clé dans le constructeur
client = AleaQClient(api_key="sk_plat_xxx")

# Option 2 — variable d'environnement
import os
os.environ["ALEA_Q_API_KEY"] = "sk_plat_xxx"
client = AleaQClient()
```

Le tier est détecté automatiquement depuis le préfixe de la clé
(`sk_ivor_` / `sk_silv_` / `sk_plat_`) :

```python
client = AleaQClient(api_key="sk_plat_xxx")
print(client.tier)  # "platinum"
```

## Compte authentifié

```python
info = client.me()
print(info.tier)                    # "platinum"
print(info.quota.remaining_today)   # entiers restants aujourd'hui
print(info.usage.total_calls)       # appels cumulés
```

## Tiers disponibles

| Tier     | Source                          | Certification            | Latence    |
|----------|----------------------------------|--------------------------|------------|
| Platinum | Ordinateur quantique réel        | Device-Independent       | < 1ms pool |
| Silver   | DRBG seedé quantique              | Seed quantique            | < 1ms      |
| Ivory    | Simulateur Toeplitz              | Haute qualité stat.      | < 0.1ms    |

## Vérification des certificats Platinum

Chaque réponse Platinum inclut un certificat d'attestation vérifiable sans contacter CERTROPI.

```python
# Vérification automatique à la génération
result = client.platinum.generate(
    n_bits=256,
    verify=True,
    pubkey_path="certropi_public.pem",
)

# Vérification manuelle depuis un dict
from alea_q import verify_certificate_dict
S = verify_certificate_dict(result.certificate, pubkey_path="certropi_public.pem")
print(f"Certificat valide — S={S:.4f}")

# Vérification depuis un fichier JSON
from alea_q import verify_certificate
verify_certificate("cert.json", pubkey_path="certropi_public.pem")
```

Récupérer la clé publique CERTROPI :
```bash
curl https://api.alea-q.com/v1/platinum/pubkey -o certropi_public.pem
# ou
python -c "
from alea_q import AleaQClient
client = AleaQClient()
open('certropi_public.pem', 'wb').write(client.platinum.get_public_key())
"
```

## Context manager

```python
with AleaQClient(api_key="sk_plat_xxx") as client:
    result = client.platinum.generate(n_bits=256)
```

## Client asynchrone

```python
import asyncio
from alea_q import AsyncAleaQClient

async def main():
    async with AsyncAleaQClient(api_key="sk_plat_xxx") as client:
        result = await client.platinum.generate(n_bits=256)
        print(result.as_hex())

asyncio.run(main())
```

## Streaming Silver (temps réel)

Flux continu d'entiers en WebSocket — utile pour alimenter en continu un
consommateur d'entropie (HSM, pool applicatif, génération de clés en
masse) sans refaire une requête HTTP par lot.

Nécessite l'extra `stream` : `pip install "alea-q[stream]"`

```python
import asyncio
from alea_q import AsyncAleaQClient

async def main():
    async with AsyncAleaQClient(api_key="sk_silv_xxx") as client:
        count = 0
        async for value in client.silver.stream(bits_per_second=100_000, n_bits=256):
            print(value)
            count += 1
            if count >= 1000:
                break  # ferme proprement le flux côté client

asyncio.run(main())
```

Le débit est exprimé en **bits/seconde**, pas en nombre d'entiers — cohérent
avec la facturation volumétrique du service et indépendant de `n_bits`
(la largeur de chaque entier livré). Le flux reste ouvert tant que la
boucle `async for` n'est pas interrompue (`break`) et que la clé reste
valide — une révocation de clé en cours de flux le referme immédiatement.

Gestion des erreurs spécifiques au streaming :

```python
from alea_q import AuthenticationError, InsufficientBalance, QuotaExceeded

async def main():
    async with AsyncAleaQClient(api_key="sk_silv_xxx") as client:
        try:
            async for value in client.silver.stream(bits_per_second=100_000):
                ...
        except AuthenticationError:
            print("Clé invalide ou révoquée en cours de flux")
        except InsufficientBalance:
            print("Solde prépayé épuisé — recharger via le support")
        except QuotaExceeded:
            print("Capacité de débit disponible dépassée — réessayer plus tard")

asyncio.run(main())
```

Le client synchrone (`AleaQClient`) ne propose pas le streaming — le
flux est intrinsèquement asynchrone, utiliser `AsyncAleaQClient`.

## Livraison chiffrée post-quantique (Platinum, `recipient_pk`)

Pour ne jamais faire transiter la valeur générée en clair, `generate()`
accepte (Platinum uniquement) un `recipient_pk` — une clé publique
ML-KEM-768 que vous générez localement. La valeur revient alors
enveloppée (ML-KEM + AES-256-GCM + signature SLH-DSA) dans `.envelope`
au lieu d'être renvoyée en clair dans `.value` :

```python
import base64
from alea_q import mlkem_decaps

# my_mlkem_dk / my_mlkem_pubkey_hex : paire de clés ML-KEM-768 générée
# localement (ex. via pyca/cryptography — jamais transmise au serveur).
result = client.platinum.generate(n_bits=256, recipient_pk=my_mlkem_pubkey_hex)
if result.envelope:
    # result.value est vide — la valeur réelle est dans
    # result.envelope["encrypted_payload"], chiffrée pour my_mlkem_pubkey_hex.
    # La décapsulation reste locale : le serveur ne voit jamais votre clé
    # privée ni le secret déchiffré.
    #
    # Nécessite l'extra 'verify' : pip install 'alea-q[verify]'
    #
    # ATTENTION : les champs de l'enveloppe (ciphertext, encrypted_payload,
    # nonce, signature, certropi_pk) sont en BASE64.
    ciphertext = base64.b64decode(result.envelope["ciphertext"])
    shared_key = mlkem_decaps(my_mlkem_dk, ciphertext, param_set="ML_KEM_768")
    # shared_key (32 octets) est la clé AES-256-GCM utilisée pour chiffrer
    # result.envelope["encrypted_payload"] (nonce en base64 lui aussi) —
    # déchiffrement AES-GCM restant à la charge de l'appelant, non fourni
    # par ce SDK à ce jour.
```

## Gestion des erreurs

```python
from alea_q import AleaQClient
from alea_q.exceptions import BackendUnavailable, AuthenticationError, InsufficientBalance
import time

client = AleaQClient()

try:
    result = client.platinum.generate(n_bits=256)
except BackendUnavailable as e:
    print(f"QPU indisponible — réessayer dans {e.retry_after}s")
    time.sleep(e.retry_after or 300)
except AuthenticationError:
    print("Clé API invalide")
except InsufficientBalance:
    print("Solde prépayé épuisé (mode pay-as-you-go) — recharger via le support")
```

## Configuration

| Variable d'env       | Description                              | Défaut                      |
|----------------------|------------------------------------------|-----------------------------|
| `ALEA_Q_API_KEY`     | Clé API                                  | —                           |
| `ALEA_Q_BASE_URL`    | URL de base de l'API                     | `https://api.alea-q.com`    |
| `ALEA_Q_TIMEOUT`     | Timeout HTTP en secondes                 | `60`                        |
| `ALEA_Q_MAX_RETRIES` | Nombre de retries automatiques           | `2`                         |
| `ALEA_Q_PUBKEY_PATH` | Chemin clé publique CERTROPI             | `certropi_public.pem`       |

`https://api.certropi.com` est un alias B2B de la même API — les deux
domaines répondent de manière identique, `ALEA_Q_BASE_URL` accepte
indifféremment l'un ou l'autre.

## Développement

```bash
pip install -e ".[dev]"

# Suite de tests (mock HTTP via respx — aucun accès réseau requis)
pytest

# Avec couverture
pytest --cov=alea_q --cov-report=term-missing

# Tests contre l'API réelle (marqués `smoke`, nécessitent ALEA_Q_API_KEY
# et un accès réseau — exclus par défaut)
pytest -m smoke
```

## Licence

Propriétaire — CERTROPI © 2026

