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/README.md

12 KiB

datekeys-dart

Librería Dart del protocolo DateKeys: DateKey, DateKeyCap (.dkc) y DateKeys Access Key (.dkk). Es Dart puro, sin Flutter ni plataforma, para que la app Flutter la use como dependencia de ruta (datekeys: { path: ../datekeys-dart }).

Es la tercera implementación de la especificación, después de la de referencia en Go (datekeys-go) y la de TypeScript (datekeys-ts). Como la de TypeScript, no genera datos de prueba propios: se contrasta con el testdata/ de datekeys-go, y con los vectores de test/vectors/, que calcula Go con los programas de tool/ (ver «Datos de prueba»).

Estado

Están hechas las etapas 0, 1 y 2 del plan (docs/PLAN_dart.md del espacio de trabajo):

  • la etapa 0, el paquete, sus herramientas y testdata/ sincronizado;
  • la etapa 1, los errores normativos, los bytes, el perfil CBOR del §58 y el DER estricto, también el de los tiempos;
  • la etapa 2, las primitivas y la lectura de age.

La etapa 1 porta tres ficheros de datekeys-go en 601e6d2, con las mismas lecturas, las mismas comprobaciones en el mismo orden y los mismos textos de error:

Módulo Contenido En Go
lib/src/errors.dart Los 19 códigos del §69 en su orden (ErrorCode), DateKeysException con el mensaje contexto: CÓDIGO, wrap, withContext y errorCode errors.go
lib/src/bytes.dart Hexadecimal, comparación y concatenación; UTF-8 estricto que conserva un U+FEFF inicial; utf8.DecodeRune y el %q de Go, con la tabla de strconv.IsPrint de Go 1.26 unicode/utf8, strconv
lib/src/cbor.dart CborEncoder, CborDecoder, unmarshalCbor, peekSchema, checkSchema y walkCbor codec/codec.go
lib/src/der.dart check, split, content, setOfSorted y parseTime, con UTCTime y GeneralizedTime exactos al nanosegundo. Es interno, como en Go internal/der/der.go, ya del borrador v0.12

La etapa 2 trae las primitivas, todas de código propio salvo SHA-512, y la lectura de los ficheros age de filippo.io/age v1.3.2. Son internas, como en Go y en datekeys-ts: lib/datekeys.dart no las exporta.

Módulo Contenido En Go
lib/src/sha256.dart SHA-256 con su compresión; HMAC-SHA256 con los estados interior y exterior de la clave calculados una vez; HKDF-SHA256 (RFC 5869); PBKDF2-HMAC-SHA256 (RFC 8018), cuyas iteraciones son dos compresiones sobre palabras, sin bytes entre medias crypto/sha256, crypto/hmac, x/crypto/hkdf, x/crypto/pbkdf2
lib/src/scrypt.dart scrypt (RFC 7914) con Salsa20/8, con las comprobaciones y los textos de scrypt.Key x/crypto/scrypt
lib/src/chacha20poly1305.dart ChaCha20, Poly1305 y ChaCha20-Poly1305 (RFC 8439); el tag se compara en tiempo constante x/crypto/chacha20poly1305
lib/src/curve25519.dart X25519 (RFC 7748) con clamping y el secreto todo a ceros rechazado; la verificación estricta de Ed25519 del §29.9, con canonical, smallOrder y onCurve x/crypto/curve25519, internal/ed25519strict
lib/src/base64.dart El Base64 de Go, con su modo estricto y el offset de sus errores encoding/base64
lib/src/bech32.dart El Bech32 de age, con sus textos de error codec/bech32
lib/src/age.dart La cabecera y sus límites (2 MiB, 1024 stanzas, un tipo y 128 argumentos por stanza), el MAC de la cabecera, los stanzas X25519 y scrypt, la clave del payload y el STREAM, con los mismos casos de fin de fichero que Go. Cada fallo es una AgeException con el texto de Go y su fase: la cabecera (age.Decrypt) o el payload (la lectura del STREAM) filippo.io/age, internal/format, internal/stream
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 y las identities del payload y del acceso, con los textos y los códigos de Go agewrap

Notas de la etapa 2:

  • La identity de scrypt rechaza por defecto un factor de trabajo por encima de 16, el de los ficheros de clave de autor (§29.12), como SetMaxWorkFactor(16) en el authorkey de Go. El de age es 22: 4 GiB de memoria, que un fichero hostil podría pedir a un móvil. ScryptIdentity(maxWorkFactor: …) lo cambia.
  • El STREAM se descifra según llega el texto cifrado (AgePayloadDecryptor), como el lector de Go: entrega el texto de cada chunk en cuanto se autentica, y un fallo que revela un byte posterior llega en la llamada siguiente.
  • La identity de OUTER_TIME_AGE necesita tlock y llega con la etapa 3; la del perfil, con la etapa 4. Por eso checkTimeStanzas recibe la ronda, el chain hash y el id del perfil.

Los enteros son exactos en la VM y en la web. El int de Dart tiene 64 bits con signo en la VM y en la web es un double, exacto hasta 2^53. Por eso la librería no usa un int por encima de 2^53-1, ni desplazamientos u operaciones de bits de más de 31 bits:

  • un entero de CBOR es un int hasta 2^53-1 y un BigInt por encima, como el number | bigint de datekeys-ts;
  • CborDecoder.uint devuelve un int, porque todos los esquemas acotan sus enteros en 2^53-1, y uint64 devuelve un BigInt;
  • SHA-256, ChaCha20 y Salsa20 trabajan con palabras de 32 bits: suman de dos a cinco y enmascaran la suma, y en cada rotación enmascaran la mitad desplazada a la izquierda, así que la VM y la web dan los mismos 32 bits;
  • Poly1305 guarda el acumulador y r en diez limbs de 13 bits: cada producto queda por debajo de 2^31 y cada suma por debajo de 2^35, y sus acarreos se toman con ~/, no con un desplazamiento, que en la web truncaría a 32 bits;
  • el cuerpo de curve25519 es el de TweetNaCl en su versión de JavaScript: dieciséis limbs de 16 bits en un Float64List, con sumas de productos por debajo de 2^44.

BigInt no es de tiempo constante. La librería lo usa solo con datos públicos: la reducción de escalares módulo ℓ y las comprobaciones de onCurve en Ed25519, que solo verifica, y el factor de trabajo de un stanza scrypt. Los secretos (el escalar de X25519, las claves de HMAC, ChaCha20 y Poly1305) van en la aritmética de limbs, sin ramas ni índices que dependan de ellos; ni la VM ni un motor de JavaScript prometen tiempo constante, aun así.

Las etapas siguientes traen el resto del protocolo en este orden:

Etapa Contenido
3 BLS12-381 y tlock
4 Formatos, inspección (pasos 1 a 8) y apertura (pasos 9 a 18)
5 Firma y sello, con los textos de los veredictos
6 Escritor
7 Localizador y claves de autor

Rendimiento

dart run tool/bench.dart mide las primitivas en la VM; compilado a JavaScript, dart compile js -O2 -o bench.js tool/bench.dart && node bench.js. Las cifras del 5 de octubre de 2026, en el PC de desarrollo (Windows 11, Dart 3.13, Node.js 24.9):

Operación VM Node.js
X25519, un acuerdo de claves 1,2 ms 0,9 ms
Ed25519 estricto, una verificación 4,5 ms 9,4 ms
ChaCha20-Poly1305, 1 MiB, cifrar o descifrar 21 ms 16 ms
PBKDF2-HMAC-SHA256, 600 000 iteraciones (§38.1) 1,1 s 0,9 s
scrypt con logN 16 y r 8 (§29.12) 0,55 s 0,9 s

En la VM, el HMAC de package:crypto, que calcula otra vez los estados de la clave en cada llamada, tarda unos 3,5 s en las mismas 600 000 iteraciones.

La app llama a PBKDF2 y a scrypt en un Isolate (plan, «Rendimiento»). Las cifras en un móvil de gama media se miden en la etapa 3.

Versiones

Número Dónde Hoy
Librería version de lib/src/version.dart y de pubspec.yaml, que una prueba mantiene iguales 0.1.0-dev
Especificación specVersion de lib/src/version.dart: la que nombra el campo spec de cada fichero de testdata/ 0.11

La rama sigue la versión de la especificación: v0.11, hasta que datekeys-go cierre la v0.12. La etapa 5 se sincronizará ya con la v0.12.

Dependencias

  • En ejecución: solo package:crypto, para SHA-1, SHA-2 y HMAC. Todo lo demás es código propio: HKDF, PBKDF2, scrypt, ChaCha20-Poly1305, X25519, Ed25519, BLS12-381, ECDSA, RSA, DER, CMS y CBOR. Hoy la librería usa de package:crypto solo SHA-512, en Ed25519: SHA-256 y HMAC-SHA256 son propios, porque PBKDF2 los necesita con los estados de la clave calculados una vez.
  • En desarrollo: solo package:test, aprobado el 5 de octubre de 2026. No hay paquete de lints: las reglas del análisis están en analysis_options.yaml.
  • pubspec.lock va en el repositorio, para que las pruebas usen siempre las mismas versiones.

Nada nuevo sin aprobación escrita del autor.

Comprobar

dart pub get
tool/check.sh

tool/check.sh es el gate local, como scripts/check.sh en datekeys-go y npm run verify en datekeys-ts. Comprueba:

  • el formato;
  • dart analyze --fatal-infos;
  • dart test;
  • dart test -p node: las pruebas que no leen ficheros corren también compiladas a JavaScript, en Node.js, para comprobar que los enteros son exactos en la web. Las que leen ficheros llevan @TestOn('vm'). Por eso el gate necesita Node.js, como datekeys-ts; lo decidió el autor el 5 de octubre de 2026;
  • la copia de testdata/ frente al repositorio de Go, que debe estar al lado, en ../datekeys-go.

Datos de prueba

testdata/ es una copia de datekeys-go/testdata en un commit fijo. testdata/SOURCE.json registra el commit y el SHA-256 de cada fichero, igual que en datekeys-ts. La copia actual es la del tag spec-v0.11 (ae33434).

dart run tool/sync_testdata.dart sync --commit spec-v0.11
dart run tool/sync_testdata.dart check --against ../datekeys-go
  • sync lee los ficheros con git en ese commit, así que nunca entran cambios sin commit del repositorio de Go.
  • Los ficheros de testdata/ no se editan ni se generan aquí.
  • En cada dart test, test/testdata_test.dart comprueba la copia, y que cada fichero nombre specVersion.

test/vectors/ tiene los vectores de las primitivas y de age. Los escribe Go, con las librerías de la caché de módulos que usan datekeys-go y filippo.io/age; ningún valor esperado se escribe a mano. Los generadores van en tool/, con //go:build ignore, y se ejecutan en el contexto del módulo de datekeys-go, sin cambiar nada en él:

cd ../datekeys-go && go run ../datekeys-dart/tool/gen_primitive_vectors.go -out ../datekeys-dart/test/vectors
cd ../datekeys-go && go run ../datekeys-dart/tool/gen_age_vectors.go -out ../datekeys-dart/test/vectors
Fichero Contenido
primitives.json SHA-256, HMAC, HKDF (RFC 5869), PBKDF2 (con el vector del §38.1), scrypt (RFC 7914), ChaCha20, Poly1305 y ChaCha20-Poly1305 (RFC 8439), X25519 (RFC 7748, BoringSSL y los puntos de orden pequeño), Ed25519 (las 64 primeras líneas de sign.input de Go, cuyas tres primeras son las de la RFC 8032, y sus mutaciones), el Base64 de Go y el Bech32 de age
primitives.g.dart El mismo JSON como constante de Dart, para las pruebas compiladas a JavaScript, que no leen ficheros
age.json Ficheros con stanzas 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 del error de Go en cada caso
age_fixtures.json El PAYLOAD_AGE de cada fixture con su payload_identity, y el INNER_ACCESS_AGE de los time_and_key, que Go saca de OUTER_TIME_AGE con el release del fixture
  • Los fixtures que leen los generadores son los de testdata/ de este repositorio, la copia sincronizada.
  • primitives.json sale igual en cada ejecución. age saca sus claves y nonces de crypto/rand, así que age.json y age_fixtures.json cambian en cada ejecución; las pruebas leen lo que esté en el repositorio.
  • Un fichero age de más de un chunk se guarda como su cabecera, su nonce y su file key: la prueba cifra otra vez el texto documentado y comprueba el SHA-256 del fichero entero antes de leerlo. Las cabeceras de megabytes se escriben como partes que se repiten.

Licencia

Apache-2.0 (LICENSE), como datekeys-go y datekeys-ts. La especificación tiene su propia licencia, CC-BY-4.0.

Powered by TurnKey Linux.