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

18 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 6 pendiente

src/lib/dkc

Sin dependencias de ejecución. Funciona en navegadores y en Node 20+: solo usa Uint8Array, DataView, TextEncoder/TextDecoder, BigInt y crypto.subtle (SHA-256).

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 (afb44a3)
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 Perfil de §58: Encoder con error persistente, Decoder estricto, unmarshal (decodifica, reencodifica y compara; onReject para borrar secretos), peek/checkSchema (tipo y versión antes del resto), wideUint (los dos campos que Go lee como uint64) y walk (lector genérico acotado) codec
extension.ts Mapas de extensión, reglas del array (1 a 64, orden por bytes UTF-8 de extension_id, sin repetidos ni solapes), registros, críticas y no críticas extension
profile.ts Provider Profile: CBOR exacto, profile_hash, validación completa, 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
datekey.ts dk1_ canónico, ronda desde una fecha con precisión de nanosegundos, 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 capsule, accesskey
framing.ts Prelude DKC1 (16 bytes) y DKK1 (12 bytes), longitudes, límites de §57 y troceo de secciones capsule/framing.go
age.ts Parser estricto de la cabecera age v1 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 capsule/inspect.go, cmd/datekeys
index.ts Reexporta todo
testing/ Solo para tests: lectura de testdata/, 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 afb44a3 mediante un oráculo diferencial fuera del repositorio: unos 310 000 casos (cápsulas mutadas byte a byte y estructuralmente, cabeceras, controles, .dkk, perfiles, cabeceras age, dk1_, fechas y cabeceras de schema). Coinciden el veredicto, el código, el paso y los campos, los textos de todos los pasos superados y los textos de fallo de framing, age, DateKey, perfiles y extensiones. La vista de inspect de cada fixture es idéntica byte a byte a la salida de datekeys inspect -json.

Enteros: el decodificador rechaza al leerlo todo entero por encima de 2⁵³ − 1 (plan §5), salvo en access_policy de PUBLIC_HEADER y en extension_version. Go lee esos dos campos como uint64 y comprueba su rango después de la DateKey y de los demás campos, con otro código; Decoder.wideUint los lee igual (un valor por encima de 2⁵³ − 1 queda como Infinity con sus 8 bytes, para reencodificarlo y escribirlo en decimal), y el orden, el código y el texto coinciden con Go.

Diferencias conocidas:

  • Enteros por encima de 2⁵³ − 1 en los demás sitios. En walk y en los campos de perfil, control y .dkk, TypeScript los rechaza al decodificar y Go los acepta como uint64 y los rechaza después. El código es siempre ERR_NON_CANONICAL_CBOR en los dos y el paso el mismo; cambia el texto. Un caso accept de cbor.json con un entero así (el genérico de Go) se espera rechazado aquí con ERR_NON_CANONICAL_CBOR.
  • Textos de errores de decodificación CBOR. Los de Go vienen de fxamacker/cbor (codec: decode: …); los de TypeScript describen el mismo fallo con otras palabras. El código y el paso coinciden.

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.

El lector de schema (peek) reproduce a propósito lo que acepta codec.Peek de afb44a3, incluidas sus rarezas (una versión null cuenta como 0, un valor simple e2 como 2), para que el código ERR_UNSUPPORTED_VERSION frente a ERR_NON_CANONICAL_CBOR sea el mismo. Si el codec propio de Go (paso 2b) cambia ese comportamiento, habrá que seguirlo.

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 y no están autenticadas hasta el paso 15;
  • 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; o si aparece en el sitio algún secreto de los fixtures (.dkk, textos en claro, identidades, payload_identity, access_material, control_cbor).

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. Ahora mismo no hay ninguna: package.json solo tiene devDependencies. El sitio lleva compilado 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 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/vectors/dk1.json, quicknet_rounds.json, profile_quicknet.json: se ejecutan todos.
  • Ficheros que Go producirá (plan §6 y §7), ya previstos en vectors.test.ts; se saltan mientras no existan y, cuando existen, cada caso se revisa antes de ejecutarlo: falla el fichero cuya estructura no se reconoce y el caso al que le falta un campo, nunca se salta en silencio.
    • vectors/cbor.json: accept/reject con walk y bloques por schema (public_header, control, access_key, profile, extension…), cada caso con bytes y error o value. Un bloque sin decodificador, vacío o con un caso sin bytes falla.
    • vectors/mutations.json: casos con bytes, código normativo (error, code o want) y step numérico; se ejecutan los de los pasos 1 a 8 (al menos 23, plan §7.4) y los demás se muestran como saltados.
    • vectors/inspect_differential.json: casos con bytes y el veredicto de Go (valid, error o checks, en el caso o bajo view, inspect, result, go, verdict, expected o want); un caso sin veredicto falla.
    • **/*.inspect.json: salida de datekeys inspect -json junto a su .dkc; se compara la vista entera salvo file, y el texto de los pasos superados.
  • Bytes de un caso: hex es hex estricto; base64/b64, base64 canónico; file/path, un fichero bajo testdata/; dkc, bytes, input y data, hex estricto, si no un fichero bajo testdata/, si no base64 canónico. Cualquier otro texto hace fallar el caso.
  • 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.

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

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 (commit afb44a3, rama v0.8.2).

Powered by TurnKey Linux.