|
|
# 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
|
|
|
|
|
|
### El localizador de `datekeys.capsule` (06-10-2026)
|
|
|
|
|
|
El paquete `locator` de `datekeys-go` en `spec-v0.12` (§43 a §44.1), con los mismos checks en el mismo orden, los mismos códigos y los mismos textos de error, byte a byte, como lo portó `datekeys-dart` en sus partes 7a y 7b.
|
|
|
|
|
|
- **Lo que no necesita criptografía** (`locator.ts`, `ipaddr.ts`): los datos de la extensión (`parseInfo` e `infoExtension`); `standardExtensions`, el registro de las extensiones de la especificación, que por defecto comprueba los datos de `datekeys.capsule` como `locator.Standard`; las direcciones, con cada regla del §44.1 de la v0.12 (`checkURI`, `addressHost`, `usableAddresses`), los bloques de IANA comparados byte a byte sobre los 16 bytes de una IPv6 y los CID v1 en base32; `checkResolvedIp`, la IP a la que resuelve un nombre, como la de `datekeys-dart`, que hoy rechaza `64:ff9b::/96`; el texto en claro con su relleno (`marshalLocator`, `unmarshalLocator`, `plaintextLength`); y el resto en su host (`restIn`, `hide`).
|
|
|
- **La criptografía** (`ageio.ts`, `envelope.ts`): abrir un localizador sellado con el release de su ronda (`openSealed`, `openInfoLocator`), que lee como mucho 1 MiB como Go; abrir el sobre (`openEnvelope`); sellar (`seal`) y crear el sobre (`newEnvelope`), con una fuente de lo aleatorio inyectable que se lee en el orden de Go. `ageio.ts` lee y escribe ficheros `age` como `filippo.io/age` 1.3.2, con sus textos, porque `locator.Open` y `OpenEnvelope` los copian. Sin dependencias nuevas: usa los módulos de noble que ya usa `x25519.ts`, y HMAC es el HKDF-Extract de `@noble/hashes/hkdf.js`.
|
|
|
- **Nada entra en `/inspect`.** `index.ts` no reexporta el localizador: la nota trae las tablas de Unicode, y el sobre, noble. `dependencies.test.ts` añade `ageio.ts` a los que pueden importar noble y los cuatro módulos a los que `index.ts` no reexporta, y `check-build.mjs` falla si una página carga el localizador con su primera carga.
|
|
|
- **Contra Go**, todo con el resultado y el texto de Go:
|
|
|
- `vectors.test.ts` corre entero `testdata/vectors/locator.json`, en vez de mirar solo su campo `spec`;
|
|
|
- `testing/locator-uris.json` y `testing/locator-vectors.json`, de `scripts/locator-go-vectors.go`: 6 531 casos de direcciones, IP, textos en claro, localizadores sellados, sobres, datos de la extensión, el registro y la apertura de una cápsula cuya `.dkk` lleva `datekeys.capsule`, y las 20 585 bases del relleno de −4 100 a 16 484;
|
|
|
- `testing/locator-seal.json`, de `scripts/locator-seal-go-vectors.go`: con la misma semilla, `seal` y `newEnvelope` sacan los mismos valores que Go en el mismo orden y escriben los mismos bytes;
|
|
|
- `testing/locator-interop.json`: Go abre los localizadores y los sobres que escribe esta librería (`scripts/locator-ts-samples.mjs` y `scripts/locator-go-verdicts.go`), de 0 bytes a 16 MiB y un byte.
|
|
|
|
|
|
Los generadores son los de `datekeys-dart` con las semillas de este repositorio, y corren en una exportación de `datekeys-go` en `spec-v0.12`.
|
|
|
- **Lo que el autor tiene pendiente** se queda como en Go: un CID no canónico pasa, `https://[[2000::]/` pasa, `parseInfo` comprueba menos de lo que podría y `openSealed` lee 1 MiB.
|
|
|
- **Pruebas.** `locator.test.ts`, `envelope.test.ts` y `locator.interop.test.ts`, con los cuatro módulos al 100 % de cobertura: 7 901 pruebas en total.
|
|
|
|
|
|
### La especificación 0.12, aprobada (06-10-2026)
|
|
|
|
|
|
- El autor aprobó el 6 de octubre de 2026 el borrador v0.12, tal como estaba: `datekeys-go` lo cierra con el tag `spec-v0.12` (`fe405e2`). `SPEC_VERSION` pasa a `0.12`, y `testdata` se sincroniza con ese tag: solo cambia el campo `spec` de cada fichero.
|
|
|
- `testing/mutation-texts.json` se regenera con `scripts/mutation-go-texts.go`: solo cambia su campo `spec`.
|
|
|
- La librería ya seguía el borrador: no cambia nada más. `npm run verify` pasa con 7 832 pruebas.
|
|
|
|
|
|
### Los vectores compartidos de la llave de palabras (06-10-2026)
|
|
|
|
|
|
- `testdata` se sincroniza con `datekeys-go` en `084728d`, que añade `vectors/wordkey.json`: los casos de la llave de palabras que pide el §64 de la v0.11, que hasta ahora solo estaban en las pruebas de Go. Ningún otro fichero cambia.
|
|
|
- Un bloque nuevo de `vectors.test.ts` los corre: las palabras de 45 textos, entre ellos cada espacio del §38.1 y tres que no lo son; lo que hace un escritor con 20 textos, con el texto del error de Go; y 6 identidades con su recipient, el vector del §38.1 el primero. La librería no cambia: todos coinciden.
|
|
|
|
|
|
### El borrador v0.12: `testdata`, la regla del §72 y la nota de `inspect` (05-10-2026)
|
|
|
|
|
|
- `testdata` se sincroniza con la cabeza de la rama `v0.12` de `datekeys-go` (`601e6d2`); `SPEC_VERSION` sigue en `0.11` hasta que el autor apruebe el borrador. Trae:
|
|
|
- los fixtures `format3_unsigned`, el contenido de `format3_signed` sin firma y con la misma P, y `format3_note`, con nota pública;
|
|
|
- `format3_seal_unsupported` con `seal_type` 4294967295;
|
|
|
- `note.json`, `security.json` con su contexto y sus líneas, los 135 casos de `security_cms.json` y los 218 de `mutations.json`, 178 de ellos del §64.
|
|
|
- **Los vectores de Go.** `mutation-texts.json` se regenera con `scripts/mutation-go-texts.go`: cambian solo los ocho casos nuevos y el nombre del de `seal_type`. `ibe-vectors.json` gana las entradas de `format3_note`, `format3_unsigned` y el `format3_seal_unsupported` nuevo, sin tocar sus valores congelados.
|
|
|
- **La regla de los codificadores del §72**, `checkWrite` en `extension.ts`, con `NOTE_ID` y `CAPSULE_ID`, como `extension.CheckWrite` de Go:
|
|
|
- el escritor de cápsulas rechaza `datekeys.note` fuera del array no crítico de la cabecera o con datos que incumplen sus reglas, y `datekeys.capsule` en una cápsula;
|
|
|
- el de `.dkk` rechaza una nota, y `datekeys.capsule` fuera de su array no crítico o sin datos;
|
|
|
- los textos de error son los de Go.
|
|
|
- **La nota pública.** `checkNoteData` comprueba los bytes de una nota en el orden de Go, y `unusableNote` distingue una nota inservible de ninguna. `inspect` lee la nota bajo demanda, solo si la cabecera lleva una, y la vista de `inspect -json` da `public_note` y `public_note_unusable`, como la CLI de Go. Por eso `/inspect` sigue sin cargar las tablas de Unicode.
|
|
|
- **Pruebas.** `note.json`, y el bloque de `mutations.json` con sus recuentos nuevos: 7 759 pruebas en total.
|
|
|
|
|
|
### El lector de certificados y los textos del borrador v0.12 (05-10-2026)
|
|
|
|
|
|
Los fallos T2, T6, T7 y T8, y lo que toca a TypeScript de E2, E3, E7, E8 y E9, de `spec_v0.11/revision_sesion_1_2_octubre.md` (en `../docs`), como los arregla `datekeys-go` en la rama `v0.12` (`601e6d2`), cuyos `security.json` y `security_cms.json` leen las pruebas.
|
|
|
|
|
|
- **El certificado, campo a campo**, con el perfil del §29.10 del borrador v0.12: versión 3, los campos en orden, nombres de SET no vacíos, la validez en DER y sin fracción, las extensiones sin repetir y un `subjectKeyIdentifier` no vacío. Uno que lo incumple no decide nada salvo que lo nombre un `SignerInfo`, y dos copias de uno cuentan como una. El texto de un nombre sale solo de los cinco tipos de cadena, en su alfabeto, sin quitar nada y nunca de un atributo repetido. El titular es su `givenName` y su `surname` antes que su `commonName`, que puede llevar el NIF, y el emisor, su `commonName` o su `organizationName`, ya no el texto de todos sus atributos.
|
|
|
- **Identificadores, claves y sellos.** Los OID se comparan por los bytes de su DER: un arco de 2³¹ o más es solo uno que la tabla no tiene. Un SET OF puede repetir un elemento, así que una TSA que manda dos veces su certificado ya no da S2. La clave RSA lleva parámetros NULL, exactamente un módulo y un exponente, y un módulo impar, y una clave de otro esquema que su algoritmo da F2, no F5. Un `messageImprint` de otra longitud da S3, los `crls` de un token no deciden nada, y los milisegundos y microsegundos de `accuracy` son INTEGER mínimos. `der.ts` comprueba las horas en sus formas de X.690 y admite los tipos de cadena restringidos, NumericString entre ellos.
|
|
|
- **Los textos de los veredictos.** Cada nombre de un certificado va entre « y », y se muestra si cumple las reglas del autor declarado, tiene como mucho 64 puntos de código y no lleva dos espacios seguidos; si no, su SHA-256. La línea de cada firmante de F6 nombra la autoridad de su sello, y si alguna dice «antes de la fecha de apertura», la sigue «DateKeys no comprueba quién emitió los sellos.». El resultado de un firmante ajeno va en español, y una hora lleva la fracción de su sello.
|
|
|
- **Pruebas.** `cms.test.ts`, `der.test.ts` y `securitycms.test.ts` portan las de `internal/cms`, `internal/der` y `signature2_test.go`, y la del emisor sin `commonName` ya no lo compara consigo mismo. `vectors.test.ts` lee `security.json` con su contexto y sus líneas, y compara en cada caso de `security_cms.json` los veredictos, los firmantes exigidos y los ajenos, el sello y las líneas.
|
|
|
- **Diferencial con Go.** `capsule.EvaluateSecurityIn` y `Verdicts.Lines` de `601e6d2` dan lo mismo que `evaluateSecurity` y `verdictLines`, campo a campo, en 63 623 áreas: las de los vectores, sus mutaciones byte a byte y elemento a elemento, y áreas firmadas y selladas de verdad cuyos certificados, firmas y tokens varían campo a campo. Antes de estos cambios diferían en 13 296 de 42 986.
|
|
|
|
|
|
### Arreglos de la revisión de la sesión del 1 y 2 de octubre (02-10-2026)
|
|
|
|
|
|
Los fallos T1, T3, T4, T5, T9 a T12, T14 y parte de T13 de `spec_v0.11/revision_sesion_1_2_octubre.md` (en `../docs`). Cada texto y cada veredicto nuevo se contrastó con un oráculo de Go sobre el tag `spec-v0.11`.
|
|
|
|
|
|
- **CMS como lo lee Go.** Un emisor que incumple las reglas del autor declarado se muestra con el SHA-256 de su `Name` (`RawIssuer`), y no con el del certificado. Una clave ECDSA solo cuenta sin comprimir, `0x04` y las dos coordenadas, como en `x509.ParsePKIXPublicKey`: con el punto comprimido, un firmante es «no verificable» (F5) y un sello da S1. Un `UTF8String` y las horas del certificado y del token conservan un U+FEFF inicial, así que ese nombre se muestra con el hash y esa hora rompe el perfil (F1, S2).
|
|
|
- **Lectura lineal.** `oidOf` e `intOf` leen en tiempo lineal, sin un desplazamiento por byte, que tardaba unos 700 ms con 60 KB; los atributos de un tipo se añaden sin copiarse.
|
|
|
- **`evaluateSecurity` nunca lanza.** Una excepción al evaluar la firma da F1, y una al evaluar el sello, S2, cada una sin tocar el otro veredicto; una al decodificar el área da X. La referencia hará lo mismo desde la v0.12.
|
|
|
- **Pruebas que no probaban lo que decían.** Las de `cms.test.ts` de un segundo content-type y dos signature-time-stamp fallaban por un SET OF desordenado, y ahora fallan por la regla de recuento. La de profundidad de `der.test.ts` fallaba por la longitud, y ahora prueba la frontera de 32 niveles.
|
|
|
- **La nota pública.** `checkNote` rechaza un texto con UTF-16 mal formado, con el texto de Go para el UTF-8 inválido, así que ninguna nota se escribe alterada. `publicNote` conserva un U+FEFF inicial, que la deja inservible como en Go. También lo conservan `authorCode` y la lectura de la respuesta de los relays de drand; el head y las rutas ya lo hacían, y sus pruebas lo fijan con los textos de Go.
|
|
|
- **El escritor.** `testVectors` y `areaLen` salen de las opciones públicas: con ellas, cualquiera podía escribir el formato 2 o un área de 512 bytes, que delata la falta de firma (§55.2).
|
|
|
- Lo que solo pide un generador de vectores es el argumento `TestVectors` del núcleo, que solo pasan los ayudantes de `testing/encrypt.ts`: `encryptVectors`, `encryptWith` y `encryptFilesWith`.
|
|
|
- `encrypt` conserva la forma de `capsule.Encrypt`: sin generador, falla con su texto.
|
|
|
- `dependencies.test.ts` y `check-build.mjs` dejan `testing/` fuera de la librería y de las páginas.
|
|
|
- **Las opciones, como en Go.** Como generador, `encrypt` rechaza la nota y el área con el texto de `capsule.Encrypt`. Los errores de la nota llevan `capsule: `, y las opciones se comprueban en el orden de `newSealer`.
|
|
|
- **El tamaño con nota.** `capsuleLength` acepta la nota pública y predice exactamente el tamaño del `.dkc` con ella.
|
|
|
- **Interoperabilidad.** `capsule-vectors.json` se regenera con el escritor actual: las seis cápsulas de formato 3 llevan el área de 32 KiB, y hay dos más con nota pública, una de 1024 bytes. Go las abre con cada credencial, encuentra el área de 32 KiB y lee la nota con `Header.PublicNote`. El fichero gana el texto de `capsule.Encrypt` para una nota en el formato 2, y los de `capsule.EncryptFiles` para nueve notas inválidas.
|
|
|
- **Documentación.** El README completa la tabla de módulos (`author.ts`, `ed25519strict.ts`, `der.ts`, `cms.ts`, `securitycms.ts` y `note.ts`), los veredictos de la v0.11, las dependencias y sus guardas, y los recuentos del corpus de mutaciones; los comentarios ya no hablan del área de 512 bytes ni de una versión sin `alg`.
|
|
|
- **Pendiente, con la v0.12:** el lector de certificados, cuyo perfil se fija ahora en Go (T2, T6, T7 y T8), el texto del emisor sin CN y la prueba de `cms.test.ts` que lo compara consigo mismo.
|
|
|
|
|
|
### `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-<fecha>.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-<apertura en UTC>`.
|
|
|
- `/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.
|