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>
This commit is contained in:
CharonandClaude Opus 5.5 committed 2026-10-28 04:27:14 -05:00
1 parent e7b9c0c8bf
commit abe680e853
3 files changed
+893

No files matched your search

+73
View File
@@ -0,0 +1,73 @@
# 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`.
+385
View File
@@ -0,0 +1,385 @@
#!/usr/bin/env python3
"""Vectores de prueba de la firma OIT de las invitaciones V1 (CONTRATO_INVITACIONES_V1 §3.1).
python3 generar.py > invitaciones-v1.json
Bytes firmados: etiqueta || 0x00 || JCS(cuerpo). Ed25519 en Python puro, sobre el código de
referencia del RFC 8032 §6, para no depender de la misma librería que se va a probar. Antes de
emitir nada se comprueba contra los vectores del RFC 8032 (TEST 1 y 2) y contra OpenSSL.
JCS (RFC 8785) se implementa sólo para el dominio de estos cuerpos: claves y cadenas ASCII y
enteros < 2^53. Para ese dominio JCS es exactamente claves ordenadas, sin espacios y los escapes
mínimos de JSON; fuera de él, este generador se niega.
Las llaves son las semillas PÚBLICAS de los TEST 1 y 2 del RFC 8032: nunca valen para nada real.
"""
import base64, calendar, hashlib, json, os, subprocess, sys, tempfile
# ── Ed25519 (RFC 8032 §6) ────────────────────────────────────────────────────
p = 2**255 - 19
L = 2**252 + 27742317777372353535851937790883648493
d = -121665 * pow(121666, p - 2, p) % p
SQRT_M1 = pow(2, (p - 1) // 4, p)
IDENTIDAD = (0, 1, 1, 0)
def sha512_modl(b):
return int.from_bytes(hashlib.sha512(b).digest(), "little") % L
def sumar(P, Q):
A = (P[1] - P[0]) * (Q[1] - Q[0]) % p
B = (P[1] + P[0]) * (Q[1] + Q[0]) % p
C = 2 * P[3] * Q[3] * d % p
D = 2 * P[2] * Q[2] % p
E, F, G, H = B - A, D - C, D + C, B + A
return (E * F % p, G * H % p, F * G % p, E * H % p)
def multiplicar(s, P):
Q = IDENTIDAD
while s > 0:
if s & 1:
Q = sumar(Q, P)
P = sumar(P, P)
s >>= 1
return Q
def iguales(P, Q):
return (P[0] * Q[2] - Q[0] * P[2]) % p == 0 and (P[1] * Q[2] - Q[1] * P[2]) % p == 0
def recuperar_x(y, signo):
if y >= p:
return None
x2 = (y * y - 1) * pow(d * y * y + 1, p - 2, p)
if x2 == 0:
return None if signo else 0
x = pow(x2, (p + 3) // 8, p)
if (x * x - x2) % p != 0:
x = x * SQRT_M1 % p
if (x * x - x2) % p != 0:
return None
if (x & 1) != signo:
x = p - x
return x
gy = 4 * pow(5, p - 2, p) % p
gx = recuperar_x(gy, 0)
BASE = (gx, gy, 1, gx * gy % p)
def comprimir(P):
zi = pow(P[2], p - 2, p)
x, y = P[0] * zi % p, P[1] * zi % p
return int.to_bytes(y | ((x & 1) << 255), 32, "little")
def descomprimir(s, laxo=False):
"""Estricto: rechaza y >= p y el «cero negativo», como el RFC 8032. Laxo: reduce y mod p,
como hacen los verificadores permisivos (ZIP-215 y otros)."""
y = int.from_bytes(s, "little")
signo = y >> 255
y &= (1 << 255) - 1
if laxo:
y %= p
x = recuperar_x(y, signo)
if x is None and laxo and signo:
x = recuperar_x(y, 0)
if x is None:
return None
return (x, y, 1, x * y % p)
def expandir(semilla):
h = hashlib.sha512(semilla).digest()
a = int.from_bytes(h[:32], "little")
a &= (1 << 254) - 8
a |= 1 << 254
return a, h[32:]
def publica(semilla):
return comprimir(multiplicar(expandir(semilla)[0], BASE))
def firmar(semilla, msg):
a, prefijo = expandir(semilla)
A = comprimir(multiplicar(a, BASE))
r = sha512_modl(prefijo + msg)
R = comprimir(multiplicar(r, BASE))
h = sha512_modl(R + A + msg)
return R + int.to_bytes((r + h * a) % L, 32, "little")
def orden_pequeno(P):
return iguales(multiplicar(8, P), IDENTIDAD)
def verificar(pub, msg, firma, modo):
"""modo «estricto»: lo que §3.1 exige (codificación canónica de A y R, S < L, A de orden no
pequeño). modo «laxo»: sólo la ecuación, con A y S tal como lleguen. Sirve para enseñar qué
vectores pasan en un verificador permisivo y por qué hace falta cada comprobación."""
if len(pub) != 32 or len(firma) != 64:
return False
estricto = modo == "estricto"
A = descomprimir(pub, laxo=not estricto)
R = descomprimir(firma[:32], laxo=not estricto)
if A is None or R is None:
return False
S = int.from_bytes(firma[32:], "little")
if estricto and (S >= L or comprimir(A) != pub or orden_pequeno(A)):
return False
h = sha512_modl(firma[:32] + pub + msg)
return iguales(multiplicar(S % L if not estricto else S, BASE), sumar(R, multiplicar(h, A)))
# ── comprobaciones del propio generador ─────────────────────────────────────
RFC = [ # RFC 8032 §7.1, TEST 1 y TEST 2
("9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60",
"d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a", "",
"e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e06522490155"
"5fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b"),
("4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb",
"3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c", "72",
"92a009a9f0d4cab8720e820b5f642540a2b27b5416503f8fb3762223ebdb69da"
"085ac1e43e15996e458f3613d0f11d8c387b2eaeb4302aeeb00d291612bb0c00"),
]
for sk, pk, m, sig in RFC:
sk, m = bytes.fromhex(sk), bytes.fromhex(m)
assert publica(sk).hex() == pk, "la llave pública no coincide con el RFC 8032"
assert firmar(sk, m).hex() == sig, "la firma no coincide con el RFC 8032"
assert verificar(bytes.fromhex(pk), m, bytes.fromhex(sig), "estricto")
assert not verificar(bytes.fromhex(pk), m + b"x", bytes.fromhex(sig), "estricto")
def openssl_firma(semilla, msg):
"""Segunda implementación independiente: OpenSSL, con la misma semilla (Ed25519 es
determinista, así que la firma tiene que salir idéntica byte a byte)."""
der = bytes.fromhex("302e020100300506032b657004220420") + semilla
with tempfile.TemporaryDirectory() as t:
k, mm = os.path.join(t, "k.der"), os.path.join(t, "m")
open(k, "wb").write(der)
open(mm, "wb").write(msg)
return subprocess.run(["openssl", "pkeyutl", "-sign", "-inkey", k, "-keyform", "DER",
"-rawin", "-in", mm], capture_output=True, check=True).stdout
def openssl_verifica(pub, msg, firma):
"""Lo que dice un verificador de terceros muy usado. Medido el 2026-10-28 con OpenSSL 3.6.4:
ACEPTA F8, F9 y F10, las firmas forjadas sin llave privada."""
with tempfile.TemporaryDirectory() as t:
k, mm, ss = (os.path.join(t, n) for n in ("k.der", "m", "s"))
open(k, "wb").write(bytes.fromhex("302a300506032b6570032100") + pub)
open(mm, "wb").write(msg)
open(ss, "wb").write(firma)
return subprocess.run(["openssl", "pkeyutl", "-verify", "-pubin", "-inkey", k, "-keyform", "DER",
"-rawin", "-in", mm, "-sigfile", ss], capture_output=True).returncode == 0
# ── JCS restringido ─────────────────────────────────────────────────────────
def jcs(obj):
for k, v in obj.items():
assert isinstance(k, str) and k.isascii(), k
if isinstance(v, str):
assert v.isascii() and all(0x20 <= ord(c) < 0x7f and c not in '"\\' for c in v), v
else:
assert isinstance(v, int) and not isinstance(v, bool) and 0 <= v < 2**53, v
# con claves ASCII, el orden por unidades UTF-16 de RFC 8785 es el orden de bytes
return json.dumps(obj, sort_keys=True, separators=(",", ":"), ensure_ascii=True).encode()
def b64u(b):
return base64.urlsafe_b64encode(b).rstrip(b"=").decode()
def det(etiqueta, n):
"""Bytes de prueba deterministas. En producción, nonce y token son ALEATORIOS."""
return hashlib.sha256(b"origin-invitaciones/vectores-de-prueba/" + etiqueta.encode()).digest()[:n]
ETQ = {op: f"origin-invitaciones/{op}/v1" for op in ("crear", "canjear", "revocar")}
AUD = "originmetaverse.com"
TS = calendar.timegm((2026, 10, 28, 12, 0, 0))
LLAVE_A = bytes.fromhex(RFC[0][0]) # quien invita
LLAVE_B = bytes.fromhex(RFC[1][0]) # quien canjea
PUB_A, PUB_B = publica(LLAVE_A), publica(LLAVE_B)
TOKEN = b64u(det("token", 32))
HUELLA = hashlib.sha256(TOKEN.encode()).hexdigest()
assert len(TOKEN) == 43
def mensaje(etiqueta, cuerpo):
return etiqueta.encode() + b"\x00" + jcs(cuerpo)
def cuerpo(pub, nonce, **extra):
return {"aud": AUD, "ts": TS, "nonce": b64u(det(nonce, 16)), "pub": b64u(pub), **extra}
C_CREAR = cuerpo(PUB_A, "n1", destino="mundo-de-prueba.v1", expiraEn=TS + 7 * 86400)
C_CANJEAR = cuerpo(PUB_B, "n2", token=TOKEN)
C_REVOCAR = cuerpo(PUB_A, "n3", huella=HUELLA)
vectores = []
def caso(id, op, c, firma, llave_cabecera, espera, motivo, reloj=TS, nota=None, transporte=None,
firmado_con=None):
m = mensaje(ETQ[op], c)
v = {
"id": id, "operacion": op, "etiqueta": ETQ[op],
"cuerpo": c, "jcs": jcs(c).decode(),
"transporte_json": transporte if transporte is not None else jcs(c).decode(),
"mensaje_hex": m.hex(),
"cabeceras": {"Origin-Llave": b64u(llave_cabecera), "Origin-Firma": b64u(firma)},
"reloj_del_servidor": reloj,
"espera": espera, "motivo": motivo,
"verificador_estricto_acepta_la_firma": verificar(llave_cabecera, m, firma, "estricto"),
"verificador_laxo_acepta_la_firma": verificar(llave_cabecera, m, firma, "laxo"),
"openssl_acepta_la_firma": openssl_verifica(llave_cabecera, m, firma),
}
if firmado_con:
v["firmado_con"] = firmado_con
if nota:
v["nota"] = nota
vectores.append(v)
return v
# Válidos
for id, op, c, sk, pub in (("V1", "crear", C_CREAR, LLAVE_A, PUB_A),
("V2", "canjear", C_CANJEAR, LLAVE_B, PUB_B),
("V3", "revocar", C_REVOCAR, LLAVE_A, PUB_A)):
f = firmar(sk, mensaje(ETQ[op], c))
assert f == openssl_firma(sk, mensaje(ETQ[op], c)), "OpenSSL no da la misma firma"
caso(id, op, c, f, pub, "acepta", "petición válida")
F_V1 = firmar(LLAVE_A, mensaje(ETQ["crear"], C_CREAR))
# El orden y los espacios del transporte no importan: el servidor re-canonicaliza.
caso("V4", "crear", C_CREAR, F_V1, PUB_A, "acepta",
"mismo V1 con otro orden de claves y espacios en el transporte: el servidor firma sobre JCS, "
"no sobre los bytes recibidos",
transporte='{ "pub": "%s", "ts": %d, "nonce": "%s", "aud": "%s", "expiraEn": %d, "destino": "%s" }'
% (C_CREAR["pub"], TS, C_CREAR["nonce"], AUD, C_CREAR["expiraEn"], C_CREAR["destino"]),
nota="usar como petición nueva; si se manda DESPUÉS de V1, es el caso F2 (nonce repetido)")
# Deben fallar
caso("F1", "crear", C_CREAR, F_V1, PUB_A, "rechaza", "nonce repetido",
nota="enviar V1 y luego exactamente la misma petición: la firma es buena, el par (pub, nonce) "
"ya se usó dentro de la ventana")
caso("F2", "crear", C_CREAR, F_V1, PUB_A, "rechaza", "ts fuera de la ventana de ±5 min",
reloj=TS + 301, nota="mismo V1, con el reloj del servidor 301 s por delante")
caso("F3", "crear", C_CREAR, F_V1, PUB_A, "rechaza", "ts fuera de la ventana de ±5 min",
reloj=TS - 301, nota="mismo V1, con el reloj del servidor 301 s por detrás (ts del futuro)")
# F4: la firma de «crear» puesta sobre un cuerpo de «canjear»
f_cruzada = firmar(LLAVE_B, mensaje(ETQ["crear"], C_CANJEAR))
caso("F4", "canjear", C_CANJEAR, f_cruzada, PUB_B, "rechaza",
"etiqueta cruzada: la firma se hizo con la etiqueta de crear sobre el cuerpo de canjear",
firmado_con=ETQ["crear"])
# F5: Origin-Llave distinta del pub firmado
caso("F5", "crear", C_CREAR, F_V1, PUB_B, "rechaza",
"Origin-Llave no es igual al pub del cuerpo (debe rechazarse ANTES de verificar)")
# F6: S no canónico (S + L): misma firma, otros bytes
S = int.from_bytes(F_V1[32:], "little")
f_mal = F_V1[:32] + int.to_bytes(S + L, 32, "little")
caso("F6", "crear", C_CREAR, f_mal, PUB_A, "rechaza", "S no canónico (S >= L): firma maleable")
# F7: firma de otro cuerpo (cambió destino después de firmar)
otro = dict(C_CREAR, destino="otro-mundo")
caso("F7", "crear", otro, F_V1, PUB_A, "rechaza", "el cuerpo no es el firmado (destino cambiado)")
# F8-F10: llaves de orden pequeño con la firma trivial, sin llave privada
def forjar(pub_bytes, etiqueta_nonce):
A = descomprimir(pub_bytes, laxo=True)
sub = [IDENTIDAD]
while True:
nxt = sumar(sub[-1], A)
if iguales(nxt, IDENTIDAD):
break
sub.append(nxt)
for intento in range(64):
c = cuerpo(pub_bytes, f"{etiqueta_nonce}-{intento}",
destino="mundo-de-prueba.v1", expiraEn=TS + 7 * 86400)
m = mensaje(ETQ["crear"], c)
for Rp in sub:
Rb = comprimir(Rp)
h = sha512_modl(Rb + pub_bytes + m)
if iguales(sumar(Rp, multiplicar(h, A)), IDENTIDAD):
return c, Rb + bytes(32)
raise SystemExit("no se pudo forjar")
for id, hexpub, nombre in (
("F8", "01" + "00" * 31, "la identidad (orden 1)"),
("F9", "ec" + "ff" * 30 + "7f", "el punto de orden 2"),
("F10", "ee" + "ff" * 30 + "7f", "una codificación NO canónica de la identidad (y = p + 1)")):
pb = bytes.fromhex(hexpub)
c, f = forjar(pb, id)
v = caso(id, "crear", c, f, pb, "rechaza",
f"llave de orden pequeño: {nombre}. Firma trivial R de orden pequeño y S = 0, hecha SIN "
"llave privada",
nota="la ecuación de verificación SE CUMPLE: sólo la rechaza la comprobación explícita "
"de llave fuerte (en la cadena, is_strong_ed25519_key; RV14)")
assert v["verificador_laxo_acepta_la_firma"], f"{id}: la forja debería pasar la ecuación"
if not v["openssl_acepta_la_firma"]:
print(f"aviso: este OpenSSL ya rechaza {id}", file=sys.stderr)
assert not v["verificador_estricto_acepta_la_firma"]
# F11: clave duplicada en el transporte
caso("F11", "crear", C_CREAR, F_V1, PUB_A, "rechaza",
"clave duplicada en el JSON recibido. JCS (RFC 8785 §3.1, I-JSON) no la admite, y JSON.parse "
"se queda en silencio con la última",
transporte=jcs(C_CREAR).decode()[:-1] + ',"destino":"otro-mundo"}')
# coherencia final
for v in vectores:
if v["espera"] == "acepta":
assert v["verificador_estricto_acepta_la_firma"], v["id"]
for id in ("F4", "F6", "F7"):
v = next(x for x in vectores if x["id"] == id)
assert not v["verificador_estricto_acepta_la_firma"], id
salida = {
"version": "origin-invitaciones/vectores/v1",
"contrato": "origin-metaverse-web docs/CONTRATO_INVITACIONES_V1.md §3.1 (d5f5f5e)",
"autora": "Charon, 2026-10-28",
"mensaje_firmado": "utf8(etiqueta) || 0x00 || JCS(cuerpo)",
"codificacion": "pub, firma, nonce y token en base64url sin relleno; huella = sha256(token) en hex",
"llaves_de_prueba": {
"A_quien_invita": {"semilla_hex": LLAVE_A.hex(), "pub_hex": PUB_A.hex(), "pub_b64url": b64u(PUB_A),
"origen": "RFC 8032 §7.1 TEST 1 (pública: nunca para nada real)"},
"B_quien_canjea": {"semilla_hex": LLAVE_B.hex(), "pub_hex": PUB_B.hex(), "pub_b64url": b64u(PUB_B),
"origen": "RFC 8032 §7.1 TEST 2"},
},
"token_de_prueba": TOKEN, "huella_de_prueba": HUELLA,
"comprobado_con": ["RFC 8032 TEST 1 y 2 (llave y firma)", "OpenSSL " + subprocess.run(
["openssl", "version"], capture_output=True, text=True).stdout.split()[1] + " (V1-V3 byte a byte)"],
"como_usarlos": [
"Cada vector es una petición: transporte_json es el cuerpo HTTP y cabeceras sus cabeceras.",
"Fija el reloj del servidor en reloj_del_servidor y empieza cada vector con el almacén de "
"nonces vacío, salvo F1, que va justo después de V1.",
"espera = acepta | rechaza. Un test que no sepa reprobar no vale: quita cada comprobación "
"y mira que su vector pase a aceptarse.",
"verificador_laxo_acepta_la_firma dice qué pasaría con un verificador permisivo: F6 y F8-F10 "
"pasan ahí. F8-F10 cumplen la ecuación: OpenSSL 3.6.4 los ACEPTA (openssl_acepta_la_firma). "
"Si tu verify con zip215:false tampoco los rechaza, falta la comprobación de llave fuerte: "
"decodificar pub, exigir que vuelva a codificarse igual y que no sea de orden pequeño.",
],
"supuestos_por_confirmar": [
"expiraEn es un instante absoluto en segundos (ts + 7 días aquí). Si es una duración, se regeneran.",
"canjear firma {aud, nonce, pub, token, ts}: pub es la OIT de quien canjea y el token (la "
"preimagen) va DENTRO de lo firmado. El §3 punto 3 dice {token, canjeadorOit, firma} y el "
"§3.1 dice «la huella del token»: con la huella sola el servidor no puede comprobar la preimagen.",
"revocar firma {aud, huella, nonce, pub, ts}, con pub igual a invitadorOit.",
],
"vectores": vectores,
}
json.dump(salida, sys.stdout, ensure_ascii=False, indent=2)
print()
@@ -0,0 +1,435 @@
{
"version": "origin-invitaciones/vectores/v1",
"contrato": "origin-metaverse-web docs/CONTRATO_INVITACIONES_V1.md §3.1 (d5f5f5e)",
"autora": "Charon, 2026-10-28",
"mensaje_firmado": "utf8(etiqueta) || 0x00 || JCS(cuerpo)",
"codificacion": "pub, firma, nonce y token en base64url sin relleno; huella = sha256(token) en hex",
"llaves_de_prueba": {
"A_quien_invita": {
"semilla_hex": "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60",
"pub_hex": "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a",
"pub_b64url": "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo",
"origen": "RFC 8032 §7.1 TEST 1 (pública: nunca para nada real)"
},
"B_quien_canjea": {
"semilla_hex": "4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb",
"pub_hex": "3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c",
"pub_b64url": "PUAXw-hDiVqStwqnTRt-vJyYLM8uxJaMwM1V8Sr0Zgw",
"origen": "RFC 8032 §7.1 TEST 2"
}
},
"token_de_prueba": "HVC4TTwAc5X9awxdLTV8X9kDOcw_g3yUJSOadF_UW64",
"huella_de_prueba": "d24ddd65bd757dc69b7a630d8410556221a433c4329531d22f66d23d4348b76c",
"comprobado_con": [
"RFC 8032 TEST 1 y 2 (llave y firma)",
"OpenSSL 3.6.4 (V1-V3 byte a byte)"
],
"como_usarlos": [
"Cada vector es una petición: transporte_json es el cuerpo HTTP y cabeceras sus cabeceras.",
"Fija el reloj del servidor en reloj_del_servidor y empieza cada vector con el almacén de nonces vacío, salvo F1, que va justo después de V1.",
"espera = acepta | rechaza. Un test que no sepa reprobar no vale: quita cada comprobación y mira que su vector pase a aceptarse.",
"verificador_laxo_acepta_la_firma dice qué pasaría con un verificador permisivo: F6 y F8-F10 pasan ahí. F8-F10 cumplen la ecuación: OpenSSL 3.6.4 los ACEPTA (openssl_acepta_la_firma). Si tu verify con zip215:false tampoco los rechaza, falta la comprobación de llave fuerte: decodificar pub, exigir que vuelva a codificarse igual y que no sea de orden pequeño."
],
"supuestos_por_confirmar": [
"expiraEn es un instante absoluto en segundos (ts + 7 días aquí). Si es una duración, se regeneran.",
"canjear firma {aud, nonce, pub, token, ts}: pub es la OIT de quien canjea y el token (la preimagen) va DENTRO de lo firmado. El §3 punto 3 dice {token, canjeadorOit, firma} y el §3.1 dice «la huella del token»: con la huella sola el servidor no puede comprobar la preimagen.",
"revocar firma {aud, huella, nonce, pub, ts}, con pub igual a invitadorOit."
],
"vectores": [
{
"id": "V1",
"operacion": "crear",
"etiqueta": "origin-invitaciones/crear/v1",
"cuerpo": {
"aud": "originmetaverse.com",
"ts": 1793188800,
"nonce": "l-SsDclPrSgeO_MdNULifQ",
"pub": "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo",
"destino": "mundo-de-prueba.v1",
"expiraEn": 1793793600
},
"jcs": "{\"aud\":\"originmetaverse.com\",\"destino\":\"mundo-de-prueba.v1\",\"expiraEn\":1793793600,\"nonce\":\"l-SsDclPrSgeO_MdNULifQ\",\"pub\":\"11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo\",\"ts\":1793188800}",
"transporte_json": "{\"aud\":\"originmetaverse.com\",\"destino\":\"mundo-de-prueba.v1\",\"expiraEn\":1793793600,\"nonce\":\"l-SsDclPrSgeO_MdNULifQ\",\"pub\":\"11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo\",\"ts\":1793188800}",
"mensaje_hex": "6f726967696e2d696e7669746163696f6e65732f63726561722f7631007b22617564223a226f726967696e6d65746176657273652e636f6d222c2264657374696e6f223a226d756e646f2d64652d7072756562612e7631222c22657870697261456e223a313739333739333630302c226e6f6e6365223a226c2d537344636c50725367654f5f4d644e554c696651222c22707562223a223131715941594b7843726656535f3754795751484f6737686376506170694d6c727749616150634855526f222c227473223a313739333138383830307d",
"cabeceras": {
"Origin-Llave": "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo",
"Origin-Firma": "94ykfqmBeDJEvKgBdGBGmdUZFQPprWW2F79-heC7ZIn9-hwpu-aQh5vwf7MCSZ0E3ead6U9JeqJUdgY0Oc8HAg"
},
"reloj_del_servidor": 1793188800,
"espera": "acepta",
"motivo": "petición válida",
"verificador_estricto_acepta_la_firma": true,
"verificador_laxo_acepta_la_firma": true,
"openssl_acepta_la_firma": true
},
{
"id": "V2",
"operacion": "canjear",
"etiqueta": "origin-invitaciones/canjear/v1",
"cuerpo": {
"aud": "originmetaverse.com",
"ts": 1793188800,
"nonce": "ioBVewvUqbJNvOUwlE380w",
"pub": "PUAXw-hDiVqStwqnTRt-vJyYLM8uxJaMwM1V8Sr0Zgw",
"token": "HVC4TTwAc5X9awxdLTV8X9kDOcw_g3yUJSOadF_UW64"
},
"jcs": "{\"aud\":\"originmetaverse.com\",\"nonce\":\"ioBVewvUqbJNvOUwlE380w\",\"pub\":\"PUAXw-hDiVqStwqnTRt-vJyYLM8uxJaMwM1V8Sr0Zgw\",\"token\":\"HVC4TTwAc5X9awxdLTV8X9kDOcw_g3yUJSOadF_UW64\",\"ts\":1793188800}",
"transporte_json": "{\"aud\":\"originmetaverse.com\",\"nonce\":\"ioBVewvUqbJNvOUwlE380w\",\"pub\":\"PUAXw-hDiVqStwqnTRt-vJyYLM8uxJaMwM1V8Sr0Zgw\",\"token\":\"HVC4TTwAc5X9awxdLTV8X9kDOcw_g3yUJSOadF_UW64\",\"ts\":1793188800}",
"mensaje_hex": "6f726967696e2d696e7669746163696f6e65732f63616e6a6561722f7631007b22617564223a226f726967696e6d65746176657273652e636f6d222c226e6f6e6365223a22696f42566577765571624a4e764f55776c4533383077222c22707562223a2250554158772d6844695671537477716e5452742d764a79594c4d3875784a614d774d3156385372305a6777222c22746f6b656e223a22485643345454774163355839617778644c54563858396b444f63775f673379554a534f6164465f55573634222c227473223a313739333138383830307d",
"cabeceras": {
"Origin-Llave": "PUAXw-hDiVqStwqnTRt-vJyYLM8uxJaMwM1V8Sr0Zgw",
"Origin-Firma": "3Z79SlRC62mrNAzMLP6o1CNbIdj2uIWlc2sWzQvQDhZP5m9dHCKPjrI3SqM33eZQRnYx7b4ouq2ARR2Yi-ujBA"
},
"reloj_del_servidor": 1793188800,
"espera": "acepta",
"motivo": "petición válida",
"verificador_estricto_acepta_la_firma": true,
"verificador_laxo_acepta_la_firma": true,
"openssl_acepta_la_firma": true
},
{
"id": "V3",
"operacion": "revocar",
"etiqueta": "origin-invitaciones/revocar/v1",
"cuerpo": {
"aud": "originmetaverse.com",
"ts": 1793188800,
"nonce": "CbdsIxFrDPvBSN_2qYeVcw",
"pub": "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo",
"huella": "d24ddd65bd757dc69b7a630d8410556221a433c4329531d22f66d23d4348b76c"
},
"jcs": "{\"aud\":\"originmetaverse.com\",\"huella\":\"d24ddd65bd757dc69b7a630d8410556221a433c4329531d22f66d23d4348b76c\",\"nonce\":\"CbdsIxFrDPvBSN_2qYeVcw\",\"pub\":\"11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo\",\"ts\":1793188800}",
"transporte_json": "{\"aud\":\"originmetaverse.com\",\"huella\":\"d24ddd65bd757dc69b7a630d8410556221a433c4329531d22f66d23d4348b76c\",\"nonce\":\"CbdsIxFrDPvBSN_2qYeVcw\",\"pub\":\"11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo\",\"ts\":1793188800}",
"mensaje_hex": "6f726967696e2d696e7669746163696f6e65732f7265766f6361722f7631007b22617564223a226f726967696e6d65746176657273652e636f6d222c226875656c6c61223a2264323464646436356264373537646336396237613633306438343130353536323231613433336334333239353331643232663636643233643433343862373663222c226e6f6e6365223a22436264734978467244507642534e5f32715965566377222c22707562223a223131715941594b7843726656535f3754795751484f6737686376506170694d6c727749616150634855526f222c227473223a313739333138383830307d",
"cabeceras": {
"Origin-Llave": "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo",
"Origin-Firma": "FR69zi1RDZYrREfVHhZkoO190h27Mrw74_NLPIxze79_QZMRXL8f700OPAzOu00CQLaDIuel4VD30mvqtRcHBQ"
},
"reloj_del_servidor": 1793188800,
"espera": "acepta",
"motivo": "petición válida",
"verificador_estricto_acepta_la_firma": true,
"verificador_laxo_acepta_la_firma": true,
"openssl_acepta_la_firma": true
},
{
"id": "V4",
"operacion": "crear",
"etiqueta": "origin-invitaciones/crear/v1",
"cuerpo": {
"aud": "originmetaverse.com",
"ts": 1793188800,
"nonce": "l-SsDclPrSgeO_MdNULifQ",
"pub": "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo",
"destino": "mundo-de-prueba.v1",
"expiraEn": 1793793600
},
"jcs": "{\"aud\":\"originmetaverse.com\",\"destino\":\"mundo-de-prueba.v1\",\"expiraEn\":1793793600,\"nonce\":\"l-SsDclPrSgeO_MdNULifQ\",\"pub\":\"11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo\",\"ts\":1793188800}",
"transporte_json": "{ \"pub\": \"11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo\", \"ts\": 1793188800, \"nonce\": \"l-SsDclPrSgeO_MdNULifQ\", \"aud\": \"originmetaverse.com\", \"expiraEn\": 1793793600, \"destino\": \"mundo-de-prueba.v1\" }",
"mensaje_hex": "6f726967696e2d696e7669746163696f6e65732f63726561722f7631007b22617564223a226f726967696e6d65746176657273652e636f6d222c2264657374696e6f223a226d756e646f2d64652d7072756562612e7631222c22657870697261456e223a313739333739333630302c226e6f6e6365223a226c2d537344636c50725367654f5f4d644e554c696651222c22707562223a223131715941594b7843726656535f3754795751484f6737686376506170694d6c727749616150634855526f222c227473223a313739333138383830307d",
"cabeceras": {
"Origin-Llave": "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo",
"Origin-Firma": "94ykfqmBeDJEvKgBdGBGmdUZFQPprWW2F79-heC7ZIn9-hwpu-aQh5vwf7MCSZ0E3ead6U9JeqJUdgY0Oc8HAg"
},
"reloj_del_servidor": 1793188800,
"espera": "acepta",
"motivo": "mismo V1 con otro orden de claves y espacios en el transporte: el servidor firma sobre JCS, no sobre los bytes recibidos",
"verificador_estricto_acepta_la_firma": true,
"verificador_laxo_acepta_la_firma": true,
"openssl_acepta_la_firma": true,
"nota": "usar como petición nueva; si se manda DESPUÉS de V1, es el caso F2 (nonce repetido)"
},
{
"id": "F1",
"operacion": "crear",
"etiqueta": "origin-invitaciones/crear/v1",
"cuerpo": {
"aud": "originmetaverse.com",
"ts": 1793188800,
"nonce": "l-SsDclPrSgeO_MdNULifQ",
"pub": "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo",
"destino": "mundo-de-prueba.v1",
"expiraEn": 1793793600
},
"jcs": "{\"aud\":\"originmetaverse.com\",\"destino\":\"mundo-de-prueba.v1\",\"expiraEn\":1793793600,\"nonce\":\"l-SsDclPrSgeO_MdNULifQ\",\"pub\":\"11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo\",\"ts\":1793188800}",
"transporte_json": "{\"aud\":\"originmetaverse.com\",\"destino\":\"mundo-de-prueba.v1\",\"expiraEn\":1793793600,\"nonce\":\"l-SsDclPrSgeO_MdNULifQ\",\"pub\":\"11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo\",\"ts\":1793188800}",
"mensaje_hex": "6f726967696e2d696e7669746163696f6e65732f63726561722f7631007b22617564223a226f726967696e6d65746176657273652e636f6d222c2264657374696e6f223a226d756e646f2d64652d7072756562612e7631222c22657870697261456e223a313739333739333630302c226e6f6e6365223a226c2d537344636c50725367654f5f4d644e554c696651222c22707562223a223131715941594b7843726656535f3754795751484f6737686376506170694d6c727749616150634855526f222c227473223a313739333138383830307d",
"cabeceras": {
"Origin-Llave": "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo",
"Origin-Firma": "94ykfqmBeDJEvKgBdGBGmdUZFQPprWW2F79-heC7ZIn9-hwpu-aQh5vwf7MCSZ0E3ead6U9JeqJUdgY0Oc8HAg"
},
"reloj_del_servidor": 1793188800,
"espera": "rechaza",
"motivo": "nonce repetido",
"verificador_estricto_acepta_la_firma": true,
"verificador_laxo_acepta_la_firma": true,
"openssl_acepta_la_firma": true,
"nota": "enviar V1 y luego exactamente la misma petición: la firma es buena, el par (pub, nonce) ya se usó dentro de la ventana"
},
{
"id": "F2",
"operacion": "crear",
"etiqueta": "origin-invitaciones/crear/v1",
"cuerpo": {
"aud": "originmetaverse.com",
"ts": 1793188800,
"nonce": "l-SsDclPrSgeO_MdNULifQ",
"pub": "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo",
"destino": "mundo-de-prueba.v1",
"expiraEn": 1793793600
},
"jcs": "{\"aud\":\"originmetaverse.com\",\"destino\":\"mundo-de-prueba.v1\",\"expiraEn\":1793793600,\"nonce\":\"l-SsDclPrSgeO_MdNULifQ\",\"pub\":\"11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo\",\"ts\":1793188800}",
"transporte_json": "{\"aud\":\"originmetaverse.com\",\"destino\":\"mundo-de-prueba.v1\",\"expiraEn\":1793793600,\"nonce\":\"l-SsDclPrSgeO_MdNULifQ\",\"pub\":\"11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo\",\"ts\":1793188800}",
"mensaje_hex": "6f726967696e2d696e7669746163696f6e65732f63726561722f7631007b22617564223a226f726967696e6d65746176657273652e636f6d222c2264657374696e6f223a226d756e646f2d64652d7072756562612e7631222c22657870697261456e223a313739333739333630302c226e6f6e6365223a226c2d537344636c50725367654f5f4d644e554c696651222c22707562223a223131715941594b7843726656535f3754795751484f6737686376506170694d6c727749616150634855526f222c227473223a313739333138383830307d",
"cabeceras": {
"Origin-Llave": "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo",
"Origin-Firma": "94ykfqmBeDJEvKgBdGBGmdUZFQPprWW2F79-heC7ZIn9-hwpu-aQh5vwf7MCSZ0E3ead6U9JeqJUdgY0Oc8HAg"
},
"reloj_del_servidor": 1793189101,
"espera": "rechaza",
"motivo": "ts fuera de la ventana de ±5 min",
"verificador_estricto_acepta_la_firma": true,
"verificador_laxo_acepta_la_firma": true,
"openssl_acepta_la_firma": true,
"nota": "mismo V1, con el reloj del servidor 301 s por delante"
},
{
"id": "F3",
"operacion": "crear",
"etiqueta": "origin-invitaciones/crear/v1",
"cuerpo": {
"aud": "originmetaverse.com",
"ts": 1793188800,
"nonce": "l-SsDclPrSgeO_MdNULifQ",
"pub": "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo",
"destino": "mundo-de-prueba.v1",
"expiraEn": 1793793600
},
"jcs": "{\"aud\":\"originmetaverse.com\",\"destino\":\"mundo-de-prueba.v1\",\"expiraEn\":1793793600,\"nonce\":\"l-SsDclPrSgeO_MdNULifQ\",\"pub\":\"11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo\",\"ts\":1793188800}",
"transporte_json": "{\"aud\":\"originmetaverse.com\",\"destino\":\"mundo-de-prueba.v1\",\"expiraEn\":1793793600,\"nonce\":\"l-SsDclPrSgeO_MdNULifQ\",\"pub\":\"11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo\",\"ts\":1793188800}",
"mensaje_hex": "6f726967696e2d696e7669746163696f6e65732f63726561722f7631007b22617564223a226f726967696e6d65746176657273652e636f6d222c2264657374696e6f223a226d756e646f2d64652d7072756562612e7631222c22657870697261456e223a313739333739333630302c226e6f6e6365223a226c2d537344636c50725367654f5f4d644e554c696651222c22707562223a223131715941594b7843726656535f3754795751484f6737686376506170694d6c727749616150634855526f222c227473223a313739333138383830307d",
"cabeceras": {
"Origin-Llave": "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo",
"Origin-Firma": "94ykfqmBeDJEvKgBdGBGmdUZFQPprWW2F79-heC7ZIn9-hwpu-aQh5vwf7MCSZ0E3ead6U9JeqJUdgY0Oc8HAg"
},
"reloj_del_servidor": 1793188499,
"espera": "rechaza",
"motivo": "ts fuera de la ventana de ±5 min",
"verificador_estricto_acepta_la_firma": true,
"verificador_laxo_acepta_la_firma": true,
"openssl_acepta_la_firma": true,
"nota": "mismo V1, con el reloj del servidor 301 s por detrás (ts del futuro)"
},
{
"id": "F4",
"operacion": "canjear",
"etiqueta": "origin-invitaciones/canjear/v1",
"cuerpo": {
"aud": "originmetaverse.com",
"ts": 1793188800,
"nonce": "ioBVewvUqbJNvOUwlE380w",
"pub": "PUAXw-hDiVqStwqnTRt-vJyYLM8uxJaMwM1V8Sr0Zgw",
"token": "HVC4TTwAc5X9awxdLTV8X9kDOcw_g3yUJSOadF_UW64"
},
"jcs": "{\"aud\":\"originmetaverse.com\",\"nonce\":\"ioBVewvUqbJNvOUwlE380w\",\"pub\":\"PUAXw-hDiVqStwqnTRt-vJyYLM8uxJaMwM1V8Sr0Zgw\",\"token\":\"HVC4TTwAc5X9awxdLTV8X9kDOcw_g3yUJSOadF_UW64\",\"ts\":1793188800}",
"transporte_json": "{\"aud\":\"originmetaverse.com\",\"nonce\":\"ioBVewvUqbJNvOUwlE380w\",\"pub\":\"PUAXw-hDiVqStwqnTRt-vJyYLM8uxJaMwM1V8Sr0Zgw\",\"token\":\"HVC4TTwAc5X9awxdLTV8X9kDOcw_g3yUJSOadF_UW64\",\"ts\":1793188800}",
"mensaje_hex": "6f726967696e2d696e7669746163696f6e65732f63616e6a6561722f7631007b22617564223a226f726967696e6d65746176657273652e636f6d222c226e6f6e6365223a22696f42566577765571624a4e764f55776c4533383077222c22707562223a2250554158772d6844695671537477716e5452742d764a79594c4d3875784a614d774d3156385372305a6777222c22746f6b656e223a22485643345454774163355839617778644c54563858396b444f63775f673379554a534f6164465f55573634222c227473223a313739333138383830307d",
"cabeceras": {
"Origin-Llave": "PUAXw-hDiVqStwqnTRt-vJyYLM8uxJaMwM1V8Sr0Zgw",
"Origin-Firma": "8tzYumpNlvl9a42l970jmXJB5PFYUq1pK-RJagxXDjq74a_hkeOVtDqGrOK0eopWrfNs9kWwE423IP99fsKDDg"
},
"reloj_del_servidor": 1793188800,
"espera": "rechaza",
"motivo": "etiqueta cruzada: la firma se hizo con la etiqueta de crear sobre el cuerpo de canjear",
"verificador_estricto_acepta_la_firma": false,
"verificador_laxo_acepta_la_firma": false,
"openssl_acepta_la_firma": false,
"firmado_con": "origin-invitaciones/crear/v1"
},
{
"id": "F5",
"operacion": "crear",
"etiqueta": "origin-invitaciones/crear/v1",
"cuerpo": {
"aud": "originmetaverse.com",
"ts": 1793188800,
"nonce": "l-SsDclPrSgeO_MdNULifQ",
"pub": "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo",
"destino": "mundo-de-prueba.v1",
"expiraEn": 1793793600
},
"jcs": "{\"aud\":\"originmetaverse.com\",\"destino\":\"mundo-de-prueba.v1\",\"expiraEn\":1793793600,\"nonce\":\"l-SsDclPrSgeO_MdNULifQ\",\"pub\":\"11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo\",\"ts\":1793188800}",
"transporte_json": "{\"aud\":\"originmetaverse.com\",\"destino\":\"mundo-de-prueba.v1\",\"expiraEn\":1793793600,\"nonce\":\"l-SsDclPrSgeO_MdNULifQ\",\"pub\":\"11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo\",\"ts\":1793188800}",
"mensaje_hex": "6f726967696e2d696e7669746163696f6e65732f63726561722f7631007b22617564223a226f726967696e6d65746176657273652e636f6d222c2264657374696e6f223a226d756e646f2d64652d7072756562612e7631222c22657870697261456e223a313739333739333630302c226e6f6e6365223a226c2d537344636c50725367654f5f4d644e554c696651222c22707562223a223131715941594b7843726656535f3754795751484f6737686376506170694d6c727749616150634855526f222c227473223a313739333138383830307d",
"cabeceras": {
"Origin-Llave": "PUAXw-hDiVqStwqnTRt-vJyYLM8uxJaMwM1V8Sr0Zgw",
"Origin-Firma": "94ykfqmBeDJEvKgBdGBGmdUZFQPprWW2F79-heC7ZIn9-hwpu-aQh5vwf7MCSZ0E3ead6U9JeqJUdgY0Oc8HAg"
},
"reloj_del_servidor": 1793188800,
"espera": "rechaza",
"motivo": "Origin-Llave no es igual al pub del cuerpo (debe rechazarse ANTES de verificar)",
"verificador_estricto_acepta_la_firma": false,
"verificador_laxo_acepta_la_firma": false,
"openssl_acepta_la_firma": false
},
{
"id": "F6",
"operacion": "crear",
"etiqueta": "origin-invitaciones/crear/v1",
"cuerpo": {
"aud": "originmetaverse.com",
"ts": 1793188800,
"nonce": "l-SsDclPrSgeO_MdNULifQ",
"pub": "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo",
"destino": "mundo-de-prueba.v1",
"expiraEn": 1793793600
},
"jcs": "{\"aud\":\"originmetaverse.com\",\"destino\":\"mundo-de-prueba.v1\",\"expiraEn\":1793793600,\"nonce\":\"l-SsDclPrSgeO_MdNULifQ\",\"pub\":\"11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo\",\"ts\":1793188800}",
"transporte_json": "{\"aud\":\"originmetaverse.com\",\"destino\":\"mundo-de-prueba.v1\",\"expiraEn\":1793793600,\"nonce\":\"l-SsDclPrSgeO_MdNULifQ\",\"pub\":\"11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo\",\"ts\":1793188800}",
"mensaje_hex": "6f726967696e2d696e7669746163696f6e65732f63726561722f7631007b22617564223a226f726967696e6d65746176657273652e636f6d222c2264657374696e6f223a226d756e646f2d64652d7072756562612e7631222c22657870697261456e223a313739333739333630302c226e6f6e6365223a226c2d537344636c50725367654f5f4d644e554c696651222c22707562223a223131715941594b7843726656535f3754795751484f6737686376506170694d6c727749616150634855526f222c227473223a313739333138383830307d",
"cabeceras": {
"Origin-Llave": "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo",
"Origin-Firma": "94ykfqmBeDJEvKgBdGBGmdUZFQPprWW2F79-heC7ZInqzhKG1Umj33GNd1bhQnwZ3ead6U9JeqJUdgY0Oc8HEg"
},
"reloj_del_servidor": 1793188800,
"espera": "rechaza",
"motivo": "S no canónico (S >= L): firma maleable",
"verificador_estricto_acepta_la_firma": false,
"verificador_laxo_acepta_la_firma": true,
"openssl_acepta_la_firma": false
},
{
"id": "F7",
"operacion": "crear",
"etiqueta": "origin-invitaciones/crear/v1",
"cuerpo": {
"aud": "originmetaverse.com",
"ts": 1793188800,
"nonce": "l-SsDclPrSgeO_MdNULifQ",
"pub": "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo",
"destino": "otro-mundo",
"expiraEn": 1793793600
},
"jcs": "{\"aud\":\"originmetaverse.com\",\"destino\":\"otro-mundo\",\"expiraEn\":1793793600,\"nonce\":\"l-SsDclPrSgeO_MdNULifQ\",\"pub\":\"11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo\",\"ts\":1793188800}",
"transporte_json": "{\"aud\":\"originmetaverse.com\",\"destino\":\"otro-mundo\",\"expiraEn\":1793793600,\"nonce\":\"l-SsDclPrSgeO_MdNULifQ\",\"pub\":\"11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo\",\"ts\":1793188800}",
"mensaje_hex": "6f726967696e2d696e7669746163696f6e65732f63726561722f7631007b22617564223a226f726967696e6d65746176657273652e636f6d222c2264657374696e6f223a226f74726f2d6d756e646f222c22657870697261456e223a313739333739333630302c226e6f6e6365223a226c2d537344636c50725367654f5f4d644e554c696651222c22707562223a223131715941594b7843726656535f3754795751484f6737686376506170694d6c727749616150634855526f222c227473223a313739333138383830307d",
"cabeceras": {
"Origin-Llave": "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo",
"Origin-Firma": "94ykfqmBeDJEvKgBdGBGmdUZFQPprWW2F79-heC7ZIn9-hwpu-aQh5vwf7MCSZ0E3ead6U9JeqJUdgY0Oc8HAg"
},
"reloj_del_servidor": 1793188800,
"espera": "rechaza",
"motivo": "el cuerpo no es el firmado (destino cambiado)",
"verificador_estricto_acepta_la_firma": false,
"verificador_laxo_acepta_la_firma": false,
"openssl_acepta_la_firma": false
},
{
"id": "F8",
"operacion": "crear",
"etiqueta": "origin-invitaciones/crear/v1",
"cuerpo": {
"aud": "originmetaverse.com",
"ts": 1793188800,
"nonce": "XJeasE1GFNTkdfN8ER9KCg",
"pub": "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
"destino": "mundo-de-prueba.v1",
"expiraEn": 1793793600
},
"jcs": "{\"aud\":\"originmetaverse.com\",\"destino\":\"mundo-de-prueba.v1\",\"expiraEn\":1793793600,\"nonce\":\"XJeasE1GFNTkdfN8ER9KCg\",\"pub\":\"AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\",\"ts\":1793188800}",
"transporte_json": "{\"aud\":\"originmetaverse.com\",\"destino\":\"mundo-de-prueba.v1\",\"expiraEn\":1793793600,\"nonce\":\"XJeasE1GFNTkdfN8ER9KCg\",\"pub\":\"AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\",\"ts\":1793188800}",
"mensaje_hex": "6f726967696e2d696e7669746163696f6e65732f63726561722f7631007b22617564223a226f726967696e6d65746176657273652e636f6d222c2264657374696e6f223a226d756e646f2d64652d7072756562612e7631222c22657870697261456e223a313739333739333630302c226e6f6e6365223a22584a656173453147464e546b64664e384552394b4367222c22707562223a2241514141414141414141414141414141414141414141414141414141414141414141414141414141414141222c227473223a313739333138383830307d",
"cabeceras": {
"Origin-Llave": "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
"Origin-Firma": "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
},
"reloj_del_servidor": 1793188800,
"espera": "rechaza",
"motivo": "llave de orden pequeño: la identidad (orden 1). Firma trivial R de orden pequeño y S = 0, hecha SIN llave privada",
"verificador_estricto_acepta_la_firma": false,
"verificador_laxo_acepta_la_firma": true,
"openssl_acepta_la_firma": true,
"nota": "la ecuación de verificación SE CUMPLE: sólo la rechaza la comprobación explícita de llave fuerte (en la cadena, is_strong_ed25519_key; RV14)"
},
{
"id": "F9",
"operacion": "crear",
"etiqueta": "origin-invitaciones/crear/v1",
"cuerpo": {
"aud": "originmetaverse.com",
"ts": 1793188800,
"nonce": "REfXPa4tGjc5slEI5bpktg",
"pub": "7P_______________________________________38",
"destino": "mundo-de-prueba.v1",
"expiraEn": 1793793600
},
"jcs": "{\"aud\":\"originmetaverse.com\",\"destino\":\"mundo-de-prueba.v1\",\"expiraEn\":1793793600,\"nonce\":\"REfXPa4tGjc5slEI5bpktg\",\"pub\":\"7P_______________________________________38\",\"ts\":1793188800}",
"transporte_json": "{\"aud\":\"originmetaverse.com\",\"destino\":\"mundo-de-prueba.v1\",\"expiraEn\":1793793600,\"nonce\":\"REfXPa4tGjc5slEI5bpktg\",\"pub\":\"7P_______________________________________38\",\"ts\":1793188800}",
"mensaje_hex": "6f726967696e2d696e7669746163696f6e65732f63726561722f7631007b22617564223a226f726967696e6d65746176657273652e636f6d222c2264657374696e6f223a226d756e646f2d64652d7072756562612e7631222c22657870697261456e223a313739333739333630302c226e6f6e6365223a225245665850613474476a6335736c45493562706b7467222c22707562223a2237505f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f3338222c227473223a313739333138383830307d",
"cabeceras": {
"Origin-Llave": "7P_______________________________________38",
"Origin-Firma": "7P_______________________________________38AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
},
"reloj_del_servidor": 1793188800,
"espera": "rechaza",
"motivo": "llave de orden pequeño: el punto de orden 2. Firma trivial R de orden pequeño y S = 0, hecha SIN llave privada",
"verificador_estricto_acepta_la_firma": false,
"verificador_laxo_acepta_la_firma": true,
"openssl_acepta_la_firma": true,
"nota": "la ecuación de verificación SE CUMPLE: sólo la rechaza la comprobación explícita de llave fuerte (en la cadena, is_strong_ed25519_key; RV14)"
},
{
"id": "F10",
"operacion": "crear",
"etiqueta": "origin-invitaciones/crear/v1",
"cuerpo": {
"aud": "originmetaverse.com",
"ts": 1793188800,
"nonce": "PBi0xI2621syp0145Qoi5Q",
"pub": "7v_______________________________________38",
"destino": "mundo-de-prueba.v1",
"expiraEn": 1793793600
},
"jcs": "{\"aud\":\"originmetaverse.com\",\"destino\":\"mundo-de-prueba.v1\",\"expiraEn\":1793793600,\"nonce\":\"PBi0xI2621syp0145Qoi5Q\",\"pub\":\"7v_______________________________________38\",\"ts\":1793188800}",
"transporte_json": "{\"aud\":\"originmetaverse.com\",\"destino\":\"mundo-de-prueba.v1\",\"expiraEn\":1793793600,\"nonce\":\"PBi0xI2621syp0145Qoi5Q\",\"pub\":\"7v_______________________________________38\",\"ts\":1793188800}",
"mensaje_hex": "6f726967696e2d696e7669746163696f6e65732f63726561722f7631007b22617564223a226f726967696e6d65746176657273652e636f6d222c2264657374696e6f223a226d756e646f2d64652d7072756562612e7631222c22657870697261456e223a313739333739333630302c226e6f6e6365223a225042693078493236323173797030313435516f693551222c22707562223a2237765f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f5f3338222c227473223a313739333138383830307d",
"cabeceras": {
"Origin-Llave": "7v_______________________________________38",
"Origin-Firma": "AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
},
"reloj_del_servidor": 1793188800,
"espera": "rechaza",
"motivo": "llave de orden pequeño: una codificación NO canónica de la identidad (y = p + 1). Firma trivial R de orden pequeño y S = 0, hecha SIN llave privada",
"verificador_estricto_acepta_la_firma": false,
"verificador_laxo_acepta_la_firma": true,
"openssl_acepta_la_firma": true,
"nota": "la ecuación de verificación SE CUMPLE: sólo la rechaza la comprobación explícita de llave fuerte (en la cadena, is_strong_ed25519_key; RV14)"
},
{
"id": "F11",
"operacion": "crear",
"etiqueta": "origin-invitaciones/crear/v1",
"cuerpo": {
"aud": "originmetaverse.com",
"ts": 1793188800,
"nonce": "l-SsDclPrSgeO_MdNULifQ",
"pub": "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo",
"destino": "mundo-de-prueba.v1",
"expiraEn": 1793793600
},
"jcs": "{\"aud\":\"originmetaverse.com\",\"destino\":\"mundo-de-prueba.v1\",\"expiraEn\":1793793600,\"nonce\":\"l-SsDclPrSgeO_MdNULifQ\",\"pub\":\"11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo\",\"ts\":1793188800}",
"transporte_json": "{\"aud\":\"originmetaverse.com\",\"destino\":\"mundo-de-prueba.v1\",\"expiraEn\":1793793600,\"nonce\":\"l-SsDclPrSgeO_MdNULifQ\",\"pub\":\"11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo\",\"ts\":1793188800,\"destino\":\"otro-mundo\"}",
"mensaje_hex": "6f726967696e2d696e7669746163696f6e65732f63726561722f7631007b22617564223a226f726967696e6d65746176657273652e636f6d222c2264657374696e6f223a226d756e646f2d64652d7072756562612e7631222c22657870697261456e223a313739333739333630302c226e6f6e6365223a226c2d537344636c50725367654f5f4d644e554c696651222c22707562223a223131715941594b7843726656535f3754795751484f6737686376506170694d6c727749616150634855526f222c227473223a313739333138383830307d",
"cabeceras": {
"Origin-Llave": "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo",
"Origin-Firma": "94ykfqmBeDJEvKgBdGBGmdUZFQPprWW2F79-heC7ZIn9-hwpu-aQh5vwf7MCSZ0E3ead6U9JeqJUdgY0Oc8HAg"
},
"reloj_del_servidor": 1793188800,
"espera": "rechaza",
"motivo": "clave duplicada en el JSON recibido. JCS (RFC 8785 §3.1, I-JSON) no la admite, y JSON.parse se queda en silencio con la última",
"verificador_estricto_acepta_la_firma": true,
"verificador_laxo_acepta_la_firma": true,
"openssl_acepta_la_firma": true
}
]
}