27 KiB
Plan: fase 2 del SDK TypeScript. Descifrado y cifrado tlock sobre noble 2, verificación local de releases y canonicidad de puntos
Estado: v2, 26 de septiembre de 2026. Decisiones de la sección 2 confirmadas por el autor el 26-09-2026, con los ajustes de dos revisiones (la de Claude Opus y la de Fable), verificados contra el código, el spec y npm. Sustituye la "Fase 2, cifrar y descifrar" de la sección 8 del plan v2 (PLAN_codec_cbor_y_pagina_svelte.md), cuyo bloque "Piezas de tlock-js y drand-client" contenía dos errores: para Quicknet el descifrador es decryptOnG2, no decryptOnG1, y drand-client no hace falta.
Alcance: abrir y crear cápsulas del perfil Quicknet en el navegador, sin red, sobre la librería TypeScript de App. El núcleo IBE se escribe en el proyecto sobre @noble/curves 2.x, los releases se verifican con noble y el perfil pinneado, y la envoltura age la pone age-encryption. Ni tlock-js ni drand-client entran en el bundle.
Regla de dependencias: la del plan v2. En ejecución, solo age, drand, tlock y lo que ellas arrastran, en cualquier lenguaje; @noble/curves y @noble/hashes entran por ser lo que age-encryption arrastra. Toda dependencia nueva se propone por escrito y no se instala sin aprobación (sección 3).
1. Situación de partida
Todo lo de esta tabla se ejecutó el 26-09-2026 sobre tlock-js 0.9.0, @noble/curves 1.9.7 y 2.4.0, los fixtures de App/testdata y la librería Go. Los scripts quedaron en el scratchpad de la sesión (…\scratchpad\noble2ibe\c, …\scratchpad\override, …\scratchpad\noblehist); si el directorio temporal ya no existe, cada punto se reproduce a partir de la descripción.
| Hecho | Detalle |
|---|---|
| Las 5 615 discrepancias de noble 1.9.7 frente a Go son un solo fenómeno | 5 612 codificaciones no canónicas de coordenada (x + p, c0 o c1 + k·p) que decodifican al punto correcto y 3 identidades con flags o carga no nula. Cero en sentido contrario, cero fallos de subgrupo: fromHex de 1.9.7 ya ejecuta assertValidity. Sin la comprobación de longitud, 102 más por codificaciones sin comprimir. |
| Efecto en tlock-js sobre 1.9.7 | decryptOnG2 acepta un U recodificado como c0 + p y una firma x + p y devuelve la misma file key. bls12-381.js:258 (G1) y :358 (G2) reducen módulo p. |
| Efecto en Go | U no canónico, identidad, fuera de curva, fuera de subgrupo o cuerpo de 127 o 129 bytes: ERR_INTEGRITY en el paso 11. Firma no canónica, identidad, negada o de otra ronda: ERR_RELEASE_INVALID en el paso 10. Firma y U malos a la vez: gana el paso 10. Con el cliente drand incorporado, una firma mala servida por el relay es ERR_RELEASE_UNAVAILABLE en el paso 9. |
checkCompressedPoint como puerta |
Exigiendo 'point', bloquea todas las variantes anteriores, incluida la identidad canónica, y coincide con Go en las 49 451 entradas de los corpus. 11,4 ms por punto G2 y 6,5 ms por G1 en Node 24. |
| Un override a noble 2.4.0 no carga | npm install termina con exit 0 y sin avisos. Después: ERR_PACKAGE_PATH_NOT_EXPORTED (2.x es ESM y solo exporta subrutas .js), ProjectivePoint y toRawBytes no existen, fromHex solo acepta string, sha256.update("IBE-H2") lanza con hashes 2.x. Con 12 a 18 líneas cambiadas el núcleo corre e interopera en ambos sentidos con 1.9.7. |
| Módulo mínimo sobre noble 2.4.0 | 44 líneas de código (64 con comentarios), solo descifrado Quicknet. Pasa el tsconfig estricto de App. Descifra los cinco fixtures con las mismas file keys que tlock-js y que tlock.TimeUnlock en Go; el MAC de la cabecera age verifica; no carga nada de tlock-js. Rechaza el U no canónico en fromBytes como Go. |
| Serialización de GT | Fp12.toBytes de noble (igual en 1.9.7 y 2.4.0) escribe c0 primero en cada nivel. kilic, kyber y fp12ToBytes de tlock-js escriben los doce limbs de 48 bytes en orden inverso. Con el orden de noble la clave sale mal (control negativo). |
| Verificación de releases sin drand-client | shortSignatures.verify(sig, shortSignatures.hash(sha256(uint64be(ronda)), DST), pk) de noble 2.4.0 da true para la ronda 1000 y false para la 999 con la firma real. El DST por defecto de G1 es el de Quicknet. Ninguna versión de drand-client ha estado en noble 2.x; su verificación rfc9380 aceptó tres codificaciones alternativas de la firma real, cada una con un randomness distinto. |
| tlock-js 0.9.0 | gitHead 17d817e (18-03-2024) es el commit que pasó a @noble/curves; el código auditado en 2022-2023 corría sobre @noble/bls12-381; fp.ts es nuevo en 0.9.0 y cerca del 40 % de ibe.ts cambió después. Sin tags ni releases, master sin cambios desde entonces. Cinco dependencias de ejecución; instalación limpia de 33 paquetes y 10,6 MB. npm audit sin avisos. Su guarda de longitud de mensaje solo salta a partir de 512 bytes. |
| Spec v0.8.2 | No nombra U, V ni W, ni sus tamaños, ni la canonicidad de las codificaciones. §12.1 pide "la codificación comprimida de BLS12-381 que usa drand", subgrupo y no infinito, sin x < p. El paso 11 de §63 solo dice que un cuerpo que no es un ciphertext tlock del scheme da ERR_INTEGRITY. La regla de facto es el decodificador kilic. |
| Librería TypeScript hoy | src/lib/dkc ejecuta los pasos 1 a 8 e inspect; age.ts parsea cabeceras age y checkTimeStanzas aplica cardinalidad, tipo, argumentos, ronda y chain hash con los códigos de Go; bls12381.ts valida puntos comprimidos; no hay writer de cápsulas. @noble/curves 2.4.0 es dependencia de desarrollo y un test prohíbe importarla fuera de tests. |
| Repositorio | main en d5e3236, en verde: la alineación con datekeys-go está fusionada y testdata sincronizado a 692cf87 (rama v0.8.2). La rama wip/align-3820066 ya no existe. |
age-encryption 0.3.1 |
Declara @noble/curves ^2.0.1 y @noble/hashes ^2.0.1, que resuelven a la 2.4.0 de App sin override. Pero arrastra @noble/post-quantum 0.5.4, que declara @noble/curves ~2.0.0 y @noble/hashes ~2.0.0: habrá una segunda copia de noble 2.0.x anidada bajo post-quantum, que la usa para ML-KEM y X25519, no para BLS. Admite la Streams API para cifrar y descifrar. |
2. Decisiones
Confirmadas por el autor el 26-09-2026 (paso 0 de la sección 10).
- Núcleo IBE propio en
src/lib/dkc/ibe.ts, derivado detlock-js/crypto/ibe.ts(commit17d817e, licencia Apache-2.0 OR MIT, con aviso de procedencia) y con la semántica dedrand/kyber encrypt/ibe. Sobre@noble/curves2.3.0 o posterior. Se descarta parcheartlock-jsy se descarta una puerta alrededor detlock-jssobre noble 1.9.7, que dejaría dos versiones mayores de noble en el bundle. - Puerta de canonicidad: toda firma, U y clave pública pasa por
checkCompressedPointy solo sigue si el resultado es exactamente'point'. Aunque noble 2.3.0 y posteriores rechazan lo mismo, la puerta fija la precedencia y el código de error, y trata la identidad canónica antes del pairing, donde noble la acepta y falla con otro mensaje. - Solo Quicknet (
bls-unchained-g1-rfc9380: clave pública en G2, firmas en G1, U en G2). Los otros dos schemes de drand quedan fuera de esta fase; se anota en la sección 12. - Releases sin red en esta fase: la obtención (paso 9) queda fuera del SDK. El SDK recibe el release y lo verifica localmente (paso 10). La CSP
connect-src 'self'de la página no cambia. El contrato deReleaseSourcese fija ya (sección 5): una fuente verifica cada respuesta, y si no obtiene ningún release verificado el resultado esERR_RELEASE_UNAVAILABLEen el paso 9, como el cliente drand de Go (provider/drand/client.go); un release entregado por quien llama que no verifica esERR_RELEASE_INVALIDen el paso 10. Para el producto: la página habla con un único origen, la Release API propia, que consulta varios relays y verifica en el servidor (§47, §48); el SDK ofrece además la consulta directa a drand como fuente opcional (§49 SHOULD), nunca como opción por defecto de la página. age-encryption0.3.1 para las tres envolturasage, conIdentityyRecipientpropios para el stanzatlock. Confirma la decisión 4 del plan v2. Sin overrides de npm: el rango^2.0.1resuelve a 2.4.0, y la copia 2.0.x que trae@noble/post-quantum(que declara~2.0.0a propósito) se acepta si solo la usa post-quantum. Las guardas de la sección 3 lo comprueban y el paso 2 mide su coste en el bundle.- Cambio de spec: canonicidad de puntos y contenido del cuerpo del stanza tlock (sección 7), como enmienda de v0.8.2, que no está fusionada ni etiquetada. En curso en
datekeys-go, ramav0.8.2, junto con la limpieza del texto de error de kyber. - Apertura en streaming:
PAYLOAD_AGEse descifra con la Streams API deage-encryptionhacia un fichero temporal en OPFS, que solo se muestra o se ofrece para descargar cuandoagetermina sin error (§56). §57 no acotaPAYLOAD_AGE, así que el límite es la cuota de almacenamiento del navegador, que se consulta connavigator.storage.estimate()antes de empezar. - Licencia de
App: Apache-2.0, comodatekeys-go.ibe.tsconserva el aviso de copyright y licencia detlock-js(Apache-2.0 OR MIT).
3. Dependencias de ejecución y guardas
| Paquete | Versión | Estado |
|---|---|---|
age-encryption |
0.3.1 | aprobada (decisión 5) e instalada el 28-09-2026. Arrastra @noble/ciphers 2.4.0, @noble/curves 2, @noble/hashes 2, @noble/post-quantum 0.5.4 (con su propia copia de @noble/curves y @noble/hashes 2.0.1) y @scure/base 2.4.0. |
@noble/curves |
2.4.0 | pasó de desarrollo a ejecución el 28-09-2026. Fijada exacta. |
@noble/hashes |
2.4.0 | dependencia exacta de @noble/curves 2.4.0; declarada explícita el 28-09-2026 por importarse directamente. |
tlock-js, drand-client |
— | no se instalan. |
Guardas, todas en tests que corren en cada ejecución:
- ningún fichero de
src/importatlock-jsnidrand-client; @noble/*solo se importa desdesrc/lib/dkc/ibe.ts,src/lib/dkc/release.tsy los tests. El test actual "only tests import @noble/curves" debls12381.contrast.test.tspasa a una lista blanca con esos dos ficheros;ibe.ts,release.tsy el test de contraste debls12381.tsresuelven@noble/curvesy@noble/hashesa la copia de la raíz, exactamente 2.4.0 (2.3.0 o posterior es el mínimo por la corrección de canonicidad), y ningún fichero desrc/importa una copia anidada;package-lock.jsonno contiene ninguna versión 1.x de@noble/curvesni de@noble/hashes, y cualquier copia 2.x distinta de la raíz está anidada bajo@noble/post-quantum;scripts/check-build.mjscomprueba además que el sitio construido no contienetlock-js,drand-clientni@babel, e informa del tamaño del bundle de la página de apertura.
4. Módulo IBE
src/lib/dkc/ibe.ts, sin dependencias fuera de @noble/curves, @noble/hashes y bls12381.ts.
Semilla. El módulo de descifrado medido el 26-09-2026 (scratchpad\noble2ibe\c\ibe.ts), con sus importaciones cambiadas a especificadores normales (@noble/curves/bls12-381.js, @noble/curves/utils.js, @noble/hashes/sha2.js).
Contenido.
Ciphertext { U, V, W }ydecryptOnG2(signature, ct): longitudes (48, 96,|V| == |W| <= 32), puerta'point'sobre firma y U,G1.Point.fromBytesyG2.Point.fromBytesconassertValidity,pairing(sig, U),sigma = V xor H2(gt),msg = W xor H4(sigma),r = H3(sigma, msg), comprobaciónr·G2 == U.encryptOnG2RFC9380(publicKey, id, msg): puerta'point'sobre la clave,|msg| <= 32(kyber compruebalen(msg) > Hash().Size(); tlock-js tiene aquí un fallo que no se copia),Qid = G1.hashToCurve(id, { DST: 'BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_NUL_' }),gt = pairing(Qid, pk),sigmade|msg|bytes concrypto.getRandomValues,r = H3(sigma, msg),U = r·G2,V = sigma xor H2(gt^r),W = msg xor H4(sigma).Fp12.powparagt^r.H2(gt) = SHA-256("IBE-H2" ‖ GT)[:len]con GT serializado en el orden de kilic: para[gt.c1, gt.c0], para[a.c2, a.c1, a.c0],Fp.toBytes(b.c1) ‖ Fp.toBytes(b.c0). NuncaFp12.toBytesde noble.H3(sigma, msg):base = SHA-256("IBE-H3" ‖ sigma ‖ msg); parai = 1 … 65534,d = SHA-256(uint16le(i) ‖ base),d[0] >>= 1, se acepta el primerdbig-endian menor queFr.ORDER. Si el bucle termina, error.H4(sigma) = SHA-256("IBE-H4" ‖ sigma)[:len].roundIdentity(round) = SHA-256(uint64be(round)).- Errores: una clase
IbeErrorcon un motivo fijo (length,encoding,identity,proof). Ningún mensaje de error incluyesigma,msg,rni bytes de entrada. Es la lección del texto de error de kyber, quedatekeys-gocopiaba a sus diagnósticos (se corrige en la misma rama que la enmienda de la sección 7). sigmay la file key se borran (fill(0)) en cuanto dejan de usarse, en todos los caminos, como hace la librería conaccess_materialeI_PAYLOAD.- Para tests, una variante interna de
encryptOnG2RFC9380recibesigmaen vez de generarlo, de modo que el cifrado se compara byte a byte con Go. No se exporta desdeindex.ts.
Vectores de Go. La referencia de los tests IBE es la librería Go, no los tests de tlock-js, cuyo ibe.ts cambió en un 40 % tras la auditoría. scripts/ibe-go-vectors.go, como scripts/bls12381-go-verdicts.go, genera con kyber y tlock: los bytes de GT de e(G1, G2) y de su cuadrado, H2, H3 y H4 sobre entradas fijas, la file key de cada stanza de los fixtures con la firma de su sidecar y un cifrado con sigma fijo. El resultado se congela en src/lib/dkc/testing/. El vector de GT de tlock-js (cb87319f24560b5231579a09ad79f12e) coincide con el de kyber (comprobado el 26-09-2026) y se conserva como contraste.
Serialización del stanza. Cuerpo U ‖ V ‖ W de 128 bytes para una file key de 16; argumentos [decimal canónico de la ronda, chain hash en hexadecimal minúsculo]. La lectura ya existe en age.ts; la escritura se añade en age.ts junto a ella.
5. Verificación de releases
src/lib/dkc/release.ts, verifyRelease(profile, round, release), en el mismo orden que provider.Verify de Go (provider/provider.go:53-76), que decodifica la clave antes de mirar la firma:
- ronda fuera del rango del perfil →
ERR_DATEKEY_INVALID; - ronda del release distinta de la ronda de la condición →
ERR_ROUND_MISMATCH; - firma que no mide 48 bytes →
ERR_RELEASE_INVALID; - clave del perfil pinneado, ya validada en los pasos 1 a 8; si no decodifica →
ERR_UNKNOWN_PROFILE; checkCompressedPoint('G1', firma) !== 'point'→ERR_RELEASE_INVALID;shortSignatures.verify(firma, shortSignatures.hash(sha256(uint64be(ronda)), DST_QUICKNET), clave)distinto detrue→ERR_RELEASE_INVALID. Una excepción de noble en este punto también esERR_RELEASE_INVALID.
La interfaz ReleaseSource del SDK solo tiene una implementación estática en esta fase: el release embebido en el sidecar del fixture o suministrado por la aplicación. Su contrato se fija ya para las fuentes con red de fases posteriores: una fuente llama a verifyRelease con cada respuesta y, si ninguna verifica, falla con ERR_RELEASE_UNAVAILABLE en el paso 9; solo un release entregado directamente por quien llama da ERR_RELEASE_INVALID en el paso 10. Es el comportamiento de provider/drand/client.go en Go.
Además, desde la corrección 6 de §76, cualquier fallo de una fuente se informa en el paso 9 con ERR_RELEASE_UNAVAILABLE y ningún otro código. Si el error de la fuente lleva otro código normativo, o ninguno, se conserva solo su texto. Si el contexto termina, se sigue pudiendo detectar. Así lo hacen capsule.Open (sourceFailure) y provider/drand.Client en Go 9ac9cd9, y open.ts debe hacer lo mismo.
6. Apertura y cifrado con age-encryption
src/lib/dkc/open.ts reproduce los pasos 9 a 18 de capsule/open.go, sobre los pasos 1 a 8 que ya ejecuta inspect:
- paso 9, release por
ReleaseSource; paso 10,verifyRelease; - paso 11,
OUTER_TIME_AGEconDecrypterdeage-encryptiony unaIdentitypropia cuyounwrapFileKey(stanzas)ejecuta, en este orden,checkTimeStanzas(cardinalidad de §63, comoagewrap.TimeIdentity.Unwrapen Go),verifyReleaseotra vez, la comprobación de 128 bytes del cuerpo,decryptOnG2y la longitud 16 de la file key. Cada fallo de cuerpo o de descifrado esERR_INTEGRITY; el MAC de la cabecera lo compruebaage-encryption; - pasos 12 a 18 como en Go: estructura de política,
INNER_ACCESS_AGEcon las identidades del llamante,CONTROL_CBORcanónico,header_bindingsobre los bytes exactos, identidad de payload yPAYLOAD_AGE; PAYLOAD_AGEse descifra en streaming (decisión 7):File.slice(offset).stream()entra enDecrypter.decrypt, la salida se escribe en un fichero temporal de OPFS y el plaintext solo se entrega cuando el stream termina sin error; si falla la autenticación en cualquier chunk, el fichero temporal se borra y no se muestra nada (§56). Antes de empezar se compara el tamaño dePAYLOAD_AGEcon la cuota libre.
Cifrado: Encrypter con un Recipient propio cuyo wrapFileKey(fileKey) llama a encryptOnG2RFC9380(clave del perfil, roundIdentity(ronda), fileKey) y devuelve el stanza tlock. La construcción completa de un .dkc (prelude, cabecera, control sellado) requiere el writer TypeScript, que es fase 3; en esta fase el cifrado se prueba a nivel de stanza y de fichero age (sección 8).
Antes de escribir código se comprueba en el paquete instalado la forma exacta de Identity, Recipient y Stanza de age-encryption 0.3.1 y se anota en este plan.
7. Cambios en la especificación y en datekeys-go
Todo en un único cambio normativo, con vectores congelados, según la política de §76 (caso de "segunda implementación independiente" y "prueba de interoperabilidad").
- Definición de codificación canónica de punto, en §12 o en un apartado nuevo: la serialización comprimida de BLS12-381 que produce drand, 48 bytes en G1 y 96 en G2; bit de compresión a 1; bit de infinito solo en el punto en el infinito, con el resto de bytes a cero; coordenadas big-endian menores que p, y en G2 c1 seguido de c0, ambas menores que p; el punto está en el subgrupo de orden primo. Un decodificador MUST rechazar cualquier otra cadena de bytes, en particular x + p y una identidad con carga o con el bit de compresión ausente.
- §12.1, punto 2:
public_keyes una codificación canónica según esa definición, distinta del infinito. Código sin cambio:ERR_UNKNOWN_PROFILE. - §63, paso 10: la firma es una codificación canónica de un punto del grupo de firmas del scheme, distinta del infinito, y verifica como firma de la ronda; en otro caso
ERR_RELEASE_INVALID. Se mantiene la precedencia deERR_ROUND_MISMATCH. - §63, paso 11: el cuerpo del stanza
tlockesU ‖ V ‖ W, con|U|igual al tamaño de punto del grupo de claves del scheme (96 para Quicknet),|V| = |W| = 16; U es una codificación canónica de un punto de ese grupo distinta del infinito; el descifrado IBE-CCA compruebar·G == U. Cualquier fallo, incluida una longitud distinta de 128, esERR_INTEGRITY. Se cita drand/tlock para H2, H3 y H4. - §64, mutaciones nuevas con código y paso, verificados en Go el 26-09-2026: U con c0 + p, U identidad canónica, U con flag de infinito y carga, cuerpo de 127 y 129 bytes →
ERR_INTEGRITY, paso 11; firma x + p (requiere un release de prueba cuya x lo permita, o un vector sintético documentado), firma identidad, firma con flag de infinito y carga, firma negada →ERR_RELEASE_INVALID, paso 10; firma mala y U malo a la vez →ERR_RELEASE_INVALID, paso 10. datekeys-go: las mismas mutaciones eninternal/testkit/mutations.go, regeneración detestdata/vectors/mutations.json,traceability.mdyCHANGELOG.md. El comportamiento del código no cambia. Aparte, la tarea ya propuesta de no copiar el texto de error de kyber a los errores y aInspection.Checks[].Detail.App:testdata:syncal commit resultante y reproducción en TypeScript de código y paso de cada mutación nueva.
8. Tests
ibe.test.ts. Los vectores de Go de la sección 4 (GT, H2, H3, H4, las file keys de los fixtures y el cifrado consigmafijo), y como contraste el de tlock-js (gtToHashde 16 bytescb87319f24560b5231579a09ad79f12e, igual al de kyber). Los cinco fixtures: la file key obtenida con la firma del sidecar abre la cabeceraage. Control negativo: serializar GT conFp12.toBytesde noble da otra clave. U con c0 + p, U identidad, U negado y firma de otra ronda: rechazados con el motivo esperado. Ida y vueltaencryptOnG2RFC9380→decryptOnG2con una clave sintética. Cobertura del 100 %.release.test.ts. Ronda 1000 con la firma real: válida; 999: inválida; codificaciones alternativas de la misma firma (sin comprimir, x + p, flag de signo alterado):ERR_RELEASE_INVALID; longitud 47 y 96; identidad. Cobertura del 100 %.open.test.ts. Los cinco fixtures se abren con el release del sidecar y el SHA-256 del plaintext coincide con el sidecar; los.dkkdetime_and_keyabren con sus identidades. El corpus de mutaciones exportado, incluidas las nuevas de la sección 7, reproduce código y paso.- Interoperabilidad TS → Go a nivel IBE.
scripts/ibe-go-roundtrip.go, comoscripts/bls12381-go-verdicts.go: TypeScript cifra 16 bytes para la ronda 1000 con la clave de Quicknet y escribeU ‖ V ‖ W; Go los abre contlock.BytesToCiphertextytlock.TimeUnlockcon la firma real y compara. Se ejecuta a mano y su resultado se congela como vector. - Interoperabilidad TS → Go a nivel de fichero
age. Un ficheroagecon un stanzatlockescrito porage-encryptioncon elRecipientpropio, abierto poragewrapde Go con la firma real. Se ejecuta a mano y se congela. - Guardas de la sección 3.
- Rendimiento, informativo: tiempo de
decryptOnG2en frío y en caliente en Node y en el navegador de la página.
9. Página
La ruta /inspect gana una acción "abrir": con un fixture o un .dkc arrastrado, un release pegado o el del sidecar, y las identidades .dkk cuando la política las exige. Muestra el resultado de cada paso 9 a 18 como hoy muestra 1 a 8, y el SHA-256 del plaintext; para los fixtures, además el plaintext. Sin red: la CSP no cambia y check-build lo comprueba. El plaintext nunca se guarda ni se envía.
10. Orden de trabajo y criterios de aceptación
| Paso | Contenido | Hecho cuando |
|---|---|---|
| 0 | Confirmar las decisiones de la sección 2 y aprobar las dependencias de la sección 3 | hecho el 26-09-2026 |
| 1 | Precondición: main verde con testdata sincronizado al último commit de datekeys-go |
cumplida en d5e3236 (692cf87) y de nuevo en 71ab8fb, con testdata en 9ac9cd9 (spec-v0.8.2) |
| 2 | Dependencias y guardas | instaladas con versiones exactas; npm audit --omit=dev sin avisos; guardas en verde; en el lockfile, un solo noble 2.4.0 en la raíz, la copia 2.0.1 solo bajo @noble/post-quantum y ningún 1.x. Hecho el 28-09-2026: el README de App recoge las guardas, la medida del bundle y el resultado de npm audit |
| 3 | ibe.ts de descifrado desde la semilla, roundIdentity, escritura del stanza en age.ts, ibe.test.ts sin la parte de cifrado |
los cinco fixtures dan la file key correcta; U no canónico e identidad rechazados; cobertura 100 %. Hecho el 28-09-2026: vectores de scripts/ibe-go-vectors.go en src/lib/dkc/testing/ibe-vectors.json; ibe.ts al 100 %, fijado como umbral. ibe.ts también pasa el cuerpo U ‖ V ‖ W a bytes; los argumentos del stanza y su paso al Stanza de age-encryption, que guarda el tipo en args[0], van al paso 7, con el Recipient que los usa |
| 4 | release.ts y release.test.ts |
ronda real válida, alias rechazados. Hecho el 28-09-2026: - verifyRelease con el orden y los textos de provider.Verify;- ReleaseSource, con el contrato de la sección 5, y suppliedRelease;- los casos de TestVerifyRejects de Go y los 7 del corpus de mutaciones que fallan en el paso 10;- las firmas publicadas de las rondas 1000, 1001, 2000 y 1004, esta última obtenida restando p a la codificación x + p del corpus; - cobertura del 100 %, fijada como umbral |
| 5 | open.ts con la Identity propia, pasos 9 a 18, open.test.ts |
los cinco fixtures se abren y el plaintext coincide con el sidecar; el corpus de mutaciones existente reproduce código y paso |
| 6 | Spec, mutaciones en Go, testdata:sync, reproducción en TypeScript (sección 7) |
texto aprobado; vectores congelados en ambos repositorios; commits en Gitea |
| 7 | encryptOnG2RFC9380, Recipient propio, ida y vuelta, interoperabilidad TS → Go a nivel IBE y de fichero age (sección 8, puntos 4 y 5) |
Go abre lo que TypeScript cifra; vectores congelados |
| 8 | Página (sección 9) | un fixture time_and_key se abre en el navegador sin red; check-build en verde; tamaño del bundle anotado en el README |
Cada paso termina con npm run verify en verde y un commit en Gitea. El paso 6 puede ir en paralelo con el 4 y el 5.
11. Riesgos
- API de
age-encryption0.3.1 paraIdentity,RecipientyStanza. Mitigación: comprobarla en el paquete instalado antes del paso 5 y fijar la versión exacta. - Cambios de API de noble 2.x. Ya ocurrió entre 1.x y 2.x. Mitigación: versión exacta, guardas y el test del vector de GT, que detecta cualquier cambio de orden o de
Fp.toBytes. - Orden de bytes de GT. Es el único punto donde una implementación puede coincidir en todo lo demás y fallar aquí. Mitigación: el vector de tlock-js y el control negativo.
- Doble noble en el bundle.
@noble/post-quantum0.5.4 ya trae su propia copia 2.0.x; si alguna dependencia futura arrastra 1.x, sería peor. Mitigación: las guardas de la sección 3 (ningún 1.x, copias 2.x distintas solo anidadas bajo post-quantum, el código BLS en la 2.4.0 exacta) y la medida del bundle en el paso 2. - Diagnósticos con material interno. Mitigación: la regla de errores de la sección 4 y un test que busca en los mensajes de error los bytes de
sigma,msgyr. H3devuelve 0, con probabilidad 2⁻²⁵⁵:multiply(0)lanzaRangeError. Mitigación: tratar cualquier excepción del cálculo comoproofy cubrirlo con un test que inyecter = 0.- Rendimiento en el navegador. Un pairing y una multiplicación escalar en G2 rondan los 40 a 300 ms en Node; la puerta añade 11 ms por punto. Mitigación: medir en el paso 8 y, si hace falta, descifrar en un worker.
- Rama
wip/align-3820066. Si no se fusiona antes, el paso 3 partiría de un árbol distinto del que verifican los tests actuales. Mitigación: el paso 1. - Aprobación de la spec. El paso 6 depende del autor; los pasos 3 a 5 no.
12. Fuera de alcance y decisiones aplazadas
- Writer completo de
.dkcy.dkken TypeScript y la prueba TS → Go a nivel de cápsula: fase 3. - Los schemes
pedersen-bls-unchainedybls-unchained-on-g1: sin uso previsto; si se añaden, el módulo crece conencryptOnG1ydecryptOnG1y la puerta cambia de grupo. - Obtención de releases por red desde el SDK: fuera de esta fase; el servidor y la CLI Go siguen siendo la vía.
- Reutilización de la implementación
agede tlock-js: descartada;age-encryptiones la implementación oficial.