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>
The passphrase of a key comes from a file, or from the standard input with
"-", never from the command line or the environment, so the CLI needs no
terminal library. decrypt -expect-author shows the signature as always and
then fails, with the files already written, unless it is F3 with that key.
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>
capsule/signature.go has what an author signs and a seal seals, as the
spec v0.11 draft defines it (29.8, 29.11): payload_commit, control_commit
over CONTROL_SIG, without I_PAYLOAD and with L at zero, head_digest,
signers_digest, AUTHOR_MESSAGE as ASCII text of 99 bytes with its code,
SIG_PART over the exact content of key 2, and SEAL_SUBJECT.
EvaluateSecurityIn checks a signature of alg 1 with the strict profile in
the context of a capsule: F4, or F3 with a key the person saved; F2 when
it does not verify; F1 without context, as in v0.10, and for alg 2, not
yet implemented. Verdicts carries the key and the label for the texts.
Not wired yet: the writer does not sign and still writes the area of 512
bytes, and the reader still calls EvaluateSecurity without context. The
handoff of docs lists what is left.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Package authorkey writes and reads the keys of the author signature of
alg 1 (spec v0.11 draft, 29.12): an Ed25519 seed as
DKAUTHOR-SECRET-KEY-1..., 79 characters in upper case, and its public key
as dkauthor1..., 67 in lower case, with the Bech32 of the module. A
public key in another case, of another length or prefix, or one that the
strict profile could never accept, is refused.
A key file is the line of the secret key with a comment of its public
key, and by default it is encrypted with age and a passphrase, scrypt
with a work factor of 16, 64 MiB, which a phone can afford.
Co-Authored-By: Claude Opus 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>
As the author asks: the rest of the envelope can be stored inside a host
file, an image, a video or any file, and each address of the locator says
at which byte it starts (44.1).
- The envelope is split: its age header, up to the MAC, goes in the
time-locked locator, and only the rest, nonce and STREAM, is stored
outside. Those bytes carry no mark: nobody can tell they are an age
file, let alone a capsule. The reader joins both and decrypts with
I_SOBRE.
- An address is a map with the URI and an optional offset; the reader
asks only for those bytes, with an HTTP range when it can, and checks
their SHA-256.
- Concealment, not steganography: an analysis of the host can see extra
bytes, not what they are. Only storage that keeps the file byte for
byte works; social networks and messaging applications spoil a host.
- The CDDL of the locator follows.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The author drops the list of recognized authorities: a signature is
checked only when the capsule opens, maybe decades later, and a court
or a notary will use the official validator of their country, with the
trust lists of that moment. What matters is that the capsule keeps the
evidence of the moment of signing (signature, certificate, CAdES-T and
OCSP), which it does; a list would never cover every country and adds
no legal value.
- 29.13 and the verdicts F7 and S6 go away. F6 and S4 say that DateKeys
does not check who issued the certificate or the seal, and point to an
official validator; the names come from the certificates.
- A reader may keep an author key received by another channel, but never
offers to keep one from a capsule.
- The text is no longer Spanish only: any country, any signing
application; P-521 joins the table, and brainpool, GOST and SM2 are
named as not supported, as future work.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
As the spec v0.11 draft decides after the review (38.1):
- the salt is "DateKeys llave de palabras v2|chain|round|capsule_id", so
the same words give another key in each capsule and a dictionary
cannot attack together the many capsules of a popular round;
- the words are lowered with the table of Unicode 18.0.0 of pathrule;
- wordkey.Check refuses controls, Default_Ignorable code points and
unassigned ones, and counts toward the six words only the different
ones of three letters or more.
EncryptOptions.Words takes the words: the writer derives their identity
once it has drawn capsule_id, and adds its recipient to the credentials.
The CLI passes them to the writer, and decrypt salts them with the
capsule_id of the capsule. New vector of 38.1.
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>
The author's decisions after the review of 1 October 2026:
- Recognized authorities (29.13): a list pinned in the SDK for the
certificates of signers and for timestamp authorities, checked at the
time of the seal, without revocation. Without it a certificate or a
seal was checked with what it brings itself. New verdicts F7 and S6.
- The key of words is salted with capsule_id too (salt v2), so that the
capsules of a popular round cannot be attacked together; the writer
rejects controls and ignorable code points and should reject short or
repeated words. New vector.
- What is stored outside is an opaque envelope: the .dkc encrypted for a
random identity whose key goes in the time-locked locator, padded to
4096 bytes, with https and ipfs addresses only.
- AUTHOR_MESSAGE is ASCII text of 99 bytes with the digest in hex and an
8-character code to compare before signing.
Fixes from the three reports: SIG_PART over the exact content of key 2
for any alg; one procedure per signer in 29.10, with F1 only for the
shape of what is present; a closed table of OIDs and DER with sorted SET
OF; the token checks its content-type and message-digest; S4 needs
genTime + accuracy before the round; F1 or F2 in 29.9 and the noble text;
alg and seal_type 4294967295 reserved for tests, and the five v0.10
vectors that change verdict listed in 76; the leftovers of v0.10 in
55.1, 27, 63, 72 and 73; the note as one line of the declared author
rules; extension data never gives ERR_NON_CANONICAL_CBOR; what a
certificate signature reveals; blind signing in 7.9; no secrets at rest
while waiting for a signature; the figures of 76.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Delivery 2 of capsule format 3, with the decisions the author took on
1 October 2026 after the review of Fable and Astra:
- A fixed area of 32 KiB for every capsule of a v0.11 writer, signed or
not, and 64 KiB only when the person enlarges it expressly (29.2).
- What is signed (29.8): AUTHOR_MESSAGE of 66 bytes over control_commit
of CONTROL_SIG, without I_PAYLOAD and with payload_length at zero,
head_digest and signers_digest. The size of the area never changes
what is signed, so the area can grow after signing.
- alg 1, strict Ed25519 with dkauthor1 keys (29.9, 29.12), and alg 2, a
detached CMS signature with one or more X.509 signers, the list of
required signers in key 1 and a CAdES-T timestamp per signer (29.10).
- seal_type 2, an RFC 3161 seal over SEAL_SUBJECT for capsules without a
certificate signature (29.11), and the verdicts F2 to F6 and S3 to S5.
- The key of words (38.1), the public note datekeys.note (24.1) and the
capsule extension datekeys.capsule of the .dkk, with the note, the date
and a locator encrypted for the date (44.1).
- Writer rules 19 to 24, privacy, threat model 7.9, mutation tests,
compatibility with v0.10 readers, the extension registry, provisional
items (the 32 KiB to be measured) and the change log in 76.
The CDDL adds the schemas of alg 1, alg 2, seal_type 2 and the two
extensions. The reference still implements v0.10.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The author asked for keys that people can keep without files. Package
wordkey derives the X25519 identity of words a person chooses, at least
six: their NFD by the tables of pathrule without the marks U+0300 to
U+036F, each code point in lower case by its simple mapping, split at
white space, joined by one space, and stretched with PBKDF2-HMAC-SHA256
of the standard library, 600 000 rounds, salted with the chain hash and
the round of the capsule. It is wordkey.ts of datekeys-ts byte for byte:
both check the same vector.
encrypt takes -words or -words-file for a time_and_key capsule, and adds
the key as one more recipient, derived for the round of -at; decrypt
takes them and adds the identity, for the round the capsule shows. The
format does not change. Checked: the CLI opened a capsule that the page
wrote with words, with the release from the public relays.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A new flag, -ts, writes the same tables as tables.go as a TypeScript
module for the second implementation, which spec 29.5.1 requires to
use tables generated from the same pinned files and never the Unicode
functions of its platform: plain arrays of numbers, the sources and
TablesDigest, which its tests recompute from the arrays with the
canonical text of pathrule.Canonical.
go run ./internal/pathrule/gen -data .cache -ts ../datekeys-ts/src/lib/dkc/pathrule-tables.ts
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The author authorized the tag on 30 September 2026. spec/README.md, the
two READMEs and the comment of SpecVersion name spec-v0.10 as a tag,
no longer as one to come. The text of the specification and its
SHA-256 do not change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Three fuzz targets, in scripts/fuzz.sh too: FuzzDecodeHead (a head
that is accepted re-encodes to its input, and a rejection carries one
normative code), FuzzEvaluateSecurity (verdicts of this version, X for
both or for neither) and FuzzCheckPath (the rules of one entry and the
decoder of the head agree on every path). About a million runs each,
clean; scripts/check.sh 60s is clean.
- Spec v0.10, section 67: the fixtures of format 3 exist, so "Serán ...
(por implementar)" reads "Son ...", as for those of format 2. No rule
changes.
- spec/README.md: v0.10 approved by its author on 30 September 2026 and
implemented on this branch, with the SHA-256 of its text; the tag
spec-v0.10 waits for the author.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
decrypt creates the folder of a format 3 capsule at step 17, after the
release is requested. With a missing parent it failed only then, from
the Sink, reported with ERR_INTEGRITY as any failure of the output is.
It now checks the parent right after the prelude, with a message of
its own and no request.
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>
datekeys encrypt writes format 3 and datekeys decrypt writes its files
to a new folder, with the presentation of spec 29.7.
- encrypt: -in is repeatable and takes files and folders; a folder
gives its name as the first segment, as a browser does, and is
walked with Lstat, following no link, taking regular files only.
.DS_Store, Thumbs.db, desktop.ini, ._* and __MACOSX are left out of
folders, and each one left out is reported (62.1 rule 15). New
-comment, -author and -no-mtime; the mtimes are kept by default
(rule 16). A capsule may hold a comment alone. The copy of a pipe to
a temporary file goes, as only regular files are taken.
- decrypt: the prelude decides. Format 3 claims -out with os.Mkdir,
only when there are files, stages the tree in -out/.datekeys-*
through an os.Root with O_EXCL and mode 0600, sets the mtimes, and
moves each entry of the first level into place at step 18; any
failure removes the folder (spec 56). Formats 1 and 2 still write a
file.
- The presentation goes to stdout: the verdicts, the declared author
and the comment box with their labels, the paths, and the verdicts
again. Every line of the creator goes in pieces of at most W - 3
columns behind the prefix, counting 2 for anything but printable
ASCII, with its TABs expanded to multiples of 8; W is the width of
the terminal, asked with syscall on Unix and Windows, or 80. Risky
names get a warning: shortcuts, desktop.ini, .git, programs and a
leading dash, compared by their key of R7.
- Encrypt no longer runs in the CLI: only the test data generators
set TestVectors.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
EncryptFiles writes format 3 (spec 29.2 to 29.6, 61, 62, 62.1): the
files of a list of Sources, each read twice, with the comment and the
declared author.
- Before anything is written: the paths and the texts are checked with
the rules of the reader, in the words of a writer, naming the rule
and the character, and the two paths of an R7 collision (rule 15);
the comment has its CR LF and lone CR turned into LF (29.6); L is
measured with a head whose salt and SHA-256 are zero, as long as the
final one, and the first reading hashes each file, which must have
exactly its Size.
- The files go in the byte order of their paths (R8), whatever the
order of the Sources; the mtime is kept only from 1970 to 9999,
never clipped (rule 16); at least one file or a comment (rule 14).
- The head, with a fresh salt, the control and the security area are
decoded with the rules of the reader before sealing (rule 17), and
the frame is checked against L. The area is 512 bytes with the
empty security, whatever the options (rule 13).
- The second reading writes each file into PAYLOAD_AGE and fails if its
size or SHA-256 changed (rule 18).
- Encrypt and EncryptFiles share the sealing; Encrypt writes format 2
only with the new TestVectors option (rule 1), and takes no head.
The test data generators set it, and so does the CLI until step 5
moves it to EncryptFiles.
- Result.Head is the head written. DecodeHead keeps the check of the
critical extensions apart, so that the self-check decodes the head
as the one of the control does.
- The examples and the live test write with EncryptFiles.
- The reader tests had a literal U+202E, now escaped.
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>
- Format3 and the control of schema version 3, with the keys of
version 2 (spec 31). The PRELUDE still rejects VERSION 3 until the
reader opens format 3, in step 3, with the test data that expect it.
- The frame of BODY (spec 29.2): AREA_LEN, SECURITY_LEN and HEAD_LEN,
their limits against L and the zeros of the area, all ERR_INTEGRITY.
- security (spec 29.3, 29.7): the outer map, with the signature and
the seal as separately encoded byte strings, and its verdicts X, F0,
F1, S0, S1 and S2, which never fail. The first row that holds
decides, so a seal that breaks its schema is S2 before its type is
read. Writers of this version write it empty, 22 bytes.
- The head (spec 29.4): layer 2 with its type tag and version 1;
layer 3 with the CDDL, R1 and R8; layer 4 in key order, the comment
and the declared author, each file with R2 to R6c, R10 and its
layout, R7 and R9 over the tree, all ERR_HEAD_INVALID, and then the
critical extensions of the new extension.Head object.
- ERR_HEAD_INVALID is declared; All lists it once SpecVersion moves to
0.10 with the test data, in step 6.
- The control tests take format 4 as the caller error that format 3
was, as section 76 of the spec anticipated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
internal/pathrule checks the paths and the texts of a format 3 head
(spec 29.5, 29.6) with its own tables, never with the Unicode functions
of the platform, whose version changes with each runtime.
- gen reads the 19 pinned data files (UnicodeData, DerivedCoreProperties,
CaseFolding and emoji-variation-sequences of Unicode 18.0.0, and the
15 WindowsBestFit tables), checks their SHA-256 and writes tables.go:
assigned code points, Default_Ignorable_Code_Point, full canonical
decompositions and combining classes, C and F folding, the bases of
the emoji variation sequences, and the non-ASCII code points each
code page maps to ASCII. The data files stay out of git, in .cache.
- NFD, Fold and the key of R7; CheckPath with R2 to R6c and R10,
CheckTree with R7 and then R9, and CheckComment and CheckAuthor with
the invisible-character rule. Errors name the rule and never echo the
creator's text, so that another implementation can match them.
- The canonical text of the tables has a SHA-256, TablesDigest, which
the tests recompute and a TypeScript implementation will share.
- Checked against golang.org/x/text (Unicode 15.0.0) outside this
module: NFD matches on every code point both know, and folding only
differs on the 86 Cherokee letters that CaseFolding.txt folds to upper
case, as these tables do.
- The spec pins the SHA-256 of the 19 files in 29.5.1.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The author approved it on 30-09-2026, after Unicode 18.0.0 warned that
sequences of invisible variation selectors are used to attack AI
applications. The comment and the declared author allowed the tag
characters and loose variation selectors, which hide text a model
reads, and paths allowed runs of ZWJ, ZWNJ, VS15 and VS16.
- R4b: VS15 and VS16 only right after a character that
emoji-variation-sequences.txt pairs with that selector, and ZWJ and
ZWNJ never first, last or twice in a row in a segment.
- Section 29.6: the comment and the declared author carry no
Default_Ignorable_Code_Point except that whitelist, placed as R4b
says, each line standing for a segment.
- The tables add emoji-variation-sequences.txt; section 76 records the
change as number 12, with mutations and vectors.
- The five new references of section 77 regain the two trailing spaces
that break their line, like the others.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The author chose Unicode 18.0.0, released on 16 September 2026, over
the 17.0.0 of the design: the tables freeze with capsule format 3, so
an older version would refuse for good characters that are already
standard. Section 29.5.1 now says the Unicode version is fixed with
the format, and that moving to another needs a new format (22).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- R6c only rejects a best-fit projection with '/', '\', ':' or U+0000,
and still applies R3, R5, R6 and R6b to it: bestfit1250 maps "¿" to
'?' and bestfit874 maps "§" and "♥" to C0 controls, so the stricter
rule would have refused ordinary Spanish names.
- Step 17: codes other than ERR_INTEGRITY are reported only after
reading PAYLOAD_AGE to EOF; 17.1 leaves the padding to 17.8; 17.3 and
17.6 never fail. The precedence case is the cut right after a
complete chunk, where filippo.io/age and age-encryption differ.
- Verdicts: the first matching row decides, and alg or seal_type are
read only from content that meets its schema. Sections 58 and 70
exempt SECURITY_CBOR, which only changes verdicts.
- Normative verdict texts, and the presentation rule for any text
output, paths included.
- The sink rule of section 70, the test-vector exemption of writer
rule 13 and the mtime rule 16.
- More mutations and vector coverage; a complete list of changed test
data in section 76 (checked: only two of the 125 mutations and none
of the 4380 differential cases leave VERSION 3); corrected cases.
- A closed list of the Unicode and WindowsBestFit tables, and
editorial fixes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Delivery 1 of capsule format 3, as the author decided on 30-09-2026:
several files with their paths, sizes, SHA-256 and dates, encrypted in
the plaintext of PAYLOAD_AGE, and a security area that later versions
will fill with an author signature and a timestamp seal without
changing the format.
- VERSION 3 and CONTROL_CBOR schema 3; writers write only format 3,
readers open the three formats.
- BODY with a 12-byte frame (AREA_LEN, SECURITY_LEN, HEAD_LEN), the
security area fixed by spec version (512 bytes here), the head and
the files (new sections 29.2 to 29.7).
- The head is always version 1 in format 3; path rules R1 to R10 on
pinned Unicode 17.0.0 and WindowsBestFit tables; text rules; the
verdicts X, F0, F1, S0, S1 and S2 and their presentation.
- Step 17 split into 17.1 to 17.8 with its precedence, and the new
code ERR_HEAD_INVALID.
- Atomic delivery of several files (56), limits (57), writer rules 13
to 18, mutation tests, fixtures and the change log in 76.
- The CDDL gains control-v3, security, author-signature, seal, head and
file.
The reference implementation still implements v0.9. The design, its
reviews and the author's decisions are in the private docs repository,
spec_v0.10/formato3_diseno.md.
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 final review of the draft found 22 problems, and none had been applied
yet. All of them are applied now, together with four places that repeated
them (§62.1 rules 1 and 4, §76 changes 4 and 10).
- §29.1, §76 change 4: 32-bit arithmetic first gives a wrong P at
L = 2 113 929 217 with signed operators and at L = 4 227 858 433 with
>>> 0, not at 2^32 + 1. The vector table gains a row for each, checked
against a BigInt Padme. Above 2^32, Padme exceeds bloque256 except at
L_MAX. The Node log2 error changes E and lastBits, not P or S.
- §56, §76 change 4: a reader MUST NOT present the content as valid before
step 17 ends; a streaming reader MUST NOT write the padding and MUST
signal the step 17 error so that what it wrote is discarded. The author
chose this over the stricter rule, which forbade delivering any byte
before step 17 and so the streaming Open(dst) of the reference.
- §64: the 15 and 17 stanza mutations recalculate the PRELUDE and
header_binding; every time_and_key mutation offers the identity, because
any change breaks the capsule_digest of a .dkk; the duplicate recipient
mutation names its identity. Three new mutations cover the shape of
payload_length (7 or 9 bytes, a CBOR integer); the format 2 cbor.json
vectors of §76 list them too.
- §39, §62.1 rule 4: the official test vectors may show which slots are
dummies. §70, §62.1 rule 1: the format 2 rule binds implementations that
write capsules, and a test vector generator MAY write format 1.
- §62 step 8 gets the 64 MiB limit of §61 step 6.
- Accuracy: §22 (an older reader detects a new padding code only after the
network request; the .dkk schema is §41), §37 and §76 change 10 (the §63
rules do not detect those recipients), §55.2 (the writer knows the number
of parties; relays are §48), §76 changes 1 and 8.
- Wording: §62.1 rules 7 and 11, §63 step 4, §76 change 11, the field names
in the CDDL comments, and a v0.9 entry in spec/README.md.
go test ./... passes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Draft decided by the author on 29-09-2026: format 2 with exactly 16
X25519 stanzas in INNER_ACCESS_AGE (dummies, shuffled), payload padding
(code 1 bloque256, code 2 reforzado = max(bloque256, Padme)) with L and
the code in CONTROL_CBOR v2, VERSION 2 so v0.8.2 readers reject early,
a privacy section (§55.2) and writer rules (§62.1). The CDDL holds the
draft schemas. The fixes from the final review were being applied when
work stopped: they may be partial. See App/docs/HANDOFF.md.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The row of spec §70 names datekeys.SpecVersion, datekeys.Version and the version command, which 5b342d3 added without this row.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- datekeys.SpecVersion ("0.8.2") names the specification the module
implements. A test ties it to the spec file, its title and
spec/README.md, and TestCatalogueMatchesSpec and the vector files use
it (testkit.SpecVersion now aliases it), so the vectors regenerate
unchanged.
- datekeys.Version() is the version of the module as the go command
recorded it. That is a tag, or for a binary built in a checkout the
pseudo-version of its commit (for example
v0.0.0-20260928105528-9ac9cd952f04), or (devel) when it is unknown, as
in tests or under a replace directive to a directory. It works as the
main module and as a dependency, whatever the module path, which it
reads from the root package.
- `datekeys version` (also -version and --version) prints both and the
Go toolchain.
- README.md and README.es.md explain the three versions (format,
specification, module) and what the code on main covers.
traceability §70 and CHANGELOG follow.
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>
Extension data becomes a non-empty opaque bstr bounded by the frame of its
container; the base protocol never decodes or validates it. Arrays hold at
most 64 extensions, extension_version is bounded to 2^32-1 and the profile's
period and genesis_time to 2^53-1. The multiplicity exception is removed,
ERR_EXTENSION_DATA_INVALID is added, the §57 limits become MUST with an
explicit error mapping, §58 names the protocol's CBOR profile and §72 sets
the registration rules. §76 records the six reproducible cases behind the
change. The implementation follows in the next commit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Gitea server has no Actions, so scripts/check.sh is the gate. A
versioned .githooks/pre-push runs it; CONTRIBUTING explains how to
enable it per clone and when it may be skipped.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The module is imported as g.activething.com/go/DateKeys, the path the
project's Gitea advertises. The .github directory is gone: workflows
now live in .gitea/workflows, use the gitea.com action mirrors and
install every tool from its Go module; releases go to this Gitea with
goreleaser and a key-based cosign signature; Dependabot is replaced by
a nightly report of available updates. scripts/check.sh runs the same
checks on any machine and is the gate while the server has no runner.
SECURITY.md, README and CONTRIBUTING no longer refer to GitHub.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
go.mod required go 1.26.0, whose standard library has known
vulnerabilities reachable from this module according to govulncheck.
Raise the minimum to the latest 1.26 patch release.
Co-Authored-By: Claude Fable 5.1 <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>