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

814 lines
104 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 a 5 del plan (`docs/PLAN_dart.md` del espacio de trabajo), las partes 6a y 6b de la etapa 6 y la etapa 7:
- 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 3, BLS12-381 y tlock: el emparejamiento, el hash a G1, el IBE de drand y la verificación de la firma de la ronda;
- la etapa 4, en tres partes:
- la 4a, las reglas de rutas y de textos con las tablas de Unicode 18.0.0, y la llave de palabras;
- la 4b, los formatos de la cápsula y de la llave de acceso: las tramas, `PUBLIC_HEADER`, `CONTROL_CBOR` de los tres formatos, la `.dkk`, las extensiones, el Provider Profile, la DateKey con sus rondas y el relleno;
- la 4c, el head del formato 3, la nota pública, la inspección (pasos 1 a 8) y la apertura (pasos 9 a 18) de los tres formatos;
- la etapa 5, en tres partes:
- la 5b, los compromisos de lo que firma un autor y sella un sello, `SECURITY_CBOR`, la firma de `alg` 1 y los veredictos con sus textos y sus líneas;
- la 5a, el lector de las firmas CMS y de los sellos RFC 3161 con el perfil de certificado del borrador v0.12, ECDSA y RSA. Es interno;
- la 5c, la firma de `alg` 2 y el sello de `seal_type` 2 en los veredictos, que une el lector de la 5a a la evaluación de la 5b;
- de la etapa 6, la parte 6a: la escritura de `age`, con la fuente inyectable de lo aleatorio, los recipients X25519, scrypt y tlock y las longitudes de un fichero antes de escribirlo. Es interna: la usan el escritor de la cápsula, la parte 6b, y las claves de autor, la parte 7b.
- la parte 6b de la etapa 6, el escritor de cápsulas del formato 3: `time_only` y `time_and_key`, los 16 huecos con señuelos, la `.dkk`, la llave de palabras, la nota pública, el área de seguridad con la firma y el sello, el relleno y las autocomprobaciones del §62.1, con las fuentes leídas en streaming.
- la etapa 7, en dos partes:
- la 7a, el localizador y el sobre de `datekeys.capsule`: los datos de la extensión, la lectura y la apertura del localizador, las reglas de sus direcciones y de la IP a la que resuelve un nombre, y el resto del sobre;
- la 7b, las claves de autor `dkauthor1…`, con su firma Ed25519 y su fichero cifrado con scrypt, y el sellado del localizador y la creación del sobre, sobre el escritor de `age` de la 6a.
La librería ya abre cápsulas reales, de los tres formatos, con todas sus credenciales, y evalúa toda su área de seguridad como Go: la firma de `alg` 1, la de `alg` 2 con certificados, el sello de `seal_type` 2 y todos los veredictos de su forma. Y ya escribe cápsulas del formato 3, con todas sus credenciales, su firma y su sello, byte a byte como Go con los mismos valores aleatorios, y Go las abre. Firma con una clave de autor y sella localizadores.
Las etapas 2 y 3 se hicieron en paralelo, en ramas aparte desde la etapa 1, y se integraron el 5 de octubre de 2026. Las partes 4a y 4b también: la 4a, en la rama `stage4a`, se integró encima de la 4b el mismo día. La 4c se hizo después, en la rama `v0.11`, y la 5b también, en paralelo con la 5a, el lector de CMS, que se hizo en la rama `stage5a` y se integró encima de la 5b el mismo día. La 5c, que necesitaba las dos, se hizo después, en la rama `v0.11`. La 6a se hizo en la rama `v0.11`, en paralelo con la 7a, la lectura del localizador, en la rama `stage7a`. La 7a se integró encima de la 6a el mismo día. La 6b, el escritor de cápsulas, se hizo en la rama `v0.11`, en paralelo con la 7b, las claves de autor y el sellado del localizador, en la rama `stage7b`, desde `b23a0ee`. La 7b se integró encima de la 6b el mismo día.
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`** se compone en la etapa 4, con el perfil: `checkTimeStanzas` de esta etapa, que recibe la ronda, el chain hash y el id del perfil, y `checkTlockProfile` y `unwrapTlockStanza` de la etapa 3.
La etapa 3 porta BLS12-381 de `kilic/bls12-381` v0.1.0, sobre el que calcula `drand/kyber-bls12381` v0.3.4 para drand y tlock; el IBE de `encrypt/ibe` de `drand/kyber` v1.3.2, el de `tlock` v1.2.0 con Quicknet; y `provider.Verify` y el stanza tlock de `agewrap` de `datekeys-go`, con las mismas comprobaciones en el mismo orden y los mismos textos de error. Como `datekeys-ts`, verifica solo el scheme de Quicknet, `bls-unchained-g1-rfc9380`. Todo es código propio: de `package:crypto` usa solo SHA-256.
| Módulo | Contenido | En Go |
|---|---|---|
| `lib/src/bls12381_fp.dart` | El cuerpo Fp sobre `BigInt` (`Fp`) y las sumas de productos sin reducir (`FpWide`): la única capa que toca la representación de un elemento | `fp.go` de kilic |
| `lib/src/bls12381_tower.dart` | Fp2, Fp6 y Fp12 con las fórmulas de kilic, cada coeficiente reducido una vez; el Frobenius y el cuadrado ciclotómico; GT serializado c1 antes que c0 en cada nivel | `fp2.go`, `fp6.go`, `fp12.go` de kilic |
| `lib/src/bls12381_curve.dart` | G1 y G2 en coordenadas jacobianas, su codificación comprimida y los veredictos de `FromCompressed` (§12.2): `checkCompressedPoint` | `g1.go`, `g2.go` de kilic |
| `lib/src/bls12381_pairing.dart` | El emparejamiento ate óptimo: el bucle de Miller con las rectas de kilic y su exponenciación final | `pairing.go` de kilic |
| `lib/src/bls12381_hash.dart` | `expand_message_xmd`, `hash_to_field`, el mapa SWU simplificado, la isogenia de grado 11 y el cofactor: el hash a G1 del RFC 9380 con el DST de Quicknet | `hash_to_field.go`, `swu.go`, `isogeny.go` de kilic |
| `lib/src/ibe.dart` | El IBE-CCA de tlock sobre G2 (§63 paso 11): H2, H3 con su rechazo de candidatos, H4, la identidad de la ronda, el descifrado y el cifrado, este con sigma inyectable | `encrypt/ibe` de kyber |
| `lib/src/release.dart` | `verifyRelease` (§17, §51, §63 paso 10), `Release`, `ReleaseSource`, `suppliedRelease` y `fetchRelease`, la regla del paso 9 | `provider` |
| `lib/src/tlock.dart` | El stanza tlock de `OUTER_TIME_AGE` (§32, §35, §63 paso 11): abrirlo, como `NewTimeIdentity` y su `Unwrap`, y escribirlo, como `NewTimeRecipient` | `agewrap` |
Notas de la etapa 3:
- **La capa del cuerpo.** `Fp` es un extension type sobre `BigInt`, sin envoltorio en ejecución, y `FpWide` una suma de productos sin reducir. La torre, las curvas, el emparejamiento y el hash solo usan sus operaciones: una implementación con limbs fijos, más rápida, cambiaría ese fichero y nada más.
- **Los veredictos de un punto** son los de `FromCompressed` de kilic: el flag de compresión a 1; el punto en el infinito solo como `0xc0` y ceros; coordenadas menores que p, sin reducir; un punto de la curva; y un punto del subgrupo de orden r. En G1 el subgrupo se comprueba con [r]·P = O; en G2, con ψ(P) = [x]·P (Scott, eprint 2021/1130, como noble), que las pruebas comparan con [r]·P = O en puntos fuera del subgrupo, también con torsión de cada orden pequeño del cofactor.
- **GT** es el valor del emparejamiento de kilic, con su exponenciación final, f^(3·(p⁴ − p² + 1)/r), y se serializa en su orden (§63, «Serialización de GT en H2»).
- **El hash a G1** sigue a kilic, que suma las dos salidas del mapa en E′ antes de la isogenia. kilic se aparta del RFC 9380 solo donde ningún hash llega: dos salidas iguales u opuestas, que suma con la fórmula de E. Este código las suma con la ley de grupo de E′, como el RFC.
- **Las diferencias con Go, a propósito,** son las de `datekeys-ts`: otro scheme que el de Quicknet da `ERR_UNKNOWN_PROFILE` con un texto propio, donde Go verificaría los otros schemes de drand; y el punto en el infinito nunca es una firma válida (§63 paso 10), 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 (§12.1). Las pruebas fijan las dos.
- **El stanza tlock.** `unwrapTlockStanza` recibe los argumentos y el cuerpo del único stanza y hace lo que `NewTimeIdentity` y su `Unwrap` en Go: el perfil, los argumentos, otra vez el release y el cuerpo. El número y el tipo de los stanzas los comprueban las reglas de `agewrap` de la etapa 2, que necesitan la cabecera entera. Se guarda el último release verificado: el paso 11 vuelve a verificar el del paso 10, como en Go, sin otro emparejamiento.
- **La fuente de releases.** La librería no trae cliente HTTP: recibe una `ReleaseSource`, como `OpenOptions.Source` en Go. `fetchRelease` aplica la regla del paso 9: lo que lance la fuente es `ERR_RELEASE_UNAVAILABLE`, con su texto y ningún otro código.
- **Lo que se exporta.** `lib/datekeys.dart` exporta la verificación de releases y `checkCompressedPoint`, como `datekeys-ts`. El IBE y el stanza tlock son internos: los usará la apertura de la etapa 4. El cifrado no se exporta todavía: es del escritor, la etapa 6.
La parte 4a de la etapa 4 porta `internal/pathrule` y `wordkey` de `datekeys-go` en `c531e93`, la cabeza de la rama `v0.12`, que sigue la v0.11 con las correcciones de la revisión del 2 de octubre; los dos paquetes no cambian desde el tag `spec-v0.11`. Tienen las mismas comprobaciones, en el mismo orden y con los mismos textos de error:
| Módulo | Contenido | En Go |
|---|---|---|
| `lib/src/pathrule_tables.dart` | Las tablas de Unicode 18.0.0 y de WindowsBestFit del §29.5.1, que escribe `internal/pathrule/gen -dart` de `datekeys-go`; no se editan | `internal/pathrule/tables.go` |
| `lib/src/pathrule.dart` | NFD con el orden canónico y la descomposición de Hangul, el pliegue de CaseFolding con U+0131 → U+0069, la minúscula simple, `Default_Ignorable` con la lista blanca de R4 y las proyecciones best-fit; las reglas de una ruta (R2 a R6c y R10), las del árbol (R7 con su clave, y R9) y las de los textos del §29.6, el comentario y el autor declarado; y `canonicalTables`, cuyo SHA-256 es `tablesDigest` | `internal/pathrule` |
| `lib/src/wordkey.dart` | La llave de palabras del §38.1: `normalizeWords`, `checkWords`, `wordKey`, con el PBKDF2-HMAC-SHA256 de `sha256.dart` y 600 000 iteraciones sobre `wordKeyPassword` y `wordKeySalt`, la P y la S del §38.1, y `wordIdentity` | `wordkey` |
Notas de la parte 4a:
- **Bytes, como Go.** Go lee un string como bytes, y estas funciones también:
- las acabadas en `Utf8` toman los bytes de un string de Go. Un byte que no es UTF-8 válido es la runa U+FFFD, como en el `for range` de Go. Los límites cuentan bytes, salvo el de R3 sobre el NFD, en unidades UTF-16, como dice el spec, y el de R6b, en puntos de código;
- las demás toman un `String` como lo escribe `utf8Bytes`, que pone un surrogate suelto como los tres bytes de su punto de código, y dan lo que da Go con esos bytes.
Un lector solo ve UTF-8 válido (§58), y un escritor rechaza antes un `String` mal formado, como el de Go. Aun así, cualquier entrada da el resultado de Go, rarezas incluidas. R4b busca el final de un segmento sumando el tamaño de cada runa, así que Go cuenta cada byte no válido como los tres bytes de U+FFFD: acepta los bytes `ff 61 e2 80 8d`, con el ZWJ al final, y dice «U+200D at the end» de un ZWJ que no lo está. Los vectores lo fijan.
- **Ninguna función de Unicode de la plataforma** (§29.5.1): ni `toLowerCase` ni `RegExp`. La expresión regular de R6b se comprueba a mano: la base acaba en `~` y de 1 a 6 dígitos ASCII.
- **Los errores.** `PathRuleException` es `pathrule.Error`: la regla, el detalle y, en R7, las dos rutas que nombra, la posterior primero. No lleva código normativo: la cabecera lo dará como `ERR_HEAD_INVALID` y la nota pública como `ERR_EXTENSION_DATA_INVALID`, en la etapa 4c. Como en Go, cada regla devuelve su violación y solo las funciones públicas lanzan. `WordKeyException` lleva el texto de `wordkey.Check`.
- **La ronda** de la llave de palabras es un `int` de 0 a 2^53-1, como toda ronda de un perfil pinneado; en Go es un `uint64`.
- **Una diferencia con `datekeys-ts`:** con un `String` mal formado, que ningún lector ni escritor pasa a estas reglas, `datekeys-ts` toma un surrogate suelto como un punto de código; aquí son los tres bytes que daría Go.
- **Lo que se exporta.** `lib/datekeys.dart` exporta la llave de palabras, como el paquete público `wordkey` de Go: `normalizeWords`, `checkWords`, `wordKey`, `WordKeyException` y sus constantes. La CLI de Go y `datekeys-ts` derivan la llave fuera de la apertura y la pasan como una identity más. `wordIdentity` es interna, porque devuelve una identity de `age.dart`; las reglas de rutas y textos también lo son, como `internal/pathrule`, y las usarán la cabecera y la nota de la etapa 4c.
La parte 4b de la etapa 4 porta los formatos de la cápsula y de la llave de acceso de `datekeys-go` en `c531e93` (la rama `v0.12`: la v0.11 con los arreglos de la revisión del 2 de octubre), con las mismas comprobaciones en el mismo orden, las mismas capas del §69.1 y los mismos textos de error. El API sigue al de `datekeys-ts`:
| Módulo | Contenido | En Go |
|---|---|---|
| `lib/src/framing.dart` | El PRELUDE y la trama de la `.dkk` (`parsePrelude`, `splitAccessKey`), `splitCapsule` con los pasos 1 a 3 de la inspección y `headerBinding`; `CapsuleFormat` | `capsule/framing.go`, `inspect.go`; `accesskey.Decode` |
| `lib/src/padding.dart` | `PaddingRule`, `paddedLength`, `payloadAgeLength` y `PaddingCheck`, la comprobación del plaintext frente a L y P | `capsule/padding.go`; `checkPadding` de `open.go` |
| `lib/src/body.dart` | La trama de `BODY` y el área del formato 3, sin el head | `ParseBodyFrame`, `CheckArea` de `format3.go` |
| `lib/src/digest.dart` | El SHA-256 incremental del `capsule_digest` y su comparación | `checkCapsuleDigest` de `open.go` |
| `lib/src/extension.dart` | Los arrays de extensiones, los registros con sus lugares, `checkCritical`, `checkNoncritical` y la regla de los codificadores del §72 (`checkWrite`, `StandardExtensions`) | `extension` |
| `lib/src/schema.dart` | Lo común de los decodificadores de objetos: la clave de un fallo, las claves obligatorias, los arrays de extensiones | `required`, `encodeExtensions` de `capsule` |
| `lib/src/profile.dart` | El Provider Profile: su CBOR, `profile_hash`, el §12.1, `maxRound`, Quicknet y el registro | `profile` |
| `lib/src/datekey.dart` | `dk1_`, las rondas y sus horas, `Instant` y el RFC 3339 de Go | `datekey`; `time.Parse`, `Format` |
| `lib/src/header.dart` | `PUBLIC_HEADER` y `AccessPolicy` | `DecodeHeader`, `EncodeHeader` |
| `lib/src/control.dart` | `CONTROL_CBOR` de las versiones 1, 2 y 3 | `DecodeControl`, `EncodeControl` |
| `lib/src/accesskey.dart` | La `.dkk`: su cuerpo, su trama y su escritura con la regla del §72 | `accesskey` |
Notas de la etapa 4b:
- **Los enums.** El formato, la regla de relleno y la política son enums de Dart (`CapsuleFormat`, `PaddingRule`, `AccessPolicy`), donde Go y `datekeys-ts` usan números. La regla de relleno no se llama `Padding`, que es un widget de Flutter.
- **Los errores sin código.** Lo que Go devuelve sin código normativo, como un código de relleno que no existe al codificar, un L por encima de L_MAX en `paddedLength` o una extensión de la especificación fuera de su sitio al escribir, es un `ArgumentError` con el texto de Go: un error del llamador.
- **Las extensiones de la especificación.** `StandardExtensions` es el `Standard` de Go. Las reglas de texto de la nota necesitan las tablas de las rutas, y la `data` de `datekeys.capsule` es del localizador: se le dan sus comprobaciones (`validateNote`, `validateCapsule`), y sin ellas solo se comprueba que haya `data`, como el `Standard` de Go sin `ValidateCapsule`.
- **Los enteros.** Una ronda, L y P son un `int` hasta 2^53-1, exactos en la web; un entero de ocho bytes se lee como dos mitades de 32 bits, y Padmé se calcula con potencias de dos, sin desplazamientos.
- **Un nombre con un surrogate suelto** se cita en los textos como Go citaría sus bytes en UTF-8 generalizado (`\xed\xa0\x80`), como en la etapa 1; `datekeys-ts` escribe U+FFFD. Go no tiene esos nombres.
- **La rama `v0.12` de Go frente al tag `spec-v0.11`.** Para estos paquetes, la única diferencia es la regla de los codificadores del §72: en `spec-v0.11`, `accesskey.Encode` escribe `datekeys.note` en una `.dkk` y `datekeys.capsule` en un array crítico. El generador da la misma salida en las dos para todo lo demás.
- **Lo que se exporta.** `lib/datekeys.dart` exporta los formatos, como `index.ts` de `datekeys-ts`. La trama de `BODY`, el digest y lo común de los esquemas son internos: los usa la parte 4c, con el head, la nota, la inspección y la apertura.
La parte 4c de la etapa 4 porta el head, la nota pública, la inspección y la apertura de `datekeys-go` en `c531e93`, con las mismas comprobaciones en el mismo orden, los mismos códigos y pasos y los mismos textos de error, también los del detalle de cada paso. El API sigue al de `datekeys-ts` y a `OpenOptions` y `Opened` de Go:
| Módulo | Contenido | En Go |
|---|---|---|
| `lib/src/note.dart` | La nota pública del §24.1: `checkNote`, `checkNoteData`, `newNote`, `publicNote` y `unusableNote`, y `Header.publicNote` y `Header.unusableNote` | `CheckNote`, `NewNote` y `Note` de `extension`; `Header.PublicNote` y `UnusableNote` |
| `lib/src/head.dart` | El head del formato 3 (§29.4 a §29.6) en sus capas: R1 y R8 en la tercera, las reglas de rutas, del comentario y del autor de `pathrule` en la cuarta como `ERR_HEAD_INVALID`, sus extensiones y el final frente a `CONTENT`; y su codificación | `DecodeHead`, `EncodeHead` y `CheckHeadEnd` de `format3.go` |
| `lib/src/inspect.dart` | Los pasos 1 a 8 (`inspectCapsule`, `inspectCapsuleSource`), `Inspection` con la comprobación de cada paso, `inspectedLength`, `maxAccessKeyRead` y la vista de `datekeys inspect -json` (`inspectView`, `inspectJson`) | `inspect.go`; `internal/inspectview` |
| `lib/src/source.dart` | `ByteSource`, una cápsula que se lee por tramos, y `BytesSource`, la de unos bytes en memoria | `io.ReadSeeker` |
| `lib/src/open.dart` | Los pasos 9 a 18 (`openCapsule`, `openCapsuleSource`), `OpenOptions` y `Opened`: las credenciales y el release (9), su verificación (10), `OUTER_TIME_AGE` (11), la estructura frente a la política (12), `INNER_ACCESS_AGE` (13), `CONTROL_CBOR` (14), `header_binding` (15), `I_PAYLOAD` y P (16), `PAYLOAD_AGE` en streaming (17) y la publicación (18) | `open.go` |
| `lib/src/open3.dart` | El paso 17 del formato 3: la trama de `BODY` y el área, el head, cada fichero al sink con su SHA-256 y el relleno, con la precedencia del §63 | `open3.go` |
| `lib/src/sink.dart` | `ByteSink`, que recibe el contenido de los formatos 1 y 2 y cada fichero, y `FileSink`, que recibe los ficheros del formato 3; `MemoryByteSink` y `MemoryFileSink` | `dst` y `Sink` de `capsule.Open` |
| `lib/src/verdicts.dart` | `Verdict`, `Verdicts` y el punto de enganche de la etapa 5: `SecurityEvaluator`, con su `SecurityInput`, y `notEvaluated` | `Verdicts` y `newSecurityContext` |
Notas de la parte 4c:
- **La entrada** es una cápsula en memoria (`openCapsule`) o una `ByteSource` (`openCapsuleSource`), que la app adapta de un `RandomAccessFile` o de un `Blob`. De una fuente solo se leen los primeros `inspectedLength` bytes, y 16 más para el nonce de `PAYLOAD_AGE`, antes de pedir el release; el `capsule_digest` de una `.dkk` se calcula leyendo la fuente por tramos de 1 MiB, y `PAYLOAD_AGE` llega por tramos de 16 chunks. Nada se reserva según L, P ni las longitudes de `BODY` (§57). Lo que lance la fuente sale del `openCapsuleSource` como un error del llamador, nunca como un veredicto sobre la cápsula, como en `datekeys-ts`.
- **La salida.** El contenido de los formatos 1 y 2 va a `OpenOptions.output`, un `ByteSink`, según `age` autentica cada chunk, y los ficheros del formato 3 a `OpenOptions.sink`, un `FileSink`, en el orden del head. Los dos son obligatorios para su formato: sin ellos, la apertura lanza un `ArgumentError` justo después del paso 2, antes de pedir nada, como `ErrSinkRequired` y `errWriterRequired` de Go. La salida se cierra solo al publicar y se aborta tras cualquier fallo, también de un paso anterior al 17 y también para una cápsula del formato 3; el sink se aborta tras cualquier fallo posterior a su `begin` (§56). Lo que reciben es suyo: copias, nunca vistas de un búfer que la apertura siga leyendo.
- **El resultado.** `openCapsule` no lanza por una cápsula inválida: el fallo está en `Opened.error` y es la última comprobación, con el texto de Go. `Opened.checks` son las de todos los pasos, con el detalle que Go escribe en cada uno.
- **Las credenciales.** `OpenOptions.identities` son identities X25519 en bruto, de 32 bytes: las del llamador y la llave de palabras, que `wordKey` deriva de la cadena, la ronda y el `capsule_id` que da la inspección, como hace la CLI de Go. La `.dkk` va decodificada (`accessKey`) o todavía codificada (`accessKeyFile`), que se decodifica en el paso 9.a y solo para `time_and_key`. La apertura no borra las credenciales del llamador; sí sus copias, la `.dkk` que decodifica, `I_PAYLOAD` y las claves de cada fichero `age`.
- **La firma y el sello** son de la etapa 5. El área de `security` se lee solo hasta donde lo exigen la trama de `BODY` y el área, y sus veredictos los da `OpenOptions.evaluator`, que recibe lo que toman `newSecurityContext` y `EvaluateSecurityIn` de Go: `SECURITY_CBOR`, los bytes del head, el control decodificado y el formato, el `round_time` y las claves de autor. Se llama, como en Go, al leer el head y antes de decodificarlo. Desde la parte 5b es `evaluateSecurityInput`; `notEvaluated` da `Verdicts.notEvaluated`, para quien no muestra veredictos; uno que lance da `Verdicts.failed` y la cápsula se abre igual (§29.3). `OpenOptions.accept`, el `Accept` de Go, ve los veredictos tras todas las comprobaciones del paso 17 y antes del 18: si lanza, no se publica nada, el sink se aborta y `Opened.refusal` guarda lo que lanzó.
- **`StandardExtensions`** comprueba ya la nota con las reglas del §24.1, como el `Standard` de Go. El parámetro `validateNote` de la parte 4b, que podía sustituirlas, ya no existe.
- **Las diferencias con Go** son de forma: Go da `Opened.PayloadLength` también tras un fallo (los bytes escritos), y aquí `payloadLength` es solo el de una cápsula abierta; y una identity que no mide 32 bytes es un `ArgumentError` antes de empezar, donde Go recibe objetos `age.Identity`.
- **Un fallo de dart2js** de Dart 3.13, ajeno a la librería, que conviene saber en la web: si un objeto llega al campo de otro a través de `c ? null : objeto`, el compilador puede perder las escrituras que reciba allí y leer después sus campos con el valor inicial. Le pasó a `tool/open_bench.dart` con un sink que cuenta bytes, y se reproduce sin la librería. Para pasar el sink o la salida a `OpenOptions`, mejor sin ese condicional.
La parte 5b de la etapa 5 porta el área de seguridad del formato 3 de `datekeys-go` en `c531e93`, el borrador v0.12, salvo lo que necesita el lector de CMS de las partes 5a y 5c. El API sigue al de Go:
| Módulo | Contenido | En Go |
|---|---|---|
| `lib/src/author.dart` | Lo que firma un autor y sella un sello (§29.8, §29.11): `payloadCommit`, `controlCommit` sobre `CONTROL_SIG` en cada formato, `headDigest`, `signersDigest`, `authorMessage`, sus 99 bytes y su prefijo, `authorCode`, `sigPart` y `sealSubject` | `signature.go` |
| `lib/src/security.dart` | `SECURITY_CBOR` (§29.3) con sus esquemas y sus límites, sus codificadores y sus lectores (`decodeSecurity`, `decodeAuthorSignature`, `decodeSeal`); `evaluateSecurity`, con la firma de `alg` 1; `SecurityContext`, `securityContext` y `evaluateSecurityInput`, el evaluador de la apertura; `holderText`, el nombre de un certificado del §29.7; y `CmsEvaluator`, la frontera con el lector de CMS | `format3.go`, `signature.go`, `newSecurityContext` de `open3.go`, `holderText` de `signature2.go` |
| `lib/src/verdicts.dart` | Los textos de los veredictos (`Verdict.text`), sus líneas (`Verdicts.lines`), el sello más temprano (`Verdicts.sealedAt`), `Detail`, `SignerLine` y `SignerResult` | `Verdict.Text`, `Verdicts.Lines`, `SealedAt`, `Detail` y `SignerLine` de `format3.go` |
Notas de la parte 5b:
- **El área nunca decide la apertura** (§29.3). `evaluateSecurity` no lanza: X para un mapa exterior que falla su capa 2 o 3, la versión 2 incluida; y para la firma y el sello por separado, la primera fila de la tabla del §29.7 que se cumple. Un fallo dentro de una parte, sea cual sea, es de esa parte, F1 para la firma y S2 para el sello, como Go recupera un panic. Sin contexto lee como un lector de la v0.10: toda firma es F1 y un sello de `seal_type` 2, S1.
- **`alg` 1** se verifica con `verifyStrict`, y la clave se busca entre las guardadas por su cadena `dkauthor1…`, exacta, como en Go: una guardada en mayúsculas no coincide. Las claves de autor y su fichero son de la etapa 7.
- **La frontera con la parte 5c es `CmsEvaluator`:**
- `evaluateSignature(signers, value, hasSeal, context)` da null, que es F1, o F2, F5 o F6 con el `Detail` de los firmantes, como `evaluateCMS` de Go;
- `evaluateSeal(token, signature, context)` da de S1 a S5, y con S4 y S5 la autoridad y t, como `evaluateSeal` de Go.
`evaluateSecurity` lo recibe en `cms`. Sin él, la firma de `alg` 2 y el sello de `seal_type` 2 en un contexto quedan en null, sin evaluar, y la otra parte se evalúa igual: `Verdicts.evaluated` es falso y las líneas son las de la otra parte. Lo que lance el lector, o un veredicto fuera de su rango, es F1 o S2. La 5c lo implementa en `securitycms.dart`, `cmsReader`, que es desde entonces el `cms` por defecto; `holderText` estaba ya aquí para ella.
- **Las líneas** son las de Go, byte a byte: los nombres de un certificado entre « y », la autoridad de cada sello en las de F6 con su aviso, los resultados en español y t en RFC 3339 con la fracción del sello. Partir cada línea en filas con ` ↳ ` (§29.7) es de la CLI de Go (`cmd/datekeys/present.go`), no del paquete `capsule`: no se porta, y lo hará la app en una salida de texto.
- **El contexto de la apertura** es `newSecurityContext` con el `head_digest`: un control cuyo `CONTROL_SIG` no se puede codificar deja `control_commit` a cero, como en Go, y la firma no verifica.
- **`authorCode`** toma los bytes del mensaje como Go toma los de un string: un byte que no es UTF-8 se lee como U+FFFD.
- **Lo que se exporta.** `lib/datekeys.dart` exporta `author.dart` y `security.dart` enteros, como el paquete `capsule` de Go; `datekeys-ts` los deja internos.
La parte 5a de la etapa 5 porta `internal/cms` de `datekeys-go` en `c531e93`, la cabeza de la rama `v0.12`, con el perfil de certificado del borrador v0.12 (§29.7, §29.10 y §29.11), y la ECDSA y la RSA de Go que usa, con las mismas lecturas, las mismas comprobaciones en el mismo orden, los mismos resultados y los mismos textos de error:
| Módulo | Contenido | En Go |
|---|---|---|
| `lib/src/nist_curves.dart` | P-256, P-384 y P-521 sobre `BigInt`: la lectura de un punto sin comprimir y u1·G + u2·Q en coordenadas jacobianas, con el truco de Shamir | `ecdsa.ParseUncompressedPublicKey`, `crypto/internal/fips140/nistec` |
| `lib/src/ecdsa.dart` | `verifyEcdsaAsn1`: la firma leída como la lee `cryptobyte`, r y s entre 1 y n − 1 sin reducirlos, una s por encima de n/2 aceptada y el hash cortado a los bits del orden | `ecdsa.VerifyASN1` |
| `lib/src/rsa.dart` | `verifyPkcs1v15`, que construye la codificación y la compara entera, y `verifyPss`, con MGF1 y una sal de la longitud del hash; `Sha2`, los hashes de la tabla, con el `DigestInfo` de cada uno | `rsa.VerifyPKCS1v15`, `rsa.VerifyPSS` |
| `lib/src/cms.dart` | `parseCert` con el perfil del §29.10, campo a campo, y `Cert` con `holder`, `issuerName` y `validAt`; `parseSignature`, `SignedData` y `SignerInfo` con `check`, que da un `CmsResult`; `parseToken` y `Token` con `check`; `addDuration`, t más la precisión; y los errores: `CmsFormException`, `CmsAlgorithmException` y `CertificateException` | `internal/cms` |
Notas de la parte 5a:
- **Lo que trae.** El lector de la firma de `alg` 2 y del sello de `seal_type` 2. Los compromisos, `SECURITY_CBOR`, la firma de `alg` 1 y los veredictos con sus textos son de la parte 5b, y la unión de este lector con los veredictos, de la 5c: `evaluateCMS` y `evaluateSeal`, con la línea de cada firmante.
- **Los nombres** siguen a los de Go, para importar el módulo con prefijo (`import 'cms.dart' as cms;`): `ErrForm` y `ErrAlgorithm` son `CmsFormException` y `CmsAlgorithmException`, las dos de la clase sellada `CmsException`, y el `Result` de Go es `CmsResult`, porque `CheckResult` ya es de la inspección. Cada error lleva el texto de `err.Error()` de Go.
- **El perfil del certificado.** Los identificadores de objeto se comparan por los bytes de su DER, así que un arco de 2^64 + 3 no es el 3 de nadie; un SET OF puede repetir un elemento; el titular sale de `givenName` y `surname` si los dos tienen texto no vacío, antes que del `commonName`; el emisor, del `commonName` o, si no tiene uno con texto, del `organizationName`. Como Go, y no como `datekeys-ts`, `parseCert` no comprueba antes que los bytes sean DER: dentro de una firma siempre lo son.
- **Las curvas** son las tres de la tabla del §29.10, P-521 incluida, porque Go la acepta. Las claves RSA miden de 2048 a 4096 bits, con un módulo impar y un exponente impar de 3 a 2³¹ − 1; una clave de otro esquema que el algoritmo da `invalid`, como en Go.
- **Los tiempos** son `Instant`, al nanosegundo. La precisión de un token es una `Duration`: como mucho 2³¹ − 1 segundos, 999 milisegundos y 999 microsegundos, cuyos microsegundos quedan por debajo de 2^53, exactos en la web. `addDuration` da t más la precisión, lo que el §29.7 compara con `round_time`.
- **Los vectores** salen de la rama `v0.12` de Go en `c531e93`, en una exportación suya. La 5a se hizo con `testdata/` todavía en `spec-v0.11`, así que toma del `security_cms.json` y de los fixtures del borrador lo que necesita a través de su generador, de la exportación.
- **Interno**, como `internal/cms`: `lib/datekeys.dart` no lo exporta.
La parte 5c de la etapa 5 porta `signature2.go` de `datekeys-go` en `c531e93`, el borrador v0.12: une el lector de CMS de la 5a a la evaluación de la 5b, con el mismo orden de comprobaciones, los mismos veredictos, el mismo resultado de cada firmante y el mismo detalle, que las líneas de la 5b escriben como Go:
| Módulo | Contenido | En Go |
|---|---|---|
| `lib/src/securitycms.dart` | `cmsReader`, el `CmsEvaluator` por defecto: la firma de `alg` 2 (`SIGNERS`, la `SignedData`, la línea de cada firmante exigido y de cada ajeno, y F2, F5 o F6 con su detalle) y el sello de `seal_type` 2 (de S1 a S5, con la autoridad y t); `encodeSigners` y `maxSigners` | `evaluateCMS`, `signerLine`, `evaluateSeal`, `EncodeSigners` y `MaxSigners` de `signature2.go` |
Notas de la parte 5c:
- **El orden de las comprobaciones** es el de Go y el del §29.10: `SIGNERS`, con su perfil y como mucho 16 entradas, antes que la `SignedData`, y un fallo de cualquiera de los dos es F1. Después, cada firmante exigido, en el orden de `SIGNERS`, y cada ajeno, en el orden de la codificación, con su resultado: no verificable, inválido, sin sello, con el sello inválido, fuera de validez o válido. El sello de un firmante es su token, leído con el perfil del §29.11 sobre el valor de su firma: S2, S1 o S3 es un sello inválido, y su `messageImprint` puede usar cualquier hash de la tabla. F2 va antes que F5, y una clave 3 junto a una firma de `alg` 2 es F5.
- **La validez de un certificado** se comprueba en t, el instante de su sello, con los dos extremos incluidos y al nanosegundo (§29.10, paso 6), como `ValidAt(tok.GenTime)` de Go: ni en `round_time` ni en el reloj de la apertura. La de la autoridad, en `genTime`, la comprueba el lector de la 5a.
- **Antes de la fecha de apertura** quiere decir t más la precisión estrictamente antes de `round_time`. Sin `round_time` ningún sello es anterior, y un `round_time` en el instante cero de Go, 0001-01-01T00:00:00Z, cuenta como ninguno, como `IsZero` en Go. `Verdicts.sealedAt` tampoco cuenta un sello en ese instante, como `SealedAt`; su línea lo muestra igual.
- **Lo que lanza el lector de CMS** con una firma o un token que incumple su perfil, una `CmsException`, es un veredicto: F1, un sello inválido, S2 o S1. Cualquier otra cosa llega a `evaluateSecurity`, que la hace un fallo de su parte, F1 o S2, como Go recupera un panic.
- **El evaluador por defecto.** `cmsReader` es el `cms` por defecto de `evaluateSecurity`, y con él el de `evaluateSecurityInput` y de la apertura: nada que Go evalúe queda sin evaluar. Con `cms: null` la firma de `alg` 2 y el sello de `seal_type` 2 quedan en null, para quien no quiera pagar su coste, por ejemplo en la web (ver «Rendimiento»).
- **Lo que se exporta** es lo del paquete `capsule` de Go: `encodeSigners` y `maxSigners`, y `cmsReader`. `cms.dart` sigue interno.
La parte 6a de la etapa 6 porta la escritura de `age` de `filippo.io/age` v1.3.2 y los recipients de `agewrap` de `datekeys-go` en `c531e93`, con las mismas comprobaciones en el mismo orden, los mismos textos de error y los mismos valores aleatorios, sacados en el mismo orden: con los mismos valores, escribe los mismos bytes que Go. Es lo que necesitan el escritor de la cápsula, la parte 6b, y las claves de autor, la etapa 7b:
| Módulo | Contenido | En Go |
|---|---|---|
| `lib/src/random.dart` | `RandomSource`, la fuente inyectable de todo lo aleatorio de un escritor; `secureRandom`, la de por defecto, el CSPRNG de la plataforma (`Random.secure`); `SeededRandomSource`, determinista, solo para pruebas y vectores; `randomIndex`, un entero uniforme como lo saca `crypto/rand.Int`, y `permute`, como el `permute` de `capsule.Encrypt` | `crypto/rand`; `permute` de `capsule/encrypt.go` |
| `lib/src/age_writer.dart` | `AgeEncryptor` y `ageEncrypt`, `age.Encrypt` con la cabecera, su MAC y el nonce; `AgePayloadEncryptor`, el STREAM según llega el texto, en chunks de 64 KiB; `AgeRecipient`, un recipient con sus etiquetas; `marshalAgeHeader`; y las longitudes, `ageStreamLength`, `ageStanzaLength`, `ageHeaderLength` y `ageFileLength` | `age.go`, `primitives.go`, `internal/format` e `internal/stream` de `age` |
| `lib/src/recipient.dart` | `X25519Recipient`, con sus cadenas `age1…`; `ScryptRecipient`; `TimeRecipient`, el de tlock; `checkX25519Recipient`, las reglas del §37; `generateX25519Identity`; `rawX25519Identity` y `rawX25519Recipient`; y la longitud de cada stanza, `x25519StanzaLength`, `scryptStanzaLength` y `tlockStanzaLength` | `x25519.go` y `scrypt.go` de `age`; `TimeRecipient`, `CheckX25519Recipient`, `RawX25519Identity` y `RawX25519Recipient` de `agewrap` |
Notas de la parte 6a:
- **Lo aleatorio** sale todo de una `RandomSource`, `secureRandom` por defecto: la file key, el secreto efímero de cada stanza X25519, la sal y la etiqueta de un stanza scrypt, sigma y la etiqueta del stanza tlock, y el nonce del payload. `AgeEncryptor` los saca en el orden de `age.Encrypt`: la file key, lo de cada recipient, en su orden, y el nonce. Por eso, con `SeededRandomSource`, el keystream de ChaCha20 bajo el SHA-256 de una semilla, escribe los mismos bytes que Go cuando su `crypto/rand` lee el mismo keystream. También sigma de tlock sale de la fuente: `encryptOnG2` de `ibe.dart` y `wrapTlockStanza` de `tlock.dart` la reciben, con `secureRandom` por defecto, y ya no tienen un `Random.secure` propio.
- **El STREAM** se cifra según llega el texto (`AgePayloadEncryptor`), como el `EncryptWriter` de Go: un chunk lleno solo se cifra cuando llega un byte más, porque hasta entonces puede ser el último. Así el último chunk es completo cuando el texto es un múltiplo de 64 KiB distinto de cero, y vacío solo cuando el texto lo es. El fichero son la cabecera, el nonce y los chunks que devuelven `add` y `close`, en ese orden, y la memoria no crece con su tamaño.
- **Las etiquetas** son las de Go: X25519 no tiene; scrypt tiene una al azar, para no mezclarse con ningún otro recipient, otro scrypt incluido; y tlock, `datekeys-tlock-` y 16 bytes al azar, para que `OUTER_TIME_AGE` lleve solo su stanza. `age.Encrypt` rechaza un recipient con otras etiquetas que el primero, con su texto, que las cita.
- **Los errores** de `age.Encrypt` son `AgeException`, con el texto de Go: ningún recipient, etiquetas que no se pueden mezclar, un recipient que no envuelve la file key (`failed to wrap key for recipient #i: …`, y una clave X25519 de orden bajo da el texto de `crypto/ecdh`) y stanzas que no se pueden escribir. Una `DateKeysException` de un recipient conserva su código, con el prefijo de Go. `checkX25519Recipient` lanza un `ArgumentError` con el texto de `agewrap`, porque en Go no tiene código normativo: es un error del llamador. Usar el STREAM tras `close` es un `StateError`, con el texto de Go.
- **Las reglas del §37.** `X25519Recipient` acepta cualquier clave de 32 bytes, como `age`, y una de orden bajo falla al envolver la file key. Las que un escritor no debe usar, una no canónica o una de orden bajo, las rechaza `checkX25519Recipient`, como `agewrap.CheckX25519Recipient`.
- **Las longitudes** de un fichero salen de la de su texto y de la forma de sus stanzas, sin cifrar nada (`ageFileLength`): un stanza X25519 mide 98 bytes; uno scrypt, 78 más las cifras del factor de trabajo; y el de tlock, 249 más las cifras de la ronda (§62.1). La parte 6b puede sacar de ahí `SEALED_CONTROL_LEN` antes de sellar el control, sin el sellado de prueba de Go, que en Node.js costaría medio segundo más.
- **El tiempo constante.** El cifrado del stanza tlock hace su aritmética con `BigInt`, que no es de tiempo constante, sobre sigma y r, que son secretos; el autor lo aceptó el 6 de octubre de 2026. X25519, ChaCha20-Poly1305 y scrypt son los de la etapa 2.
- **`Random.secure` en Node.js** no está disponible dentro de `dart test -p node`, así que las pruebas compiladas a JavaScript usan `SeededRandomSource`. Fuera de las pruebas, con `globalThis.self = globalThis`, sí lo está.
- **Lo que se exporta:** nada. La escritura de `age` es interna, como su lectura y `agewrap`; el escritor de la parte 6b exportará lo que necesite la app.
La parte 6b de la etapa 6 porta el escritor de cápsulas del formato 3 de `datekeys-go` en `c531e93`: `EncryptFiles`, `sealer` y el área de seguridad de `encrypt.go` y `encrypt3.go`, con las mismas comprobaciones en el mismo orden, los mismos códigos, los mismos textos de error y los mismos valores aleatorios, sacados en el mismo orden. Con los mismos valores, escribe los mismos bytes que Go:
| Módulo | Contenido | En Go |
|---|---|---|
| `lib/src/encrypt3.dart` | `encryptFiles`, con `EncryptOptions`, `FileSource`, `EncryptResult` y `CapsuleWriteException`; y los enganches de la firma y del sello: `AuthorSigner` (`alg` 1), que es la `AuthorKey` de la 7b, `CmsSigner` (`alg` 2) y `Sealer` (`seal_type` 2) | `EncryptFiles`, `EncryptOptions`, `Source`, `Result`, `AuthorKey`, `CMSSigner` y `Sealer` de `capsule` |
Notas de la parte 6b:
- **Lo que escribe:**
- `time_only`, o `time_and_key` con de 1 a 16 credenciales: recipients X25519, una `.dkk` nueva y la llave de palabras. La llave se deriva cuando sale `capsule_id`, que la sala;
- los 16 huecos de `INNER_ACCESS_AGE`, con un señuelo en cada hueco libre y en un orden al azar, con el `permute` de la 6a;
- el head: los ficheros en el orden de los bytes de sus rutas, su mtime cuando cae entre 1970 y 9999, el comentario con sus CR LF y sus CR sueltos como LF, y el autor declarado;
- la nota pública y las extensiones de `PUBLIC_HEADER`, `CONTROL_CBOR` y el head, con la regla del §72;
- el relleno Reforzado, el de por defecto, o Bloque256;
- el área de 32 KiB; la de 64 KiB con `largeArea`, solo cuando lo firmado no cabe; o la de `testAreaLen`, solo para un generador de vectores.
- **Dos lecturas en streaming.** `FileSource.open` da un `Stream` del fichero desde su principio, y el escritor lo llama dos veces. La primera lectura saca el SHA-256 de cada fichero. La segunda, después de la firma, lo cifra chunk a chunk hacia el sink, y un fichero que cambió de tamaño o de SHA-256 hace fallar la escritura (regla 18). La memoria no crece con el tamaño de los ficheros: cada trozo de la fuente pasa al STREAM de `age` y de ahí al sink. Una prueba lo comprueba con una fuente y un sink lentos.
- **El sink** es un `ByteSink`, el de la apertura. Recibe el PRELUDE, `PUBLIC_HEADER`, `SEALED_CONTROL` y `PAYLOAD_AGE` por trozos, cada trozo suyo, y se cierra cuando la cápsula está completa y pasó sus autocomprobaciones. Ante cualquier fallo se aborta, y lo que recibió es una cápsula parcial que hay que descartar (regla 9). Nada le llega antes de la segunda lectura, que va después de la firma. Go escribe en un `io.Writer`, y no lo cierra.
- **La longitud de `SEALED_CONTROL`.** El PRELUDE la lleva y `header_binding` cubre el PRELUDE, así que hace falta antes de sellar el control. Go la mide sellando un control de prueba de la misma longitud. Aquí sale de la longitud del control y de la forma de sus stanzas (`ageFileLength` de la 6a), sin ese sellado, que en Node.js costaría medio segundo más. Los valores aleatorios que Go saca para él se sacan y se tiran, uno a uno, para que los siguientes sean los de Go; el sellado real se comprueba contra esa longitud.
- **Las autocomprobaciones del §62.1** son las de Go:
- `PUBLIC_HEADER`, `CONTROL_CBOR` y el head se decodifican con las reglas del lector;
- `INNER_ACCESS_AGE` lleva 16 stanzas X25519 distintos, y la `.dkk` abre uno solo, que da el control;
- `PAYLOAD_AGE` mide lo que da P, e `I_PAYLOAD` abre su cabecera;
- la trama de `BODY` y el final del head frente a `CONTENT`;
- el área de seguridad se evalúa con el lector de esta librería, en el contexto de la cápsula, y debe dar los veredictos que se pidieron.
- **La firma y el sello:**
- `authorKey`, un `AuthorSigner` como la `AuthorKey` de la 7b, firma `AUTHOR_MESSAGE` con `alg` 1, y la firma se comprueba con el perfil estricto antes de escribir nada;
- `CmsSigner` recibe `AUTHOR_MESSAGE` y devuelve la firma CMS, que debe estar completa, con un sello por firmante (F6);
- `Sealer` sella `SEAL_SUBJECT` después de la firma; un sello S5 vale también, porque el reloj del escritor y el de la autoridad pueden diferir.
Los tres pueden devolver un `Future`: la persona firma fuera, con su aplicación, el tiempo que necesite. Lo firmado no depende del área, así que el área se elige después y nadie firma dos veces. La librería aún no tiene clave Ed25519 propia: la trae la parte 7b, y se conectará al integrar las dos. Hasta entonces, las pruebas dan al escritor las firmas, las firmas CMS y los sellos que sacó Go para los mismos mensajes, y comprueban que los pide para esos mensajes.
- **El reloj** es `EncryptOptions.now`, obligatorio: la librería no lee la hora.
- **Los errores** llevan los textos de Go, con los nombres de las opciones en Go (`EncryptOptions.TestAreaLen`, `LargeArea`, `AuthorKey`…):
- una `DateKeysException` con su código;
- un `ArgumentError` para lo que Go rechaza sin código antes de leer un fichero: un error del llamador, como en el resto de la librería;
- una `CapsuleWriteException` para lo demás sin código: una fuente que falla o cambia, un enganche que falla, un área que no cabe o una autocomprobación.
Lo que lanza una fuente o un enganche va dentro, con su texto, y una `DateKeysException` conserva su código, como el `%w` de Go.
- **Lo que no se porta:**
- `Encrypt`, el escritor del formato 2, que solo un generador de vectores puede usar (§62.1, regla 1);
- lo que en Dart no puede pasar: un nil con tipo en una interfaz, un perfil o un reloj que falten, una política desconocida, `Length` y un recipient que no sea X25519. Los tipos de `EncryptOptions` lo descartan.
Un `String` mal formado en una ruta, el comentario o el autor no es UTF-8 válido, con el texto de Go para esos bytes.
- **Lo que se exporta.** `lib/datekeys.dart` exporta `encrypt3.dart` y lo que necesitan sus opciones: `X25519Recipient` y `checkX25519Recipient`, y `RandomSource` y `secureRandom`.
La parte 7a de la etapa 7 porta el paquete `locator` de `datekeys-go` en `c531e93`, el borrador v0.12 (§43 a §44.1, con el cambio 6 del §76), salvo `Seal`, con las mismas comprobaciones en el mismo orden, los mismos códigos y los mismos textos de error:
| Módulo | Contenido | En Go |
|---|---|---|
| `lib/src/locator.dart` | Los datos de `datekeys.capsule` (`CapsuleInfo`, `parseCapsuleInfo`, `CapsuleInfo.toExtension` y `checkCapsuleData`); las direcciones (`LocatorAddress` con su `host`, y `checkAddressUri`) y la IP a la que resuelve un nombre (`checkResolvedIp`); el localizador (`Locator`, `unmarshalLocator`, `Locator.marshal`, `locatorPlaintextLength` y `Locator.usable`) y su apertura (`openLocator` y `CapsuleInfo.openLocator`); y el sobre (`Locator.restIn`, `Locator.openEnvelope`, `hideRest` y `splitEnvelope`) | `locator.go`, `open.go` y `hide.go` de `locator` |
| `lib/src/ipaddr.dart` | Las direcciones IP como las lee `netip.ParseAddr` y las escribe su `String`, y `publicIP` con los bloques del §44.1 | `net/netip`; `publicIP` de `locator.go` |
Notas de la parte 7a:
- **Lo que queda fuera.** La librería no descarga nada. La app pide el resto solo cuando la persona lo pide, después de mostrarle el host o el CID (`LocatorAddress.host`), y solo a una dirección que acepte `checkAddressUri` (`Locator.usable`); no sigue una redirección a una dirección que `checkAddressUri` rechace; comprueba con `checkResolvedIp`, en cada conexión, que la IP a la que resuelve un nombre es pública; lee solo los bytes del resto, y se los da a `Locator.openEnvelope`, que comprueba su SHA-256, descifra el `.dkc` y comprueba el suyo (§44.1).
- **`checkResolvedIp`** no está en Go, cuyo lector no descarga. Recibe los 4 o los 16 bytes de la dirección, los de `InternetAddress.rawAddress`, y la clasifica como `publicIP`: una IPv4 mapeada en IPv6 no es pública, así que la app pasa una IPv4 como sus 4 bytes. En una red móvil solo IPv6, el NAT64 del sistema da a un nombre con solo IPv4 una dirección de `64:ff9b::/96`, que el §44.1 no deja usar.
- **Sellar y crear un sobre.** `Seal` y el cifrado `age` de `NewEnvelope` necesitan el escritor de `age` de la etapa 6: son de la parte 7b. `splitEnvelope` es el resto de `NewEnvelope`: parte el fichero `age` del sobre en su cabecera y su resto, con los textos de `headerEnd`.
- **Las direcciones** se leen como están escritas, sin decodificar nada, sobre sus bytes UTF-8, como lee Go un string: el primer byte que RFC 3986 no admite se cita como lo cita el `%q` de Go, una runa (0xc3 es `'Ã'`). Las IP las lee y las clasifica `ipaddr.dart`, código propio con la aceptación exacta de `netip.ParseAddr`, nunca `InternetAddress` de `dart:io`, que acepta otras notaciones; una IPv6 son 16 bytes, y cada bloque se compara byte a byte.
- **Dos rarezas de Go que se conservan,** porque los vectores son los de Go: el host de una IPv6 es `strings.Trim(host, "[]")`, así que `https://[[2000::]/` vale; e `isCIDv1` mira que los bits sobrantes sean cero, no cuántos son, así que un CID con un carácter más cuyos bits son cero vale también, con otro texto que el canónico. Son de Go, no del texto del §44.1: si Go las corrige, sus vectores lo dirán.
- **La apertura** lee, como Go con `io.LimitReader`, como mucho 1 MiB del texto del localizador: un chunk de STREAM después de ese MiB no se descifra ni se comprueba, y lo leído no tiene la longitud de un localizador.
- **Los errores no llevan código normativo,** como en Go: un localizador que no se lee o no se abre, o una dirección fuera de las reglas, es inutilizable, y su error es una `LocatorException` con el texto de Go. Los datos de la extensión llevan un código solo, `ERR_EXTENSION_DATA_INVALID`, que la hace inutilizable a ella y nunca a la `.dkk` (§54).
- **`StandardExtensions`** comprueba por defecto la `data` de `datekeys.capsule` con `checkCapsuleData`, como `locator.Standard` de Go: al abrir una `.dkk` con ella, una extensión `datekeys.capsule` que no se lee queda inutilizable, con el texto de Go. Con `validateCapsule: null` solo comprueba que haya `data`, como el `extension.Standard` de Go sin `ValidateCapsule`: es el registro con el que el escritor de la `.dkk` comprueba lo que escribe, como el de Go, porque el codificador de `datekeys.capsule`, `CapsuleInfo.toExtension`, lee lo que escribe.
- **Los tipos de Go.** Un `Locator` tiene claves y resúmenes de 32 bytes, y ningún tamaño ni desplazamiento negativo, como los arrays y los `uint64` de Go: lo contrario es un `ArgumentError`. `CapsuleInfo.openLocator` usa el registro de perfiles por defecto, Quicknet, si no se le da uno, como la apertura de una cápsula; Go lo exige.
- **Lo que se exporta.** `lib/datekeys.dart` exporta `locator.dart` entero, como el paquete público `locator` de Go. `ipaddr.dart` es interno.
La parte 7b de la etapa 7 porta el paquete `authorkey` de `datekeys-go` en `c531e93`, el borrador v0.12 (§29.9, §29.12), y `Seal` y `NewEnvelope` del paquete `locator` (§44.1), con las mismas comprobaciones en el mismo orden y los mismos textos de error. Con los mismos valores aleatorios escribe los mismos bytes que Go:
| Módulo | Contenido | En Go |
|---|---|---|
| `lib/src/authorkey.dart` | `AuthorKey`, con `generate`, `fromSeed`, `publicKey`, `publicString`, `sign`, `clear`, `secret` y un `toString` que la oculta; `authorPublicString`, `parseAuthorPublic` y `parseAuthorSecret`, también sobre los bytes de un string de Go (`parseAuthorPublicUtf8`, `parseAuthorSecretUtf8`); `marshalAuthorKey`; `encryptAuthorKey`, con scrypt de logN 16; `readAuthorKey`; y `AuthorKeyException` | `authorkey` |
| `lib/src/ed25519_sign.dart` | La firma Ed25519 (RFC 8032, 5.1.5 y 5.1.6): `ed25519PublicKey` y `ed25519Sign`, el `crypto_sign` de TweetNaCl con el SHA-512 de `package:crypto`, y la reducción módulo ℓ de TweetNaCl, `modL` | `crypto/ed25519` |
| `lib/src/go_unicode.dart` | Los puntos de código que cambian `unicode.ToLower` y `unicode.ToUpper` de Go 1.26.8, Unicode 15.0.0, y los espacios de `unicode.IsSpace`, que escribe `tool/go_unicode_tables.go`; no se edita | `unicode` |
| `lib/src/locator_seal.dart` | `sealLocator`, `Seal` de Go, y `newEnvelope`, `NewEnvelope` de Go, sobre `TimeRecipient`, `generateX25519Identity` y `ageEncrypt` de la 6a y `splitEnvelope` de la 7a | `Seal` y `NewEnvelope` de `locator.go` |
Notas de la parte 7b:
- **La firma** es la de TweetNaCl en su versión de JavaScript: el cuerpo de `curve25519.dart`, dieciséis limbs de 16 bits en un `Float64List`, con el producto como un bucle, porque `curve25519.dart` guarda su aritmética como privada y la parte solo crea ficheros nuevos; y los escalares módulo ℓ en 64 limbs de 8 bits, con los acarreos de `modL` como divisiones por 256 redondeadas hacia abajo, que son los desplazamientos de TweetNaCl para sus valores, por debajo de 2^34. El escalar secreto y el nonce no pasan nunca por `BigInt` ni por una rama. Como Go, `ed25519Sign` mete en el hash la clave pública que recibe, sin derivarla otra vez de la semilla.
- **Las cadenas y las líneas de un fichero** se leen como las lee Go, como bytes: la mayúscula o la minúscula de una clave es la de `strings.ToUpper` y `strings.ToLower` de Go, y los espacios alrededor de una línea los de `strings.TrimSpace`, con las tablas de `go_unicode.dart`, ni las de la plataforma ni las de las rutas (Unicode 18.0.0); un byte que no es UTF-8 es U+FFFD, como en Go. Un string que no es ASCII no llega al Bech32 de la etapa 2, cuya comprobación de «mixed case» usa las mayúsculas de la plataforma: el error de Go se calcula aquí, sobre las runas. Las líneas son las de `bufio.Scanner`: una de 64 KiB o más da `bufio.Scanner: token too long`, sin el prefijo `authorkey:`, como en Go.
- **El fichero de una clave** se escribe con `ScryptRecipient` y `ageEncrypt` de la 6a, con un factor de trabajo de 16, y se lee con el lector de `age` de la etapa 2, con un máximo de 16, como `SetMaxWorkFactor(16)`: un fichero de 64 KiB como mucho, en claro o cifrado.
- **Los errores** son una `AuthorKeyException` con el texto de Go, sin código normativo. Una semilla o una clave pública de otra longitud son un `ArgumentError` con el texto de Go: un error del llamador. El de `PublicString` de Go tiene los dos números al revés, «a public key has 32 bytes, not 31», y aquí también.
- **Una diferencia con Go, a propósito:** tras `clear`, una clave lanza un `StateError` en cada uso; en Go, `Clear` deja a ceros la semilla y la clave pública, y la clave sigue dando sus valores y firmando con ellos, una firma que nunca verifica.
- **El material de la clave.** `clear` borra la semilla y la clave pública de la clave, y las funciones borran sus copias, también el texto de un fichero de clave y el del localizador sellado. Dart no puede prometer más: el recolector puede haber copiado un búfer, `package:crypto` guarda los suyos, y un `String`, como el de `AuthorKey.secret` o una contraseña, no se puede borrar. Nada es de tiempo constante: ni la VM ni un motor de JavaScript lo prometen.
- **El sellado** es el de Go, en su orden: `sealLocator` escribe primero el texto del localizador, con las comprobaciones de `Locator.marshal`, después crea el recipient tlock de la ronda, con las de `TimeRecipient`, y cifra; un fallo de cualquiera de los dos llega antes de sacar ningún valor aleatorio. `newEnvelope` saca primero I_SOBRE y después lo que saca `age`, como `GenerateX25519Identity` y `age.Encrypt` en Go. El cifrado tlock no es de tiempo constante (parte 6a).
- **Con la parte 6b:** `AuthorKey` implementa `AuthorSigner`, el enganche de la firma de `alg` 1 del escritor, que es la interfaz `AuthorKey` del paquete `capsule` de Go, con `Public()` y `Sign(msg)`. Las pruebas del escritor firman las cápsulas de `alg` 1 con la `AuthorKey` de la semilla de Go, y salen los mismos bytes.
- **Lo que se exporta.** `lib/datekeys.dart` exporta `authorkey.dart`, salvo las dos funciones sobre bytes, como el paquete público `authorkey` de Go, y `locator_seal.dart`. La firma Ed25519 y las tablas de Unicode de Go son internas.
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;
- en BLS12-381 los elementos del cuerpo y los escalares son `BigInt`, y una ronda es un `int` hasta 2^53-1, cuyos 8 bytes se escriben como dos mitades de 32 bits;
- en las tablas de Unicode, la clase de combinación de un punto de código va en una entrada que vale el punto por 256 más la clase, por debajo de 2^29, así que sus desplazamientos quedan en 32 bits;
- en ECDSA y RSA los enteros grandes son `BigInt`; las longitudes de DER, los INTEGER pequeños y el exponente de RSA son un `int` de cuatro bytes como mucho, por debajo de 2^32, que se lee sin desplazamientos;
- al escribir `age`, el contador de 11 bytes del nonce del STREAM se incrementa byte a byte, las longitudes son sumas y cocientes por debajo de 2^53, y `randomIndex` lee sus cuatro bytes como mucho multiplicando por 256, sin un desplazamiento que la web truncaría.
- en el escritor de cápsulas, el tamaño de cada fichero, su suma, L y P son un `int` hasta L_MAX, por debajo de 2^53, y el escritor rechaza unos ficheros que pasan de L_MAX antes de sumar el siguiente;
- en el localizador, una IPv6 son 16 bytes y sus bloques se comparan byte a byte, nunca como un entero de 64 o 128 bits, y los varint de un CID, de hasta 63 bits, son `BigInt`; el tamaño del resto y los desplazamientos son un `int` hasta 2^53-1.
- en el localizador, una IPv6 son 16 bytes y sus bloques se comparan byte a byte, nunca como un entero de 64 o 128 bits, y los varint de un CID, de hasta 63 bits, son `BigInt`; el tamaño del resto y los desplazamientos son un `int` hasta 2^53-1;
- en la firma Ed25519, los escalares módulo ℓ son 64 limbs de 8 bits en un `Float64List`, sumas de productos de bytes por debajo de 2^21 que `modL` reduce con valores por debajo de 2^34, y sus acarreos son divisiones redondeadas hacia abajo, no desplazamientos.
**`BigInt` no es de tiempo constante.** En las primitivas se 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 de las primitivas (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í. La firma Ed25519 de la parte 7b sigue la misma regla: el escalar secreto y el nonce van en limbs, también al reducirlos módulo ℓ, nunca en `BigInt`.
ECDSA y RSA hacen con `BigInt` toda su aritmética, y solo verifican: la clave de un certificado, la firma y el mensaje son públicos. La librería no firma con ninguno de los dos.
BLS12-381, en cambio, hace con `BigInt` toda su aritmética. Verificar un release y descifrar un stanza tlock solo manejan datos públicos: la firma de la ronda lo es desde que drand la publica, y el stanza va en la cápsula. Cifrar no: sigma y r son secretos, y el tiempo del código depende de ellos. El autor aceptó el 6 de octubre de 2026 que el escritor cifre así el stanza tlock (parte 6a); una capa del cuerpo de limbs fijos, sin ramas que dependan de los datos, lo evitaría.
Con la 6b y la 7b están hechas todas las etapas del plan, también la firma de `alg` 1 en el escritor con la clave de autor de la 7b.
## Rendimiento
Las cifras del 5 de octubre de 2026 son del PC de desarrollo (Windows 11, Dart 3.13, Node.js 24.9). No hay cifras de un móvil de gama media: están por medir.
### Primitivas
`dart run tool/bench.dart` mide las primitivas en la VM. Compilado a JavaScript:
```bash
dart compile js -O2 -o bench.js tool/bench.dart
```
```bash
node bench.js
```
| 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»).
### BLS12-381 y tlock
`dart run tool/bls12381_bench.dart` mide BLS12-381 y tlock en la VM. Compilado a JavaScript:
```bash
dart compile js -O2 -o bench.js tool/bls12381_bench.dart
```
```bash
node -e "globalThis.self = globalThis; require('./bench.js')"
```
Son medianas de 15 ejecuciones en la VM y de 7 en Node.js:
| Operación | VM | Node.js |
|---|---|---|
| Decodificar un punto de G1, la firma | 3 ms | 60 ms |
| Decodificar un punto de G2, U | 1,3 ms | 40 ms |
| Un emparejamiento | 11 ms | 230 ms |
| Verificar la firma de una ronda de Quicknet | 16 ms | 380 ms |
| La misma verificación, decodificando además la clave pinneada | 18 ms | 420 ms |
| Descifrar un stanza tlock (IBE) | 20 ms | 435 ms |
| Cifrar un stanza tlock (IBE) | 20 ms | 460 ms |
| Pasos 10 y 11: verificar el release y abrir el stanza | 35 ms | 760 ms |
La verificación hace el hash a G1, decodifica la firma y comprueba e(H(m), clave) · e(−firma, G2) = 1 con un solo bucle de Miller para los dos pares y una exponenciación final. El descifrado decodifica U, hace un emparejamiento y comprueba U = r·G2. En los pasos 10 y 11, el paso 11 verifica otra vez el release, como en Go, sin repetir el emparejamiento del paso 10.
Una estimación para un móvil: si uno de gama media, con Dart compilado AOT para ARM64, va de dos a cuatro veces más lento que esta VM, la verificación y el descifrado tardarán entre 30 y 80 ms cada uno, y los pasos 10 y 11 juntos, entre 70 y 150 ms. La app los llama en un `Isolate` (plan, «Rendimiento»). En la web, el `BigInt` de dart2js es unas veinte veces más lento que el de la VM; una capa del cuerpo de limbs fijos sería la forma de acelerarlo.
### Rutas, textos y llave de palabras
`dart run tool/pathrule_bench.dart` mide las reglas de rutas y textos y la llave de palabras en la VM. Compilado a JavaScript:
```bash
dart compile js -O2 -o pathrule_bench.js tool/pathrule_bench.dart
```
```bash
node pathrule_bench.js
```
Son medianas de tres ejecuciones, el 5 de octubre de 2026:
| Operación | VM | Node.js |
|---|---|---|
| `checkPath` de una ruta ASCII de 38 caracteres | 9,5 µs | 12 µs |
| `checkPath` de una ruta de 28 caracteres con acentos y U+2665, que R6c proyecta con sus 15 tablas | 28 µs | 38 µs |
| `checkComment` de un comentario de 16 351 bytes, casi el máximo | 0,7 ms | 0,6 ms |
| `checkTree` de 1000 rutas | 5,3 ms | 6,1 ms |
| La cabecera más grande: `checkPath` de 65 535 rutas como la segunda, y `checkTree` | 2,0 s | 2,3 s |
| `normalizeWords` y `checkWords` de seis palabras | 6,6 µs | 9,2 µs |
| `wordKey`: PBKDF2-HMAC-SHA256 con 600 000 iteraciones (§38.1) | 1,1 s | 0,7 s |
La llave de palabras tarda un segundo, y una cabecera de 65 535 rutas que R6c proyecta, dos: la app llama a las dos en un `Isolate` (plan, «Rendimiento»). R6c comprueba la proyección de cada tabla aunque sea igual a la de una tabla anterior, como Go; saltarse las repetidas da el mismo resultado y baja la segunda ruta de 28 a 18 µs, y la cabecera más grande a 1,45 s.
### La apertura
`dart run tool/open_bench.dart` mide la apertura en la VM. Compilado a JavaScript:
```bash
dart compile js -O2 -o open_bench.js tool/open_bench.dart
```
```bash
node -e "globalThis.self = globalThis; require('./open_bench.js')"
```
Cada apertura verifica su release: entre dos, la de otro release hace que cada una pague su emparejamiento. Las cápsulas grandes las hace `test/large_capsule.dart` desde los fixtures, sellando otra vez su `PAYLOAD_AGE`, y en el formato 3 su control, como puede quien conoce sus claves; se abren desde una `ByteSource`, en streaming. Son medianas de tres ejecuciones en la VM y de dos en Node.js, el 5 de octubre de 2026:
| Operación | VM | Node.js |
|---|---|---|
| Abrir `time_only_extensions`, formato 1, `time_only` | 52 ms | 0,84 s |
| Abrir `time_and_key_portable` con su `.dkk`, formato 1 | 48 ms | 0,80 s |
| Abrir `format2_time_and_key_recipients` con su `.dkk`, 16 stanzas | 73 ms | 0,82 s |
| Abrir `format3_single`, un fichero | 52 ms | 0,81 s |
| Abrir `format3_time_and_key_portable` con su `.dkk` | 72 ms | 0,82 s |
| Abrir `format3_signed_cms`, una firma de `alg` 2 de dos firmantes, cada uno con su sello | 55 ms, de ellos el área de seguridad 9,4 ms | 0,95 s, de ellos el área 0,13 s |
| Abrir `format3_sealed`, una firma de `alg` 1 y un sello de `seal_type` 2 | 55 ms, de ellos el área 8,5 ms | 0,89 s, de ellos el área 0,05 s |
| 64 MiB en el formato 1, en streaming | 1,50 s, 43 MiB/s | 16 MiB: 0,73 s, 22 MiB/s |
| 64 MiB en un fichero del formato 3, en streaming | 2,64 s, 24 MiB/s | 16 MiB: 0,92 s, 17 MiB/s |
Una apertura pequeña es casi toda los pasos 10 y 11, la verificación del release y el IBE. El área de seguridad de las dos cápsulas con certificados la evalúa el evaluador por defecto, con el lector de CMS, y su tiempo se mide dentro de la apertura; sus dos filas son del 6 de octubre. En `format3_signed_cms` son cuatro verificaciones, la ECDSA de P-256 de Ana, la RSA-2048 de Luis y las ECDSA de P-256 de sus dos sellos; en `format3_sealed`, la de Ed25519 de la firma de `alg` 1 y la de P-256 del sello. En Node.js manda la ECDSA sobre el `BigInt` compilado a JavaScript (ver «ECDSA, RSA y CMS»). En una grande manda ChaCha20-Poly1305, unos 21 ms por MiB en la VM; en el formato 3 se suma el SHA-256 de cada fichero, otros 17 ms por MiB, el de este código como el de `package:crypto`. La memoria no crece con el tamaño: la fuente se lee por tramos de 1 MiB y el texto llega al sink chunk a chunk.
### El escritor de `age`
`dart run tool/age_writer_bench.dart` mide el escritor de `age` en la VM. Compilado a JavaScript:
```bash
dart compile js -O2 -o age_writer_bench.js tool/age_writer_bench.dart
```
```bash
node -e "globalThis.self = globalThis; require('./age_writer_bench.js')"
```
Lo aleatorio sale del CSPRNG de la plataforma, como en producción. Son medianas de entre tres y quince ejecuciones, el 6 de octubre de 2026:
| Operación | VM | Node.js |
|---|---|---|
| La cabecera de `PAYLOAD_AGE`, un stanza X25519 | 4 ms | 4 ms |
| `INNER_ACCESS_AGE` de 16 stanzas X25519 sobre 103 bytes | 45 ms | 35 ms |
| `OUTER_TIME_AGE`, el stanza tlock de la ronda 1000, 2128 bytes | 40 ms | 0,55 s |
| Un fichero de clave de autor, scrypt con logN 16 | 0,6 s | 0,85 s |
| 64 MiB en streaming, en trozos de 1 MiB | 1,38 s, 46 MiB/s | 16 MiB: 0,33 s, 49 MiB/s |
Un stanza X25519 son dos X25519, la clave efímera y el secreto compartido, de algo más de 1 ms cada uno. El de tlock es el cifrado del IBE, con su emparejamiento, sobre `BigInt`. En un fichero grande manda ChaCha20-Poly1305, como al abrir; la memoria no crece con el tamaño, porque cada chunk sale en cuanto se cifra.
### El escritor de cápsulas
`dart run tool/encrypt3_bench.dart` mide el escritor de cápsulas en la VM. Compilado a JavaScript:
```bash
dart compile js -O2 -o encrypt3_bench.js tool/encrypt3_bench.dart
```
```bash
node -e "globalThis.self = globalThis; require('./encrypt3_bench.js')"
```
Lo aleatorio sale del CSPRNG de la plataforma, y el sink solo cuenta lo que recibe. Son medianas de entre tres y nueve ejecuciones, el 6 de octubre de 2026:
| Operación | VM | Node.js |
|---|---|---|
| Una cápsula `time_only` de un fichero pequeño | 48 ms | 0,71 s |
| Una cápsula `time_and_key` de un fichero pequeño, con una `.dkk` | 0,12 s | 0,75 s |
| Un fichero de 64 MiB, leído dos veces en streaming en trozos de 1 MiB | 4,7 s, 14 MiB/s | 16 MiB: 1,95 s, 8 MiB/s |
Una cápsula pequeña cuesta sobre todo el cifrado del stanza tlock, el mismo de la 6a; con `time_and_key`, también los 16 stanzas X25519 y la `.dkk`, que se comprueba abriendo `INNER_ACCESS_AGE`. En un fichero grande manda el SHA-256, que se calcula tres veces, como en Go: el de cada lectura del fichero y el de la cápsula para la `.dkk`, a unos 62 MiB/s cada uno en la VM. Con una llave de palabras se suman los 600 000 pasos de PBKDF2.
### Las claves de autor y el sellado del localizador
`dart run tool/authorkey_bench.dart` mide la parte 7b en la VM. Compilado a JavaScript:
```bash
dart compile js -O2 -o authorkey_bench.js tool/authorkey_bench.dart
```
```bash
node -e "globalThis.self = globalThis; require('./authorkey_bench.js')"
```
Lo aleatorio sale del CSPRNG de la plataforma, como en producción. Son medianas de entre cinco y treinta y una ejecuciones, el 6 de octubre de 2026:
| Operación | VM | Node.js |
|---|---|---|
| `AuthorKey.generate` | 4,5 ms | 9 ms |
| Una firma Ed25519 de 99 bytes, lo que mide `AUTHOR_MESSAGE` | 4,5 ms | 9 ms |
| La verificación estricta de esa firma, para comparar | 4,6 ms | 9 ms |
| Una firma Ed25519 de 1 MiB | 26 ms | 0,92 s |
| `encryptAuthorKey`, scrypt con logN 16 | 0,57 s | 0,77 s |
| `readAuthorKey` de ese fichero | 0,57 s | 1,0 s |
| `sealLocator` para la ronda 1000 | 30 ms | 0,52 s |
| `newEnvelope` de un `.dkc` de 1 MiB | 56 ms | 42 ms |
Una firma es una multiplicación escalar del punto base y dos SHA-512; la verificación, dos multiplicaciones con el producto desenrollado, tarda lo mismo. En un mensaje grande manda el SHA-512 de `package:crypto`, lento compilado a JavaScript, donde trabaja con enteros de 64 bits emulados; la firma de `alg` 1 firma siempre los 99 bytes de `AUTHOR_MESSAGE`. El fichero de una clave es casi todo scrypt, y sellar un localizador, el cifrado tlock.
### ECDSA, RSA y CMS
`dart run tool/cms_bench.dart` mide ECDSA, RSA y el lector de CMS en la VM. Compilado a JavaScript:
```bash
dart compile js -O2 -o cms_bench.js tool/cms_bench.dart
```
```bash
node cms_bench.js
```
Son medianas de tres ejecuciones, el 5 de octubre de 2026:
| Operación | VM | Node.js |
|---|---|---|
| ECDSA P-256 con SHA-256, una verificación | 2,4 ms | 32 ms |
| ECDSA P-384 con SHA-384, una verificación | 5,6 ms | 96 ms |
| ECDSA P-521 con SHA-512, una verificación | 12 ms | 235 ms |
| RSA de 2048 bits, PKCS #1 v1.5 o PSS, una verificación | 0,10 ms | 5,1 ms |
| RSA de 3072 bits, una verificación | 0,17 ms | 9,1 ms |
| RSA de 4096 bits, una verificación | 0,29 ms | 17 ms |
| `parseCert` de un certificado de 337 bytes (P-256) o de 739 (RSA-2048) | 0,01 a 0,02 ms | 0,02 ms |
| `parseSignature` y `check` de una cofirma de ECDSA y RSA | 3,6 ms | 36 ms |
| `parseToken` y `check` de un token de ECDSA P-256 | 2,6 ms | 30 ms |
Manda ECDSA: con el exponente 65 537, una verificación de RSA son 17 multiplicaciones modulares, y una de ECDSA en P-256, unos 256 dobles de punto y 190 sumas. Una firma de dos firmantes con sus sellos son cuatro verificaciones: unos 10 ms en la VM y 0,13 s en Node.js con P-256.
## 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. Desde la etapa 5, `testdata/` es ya el de la rama `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` SHA-512, en Ed25519, al verificar y al firmar, SHA-256, en BLS12-381 y tlock, y SHA-1, SHA-256, SHA-384 y SHA-512, en CMS. Las primitivas tienen además su propio SHA-256 con HMAC-SHA256, 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
```bash
dart pub get
```
```bash
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 de la rama `v0.12` de `datekeys-go` en `084728d`, la misma que tiene `datekeys-ts`: 134 ficheros, con `wordkey.json`, los vectores compartidos de la llave de palabras, con los veredictos y las líneas del borrador v0.12. Cada fichero dice aún `"spec": "0.11"`, como el `SpecVersion` de Go, hasta que el autor apruebe el borrador.
```bash
dart run tool/sync_testdata.dart sync --commit 084728d
```
```bash
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, de la lectura y la escritura de `age`, de BLS12-381, de tlock, de las reglas de rutas y textos, de la llave de palabras, de los formatos, de la apertura, del área de seguridad, de la firma con certificados y el sello, del lector de CMS, del localizador, de las claves de autor y de la firma Ed25519, y del sellado del localizador y del sobre. Los escribe Go, con las librerías de la caché de módulos que usa `datekeys-go` (`x/crypto`, `filippo.io/age`, `kilic/bls12-381`, `drand/kyber`, `kyber-bls12381` y `tlock`) y sus paquetes, como `provider`, `agewrap`, `capsule`, `internal/pathrule`, `internal/testkit` y `wordkey`; 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; los de las rutas, de la apertura, del lector de CMS y del escritor de `age`, en una exportación suya, y el último, `tool/cms_go_vectors_test.go`, como una prueba de Go. Los de BLS12-381 y tlock dan la misma salida con el tag `spec-v0.11` y con el borrador v0.12, y leen ficheros congelados de `datekeys-ts`, cuya carpeta en esta máquina se llama todavía `App`:
```bash
cd ../datekeys-go && go run ../datekeys-dart/tool/gen_primitive_vectors.go -out ../datekeys-dart/test/vectors
```
```bash
cd ../datekeys-go && go run ../datekeys-dart/tool/gen_age_vectors.go -out ../datekeys-dart/test/vectors
```
```bash
cd ../datekeys-go && go run ../datekeys-dart/tool/bls12381_go_vectors.go ../datekeys-ts/src/lib/dkc/testing/bls12381-vectors.json > ../datekeys-dart/test/vectors/bls12381_vectors.json
```
```bash
cd ../datekeys-go && go run ../datekeys-dart/tool/ibe_go_vectors.go ../datekeys-dart/testdata/fixtures ../datekeys-ts/src/lib/dkc/testing/ibe-vectors.json > ../datekeys-dart/test/vectors/ibe_vectors.json
```
```bash
cd ../datekeys-go && go run ../datekeys-dart/tool/tlock_go_vectors.go ../datekeys-ts/src/lib/dkc/testing/tlock-vectors.json > ../datekeys-dart/test/vectors/tlock_vectors.json
```
```bash
cd ../datekeys-go && go run ../datekeys-dart/tool/release_go_vectors.go ../datekeys-dart/testdata > ../datekeys-dart/test/vectors/release_vectors.json
```
```bash
cd ../datekeys-go && go run ../datekeys-dart/tool/formats_go_vectors.go -testdata ../datekeys-dart/testdata -out ../datekeys-dart/test/vectors
```
```bash
cd ../datekeys-go && go run ../datekeys-dart/tool/wordkey_go_vectors.go -out ../datekeys-dart/test/vectors
```
```bash
cd ../datekeys-go && go run ../datekeys-dart/tool/mutation_go_texts.go ../datekeys-dart/testdata > ../datekeys-dart/test/vectors/mutation_texts.json
```
```bash
cd ../datekeys-go && go run ../datekeys-dart/tool/security_go_vectors.go -testdata ../datekeys-dart/testdata -out ../datekeys-dart/test/vectors
```
```bash
cd ../datekeys-go && go run ../datekeys-dart/tool/locator_go_vectors.go -testdata ../datekeys-dart/testdata -out ../datekeys-dart/test/vectors
```
`publicIP` y `headerEnd` no se exportan en el paquete `locator`: `tool/locator_go_vectors.go` llega a ellos con `go:linkname`. Lo aleatorio de `age`, tlock y `NewEnvelope` sale de `crypto/rand.Reader`, que el generador cambia por un ChaCha8 de una semilla fija: la salida es la misma en cada ejecución.
`holderText` no se exporta en el paquete `capsule`: `tool/security_go_vectors.go` llega a él con `go:linkname`, que Go permite con un paquete de fuera de la biblioteca estándar. El mismo programa escribe los vectores de la parte 5c, `securitycms_vectors.json`, con firmas CMS y tokens RFC 3161 que hace él mismo como los hace `internal/cms/cmstest` para las pruebas de la referencia: ese paquete no se puede importar desde fuera del árbol de `datekeys-go`, así que el generador reescribe la parte que necesita. Las claves salen de etiquetas, ECDSA firma con el nonce del RFC 6979 y RSA con PKCS #1 v1.5, así que la salida no depende de `crypto/rand`.
`internal/pathrule` solo se puede importar desde el árbol de `datekeys-go`, así que el generador de las rutas corre en una exportación suya, hecha con `git archive`, sin tocar el repositorio:
```bash
commit=$(git -C ../datekeys-go rev-parse v0.12)
out=$PWD/test/vectors
tmp=$(mktemp -d)
git -C ../datekeys-go archive "$commit" | tar -x -C "$tmp"
cp tool/pathrule_go_vectors.go "$tmp"
(cd "$tmp" && go run ./pathrule_go_vectors.go -source "$commit" -out "$out")
rm -rf "$tmp"
```
El de la apertura también, porque usa `internal/testkit`, `internal/cbortest` e `internal/inspectview`. El del lector de CMS, que importa `internal/cms` y `internal/cms/cmstest`, corre además como una prueba de Go, para que `testing/cryptotest.SetGlobalRandom` haga deterministas las claves y las firmas:
```bash
commit=$(git -C ../datekeys-go rev-parse v0.12)
out=$PWD/test/vectors
tmp=$(mktemp -d)
git -C ../datekeys-go archive "$commit" | tar -x -C "$tmp"
mkdir "$tmp/cmsvectors"
cp tool/cms_go_vectors_test.go "$tmp/cmsvectors/"
(cd "$tmp/cmsvectors" && go test -run TestWriteVectors -count=1 -args -source "$commit" -out "$out")
rm -rf "$tmp"
```
La exportación para la apertura:
```bash
commit=$(git -C ../datekeys-go rev-parse v0.12)
tmp=$(mktemp -d)
git -C ../datekeys-go archive "$commit" | tar -x -C "$tmp"
cp tool/open_go_vectors.go "$tmp"
(cd "$tmp" && go run ./open_go_vectors.go -source "$commit" -testdata "$OLDPWD/testdata" -out "$OLDPWD/test/vectors")
rm -rf "$tmp"
```
Los del escritor de `age` también corren en una exportación, porque `tool/age_writer_go_vectors.go` comprueba sus ficheros con `internal/testkit`. Mientras escribe cada caso, sustituye `crypto/rand.Reader` por el keystream de `SeededRandomSource`, así que `age.Encrypt` saca de él la file key, los secretos efímeros, las sales, sigma, las etiquetas y el nonce, y la salida es la misma en cada ejecución:
```bash
commit=$(git -C ../datekeys-go rev-parse v0.12)
out=$PWD/test/vectors
tmp=$(mktemp -d)
git -C ../datekeys-go archive "$commit" | tar -x -C "$tmp"
cp tool/age_writer_go_vectors.go "$tmp"
(cd "$tmp" && go run ./age_writer_go_vectors.go -source "$commit" -out "$out")
rm -rf "$tmp"
```
Los de la parte 7b corren en el módulo de `datekeys-go`, porque solo importan paquetes públicos: `authorkey`, `locator`, `profile`, `provider` y `codec/bech32`, cuyo `createChecksum` el primero alcanza con `go:linkname` para escribir cadenas Bech32 con cualquier relleno. Mientras escriben cada caso, `crypto/rand.Reader` lee el keystream de `SeededRandomSource`, como en el generador del escritor de `age`; `authorkey.Generate` lo lee también, gracias al `//go:debug cryptocustomrand=1` del generador, sin el que `ed25519.GenerateKey` de Go 1.26 no mira `crypto/rand.Reader`:
```bash
cd ../datekeys-go && go run ../datekeys-dart/tool/authorkey_go_vectors.go -source $(git rev-parse v0.12) -testdata ../datekeys-dart/testdata -out ../datekeys-dart/test/vectors
```
```bash
cd ../datekeys-go && go run ../datekeys-dart/tool/locator_seal_go_vectors.go -source $(git rev-parse v0.12) -testdata ../datekeys-dart/testdata -out ../datekeys-dart/test/vectors
```
Las tablas de Unicode de Go de `lib/src/go_unicode.dart` las escribe otro programa, con la versión de Go de `datekeys-go`:
```bash
cd ../datekeys-go && go run ../datekeys-dart/tool/go_unicode_tables.go -out ../datekeys-dart/lib/src/go_unicode.dart
```
En la otra dirección, este repositorio escribe los ficheros y Go los abre: `tool/age_interop_dart_samples.dart` escribe los de las recetas de `test/age_interop_support.dart`, y `tool/age_interop_go_verdicts.go` los abre con `age` y las identities de `agewrap` y escribe sus veredictos:
```bash
commit=$(git -C ../datekeys-go rev-parse v0.12)
root=$PWD
tmp=$(mktemp -d)
dart run tool/age_interop_dart_samples.dart "$tmp/samples"
git -C ../datekeys-go archive "$commit" | tar -x -C "$tmp"
cp tool/age_interop_go_verdicts.go "$tmp"
(cd "$tmp" && go run ./age_interop_go_verdicts.go -source "$commit" -samples "$tmp/samples" -out "$root/test/vectors")
rm -rf "$tmp"
```
Los de la parte 7b, igual: `tool/seal_interop_dart_samples.dart` escribe los ficheros de las recetas de `test/seal_interop_support.dart`, y `tool/seal_interop_go_verdicts.go` los abre en el módulo de `datekeys-go`:
```bash
tmp=$(mktemp -d)
dart run tool/seal_interop_dart_samples.dart "$tmp"
(cd ../datekeys-go && go run ../datekeys-dart/tool/seal_interop_go_verdicts.go -source $(git rev-parse v0.12) -testdata ../datekeys-dart/testdata -samples "$tmp" -out ../datekeys-dart/test/vectors)
rm -rf "$tmp"
```
Los del escritor de cápsulas corren como una prueba de Go en una exportación, como los del lector de CMS, porque los enganches de la firma y del sello usan `internal/cms/cmstest`, cuyas claves y firmas solo fija `testing/cryptotest.SetGlobalRandom`. Mientras escribe cada receta, `crypto/rand.Reader` lee el keystream de `SeededRandomSource`, así que `capsule.EncryptFiles` saca de él todos sus valores, y la salida es la misma en cada ejecución:
```bash
commit=$(git -C ../datekeys-go rev-parse v0.12)
out=$PWD/test/vectors
tmp=$(mktemp -d)
git -C ../datekeys-go archive "$commit" | tar -x -C "$tmp"
mkdir "$tmp/capsulewriter"
cp tool/capsule_writer_go_vectors_test.go "$tmp/capsulewriter/"
(cd "$tmp/capsulewriter" && go test -run TestWriteVectors -count=1 -args -source "$commit" -out "$out")
rm -rf "$tmp"
```
En la otra dirección, `tool/capsule_interop_dart_samples.dart` escribe las cápsulas de las recetas de `test/capsule_interop_support.dart`, y `tool/capsule_interop_go_verdicts.go` las abre con Go y escribe sus veredictos:
```bash
commit=$(git -C ../datekeys-go rev-parse v0.12)
root=$PWD
tmp=$(mktemp -d)
dart run tool/capsule_interop_dart_samples.dart "$tmp/samples"
git -C ../datekeys-go archive "$commit" | tar -x -C "$tmp"
cp tool/capsule_interop_go_verdicts.go "$tmp"
(cd "$tmp" && go run ./capsule_interop_go_verdicts.go -source "$commit" -samples "$tmp/samples" -out "$root/test/vectors")
rm -rf "$tmp"
```
| 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 |
| `bls12381_vectors.json` | Las 157 codificaciones límite congeladas de `datekeys-ts` con el veredicto de Go calculado otra vez, y, con una semilla fija, decodificaciones (con la clase del fallo de kilic: formato, curva o subgrupo), sumas, múltiplos, emparejamientos, hashes a G1 (también los mensajes del apéndice J.9.1 del RFC 9380), el mapa de un elemento, los excepcionales incluidos, y firmas BLS sobre G1 |
| `ibe_vectors.json` | GT y H2, H3 con candidatos rechazados, H4, identidades de rondas, el stanza tlock de cada fixture de `testdata/` con sus valores intermedios y su file key, los ciphertexts de kyber y los veredictos de `DecryptCCAonG2` sobre copias editadas del stanza de `time_only` |
| `tlock_vectors.json` | El cifrado de kyber con sigma fijo, reproducido byte a byte, y los ciphertexts que escribió `datekeys-ts` y abrió Go |
| `release_vectors.json` | Los veredictos, códigos y textos de `provider.Verify`, de `NewTimeIdentity` con su `Unwrap` y de `NewTimeRecipient` |
| `formats_*.json` | El diferencial de los formatos, de `datekeys-go` en `c531e93`: el resultado, el código y el texto de Go en unos 6 400 casos, válidos y rotos en cada capa del §69.1, con una semilla fija y sobre los fixtures editados. Las tramas, `PUBLIC_HEADER`, `CONTROL_CBOR` de los tres formatos, la `.dkk`, los perfiles, las extensiones con sus registros, `dk1_`, el RFC 3339, las rondas, el relleno hasta L_MAX, la comprobación del relleno de `capsule.Open`, los codificadores, los límites del §57, `BODY` y, para la precedencia del §69.1, cada fallo de una lista, solo y con cada otro |
| `formats_vectors.g.dart` | Uno de cada ocho casos de cada sección de `formats_*.json`, como constantes de Dart para las pruebas compiladas a JavaScript |
| `pathrule_vectors.json` | Las reglas de `internal/pathrule`: los casos de las pruebas de Go y de `datekeys-ts` y los dos lados de los límites de R2, R3 y R6b; 1300 cadenas y 350 árboles de una semilla fija (marcas combinantes, Hangul, ignorables, emoji, sosias best-fit de ASCII, nombres de dispositivo, alias 8.3, textos y bytes que no son UTF-8 válido) con el resultado de `CheckPath`, `CheckComment` y `CheckAuthor`, su NFD y su clave; los casos de R9; y, por plano, el SHA-256 de una línea por punto de código de cada función |
| `pathrule_vectors.g.dart` | El mismo JSON como constante de Dart |
| `wordkey_vectors.json` | La llave de palabras de `wordkey`: 400 textos con sus palabras, 515 listas de palabras con el resultado de `Check`, y cuatro llaves con su contraseña P y su sal S, el PBKDF2 de 1000 iteraciones para Node.js y el recipient; la primera es el vector del §38.1 |
| `wordkey_vectors.g.dart` | El mismo JSON como constante de Dart |
| `mutation_texts.json` | De `tool/mutation_go_texts.go`, port de `scripts/mutation-go-texts.go` de `datekeys-ts` sobre el `testdata/` de este repositorio: el texto del error de `capsule.Open` en cada caso del corpus de mutaciones, `ok` si se abre, y sus comprobaciones con el detalle de cada paso. Si Go y el corpus discreparan en el código o el paso de un caso, lo diría con `go_error` y `go_step`: en ninguno de los 218 casos del corpus del borrador v0.12 lo hacen con Go en `c531e93` |
| `open_cases.json` | `capsule.Open` sobre cada fixture con cada una de sus credenciales, y sobre fixtures editados o con otras opciones en cada paso que el corpus no alcanza: la trama, los campos, las extensiones y los vínculos de una `.dkk` en el paso 9.a, el reloj y los fallos de la fuente del release, las cabeceras `age` de los pasos 11 y 17, un stanza X25519 mal formado en `INNER_ACCESS_AGE`, `CONTROL_CBOR` sellado otra vez, `BODY` del formato 3 sellado otra vez, los sinks y la salida que fallan, el rechazo de `Accept` y las extensiones inutilizables de cada objeto. Cada caso tiene el resultado, el texto, el paso, las comprobaciones con su detalle, las peticiones del release, el estado del sink y el contenido o los ficheros, y los del formato 3 que se abren, sus veredictos con sus líneas, también con la clave de autor guardada |
| `open_heads.json` | `capsule.DecodeHead` de heads de una semilla fija, válidos y rotos en cada capa del §69.1, sin registro y con uno, y `capsule.EncodeHead` |
| `open_notes.json` | `extension.CheckNote` sobre textos y bytes, `extension.Note` y `Header.UnusableNote`, y `extension.Standard` con una nota |
| `open_inspect.json` | El texto del error de `capsule.Inspect` en cada mutación de `inspect_differential.json`, y la salida exacta de `datekeys inspect -json` con notas públicas y extensiones inutilizables |
| `open_vectors.g.dart` | Siete fixtures pequeños, lo que sus registros dicen de ellos y una parte de los cuatro ficheros `open_*.json`, como constantes de Dart |
| `security_vectors.json` | El área de seguridad del paquete `capsule`: los compromisos, también `ControlCommit` del control de cada fixture en cada formato; los codificadores; 1730 evaluaciones con `EvaluateSecurityIn` en 23 contextos y sin contexto, del mapa exterior, de `author-signature` y de `seal` rotos de todas las formas de sus esquemas y en sus límites, de firmas de `alg` 1 válidas e inválidas, guardadas o no, con los casos de «Taming the many EdDSAs» hechos sobre `AUTHOR_MESSAGE`, y de mutaciones de una semilla fija, con los veredictos, las líneas, `alg` y `seal_type` leídos y las partes que solo evalúa el lector de CMS; las líneas y el sello más temprano de veredictos con cada detalle; y `holderText` |
| `security_vectors.g.dart` | Todo `security_vectors.json` salvo siete de cada ocho evaluaciones, como constante de Dart |
| `securitycms_vectors.json` | La firma de `alg` 2 y el sello de `seal_type` 2 como los evalúa `EvaluateSecurityIn`: 760 áreas que hace el generador, con los veredictos, las líneas, el detalle de cada firmante y del sello y el sello más temprano. Un firmante exigido de cada resultado junto a ajenos de cada resultado, sin `round_time`, al abrir en el instante de los sellos, en el contexto de otro head y con una clave 3; tres firmantes exigidos sacados de una semilla; `SIGNERS` de 16 y 17 entradas; la validez de un certificado en el instante de su sello, al nanosegundo, de un `UTCTime` a un `GeneralizedTime` y con diez cifras de fracción, y la de la autoridad; t más la precisión frente a `round_time` a un nanosegundo de cada lado, con cada forma de la precisión, en el instante cero de Go y en el último segundo de 9999; un sello de cada veredicto junto a una firma de cada veredicto; y mutaciones de la `SignedData`, de `SIGNERS` y de los tokens. Los certificados, los tokens y los `SignerInfo` que se repiten van una vez, como trozos |
| `securitycms_vectors.g.dart` | Una parte de `securitycms_vectors.json`, con cada par de veredictos de cada grupo, y los fixtures `format3_signed_cms` y `format3_sealed` con lo que dicen sus registros, como constantes de Dart |
| `cms_ecdsa.json` | Las curvas de `crypto/elliptic`; puntos que lee o no `ecdsa.ParseUncompressedPublicKey`; y firmas de `ecdsa.VerifyASN1`: válidas, con s por encima de n/2, r o s de n o más, cero, negativas o no mínimas, en otra codificación, sobre otro hash, y con claves de d = 1 y d = n − 1, con u1·G + u2·Q en el infinito |
| `cms_rsa.json` | Claves de 2048, 2049, 3072, 4095 y 4096 bits con los exponentes 3, 65 537 y 2³¹ − 1, y firmas de `rsa.VerifyPKCS1v15` y `rsa.VerifyPSS` como las comprueba `internal/cms`: válidas, con un byte más o menos, de n o más, y codificaciones hechas a mano y firmadas con la clave privada, cada una con un defecto de su relleno |
| `cms_certs.json` | `cms.ParseCert`, sus campos, `Holder`, `IssuerName` y `ValidAt` alrededor de los dos extremos, en los certificados de las pruebas de `internal/cms`, en identificadores cuyo último arco da la vuelta en 32 o 64 bits, y en certificados editados nodo a nodo y bit a bit |
| `cms_signatures.json`, `cms_algorithms.json` | `cms.ParseSignature` y `SignerInfo.Check` en las firmas de las pruebas de `internal/cms` (la forma, los algoritmos y las claves), con identificadores que dan la vuelta y SET OF con un elemento repetido |
| `cms_tokens.json` | `cms.ParseToken` y `Token.Check` en los tokens de esas pruebas, el TSTInfo campo a campo, la precisión y el `messageImprint` en sus límites, y la autoridad en los extremos de su validez, al nanosegundo |
| `cms_mutations.json` | Firmas y tokens editados nodo a nodo y bit a bit, cada edición como el nodo, la operación y el SHA-256 del resultado |
| `cms_corpus.json` | Cada firma CMS y cada token de `security_cms.json` y de los fixtures `format3_signed_cms` y `format3_sealed` del borrador v0.12, leídos como los leen los veredictos de `capsule`, firmante a firmante |
| `cms_vectors.g.dart` | Una parte de cada fichero `cms_*.json`, como constantes de Dart |
| `age_writer.json` | Los ficheros que escribe `age.Encrypt` con el keystream de una semilla como `crypto/rand`, con cada valor que saca, su tamaño y su orden: un recipient X25519, sobre textos de 0 a 3 MiB, en los bordes de los chunks de 64 KiB; dos, tres y dieciséis, y uno con el bit 255 a 1, que `age` acepta; scrypt con factores de trabajo de 1 a 16 y contraseñas en UTF-8; y el stanza tlock de rondas de 1 a 11 cifras. Uno pequeño va entero, y uno grande, como su cabecera, su longitud y su SHA-256. Además: el keystream de varias semillas, `crypto/rand.Int` y el `permute` de `capsule.Encrypt` sobre él; los errores de `age.Encrypt`, con lo que saca antes de cada uno; los de `NewScryptRecipient`, `SetWorkFactor` y `NewTimeRecipient`; el STREAM tras `Close`; `ParseX25519Recipient`, `CheckX25519Recipient` y `GenerateX25519Identity`; y `testkit.StreamLen` y `capsule.PayloadAgeLength` |
| `age_writer.g.dart` | El mismo JSON como constante de Dart |
| `age_interop.json` | Los ficheros que escribe este repositorio con `SeededRandomSource`, con las recetas de `test/age_interop_support.dart`: X25519 con textos de 0 a 3 MiB, uno escrito en trozos, tres y dieciséis recipients, tlock en la ronda 1000, tlock sobre dieciséis X25519 como un `SEALED_CONTROL`, y scrypt con los factores de trabajo 10 y 16. Cada uno con su longitud, su SHA-256, el fichero si es pequeño, la longitud de su cabecera, sus stanzas, las reglas de `agewrap` sobre ellos y el veredicto de Go con cada identity: el texto que abre, o el error |
| `age_interop.g.dart` | El mismo JSON como constante de Dart |
| `locator_uris.json` | `CheckURI` y `Address.Host` en las 247 direcciones de `locator.json`, con su texto, y en 2 800 más, hechas en cada borde del §44.1 y sacadas de una semilla: IPv4 e IPv6 en cada notación que acepta o rechaza `netip.ParseAddr`, con zonas, mapeadas, compatibles, de NAT64, 6to4 y Teredo, la primera y la última dirección de cada bloque de IANA y sus vecinas, segmentos largos y punycode, nombres locales en mayúsculas y minúsculas, puertos, porcentajes, segmentos «.» y «..» y CID canónicos o no; `CheckURI` en bytes con un surrogate; `netip.ParseAddr`, con la dirección, su zona y su `String`, en 1 700 cadenas; y `publicIP` en los bytes de 868 direcciones |
| `locator_uris.g.dart` | El mismo JSON como constante de Dart |
| `locator_vectors.json` | Los textos de los demás casos de `locator.json`; `PlaintextLength` de -4100 a 16484; `Unmarshal` en 482 plaintexts de tres bases, válidos y rotos byte a byte y campo a campo; `Marshal` en cada límite y en cada borde del relleno; `Open` en 118 localizadores sellados de cuatro rondas, editados o con otros releases y perfiles; `Open` de ficheros cuyo texto pasa de 1 MiB; `OpenEnvelope`, `RestIn`, `Hide` y el corte de `NewEnvelope`; `Info.Extension`, `Info.OpenLocator` y `ParseInfo`; `locator.Standard` como registro; y `capsule.Open` de un fixture cuya `.dkk` lleva `datekeys.capsule` |
| `locator_vectors.g.dart` | Una parte de `locator_vectors.json`, como constante de Dart |
| `capsule_writer.json` | Lo que escribe `capsule.EncryptFiles` de Go en 87 recetas con el keystream de una semilla como `crypto/rand`: el tamaño de cada valor que saca; lo que recibió y devolvió cada enganche, la clave de autor de `alg` 1, la firma CMS de `alg` 2 y el sello; y, de cada una de las 21 cápsulas, su longitud y su SHA-256, la cápsula entera si es pequeña, la `.dkk`, el `Result`, `PUBLIC_HEADER`, `CONTROL_CBOR`, el head, si sus capas y su `.dkk` se codifican otra vez igual, y `capsule.Open` con cada credencial, todas juntas y ninguna. Las cápsulas llevan `time_only` y `time_and_key` con de 1 a 16 credenciales, palabras, `.dkk`, nota, extensiones en cada array, mtimes en sus bordes, rutas en el orden de sus bytes, chunks enteros, una firma de cada `alg`, el sello, el área de 64 KiB y la de 512 bytes. Los 66 errores, con su texto, su código y los bytes escritos antes, son los de las opciones, las rutas, los textos, las credenciales, las extensiones, el reloj, el perfil, las fuentes que fallan o cambian en cada lectura y los enganches |
| `capsule_writer.g.dart` | Los casos de `capsule_writer.json` marcados `node`, como constante de Dart |
| `capsule_interop.json` | Las cápsulas que escribe este repositorio con las recetas de `test/capsule_interop_support.dart`, seis con `SeededRandomSource` y seis con el CSPRNG de la plataforma: su longitud y su SHA-256, `capsule.Inspect`, `capsule.Open` con cada credencial, todas juntas y ninguna, si sus capas y su `.dkk` se codifican otra vez igual, y si `capsule.EncryptFiles` escribe los mismos bytes con la misma semilla |
| `capsule_interop.g.dart` | Las muestras de `capsule_interop.json` que no están marcadas `node` a falso, como constante de Dart |
| `authorkey.json` | Las firmas de `crypto/ed25519`: líneas de `sign.input`, cuyas 0, 1, 2 y 1023 son los tests 1, 2, 3 y 1024 de la RFC 8032, TEST SHA(abc), semillas de una semilla con mensajes de 0 bytes a 1 MiB, y claves privadas cuya segunda mitad es otra clave pública; los escalares de `math/big`, x mod ℓ y (a·b + c) mod ℓ, en sus esquinas y al azar; y `authorkey`: `NewFromSeed`, `Public`, `PublicString`, `Secret`, `Marshal`, `String` y sus errores, `Generate` y `Encrypt` con cada valor que sacan, `ParsePublic` y `ParseSecret` sobre cadenas válidas, en otra caja, de otras longitudes, con cada error de Bech32, otros prefijos y rellenos, claves fuera del perfil estricto y bytes que no son UTF-8, y sobre las runas de cada borde de los conjuntos de Go; y `Read` de ficheros en claro y cifrados, con sus límites, cada espacio de Go y sus vecinos, y factores de trabajo de 1 a 31. Cada caso con el resultado o el texto de Go |
| `authorkey.g.dart` | `authorkey.json` con solo las firmas marcadas para Node.js y una de cada ocho runas, como constante de Dart |
| `locator_seal.json` | `Seal` de localizadores de uno a tres bloques para rondas de 1 a la última de Quicknet, que Go abre con el release de su ronda cuando lo tiene; sus rechazos; `NewEnvelope` de `.dkc` de 0 bytes a 1 MiB; y el camino entero, del sobre a la apertura del `.dkc`. Con lo que saca `crypto/rand` en cada caso |
| `locator_seal.g.dart` | El mismo JSON como constante de Dart |
| `seal_interop.json` | Lo que hace Go con lo que escribe este repositorio en la parte 7b, con las recetas de `test/seal_interop_support.dart`: localizadores sellados de cuatro rondas con sus sobres, que abre con `locator.Open` y `OpenEnvelope`; ficheros de clave, en claro y cifrados, que lee con `authorkey.Read`; y firmas de 0 bytes a 70 000, que comprueba con `crypto/ed25519`. Con el SHA-256 de cada fichero y el veredicto de Go |
| `seal_interop.g.dart` | El mismo JSON como constante de Dart |
- Los fixtures que leen los generadores son los de `testdata/` de este repositorio, la copia sincronizada.
- `primitives.json`, los cuatro ficheros de BLS12-381 y tlock, `pathrule_vectors.json`, `wordkey_vectors.json`, los de los formatos, `security_vectors.json`, `securitycms_vectors.json`, los del lector de CMS, los del escritor de `age`, los del localizador, los de la parte 7b y `capsule_writer.json` salen iguales en cada ejecución, los del lector de CMS, los del localizador y `capsule_writer.json` con Go 1.26.8. `capsule_interop.json` cambia en cada ejecución en sus seis muestras del CSPRNG, que es lo que comprueban. Lo aleatorio de tlock, los ciphertexts de kyber con su sigma y los de `datekeys-ts`, se lee de los ficheros congelados de `datekeys-ts` en `289fe71`, y Go los descifra otra vez. `age`, en cambio, 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. `age.json` no lee `testdata/` y es el congelado de la etapa 2; `age_fixtures.json` se escribió otra vez con el `testdata/` de la v0.12, y fuera de los dos fixtures nuevos y del de `seal_type` 4294967295 sale igual.
- 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.
- `locator_vectors.json` guarda un valor editado como ediciones de una base, con una forma más: `[at, 0, byte, n]` inserta n copias del byte. Una dirección con una racha de 32 bytes iguales o más va como `[antes, byte, n, después]`, y un texto de error, como su índice en `texts`. Los ficheros cuyo texto pasa de 1 MiB van como su cabecera, su nonce, su file key y la lista de sus chunks: la prueba los escribe otra vez y comprueba su SHA-256 antes de abrirlos.
- Los casos de `open_cases.json` que editan un fixture lo sellan otra vez como el `testkit` de la referencia, con las file keys y los nonces del fixture, así que salen iguales en cada ejecución, y se guardan como ediciones del fixture.
- Los ficheros del lector de CMS escriben una vez por fichero los certificados que se repiten: el DER de un caso es entonces una lista de trozos, el hexadecimal de unos bytes o el índice de un certificado, con el SHA-256 del resultado. `securitycms_vectors.json` hace lo mismo con los certificados, los tokens y los `SignerInfo`, cada trozo hecho a su vez de los anteriores, y una mutación se guarda como su base, el objetivo de la edición (la `SignedData`, `SIGNERS` o el token) y la edición.
## 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.