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 builder of the tests, cmstest:
- NewCert writes a certificate from the DER of its tbsCertificate, field by
field: names of any string type with any bytes (UTF8String,
PrintableString with an underscore or an at sign, IA5String,
TeletexString, BMPString of odd length or with a surrogate,
VisibleString, NumericString), an attribute twice or none, no version,
times with a fraction, an extension twice, a compressed EC key, an even
modulus, and any signature. A Signer made so serves Signature and Token.
- Options for the version of a SignerInfo, the hashAlgorithm and the
certHash of an ESSCertIDv2, a signatureAlgorithm other than the one of
the key, a certificate twice, two content-type attributes, an attribute
with an arc of 2^31, a SignerInfo twice, BER, signerInfos out of order,
two signature-time-stamp attributes, and edits of the SignedData and of
each SignerInfo.
- Token options for any accuracy, a genTime of free text, ordering FALSE,
a field after the last, an imprint of any length, no message-digest, a
CRL in crls and the certificate of the authority twice.
- Edits of the DER after signing: Edit, Retag, Withdraw (a SignerInfo
removed), WithoutTimeStamp (a CAdES-T removed), Merge (a co-signature)
and Indefinite.
The tests of internal/cms and internal/der fail for each check of cms.go,
cert.go, verify.go and der.go. A mutation run, which replaces each leaf of
each condition by false and by true, one at a time, kills every mutant
that is not equivalent to the code it mutates.
FuzzParseSignature, FuzzParseToken, FuzzParseCert and FuzzDERCheck, seeded
with security_cms.json and with what cmstest builds: no panic, what Check
accepts Split reads, and the parsers fail only with ErrForm or
ErrAlgorithm.
capsule: the case of a seal outside the validity of the certificate gave
an invalid seal; it now tests a certificate that expired before a valid
seal (out of validity) apart from an authority that was not valid at its
time (invalid seal). SIGNERS out of order, empty, too long, with 31 bytes
or with a hash twice are F1 beside a CMS signature that is valid for the
AUTHOR_MESSAGE of those SIGNERS, and the names of certificates show as
spec v0.12 §29.7 says.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
der.Check refused the universal types 7, 18, 21, 25 and 27
(ObjectDescriptor, NumericString, VideotexString, GraphicString and
GeneralString), which DER writes primitive with their content as it is
(X.690 10.2). A certificate whose name holds one of them, as the INN of a
Russian certificate or the countryCode3n of X.520, made the whole CMS
signature F1, and a token S2, while spec v0.12 §29.10 asks for DER and reads
the name with its profile: any value, which is no text when it is not of
the five string types. Such a certificate now meets the profile; its value
shows as no text.
REAL, RELATIVE-OID, TIME and the reserved tags stay refused: their DER has
rules of its own, and no certificate, signature or token uses them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
What the update of the documentation found without a test: Header.UnusableNote
and the notice of decrypt, the helper that keeps a panic of a parser of
security in its own part, and that EncryptFiles writes nothing of the
capsule while it waits for a signature (spec 62.1 rules 19 and 25). The
usage text of decrypt -expect-author says that it writes no file.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The session that closed v0.11 left the documentation at v0.10 (review of
2 October, G14).
- README.md and README.es.md say the same again: the reference implements
v0.11, tagged, and the branch v0.12 the draft; SpecVersion 0.11; the area
of 32 KiB in the picture of BODY; the table of modules with authorkey,
internal/cms, internal/der, locator, wordkey and the public note; and what
the CLI does now: encrypt -sign shows the key and the code of
AUTHOR_MESSAGE before it signs, decrypt -expect-author writes nothing
unless the key of an F4 matches, the lines of the verdicts break behind a
mark, and decrypt and inspect say when a public note is not shown. The
security properties no longer say that no signature is checked.
- SECURITY.md: the scope is v0.11 and the draft v0.12; the limits of a
signature, a seal and a key of words; the standard library among the
cryptographic dependencies.
- docs/traceability.md at the draft v0.12: rows for 24.1, 29.8 to 29.12,
38.1 and 44.1, and rows 29.2, 29.3, 29.7, 62.1, 64, 67, 70, 72 and 76 up
to date, with the code and the tests of each. The cases of spec 64 that
security_cms.json and locator.json still lack are marked pending.
- CHANGELOG.md: an entry for the draft v0.12: the review and its fixes, the
draft, the CMS reader with its own profile, the addresses of the locator,
the CLI, the new test data, and what is pending.
- capsule/format3.go: the comments of EncodeSecurityWith,
EncodeAuthorSignature and EncodeSeal no longer say that this version
defines no alg and no seal_type.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
FuzzUnmarshal, FuzzParseInfo and FuzzCheckURI, in scripts/fuzz.sh: no
panic, no usable address that the rules refuse, ERR_EXTENSION_DATA_INVALID
as the only code of the data of datekeys.capsule, and a host shown that is
in the address as written. 40 s each with -parallel 4, clean.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
From the details of the review: author keygen -plain no longer speaks of a
passphrase that the file does not have, and decrypt -expect-author leaves a
prelude that does not parse to Open, which reports it at step 1 or 2 with
its code, instead of calling it a capsule that is not of format 3.
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 last commit added the fixture and left the lists of the tests and the
count of frozen inspect outputs behind: TestInspectJSONGoldens failed.
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>
Fixes of the review of the session of 1 and 2 October in internal/cms and
internal/der:
- Object identifiers are compared by the bytes of their DER: an arc of 2^31
or more no longer makes an attribute that decides nothing fail the
signature (F1), and an algorithm with one is outside the table (F5, S1),
as spec v0.11 says.
- A SET OF may repeat an element, as X.690 allows: a TSA that sends its
certificate twice no longer gives S2. Two copies of a certificate are one.
- Certificates are read with a profile of their own instead of
encoding/asn1 and crypto/x509, field by field, so that a second
implementation can read them the same: version 3, the fields in order,
names of non-empty SETs, times of validity in DER without a fraction,
extensions without repetition, and the text of a name only from
UTF8String, PrintableString, IA5String, TeletexString in ASCII and
BMPString without surrogates, nothing removed from it. A certificate that
breaks the profile decides nothing unless a SignerInfo names it. The
holder is givenName and surname before commonName, which in the
certificates of the FNMT carries the NIF; the issuer is its commonName or
its organizationName.
- The key: RSA with NULL parameters, exactly a modulus and an exponent, the
modulus odd; EC only uncompressed on P-256, P-384 and P-521.
- A key of another scheme than its algorithm is invalid (F2) and not
outside the table (F5), as step 3 of 29.10 says; a messageImprint of
another length is S3; the crls of a token decide nothing.
- DER: UTCTime and GeneralizedTime in their forms of X.690, a date that
exists, and the millis and micros of accuracy as minimal INTEGERs.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fixes of the review of the session of 1 and 2 October that the text of
spec v0.11 already asks for:
- authorkey: String and GoString hide the secret key, which only Secret
returns; ParsePublic refuses a key that is not a point of the curve
(ed25519strict.OnCurve, checked against the square root of testkit).
- capsule: a typed nil in AuthorKey, CMSSigner or Sealer is an error, never
a capsule without the signature or the seal that was asked for. A panic
while evaluating the signature or the seal fails only that part, F1 or
S2, not both. OpenOptions.Accept sees the verdicts before step 18 and can
refuse to publish the files.
- extension.CheckWrite, the rule of encoders of spec 72: the writers of
capsules and .dkk files refuse datekeys.note and datekeys.capsule outside
the arrays where they are registered, or with invalid data.
- CLI: encrypt -sign shows the author key and the code of AUTHOR_MESSAGE
before it signs (rule 20); decrypt -expect-author compares the key of an
F4 and writes nothing unless it matches; decrypt notifies a public note
that it does not show; the lines of the verdicts break at the last space
that fits, each row after the first behind a mark, so that the terminal
never breaks them; L is the payload, not the content.
- locator: a reader rejects an address that breaks 44.1 and keeps the
others; addresses refuse the special-purpose blocks of IANA, IPv6 outside
2000::/3, localhost and local names, characters outside RFC 3986, dot
segments, and a CID that does not decode to version 1 and a multihash;
ParseInfo checks that the locator is an age file with one tlock stanza
for the round of its DateKey; Info.Extension reads what it writes; its
errors carry no normative code.
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>
The TSTInfo is read field by field in DER, with accuracy from zero and
millis and micros from 1 to 999, genTime in UTC with Z, no default written
and nothing after the last field. The ContentInfo and the SignerInfo must be
SEQUENCEs, a SignerInfo version must match its sid, an attribute needs a
value and is counted by attribute and not by value, a signing-certificate
beside the v2 decides nothing, PSS parameters come in order without the
trailer, and der.Check refuses the end of contents and the universal tags
the profile does not use.
The writer signs before L is fixed: write asks prepare for the final L, so
the area grows to 64 KiB only when what was signed does not fit and LargeArea
allows it, and nobody signs twice for it. Typed nils are nil, the exclusions
are checked before a file is read, Encrypt refuses the signing options, and
EvaluateSecurityIn gives X if a parser panics. The issuer of a certificate is
filtered like its holder.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
CheckURI works on the raw authority, as an HTTP client reads it: no percent
signs, userinfo or backslashes, a host of letters, digits and hyphens or a
public IP literal, a port from 1 to 65535, and Host returns that host. The
integers of the locator stop at 2^53 - 1, and Info.Extension refuses what
ParseInfo would. extension.Standard validates datekeys.capsule through
locator.Standard, and Info.OpenLocator ties the locator to the round of its
own DateKey.
inspect shows the public note as text of the creator, with its prefix and
wrapping and the warning, and says when a note is unusable. encrypt -note
warns that it is public. decrypt -expect-author fails before the release
is requested when the capsule is not format 3. Author key files are read
with the work factor of the spec as their maximum, the passphrase is not
read from a terminal, and two copies of secrets are cleared.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
locator has the data of the extension of a .dkk, the locator sealed with
tlock for the round of the DateKey, and the envelope: the .dkc in age, split
into a header that the locator carries and a rest that can hide inside
another file. The plaintext of the locator measures the least multiple of
4096 bytes that holds it. Nothing is downloaded: a reader gives the rest to
OpenEnvelope, which checks its size and digests.
README and CHANGELOG describe what the draft adds.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
extension.CheckNote, NewNote and Note apply the rules of spec v0.11 24.1,
and extension.Standard registers the note for the noncritical array of
PUBLIC_HEADER only. Header.PublicNote reads it, and header_binding ties it to
the control: a note changed after writing fails step 15. The CLI writes it
with -note and shows it as text of the creator, with the warning that nobody
can check it before the date.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The reader evaluates a signature of alg 2 (F1, F2, F5, F6, with the signers
named) and a seal of seal_type 2 (S1 to S5) in the context of the capsule,
with SIGNERS in strictly ascending order of certificate hashes. The writer
takes a CMSSigner, which gets AUTHOR_MESSAGE and returns what the person
signed outside, and a Sealer, which asks an authority for the token over
SEAL_SUBJECT; it checks the result with the rules of the reader and writes
nothing unless every required signer is valid and sealed.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
internal/der checks that bytes are one element of DER. internal/cms reads
the detached CMS signature of spec v0.11 29.10 and the RFC 3161 token of
29.11, in the order of the spec, with the closed table of algorithms (RSA
PKCS 1 and PSS of 2048 to 4096 bits, ECDSA on P-256, P-384 and P-521,
SHA-2), with the standard library only. A certificate is read with
encoding/asn1, so that a key of a curve Go lacks makes a signature "not
verifiable" and not malformed. internal/cms/cmstest builds them for tests.
Not wired into capsule yet.
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>
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>