You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
DateKeys-App/README.md

31 KiB

DateKeys App

Implementación en TypeScript del protocolo DateKeys v0.8.2 y página de prueba en el navegador. Sustituye al prototipo, archivado en ../AppOld (API Quicknet en Go, CLI tlock y cliente Svelte, commit 4d2b0a1).

La implementación de referencia es la librería Go g.activething.com/go/DateKeys, en ../datekeys-go. El plan de trabajo está en docs/PLAN_codec_cbor_y_pagina_svelte.md.

Contenido

Parte Ubicación Paso del plan Estado
Codec CBOR del subconjunto, parsers de schema, DateKey, inspect src/lib/dkc/ 4 hecho
Página inspector, sin red SvelteKit estático: src/routes/, src/lib/inspector/, src/lib/components/ 5 hecho
Cifrado y descifrado en el navegador fase 2: docs/PLAN_fase2_ibe_noble2.md 6 en curso: dependencias y guardas hechas (paso 2 de la fase)

Versiones

Hay tres números de versión, cada uno con su significado, como en la referencia Go:

Versión Dónde Cambia cuando
Formato Dentro de los objetos: la versión de framing de DKC1 y DKK1 y la de schema de la clave 1, hoy todas 1 Cambia el formato. Un lector rechaza una versión que no conoce (§70)
Especificación SPEC_VERSION de src/lib/dkc/version.ts, hoy 0.8.2: la del tag spec-v0.8.2 de datekeys-go Cambia el texto normativo
Librería VERSION de src/lib/dkc/version.ts, igual al campo version de package.json Cambia la API o el comportamiento. Versionado semántico, sin promesa de estabilidad antes de 1.0.0

version.test.ts comprueba que VERSION coincide con package.json y con su lockfile, y que SPEC_VERSION es la versión que nombran los vectores y fixtures compartidos; vectors.test.ts exige esa versión a cada fichero. El pie de la página muestra las dos.

La versión actual es 0.1.0-dev. Será 0.1.0 cuando la fase 2 añada la apertura de cápsulas. Cubre:

  • la especificación 0.8.2, con versiones de formato 1;
  • solo el scheme de Quicknet (bls-unchained-g1-rfc9380): un perfil de otro scheme se inspecciona, pero su release no se verifica (decisión 3 del plan de la fase 2);
  • la inspección de los pasos 1 a 8, el IBE de tlock (ibe.ts) y la verificación de releases (release.ts). La apertura (pasos 9 a 18) llega en la fase 2 y el cifrado en la fase 3;
  • todos los vectores y fixtures compartidos de datekeys-go en 9ac9cd9 (spec-v0.8.2);
  • navegadores con Web Crypto y Node 20 o posterior.

CHANGELOG.md recoge los cambios de cada versión.

src/lib/dkc

Lo que hay hoy (pasos 1 a 8) no importa ninguna dependencia. Funciona en navegadores y en Node 20+: solo usa Uint8Array, DataView, TextEncoder/TextDecoder, BigInt y crypto.subtle (SHA-256). La fase 2 añade las dependencias de ejecución de su sección, y noble solo lo importarán ibe.ts y release.ts.

crypto.subtle solo existe en contextos seguros: https, o http en localhost. La página del paso 5 servida por http desde una IP de la red local (por ejemplo vite --host para probar en un móvil) no lo tiene, y inspect rechaza entonces con un Error que lo dice (SHA-256 needs Web Crypto (crypto.subtle), …) en vez de dar un veredicto. El registro por defecto no memoriza ese fallo: la siguiente llamada lo vuelve a intentar.

Fichero Contenido Equivale en Go (f6f2e9f)
errors.ts DateKeysError con el código normativo de §69 (ERR_*); mensajes con la forma contexto: CÓDIGO de Go errors.go
bytes.ts Hex, UTF-8 estricto, goQuote (el %q de Go, con la tabla de strconv.IsPrint de Go 1.26 fijada en el código) y sha256 (Web Crypto) strconv, unicode/utf8
cbor.ts El codec propio de la referencia, con las mismas lecturas, las mismas comprobaciones en el mismo orden y los mismos textos de error: Encoder con error persistente; Decoder, cursor estricto (map/key/endMap, array, uint/uint64, bstr, text, done); unmarshal (decodifica, reencodifica y compara; onReject para borrar secretos); peek/checkSchema (capa 2 de §69.1: tipo y versión antes que nada) y walk (lector genérico acotado en profundidad y longitud) codec
schema.ts Lo que comparten los decodificadores de PUBLIC_HEADER, CONTROL_CBOR y el cuerpo de la .dkk: key N: en los errores, claves obligatorias y arrays de extensiones capsule/framing.go, accesskey
extension.ts Arrays de extensiones, leídos con su objeto (capa 3 de §69.1). Registros con ubicación opcional (registeredIn): una extensión conocida fuera de los objetos y arrays de su registro cuenta allí como desconocida (§54, §72), como extension.Placement en Go. Reglas del array: de 1 a 64, en orden estrictamente ascendente de los bytes UTF-8 de extension_id (nunca por unidades UTF-16), extension_version hasta 2³² − 1, data ausente o bstr no vacío, ningún id en los dos arrays; registros, críticas y no críticas (capa 4) extension
profile.ts Provider Profile: CBOR exacto, profile_hash, reglas 1 a 4 de §12.1 en su orden (el límite de period de §74 en la capa del esquema; alfabetos, clave pública del grupo del scheme y fórmula de chain_hash), registro pinneado; Quicknet fijado por su CBOR y su hash profile
bls12381.ts Pertenencia de claves públicas BLS12-381 comprimidas (G1 y G2) al subgrupo, como FromCompressed de kilic kyber-bls12381
ibe.ts IBE-CCA de tlock sobre G2 para Quicknet (§63 paso 11): decryptOnG2, con la puerta de codificación canónica de bls12381.ts sobre la firma y U; H2 sobre GT serializado en el orden de kilic (nunca Fp12.toBytes de noble), H3 y H4; roundIdentity; el cuerpo U ‖ V ‖ W de 128 bytes del stanza. Errores IbeError con motivo (length, encoding, identity, proof) y texto fijos, sin ningún valor del cálculo; borra sigma y los hashes derivados. Sobre @noble/curves 2.4.0; lleva el aviso MIT de tlock-js, cuya estructura sigue. Aún no lo reexporta index.ts: lo usará la apertura (paso 5 de la fase 2) encrypt/ibe de drand/kyber (DecryptCCAonG2), tlock.BytesToCiphertext y TimeUnlock
release.ts Verificación local del release (§17, §51, §63 paso 10), en el orden y con los textos de provider.Verify:
1. el rango de la ronda (ERR_DATEKEY_INVALID);
2. la ronda del release antes que la firma (ERR_ROUND_MISMATCH);
3. la longitud de la firma;
4. la clave pinneada (ERR_UNKNOWN_PROFILE);
5. la firma: codificación canónica de un punto de G1 que no sea el infinito, y firma BLS válida de la ronda sobre @noble/curves 2.4.0, con el DST de RFC 9380 para G1 (ERR_RELEASE_INVALID).
Nada de noble se copia a los errores. Solo verifica el scheme de Quicknet: un perfil de otro scheme falla con ERR_UNKNOWN_PROFILE tras las comprobaciones de ronda, donde la referencia sí lo verificaría (decisión 3 del plan de la fase 2). También define ReleaseSource, con su contrato de fuentes de red y de la corrección 6, y suppliedRelease, el release que entrega quien llama. Aún no lo reexporta index.ts
provider (Verify, ReleaseSource)
datekey.ts dk1_ canónico con las reglas de lectura de §19 (CR, LF y todo carácter fuera del alfabeto fallan el paso 1; números JSON por su valor decimal exacto), ronda desde una fecha con precisión de nanosegundos y cota de 9999-12-31T23:59:59Z (§15), parser RFC 3339 equivalente a time.Parse(time.RFC3339Nano, …) datekey
header.ts, control.ts, accesskey.ts PUBLIC_HEADER, CONTROL_CBOR y .dkk (cuerpo y trama), decodificar y codificar, con las capas de §69.1 capsule, accesskey
framing.ts Prelude DKC1 (16 bytes) y DKK1 (12 bytes) en el orden de §23 y §40, longitudes de 1 byte hasta los límites de §57, y troceo de secciones capsule/framing.go
age.ts Parser estricto de la cabecera age v1 (§28.1) sobre los ficheros binarios, con los textos de error de age; reglas de stanzas; MAX_AGE_HEADER_LEN (2 MiB), el límite que usa la página para leer solo el prefijo de un .dkc grande agewrap, filippo.io/age/internal/format
inspect.ts Pasos 1 a 8 de §63 y la vista JSON de datekeys inspect -json (inspectView, inspectJSON) capsule/inspect.go, internal/inspectview
index.ts Reexporta todo, salvo ibe.ts y release.ts por ahora
testing/ Solo para tests: lectura de testdata/ y de sus formatos (vectors.ts: ediciones, vectores), constructores de CBOR en hex, cirugía de cápsulas

Los tests (*.test.ts) están junto a cada fichero.

Equivalencia con la referencia Go

El comportamiento se contrastó con la librería Go en 3820066 (692cf87 solo cambia la regla de UTF-8 de dk1_ descrita abajo) mediante un oráculo diferencial fuera del repositorio: 483 527 entradas de tres semillas. Son mutaciones de los cinco .dkc oficiales de todas las clases (bits, bytes, truncados, inserciones, borrados, longitudes y campos del prelude, cabeceras reescritas con cambios de CBOR, DateKeys, arrays de extensiones con y sin registro de extensiones, cabeceras age de SEALED_CONTROL y de PAYLOAD_AGE, argumentos del stanza tlock, rondas, secciones cambiadas de sitio y combinaciones de varios defectos); CBOR de cada esquema (PUBLIC_HEADER, CONTROL_CBOR, cuerpo y fichero .dkk, Provider Profile, arrays de extensiones); walk, peek y checkSchema; cabeceras age, preludes, cadenas dk1_, rondas y fechas. En todas coinciden el veredicto, el código y el paso, y también el texto del error, el valor decodificado y la salida entera de datekeys inspect -json, byte a byte. Un segundo diferencial con otro generador, de 407 196 entradas, se repitió contra f6f2e9f (la enmienda de canonicidad de puntos) con el mismo resultado, textos incluidos.

Como la referencia desde f6f2e9f, los errores no copian el texto de una librería: una cabecera age que no se puede leer da siempre agewrap: not an age v1 header: malformed, truncated or beyond the parser limits (ERR_INTEGRITY). El motivo del parser, con las palabras de age, queda en cause del error, fuera del mensaje, y los tests lo comparan con los textos de age para comprobar que se rechaza por la misma razón.

Precedencia de errores (§69.1): primero la trama; después el tipo y la versión de esquema (checkSchema); después el perfil CBOR y el CDDL, con los límites de implementación de §74, en una sola decodificación (unmarshal con el decodificador del esquema, que ya comprueba tipos, tamaños, rangos, access_policy, extension_version, el máximo de 64 extensiones y su orden); y solo entonces los campos con código propio, en orden de clave: DateKey, perfil fijado y extensiones críticas en PUBLIC_HEADER; access_type y access_material en la .dkk; los campos y la autocomprobación de chain_hash en el Provider Profile. Entre pasos decide el orden de §63. Un PUBLIC_HEADER que rompe a la vez el CDDL y la DateKey da ERR_NON_CANONICAL_CBOR, como en la referencia de 3820066; la de afb44a3 leía access_policy y extension_version como uint64 y los acotaba después de la DateKey, y la vía wideUint que lo imitaba ya no existe.

Enteros: el decodificador lee cada entero hasta 2⁶⁴ − 1 (un bigint por encima de 2⁵³ − 1) y lo compara con el máximo de su campo, con el texto de la referencia (unsigned integer 9007199254740992 above 9007199254740991). Ningún campo de un objeto del protocolo admite más de 2⁵³ − 1; walk admite cualquier uint64, como codec.Walk.

peek lee la capa 2 de §69.1 como codec.Peek: una cabecera de mapa de longitud definida que anuncia al menos dos entradas y no más de la mitad de los bytes que la siguen, la clave 0 con un texto de hasta 64 bytes y la clave 1 con un entero de hasta 2⁵³ − 1, todo en su forma más corta. No lee nada más: una versión 2 es ERR_UNSUPPORTED_VERSION sea lo que sea lo que venga detrás, y cualquier otra cosa en esas posiciones, ERR_NON_CANONICAL_CBOR.

UTF-8 inválido en el JSON de un dk1_ hace fallar el paso 2 de §19 (ERR_DATEKEY_INVALID) en las dos implementaciones, también en un miembro que un nombre repetido reemplaza después. Hasta 692cf87 la referencia Go lo sustituía por U+FFFD (encoding/json) y en ese caso daba ERR_DATEKEY_NON_CANONICAL; el diferencial de esta implementación lo encontró y la referencia se corrigió para seguir al spec. datekey.test.ts y el vector invalid UTF-8 in a member a repeated name overwrites de dk1.json lo fijan.

goQuote escribe como Go 1.26 (Unicode 15.0.0) los textos entre comillas de los detalles de fallo: usa una tabla de strconv.IsPrint generada con Go y no \p{…}, porque cada motor JavaScript trae su propia versión de Unicode (V8 en Node 24 ya imprime runas de Unicode 16 que Go escapa, como U+31E4). bytes.test.ts fija el SHA-256 del conjunto completo de runas imprimibles. Si el toolchain de la referencia cambia de versión de Unicode, se regenera con go run scripts/go-isprint-table.go y se actualizan la tabla y el hash.

Secretos: access_material de un .dkk e I_PAYLOAD de CONTROL_CBOR se borran en todos los caminos, también cuando la decodificación falla a medias o unmarshal rechaza el valor, como el clear diferido de Go.

Página inspector

Sitio SvelteKit estático (@sveltejs/adapter-static, strict): las dos páginas se prerenderizan a HTML y no hay código de servidor.

Ruta Contenido
/ Qué es el inspector y qué garantiza; enlaza con /inspect
/inspect El inspector

/inspect carga un .dkc con el selector de ficheros, soltándolo en cualquier parte de la página o desde la lista de fixtures oficiales, y ejecuta inspect (pasos 1 a 8 de §63) sin registro de extensiones, como datekeys inspect. Muestra:

  • cada paso con su número, su nombre de la CLI, superado o el código normativo, y el detalle (los caracteres invisibles o de control se escriben como \uXXXX);
  • el veredicto, capsule_id, la DateKey compacta y decodificada (red y ronda), el perfil fijado, la fecha de apertura en UTC y en la hora local del navegador, access_policy, los campos del prelude, los argumentos del stanza tlock frente al perfil fijado y el número y tipo de stanzas de OUTER_TIME_AGE y PAYLOAD_AGE;
  • las extensiones de PUBLIC_HEADER según el contrato del plan §8: id (entre comillas y escapado si tiene caracteres no imprimibles), versión, crítica o no, conocida o no, longitud y hex (plegado si pasa de 64 bytes); texto si los bytes son UTF-8 imprimible; vista CBOR con walk si son un ítem del perfil de §58, marcada "informativo, no validado por el protocolo"; y el aviso de que son públicas, de que nada las vincula al resto de la cápsula antes del paso 15 y de que ni entonces prueban autoría (§55.1);
  • Copiar JSON, que copia exactamente la salida de datekeys inspect -json (cliJSON: el json.Encoder de Go con sangría de dos espacios, <, >, &, U+2028 y U+2029 escapados y salto de línea final). file es el nombre del fichero.

No pide ni acepta secretos. Todo el texto leído de la cápsula pasa por interpolación de texto de Svelte (nunca {@html}), y ni las entradas de mapas CBOR ni los identificadores de extensión se usan nunca como claves de objetos o Map de JavaScript.

Fichero Contenido
src/lib/inspector/report.ts buildReport: el modelo de la página a partir de Inspection, sin DOM ni reloj
src/lib/inspector/format.ts Nombres y glosas de pasos, códigos y políticas; texto imprimible y escapado; números y fechas en español; cliJSON
src/lib/inspector/diagnostic.ts Notación de diagnóstico CBOR (RFC 8949 §8) de walk, acotada a 16 384 caracteres
src/lib/inspector/load.ts Lectura por prefijo: de un .dkc grande solo se leen 16 + PUBLIC_HEADER_LEN + SEALED_CONTROL_LEN + 2 MiB + 1 bytes, y solo 16 si los pasos 1 y 2 rechazan el prelude (otro tipo de fichero, un .dkk, longitudes fuera de §57), siempre con el mismo resultado que el fichero entero (lo comprueba load.test.ts)
src/lib/inspector/fixtures.ts Los fixtures oficiales, empaquetados desde testdata/fixtures
src/lib/components/ InspectionReport, StepList, ExtensionList, DataView, Mark
src/routes/ Layout, portada e inspector

Fixtures

fixtures.ts importa con import.meta.glob los .dkc de testdata/fixtures como URL (?url) y, de cada registro JSON, solo el campo description. testdata/ sigue siendo la única fuente: Vite copia cada .dkc como fichero con hash en _app/immutable/assets/ y nunca lo incrusta como data: (assetsInlineLimit: 0), y no se copia nada más. Los .dkk, los textos en claro y los demás campos de los registros (payload_identity, control_cbor…) no llegan al sitio; check-build.mjs lo comprueba. En desarrollo, server.fs.allow deja que Vite sirva testdata/fixtures.

Sin red: la Content-Security-Policy

kit.csp (svelte.config.js, modo hash) pone en cada página prerenderizada, como primer elemento que carga algo, un <meta http-equiv="content-security-policy">:

default-src 'self'; frame-src 'none'; worker-src 'none'; connect-src 'self'; font-src 'self';
img-src 'self'; manifest-src 'self'; object-src 'none'; script-src 'self' 'sha256-…';
style-src 'self'; style-src-attr 'unsafe-hashes' 'sha256-…'; base-uri 'none'; form-action 'none'
  • connect-src 'self': fetch solo llega al propio origen, y solo se usa para los fixtures.
  • script-src: los módulos del sitio y el hash SHA-256 del único script en línea, el arranque de SvelteKit (los nonces no sirven en HTML prerenderizado).
  • style-src 'self': solo hojas de estilo del sitio; sin fuentes web ni CDN, con las fuentes del sistema.
  • style-src-attr: solo el atributo style del anunciador de rutas de SvelteKit, por su hash (ANNOUNCER_STYLE_HASH, válido para @sveltejs/kit 2.70.3; app.css lo oculta también si el navegador bloquea el atributo).

npm run build ejecuta después scripts/check-build.mjs (postbuild; también npm run build:check), que falla si una ruta no tiene su HTML prerenderizado; si una página no tiene exactamente esa política, con la etiqueta antes de cualquier elemento que cargue recursos; si un script en línea no está en script-src o sobra un hash; si style-src-attr no coincide con los atributos style del bundle; si hay estilos en línea, manejadores de eventos en atributos, @import o URL a otro origen; si algún .dkc oficial no está byte a byte; si aparece en el sitio algún secreto de los fixtures (.dkk, textos en claro, identidades, payload_identity, access_material, control_cbor); o si el bundle del cliente contiene tlock-js, drand-client o helpers de Babel, o una copia anidada de un paquete que no sea la de noble bajo @noble/post-quantum. vite.config.ts registra los módulos de cada chunk en .svelte-kit/output/client-modules.json, fuera del sitio. Al terminar informa del JavaScript que carga cada página, en bytes y con gzip, y de los paquetes npm que lleva el bundle.

En un hosting estático basta con servir build/. Las directivas que solo funcionan como cabecera HTTP (frame-ancestors, sandbox, report-to) quedan para el servidor que la aloje. crypto.subtle exige contexto seguro: https, o http en localhost.

Reglas

  • Los fixtures y vectores del Go son la verdad. Este proyecto nunca genera fixtures propios: testdata/ es una copia exacta de un commit de la librería Go.
  • Dependencias de ejecución: solo age, drand, tlock y lo que ellas arrastran; hoy, las de la sección «Dependencias de ejecución». El sitio lleva compilado además el runtime de cliente de Svelte y SvelteKit, el tooling que el plan elige para la página (sección 13).
  • Tooling de desarrollo: solo el de la lista siguiente. Cualquier otra dependencia se propone por escrito y no se instala sin aprobación.

Comandos

npm test            # vitest, todos los tests
npm run coverage    # tests con cobertura v8; falla por debajo de los umbrales
npm run typecheck   # svelte-kit sync y tsc sobre todo y sobre la librería sin tipos de Node
npm run check       # svelte-kit sync y svelte-check (componentes y rutas), falla con avisos
npm run dev         # servidor de desarrollo: http://localhost:5173/inspect
npm run build       # sitio estático en build/ y, después, scripts/check-build.mjs
npm run preview     # sirve build/: http://localhost:4173/inspect
npm run build:check # solo la comprobación del sitio ya construido
npm run verify      # check, typecheck, coverage y build (con su comprobación)

Umbrales de cobertura (vitest.config.ts): cbor.ts, ibe.ts y release.ts al 100 % en líneas, ramas, funciones y sentencias; el conjunto de src/lib/dkc al 95/90/95/95, y el de src/lib/inspector también.

vitest.config.ts es la configuración de los tests; vite.config.ts, la del sitio con el plugin de SvelteKit. Vitest prefiere la primera, así que los tests de src/lib corren sin SvelteKit, y src/lib/inspector importa la librería por rutas relativas, sin el alias $lib. tsconfig.json extiende el que genera svelte-kit sync (por eso typecheck y check lo ejecutan antes, y npm install también, con prepare).

npm run typecheck pasa dos veces: tsconfig.json (todo, con tipos de Node para los tests) y tsconfig.lib.json (solo la librería y sin tipos de Node, para que no se cuele ninguna API que no exista en el navegador).

Vectores y fixtures

  • testdata/fixtures/*.json: cada .dkc pasa los pasos 1 a 8 con los valores registrados (prelude, PUBLIC_HEADER, DateKey, capsule_id, política, unlock_at, extensiones con sus bytes exactos, stanzas, header_binding, CONTROL_CBOR); cada .dkk se decodifica campo a campo y se reencodifica byte a byte. Los fixtures nuevos se recogen solos.

  • testdata/fixtures/*.inspect.json: la vista de inspect de cada .dkc, escrita con inspectJSON como la imprime la CLI (file incluido), es idéntica byte a byte al fichero, también el texto de cada paso.

  • testdata/vectors/dk1.json (con los tres vectores de los refinamientos de §19: LF dentro del Base64, CR y LF después, y la versión 1.0000000000000001), quicknet_rounds.json y profile_quicknet.json: se ejecutan todos.

  • testdata/vectors/cbor.json: cada vector genérico (accept y reject) pasa por walk con los max_depth y max_len del fichero; los enteros aceptados comparan su value (número o, por encima de 2⁵³ − 1, bigint), y los rechazados «above max_len» o «above max_depth» se aceptan sin ese límite. Cada vector de schemas pasa por el decodificador de su esquema (decodeProfile, decodeHeader, decodeControl, decodeAccessKeyBody) con el código exacto, y un objeto aceptado se reescribe a los mismos bytes.

  • testdata/vectors/tlock_ibe.json: el vector de H2 del IBE de tlock (§63 paso 11). Hasta que llegue ibe.ts (fase 2), el test lo recalcula con @noble/curves 2.4.0: los puntos son canónicos para bls12381.ts, el pairing serializado en el orden de kilic es el GT del vector y su H2 coincide; el orden propio de noble (Fp12.toBytes) da otro hash.

  • testdata/vectors/mutations.json: se leen los 65 casos enteros (ediciones sobre un fixture o hex congelado, release, reloj, registro, extensiones, .dkk e identidades). Los 31 de los pasos 1 a 8 pasan por inspect con su registro y sus extensiones, y dan el mismo código y el mismo paso; los 34 de los pasos 9 a 18 (entre ellos las 10 mutaciones de la enmienda de canonicidad de puntos, en los pasos 10 y 11) necesitan open (fase 2) y se saltan uno a uno con ese motivo, y un test fija los dos recuentos. Las .dkk ofrecidas se decodifican.

  • testdata/vectors/inspect_differential.json: las 1 825 mutaciones dan el mismo veredicto, código y paso que Go; los bases se comprueban por su SHA-256.

  • src/lib/dkc/testing/ibe-vectors.json: los valores de referencia de ibe.ts. Los escribe scripts/ibe-go-vectors.go con kyber, tlock y age, las librerías de la referencia Go, y ibe.test.ts los comprueba todos:

    • el GT de e(G1, G2) y de su cuadrado, con H2 de 16 y 32 bytes;
    • H3 y H4 sobre entradas fijas, entre ellas una H3 aceptada en la segunda iteración y otra en la tercera;
    • la identidad de varias rondas;
    • para el stanza tlock de cada fixture oficial, el pairing, sigma, r y la file key. La file key es la que devuelve tlock.TimeUnlock, y con ella age abre el OUTER_TIME_AGE. El test lo repite con age-encryption: el MAC de la cabecera y STREAM verifican, y el contenido es el control_cbor del registro o un INNER_ACCESS_AGE;
    • mensajes de 0, 1, 16 y 32 bytes cifrados por EncryptCCAonG2 de kyber;
    • el veredicto de DecryptCCAonG2 sobre copias editadas del stanza de time_only: U con c0 + p, en el infinito o negado, V o W alterados, la firma de otra ronda, negada o en el infinito, y longitudes erróneas.

    H2, H3 y H4 no son públicas en kyber: el script las reescribe con sus etiquetas y las comprueba en cada fixture contra la file key de tlock y contra U = r·G2. Los cifrados de kyber usan un sigma aleatorio, así que el fichero se genera una vez y se congela. Para regenerarlo, desde un módulo Go temporal que requiera la referencia (replace g.activething.com/go/DateKeys => ../datekeys-go, GOFLAGS=-mod=mod): go run ibe-go-vectors.go ../App/testdata/fixtures > ibe-vectors.json.

  • Todo se lee con los formatos de testdata/README.md (testing/vectors.ts): una clave desconocida o que falta, un valor de otro tipo, un código que no es de §69 o una edición fuera de su base hacen fallar el fichero con su motivo; nada se salta en silencio.

  • Todo fichero de testdata/ tiene que ejecutarlo algún test: un nombre nuevo exportado por Go (otro vectors/*.json, un fichero de fixture que ningún JSON nombra) hace fallar testdata/ holds no file that no test runs hasta que se le añade su bloque.

Dependencias de ejecución

Aprobadas en el plan de la fase 2 (sección 3 y decisión 5) e instaladas con su versión exacta. ibe.ts importa noble desde el paso 3, pero la página todavía no lo usa, así que el sitio no cambia hasta que llegue la apertura.

Paquete Versión Licencia Uso
age-encryption 0.3.1 BSD-3-Clause las tres envolturas age (§28), con Identity y Recipient propios para el stanza tlock
@noble/curves 2.4.0 MIT BLS12-381 del núcleo IBE y de la verificación de releases; también el oráculo de bls12381.contrast.test.ts
@noble/hashes 2.4.0 MIT los hashes del IBE; se declara porque se importa directamente

age-encryption arrastra @noble/ciphers 2.4.0, @scure/base 2.4.0 y @noble/post-quantum 0.5.4, todos con licencia MIT. @noble/post-quantum fija @noble/curves y @noble/hashes a ~2.0.0 y trae su propia copia 2.0.1, que usa para el ML-KEM híbrido. La decisión 5 la acepta, sin overrides de npm.

Guardas de src/lib/dependencies.test.ts, en cada npm test:

  • package.json declara exactamente estas tres dependencias, con versión exacta;
  • package-lock.json no contiene tlock-js ni drand-client, ningún noble 1.x, ni más copias 2.x de @noble/curves o @noble/hashes que la 2.4.0 de la raíz y la 2.0.1 bajo @noble/post-quantum;
  • ningún fichero de src/ importa tlock-js ni drand-client;
  • solo ibe.ts, release.ts y los tests nombran @noble/, siempre con subrutas de @noble/curves y @noble/hashes que resuelven a la copia 2.4.0 de la raíz;
  • cada comprobación se ejecuta también sobre entradas malas, así que una guarda que dejara de detectar algo fallaría.

check-build.mjs hace la misma comprobación sobre el bundle del cliente.

Coste medido en el bundle el 28-09-2026, con una compilación de prueba de Vite 8 minificada (gzip de nivel 9):

Qué se importa Minificado gzip
Decrypter de age-encryption 153 645 B 48 032 B
Decrypter y Encrypter 183 650 B 55 915 B
bls12_381 y sha256 de noble 92 637 B 28 090 B
Todo lo anterior 239 454 B 72 783 B

age-encryption importa de forma estática sus recipients ML-KEM híbridos, P-256 y scrypt. Por eso el Decrypter arrastra @noble/post-quantum y la copia 2.0.1 de noble, aunque DateKeys no los use: son unos 99 KB de los 212 KB de código antes de minificar. Como referencia, /inspect carga hoy unos 157 KB de JavaScript (58,7 KB con gzip). La cifra exacta cambia unos bytes en cada compilación, por la versión que SvelteKit incrusta.

npm audit --omit=dev no encuentra vulnerabilidades. El npm audit completo encuentra 2 de gravedad baja en el tooling: cookie < 0.7.0 (GHSA-pxg6-pf52-xh8x), que llega a través de @sveltejs/kit 2.70.3. Esa es la última versión y sigue pidiendo cookie ^0.6.0. Solo afecta a la gestión de cookies del servidor de SvelteKit, que un sitio estático no usa.

Tooling de desarrollo

Paquete Versión Estado
typescript 5.9.3 en package.json; ya usado en el prototipo
vitest 5.0.1 en package.json; ya usado en el prototipo
@vitest/coverage-v8 5.0.1 en package.json; cobertura del 100 % del codec
@types/node 24.13.6 en package.json; tests que leen testdata/ desde disco
vite 8.3.0 en package.json; dependencia peer obligatoria de vitest 5.0.1 y base del paso 5; la versión del prototipo
svelte 5.57.1 en package.json; paso 5
@sveltejs/kit 2.70.3 en package.json; paso 5
@sveltejs/vite-plugin-svelte 7.3.0 en package.json; paso 5
svelte-check 4.7.6 en package.json; paso 5, npm run check
@sveltejs/adapter-static 3.0.10 en package.json; paso 5: la página es estática y no necesita servidor

@noble/curves 2.4.0 estaba en esta lista como oráculo de bls12381.contrast.test.ts y ha pasado a dependencia de ejecución en la fase 2.

Todas las versiones se fijan exactas y package-lock.json se versiona. .npmrc activa legacy-peer-deps porque npm 11.5.2 falla al resolver los peers opcionales de vitest 5.0.1 (Cannot read properties of null (reading 'edgesOut')); con esa opción npm no instala peers, así que el peer obligatorio vite está declarado explícitamente.

testdata

npm run testdata:sync

Copia testdata/ de ../datekeys-go en HEAD, leyendo los blobs con git para no arrastrar cambios sin commit, y escribe testdata/SOURCE.json con el commit completo y el SHA-256 de cada fichero. Para fijar otro commit: node scripts/sync-testdata.mjs sync --commit <rev>.

npm run testdata:check

Comprueba que los ficheros coinciden con SOURCE.json, sin faltantes ni sobrantes, y vuelve a leerlos del repositorio Go en el commit registrado. Sin la opción --against, node scripts/sync-testdata.mjs check verifica solo la copia local. Ambos comandos usan solo Node y git.

.gitattributes marca testdata/** como binario para que git no altere ningún byte.

Copia actual: la de testdata/SOURCE.json (rama v0.8.2).

Licencia

Apache-2.0 (LICENSE), como la librería Go de referencia. El código que se derive de terceros conserva su aviso de copyright y licencia en el propio fichero; el primero previsto es el núcleo IBE de la fase 2, derivado de tlock-js (Apache-2.0 OR MIT).

Powered by TurnKey Linux.