|
|
# DateKeys test data
|
|
|
|
|
|
Official vectors, fixtures and corpora of the DateKeys Protocol Specification
|
|
|
v0.10, 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). 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 and v0.9 carry `"spec": "0.10"` 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.
|
|
|
|
|
|
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 and their verdicts | §29.3, §29.7 |
|
|
|
| `vectors/ed25519_strict.json` | Ed25519 signatures and the result of the strict profile of the author signature | v0.11 §29.9 |
|
|
|
| `vectors/mutations.json` | the mutation corpus: the 169 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-one 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.
|
|
|
|
|
|
Twelve 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_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;
|
|
|
a writer of v0.11 writes the area of 32768 bytes, as in the last three, which
|
|
|
`capsule.EncryptFiles` writes with a signer and a sealer. 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, an `alg` that no version
|
|
|
defines, with a random key of 32 bytes and a random signature of 64, and that
|
|
|
with a seal of `seal_type` 1 and a random token of 32 bytes. None of them has
|
|
|
a verdict that stops the opening.
|
|
|
|
|
|
The last three are signed and sealed with test keys (spec v0.11, §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…`; 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.10",
|
|
|
"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.10",
|
|
|
"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.10",
|
|
|
"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,
|
|
|
and their verdicts (spec §29.3, §29.7), which never stop the opening. This
|
|
|
version implements no `alg` and no `seal_type`, so a reader of it reaches X,
|
|
|
F0, F1, S0, S1 and S2 only.
|
|
|
|
|
|
```json
|
|
|
{ "name": "a signature of alg 0, and the seal intact", "hex": "a4006f…", "signature": "F1", "seal": "S1" }
|
|
|
```
|
|
|
|
|
|
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: `alg` 0, an empty key or content that is not CBOR
|
|
|
give F1 with the seal intact, and a seal that breaks its schema gives S2 even
|
|
|
with an unknown `seal_type`, which is read only from a seal that meets it.
|
|
|
|
|
|
## `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, each with
|
|
|
the context of its capsule and the verdicts of spec v0.11 §29.7, §29.10 and
|
|
|
§29.11. They complete `security.json`, whose areas have no valid signature or
|
|
|
seal. 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. Delete the file to make it again.
|
|
|
|
|
|
```json
|
|
|
{ "name": "alg 2: a required signer is absent", "security_cbor": "a4…",
|
|
|
"context": { "control_commit": "…", "head_digest": "…", "round_time": "2030-01-01T00:00:00Z" },
|
|
|
"signature": "F5", "seal": "S0", "signers": [ { "holder": "Ana López", "result": "valid", … }, { "holder": "<sha256>", "result": "absent", … } ] }
|
|
|
```
|
|
|
|
|
|
`context` is what a verdict needs besides `SECURITY_CBOR`; with `no_context`
|
|
|
the area is read as a reader of v0.10 does, without a capsule, and any
|
|
|
signature is F1 and any seal S1. `signers` are the results of the required
|
|
|
signers in the order of SIGNERS, and `foreign_signers` those that are not
|
|
|
required and never count. A valid seal gives `seal_holder` and `seal_time`.
|
|
|
The cases cover F6 with two signers, with a seal after the round time and
|
|
|
with a signer who is not required; F5 for an absent signer, no seal, a seal
|
|
|
from before the certificate was valid and a key 3 beside the signature; F2 in
|
|
|
the context of another head; F1 for SIGNERS out of order or empty, a signature
|
|
|
that is not a CMS, and the lack of a context; and, over an `alg` 1 signature,
|
|
|
the seals S4, S5 (also when the accuracy reaches the round time), S3 (another
|
|
|
subject, an authority expired at its time), S2 (a TSTInfo of version 2, not
|
|
|
DER), S1 (a SHA-384 imprint) and a seal over a capsule without a signature.
|
|
|
|
|
|
## `vectors/locator.json`
|
|
|
|
|
|
The extension `datekeys.capsule` of a `.dkk` and what it points to (spec
|
|
|
v0.11, §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 (`quicknet_rounds.json`),
|
|
|
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 locator hold randomness.
|
|
|
|
|
|
`padding_cases` give the length of the plaintext of the locator for the length
|
|
|
of its CBOR without the padding of key 6: the least multiple of 4096 that key 6
|
|
|
can fill exactly, which skips a multiple where the CBOR length of key 6 jumps
|
|
|
(a base of 4070 gives 8192). `uri_cases` give the verdict of the rules of §44.1
|
|
|
on an address: the scheme `https` or `ipfs`, the raw ASCII authority with no
|
|
|
percent sign or userinfo, a host of letters, digits and hyphens or a public IP
|
|
|
literal, and a port from 1 to 65535.
|
|
|
|
|
|
## `vectors/ed25519_strict.json`
|
|
|
|
|
|
Ed25519 signatures, in hexadecimal, and whether the strict profile of the
|
|
|
author signature accepts them (spec v0.11, §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 169 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 47 of the list of format 3 (160 to 206),
|
|
|
and 3 further cases (207 to 209). 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 three cases of security of the list of
|
|
|
format 3.
|
|
|
|
|
|
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 47 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`;
|
|
|
- three cases of security that open, without a code, with the verdicts X,
|
|
|
F1 and S1.
|
|
|
|
|
|
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`.
|
|
|
|
|
|
## 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` and the `lines` that show
|
|
|
them.
|
|
|
- `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.
|