scripts/recovery is a program that opens a capsule with only the Go
standard library, golang.org/x/crypto, filippo.io/age and the BLS12-381
library of drand/kyber-bls12381, as the informative annex of the draft
v0.15 describes it: the pinned Quicknet parameters, the release object,
the frame, the BLS verification of the release, the tlock stanza with H2,
H3 and H4, the age file of SEALED_CONTROL opened with its file key (HKDF,
header MAC and STREAM written out), the X25519 layers with age, and the
content of formats 1, 2 and 3. A test forbids importing this module, tlock
and drand. scripts/recovery_check.sh opens a time_only and a time_and_key
fixture of format 3 with it and compares what it recovers; scripts/check.sh
runs it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The release of a round becomes a file, .dkr: a release object in
deterministic CBOR, {0: "datekeys-release", 1: 1, 2: chain_hash, 3: round,
4: signature}, which provider.EncodeRelease writes and DecodeRelease reads
with its layers (size, type and version, schema). provider.ParseRelease
also reads drand's JSON as the input of the caller. Verify checks the chain
hash a release names before its round and its signature, with
ERR_PROFILE_MISMATCH. provider.Archive reads a local release archive, the
informative format of the draft.
capsule.OpenOptions.Release takes a release in hand, a provider.Supplier,
exclusive with Source: Open does not compare it with the clock (step 9.c,
option B) and reports a clock behind it in Opened.ClockBehind; a network
source is still never asked before the round time. The CLI gains
decrypt -release FILE (.dkr, drand's JSON or a local archive),
decrypt -save-release FILE.dkr and the command release, which fetches,
verifies and saves the .dkr without opening the capsule.
Test data: vectors/release.json, releases/<round>.dkr for rounds 1000,
1001, 1004 and 2000, and a local archive of rounds 1000 to 1004. In
mutations.json every case says its source, "supplied" or "network"; the
case "round not reached yet", a release in hand, now opens, and four cases
are added: the same with a network source, a release of another round from
a network source, and two release objects of another chain. SpecVersion
stays 0.14 until the author approves the draft.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The traceability table, the READMEs, SECURITY.md and the header comment
of datekeys.cddl still described earlier versions: three drand schemes,
18 normative errors, a CDDL of v0.11 and signed releases that do not exist
yet. No normative text and no schema changes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The author approved the draft v0.14 on 6 October 2026 with the
recommendation of each of its ten decisions; decision 8, one scheme of
drand, is the previous commit. Only the date of the header changes here.
SpecVersion is 0.14, the records of the fixtures and the vectors say so,
and the frozen security_cms.json and locator.json change only their spec
field. spec/README.md records the SHA-256 of the text.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The author approved the draft with its recommendations, decision 8 among
them: section 12.1 admits only the scheme whose verification and tlock
decryption the text now writes byte for byte. profile.Validate refuses the
other two unchained schemes of drand with ERR_UNKNOWN_PROFILE. No pinned
profile and no vector changes; section 76 records it as change 7.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The draft writes down what the completeness review of 6 October 2026 found
missing, and changes no format and no verdict: what the protocol does not
guarantee, the provider and the states of a profile, signatures and seals
against a quantum adversary, the web client, the entropy of a key of words,
and errata of section 76. Steps 10 and 11 of section 63 now give the root
of trust byte for byte, as the three implementations apply it: the message
a Quicknet round signs, its hash to G1 with its DST, and H2, H3 and H4 of
the tlock IBE. testdata/vectors/tlock_steps.json gives every intermediate
value for four published rounds, checked against drand, kyber and tlock.
SpecVersion stays 0.13 until the author approves the draft.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Spec section 44.1 makes unusable a locator whose round or chain is not that
of its DateKey, and its plaintext is 4096 bytes or a multiple. ParseInfo
checked only the round: it now checks that the chain hash is lower-case
hexadecimal and, for a profile this module pins, the chain of the DateKey;
and that the body after the age header holds a plaintext of 4096 bytes or a
multiple, so that a header without a body is refused. Info.Extension, which
reads what it writes, refuses a DateKey of a profile that is not pinned.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The author approved the draft v0.13 on 6 October 2026, as it stood: only
the date of its header changes. SpecVersion is 0.13, the records of the
fixtures and the vectors say so, and the frozen security_cms.json and
locator.json change only their spec field. spec/README.md records the
SHA-256 of the text, and the READMEs, SECURITY.md, the traceability table,
testdata/README.md and the changelog name v0.13.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Spec v0.12, section 44.1, already asked for the CID in its canonical form
and an IPv6 literal: a CID with a character more, of zero bits, decoded to
the same bytes and passed, and https://[[2000::]/ passed because checkHost
trimmed every bracket. isCIDv1 refuses 5 or more bits left over, and
checkHost takes one pair of brackets.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On an IPv6-only network with DNS64, a name that has only IPv4 addresses
resolves to an address of NAT64, which spec v0.12, section 44.1, always
rejected: a reader on such a network, as many mobile ones, could not
download the rest of an envelope. The draft v0.13 counts an address of
64:ff9b::/96, or of the NAT64 prefix of the network, by the IPv4 address it
holds (RFC 6052). An address of NAT64 written in a locator is still
rejected. Section 76 records the change with its case.
locator.CheckResolvedIP implements it for the readers that download, and
testdata/vectors/resolved_ip.json gives 42 cases. SpecVersion stays 0.12
until the author approves the draft.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The author approved the draft v0.12 on 6 October 2026, as it stood: only
the date of its header changes, and no normative text is added. SpecVersion
is 0.12, the records of the fixtures and the vectors say so, and the frozen
security_cms.json and locator.json change only their spec field.
spec/README.md records the SHA-256 of the text, and the READMEs, SECURITY.md,
the traceability table, testdata/README.md and the changelog name v0.12.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Spec section 64 of v0.11 asks for the vectors of the key of words, which
were only in the tests of package wordkey. The file has the words of 45
texts, among them each space of section 38.1 and three that are not; what a
writer does with 20 texts, with the text of the error of wordkey.Check; and
6 identities with their recipients, the vector of section 38.1 first. It is
regenerated by genfixtures and checked by TestVectorFilesAreCurrent.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
internal/pathrule/gen -dart renders the Unicode and best-fit tables as
const lists of a Dart library, as -ts does for datekeys-ts: the same data
and the same TablesDigest, with the formatter turned off for the file.
tables.go and the TypeScript module come out unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Section 29.7 said givenName and surname "si tiene uno de cada", and the
organizationName of the issuer "si no tiene" commonName, and left open an
attribute without text. The reference treats it as absent: the holder is
givenName and surname only when both have text that is not empty, and the
issuer falls back to its organizationName when its commonName is absent,
repeated or not text (internal/cms, cert_test.go). The datekeys-ts port
follows it, and the text now says so: "con texto" in 29.7 and in change 3
of section 76. The author decided it on 5 October; no code changes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The text of 44.1 now says what the reference already refused and a
second implementation that followed the text would have accepted:
segments of 1 to 63 characters that neither start nor end with a hyphen,
the scheme in lower case, 0x and the local names in either case, an IPv4
without leading zeros, and a CID of at most 128 characters in canonical
base32, with minimal varints and a digest of at least one byte. A port is
written without leading zeros: the reference accepted 0443 and 00443 and
refused 000443, the same number, and now refuses the three. Change 6 of
section 76 records it, and section 64 lists the cases.
locator.json is made again with 30 more addresses, 247 in all, and
TestAddresses checks the same rules.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- testdata/README.md: the files of v0.11 and of the draft, the spec
label 0.11 until the approval, 26 fixtures with format3_unsigned and
format3_note, security.json in its context with lines, security_cms.json
without the cases of no context, note.json, the new parts of
locator.json, and the corpus of 218 cases, 178 of the spec, with the
eight of the list of v0.11. The release of round 1000 is in the records
and in mutations.json, not in quicknet_rounds.json.
- docs/traceability.md: the cases of section 64 that security_cms.json
and locator.json now hold are no longer pending.
- CHANGELOG: security_cms.json and locator.json under the test data of
the draft, instead of pending.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- uri_cases: the first and the last address of each IPv4 block of 44.1,
with the public addresses next to them; the IPv6 blocks and the
addresses that hold an IPv4 one; the local names; characters outside
RFC 3986 and broken percent signs; "." and ".." segments; base32 that
is not a CID v1.
- mixed: a locator whose http and NAT64 addresses a reader rejects, and
whose third address it uses to find the rest.
- rest_cases: the rest alone, a host with bytes after the rest, a rest
with a byte changed, an offset that is not its own, a rest cut short.
- extension_cases: a locator sealed for round 1001 with a DateKey of
round 1000, and the other data that a reader cannot use.
- plaintext_cases: change 7, the bases 4094, 4070 and 3837 completed to
4096 with an empty key 6 or a length not in its shortest form, and a
defect in each field of the map.
- padding_cases: the bases 3837, 4070, 4094 and 4095 and those of the
next multiple, checked against the rule of 44.1.
The generator checks every case against this module and moves to its
own file. The vector is frozen: delete it to make it again.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A paragraph followed directly by "---" is a setext heading in Markdown,
so the closing paragraphs of sections 70 and 72 rendered as titles. The
other three rules follow list items and rendered right; they get the
blank line too, for one style. Editorial: no text changes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
security_cms.json says 0.11, as every file of testdata, until SpecVersion
moves with the approval of the draft v0.12 whose verdicts it gives; its test
checks SpecVersion. scripts/fuzz.sh runs FuzzDERCheck, FuzzParseSignature,
FuzzParseToken and FuzzParseCert.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The check of a minimal INTEGER in the millis of an accuracy was tested only
with 128 and 999, whose low byte has its high bit set: a check that took
every two-byte value under 0x8000 for one that is not minimal left them
all passing. 300, 01 2c, is minimal and must read.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The frozen vector of alg 2 and seal_type 2, made again with the profile of
v0.12:
- each case carries the lines of Verdicts.Lines, so that a second
implementation compares the texts byte for byte, and its times keep the
fraction of the token;
- the two cases without a context, which gave the verdicts of a reader of
v0.10, are gone, and the case named a seal from before the certificate
was valid, which gave an invalid seal, is named so;
- new cases for each row of §29.7 and each item of the lists of §64 for
v0.11 and v0.12: out of validity with a valid authority, SIGNERS that
break its rule beside a valid CMS (out of order, empty, 17 entries, 31
bytes, a hash twice) and 16 signers, the version against the sid, two
content-type attributes, the ESSCertIDv2, PSS with and without
trailerField, an arc of 2^31, a certificate twice or of version 1, keys
outside the table, every hash and curve of the table, BER, two
SignerInfo of one certificate, two time-stamps, the names of the holder
and of the issuer in each string type and against each rule, and the
edges of the token: accuracy, genTime, ordering, fields after the last,
the imprint, crls and the authority.
The generator checks each case against what the spec gives, written apart
from the code: the verdicts, the result of each signer and the lines,
built from the texts of §29.7. It fails when the reader gives anything
else. capsule reads every field of the file, the lines included.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The 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>