*v2, 2 de octubre de 2026. El autor hará la app en Flutter, así que la librería del protocolo hace falta en Dart. La v1 de este plan la escribió una sesión hecha por descuido con Sonnet 5.5. La revisión de esa sesión ([spec_v0.11/revision_sesion_1_2_octubre.md](spec_v0.11/revision_sesion_1_2_octubre.md), R3) encontró que le faltaban piezas, y esta versión las añade. Las tres decisiones del autor están en «Decidido».*
Un paquete Dart **puro**: sin Flutter y sin plataforma.
- Implementa el mismo protocolo que `datekeys-go` y `datekeys-ts`, para que la app Flutter lo use como dependencia de ruta o de git.
- Funciona en la VM de Dart, en Flutter (móvil y escritorio) y en la web si no usa `dart:io`.
- Es la tercera implementación del spec. Como la de TypeScript, **no genera datos de prueba propios**: se contrasta con el `testdata/` de `datekeys-go`, sincronizado a un commit fijo.
Estado de la máquina: el 5-10, Flutter está instalado en `C:\dev\flutter`, y su Dart 3.13.0 es el `dart` del PATH. La librería no necesita Flutter; la app sí.
**Hecho:** la etapa 0 (`5aa5eed`), la etapa 1 (`da63bc3` a `10b0895`), la etapa 2 (`0c21f55` a `82fa714`) y la etapa 3 (`cdd1c89` a `b81bdfa`), el 5-10, en la rama `v0.11` de `datekeys-dart`. El estado de cada etapa está en su README.
| 0 | Esqueleto del paquete: `dart analyze` y `dart test`, `testdata/` sincronizado con un `SOURCE.json`, licencia Apache-2.0 | `scripts/sync-testdata.mjs` |
| 1 | Bytes, errores normativos, CBOR determinista (§58) y DER estricto, también el de los tiempos | `bytes.ts`, `errors.ts`, `cbor.ts`, `der.ts` |
| 2 | Primitivas: SHA-1/2 y HMAC (de `package:crypto`); HKDF, PBKDF2, scrypt, ChaCha20-Poly1305, X25519 y Ed25519 estricto; `age` (stanza X25519, stanza scrypt y STREAM) | `age.ts`, `agefile.ts`, `x25519.ts`, `ed25519strict.ts` |
| 3 | BLS12-381 y tlock: emparejamiento, hash a curva, IBE-CCA de drand y verificación de la firma de la ronda | `bls12381.ts`, `ibe.ts`, `tlock.ts`, `release.ts` |
| 5 | Firma y sello: los compromisos, `alg` 1 (Ed25519), `alg` 2 (CMS con el perfil de certificado del §29.10, ECDSA y RSA en `BigInt`) y RFC 3161, y los veredictos con sus textos | `author.ts`, `cms.ts`, `securitycms.ts`, `security.ts` |
| 6 | Escritor: formato 3 con el área de 32 KiB, `time_only` y `time_and_key`, llave de palabras y nota pública | `writer.ts`, `encrypt.ts`, `wordkey.ts`, `note.ts` |
| 7 | Localizador y sobre (`datekeys.capsule`) y claves de autor `dkauthor1…`, con su fichero cifrado con scrypt | `locator` y `authorkey` de Go |
- la descarga de releases de drand, porque el cliente HTTP lo pone la app;
- la descarga del resto de un localizador, también de la app, con las reglas de direcciones del §44.1 y la comprobación de la IP resuelta en cada conexión;
- **PBKDF2-HMAC-SHA256 con 600 000 iteraciones** (§38.1) para abrir con una llave de palabras, que es lectura, no escritura. Con el API general de `package:crypto`, cada iteración reserva memoria y recalcula el estado de las dos claves del HMAC: en un móvil tarda bastantes segundos. Hace falta un HMAC-SHA256 propio que precalcule los estados interno y externo una sola vez y reutilice los búferes; y la app lo llama en un `Isolate`. El vector de §38.1 y los de la página dan la referencia.
- **scrypt** con logN = 16 (§29.12), para leer y escribir los ficheros de clave de autor: el stanza scrypt de `age`, con su límite de factor de trabajo, como `SetMaxWorkFactor(16)` de Go.
- **Las tablas de Unicode 18.0.0** (NFD, minúsculas simples y best-fit) para las reglas de rutas y de texto (§29.5.1) y para la llave de palabras. No se escriben a mano: el generador de Go, `internal/pathrule/gen`, ya escribe el módulo de TypeScript con `-ts`, y hará falta una salida `-dart`. El digest de las tablas recalculado en Dart debe coincidir con `TablesDigest`, como en TypeScript.
- **Los vectores de firma y sello.** En la v0.11 cubrían mucho menos de lo que dice el spec: una implementación con fallos los pasaba todos. La revisión del 2 de octubre los está completando en la rama `v0.12` de `datekeys-go`, con el perfil de certificado del borrador v0.12. La etapa 5 debe sincronizarse con esa versión, no con `spec-v0.11`. Las etapas 0 a 4 no dependen de eso.
- **BLS12-381:** abrir una cápsula verifica la firma de la ronda y hace un emparejamiento. Con `BigInt` en la VM de Dart serán cientos de milisegundos o más, y bastante más compilado a JavaScript.
- **PBKDF2 con 600 000 iteraciones,** solo al abrir o crear con una llave de palabras.
La app debe llamar a los dos en un `Isolate` para no congelar la interfaz. La librería no lo hace por sí misma. Las cifras reales se miden en la etapa 3, en la VM y en un móvil de gama media.
Las etapas 1 y 2 se encargaron a un agente Opus, que trabaja en el propio `datekeys-dart`. La sesión revisa después su trabajo y pasa el gate. Las dos salieron bien así. El encargo de la etapa 2, que se relanzó el 5-10 y cerró el mismo día, fue este:
- HMAC-SHA256 propio, con una compresión SHA-256 propia y los estados interior y exterior calculados una vez, para el PBKDF2 de 600 000 iteraciones;
- HKDF-SHA256, scrypt con Salsa20/8, ChaCha20-Poly1305 con comparación del tag en tiempo constante, X25519 con clamping y secreto todo a ceros rechazado, y Ed25519 estricto (port de `internal/ed25519strict`);
- lectura de `age` v1 portada de `filippo.io/age` v1.3.2: la cabecera y sus límites, el MAC, los stanzas X25519 y scrypt (con factor de trabajo acotado, como `authorkey` de Go), la clave del payload y el STREAM de `internal/stream`, con los mismos casos de fin de fichero que Go, y las reglas de `agewrap` y del §28.1;
- las identidades `AGE-SECRET-KEY-1…` en Bech32;
- errores con la finura suficiente para reproducir en la etapa 4 los textos de Go (ver `agefile.ts` y `mutation-texts.json` en `datekeys-ts`).
- **Exactitud:**
- los enteros siguen la regla de la etapa 1: exactos en la VM y en la web;
- en Poly1305, los productos y las sumas de los limbs quedan por debajo de 2^53;
- la aritmética de 32 bits es correcta también compilada a JavaScript.
- **Vectores:**
- los de las RFC 7748, 8439, 5869, 7914 y 8032 y los de PBKDF2, producidos con las librerías de Go de la caché de módulos, nunca escritos de memoria. El generador va en `tool/` con `//go:build ignore`, y su salida en `test/vectors/`;
- ficheros `age` de `filippo.io/age`, con recipients X25519 y scrypt, cortes y manipulaciones;
-`ed25519_strict.json`;
- el PAYLOAD_AGE de cada fixture, con su `payload_identity`, y el INNER_ACCESS_AGE de los `time_and_key`.
- **Pruebas:**
- las que leen ficheros, con `@TestOn('vm')`;
- pruebas de las primitivas que también corran en Node, con parámetros pequeños;
- al final, fallos inyectados de uno en uno para comprobar que las pruebas los detectan.
- **Informe:** los tiempos en la VM y en Node de X25519, Ed25519, ChaCha20-Poly1305 por MiB, PBKDF2 a 600 000 iteraciones y scrypt con logN 16; y los commits.
- **Reglas:**
- sin dependencias nuevas;
- código, comentarios y commits en inglés, y README y CHANGELOG en español;
-`tool/check.sh` antes de cada commit;
- decir en el código y en el README que `BigInt` no es de tiempo constante;
- no tocar `testdata/`, `datekeys-go` ni `datekeys-ts`.
**Revisión de la etapa 2 (5-10).** La sesión comparó cada módulo con su original de Go, línea a línea: `internal/format`, `decryptHdr`, las identities X25519 y scrypt y `internal/stream` de `age` v1.3.2; `agewrap` y `internal/ed25519strict` de `datekeys-go`; el `scrypt` de `x/crypto`; el Base64 de Go; y el Bech32 de `age`. Comprobó también, contra TweetNaCl, las cotas de los enteros en la web. No encontró ningún fallo. `tool/check.sh` pasó: 296 pruebas en la VM y 83 en Node. Quedan tres notas menores:
- el mensaje de `4850b6b` dice que todos los valores caben en 32 bits, y no es así antes de las máscaras;
- el texto de «mixed case» del Bech32 usa las mayúsculas y minúsculas de Unicode de Dart, como el TypeScript, y puede diferir del de Go con entradas no ASCII que los tres rechazan;
-`age.json` y `age_fixtures.json` cambian en cada ejecución del generador, porque `age` usa `crypto/rand`: son vectores congelados.
**La etapa 3, BLS12-381 y tlock,** siguió el mismo esquema. El 5-10 el autor pidió adelantarla, así que corrió en paralelo con la 2. Su agente trabajó en un worktree aparte, en la rama `stage3`, que salía de `c8bf3c0`. Solo creó ficheros nuevos, y dejó los cambios a los ficheros comunes para un último commit.
La sesión revisó la etapa 3 contra Go y contra el TypeScript:
-`verifyRelease`, contra `provider.Verify` de Go;
-`unwrapTlockStanza`, contra `NewTimeIdentity` y su `Unwrap`;
- el IBE, contra `DecryptCCAonG2`, `EncryptCCAonG2`, `h3` (con el desplazamiento del primer byte y el límite de iteraciones), H2 y H4 de kyber;
- la decodificación de puntos, contra `FromCompressed` de kilic: flags, signo y subgrupo;
-`fetchRelease`, contra `sourceFailure` de `capsule`.
No encontró ningún fallo. La aritmética de la curva la cubren los vectores de Go: GT, hash a G1, firmas y el stanza de cada fixture.
Después la integró: `rebase` sobre `v0.11`, con conflictos solo en el README, el CHANGELOG y `lib/datekeys.dart`, y avance de `v0.11` sin fusión. El gate pasó, con 367 pruebas en la VM, más una aplazada, y 113 en Node. La sesión borró luego el worktree y la rama.
Notas:
- **Diferencias con Go, a propósito, las mismas que en el TypeScript:**
- solo se verifica el scheme de Quicknet;
- una firma en el infinito nunca vale, mientras que Go la acepta si la clave también es el infinito, una clave que ningún perfil pinneado tiene.
- **SHA-256:** BLS12-381 y tlock usan el de `package:crypto`, aunque la etapa 2 ya trae uno propio. Las dos opciones están permitidas, y se puede unificar más adelante.
- **Pendiente:**
- medir en un móvil de gama media;
-`Random.secure` no funciona en `dart test -p node`, así que las dos pruebas que sacan un sigma aleatorio corren solo en la VM.
- **Tiempos en la VM:**
- un emparejamiento, 11 ms;
- la verificación de la firma de una ronda, 16 ms;
- el descifrado del stanza, 20 ms;
- los pasos 10 y 11 juntos, 35 ms.
En Node, unas veinte veces más.
**Siguiente, la etapa 4:** formatos, inspección y apertura. Necesita una salida `-dart` del generador `internal/pathrule/gen` de `datekeys-go`, es decir, un cambio en el repo de Go.