- **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.
- 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.
-`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')`.
- 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.