# Changelog Cambios notables de la librería TypeScript y de la página. El proyecto usa versionado semántico; mientras sea 0.x, no hay promesa de estabilidad. La sección «Versiones» del [README](README.md) explica qué cubre cada número. ## Especificación 0.10, en la rama `v0.10` — sin versión ### `testdata` en `spec-v0.11` (01-10-2026) - `testdata` se sincroniza con el tag `spec-v0.11` de `datekeys-go` (`ae33434`), y `SPEC_VERSION` pasa a `0.11`. Trae tres fixtures (`format3_signed`, con firma de clave propia; `format3_signed_cms`, con dos certificados sellados; `format3_sealed`, con firma y sello RFC 3161) y tres ficheros de vectores (`ed25519_strict.json`, `security_cms.json` y `locator.json`). El corpus de mutaciones pasa a 210 casos, con uno fuera del §64: una firma de `alg` 1 que no verifica, F2. `ibe-vectors.json` añade los tres fixtures y rehace los de `format3_signature_unsupported` y `format3_seal_unsupported`; `mutation-texts.json` se rehace con el `capsule.Open` de esa referencia. - **La firma de clave propia, `alg` 1** (§29.8, §29.9), portada: `ed25519strict.ts` comprueba las cuatro condiciones del perfil estricto con la aritmética de `@noble/curves` (que solo ofrece la ecuación con cofactor) y da la respuesta de Go en los 18 vectores de `ed25519_strict.json`; `author.ts` calcula `payload_commit`, `control_commit`, `head_digest`, `signers_digest`, `AUTHOR_MESSAGE` y su código, y los registros de `format3_signed` los confirman. `evaluateSecurity(área, contexto)` da F2, F3 y F4, `open` pasa el contexto del control y del head, y `OpenOptions.authorKeys` son las claves que la persona guardó. Los dos módulos usan solo `@noble/curves` y `@noble/hashes`, que ya iban en el bundle: ningún paquete nuevo, y entran en la lista de quien puede importar noble. - **La firma con certificados, `alg` 2, y el sello RFC 3161, `seal_type` 2** (§29.10, §29.11), portados sin dependencias nuevas: `der.ts` comprueba el DER byte a byte, `cms.ts` lee la firma CMS y el token con la tabla cerrada de algoritmos (RSA PKCS #1 y PSS en `BigInt`, síncrono, y ECDSA con la aritmética de `@noble/curves`) y `securitycms.ts` da F1, F2, F5 y F6 con los firmantes nombrados, y S1 a S5 con la autoridad del sello. `evaluateSecurity` los devuelve con su `detail`, `verdictLines` escribe las líneas de F6 y S4, y `open` pasa la hora de la ronda. Reproducen los 22 casos de `security_cms.json`, con los resultados de cada firmante, y los fixtures `format3_signed_cms` y `format3_sealed`. `testing/cmsbuild.ts` construye firmas y tokens de prueba con WebCrypto, y `cms.test.ts` porta los casos hostiles de Go. - **El escritor de la v0.11** (§29.2, §24.1, §62.1 regla 13): `encryptFiles` escribe el área de seguridad de 32 KiB (`AREA_LEN` pasa de 512 a 32768) y acepta `publicNote`, la extensión `datekeys.note` de la cabecera pública (`note.ts`: `checkNote`, `newNote`, `publicNote`, con las reglas de texto del autor declarado). Otra área solo la escribe un generador de vectores, con `testVectors` y `areaLen`, y así los tests reproducen byte a byte los fixtures que escribió un escritor de la v0.10, de 512 bytes. `lengths.ts` y la página calculan L con el área nueva. Una nota cambiada después de escribir la cápsula falla en el paso 15. - **Lo que esta biblioteca no hace todavía:** el escritor no firma ni pide sellos (`alg` 1, `alg` 2 y RFC 3161 solo se leen y verifican), la página no pide ni muestra la nota, y el localizador del §44.1 (la extensión `datekeys.capsule` de la `.dkk`, su sobre y su relleno) y la página de firma. `locator.json` solo se comprueba en su estructura. El formato 3 de la especificación 0.10, según `PLAN_formato3_ts.md` (en `../docs`). La versión que lo publique la decide el autor. ### Llave de palabras y firma de drand - Llave de palabras v2, como decide el borrador de la especificación 0.11 (§38.1): la sal lleva además el `capsule_id`, así que las mismas palabras dan otra llave en cada cápsula; las palabras se pasan a minúsculas con la tabla de Unicode 18.0.0 de `pathrule.ts`, no con la de la plataforma; cuentan solo las distintas de 3 letras o más; y se rechazan los caracteres invisibles. `encryptFiles` recibe las palabras (`words`) y deriva la llave al sortear el `capsule_id`. `/create` pide escribirlas dos veces y muestra cómo se guardan. Las cápsulas hechas antes con palabras ya no se abren con ellas. El módulo pasa a `src/lib/dkc/wordkey.ts`. - `/inspect` pide la firma de la ronda a los relays de drand con un botón (`drand.ts`), además de poder pegarla. - Una cápsula «solo con una llave» puede abrirse con palabras que elige quien la crea, al menos 6, en vez del fichero `.dkk` (`wordkey.ts`): PBKDF2-SHA256 de 600.000 vueltas, con la red y la ronda como sal, da una clave X25519 que entra como una persona de `age` más. El formato no cambia; dan igual mayúsculas, acentos y espacios. ### Paso 8: revisión adversarial y rediseño de `/create` - Correcciones de la revisión: el payload llega a `age` en trozos de un chunk (`chunked`), el sumidero nunca se aborta tras el commit y recibe copias, el escritor copia cada trozo que lee y acepta fuentes escritas como clase, y las extensiones de tipos incorrectos son un `TypeError`. - El sitio es siempre claro. `/create` se rediseña como una carta al futuro: tres preguntas, fechas rápidas, la fecha de apertura en un sobre de correo aéreo con el botón, y los detalles técnicos plegados; los mensajes ya no citan R4 ni §29.6. - `/inspect` marca el principio de un fichero como contenido del creador, sin comprobar, y conserva ZWNJ y ZWJ en el comentario y el autor. ### Paso 7: `/create` con ficheros y carpetas - La página cifra ficheros y carpetas, elegidos o soltados, con un comentario y un autor declarado. Las rutas se pueden editar y se comprueban mientras se escriben, cada problema en su fila y en español; los ficheros de los sistemas quedan fuera, tachados, salvo que se marquen; y una casilla, marcada por defecto, guarda la fecha de cada fichero. - El tamaño exacto del `.dkc` antes de escribir, con los ficheros medidos una vez (`measureFiles`, `headLengthOf` y `bodyLengthOf` en `lengths.ts`), y el progreso de las dos lecturas de `encryptFiles`. - `create-files.ts` (la lista, sin tablas) y `create-check.ts` (las reglas en español, bajo demanda), al 100 %. Go abre una cápsula de la página, y `/inspect` también. ### Paso 6: `/inspect` abre el formato 3 - La página abre las cápsulas de formato 3: los ficheros van al fichero temporal por un `ZipSink`, o a la memoria, y se muestran primero los veredictos, después el autor declarado y el comentario, y luego cada ruta como texto, con su tamaño, su fecha y los avisos de la CLI de la referencia. Se descarga el fichero, si es el único, con el ZIP de su carpeta como segunda opción, o el ZIP de todos y cada fichero por separado. - `files.ts` (avisos, rutas y descargas) y `opener.ts` (`OpenedFiles`, la falta de espacio), cargados bajo demanda; `ZipSink` comprueba el espacio al empezar. - `check-build.mjs` exige que las tablas de las rutas no entren en la primera carga de ninguna página. ### Paso 5: el ZIP de la página - `zipsink.ts`: `ZipSink`, el sumidero de la página sobre el fichero temporal de OPFS. Un fichero de un segmento va tal cual, y los demás casos a un ZIP propio, con el CRC-32 de cada entrada parcheado con una escritura posicionada y el directorio central al hacer commit; nada se publica antes del paso 18. `zipOf` hace el mismo ZIP en memoria. - `tempfile.ts`: el `writable` de un fichero temporal acepta trozos con posición (`TempChunk`), como `FileSystemWritableFileStream`. - Interoperabilidad: `archive/zip` de Go lee las muestras de la página, 65 535 entradas con ZIP64 incluidas, con sus nombres, CRC-32, tamaños, fechas y contenidos (`testing/zip-vectors.json`, de `scripts/zip-ts-samples.mjs` y `scripts/zip-go-read.go`). ### Paso 4: la escritura del formato 3 - `writer.ts` se parte como el writer de Go: `newSealer` comprueba las opciones que no dependen del contenido, y `seal` escribe la cápsula de un formato alrededor de un contenido dado en trozos. El formato 2 no cambia. - `encryptFiles(files, opts)` escribe un `.dkc` de formato 3 con sus ficheros, el comentario y el autor declarado, y las extensiones del head, como `capsule.EncryptFiles`: lee cada fichero dos veces y falla, con el texto de Go, si cambió entre las dos lecturas. `fileSource` hace la fuente de un `File`. Los sorteos ganan la sal del head. - `lengths.ts`: `bodyLength`, `headLength`, `mtimeSeconds` y `headComment`, para dar el tamaño exacto del `.dkc` antes de escribirlo. - `encrypt` escribe el formato 2 solo con `testVectors`, y sin comentario, autor ni extensiones del head, con los textos de `capsule.Encrypt`; las pruebas, `encryptWith` y los scripts de muestras lo piden. - `/create` escribe el formato 3 con `encryptFiles`: el fichero va bajo su nombre y con su fecha de modificación, y el plan da el tamaño exacto con `bodyLength`. - Interoperabilidad: `capsule-vectors.json` se regenera con seis cápsulas de formato 3 escritas por `encryptFiles`, que Go abre en un `Sink` con cada credencial, con los mismos ficheros, head y veredictos; las trece de formato 2 y los demás bloques se regeneran igual. - Pruebas: los cinco fixtures que escribió `EncryptFiles`, byte a byte; los tamaños frente a lo escrito y 300 heads aleatorios frente a `encodeHead`; y las entradas inválidas, con los textos que da `capsule.EncryptFiles` a las mismas entradas. ### Paso 3: la lectura del formato 3, con `testdata` en `spec-v0.10` - `testdata` se sincroniza con el tag `spec-v0.10` de `datekeys-go` (`cc35d2c`), y `SPEC_VERSION` pasa a `0.10`. - `framing.ts`: `FORMAT_3`, e `isPadded` para los formatos 2 y 3. `control.ts`: la versión de schema 3, con las claves 6 y 7 de la 2. - `open.ts` y `open3.ts`: el paso 17 del formato 3 en sus subpasos, con la precedencia y los textos de `capsule.Open`. Los ficheros van a `OpenOptions.sink` (`sink.ts`: `Sink` y `MemorySink`), que solo los publica en el paso 18; sin sumidero, `open` rechaza con un `TypeError` justo tras el paso 2. `Opened` gana `head`, `verdicts`, `areaLen` y `unusableHeadExtensions`. - La página: `opener.ts` abre en memoria los fixtures del formato 3; el panel de apertura dice que aún no entrega sus ficheros, y los textos de los pasos nombran el formato 3. - Pruebas: los 21 fixtures; los 209 casos del corpus, con el texto exacto de `capsule.Open`, congelado con `scripts/mutation-go-texts.go` en `testing/mutation-texts.json`; las 5 110 mutaciones del diferencial; los vectores de rutas, pliegue, head y seguridad; y los textos del paso 17 con textos en claro preparados, contrastados con Go. `ibe-vectors.json` gana los nueve fixtures nuevos. - `check-build.mjs` impide publicar el head, la sal, el comentario y las rutas de los fixtures del formato 3. ### Paso 2: el códec del formato 3 - `body.ts`, `security.ts` y `head.ts`, como `capsule/format3.go`: la trama de `BODY`, los veredictos del área de seguridad con sus textos, y el head con las capas de §69.1. `ERR_HEAD_INVALID` es el código 19. ### Pasos 0, 1 y parte del 5: las reglas de las rutas y el ZIP de la página - `pathrule-tables.ts`, generado por `datekeys-go` con Unicode 18.0.0 y WindowsBestFit, y `pathrule.ts`, las reglas de las rutas y de los textos del head con los textos de error de Go. - `crc32.ts` y `zip.ts`, en `src/lib/inspector`: la disposición del ZIP en que la página entregará los ficheros, con entradas almacenadas, nombres UTF-8, las fechas en DOS, NTFS y el sello extendido, y ZIP64. ## 0.2.0 — sin publicar Fase 3: la escritura de cápsulas de formato 2, según `PLAN_fase3_escritura.md` (v3, en `../docs`). Hecha, pasos 0 a 7; la versión sigue sin publicar hasta que el autor la cierre. ### Después del paso 7: ayuda de `age` y el texto en claro a la vista - `/inspect` muestra el principio del texto en claro de cualquier cápsula que se abre, no solo de los fixtures, si es texto: UTF-8 imprimible, hasta 100 000 caracteres de sus primeros 128 KiB. Se ve también un texto escrito en Windows, con CR LF o BOM; la descarga conserva los bytes exactos. `opener.ts` da esos primeros bytes (`PREVIEW_BYTES`), y `opening.ts` decide qué se muestra (`plaintextPreview`). - Ayuda de las claves de `age`. En `/create`, un bloque plegable explica qué es un destinatario `age1…` y cómo se consigue con `age-keygen`. En `/inspect`, el campo de identidades dice qué pegar: la línea `AGE-SECRET-KEY-1…` del fichero de `age-keygen`, o el fichero entero. - Al abrir, el contenido va justo debajo del veredicto. La descarga de una cápsula sin extensión propia, como `capsula-.dkc`, toma la del contenido (`contentExtension`): `.txt`, `.pdf`, `.png`, `.jpg` y otras por sus primeros bytes. La cápsula no guarda el nombre del fichero (§6, §55.2). - `vite preview` sirve las páginas con `Cache-Control: no-cache`, para que una pestaña recargada tras compilar no se quede con la página anterior, cuyos ficheros ya no existen. ### Pasos 6 y 7: la página `/create` - El autor confirma las decisiones de la página con cada recomendación: `time_only` por defecto, la zona del dispositivo con un selector, los avisos de §53 y §50 desde 365 días, un aviso de protocolo preliminar y los nombres `capsula-`. - `/create` cifra un fichero propio en un `.dkc` de formato 2 y, si se pide, en una `.dkk` portable, en el navegador y sin red. Antes de cifrar muestra el instante efectivo, la ronda, la `dk1_`, el tamaño exacto del `.dkc` y lo que deja ver hasta la fecha. El `.dkc` va a un fichero temporal de OPFS, o a memoria hasta 64 MiB, y la escritura se puede cancelar. La `.dkk` vive solo en memoria, y la página avisa si se sale sin descargarla. El resultado lleva el informe de los pasos 1 a 8 de lo escrito. - `lengths.ts`: `sealedControlLength` sale de `writer.ts`, y `capsuleLength` da el tamaño del `.dkc` antes de escribirlo; el bucle de propiedades lo comprueba en cada cápsula. `datekey.ts`: `LONG_HORIZON_SECONDS` e `isLongHorizon`. - `localtime.ts`, `create-input.ts` y `creator.ts`, al 100 %. `/inspect` limpia también la zona de crear. `check-build.mjs` comprueba la carga bajo demanda de las dos páginas que la tienen. - Comprobado en el navegador: una cápsula creada para dentro de cuatro minutos se abrió después en `/inspect` con el release pegado y con `datekeys decrypt` de Go, con el mismo contenido. - Una revisión adversarial encontró un fallo mayor y ocho menores, todos corregidos. El mayor: la `.dkk` que es la única credencial se podía borrar sin confirmación. Ahora la página la pide al olvidarla y al crear otra cápsula. Entre los menores: la zona desconocida del dispositivo pasa a UTC; el reloj de la página se lee cada segundo; hay un mensaje propio para un reloj anterior a Quicknet; lo escrito no se ofrece si los pasos 1 a 8 lo rechazan; y el foco va a «Cancelar» durante la escritura. `localtime.test.ts` compara la conversión con una búsqueda exhaustiva alrededor de todos los cambios de hora de 2030. ### Paso 5: interoperabilidad con Go a nivel de cápsula - `interop.test.ts` y `testing/capsule-vectors.json`: Go abre con `capsule.Open` las trece cápsulas de muestra que escribe `encrypt`, con cada credencial, y reencodifica sus objetos a los mismos bytes. Cubren las dos políticas y las dos reglas, de 0 a 16 credenciales, los bordes de trozo y las extensiones. - Go rechaza cuatro mezclas de dos cápsulas con el mismo código y paso que `open`, codifica igual 500 entradas aleatorias de los codificadores, y da los mismos textos que esta librería con 22 recipients y 21 opciones inválidas. - Lo generan `scripts/capsule-ts-samples.mjs` y `scripts/capsule-go-verdicts.go`, y se congela; el test comprueba en cada ejecución los veredictos de Go y que `open` da lo mismo sobre los bytes congelados. ### Pasos 3 y 4: el writer - `encrypt.ts` y `writer.ts`: `encrypt(src, opts)` escribe un `.dkc` de formato 2 y, si se pide, una `.dkk` portable, como `capsule.Encrypt`: L conocida de antemano, relleno `reforzado` por defecto, de 1 a 16 credenciales con señuelos en un orden uniforme, `SEALED_CONTROL_LEN` con la fórmula del §62.1 y las autocomprobaciones de la regla 11 y dos más. Streaming desde `Uint8Array`, `Blob` o `ReadableStream`, hacia memoria o hacia un `WritableStream` que solo se cierra con la cápsula completa y comprobada. - Reproduce byte a byte las secciones deterministas de los siete fixtures de formato 2 de Go, y todo lo que escribe se abre con `open`. - Tests de streaming, de errores internos con `age-encryption` sustituido y un bucle de propiedades (50 semillas en cada ejecución; 500 pasaron a mano). ### Paso 2: piezas de apoyo - `recipient.ts`: recipients `age1…` como `age` 1.3.2, las reglas de §37 con los textos de `agewrap.CheckX25519Recipient` y la lista de recipients de una persona, con errores por número de línea. Sin noble. - `random.ts`: el índice sin sesgo y la permutación de Fisher–Yates del orden de los 16 huecos, con la prueba de uniformidad de Go. - `agefile.ts`: los ficheros `age` enteros que antes eran privados de la apertura, para compartirlos con el writer. - `x25519.ts`: `newX25519Identity` y `x25519PublicKey`, con los vectores de RFC 7748. `digest.ts`: `sha256Hasher`. `datekey.ts`: `compareInstants`, que usa `open.ts`, e `isInstant`. - `tempfile.ts`: la zona de la apertura y la de crear (`OPEN_AREA`, `CREATE_AREA`), con su limpieza por separado. - Guardas: solo `agefile.ts`, `open.ts`, `tlock.ts`, `writer.ts` y los tests importan `age-encryption`; solo `encrypt.ts` y `testing/` importan `writer.ts`; `index.ts` no reexporta la apertura ni el writer. ## 0.1.0 — 29 de septiembre de 2026 Implementa la especificación DateKeys 0.9 (tag `spec-v0.9` de `datekeys-go`) para el perfil Quicknet, y pasa todos los vectores y fixtures compartidos de `datekeys-go` en `7e2d83c`. Hasta el 29-09-2026 implementaba la 0.8.2 (`9ac9cd9`). ### Especificación 0.9: el formato 2 - El prelude lee el formato de la cápsula, 1 o 2 (`Prelude.format`); otro `VERSION` es `ERR_UNSUPPORTED_VERSION` en el paso 2. `DKC_FRAMING_VERSION` desaparece: `FORMAT_1`, `FORMAT_2` e `isFormat`. - `CONTROL_CBOR` se lee y se escribe para un formato, `decodeControl(b, format)` y `encodeControl(c, format)`: su versión de schema es la del formato, y en el formato 2 lleva `payload_length` (8 bytes, hasta L_MAX) y `padding` (claves 6 y 7). - `padding.ts`: las reglas `bloque256` y `reforzado` de §29.1, exactas hasta L_MAX = 2⁵³ − 2⁴⁶, y la longitud de `PAYLOAD_AGE`. - `open`: exactamente 16 stanzas en `INNER_ACCESS_AGE` del formato 2 (paso 12, `ACCESS_SLOTS`), P en el paso 16, y en el 17 un texto en claro de P bytes con ceros tras el contenido, del que solo se entregan los L primeros bytes, nunca el relleno. El paso 17 se registra también cuando se supera, y el 18 da los bytes de contenido, como la referencia. `Opened` da `format`, `payloadLength` y, en el formato 2, `padding` y `paddedLength`. - La vista JSON de `inspect` lleva `format`, como `datekeys inspect -json`. - La página muestra el formato de la cápsula, avisa cuando es el 1, que no oculta el número de credenciales ni la longitud exacta del contenido, y al abrir una del formato 2 da la regla de relleno y P. - `testdata` sincronizado con `spec-v0.9`: doce fixtures, siete de ellos del formato 2, `padding.json`, 125 casos de mutación y 4 380 del diferencial. `ibe-vectors.json` añade los siete fixtures nuevos. ### Hecho - Codec CBOR del perfil de §58 con los textos de error de la referencia Go. - Schemas del Provider Profile, `PUBLIC_HEADER`, `CONTROL_CBOR` y `.dkk`. - Tramas DKC1 y DKK1, cabeceras `age` y DateKey (`dk1_`). - Extensiones, con los objetos y arrays de su registro. - La inspección de los pasos 1 a 8 de §63. - La página estática `/inspect`, sin red. Tras cada compilación se comprueban su política de seguridad y los paquetes de su bundle. - Fase 2: - dependencias de ejecución (`age-encryption` 0.3.1, `@noble/curves` y `@noble/hashes` 2.4.0) con sus guardas; - el IBE de tlock (`ibe.ts`) y la verificación local de releases (`release.ts`), contrastados con la referencia Go; - la apertura, pasos 9 a 18 de §63 (`open.ts`), con el texto en claro en memoria. Las identidades estrictas de `agewrap` se apoyan en `x25519.ts`, que abre cada stanza X25519 por separado, y en `bech32.ts`. Los 65 casos del corpus de mutaciones pasan por `open` con el código y el paso de Go, y los cinco fixtures oficiales se abren a su texto en claro; - `@noble/ciphers` 2.4.0 como dependencia directa, aprobada el 28-09-2026: la copia que ya trae `age-encryption`; - la apertura en streaming. La entrada puede ser un `Blob`, del que se lee solo el prefijo de los pasos 1 a 8 (`prefix.ts`, antes en la página) y se descifra `PAYLOAD_AGE` en streaming. La salida puede ser un `WritableStream`, que se cierra solo tras el paso 18 y se aborta ante cualquier fallo. En el navegador, con un fichero OPFS, un fallo de STREAM deja intacto su contenido anterior. - el cifrado del stanza tlock (`encryptOnG2RFC9380`) y el `Recipient` de `OUTER_TIME_AGE` (`tlock.ts`). Con sigma fijo, el cifrado reproduce byte a byte los vectores de Go. Además Go abre lo que cifra esta librería: el cuerpo IBE con `tlock.TimeUnlock` y el fichero `age` con `agewrap.NewTimeIdentity`; - la acción "abrir" de `/inspect` (paso 8), cuyo código, con noble y `age-encryption`, se carga bajo demanda: - el release lo pega quien abre (la respuesta de drand o la firma sola), o sale del registro de un fixture. La página nunca lo pide a la red y solo lee su ronda y su firma; - las credenciales de `time_and_key` son una `.dkk` o identidades `AGE-SECRET-KEY-1…`; - el texto en claro de un fichero propio va a un fichero temporal de OPFS que solo se confirma tras el paso 18. Se ofrece para descargar y se borra al pedirlo, con otra cápsula, al salir o en la visita siguiente; - `readAccessKey` en `prefix.ts`. - `VERSION` y `SPEC_VERSION`, también en el pie de la página. - `licenses.txt` en el sitio, con los avisos de `tlock-js` y `age` y los de cada paquete del bundle.