|
|
2 days ago | |
|---|---|---|
| lib | 2 days ago | |
| test | 2 days ago | |
| testdata | 2 days ago | |
| tool | 2 days ago | |
| .gitattributes | 2 days ago | |
| .gitignore | 2 days ago | |
| CHANGELOG.md | 2 days ago | |
| LICENSE | 2 days ago | |
| README.md | 2 days ago | |
| analysis_options.yaml | 2 days ago | |
| pubspec.lock | 2 days ago | |
| pubspec.yaml | 2 days ago | |
README.md
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 elauthorkeyde Go. El deagees 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_AGEnecesita tlock y llega con la etapa 3; la del perfil, con la etapa 4. Por esocheckTimeStanzasrecibe 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
inthasta 2^53-1 y unBigIntpor encima, como elnumber | bigintdedatekeys-ts; CborDecoder.uintdevuelve unint, porque todos los esquemas acotan sus enteros en 2^53-1, yuint64devuelve unBigInt;- 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 depackage:cryptosolo 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 enanalysis_options.yaml. pubspec.lockva 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, comodatekeys-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
synclee 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.dartcomprueba la copia, y que cada fichero nombrespecVersion.
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.jsonsale igual en cada ejecución.agesaca sus claves y nonces decrypto/rand, así queage.jsonyage_fixtures.jsoncambian en cada ejecución; las pruebas leen lo que esté en el repositorio.- Un fichero
agede 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.