The author approved the draft v0.15 on 7 October 2026, after removing the
.dkr file from it: the long-term recovery of capsules rests on the release
object, archives and cache services of all rounds, a release in hand that
the clock does not stop, and the recovery annex. Only the date of the
header changes here. SpecVersion is 0.15, the records of the fixtures and
the vectors say so, and the frozen security_cms.json and locator.json
change only their spec field. spec/README.md records the SHA-256.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The author's decision of 7 October 2026. A release saved next to a capsule
cannot exist when the capsule is made, and once the date comes the capsule
can be opened: such a file only opens it again and does not cover the real
case, someone opening it decades later when drand is gone and nobody saved
anything. Long-term recovery rests instead on archives and cache services
that keep the releases of all rounds; a reader asks for its round and
verifies the signature against the pinned key.
Spec: the .dkr extension (section 20, back to v0.14), sections 1, 4, 8, 45,
47.1, 49, 50 (rewritten), 53, 62.1 (rule 28 removed, rule 26 reworded),
63, 70, 73, 74 (the datekeys.release .dkk extension dropped too), 76
(the v0.15 block, with the discarded design) and the annex 79. The
release object, the chain hash at step 10, step 9.c option B and the
archive format stay.
Code: decrypt -save-release and the command datekeys release are gone,
with writeRelease and their tests; decrypt -release FILE stays. The
release objects of testdata/releases are now <round>.cbor, and
TestVectorFilesAreCurrent fails on a file the generator no longer writes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The release of a round becomes a file, .dkr: a release object in
deterministic CBOR, {0: "datekeys-release", 1: 1, 2: chain_hash, 3: round,
4: signature}, which provider.EncodeRelease writes and DecodeRelease reads
with its layers (size, type and version, schema). provider.ParseRelease
also reads drand's JSON as the input of the caller. Verify checks the chain
hash a release names before its round and its signature, with
ERR_PROFILE_MISMATCH. provider.Archive reads a local release archive, the
informative format of the draft.
capsule.OpenOptions.Release takes a release in hand, a provider.Supplier,
exclusive with Source: Open does not compare it with the clock (step 9.c,
option B) and reports a clock behind it in Opened.ClockBehind; a network
source is still never asked before the round time. The CLI gains
decrypt -release FILE (.dkr, drand's JSON or a local archive),
decrypt -save-release FILE.dkr and the command release, which fetches,
verifies and saves the .dkr without opening the capsule.
Test data: vectors/release.json, releases/<round>.dkr for rounds 1000,
1001, 1004 and 2000, and a local archive of rounds 1000 to 1004. In
mutations.json every case says its source, "supplied" or "network"; the
case "round not reached yet", a release in hand, now opens, and four cases
are added: the same with a network source, a release of another round from
a network source, and two release objects of another chain. SpecVersion
stays 0.14 until the author approves the draft.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The author approved the draft v0.14 on 6 October 2026 with the
recommendation of each of its ten decisions; decision 8, one scheme of
drand, is the previous commit. Only the date of the header changes here.
SpecVersion is 0.14, the records of the fixtures and the vectors say so,
and the frozen security_cms.json and locator.json change only their spec
field. spec/README.md records the SHA-256 of the text.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The draft writes down what the completeness review of 6 October 2026 found
missing, and changes no format and no verdict: what the protocol does not
guarantee, the provider and the states of a profile, signatures and seals
against a quantum adversary, the web client, the entropy of a key of words,
and errata of section 76. Steps 10 and 11 of section 63 now give the root
of trust byte for byte, as the three implementations apply it: the message
a Quicknet round signs, its hash to G1 with its DST, and H2, H3 and H4 of
the tlock IBE. testdata/vectors/tlock_steps.json gives every intermediate
value for four published rounds, checked against drand, kyber and tlock.
SpecVersion stays 0.13 until the author approves the draft.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The author approved the draft v0.13 on 6 October 2026, as it stood: only
the date of its header changes. SpecVersion is 0.13, the records of the
fixtures and the vectors say so, and the frozen security_cms.json and
locator.json change only their spec field. spec/README.md records the
SHA-256 of the text, and the READMEs, SECURITY.md, the traceability table,
testdata/README.md and the changelog name v0.13.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On an IPv6-only network with DNS64, a name that has only IPv4 addresses
resolves to an address of NAT64, which spec v0.12, section 44.1, always
rejected: a reader on such a network, as many mobile ones, could not
download the rest of an envelope. The draft v0.13 counts an address of
64:ff9b::/96, or of the NAT64 prefix of the network, by the IPv4 address it
holds (RFC 6052). An address of NAT64 written in a locator is still
rejected. Section 76 records the change with its case.
locator.CheckResolvedIP implements it for the readers that download, and
testdata/vectors/resolved_ip.json gives 42 cases. SpecVersion stays 0.12
until the author approves the draft.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The author approved the draft v0.12 on 6 October 2026, as it stood: only
the date of its header changes, and no normative text is added. SpecVersion
is 0.12, the records of the fixtures and the vectors say so, and the frozen
security_cms.json and locator.json change only their spec field.
spec/README.md records the SHA-256 of the text, and the READMEs, SECURITY.md,
the traceability table, testdata/README.md and the changelog name v0.12.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Spec section 64 of v0.11 asks for the vectors of the key of words, which
were only in the tests of package wordkey. The file has the words of 45
texts, among them each space of section 38.1 and three that are not; what a
writer does with 20 texts, with the text of the error of wordkey.Check; and
6 identities with their recipients, the vector of section 38.1 first. It is
regenerated by genfixtures and checked by TestVectorFilesAreCurrent.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The text of 44.1 now says what the reference already refused and a
second implementation that followed the text would have accepted:
segments of 1 to 63 characters that neither start nor end with a hyphen,
the scheme in lower case, 0x and the local names in either case, an IPv4
without leading zeros, and a CID of at most 128 characters in canonical
base32, with minimal varints and a digest of at least one byte. A port is
written without leading zeros: the reference accepted 0443 and 00443 and
refused 000443, the same number, and now refuses the three. Change 6 of
section 76 records it, and section 64 lists the cases.
locator.json is made again with 30 more addresses, 247 in all, and
TestAddresses checks the same rules.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- testdata/README.md: the files of v0.11 and of the draft, the spec
label 0.11 until the approval, 26 fixtures with format3_unsigned and
format3_note, security.json in its context with lines, security_cms.json
without the cases of no context, note.json, the new parts of
locator.json, and the corpus of 218 cases, 178 of the spec, with the
eight of the list of v0.11. The release of round 1000 is in the records
and in mutations.json, not in quicknet_rounds.json.
- docs/traceability.md: the cases of section 64 that security_cms.json
and locator.json now hold are no longer pending.
- CHANGELOG: security_cms.json and locator.json under the test data of
the draft, instead of pending.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- uri_cases: 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 local names; characters outside
RFC 3986 and broken percent signs; "." and ".." segments; base32 that
is not a CID v1.
- mixed: a locator whose http and NAT64 addresses a reader rejects, and
whose third address it uses to find the rest.
- rest_cases: the rest alone, a host with bytes after the rest, a rest
with a byte changed, an offset that is not its own, a rest cut short.
- extension_cases: a locator sealed for round 1001 with a DateKey of
round 1000, and the other data that a reader cannot use.
- plaintext_cases: change 7, the bases 4094, 4070 and 3837 completed to
4096 with an empty key 6 or a length not in its shortest form, and a
defect in each field of the map.
- padding_cases: the bases 3837, 4070, 4094 and 4095 and those of the
next multiple, checked against the rule of 44.1.
The generator checks every case against this module and moves to its
own file. The vector is frozen: delete it to make it again.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
security_cms.json says 0.11, as every file of testdata, until SpecVersion
moves with the approval of the draft v0.12 whose verdicts it gives; its test
checks SpecVersion. scripts/fuzz.sh runs FuzzDERCheck, FuzzParseSignature,
FuzzParseToken and FuzzParseCert.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The frozen vector of alg 2 and seal_type 2, made again with the profile of
v0.12:
- each case carries the lines of Verdicts.Lines, so that a second
implementation compares the texts byte for byte, and its times keep the
fraction of the token;
- the two cases without a context, which gave the verdicts of a reader of
v0.10, are gone, and the case named a seal from before the certificate
was valid, which gave an invalid seal, is named so;
- new cases for each row of §29.7 and each item of the lists of §64 for
v0.11 and v0.12: out of validity with a valid authority, SIGNERS that
break its rule beside a valid CMS (out of order, empty, 17 entries, 31
bytes, a hash twice) and 16 signers, the version against the sid, two
content-type attributes, the ESSCertIDv2, PSS with and without
trailerField, an arc of 2^31, a certificate twice or of version 1, keys
outside the table, every hash and curve of the table, BER, two
SignerInfo of one certificate, two time-stamps, the names of the holder
and of the issuer in each string type and against each rule, and the
edges of the token: accuracy, genTime, ordering, fields after the last,
the imprint, crls and the authority.
The generator checks each case against what the spec gives, written apart
from the code: the verdicts, the result of each signer and the lines,
built from the texts of §29.7. It fails when the reader gives anything
else. capsule reads every field of the file, the lines included.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The review found no vector of the public note in testdata (D3). note.json
gives the data of datekeys.note and what the rules of 24.1 make of it, the
result and the exact text of the rule it breaks: notes that pass, from one
byte to 1024, with letters that are not ASCII and an emoji with VS16; and
notes that a writer refuses and a reader does not show, empty, of 1025
bytes, with a tab, a line feed, a space at an end, a bidi control, an
ignorable, a byte order mark, bytes that are not UTF-8, the UTF-8 of a lone
surrogate and a noncharacter.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The review found that the cases that spec 64 lists for the signature of
alg 1, the area and the public note were tested in Go and not exported to
testdata, where a second implementation reads them (D3). mutations.json
adds eight, from format3_signed, format3_unsigned and a new format3_note:
- the signature altered (F2), removed (F0), made again with another key
(F4 of that key) and transplanted to another capsule (F2);
- a key of 31 bytes and a signature of 65 (F1);
- the area widened to 64 KiB after signing (F4, the same AUTHOR_MESSAGE);
- the public note changed in PUBLIC_HEADER (ERR_HEADER_BINDING, step 15).
The frozen cases of the corpus do not change. The records of the fixtures
give the extensions of the header as it is written, the note included.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The review found that security.json said spec 0.11 and still gave the
verdicts of a reader of v0.10, evaluated without context, contrary to what
section 76 announced (D1).
- security.json carries the context of a capsule, its commitments and the
time of its round, and each case its verdicts and the lines of the
official SDK in it: a signature of alg 1 that does not verify is F2, a
token of seal_type 2 that is not DER is S2, and new cases give a valid
signature of alg 1 (F4) and alg and seal_type 4294967295 (F1, S1).
- mutations.json: the signature of alg 1 that does not verify (F2) is a
case of 64, and the seal that opens with S1 uses seal_type 4294967295,
not seal_type 1, which a later version may define.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The review found that genfixtures -force rewrote the five fixtures of
format 3 of v0.10 with the area of 32 KiB and then failed, leaving testdata
half done. EncryptFiles takes TestAreaLen, only with TestVectors, as Encrypt
takes TestVectors for format 2 (spec 62.1 rules 1 and 13), and the
generator gives those fixtures their area of 512 bytes: -force now
regenerates them with the same L and P.
- format3_seal_unsupported uses seal_type 4294967295, reserved for tests,
as spec 67 says, instead of seal_type 1, which a later version may
define; capsule.AlgTest and SealTypeTest name the two values.
- format3_unsigned: the capsule of format3_signed without its signature,
with the area of 32 KiB: the same P (spec 64).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The draft v0.12 fixes what the review of the implementation of v0.11
found, without changing any format: the names of certificates in the
verdicts, the seal of each signer in the lines of F6 with the warning that
nobody checks who issued it, the holder by givenName and surname before
the commonName that carries the NIF, a profile of the certificate field by
field, identifiers by their bytes, repeated elements of a SET OF, the
edge cases of the token, the addresses and the padding of the locator, and
the errata of 44.1, 55.2, 64, 67 and 76. Section 76 lists each change with
its case. The CDDL fixes the sizes of the locator.
The reader shows the names of certificates between quotes, refuses one of
more than 64 code points or with two spaces in a row, names the authority
of each seal of F6 and adds the warning when a line says before the date,
and writes the result of a foreign signer in Spanish. The records of
format3_signed_cms and format3_sealed follow.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The author approved the text and the six open decisions on 1 October 2026.
The spec says now what the review left open: the form of the CMS signature
and of the TSTInfo field by field, the ESSCertIDv2 with SHA-256 written, the
padding of the locator at its boundaries, base32 CIDs, the addresses read
without decoding, the issuer shown by the rules of the holder, and the area
decided after the signatures. SpecVersion is 0.11, the records of fixtures
and vectors say so, and decrypt shows an mtime later than a valid seal as an
inconsistency, which 29.7 asks as a SHOULD.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Fixtures format3_signed_cms (alg 2, two certificates, each sealed, F6) and
format3_sealed (alg 1 and a seal of seal_type 2, F4 and S4), with the
certificates, SIGNERS, the commitments, SEAL_SUBJECT, the token and the
result of each signer in their records. vectors/security_cms.json has 22
frozen areas with their context and verdicts (F1, F2, F5, F6, S1 to S5), and
vectors/locator.json the extension datekeys.capsule with its envelope hidden
in a host, its locator sealed with tlock, the padding at the boundaries and
the rules of the addresses. The README of testdata describes all of it.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
format3_signed is written by EncryptFiles with a test key. Its record gives
the seed of the key, control_commit, head_digest, signers_digest,
AUTHOR_MESSAGE with its code, the signature and the content of key 2, and
verdicts carries the dkauthor1 key. The conformance test recomputes all of
it from the control, the head and the security area, and signs again from
the seed. A second test changes the context, a bit of the signature or of
the key, and the key itself, and removes the signature.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
AreaLen is 32 KiB, and LargeArea asks for 64 KiB. EncryptOptions.AuthorKey
signs inside sealer.write, through a prepare hook that gets the final
control: SECURITY_CBOR and the frame are built and evaluated with the rules
of the reader before anything is written. OpenOptions.AuthorKeys feeds
EvaluateSecurityIn from openBody with control_commit, head_digest and the
round time: F4, F3 with a saved key, F2 when it does not verify.
The fixtures of v0.10 keep the area of 512 (AreaUnit). The two
"unsupported" fixtures use alg 4294967295, since a random alg 1 is now F2.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
internal/ed25519strict verifies the author signature of alg 1 with the
strict profile of the spec v0.11 draft (29.9): A canonical and not of
small order, checked with an encoding check and the table of the eight
points of small order, then crypto/ed25519 for S, R and the equation
without the cofactor. No arithmetic on points and no new module.
testdata/vectors/ed25519_strict.json has 18 signatures after the cases of
«Taming the many EdDSAs», built by testkit with arithmetic on the curve in
math/big, only for the vectors, which also checks the table of small
order. crypto/ed25519 accepts 11 of them that the profile rejects: the
eight points of small order and the non-canonical keys as A.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The key of words lowers each code point with the simple mapping of
UnicodeData.txt, field 13, of Unicode 18.0.0 (spec v0.11, 38.1), not
with unicode.ToLower, which carries Unicode 15.0.0: U+A7CB lowers to
U+0264 in Unicode 16.0 and later, and a key made in the page, whose
platform knows it, would not open in the CLI.
The generator writes the table for Go and for datekeys-ts, and the
canonical text gains its lines, so TablesDigest changes; paths.json and
path_fold.json record the new one.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- README and README.es: specification 0.10, format 3 written and formats
1 to 3 read; BODY in the picture of a capsule; the CLI with folders,
-comment, -author, -no-mtime and decrypt into a new folder, with the
presentation of 29.7; EncryptFiles, Source and Sink in the library; the
metadata that format 3 hides, safe extraction, and the declared author
and comment that prove nothing; the fixtures, vectors and mutations of
format 3; 18 normative errors.
- CHANGELOG: the section of specification v0.10.
- docs/traceability.md: rows 29.2 to 29.7 and 29.5.1, and the rows that
format 3 changes (22, 23, 28, 29, 29.1, 31, 33, 39, 56, 57, 61 to 64,
67 to 70, 75 and 76); ERR_HEAD_INVALID in the error mapping; two
implementation decisions, the Sink and the width of the terminal.
- testdata/README.md: the nine fixtures of format 3 and their records,
the four new vector files, the mutation corpus of 209 cases with its
list of format 3 and the verdicts of the cases that open, and the two
bases of format 3 of the differential corpus.
- SECURITY.md: specification v0.10, and the head as untrusted input.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The mutation corpus of spec 64 gains format 3: 209 cases, 169 of the
spec.
- The 33 mutations of the first two lists on format3_single and
format3_time_and_key_portable, named "format 3: ...", with a sibling
written by EncryptFiles and built capsules that hold a BODY.
- The list of format 3, 47 cases: one for each value of a line with
several (AREA_LEN 0, 511, 513 and 66048; SECURITY_LEN 0 and 513;
HEAD_LEN 0 and 2^24 + 1), and the three that open without a code,
with the verdicts X, F1 and S1. VERSION 4 is "format 3: version
changed", as in format 2. Each seals BODY again with FK_PAYLOAD and
the nonce of its fixture, and the control with the new L when L
changes; the two that need a head followed by another STREAM chunk
derive from format3_tree, whose comment takes the bytes the path
loses so that only the head and its chunk change.
- Further cases: format 3 relabeled 1, and time_and_key relabeled 2
with the identity and with the .dkk.
- A mutation may expect the capsule to open with its verdicts; the
exported case records them, with the result ok at step 0.
- Splice gives an edit for each run of changed bytes, runs closer than
16 bytes merged, and one more for what one side has beyond the
other: a head sealed again changes its bytes and the tag of its
chunk, 64 KiB apart. Earlier cases are written with more edits and
give the same capsules. The corpus is 706 KB, 476 KB of them the
capsule of 65536 implicit folders, whose head is 235 KB.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The vector files of spec 67 for format 3, generated with the result
each case is written for, so that the generator fails when the
implementation or the tables change:
- paths.json: 83 paths with the result of the rules of one entry, the
violation worded as every implementation must word it, and 16 trees,
the paths of a head and the result of decoding it: U+00A0, accepted,
and U+3000, R6c, at both ends of a segment; best-fit, full-width
forms, U+00B4 of cp1253; 8.3 aliases with ~1; Cn; U+206A to U+206F,
tags and other ignorables; a dot and ZWJ, and ZWJ alone; 127 and 85
times U+0390; U+F03A; .datekeys-x at two levels; 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 and the flag of Scotland; b/.. and a.
- path_fold.json: 22 segments with their NFD and their key of R7,
among them the entries F of CaseFolding, the dotless i, the Kelvin
and Angstrom signs, Cherokee, Hangul and the whitelist dropped
before NFD.
- head_schema.json: 63 heads through layers 2, 3 and 4, in key order,
with the violation of each ERR_HEAD_INVALID; they add comments with
tags and with loose variation selectors.
- security.json: 21 areas with their verdicts, X, F0, F1, S0, S1 and
S2, among them key 2 that is not a byte string, a key 4, a byte more,
alg 0, an empty key with the seal intact, and a seal that breaks its
schema with an unknown seal_type.
- cbor.json gains the control of schema version 3.
A test replays every committed vector through the implementation, and
another checks that the files are current.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The fixtures of spec 67 for format 3, each with its record, its BODY,
its inspect output and, for time_and_key, its .dkk:
- format3_single, format3_tree (five files in three folders, one over
two STREAM chunks, one without mtime, a comment and a declared
author), format3_comment_only (no files; a TAB in the comment),
format3_bloque256 and format3_time_and_key_portable, written with
EncryptFiles;
- format3_area_1024, format3_security_v2 (verdict X),
format3_signature_unsupported (an author-signature of alg 1 with a
random key of 32 bytes and a random signature of 64: F1) and
format3_seal_unsupported (that and a seal of seal_type 1: F1 and S1),
which only a generator of test vectors writes (62.1 rule 13), built
with testkit.Build.
The record of a format 3 fixture adds the area, SECURITY_CBOR,
HEAD_CBOR, the salt, the comment, the declared author, the head
extensions, the offset of CONTENT in BODY, each file with its layout,
SHA-256 and mtime, and the verdicts with their lines; its plaintext
file is BODY. The generator writes, then recovers every value by
opening layer by layer for the three formats alike, and refreshes the
records of format 3 through a Sink.
Tests: the conformance test checks BODY, the head, security and every
file, and opens through a MemorySink; the .dkk tests take the .dkk of
formats 2 and 3 too (spec 68); the CLI decrypts five of the fixtures
into folders; the control fuzz target decodes with the three schema
versions. The differential corpus gains two bases, format3_single and
format3_time_and_key_portable, one per policy: 5110 cases, the earlier
ones unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- SpecVersion is 0.10: the version command, the catalogue test and the
spec field of every test data file name spec v0.10. The regenerated
test data change in that field only.
- All lists ERR_HEAD_INVALID, last, as section 69 of the spec does.
- The tests of the path rules and of format 3 held literal invisible
and combining characters (ZWJ, VS16, U+202E, soft hyphen, the Kelvin
sign and others), which an editor could normalize or hide; they are
Go escapes now, with the same values.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Open reads format 3 (spec 29.2 to 29.7, 63 steps 17 and 18): the
PRELUDE accepts VERSION 3, and the files go to a Sink.
- Sink: Begin with the validated head, Create for each file in the
order of the head, and Commit only after every check of step 17;
after any failure that follows a successful Begin, Abort, once. A
format 3 capsule without a Sink fails right after step 2 with
ErrSinkRequired, a caller error with no code, no failed step and no
request; a capsule of format 1 or 2 without dst fails there too.
- Step 17 in its substeps: the frame and the area, security and its
verdicts, which never fail, the head, the files filling CONTENT, the
SHA-256 of each file and the padding. A failure of age or a
plaintext whose length is not P prevails; otherwise the first
substep that fails decides, and a code other than ERR_INTEGRITY is
reported only after reading PAYLOAD_AGE to its end.
- The reads of BODY grow with the bytes received, never with AREA_LEN,
HEAD_LEN or a declared size (spec 57); a test measures it.
- A failure of the Sink is the caller's own error with ERR_INTEGRITY,
as one of dst is in formats 1 and 2.
- Opened gains Head, Verdicts, AreaLen and UnusableHeadExtensions.
- Test data: "version changed" sets VERSION 4, and the format 2 list
gains "format 2 time_only relabeled format 3", which fails at step
14, as section 64 of spec v0.10 lists: 126 cases, 89 of the spec.
The randomly built capsules keep their recorded bytes.
- testkit: Build writes format 3 and can edit the padded plaintext;
Head3, Body3, DiscardSink and MemorySink build and open BODY.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The reference moves to the DateKeys Protocol Specification v0.9, approved
by its author on 29 September 2026. Encrypt writes capsule format 2 only;
Open and Inspect read formats 1 and 2, and a format 1 capsule keeps the
verdict v0.8.2 gave it.
Format 2 (spec §22, §29.1, §31, §39):
- VERSION in the PRELUDE is the capsule format, capsule.Format; any other
value is ERR_UNSUPPORTED_VERSION at step 2.
- CONTROL_CBOR has the schema version of its format. Version 2 adds key 6,
payload_length (8 bytes, big-endian, at most L_MAX = 2^53 - 2^46), and
key 7, padding (1 bloque256, 2 reforzado); it is 103 bytes without
extensions, whatever L.
- The payload is the content padded with zeros to P = rule(L). Step 17
checks the length and the zeros, and Open writes only the first L bytes.
- INNER_ACCESS_AGE holds exactly 16 X25519 stanzas: 1 to 16 credentials,
and a dummy in each slot left, in a uniformly random order.
Writer rules (spec §62.1): EncryptOptions.Length is required and the
source must deliver exactly that many bytes; recipients that are not
canonical or of low order are rejected (agewrap.CheckX25519Recipient);
self-checks of the header, the control, INNER_ACCESS_AGE and PAYLOAD_AGE.
The CLI measures its input, takes -padding and reports the format.
Test data: seven format 2 fixtures, padding vectors checked against
math/big, format 2 CBOR vectors, and the mutation corpus in both formats
with the 22 cases of the third list of spec §64, built without randomness
by sealing the fixtures again with their known keys and nonces. The
format 1 fixtures are kept byte for byte and never regenerated; the
differential corpus keeps its 1825 cases and adds a block per format 2
fixture. The spec copy loses its "to be implemented" markers, and the
READMEs, CHANGELOG, traceability and testdata/README.md follow v0.9.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The second round of the formal review confirmed the nine corrections of
c57ed48 and asked for these, recorded in §76 as corrections 4 to 6 and
an editorial note:
- §72: an encoder MUST NOT write a registered extension in an object or
array it is not registered for; §54: a reader MUST NOT interpret the
data of a noncritical one it ignores for that reason. capsule.Encrypt
and accesskey.Encode take no Registry, so the application applies the
rule; their documentation and extension.Placement say so.
- §17 and §51 give the step-10 codes only for a directly supplied
release, as step 10 does; a network source discards a failing one at
step 9.
- Step 9 reports ERR_RELEASE_UNAVAILABLE and no other code, whatever the
failure of the source. provider/drand.Client keeps each relay's failure
as text only (errors.Join made a relay's ERR_ROUND_MISMATCH match with
errors.Is), and capsule.Open keeps only the text of a source error that
carries another code (a caller's source failing with
ERR_RELEASE_INVALID gave that code at step 9). A context that ended
stays detectable: Fetch now has a single failure path, so the canceled
and deadline cases are deterministic.
- TestExtensionPlacement covers the noncritical array of a .dkk: with
the object-blind extension.CheckNoncritical at step 9.a it fails.
- Editorial: §28.1 "analizan solo la cabecera age", one arrow at step 9,
two §76 introductions; §73 lines for release sources and placement.
- testdata/README.md says the corpus registers its extensions in both
arrays of every object; traceability, CHANGELOG and both READMEs
(integrity holds against whoever lacks the file keys, §27, §55.1)
follow. Spec dated 28 September 2026; new SHA-256 in spec/README.md.
No fixture or vector changes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A formal review of the whole v0.8.2 text found it approvable after
these corrections, recorded in §76 ("Correcciones de la revisión
formal"):
- §27 no longer calls header_binding the authenticity of PUBLIC_HEADER:
it binds the header to the opened control, never authorship or date
(§55.1); the age MAC only protects against whoever lacks the file key.
- §63 steps 9 and 10: a network source (relay, Release API, cache) MUST
verify every response and gives ERR_RELEASE_UNAVAILABLE at step 9 when
none verifies; the step-10 codes are for a directly supplied release.
The reference already behaved so; TestReleaseFromANetworkSource pins
both paths.
- §54 and §72: registrations declare the objects and arrays where an
extension may appear, and a known extension out of place counts as
unknown there. The reference gains the optional extension.Placement
interface, used at steps 4, 9.a and 14.
- §63 step 11 fixes the GT serialization hashed by H2 (kilic/kyber order)
with the frozen vector H2(e(G1, G2))[:16] = cb87319f..., shared as
testdata/vectors/tlock_ibe.json; H2-H4 are cited to drand/kyber.
- Step 5 makes the SEALED_CONTROL read mandatory, step 15 names
ERR_HEADER_BINDING, §21 makes capsule_id 16 CSPRNG bytes a MUST, §76
is made accurate (four dk1.json vectors, the §36 time_only rule, two
cases rewritten against the texts that really existed), and editorial
fixes in §5, §36, §55.1, §69.1 and §77. §73 lists the three new
decisions.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Amendment of the unreleased v0.8.2, recorded in §76 with its case: the
second implementation's phase-2 research found that tlock-js over
@noble/curves 1.9.7 accepts U re-encoded as c0 + p and a signature
x + p and returns the same file key, while the reference rejects both
(noble 1.9.7 differed from kilic on 5,615 of 41,686 encodings), and the
spec did not say which encodings are valid.
- §12.2 defines the canonical encoding of a BLS12-381 point (drand's
compressed ZCash form) and requires decoders to reject every other
byte string; §12.1 applies it to public_key.
- §63 step 10 applies it to the release signature (ERR_RELEASE_INVALID)
and step 11 defines the tlock stanza body U || V || W (96 + 16 + 16
bytes for Quicknet) with a canonical, non-infinity U (ERR_INTEGRITY).
- §64 gains ten mutations, exported to mutations.json (65 cases). The
signature x + p case uses published Quicknet round 1004, the first
after 1000 whose x allows x + p < 2^381. The reference already gave
every stated code and step.
Errors no longer copy text from tlock, kyber, age, drand or
kyber-bls12381. kyber's IBE error carried the candidate plaintext and r,
and with one bit of W flipped the message disclosed the real tlock file
key with that bit flipped. Every such place now uses a fixed reason with
its normative sentinel; TestTlockFailureDiagnosticsCarryNoSecrets fails
with the old wrapping.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
encoding/json replaces invalid UTF-8 with U+FFFD inside strings, so a
member that a repeated name overwrites passed steps 2 and 3 and ended as
ERR_DATEKEY_NON_CANONICAL at step 6, while §19 makes invalid UTF-8 fail
step 2 with ERR_DATEKEY_INVALID. parseJSON now checks utf8.Valid first.
Found by the differential of the TypeScript implementation; pinned by
TestReadingRules (which fails without the fix) and a new dk1.json vector.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Approved refinements, each recorded with its reproducible case in the
§76 v0.8.2 subsection:
- §69.1: layered error model with normative precedence (frame, type tag
and version, CBOR profile and CDDL, then fields with their own code in
ascending key order; across steps the §63 order decides), with a scope
paragraph for the optional steps 5, 6 and 8.
- §55.1: normative trust table per section (who can write it, from which
step it is bound, what it never proves); §72: security-relevant claims
go in CONTROL_CBOR or under a signature, .dkk data is advisory.
- §31/§54: extension arrays in strictly ascending unsigned byte order of
extension_id (one rule for order and uniqueness).
- Gaps a second implementation needed: §28.1 malformed age headers,
§15/§19 latest unlock time and dk1_ reading rules, §22/§23/§57 length
lower bounds, §63 step 8 tlock argument comparison and step 9 order,
§12.1 profile validation with the drand chain-hash formula, §74 table
of implementation limits.
Reference alignment: .dkk errors only at step 9.a (new
OpenOptions.AccessKeyFile, used by the CLI), CR/LF in dk1_ is
ERR_DATEKEY_INVALID, BODY_LEN 0 is ERR_INTEGRITY, nil identities are not
credentials, and AccessIdentity tries every identity on every stanza so
its verdict does not depend on their order. dk1.json gains three
vectors; every other testdata file is byte-identical.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Step 2b: package codec is rewritten without reflection or struct tags. A
strict Decoder accepts only the spec §58 profile, Unmarshal decodes,
re-encodes and compares, Peek reads the type tag and version, and Walk is a
bounded iterative helper for vectors and fuzzing. Every schema has its own
hand-written encoder and decoder that checks all CDDL rules before the
fields with their own error codes. github.com/fxamacker/cbor/v2 and
github.com/x448/float16 are gone; nothing replaces them. Valid objects
encode and decode exactly as before (1.34 million differential verdicts);
the invalid-input differences are documented in CHANGELOG and
traceability decision 12. A review found and fixed an access_policy check
that truncated to uint8.
Step 3: testdata gains vectors/cbor.json (generic and per-schema CBOR
vectors), vectors/mutations.json (the 55-case mutation corpus, replayable
offline), vectors/inspect_differential.json (1,825 fixed-seed mutations
with the Go verdict) and one inspect -json golden per fixture, all
regenerated byte-identically by genfixtures and documented in
testdata/README.md for second implementations.
Gate green with 90 s of fuzzing per target on all 15 targets; codec at
100 % coverage; pre-existing testdata byte-identical.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- extension: data is an opaque []byte; New takes []byte and rejects empty
data; key 2 must be a non-empty shortest-form bstr; at most 64 extensions
per array; extension_version <= 2^32-1; CheckDisjoint is a linear merge;
optional DataValidator with ERR_EXTENSION_DATA_INVALID for known critical
extensions and Unusable reports for known noncritical ones.
- capsule, accesskey: frame limits on encode and decode with ERR_INTEGRITY;
Encrypt and MarshalBody decode their own output before sealing or
returning it; unusable extensions are reported.
- profile: period and genesis_time bounded to 2^53-1, genesis decoded as
unsigned.
- codec: Valid removed; empty values never encode as null.
- Regression tests for the nested-data seal/open asymmetry, the
nondeterministic verdict on NaN-keyed data and the quadratic disjointness
check; three new §64 mutations and five more.
- Fixtures: time_only_extensions regenerated with opaque data, new
time_and_key_portable_extension.dkk; genfixtures gains -only.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reference implementation in Go, built from the implementation plan
(milestones M0 to M5): datekey, profile, provider, codec, agewrap,
extension, capsule, accesskey, the datekeys CLI, official vectors and
fixtures, the mutation corpus, fuzz targets, interop and live tests,
CI workflows, traceability and policy documents.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>