diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a7c117..a5e0313 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ Cambios notables de la librería TypeScript y de la página. El proyecto usa ver ## Especificación 0.10, en la rama `v0.10` — sin versión +### Los vectores compartidos de la llave de palabras (06-10-2026) + +- `testdata` se sincroniza con `datekeys-go` en `084728d`, que añade `vectors/wordkey.json`: los casos de la llave de palabras que pide el §64 de la v0.11, que hasta ahora solo estaban en las pruebas de Go. Ningún otro fichero cambia. +- Un bloque nuevo de `vectors.test.ts` los corre: las palabras de 45 textos, entre ellos cada espacio del §38.1 y tres que no lo son; lo que hace un escritor con 20 textos, con el texto del error de Go; y 6 identidades con su recipient, el vector del §38.1 el primero. La librería no cambia: todos coinciden. + ### El borrador v0.12: `testdata`, la regla del §72 y la nota de `inspect` (05-10-2026) - `testdata` se sincroniza con la cabeza de la rama `v0.12` de `datekeys-go` (`601e6d2`); `SPEC_VERSION` sigue en `0.11` hasta que el autor apruebe el borrador. Trae: diff --git a/README.md b/README.md index 686f02b..11d9874 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Hay tres números de versión, cada uno con su significado, como en la referenci | Versión | Dónde | Cambia cuando | |---|---|---| | Formato | Dentro de los objetos: el formato de la cápsula, el `VERSION` del prelude de DKC1, 1, 2 o 3 al leer, que fija también la versión de schema de CONTROL_CBOR; y 1 en la trama DKK1 y en el schema de los demás objetos | Cambia el formato. Un lector rechaza una versión que no conoce (§22, §70) | -| Especificación | `SPEC_VERSION` de `src/lib/dkc/version.ts`, hoy `0.11`: la del tag `spec-v0.11` de `datekeys-go`. `testdata` sigue ya el borrador v0.12, la cabeza de la rama `v0.12` (`601e6d2`), cuyos ficheros dicen `0.11` hasta que el autor lo apruebe | Cambia el texto normativo | +| Especificación | `SPEC_VERSION` de `src/lib/dkc/version.ts`, hoy `0.11`: la del tag `spec-v0.11` de `datekeys-go`. `testdata` sigue ya el borrador v0.12, la cabeza de la rama `v0.12` (`084728d`), cuyos ficheros dicen `0.11` hasta que el autor lo apruebe | Cambia el texto normativo | | Librería | `VERSION` de `src/lib/dkc/version.ts`, igual al campo `version` de `package.json` | Cambia la API o el comportamiento. Versionado semántico, sin promesa de estabilidad antes de 1.0.0 | `version.test.ts` comprueba que `VERSION` coincide con `package.json` y con su lockfile, y que `SPEC_VERSION` es la versión que nombran los vectores y fixtures compartidos; `vectors.test.ts` exige esa versión a cada fichero. El pie de la página muestra las dos. @@ -408,7 +408,7 @@ Comprueba que los ficheros coinciden con `SOURCE.json`, sin faltantes ni sobrant `.gitattributes` marca `testdata/**` como binario para que git no altere ningún byte. -Copia actual: la de `testdata/SOURCE.json` (la cabeza de la rama `v0.12` de `datekeys-go`, `601e6d2`, con el borrador v0.12), que se sincroniza con `node scripts/sync-testdata.mjs sync --commit 601e6d2`. +Copia actual: la de `testdata/SOURCE.json` (la cabeza de la rama `v0.12` de `datekeys-go`, `084728d`, con el borrador v0.12), que se sincroniza con `node scripts/sync-testdata.mjs sync --commit 084728d`. ## Licencia diff --git a/src/lib/dkc/vectors.test.ts b/src/lib/dkc/vectors.test.ts index 4176411..dd25b33 100644 --- a/src/lib/dkc/vectors.test.ts +++ b/src/lib/dkc/vectors.test.ts @@ -33,6 +33,7 @@ import { bls12_381 } from '@noble/curves/bls12-381.js'; import { readFileSync } from 'node:fs'; import { describe, expect, it } from 'vitest'; import { decodeAccessKey, decodeAccessKeyBody, marshalAccessKeyBody, wipeAccessKey } from './accesskey.ts'; +import { bech32Encode } from './bech32.ts'; import { checkCompressedPoint } from './bls12381.ts'; import { concatBytes, decodeUtf8, equalBytes, sha256, toHex } from './bytes.ts'; import { type Item, MAX_SAFE_UINT, walk } from './cbor.ts'; @@ -83,7 +84,8 @@ import { verdict, } from './testing/vectors.ts'; import { SPEC_VERSION } from './version.ts'; -import { parseX25519Identity } from './x25519.ts'; +import { checkWords, normalizeWords, wordKey } from './wordkey.ts'; +import { parseX25519Identity, x25519PublicKey } from './x25519.ts'; /** The code of a thrown DateKeysError, "ok" when `fn` returns; anything else is rethrown. */ async function resultOf(fn: () => unknown): Promise { @@ -1238,6 +1240,93 @@ describe('vectors/security.json', () => { // --------------------------------------------------------------------------- // Every synced file is run +// --------------------------------------------------------------------------- +// vectors/wordkey.json +// +// The key of words (spec §38.1): the words of a text, what a writer does with +// them, ok or the error of Go, and the identity that they derive, with its +// recipient. + +interface WordCheckVector { + name: string; + text: string; + words: string[]; + error?: string; +} +interface WordKeyVector { + name: string; + words: string[]; + chainHash: string; + round: number; + capsuleId: Uint8Array; + key: string; + recipient: string; +} + +describe('vectors/wordkey.json', () => { + const strings = (v: unknown, at: string): string[] => array(v, at).map((w, i) => str(w, `${at}[${i}]`)); + const f = load('vectors/wordkey.json', (json) => { + const o = object(json, 'wordkey.json'); + keys(o, 'wordkey.json', ['spec', 'description', 'normalize', 'check', 'keys']); + checkSpec(o, 'wordkey.json'); + const normalize = array(o.normalize, 'normalize').map((v, i) => { + const at = `normalize[${i}]`; + const c = object(v, at); + keys(c, at, ['name', 'text', 'words']); + return { name: str(c.name, `${at}.name`), text: str(c.text, `${at}.text`), words: strings(c.words, `${at}.words`) }; + }); + const check = array(o.check, 'check').map((v, i): WordCheckVector => { + const at = `check[${i}]`; + const c = object(v, at); + const res = str(c.result, `${at}.result`); + if (res !== 'ok' && res !== 'error') throw new FormatError(`${at}.result`, 'not ok or error'); + keys(c, at, ['name', 'text', 'words', 'result', ...(res === 'ok' ? [] : ['error'])]); + const w: WordCheckVector = { name: str(c.name, `${at}.name`), text: str(c.text, `${at}.text`), words: strings(c.words, `${at}.words`) }; + if (res === 'error') w.error = str(c.error, `${at}.error`); + return w; + }); + const keyList = array(o.keys, 'keys').map((v, i): WordKeyVector => { + const at = `keys[${i}]`; + const c = object(v, at); + keys(c, at, ['name', 'words', 'chain_hash', 'round', 'capsule_id', 'key', 'recipient']); + return { + name: str(c.name, `${at}.name`), + words: strings(c.words, `${at}.words`), + chainHash: toHex(hexBytes(c.chain_hash, `${at}.chain_hash`)), + round: int(c.round, `${at}.round`), + capsuleId: hexBytes(c.capsule_id, `${at}.capsule_id`), + key: toHex(hexBytes(c.key, `${at}.key`)), + recipient: str(c.recipient, `${at}.recipient`), + }; + }); + return { normalize, check, keys: keyList }; + }); + if (f === undefined) return; + + it('has the cases of README: each space of §38.1, texts that pass and fail, and the vector of §38.1', () => { + expect(f.normalize.filter((c) => / is a space$/.test(c.name))).toHaveLength(25); + expect(f.check.some((c) => c.error === undefined)).toBe(true); + expect(f.check.some((c) => c.error?.includes('U+200B'))).toBe(true); + expect(f.keys[0]?.key).toBe('fceec4d8ca8de86c85a1f26ed49f82a2b38431bd0ce36db995ae7dfd49b96e41'); + }); + + it.each(f.normalize.map((c) => [c.name, c] as const))('normalize: %s', async (_name, c) => { + expect(await normalizeWords(c.text)).toEqual(c.words); + }); + + it.each(f.check.map((c) => [c.name, c] as const))('check: %s', async (_name, c) => { + expect(await normalizeWords(c.text)).toEqual(c.words); + if (c.error === undefined) await expect(checkWords(c.words)).resolves.toBeUndefined(); + else await expect(checkWords(c.words)).rejects.toThrow(new Error(c.error)); + }); + + it.each(f.keys.map((c) => [c.name, c] as const))('key: %s', async (_name, c) => { + const key = await wordKey(c.words, c.chainHash, c.round, c.capsuleId); + expect(toHex(key)).toBe(c.key); + expect(bech32Encode('age', x25519PublicKey(key))).toBe(c.recipient); + }); +}); + // The vector files the blocks above run; a new export needs its own block. const VECTOR_FILES = [ 'vectors/cbor.json', @@ -1256,6 +1345,7 @@ const VECTOR_FILES = [ 'vectors/security.json', 'vectors/security_cms.json', 'vectors/tlock_ibe.json', + 'vectors/wordkey.json', ]; describe('testdata/', () => { diff --git a/testdata/README.md b/testdata/README.md index c2ce04b..2422f03 100644 --- a/testdata/README.md +++ b/testdata/README.md @@ -56,6 +56,7 @@ Conventions for every file: | `vectors/security_cms.json` | security areas with a signature of `alg` 2 or a seal of `seal_type` 2, each with its context, verdicts, results and lines | §29.7, §29.10, §29.11 | | `vectors/ed25519_strict.json` | Ed25519 signatures and the result of the strict profile of the author signature | §29.9 | | `vectors/note.json` | the data of the public note and the result of its rules | §24.1, §29.6 | +| `vectors/wordkey.json` | the key of words: the words of a text, what a writer refuses, and the identity the words derive | §38.1, §64 | | `vectors/locator.json` | the extension `datekeys.capsule` of a `.dkk`, its envelope and its locator, and what a reader rejects and uses of them | §44.1, §64 | | `vectors/mutations.json` | the mutation corpus: the 178 mutations of §64 and further cases | §63, §64 | | `vectors/inspect_differential.json` | 5110 mutations of fourteen fixtures with the verdict of steps 1 to 8 | §63 | @@ -584,6 +585,39 @@ feed, a space at either end, U+202E, U+200B, a byte order mark, a noncharacter, a byte that is not UTF-8 and the UTF-8 of a lone surrogate, refused. +## `vectors/wordkey.json` + +The key of words of spec §38.1, the cases that §64 of v0.11 asks for, in +three lists: + +- `normalize`: a `text` and its `words`, after NFD with the tables of Unicode + 18.0.0, without U+0300 to U+036F, with the simple lower case of each code + point and split by the spaces of the list of §38.1. Among the cases: the + vector of §38.1; «Ábaco ÁRBOL», «abaco arbol» and «ábaco árbol», which give + the same words; accents written as combining marks; punctuation, which + counts; a final sigma, a capital sharp s, a dotless i, the angstrom sign, + Hangul, fullwidth letters and U+A7CB, whose lower case is that of Unicode + 18.0.0; each of the 25 spaces of the list, one by one; and U+200B, U+180E + and U+FEFF, which are not spaces. +- `check`: a `text`, its `words` and what a writer does with them: `result` + `ok`, or `error` with the text of the reference in `error`. At least six + different words of three characters or more; a control, a + Default_Ignorable code point (U+200B, the soft hyphen, a variation + selector, U+FEFF) or a code point unassigned in Unicode 18.0.0 is refused, + and a private use code point or an emoji is not. +- `keys`: the identity of `words` for the capsule `capsule_id` of `round` of + the chain `chain_hash`, PBKDF2-HMAC-SHA256 of 600000 rounds: `key`, the raw + X25519 identity, and `recipient`, its `age1…` recipient. The vector of + §38.1; the next round, another `capsule_id` and another chain hash, each a + different key; round 2^53 − 1; and words that are not ASCII. + +```json +{ "name": "a zero width space, which the writer refuses", "text": "perro luna casa verde tren mar\u200b", "words": ["perro", "luna", "casa", "verde", "tren", "mar\u200b"], "result": "error", "error": "wordkey: the words hold the invisible character U+200B" } +``` + +The texts are valid UTF-8: a reader of JSON in another language cannot hold +anything else in a string. + ## `vectors/ed25519_strict.json` Ed25519 signatures, in hexadecimal, and whether the strict profile of the diff --git a/testdata/SOURCE.json b/testdata/SOURCE.json index d947ea7..00b0284 100644 --- a/testdata/SOURCE.json +++ b/testdata/SOURCE.json @@ -1,8 +1,8 @@ { "module": "g.activething.com/go/DateKeys", - "commit": "601e6d217804eacadea48df9d1f4d4c9b22dacd2", + "commit": "084728d3f7294cd93f644a5f87176a20e2a5de6e", "files": { - "README.md": "e56b52887b45b36af400a89aae34809f2f6be4366261255c68398761811e0584", + "README.md": "3bd654992338f0450922da4907644eee20cf0792d2cea80fc26c5accec7e514c", "fixtures/empty_payload.dkc": "871e9bf05b52bbae17f3adfbbf97b46e7f0e53aa8f57bcaa506e43f36f53a9d4", "fixtures/empty_payload.inspect.json": "373e5d012b023ad58bbb54cbdffe0bed9e50c637438a4083ddb74d5414c59f59", "fixtures/empty_payload.json": "588c2573d99b953d490e3d9caaa392b804398b1f91d4f95492a01dd5e7e1f8ba", @@ -134,6 +134,7 @@ "vectors/quicknet_rounds.json": "0f11bf5c5da88b1e929bb39439001a3a1f8447a85a4b3a25296d40d71a261e50", "vectors/security.json": "158df388fe881b75989e319eaadc88437b5031cd62f6c8cfc9cc421c3eacff89", "vectors/security_cms.json": "1ef9c74f1998fc06ab459ab885e3654bf4375f5baa276ea2a1de3f08e64a32b4", - "vectors/tlock_ibe.json": "27e9d9ebac4f07700661d2b4c524b10d065c9698e9acc68baa3d2c01bdbb24f2" + "vectors/tlock_ibe.json": "27e9d9ebac4f07700661d2b4c524b10d065c9698e9acc68baa3d2c01bdbb24f2", + "vectors/wordkey.json": "882593e1ed63cb5cfa6231a1bdc4060a04be5233db1a971d9dcbfff3031c071a" } } diff --git a/testdata/vectors/wordkey.json b/testdata/vectors/wordkey.json new file mode 100644 index 0000000..3dfe96d --- /dev/null +++ b/testdata/vectors/wordkey.json @@ -0,0 +1,732 @@ +{ + "spec": "0.11", + "description": "The key of words (spec §38.1): the words of a text, after NFD, without U+0300 to U+036F, in simple lower case of Unicode 18.0.0 and split by the spaces of the list; what a writer refuses; and the identity, PBKDF2-HMAC-SHA256 of 600000 rounds, for a chain hash, a round and a capsule_id. See testdata/README.md.", + "normalize": [ + { + "name": "the words of the vector of §38.1", + "text": "perro luna casa verde tren mar", + "words": [ + "perro", + "luna", + "casa", + "verde", + "tren", + "mar" + ] + }, + { + "name": "accents, upper case and runs of spaces", + "text": " Ábaco ÁRBOL\tniño ΣΑΣ İ ", + "words": [ + "abaco", + "arbol", + "nino", + "σασ", + "i" + ] + }, + { + "name": "«Ábaco ÁRBOL» of §38.1", + "text": "Ábaco ÁRBOL", + "words": [ + "abaco", + "arbol" + ] + }, + { + "name": "«abaco arbol» of §38.1", + "text": "abaco arbol", + "words": [ + "abaco", + "arbol" + ] + }, + { + "name": "«ábaco árbol» of §38.1", + "text": "ábaco árbol", + "words": [ + "abaco", + "arbol" + ] + }, + { + "name": "the accents written as combining marks", + "text": "ábaco árbol", + "words": [ + "abaco", + "arbol" + ] + }, + { + "name": "punctuation counts", + "text": "Perro, luna.", + "words": [ + "perro,", + "luna." + ] + }, + { + "name": "no text", + "text": "", + "words": [] + }, + { + "name": "only spaces", + "text": " \t  ", + "words": [] + }, + { + "name": "the simple lower case of a final sigma", + "text": "ΟΔΟΣ", + "words": [ + "οδοσ" + ] + }, + { + "name": "capital sharp s, lowered by its simple mapping", + "text": "STRAẞE", + "words": [ + "straße" + ] + }, + { + "name": "a dotless i stays", + "text": "ı", + "words": [ + "ı" + ] + }, + { + "name": "the angstrom sign, whose NFD is A and a ring", + "text": "Å", + "words": [ + "a" + ] + }, + { + "name": "Hangul, decomposed into jamo", + "text": "한글", + "words": [ + "한글" + ] + }, + { + "name": "a combining mark outside U+0300 to U+036F stays", + "text": "a⃗", + "words": [ + "a⃗" + ] + }, + { + "name": "fullwidth letters are not folded to ASCII", + "text": "ABC", + "words": [ + "abc" + ] + }, + { + "name": "U+A7CB, lowered by Unicode 18.0.0 to U+0264", + "text": "Ɤ", + "words": [ + "ɤ" + ] + }, + { + "name": "a zero width space is not a space", + "text": "a​b", + "words": [ + "a​b" + ] + }, + { + "name": "U+180E is not a space", + "text": "a᠎b", + "words": [ + "a᠎b" + ] + }, + { + "name": "a byte order mark is not a space", + "text": "ab", + "words": [ + "ab" + ] + }, + { + "name": "U+0009 is a space", + "text": "uno\tdos", + "words": [ + "uno", + "dos" + ] + }, + { + "name": "U+000A is a space", + "text": "uno\ndos", + "words": [ + "uno", + "dos" + ] + }, + { + "name": "U+000B is a space", + "text": "uno\u000bdos", + "words": [ + "uno", + "dos" + ] + }, + { + "name": "U+000C is a space", + "text": "uno\fdos", + "words": [ + "uno", + "dos" + ] + }, + { + "name": "U+000D is a space", + "text": "uno\rdos", + "words": [ + "uno", + "dos" + ] + }, + { + "name": "U+0020 is a space", + "text": "uno dos", + "words": [ + "uno", + "dos" + ] + }, + { + "name": "U+0085 is a space", + "text": "uno…dos", + "words": [ + "uno", + "dos" + ] + }, + { + "name": "U+00A0 is a space", + "text": "uno dos", + "words": [ + "uno", + "dos" + ] + }, + { + "name": "U+1680 is a space", + "text": "uno dos", + "words": [ + "uno", + "dos" + ] + }, + { + "name": "U+2000 is a space", + "text": "uno dos", + "words": [ + "uno", + "dos" + ] + }, + { + "name": "U+2001 is a space", + "text": "uno dos", + "words": [ + "uno", + "dos" + ] + }, + { + "name": "U+2002 is a space", + "text": "uno dos", + "words": [ + "uno", + "dos" + ] + }, + { + "name": "U+2003 is a space", + "text": "uno dos", + "words": [ + "uno", + "dos" + ] + }, + { + "name": "U+2004 is a space", + "text": "uno dos", + "words": [ + "uno", + "dos" + ] + }, + { + "name": "U+2005 is a space", + "text": "uno dos", + "words": [ + "uno", + "dos" + ] + }, + { + "name": "U+2006 is a space", + "text": "uno dos", + "words": [ + "uno", + "dos" + ] + }, + { + "name": "U+2007 is a space", + "text": "uno dos", + "words": [ + "uno", + "dos" + ] + }, + { + "name": "U+2008 is a space", + "text": "uno dos", + "words": [ + "uno", + "dos" + ] + }, + { + "name": "U+2009 is a space", + "text": "uno dos", + "words": [ + "uno", + "dos" + ] + }, + { + "name": "U+200A is a space", + "text": "uno dos", + "words": [ + "uno", + "dos" + ] + }, + { + "name": "U+2028 is a space", + "text": "uno\u2028dos", + "words": [ + "uno", + "dos" + ] + }, + { + "name": "U+2029 is a space", + "text": "uno\u2029dos", + "words": [ + "uno", + "dos" + ] + }, + { + "name": "U+202F is a space", + "text": "uno dos", + "words": [ + "uno", + "dos" + ] + }, + { + "name": "U+205F is a space", + "text": "uno dos", + "words": [ + "uno", + "dos" + ] + }, + { + "name": "U+3000 is a space", + "text": "uno dos", + "words": [ + "uno", + "dos" + ] + } + ], + "check": [ + { + "name": "six words", + "text": "perro luna casa verde tren mar", + "words": [ + "perro", + "luna", + "casa", + "verde", + "tren", + "mar" + ], + "result": "ok" + }, + { + "name": "six words with upper case and accents", + "text": "Perro LUNA casa verde trén mar", + "words": [ + "perro", + "luna", + "casa", + "verde", + "tren", + "mar" + ], + "result": "ok" + }, + { + "name": "three words", + "text": "uno dos tres", + "words": [ + "uno", + "dos", + "tres" + ], + "result": "error", + "error": "wordkey: a key of words needs at least 6 different words of 3 or more letters, not 3" + }, + { + "name": "only short words", + "text": "a b c d e f g h", + "words": [ + "a", + "b", + "c", + "d", + "e", + "f", + "g", + "h" + ], + "result": "error", + "error": "wordkey: a key of words needs at least 6 different words of 3 or more letters, not 0" + }, + { + "name": "one word six times", + "text": "perro perro perro perro perro perro", + "words": [ + "perro", + "perro", + "perro", + "perro", + "perro", + "perro" + ], + "result": "error", + "error": "wordkey: a key of words needs at least 6 different words of 3 or more letters, not 1" + }, + { + "name": "short words do not count", + "text": "de la casa al mar en tren verde", + "words": [ + "de", + "la", + "casa", + "al", + "mar", + "en", + "tren", + "verde" + ], + "result": "error", + "error": "wordkey: a key of words needs at least 6 different words of 3 or more letters, not 4" + }, + { + "name": "five words that count and a short one", + "text": "perro luna casa verde tren y", + "words": [ + "perro", + "luna", + "casa", + "verde", + "tren", + "y" + ], + "result": "error", + "error": "wordkey: a key of words needs at least 6 different words of 3 or more letters, not 5" + }, + { + "name": "a word of three letters counts", + "text": "sol luna casa verde tren mar", + "words": [ + "sol", + "luna", + "casa", + "verde", + "tren", + "mar" + ], + "result": "ok" + }, + { + "name": "a word of three letters that are not ASCII counts", + "text": "ñuñ luna casa verde tren mar", + "words": [ + "nun", + "luna", + "casa", + "verde", + "tren", + "mar" + ], + "result": "ok" + }, + { + "name": "the same words in other case count once", + "text": "perro PERRO luna casa verde tren", + "words": [ + "perro", + "perro", + "luna", + "casa", + "verde", + "tren" + ], + "result": "error", + "error": "wordkey: a key of words needs at least 6 different words of 3 or more letters, not 5" + }, + { + "name": "a zero width space, which the writer refuses", + "text": "perro luna casa verde tren mar​", + "words": [ + "perro", + "luna", + "casa", + "verde", + "tren", + "mar​" + ], + "result": "error", + "error": "wordkey: the words hold the invisible character U+200B" + }, + { + "name": "a soft hyphen, ignorable", + "text": "per­ro luna casa verde tren mar", + "words": [ + "per­ro", + "luna", + "casa", + "verde", + "tren", + "mar" + ], + "result": "error", + "error": "wordkey: the words hold the invisible character U+00AD" + }, + { + "name": "a variation selector, ignorable", + "text": "perro luna casa verde tren mar️", + "words": [ + "perro", + "luna", + "casa", + "verde", + "tren", + "mar️" + ], + "result": "error", + "error": "wordkey: the words hold the invisible character U+FE0F" + }, + { + "name": "a byte order mark", + "text": "perro luna casa verde tren mar", + "words": [ + "perro", + "luna", + "casa", + "verde", + "tren", + "mar" + ], + "result": "error", + "error": "wordkey: the words hold the invisible character U+FEFF" + }, + { + "name": "a control character", + "text": "perro luna casa verde tren mar\u0001", + "words": [ + "perro", + "luna", + "casa", + "verde", + "tren", + "mar\u0001" + ], + "result": "error", + "error": "wordkey: the words hold the control character U+0001" + }, + { + "name": "a C1 control character", + "text": "perro luna casa verde tren marŸ", + "words": [ + "perro", + "luna", + "casa", + "verde", + "tren", + "marŸ" + ], + "result": "error", + "error": "wordkey: the words hold the control character U+009F" + }, + { + "name": "DEL", + "text": "perro luna casa verde tren mar", + "words": [ + "perro", + "luna", + "casa", + "verde", + "tren", + "mar" + ], + "result": "error", + "error": "wordkey: the words hold the control character U+007F" + }, + { + "name": "an unassigned code point", + "text": "perro luna casa verde tren mar͸", + "words": [ + "perro", + "luna", + "casa", + "verde", + "tren", + "mar͸" + ], + "result": "error", + "error": "wordkey: the words hold U+0378, unassigned in Unicode 18.0.0" + }, + { + "name": "a private use code point is assigned", + "text": "perro luna casa verde tren mar", + "words": [ + "perro", + "luna", + "casa", + "verde", + "tren", + "mar" + ], + "result": "ok" + }, + { + "name": "an emoji", + "text": "perro luna casa verde tren mar 🌍", + "words": [ + "perro", + "luna", + "casa", + "verde", + "tren", + "mar", + "🌍" + ], + "result": "ok" + } + ], + "keys": [ + { + "name": "the vector of §38.1", + "words": [ + "perro", + "luna", + "casa", + "verde", + "tren", + "mar" + ], + "chain_hash": "52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971", + "round": 1000, + "capsule_id": "000102030405060708090a0b0c0d0e0f", + "key": "fceec4d8ca8de86c85a1f26ed49f82a2b38431bd0ce36db995ae7dfd49b96e41", + "recipient": "age1fqk6hflp8q5um2qrl5ckd8lu4x75rhtuncfus7f7up3v3v0zss0stdpqkr" + }, + { + "name": "the next round", + "words": [ + "perro", + "luna", + "casa", + "verde", + "tren", + "mar" + ], + "chain_hash": "52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971", + "round": 1001, + "capsule_id": "000102030405060708090a0b0c0d0e0f", + "key": "f4faecd9cac596b1a99c399d92cb031955201e5fc0db2c5d67163b1d244ef960", + "recipient": "age1f53tt9sjxc7l635jxc2xnerssquk7dl62j6nhvtjlefjwv5elgqsy2h9ae" + }, + { + "name": "another capsule_id", + "words": [ + "perro", + "luna", + "casa", + "verde", + "tren", + "mar" + ], + "chain_hash": "52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971", + "round": 1000, + "capsule_id": "000102030405060708090a0b0c0d0e0e", + "key": "08fe6614ea4bbcfc324c69c370d22ad5d32e09333606b565c3831fe0110d2fe2", + "recipient": "age1e2quqz6svvjqa5wrpwlkyr9362vqw8qfz43thq39977sts5wj4sqv6ep86" + }, + { + "name": "another chain hash", + "words": [ + "perro", + "luna", + "casa", + "verde", + "tren", + "mar" + ], + "chain_hash": "8990e7a9aaed2ffed73dbd7092123d6f289930540d7651336225dc172e51b2ce", + "round": 1000, + "capsule_id": "000102030405060708090a0b0c0d0e0f", + "key": "5da2ab56a8c841ca05970470bca4c7c108543acf2e4129e6743a09c0443577ed", + "recipient": "age1zn5cqedknwdlqsts23azduere4zlugc3fvz7m0sfrhcsfpz05axswl0km6" + }, + { + "name": "round 2^53 - 1", + "words": [ + "perro", + "luna", + "casa", + "verde", + "tren", + "mar" + ], + "chain_hash": "52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971", + "round": 9007199254740991, + "capsule_id": "000102030405060708090a0b0c0d0e0f", + "key": "6077e1675c84264fbe03a33abb1359520fc6f9fdb9b293603779127327bce7d4", + "recipient": "age1fjv9e2a6vd7pcr20llzsxthauam80snlskxcrr7rp0ulvy62xyuqt3v3jf" + }, + { + "name": "words that are not ASCII", + "words": [ + "abaco", + "arbol", + "nino", + "σασ", + "nandu", + "straße" + ], + "chain_hash": "52db9ba70e0cc0f6eaf7803dd07447a1f5477735fd3f661792ba94600c84e971", + "round": 1000, + "capsule_id": "ffeeddccbbaa99887766554433221100", + "key": "f64bce685596c1db68058125c0caf7cf4ab949d29d2829dff5627da5f758e6cd", + "recipient": "age1vc7tfsz048an895g5nxpkpj99cd54j44c3qv95kg57yu3klfxfdstpvqm6" + } + ] +}