Metadata-Version: 2.4
Name: quipu-crypto
Version: 0.10.0
Classifier: Development Status :: 3 - Alpha
Classifier: Programming Language :: Rust
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Security :: Cryptography
Classifier: License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)
Classifier: Operating System :: OS Independent
License-File: LICENSE
License-File: LICENSE-COMMERCIAL
Summary: Codec criptográfico post-cuántico híbrido (X25519+ML-KEM-1024, Ed25519+ML-DSA-87) con endurecimiento online verificable por VOPRF conforme a RFC 9497. Datos en reposo.
Keywords: cryptography,post-quantum,encoding,oprf,aead
Home-Page: https://github.com/isazajuancarlos/quipu
Author-email: Juan Carlos Isaza Arenas <isazajuancarlos@gmail.com>
License: AGPL-3.0-or-later
Requires-Python: >=3.9
Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
Project-URL: Homepage, https://github.com/isazajuancarlos/quipu
Project-URL: Repository, https://github.com/isazajuancarlos/quipu

# Quipu

[![License: AGPL v3](https://img.shields.io/badge/License-AGPL_v3-blue.svg)](LICENSE)
[![crates.io](https://img.shields.io/crates/v/quipu.svg)](https://crates.io/crates/quipu)
[![docs.rs](https://img.shields.io/docsrs/quipu)](https://docs.rs/quipu)
[![CI](https://github.com/isazajuancarlos/quipu/actions/workflows/ci.yml/badge.svg)](https://github.com/isazajuancarlos/quipu/actions/workflows/ci.yml)
[![post-quantum](https://img.shields.io/badge/post--quantum-ML--KEM--1024-purple.svg)](#modos)

Librería de codificación con **protección criptográfica** y **simbología propia**.

> 🇬🇧 *Quipu is a free/libre (AGPL-3.0) library that encrypts and encodes data
> using only vetted cryptographic primitives (XChaCha20-Poly1305, Argon2id,
> HKDF), with a hybrid post-quantum mode (X25519 + ML-KEM-1024) and a verifiable
> online hardening mode (RFC 9497 VOPRF + DLEQ). It never invents primitives —
> security lives in the keys, not in hiding the format.*

> Filosofía "rueda y oruga": donde existe buena criptografía, la **reutilizamos**
> (XChaCha20-Poly1305, Argon2id, HKDF, ML-KEM, X25519); donde hay terreno nuevo
> (representación, simbología, formato), **innovamos**. Nunca inventamos primitivas
> criptográficas: la seguridad vive en la clave + el AEAD, no en la representación.

## Qué hace

Protege datos y los representa como **símbolos** (texto denso o una imagen),
de forma reversible y autenticada.

```
datos → KDF(passphrase+pepper) → AEAD → contenedor → codec base-N → diccionario → símbolos
```

## Modos

| Modo | API (Rust) | Descripción |
|---|---|---|
| Simétrico (passphrase) | `api::encode` / `api::decode` | Argon2id + XChaCha20-Poly1305 |
| Post-cuántico (clave pública) | `api::encode_to_recipient` / `decode_as_recipient` | Híbrido **X25519 + ML-KEM-1024** (transcript ligado estilo X-Wing) |
| Online (endurecimiento) | `api::encode_online` / `decode_online` | **VOPRF conforme a [RFC 9497](https://www.rfc-editor.org/rfc/rfc9497.html)** (ristretto255-SHA512, prueba DLEQ): el cliente detecta un servidor deshonesto |
| Firmado (autenticidad) | `api::encode_signed` / `decode_verified` | Firma híbrida **Ed25519 + ML-DSA-87** (combinador AND). Autenticidad y no-repudio verificables; **no** confidencialidad |
| Firmado triple (alta garantía, feature `slh`) | `api::encode_signed_triple` / `decode_verified_triple` | Firma triple-híbrida **Ed25519 + ML-DSA-87 + SLH-DSA-256s** (AND 3-de-3): infalsificable mientras sobreviva ≥1 de {curva, retículo, hash}. Opt-in; firma ~34 KB |
| Streaming (archivos grandes) | `api::encrypt_stream` / `decrypt_stream` | Cifrado por chunks (memoria acotada) para datos en reposo grandes; resistente a truncación/reordenamiento/splice. Contenedor `QST1` |
| Señuelos / Honey (feature `honey`) | `honey::encrypt_pin` / `decrypt_pin` (y genérico `encrypt`/`decrypt`) | **Honey Encryption** para secretos de baja entropía (PIN, frase mnemónica): cualquier passphrase equivocada descifra a **otro secreto plausible**, no a un error → sin oráculo de fuerza bruta. Opt-in. **Sin autenticación por diseño** (un tag sería un oráculo); no sustituye al núcleo AEAD, solo para secuencias uniformes |

## Custodia de claves (k-de-n, feature `escrow`)

`quipu::shamir` reparte un secreto en `n` comparticiones de las que **k**
cualesquiera lo reconstruyen y **k-1 no revelan nada**. Va **tras un feature
gate opt-in**: es una herramienta de escrow, no del núcleo de cifrado, y quien
no la necesite no la lleva compilada. Sirve para respaldar la
clave del servidor OPRF, custodiar la clave de firma de un integrador o montar
un escrow contractual — **sin red y sin HSM**, que es la condición de un
despliegue air-gapped.

```rust
let comparticiones = quipu::shamir::split(&clave, 3, 5)?;   // 3 de 5
let clave = quipu::shamir::combine(&comparticiones[..3])?;
```

Cada compartición lleva un verificador, así que una corrupta o de otro reparto
**se detecta** en vez de devolver basura. Ese verificador permitiría comprobar
conjeturas de un secreto adivinable, así que el módulo **rechaza secretos más
cortos que el material de clave más pequeño que produce la propia arquitectura**
(`kdf::KEY_LEN`, 32 bytes): es para claves, y para lo adivinable está `honey`. No es firma umbral — el secreto se reconstruye en memoria para usarlo.

## Firma en un dispositivo (HSM/PKCS#11, feature `hsm`)

La clave privada de firma puede vivir en un **HSM, token o tarjeta PKCS#11 y no
salir de ahí**. Es la respuesta a la primera pregunta de un comité de seguridad,
y funciona con la firma híbrida completa: las **dos mitades** —Ed25519 y
ML-DSA-87— se generan y se usan **dentro** del dispositivo; de la librería solo
salen firmas y la clave pública.

El trait `firmante::Custodio` separa *quién guarda la clave* de *cómo se arma la
firma*. Pide operaciones, nunca material: no existe forma de sacar la clave,
porque el punto entero es que no salga. Una firma hecha en un HSM y una hecha en
memoria son **idénticas byte a byte** y las verifica el mismo verificador.

```rust
// El custodio en memoria de siempre (predeterminado, sin feature):
let firma = firmante::firmar(&firmante::EnMemoria::nuevo(sk), mensaje)?;

// O contra un dispositivo PKCS#11, con la clave dentro (feature `hsm`):
let custodio = CustodioPkcs11::por_etiqueta(sesion, "firma-ed", "firma-ml")?;
let firma = firmante::firmar(&custodio, mensaje)?;  // la clave nunca cruza aquí
```

Con `escrow`, `firmar_con_comparticiones` reconstruye desde Shamir, firma y
borra en una sola llamada de Rust, sin que la clave cruce a los bindings.
Probado de punta a punta —128 firmas concurrentes contra un token real, cada una
verificada— y en el binding de Python (`quipu.CustodioHsm`), que va en la rueda.

## Diccionarios (simbología enchufable)

- `dictionaries::ascii94()` — 94 símbolos ASCII (copy-paste universal).
- `dictionaries::flagship()` — 4096 glifos (12 bits/símbolo, ~2× más denso).
- `dictionaries::from_range(start, count)` — alfabeto a medida.

## Seguridad y endurecimiento

- **Precapas**: normalización NFKC, pepper, padding Padmé (oculta longitud),
  binding de contexto (AAD), HKDF (separación de subclaves).
- **Antihacker**: borrado de claves en memoria (`zeroize`), comparación en tiempo
  constante, validación de parámetros KDF, errores uniformes.
- **Fallo de entropía, no sustitución silenciosa**: cuando el sistema operativo
  no puede dar aleatoriedad, Quipu **no cae a una fuente más débil** — ninguna
  clave nace de un RNG muerto. El fallo se informa como un error accionable
  (¿reintento yo, o arreglo el despliegue?) con un reintento acotado para el
  único caso transitorio, en vez de un `panic`: así la limpieza de memoria
  (`Drop`/zeroize) se ejecuta incluso ahí, que es justo cuando más importa. Es
  el modo de fallo de Debian OpenSSL 2008 —claves predecibles que *parecen*
  correctas— prevenido por construcción, y hay una autoprueba que avisa al
  arrancar en vez de matar el proceso.
- **Autopruebas de arranque** (`quipu::selftest`): 14 vectores de respuesta
  conocida sobre **el binario que realmente se ejecuta**, no sobre el build de
  CI. Corren una vez por proceso al entrar por cualquier punto del núcleo, y si
  alguna falla el módulo **se niega a operar** en vez de producir resultados
  silenciosamente incorrectos.

  Una autoprueba fallida **no significa que Quipu falle**: significa que la
  máquina no está ejecutando la criptografía correctamente — una rueda compilada
  para otro procesador, un archivo dañado o sustituido, memoria defectuosa. No
  introducen modos de fallo, hacen visibles los que ya existían.

  Van más allá de lo que exigen FIPS 140-3 y los GM/T chinos en tres puntos:
  usan **vectores publicados** donde existen (HKDF contra el RFC 5869, no
  vectores propios que solo demuestran consistencia consigo mismos), incluyen
  **pruebas negativas** (lo manipulado debe *fallar*), y vigilan la **salud del
  RNG** en continuo. Cada comprobación está probada de que **discrimina**: una
  que devolviera siempre `true` pasaría una batería convencional igual que una
  correcta.

  Verificadas con 1300 operaciones simuladas —200 pasadas, 100 hebras
  concurrentes, 1000 llamadas repetidas— y con inyección de fallo para ejercitar
  el camino de error, ambas en CI.
- **Hackerbot**: red-team interno (tamper/truncation/uniqueness). Encontró y se
  corrigió un DoS por parámetros Argon2 maliciosos.
- **Security Lab** (features `lab` / `lab-offline`, no viajan en el build
  publicado): red-team **adaptativo** que se ataca a sí mismo. Núcleo en CI
  (fuga de formato + falsificación de firmas) con corpus encadenado y meta-tests
  que fallan si se debilita una defensa antihacker; y un **banco offline aislado**
  (contenedor sin red) para timing y coste de guessing acelerado por IA.
  `cargo run --example securitylab --features lab` · `bash lab/run.sh`. Ver
  [`lab/README.md`](lab/README.md) y `THREAT_MODEL.md` §9.

## Uso (Rust)

```rust
use quipu::api::{encode, decode, Options};
use quipu::dictionaries;

let dict = dictionaries::ascii94();
let sym = encode(b"secreto", "passphrase", &dict, &Options::default());
let data = decode(&sym, "passphrase", &dict, b"").unwrap();
```

Firma híbrida (autenticidad verificable por terceros, post-cuántica):

```rust
use quipu::api::{encode_signed, decode_verified};
use quipu::{dictionaries, pqsign};

let dict = dictionaries::ascii94();
let (vk, sk) = pqsign::generate_keypair();
let signed = encode_signed(b"acta oficial", &sk, &dict);
let msg = decode_verified(&signed, &vk, &dict).unwrap(); // falla si se altera
```

## Uso (Python)

```bash
pip install quipu-crypto   # se instala como "quipu-crypto", se importa como "quipu"
```

```python
import quipu
s = quipu.encode(b"secreto", "passphrase")
assert quipu.decode(s, "passphrase") == b"secreto"

# Post-cuántico
pub, sec = quipu.generate_keypair()
s = quipu.encode_to_recipient(b"secreto", pub)
assert quipu.decode_as_recipient(s, sec) == b"secreto"

# Firma híbrida (autenticidad, post-cuántica)
vk, sk = quipu.generate_signing_keypair()
signed = quipu.encode_signed(b"acta oficial", sk)
assert quipu.decode_verified(signed, vk) == b"acta oficial"  # falla si se altera

# Streaming AEAD para datos grandes (salida binaria, no símbolos)
blob = quipu.encrypt_stream(b"...datos grandes...", "passphrase")
assert quipu.decrypt_stream(blob, "passphrase") == b"...datos grandes..."
```

## Ejemplos funcionales

Round-trip de todos los modos, listo para correr:

```bash
cargo run --example quickstart          # Rust  (examples/quickstart.rs)
python examples/quickstart.py           # Python (examples/quickstart.py)
```

## Construir y probar

```bash
cargo test                      # tests unit + property
cargo clippy --all-targets      # lint
cargo run --example demo        # demo simétrico
cargo run --example v2demo      # post-cuántico + OPRF + imagen
cargo run --example hackerbot   # red-team
cargo run --example testplatform --release   # batería completa
cargo run --example securitylab --features lab   # laboratorio de seguridad (red-team adaptativo)
cargo run --example redteam --features "lab slh honey" --release   # red-team consolidado (todas las superficies)
bash lab/run.sh   # banco offline aislado (timing + guessing) — Etapa B

# Fuzzing coverage-guided (libFuzzer, nightly). Targets: parse_container,
# honey_decrypt, unpad, codec_roundtrip.
cargo +nightly fuzz run honey_decrypt

# Bindings Python
source venv/bin/activate
maturin develop --features python
python tests/python/test_quipu.py
```

## Estado

v1 + v1.1 + v2 + streaming AEAD (`QST1`) + honey (`QHNY`) + firmas (híbrida
Ed25519+ML-DSA-87 y triple con SLH-DSA) implementados con TDD estricto.
**267 tests Rust + Wycheproof + 15 Python** verdes, clippy limpio, fuzzing sin
crashes, Miri sin UB. Bindings multi-lenguaje sobre la C ABI, cada uno con
interop cross-language: **10 tests de ABI + integración C, 12 Node, 12 Go**.
Parámetros post-cuánticos en **categoría de seguridad NIST 5 (CNSA 2.0)**:
**ML-KEM-1024** y **ML-DSA-87**. Modo online con **VOPRF conforme a RFC 9497**
(ristretto255-SHA512), verificado contra los **vectores oficiales del Apéndice
A.1.2**, KEM híbrido con transcript ligado estilo X-Wing, **firma híbrida Ed25519 +
ML-DSA-87** (combinador AND), y
**pre-auditoría** propia (ver `INFORME_PREAUDITORIA.txt` y `MODELO_DE_AMENAZA.txt`).
**Security Lab** (red-team adaptativo auto-hospedado): 14 ataques en CI
(`--features lab`) + banco offline de timing/guessing (`--features lab-offline`).

> ⚠️ Proyecto en desarrollo. La pre-auditoría interna NO sustituye una auditoría
> criptográfica **independiente**: no usar para proteger datos críticos reales
> hasta ese sello externo.

## La familia: un núcleo, dos perfiles

Quipu no es un crate: es un **núcleo agnóstico de primitivas** y perfiles finos
encima que declaran con qué criptografía se comprometen.

| Crate | Qué es |
|---|---|
| [`crates/quipu-nucleo`](crates/quipu-nucleo) | Todo lo que **no** es criptografía: formato del contenedor, codec base-N, Reed-Solomon, relleno Padmé. **Cero primitivas.** |
| `quipu` (este crate) | El perfil por defecto: **XChaCha20-Poly1305**, HKDF-SHA-256, nonce extendido de 192 bits. |
| [`crates/quipu-cnsa`](crates/quipu-cnsa) | El perfil alineado con **CNSA 2.0**: AES-256-GCM, HKDF-SHA-384, nonce de 96 bits. **NO validado FIPS 140-3.** |

La relación es la de Devuan con Debian: no una rama de mantenimiento, sino un
**compromiso declarado** que comparte casi todo. El formato, el codec y el canal
visual viven una sola vez en el núcleo, así que **un fallo se arregla una vez** —
no dos ramas divergiendo hasta que una recibe el parche y la otra no.

**Si puedes elegir, usa `quipu`.** El perfil CNSA existe para quien tiene un
mandato normativo: en hardware sin aceleración AES, AES-GCM es una *regresión*
—más lento y más difícil de escribir en tiempo constante, por sus tablas de
sustitución—. ChaCha20 no tiene tablas y es constante por construcción.

## Endurecimiento de contraseñas (servicio OPRF)

```
Argon2 solo:  robas la BD -> fuerza bruta offline, a la velocidad de tu GPU.
Con VOPRF:    robas la BD -> no derivas nada sin la clave del servidor. Cada
              intento exige una petición que el operador ve, limita y corta.
```

Hay una instancia gestionada en **`https://oprf.xiliux.com`** (beta). El cliente
va aparte y es **Apache-2.0**: no arrastra la AGPL de este núcleo a tu servidor
de autenticación.

```bash
pip install quipu-oprf-django   # Django: solo toca PASSWORD_HASHERS
pip install quipu-voprf         # las primitivas, para cualquier otro stack
```

La contraseña sale **cegada** (el servidor nunca la ve) y el servidor no puede
mentir: adjunta una prueba DLEQ que el cliente verifica contra una clave pública
**fijada fuera de banda**. Falla cerrado: si el servicio no responde o la prueba
no valida, no se degrada a "sin endurecer".

- [`crates/quipu-voprf`](crates/quipu-voprf) — primitivas VOPRF (RFC 9497), Apache-2.0
- [`crates/quipu-oprf-server`](crates/quipu-oprf-server) — el servidor, auto-hospedable
- [`integrations/`](integrations) — Django (publicado), Express y Go (sin publicar)

## Documentación

- [`docs/HOJA_DE_RUTA.md`](docs/HOJA_DE_RUTA.md) — **qué falta y en qué orden**,
  con el estado medido y las decisiones ya tomadas para no reabrirlas.
- [`docs/RAMAS.md`](docs/RAMAS.md) — el modelo de ramas (estable, testing,
  desarrollo) y por qué la promoción no se hace a mano.
- [`docs/SPEC.md`](docs/SPEC.md) — **especificación técnica** (formato del
  contenedor, KDF, modo híbrido, VOPRF/DLEQ, separación de dominios).
- [`docs/THREAT_MODEL.md`](docs/THREAT_MODEL.md) — modelo de amenaza (EN)
  · original [`MODELO_DE_AMENAZA.txt`](MODELO_DE_AMENAZA.txt) (ES).
- [`docs/PRE_AUDIT.md`](docs/PRE_AUDIT.md) — pre-auditoría interna (EN)
  · original [`INFORME_PREAUDITORIA.txt`](INFORME_PREAUDITORIA.txt) (ES).
- [`SECURITY.md`](SECURITY.md) — política de seguridad y reporte de fallos.
- [`docs/RELEASES.md`](docs/RELEASES.md) — cómo verificar la autenticidad de un
  release (attestations PEP 740 + firmas sigstore/cosign).
- [`CONTRIBUTING.md`](CONTRIBUTING.md) — cómo contribuir · [`CHANGELOG.md`](CHANGELOG.md).
- [`LICENSING.md`](LICENSING.md) — modelo de licenciamiento dual.
- [`docs/announcement.md`](docs/announcement.md) — artículo de diseño (EN/ES).
- [`docs/superpowers/specs/2026-07-01-quipu-security-lab-design.md`](docs/superpowers/specs/2026-07-01-quipu-security-lab-design.md)
  — diseño del **Security Lab** (red-team adaptativo, feature `lab`).

> ⚠️ La pre-auditoría interna es preparación, **no** sustituye una auditoría
> independiente. Ese sello externo es el siguiente paso del proyecto (solicitud
> enviada al OTF Security Lab).

## Licencia

Modelo de **licencia dual** (open-core). **No todo el repositorio es AGPL**: lo
que un cliente del servicio OPRF enlaza dentro de su propio servidor es permisivo.

| Componente | Licencia |
|---|---|
| `quipu` (núcleo) y sus bindings | `AGPL-3.0-or-later` (ver `LICENSE`) |
| `crates/quipu-nucleo` (formato y canal visual) | `AGPL-3.0-or-later` / comercial |
| `crates/quipu-cnsa` (perfil CNSA 2.0) | `AGPL-3.0-or-later` / comercial |
| `crates/quipu-voprf` → [`quipu-voprf`](https://pypi.org/project/quipu-voprf/) | **`Apache-2.0`** |
| `integrations/django` → [`quipu-oprf-django`](https://pypi.org/project/quipu-oprf-django/) | **`Apache-2.0`** |
| `crates/quipu-oprf-server` | `AGPL-3.0-or-later` / comercial |

### Qué se cobra, exactamente

**Quipu es libre y siempre lo será.** Puedes usarlo hoy sin pagar nada. La única
condición es publicar el código de lo que construyas encima. Si eso no te sirve,
te vendemos la exención de esa obligación.

Dicho de otro modo: **no se cobra por el uso, se cobra por el derecho a no
publicar.** El copyleft no prohíbe cobrar —la GPL dice literalmente que puedes
cobrar cualquier precio o ninguno—; lo que restringe es el **secreto**, no el
precio.

- **Licencia comercial** — para producto propietario cerrado o SaaS sin abrir
  código. Términos en [`LICENSE-COMMERCIAL`](LICENSE-COMMERCIAL). Es una
  concesión **adicional y paralela** a la AGPL, no una sustitución: con contrato
  o sin él conservas todo lo que la AGPL concede a cualquiera —usar, estudiar,
  modificar, redistribuir, vender, bifurcar e incluso competir—. Lo único que
  añade es la exención del copyleft de red.
- **Servidor OPRF gestionado** — negocio distinto y complementario: ahí no se
  vende exención sino no tener que operar la infraestructura ni custodiar la
  clave.

**Si puedes cumplir el copyleft, no necesitas comprarnos nada.** Un proyecto
libre, uno académico o una entidad con política de software abierto usan Quipu
gratis, y nos interesa que lo hagan.

**Por qué AGPL y no GPL:** con GPL a secas, quien corre el software como servicio
en red nunca lo *distribuye*, así que nunca dispara el copyleft. El artículo 13
de la AGPL cierra ese hueco. No fue una elección ideológica.

Es la misma estructura que **Qt** o **MySQL**: licencia libre para quien cumple,
licencia comercial para quien necesita términos propietarios.

Copyright (c) 2024-2026 Juan Carlos Isaza Arenas — titular único; ver
[`COPYRIGHT`](COPYRIGHT). El uso del nombre «Quipu» se rige por
[`TRADEMARK.md`](TRADEMARK.md).

Las primitivas VOPRF viven en un crate **separado** (no solo con otra etiqueta):
la licencia de un envoltorio no relicencia su dependencia. Detalles y el porqué
en [`LICENSING.md`](LICENSING.md) §0. Contacto: isazajuancarlos@gmail.com

