Run the shared vectors of the key of words: testdata at 084728d

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

@ -4,6 +4,11 @@ Cambios notables de la librería Dart. El proyecto usa versionado semántico; mi
## Especificación 0.11, en la rama `v0.11` — sin versión
### 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.
- `test/wordkey_testdata_test.dart` los corre, en la VM: 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.
### Etapa 7b: las claves de autor y el sellado del localizador (06-10-2026)
- **Las claves de autor** (`lib/src/authorkey.dart`), port del paquete `authorkey` de `datekeys-go` en `c531e93`, el borrador v0.12 (§29.9, §29.12), con las mismas comprobaciones en el mismo orden y los mismos textos de error:

@ -572,10 +572,10 @@ 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 la rama `v0.12` de `datekeys-go` en `c531e93`, cuyo `testdata/` es el de `601e6d2`, el que tiene `datekeys-ts`: 133 ficheros, con los veredictos y las líneas del borrador v0.12. Cada fichero dice aún `"spec": "0.11"`, como el `SpecVersion` de Go, hasta que el autor apruebe el borrador.
`testdata/` es una copia de `datekeys-go/testdata` en un commit fijo. `testdata/SOURCE.json` registra el commit y el SHA-256 de cada fichero, igual que en `datekeys-ts`. La copia actual es la de la rama `v0.12` de `datekeys-go` en `084728d`, la misma que tiene `datekeys-ts`: 134 ficheros, con `wordkey.json`, los vectores compartidos de la llave de palabras, con los veredictos y las líneas del borrador v0.12. Cada fichero dice aún `"spec": "0.11"`, como el `SpecVersion` de Go, hasta que el autor apruebe el borrador.
```bash
dart run tool/sync_testdata.dart sync --commit c531e93
dart run tool/sync_testdata.dart sync --commit 084728d
```
```bash

@ -0,0 +1,91 @@
// The key of words against testdata/vectors/wordkey.json, the shared cases
// of spec §38.1 that §64 of v0.11 asks for: the words of each text, what a
// writer does with them, with the error of Go's wordkey.Check, and the
// identity and the recipient that the words derive. Each key is 600 000
// iterations of PBKDF2: on the VM only.
@TestOn('vm')
library;
import 'dart:convert';
import 'dart:io';
import 'package:datekeys/datekeys.dart';
import 'package:datekeys/src/wordkey.dart';
import 'package:test/test.dart';
typedef Json = Map<String, Object?>;
List<String> _strings(Object? v) => (v! as List).cast<String>();
void main() {
final f = jsonDecode(
File('testdata/vectors/wordkey.json').readAsStringSync(),
) as Json;
final normalize = (f['normalize']! as List).cast<Json>();
final check = (f['check']! as List).cast<Json>();
final keys = (f['keys']! as List).cast<Json>();
test('wordkey.json names this specification and has the cases of README', () {
expect(f['spec'], specVersion);
expect(
normalize.where((c) => (c['name']! as String).endsWith(' is a space')),
hasLength(25),
);
expect(check.where((c) => c['result'] == 'ok'), isNotEmpty);
expect(
check.where((c) => '${c['error']}'.contains('U+200B')),
hasLength(1),
);
expect(
keys.first['key'],
'fceec4d8ca8de86c85a1f26ed49f82a2b38431bd0ce36db995ae7dfd49b96e41',
);
});
group('normalize', () {
for (final c in normalize) {
test('${c['name']}', () {
expect(normalizeWords(c['text']! as String), _strings(c['words']));
});
}
});
group('check', () {
for (final c in check) {
test('${c['name']}', () {
final words = normalizeWords(c['text']! as String);
expect(words, _strings(c['words']));
if (c['result'] == 'ok') {
checkWords(words);
} else {
expect(c['result'], 'error');
expect(
() => checkWords(words),
throwsA(
isA<WordKeyException>().having(
(e) => e.message,
'message',
c['error'],
),
),
);
}
});
}
});
group('keys', () {
for (final c in keys) {
test('${c['name']}', () {
final id = wordIdentity(
_strings(c['words']),
fromHex(c['chain_hash']! as String),
c['round']! as int,
fromHex(c['capsule_id']! as String),
);
expect(toHex(id.secretKey), c['key']);
expect(id.recipientString, c['recipient']);
});
}
});
}

34
testdata/README.md vendored

@ -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

@ -1,8 +1,8 @@
{
"module": "g.activething.com/go/DateKeys",
"commit": "c531e936fa8188a4aab01575607cc85d4f5d4fa4",
"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"
}
}

@ -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"
}
]
}
Loading…
Cancel
Save

Powered by TurnKey Linux.