You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
DateKeys/testdata/README.md

65 KiB

DateKeys test data

Official vectors, fixtures and corpora of the DateKeys Protocol Specification v0.14, 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.14", the version this module declares. The branch v0.15 adds what the draft v0.15 defines, the release object and a release in the caller's hand (spec v0.15, §47.1, §63 step 9.c), and keeps that field until the draft is approved: vectors/release.json, the files of releases/, and the field source of mutations.json. What v0.12 changes from v0.11, 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 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 v0.12.

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/release.json the release object, the content of a .dkr file, and drand's JSON, each with the result of step 10; the lookups of a local release archive §47.1, §50, §63 step 10 (v0.15)
releases/<round>.dkr the release object of each published round of the tests: 1000, 1001, 1004 and 2000 §47.1 (v0.15)
releases/archive_1000_1004.bin a local release archive, the informative format of §50, of rounds 1000 to 1004, two of them missing §50 (v0.15)
vectors/tlock_steps.json steps 10 and 11 for Quicknet value by value: the message of a round, its hash to G1, and the decryption of a tlock stanza with H2, H4, H3 and the file key §63 steps 10 and 11 (v0.14)
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/resolved_ip.json the IP address a name of a locator resolves to, NAT64 included, and whether a reader may connect §44.1 (v0.13)
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, each with the kind of its release source §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:

{ "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

{
  "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.

{
  "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/release.json and releases/

The release object of spec v0.15, §47.1: the release of a round as a file, .dkr, that a person keeps next to the capsule. It is deterministic CBOR with the profile of §58, a map of five keys, all required:

0 → "datekeys-release"
1 → 1
2 → chain_hash (32 bytes)
3 → round (1 to 2^53 − 1)
4 → signature (1 to 96 bytes; 48 in Quicknet)

The object of a round above 255 measures 111 bytes. releases/<round>.dkr is the object of each published round the fixtures use, 1000, 1001, 1004 and 2000, with the Quicknet chain hash: the release that opens each fixture, as a file.

release.json has three lists:

  • objects: an encoding in hexadecimal, the round of the DateKey it is checked against, and the result, ok or a code, with the text of the error of the reference. When the encoding decodes, release is what it says: round, signature and chain_hash. The checks are those of step 10 for a release in the caller's hand (spec v0.15, §63): the size of the object, from 1 to 1024 bytes, before anything else; its type and version; its encoding and schema (all three ERR_NON_CANONICAL_CBOR, but a version other than 1, ERR_UNSUPPORTED_VERSION); then, against the pinned profile and the DateKey, the chain hash (ERR_PROFILE_MISMATCH), the round (ERR_ROUND_MISMATCH) and the signature (ERR_RELEASE_INVALID), in that order. A case with several faults gets the code of the first.
  • json: drand's JSON, which a reader accepts too as the input of the caller, never as the release object: an input whose first byte other than a JSON space is {. It has round and signature in hexadecimal, of either case, and may have randomness, which must then be SHA-256 of the signature; it names no chain, so its release has no chain_hash. Any failure to read it is ERR_RELEASE_INVALID, and so is one of more than 8192 bytes; then the round and the signature, as for an object.
  • archive: the lookups of the local archive releases/archive_1000_1004.bin, an informative format (spec v0.15, §50). It is the header, the deterministic CBOR map {0: "datekeys-release-archive", 1: 1, 2: chain_hash, 3: first round, 4: number of rounds}, followed by the 48-byte signature of each round, one after another; a round the archive lacks is 48 zero bytes. This one holds rounds 1000 to 1004, and 1002 and 1003 are zeros. Each lookup gives a round and its result: ok with the encoding of the release object the archive supplies, the .dkr of that round, or ERR_RELEASE_UNAVAILABLE for a round the archive lacks or does not cover.

The texts are those of the reference, for an implementation that wants to match them; the codes are normative.

vectors/tlock_steps.json

Steps 10 and 11 of spec §63 for Quicknet, every intermediate value written out, over the published releases of rounds 1000, 1001, 1004 and 2000 (spec v0.14: the paragraphs "Mensaje de ronda y hash a G1" and "H3 y H4" after the flow).

{
  "spec": "0.14",
  "description": "…",
  "profile": "datekeys:quicknet:v1",
  "scheme": "bls-unchained-g1-rfc9380",
  "chain_hash": "52db…",
  "public_key": "83cf…",
  "dst": "BLS_SIG_BLS12381G1_XMD:SHA-256_SSWU_RO_NUL_",
  "tags": { "h2": "4942452d4832", "h3": "4942452d4833", "h4": "4942452d4834" },
  "vectors": [
    {
      "name": "round 1000, stanza 0",
      "round": 1000,
      "signature": "b446…",
      "message": "f652…",
      "hash_to_g1": "8f5a…",
      "body": "a73e…", "u": "a73e…", "v": "f628…", "w": "b799…",
      "pairing": "13dc…",
      "h2": "…", "sigma": "…", "h4": "…", "file_key": "…",
      "h3_base": "…",
      "h3_tries": [ { "i": 1, "digest": "…", "shifted": "…", "accepted": true } ],
      "r": "20fd…"
    }
  ]
}

Every byte string is hex. The top-level fields are those of the pinned profile (spec §12), the DST of the hash to G1 as text and the tags of H2, H3 and H4 as bytes: the ASCII of IBE-H2, IBE-H3 and IBE-H4.

Step 10, the release:

  • signature: the published signature of round, the release, compressed in G1 (spec §12.2).
  • message: M = SHA-256 of the round as 8 bytes big-endian, the message an unchained drand scheme signs.
  • hash_to_g1: H(M), hash_to_curve of RFC 9380 with the suite BLS12381G1_XMD:SHA-256_SSWU_RO_ and the DST dst, compressed. The signature verifies: e(H(M), public_key) = e(signature, G2), with G2 the generator of G2.

Step 11, one tlock stanza of the round:

  • body: the stanza body U || V || W, 128 bytes, and u, v and w its three parts: U compressed in G2, V and W of 16 bytes.
  • pairing: e(signature, U), 576 bytes in the order of tlock_ibe.json.
  • h2: SHA-256 of IBE-H2 and pairing, truncated to 16 bytes.
  • sigma: V XOR h2.
  • h4: SHA-256 of IBE-H4 and sigma, truncated to 16 bytes.
  • file_key: W XOR h4, FK_TIME, the file key of OUTER_TIME_AGE.
  • h3_base: SHA-256 of IBE-H3, sigma and file_key.
  • h3_tries: the tries of H3, in order. Try i hashes the counter i as 2 bytes little-endian followed by h3_base (digest), then shifts the first byte of the digest one bit to the right (shifted); the try is accepted when shifted, read as a big-endian integer, is below the order r of the groups (spec §12.2). Only the last try is accepted. The shift moves every bit of the first byte; clearing only its top bit gives another r.
  • r: the accepted shifted, the scalar of the check r·G2 = U.

The last vector is the first stanza of round 1000, in the order of the generator, whose H3 needs at least three tries: it accepts its fourth, where clearing the top bit would accept the second. A reader checks every value in this order and the check r·G2 = U.

The generator chooses sigma and file_key per stanza, derives r, U, V and W with its own H2, H3 and H4, and checks the result against the libraries the reference uses: message against DigestBeacon of the drand scheme, hash_to_g1 against the pairing equation with the published signature, and the body against tlock.TimeUnlock, DecryptCCAonG2 of drand/kyber and the tlock identity of agewrap, which give back file_key only if their H2, H3 and H4 are the ones written here.

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.

{
  "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.

{
  "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).

{ "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.

{
  "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 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.

{ "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.

{ "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/resolved_ip.json

The IP address that the name of an https address of a locator resolves to, which a reader checks on every connection (spec §44.1 of v0.13): ip, nat64, the NAT64 prefix of the network that the reader knows, or "" for none, and result, ok, or error with the text of the reference in error.

{ "name": "the well-known prefix with 192.168.1.10", "ip": "64:ff9b::c0a8:10a", "nat64": "", "result": "error", "error": "locator: an https address whose name resolves to 64:ff9b::c0a8:10a, an address of NAT64 that holds 192.168.1.10, an IP address that is not public" }

A public address is accepted. An address of NAT64 (RFC 6052) of 64:ff9b::/96, or of the prefix of the network, counts by the IPv4 address it holds, at the positions of RFC 6052: the cases put a public one and one of several blocks that are not public in each, and the prefix of the network in each length of RFC 6052. A prefix of another length, with bits after its length, outside 64:ff9b::/16 and the public IPv6 addresses, or of IPv4, is refused. Among the addresses that are not public: IPv4-mapped, 6to4, Teredo, link-local, unique local, loopback, and the local-use prefix of RFC 8215 without the prefix of the network.

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.
{ "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.

{ "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.

{
  "name": "version changed",
  "spec": true,
  "dkc": { "base": "time_only.dkc", "edits": [[4, 1, "04"]] },
  "release": { "round": 1000, "signature": "b446…" },
  "source": "supplied",
  "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), 3 further cases (216 to 218), and the 4 cases of the source of the release of v0.15 (219 to 222). 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). chain_hash, present in two cases only, is the chain the release object names when it is not the Quicknet chain.
  • source (v0.15): what kind of source answers, spec v0.15 §63 step 9:
    • supplied: the release is in the caller's hand, as a .dkr would be. The reader is given the release object of release, with the Quicknet chain hash unless chain_hash says another, and decodes and verifies it at step 10: one that breaks a rule of step 10 gets the code of step 10. The clock is not compared with the round time (step 9.c): with a now before it, the capsule opens all the same. Every case but three is supplied.
    • network: a network source, such as a drand relay. It is never asked before the round time (step 9.c), and it verifies its answer with the rules of step 10 and discards it when it fails, so the reader gets no release: ERR_RELEASE_UNAVAILABLE at step 9.
  • now: the reader's clock, RFC 3339. A network source is not asked before the round time of the DateKey; a release in hand is not compared with it.
  • 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, to a network source or to the caller's release in hand. 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.

What v0.15 changed in this file: every case gained source, supplied but for three; the further case "round not reached yet", a valid release of round 1000 and a clock one nanosecond before its round time, was ERR_RELEASE_UNAVAILABLE at step 9 and now opens (ok, step 0, with network true), because the release is in the caller's hand; and four cases were added at the end: the same capsule and clock with a network source, still ERR_RELEASE_UNAVAILABLE at step 9 without any request; a release of another round from a network source, discarded, ERR_RELEASE_UNAVAILABLE at step 9, where the same release in hand is ERR_ROUND_MISMATCH at step 10; and two release objects of another chain, ERR_PROFILE_MISMATCH at step 10, one of them of another round too and with a clock behind. No other case changed its result. 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 to a network source (9.c), while a release in hand is not compared with it; 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;
  • a release of a supplied case is in the caller's hand, so an invalid one fails at step 10, and step 9.c does not apply to it; a network source discards it at step 9, and is not asked before the round time;
  • 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.

{
  "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.

Powered by TurnKey Linux.