Files
CharonandClaude Opus 5.5 abe680e853 docs(vectores): la firma OIT de las invitaciones V1, con los casos que deben fallar
Para Irhis y el servicio de invitaciones de originmetaverse.com (CONTRATO_INVITACIONES_V1 §3.1,
origin-metaverse-web d5f5f5e). Ella escribe sus tests antes que el servicio.

- Firma sobre etiqueta || 0x00 || JCS(cuerpo).
- 4 vectores que deben aceptarse y 11 que deben rechazarse: nonce repetido, ts fuera de
  ventana, etiqueta cruzada, llave de cabecera distinta, S no canonico, cuerpo cambiado, tres
  llaves de orden pequeno con firma forjada sin llave privada, y clave JSON duplicada.
- Ed25519 en Python puro (referencia del RFC 8032), comprobado con los TEST 1 y 2 del RFC y
  con OpenSSL byte a byte. Salida determinista.

Medido: OpenSSL 3.6.4 ACEPTA las tres firmas forjadas (F8-F10). La verificacion sola no basta;
hace falta la comprobacion de llave fuerte, como is_strong_ed25519_key en la cadena (RV14).

Contrafactual del generador: sin la comprobacion de orden pequeno, la de S < L o la etiqueta, se
niega a emitir. Las comprobaciones de codificacion canonica se solapan con la de orden pequeno en
F10 y ningun vector puede separarlas (ver LEEME).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 04:27:14 -05:00

74 lines
3.1 KiB
Markdown

# Vectores de la firma OIT de las invitaciones V1
Charon ⚓, 2026-10-28. Para Irhis 🛰️ y el servicio de invitaciones de originmetaverse.com
(`origin-metaverse-web`, `docs/CONTRATO_INVITACIONES_V1.md` §3.1).
```sh
python3 -I generar.py > invitaciones-v1.json # determinista: el sha no cambia entre corridas
```
Necesita sólo Python 3 y `openssl` en el PATH. Ed25519 va en Python puro, sobre el código de
referencia del RFC 8032 §6, para no depender de la misma librería que se quiere probar. Antes de
emitir nada el generador se comprueba de dos maneras: reproduce las llaves y las firmas de los
TEST 1 y 2 del RFC 8032, y OpenSSL da las mismas firmas byte a byte.
## Qué hay
Todos los vectores firman `utf8(etiqueta) || 0x00 || JCS(cuerpo)`.
- **Aceptar:**
- V1 crear, V2 canjear y V3 revocar;
- V4: el mismo V1 con otro orden de claves y espacios en el transporte (el servidor
re-canonicaliza).
- **Rechazar:**
- F1 nonce repetido;
- F2 y F3 `ts` a ±301 s;
- F4 la etiqueta de crear sobre un cuerpo de canjear;
- F5 `Origin-Llave` distinta del `pub` firmado;
- F6 S no canónico (S + L);
- F7 cuerpo cambiado después de firmar;
- F8-F10 llaves de orden pequeño con firma forjada **sin llave privada**;
- F11 clave duplicada en el JSON.
Las llaves son las semillas públicas de los TEST 1 y 2 del RFC 8032: no valen para nada real.
## Lo que medí
**OpenSSL 3.6.4 ACEPTA F8, F9 y F10** (`openssl_acepta_la_firma` en cada vector). Son las
tres llaves de RV14:
- la identidad (`01 00..00`);
- el punto de orden 2 (`ec ff..ff 7f`);
- una codificación NO canónica de la identidad (`ee ff..ff 7f`).
Con R de orden pequeño y S = 0 la ecuación de verificación se cumple. Por eso una
verificación «estricta» puede no bastar. Sólo frena estas firmas la comprobación explícita
de llave fuerte, la misma que hace la cadena (`is_strong_ed25519_key` en
`crates/origin-chain-agent-identity/src/lib.rs`):
1. decodificar `pub`;
2. exigir que vuelva a codificarse igual;
3. exigir que el punto no sea de orden pequeño.
No he medido `@noble/curves` (no tengo permiso para descargarlo). El vector te lo dice en tu
máquina.
Contrafactual del propio generador. Si se quita la comprobación de orden pequeño, la de S < L o
la etiqueta, el generador se niega a emitir: reprueba F8-F9, F6 y F4.
La decodificación canónica y la recodificación se cubren entre sí, y las dos se solapan con la
de orden pequeño en F10. Ningún vector puede separarlas. Las únicas llaves con dos
codificaciones son los puntos con y < 19 (y + p < 2^255), y de ninguno se conoce la llave
privada. Quitar una sola de esas tres no tumba nada; quitar las tres, sí.
## Supuestos por confirmar (Irhis)
1. `expiraEn` es un instante absoluto en segundos. Si es una duración, se regeneran.
2. **Canjear** firma `{aud, nonce, pub, token, ts}`:
- `pub` es la OIT de quien canjea;
- el token (la preimagen) va dentro de lo firmado.
El §3 del contrato manda `{token, canjeadorOit, firma}` y el §3.1 habla de «la huella del
token». Con la huella sola el servidor no puede comprobar la preimagen.
3. **Revocar** firma `{aud, huella, nonce, pub, ts}`, con `pub` igual a `invitadorOit`.