|
|
# DateKeys test data
|
|
|
|
|
|
Official vectors, fixtures and corpora of the DateKeys Protocol Specification
|
|
|
v0.11 and of the draft v0.12, generated by the reference implementation.
|
|
|
Another implementation consumes them as they are: this file documents every
|
|
|
format, so that no Go code has to be read. The rules that decide each verdict
|
|
|
are in the specification; this file points to them, and states only what
|
|
|
belongs to the files themselves.
|
|
|
|
|
|
```
|
|
|
go run ./internal/testkit/genfixtures -out testdata
|
|
|
```
|
|
|
|
|
|
regenerates everything except the `.dkc` and `.dkk` fixtures, which are
|
|
|
generated once and frozen (spec §67), and the vectors `security_cms.json` and
|
|
|
`locator.json`, frozen too because they hold randomness: delete one of them
|
|
|
to make it again. The records of each fixture (`<name>.json`,
|
|
|
`<name>.dkk.json`, `<name>.inspect.json`) are recomputed from its frozen bytes,
|
|
|
so the fixtures of v0.8.2, v0.9 and v0.10 carry `"spec": "0.11"` and the fields
|
|
|
added since. The local gate (`scripts/check.sh`) and CI run it and fail if any
|
|
|
committed file changes: every file below is exactly what the implementation
|
|
|
computes today.
|
|
|
|
|
|
The `spec` field of every file is `"0.11"`, the version this module declares,
|
|
|
until the author approves the draft v0.12. What the draft changes, the texts
|
|
|
of the verdicts of a certificate and of a seal, the profile of a certificate
|
|
|
and the rules of the addresses and of the padding of a locator, is already in
|
|
|
the files: the verdicts and the lines of `security.json`, `security_cms.json`
|
|
|
and `mutations.json`, and the cases of `locator.json`, are those of the draft.
|
|
|
|
|
|
Conventions for every file:
|
|
|
|
|
|
- JSON in UTF-8, with LF line endings. Binary values are lowercase hex strings.
|
|
|
- `error`, `result` and similar fields hold the normative codes of spec §69, such
|
|
|
as `ERR_NON_CANONICAL_CBOR`.
|
|
|
- `step` is a step of the reading flow of spec §63, 1 to 18. Steps 1 to 8 are the
|
|
|
pre-unlock checks (`datekeys inspect`, `capsule.Inspect`): no network, no
|
|
|
secret.
|
|
|
- The `spec` field names the version of the specification.
|
|
|
- `format` is a capsule format, the `VERSION` of the PRELUDE (spec §22): 1, 2
|
|
|
or 3. A writer produces format 3 only; formats 1 and 2, those of v0.8.2 and
|
|
|
v0.9, are still read (spec §70).
|
|
|
|
|
|
| File | Content | Spec |
|
|
|
|---|---|---|
|
|
|
| `vectors/profile_quicknet.json` | Quicknet Provider Profile: its canonical CBOR and `profile_hash` | §11, §12 |
|
|
|
| `vectors/quicknet_rounds.json` | date → round resolution | §15, §16, §65 |
|
|
|
| `vectors/dk1.json` | canonical `dk1_` strings, and rejected encodings with their code | §18, §19, §66 |
|
|
|
| `vectors/cbor.json` | the CBOR profile, and one block of vectors per schema, CONTROL_CBOR in the three formats | §58, CDDL |
|
|
|
| `vectors/tlock_ibe.json` | H2 of the tlock IBE: the serialization of an element of GT | §63 step 11 |
|
|
|
| `vectors/padding.json` | the padding of formats 2 and 3: P for each content length L, and the length of PAYLOAD_AGE | §29.1 |
|
|
|
| `vectors/paths.json` | the paths of a format 3 head: the rules of one entry, and those of the paths of a head | §29.5 |
|
|
|
| `vectors/path_fold.json` | the key of R7 of segments, and their NFD | §29.5, §29.5.1 |
|
|
|
| `vectors/head_schema.json` | heads of format 3 and the result of decoding them | §29.4 to §29.6, §69.1 |
|
|
|
| `vectors/security.json` | security areas of format 3 in the context of a capsule, their verdicts and the lines that show them | §29.3, §29.7, §29.9 |
|
|
|
| `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 |
|
|
|
| `fixtures/<name>.dkc`, `<name>.json` | official capsules and every intermediate value | §67 |
|
|
|
| `fixtures/<name>.dkk`, `<name>.dkk.json` | official access keys | §68 |
|
|
|
| `fixtures/<name>.plaintext` | the content of each capsule: what the reader delivers in formats 1 and 2, without the padding of format 2, and in format 3 BODY, whose files its record lays out | §67 |
|
|
|
| `fixtures/<name>.inspect.json` | the exact output of `datekeys inspect -json` for each `.dkc` | §63 |
|
|
|
|
|
|
There are twenty-six official capsules. Five are in format 1, the fixtures of
|
|
|
v0.8.2, kept for compatibility: `time_only`, `time_only_extensions`,
|
|
|
`time_and_key_portable`, `time_and_key_recipients` and `empty_payload`. Seven
|
|
|
are in format 2, the fixtures of v0.9, kept for compatibility too:
|
|
|
|
|
|
| Fixture | Policy | Credentials | L | Padding code | P |
|
|
|
|---|---|---|---|---|---|
|
|
|
| `format2_time_only` | `time_only` | — | 78000 | 2, reforzado | 79872 |
|
|
|
| `format2_time_only_bloque256` | `time_only` | — | 78000, the same content | 1, bloque256 | 78080 |
|
|
|
| `format2_empty_payload` | `time_only` | — | 0 | 2 | 256 |
|
|
|
| `format2_time_only_extensions` | `time_only` | — | 34 | 2 | 256 |
|
|
|
| `format2_time_and_key_portable` | `time_and_key` | a portable `.dkk`; 15 dummies | 46 | 2 | 256 |
|
|
|
| `format2_time_and_key_recipients` | `time_and_key` | a portable `.dkk` and three X25519 identities; 12 dummies | 41 | 2 | 256 |
|
|
|
| `format2_time_and_key_sixteen` | `time_and_key` | sixteen X25519 identities; no dummy | 41 | 2 | 256 |
|
|
|
|
|
|
The first two have a P above 64 KiB, so their PAYLOAD_AGE has two STREAM
|
|
|
chunks. `format2_time_only_extensions` carries a noncritical PUBLIC_HEADER
|
|
|
extension and a noncritical CONTROL_CBOR extension. The release that opens each
|
|
|
capsule, a published Quicknet signature, is in its `<name>.json`, so they all
|
|
|
decrypt offline.
|
|
|
|
|
|
Fourteen are in format 3. Their plaintext file is BODY, L bytes: the frame, the
|
|
|
security area, the head and the files (spec §29.2).
|
|
|
|
|
|
| Fixture | Policy | Files | Comment | L | Padding code | P | Area | Verdicts |
|
|
|
|---|---|---|---|---|---|---|---|---|
|
|
|
| `format3_single` | `time_only` | 1, `nota.txt`, with mtime | — | 659 | 2 | 768 | 512 | F0, S0 |
|
|
|
| `format3_tree` | `time_only` | 5 in three folders, one of 80000 bytes, one without mtime | two lines, and a declared author | 84078 | 2 | 86016 | 512 | F0, S0 |
|
|
|
| `format3_comment_only` | `time_only` | — | two lines, the second with a TAB, and a declared author | 636 | 2 | 768 | 512 | F0, S0 |
|
|
|
| `format3_bloque256` | `time_only` | 1 of 20000 bytes | — | 20644 | 1 | 20736 | 512 | F0, S0 |
|
|
|
| `format3_time_and_key_portable` | `time_and_key`, a portable `.dkk` and 15 dummies | 1 | — | 688 | 2 | 768 | 512 | F0, S0 |
|
|
|
| `format3_area_1024` | `time_only` | 1 | — | 1171 | 2 | 1280 | 1024 | F0, S0 |
|
|
|
| `format3_security_v2` | `time_only` | 1 | — | 659 | 2 | 768 | 512 | X |
|
|
|
| `format3_signature_unsupported` | `time_only` | 1 | — | 659 | 2 | 768 | 512 | F1, S0 |
|
|
|
| `format3_seal_unsupported` | `time_only` | 1 | — | 659 | 2 | 768 | 512 | F1, S1 |
|
|
|
| `format3_unsigned` | `time_only` | 1, `nota.txt`, with mtime | — | 32915 | 2 | 34816 | 32768 | F0, S0 |
|
|
|
| `format3_note` | `time_only`, with a public note | 1, `nota.txt`, with mtime | — | 32915 | 2 | 34816 | 32768 | F0, S0 |
|
|
|
| `format3_signed` | `time_only` | 1, `nota.txt`, with mtime | — | 32915 | 2 | 34816 | 32768 | F4, S0 |
|
|
|
| `format3_signed_cms` | `time_only` | 1, `nota.txt`, with mtime | — | 32915 | 2 | 34816 | 32768 | F6, S0 |
|
|
|
| `format3_sealed` | `time_only` | 1, `nota.txt`, with mtime | — | 32915 | 2 | 34816 | 32768 | F4, S4 |
|
|
|
|
|
|
The first five were written by a writer of v0.10, with the area of 512 bytes.
|
|
|
The next four only a generator of test vectors may write (spec §62.1 rule 13):
|
|
|
an area larger than 512 bytes, a security map of version 2, which a reader of
|
|
|
this version cannot read, an author signature of `alg` 4294967295, reserved
|
|
|
for tests, with a random key of 32 bytes and a random signature of 64, and
|
|
|
that with a seal of `seal_type` 4294967295, reserved for tests too, and a
|
|
|
random token of 32 bytes. None of them has a verdict that stops the opening.
|
|
|
|
|
|
The last five were written by a writer of v0.11, with the area of 32768 bytes:
|
|
|
|
|
|
- `format3_unsigned` holds the content of `format3_signed` without a
|
|
|
signature, and its P is the same (spec §64: the same content with a
|
|
|
signature and without one, in the common area).
|
|
|
- `format3_note` carries the public note «Cartas del viaje a Lisboa»,
|
|
|
`datekeys.note` in the noncritical array of PUBLIC_HEADER (spec §24.1),
|
|
|
which its record gives in `header_extensions`.
|
|
|
- The last three are those that `capsule.EncryptFiles` writes with a signer
|
|
|
and a sealer.
|
|
|
|
|
|
Those three are signed and sealed with test keys (spec §29.8 to §29.11), and
|
|
|
their record has a `signature` object, and `seal` in the third:
|
|
|
|
|
|
- `format3_signed`: `alg` 1. The seed of the test key, which is not a secret,
|
|
|
is in `secret_seed`: Ed25519 is deterministic, so signing `author_message`
|
|
|
with it gives `signature` again. Opening it gives F4 and the key `author_key`,
|
|
|
`dkauthor1…`, which `verdicts` repeats; with that key among the saved ones,
|
|
|
F3.
|
|
|
- `format3_signed_cms`: `alg` 2, two certificates, an ECDSA P-256 one and an
|
|
|
RSA 2048 one, each with a seal CAdES-T from a test authority, dated before
|
|
|
the round time. The record has `signers` (SIGNERS in hexadecimal),
|
|
|
`certificates` (the DER of each), `signature` (the DER of the CMS signature)
|
|
|
and `signer_results`, one for each required signer in the order of SIGNERS:
|
|
|
holder, issuer that the certificate says, result, seal time, and whether the
|
|
|
seal, with its accuracy, precedes the round time. The private keys are not
|
|
|
kept: ECDSA and RSA-PSS are not deterministic, and a reader only verifies.
|
|
|
- `format3_sealed`: `alg` 1 as the first, and a seal of `seal_type` 2 over
|
|
|
`SEAL_SUBJECT`. The record has `seal`: `seal_subject`, the `token` in
|
|
|
hexadecimal, the `holder` of the authority as §29.7 shows it, and the time.
|
|
|
|
|
|
In the three, the record gives the commitments `control_commit`, `head_digest`
|
|
|
and `signers_digest`, the text `author_message` and its `author_code`, and the
|
|
|
exact content of key 2 of `SECURITY_CBOR`. An implementation checks them from
|
|
|
the control, the head and the security area of the fixture, and the verdicts
|
|
|
from `verdicts`. The certificates and the tokens are random, so these fixtures
|
|
|
are frozen once written like the others. The BODY of `format3_tree` is over two STREAM
|
|
|
chunks, with its head in the first.
|
|
|
|
|
|
## Edited files
|
|
|
|
|
|
`mutations.json` and `inspect_differential.json` give each mutated `.dkc` as
|
|
|
edits of a base file, not as its full bytes:
|
|
|
|
|
|
```json
|
|
|
{ "base": "time_only.dkc", "edits": [[4, 1, "04"]] }
|
|
|
```
|
|
|
|
|
|
- `base` is a file of `testdata/fixtures`. In `mutations.json` it may be absent:
|
|
|
the base is then the empty file, and the single edit holds the whole capsule.
|
|
|
- An edit is `[at, delete, insert]`: the `delete` bytes at offset `at` of the
|
|
|
base are replaced by the bytes of the hex string `insert`.
|
|
|
- The edits of one file refer to offsets of the unmodified base, are sorted by
|
|
|
`at` and do not overlap. The result is therefore built in one pass: copy the
|
|
|
base up to `at`, append `insert`, skip `delete` bytes of the base, go on with
|
|
|
the next edit, and copy the rest of the base.
|
|
|
- `[0, 78799, ""]` on `time_only.dkc` is the empty file; `"edits": []` is the
|
|
|
base unchanged.
|
|
|
|
|
|
## `vectors/cbor.json`
|
|
|
|
|
|
```json
|
|
|
{
|
|
|
"spec": "0.11",
|
|
|
"walk": { "max_depth": 3, "max_len": 64 },
|
|
|
"accept": [ { "name": "uint 2^53 eight bytes", "hex": "1b0020000000000000", "value": "9007199254740992" } ],
|
|
|
"reject": [ { "name": "tag", "hex": "c101", "error": "ERR_NON_CANONICAL_CBOR" } ],
|
|
|
"schemas": [ { "block": "extension", "schema": "public_header", "name": "65 extensions", "hex": "a600…", "result": "ERR_NON_CANONICAL_CBOR" } ]
|
|
|
}
|
|
|
```
|
|
|
|
|
|
### Generic vectors: `accept` and `reject`
|
|
|
|
|
|
Each `hex` is checked as exactly one data item of the CBOR profile of spec §58:
|
|
|
major types 0, 2, 3, 4 and 5 only; integers and lengths in their shortest form;
|
|
|
definite lengths; map keys that are unsigned integers in strictly ascending
|
|
|
order; valid UTF-8 text; nothing after the item. `accept` holds the inputs that
|
|
|
pass, `reject` those that fail, all with `ERR_NON_CANONICAL_CBOR`.
|
|
|
|
|
|
The `walk` limits apply as well, as in the reference `codec.Walk`: containers
|
|
|
nest at most `max_depth` deep (a scalar has depth 0, `81818100` has depth 3),
|
|
|
and every byte string and text string has at most `max_len` bytes, every array
|
|
|
at most `max_len` items and every map at most `max_len` entries. The vectors
|
|
|
named "above max_len" or "above max_depth" fail on these limits only.
|
|
|
|
|
|
`value` is present for an accepted unsigned integer: a JSON number up to
|
|
|
2^53 − 1, and a decimal string above, so that no reader loses precision.
|
|
|
|
|
|
Among them: shortest-form boundaries, `a200010101` (the two-key map
|
|
|
`{0: 1, 1: 1}`), a text with a leading BOM, keys out of order or repeated,
|
|
|
non-integer keys, indefinite lengths, tags, floats, simple values, negative
|
|
|
integers, truncation, lengths beyond the input, trailing bytes, overlong UTF-8
|
|
|
and surrogates.
|
|
|
|
|
|
### Schema vectors: `schemas`
|
|
|
|
|
|
Each vector is one encoded object:
|
|
|
|
|
|
- `schema` names the object and the decoder to run on `hex`:
|
|
|
- `provider_profile`: a Provider Profile (§11), decoded and then validated
|
|
|
as a profile to pin, by rules 1 to 3 of spec §12.1 in their order: the
|
|
|
CDDL with the `period` limit of the reference, the field rules
|
|
|
(`ERR_UNKNOWN_PROFILE`) and the chain-hash self-check
|
|
|
(`ERR_PROFILE_MISMATCH`), whose formula §12.1 gives. No `profile_hash` is
|
|
|
expected (rule 4). A vector that changes a hashed key recomputes
|
|
|
`chain_hash`, unless its name says that the chain hash no longer matches.
|
|
|
- `public_header`: PUBLIC_HEADER (§24). No profile registry is consulted and
|
|
|
no extension is known: a header naming an unpinned profile is valid here
|
|
|
(`ERR_UNKNOWN_PROFILE` comes from the registry at step 4), and critical
|
|
|
extensions are not checked here.
|
|
|
- `control_cbor`: CONTROL_CBOR (§31), decoded for the capsule format given
|
|
|
by `format`, 1 when the field is absent: the schema version must equal it
|
|
|
(another is `ERR_UNSUPPORTED_VERSION`, layer 2), and keys 6,
|
|
|
`payload_length`, and 7, `padding`, are required in formats 2 and 3 and
|
|
|
not defined in format 1. The names of the vectors of formats 2 and 3
|
|
|
start with "format 2: " and "format 3: ".
|
|
|
- `dkk_body`: BODY_CBOR of a `.dkk` (§41), without the 12-byte DKK1
|
|
|
prelude.
|
|
|
- `block` names the schema the vector exercises: the same as `schema`, or
|
|
|
`verification_metadata` (the `.dkk` body's key 6 varies) or `extension` (the
|
|
|
PUBLIC_HEADER's key 6, `noncritical_extensions`, varies).
|
|
|
- `result` is `ok` or the error code.
|
|
|
|
|
|
When bytes break several rules, the code is the one of the first failing
|
|
|
layer of spec §69.1: the type tag and the schema version, read first from keys
|
|
|
0 and 1 (layer 2), then the CDDL, the implementation limits included, and the
|
|
|
re-encoding (layer 3), and only then the fields with codes of their own in
|
|
|
ascending key order (layer 4). The objects of these vectors have no frame, so
|
|
|
layer 1 does not apply, and their critical extensions are not checked (see
|
|
|
above).
|
|
|
|
|
|
Each block has a minimal valid object, an unknown key, a missing required key, a
|
|
|
wrong type and values out of size or range. The `extension` block has, besides:
|
|
|
data `40` (empty) and `5801xx` (length not in its shortest form), data of every
|
|
|
other type (text, `null`, integer, map, array, tag, indefinite length), 64 and
|
|
|
65 extensions, an `extension_id` starting with a BOM, the pair U+FF61 and
|
|
|
U+10000 in UTF-8 byte order (valid) and in UTF-16 order (invalid), and
|
|
|
`extension_version` 2^32 − 1 (valid), 2^32 and 2^53 (invalid).
|
|
|
|
|
|
The `control_cbor` block has, besides, a key 6 in format 1, where it is not
|
|
|
defined, and in format 2: the minimal control of 103 bytes (L = 0),
|
|
|
`payload_length` 78000, 2^32 + 1 and L_MAX = 2^53 − 2^46 (valid), L_MAX + 1,
|
|
|
2^53 and 2^64 − 1 (invalid), a `payload_length` of 7 or 9 bytes, as an
|
|
|
unsigned integer or with a length not in its shortest form, `padding` 1
|
|
|
(valid), 0, 3, 257 and as a byte string, a missing key 6 or 7, keys 6 and 7
|
|
|
out of order, an unknown key 8, and schema versions 1 and 3
|
|
|
(`ERR_UNSUPPORTED_VERSION`); and in format 3: the minimal control, both
|
|
|
extension arrays, `payload_length` 84078 with `padding` 1 (valid),
|
|
|
`payload_length` L_MAX + 1, a missing key 6 or 7, schema version 3 without
|
|
|
keys 6 and 7, and schema versions 1, 2 and 4 (`ERR_UNSUPPORTED_VERSION`).
|
|
|
|
|
|
#### Implementation limits
|
|
|
|
|
|
The vectors apply the implementation limits of the reference that spec §74
|
|
|
lists: the maximum `extension_id` length, the Provider Profile `period` of one
|
|
|
day, the maximum name lengths and the `public_key` length. The vectors named
|
|
|
"the implementation limit" sit at a limit and are valid; those named "above
|
|
|
the implementation limit" go one past it and are otherwise valid, so that an
|
|
|
implementation without the limit accepts them. The name alphabets, the
|
|
|
`genesis_time` range and the drand rules of the `provider_profile`
|
|
|
validation are not limits: they are rules of spec §12.1. Neither is the
|
|
|
minimum of one byte of `extension_id` (spec §31).
|
|
|
|
|
|
## `vectors/tlock_ibe.json`
|
|
|
|
|
|
H2, the hash of an element of GT in the IBE-CCA with which tlock wraps
|
|
|
FK_TIME: spec §63 step 11, and the paragraph "Serialización de GT en H2" after
|
|
|
the flow.
|
|
|
|
|
|
```json
|
|
|
{
|
|
|
"spec": "0.11",
|
|
|
"description": "…",
|
|
|
"vectors": [
|
|
|
{ "name": "H2(e(G1, G2)), the generators of G1 and G2", "g1": "97f1…", "g2": "93e0…", "gt": "0f41…", "h2": "cb87319f24560b5231579a09ad79f12e" }
|
|
|
]
|
|
|
}
|
|
|
```
|
|
|
|
|
|
- `g1`, `g2`: a point of G1 and one of G2 in the canonical compressed
|
|
|
encoding of spec §12.2, 48 and 96 bytes; the vector uses the generators.
|
|
|
- `gt`: e(g1, g2), the pairing of G1 × G2 of step 11, in 576 bytes. Over the
|
|
|
tower Fp2 = Fp[u]/(u² + 1), Fp6 = Fp2[v]/(v³ − (u + 1)) and
|
|
|
Fp12 = Fp6[w]/(w² − v), every element is written with its coefficients from
|
|
|
the highest degree to the lowest: c1 then c0 for Fp12, c2, c1 and c0 for
|
|
|
Fp6, c1 then c0 for Fp2, and each element of Fp in 48 bytes big-endian. It
|
|
|
is the order of kilic/bls12-381, which drand/kyber and tlock use.
|
|
|
- `h2`: SHA-256 of the ASCII bytes `IBE-H2` followed by `gt`, truncated to
|
|
|
its first 16 bytes, the length of V.
|
|
|
|
|
|
A reader checks that its pairing of `g1` and `g2` serializes to `gt` and that
|
|
|
its H2 of those bytes is `h2`; the vector fixes the pairing and the
|
|
|
serialization at once. The same 576 bytes with the twelve coordinates of Fp in
|
|
|
reverse order, c0 first at every level as `Fp12.toBytes` of `@noble/curves`
|
|
|
writes them, give `0118eea9d5971745f71e3c94926f1717` and another FK_TIME.
|
|
|
|
|
|
## `vectors/padding.json`
|
|
|
|
|
|
The padding of the payload of a capsule of format 2 or 3, spec §29.1, where L
|
|
|
is the length of BODY in format 3: for each content length L, the length P of the plaintext of PAYLOAD_AGE with each code, and the
|
|
|
length of PAYLOAD_AGE.
|
|
|
|
|
|
```json
|
|
|
{
|
|
|
"spec": "0.11",
|
|
|
"l_max": 8936830510563328,
|
|
|
"vectors": [
|
|
|
{ "l": 78000, "bloque256": 78080, "reforzado": 79872, "payload_age_bloque256": 78296, "payload_age_reforzado": 80088, "e": 16, "s": 5, "last_bits": 11 }
|
|
|
],
|
|
|
"rejected": [8936830510563329, 9007199254740992]
|
|
|
}
|
|
|
```
|
|
|
|
|
|
- `l`: L, the length of the content.
|
|
|
- `bloque256`: P with code 1, 256 · ⌈L / 256⌉, at least 256. `reforzado`: P
|
|
|
with code 2, the larger of that and Padmé.
|
|
|
- `payload_age_bloque256`, `payload_age_reforzado`: the length of PAYLOAD_AGE
|
|
|
for each P, 184 + P + 16 · max(1, ⌈P / 65536⌉): an age header of one X25519
|
|
|
stanza (168 bytes), the 16-byte nonce, and the P bytes in STREAM chunks of
|
|
|
64 KiB with a 16-byte tag each.
|
|
|
- `e`, `s`, `last_bits`: the intermediate values of Padmé, E = bitlen(L) − 1,
|
|
|
S = bitlen(E) and E − S; only for L > 256, and informative.
|
|
|
- `l_max`: L_MAX = 2^53 − 2^46, the largest L.
|
|
|
- `rejected`: lengths above L_MAX, which a writer rejects and which, as key 6
|
|
|
of a control, are `ERR_NON_CANONICAL_CBOR`.
|
|
|
|
|
|
Every number is below 2^53, so a JSON number holds it exactly. The lengths
|
|
|
include both sides of the boundaries where 32-bit arithmetic breaks
|
|
|
(2^31 − 2^25, 2^32 − 2^26, 2^32) and 2^49 − 1, the first L for which a
|
|
|
floating-point logarithm gives an E one too large (P does not change).
|
|
|
|
|
|
## `vectors/paths.json` and `vectors/path_fold.json`
|
|
|
|
|
|
The rules of the paths of a format 3 head, spec §29.5, with the tables of
|
|
|
§29.5.1. Both files name the tables they were computed with:
|
|
|
`"unicode_version": "18.0.0"` and `tables_digest`, the SHA-256 of the
|
|
|
canonical text of the generated tables, which a second implementation that
|
|
|
generates them from the same files can recompute.
|
|
|
|
|
|
```json
|
|
|
{
|
|
|
"paths": [ { "name": "TAB", "path": "a\tb", "result": "R4: segment 1: control U+0009" } ],
|
|
|
"trees": [ { "name": "A.txt and a.txt", "paths": ["A.txt", "a.txt"], "result": "ERR_HEAD_INVALID", "detail": "R7: path 2 collides with path 1 in segment 1" } ]
|
|
|
}
|
|
|
```
|
|
|
|
|
|
- `paths`: one path and the rules of one entry, R2, R3, R4, R4b, R5, R6, R6b,
|
|
|
R6c and R10, in that order; `result` is `ok` or the violation of the first
|
|
|
rule that fails, as every implementation must word it: the rule, the
|
|
|
segment when it is about one, and the character as `U+` and at least four
|
|
|
upper-case hexadecimal digits, never the text itself.
|
|
|
- `trees`: the paths of a head, each a file of 0 bytes, in the order given,
|
|
|
and the result of decoding that head (see `head_schema.json`): R1 and R8
|
|
|
in layer 3 (`ERR_NON_CANONICAL_CBOR`), then each entry and R7 and R9 over
|
|
|
the tree in layer 4 (`ERR_HEAD_INVALID`, with its `detail`).
|
|
|
|
|
|
Among them: U+00A0, accepted, and U+3000, refused by R6c, at both ends of a
|
|
|
segment; the best-fit projections, the full-width forms of '/', '\', ':'
|
|
|
and '.', and CON.txt in full-width forms; 8.3 aliases, `~1` alone included;
|
|
|
unassigned code points and noncharacters; U+206A to U+206F, the tags and other
|
|
|
ignorables outside the whitelist; a dot followed by ZWJ and a segment of ZWJ
|
|
|
alone; 127 and 85 times U+0390; U+F03A; `.datekeys-x` at the first level and
|
|
|
at another; U+FF5E and U+1F600 in both orders; `ab` with and without ZWNJ;
|
|
|
U+00BF, U+00A7 and U+2665, accepted; VS16 after U+2764 and after `a`; ZWJ at
|
|
|
the start, at the end and twice; the rainbow flag, accepted, and the flag of
|
|
|
Scotland, refused; and `["b/..", "a"]`, an error of R8 before R3.
|
|
|
|
|
|
`path_fold.json` gives, for each `segment`, its `nfd` and its `key` of R7,
|
|
|
NFD(fold(NFD(s'))) with s' the segment without ZWNJ, ZWJ, VS15 and VS16:
|
|
|
among them the entries F of CaseFolding, the dotless i, the Kelvin and
|
|
|
Angstrom signs, Cherokee, Hangul, the canonical order of two marks, and the
|
|
|
whitelist dropped before NFD.
|
|
|
|
|
|
## `vectors/head_schema.json`
|
|
|
|
|
|
Heads of format 3, HEAD_CBOR, and the result of decoding them with no
|
|
|
extension known (spec §29.4 to §29.6): `ok`, or the code of the first
|
|
|
failing layer of §69.1. A head is decoded alone: the frame of BODY that
|
|
|
bounds it and the files it lays out are not checked here (spec §63 steps
|
|
|
17.2 and 17.5).
|
|
|
|
|
|
```json
|
|
|
{ "name": "a comment with U+202E", "hex": "a4006d…", "result": "ERR_HEAD_INVALID", "detail": "comment: text: bidirectional control U+202E" }
|
|
|
```
|
|
|
|
|
|
- Layer 2: the type tag and version 1; another type tag is
|
|
|
`ERR_NON_CANONICAL_CBOR`, another version `ERR_UNSUPPORTED_VERSION`.
|
|
|
- Layer 3: the CDDL with its sizes and ranges, R1 and R8, and the equality
|
|
|
with the re-encoding: `ERR_NON_CANONICAL_CBOR`.
|
|
|
- Layer 4, in key order: the comment and the declared author (§29.6), then
|
|
|
each file with R2 to R6c, R4b and R10 and its layout, then R7 and R9 over
|
|
|
the tree, all `ERR_HEAD_INVALID`, and then the critical extensions,
|
|
|
`ERR_EXTENSION_CRITICAL_UNKNOWN` with no extension known.
|
|
|
- `detail`, for `ERR_HEAD_INVALID` only: the violation as every
|
|
|
implementation must word it, `comment: text: …`, `declared author:
|
|
|
text: …`, `file N: …` for an entry, counted from 1, or the violation of the
|
|
|
tree.
|
|
|
|
|
|
Besides the cases of each layer, it has a comment with tags that spell a
|
|
|
text, one with VS16 after a letter and one with a run of variation
|
|
|
selectors, one with VS17, one with CR LF, and the precedence of a comment and
|
|
|
a path that both break, and of a path that breaks and an unknown critical
|
|
|
extension.
|
|
|
|
|
|
## `vectors/security.json`
|
|
|
|
|
|
Security areas of format 3, SECURITY_CBOR exactly as its SECURITY_LEN bytes,
|
|
|
with their verdicts and the lines that show them (spec §29.3, §29.7, §29.9),
|
|
|
which never stop the opening. Every area is evaluated in the `context` of the
|
|
|
file, what a verdict needs besides the area: `control_commit` and
|
|
|
`head_digest` in hexadecimal, and `round_time` in RFC 3339.
|
|
|
|
|
|
```json
|
|
|
{
|
|
|
"context": { "control_commit": "0101…", "head_digest": "0202…", "round_time": "2030-01-01T00:00:00Z" },
|
|
|
"vectors": [ { "name": "a signature of alg 4294967295, reserved for tests", "hex": "a300…", "signature": "F1", "seal": "S0", "lines": ["No se ha comprobado ninguna firma: trátala como no firmada."] } ]
|
|
|
}
|
|
|
```
|
|
|
|
|
|
X stands for both, when the outer map fails its layer 2 or 3: key 2 that is
|
|
|
not a byte string, or an empty one, an unknown key 4, a byte more after the
|
|
|
map, version 2, another type tag, keys out of order, an array. Otherwise the
|
|
|
signature and the seal are evaluated apart, and the first row of the table of
|
|
|
§29.7 that holds decides:
|
|
|
|
|
|
- an empty area, as a writer without a signer writes it: F0 and S0;
|
|
|
- a signature of `alg` 1 that verifies over the context: F4, and one that
|
|
|
does not: F2;
|
|
|
- `alg` 0 or 4294967295, an empty key, or content that is not CBOR or has a
|
|
|
byte more: F1, with the seal intact;
|
|
|
- a seal of `seal_type` 1, 3 or 4294967295: S1; one of `seal_type` 0, one that
|
|
|
breaks its schema, even with an unknown `seal_type`, which is read only from
|
|
|
a seal that meets it, and one of `seal_type` 2 whose token is not DER: S2.
|
|
|
|
|
|
`lines` are the verdicts as §29.7 words them, which an implementation writes
|
|
|
byte for byte. The valid signatures of `alg` 2 and seals of `seal_type` 2 are
|
|
|
in `security_cms.json`.
|
|
|
|
|
|
## `vectors/security_cms.json`
|
|
|
|
|
|
Security areas with an author signature of `alg` 2, a CMS signature with
|
|
|
certificates, or a time seal of `seal_type` 2, an RFC 3161 token: 135 cases,
|
|
|
each with the context of its capsule, the verdicts, the result of each signer
|
|
|
and the lines of the draft v0.12, §29.7, §29.10 and §29.11. They complete
|
|
|
`security.json`, whose areas have no valid signature or seal of these kinds.
|
|
|
The file is frozen: the certificates and the tokens are made once, with test
|
|
|
keys, so a second implementation reads them and must reach the same verdicts
|
|
|
and write the same lines. Delete the file to make it again.
|
|
|
|
|
|
```json
|
|
|
{ "name": "alg 2: a required signer is absent: F5", "security_cbor": "a3…",
|
|
|
"context": { "control_commit": "…", "head_digest": "…", "round_time": "2030-01-01T00:00:00Z" },
|
|
|
"signature": "F5", "seal": "S0",
|
|
|
"signers": [ { "holder": "Ana López", "issuer": "Ana López", "result": "valid", "seal_time": "2026-09-30T12:00:00Z", "before_round_time": true }, … ],
|
|
|
"lines": ["…"] }
|
|
|
```
|
|
|
|
|
|
- `context`: what a verdict needs besides `SECURITY_CBOR`, as in
|
|
|
`security.json`.
|
|
|
- `signers`: the results of the required signers, in the order of SIGNERS;
|
|
|
`foreign_signers`: those of the signers that are not required, which never
|
|
|
count. Each has the `holder` and the `issuer` as §29.7 shows them, its
|
|
|
`result` (`valid`, `invalid`, `absent`, `without seal`, `invalid seal`,
|
|
|
`out of validity` or `not verifiable`), the `seal_time` of its CAdES-T when
|
|
|
it has one, and `before_round_time`, whether that time plus its accuracy
|
|
|
precedes the round time.
|
|
|
- `seal_holder` and `seal_time`: the authority and the time of a valid seal
|
|
|
of key 3.
|
|
|
- `lines`: the verdicts as the official SDK shows them (§29.7), byte for byte:
|
|
|
the names between « and », the line of each signer with its authority, the
|
|
|
warning that DateKeys does not check who issued the seals, and the times in
|
|
|
RFC 3339 with the fraction of the token.
|
|
|
|
|
|
A time is in RFC 3339, with the fraction of the token when it has one. The
|
|
|
cases follow each row of §29.7 and each item of the lists of §64 for v0.11
|
|
|
and v0.12: F6 with two signers, after the round time, with a signer who is not
|
|
|
required and with 16 signers; F5 for an absent signer, a withdrawn CAdES-T, no
|
|
|
seal, a certificate out of validity and keys outside the table; F2 in the
|
|
|
context of another head and for a message-digest of another message; F1 for
|
|
|
SIGNERS that break its rule beside a valid CMS, BER, two SignerInfo of one
|
|
|
certificate, the version against the `sid`, two content-type attributes, the
|
|
|
ESSCertIDv2 and the certificate of the signer; every hash and curve of the
|
|
|
table, RSASSA-PSS with and without `trailerField`, an attribute with an arc of
|
|
|
2^31 and a certificate twice; the names of the holder and of the issuer in
|
|
|
each string type and against each rule, `givenName` and `surname` before a
|
|
|
`commonName` with its NIF included; and, over an `alg` 1 signature, the seals
|
|
|
S1 to S5 at the edges of the token: its accuracy, its `genTime`, `ordering`,
|
|
|
a field after the last, the imprint, `crls` and the authority.
|
|
|
|
|
|
## `vectors/locator.json`
|
|
|
|
|
|
The extension `datekeys.capsule` of a `.dkk` and what it points to (spec
|
|
|
§44.1): a `.dkc` of patterned bytes in an envelope of age whose header
|
|
|
(`envelope_header`) goes in the locator and whose `rest`, without a mark, is
|
|
|
hidden in a `host` file at `host_offset`; the locator sealed with tlock for
|
|
|
round 1000 (`locator_sealed`), with its plaintext of 4096 bytes
|
|
|
(`locator_plaintext`) and its fields; and the data of the extension
|
|
|
(`extension_data`), with the note `note` and the DateKey `datekey`. A reader
|
|
|
opens the locator with the release of round 1000, which `mutations.json` and
|
|
|
the records of the fixtures of that round give, finds the rest in the host,
|
|
|
checks `rest_size`, `rest_digest` and `capsule_digest`, and gets the `.dkc`
|
|
|
back. The file is frozen: the envelope and the locators hold randomness.
|
|
|
|
|
|
On the same envelope, the cases of §64, each checked against the reference
|
|
|
when the file is made:
|
|
|
|
|
|
- `padding_cases`: the length of the plaintext of a locator for `base`, the
|
|
|
length of its CBOR without key 6: the least multiple of 4096 that holds it,
|
|
|
or the next one when key 6 cannot complete it, because 1, 2, 26 or 259
|
|
|
bytes are missing (§44.1). Among them the bases 3837, 4070, 4094 and 4095,
|
|
|
which give 8192, those of the next multiple, which give 12288, and their
|
|
|
neighbours.
|
|
|
- `uri_cases`: an address and whether the rules of §44.1 accept it (`ok`):
|
|
|
the scheme, the authority without a percent sign, userinfo or a backslash,
|
|
|
the port, each character outside RFC 3986 and a percent sign without two
|
|
|
hexadecimal digits, the "." and ".." segments, written or with `%2e`, in the
|
|
|
path but not in the query or the fragment, the first and the last address
|
|
|
of each IPv4 block of §44.1 with the public addresses next to them, the
|
|
|
IPv6 blocks and the addresses that hold an IPv4 one, the names that only a
|
|
|
machine or a local network resolves, a last segment that is numeric or
|
|
|
starts with `0x`, and base32 that is not a CID v1. Then what the text of
|
|
|
v0.12 fixes besides: segments of 63 and 64 characters, or with a hyphen at
|
|
|
an end, the scheme, `0X` and the local names in upper case, an IPv4 and a
|
|
|
port with leading zeros, and a CID with the bits left over not zero, with
|
|
|
padding, in upper case, with a varint that is not minimal, with an empty
|
|
|
digest, or of 128 and 136 characters.
|
|
|
- `mixed`: a second locator of the envelope, sealed for round 1000 too. Of its
|
|
|
`addresses`, a reader rejects the first two, an `http` one and one of NAT64
|
|
|
that leads to 127.0.0.1, and uses the third (`usable`), which finds the rest
|
|
|
in `host`: the locator reads all the same. A writer never writes it.
|
|
|
- `rest_cases`: a `resource` as a reader downloads it, the `offset` that an
|
|
|
address gives, and whether the rest read there opens the envelope (`opens`):
|
|
|
the rest alone, the host with bytes after the rest, of which only
|
|
|
`rest_size` bytes from the offset are read, a byte of the rest changed, an
|
|
|
offset that is not its own, and a rest cut short.
|
|
|
- `extension_cases`: data of `datekeys.capsule` and whether a reader can use
|
|
|
it (`ok`): without a locator, and unusable, with only
|
|
|
`ERR_EXTENSION_DATA_INVALID`, for a locator sealed for round 1001 beside a
|
|
|
DateKey of round 1000, a locator that is not an age file or is empty, no
|
|
|
DateKey or one that is not canonical, a note that breaks its rules and a
|
|
|
key 3.
|
|
|
- `plaintext_cases`: plaintexts of a locator of the envelope, each with the
|
|
|
defect that its name says or none, and whether a reader reads them (`ok`).
|
|
|
The bases 4094, 4070 and 3837 completed to 4096 with an empty key 6 or with
|
|
|
its length not in its shortest form, against the 8192 bytes of the base
|
|
|
4094 (§76 of v0.12, change 7); eight addresses and nine, none, an empty one
|
|
|
and one of 1025 bytes, which make the whole locator unreadable, an offset of
|
|
|
0 written, an offset and a `resto_size` of 2^53; two blocks where one
|
|
|
suffices, padding that is not zeros, a byte less and a byte more. An
|
|
|
address that breaks the rules of §44.1 does not make a locator unreadable:
|
|
|
that is `mixed`.
|
|
|
|
|
|
## `vectors/note.json`
|
|
|
|
|
|
The data of the public note, `datekeys.note` version 1 in the noncritical
|
|
|
array of PUBLIC_HEADER (spec §24.1): text in UTF-8, from 1 to 1024 bytes, that
|
|
|
meets the rules of the declared author of §29.6. A writer writes a note only
|
|
|
when its `result` is `ok`; a reader shows it only then, and otherwise treats
|
|
|
the note as unusable, never the capsule (§54), and says so.
|
|
|
|
|
|
```json
|
|
|
{ "name": "a bidi override", "data": "61e280ae62", "result": "ERR_EXTENSION_DATA_INVALID", "detail": "a public note that breaks the rules of text: text: bidirectional control U+202E" }
|
|
|
```
|
|
|
|
|
|
`detail` is the text of the rule that a note breaks, without the code, as the
|
|
|
reference words it. Among the cases: letters that are not ASCII and an emoji,
|
|
|
an emoji with VS16, 1024 bytes, accepted; no byte, 1025 bytes, a tab, a line
|
|
|
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
|
|
|
author signature accepts them (spec §29.9): the equation of RFC 8032
|
|
|
without the cofactor, A and R canonical, S below ℓ and A not of small order.
|
|
|
They follow the cases of «Taming the many EdDSAs»: S + ℓ, the top bits of S, a
|
|
|
non-canonical R, the eight points of small order as A, non-canonical
|
|
|
encodings of A, a key of mixed order with and without the cofactor, and an R
|
|
|
of small order with a key of prime order, which this profile accepts.
|
|
|
|
|
|
```json
|
|
|
{ "name": "A of small order, the point 0 of the torsion, R the identity and S = 0", "message": "…", "public_key": "0100…", "signature": "0100…", "valid": false, "stdlib": true }
|
|
|
```
|
|
|
|
|
|
`stdlib` is what `crypto/ed25519` of Go answers, for the record: where it is
|
|
|
true and `valid` is false, an implementation needs the checks of the profile
|
|
|
before the equation, as `internal/ed25519strict` does.
|
|
|
|
|
|
## `vectors/mutations.json`
|
|
|
|
|
|
The mutation corpus of spec §64, as frozen data. Each case is a `.dkc`, what
|
|
|
the reader is given to open it, and the exact error and step at which the full
|
|
|
reading flow (`capsule.Open`, §63) must fail.
|
|
|
|
|
|
```json
|
|
|
{
|
|
|
"name": "version changed",
|
|
|
"spec": true,
|
|
|
"dkc": { "base": "time_only.dkc", "edits": [[4, 1, "04"]] },
|
|
|
"release": { "round": 1000, "signature": "b446…" },
|
|
|
"now": "2023-08-23T15:59:24Z",
|
|
|
"registry": "default",
|
|
|
"network": false,
|
|
|
"frozen": false,
|
|
|
"error": "ERR_UNSUPPORTED_VERSION",
|
|
|
"step": 2
|
|
|
}
|
|
|
```
|
|
|
|
|
|
- `name`: unique, stable.
|
|
|
- `spec`: true for the 178 mutations listed in spec §64, false for the further
|
|
|
cases of the reference. The cases come in this order: the 33 mutations of
|
|
|
the first two lists of §64 on the format 1 fixtures (cases 1 to 33), 32
|
|
|
further cases (34 to 65), the same 33 mutations on the format 2 fixtures,
|
|
|
named "format 2: …" (66 to 98), the 23 of the list of format 2 (99 to 121),
|
|
|
5 further cases (122 to 126), the same 33 on the format 3 fixtures, named
|
|
|
"format 3: …" (127 to 159), the 48 of the list of format 3 (160 to 207), the
|
|
|
8 of the list of v0.11 that a capsule can hold (208 to 215), and 3 further
|
|
|
cases (216 to 218). A line of the lists of §64 with several values, such as
|
|
|
"AREA_LEN 0, 511, 513 o 66048", is one case for each.
|
|
|
- `dkc`: the capsule, as edits of a fixture (see above). The reader gets it as a
|
|
|
seekable file, so that the `capsule_digest` of an offered `.dkk` is checked
|
|
|
before any release request (spec §63 step 9.a).
|
|
|
- `dkk`: the hex of a complete `.dkk` file (prelude and body) offered as the
|
|
|
access credential; absent when none is offered.
|
|
|
- `identities`: age X25519 identities (`AGE-SECRET-KEY-1…`) offered as access
|
|
|
credentials; absent when none.
|
|
|
- `release`: what the release source answers to every request, whatever round
|
|
|
is asked for. The reader must verify it (§51): a case may serve a release of
|
|
|
another round, a round with the signature of another, or a signature that is
|
|
|
not the canonical encoding of a point (§12.2). `null` means that no release
|
|
|
is available (`ERR_RELEASE_UNAVAILABLE`). The release is supplied directly,
|
|
|
as the caller's own, so one that breaks the rules of step 10 gets the code
|
|
|
of step 10; a source that fetched it over a network would have discarded
|
|
|
it, and the code would be `ERR_RELEASE_UNAVAILABLE` at step 9 (spec §63
|
|
|
steps 9 and 10).
|
|
|
- `now`: the reader's clock, RFC 3339. No release is requested before the round
|
|
|
time of the DateKey.
|
|
|
- `registry`: `default` pins exactly the Quicknet profile of
|
|
|
`profile_quicknet.json`; `empty` pins none.
|
|
|
- `extensions`: the extensions the application implements. Each entry is known
|
|
|
at `(id, version)`, and its data is valid only when it equals the bytes of
|
|
|
`valid_data`. Each entry is registered in both arrays of every object
|
|
|
(§72), as a Go `extension.Registry` that does not implement
|
|
|
`extension.Placement`: an extension outside its registration counts as
|
|
|
unknown there (§54), so the cases "known critical PUBLIC_HEADER extension
|
|
|
with invalid data", "known critical CONTROL_CBOR extension with invalid
|
|
|
data" and "known critical .dkk extension with invalid data", at steps 4, 14
|
|
|
and 9, depend on it. Absent: the application knows no extension, the state
|
|
|
of the base protocol V1.
|
|
|
- `network`: whether the failure may come after a release request. When false,
|
|
|
the reader must fail without requesting any release (§27, §63): every failure
|
|
|
of steps 1 to 8 and of step 9 before the request (9.a to 9.c).
|
|
|
- `frozen`: the capsule was built once with age randomness; its bytes are kept
|
|
|
and never regenerated. These cases have no `base`.
|
|
|
- `error`, `step`: the expected code and the step of §63 that fails. For a
|
|
|
capsule that opens, `error` is `ok`, `step` is 0 and `verdicts` holds the
|
|
|
verdicts of its security area, `signature` and `seal`, with the `lines`
|
|
|
that show them (spec §29.7): the four cases of security of the list of
|
|
|
format 3, and seven of the list of v0.11.
|
|
|
|
|
|
Every case reproduces offline: the recorded release stands in for the network.
|
|
|
A reader that implements only steps 1 to 8 can replay every case whose `step` is
|
|
|
at most 8: 57 cases, 39 of them from §64. Steps 1 to 8 are summarised in "The
|
|
|
checks of steps 1 to 8" below.
|
|
|
|
|
|
### Credentials and the release: step 9
|
|
|
|
|
|
What happens between step 8 and the release request is spec §63 step 9: for
|
|
|
`time_and_key` only, an offered `.dkk` is checked as an object and then bound
|
|
|
to the capsule (9.a), at least one credential must be offered (9.b), and for
|
|
|
either policy a `now` before the round time of the DateKey fails without a
|
|
|
request (9.c); only then is the release requested. For `time_only` the
|
|
|
credentials play no part: the §64 case "access_policy=time_only with
|
|
|
time_and_key structure" offers a `.dkk` whose `capsule_digest` is that of the
|
|
|
unmutated capsule, and fails at step 12, not at step 9. The codes after the
|
|
|
request, for the release (step 10) and for the identities that open each age
|
|
|
file (steps 11, 13 and 17), are those of spec §63 as well. In this corpus:
|
|
|
|
|
|
- identities are not examined before the release (spec §63 step 13);
|
|
|
- a `release` of `null` is `ERR_RELEASE_UNAVAILABLE` at step 9;
|
|
|
- every `release` is supplied directly, so an invalid one fails at step 10:
|
|
|
the source is not a network source, which would discard it at step 9;
|
|
|
- every `.dkk` offered decodes: the corpus checks step 9.a, not the decoding
|
|
|
of a `.dkk`, whose errors spec §63 also places at step 9.a.
|
|
|
|
|
|
### Point encodings: steps 10 and 11
|
|
|
|
|
|
Ten cases of §64, in each format, test the canonical point encoding of spec
|
|
|
§12.2 and the tlock stanza body `U || V || W` of spec §63 step 11:
|
|
|
|
|
|
- five edit the tlock stanza body of `time_only.dkc`: U with c0 + p, U the
|
|
|
point at infinity (the byte 0xc0, then zeros), U with the infinity flag
|
|
|
over its own coordinate bits, and bodies of 127 and 129 bytes. Each recomputes the header
|
|
|
MAC of OUTER_TIME_AGE with FK_TIME, so the age header stays authentic and
|
|
|
only the rules of step 11 reject the capsule (`ERR_INTEGRITY`); a reader
|
|
|
whose decoder reduces coordinates modulo p opens the first one;
|
|
|
- three serve the release of `time_only.dkc` with its signature edited: the
|
|
|
point at infinity, the infinity flag over its own coordinate bits, and the
|
|
|
negated signature (the sort flag flipped: a canonical point that does not
|
|
|
verify); a fourth serves the published signature of round 1004 re-encoded
|
|
|
as x + p, over a frozen capsule for round 1004, because no fixture round
|
|
|
has a signature whose x + p fits in 381 bits. All four fail at step 10
|
|
|
(`ERR_RELEASE_INVALID`);
|
|
|
- the last one serves the negated signature with U with c0 + p: step 10
|
|
|
comes first.
|
|
|
|
|
|
In format 2 the same ten edit `format2_time_only.dkc` and its release, and the
|
|
|
x + p case has a frozen format 2 capsule for round 1004.
|
|
|
|
|
|
### Format 2: its list of §64
|
|
|
|
|
|
The 23 cases of the list of format 2 of §64 test what format 2 adds:
|
|
|
|
|
|
- the format against the rest of the capsule, with another `VERSION`, a
|
|
|
public byte (§22, §76): a format 1 `time_and_key` capsule of one stanza
|
|
|
relabeled format 2 fails at step 12, and the other relabelings, format 2 as
|
|
|
1 or 3 and format 1 as 2, at step 14, where the schema version of the
|
|
|
control is not the format;
|
|
|
- the 16 stanzas of INNER_ACCESS_AGE: 15, 17, two for one recipient, and an
|
|
|
identity that opens none of them, at steps 12 and 13;
|
|
|
- keys 6 and 7 of CONTROL_CBOR, at step 14;
|
|
|
- the plaintext of PAYLOAD_AGE, at step 17: a padding byte that is not zero,
|
|
|
a plaintext of P − 1 or P + 256 bytes or without its padding, and a control
|
|
|
that declares another code or another L.
|
|
|
|
|
|
Each derives from a fixture without randomness. What it changes is sealed again
|
|
|
with the file keys that the release and the credentials of the fixture open,
|
|
|
FK_TIME and FK_ACCESS, or that its I_PAYLOAD unwraps, FK_PAYLOAD, and with the
|
|
|
nonces of the fixture; a stanza it adds takes its ephemeral scalar from a fixed
|
|
|
seed. So each case is a `base` with edits, and regenerates byte for byte. A
|
|
|
case that changes a `time_and_key` capsule offers the identity of its `.dkk`,
|
|
|
because the `.dkk` itself would fail at step 9.a: its `capsule_digest` no
|
|
|
longer matches. Five further cases, 122 to 126, offer the `.dkk` and show
|
|
|
that.
|
|
|
|
|
|
### Format 3: its list of §64
|
|
|
|
|
|
The 48 cases of the list of format 3 of §64 test what format 3 adds, all at
|
|
|
step 17 but the first:
|
|
|
|
|
|
- `VERSION` 2 on a format 3 capsule, at step 14 (`VERSION` 4 is
|
|
|
"format 3: version changed", as in format 2);
|
|
|
- the frame of BODY and the area (§29.2): each value of `AREA_LEN`,
|
|
|
`SECURITY_LEN` and `HEAD_LEN` that §64 lists, a frame that does not fit in
|
|
|
L, L < 12, a byte of the area that is not zero, all `ERR_INTEGRITY`;
|
|
|
- the head (§29.4 to §29.6): its version and type tag, a byte more within
|
|
|
`HEAD_LEN`, R8 and R1 in layer 3, and in layer 4 the paths, the comment,
|
|
|
the declared author, the layout and the tree, 65536 implicit folders
|
|
|
included;
|
|
|
- the files: the end of the last file that is not C, and a byte of a file
|
|
|
changed, `ERR_INTEGRITY`;
|
|
|
- the precedence of the end of step 17: a path `..` with a padding byte that
|
|
|
is not zero is `ERR_HEAD_INVALID`, but with the next STREAM chunk corrupt,
|
|
|
or with PAYLOAD_AGE cut right after the chunk that holds the head, it is
|
|
|
`ERR_INTEGRITY`;
|
|
|
- four cases of security that open, without a code, with the verdicts X,
|
|
|
F1, F2 and S1: a security map of version 2, a signature of `alg`
|
|
|
4294967295, a signature of `alg` 1 that does not verify and a seal of
|
|
|
`seal_type` 4294967295.
|
|
|
|
|
|
They derive from `format3_single`, and the two that need a head followed by
|
|
|
another chunk from `format3_tree`. What a case changes in BODY is sealed again
|
|
|
with FK_PAYLOAD and the nonce of its fixture, followed by the zeros of the
|
|
|
padding up to the P of its L, and when L changes the control is sealed again
|
|
|
with the new L, as anyone can seal the control of a `time_only` capsule
|
|
|
(spec §36.1). The case of 65536 implicit folders carries a head of 235 KB,
|
|
|
2115 paths, all but the last of 32 segments; it is most of the size of the
|
|
|
file. The three
|
|
|
further cases relabel format 3 as 1, and a `time_and_key` capsule as 2 with
|
|
|
its identity and with its `.dkk`.
|
|
|
|
|
|
### Signature, seal and note: the list of v0.11
|
|
|
|
|
|
Eight cases of the list of v0.11 of §64 change a capsule; the rest of that
|
|
|
list is in `ed25519_strict.json`, `security_cms.json`, `note.json` and
|
|
|
`locator.json`, and in the fixtures `format3_unsigned` and `format3_signed`,
|
|
|
which have the same P. Seven open with their verdicts:
|
|
|
|
|
|
- on `format3_signed`, the signature of `alg` 1 altered (F2), removed (F0),
|
|
|
made again with another key (F4, with the key of that signature), with a
|
|
|
key of 31 bytes or a signature of 65 bytes (F1), and the area widened from
|
|
|
32 KiB to 64 KiB after signing, which leaves the signature valid and
|
|
|
`AUTHOR_MESSAGE` unchanged (F4);
|
|
|
- the signature of `format3_signed` transplanted to `format3_unsigned` (F2).
|
|
|
|
|
|
The eighth changes the public note in the PUBLIC_HEADER of `format3_note`:
|
|
|
`ERR_HEADER_BINDING` at step 15.
|
|
|
|
|
|
## The checks of steps 1 to 8
|
|
|
|
|
|
`mutations.json` and `inspect_differential.json` follow the rules of the
|
|
|
specification for steps 1 to 8, with the `default` registry (Quicknet pinned),
|
|
|
no extension known unless a case lists some, and no secret. The first failure
|
|
|
ends the flow, and within one object the first failing layer decides the code
|
|
|
(spec §69.1):
|
|
|
|
|
|
| Step | Rules | Spec |
|
|
|
|---|---|---|
|
|
|
| 1, 2 | magic, truncated prelude, `VERSION` 1, 2 or 3 (the format), FLAGS and RESERVED, `PUBLIC_HEADER_LEN` in 1 to 1048576 and `SEALED_CONTROL_LEN` in 1 to 67108864 | §22, §23 |
|
|
|
| 3 | the PUBLIC_HEADER bytes are present | §23 |
|
|
|
| 4 | PUBLIC_HEADER: layers 2 to 4, the `public_header` decoder of the schema vectors, then the pinned profile of the DateKey and the critical extensions; with no extension known, every `critical_extensions` array fails | §63, §69.1 |
|
|
|
| 5 | the SEALED_CONTROL bytes are present, then its age header, parsed within `SEALED_CONTROL_LEN` bytes: malformed is `ERR_INTEGRITY`, one tlock stanza is required | §28.1 |
|
|
|
| 6 | the age header of PAYLOAD_AGE, from its offset to the end of the file: malformed is `ERR_INTEGRITY`, one X25519 stanza is required | §22, §28.1 |
|
|
|
| 7 | the round time of the DateKey is at most 9999-12-31T23:59:59Z (Quicknet: round 83903165811 at most); a `dk1_` round above it passes step 4 and fails here | §15 |
|
|
|
| 8 | the tlock stanza has exactly two arguments, the canonical decimal round and the lowercase hex chain hash, compared as strings | §63 step 8 |
|
|
|
|
|
|
The reference parses age headers with `filippo.io/age` v1.3.2, whose parser
|
|
|
limits (1024 stanzas, 128 arguments after the type, 2 MiB) spec §74 lists as
|
|
|
implementation limits. No case of these corpora depends on them.
|
|
|
|
|
|
## `vectors/inspect_differential.json`
|
|
|
|
|
|
A differential corpus of the pre-unlock checks: 5110 deterministic mutations of
|
|
|
fourteen official `.dkc` fixtures, 365 of each, with the verdict of steps 1
|
|
|
to 8 of §63 as the reference computes it (`capsule.Inspect` with the `default`
|
|
|
registry, no extension known, no network, no secret), by the rules that "The
|
|
|
checks of steps 1 to 8" above points to. The first 1825, those of the five
|
|
|
format 1 fixtures, are the corpus of v0.8.2 unchanged; the seven format 2
|
|
|
fixtures follow, and then two of format 3, `format3_single` and
|
|
|
`format3_time_and_key_portable`, one for each policy: steps 1 to 8 see nothing
|
|
|
of format 3 that format 2 does not have, but `VERSION`. The `format` field of the file describes the layout below in
|
|
|
words; it is not a capsule format.
|
|
|
|
|
|
```json
|
|
|
{
|
|
|
"seed": 20260925,
|
|
|
"bases": [ { "file": "time_only.dkc", "sha256": "99e9…" } ],
|
|
|
"mutations": [
|
|
|
{"base":0,"kind":"flip","edits":[[121,1,"b3"]],"result":"ERR_NON_CANONICAL_CBOR","step":4},
|
|
|
{"base":0,"kind":"flip","edits":[[725,1,"4d"]],"result":"ok"}
|
|
|
]
|
|
|
}
|
|
|
```
|
|
|
|
|
|
- `bases`: the fixtures, with the SHA-256 of their exact bytes. `base` in a
|
|
|
mutation is an index into this list.
|
|
|
- `edits`: see "Edited files".
|
|
|
- `result`: `ok` when steps 1 to 8 pass, or the error code; `step` is the step
|
|
|
that failed, absent when `ok`.
|
|
|
- `kind` names the generator and is informative: `flip` (one bit), `byte` (one
|
|
|
byte replaced), `truncate`, `insert` and `delete` (one to four bytes),
|
|
|
`length` (PUBLIC_HEADER_LEN and SEALED_CONTROL_LEN), `header` (PUBLIC_HEADER
|
|
|
re-encoded with one CBOR-aware change: a key removed, added or retyped, the
|
|
|
type tag, version, `capsule_id`, DateKey or `access_policy` changed, an
|
|
|
extension array added, a head not in its shortest form, keys out of order or
|
|
|
repeated, the whole item tagged, wrapped, made indefinite, truncated or
|
|
|
followed by bytes), `datekey` (the DateKey string alone), `age` (the age
|
|
|
header of SEALED_CONTROL or PAYLOAD_AGE edited: intro line, stanza type,
|
|
|
arguments, stanzas added or removed, body lines, MAC line, line endings).
|
|
|
Most `header`, `datekey` and SEALED_CONTROL `age` mutations also rewrite the
|
|
|
prelude lengths to match; some keep the old ones on purpose.
|
|
|
- `seed` seeds the generator of the reference and is informative too: every
|
|
|
mutation is stored explicitly.
|
|
|
|
|
|
## `fixtures/<name>.inspect.json`
|
|
|
|
|
|
For each official `.dkc`, the exact bytes that `datekeys inspect -json -in
|
|
|
<name>.dkc` prints when run in `testdata/fixtures`: JSON indented with two
|
|
|
spaces, fields in this order, and a final newline. The pre-unlock checks use
|
|
|
the `default` registry.
|
|
|
|
|
|
| Field | Content |
|
|
|
|---|---|
|
|
|
| `file` | the `-in` argument, `<name>.dkc` |
|
|
|
| `format` | the capsule format, once step 2 has passed |
|
|
|
| `capsule_id` | hex, once step 4 has decoded the header |
|
|
|
| `datekey`, `profile`, `round` | the canonical `dk1_` string, its profile and round |
|
|
|
| `unlock_at` | the round time of the DateKey, RFC 3339 in UTC, once step 7 passes |
|
|
|
| `access_policy` | `time_only` or `time_and_key` |
|
|
|
| `valid` | true when steps 1 to 8 pass |
|
|
|
| `error` | the code of the failure, absent when valid |
|
|
|
| `checks` | one entry per step run: `step`, `name`, `ok`, `detail` (free text) and, for a failed step, `error` |
|
|
|
|
|
|
`detail` is informative text of the reference; a second implementation compares
|
|
|
at least `step`, `name`, `ok` and `error`, and every other field.
|
|
|
|
|
|
## Existing vectors and fixtures
|
|
|
|
|
|
- `vectors/profile_quicknet.json`: the Quicknet profile fields,
|
|
|
`canonical_cbor` (hex) and `profile_hash`.
|
|
|
- `vectors/quicknet_rounds.json`: `vectors` of `requested` instants (RFC 3339
|
|
|
with nanoseconds) and the resolved `round` and `effective` time, or `error`.
|
|
|
- `vectors/dk1.json`: valid `vectors` with `network`, `round`,
|
|
|
`canonical_json`, `base64url` and `dk1`; invalid ones with `input` and the
|
|
|
`error` code (`accepted` would mean the input decodes).
|
|
|
- `fixtures/<name>.json`: for each `.dkc`, its `format`, its SHA-256, the
|
|
|
release that opens it, the hex of the prelude, PUBLIC_HEADER and
|
|
|
CONTROL_CBOR, the DateKey, `capsule_id`, `header_binding`,
|
|
|
`payload_identity` (I_PAYLOAD, a test secret), the visible stanzas of each
|
|
|
age file, the identities or `.dkk` that open it, `payload_length` (L), the
|
|
|
exact extension data, and the result of every step of §63. In format 2 it
|
|
|
adds `padding` (the code) and `padded_length` (P) and, for `time_and_key`,
|
|
|
`access_key_stanza` and `identity_stanzas`: the index, from 0, in
|
|
|
`inner_stanzas` of the stanza that the `.dkk` and each identity of
|
|
|
`identities`, in order, open. The other stanzas are dummies; the official
|
|
|
vectors are the only place where that is recorded (spec §39). In format 3,
|
|
|
as in format 2, with `payload_length` the length of BODY, it adds
|
|
|
`area_len`, `security_cbor`, `head_cbor`, `salt`, `comment`,
|
|
|
`declared_author`, `head_extensions`, `content_offset`, the offset of the
|
|
|
files in BODY, 12 + `AREA_LEN` + `HEAD_LEN`, `files`, each with its `path`,
|
|
|
`size`, `start`, `end`, `sha256` and `mtime` when it has one, its bytes
|
|
|
being those of BODY from `content_offset + start` to `content_offset +
|
|
|
end`, and `verdicts`, with `signature`, `seal`, the `lines` that show
|
|
|
them and, with F4, the `author_key`.
|
|
|
- `fixtures/<name>.dkk.json`: for each `.dkk`, its SHA-256, `credential_id`,
|
|
|
`capsule_id`, `access_type`, `access_material` (a test secret),
|
|
|
`capsule_digest`, extensions and the capsule it opens.
|