diff --git a/CHANGELOG.md b/CHANGELOG.md index ed5740f..b046c9c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,22 @@ Cambios notables de la librería Dart. El proyecto usa versionado semántico; mi ## Especificación 0.15, en la rama `v0.15` — sin versión +### Los dados de la llave de palabras (07-10-2026) + +- `datekeys-go` añade en `92e7154`, en la rama `v0.15`, los dados que decidió el autor, para quien no se fía de los números al azar de un ordenador: cinco dados por palabra, leídos en un orden fijo, dan un número de 11111 a 66666, la posición de la palabra en una lista de 7776, el primer dado el más significativo. Son `wordkey.DiceNumber`, `DiceWord`, `DiceWords` y `DiceList`, con `encrypt -dice` y `datekeys wordlist` en la CLI. No cambia ningún formato ni ninguna derivación. +- **`lib/src/wordlist.dart`** porta `dice.go`, con las mismas comprobaciones en el mismo orden y los mismos textos de error, en una `WordKeyException`: + - `diceNumber` da los dados de una posición, y `diceListSize` es 7776; + - `diceWord` da la palabra de cinco dados, después de comprobar el tamaño de la lista: cinco bytes, cada uno una cifra del 1 al 6; + - `diceWords` da las palabras de varios números separados por espacios, y rechaza, por este orden, una lista de otro tamaño; menos de 6 números; y, número a número, uno que no es de cinco dados o que da una palabra que ya salió, y hay que volver a tirar. Separa los números como `strings.Fields` de Go, en los espacios de `unicode.IsSpace`, que son los del §38.1 en los que `normalizeWords` separa las palabras, y no cambia nada más: una cifra de ancho completo o una marca combinante no es un dado; + - `diceList` escribe la lista numerada para los dados, una línea por palabra con sus dados y un tabulador, como publica la EFF la suya: para `en`, sus bytes UTF-8 son el fichero de la EFF, byte a byte (SHA-256 `addd3553…`), y para `es` tienen el SHA-256 `611f779a…`, los dos de `wordlists/README.md`. + + `lib/datekeys.dart` los exporta. `diceWordUtf8` y `diceWordsUtf8`, sobre los bytes de un string de Go, son internas, para las pruebas. Los espacios son los de `goIsSpace` de `go_unicode.dart`, el `unicode.IsSpace` de Go. +- **`testdata` y `wordlists`** pasan a `92e7154`: solo cambian `wordlists/README.md`, con el SHA-256 de cada lista numerada para los dados, y el commit de los dos `SOURCE.json`. +- **Vectores de Go:** `tool/wordlist_go_vectors.go` escribe también los dados, en una exportación de `datekeys-go` en `92e7154`: `DiceNumber` en 24 posiciones; `DiceWord` en 63 casos y `DiceWords` en 78, sobre las listas de Go y sobre listas de índices, con los casos de `TestDiceWord` y `TestDiceWords`, cada espacio de `unicode.IsSpace` entre los números y puntos de código y bytes que no lo son, el orden de las comprobaciones, listas con una palabra más de una vez, también con caracteres que `%q` escapa, y listas de otro tamaño; el SHA-256 de `DiceList` de cada lista; y `DiceWords` con cada punto de código de los planos 0, 1 y 14 entre los dos primeros números, como el SHA-256 de sus resultados. El resto de los vectores sale igual: cambian solo su campo `source` y su descripción. +- **Pruebas.** 12 nuevas en la VM y 7 en Node.js: `test/wordlist_test.dart`, también compilado a JavaScript, con los casos de `TestDiceNumber` de Go, los de `TestDiceWord` y `TestDiceList` que no necesitan una lista de Go, sobre una lista de índices, los vectores sobre los bytes de Go y sobre Strings, y los espacios del plano 0; y `test/wordlist_vm_test.dart`, con los casos de `TestDiceWord`, `TestDiceWords` y `TestDiceList` sobre las dos listas de `wordlists/`, los vectores sobre ellas y los espacios de los planos 0, 1 y 14, que son los de `normalizeWords`. En total, 2243 en la VM y 712 en Node.js. +- **Fallos inyectados** en los dados, uno a uno y revertidos: los 17, en las cifras y su orden, los límites, el orden de las comprobaciones, los espacios, los bytes que no son UTF-8 válido, la palabra que sale dos veces, la lista numerada y los textos, los detectan pruebas que corren también en Node.js. Uno se escapaba al principio, la palabra repetida sin el `%q` de Go, porque las palabras de una lista no tienen nada que escapar, y los vectores tienen ahora una lista con caracteres que `%q` escapa. +- **Diferencias con Go:** ninguna. `diceWord` y `diceWords` toman un String como sus bytes UTF-8, como el resto de `wordlist.dart`, y los bytes UTF-8 del String de `diceList` son el string de Go. + ### Las palabras al azar de la llave de palabras (07-10-2026) - `datekeys-go` saca al azar, en la rama `v0.15` después del tag `spec-v0.15` (`c49c67c` y `27a75ee`), las palabras de una llave de palabras, el SHOULD del §38.1 de ofrecer palabras al azar de una lista pública: `wordkey.Generate`, `List`, `CheckList`, `Bits` y la lista española `wordkey/lists/es.txt`, de 7776 palabras, un borrador sin revisar. No cambia ningún formato ni ninguna derivación. diff --git a/README.md b/README.md index 51a80ba..26bcf3f 100644 --- a/README.md +++ b/README.md @@ -24,9 +24,9 @@ Están hechas las etapas 0 a 5 del plan (`docs/PLAN_dart.md` del espacio de trab - 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; -- fuera del plan, con la v0.15, las palabras al azar de la llave de palabras: las listas de palabras de `datekeys-go`, tomadas solo con su SHA-256 fijado y comprobadas con el alfabeto de su idioma, las palabras sacadas al azar de una y su fuerza en bits. +- fuera del plan, con la v0.15, las palabras al azar de la llave de palabras: las listas de palabras de `datekeys-go`, tomadas solo con su SHA-256 fijado y comprobadas con el alfabeto de su idioma, las palabras sacadas al azar de una y su fuerza en bits, y las de cinco dados cada una, con la lista numerada para los dados. -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. Y saca al azar las palabras de una llave de palabras de las listas de `datekeys-go`, que toma solo con su SHA-256 fijado. +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. Y saca al azar las palabras de una llave de palabras de las listas de `datekeys-go`, que toma solo con su SHA-256 fijado, o las da de unos dados. 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. @@ -91,20 +91,21 @@ La especificación 0.15 da forma de dato al release de una ronda y distingue dos ### Las palabras al azar de la llave de palabras (v0.15) -El §38.1 pide que un escritor ofrezca por defecto palabras al azar de una lista pública: al menos 6 de una lista de 2048 o más. `datekeys-go` las añade en la rama `v0.15`, después del tag `spec-v0.15` (`c49c67c` y `27a75ee`), sin cambiar ningún formato ni ninguna derivación, y `wordlist.dart` las porta de `generate.go` de `wordkey` en `27a75ee`, con las mismas comprobaciones en el mismo orden y los mismos textos de error: +El §38.1 pide que un escritor ofrezca por defecto palabras al azar de una lista pública: al menos 6 de una lista de 2048 o más. `datekeys-go` las añade en la rama `v0.15`, después del tag `spec-v0.15` (`c49c67c` y `27a75ee`), y los dados en `92e7154`, sin cambiar ningún formato ni ninguna derivación, y `wordlist.dart` las porta de `generate.go` de `wordkey` en `27a75ee` y de `dice.go` en `92e7154`, con las mismas comprobaciones en el mismo orden y los mismos textos de error: | Módulo | Contenido | En Go | |---|---|---| -| `lib/src/wordlist.dart` | `readWordList`, las palabras del fichero de una lista; `wordListSha256`, el SHA-256 fijado de cada lista, y `wordListLanguages`; `checkWordList`, las reglas de una lista, con el alfabeto de su idioma; `generateWords`, las palabras sacadas al azar de una lista; `wordBits`, su fuerza en bits; y `defaultWordCount` y `minListSize` | `List`, `Languages`, `CheckList`, `Generate`, `Bits`, `DefaultCount` y `MinListSize` de `wordkey` | +| `lib/src/wordlist.dart` | `readWordList`, las palabras del fichero de una lista; `wordListSha256`, el SHA-256 fijado de cada lista, y `wordListLanguages`; `checkWordList`, las reglas de una lista, con el alfabeto de su idioma; `generateWords`, las palabras sacadas al azar de una lista; `wordBits`, su fuerza en bits; `defaultWordCount` y `minListSize`; y los dados: `diceWords`, las palabras de cinco dados cada una; `diceWord`, la de cinco dados; `diceNumber`, los dados de una posición; `diceList`, la lista numerada para los dados; y `diceListSize` | `List`, `Languages`, `CheckList`, `Generate`, `Bits`, `DefaultCount` y `MinListSize` de `wordkey`; y `DiceWords`, `DiceWord`, `DiceNumber`, `DiceList` y `DiceListSize` | Notas: -- **Las listas no van en la librería.** Go las lleva dentro del módulo; aquí la app descarga o empaqueta el fichero de una lista, `wordkey/lists/.txt` de `datekeys-go`, y se lo da a `readWordList`, que no se fía de él: su SHA-256 debe ser el de `wordListSha256`, con el texto «the list "es" has the SHA-256 …, not …», el mismo de `datekeys-ts`, y sus palabras deben pasar `checkWordList`. Una lista cambia solo con su hash. Hoy hay una, `es`: 7776 palabras, un borrador que todavía no ha revisado un hablante nativo. `wordlists/` tiene la copia de las listas (ver «Datos de prueba»). +- **Las listas no van en la librería.** Go las lleva dentro del módulo; aquí la app descarga o empaqueta el fichero de una lista, `wordkey/lists/.txt` de `datekeys-go`, y se lo da a `readWordList`, que no se fía de él: su SHA-256 debe ser el de `wordListSha256`, con el texto «the list "es" has the SHA-256 …, not …», el mismo de `datekeys-ts`, y sus palabras deben pasar `checkWordList`. Una lista cambia solo con su hash. Hoy hay dos, de 7776 palabras cada una: `en`, la lista grande de la EFF, y `es`, un borrador que todavía no ha revisado un hablante nativo. `wordlists/` tiene la copia de las listas (ver «Datos de prueba»). - **El alfabeto** de cada idioma lo da el código, nunca la lista: para `es`, de la `a` a la `z`, `á`, `é`, `í`, `ó`, `ú`, `ü` y `ñ`, en minúscula y NFC. Una letra de otra escritura que se parece a una de ellas, como la `а` cirílica (U+0430), se volvería a teclear con la latina, y la cápsula no se abriría. Tampoco pasan una mayúscula, una cifra, el CR de un fichero con líneas CRLF ni dos palabras que son una una vez normalizadas, como «papa» y «papá», que valdrían por una. - **El sorteo** es el de Go: cada índice como lo saca `crypto/rand.Int`, con `randomIndex` de la parte 6a, y un índice que ya salió se saca otra vez, así que los mismos bytes dan las mismas palabras que Go. Son 7 palabras por defecto: 7 de 7776 son unos 90 bits (`wordBits`), y 6 de 2048, el mínimo, unos 66. - **`wordBits`** suma el `math.Log2` de Go, portado: `Frexp` y el `Log` de FreeBSD que porta Go, con sus operaciones en su orden, así que el double es el de Go en la VM y en la web, bit a bit. El `log` de la plataforma no es el de Go, y `math.log(x) / math.ln2` no da exacto ni el log2 de 2^29 en la VM: la CLI de Go trunca los bits a un entero. +- **Los dados**, para quien no se fía de los números al azar de un ordenador: cinco dados por palabra, leídos en un orden fijo, dan un número de 11111 a 66666, la posición de la palabra en una lista de 7776 (`diceListSize`), el primer dado la cifra más significativa en base 6. `diceWord` toma cinco bytes, cada uno una cifra del 1 al 6, después de comprobar el tamaño de la lista. `diceWords` toma varios números separados por espacios, al menos 6, y nunca da dos veces la misma palabra: hay que volver a tirar los dados. Los números se separan como los separa `strings.Fields` de Go, en los espacios de `unicode.IsSpace`, que son los del §38.1 en los que `normalizeWords` separa las palabras, y nada más cambia: una cifra de ancho completo o una marca combinante no es un dado. Unos dados justos sacan cada palabra como `generateWords`, así que las palabras son igual de fuertes. `diceList` escribe la lista numerada para los dados, para imprimirla: una línea por palabra, con sus dados, un tabulador y la palabra, como publica la EFF la suya. Para `en`, sus bytes UTF-8 son el fichero de la EFF, byte a byte (SHA-256 `addd3553…`), y para `es` tienen el SHA-256 `611f779a…`, como los recoge `wordlists/README.md`. - **Las diferencias con Go.** Una `RandomSource` no se acaba: lo que lance la fuente sale de `generateWords` tal cual, donde un `io.Reader` de Go que se acaba da `wordkey: EOF`. `readWordList` comprueba el SHA-256, que Go no necesita para sus listas. El fichero se lee como lee Go un string, como bytes: un byte que no es UTF-8 válido es U+FFFD, que ningún alfabeto tiene, así que la comprobación lo rechaza con el texto de Go, y las palabras que pasan se decodifican en estricto. -- **La licencia.** Las listas no son código: `es.txt` es una adaptación de FrequencyWords de Hermit Dave y tiene licencia CC BY-SA 4.0, no Apache-2.0. `wordlists/README.md`, copiado de Go, recoge su fuente, su método y su licencia. -- **Lo que se exporta.** `lib/datekeys.dart` exporta `readWordList`, `checkWordList`, `generateWords`, `wordBits`, `wordListSha256`, `wordListLanguages`, `defaultWordCount` y `minListSize`, como el paquete público `wordkey` de Go. `checkWordListUtf8`, sobre bytes, y `parseWordList`, `readWordList` sin el SHA-256, son internas, para las pruebas. `wordkey.dart` comparte con `wordlist.dart` la comprobación de cada runa, `checkWordRune`, también interna. +- **La licencia.** Las listas no son código: `en.txt` es material original de la EFF, con licencia CC BY 4.0, y `es.txt` es una adaptación de FrequencyWords de Hermit Dave y tiene licencia CC BY-SA 4.0, no Apache-2.0. `wordlists/README.md`, copiado de Go, recoge su fuente, su método y su licencia. +- **Lo que se exporta.** `lib/datekeys.dart` exporta `readWordList`, `checkWordList`, `generateWords`, `wordBits`, `wordListSha256`, `wordListLanguages`, `defaultWordCount` y `minListSize`, y `diceNumber`, `diceWord`, `diceWords`, `diceList` y `diceListSize`, como el paquete público `wordkey` de Go. `checkWordListUtf8`, `diceWordUtf8` y `diceWordsUtf8`, sobre bytes, y `parseWordList`, `readWordList` sin el SHA-256, son internas, para las pruebas. `wordkey.dart` comparte con `wordlist.dart` la comprobación de cada runa, `checkWordRune`, también interna. 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: @@ -599,12 +600,12 @@ tool/check.sh ## 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 `datekeys-go` en `e671032`, la rama `v0.15` después del tag `spec-v0.15`, cuyo `testdata/` es el del tag, `fe50885`, byte a byte: 142 ficheros, con `release.json` y `releases/`, el objeto release, el JSON de drand y el archivo de releases de la v0.15, con el campo `source` de `mutations.json`, con `tlock_steps.json`, los pasos 10 y 11 de Quicknet valor a valor, con `resolved_ip.json`, con `wordkey.json`, los vectores compartidos de la llave de palabras, y los veredictos y las líneas de la v0.12. Cada fichero dice `"spec": "0.15"`, como el `SpecVersion` de Go. +`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 `datekeys-go` en `92e7154`, la rama `v0.15` después del tag `spec-v0.15`, cuyo `testdata/` es el del tag, `fe50885`, byte a byte: 142 ficheros, con `release.json` y `releases/`, el objeto release, el JSON de drand y el archivo de releases de la v0.15, con el campo `source` de `mutations.json`, con `tlock_steps.json`, los pasos 10 y 11 de Quicknet valor a valor, con `resolved_ip.json`, con `wordkey.json`, los vectores compartidos de la llave de palabras, y los veredictos y las líneas de la v0.12. Cada fichero dice `"spec": "0.15"`, como el `SpecVersion` de Go. -`wordlists/` es la copia de `wordkey/lists` del mismo commit: las listas de palabras de `wordkey.Generate`, hoy `en.txt`, la de la EFF, y `es.txt`, y su `README.md`, con la fuente, el método y la licencia de cada lista. `wordlists/SOURCE.json` tiene el formato de `testdata/SOURCE.json`, y `.gitattributes` guarda sus bytes exactos, como los de `testdata/`: `lib/src/wordlist.dart` fija el SHA-256 de cada lista. +`wordlists/` es la copia de `wordkey/lists` del mismo commit: las listas de palabras de `wordkey.Generate`, hoy `en.txt`, la de la EFF, y `es.txt`, y su `README.md`, con la fuente, el método y la licencia de cada lista, y el SHA-256 de cada lista numerada para los dados. `wordlists/SOURCE.json` tiene el formato de `testdata/SOURCE.json`, y `.gitattributes` guarda sus bytes exactos, como los de `testdata/`: `lib/src/wordlist.dart` fija el SHA-256 de cada lista. ```bash -dart run tool/sync_testdata.dart sync --commit e671032 +dart run tool/sync_testdata.dart sync --commit 92e7154 ``` ```bash @@ -614,7 +615,7 @@ dart run tool/sync_testdata.dart check --against ../datekeys-go - `sync` copia los dos árboles del mismo commit y lee los ficheros con git, así que nunca entran cambios sin commit del repositorio de Go. Lo lee todo antes de tocar las copias, y un árbol que falta en el commit es un error. - `check` comprueba los dos árboles con las mismas reglas, sin ficheros que falten, sobren o cambien, y escribe una línea por árbol, la de `testdata` primero. - Los ficheros de `testdata/` y de `wordlists/` no se editan ni se generan aquí. -- En cada `dart test`, `test/testdata_test.dart` comprueba las dos copias, y que cada fichero de `testdata/` nombre `specVersion`. `test/wordlist_vm_test.dart` lee la lista española de `wordlists/` con `readWordList`. +- En cada `dart test`, `test/testdata_test.dart` comprueba las dos copias, y que cada fichero de `testdata/` nombre `specVersion`. `test/wordlist_vm_test.dart` lee las dos listas de `wordlists/` con `readWordList`, y numera las dos para los dados. - `test/tlock_steps_vm_test.dart` recorre `tlock_steps.json` valor a valor con el código de la librería: M, H(M), la ecuación del emparejamiento, las partes del stanza, e(firma, U), H2, sigma, H4, la file key, cada intento de H3 y r, r·G2 = U, y las comprobaciones negativas del generador de Go (otro DST, la ronda sin SHA-256 y H3 que borra solo el bit más alto). `test/tlock_steps_test.dart` hace lo mismo compilado a JavaScript, sobre `test/vectors/tlock_steps.g.dart`, la copia del fichero que escribe `dart run tool/tlock_steps_copy.dart` tras cada `sync`; la prueba de la VM comprueba que es el fichero byte a byte. - `test/release_object_vm_test.dart` recorre `release.json` caso a caso, con el resultado y el texto de Go: los objetos, con sus capas y el paso 10, el release que dice cada uno y su codificación otra vez byte a byte; el JSON de drand; y las búsquedas en el archivo. Comprueba además cada fichero de `releases/` y los textos de `provider.Archive` sobre archivos editados (`test/vectors/release_archive_texts.json`). `test/release_object_test.dart` hace lo mismo compilado a JavaScript, sobre `test/vectors/release.g.dart`, que escribe `dart run tool/release_copy.dart` tras cada `sync`. - `test/open_corpus_test.dart` abre cada caso de `mutations.json` con su `source`: `supplied`, el objeto release de su `release` en `OpenOptions.release`, o `network`, una fuente que verifica el release y lo descarta si no cumple el paso 10, como `testkit` de Go. @@ -722,10 +723,10 @@ cp tool/age_writer_go_vectors.go "$tmp" rm -rf "$tmp" ``` -El de las listas de palabras corre en una exportación del commit de `wordlists/SOURCE.json`, para que sus vectores sean los de esas listas: `tool/wordlist_go_vectors.go` lee `wordkey/lists` y da a `Generate`, en lugar de `crypto/rand`, el keystream de `SeededRandomSource`, bloques de SHA-256 o bytes que se acaban. La salida es la misma en cada ejecución: +El de las listas de palabras corre en una exportación del commit de `wordlists/SOURCE.json`, para que sus vectores sean los de esas listas: `tool/wordlist_go_vectors.go` lee `wordkey/lists`, da a `Generate`, en lugar de `crypto/rand`, el keystream de `SeededRandomSource`, bloques de SHA-256 o bytes que se acaban, y tira los dados sobre las listas de Go y sobre listas de índices. La salida es la misma en cada ejecución: ```bash -commit=$(git -C ../datekeys-go rev-parse e671032) +commit=$(git -C ../datekeys-go rev-parse 92e7154) out=$PWD/test/vectors tmp=$(mktemp -d) git -C ../datekeys-go archive "$commit" | tar -x -C "$tmp" @@ -816,7 +817,7 @@ rm -rf "$tmp" | `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 | -| `wordlist_vectors.json` | Las listas de palabras de `wordkey` (`generate.go`), en `e671032`: el SHA-256, los bytes y las palabras de cada lista de Go; el cuerpo de `List` sobre 19 textos de una lista base, con y sin el LF final, con líneas CRLF, una línea vacía, una marca de orden de bytes y bytes que no son UTF-8 válido; `CheckList` sobre 83 listas editadas, con los casos de `TestCheckList`; `CheckList` con cada punto de código de los planos 0, 1 y 14 en la primera palabra, como el SHA-256 de sus resultados, y uno a uno hasta U+017F; `Generate` en 51 casos, sobre la lista española y listas de 12 a 2^20 + 1 palabras, con el keystream de `SeededRandomSource`, bloques de SHA-256 y bytes que se acaban, entre ellos la semilla de `TestGenerate`, que solo saca dos índices y se queda sin bytes; y `Bits`, el double de Go bit a bit, en 221 casos y cuatro resúmenes | +| `wordlist_vectors.json` | Las listas de palabras de `wordkey` (`generate.go`) y sus dados (`dice.go`), en `92e7154`: el SHA-256, los bytes y las palabras de cada lista de Go; el cuerpo de `List` sobre 19 textos de una lista base, con y sin el LF final, con líneas CRLF, una línea vacía, una marca de orden de bytes y bytes que no son UTF-8 válido; `CheckList` sobre 83 listas editadas, con los casos de `TestCheckList`; `CheckList` con cada punto de código de los planos 0, 1 y 14 en la primera palabra, como el SHA-256 de sus resultados, y uno a uno hasta U+017F; `Generate` en 51 casos, sobre la lista española y listas de 12 a 2^20 + 1 palabras, con el keystream de `SeededRandomSource`, bloques de SHA-256 y bytes que se acaban, entre ellos la semilla de `TestGenerate`, que solo saca dos índices y se queda sin bytes; `Bits`, el double de Go bit a bit, en 221 casos y cuatro resúmenes; y los dados: `DiceNumber` en 24 posiciones; `DiceWord` en 63 casos y `DiceWords` en 78, sobre las listas de Go y sobre listas de índices, con los casos de `TestDiceWord` y `TestDiceWords`, cada espacio de `unicode.IsSpace` entre los números y puntos de código y bytes que no lo son, el orden de las comprobaciones, listas con una palabra más de una vez, también con caracteres que `%q` escapa, y listas de otro tamaño; el SHA-256 de `DiceList` de cada lista, el fichero de la EFF para `en`; y `DiceWords` con cada punto de código de los planos 0, 1 y 14 entre los dos primeros números, como el SHA-256 de sus resultados | | `wordlist_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 222 casos del corpus de la v0.15 lo hacen con Go en `fe50885`, que abre cada caso con su `source`, como `testkit` | | `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 | diff --git a/lib/datekeys.dart b/lib/datekeys.dart index 3064d55..45a5a1a 100644 --- a/lib/datekeys.dart +++ b/lib/datekeys.dart @@ -42,7 +42,8 @@ /// the word lists of datekeys-go, which [readWordList] takes only with the /// SHA-256 that [wordListSha256] pins and checks against the alphabet of /// their language, the words that [generateWords] draws from one, and their -/// strength in bits, [wordBits]. +/// strength in bits, [wordBits]; and the words of five dice each, +/// [diceWords], with the list numbered for dice, [diceList]. /// /// DER, the primitives, the reading and the writing of age, the recipients /// and the random sources, agewrap, the curve arithmetic, the IBE of tlock @@ -130,6 +131,11 @@ export 'src/wordlist.dart' show checkWordList, defaultWordCount, + diceList, + diceListSize, + diceNumber, + diceWord, + diceWords, generateWords, minListSize, readWordList, diff --git a/lib/src/wordlist.dart b/lib/src/wordlist.dart index a359fb2..a1646ad 100644 --- a/lib/src/wordlist.dart +++ b/lib/src/wordlist.dart @@ -4,7 +4,10 @@ /// (generate.go): the word lists of datekeys-go, taken only with the /// SHA-256 pinned here and checked against the alphabet of their language; /// the words drawn from one, uniformly, with a [RandomSource]; and their -/// strength in bits. +/// strength in bits. And the words of dice, as DiceNumber, DiceWord, +/// DiceWords and DiceList (dice.go), for whoever does not trust the random +/// numbers of a computer: five dice for each word of a list of +/// [diceListSize] words, and the list numbered for dice, to print it. /// /// Go builds its lists into the module; this library holds none. The app /// downloads or bundles the file of a list, `wordkey/lists/.txt` of @@ -26,6 +29,7 @@ import 'dart:math' as math; import 'dart:typed_data'; import 'bytes.dart'; +import 'go_unicode.dart' show goIsSpace; import 'pathrule.dart' show codePointName; import 'random.dart'; import 'sha256.dart'; @@ -296,3 +300,164 @@ double _log(double x) { final hfsq = 0.5 * f * f; return k * ln2Hi - ((hfsq - (s * (hfsq + r) + k * ln2Lo)) - f); } + +/// The size of a list that dice draw from, Go's DiceListSize: five dice, +/// each from 1 to 6, give 6^5 positions. Whoever does not trust the random +/// numbers of a computer rolls them, and looks the words up in the list +/// numbered for dice ([diceList]). +const diceListSize = 7776; + +/// The dice of the word at position [i] of a list of [diceListSize] words, +/// as Go's DiceNumber: five digits from 1 to 6, each one more than a digit +/// of [i] in base 6, the first the most significant, so that 11111 is the +/// first word and 66666 the last, as in the list of the EFF. Throws a +/// [WordKeyException] with Go's text, `wordkey: no dice give position 7776 +/// of a list of 7776 words`, for a position outside the list. +String diceNumber(int i) { + if (i < 0 || i >= diceListSize) { + throw WordKeyException( + 'wordkey: no dice give position $i of a list of $diceListSize words', + ); + } + final dice = Uint8List(5); + var n = i; + for (var k = dice.length - 1; k >= 0; k--) { + dice[k] = 0x31 + n % 6; + n ~/= 6; + } + return String.fromCharCodes(dice); +} + +/// The word of [list] that [dice] give, as Go's DiceWord: five digits from +/// 1 to 6, as [diceNumber] numbers the words. The list must have +/// [diceListSize] words, and is taken as it is: one that [readWordList] +/// gave. [dice] is taken as its UTF-8 bytes, as Go takes a string: five +/// bytes, each a digit from 1 to 6, and nothing else, so that white space +/// around them or a full-width digit is refused. Throws a +/// [WordKeyException] with Go's text: `wordkey: dice draw from a list of +/// 7776 words, not 2048` for a list of another size, first; then +/// `wordkey: "1116" is not five dice: five digits from 1 to 6`. +String diceWord(List list, String dice) => + diceWordUtf8(list, utf8Bytes(dice)); + +/// [diceWord] of the bytes of a Go string, [dice]. +String diceWordUtf8(List list, List dice) { + _checkDiceList(list); + return list[_diceIndex(dice)]; +} + +/// The words that [dice] give, in their order, as Go's DiceWords: one +/// number of five dice for each word, as [diceWord] reads it, separated by +/// white space, at least [minWords] of them, and never the same word twice, +/// which whoever rolls rolls again. Fair dice draw each word as +/// [generateWords] does, so the words are as strong ([wordBits]). +/// +/// [dice] is split as Go's strings.Fields splits its UTF-8 bytes: at the +/// white space of Go's unicode.IsSpace, the set of spec §38.1 at which +/// [normalizeWords] splits (the space, the tab, the line feed, U+00A0, +/// U+3000 and the others), and nothing else changes: the numbers are not +/// normalized, and a combining mark stays in its number. Throws a +/// [WordKeyException] with Go's text for the first failure, in this order: +/// the size of [list], as [diceWord]; fewer than [minWords] numbers, +/// `wordkey: a key of words needs at least 6 words, not 5`; then, number by +/// number, one that is not five dice, with the text of [diceWord], or one +/// that gives a word already given, `wordkey: the dice 11111 give "abacus" +/// a second time; roll them again`. +List diceWords(List list, String dice) => + diceWordsUtf8(list, utf8Bytes(dice)); + +/// [diceWords] of the bytes of a Go string, [dice]. +List diceWordsUtf8(List list, List dice) { + _checkDiceList(list); + final numbers = _fields(dice); + if (numbers.length < minWords) { + throw WordKeyException( + 'wordkey: a key of words needs at least $minWords words, not ' + '${numbers.length}', + ); + } + // The words given so far: Go compares the words, not their dice, and a + // list that holds a word twice gives it twice from two numbers. + final seen = {}; + final words = []; + for (final n in numbers) { + final w = list[_diceIndex(n)]; + if (!seen.add(w)) { + // Five digits from 1 to 6, as Go's %s writes their bytes. + throw WordKeyException( + 'wordkey: the dice ${String.fromCharCodes(n)} give ${_q(w)} a ' + 'second time; roll them again', + ); + } + words.add(w); + } + return words; +} + +/// [list] numbered for dice, to print it, as Go's DiceList: a line for each +/// word, its dice ([diceNumber]), a tab, the word and a line feed, as the +/// EFF publishes its list. For the English list, its UTF-8 bytes are the +/// file of the EFF, byte for byte. Throws a [WordKeyException] with Go's +/// text for a list of another size, as [diceWord]. +String diceList(List list) { + _checkDiceList(list); + final out = StringBuffer(); + for (var i = 0; i < list.length; i++) { + out + ..write(diceNumber(i)) + ..write('\t') + ..write(list[i]) + ..write('\n'); + } + return out.toString(); +} + +// The first check of DiceWord, DiceWords and DiceList of Go: the size of +// the list. +void _checkDiceList(List list) { + if (list.length != diceListSize) { + throw WordKeyException( + 'wordkey: dice draw from a list of $diceListSize words, not ' + '${list.length}', + ); + } +} + +// The position that [dice], the bytes of a Go string, give in a list of +// diceListSize words, as Go's DiceWord reads them: five bytes, then each a +// digit from 1 to 6, the first the most significant digit in base 6. +int _diceIndex(List dice) { + if (dice.length != 5) throw _notDice(dice); + var i = 0; + for (final c in dice) { + if (c < 0x31 || c > 0x36) throw _notDice(dice); + i = i * 6 + c - 0x31; + } + return i; +} + +WordKeyException _notDice(List dice) => WordKeyException( + 'wordkey: ${goQuote(dice)} is not five dice: five digits from 1 to 6', +); + +// The fields of [text], the bytes of a Go string, as Go's strings.Fields +// cuts them: the runs between the runes of Go's unicode.IsSpace, read as +// Go's `for range` reads them, so that a byte that is not valid UTF-8 is +// U+FFFD, which is not white space and stays in its field. +List _fields(List text) { + final b = text is Uint8List ? text : Uint8List.fromList(text); + final fields = []; + var start = -1; + for (var i = 0; i < b.length;) { + final (r, size) = b[i] < 0x80 ? (b[i], 1) : decodeRune(b, i); + if (goIsSpace(r)) { + if (start >= 0) fields.add(Uint8List.sublistView(b, start, i)); + start = -1; + } else if (start < 0) { + start = i; + } + i += size; + } + if (start >= 0) fields.add(Uint8List.sublistView(b, start)); + return fields; +} diff --git a/test/vectors/wordlist_vectors.g.dart b/test/vectors/wordlist_vectors.g.dart index 5eee347..56d50b8 100644 --- a/test/vectors/wordlist_vectors.g.dart +++ b/test/vectors/wordlist_vectors.g.dart @@ -5,14 +5,16 @@ /// The text of test/vectors/wordlist_vectors.json. const wordlistVectorsJson = r''' { - "description": "The word lists of package wordkey of the Go reference (generate.go, spec \u00a738.1) for lib/src/wordlist.dart; see tool/wordlist_go_vectors.go. Binary values and words are lower-case hex. The base list is word i = pal and three letters, a + i/676, a + i/26%26 and a + i%26; a case of size n takes its first n words, replaces the word at each at of set, and adds those of append. lists: the SHA-256, bytes and words of each built-in list. parse: the body of List on the text prefix, the base list joined by sep, and suffix (sha256 is that of the text): the words read, and ok or the error. check_list: CheckList, ok or the error (sha256 is that of the words joined by LF). alphabet: CheckList of the base list of 2048 whose first word is pala, a code point and zz, for each code point of the planes but the surrogates: the SHA-256 of the lines U+XXXX result, the code points of the words it accepts, and alphabet_results, each result up to U+017F. generate: Generate on the list of the Spanish words (list es) or on a list of size words whose indices they are, reading a stream: seeded, the keystream of SeededRandomSource of the seed; counter, SHA-256 of the label and a 32-bit big-endian counter from 0, block after block; bytes, hex repeated repeat times, which end. The indices drawn, the bytes read and the next four of an endless stream, or the error. bits: Bits, its shortest decimal and the hex of its float64. bits_digests: the SHA-256 of the 8 big-endian bytes of each Bits of the sizes and counts, inclusive, sizes outer.", + "description": "The word lists of package wordkey of the Go reference (generate.go and dice.go, spec \u00a738.1) for lib/src/wordlist.dart; see tool/wordlist_go_vectors.go. Binary values and words are lower-case hex. The base list is word i = pal and three letters, a + i/676, a + i/26%26 and a + i%26; a case of size n takes its first n words, replaces the word at each at of set, and adds those of append. lists: the SHA-256, bytes and words of each built-in list. parse: the body of List on the text prefix, the base list joined by sep, and suffix (sha256 is that of the text): the words read, and ok or the error. check_list: CheckList, ok or the error (sha256 is that of the words joined by LF). alphabet: CheckList of the base list of 2048 whose first word is pala, a code point and zz, for each code point of the planes but the surrogates: the SHA-256 of the lines U+XXXX result, the code points of the words it accepts, and alphabet_results, each result up to U+017F. generate: Generate on the list of the Spanish words (list es) or on a list of size words whose indices they are, reading a stream: seeded, the keystream of SeededRandomSource of the seed; counter, SHA-256 of the label and a 32-bit big-endian counter from 0, block after block; bytes, hex repeated repeat times, which end. The indices drawn, the bytes read and the next four of an endless stream, or the error. bits: Bits, its shortest decimal and the hex of its float64. bits_digests: the SHA-256 of the 8 big-endian bytes of each Bits of the sizes and counts, inclusive, sizes outer. dice_list_size: DiceListSize. dice_space: DiceWords of the text 11111, a code point and 11112 11113 11114 11115 11116 11121 on the list of the 7776 indices, for each code point of the planes but the surrogates: the SHA-256 of the lines U+XXXX result, and split, the code points at which the numbers are split. dice_number: DiceNumber of i, the dice or the error. dice_word and dice_words: DiceWord and DiceWords of the bytes dice, in hex, on the English or the Spanish list (list en or es) or on a list of size words, each its index in decimal, modulo mod if there is one, followed by suffix: the word or the words, as text, or the error. dice_list: DiceList of the list, the SHA-256 and the bytes of its text, or the error.", "generator": "tool/wordlist_go_vectors.go, go1.26.8", - "source": "e671032e04a3cd0999cf073ebedf50c45b088683", + "source": "92e7154c23945c9b9a2893750b6d4d128f848d3e", "default_count": 7, "min_list_size": 2048, "min_words": 6, "min_letters": 3, "alphabet": {"word":"pala, the code point and zz, the first word of the base list of 2048","planes":[0,1,14],"count":194560,"sha256":"9119a1741586b087cc8fdcce99b00931d34c926f5dc54d7fa0ff250402a11ba9","ok":["a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","\u00e1","\u00e9","\u00ed","\u00f1","\u00f3","\u00fa","\u00fc"]}, + "dice_list_size": 7776, + "dice_space": {"text":"11111, the code point and 11112 11113 11114 11115 11116 11121, on the list of the 7776 indices","planes":[0,1,14],"count":194560,"sha256":"cf34ff425b6da4822dd798828c6172861df2e8497502991bc96e67b36a9c64d1","split":[9,10,11,12,13,32,133,160,5760,8192,8193,8194,8195,8196,8197,8198,8199,8200,8201,8202,8232,8233,8239,8287,12288]}, "lists": [ {"lang":"en","sha256":"6d557f0693958fb5e650b68b5bee585eb82cf4da32965505c789e924743bc522","bytes":62144,"words":7776}, {"lang":"es","sha256":"ff77b487765c000da97cca58fe94a2cdb947303e7a07460614d7d95d800034fe","bytes":61843,"words":7776} @@ -790,6 +792,187 @@ const wordlistVectorsJson = r''' {"name":"6 to 8 words of 2048 to 10000","sizes":[2048,10000],"counts":[6,8],"sha256":"9e055008813b6d2ec5b9477a777c44d30d650134cd890eeb679552403297332e"}, {"name":"0 to 600 words of 7776","sizes":[7776,7776],"counts":[0,600],"sha256":"b6e756155be0121f6db294a47b3dbb947652cc931fb078f1db5a9e39643c905a"}, {"name":"1 word of 2^32 - 4096 to 2^32 + 4096","sizes":[4294963200,4294971392],"counts":[1,1],"sha256":"a95430f39eda0ef2865e11d1b92c8b0e604e4a9387ac881905d77af880240d2d"} + ], + "dice_number": [ + {"i":0,"dice":"11111"}, + {"i":1,"dice":"11112"}, + {"i":5,"dice":"11116"}, + {"i":6,"dice":"11121"}, + {"i":35,"dice":"11166"}, + {"i":36,"dice":"11211"}, + {"i":215,"dice":"11666"}, + {"i":216,"dice":"12111"}, + {"i":1295,"dice":"16666"}, + {"i":1296,"dice":"21111"}, + {"i":2047,"dice":"24362"}, + {"i":2048,"dice":"24363"}, + {"i":3495,"dice":"35214"}, + {"i":7774,"dice":"66665"}, + {"i":7775,"dice":"66666"}, + {"i":-1,"error":"wordkey: no dice give position -1 of a list of 7776 words"}, + {"i":7776,"error":"wordkey: no dice give position 7776 of a list of 7776 words"}, + {"i":7777,"error":"wordkey: no dice give position 7777 of a list of 7776 words"}, + {"i":-7776,"error":"wordkey: no dice give position -7776 of a list of 7776 words"}, + {"i":2147483648,"error":"wordkey: no dice give position 2147483648 of a list of 7776 words"}, + {"i":-2147483648,"error":"wordkey: no dice give position -2147483648 of a list of 7776 words"}, + {"i":4294967296,"error":"wordkey: no dice give position 4294967296 of a list of 7776 words"}, + {"i":9007199254740991,"error":"wordkey: no dice give position 9007199254740991 of a list of 7776 words"}, + {"i":-9007199254740991,"error":"wordkey: no dice give position -9007199254740991 of a list of 7776 words"} + ], + "dice_word": [ + {"name":"en 11111","list":"en","size":7776,"dice":"3131313131","word":"abacus"}, + {"name":"en 11112","list":"en","size":7776,"dice":"3131313132","word":"abdomen"}, + {"name":"en 35214","list":"en","size":7776,"dice":"3335323134","word":"jovial"}, + {"name":"en 66666","list":"en","size":7776,"dice":"3636363636","word":"zoom"}, + {"name":"es 11111","list":"es","size":7776,"dice":"3131313131","word":"abad"}, + {"name":"es 35214","list":"es","size":7776,"dice":"3335323134","word":"glaciar"}, + {"name":"es 66666","list":"es","size":7776,"dice":"3636363636","word":"\u00fatil"}, + {"name":"11111","size":7776,"dice":"3131313131","word":"0"}, + {"name":"11112","size":7776,"dice":"3131313132","word":"1"}, + {"name":"11116","size":7776,"dice":"3131313136","word":"5"}, + {"name":"11121","size":7776,"dice":"3131313231","word":"6"}, + {"name":"11166","size":7776,"dice":"3131313636","word":"35"}, + {"name":"11211","size":7776,"dice":"3131323131","word":"36"}, + {"name":"16666","size":7776,"dice":"3136363636","word":"1295"}, + {"name":"21111","size":7776,"dice":"3231313131","word":"1296"}, + {"name":"12345","size":7776,"dice":"3132333435","word":"310"}, + {"name":"35214","size":7776,"dice":"3335323134","word":"3495"}, + {"name":"65432","size":7776,"dice":"3635343332","word":"7465"}, + {"name":"66665","size":7776,"dice":"3636363635","word":"7774"}, + {"name":"66666","size":7776,"dice":"3636363636","word":"7775"}, + {"name":"no dice","size":7776,"dice":"","error":"wordkey: \"\" is not five dice: five digits from 1 to 6"}, + {"name":"four dice","size":7776,"dice":"31313131","error":"wordkey: \"1111\" is not five dice: five digits from 1 to 6"}, + {"name":"six dice","size":7776,"dice":"313131313131","error":"wordkey: \"111111\" is not five dice: five digits from 1 to 6"}, + {"name":"a 0","size":7776,"dice":"3131313130","error":"wordkey: \"11110\" is not five dice: five digits from 1 to 6"}, + {"name":"a 7","size":7776,"dice":"3131313137","error":"wordkey: \"11117\" is not five dice: five digits from 1 to 6"}, + {"name":"a letter","size":7776,"dice":"6131313131","error":"wordkey: \"a1111\" is not five dice: five digits from 1 to 6"}, + {"name":"four dice and a space","size":7776,"dice":"3131313120","error":"wordkey: \"1111 \" is not five dice: five digits from 1 to 6"}, + {"name":"a full-width 1 and four dice","size":7776,"dice":"efbc9131313131","error":"wordkey: \"\uff111111\" is not five dice: five digits from 1 to 6"}, + {"name":"a space and four dice","size":7776,"dice":"2031313131","error":"wordkey: \" 1111\" is not five dice: five digits from 1 to 6"}, + {"name":"a space and five dice","size":7776,"dice":"203131313131","error":"wordkey: \" 11111\" is not five dice: five digits from 1 to 6"}, + {"name":"five dice and a space","size":7776,"dice":"313131313120","error":"wordkey: \"11111 \" is not five dice: five digits from 1 to 6"}, + {"name":"five dice and LF","size":7776,"dice":"31313131310a","error":"wordkey: \"11111\\n\" is not five dice: five digits from 1 to 6"}, + {"name":"a tab and four dice","size":7776,"dice":"0931313131","error":"wordkey: \"\\t1111\" is not five dice: five digits from 1 to 6"}, + {"name":"two and two dice around a space","size":7776,"dice":"3131203131","error":"wordkey: \"11 11\" is not five dice: five digits from 1 to 6"}, + {"name":"NUL","size":7776,"dice":"3131313100","error":"wordkey: \"1111\\x00\" is not five dice: five digits from 1 to 6"}, + {"name":"DEL","size":7776,"dice":"313131317f","error":"wordkey: \"1111\\x7f\" is not five dice: five digits from 1 to 6"}, + {"name":"a quotation mark","size":7776,"dice":"3131313122","error":"wordkey: \"1111\\\"\" is not five dice: five digits from 1 to 6"}, + {"name":"a backslash","size":7776,"dice":"313131315c","error":"wordkey: \"1111\\\\\" is not five dice: five digits from 1 to 6"}, + {"name":"00000","size":7776,"dice":"3030303030","error":"wordkey: \"00000\" is not five dice: five digits from 1 to 6"}, + {"name":"77777","size":7776,"dice":"3737373737","error":"wordkey: \"77777\" is not five dice: five digits from 1 to 6"}, + {"name":"99999","size":7776,"dice":"3939393939","error":"wordkey: \"99999\" is not five dice: five digits from 1 to 6"}, + {"name":"a slash, the byte before 0","size":7776,"dice":"313131312f","error":"wordkey: \"1111/\" is not five dice: five digits from 1 to 6"}, + {"name":"a colon, the byte after 9","size":7776,"dice":"313131313a","error":"wordkey: \"1111:\" is not five dice: five digits from 1 to 6"}, + {"name":"five full-width digits","size":7776,"dice":"efbc91efbc92efbc93efbc94efbc95","error":"wordkey: \"\uff11\uff12\uff13\uff14\uff15\" is not five dice: five digits from 1 to 6"}, + {"name":"five Arabic-Indic digits","size":7776,"dice":"d9a1d9a2d9a3d9a4d9a5","error":"wordkey: \"\u0661\u0662\u0663\u0664\u0665\" is not five dice: five digits from 1 to 6"}, + {"name":"a superscript 1","size":7776,"dice":"31313131c2b9","error":"wordkey: \"1111\u00b9\" is not five dice: five digits from 1 to 6"}, + {"name":"a circled 1 and two dice: five bytes","size":7776,"dice":"e291a03131","error":"wordkey: \"\u246011\" is not five dice: five digits from 1 to 6"}, + {"name":"a mathematical bold 1 and a die: five bytes","size":7776,"dice":"f09d9f8f31","error":"wordkey: \"\ud835\udfcf1\" is not five dice: five digits from 1 to 6"}, + {"name":"four dice and an e with an acute accent: five characters","size":7776,"dice":"31313131c3a9","error":"wordkey: \"1111\u00e9\" is not five dice: five digits from 1 to 6"}, + {"name":"three dice and an e with an acute accent: five bytes","size":7776,"dice":"313131c3a9","error":"wordkey: \"111\u00e9\" is not five dice: five digits from 1 to 6"}, + {"name":"five dice and a combining acute accent","size":7776,"dice":"3131313131cc81","error":"wordkey: \"11111\u0301\" is not five dice: five digits from 1 to 6"}, + {"name":"three dice and the emoji of a die: five UTF-16 code units","size":7776,"dice":"313131f09f8eb2","error":"wordkey: \"111\ud83c\udfb2\" is not five dice: five digits from 1 to 6"}, + {"name":"NBSP and four dice","size":7776,"dice":"c2a031313131","error":"wordkey: \"\\u00a01111\" is not five dice: five digits from 1 to 6"}, + {"name":"an ideographic space and five dice","size":7776,"dice":"e380803131313131","error":"wordkey: \"\\u300011111\" is not five dice: five digits from 1 to 6"}, + {"name":"two dice and a surrogate in UTF-8 bytes: five bytes","size":7776,"dice":"3131eda080","error":"wordkey: \"11\\xed\\xa0\\x80\" is not five dice: five digits from 1 to 6"}, + {"name":"four dice and a byte that is not valid UTF-8","size":7776,"dice":"31313131ff","error":"wordkey: \"1111\\xff\" is not five dice: five digits from 1 to 6"}, + {"name":"five bytes that are not valid UTF-8","size":7776,"dice":"fffefdfcfb","error":"wordkey: \"\\xff\\xfe\\xfd\\xfc\\xfb\" is not five dice: five digits from 1 to 6"}, + {"name":"four dice and a sequence cut short","size":7776,"dice":"31313131c3","error":"wordkey: \"1111\\xc3\" is not five dice: five digits from 1 to 6"}, + {"name":"\"11111\" on a list of 2048 words","size":2048,"dice":"3131313131","error":"wordkey: dice draw from a list of 7776 words, not 2048"}, + {"name":"\"11111\" on a list of 7775 words","size":7775,"dice":"3131313131","error":"wordkey: dice draw from a list of 7776 words, not 7775"}, + {"name":"\"66666\" on a list of 7777 words","size":7777,"dice":"3636363636","error":"wordkey: dice draw from a list of 7776 words, not 7777"}, + {"name":"\"\" on a list of 0 words","size":0,"dice":"","error":"wordkey: dice draw from a list of 7776 words, not 0"}, + {"name":"\"x\" on a list of 1 words","size":1,"dice":"78","error":"wordkey: dice draw from a list of 7776 words, not 1"} + ], + "dice_words": [ + {"name":"en, seven numbers among white space","list":"en","size":7776,"dice":"2031313131312031313131320931313131330a313131313420313131313520313131313620313131323120","words":["abacus","abdomen","abdominal","abide","abiding","ability","ablaze"]}, + {"name":"en, five numbers","list":"en","size":7776,"dice":"3131313131203131313132203131313133203131313134203131313135","error":"wordkey: a key of words needs at least 6 words, not 5"}, + {"name":"en, no numbers","list":"en","size":7776,"dice":"","error":"wordkey: a key of words needs at least 6 words, not 0"}, + {"name":"en, four dice","list":"en","size":7776,"dice":"31313131312031313131322031313131332031313131342031313131352031313136","error":"wordkey: \"1116\" is not five dice: five digits from 1 to 6"}, + {"name":"en, 11111 twice","list":"en","size":7776,"dice":"3131313131203131313132203131313133203131313134203131313135203131313131","error":"wordkey: the dice 11111 give \"abacus\" a second time; roll them again"}, + {"name":"en, a comma between two numbers","list":"en","size":7776,"dice":"31313131312c3131313132203131313133203131313134203131313135203131313136203131313231","error":"wordkey: \"11111,11112\" is not five dice: five digits from 1 to 6"}, + {"name":"seven numbers among white space","size":7776,"dice":"2031313131312031313131320931313131330a313131313420313131313520313131313620313131323120","words":["0","1","2","3","4","5","6"]}, + {"name":"five numbers","size":7776,"dice":"3131313131203131313132203131313133203131313134203131313135","error":"wordkey: a key of words needs at least 6 words, not 5"}, + {"name":"no numbers","size":7776,"dice":"","error":"wordkey: a key of words needs at least 6 words, not 0"}, + {"name":"four dice","size":7776,"dice":"31313131312031313131322031313131332031313131342031313131352031313136","error":"wordkey: \"1116\" is not five dice: five digits from 1 to 6"}, + {"name":"11111 twice","size":7776,"dice":"3131313131203131313132203131313133203131313134203131313135203131313131","error":"wordkey: the dice 11111 give \"0\" a second time; roll them again"}, + {"name":"a comma between two numbers","size":7776,"dice":"31313131312c3131313132203131313133203131313134203131313135203131313136203131313231","error":"wordkey: \"11111,11112\" is not five dice: five digits from 1 to 6"}, + {"name":"a list of 7775 words","size":7775,"dice":"3131313131203131313132203131313133203131313134203131313135203131313136","error":"wordkey: dice draw from a list of 7776 words, not 7775"}, + {"name":"es, seven numbers","list":"es","size":7776,"dice":"3636363636203131313132203335323134203132333435203534333231203233343536203131313131","words":["\u00fatil","abad\u00eda","glaciar","alimentar","propicio","contener","abad"]}, + {"name":"es, 66666 twice","list":"es","size":7776,"dice":"3636363636203131313131203335323134203132333435203534333231203233343536203636363636","error":"wordkey: the dice 66666 give \"\u00fatil\" a second time; roll them again"}, + {"name":"six numbers","size":7776,"dice":"3131313132203131313133203131313134203131313135203131313136203131313231","words":["1","2","3","4","5","6"]}, + {"name":"twelve numbers","size":7776,"dice":"3131313131203232323232203333333333203434343434203535353535203636363636203132333435203233343536203334353631203435363132203536313233203631323334","words":["0","1555","3110","4665","6220","7775","310","1865","3414","4933","6272","6531"]}, + {"name":"white space only","size":7776,"dice":"20090ac2a0e3808020","error":"wordkey: a key of words needs at least 6 words, not 0"}, + {"name":"U+0009 between the numbers","size":7776,"dice":"3131313131093131313132093131313133093131313134093131313135093131313136093131313231","words":["0","1","2","3","4","5","6"]}, + {"name":"U+000A between the numbers","size":7776,"dice":"31313131310a31313131320a31313131330a31313131340a31313131350a31313131360a3131313231","words":["0","1","2","3","4","5","6"]}, + {"name":"U+000B between the numbers","size":7776,"dice":"31313131310b31313131320b31313131330b31313131340b31313131350b31313131360b3131313231","words":["0","1","2","3","4","5","6"]}, + {"name":"U+000C between the numbers","size":7776,"dice":"31313131310c31313131320c31313131330c31313131340c31313131350c31313131360c3131313231","words":["0","1","2","3","4","5","6"]}, + {"name":"U+000D between the numbers","size":7776,"dice":"31313131310d31313131320d31313131330d31313131340d31313131350d31313131360d3131313231","words":["0","1","2","3","4","5","6"]}, + {"name":"U+0020 between the numbers","size":7776,"dice":"3131313131203131313132203131313133203131313134203131313135203131313136203131313231","words":["0","1","2","3","4","5","6"]}, + {"name":"U+0085 between the numbers","size":7776,"dice":"3131313131c2853131313132c2853131313133c2853131313134c2853131313135c2853131313136c2853131313231","words":["0","1","2","3","4","5","6"]}, + {"name":"U+00A0 between the numbers","size":7776,"dice":"3131313131c2a03131313132c2a03131313133c2a03131313134c2a03131313135c2a03131313136c2a03131313231","words":["0","1","2","3","4","5","6"]}, + {"name":"U+1680 between the numbers","size":7776,"dice":"3131313131e19a803131313132e19a803131313133e19a803131313134e19a803131313135e19a803131313136e19a803131313231","words":["0","1","2","3","4","5","6"]}, + {"name":"U+2000 between the numbers","size":7776,"dice":"3131313131e280803131313132e280803131313133e280803131313134e280803131313135e280803131313136e280803131313231","words":["0","1","2","3","4","5","6"]}, + {"name":"U+2001 between the numbers","size":7776,"dice":"3131313131e280813131313132e280813131313133e280813131313134e280813131313135e280813131313136e280813131313231","words":["0","1","2","3","4","5","6"]}, + {"name":"U+2002 between the numbers","size":7776,"dice":"3131313131e280823131313132e280823131313133e280823131313134e280823131313135e280823131313136e280823131313231","words":["0","1","2","3","4","5","6"]}, + {"name":"U+2003 between the numbers","size":7776,"dice":"3131313131e280833131313132e280833131313133e280833131313134e280833131313135e280833131313136e280833131313231","words":["0","1","2","3","4","5","6"]}, + {"name":"U+2004 between the numbers","size":7776,"dice":"3131313131e280843131313132e280843131313133e280843131313134e280843131313135e280843131313136e280843131313231","words":["0","1","2","3","4","5","6"]}, + {"name":"U+2005 between the numbers","size":7776,"dice":"3131313131e280853131313132e280853131313133e280853131313134e280853131313135e280853131313136e280853131313231","words":["0","1","2","3","4","5","6"]}, + {"name":"U+2006 between the numbers","size":7776,"dice":"3131313131e280863131313132e280863131313133e280863131313134e280863131313135e280863131313136e280863131313231","words":["0","1","2","3","4","5","6"]}, + {"name":"U+2007 between the numbers","size":7776,"dice":"3131313131e280873131313132e280873131313133e280873131313134e280873131313135e280873131313136e280873131313231","words":["0","1","2","3","4","5","6"]}, + {"name":"U+2008 between the numbers","size":7776,"dice":"3131313131e280883131313132e280883131313133e280883131313134e280883131313135e280883131313136e280883131313231","words":["0","1","2","3","4","5","6"]}, + {"name":"U+2009 between the numbers","size":7776,"dice":"3131313131e280893131313132e280893131313133e280893131313134e280893131313135e280893131313136e280893131313231","words":["0","1","2","3","4","5","6"]}, + {"name":"U+200A between the numbers","size":7776,"dice":"3131313131e2808a3131313132e2808a3131313133e2808a3131313134e2808a3131313135e2808a3131313136e2808a3131313231","words":["0","1","2","3","4","5","6"]}, + {"name":"U+2028 between the numbers","size":7776,"dice":"3131313131e280a83131313132e280a83131313133e280a83131313134e280a83131313135e280a83131313136e280a83131313231","words":["0","1","2","3","4","5","6"]}, + {"name":"U+2029 between the numbers","size":7776,"dice":"3131313131e280a93131313132e280a93131313133e280a93131313134e280a93131313135e280a93131313136e280a93131313231","words":["0","1","2","3","4","5","6"]}, + {"name":"U+202F between the numbers","size":7776,"dice":"3131313131e280af3131313132e280af3131313133e280af3131313134e280af3131313135e280af3131313136e280af3131313231","words":["0","1","2","3","4","5","6"]}, + {"name":"U+205F between the numbers","size":7776,"dice":"3131313131e2819f3131313132e2819f3131313133e2819f3131313134e2819f3131313135e2819f3131313136e2819f3131313231","words":["0","1","2","3","4","5","6"]}, + {"name":"U+3000 between the numbers","size":7776,"dice":"3131313131e380803131313132e380803131313133e380803131313134e380803131313135e380803131313136e380803131313231","words":["0","1","2","3","4","5","6"]}, + {"name":"U+001C between the first two numbers","size":7776,"dice":"31313131311c3131313132203131313133203131313134203131313135203131313136203131313231","error":"wordkey: \"11111\\x1c11112\" is not five dice: five digits from 1 to 6"}, + {"name":"U+001D between the first two numbers","size":7776,"dice":"31313131311d3131313132203131313133203131313134203131313135203131313136203131313231","error":"wordkey: \"11111\\x1d11112\" is not five dice: five digits from 1 to 6"}, + {"name":"U+001E between the first two numbers","size":7776,"dice":"31313131311e3131313132203131313133203131313134203131313135203131313136203131313231","error":"wordkey: \"11111\\x1e11112\" is not five dice: five digits from 1 to 6"}, + {"name":"U+001F between the first two numbers","size":7776,"dice":"31313131311f3131313132203131313133203131313134203131313135203131313136203131313231","error":"wordkey: \"11111\\x1f11112\" is not five dice: five digits from 1 to 6"}, + {"name":"U+00AD between the first two numbers","size":7776,"dice":"3131313131c2ad3131313132203131313133203131313134203131313135203131313136203131313231","error":"wordkey: \"11111\\u00ad11112\" is not five dice: five digits from 1 to 6"}, + {"name":"U+0301 between the first two numbers","size":7776,"dice":"3131313131cc813131313132203131313133203131313134203131313135203131313136203131313231","error":"wordkey: \"11111\u030111112\" is not five dice: five digits from 1 to 6"}, + {"name":"U+180E between the first two numbers","size":7776,"dice":"3131313131e1a08e3131313132203131313133203131313134203131313135203131313136203131313231","error":"wordkey: \"11111\\u180e11112\" is not five dice: five digits from 1 to 6"}, + {"name":"U+200B between the first two numbers","size":7776,"dice":"3131313131e2808b3131313132203131313133203131313134203131313135203131313136203131313231","error":"wordkey: \"11111\\u200b11112\" is not five dice: five digits from 1 to 6"}, + {"name":"U+200C between the first two numbers","size":7776,"dice":"3131313131e2808c3131313132203131313133203131313134203131313135203131313136203131313231","error":"wordkey: \"11111\\u200c11112\" is not five dice: five digits from 1 to 6"}, + {"name":"U+200D between the first two numbers","size":7776,"dice":"3131313131e2808d3131313132203131313133203131313134203131313135203131313136203131313231","error":"wordkey: \"11111\\u200d11112\" is not five dice: five digits from 1 to 6"}, + {"name":"U+2060 between the first two numbers","size":7776,"dice":"3131313131e281a03131313132203131313133203131313134203131313135203131313136203131313231","error":"wordkey: \"11111\\u206011112\" is not five dice: five digits from 1 to 6"}, + {"name":"U+2800 between the first two numbers","size":7776,"dice":"3131313131e2a0803131313132203131313133203131313134203131313135203131313136203131313231","error":"wordkey: \"11111\u280011112\" is not five dice: five digits from 1 to 6"}, + {"name":"U+3164 between the first two numbers","size":7776,"dice":"3131313131e385a43131313132203131313133203131313134203131313135203131313136203131313231","error":"wordkey: \"11111\u316411112\" is not five dice: five digits from 1 to 6"}, + {"name":"U+FEFF between the first two numbers","size":7776,"dice":"3131313131efbbbf3131313132203131313133203131313134203131313135203131313136203131313231","error":"wordkey: \"11111\\ufeff11112\" is not five dice: five digits from 1 to 6"}, + {"name":"U+FFFD between the first two numbers","size":7776,"dice":"3131313131efbfbd3131313132203131313133203131313134203131313135203131313136203131313231","error":"wordkey: \"11111\ufffd11112\" is not five dice: five digits from 1 to 6"}, + {"name":"the byte A0 alone, which is not NBSP","size":7776,"dice":"3131313131a03131313132203131313133203131313134203131313135203131313136203131313231","error":"wordkey: \"11111\\xa011112\" is not five dice: five digits from 1 to 6"}, + {"name":"the byte 85 alone, which is not NEL","size":7776,"dice":"3131313131853131313132203131313133203131313134203131313135203131313136203131313231","error":"wordkey: \"11111\\x8511112\" is not five dice: five digits from 1 to 6"}, + {"name":"a byte that is not valid UTF-8 after a number","size":7776,"dice":"3131313131ff203131313132203131313133203131313134203131313135203131313136203131313231","error":"wordkey: \"11111\\xff\" is not five dice: five digits from 1 to 6"}, + {"name":"a surrogate in UTF-8 bytes after a number","size":7776,"dice":"3131313131eda080203131313132203131313133203131313134203131313135203131313136203131313231","error":"wordkey: \"11111\\xed\\xa0\\x80\" is not five dice: five digits from 1 to 6"}, + {"name":"five full-width digits","size":7776,"dice":"efbc91efbc91efbc91efbc91efbc91203131313132203131313133203131313134203131313135203131313136203131313231","error":"wordkey: \"\uff11\uff11\uff11\uff11\uff11\" is not five dice: five digits from 1 to 6"}, + {"name":"a combining acute accent after a number","size":7776,"dice":"3131313131cc81203131313132203131313133203131313134203131313135203131313136203131313231","error":"wordkey: \"11111\u0301\" is not five dice: five digits from 1 to 6"}, + {"name":"a combining acute accent before a number","size":7776,"dice":"cc813131313131203131313132203131313133203131313134203131313135203131313136203131313231","error":"wordkey: \"\u030111111\" is not five dice: five digits from 1 to 6"}, + {"name":"five fields that are not dice: the count first","size":7776,"dice":"612062206320642065","error":"wordkey: a key of words needs at least 6 words, not 5"}, + {"name":"six fields, the first not dice","size":7776,"dice":"616263203131313131203131313132203131313133203131313134203131313135","error":"wordkey: \"abc\" is not five dice: five digits from 1 to 6"}, + {"name":"not dice before a word given twice","size":7776,"dice":"313131313120313131313220616263203131313131203131313133203131313134","error":"wordkey: \"abc\" is not five dice: five digits from 1 to 6"}, + {"name":"a word given twice before not dice","size":7776,"dice":"313131313120313131313220313131313320313131313420313131313520313131313120616263","error":"wordkey: the dice 11111 give \"0\" a second time; roll them again"}, + {"name":"indices modulo 1: 11112 11113 11114 11115 11116 11121","size":7776,"mod":1,"dice":"3131313132203131313133203131313134203131313135203131313136203131313231","error":"wordkey: the dice 11113 give \"0\" a second time; roll them again"}, + {"name":"indices modulo 7: 11111 11112 11113 11114 11115 11116 11121","size":7776,"mod":7,"dice":"3131313131203131313132203131313133203131313134203131313135203131313136203131313231","words":["0","1","2","3","4","5","6"]}, + {"name":"indices modulo 7: 11111 11112 11113 11114 11115 11116 11121 11122","size":7776,"mod":7,"dice":"3131313131203131313132203131313133203131313134203131313135203131313136203131313231203131313232","error":"wordkey: the dice 11122 give \"0\" a second time; roll them again"}, + {"name":"indices modulo 1000: 11111 11112 11113 11114 11115 15455","size":7776,"mod":1000,"dice":"3131313131203131313132203131313133203131313134203131313135203135343535","error":"wordkey: the dice 15455 give \"0\" a second time; roll them again"}, + {"name":"indices modulo 7 and a suffix: 11111 11112 11113 11114 11115 11116 11121","size":7776,"mod":7,"suffix":"\"\\\t\u00a0\u200b","dice":"3131313131203131313132203131313133203131313134203131313135203131313136203131313231","words":["0\"\\\t\u00a0\u200b","1\"\\\t\u00a0\u200b","2\"\\\t\u00a0\u200b","3\"\\\t\u00a0\u200b","4\"\\\t\u00a0\u200b","5\"\\\t\u00a0\u200b","6\"\\\t\u00a0\u200b"]}, + {"name":"indices modulo 7 and a suffix: 11111 11112 11113 11114 11115 11116 11121 11122","size":7776,"mod":7,"suffix":"\"\\\t\u00a0\u200b","dice":"3131313131203131313132203131313133203131313134203131313135203131313136203131313231203131313232","error":"wordkey: the dice 11122 give \"0\\\"\\\\\\t\\u00a0\\u200b\" a second time; roll them again"}, + {"name":"\"\" on a list of 0 words","size":0,"dice":"","error":"wordkey: dice draw from a list of 7776 words, not 0"}, + {"name":"\"11111 11112 11113 11114 11115 11116 11121\" on a list of 2048 words","size":2048,"dice":"3131313131203131313132203131313133203131313134203131313135203131313136203131313231","error":"wordkey: dice draw from a list of 7776 words, not 2048"}, + {"name":"\"x\" on a list of 7777 words","size":7777,"dice":"78","error":"wordkey: dice draw from a list of 7776 words, not 7777"} + ], + "dice_list": [ + {"list":"en","size":7776,"sha256":"addd35536511597a02fa0a9ff1e5284677b8883b83e986e43f15a3db996b903e","bytes":108800}, + {"list":"es","size":7776,"sha256":"611f779a33df74587e9dfb486bb0185a9dfd4d1384e75993a21247ad31cefddb","bytes":108499}, + {"size":7776,"sha256":"267618e6e9109b66da2804c1d77097273dd6bc3144bf60fd35988322293db59e","bytes":84426}, + {"size":0,"error":"wordkey: dice draw from a list of 7776 words, not 0"}, + {"size":1,"error":"wordkey: dice draw from a list of 7776 words, not 1"}, + {"size":2048,"error":"wordkey: dice draw from a list of 7776 words, not 2048"}, + {"size":7775,"error":"wordkey: dice draw from a list of 7776 words, not 7775"}, + {"size":7777,"error":"wordkey: dice draw from a list of 7776 words, not 7777"} ] } '''; diff --git a/test/vectors/wordlist_vectors.json b/test/vectors/wordlist_vectors.json index 8c465f6..6719109 100644 --- a/test/vectors/wordlist_vectors.json +++ b/test/vectors/wordlist_vectors.json @@ -1,12 +1,14 @@ { - "description": "The word lists of package wordkey of the Go reference (generate.go, spec \u00a738.1) for lib/src/wordlist.dart; see tool/wordlist_go_vectors.go. Binary values and words are lower-case hex. The base list is word i = pal and three letters, a + i/676, a + i/26%26 and a + i%26; a case of size n takes its first n words, replaces the word at each at of set, and adds those of append. lists: the SHA-256, bytes and words of each built-in list. parse: the body of List on the text prefix, the base list joined by sep, and suffix (sha256 is that of the text): the words read, and ok or the error. check_list: CheckList, ok or the error (sha256 is that of the words joined by LF). alphabet: CheckList of the base list of 2048 whose first word is pala, a code point and zz, for each code point of the planes but the surrogates: the SHA-256 of the lines U+XXXX result, the code points of the words it accepts, and alphabet_results, each result up to U+017F. generate: Generate on the list of the Spanish words (list es) or on a list of size words whose indices they are, reading a stream: seeded, the keystream of SeededRandomSource of the seed; counter, SHA-256 of the label and a 32-bit big-endian counter from 0, block after block; bytes, hex repeated repeat times, which end. The indices drawn, the bytes read and the next four of an endless stream, or the error. bits: Bits, its shortest decimal and the hex of its float64. bits_digests: the SHA-256 of the 8 big-endian bytes of each Bits of the sizes and counts, inclusive, sizes outer.", + "description": "The word lists of package wordkey of the Go reference (generate.go and dice.go, spec \u00a738.1) for lib/src/wordlist.dart; see tool/wordlist_go_vectors.go. Binary values and words are lower-case hex. The base list is word i = pal and three letters, a + i/676, a + i/26%26 and a + i%26; a case of size n takes its first n words, replaces the word at each at of set, and adds those of append. lists: the SHA-256, bytes and words of each built-in list. parse: the body of List on the text prefix, the base list joined by sep, and suffix (sha256 is that of the text): the words read, and ok or the error. check_list: CheckList, ok or the error (sha256 is that of the words joined by LF). alphabet: CheckList of the base list of 2048 whose first word is pala, a code point and zz, for each code point of the planes but the surrogates: the SHA-256 of the lines U+XXXX result, the code points of the words it accepts, and alphabet_results, each result up to U+017F. generate: Generate on the list of the Spanish words (list es) or on a list of size words whose indices they are, reading a stream: seeded, the keystream of SeededRandomSource of the seed; counter, SHA-256 of the label and a 32-bit big-endian counter from 0, block after block; bytes, hex repeated repeat times, which end. The indices drawn, the bytes read and the next four of an endless stream, or the error. bits: Bits, its shortest decimal and the hex of its float64. bits_digests: the SHA-256 of the 8 big-endian bytes of each Bits of the sizes and counts, inclusive, sizes outer. dice_list_size: DiceListSize. dice_space: DiceWords of the text 11111, a code point and 11112 11113 11114 11115 11116 11121 on the list of the 7776 indices, for each code point of the planes but the surrogates: the SHA-256 of the lines U+XXXX result, and split, the code points at which the numbers are split. dice_number: DiceNumber of i, the dice or the error. dice_word and dice_words: DiceWord and DiceWords of the bytes dice, in hex, on the English or the Spanish list (list en or es) or on a list of size words, each its index in decimal, modulo mod if there is one, followed by suffix: the word or the words, as text, or the error. dice_list: DiceList of the list, the SHA-256 and the bytes of its text, or the error.", "generator": "tool/wordlist_go_vectors.go, go1.26.8", - "source": "e671032e04a3cd0999cf073ebedf50c45b088683", + "source": "92e7154c23945c9b9a2893750b6d4d128f848d3e", "default_count": 7, "min_list_size": 2048, "min_words": 6, "min_letters": 3, "alphabet": {"word":"pala, the code point and zz, the first word of the base list of 2048","planes":[0,1,14],"count":194560,"sha256":"9119a1741586b087cc8fdcce99b00931d34c926f5dc54d7fa0ff250402a11ba9","ok":["a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","\u00e1","\u00e9","\u00ed","\u00f1","\u00f3","\u00fa","\u00fc"]}, + "dice_list_size": 7776, + "dice_space": {"text":"11111, the code point and 11112 11113 11114 11115 11116 11121, on the list of the 7776 indices","planes":[0,1,14],"count":194560,"sha256":"cf34ff425b6da4822dd798828c6172861df2e8497502991bc96e67b36a9c64d1","split":[9,10,11,12,13,32,133,160,5760,8192,8193,8194,8195,8196,8197,8198,8199,8200,8201,8202,8232,8233,8239,8287,12288]}, "lists": [ {"lang":"en","sha256":"6d557f0693958fb5e650b68b5bee585eb82cf4da32965505c789e924743bc522","bytes":62144,"words":7776}, {"lang":"es","sha256":"ff77b487765c000da97cca58fe94a2cdb947303e7a07460614d7d95d800034fe","bytes":61843,"words":7776} @@ -784,5 +786,186 @@ {"name":"6 to 8 words of 2048 to 10000","sizes":[2048,10000],"counts":[6,8],"sha256":"9e055008813b6d2ec5b9477a777c44d30d650134cd890eeb679552403297332e"}, {"name":"0 to 600 words of 7776","sizes":[7776,7776],"counts":[0,600],"sha256":"b6e756155be0121f6db294a47b3dbb947652cc931fb078f1db5a9e39643c905a"}, {"name":"1 word of 2^32 - 4096 to 2^32 + 4096","sizes":[4294963200,4294971392],"counts":[1,1],"sha256":"a95430f39eda0ef2865e11d1b92c8b0e604e4a9387ac881905d77af880240d2d"} + ], + "dice_number": [ + {"i":0,"dice":"11111"}, + {"i":1,"dice":"11112"}, + {"i":5,"dice":"11116"}, + {"i":6,"dice":"11121"}, + {"i":35,"dice":"11166"}, + {"i":36,"dice":"11211"}, + {"i":215,"dice":"11666"}, + {"i":216,"dice":"12111"}, + {"i":1295,"dice":"16666"}, + {"i":1296,"dice":"21111"}, + {"i":2047,"dice":"24362"}, + {"i":2048,"dice":"24363"}, + {"i":3495,"dice":"35214"}, + {"i":7774,"dice":"66665"}, + {"i":7775,"dice":"66666"}, + {"i":-1,"error":"wordkey: no dice give position -1 of a list of 7776 words"}, + {"i":7776,"error":"wordkey: no dice give position 7776 of a list of 7776 words"}, + {"i":7777,"error":"wordkey: no dice give position 7777 of a list of 7776 words"}, + {"i":-7776,"error":"wordkey: no dice give position -7776 of a list of 7776 words"}, + {"i":2147483648,"error":"wordkey: no dice give position 2147483648 of a list of 7776 words"}, + {"i":-2147483648,"error":"wordkey: no dice give position -2147483648 of a list of 7776 words"}, + {"i":4294967296,"error":"wordkey: no dice give position 4294967296 of a list of 7776 words"}, + {"i":9007199254740991,"error":"wordkey: no dice give position 9007199254740991 of a list of 7776 words"}, + {"i":-9007199254740991,"error":"wordkey: no dice give position -9007199254740991 of a list of 7776 words"} + ], + "dice_word": [ + {"name":"en 11111","list":"en","size":7776,"dice":"3131313131","word":"abacus"}, + {"name":"en 11112","list":"en","size":7776,"dice":"3131313132","word":"abdomen"}, + {"name":"en 35214","list":"en","size":7776,"dice":"3335323134","word":"jovial"}, + {"name":"en 66666","list":"en","size":7776,"dice":"3636363636","word":"zoom"}, + {"name":"es 11111","list":"es","size":7776,"dice":"3131313131","word":"abad"}, + {"name":"es 35214","list":"es","size":7776,"dice":"3335323134","word":"glaciar"}, + {"name":"es 66666","list":"es","size":7776,"dice":"3636363636","word":"\u00fatil"}, + {"name":"11111","size":7776,"dice":"3131313131","word":"0"}, + {"name":"11112","size":7776,"dice":"3131313132","word":"1"}, + {"name":"11116","size":7776,"dice":"3131313136","word":"5"}, + {"name":"11121","size":7776,"dice":"3131313231","word":"6"}, + {"name":"11166","size":7776,"dice":"3131313636","word":"35"}, + {"name":"11211","size":7776,"dice":"3131323131","word":"36"}, + {"name":"16666","size":7776,"dice":"3136363636","word":"1295"}, + {"name":"21111","size":7776,"dice":"3231313131","word":"1296"}, + {"name":"12345","size":7776,"dice":"3132333435","word":"310"}, + {"name":"35214","size":7776,"dice":"3335323134","word":"3495"}, + {"name":"65432","size":7776,"dice":"3635343332","word":"7465"}, + {"name":"66665","size":7776,"dice":"3636363635","word":"7774"}, + {"name":"66666","size":7776,"dice":"3636363636","word":"7775"}, + {"name":"no dice","size":7776,"dice":"","error":"wordkey: \"\" is not five dice: five digits from 1 to 6"}, + {"name":"four dice","size":7776,"dice":"31313131","error":"wordkey: \"1111\" is not five dice: five digits from 1 to 6"}, + {"name":"six dice","size":7776,"dice":"313131313131","error":"wordkey: \"111111\" is not five dice: five digits from 1 to 6"}, + {"name":"a 0","size":7776,"dice":"3131313130","error":"wordkey: \"11110\" is not five dice: five digits from 1 to 6"}, + {"name":"a 7","size":7776,"dice":"3131313137","error":"wordkey: \"11117\" is not five dice: five digits from 1 to 6"}, + {"name":"a letter","size":7776,"dice":"6131313131","error":"wordkey: \"a1111\" is not five dice: five digits from 1 to 6"}, + {"name":"four dice and a space","size":7776,"dice":"3131313120","error":"wordkey: \"1111 \" is not five dice: five digits from 1 to 6"}, + {"name":"a full-width 1 and four dice","size":7776,"dice":"efbc9131313131","error":"wordkey: \"\uff111111\" is not five dice: five digits from 1 to 6"}, + {"name":"a space and four dice","size":7776,"dice":"2031313131","error":"wordkey: \" 1111\" is not five dice: five digits from 1 to 6"}, + {"name":"a space and five dice","size":7776,"dice":"203131313131","error":"wordkey: \" 11111\" is not five dice: five digits from 1 to 6"}, + {"name":"five dice and a space","size":7776,"dice":"313131313120","error":"wordkey: \"11111 \" is not five dice: five digits from 1 to 6"}, + {"name":"five dice and LF","size":7776,"dice":"31313131310a","error":"wordkey: \"11111\\n\" is not five dice: five digits from 1 to 6"}, + {"name":"a tab and four dice","size":7776,"dice":"0931313131","error":"wordkey: \"\\t1111\" is not five dice: five digits from 1 to 6"}, + {"name":"two and two dice around a space","size":7776,"dice":"3131203131","error":"wordkey: \"11 11\" is not five dice: five digits from 1 to 6"}, + {"name":"NUL","size":7776,"dice":"3131313100","error":"wordkey: \"1111\\x00\" is not five dice: five digits from 1 to 6"}, + {"name":"DEL","size":7776,"dice":"313131317f","error":"wordkey: \"1111\\x7f\" is not five dice: five digits from 1 to 6"}, + {"name":"a quotation mark","size":7776,"dice":"3131313122","error":"wordkey: \"1111\\\"\" is not five dice: five digits from 1 to 6"}, + {"name":"a backslash","size":7776,"dice":"313131315c","error":"wordkey: \"1111\\\\\" is not five dice: five digits from 1 to 6"}, + {"name":"00000","size":7776,"dice":"3030303030","error":"wordkey: \"00000\" is not five dice: five digits from 1 to 6"}, + {"name":"77777","size":7776,"dice":"3737373737","error":"wordkey: \"77777\" is not five dice: five digits from 1 to 6"}, + {"name":"99999","size":7776,"dice":"3939393939","error":"wordkey: \"99999\" is not five dice: five digits from 1 to 6"}, + {"name":"a slash, the byte before 0","size":7776,"dice":"313131312f","error":"wordkey: \"1111/\" is not five dice: five digits from 1 to 6"}, + {"name":"a colon, the byte after 9","size":7776,"dice":"313131313a","error":"wordkey: \"1111:\" is not five dice: five digits from 1 to 6"}, + {"name":"five full-width digits","size":7776,"dice":"efbc91efbc92efbc93efbc94efbc95","error":"wordkey: \"\uff11\uff12\uff13\uff14\uff15\" is not five dice: five digits from 1 to 6"}, + {"name":"five Arabic-Indic digits","size":7776,"dice":"d9a1d9a2d9a3d9a4d9a5","error":"wordkey: \"\u0661\u0662\u0663\u0664\u0665\" is not five dice: five digits from 1 to 6"}, + {"name":"a superscript 1","size":7776,"dice":"31313131c2b9","error":"wordkey: \"1111\u00b9\" is not five dice: five digits from 1 to 6"}, + {"name":"a circled 1 and two dice: five bytes","size":7776,"dice":"e291a03131","error":"wordkey: \"\u246011\" is not five dice: five digits from 1 to 6"}, + {"name":"a mathematical bold 1 and a die: five bytes","size":7776,"dice":"f09d9f8f31","error":"wordkey: \"\ud835\udfcf1\" is not five dice: five digits from 1 to 6"}, + {"name":"four dice and an e with an acute accent: five characters","size":7776,"dice":"31313131c3a9","error":"wordkey: \"1111\u00e9\" is not five dice: five digits from 1 to 6"}, + {"name":"three dice and an e with an acute accent: five bytes","size":7776,"dice":"313131c3a9","error":"wordkey: \"111\u00e9\" is not five dice: five digits from 1 to 6"}, + {"name":"five dice and a combining acute accent","size":7776,"dice":"3131313131cc81","error":"wordkey: \"11111\u0301\" is not five dice: five digits from 1 to 6"}, + {"name":"three dice and the emoji of a die: five UTF-16 code units","size":7776,"dice":"313131f09f8eb2","error":"wordkey: \"111\ud83c\udfb2\" is not five dice: five digits from 1 to 6"}, + {"name":"NBSP and four dice","size":7776,"dice":"c2a031313131","error":"wordkey: \"\\u00a01111\" is not five dice: five digits from 1 to 6"}, + {"name":"an ideographic space and five dice","size":7776,"dice":"e380803131313131","error":"wordkey: \"\\u300011111\" is not five dice: five digits from 1 to 6"}, + {"name":"two dice and a surrogate in UTF-8 bytes: five bytes","size":7776,"dice":"3131eda080","error":"wordkey: \"11\\xed\\xa0\\x80\" is not five dice: five digits from 1 to 6"}, + {"name":"four dice and a byte that is not valid UTF-8","size":7776,"dice":"31313131ff","error":"wordkey: \"1111\\xff\" is not five dice: five digits from 1 to 6"}, + {"name":"five bytes that are not valid UTF-8","size":7776,"dice":"fffefdfcfb","error":"wordkey: \"\\xff\\xfe\\xfd\\xfc\\xfb\" is not five dice: five digits from 1 to 6"}, + {"name":"four dice and a sequence cut short","size":7776,"dice":"31313131c3","error":"wordkey: \"1111\\xc3\" is not five dice: five digits from 1 to 6"}, + {"name":"\"11111\" on a list of 2048 words","size":2048,"dice":"3131313131","error":"wordkey: dice draw from a list of 7776 words, not 2048"}, + {"name":"\"11111\" on a list of 7775 words","size":7775,"dice":"3131313131","error":"wordkey: dice draw from a list of 7776 words, not 7775"}, + {"name":"\"66666\" on a list of 7777 words","size":7777,"dice":"3636363636","error":"wordkey: dice draw from a list of 7776 words, not 7777"}, + {"name":"\"\" on a list of 0 words","size":0,"dice":"","error":"wordkey: dice draw from a list of 7776 words, not 0"}, + {"name":"\"x\" on a list of 1 words","size":1,"dice":"78","error":"wordkey: dice draw from a list of 7776 words, not 1"} + ], + "dice_words": [ + {"name":"en, seven numbers among white space","list":"en","size":7776,"dice":"2031313131312031313131320931313131330a313131313420313131313520313131313620313131323120","words":["abacus","abdomen","abdominal","abide","abiding","ability","ablaze"]}, + {"name":"en, five numbers","list":"en","size":7776,"dice":"3131313131203131313132203131313133203131313134203131313135","error":"wordkey: a key of words needs at least 6 words, not 5"}, + {"name":"en, no numbers","list":"en","size":7776,"dice":"","error":"wordkey: a key of words needs at least 6 words, not 0"}, + {"name":"en, four dice","list":"en","size":7776,"dice":"31313131312031313131322031313131332031313131342031313131352031313136","error":"wordkey: \"1116\" is not five dice: five digits from 1 to 6"}, + {"name":"en, 11111 twice","list":"en","size":7776,"dice":"3131313131203131313132203131313133203131313134203131313135203131313131","error":"wordkey: the dice 11111 give \"abacus\" a second time; roll them again"}, + {"name":"en, a comma between two numbers","list":"en","size":7776,"dice":"31313131312c3131313132203131313133203131313134203131313135203131313136203131313231","error":"wordkey: \"11111,11112\" is not five dice: five digits from 1 to 6"}, + {"name":"seven numbers among white space","size":7776,"dice":"2031313131312031313131320931313131330a313131313420313131313520313131313620313131323120","words":["0","1","2","3","4","5","6"]}, + {"name":"five numbers","size":7776,"dice":"3131313131203131313132203131313133203131313134203131313135","error":"wordkey: a key of words needs at least 6 words, not 5"}, + {"name":"no numbers","size":7776,"dice":"","error":"wordkey: a key of words needs at least 6 words, not 0"}, + {"name":"four dice","size":7776,"dice":"31313131312031313131322031313131332031313131342031313131352031313136","error":"wordkey: \"1116\" is not five dice: five digits from 1 to 6"}, + {"name":"11111 twice","size":7776,"dice":"3131313131203131313132203131313133203131313134203131313135203131313131","error":"wordkey: the dice 11111 give \"0\" a second time; roll them again"}, + {"name":"a comma between two numbers","size":7776,"dice":"31313131312c3131313132203131313133203131313134203131313135203131313136203131313231","error":"wordkey: \"11111,11112\" is not five dice: five digits from 1 to 6"}, + {"name":"a list of 7775 words","size":7775,"dice":"3131313131203131313132203131313133203131313134203131313135203131313136","error":"wordkey: dice draw from a list of 7776 words, not 7775"}, + {"name":"es, seven numbers","list":"es","size":7776,"dice":"3636363636203131313132203335323134203132333435203534333231203233343536203131313131","words":["\u00fatil","abad\u00eda","glaciar","alimentar","propicio","contener","abad"]}, + {"name":"es, 66666 twice","list":"es","size":7776,"dice":"3636363636203131313131203335323134203132333435203534333231203233343536203636363636","error":"wordkey: the dice 66666 give \"\u00fatil\" a second time; roll them again"}, + {"name":"six numbers","size":7776,"dice":"3131313132203131313133203131313134203131313135203131313136203131313231","words":["1","2","3","4","5","6"]}, + {"name":"twelve numbers","size":7776,"dice":"3131313131203232323232203333333333203434343434203535353535203636363636203132333435203233343536203334353631203435363132203536313233203631323334","words":["0","1555","3110","4665","6220","7775","310","1865","3414","4933","6272","6531"]}, + {"name":"white space only","size":7776,"dice":"20090ac2a0e3808020","error":"wordkey: a key of words needs at least 6 words, not 0"}, + {"name":"U+0009 between the numbers","size":7776,"dice":"3131313131093131313132093131313133093131313134093131313135093131313136093131313231","words":["0","1","2","3","4","5","6"]}, + {"name":"U+000A between the numbers","size":7776,"dice":"31313131310a31313131320a31313131330a31313131340a31313131350a31313131360a3131313231","words":["0","1","2","3","4","5","6"]}, + {"name":"U+000B between the numbers","size":7776,"dice":"31313131310b31313131320b31313131330b31313131340b31313131350b31313131360b3131313231","words":["0","1","2","3","4","5","6"]}, + {"name":"U+000C between the numbers","size":7776,"dice":"31313131310c31313131320c31313131330c31313131340c31313131350c31313131360c3131313231","words":["0","1","2","3","4","5","6"]}, + {"name":"U+000D between the numbers","size":7776,"dice":"31313131310d31313131320d31313131330d31313131340d31313131350d31313131360d3131313231","words":["0","1","2","3","4","5","6"]}, + {"name":"U+0020 between the numbers","size":7776,"dice":"3131313131203131313132203131313133203131313134203131313135203131313136203131313231","words":["0","1","2","3","4","5","6"]}, + {"name":"U+0085 between the numbers","size":7776,"dice":"3131313131c2853131313132c2853131313133c2853131313134c2853131313135c2853131313136c2853131313231","words":["0","1","2","3","4","5","6"]}, + {"name":"U+00A0 between the numbers","size":7776,"dice":"3131313131c2a03131313132c2a03131313133c2a03131313134c2a03131313135c2a03131313136c2a03131313231","words":["0","1","2","3","4","5","6"]}, + {"name":"U+1680 between the numbers","size":7776,"dice":"3131313131e19a803131313132e19a803131313133e19a803131313134e19a803131313135e19a803131313136e19a803131313231","words":["0","1","2","3","4","5","6"]}, + {"name":"U+2000 between the numbers","size":7776,"dice":"3131313131e280803131313132e280803131313133e280803131313134e280803131313135e280803131313136e280803131313231","words":["0","1","2","3","4","5","6"]}, + {"name":"U+2001 between the numbers","size":7776,"dice":"3131313131e280813131313132e280813131313133e280813131313134e280813131313135e280813131313136e280813131313231","words":["0","1","2","3","4","5","6"]}, + {"name":"U+2002 between the numbers","size":7776,"dice":"3131313131e280823131313132e280823131313133e280823131313134e280823131313135e280823131313136e280823131313231","words":["0","1","2","3","4","5","6"]}, + {"name":"U+2003 between the numbers","size":7776,"dice":"3131313131e280833131313132e280833131313133e280833131313134e280833131313135e280833131313136e280833131313231","words":["0","1","2","3","4","5","6"]}, + {"name":"U+2004 between the numbers","size":7776,"dice":"3131313131e280843131313132e280843131313133e280843131313134e280843131313135e280843131313136e280843131313231","words":["0","1","2","3","4","5","6"]}, + {"name":"U+2005 between the numbers","size":7776,"dice":"3131313131e280853131313132e280853131313133e280853131313134e280853131313135e280853131313136e280853131313231","words":["0","1","2","3","4","5","6"]}, + {"name":"U+2006 between the numbers","size":7776,"dice":"3131313131e280863131313132e280863131313133e280863131313134e280863131313135e280863131313136e280863131313231","words":["0","1","2","3","4","5","6"]}, + {"name":"U+2007 between the numbers","size":7776,"dice":"3131313131e280873131313132e280873131313133e280873131313134e280873131313135e280873131313136e280873131313231","words":["0","1","2","3","4","5","6"]}, + {"name":"U+2008 between the numbers","size":7776,"dice":"3131313131e280883131313132e280883131313133e280883131313134e280883131313135e280883131313136e280883131313231","words":["0","1","2","3","4","5","6"]}, + {"name":"U+2009 between the numbers","size":7776,"dice":"3131313131e280893131313132e280893131313133e280893131313134e280893131313135e280893131313136e280893131313231","words":["0","1","2","3","4","5","6"]}, + {"name":"U+200A between the numbers","size":7776,"dice":"3131313131e2808a3131313132e2808a3131313133e2808a3131313134e2808a3131313135e2808a3131313136e2808a3131313231","words":["0","1","2","3","4","5","6"]}, + {"name":"U+2028 between the numbers","size":7776,"dice":"3131313131e280a83131313132e280a83131313133e280a83131313134e280a83131313135e280a83131313136e280a83131313231","words":["0","1","2","3","4","5","6"]}, + {"name":"U+2029 between the numbers","size":7776,"dice":"3131313131e280a93131313132e280a93131313133e280a93131313134e280a93131313135e280a93131313136e280a93131313231","words":["0","1","2","3","4","5","6"]}, + {"name":"U+202F between the numbers","size":7776,"dice":"3131313131e280af3131313132e280af3131313133e280af3131313134e280af3131313135e280af3131313136e280af3131313231","words":["0","1","2","3","4","5","6"]}, + {"name":"U+205F between the numbers","size":7776,"dice":"3131313131e2819f3131313132e2819f3131313133e2819f3131313134e2819f3131313135e2819f3131313136e2819f3131313231","words":["0","1","2","3","4","5","6"]}, + {"name":"U+3000 between the numbers","size":7776,"dice":"3131313131e380803131313132e380803131313133e380803131313134e380803131313135e380803131313136e380803131313231","words":["0","1","2","3","4","5","6"]}, + {"name":"U+001C between the first two numbers","size":7776,"dice":"31313131311c3131313132203131313133203131313134203131313135203131313136203131313231","error":"wordkey: \"11111\\x1c11112\" is not five dice: five digits from 1 to 6"}, + {"name":"U+001D between the first two numbers","size":7776,"dice":"31313131311d3131313132203131313133203131313134203131313135203131313136203131313231","error":"wordkey: \"11111\\x1d11112\" is not five dice: five digits from 1 to 6"}, + {"name":"U+001E between the first two numbers","size":7776,"dice":"31313131311e3131313132203131313133203131313134203131313135203131313136203131313231","error":"wordkey: \"11111\\x1e11112\" is not five dice: five digits from 1 to 6"}, + {"name":"U+001F between the first two numbers","size":7776,"dice":"31313131311f3131313132203131313133203131313134203131313135203131313136203131313231","error":"wordkey: \"11111\\x1f11112\" is not five dice: five digits from 1 to 6"}, + {"name":"U+00AD between the first two numbers","size":7776,"dice":"3131313131c2ad3131313132203131313133203131313134203131313135203131313136203131313231","error":"wordkey: \"11111\\u00ad11112\" is not five dice: five digits from 1 to 6"}, + {"name":"U+0301 between the first two numbers","size":7776,"dice":"3131313131cc813131313132203131313133203131313134203131313135203131313136203131313231","error":"wordkey: \"11111\u030111112\" is not five dice: five digits from 1 to 6"}, + {"name":"U+180E between the first two numbers","size":7776,"dice":"3131313131e1a08e3131313132203131313133203131313134203131313135203131313136203131313231","error":"wordkey: \"11111\\u180e11112\" is not five dice: five digits from 1 to 6"}, + {"name":"U+200B between the first two numbers","size":7776,"dice":"3131313131e2808b3131313132203131313133203131313134203131313135203131313136203131313231","error":"wordkey: \"11111\\u200b11112\" is not five dice: five digits from 1 to 6"}, + {"name":"U+200C between the first two numbers","size":7776,"dice":"3131313131e2808c3131313132203131313133203131313134203131313135203131313136203131313231","error":"wordkey: \"11111\\u200c11112\" is not five dice: five digits from 1 to 6"}, + {"name":"U+200D between the first two numbers","size":7776,"dice":"3131313131e2808d3131313132203131313133203131313134203131313135203131313136203131313231","error":"wordkey: \"11111\\u200d11112\" is not five dice: five digits from 1 to 6"}, + {"name":"U+2060 between the first two numbers","size":7776,"dice":"3131313131e281a03131313132203131313133203131313134203131313135203131313136203131313231","error":"wordkey: \"11111\\u206011112\" is not five dice: five digits from 1 to 6"}, + {"name":"U+2800 between the first two numbers","size":7776,"dice":"3131313131e2a0803131313132203131313133203131313134203131313135203131313136203131313231","error":"wordkey: \"11111\u280011112\" is not five dice: five digits from 1 to 6"}, + {"name":"U+3164 between the first two numbers","size":7776,"dice":"3131313131e385a43131313132203131313133203131313134203131313135203131313136203131313231","error":"wordkey: \"11111\u316411112\" is not five dice: five digits from 1 to 6"}, + {"name":"U+FEFF between the first two numbers","size":7776,"dice":"3131313131efbbbf3131313132203131313133203131313134203131313135203131313136203131313231","error":"wordkey: \"11111\\ufeff11112\" is not five dice: five digits from 1 to 6"}, + {"name":"U+FFFD between the first two numbers","size":7776,"dice":"3131313131efbfbd3131313132203131313133203131313134203131313135203131313136203131313231","error":"wordkey: \"11111\ufffd11112\" is not five dice: five digits from 1 to 6"}, + {"name":"the byte A0 alone, which is not NBSP","size":7776,"dice":"3131313131a03131313132203131313133203131313134203131313135203131313136203131313231","error":"wordkey: \"11111\\xa011112\" is not five dice: five digits from 1 to 6"}, + {"name":"the byte 85 alone, which is not NEL","size":7776,"dice":"3131313131853131313132203131313133203131313134203131313135203131313136203131313231","error":"wordkey: \"11111\\x8511112\" is not five dice: five digits from 1 to 6"}, + {"name":"a byte that is not valid UTF-8 after a number","size":7776,"dice":"3131313131ff203131313132203131313133203131313134203131313135203131313136203131313231","error":"wordkey: \"11111\\xff\" is not five dice: five digits from 1 to 6"}, + {"name":"a surrogate in UTF-8 bytes after a number","size":7776,"dice":"3131313131eda080203131313132203131313133203131313134203131313135203131313136203131313231","error":"wordkey: \"11111\\xed\\xa0\\x80\" is not five dice: five digits from 1 to 6"}, + {"name":"five full-width digits","size":7776,"dice":"efbc91efbc91efbc91efbc91efbc91203131313132203131313133203131313134203131313135203131313136203131313231","error":"wordkey: \"\uff11\uff11\uff11\uff11\uff11\" is not five dice: five digits from 1 to 6"}, + {"name":"a combining acute accent after a number","size":7776,"dice":"3131313131cc81203131313132203131313133203131313134203131313135203131313136203131313231","error":"wordkey: \"11111\u0301\" is not five dice: five digits from 1 to 6"}, + {"name":"a combining acute accent before a number","size":7776,"dice":"cc813131313131203131313132203131313133203131313134203131313135203131313136203131313231","error":"wordkey: \"\u030111111\" is not five dice: five digits from 1 to 6"}, + {"name":"five fields that are not dice: the count first","size":7776,"dice":"612062206320642065","error":"wordkey: a key of words needs at least 6 words, not 5"}, + {"name":"six fields, the first not dice","size":7776,"dice":"616263203131313131203131313132203131313133203131313134203131313135","error":"wordkey: \"abc\" is not five dice: five digits from 1 to 6"}, + {"name":"not dice before a word given twice","size":7776,"dice":"313131313120313131313220616263203131313131203131313133203131313134","error":"wordkey: \"abc\" is not five dice: five digits from 1 to 6"}, + {"name":"a word given twice before not dice","size":7776,"dice":"313131313120313131313220313131313320313131313420313131313520313131313120616263","error":"wordkey: the dice 11111 give \"0\" a second time; roll them again"}, + {"name":"indices modulo 1: 11112 11113 11114 11115 11116 11121","size":7776,"mod":1,"dice":"3131313132203131313133203131313134203131313135203131313136203131313231","error":"wordkey: the dice 11113 give \"0\" a second time; roll them again"}, + {"name":"indices modulo 7: 11111 11112 11113 11114 11115 11116 11121","size":7776,"mod":7,"dice":"3131313131203131313132203131313133203131313134203131313135203131313136203131313231","words":["0","1","2","3","4","5","6"]}, + {"name":"indices modulo 7: 11111 11112 11113 11114 11115 11116 11121 11122","size":7776,"mod":7,"dice":"3131313131203131313132203131313133203131313134203131313135203131313136203131313231203131313232","error":"wordkey: the dice 11122 give \"0\" a second time; roll them again"}, + {"name":"indices modulo 1000: 11111 11112 11113 11114 11115 15455","size":7776,"mod":1000,"dice":"3131313131203131313132203131313133203131313134203131313135203135343535","error":"wordkey: the dice 15455 give \"0\" a second time; roll them again"}, + {"name":"indices modulo 7 and a suffix: 11111 11112 11113 11114 11115 11116 11121","size":7776,"mod":7,"suffix":"\"\\\t\u00a0\u200b","dice":"3131313131203131313132203131313133203131313134203131313135203131313136203131313231","words":["0\"\\\t\u00a0\u200b","1\"\\\t\u00a0\u200b","2\"\\\t\u00a0\u200b","3\"\\\t\u00a0\u200b","4\"\\\t\u00a0\u200b","5\"\\\t\u00a0\u200b","6\"\\\t\u00a0\u200b"]}, + {"name":"indices modulo 7 and a suffix: 11111 11112 11113 11114 11115 11116 11121 11122","size":7776,"mod":7,"suffix":"\"\\\t\u00a0\u200b","dice":"3131313131203131313132203131313133203131313134203131313135203131313136203131313231203131313232","error":"wordkey: the dice 11122 give \"0\\\"\\\\\\t\\u00a0\\u200b\" a second time; roll them again"}, + {"name":"\"\" on a list of 0 words","size":0,"dice":"","error":"wordkey: dice draw from a list of 7776 words, not 0"}, + {"name":"\"11111 11112 11113 11114 11115 11116 11121\" on a list of 2048 words","size":2048,"dice":"3131313131203131313132203131313133203131313134203131313135203131313136203131313231","error":"wordkey: dice draw from a list of 7776 words, not 2048"}, + {"name":"\"x\" on a list of 7777 words","size":7777,"dice":"78","error":"wordkey: dice draw from a list of 7776 words, not 7777"} + ], + "dice_list": [ + {"list":"en","size":7776,"sha256":"addd35536511597a02fa0a9ff1e5284677b8883b83e986e43f15a3db996b903e","bytes":108800}, + {"list":"es","size":7776,"sha256":"611f779a33df74587e9dfb486bb0185a9dfd4d1384e75993a21247ad31cefddb","bytes":108499}, + {"size":7776,"sha256":"267618e6e9109b66da2804c1d77097273dd6bc3144bf60fd35988322293db59e","bytes":84426}, + {"size":0,"error":"wordkey: dice draw from a list of 7776 words, not 0"}, + {"size":1,"error":"wordkey: dice draw from a list of 7776 words, not 1"}, + {"size":2048,"error":"wordkey: dice draw from a list of 7776 words, not 2048"}, + {"size":7775,"error":"wordkey: dice draw from a list of 7776 words, not 7775"}, + {"size":7777,"error":"wordkey: dice draw from a list of 7776 words, not 7777"} ] } diff --git a/test/wordlist_support.dart b/test/wordlist_support.dart index 740c5b8..d978755 100644 --- a/test/wordlist_support.dart +++ b/test/wordlist_support.dart @@ -1,7 +1,8 @@ // Helpers of the tests of the word lists: the base list of TestCheckList of // Go and its edits, as test/vectors/wordlist_vectors.json writes them; a -// list of words that are their indices; and the random sources of the -// vectors of Generate. They read no file. +// list of words that are their indices; the random sources of the vectors +// of Generate; and the String of the bytes of the dice of a case. They read +// no file. import 'dart:collection'; import 'dart:typed_data'; @@ -10,6 +11,9 @@ import 'package:datekeys/src/bytes.dart'; import 'package:datekeys/src/random.dart'; import 'package:datekeys/src/sha256.dart'; import 'package:datekeys/src/wordkey.dart' show WordKeyException; +import 'package:test/test.dart'; + +import 'wordkey_support.dart' show fromWtf8; typedef Json = Map; @@ -66,11 +70,16 @@ double hexDouble(String hex) => /// The words "0", "1", …, one less than [length]: a list whose words are /// their indices, without storing them. generateWords reads only its length -/// and the words it draws. +/// and the words it draws, and the dice the same. With [mod], each word is +/// its index modulo [mod], so that a word comes back every [mod] positions, +/// and with [suffix], the index is followed by it: the lists of the dice +/// cases of the vectors with `mod` and `suffix`. final class IndexWords extends ListBase { - IndexWords(this._length); + IndexWords(this._length, [this._mod = 0, this._suffix = '']); final int _length; + final int _mod; + final String _suffix; @override int get length => _length; @@ -81,7 +90,7 @@ final class IndexWords extends ListBase { @override String operator [](int i) { RangeError.checkValidIndex(i, this); - return '$i'; + return '${_mod == 0 ? i : i % _mod}$_suffix'; } @override @@ -166,3 +175,24 @@ RandomSource streamOf(Json s) => switch (s['kind']) { /// The bytes that crypto/rand.Int reads for each draw of an index below /// [size]: those of size − 1. int drawSize(int size) => ((size - 1).bitLength + 7) ~/ 8; + +/// The String whose [utf8Bytes] are [bytes], also when they hold a lone +/// surrogate, or null when no String has them, such as a byte FF: the +/// dice of a case of the vectors as diceWord and diceWords take them. +String? stringOf(Uint8List bytes) { + final s = fromWtf8(bytes); + return s != null && equalBytes(utf8Bytes(s), bytes) ? s : null; +} + +/// Expects [f] to give what a case [c] of dice_word or dice_words of the +/// vectors gives, its word or its words, or to throw a [WordKeyException] +/// with the text of its error. +void expectDice(Object? Function() f, Json c) { + final name = c['name']; + final error = c['error'] as String?; + if (error == null) { + expect(f(), c['word'] ?? c['words'], reason: '$name'); + } else { + expect(outcome(f), error, reason: '$name'); + } +} diff --git a/test/wordlist_test.dart b/test/wordlist_test.dart index 23e5bdd..7cd1f45 100644 --- a/test/wordlist_test.dart +++ b/test/wordlist_test.dart @@ -3,11 +3,16 @@ // computed (tool/wordlist_go_vectors.go): the constants and the pinned // lists; the text of a list read as wordkey.List reads it; wordkey.CheckList // with its texts, also on each code point up to U+017F; the words that -// wordkey.Generate draws from the same bytes; and wordkey.Bits, bit for bit. -// And the cases of TestCheckList, TestGenerate and TestBits of Go, as Go -// writes them. The vectors come from a Dart constant, so that these tests -// also run compiled to JavaScript; the Spanish list, the CSPRNG of the -// platform and the alphabet of every code point are in wordlist_vm_test.dart. +// wordkey.Generate draws from the same bytes; wordkey.Bits, bit for bit; +// and the dice of wordkey on lists of indices: DiceNumber, DiceWord and +// DiceWords with their texts, on the bytes of Go and on Strings, the white +// space of plane 0 at which the numbers are split, and the SHA-256 of +// DiceList. And the cases of TestCheckList, TestGenerate, TestBits and +// TestDiceNumber of Go, and those of TestDiceWord and TestDiceList that need +// no list of Go, as Go writes them. The vectors come from a Dart constant, +// so that these tests also run compiled to JavaScript; the lists of +// wordlists/, the CSPRNG of the platform and the alphabet and the white +// space of every code point are in wordlist_vm_test.dart. import 'dart:convert'; import 'dart:math' as math; @@ -37,6 +42,7 @@ void main() { expect(vectors['min_list_size'], minListSize); expect(vectors['min_words'], minWords); expect(vectors['min_letters'], minLetters); + expect(vectors['dice_list_size'], diceListSize); // Languages of Go, and the SHA-256 of the file of each of its lists. final lists = cases('lists'); expect(wordListLanguages(), [for (final l in lists) l['lang']]); @@ -52,6 +58,11 @@ void main() { expect(datekeys.wordListSha256, same(wordListSha256)); expect(datekeys.defaultWordCount, defaultWordCount); expect(datekeys.minListSize, minListSize); + expect(datekeys.diceNumber, diceNumber); + expect(datekeys.diceWord, diceWord); + expect(datekeys.diceWords, diceWords); + expect(datekeys.diceList, diceList); + expect(datekeys.diceListSize, diceListSize); }); group('checkWordList', () { @@ -363,4 +374,199 @@ void main() { } }); }); + + group('dice', () { + // A list of 7776 words that are their indices: the dice read only the + // size of a list and the words they give. The lists of Go are in + // wordlist_vm_test.dart. + final indices = IndexWords(diceListSize); + + test('the cases of TestDiceNumber of Go', () { + for (final (i, want) in [ + (0, '11111'), + (1, '11112'), + (5, '11116'), + (6, '11121'), + (35, '11166'), + (36, '11211'), + (1295, '16666'), + (1296, '21111'), + (7775, '66666'), + ]) { + expect(diceNumber(i), want, reason: '$i'); + } + for (final i in [-1, 7776]) { + expect( + outcome(() => diceNumber(i)), + 'wordkey: no dice give position $i of a list of 7776 words', + ); + } + }); + + test('diceNumber is DiceNumber of Go', () { + final all = cases('dice_number'); + for (final c in all) { + final i = c['i']! as int; + final error = c['error'] as String?; + if (error == null) { + expect(diceNumber(i), c['dice'], reason: '$i'); + } else { + expect(outcome(() => diceNumber(i)), error, reason: '$i'); + } + } + expect(all, hasLength(greaterThan(20))); + }); + + test('the cases of TestDiceWord of Go, on the list of the indices', () { + // Every position, there and back. + for (var i = 0; i < diceListSize; i++) { + expect(diceWord(indices, diceNumber(i)), '$i'); + } + // Go quotes none of their characters: the full-width 1 of U+FF11 is + // printable. + for (final dice in [ + '', + '1111', + '111111', + '11110', + '11117', + 'a1111', + '1111 ', + '\uff111111', + ]) { + expect( + outcome(() => diceWord(indices, dice)), + 'wordkey: "$dice" is not five dice: five digits from 1 to 6', + ); + } + expect( + outcome(() => diceWord(IndexWords(2048), '11111')), + 'wordkey: dice draw from a list of 7776 words, not 2048', + ); + }); + + test('diceWord is DiceWord of Go, on lists of indices', () { + var count = 0; + var strings = 0; + for (final c in cases('dice_word')) { + if (c['list'] != null) continue; + final list = IndexWords(c['size']! as int); + final dice = fromHex(c['dice']! as String); + expectDice(() => diceWordUtf8(list, dice), c); + final s = stringOf(dice); + if (s != null) { + expectDice(() => diceWord(list, s), c); + strings++; + } + count++; + } + expect(count, greaterThan(50)); + // All but three, whose bytes no String has: a byte FF, five bytes + // from FF to FB and a sequence cut short. + expect(strings, count - 3); + }); + + test('diceWords is DiceWords of Go, on lists of indices', () { + var count = 0; + var strings = 0; + for (final c in cases('dice_words')) { + if (c['list'] != null) continue; + final list = IndexWords( + c['size']! as int, + c['mod'] as int? ?? 0, + c['suffix'] as String? ?? '', + ); + final dice = fromHex(c['dice']! as String); + expectDice(() => diceWordsUtf8(list, dice), c); + final s = stringOf(dice); + if (s != null) { + expectDice(() => diceWords(list, s), c); + strings++; + } + count++; + } + expect(count, greaterThan(60)); + // All but three, whose bytes no String has: the bytes A0 and 85 + // alone, which are not U+00A0 and U+0085, and a byte FF. + expect(strings, count - 3); + }); + + test('diceWords splits at the white space of Go, and changes nothing ' + 'else', () { + // The tab, the line feed, U+00A0 and U+3000 separate the numbers. + expect( + diceWords( + indices, + '\u300011111\t11112\n11113\u00a011114\u300011115 11116\u00a0', + ), + ['0', '1', '2', '3', '4', '5'], + ); + // A combining mark stays in its number, which is then not five dice: + // no NFD and no mark dropped, as normalizeWords would. Go's %q writes + // the mark as it is. + expect( + outcome( + () => diceWords(indices, '11111\u0301 11112 11113 11114 11115 11116'), + ), + 'wordkey: "11111\u0301" is not five dice: five digits from 1 to 6', + ); + expect( + outcome( + () => diceWords( + indices, + '11111\u0301\u3000\u0301 11112 11113 11114 11115 11116', + ), + ), + 'wordkey: "11111\u0301" is not five dice: five digits from 1 to 6', + ); + // Nor is a full-width digit made an ASCII one. + expect( + outcome( + () => diceWords( + indices, + '\uff11\uff11\uff11\uff11\uff11 11112 11113 11114 11115 11116', + ), + ), + 'wordkey: "\uff11\uff11\uff11\uff11\uff11" is not five dice: ' + 'five digits from 1 to 6', + ); + // Every code point of plane 0 at which Go splits the numbers, and no + // other (the vectors have every plane in dice_space). + final split = []; + for (var r = 0; r <= 0xffff; r++) { + if (r >= 0xd800 && r <= 0xdfff) continue; + final ch = String.fromCharCode(r); + final text = '11111${ch}11112 11113 11114 11115 11116 11121'; + if (outcome(() => diceWords(indices, text)) == 'ok') split.add(r); + } + expect(split, (vectors['dice_space']! as Json)['split']); + }); + + test('diceList is DiceList of Go, on lists of indices', () { + for (final c in cases('dice_list')) { + if (c['list'] != null) continue; + final list = IndexWords(c['size']! as int); + final error = c['error'] as String?; + if (error != null) { + expect( + outcome(() => diceList(list)), + error, + reason: '${list.length}', + ); + continue; + } + final text = diceList(list); + expect(text, startsWith('11111\t0\n11112\t1\n11113\t2\n')); + expect(text, endsWith('66665\t7774\n66666\t7775\n')); + final bytes = utf8Bytes(text); + expect(toHex(sha256(bytes)), c['sha256']); + expect(bytes, hasLength(c['bytes'])); + } + // TestDiceList of Go: a list of 2048 words. + expect( + outcome(() => diceList(List.filled(2048, ''))), + 'wordkey: dice draw from a list of 7776 words, not 2048', + ); + }); + }); } diff --git a/test/wordlist_vm_test.dart b/test/wordlist_vm_test.dart index a6032b6..f82d1f9 100644 --- a/test/wordlist_vm_test.dart +++ b/test/wordlist_vm_test.dart @@ -7,8 +7,13 @@ // built into it (TestBuiltInLists), and generateWords draws from the Spanish // one the words of Go; and, // as TestGenerate and TestGenerateUniform of Go, the words drawn with the -// CSPRNG of the platform are a key, and each about as likely. On the VM -// only: they read files, use Random.secure or take a few seconds. +// CSPRNG of the platform are a key, and each about as likely. And the dice +// on the two lists: the cases of TestDiceWord, TestDiceWords and +// TestDiceList of Go, the English list numbered for dice being the file of +// the EFF, byte for byte; the dice of the vectors on the lists of Go; and +// the white space of every code point of planes 0, 1 and 14, which is that +// of normalizeWords. On the VM only: they read files, use Random.secure or +// take a few seconds. @TestOn('vm') library; @@ -181,4 +186,182 @@ void main() { ]); }); }); + + group('the dice on the lists of wordlists/', () { + final lists = { + for (final lang in wordListLanguages()) + lang: readWordList(lang, File('wordlists/$lang.txt').readAsBytesSync()), + }; + final en = lists['en']!; + final es = lists['es']!; + List cases(String name) => (vectors[name]! as List).cast(); + + test('the cases of TestDiceWord of Go', () { + // Every position, there and back. + for (var i = 0; i < en.length; i++) { + expect(diceWord(en, diceNumber(i)), en[i]); + } + for (final (list, dice, want) in [ + (en, '11111', 'abacus'), + (en, '11112', 'abdomen'), + (en, '35214', 'jovial'), + (en, '66666', 'zoom'), + (es, '11111', 'abad'), + (es, '35214', 'glaciar'), + (es, '66666', '\u00fatil'), + ]) { + expect(diceWord(list, dice), want, reason: dice); + } + for (final dice in [ + '', + '1111', + '111111', + '11110', + '11117', + 'a1111', + '1111 ', + '\uff111111', + ]) { + expect( + outcome(() => diceWord(en, dice)), + 'wordkey: "$dice" is not five dice: five digits from 1 to 6', + ); + } + expect( + outcome(() => diceWord(en.sublist(0, 2048), '11111')), + 'wordkey: dice draw from a list of 7776 words, not 2048', + ); + }); + + test('the cases of TestDiceWords of Go', () { + final words = diceWords( + en, + ' 11111 11112\t11113\n11114 11115 11116 11121 ', + ); + expect( + words.join(' '), + 'abacus abdomen abdominal abide abiding ability ablaze', + ); + checkWords(normalizeWords(words.join(' '))); + for (final (dice, want) in [ + ( + '11111 11112 11113 11114 11115', + 'wordkey: a key of words needs at least 6 words, not 5', + ), + ('', 'wordkey: a key of words needs at least 6 words, not 0'), + ( + '11111 11112 11113 11114 11115 1116', + 'wordkey: "1116" is not five dice: five digits from 1 to 6', + ), + ( + '11111 11112 11113 11114 11115 11111', + 'wordkey: the dice 11111 give "abacus" a second time; roll them ' + 'again', + ), + ( + '11111,11112 11113 11114 11115 11116 11121', + 'wordkey: "11111,11112" is not five dice: five digits from 1 to 6', + ), + ]) { + expect(outcome(() => diceWords(en, dice)), want, reason: dice); + } + expect( + outcome( + () => diceWords(en.sublist(1), '11111 11112 11113 11114 11115 11116'), + ), + 'wordkey: dice draw from a list of 7776 words, not 7775', + ); + }); + + test('the cases of TestDiceList of Go: for en, the file of the EFF', () { + // The SHA-256 of each list numbered for dice, as Go's TestDiceList + // and wordlists/README.md record them. + const hashes = { + 'en': + 'addd35536511597a02fa0a9ff1e5284677b8883b83e986e43f15a3db996b903e', + 'es': + '611f779a33df74587e9dfb486bb0185a9dfd4d1384e75993a21247ad31cefddb', + }; + final readme = File('wordlists/README.md').readAsStringSync(); + expect(hashes.keys, wordListLanguages()); + for (final MapEntry(key: lang, value: want) in hashes.entries) { + final list = lists[lang]!; + final text = diceList(list); + expect(toHex(sha256(utf8Bytes(text))), want, reason: lang); + expect(readme, contains('| `$lang` | `$want`'), reason: lang); + expect(text, endsWith('\n'), reason: lang); + final lines = text.substring(0, text.length - 1).split('\n'); + expect(lines, hasLength(diceListSize), reason: lang); + expect(lines.first, '11111\t${list.first}', reason: lang); + expect(lines.last, '66666\t${list.last}', reason: lang); + } + expect( + outcome(() => diceList(List.filled(2048, ''))), + 'wordkey: dice draw from a list of 7776 words, not 2048', + ); + }); + + test('the dice of the vectors on the lists of Go', () { + var count = 0; + for (final name in ['dice_word', 'dice_words']) { + for (final c in cases(name)) { + final lang = c['list'] as String?; + if (lang == null) continue; + final list = lists[lang]!; + final dice = fromHex(c['dice']! as String); + final s = stringOf(dice)!; + if (name == 'dice_word') { + expectDice(() => diceWordUtf8(list, dice), c); + expectDice(() => diceWord(list, s), c); + } else { + expectDice(() => diceWordsUtf8(list, dice), c); + expectDice(() => diceWords(list, s), c); + } + count++; + } + } + for (final c in cases('dice_list')) { + final lang = c['list'] as String?; + if (lang == null) continue; + final text = utf8Bytes(diceList(lists[lang]!)); + expect(toHex(sha256(text)), c['sha256'], reason: lang); + expect(text, hasLength(c['bytes']), reason: lang); + count++; + } + expect(count, greaterThan(15)); + }); + + test('the white space of every code point of planes 0, 1 and 14', () { + // 11111, the code point and six more numbers on the list of the + // indices: the lines of every result, but for the surrogates, have + // Go's SHA-256, and the numbers are split at the white space of spec + // §38.1, at which normalizeWords splits the words of a key. + final s = vectors['dice_space']! as Json; + final indices = IndexWords(diceListSize); + final digest = Sha256(); + final split = []; + final words = []; + var count = 0; + for (final p in (s['planes']! as List).cast()) { + for (var r = p << 16; r <= (p << 16 | 0xffff); r++) { + if (r >= 0xd800 && r <= 0xdfff) continue; + final c = String.fromCharCode(r); + final result = outcome( + () => diceWords( + indices, + '11111${c}11112 11113 11114 11115 11116 11121', + ), + ); + digest.add(utf8Bytes('${codePointName(r)} $result\n')); + count++; + if (result == 'ok') split.add(r); + if (normalizeWords('a${c}b').length == 2) words.add(r); + } + } + expect(count, s['count']); + expect(toHex(digest.finish()), s['sha256']); + expect(split, s['split']); + expect(words, split); + }); + }); } diff --git a/testdata/SOURCE.json b/testdata/SOURCE.json index 3a990f6..318cb1e 100644 --- a/testdata/SOURCE.json +++ b/testdata/SOURCE.json @@ -1,6 +1,6 @@ { "module": "g.activething.com/go/DateKeys", - "commit": "e671032e04a3cd0999cf073ebedf50c45b088683", + "commit": "92e7154c23945c9b9a2893750b6d4d128f848d3e", "files": { "README.md": "d26b3f507edf5afe89600f063cb509b9ef62a908b93ed476bff964b5a443bf4b", "fixtures/empty_payload.dkc": "871e9bf05b52bbae17f3adfbbf97b46e7f0e53aa8f57bcaa506e43f36f53a9d4", diff --git a/tool/wordlist_go_vectors.go b/tool/wordlist_go_vectors.go index cbb308f..1b1a1ea 100644 --- a/tool/wordlist_go_vectors.go +++ b/tool/wordlist_go_vectors.go @@ -3,7 +3,8 @@ // Writes test/vectors/wordlist_vectors.json and the same JSON as a Dart // constant, wordlist_vectors.g.dart: the results of the word lists of // package wordkey of the Go reference (generate.go: List, CheckList, -// Generate and Bits, the random words of spec §38.1) for +// Generate and Bits, the random words of spec §38.1; and dice.go: +// DiceNumber, DiceWord, DiceWords and DiceList, the words of dice) for // lib/src/wordlist.dart. Every expected value is computed here by the Go // reference; none is written by hand. // @@ -36,14 +37,35 @@ // - bits: Bits of sizes and counts, as the 64 bits of the float64 and its // shortest decimal; and bits_digests, the SHA-256 of the 8 big-endian // bytes of each Bits over ranges of sizes and counts, sizes outer. +// - dice_number: DiceNumber of positions in the list of 7776 words, where +// a digit carries, and out of it, some of them large. +// - dice_word: DiceWord of five dice, as bytes, on the English and the +// Spanish lists (the cases of TestDiceWord) and on a list of the 7776 +// indices: dice, and what is not five dice, such as white space around +// them, other digits, a combining mark, five bytes that are not five +// characters and bytes that are not valid UTF-8; and lists of other +// sizes. +// - dice_words: DiceWords of a text, as bytes, on the same lists and on +// lists of indices modulo a number, which give a word twice, also +// followed by characters that %q escapes: the cases of TestDiceWords; +// each white space of unicode.IsSpace between the numbers, and code +// points and bytes that are not; the order of the checks; and lists of +// other sizes. +// - dice_list: DiceList of each list of Go, the file of the EFF for en, +// and of a list of the 7776 indices, as the SHA-256 and the length of +// its text; and lists of other sizes. +// - dice_space: DiceWords of 11111, a code point and six more numbers on +// the list of the 7776 indices, for every code point of planes 0, 1 and +// 14 but the surrogates: the SHA-256 of the lines "U+XXXX result\n", +// and the code points at which the numbers are split. // // Binary values are lower-case hexadecimal, and the JSON is ASCII, so that // the Dart constant is too. The output is the same on every run. It imports // the package wordkey and reads wordkey/lists, so it runs in an export of // datekeys-go made with git archive, without changing the repository, at -// e671032, the branch v0.15 after the tag spec-v0.15: +// 92e7154, the branch v0.15 after the tag spec-v0.15: // -// commit=$(git -C ../datekeys-go rev-parse e671032) +// commit=$(git -C ../datekeys-go rev-parse 92e7154) // out=$PWD/test/vectors // tmp=$(mktemp -d) // git -C ../datekeys-go archive "$commit" | tar -x -C "$tmp" @@ -68,6 +90,7 @@ import ( "runtime" "strconv" "strings" + "unicode" "golang.org/x/crypto/chacha20" @@ -611,6 +634,307 @@ func bitsDigests() []bitsDigest { } } +// --------------------------------------------------------------------------- +// Dice + +// diceListOf is a list for the dice: the built-in list of the language +// list or, when list is empty, size words, each its index in decimal, +// modulo mod when mod is not 0, so that a word comes back every mod +// positions, followed by suffix. +func diceListOf(list string, size, mod int, suffix string) []string { + if list != "" { + l, err := wordkey.List(list) + check(err) + return l + } + l := numbers(size) + for i := range l { + if mod != 0 { + l[i] = strconv.Itoa(i % mod) + } + l[i] += suffix + } + return l +} + +// errText is the text of err, or nothing. +func errText(err error) string { + if err != nil { + return err.Error() + } + return "" +} + +type diceNumberCase struct { + I int `json:"i"` + Dice string `json:"dice,omitempty"` + Error string `json:"error,omitempty"` +} + +func diceNumberCases() []diceNumberCase { + var out []diceNumberCase + // The positions of TestDiceNumber and others where a digit carries, + // then positions out of the list, some of them large. + for _, i := range []int{0, 1, 5, 6, 35, 36, 215, 216, 1295, 1296, 2047, 2048, 3495, 7774, 7775, + -1, 7776, 7777, -7776, 1 << 31, -1 << 31, 1 << 32, 1<<53 - 1, -(1<<53 - 1)} { + d, err := wordkey.DiceNumber(i) + out = append(out, diceNumberCase{i, d, errText(err)}) + } + return out +} + +type diceWordCase struct { + Name string `json:"name"` + List string `json:"list,omitempty"` + Size int `json:"size"` + Dice string `json:"dice"` + Word string `json:"word,omitempty"` + Error string `json:"error,omitempty"` +} + +// diceWordOf runs DiceWord on the list of diceListOf and the bytes dice, +// which the case keeps in hex. +func diceWordOf(name, list string, size int, dice string) diceWordCase { + l := diceListOf(list, size, 0, "") + w, err := wordkey.DiceWord(l, dice) + return diceWordCase{name, list, len(l), h(dice), w, errText(err)} +} + +func diceWordCases() []diceWordCase { + var out []diceWordCase + add := func(c diceWordCase) { out = append(out, c) } + // TestDiceWord, on the lists of Go. + for _, c := range []struct{ list, dice string }{ + {"en", "11111"}, {"en", "11112"}, {"en", "35214"}, {"en", "66666"}, + {"es", "11111"}, {"es", "35214"}, {"es", "66666"}, + } { + add(diceWordOf(c.list+" "+c.dice, c.list, 0, c.dice)) + } + // Five dice on the list of the indices: where a digit carries, and + // others. + for _, dice := range []string{"11111", "11112", "11116", "11121", "11166", "11211", "16666", "21111", "12345", "35214", "65432", "66665", "66666"} { + add(diceWordOf(dice, "", wordkey.DiceListSize, dice)) + } + // Not five dice: the cases of TestDiceWord, then white space, other + // digits and characters, marks, five bytes that are not five + // characters, and bytes that are not valid UTF-8. + for _, c := range []struct{ name, dice string }{ + {"no dice", ""}, + {"four dice", "1111"}, + {"six dice", "111111"}, + {"a 0", "11110"}, + {"a 7", "11117"}, + {"a letter", "a1111"}, + {"four dice and a space", "1111 "}, + {"a full-width 1 and four dice", "\uff111111"}, + {"a space and four dice", " 1111"}, + {"a space and five dice", " 11111"}, + {"five dice and a space", "11111 "}, + {"five dice and LF", "11111\n"}, + {"a tab and four dice", "\t1111"}, + {"two and two dice around a space", "11 11"}, + {"NUL", "1111\x00"}, + {"DEL", "1111\x7f"}, + {"a quotation mark", "1111\""}, + {"a backslash", "1111\\"}, + {"00000", "00000"}, + {"77777", "77777"}, + {"99999", "99999"}, + {"a slash, the byte before 0", "1111/"}, + {"a colon, the byte after 9", "1111:"}, + {"five full-width digits", "\uff11\uff12\uff13\uff14\uff15"}, + {"five Arabic-Indic digits", "\u0661\u0662\u0663\u0664\u0665"}, + {"a superscript 1", "1111\u00b9"}, + {"a circled 1 and two dice: five bytes", "\u246011"}, + {"a mathematical bold 1 and a die: five bytes", "\U0001d7cf1"}, + {"four dice and an e with an acute accent: five characters", "1111\u00e9"}, + {"three dice and an e with an acute accent: five bytes", "111\u00e9"}, + {"five dice and a combining acute accent", "11111\u0301"}, + {"three dice and the emoji of a die: five UTF-16 code units", "111\U0001f3b2"}, + {"NBSP and four dice", "\u00a01111"}, + {"an ideographic space and five dice", "\u300011111"}, + {"two dice and a surrogate in UTF-8 bytes: five bytes", "11\xed\xa0\x80"}, + {"four dice and a byte that is not valid UTF-8", "1111\xff"}, + {"five bytes that are not valid UTF-8", "\xff\xfe\xfd\xfc\xfb"}, + {"four dice and a sequence cut short", "1111\xc3"}, + } { + add(diceWordOf(c.name, "", wordkey.DiceListSize, c.dice)) + } + // A list of another size, refused before the dice. + for _, c := range []struct { + size int + dice string + }{{2048, "11111"}, {7775, "11111"}, {7777, "66666"}, {0, ""}, {1, "x"}} { + add(diceWordOf(fmt.Sprintf("%q on a list of %d words", c.dice, c.size), "", c.size, c.dice)) + } + return out +} + +type diceWordsCase struct { + Name string `json:"name"` + List string `json:"list,omitempty"` + Size int `json:"size"` + Mod int `json:"mod,omitempty"` + Suffix string `json:"suffix,omitempty"` + Dice string `json:"dice"` + Words []string `json:"words,omitempty"` + Error string `json:"error,omitempty"` +} + +// diceWordsOf runs DiceWords on the list of diceListOf and the bytes dice, +// which the case keeps in hex. +func diceWordsOf(name, list string, size, mod int, suffix, dice string) diceWordsCase { + l := diceListOf(list, size, mod, suffix) + words, err := wordkey.DiceWords(l, dice) + return diceWordsCase{name, list, len(l), mod, suffix, h(dice), words, errText(err)} +} + +func diceWordsCases() []diceWordsCase { + var out []diceWordsCase + add := func(c diceWordsCase) { out = append(out, c) } + const size = wordkey.DiceListSize + // Seven numbers, and the six after the first. + seven := "11111 11112 11113 11114 11115 11116 11121" + six := seven[len("11111 "):] + // TestDiceWords, on the English list and on the list of the indices. + for _, list := range []string{"en", ""} { + for _, c := range []struct{ name, dice string }{ + {"seven numbers among white space", " 11111 11112\t11113\n11114 11115 11116 11121 "}, + {"five numbers", "11111 11112 11113 11114 11115"}, + {"no numbers", ""}, + {"four dice", "11111 11112 11113 11114 11115 1116"}, + {"11111 twice", "11111 11112 11113 11114 11115 11111"}, + {"a comma between two numbers", "11111,11112 11113 11114 11115 11116 11121"}, + } { + name := c.name + if list != "" { + name = list + ", " + name + } + add(diceWordsOf(name, list, size, 0, "", c.dice)) + } + } + add(diceWordsOf("a list of 7775 words", "", 7775, 0, "", "11111 11112 11113 11114 11115 11116")) + // The Spanish list: words with accents, and the last word given twice. + add(diceWordsOf("es, seven numbers", "es", 0, 0, "", "66666 11112 35214 12345 54321 23456 11111")) + add(diceWordsOf("es, 66666 twice", "es", 0, 0, "", "66666 11111 35214 12345 54321 23456 66666")) + // The fewest numbers, more, and none. + add(diceWordsOf("six numbers", "", size, 0, "", six)) + add(diceWordsOf("twelve numbers", "", size, 0, "", "11111 22222 33333 44444 55555 66666 12345 23456 34561 45612 56123 61234")) + add(diceWordsOf("white space only", "", size, 0, "", " \t\n\u00a0\u3000 ")) + // Each white space of unicode.IsSpace between the numbers; then runes + // and bytes that are not white space between the first two, or around + // a number, which is then not five dice. + for r := rune(0); r <= unicode.MaxRune; r++ { + if unicode.IsSpace(r) { + add(diceWordsOf(fmt.Sprintf("U+%04X between the numbers", r), "", size, 0, "", strings.ReplaceAll(seven, " ", string(r)))) + } + } + for _, r := range []rune{0x1c, 0x1d, 0x1e, 0x1f, 0xad, 0x301, 0x180e, 0x200b, 0x200c, 0x200d, 0x2060, 0x2800, 0x3164, 0xfeff, 0xfffd} { + add(diceWordsOf(fmt.Sprintf("U+%04X between the first two numbers", r), "", size, 0, "", "11111"+string(r)+six)) + } + for _, c := range []struct{ name, dice string }{ + {"the byte A0 alone, which is not NBSP", "11111\xa0" + six}, + {"the byte 85 alone, which is not NEL", "11111\x85" + six}, + {"a byte that is not valid UTF-8 after a number", "11111\xff " + six}, + {"a surrogate in UTF-8 bytes after a number", "11111\xed\xa0\x80 " + six}, + {"five full-width digits", "\uff11\uff11\uff11\uff11\uff11 " + six}, + {"a combining acute accent after a number", "11111\u0301 " + six}, + {"a combining acute accent before a number", "\u030111111 " + six}, + // The order of the checks. + {"five fields that are not dice: the count first", "a b c d e"}, + {"six fields, the first not dice", "abc 11111 11112 11113 11114 11115"}, + {"not dice before a word given twice", "11111 11112 abc 11111 11113 11114"}, + {"a word given twice before not dice", "11111 11112 11113 11114 11115 11111 abc"}, + } { + add(diceWordsOf(c.name, "", size, 0, "", c.dice)) + } + // Lists that hold a word more than once: DiceWords refuses the word, + // whatever the dice that give it. + n1000, err := wordkey.DiceNumber(1000) + check(err) + for _, c := range []struct { + mod int + dice string + }{{1, six}, {7, seven}, {7, seven + " 11122"}, {1000, "11111 11112 11113 11114 11115 " + n1000}} { + add(diceWordsOf(fmt.Sprintf("indices modulo %d: %s", c.mod, c.dice), "", size, c.mod, "", c.dice)) + } + // Words that %q escapes: a quotation mark, a backslash, a tab, NBSP and + // ZWSP after each index modulo 7, so that the eighth number gives the + // word of the first again. + for _, dice := range []string{seven, seven + " 11122"} { + add(diceWordsOf("indices modulo 7 and a suffix: "+dice, "", size, 7, "\"\\\t\u00a0\u200b", dice)) + } + // A list of another size, refused before the numbers. + for _, c := range []struct { + size int + dice string + }{{0, ""}, {2048, seven}, {7777, "x"}} { + add(diceWordsOf(fmt.Sprintf("%q on a list of %d words", c.dice, c.size), "", c.size, 0, "", c.dice)) + } + return out +} + +type diceListCase struct { + List string `json:"list,omitempty"` + Size int `json:"size"` + Sha256 string `json:"sha256,omitempty"` + Bytes int `json:"bytes,omitempty"` + Error string `json:"error,omitempty"` +} + +func diceListCases() []diceListCase { + var out []diceListCase + add := func(list string, size int) { + l := diceListOf(list, size, 0, "") + text, err := wordkey.DiceList(l) + c := diceListCase{List: list, Size: len(l), Error: errText(err)} + if err == nil { + c.Sha256, c.Bytes = sum(text), len(text) + } + out = append(out, c) + } + for _, lang := range wordkey.Languages() { + add(lang, 0) + } + for _, size := range []int{wordkey.DiceListSize, 0, 1, 2048, 7775, 7777} { + add("", size) + } + return out +} + +type diceSpaceHead struct { + Text string `json:"text"` + Planes []int `json:"planes"` + Count int `json:"count"` + Sha256 string `json:"sha256"` + Split []int `json:"split"` +} + +// diceSpace runs DiceWords of 11111, a code point and six more numbers on +// the list of the indices, for every code point of the planes but the +// surrogates. +func diceSpace() diceSpaceHead { + planes := []int{0, 1, 14} + l := numbers(wordkey.DiceListSize) + digest := sha256.New() + var split []int + count := 0 + for _, p := range planes { + for r := rune(p << 16); r <= rune(p<<16|0xffff); r++ { + if r >= 0xd800 && r <= 0xdfff { + continue + } + _, err := wordkey.DiceWords(l, "11111"+string(r)+"11112 11113 11114 11115 11116 11121") + fmt.Fprintf(digest, "U+%04X %s\n", r, result(err)) + count++ + if err == nil { + split = append(split, int(r)) + } + } + } + return diceSpaceHead{"11111, the code point and 11112 11113 11114 11115 11116 11121, on the list of the 7776 indices", planes, count, hex.EncodeToString(digest.Sum(nil)), split} +} + // --------------------------------------------------------------------------- // JSON @@ -672,6 +996,11 @@ func main() { gen := generateCases() bits := bitsCases() digests := bitsDigests() + space := diceSpace() + diceNumbers := diceNumberCases() + diceWord := diceWordCases() + diceWords := diceWordsCases() + diceLists := diceListCases() var w bytes.Buffer w.WriteString("{\n") @@ -679,12 +1008,15 @@ func main() { key string v any }{ - {"description", "The word lists of package wordkey of the Go reference (generate.go, spec \u00a738.1) for lib/src/wordlist.dart; see tool/wordlist_go_vectors.go. " + + {"description", "The word lists of package wordkey of the Go reference (generate.go and dice.go, spec \u00a738.1) for lib/src/wordlist.dart; see tool/wordlist_go_vectors.go. " + "Binary values and words are lower-case hex. The base list is word i = pal and three letters, a + i/676, a + i/26%26 and a + i%26; a case of size n takes its first n words, replaces the word at each at of set, and adds those of append. " + "lists: the SHA-256, bytes and words of each built-in list. parse: the body of List on the text prefix, the base list joined by sep, and suffix (sha256 is that of the text): the words read, and ok or the error. " + "check_list: CheckList, ok or the error (sha256 is that of the words joined by LF). alphabet: CheckList of the base list of 2048 whose first word is pala, a code point and zz, for each code point of the planes but the surrogates: the SHA-256 of the lines U+XXXX result, the code points of the words it accepts, and alphabet_results, each result up to U+017F. " + "generate: Generate on the list of the Spanish words (list es) or on a list of size words whose indices they are, reading a stream: seeded, the keystream of SeededRandomSource of the seed; counter, SHA-256 of the label and a 32-bit big-endian counter from 0, block after block; bytes, hex repeated repeat times, which end. The indices drawn, the bytes read and the next four of an endless stream, or the error. " + - "bits: Bits, its shortest decimal and the hex of its float64. bits_digests: the SHA-256 of the 8 big-endian bytes of each Bits of the sizes and counts, inclusive, sizes outer."}, + "bits: Bits, its shortest decimal and the hex of its float64. bits_digests: the SHA-256 of the 8 big-endian bytes of each Bits of the sizes and counts, inclusive, sizes outer. " + + "dice_list_size: DiceListSize. dice_space: DiceWords of the text 11111, a code point and 11112 11113 11114 11115 11116 11121 on the list of the 7776 indices, for each code point of the planes but the surrogates: the SHA-256 of the lines U+XXXX result, and split, the code points at which the numbers are split. " + + "dice_number: DiceNumber of i, the dice or the error. dice_word and dice_words: DiceWord and DiceWords of the bytes dice, in hex, on the English or the Spanish list (list en or es) or on a list of size words, each its index in decimal, modulo mod if there is one, followed by suffix: the word or the words, as text, or the error. " + + "dice_list: DiceList of the list, the SHA-256 and the bytes of its text, or the error."}, {"generator", "tool/wordlist_go_vectors.go, " + runtime.Version()}, {"source", *source}, {"default_count", wordkey.DefaultCount}, @@ -692,6 +1024,8 @@ func main() { {"min_words", wordkey.MinWords}, {"min_letters", wordkey.MinLetters}, {"alphabet", alpha}, + {"dice_list_size", wordkey.DiceListSize}, + {"dice_space", space}, } for _, kv := range head { fmt.Fprintf(&w, " %q: %s,\n", kv.key, marshal(kv.v)) @@ -702,7 +1036,11 @@ func main() { list(&w, "alphabet_results", runes, false) list(&w, "generate", gen, false) list(&w, "bits", bits, false) - list(&w, "bits_digests", digests, true) + list(&w, "bits_digests", digests, false) + list(&w, "dice_number", diceNumbers, false) + list(&w, "dice_word", diceWord, false) + list(&w, "dice_words", diceWords, false) + list(&w, "dice_list", diceLists, true) w.WriteString("}\n") var parsed any @@ -714,8 +1052,9 @@ func main() { } path := filepath.Join(*outDir, "wordlist_vectors.json") check(os.WriteFile(path, w.Bytes(), 0o644)) - fmt.Printf("wrote %s, %d bytes: %d lists, %d texts, %d lists checked, %d code points, %d draws, %d bits, %d digests\n", - path, w.Len(), len(lists), len(parse), len(checks), alpha.Count, len(gen), len(bits), len(digests)) + fmt.Printf("wrote %s, %d bytes: %d lists, %d texts, %d lists checked, %d code points, %d draws, %d bits, %d digests; dice: %d code points, %d numbers, %d words, %d texts, %d lists\n", + path, w.Len(), len(lists), len(parse), len(checks), alpha.Count, len(gen), len(bits), len(digests), + space.Count, len(diceNumbers), len(diceWord), len(diceWords), len(diceLists)) dart := "// Generated by tool/wordlist_go_vectors.go from wordlist_vectors.json, for\n" + "// the tests that also run compiled to JavaScript, where no file can be\n" + "// read. Do not edit.\n\n" + diff --git a/wordlists/README.md b/wordlists/README.md index 30b738e..f41960f 100644 --- a/wordlists/README.md +++ b/wordlists/README.md @@ -13,7 +13,14 @@ from the normalized text of the words, whatever list they came from. Each list has 7776 = 6^5 words, sorted, so that five dice give a word: each die minus one is a digit of the position of the word in base 6, the first die the most significant. The dice 11111 give the first word and 66666 the -last, as in the list of the EFF. +last, as in the list of the EFF. `datekeys wordlist` (`wordkey.DiceList`) +writes a list numbered for dice, to print it: a line for each word, its +dice, a tab and the word, as the EFF publishes its list. + +| List numbered for dice | SHA-256 | +|---|---| +| `en` | `addd35536511597a02fa0a9ff1e5284677b8883b83e986e43f15a3db996b903e`, the file of the EFF, byte for byte | +| `es` | `611f779a33df74587e9dfb486bb0185a9dfd4d1384e75993a21247ad31cefddb` | A list changes only with its hash in this file and in `generate_test.go`: an application that downloads a list pins its SHA-256 and refuses any other. diff --git a/wordlists/SOURCE.json b/wordlists/SOURCE.json index 286c208..7d7b35a 100644 --- a/wordlists/SOURCE.json +++ b/wordlists/SOURCE.json @@ -1,8 +1,8 @@ { "module": "g.activething.com/go/DateKeys", - "commit": "e671032e04a3cd0999cf073ebedf50c45b088683", + "commit": "92e7154c23945c9b9a2893750b6d4d128f848d3e", "files": { - "README.md": "d73a477d2ad4a03c6f1d61c21eeb8b754ec5a758346af62e6426979934eda097", + "README.md": "a29122e04cd8dac3e44d6f271da2ca99ac98e28b27e88c16b58162fe3eebedad", "en.txt": "6d557f0693958fb5e650b68b5bee585eb82cf4da32965505c789e924743bc522", "es.txt": "ff77b487765c000da97cca58fe94a2cdb947303e7a07460614d7d95d800034fe" }