Dart plan v2 and the signature plan: what the review found

PLAN_dart.md records the three decisions of the author (only
package:crypto, reading first, the package datekeys) and adds what the
first version, written on Sonnet, missed: PBKDF2 of 600 000 iterations to
open with a key of words, scrypt for the files of author keys, the Unicode
tables from the Go generator, and that the stage of signatures waits for
the vectors of the branch v0.12. PLAN_firma_go.md marks as superseded the
omission of verification_metadata with a locator, which the approved spec
keeps (43).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
main
dev 6 days ago
parent d60b856207
commit ac47bf6477

@ -1,58 +1,71 @@
# Plan: la librería Dart (`datekeys-dart`) # Plan: la librería Dart (`datekeys-dart`)
*2 de octubre de 2026. El autor hará la app en Flutter, así que la librería del protocolo hace falta en Dart. Este plan es el esqueleto para discutirlo antes de crear el repo; no hay código todavía.* *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».*
## Qué es ## Qué es
Un paquete Dart **puro** (sin Flutter, sin plataforma): 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 de escritorio, y en la web si no usa `dart:io`. Es la tercera implementación del spec v0.11 y, como la de TypeScript, **no genera datos de prueba propios**: se contrasta con `testdata/` de `datekeys-go`, sincronizado a un commit fijo (`spec-v0.11`, `ae33434`). 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: está instalado el SDK de Dart 3.13. No hay Flutter, y la librería no lo necesita; la app sí. Estado de la máquina: está instalado el SDK de Dart 3.13. No hay Flutter, y la librería no lo necesita; la app sí.
## Decidido (2 de octubre)
1. **Dependencias:** solo `package:crypto`, el paquete oficial de dart-lang, para SHA-1, SHA-2 y HMAC. Todo lo demás es código propio:
- HKDF y PBKDF2;
- ChaCha20-Poly1305, X25519 y Ed25519 estricto;
- scrypt;
- BLS12-381 con el IBE de tlock;
- ECDSA, RSA, DER, CMS y CBOR.
2. **Orden:** primero lectura y verificación (etapas 1 a 5) y después el escritor (etapas 6 y 7), como en TypeScript.
3. **Nombre del paquete:** `datekeys`, sin publicar en pub.dev.
## Alcance, por etapas ## Alcance, por etapas
Cada etapa se cierra con sus pruebas contra los vectores y fixtures de Go, como en TypeScript. El orden sigue la dificultad y lo que desbloquea. Cada etapa se cierra con sus pruebas contra los vectores y fixtures de Go, como en TypeScript. El orden sigue la dificultad y lo que desbloquea.
| Etapa | Contenido | Referencia en TypeScript | | Etapa | Contenido | Referencia en TypeScript |
|---|---|---| |---|---|---|
| 0 | Esqueleto del paquete, `dart analyze` y `dart test`, `testdata/` sincronizado con un `SOURCE.json`, licencia Apache-2.0 | `scripts/sync-testdata.mjs` | | 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), DER estricto | `bytes.ts`, `errors.ts`, `cbor.ts`, `der.ts` | | 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, HMAC, HKDF, ChaCha20-Poly1305, X25519, Ed25519 estricto; `age` (stanza X25519, STREAM) | `age.ts`, `agefile.ts`, `x25519.ts`, `ed25519strict.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, verificación de la firma de la ronda | `bls12381.ts`, `ibe.ts`, `tlock.ts`, `release.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` |
| 4 | Formatos: DateKey, perfil, cabecera, control, head, cuerpo, rutas (tablas Unicode 18.0.0), `.dkk`, inspección (pasos 1 a 8) y apertura (9 a 18) | `datekey.ts`, `profile.ts`, `header.ts`, `control.ts`, `head.ts`, `pathrule.ts`, `open.ts`, `open3.ts` | | 4 | Formatos: DateKey, perfil, cabecera, control, head, cuerpo, rutas (tablas Unicode 18.0.0), `.dkk`, llave de palabras, nota, inspección (pasos 1 a 8) y apertura (pasos 9 a 18) | `datekey.ts`, `profile.ts`, `header.ts`, `control.ts`, `head.ts`, `pathrule.ts`, `wordkey.ts`, `note.ts`, `open.ts`, `open3.ts` |
| 5 | Firma y sello: compromisos, `alg` 1 (Ed25519), `alg` 2 (CMS, ECDSA, RSA en `BigInt`) y RFC 3161, y los veredictos | `author.ts`, `cms.ts`, `securitycms.ts`, `security.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, nota pública | `writer.ts`, `encrypt.ts`, `wordkey.ts`, `note.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`), claves de autor `dkauthor1…` | `locator` de Go | | 7 | Localizador y sobre (`datekeys.capsule`) y claves de autor `dkauthor1…`, con su fichero cifrado con scrypt | `locator` y `authorkey` de Go |
Fuera del alcance de la librería: la descarga de releases de drand (el cliente HTTP lo pone la app), el almacenamiento y la interfaz. La librería recibe una fuente de releases, como `OpenOptions.Source` en Go. Queda fuera de la librería:
- 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;
- el almacenamiento y la interfaz.
## La decisión de dependencias (pendiente del autor) La librería recibe una fuente de releases, como `OpenOptions.Source` en Go.
Tu regla es evitar dependencias y enmarcar cada una con precisión. En Dart no existe un equivalente de `@noble/*` que ya iba en el bundle de TypeScript, así que hay que elegir. Lo que hace falta y quién puede darlo: ## Lo que la v1 no tenía en cuenta
| Necesidad | Código propio (puerto de TypeScript) | Paquete |
|---|---|---|
| SHA-1, SHA-2, HMAC | posible | `package:crypto`, oficial de dart-lang |
| HKDF, ChaCha20-Poly1305, X25519, Ed25519 | posible, unas 600 líneas | `package:cryptography` o `package:pointycastle` |
| Ed25519 estricto (las cuatro condiciones del §29.9) | **propio en cualquier caso**: ningún paquete ofrece el perfil estricto | — |
| ECDSA P-256/384/521 y RSA (solo verificar) | propio, con `BigInt` | `package:pointycastle` |
| BLS12-381, emparejamiento, hash a curva, IBE de tlock | **propio en cualquier caso**: no hay un paquete Dart mantenido que lo haga | — |
| CBOR determinista del §58 | propio, como en las otras dos | — |
**Mi recomendación: solo `package:crypto`** (oficial, pequeño, mantenido por el equipo de Dart) **y todo lo demás propio**, como hace Go con la biblioteca estándar y TypeScript con tres paquetes de noble. Razones: lo estricto (Ed25519, DER, CBOR) tiene que ser propio de todos modos; un paquete de criptografía general aceptaría cosas que el perfil rechaza y habría que envolverlo; y menos dependencias son menos superficie en una app que guarda secretos. El coste es real: Ed25519, X25519, ChaCha20-Poly1305 y BLS12-381 propios son unas 2 500 líneas que hay que probar a fondo, y una implementación propia no tiene la aceleración nativa que ofrece `cryptography_flutter`. - **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.
Alternativa: `package:crypto` más `package:cryptography` para ChaCha20-Poly1305, X25519 y Ed25519 (con nuestro perfil estricto por encima), y propio solo BLS12-381, ECDSA, RSA, DER, CMS y CBOR. Menos código, una dependencia más y, con `cryptography_flutter`, mejor rendimiento en móvil. - **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.
## Rendimiento ## Rendimiento
El único cuello de botella real es BLS12-381: abrir una cápsula hace una verificación de la firma de la ronda y un emparejamiento. Con `BigInt` en la VM de Dart tarda del orden de cientos de milisegundos, y bastante más compilado a JavaScript. La app debe llamarlo en un `Isolate` para no congelar la interfaz; la librería no lo hace por sí misma. Hay dos cuellos de botella:
- **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.
## Estructura del repo ## Estructura del repo
`G:\bussines\datekeys\datekeys-dart`, repo independiente, rama `v0.11` para seguir al spec como los otros dos: `G:\bussines\datekeys\datekeys-dart`, repo independiente:
``` ```
datekeys-dart/ datekeys-dart/
pubspec.yaml # name: datekeys; sin Flutter; entorno de Dart 3.x pubspec.yaml # name: datekeys; sin Flutter; entorno de Dart 3.x; solo package:crypto
lib/datekeys.dart # la API pública, como index.ts lib/datekeys.dart # la API pública, como index.ts
lib/src/… # un fichero por módulo de la tabla lib/src/… # un fichero por módulo de la tabla
test/… # dart test, contra testdata/ test/… # dart test, contra testdata/
@ -60,10 +73,6 @@ datekeys-dart/
tool/sync_testdata.dart # equivalente de sync-testdata.mjs tool/sync_testdata.dart # equivalente de sync-testdata.mjs
``` ```
La app Flutter lo usa con `datekeys: { path: ../datekeys-dart }`, o con una dependencia de git del Gitea cuando haya remoto. La app Flutter lo usa con `datekeys: { path: ../datekeys-dart }`, o con una dependencia de git cuando haya remoto.
## Lo que falta decidir
1. **Dependencias:** `package:crypto` solo (recomendado) o `package:crypto` más `package:cryptography`. **Ramas.** La librería sigue la versión del spec que implementa: empieza en `v0.11` y pasa a `v0.12` cuando `datekeys-go` cierre esa versión.
2. **Nombre del paquete:** `datekeys`, sin publicar en pub.dev.
3. **Si las etapas 1 a 5 (lectura y verificación) van antes que el escritor**, como hicimos en TypeScript. Es lo que propongo: una app que abre y verifica cápsulas ya es útil, y el escritor se prueba contra los mismos fixtures.

@ -64,7 +64,7 @@ Por eso:
- Si la cápsula y la llave traen notas distintas, manda la de la cápsula y se avisa de la diferencia. - Si la cápsula y la llave traen notas distintas, manda la de la cápsula y se avisa de la diferencia.
- **Dónde está el `.dkc`, también en la `.dkk` y cifrado para la fecha**, como pidió el autor: una o varias direcciones (`https://…`, `ipfs://…`), dentro de un fichero `age` con un stanza tlock para la ronda de la cápsula, en la misma extensión que la nota. - **Dónde está el `.dkc`, también en la `.dkk` y cifrado para la fecha**, como pidió el autor: una o varias direcciones (`https://…`, `ipfs://…`), dentro de un fichero `age` con un stanza tlock para la ronda de la cápsula, en la misma extensión que la nota.
- Nadie las lee antes de la fecha, tampoco el destinatario. En la fecha, la misma firma de drand descifra las direcciones y abre la cápsula. - Nadie las lee antes de la fecha, tampoco el destinatario. En la fecha, la misma firma de drand descifra las direcciones y abre la cápsula.
- `capsule_digest` va dentro, con las direcciones, y el escritor omite entonces `verification_metadata`. En IPFS, con CIDv1 y bloques raw, un `.dkc` de menos de 256 KiB tiene como dirección su propio SHA-256, y el digest en claro la delataría. La `.dkk` se sigue emparejando con su cápsula por `capsule_id`. - `capsule_digest` va dentro, con las direcciones. *Superado por el sobre opaco de abajo: lo que se guarda fuera tiene otro hash, así que el spec aprobado mantiene `verification_metadata` en la `.dkk` (§43, v0.11). La idea original era omitirlo, porque en IPFS, con CIDv1 y bloques raw, un `.dkc` de menos de 256 KiB tiene como dirección su propio SHA-256 y el digest en claro la delataría.*
- Ocultación, a petición del autor (01-10, `48b496f`): lo que se guarda fuera es solo el resto de un sobre `age` del `.dkc`, sin cabecera (que va en el localizador), así que son bytes sin marca; puede ir dentro de otro fichero, una imagen, un vídeo o cualquiera, y cada dirección dice en qué byte empieza. No es esteganografía: un análisis del huésped ve bytes de más. Solo vale un almacenamiento que conserva el fichero byte a byte. - Ocultación, a petición del autor (01-10, `48b496f`): lo que se guarda fuera es solo el resto de un sobre `age` del `.dkc`, sin cabecera (que va en el localizador), así que son bytes sin marca; puede ir dentro de otro fichero, una imagen, un vídeo o cualquiera, y cada dirección dice en qué byte empieza. No es esteganografía: un análisis del huésped ve bytes de más. Solo vale un almacenamiento que conserva el fichero byte a byte.
- El orden, también con IPFS: crear la cápsula, subirla y obtener su dirección, y solo entonces generar la `.dkk`. Así nunca se entrega una llave que apunta a un sitio vacío. - El orden, también con IPFS: crear la cápsula, subirla y obtener su dirección, y solo entonces generar la `.dkk`. Así nunca se entrega una llave que apunta a un sitio vacío.
- Entre medias, la llave vive solo en memoria, como en la firma en dos tiempos. Como red de seguridad, la página puede guardar antes una `.dkk` sin dirección, que abre la cápsula igual, y completarla después: nada ata la `.dkk` a la cápsula, y cifrar hacia una ronda no exige ningún secreto. - Entre medias, la llave vive solo en memoria, como en la firma en dos tiempos. Como red de seguridad, la página puede guardar antes una `.dkk` sin dirección, que abre la cápsula igual, y completarla después: nada ata la `.dkk` a la cápsula, y cifrar hacia una ronda no exige ningún secreto.
@ -112,7 +112,7 @@ Tres revisores adversariales (criptografía, interoperabilidad y privacidad) rev
- El CDDL y §57: la `data` de una extensión nunca da `ERR_NON_CANONICAL_CBOR`. - El CDDL y §57: la `data` de una extensión nunca da `ERR_NON_CANONICAL_CBOR`.
- §55.2: lo que revelan una firma con certificado, el OCSP, un intermediario y la exportación a VALIDe; el nombre, sin `serialNumber`. - §55.2: lo que revelan una firma con certificado, el OCSP, un intermediario y la exportación a VALIDe; el nombre, sin `serialNumber`.
- §7.9: la firma a ciegas, la aplicación de firma falsa, el firmante coaccionado, las claves robadas y el borrado de una clave guardada. - §7.9: la firma a ciegas, la aplicación de firma falsa, el firmante coaccionado, las claves robadas y el borrado de una clave guardada.
- Flujos: la regla 17 sobre lo definitivo; `capsule_digest` omitido con localizador; §29.2 y la regla 13 alineadas; ni `I_PAYLOAD` ni el contenido en disco mientras se espera una firma; quitar `eContent` y podar la cadena (MAY). - Flujos: la regla 17 sobre lo definitivo; `capsule_digest` omitido con localizador (superado: con el sobre opaco se mantiene, §43); §29.2 y la regla 13 alineadas; ni `I_PAYLOAD` ni el contenido en disco mientras se espera una firma; quitar `eContent` y podar la cadena (MAY).
- Las cifras de §76, la redacción de los prefijos, el mensaje de la firma de prueba, los vectores de lo firmado sobre `format3_single` y una tabla de minúsculas de Unicode 18 para Go y TypeScript. - Las cifras de §76, la redacción de los prefijos, el mensaje de la firma de prueba, los vectores de lo firmado sobre `format3_single` y una tabla de minúsculas de Unicode 18 para Go y TypeScript.
**Código pendiente por la revisión**, antes de los pasos de Go. Hechos los dos primeros, el 01-10: en `datekeys-go`, `150d952` (la tabla) y `2213b8c` (la llave), y en `datekeys-ts`, `c3c124a`. **Código pendiente por la revisión**, antes de los pasos de Go. Hechos los dos primeros, el 01-10: en `datekeys-go`, `150d952` (la tabla) y `2213b8c` (la llave), y en `datekeys-ts`, `c3c124a`.

Loading…
Cancel
Save

Powered by TurnKey Linux.