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.
 
 
 
 
 
Go to file
dev bdd04941dd
Record the phase 2 decisions and license App under Apache-2.0
2 weeks ago
.claude Inspector page: static SvelteKit site with /inspect (plan step 5) 2 weeks ago
docs Record the phase 2 decisions and license App under Apache-2.0 2 weeks ago
scripts Contrast bls12381.ts with the Go reference and an audited library 2 weeks ago
src Address the independent review of the alignment 2 weeks ago
static Inspector page: static SvelteKit site with /inspect (plan step 5) 2 weeks ago
testdata Follow spec §19 on invalid UTF-8 in dk1_ (reference fixed in 692cf87) 2 weeks ago
.gitattributes Start the DateKeys TypeScript project 2 weeks ago
.gitignore Start the DateKeys TypeScript project 2 weeks ago
.npmrc Pin the development tooling 2 weeks ago
LICENSE Record the phase 2 decisions and license App under Apache-2.0 2 weeks ago
README.md Record the phase 2 decisions and license App under Apache-2.0 2 weeks ago
package-lock.json Record the phase 2 decisions and license App under Apache-2.0 2 weeks ago
package.json Record the phase 2 decisions and license App under Apache-2.0 2 weeks ago
svelte.config.js Inspector page: static SvelteKit site with /inspect (plan step 5) 2 weeks ago
tsconfig.json Inspector page: static SvelteKit site with /inspect (plan step 5) 2 weeks ago
tsconfig.lib.json TypeScript implementation of DateKeys v0.8.2, steps 1 to 8 2 weeks ago
vite.config.ts Inspector page: static SvelteKit site with /inspect (plan step 5) 2 weeks ago
vitest.config.ts Inspector page: static SvelteKit site with /inspect (plan step 5) 2 weeks ago

README.md

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 (692cf87)
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): 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
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
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.

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; 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/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/mutations.json: se leen los 55 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 24 de los pasos 9 a 18 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.
  • 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.

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 solo desarrollo: oráculo auditado de bls12381.contrast.test.ts, que compara nuestro bls12381.ts con la referencia Go y con noble; un test falla si algo que no sea un test la importa, así que nunca llega al sitio. Arrastra @noble/hashes 2.4.0

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 692cf87, 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.