Shared vectors of the key of words: testdata/vectors/wordkey.json

Spec section 64 of v0.11 asks for the vectors of the key of words, which
were only in the tests of package wordkey. The file has the words of 45
texts, among them each space of section 38.1 and three that are not; what a
writer does with 20 texts, with the text of the error of wordkey.Check; and
6 identities with their recipients, the vector of section 38.1 first. It is
regenerated by genfixtures and checked by TestVectorFilesAreCurrent.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
v0.12
dev 1 day ago
parent c531e936fa
commit 084728d3f7

@ -133,6 +133,14 @@ reader of v0.11 opens these capsules, and this one opens those of v0.11.
3837 completed to 4096 by a key 6 that is empty or not in its shortest 3837 completed to 4096 by a key 6 that is empty or not in its shortest
form (change 7) among them. The bases 3837, 4070, 4094 and 4095 and those form (change 7) among them. The bases 3837, 4070, 4094 and 4095 and those
of the next multiple join `padding_cases`. of the next multiple join `padding_cases`.
- `wordkey.json`, new: the key of words of §38.1, which §64 of v0.11
asks for and was only in the tests of `wordkey`. 45 texts and their
words, among them each space of the list and three that are not; 20
texts and what a writer does with them, the zero width space that it
refuses among them; and 6 identities with their recipients, the vector
of §38.1 and those of the next round, another `capsule_id`, another
chain hash, round 2^53 − 1 and words that are not ASCII. For
`datekeys-ts` and `datekeys-dart`.
- `testdata/README.md` describes the files of v0.11 and of the draft. - `testdata/README.md` describes the files of v0.11 and of the draft.
## Unreleased — specification v0.11 ## Unreleased — specification v0.11

@ -68,7 +68,7 @@ the same. A case of §64 that is not in the repository yet is marked
| 36.1 | Authenticity semantics | documented in `README.md`, `SECURITY.md` | — (a property the protocol does not provide) | | 36.1 | Authenticity semantics | documented in `README.md`, `SECURITY.md` | — (a property the protocol does not provide) |
| 37 | X25519 recipient V1; the writer rejects a recipient that is not canonical (bit 255 set, or u ≥ p) or of low order, and MAY reject a point of the twist | `age.X25519Recipient`; `agewrap.X25519IdentityFromRaw`, `agewrap.CheckX25519Recipient` (run by `capsule.Encrypt`); the twist check is not implemented | `agewrap.TestRawKeys`, `TestNonCanonicalRecipients`; `capsule.TestEncryptRejectsInvalidOptions` | | 37 | X25519 recipient V1; the writer rejects a recipient that is not canonical (bit 255 set, or u ≥ p) or of low order, and MAY reject a point of the twist | `age.X25519Recipient`; `agewrap.X25519IdentityFromRaw`, `agewrap.CheckX25519Recipient` (run by `capsule.Encrypt`); the twist check is not implemented | `agewrap.TestRawKeys`, `TestNonCanonicalRecipients`; `capsule.TestEncryptRejectsInvalidOptions` |
| 38 | Portable Access Key | `EncryptOptions.NewPortableKey` (fresh `I_ACCESS` per capsule; no API accepts an existing one); `accesskey.AccessKey` | `capsule.TestPortableKeysAreNeverReused` | | 38 | Portable Access Key | `EncryptOptions.NewPortableKey` (fresh `I_ACCESS` per capsule; no API accepts an existing one); `accesskey.AccessKey` | `capsule.TestPortableKeysAreNeverReused` |
| 38.1 | Key of words: one more X25519 credential of `time_and_key`, among the 16; the normalization: NFD with the tables of Unicode 18.0.0, without U+0300 to U+036F, the simple lower case of each code point, split by the spaces of the list; PBKDF2-HMAC-SHA256 of 600 000 rounds, salted with the chain hash, the round and `capsule_id`, into a raw X25519 identity; the writer requires at least 6 words, counting only different words of 3 or more letters, and refuses controls, ignorables and unassigned code points; a reader may ask for the words instead of a `.dkk` | `wordkey` (`Normalize`, `Check`, `Key`, `Identity`, `Rounds`, `MinWords`, `MinLetters`), with `pathrule.NFD`, `Lower`, `DefaultIgnorable` and `Assigned`; `capsule.EncryptOptions.Words` (`accessRecipients`; `sealer.write` derives the identity once `capsule_id` is drawn); `cmd/datekeys`: `-words` and `-words-file` of `encrypt` and `decrypt` (`wordsText`), the words of `decrypt` salted with what `capsule.Inspect` gives | `wordkey.TestNormalize`, `TestCheck`, `TestKeyVector` (the vector of §38.1); `capsule.TestEncryptFilesWords` (the words of another `capsule_id` do not open); `cmd/datekeys.TestKeyOfWords` | | 38.1 | Key of words: one more X25519 credential of `time_and_key`, among the 16; the normalization: NFD with the tables of Unicode 18.0.0, without U+0300 to U+036F, the simple lower case of each code point, split by the spaces of the list; PBKDF2-HMAC-SHA256 of 600 000 rounds, salted with the chain hash, the round and `capsule_id`, into a raw X25519 identity; the writer requires at least 6 words, counting only different words of 3 or more letters, and refuses controls, ignorables and unassigned code points; a reader may ask for the words instead of a `.dkk` | `wordkey` (`Normalize`, `Check`, `Key`, `Identity`, `Rounds`, `MinWords`, `MinLetters`), with `pathrule.NFD`, `Lower`, `DefaultIgnorable` and `Assigned`; `capsule.EncryptOptions.Words` (`accessRecipients`; `sealer.write` derives the identity once `capsule_id` is drawn); `cmd/datekeys`: `-words` and `-words-file` of `encrypt` and `decrypt` (`wordsText`), the words of `decrypt` salted with what `capsule.Inspect` gives | `wordkey.TestNormalize`, `TestCheck`, `TestKeyVector` (the vector of §38.1); `testdata/vectors/wordkey.json` (`internal/testkit.WordKeyVectors`, `TestVectorFilesAreCurrent`): the words of a text, what a writer refuses and the identities, the cases of §64 of v0.11; `capsule.TestEncryptFilesWords` (the words of another `capsule_id` do not open); `cmd/datekeys.TestKeyOfWords` |
| 39 | Recipients of INNER_ACCESS_AGE: in formats 2 and 3 from 1 to 16 credentials, a dummy in each slot left (a fresh public key whose private key is dropped at once), the 16 in a uniformly random order; which slots are dummies is recorded only in the official vectors | `capsule/encrypt.go` (`accessRecipients`, `fillSlots`, `permute`); `agewrap.AccessIdentity` | `capsule.TestInnerHasSixteenStanzas`, `TestDummyRecipients`, `TestStanzaOrderIsUniform`, `TestCredentialBounds`, `TestFixtureRecipients`, `TestEncryptRoundTripBothPolicies`; `TestConformanceFixtures` (the stanza each credential opens, `access_key_stanza` and `identity_stanzas` in the records) | | 39 | Recipients of INNER_ACCESS_AGE: in formats 2 and 3 from 1 to 16 credentials, a dummy in each slot left (a fresh public key whose private key is dropped at once), the 16 in a uniformly random order; which slots are dummies is recorded only in the official vectors | `capsule/encrypt.go` (`accessRecipients`, `fillSlots`, `permute`); `agewrap.AccessIdentity` | `capsule.TestInnerHasSixteenStanzas`, `TestDummyRecipients`, `TestStanzaOrderIsUniform`, `TestCredentialBounds`, `TestFixtureRecipients`, `TestEncryptRoundTripBothPolicies`; `TestConformanceFixtures` (the stanza each credential opens, `access_key_stanza` and `identity_stanzas` in the records) |
| 40 | `.dkk` framing; `BODY_LEN` in 1..16 MiB (0 is `ERR_INTEGRITY`); order of the frame checks | `accesskey.Encode`, `accesskey.Decode` (the body buffer grows with the data read; every buffer holding the body is wiped) | `accesskey.TestDecodeRejects`, `TestDecodePrecedence`, `TestDecodeShortBodyAllocatesLittle`, `TestEncodeAndDecodeLeaveNoStaleMaterial`, `FuzzDecode` | | 40 | `.dkk` framing; `BODY_LEN` in 1..16 MiB (0 is `ERR_INTEGRITY`); order of the frame checks | `accesskey.Encode`, `accesskey.Decode` (the body buffer grows with the data read; every buffer holding the body is wiped) | `accesskey.TestDecodeRejects`, `TestDecodePrecedence`, `TestDecodeShortBodyAllocatesLittle`, `TestEncodeAndDecodeLeaveNoStaleMaterial`, `FuzzDecode` |
| 41 | `.dkk` BODY_CBOR | `AccessKey.MarshalBody`, `accesskey.DecodeBody` (hand-written `bodyWire` encode and decode) | `accesskey.TestFixtures`, `TestDecodeBodyStructure` | | 41 | `.dkk` BODY_CBOR | `AccessKey.MarshalBody`, `accesskey.DecodeBody` (hand-written `bodyWire` encode and decode) | `accesskey.TestFixtures`, `TestDecodeBodyStructure` |

@ -187,7 +187,11 @@ func format3Vectors(dir string) error {
if err != nil { if err != nil {
return err return err
} }
for name, v := range map[string]any{"paths.json": paths, "path_fold.json": fold, "head_schema.json": heads, "security.json": security, "ed25519_strict.json": strict, "note.json": note} { words, err := testkit.WordKeyVectors()
if err != nil {
return err
}
for name, v := range map[string]any{"paths.json": paths, "path_fold.json": fold, "head_schema.json": heads, "security.json": security, "ed25519_strict.json": strict, "note.json": note, "wordkey.json": words} {
if err := testkit.WriteJSON(filepath.Join(dir, name), v); err != nil { if err := testkit.WriteJSON(filepath.Join(dir, name), v); err != nil {
return err return err
} }

@ -267,6 +267,10 @@ func TestVectorFilesAreCurrent(t *testing.T) {
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
words, err := testkit.WordKeyVectors()
if err != nil {
t.Fatal(err)
}
for _, v := range []struct { for _, v := range []struct {
file string file string
want any want any
@ -280,6 +284,7 @@ func TestVectorFilesAreCurrent(t *testing.T) {
{"security.json", security, &testkit.SecurityVectorFile{}}, {"security.json", security, &testkit.SecurityVectorFile{}},
{"ed25519_strict.json", strict, &testkit.Ed25519StrictFile{}}, {"ed25519_strict.json", strict, &testkit.Ed25519StrictFile{}},
{"note.json", note, &testkit.NoteVectorFile{}}, {"note.json", note, &testkit.NoteVectorFile{}},
{"wordkey.json", words, &testkit.WordKeyVectorFile{}},
} { } {
if err := testkit.ReadJSON("../../testdata/vectors/"+v.file, v.got); err != nil { if err := testkit.ReadJSON("../../testdata/vectors/"+v.file, v.got); err != nil {
t.Fatal(err) t.Fatal(err)

@ -0,0 +1,212 @@
package testkit
import (
"encoding/hex"
"errors"
"fmt"
"slices"
"strings"
"g.activething.com/go/DateKeys/profile"
"g.activething.com/go/DateKeys/wordkey"
)
// WordKeyVectorFile is testdata/vectors/wordkey.json: the key of words of
// spec v0.11, §38.1. Normalize gives the words of a text; Check, what a
// writer does with them; Keys, the identity that the words derive for a
// capsule, with its recipient.
type WordKeyVectorFile struct {
Spec string `json:"spec"`
Description string `json:"description"`
Normalize []WordNormalize `json:"normalize"`
Check []WordCheck `json:"check"`
Keys []WordKeyVector `json:"keys"`
}
// WordNormalize is a text and its words, as wordkey.Normalize gives them.
type WordNormalize struct {
Name string `json:"name"`
Text string `json:"text"`
Words []string `json:"words"`
}
// WordCheck is a text, its words and the result of wordkey.Check on them:
// ResultOK, or the text of its error.
type WordCheck struct {
Name string `json:"name"`
Text string `json:"text"`
Words []string `json:"words"`
Result string `json:"result"`
Error string `json:"error,omitempty"`
}
// WordKeyVector is the identity of words for the capsule capsule_id of the
// round of the chain chain_hash: key, the raw X25519 identity, and
// recipient, its age1… recipient.
type WordKeyVector struct {
Name string `json:"name"`
Words []string `json:"words"`
ChainHash string `json:"chain_hash"`
Round uint64 `json:"round"`
CapsuleID string `json:"capsule_id"`
Key string `json:"key"`
Recipient string `json:"recipient"`
}
// cp is the string of the code point r: invisible code points are written
// by number in this file, never as themselves.
func cp(r rune) string { return string(r) }
// The spaces of spec §38.1, step 4.
var wordSpaces = []rune{
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x20, 0x85, 0xa0, 0x1680,
0x2000, 0x2001, 0x2002, 0x2003, 0x2004, 0x2005, 0x2006, 0x2007, 0x2008, 0x2009, 0x200a,
0x2028, 0x2029, 0x202f, 0x205f, 0x3000,
}
// The six words of the vector of spec §38.1.
const sixWords = "perro luna casa verde tren mar"
// WordKeyVectors computes testdata/vectors/wordkey.json, and fails if a
// vector does not get the words, the result or the key it is written for.
func WordKeyVectors() (WordKeyVectorFile, error) {
f := WordKeyVectorFile{
Spec: SpecVersion,
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.",
}
var errs []error
fail := func(format string, args ...any) { errs = append(errs, fmt.Errorf(format, args...)) }
// Normalize.
norm := []struct {
name, text string
want []string
}{
{"the words of the vector of §38.1", sixWords, strings.Fields(sixWords)},
{"accents, upper case and runs of spaces", " Ábaco" + cp(0xa0) + "ÁRBOL" + cp(0x09) + "niño ΣΑΣ İ ", []string{"abaco", "arbol", "nino", "σασ", "i"}},
{"«Ábaco ÁRBOL» of §38.1", "Ábaco ÁRBOL", []string{"abaco", "arbol"}},
{"«abaco arbol» of §38.1", "abaco arbol", []string{"abaco", "arbol"}},
{"«ábaco árbol» of §38.1", "ábaco árbol", []string{"abaco", "arbol"}},
{"the accents written as combining marks", "a" + cp(0x301) + "baco a" + cp(0x301) + "rbol", []string{"abaco", "arbol"}},
{"punctuation counts", "Perro, luna.", []string{"perro,", "luna."}},
{"no text", "", []string{}},
{"only spaces", " " + cp(0x09) + cp(0x3000) + " ", []string{}},
{"the simple lower case of a final sigma", "ΟΔΟΣ", []string{"οδοσ"}},
{"capital sharp s, lowered by its simple mapping", "STRAẞE", []string{"straße"}},
{"a dotless i stays", "ı", []string{"ı"}},
{"the angstrom sign, whose NFD is A and a ring", cp(0x212b), []string{"a"}},
{"Hangul, decomposed into jamo", "한글", []string{"ᄒ" + "ᅡ" + "ᆫ" + "ᄀ" + "ᅳ" + "ᆯ"}},
{"a combining mark outside U+0300 to U+036F stays", "a" + cp(0x20d7), []string{"a" + cp(0x20d7)}},
{"fullwidth letters are not folded to ASCII", "ABC", []string{"abc"}},
{"U+A7CB, lowered by Unicode 18.0.0 to U+0264", cp(0xa7cb), []string{cp(0x264)}},
{"a zero width space is not a space", "a" + cp(0x200b) + "b", []string{"a" + cp(0x200b) + "b"}},
{"U+180E is not a space", "a" + cp(0x180e) + "b", []string{"a" + cp(0x180e) + "b"}},
{"a byte order mark is not a space", "a" + cp(0xfeff) + "b", []string{"a" + cp(0xfeff) + "b"}},
}
for _, s := range wordSpaces {
norm = append(norm, struct {
name, text string
want []string
}{fmt.Sprintf("U+%04X is a space", s), "uno" + cp(s) + "dos", []string{"uno", "dos"}})
}
for _, c := range norm {
got := wordkey.Normalize(c.text)
if got == nil {
got = []string{}
}
if !slices.Equal(got, c.want) {
fail("normalize %q: %q, want %q", c.name, got, c.want)
}
f.Normalize = append(f.Normalize, WordNormalize{Name: c.name, Text: c.text, Words: got})
}
// Check.
for _, c := range []struct{ name, text, want string }{
{"six words", sixWords, ResultOK},
{"six words with upper case and accents", "Perro LUNA casa verde trén mar", ResultOK},
{"three words", "uno dos tres", "not 3"},
{"only short words", "a b c d e f g h", "not 0"},
{"one word six times", "perro perro perro perro perro perro", "not 1"},
{"short words do not count", "de la casa al mar en tren verde", "not 4"},
{"five words that count and a short one", "perro luna casa verde tren y", "not 5"},
{"a word of three letters counts", "sol luna casa verde tren mar", ResultOK},
{"a word of three letters that are not ASCII counts", "ñuñ luna casa verde tren mar", ResultOK},
{"the same words in other case count once", "perro PERRO luna casa verde tren", "not 5"},
{"a zero width space, which the writer refuses", sixWords + cp(0x200b), "invisible character U+200B"},
{"a soft hyphen, ignorable", "per" + cp(0xad) + "ro luna casa verde tren mar", "invisible character U+00AD"},
{"a variation selector, ignorable", sixWords + cp(0xfe0f), "invisible character U+FE0F"},
{"a byte order mark", cp(0xfeff) + sixWords, "invisible character U+FEFF"},
{"a control character", sixWords + cp(0x01), "control character U+0001"},
{"a C1 control character", sixWords + cp(0x9f), "control character U+009F"},
{"DEL", sixWords + cp(0x7f), "control character U+007F"},
{"an unassigned code point", sixWords + cp(0x0378), "U+0378, unassigned"},
{"a private use code point is assigned", sixWords + cp(0xe000), ResultOK},
{"an emoji", "perro luna casa verde tren mar 🌍", ResultOK},
} {
words := wordkey.Normalize(c.text)
if words == nil {
words = []string{}
}
v := WordCheck{Name: c.name, Text: c.text, Words: words, Result: ResultOK}
if err := wordkey.Check(words); err != nil {
v.Result, v.Error = "error", err.Error()
}
switch {
case c.want == ResultOK && v.Result != ResultOK:
fail("check %q: %s, want ok", c.name, v.Error)
case c.want != ResultOK && !strings.Contains(v.Error, c.want):
fail("check %q: %q, want %q", c.name, v.Error, c.want)
}
f.Check = append(f.Check, v)
}
// Keys.
quicknet := profile.QuicknetChainHash
const mainnet = "8990e7a9aaed2ffed73dbd7092123d6f289930540d7651336225dc172e51b2ce"
const id0 = "000102030405060708090a0b0c0d0e0f"
for _, c := range []struct {
name, text, chain string
round uint64
capsuleID, want string
}{
{"the vector of §38.1", sixWords, quicknet, 1000, id0, "fceec4d8ca8de86c85a1f26ed49f82a2b38431bd0ce36db995ae7dfd49b96e41"},
{"the next round", sixWords, quicknet, 1001, id0, ""},
{"another capsule_id", sixWords, quicknet, 1000, "000102030405060708090a0b0c0d0e0e", ""},
{"another chain hash", sixWords, mainnet, 1000, id0, ""},
{"round 2^53 - 1", sixWords, quicknet, 1<<53 - 1, id0, ""},
{"words that are not ASCII", "Ábaco ÁRBOL niño ΣΑΣ ñandú straße", quicknet, 1000, "ffeeddccbbaa99887766554433221100", ""},
} {
chain, err := hex.DecodeString(c.chain)
if err != nil {
return f, err
}
capsuleID, err := hex.DecodeString(c.capsuleID)
if err != nil {
return f, err
}
words := wordkey.Normalize(c.text)
raw, err := wordkey.Key(words, chain, c.round, capsuleID)
if err != nil {
return f, err
}
id, err := wordkey.Identity(words, chain, c.round, capsuleID)
if err != nil {
return f, err
}
key := hex.EncodeToString(raw)
if c.want != "" && key != c.want {
fail("key %q: %s, want %s", c.name, key, c.want)
}
for _, k := range f.Keys {
if k.Key == key {
fail("key %q: the same key as %q", c.name, k.Name)
}
}
f.Keys = append(f.Keys, WordKeyVector{
Name: c.name, Words: words, ChainHash: c.chain, Round: c.round, CapsuleID: c.capsuleID,
Key: key, Recipient: id.Recipient().String(),
})
}
return f, errors.Join(errs...)
}

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/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/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/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/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/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 | | `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, noncharacter, a byte that is not UTF-8 and the UTF-8 of a lone surrogate,
refused. 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` ## `vectors/ed25519_strict.json`
Ed25519 signatures, in hexadecimal, and whether the strict profile of the Ed25519 signatures, in hexadecimal, and whether the strict profile of the

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