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

lib/datekeys.dart exports the writer of capsules, and what its options
take: X25519Recipient and checkX25519Recipient, RandomSource and
secureRandom. The tests that imported them from their modules no longer
need to. tool/encrypt3_bench.dart times the writer on the VM and in
Node.js.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
v0.11
dev 1 day ago
parent cf2a36d6e5
commit 6239e30ce2

@ -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 6b: el escritor de cápsulas del formato 3 (06-10-2026)
- **El escritor** (`lib/src/encrypt3.dart`), port de `EncryptFiles`, `sealer` y el área de seguridad de `encrypt.go` y `encrypt3.go` de `datekeys-go` en `c531e93`, con las mismas comprobaciones en el mismo orden, los mismos códigos y textos de error y los mismos valores aleatorios, sacados en el mismo orden:
- `encryptFiles` escribe en un `ByteSink` los ficheros de sus `FileSource`, cada uno leído dos veces en streaming, con su SHA-256 comprobado en la segunda lectura. El sink se cierra cuando la cápsula está completa y comprobada, y se aborta ante cualquier fallo;
- `time_only` y `time_and_key`, con recipients X25519, una `.dkk` nueva y la llave de palabras, de 1 a 16 credenciales, en los 16 huecos de `INNER_ACCESS_AGE` con señuelos y en un orden al azar;
- el head, con los ficheros en el orden de los bytes de sus rutas, su mtime, el comentario y el autor declarado; la nota pública; las extensiones de cada objeto, con la regla del §72; y el relleno;
- el área de seguridad de 32 KiB, la de 64 KiB con `largeArea` y la de prueba con `testAreaLen`: vacía, o con la firma de `AuthorKey` (`alg` 1) o de `CmsSigner` (`alg` 2) y el sello de `Sealer`, hechos antes de escribir nada y evaluados por el lector de esta librería;
- las autocomprobaciones del §62.1: `PUBLIC_HEADER`, `CONTROL_CBOR`, el head, la trama de `BODY`, `INNER_ACCESS_AGE` con su `.dkk`, `PAYLOAD_AGE` y el área de seguridad;
- `EncryptOptions.now`, obligatorio, es el reloj;
- los errores son una `DateKeysException` con su código, un `ArgumentError` para las opciones que Go rechaza sin código, y una `CapsuleWriteException` para los demás.
La longitud de `SEALED_CONTROL` sale de la forma de sus stanzas, sin el sellado de prueba con el que Go la mide. Los valores aleatorios de ese sellado se sacan y se tiran, para que los siguientes sean los de Go.
- **`lib/datekeys.dart`** exporta `encrypt3.dart`, y `X25519Recipient`, `checkX25519Recipient`, `RandomSource` y `secureRandom`, que necesitan sus opciones. Tres pruebas de la 6a pierden un `import` que ya no hace falta.
- **Vectores de Go:** `tool/capsule_writer_go_vectors_test.go` corre `capsule.EncryptFiles` como una prueba de Go en una exportación de `datekeys-go`, con `crypto/rand` leyendo el keystream de `SeededRandomSource` y los enganches de las pruebas de Go: una clave de autor de una semilla, y las firmas CMS y los sellos de `internal/cms/cmstest`, que fija `testing/cryptotest.SetGlobalRandom`. `capsule_writer.json` guarda 87 recetas, con el tamaño de cada valor que saca Go y lo que recibió y devolvió cada enganche:
- 21 cápsulas, con su SHA-256, la cápsula entera si es pequeña, la `.dkk`, el `Result`, sus capas, y la apertura de Go con cada credencial, todas juntas y ninguna;
- 66 errores, con su texto, su código y los bytes escritos antes: 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.
Las pruebas escriben cada receta otra vez: los mismos valores aleatorios, en el mismo orden y del mismo tamaño, las mismas peticiones a los enganches, y los mismos bytes o el mismo error. Esta librería abre cada cápsula como la abrió Go.
- **En la otra dirección:** `tool/capsule_interop_dart_samples.dart` escribe doce cápsulas, seis con `SeededRandomSource` y seis con el CSPRNG de la plataforma, entre ellas tres MiB en tres ficheros, doscientos ficheros y catorce recipients con palabras y `.dkk`. `tool/capsule_interop_go_verdicts.go` las inspecciona y las abre con Go, con cada credencial, todas juntas y ninguna, codifica otra vez sus capas y su `.dkk`, y escribe las seis primeras con `capsule.EncryptFiles` y la misma semilla. Go abre cada cápsula con los ficheros de su receta, codifica sus capas igual y escribe los mismos bytes.
- **Pruebas:** 175 nuevas en la VM y 73 en Node.js, 1888 y 505 en total. Además de los vectores: nada llega al sink antes de la firma; el sink recibe primero el PRELUDE, y cada trozo es suyo; un sink que falla para la escritura y se aborta, y uno que no se cierra no se aborta; una fuente o un enganche que lanza una `DateKeysException` conserva su código; un `String` mal formado no es UTF-8 válido para Go; una fuente se lee en streaming; y, en la VM, dos cápsulas del CSPRNG difieren y se abren.
- **Fallos inyectados**, uno a uno y revertidos. Las pruebas detectan los veinte: los valores del sellado de prueba sin sacar; los huecos sin permutar; las rutas ordenadas por unidades UTF-16; un mtime anterior a 1970 guardado; un CR suelto del comentario conservado; el área de 64 KiB siempre que se permite; los veredictos del área sin comprobar; el SHA-256 de la segunda lectura sin comparar; una fuente que da un byte de más en un trozo, aceptada; la nota pública perdida; un recipient repetido aceptado; un `credential_id` a ceros; la llave de palabras salada con la ronda siguiente; el sink sin abortar y sin cerrar; un área de prueba con `largeArea`; un instante igual al reloj aceptado; el relleno con un byte de menos; la sal del head con un byte a cero; y `SEALED_CONTROL_LEN` con uno de más.
- **`tool/encrypt3_bench.dart`** mide el escritor: una cápsula pequeña tarda 48 ms en la VM y 0,71 s en Node.js, 0,12 s y 0,75 s con `time_and_key` y una `.dkk`; un fichero de 64 MiB, 4,7 s en la VM, y uno de 16 MiB, 1,95 s en Node.js. Las cifras, en el README.
- **Para después de integrar la 7b:** la firma de `alg` 1 con la clave de autor de la 7b, que implementa `AuthorKey`.
### Etapa 7a: el localizador y el sobre de `datekeys.capsule` (06-10-2026)
- **El localizador y el sobre** (`lib/src/locator.dart`), port del 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:

@ -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), la parte 6a de la etapa 6 y la parte 7a de la etapa 7:
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 parte 7a de 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`;
@ -19,12 +19,13 @@ Están hechas las etapas 0 a 5 del plan (`docs/PLAN_dart.md` del espacio de trab
- 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 usarán el escritor de la cápsula, la parte 6b, y las claves de autor, la etapa 7b.
- 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 usa el escritor de la cápsula, la parte 6b, y la usarán las claves de autor, la etapa 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 parte 7a de la etapa 7, 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. Sellar un localizador y crear un sobre necesitan el escritor de `age` de la etapa 6: son de la parte 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. Y ya escribe los ficheros `age` que forman una cápsula, byte a byte como Go con los mismos valores aleatorios.
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.
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.
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 después en la rama `v0.11`, en paralelo con la 7b, las claves de autor, en la rama `stage7b`.
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:
@ -216,6 +217,49 @@ Notas de la parte 6a:
- **`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: `AuthorKey` (`alg` 1), `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` 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 |
@ -245,6 +289,7 @@ Los enteros son exactos en la VM y en la web. El `int` de Dart tiene 64 bits con
- 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.
**`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í.
@ -395,6 +440,28 @@ Lo aleatorio sale del CSPRNG de la plataforma, como en producción. Son medianas
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.
### ECDSA, RSA y CMS
`dart run tool/cms_bench.dart` mide ECDSA, RSA y el lector de CMS en la VM. Compilado a JavaScript:
@ -584,6 +651,32 @@ cp tool/age_interop_go_verdicts.go "$tmp"
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` |
@ -626,9 +719,13 @@ rm -rf "$tmp"
| `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 |
- 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` y los del localizador salen iguales en cada ejecución, los del lector de CMS y los del localizador 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, los del escritor de `age`, los del localizador 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.

@ -30,6 +30,12 @@
/// addresses and of the IP that a name resolves to, and the rest of the
/// envelope. Sealing a locator and making an envelope come with part 7b.
///
/// And part 6b of stage 6: the writer of capsules of format 3,
/// [encryptFiles], with its options, its sources read in streaming, its
/// result and the hooks of the signature and the seal, [AuthorKey],
/// [CmsSigner] and [Sealer]; and what its options take: the X25519
/// recipients and the source of the random values.
///
/// 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
@ -47,6 +53,7 @@ export 'src/bytes.dart'
export 'src/cbor.dart';
export 'src/control.dart';
export 'src/datekey.dart';
export 'src/encrypt3.dart';
export 'src/errors.dart';
export 'src/extension.dart';
export 'src/framing.dart';
@ -68,6 +75,8 @@ export 'src/note.dart';
export 'src/open.dart' show OpenOptions, Opened, openCapsule, openCapsuleSource;
export 'src/padding.dart';
export 'src/profile.dart';
export 'src/random.dart' show RandomSource, secureRandom;
export 'src/recipient.dart' show X25519Recipient, checkX25519Recipient;
export 'src/release.dart'
show
PinnedProfile,

@ -14,7 +14,6 @@ import 'package:datekeys/datekeys.dart';
import 'package:datekeys/src/age.dart';
import 'package:datekeys/src/age_writer.dart';
import 'package:datekeys/src/agewrap.dart';
import 'package:datekeys/src/recipient.dart';
import 'package:datekeys/src/sha256.dart';
import 'age_writer_support.dart';

@ -10,7 +10,6 @@ import 'package:datekeys/src/age.dart';
import 'package:datekeys/src/age_writer.dart';
import 'package:datekeys/src/agewrap.dart';
import 'package:datekeys/src/bytes.dart' show utf8Bytes;
import 'package:datekeys/src/random.dart';
import 'package:datekeys/src/recipient.dart';
import 'package:datekeys/src/sha256.dart';
import 'package:datekeys/src/tlock.dart';

@ -16,7 +16,6 @@ import 'package:datekeys/src/age_writer.dart';
import 'package:datekeys/src/agewrap.dart';
import 'package:datekeys/src/base64.dart';
import 'package:datekeys/src/chacha20poly1305.dart';
import 'package:datekeys/src/random.dart';
import 'package:datekeys/src/recipient.dart';
import 'package:datekeys/src/sha256.dart';
import 'package:test/test.dart';

@ -10,8 +10,6 @@
import 'dart:typed_data';
import 'package:datekeys/datekeys.dart';
import 'package:datekeys/src/encrypt3.dart';
import 'package:datekeys/src/random.dart';
import 'capsule_writer_support.dart';

@ -13,9 +13,6 @@ import 'dart:typed_data';
import 'package:datekeys/datekeys.dart';
import 'package:datekeys/src/age.dart' show X25519Identity;
import 'package:datekeys/src/bytes.dart' show utf8Bytes;
import 'package:datekeys/src/encrypt3.dart';
import 'package:datekeys/src/random.dart';
import 'package:datekeys/src/recipient.dart';
import 'package:datekeys/src/sha256.dart';
import 'age_support.dart' show pattern;

@ -8,8 +8,6 @@ import 'dart:convert';
import 'dart:typed_data';
import 'package:datekeys/datekeys.dart';
import 'package:datekeys/src/encrypt3.dart';
import 'package:datekeys/src/random.dart';
import 'package:test/test.dart';
import 'capsule_writer_support.dart';

@ -7,7 +7,6 @@ library;
import 'dart:convert';
import 'package:datekeys/datekeys.dart';
import 'package:datekeys/src/encrypt3.dart';
import 'package:test/test.dart';
import 'capsule_writer_support.dart';

@ -0,0 +1,95 @@
// Times the writer of capsules of datekeys-dart, stage 6b, on the VM and
// compiled to JavaScript: a capsule of one small file with time_only, and
// with time_and_key and a portable key, whose 16 slots add INNER_ACCESS_AGE;
// and one file of 64 MiB on the VM and 16 MiB in Node.js, read in streaming
// in pieces of 1 MiB, to a sink that drops what it receives. Each is the
// median of a few runs. The random values come from the CSPRNG of the
// platform, as in production.
//
// dart run tool/encrypt3_bench.dart
//
// dart compile js -O2 -o encrypt3_bench.js tool/encrypt3_bench.dart
// node -e "globalThis.self = globalThis; require('./encrypt3_bench.js')"
//
// ignore_for_file: avoid_print
import 'dart:typed_data';
import 'package:datekeys/datekeys.dart';
const isWeb = identical(0, 0.0);
double median(List<double> xs) => (xs..sort())[xs.length ~/ 2];
/// The median time of [f], in milliseconds, over [runs] runs.
Future<double> time(int runs, Future<void> Function() f) async {
final times = <double>[];
for (var i = 0; i < runs; i++) {
final sw = Stopwatch()..start();
await f();
times.add(sw.elapsedMicroseconds / 1000);
}
return median(times);
}
/// A sink that counts what it receives.
final class Counting implements ByteSink {
int n = 0;
@override
void add(Uint8List bytes) => n += bytes.length;
@override
void close() {}
@override
void abort(Object reason) {}
}
EncryptOptions options({AccessPolicy policy = AccessPolicy.timeOnly}) {
final p = quicknet();
return EncryptOptions(
profile: p,
unlockAt: roundTime(p, 1000),
now: () => Instant(p.genesisTime),
policy: policy,
newPortableKey: policy == AccessPolicy.timeAndKey,
);
}
Future<void> main() async {
final small = [FileSource.bytes('nota.txt', 'Hola.\n'.codeUnits)];
final t1 = await time(9, () async {
await encryptFiles(Counting(), small, options());
});
print('time_only, one small file: ${t1.toStringAsFixed(1)} ms');
final t2 = await time(9, () async {
await encryptFiles(
Counting(),
small,
options(policy: AccessPolicy.timeAndKey),
);
});
print('time_and_key, a portable key: ${t2.toStringAsFixed(1)} ms');
final mib = isWeb ? 16 : 64;
final piece = Uint8List(1 << 20);
final big = FileSource(
path: 'big.bin',
size: mib << 20,
open: () async* {
for (var i = 0; i < mib; i++) {
yield piece;
}
},
);
final sink = Counting();
final t3 = await time(3, () async {
sink.n = 0;
await encryptFiles(sink, [big], options());
});
print(
'$mib MiB in streaming, read twice: ${(t3 / 1000).toStringAsFixed(2)} s, '
'${(mib / (t3 / 1000)).toStringAsFixed(0)} MiB/s, '
'${sink.n} bytes written',
);
}
Loading…
Cancel
Save

Powered by TurnKey Linux.