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-dart/CHANGELOG.md

134 lines
27 KiB

# Changelog
Cambios notables de la librería Dart. El proyecto usa versionado semántico; mientras sea 0.x, no hay promesa de estabilidad.
## Especificación 0.11, en la rama `v0.11` — sin versión
### Etapa 4c: el head, la nota pública, la inspección y la apertura (05-10-2026)
- **La nota pública del §24.1** (`lib/src/note.dart`), port de `CheckNote`, `NewNote` y `Note` de `extension` de `datekeys-go` en `c531e93`: `checkNote` y `checkNoteData`, con la longitud, el UTF-8 y las reglas de texto del autor declarado en ese orden y con los textos de Go; `newNote`, `publicNote` y `unusableNote`, y `Header.publicNote` y `Header.unusableNote`. `StandardExtensions` comprueba ya la nota como el `Standard` de Go, y su parámetro `validateNote` desaparece.
- **El head del formato 3** (`lib/src/head.dart`): `decodeHead` en las capas del §69.1, con R1 y R8 en la tercera, sobre los bytes UTF-8 de las rutas, nunca sobre sus unidades UTF-16; las reglas de rutas, del comentario y del autor de `pathrule` en la cuarta, como `ERR_HEAD_INVALID` con el texto de Go; la maquetación de los ficheros por restas; las extensiones críticas del head; `checkHeadEnd`; y `encodeHead`.
- **La inspección, pasos 1 a 8** (`lib/src/inspect.dart`): `inspectCapsule` e `inspectCapsuleSource`, `Inspection` con la comprobación de cada paso y su detalle, como el `Inspection` de Go; `inspectedLength`, para leer de un fichero grande solo su principio; `maxAccessKeyRead`; y `inspectView` e `inspectJson`, la salida exacta de `datekeys inspect -json`.
- **La apertura, pasos 9 a 18** (`lib/src/open.dart`, `open3.dart`): `openCapsule`, de una cápsula en memoria, y `openCapsuleSource`, de una `ByteSource` que se lee por tramos (`lib/src/source.dart`); `OpenOptions` y `Opened`, como los de Go.
- La `.dkk` del paso 9.a, decodificada o todavía codificada, con su material, sus extensiones críticas, su `capsule_id` y su `capsule_digest`, calculado sobre la fuente por tramos; las identities X25519 del llamador y la llave de palabras como una identity más.
- El release con la regla del paso 9, nunca antes de su ronda, y su verificación en el paso 10; `OUTER_TIME_AGE` con el stanza tlock; `INNER_ACCESS_AGE` con las reglas de los huecos; `CONTROL_CBOR`, `header_binding`, `I_PAYLOAD` y P.
- `PAYLOAD_AGE` en streaming: el contenido de los formatos 1 y 2 a un `ByteSink` según `age` autentica cada chunk, con el relleno del formato 2 comprobado y nunca entregado; y en el formato 3 la trama de `BODY`, el área, el head y cada fichero a un `FileSink`, con su SHA-256, en el orden y con la precedencia del §63 (`lib/src/sink.dart`, con `MemoryByteSink` y `MemoryFileSink`).
- Cada fallo con el código, el paso y el texto de Go, también los de `age`, por fases, como `classify`, y los de la fuente del release, como `sourceFailure`. Nada se presenta como válido antes de que acabe el paso 17: la salida se cierra al publicar y se aborta tras cualquier fallo, y el sink se aborta tras cualquier fallo posterior a su `begin` (§56).
- **La firma y el sello quedan para la etapa 5,** detrás de un punto de enganche (`lib/src/verdicts.dart`): el área de `security` se lee solo hasta donde lo exigen la trama de `BODY` y el área, y sus veredictos los da un `SecurityEvaluator`, que recibe `SECURITY_CBOR`, los bytes del head, el control y el formato, el `round_time` y las claves de autor, lo que toman `newSecurityContext` y `EvaluateSecurityIn` de Go. El de hoy, `notEvaluated`, no evalúa nada, y uno que falla no impide abrir. `OpenOptions.accept`, el `Accept` de Go, ve los veredictos antes del paso 18 y puede negarse a publicar los ficheros.
- **`AgePayloadDecryptor.wipe`** borra la clave del STREAM de una apertura que acaba antes del final de `PAYLOAD_AGE`.
- **Vectores de Go:**
- `tool/mutation_go_texts.go`, port de `scripts/mutation-go-texts.go` de `datekeys-ts` sobre el `testdata/` de este repositorio, escribe `mutation_texts.json`: el texto de `capsule.Open` y sus comprobaciones, con su detalle, en los 210 casos del corpus. Go en `c531e93` y el corpus de `spec-v0.11` coinciden en el código y el paso de todos, y Go en el tag `spec-v0.11` da el mismo fichero, byte a byte;
- `tool/open_go_vectors.go`, en una exportación de `datekeys-go` porque usa `internal/testkit`, escribe `open_cases.json` (cada fixture con cada credencial, y 117 aperturas de fixtures editados o con otras opciones, en cada paso que el corpus no alcanza, con los sinks y la salida que fallan y el rechazo de `Accept`), `open_heads.json`, `open_notes.json`, `open_inspect.json` (el texto de `capsule.Inspect` en las 5110 mutaciones de `inspect_differential.json`, donde Go en `c531e93` y el fichero también coinciden, y la salida de la CLI con notas públicas) y `open_vectors.g.dart`, con siete fixtures pequeños y una parte de cada fichero para Node.js. Los casos editados se sellan otra vez con las claves y los nonces de los fixtures, así que la salida es la misma en cada ejecución.
- **Pruebas.** Los 24 fixtures se abren con cada credencial que documentan, y sus 24 `.inspect.json` salen byte a byte; los 210 casos del corpus dan el código, el paso, el texto y cada comprobación de Go; los 169 casos de `open_cases.json`, también el estado del sink, el contenido o los ficheros y las extensiones inutilizables; todo, en memoria y desde una fuente que se lee a trozos, con el mismo resultado. Cápsulas de varios MiB, hechas desde los fixtures, prueban el streaming: la salida recibe cada chunk al autenticarse, y un chunk posterior que falla la aborta sin cerrarla. Una cápsula con un stanza para una llave de palabras se abre con ella. 518 pruebas nuevas en la VM y 83 en Node.js: 1320 y 291 en total.
- **Fallos inyectados**, uno a uno y revertidos: 15. Las pruebas los detectan todos en la VM: un paso fuera de orden, el `capsule_digest` sin comprobar, la salida publicada antes del final del paso 17, el SHA-256 de un fichero sin comparar, el relleno sin comprobar en los formatos 2 y 3, R8 sobre unidades UTF-16, una nota de 1025 bytes aceptada, un fallo de `age` clasificado en la otra fase, el error de una fuente con su propio código en el paso 9, una identity que abre dos stanzas aceptada, un head inválido informado sin leer hasta el final, el autor comprobado antes que el comentario, el sink sin abortar y el prefijo de la inspección un byte corto. En Node.js, 13: el orden del comentario y del autor y el prefijo solo los ven las pruebas de la VM.
- **`tool/open_bench.dart`** mide la apertura: en la VM, una cápsula pequeña tarda unos 50 ms y 64 MiB en streaming, 1,5 s en el formato 1 y 2,6 s en un fichero del formato 3. Las cifras, en el README.
- **Un fallo de dart2js** de Dart 3.13, ajeno a la librería: un objeto que llega al campo de otro a través de `c ? null : objeto` puede perder las escrituras que reciba allí. El README lo explica.
### Etapa 4a: rutas, textos y llave de palabras (05-10-2026)
- **Las reglas de rutas y de textos** (`lib/src/pathrule.dart`), port de `internal/pathrule` de `datekeys-go` en `c531e93`, sobre las tablas de Unicode 18.0.0 y WindowsBestFit que genera `datekeys-go` (`lib/src/pathrule_tables.dart`):
- NFD con el orden canónico y Hangul, el pliegue de CaseFolding con U+0131 → U+0069, la minúscula simple y `Default_Ignorable` con la lista blanca de R4;
- las reglas de una ruta, de R2 a R6c y R10, con las proyecciones de las 15 tablas best-fit; las del árbol, R7 con su clave y las dos rutas que nombra, y R9; y las de los textos del §29.6, el comentario y el autor declarado;
- los textos de error de Go, en `PathRuleException`, sin código normativo: la cabecera y la nota lo pondrán en la etapa 4c. Como en Go, cada regla devuelve su violación y solo las funciones públicas lanzan;
- `canonicalTables`, el texto de `pathrule.Canonical`: una prueba recalcula `tablesDigest` desde las listas.
- **Bytes, como Go.** Las funciones acabadas en `Utf8` toman los bytes de un string de Go, donde un byte que no es UTF-8 válido es U+FFFD, y las demás un `String` como lo escribe `utf8Bytes`. Los límites cuentan bytes, y R6b puntos de código, nunca unidades UTF-16. Así cualquier entrada da el resultado de Go, también el final de R4b tras bytes no válidos, que Go cuenta como U+FFFD de tres bytes.
- **La llave de palabras del §38.1** (`lib/src/wordkey.dart`), port de `wordkey`: `normalizeWords`, `checkWords` con los textos de Go, `wordKey`, con el PBKDF2-HMAC-SHA256 de la etapa 2 y 600 000 iteraciones, `wordKeyPassword` y `wordKeySalt`, la P y la S del §38.1, y `wordIdentity`. `lib/datekeys.dart` exporta `normalizeWords`, `checkWords`, `wordKey`, `WordKeyException` y sus constantes, como el paquete público `wordkey` de Go; `wordIdentity` queda interna, porque devuelve una identity de `age.dart`.
- **Vectores de Go** en `test/vectors/`, con su copia en Dart para Node.js:
- `pathrule_vectors.json`, de `tool/pathrule_go_vectors.go`, que corre en una exportación de `datekeys-go` porque `internal/pathrule` no se puede importar desde fuera de su árbol: los casos de las pruebas de Go y de `datekeys-ts`, los dos lados de cada límite de R2, R3 y R6b, 1300 cadenas y 350 árboles de una semilla fija, también con bytes que no son UTF-8 válido, los casos de R9 y, por plano, el SHA-256 de una línea por punto de código de cada función;
- `wordkey_vectors.json`, de `tool/wordkey_go_vectors.go`: 400 textos y sus palabras, 515 listas de palabras con el resultado de `Check` y cuatro llaves, la primera la del §38.1.
- **Pruebas.** 31 nuevas en la VM y 18 en Node.js. Integrada encima de la 4b, el total es de 802 pruebas en la VM, sin ninguna aplazada, y 208 en Node.js. Cada punto de código de los 17 planos da el resultado de Go en las siete funciones; los de los planos 0, 1 y 14, también en Node.js. Las llaves de 600 000 iteraciones corren solo en la VM.
- **Fallos inyectados**, uno a uno y revertidos: 45, en el orden canónico, los ignorables, la lista blanca, el pliegue y la minúscula, las descomposiciones, los límites, las cuentas en UTF-16, las tablas best-fit, Hangul, los bytes no válidos, R4b, R6, R7, R9, R10, los textos, el texto canónico, y en la llave de palabras el separador, los espacios, las marcas, la cuenta de letras, las palabras repetidas y la sal. Las pruebas los detectan todos, cada uno con una prueba que corre también en Node.js. Cuatro se escapaban al principio, uno de R2, uno de R3 en unidades UTF-16, U+036F en las palabras y DEL en `checkWords`: los vectores llevan ahora los dos lados de cada límite.
- **`tool/pathrule_bench.dart`** mide las reglas y la llave de palabras: en la VM, una ruta tarda de 9,5 a 28 µs, un comentario de 16 KiB 0,7 ms y la llave 1,1 s. Las cifras, en el README.
### Etapa 4b: los formatos de la cápsula y de la llave de acceso (05-10-2026)
- **Las tramas** (`lib/src/framing.dart`): el PRELUDE de un `.dkc` y la trama de una `.dkk`, con las comprobaciones de los §23 y §40 en su orden y los textos de Go; `splitCapsule`, los pasos 1 a 3 de la inspección, con su `FramingException`; y `headerBinding`. El formato de una cápsula es el enum `CapsuleFormat`.
- **El relleno** (`lib/src/padding.dart`): `paddedLength` y `payloadAgeLength` con los códigos de `PaddingRule`, exactos hasta L_MAX también en la web, sin desplazamientos ni máscaras de más de 31 bits; y `PaddingCheck`, la comprobación del plaintext de `PAYLOAD_AGE` frente a L y P del paso 17, por trozos.
- **`BODY` del formato 3** (`lib/src/body.dart`): su trama y el área, sin el head. Y el `capsule_digest` incremental (`lib/src/digest.dart`). Son internos, como en `datekeys-ts`.
- **Las extensiones** (`lib/src/extension.dart`): las reglas de un array, al decodificarlo y antes de escribirlo; los registros, con la comprobación de la `data` y los lugares del §72; las extensiones críticas y no críticas de cada objeto; y la regla de los codificadores del §72, `checkWrite`, con `StandardExtensions`, a la que se dan las comprobaciones de la nota y del localizador.
- **El Provider Profile** (`lib/src/profile.dart`): su CBOR, `profile_hash`, las reglas 1 a 3 del §12.1 en su orden, el chain hash de drand, `maxRound` y el registro con Quicknet pinneado. `Profile` implementa el `PinnedProfile` de la etapa 3.
- **La DateKey** (`lib/src/datekey.dart`): `dk1_` con la aceptación y los textos de Go, también los de su JSON; la resolución de un instante a su ronda y la hora de una ronda; e `Instant`, al nanosegundo, con el RFC 3339 de `time.Parse` y de `Format` de Go.
- **`PUBLIC_HEADER`, `CONTROL_CBOR` de las versiones 1 a 3 y la `.dkk`** (`lib/src/header.dart`, `control.dart` y `accesskey.dart`), en las capas del §69.1. `I_PAYLOAD` y `access_material` se copian una vez y se borran en todos los caminos.
- **Los errores que Go devuelve sin código normativo**, como un código de relleno que no existe o una extensión fuera de su sitio al escribir, son `ArgumentError` con el texto de Go.
- **`lib/datekeys.dart`** exporta los formatos, como `index.ts` de `datekeys-ts`.
- **Vectores de Go** (`tool/formats_go_vectors.go`, en el contexto del módulo de `datekeys-go` en `c531e93`, sin cambiar nada en él): unos 6 400 casos en `test/vectors/formats_*.json`, con el resultado, el código y el texto de Go: tramas, cabeceras, controles, `.dkk`, perfiles, extensiones, `dk1_`, RFC 3339, rondas, relleno hasta L_MAX, la comprobación del relleno de `capsule.Open`, los codificadores, los límites del §57, `BODY`, y cada fallo de una lista, solo y con cada otro, para la precedencia del §69.1. `formats_vectors.g.dart` lleva uno de cada ocho para Node.js. Con el tag `spec-v0.11` el generador da la misma salida, salvo la regla de los codificadores del §72, que ese tag no tiene.
- **Pruebas.** Los 24 fixtures y las 6 `.dkk`; `dk1.json`, `quicknet_rounds.json`, `profile_quicknet.json`, `padding.json` y los 172 esquemas de `cbor.json` que la etapa 1 dejó aplazados; y el diferencial. 404 pruebas nuevas en la VM y 77 en Node.js: 771 y 190 en total, sin ninguna aplazada.
- **Fallos inyectados**, uno a uno y revertidos: 24. Las pruebas detectan 21, en la VM, en Node.js o en las dos; el de Padmé con desplazamientos de más de 31 bits, solo en Node.js, como debe ser. Dos no se detectaban al principio, y por ellos el generador escribe ahora cada par de fallos de capas distintas y cada bit de `FLAGS` y `RESERVED`: una DateKey comprobada antes que la regla entre los arrays de extensiones, y el bit alto de `FLAGS` ignorado. Los otros tres no cambian ningún resultado: quitar la comprobación de la recodificación de `PUBLIC_HEADER` o de `CONTROL_CBOR`, porque sus decodificadores, como los de Go, ya rechazan toda forma no canónica, y leer una longitud con un desplazamiento de 24 bits, porque los operadores de bits compilados a JavaScript dan 32 bits sin signo.
### Etapa 3: BLS12-381 y tlock (05-10-2026)
- **BLS12-381, de código propio,** como lo calcula `kilic/bls12-381` v0.1.0 para `drand/kyber-bls12381` v0.3.4:
- la capa del cuerpo, `Fp`, un extension type sobre `BigInt`, con `FpWide` para las sumas de productos sin reducir; la única que toca la representación, para que unos limbs fijos la puedan sustituir sola;
- Fp2, Fp6 y Fp12 con las fórmulas de kilic, reduciendo cada coeficiente una vez, el Frobenius con sus coeficientes y el cuadrado ciclotómico;
- G1 y G2, su codificación comprimida y los veredictos de `FromCompressed` (§12.2), con `checkCompressedPoint`, que `lib/datekeys.dart` exporta como `datekeys-ts`. El subgrupo de G2 se comprueba con ψ(P) = [x]·P;
- el emparejamiento ate óptimo con las rectas y la exponenciación final de kilic: GT es su valor, serializado c1 antes que c0 en cada nivel;
- el hash a G1 del RFC 9380 con el DST de Quicknet y de tlock, sumando las salidas del mapa en E′ como kilic.
- **El IBE de tlock** (`lib/src/ibe.dart`), `DecryptCCAonG2` y `EncryptCCAonG2` de `drand/kyber` v1.3.2 para Quicknet, como `ibe.ts`: H2 sobre GT en el orden de kilic, H3 con su rechazo de candidatos, H4, la identidad de la ronda y las puertas de la firma y de U, con razones y textos fijos que no llevan ningún valor del cálculo. El cifrado admite un sigma dado, para reproducir byte a byte los vectores de Go; es interno hasta el escritor, la etapa 6.
- **Releases** (`lib/src/release.dart`): `verifyRelease` es `provider.Verify`, en su orden y con sus textos, solo para el scheme de Quicknet, como `release.ts`; `suppliedRelease`; y `fetchRelease`, la regla del paso 9: lo que lance una fuente es `ERR_RELEASE_UNAVAILABLE`. Los exporta `lib/datekeys.dart`, con `PinnedProfile`, lo que lee del perfil, que llega con la etapa 4.
- **El stanza tlock** (`lib/src/tlock.dart`): `unwrapTlockStanza` hace lo que `NewTimeIdentity` y su `Unwrap` con los argumentos y el cuerpo del stanza, y `wrapTlockStanza` lo que `NewTimeRecipient`, con los códigos y los textos de `agewrap`. Es interno: lo usará la apertura de la etapa 4.
- **Diferencias con Go, a propósito,** las de `datekeys-ts`: otro scheme que el de Quicknet da `ERR_UNKNOWN_PROFILE` con un texto propio, y el punto en el infinito nunca es una firma válida, mientras que Go la acepta si la clave pública también es el punto en el infinito, una clave que ningún perfil pinneado tiene.
- **`BigInt` no es de tiempo constante.** Verificar y descifrar solo manejan datos públicos; al cifrar, sigma y r son secretos. El README lo explica.
- **Vectores de Go** en `test/vectors/`, que escriben cuatro programas de `tool/` con las librerías de la caché de módulos, en el contexto del módulo de `datekeys-go` y sin cambiar nada en él:
- `bls12381_vectors.json`: las 157 codificaciones límite de `datekeys-ts` con el veredicto de Go, y decodificaciones, sumas, múltiplos, emparejamientos, hashes a G1, el mapa de un elemento y firmas BLS con una semilla fija;
- `ibe_vectors.json`, port del generador de `datekeys-ts` sobre los fixtures de `testdata/`: GT y H2, H3, H4, identidades de rondas, el stanza de cada fixture con su file key, los ciphertexts de kyber y los veredictos de `DecryptCCAonG2`;
- `tlock_vectors.json`, port también: el cifrado de kyber con sigma fijo y los ciphertexts que escribió `datekeys-ts` y abre Go;
- `release_vectors.json`: los veredictos, códigos y textos de `provider.Verify`, `NewTimeIdentity` con `Unwrap` y `NewTimeRecipient`.
- **Pruebas.** 71 nuevas en la VM y 30 en Node.js. La etapa se hizo en paralelo con la 2, en otra rama, desde la etapa 1; con las dos integradas hay 367 pruebas en la VM, más una aplazada, y 113 en Node.js. Las que leen los vectores llevan `@TestOn('vm')`; en Node.js corren las propiedades de la aritmética y unos pocos vectores de Go copiados en Dart, que una prueba en la VM compara con los JSON. Un emparejamiento tarda allí un cuarto de segundo, así que los bucles sacan menos casos.
- **Fallos inyectados**, uno a uno y revertidos: 25, en la torre, las curvas, el emparejamiento, el hash, el IBE, los releases y el stanza. Las pruebas los detectan todos, en la VM y en Node.js.
- **`tool/bls12381_bench.dart`** mide BLS12-381 y tlock en la VM y compilado a JavaScript: en la VM, un emparejamiento tarda unos 11 ms, la verificación de la firma de una ronda 16 ms y el descifrado de un stanza 20 ms. Las cifras, en el README.
### Etapa 2: primitivas y `age` (05-10-2026)
- **Primitivas, de código propio.** Son internas: `lib/datekeys.dart` no las exporta.
- SHA-256 con su compresión, y HMAC-SHA256 con los estados interior y exterior de la clave calculados una vez. Sobre ellos, HKDF-SHA256 (RFC 5869) y PBKDF2-HMAC-SHA256 (RFC 8018), cuyas iteraciones son dos compresiones sobre palabras: 600 000 iteraciones tardan 1,1 s en la VM, frente a unos 3,5 s con el HMAC de `package:crypto`.
- scrypt (RFC 7914) con Salsa20/8, con las comprobaciones y los textos de `scrypt.Key` de Go.
- ChaCha20, Poly1305 en diez limbs de 13 bits, y ChaCha20-Poly1305 (RFC 8439), con el tag comparado en tiempo constante.
- X25519 (RFC 7748) sobre el cuerpo de TweetNaCl en doubles, con clamping y el secreto todo a ceros rechazado con el texto de `crypto/ecdh`.
- Ed25519 estricto (§29.9), port de `internal/ed25519strict`: `verifyStrict`, `canonical`, `smallOrder` y `onCurve`. La aritmética de los puntos es la de TweetNaCl; los escalares módulo ℓ y `onCurve` usan `BigInt`, que no es de tiempo constante, solo con datos públicos.
- El Base64 de Go, con su modo estricto y el offset de sus errores, y el Bech32 de `age`, con sus textos.
- De `package:crypto` solo se usa SHA-512, en Ed25519.
- **Lectura de `age` v1**, port de `filippo.io/age` v1.3.2 (`lib/src/age.dart`):
- la cabecera y sus límites de `internal/format`, con los textos de error de Go;
- el MAC de la cabecera, sobre la cabecera escrita otra vez como la escribe `age`;
- los stanzas X25519 y scrypt. La identity de scrypt rechaza por defecto un factor de trabajo por encima de 16, como `authorkey` de Go; el de `age` es 22;
- la clave del payload y el STREAM de `internal/stream`, que se descifra según llega el texto cifrado, con los mismos casos de fin de fichero que el lector de Go: sin chunk final, chunk que no se autentica, último chunk vacío y datos tras el final;
- cada fallo es una `AgeException` con el texto de Go y su fase, la cabecera o el payload, para que la etapa 4 dé las razones fijas de `capsule.classify`. La `DateKeysException` de una identity pasa sin cambios.
- **`agewrap`** (`lib/src/agewrap.dart`): las reglas de stanzas de `OUTER_TIME_AGE`, `PAYLOAD_AGE` e `INNER_ACCESS_AGE`, la lectura de los stanzas sin secretos, `PayloadIdentity` y `AccessIdentity`, con los textos y los códigos de Go. `checkTimeStanzas` recibe la ronda, el chain hash y el id del perfil, que llegará con la etapa 4.
- **Vectores de Go** en `test/vectors/`, que escriben `tool/gen_primitive_vectors.go` y `tool/gen_age_vectors.go` con las librerías de la caché de módulos, en el contexto del módulo de `datekeys-go` y sin cambiar nada en él:
- `primitives.json` y su copia en Dart, `primitives.g.dart`: RFC 5869, 7748, 7914, 8032 (por `sign.input` de Go) y 8439, PBKDF2 con el vector del §38.1, los puntos de orden pequeño, Base64 y Bech32. Incluye dos mensajes con los que una suma de productos de Poly1305 pasa de 2^32: un acarreo tomado con un desplazamiento fallaría en la web, y una prueba lo detecta en Node;
- `age.json`: ficheros X25519 y scrypt, sus cortes y manipulaciones, un corpus de cabeceras contra la gramática del §28.1 y el límite de 2 MiB, y las reglas e identities de `agewrap`, con el texto de Go en cada caso;
- `age_fixtures.json`: el `PAYLOAD_AGE` de los 24 fixtures con su `payload_identity`, y el `INNER_ACCESS_AGE` de los 6 `time_and_key`, sacado de `OUTER_TIME_AGE` con el release del fixture.
- **Pruebas.** 296 en la VM y una aplazada, 127 más que en la etapa 1; 83 en Node.js, 27 más. Las de las primitivas y las de `age` sin ficheros corren también compiladas a JavaScript, sin los casos largos (PBKDF2 a 600 000 iteraciones, scrypt con logN 16, X25519 iterada 1000 veces).
- **Fallos inyectados**, uno a uno y revertidos: 46 en las primitivas, `age` y `agewrap`. Las pruebas detectan todos los que cambian un resultado. Cuatro no lo cambian: la máscara de una mitad de rotación de SHA-256 cuyos bits altos solo llegan a sumas enmascaradas, el redondeo de `_car` cuando sus entradas nunca son negativas, el de un acarreo del producto del cuerpo, que conserva el valor, y el acarreo del limb 1 de Poly1305 tomado con desplazamiento, cuya suma no pasa de 2^32.
- **`tool/bench.dart`** mide las primitivas en la VM y compilado a JavaScript; las cifras, en el README.
### El gate con Node.js (05-10-2026)
- `tool/check.sh` corre también `dart test -p node`: las pruebas que no leen ficheros, compiladas a JavaScript, comprueban en cada commit que los enteros son exactos en la web. Lo decidió el autor.
### Etapa 1: errores, bytes, CBOR y DER (05-10-2026)
- **Errores normativos (§69).** `ErrorCode` con los 19 códigos en el orden del spec y `DateKeysException`, con el mensaje `contexto: CÓDIGO` de Go. `wrap` y `withContext` hacen lo que `fmt.Errorf("prefijo: %w")`, y `errorCode` lo que `datekeys.Code`. Una prueba compara el catálogo con las líneas `ERR_` del §69, leído de `datekeys-go` en el tag `spec-v0.11`.
- **Bytes.** Hexadecimal, comparación y concatenación.
- UTF-8 estricto: rechaza lo que rechaza `utf8.Valid` de Go y conserva un U+FEFF inicial. El `Utf8Decoder` de `dart:convert` lo descarta, en la VM y en la web, así que no se usa.
- Un `String` con un surrogate suelto se escribe en UTF-8 generalizado (WTF-8), que no es UTF-8 válido.
- `utf8.DecodeRune` y el `%q` de Go (`strconv.Quote`). La tabla de `strconv.IsPrint` de Go 1.26 se copia de `datekeys-ts`, y una prueba fija el SHA-256 del conjunto de runas.
- **CBOR (§58, §58.1).** Port de `codec/codec.go`: `CborEncoder`, `CborDecoder`, `unmarshalCbor`, `peekSchema`, `checkSchema` y `walkCbor`, con los mismos textos de error.
- Los enteros son exactos en la VM y en la web: un `int` hasta 2^53-1 y un `BigInt` por encima, también en las claves de los mapas y en los textos de error.
- El `Encoder` de Go rechaza el UTF-8 inválido; aquí el único texto inválido es un surrogate suelto, y el error lo cita como Go citaría sus bytes en UTF-8 generalizado.
- `CborEncoder.uint` exige 0..2^53-1, con un texto propio, como en `datekeys-ts`; `uint64` escribe hasta 2^64-1.
- **DER estricto.** Port de `internal/der/der.go` en `601e6d2`, ya del borrador v0.12: `check`, `split`, `content`, `setOfSorted` y `parseTime`. `parseTime` devuelve un `DerTime` con los campos y los segundos Unix, exacto al nanosegundo. Es interno: `lib/datekeys.dart` no lo exporta.
- **Pruebas.** Port de `errors_test.go`, `codec_test.go`, `internal_test.go`, `vectors_test.go` y `der_test.go`, de `cbor.test.ts` y de `bytes.test.ts`, con los textos de error que imprime Go.
- Los objetivos de fuzzing de Go son propiedades con semilla, contra un codificador y un decodificador de referencia escritos aparte, como `internal/cbortest`.
- `testdata/vectors/cbor.json`: los 36 `accept` y los 67 `reject`, con los límites de `walk` y los valores. Los 172 vectores de `schemas` se leen y se aplazan: necesitan los decodificadores de esquema de la etapa 4.
- 169 pruebas en la VM y una aplazada. Las 56 que no leen ficheros pasan también en Node.js (`dart test -p node`); las que leen ficheros llevan `@TestOn('vm')`.
### Etapa 0: el paquete (05-10-2026)
- Paquete `datekeys` en Dart puro, sin Flutter, para Dart 3.13 y sin publicar (`publish_to: none`).
- `testdata/` sincronizado con el tag `spec-v0.11` de `datekeys-go` (`ae33434`): 124 ficheros. Su `testdata/SOURCE.json` es el mismo, byte a byte, que escribe `datekeys-ts` para ese commit.
- `tool/sync_testdata.dart`, el equivalente de `scripts/sync-testdata.mjs`. `test/testdata_test.dart` comprueba la copia y que cada fichero nombre la versión de la especificación.
- `tool/check.sh`, el gate local.
- Dependencias: `package:crypto` en ejecución y `package:test` en desarrollo.
- Licencia Apache-2.0.

Powered by TurnKey Linux.