Stage 6a in the README, the changelog and the library

The README says that part 6a of stage 6 is done, and has its section:
random.dart, age_writer.dart and recipient.dart, the order of the
random values, the STREAM, the labels, the errors, the rules of spec
section 37, the lengths, the tlock encryption that is not constant time,
which the author accepted, and what is exported, nothing. It describes
the new vectors and how the two generators make them, in an export of
datekeys-go, and gives the times of the writer. The changelog has the
entry of the part, with its tests and its injected faults, and the
library comment names it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
v0.11
dev 2 days ago
parent fbd0dd7b0a
commit fb86216c6e

@ -4,6 +4,30 @@ Cambios notables de la librería Dart. El proyecto usa versionado semántico; mi
## Especificación 0.11, en la rama `v0.11` — sin versión
### Etapa 6a: la escritura de `age` (06-10-2026)
- **La escritura de los ficheros `age`** (`lib/src/age_writer.dart`), port de `age.Encrypt`, `internal/format` e `internal/stream` de `filippo.io/age` v1.3.2, con sus comprobaciones y sus textos:
- `AgeEncryptor` y `ageEncrypt` sacan la file key, envuelven la file key para cada recipient, en su orden y con sus etiquetas, calculan el MAC de la cabecera y sacan el nonce. Rechazan, con el texto de Go, una lista sin recipients, unas etiquetas que no se pueden mezclar, un recipient que no envuelve la file key y unos stanzas que no se pueden escribir;
- `AgePayloadEncryptor` cifra el STREAM según llega el texto, como el `EncryptWriter` de Go: en chunks de 64 KiB, con el último completo cuando el texto es un múltiplo de 64 KiB distinto de cero y vacío solo cuando el texto lo es, y con el texto de Go tras `close`;
- las longitudes de un fichero salen, antes de escribirlo, de la de su texto y de la forma de sus stanzas: `ageStreamLength`, `ageStanzaLength`, `ageHeaderLength` y `ageFileLength`.
- **Los recipients** (`lib/src/recipient.dart`):
- `X25519Recipient`, con las cadenas `age1…` y los textos de `ParseX25519Recipient`, y `checkX25519Recipient`, las reglas del §37 con los textos de `agewrap`;
- `ScryptRecipient`, con su factor de trabajo y su etiqueta al azar;
- `TimeRecipient`, el de tlock, con la etiqueta `datekeys-tlock-…` de `agewrap`;
- `generateX25519Identity`, `rawX25519Identity`, `rawX25519Recipient` y la longitud de cada stanza.
- **La fuente de lo aleatorio** (`lib/src/random.dart`): `RandomSource`, inyectable; `secureRandom`, la de por defecto, con `Random.secure`; y `SeededRandomSource`, determinista, el keystream de ChaCha20 bajo el SHA-256 de una semilla, para pruebas y vectores. Todo lo aleatorio de un fichero sale de ella, en el orden de Go, sigma de tlock incluido: `encryptOnG2` y `wrapTlockStanza` la reciben, y ya no usan un `Random.secure` propio. Para los 16 huecos de la parte 6b trae además `randomIndex`, como `crypto/rand.Int`, y `permute`, como el de `capsule.Encrypt`.
- **Vectores de Go:** `tool/age_writer_go_vectors.go` corre en una exportación de `datekeys-go` y hace que `crypto/rand` lea el keystream de `SeededRandomSource`. Así `age.Encrypt`, con los recipients X25519, scrypt y tlock reales, saca valores conocidos, y `age_writer.json` guarda cada uno, con su tamaño y su orden, junto a los ficheros:
- X25519 sobre textos de 0, 1, 64 KiB − 1, 64 KiB, 64 KiB + 1, 128 KiB y más bytes, hasta 3 MiB;
- dos, tres y dieciséis recipients X25519, y uno con el bit 255 a 1, que `age` acepta;
- scrypt con factores de trabajo de 1 a 16;
- el stanza tlock de rondas de 1 a 11 cifras.
Cada fichero se comprueba con `testkit.SealAge`, que lo sella otra vez con la primera y la última extracción como file key y nonce, y Go lo abre. También guarda los errores de `age.Encrypt` y de los constructores, `ParseX25519Recipient`, `CheckX25519Recipient`, `GenerateX25519Identity`, `crypto/rand.Int`, el `permute` de `capsule` y las longitudes de `testkit` y `capsule`. `age_writer.g.dart` lleva el mismo JSON para Node.js.
- **En la otra dirección:** `tool/age_interop_dart_samples.dart` escribe los ficheros 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 `age_interop.json`. Son X25519 de 0 a 3 MiB, uno escrito en trozos, tres y dieciséis recipients, tlock abierto con el release de la ronda 1000 de los fixtures, tlock sobre dieciséis X25519 como un `SEALED_CONTROL` y scrypt con los factores de trabajo 10 y 16. Las pruebas escriben cada fichero otra vez, y debe ser el que leyó Go; Go lo abrió y sacó el texto de la receta, o lo rechazó con la identity que no debía abrirlo; y esta librería hace con él lo mismo que Go, con los mismos textos y las mismas reglas de stanzas.
- **Pruebas:** 98 nuevas en la VM y 80 en Node.js: 1670 y 412 en total. En Node.js corren los mismos vectores sin los casos caros: los ficheros de megabytes, scrypt con logN 16 y la mayoría de los de tlock.
- **Fallos inyectados**, uno a uno y revertidos: los ocho del encargo, el sexto y el séptimo en dos formas. Las pruebas los detectan todos, en la VM y en Node.js: el flag del último chunk en uno que no lo es; un chunk vacío al final tras chunks llenos; un contador del nonce que no avanza; la sal de HKDF en el orden inverso; el MAC de la cabecera sobre los stanzas en otro orden; un recipient de orden bajo aceptado, al envolver la file key y en `checkX25519Recipient`; un recipient scrypt junto a uno X25519, sin comparar las etiquetas y con un scrypt sin etiqueta; y la misma clave efímera en varios stanzas.
- **`tool/age_writer_bench.dart`** mide el escritor. En la VM, 64 MiB en streaming tardan 1,4 s, unos 46 MiB/s; compilado a JavaScript, 16 MiB tardan 0,33 s, unos 49 MiB/s. La cabecera de `PAYLOAD_AGE` cuesta 4 ms; `INNER_ACCESS_AGE`, con 16 stanzas, unos 45 ms en la VM y 35 ms en Node.js; `OUTER_TIME_AGE`, 40 ms y 0,55 s; y un fichero de clave de autor, con scrypt de logN 16, 0,6 s y 0,85 s. Las cifras, en el README.
### Etapa 5c: la firma de `alg` 2 y el sello de `seal_type` 2 en los veredictos (06-10-2026)
- **Los veredictos de la firma con certificados y del sello RFC 3161** (`lib/src/securitycms.dart`), port de `evaluateCMS`, `signerLine` y `evaluateSeal` de `signature2.go` de `datekeys-go` en `c531e93`, el borrador v0.12, sobre el lector de CMS de la 5a, con el mismo orden de comprobaciones:

@ -6,7 +6,7 @@ Es la tercera implementación de la especificación, después de la de referenci
## Estado
Están hechas las etapas 0 a 5 del plan (`docs/PLAN_dart.md` del espacio de trabajo):
Están hechas las etapas 0 a 5 del plan (`docs/PLAN_dart.md` del espacio de trabajo) y la parte 6a de la etapa 6:
- 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`;
@ -18,11 +18,12 @@ Están hechas las etapas 0 a 5 del plan (`docs/PLAN_dart.md` del espacio de trab
- 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.
- 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 usarán el escritor de la cápsula, la parte 6b, y las claves de autor, la etapa 7b.
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.
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 los ficheros `age` que forman una cápsula, byte a byte como Go con los mismos valores aleatorios.
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`.
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 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:
@ -195,6 +196,25 @@ Notas de la parte 5c:
- **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.
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`;
@ -203,19 +223,20 @@ Los enteros son exactos en la VM y en la web. El `int` de Dart tiene 64 bits con
- 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.
- 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.
**`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í.
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 escritor (etapa 6) cifrará el stanza tlock donde ese tiempo no se pueda observar, o con una capa del cuerpo de limbs fijos sin ramas que dependan de los datos.
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.
Las etapas siguientes traen el resto del protocolo en este orden:
| Etapa | Contenido |
|---|---|
| 6 | Escritor |
| 6 | Escritor: la parte 6b, la cápsula de formato 3, `time_only` y `time_and_key`, con la llave de palabras y la nota pública |
| 7 | Localizador y claves de autor |
## Rendimiento
@ -329,6 +350,30 @@ Cada apertura verifica su release: entre dos, la de otro release hace que cada u
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.
### ECDSA, RSA y CMS
`dart run tool/cms_bench.dart` mide ECDSA, RSA y el lector de CMS en la VM. Compilado a JavaScript:
@ -407,7 +452,7 @@ dart run tool/sync_testdata.dart check --against ../datekeys-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 `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, y del lector de CMS. 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 y del lector de CMS, 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`:
`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, y del lector de CMS. 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
@ -487,6 +532,31 @@ cp tool/open_go_vectors.go "$tmp"
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"
```
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"
```
| 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` |
@ -521,13 +591,17 @@ rm -rf "$tmp"
| `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 |
- 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` y los del lector de CMS salen iguales en cada ejecución, estos últimos con Go 1.26.8. 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.
- `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 y los del escritor de `age` salen iguales en cada ejecución, los del lector de CMS con Go 1.26.8. 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.
- 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.
- Las pruebas que corren en Node.js no leen ficheros. Los valores de Go que usan están en `primitives.g.dart`, `pathrule_vectors.g.dart`, `wordkey_vectors.g.dart`, `formats_vectors.g.dart`, `open_vectors.g.dart`, `security_vectors.g.dart`, `securitycms_vectors.g.dart`, `cms_vectors.g.dart`, `test/bls12381_constants.dart` y `test/ibe_constants.dart`. Unas pruebas en la VM comparan con los JSON los de las rutas, la llave de palabras, los formatos, la apertura, el área de seguridad, la firma con certificados y el sello, el lector de CMS, BLS12-381 y el IBE, y con `testdata/` los fixtures que llevan.
- Las pruebas que corren en Node.js no leen ficheros. Los valores de Go que usan están en `primitives.g.dart`, `pathrule_vectors.g.dart`, `wordkey_vectors.g.dart`, `formats_vectors.g.dart`, `open_vectors.g.dart`, `security_vectors.g.dart`, `securitycms_vectors.g.dart`, `cms_vectors.g.dart`, `age_writer.g.dart`, `age_interop.g.dart`, `test/bls12381_constants.dart` y `test/ibe_constants.dart`. Unas pruebas en la VM comparan con los JSON los de las rutas, la llave de palabras, los formatos, la apertura, el área de seguridad, la firma con certificados y el sello, el lector de CMS, el escritor de `age`, BLS12-381 y el IBE, y con `testdata/` los fixtures que llevan.
## Licencia

@ -18,8 +18,13 @@
/// SECURITY_CBOR, the signature of alg 1, the signature of alg 2 with
/// certificates and the seal of seal_type 2 with the reader of CMS, and the
/// verdicts with their texts and lines, which the opening evaluates as Go.
/// And part 6a of stage 6: the writing of age files, with the injectable
/// source of the random values of the writers and the X25519, scrypt and
/// tlock recipients, for the writer of a capsule and the files of author
/// keys.
///
/// DER, the primitives, age, agewrap, the curve arithmetic, the IBE of tlock
/// DER, the primitives, the reading and the writing of age, the recipients
/// and the random sources, agewrap, the curve arithmetic, the IBE of tlock
/// and its stanza, the rules of paths and texts on the Unicode tables, the
/// frame of BODY, the digest of a capsule, the steps of the opening, and
/// the reader of CMS with its ECDSA and RSA are internal, as in the Go

Loading…
Cancel
Save

Powered by TurnKey Linux.