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

260 lines
83 KiB

Docs at spec v0.11 and the draft v0.12: READMEs, SECURITY, traceability, CHANGELOG 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>
6 days ago
# Traceability: DateKeys Protocol Specification v0.12 ↔ datekeys-go
This table maps every normative section of the specification to the code that
implements it and to the tests that exercise it. It is updated in the same
change as any normative code, and it is the document handed to the external
reviewer together with the specification, the fixtures and the mutation corpus
(plan §10).
Paths are relative to the repository root. `§` numbers refer to
Docs at spec v0.11 and the draft v0.12: READMEs, SECURITY, traceability, CHANGELOG 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>
6 days ago
`spec/DateKeys_Protocol_Specification_v0.12.md`, the draft of the branch
`v0.12`, not approved yet; v0.11, tagged `spec-v0.11`, numbers its sections
the same. A case of §64 that is not in the repository yet is marked
*pending*.
## Section map
| § | Topic | Implementation | Tests |
|---|---|---|---|
| 3 | Guiding principle: verify locally | `profile.Registry`, `datekey.Resolve`, `provider.Verify`, `capsule.Inspect` | `capsule.TestMutationCorpus` |
Implement capsule format 2 of spec v0.9 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>
1 week ago
| 4 | Security goals, among them goal 8, the privacy of metadata in format 2 | whole module | whole suite; goal 8: the tests of rows 29.1, 39 and 55.2 |
| 7 | Threat model | creator model in `internal/testkit.Build`, `RewriteAge`; third-party edits in the mutation corpus | `agewrap.TestTimeIdentityStrictness`, `TestPayloadIdentityStrictness`, `TestAccessIdentityStrictness`, `capsule.TestMutationCorpus` |
| 9 | Provider abstraction | `provider.Condition`, `provider.Release`, `provider.ReleaseSource` | `provider/*` |
| 10 | Provider Profile | `profile.Profile`, `Profile.Validate` | `profile.TestValidateRejectsTamperedProfiles` |
| 11 | Canonical profile encoding, `profile_hash`; `period` in 1..2^53−1, `genesis_time` in 0..2^53−1 | `Profile.CanonicalCBOR`, `Profile.Hash`, `profile.Decode` (hand-written `wire` encode and decode: keys 0 to 10, all required, in order; unsigned `genesis_time`, `codec.MaxSafeUint`; `period` limited to 1..86400 s, an implementation limit marked in `spec/datekeys.cddl`, `ERR_NON_CANONICAL_CBOR`) | `profile.TestQuicknetMatchesGoldenVector`, `TestQuicknetCBORLayout`, `TestDecodeRoundTrip`, `TestDecodeStructure`, `TestIntegerRanges`, `FuzzDecode`; `testdata/vectors/profile_quicknet.json`; the `provider_profile` block of `testdata/vectors/cbor.json` (*period of one day, the implementation limit*, *… above the implementation limit*) |
| 12 | Quicknet Provider Profile V1 | `profile.Quicknet`, `profile.Quicknet*` constants | `profile.TestQuicknetMatchesGoldenVector` |
Spec v0.8.2 amendment: canonical point encoding; no library error text 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>
2 weeks ago
| 12.1 | Provider Profile validation: CDDL, field rules (`ERR_UNKNOWN_PROFILE`: the name alphabets, normative, and their lengths and the `public_key` size, implementation limits; `period` at most 2^32−1, implied by the 86400 s limit; `genesis_time` in 1..253402300798, provider `drand`, the three unchained tlock schemes, a public key that is the canonical encoding of a point of the key group (§12.2) other than the identity), then the chain-hash self-check (`ERR_PROFILE_MISMATCH`), then the pinned `profile_hash`; the same codes on the pin path and the decode path | `profile.Decode`, `Profile.Validate` (rules 1 to 3 for a value), `validateDrand` (drand `chain.Info.Hash`), `profile.NewRegistry` (encode, `Decode`, then the pinned hash) | `profile.TestDecodePrecedence` (with a G1 point outside the subgroup), `TestPinPathMatchesDecode`, `TestChainHashFormula` (the formula computed without drand), `TestPublicKeyEncodingIsCanonical`, `TestValidateRejectsTamperedProfiles`, `TestRegistry`; the `provider_profile` block of `testdata/vectors/cbor.json` |
| 12.2 | Canonical encoding of a BLS12-381 point: compressed, 48 bytes in G1 and 96 in G2 (c1 then c0); compression flag set, infinity flag only for the point at infinity with every other bit zero, sort flag for the lexicographically largest y; coordinates below p; the prime-order subgroup; every other string rejected (x + p, c0 + p, c1 + p, an identity with a payload or the sort flag, no compression flag, uncompressed forms, other lengths) | the decoder of `kilic/bls12-381` through drand's `kyber-bls12381` (`KyberG1` and `KyberG2` `UnmarshalBinary`), which the reference runs for the public key (`profile.validateDrand`), the release signature (drand `Scheme.VerifyBeacon` in `provider.Verify`) and U (`tlock.BytesToCiphertext` in `agewrap.TimeIdentity.Unwrap`); the point at infinity refused explicitly for the public key and U, and for the signature by the BLS verification | `profile.TestDrandPointDecodersAreCanonical` (fails if a dependency update makes the decoders lenient), `TestPublicKeyEncodingIsCanonical`; `provider.TestVerifyRejects`; `agewrap.TestTimeIdentityStrictness`, `TestTimeIdentityRelease`; `internal/testkit.TestPointReencodings`; `capsule.TestPointMutationsChangeOnlyTheEncoding`; the ten point mutations of §64 |
Spec v0.8.2 refinements: error precedence, trust model, strict order 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>
2 weeks ago
| 13 | Root of trust | `profile.NewRegistry`, `profile.Pin`, `profile.Default`, `QuicknetProfileHash`; chain-hash self-check in `Profile.Validate` (the drand chain-info hash, formula in spec §12.1) | `profile.TestRegistry`, `TestPinPathMatchesDecode`; mutations *unknown profile*, *empty registry*; the `provider_profile` block of `testdata/vectors/cbor.json` |
| 14 | DateKey | `datekey.DateKey` | `datekey/*` |
Spec v0.8.2 refinements: error precedence, trust model, strict order 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>
2 weeks ago
| 15 | Date → round resolution; round time at most 9999-12-31T23:59:59Z, instants before `genesis_time` rejected (`ERR_DATEKEY_INVALID`) | `datekey.Resolve`, `datekey.RoundTime`, `DateKey.Validate`, `Profile.MaxRound`, `profile.MaxUnixTime`; `capsule.Inspect` step 7 | `datekey.TestGoldenRoundVectors` (*genesis - 1s*, *after the last representable round*), `TestRoundNeverOpensEarly`, `TestResolveProperty`, `TestTimezoneIndependence`, `TestValidate`; `capsule.TestPrecedenceAcrossSteps` (step 7) |
| 16 | Normative round vector | — | `datekey.TestNormativeRoundVector`; `testdata/vectors/quicknet_rounds.json` |
Spec v0.8.2: second-round corrections from the formal review 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>
1 week ago
| 17 | Past-round attack; at step 10 the release round is compared before the signature, `ERR_ROUND_MISMATCH` for a release supplied directly, a network source discarding one of another round at step 9 (`ERR_RELEASE_UNAVAILABLE`) | `provider.Verify` (round equality first), `capsule.Encrypt` (round time ≥ requested), `agewrap.CheckTimeStanzas`, `provider/drand.Client` | `provider.TestVerifyRejects` (*another round and a short signature*); `capsule.TestReleaseFromANetworkSource`; mutations *DateKey A + release of round B*, *tlock stanza round differs from DateKey.round* |
Spec v0.8.2 refinements: error precedence, trust model, strict order 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>
2 weeks ago
| 18 | `dk1_` representation; the canonical JSON has no escapes, the `profile_id` alphabet needs none | `DateKey.CanonicalJSON`, `DateKey.Compact` | `datekey.TestGoldenDK1Vectors`, `TestNormativeRoundVector` |
| 19 | `dk1_` canonicality; steps 1 to 3 `ERR_DATEKEY_INVALID`, step 6 `ERR_DATEKEY_NON_CANONICAL`; step 1 accepts either Base64 alphabet, padding and non-zero trailing bits but no other character (CR and LF included); step 2 one RFC 8259 JSON object, no byte order mark; JSON numbers by their exact decimal value; round in 1..2^53−1 without the profile | `datekey.Parse` (`decodeBase64`, which rejects CR and LF before the Go decoders, `parseJSON`, which rejects invalid UTF-8 before `encoding/json` can replace it, `jsonUint`) | `datekey.TestGoldenDK1Vectors`, `TestReadingRules`, `TestNumberSpellings`, `FuzzParse`; mutation *non-canonical dk1_ JSON*; `testdata/vectors/dk1.json` (*byte order mark*, *line feed inside the Base64*, *carriage return and line feed after the Base64*, *version 1.0000000000000001: its exact value, not a double*, *invalid UTF-8 in a member a repeated name overwrites*) |
| 20 | File extensions and magic | magic checks in `capsule.ParsePrelude`, `accesskey.Decode` | mutation *a .dkk offered as a .dkc*; `accesskey.TestDecodeRejects` *a .dkc* |
Spec v0.8.2: corrections from the formal review 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>
2 weeks ago
| 21 | `capsule_id`: exactly 16 bytes from a CSPRNG (MUST) | `capsule.Encrypt` (16 bytes from `crypto/rand`), `capsule.DecodeHeader` | `capsule.TestPortableKeysAreNeverReused` |
| 22 | `.dkc` framing; `VERSION` is the capsule format, 1, 2 or 3, which fixes the schema version of CONTROL_CBOR, the stanzas of INNER_ACCESS_AGE and the padding of the payload; `PUBLIC_HEADER_LEN` in 1..1 MiB, `SEALED_CONTROL_LEN` in 1..64 MiB; PAYLOAD_AGE to EOF, at least an age header | `capsule.Format` (`Format1`, `Format2`, `Format3`), `capsule.Prelude` (`Format`), `capsule.ParsePrelude` | `capsule.TestFormatDispatch`, `TestFormatRelabel`, `TestFrameLengthLowerBounds`, `FuzzParsePrelude`; mutations *version changed* (`VERSION` 4, in the three formats), *flags != 0*, *reserved != 0*, *magic*, length limits, and the relabelings of the lists of formats 2 and 3 of §64 |
| 23 | PRELUDE; order of the checks of steps 1 and 2, the version being the format, 1, 2 or 3; section bytes present at steps 3 and 5 | `Prelude.Bytes`, `capsule.ParsePrelude`, `capsule.Inspect` | `capsule.TestConformanceFixtures`, `TestFrameLengthLowerBounds`, `TestPrecedenceAcrossSteps`, `TestFormatDispatch`; `testdata/vectors/inspect_differential.json` |
| 24 | PUBLIC_HEADER; keys 5 and 6 optional, 1 to 64 extensions each | `capsule.Header`, `EncodeHeader`, `DecodeHeader` (hand-written `headerWire` encode and decode; CDDL checked before the DateKey) | `capsule.TestConformanceFixtures` (exact extension data), `TestDecodeHeaderRejects`, `TestDecodeMapStructure`, `TestDecodeHeaderReportsTheCDDLFirst`, `FuzzDecodeHeader`, `FuzzEncodeImpliesDecode`; mutations *header schema version changed*, *unknown key in PUBLIC_HEADER* |
Docs at spec v0.11 and the draft v0.12: READMEs, SECURITY, traceability, CHANGELOG 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>
6 days ago
| 24.1 | Public note: `datekeys.note` version 1, only in the noncritical array of PUBLIC_HEADER; its data the text in UTF-8, 1 to 1024 bytes, under the rules of the declared author of §29.6; a reader that knows it and finds data that breaks them does not show it and says so; shown as text of the creator that nobody has checked, before the date with the warning that nobody can check who made the capsule; bound to the control by `header_binding` (step 15); the writer writes it only when asked, and warns that it is public (§62.1 rule 23) | `extension.NoteID`, `MaxNoteLen`, `CheckNote`, `NewNote`, `Note`, `Standard`; `capsule.Header.PublicNote`, `Header.UnusableNote`; `EncryptOptions.PublicNote` (`newSealer`); `cmd/datekeys`: `encrypt -note` and its warning, `showNote` (inspect), `present` (decrypt), `noteTitle`, `unusableNote`; `internal/inspectview` (`public_note`, `public_note_unusable`) | `capsule.TestPublicNote` (a note changed after writing: `ERR_HEADER_BINDING` at step 15), `TestPublicNoteRules`, `TestRegisteredExtensionsWhereRegistered`; `cmd/datekeys.TestPublicNoteCLI`; `testdata/vectors/note.json` (`internal/testkit.NoteVectors`, `NoteResult`, `TestVectorFilesAreCurrent`); fixture `format3_note`; mutation *the public note changed in PUBLIC_HEADER*; no test yet of the notice of an unusable note in the CLI |
| 25 | Declared access policy | `capsule.Policy`; `capsule.DecodeHeader` (the value read, up to 2^53−1, must be 0 or 1 before any narrowing); `capsule.Open` step 12 | `capsule.TestDecodeMapStructure` (2, 255, 256, 257, 2^32, 2^53−256 and others), `FuzzDecodeHeader` (seeds 256, 257, 2^32); mutations *access_policy=… with … structure* (four cases), *undefined access_policy*, *access_policy 256 / 257 with a consistent header_binding* |
Spec v0.8.2: corrections from the formal review 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>
2 weeks ago
| 26 | Header binding; a mismatch at step 15 is `ERR_HEADER_BINDING` | `capsule.HeaderBinding`; `capsule.Open` step 15 | `capsule.TestConformanceFixtures`; mutation *PUBLIC_HEADER_A + SEALED_CONTROL_B* |
| 27 | Pre-unlock validation; the age header MAC authenticates only against whoever does not know the file key (anyone recomputes that of OUTER_TIME_AGE once the round is published), and `header_binding` gives internal coherence, not authorship or a date (§55.1) | `capsule.Inspect` (steps 1–8), `agewrap.Stanzas` probe | `capsule.TestMutationCorpus` (no release request for any pre-unlock failure), `FuzzInspect`, `TestTrustModel`; the U and stanza body mutations of §64, whose header MAC is recomputed |
| 28 | Three age files | `capsule.EncryptFiles`, `capsule.Encrypt`, `capsule.Open` | `capsule.TestEncryptRoundTripBothPolicies`, `TestEncryptFilesRoundTrip`, `TestEncryptFilesTimeAndKey` |
Implement capsule format 2 of spec v0.9 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>
1 week ago
| 28.1 | Age file format: the C2SP header grammar (at least one stanza); malformed OUTER_TIME_AGE and PAYLOAD_AGE headers `ERR_INTEGRITY` (steps 5 and 6, or 11 and 17), a malformed INNER_ACCESS_AGE `ERR_POLICY_STRUCTURE_MISMATCH` (step 12); wrong stanza count or type `ERR_POLICY_STRUCTURE_MISMATCH`; in format 2, a plaintext of PAYLOAD_AGE of a length other than P, or whose padding is not zero, `ERR_INTEGRITY` at step 17 | `agewrap.Stanzas` (the header parser of `filippo.io/age`), `capsule.classify`, `capsule.Open` step 12, `capsule.checkPadding` | `capsule.TestMalformedAgeHeaders`, `TestPaddingChecksAtStep17`, `agewrap.TestStanzasProbe`, `FuzzStanzas`; the 10 header-without-stanzas cases of `testdata/vectors/inspect_differential.json` (5 at step 5, 5 at step 6) |
| 29 | PAYLOAD_AGE; `I_PAYLOAD` from a CSPRNG, new for each capsule and never derived; in formats 2 and 3 its plaintext is the content, BODY in format 3, followed by zeros up to P | `capsule.EncryptFiles` and `capsule.Encrypt`, through the sealer of `capsule/encrypt.go` (`age.GenerateX25519Identity`, `copyExactly`, `writeZeros`); `agewrap.PayloadIdentity`, `agewrap.CheckPayloadStanzas` | `agewrap.TestPayloadIdentityStrictness`; `capsule.TestPayloadIdentityReuse`, `TestEncryptWritesFormat2`; mutation *extra stanza in PAYLOAD_AGE* |
| 29.1 | Padding of the payload in formats 2 and 3: codes 1 (`bloque256`) and 2 (`reforzado`, Padmé), no rule without padding, exact integer arithmetic of more than 32 bits, L_MAX = 2^53 − 2^46; the reader checks that the plaintext is P bytes with a zero padding, and delivers the first L | `capsule.Padding` (`Bloque256`, `Reforzado`), `capsule.PaddedLength`, `capsule.MaxPayloadLength`, `capsule.PayloadAgeLength`; `capsule.Open` steps 16 to 18 (`checkPadding`); `EncryptOptions.Padding`, reforzado by default; `datekeys encrypt -padding` | `capsule.TestPaddingRules`, `TestPaddingChecksAtStep17`, `TestPaddingAcrossChunks`, `TestCheckPadding`, `TestEncryptWritesFormat2`; `testdata/vectors/padding.json`, generated by `internal/testkit.PaddingVectors` and checked against a Padmé in `math/big` (`internal/testkit.TestVectorFilesAreCurrent`); fixtures `format2_time_only`, `format2_time_only_bloque256`, `format2_empty_payload`, `format3_bloque256`; the padding mutations of the lists of formats 2 and 3 of §64 |
Docs at spec v0.11 and the draft v0.12: READMEs, SECURITY, traceability, CHANGELOG 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>
6 days ago
| 29.2 | Content of format 3: BODY, a frame of `AREA_LEN`, `SECURITY_LEN` and `HEAD_LEN`, the security area, zero after SECURITY_CBOR, then the head and CONTENT; the area of 32768 bytes when written, signed or not, 65536 only when the creator widens it, and any multiple of 512 from 512 to 65536 when read, the 512 bytes of the writers of v0.10 included; any violation of the frame, L < 12 included, is `ERR_INTEGRITY` at step 17 | `capsule.BodyFrame`, `ParseBodyFrame`, `CheckArea`, `AreaUnit`, `AreaLen`, `LargeAreaLen`, `MaxAreaLen`, `MaxHeadLen`; `capsule.Open` step 17 (`openBody`); `capsule.EncryptFiles` (`EncryptOptions.LargeArea`; `EncryptOptions.TestAreaLen`, with `TestVectors`, for the area of a generator of test vectors) | `capsule.TestBodyFrame`, `TestOpen3`, `TestOpen3Substeps`, `TestEncryptFilesSigned` (a signature that fits keeps 32 KiB with `LargeArea`), `TestAreaChosenAfterSigning`; fixtures `format3_*`: 512 bytes in eight of the nine of v0.10, 1024 in `format3_area_1024`, 32768 in `format3_unsigned`, `format3_signed`, `format3_sealed`, `format3_signed_cms` and `format3_note`; mutations *AREA_LEN …*, *SECURITY_LEN …*, *HEAD_LEN …*, *12 + AREA_LEN + HEAD_LEN = L + 1*, *L < 12: 11*, *a byte of the area not zero*, *the area widened to 64 KiB after signing …* |
| 29.3 | `security`: the outer map of version 1, keys 2 and 3 byte strings that hold the author signature and the seal encoded apart; layers 2 and 3 without a code; it never decides the opening, and a failure of the signature or of the seal, a panic of a parser included, changes only its own verdict; `alg` 1 and 2 and `seal_type` 2 defined, `seal_type` 1 and 3 reserved, `alg` and `seal_type` 4294967295 reserved for tests; empty, 22 bytes, without a signature or a seal; no key 3 beside `alg` 2 | `capsule.EncodeSecurity`, `EncodeSecurityWith`, `EncodeAuthorSignature`, `EncodeSeal`, `DecodeAuthorSignature`, `SecurityKey2`, `SecurityKey3`; `EvaluateSecurityIn` (`recovered`, `setSeal`) in a `SecurityContext`, and `EvaluateSecurity`, without one, as a reader of v0.10; `AlgEd25519`, `AlgCMS`, `SealTypeRFC3161`, `AlgTest`, `SealTypeTest` | `capsule.TestSecurityVerdicts`, `TestEvaluateSecurityIn`, `FuzzEvaluateSecurity` (without a context); `testdata/vectors/security.json`, in the context of a capsule, with the lines of each case (`internal/testkit.SecurityVectors`, `SecurityResultIn`, `TestFormat3VectorFiles`, `TestVectorFilesAreCurrent`); fixtures `format3_security_v2`, `format3_signature_unsupported` and `format3_seal_unsupported` (`alg` and `seal_type` 4294967295); the four mutations of the list of format 3 of §64 that open with their verdicts; no test yet of a panic of a parser |
| 29.4 | Head: version 1, a salt of 32 bytes, the comment and the declared author, 1 to 65535 files in the order of R8 with their layout, SHA-256 and mtime up to 9999, at most 16 MiB; layer 2, layer 3 with R1 and R8, then layer 4 in key order, `ERR_HEAD_INVALID`, and the critical extensions | `capsule.Head`, `File`, `EncodeHead`, `DecodeHead` (`decodeHead`, `checkHeadFields`), `CheckHeadEnd`; `extension.Head` | `capsule.TestHeadRoundTrip`, `TestDecodeHeadLayers`, `TestOpen3Substeps`; `testdata/vectors/head_schema.json` (`internal/testkit.HeadSchemaVectors`); the head mutations of the list of format 3 of §64 |
| 29.5 | Paths: R1 and R8 in layer 3; R2 to R6c, R4b and R10 for each entry, then R7 and R9 over the tree, in layer 4; the key of R7; errors that name the rule and the character, never the text | `internal/pathrule` (`CheckPath`, `CheckTree`, `Key`, `NFD`, `Fold`, `Error`) | `pathrule.TestCheckPath`, `TestCheckTree`, `TestKey`, `TestNFD`; `testdata/vectors/paths.json` and `path_fold.json` (`internal/testkit.PathVectors`, `PathFoldVectors`, `TestFormat3VectorFiles`); the path mutations of §64 |
| 29.5.1 | Tables: Unicode 18.0.0 and the 15 WindowsBestFit tables, pinned by their SHA-256, never the Unicode functions of the platform | `internal/pathrule/gen`, which checks the 19 pinned files in `.cache` and writes `tables.go`; `pathrule.UnicodeVersion`, `TablesDigest` | `pathrule.TestTablesDigest`, `TestProperties`; NFD and folding compared with `golang.org/x/text` outside this module |
| 29.6 | Text of the comment and of the declared author: no control but TAB and LF in the comment, no bidirectional control, separator, byte order mark or noncharacter, the invisibles rule with R4b for each line, no space at the ends of the author; the writer turns CR LF and a lone CR into LF | `pathrule.CheckComment`, `CheckAuthor`; `capsule.EncryptFiles` (`newHead`) | `pathrule.TestTexts`; `testdata/vectors/head_schema.json`; `capsule.TestEncryptFilesRoundTrip`, `TestEncryptFilesRejects` |
Docs at spec v0.11 and the draft v0.12: READMEs, SECURITY, traceability, CHANGELOG 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>
6 days ago
| 29.7 | Verdicts X, F0 to F6 and S0 to S5, for each part the first row of the table that holds, with the texts of the table; the name of a certificate between « and », shown when it meets the rules of the declared author, has at most 64 code points and no two spaces in a row, and its SHA-256 otherwise; the holder by `givenName` and `surname` before `commonName`, the issuer by `commonName` or `organizationName`; after F6, a line for each required signer with the authority of its seal, and «DateKeys no comprueba quién emitió los sellos.» when one says before the date; a foreign signer apart, with its result in Spanish; before the date only by a valid seal with t + accuracy < round_time, with its warning; an mtime later than a valid seal shown as an inconsistency (SHOULD); the presentation: the verdicts first and last, the labels of the text of the creator, TABs expanded, pieces of at most W − 3 columns behind `│ ` with the width counted by excess, the lines of the verdicts in rows of at most W − 3 columns, broken at the last space that fits, each row after the first behind ` ↳ `, and warnings of risky names | `capsule.Verdict`, `Verdict.Text`, `Verdicts.Lines`, `Verdicts.SealedAt`, `Detail`, `SignerLine` (`quoted`, `instant`, `resultText`), `holderText`, `MaxNameLen`; `internal/cms` `Cert.Holder`, `Cert.IssuerName`; `capsule.Open` step 17.6 (`newSecurityContext`, `openBody`), `OpenOptions.AuthorKeys` (F3), `OpenOptions.Accept` (the verdicts before step 18); `cmd/datekeys.present` (`writeVerdicts`, `rows`, `contMark`, `writeCreator`, `pieces`, `expandTabs`, `outputWidth`, `termWidth`, `risks`) | `capsule.TestSecurityVerdicts`, `TestEvaluateSecurityIn` (the lines of F3 and F4), `TestEvaluateCMS` (the lines of F6 with the authority of each seal and the warning, a late seal without it, a foreign signer), `TestEvaluateSeal` (the line of S4), `TestIssuerTextFiltered`, `TestCMSVectors`; the lines of the records of the fixtures (`capsule.TestConformanceFixtures`) and of `testdata/vectors/security.json` (`internal/testkit.TestFormat3VectorFiles`); `cmd/datekeys.TestRows`, `TestPresent`, `TestMTimeAfterSeal`, `TestAuthorSignRoundTrip`, `TestEncryptDecryptRoundTrip`, `TestDecryptFormat3Fixtures`; the names of §64 of v0.12 (two spaces, more than 64 code points, ESC, U+202E, a byte order mark, `givenName` and `surname` with a NIF in `commonName`, an issuer without text): `security_cms.json`, *pending* |
| 29.8 | Author signature, what is signed: `payload_commit`; `CONTROL_SIG`, the control with `payload_commit` in place of `I_PAYLOAD` and L at zero; `control_commit`, `head_digest`, `signers_digest`, and `AUTHOR_MESSAGE`, ASCII of 99 bytes, with its code of 8 characters; never the area or the padding, so the area can grow after signing; every value recomputed from the opened capsule | `capsule.PayloadCommit`, `ControlCommit`, `HeadDigest`, `SignersDigest`, `AuthorMessage`, `AuthorMessagePrefix`, `AuthorMessageSize`, `AuthorCode`; `capsule.Open` step 17.6 (`newSecurityContext`); the writer signs once the control is final, in the `prepare` that `EncryptFiles` gives `sealer.write` (`sealer.security`) | `capsule.TestAuthorMessage` (99 bytes, the code, `control_commit` without L and with `I_PAYLOAD`), `TestSignedFixtureVerdicts` (another control or head: F2), `TestAreaChosenAfterSigning` (signed once); `TestConformanceFixtures` (`checkSignature3`: the commitments, `AUTHOR_MESSAGE` and its code in the records of `format3_signed`, `format3_signed_cms` and `format3_sealed`); mutations *the signature of alg 1 transplanted to another capsule …*, *the area widened to 64 KiB after signing …* |
| 29.9 | Signature with a key of one's own, `alg` 1: Ed25519 of `AUTHOR_MESSAGE`; a key or a signature of another length is F1; valid only with A canonical and not of small order, `sig[63] & 0xE0` = 0, S < ℓ and the equation without the cofactor, F2 otherwise; F4, or F3 with a key the person saved | `internal/ed25519strict` (`Verify`, `Canonical`, `SmallOrder`, `SmallOrderPoints`, `OnCurve`), around `crypto/ed25519`; `capsule.evaluateSignature`; `EncryptOptions.AuthorKey`, the interface `capsule.AuthorKey` | `ed25519strict.TestVectors`, `TestVerifyRejectsWhatStdlibAccepts`, `TestCanonical`, `TestSmallOrderTable`, `TestOnCurve`; `testdata/vectors/ed25519_strict.json`, the cases of «Taming the many EdDSAs» (`internal/testkit.Ed25519StrictVectors`); `capsule.TestEvaluateSecurityIn`, `TestEncryptFilesSigned`, `TestEncryptFilesSignatureChecked`, `TestSignedFixtureVerdicts`; fixture `format3_signed`, whose signature `TestConformanceFixtures` makes again from its seed; mutations *a signature of alg 1 that does not verify …* and those of `alg` 1 of the list of v0.11: altered, removed, made again with another key, transplanted, a key of 31 bytes and a signature of 65 |
| 29.10 | Signature with certificates, `alg` 2: a detached CMS signature with the CAdES profile; `SIGNERS`, 1 to 16 SHA-256 of certificates in strictly ascending order; the form in its order (F1), with the version of a `SignerInfo` by its `sid`, attributes counted by attribute, a `signing-certificate` beside the v2 that decides nothing, an `ESSCertIDv2` with SHA-256 written, the parameters of PSS, object identifiers compared by the bytes of their DER and a SET OF that repeats an element; the closed table of algorithms; the profile of the certificate field by field, its key RSA with NULL parameters and an odd modulus, or EC uncompressed on P-256, P-384 or P-521; the result of each required signer in its order: absent, not verifiable, invalid (a key of another scheme than the algorithm included), without seal, invalid seal, out of validity, valid; F2, F5 or F6; no key 3; never who issued a certificate or whether it was revoked | `internal/der` (`Check`, `Split`, `Content`, `SetOfSorted`, `ParseTime`); `internal/cms` (`ParseSignature`, `SignedData`, `SignerInfo`, `SignerInfo.Check`, `ParseCert`, `Cert`, `Cert.ValidAt`, `ErrForm`, `ErrAlgorithm`), with the standard library only; `capsule.EncodeSigners`, `decodeSigners`, `MaxSigners`, `evaluateCMS`, `signerLine`; `EncryptOptions.CMSSigner`, the interface `capsule.CMSSigner`; `internal/cms/cmstest`, which makes certificates, signatures and tokens for the tests | `cms.TestSignatureAlgorithms` (RSA PKCS #1 v1.5 and PSS, SHA-256 to SHA-512, ECDSA on P-256, P-384 and P-521, a `sid` by `subjectKeyIdentifier`), `TestCoSignature`, `TestSignatureNotVerifiable`, `TestSignatureForm`, `TestSignatureStrictness`; `der.TestCheck`, `TestSetOfSorted`, `TestDepth`, `TestParseTime`, `TestSplit`; `capsule.TestEvaluateCMS` (a required signer absent, without seal, a key 3 beside it, another head, `SIGNERS` out of order or empty), `TestEncodeSigners`, `TestEncryptFilesCMSAndSeal`, `TestCMSVectors` (`testdata/vectors/security_cms.json`); fixture `format3_signed_cms`; the cases of `alg` 2 of §64 of v0.12 (a certificate out of validity with a valid TSA, the profile of the certificate, identifiers, repetitions, keys of another scheme or compressed): `security_cms.json`, *pending* |
| 29.11 | Time seal, RFC 3161: the CAdES-T of each signer of `alg` 2, over its signature value, or `seal_type` 2 in key 3, over `SEAL_SUBJECT`, without a signature or with `alg` 1; `SIG_PART`; the profile of the token in its order: the form (S2), with the `TSTInfo` in DER field by field, `genTime` in UTC with Z, `accuracy` of 0 to 2³¹ − 1 seconds and minimal millis and micros, `ordering` only TRUE, nothing after the last field, and `crls` deciding nothing; the algorithms (S1); the verification (S3), a `messageImprint` of another length included; S4 when t + accuracy < round_time, S5 otherwise | `internal/cms` (`ParseToken`, `Token`, `Token.Check`, `Token.ImprintIsSHA256`, `parseTSTInfo`, `parseAccuracy`); `capsule.SigPart`, `SealSubject`, `SealTypeRFC3161`, `evaluateSeal`, `signerLine`; `EncryptOptions.Sealer`, the interface `capsule.Sealer` | `cms.TestTokenOverSignature`, `TestTokenFailures`, `TestTSTInfoStrict` (a negative accuracy, millis of 0, a `genTime` with an offset or a trailing zero, `ordering` FALSE written, a field after the last); `capsule.TestEvaluateSeal`, `TestEncryptFilesCMSAndSeal`, `TestCMSVectors`; fixture `format3_sealed`; the cases of `seal_type` 2 of §64 of v0.12: `security_cms.json`, *pending* |
| 29.12 | Author keys of `alg` 1: a seed of Ed25519; the public key `dkauthor1…`, 67 characters in lower case, and the secret key `DKAUTHOR-SECRET-KEY-1…`, 79 in upper case, refused in another case, length or prefix, or with padding bits that are not zero; a public key canonical, a point of the curve and not of small order; a file of a secret key encrypted with age and a passphrase, scrypt of logN 16; a key that the person saved gives F3 with her label | `authorkey` (`Generate`, `NewFromSeed`, `Key.Public`, `Key.Sign`, `Key.Secret`, `Key.String` and `Key.GoString`, which hide the secret key, `Key.Clear`, `PublicString`, `ParsePublic`, `ParseSecret`, `Marshal`, `Encrypt`, `Read`, `WorkFactor`); `capsule.OpenOptions.AuthorKeys`; `cmd/datekeys`: `author keygen` and `author public` (`authorKeygen`, `authorPublic`, `loadAuthorKey`, `readPass`: the passphrase from a file or the standard input), `encrypt -sign` (`announced`), `decrypt -expect-author` | `authorkey.TestStrings` (a printed key never shows its secret), `TestParseRejects` (y = 2, off the curve), `TestFiles`; `ed25519strict.TestOnCurve`; `capsule.TestEncryptFilesSigned` (F3 with the key saved); `cmd/datekeys.TestAuthorSignRoundTrip` |
| 30 | PAYLOAD_AGE is a complete age file | `filippo.io/age` public API only | `capsule.TestInteropAgeOpensPayload` (`-tags interop`, official `age` CLI) |
Implement capsule format 2 of spec v0.9 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>
1 week ago
| 30.1 | CONTROL_CBOR ↔ PAYLOAD_AGE binding; in format 2, L and the code fix the length and the padding of the plaintext, which adds determinism, not authenticity | `agewrap.PayloadIdentity` | mutations *SEALED_CONTROL_A + PAYLOAD_AGE_B*, *padding code 2 changed to 1, with L = 78000*, *payload_length L - 1, the last byte of the content not zero*; `capsule.TestTrustModel` (another L of the same P); `agewrap.TestPayloadIdentityStrictness` |
| 31 | CONTROL_CBOR; its schema version is the format of its capsule, another being `ERR_UNSUPPORTED_VERSION` at step 14; keys 4 and 5 optional; keys 6, `payload_length` (8 bytes, at most L_MAX), and 7, `padding` (1 or 2), required in versions 2 and 3 and not defined in version 1, `ERR_NON_CANONICAL_CBOR`; a control of version 2 or 3 without extensions is 103 bytes; extension entry rules (`extension_id` valid UTF-8 of at least 1 byte), elements in strictly ascending bytewise order of `extension_id` (§54) | `capsule.Control` (`PayloadLength`, `Padding`), `EncodeControl` and `DecodeControl`, which take the format (hand-written `controlWire` encode and decode); `extension` | `capsule.TestConformanceFixtures` (exact extension data), `TestDecodeControlRejects`, `TestControlLengthIsConstant`, `TestDecodeMapStructure`, `FuzzDecodeControl` (the three formats), `FuzzEncodeImpliesDecode`; the `control_cbor` block of `testdata/vectors/cbor.json`, with `format`; mutations *unknown critical CONTROL_CBOR extension*, and those of keys 6 and 7 in the third list of §64 |
| 32 | `time_only` | `capsule.Encrypt`; `agewrap.TimeRecipient` | fixtures `time_only*`, `empty_payload`; `capsule.TestInteropTleOpensSealedControl` (`-tags interop`, official `tle` CLI) |
| 33 | `time_and_key`: X25519 stanzas only, one per recipient, one or more in format 1 and exactly 16 in formats 2 and 3 | the sealer of `capsule/encrypt.go` (`seal`); `agewrap.AccessIdentity`, `agewrap.AccessSlots` | fixtures `time_and_key_*`, `format2_time_and_key_*`, `format3_time_and_key_portable`; `capsule.TestEncryptRoundTripBothPolicies`, `TestInnerHasSixteenStanzas`; `agewrap.TestAccessSlots` |
| 34 | SEALED_CONTROL | `capsule.Encrypt`; `capsule.Open` step 11 | `capsule.TestConformanceFixtures` |
Spec v0.8.2 refinements: error precedence, trust model, strict order 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>
2 weeks ago
| 35 | tlock strict mode; exactly two stanza arguments compared as strings (§63 step 8) | `agewrap.TimeRecipient`, `agewrap.TimeIdentity`, `agewrap.CheckTimeStanzas` (pinned parameters only, exact stanza arguments) | `agewrap.TestTimeIdentityStrictness`, `TestInteroperabilityWithTlockLibrary`, `TestTimeIdentityRelease`; `capsule.TestTlockStanzaArgumentComparison` |
Implement capsule format 2 of spec v0.9 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>
1 week ago
| 36 | Policy ↔ structure; `time_only`: a plaintext that starts with the age intro line is a mismatch, any other is read as CONTROL_CBOR at step 14; `time_and_key`: a malformed age header, or two stanzas with one argument after the type and the same argument (a repeated X25519 ephemeral share), is a mismatch, and so is, in format 2, a number of stanzas other than 16 | `capsule.Open` step 12 (`looksLikeAge`, `agewrap.Stanzas`), `agewrap.CheckAccessStanzas` (with the slots of the format) | mutations *access_policy=…* (four cases), *non-X25519 stanza in INNER_ACCESS_AGE*; `capsule.TestMalformedAgeHeaders`; `agewrap.TestMalformedX25519Stanzas` (*repeated stanza*), `TestAccessSlots`; `capsule.TestFormat1Compatibility`; mutations *INNER_ACCESS_AGE with 15 stanzas*, *INNER_ACCESS_AGE with 17 stanzas* |
| 36.1 | Authenticity semantics | documented in `README.md`, `SECURITY.md` | — (a property the protocol does not provide) |
Implement capsule format 2 of spec v0.9 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>
1 week ago
| 37 | X25519 recipient V1; the writer rejects a recipient that is not canonical (bit 255 set, or u ≥ p) or of low order, and MAY reject a point of the twist | `age.X25519Recipient`; `agewrap.X25519IdentityFromRaw`, `agewrap.CheckX25519Recipient` (run by `capsule.Encrypt`); the twist check is not implemented | `agewrap.TestRawKeys`, `TestNonCanonicalRecipients`; `capsule.TestEncryptRejectsInvalidOptions` |
| 38 | Portable Access Key | `EncryptOptions.NewPortableKey` (fresh `I_ACCESS` per capsule; no API accepts an existing one); `accesskey.AccessKey` | `capsule.TestPortableKeysAreNeverReused` |
Docs at spec v0.11 and the draft v0.12: READMEs, SECURITY, traceability, CHANGELOG 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>
6 days ago
| 38.1 | Key of words: one more X25519 credential of `time_and_key`, among the 16; the normalization: NFD with the tables of Unicode 18.0.0, without U+0300 to U+036F, the simple lower case of each code point, split by the spaces of the list; PBKDF2-HMAC-SHA256 of 600 000 rounds, salted with the chain hash, the round and `capsule_id`, into a raw X25519 identity; the writer requires at least 6 words, counting only different words of 3 or more letters, and refuses controls, ignorables and unassigned code points; a reader may ask for the words instead of a `.dkk` | `wordkey` (`Normalize`, `Check`, `Key`, `Identity`, `Rounds`, `MinWords`, `MinLetters`), with `pathrule.NFD`, `Lower`, `DefaultIgnorable` and `Assigned`; `capsule.EncryptOptions.Words` (`accessRecipients`; `sealer.write` derives the identity once `capsule_id` is drawn); `cmd/datekeys`: `-words` and `-words-file` of `encrypt` and `decrypt` (`wordsText`), the words of `decrypt` salted with what `capsule.Inspect` gives | `wordkey.TestNormalize`, `TestCheck`, `TestKeyVector` (the vector of §38.1); `capsule.TestEncryptFilesWords` (the words of another `capsule_id` do not open); `cmd/datekeys.TestKeyOfWords` |
| 39 | Recipients of INNER_ACCESS_AGE: in formats 2 and 3 from 1 to 16 credentials, a dummy in each slot left (a fresh public key whose private key is dropped at once), the 16 in a uniformly random order; which slots are dummies is recorded only in the official vectors | `capsule/encrypt.go` (`accessRecipients`, `fillSlots`, `permute`); `agewrap.AccessIdentity` | `capsule.TestInnerHasSixteenStanzas`, `TestDummyRecipients`, `TestStanzaOrderIsUniform`, `TestCredentialBounds`, `TestFixtureRecipients`, `TestEncryptRoundTripBothPolicies`; `TestConformanceFixtures` (the stanza each credential opens, `access_key_stanza` and `identity_stanzas` in the records) |
Spec v0.8.2 refinements: error precedence, trust model, strict order 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>
2 weeks ago
| 40 | `.dkk` framing; `BODY_LEN` in 1..16 MiB (0 is `ERR_INTEGRITY`); order of the frame checks | `accesskey.Encode`, `accesskey.Decode` (the body buffer grows with the data read; every buffer holding the body is wiped) | `accesskey.TestDecodeRejects`, `TestDecodePrecedence`, `TestDecodeShortBodyAllocatesLittle`, `TestEncodeAndDecodeLeaveNoStaleMaterial`, `FuzzDecode` |
| 41 | `.dkk` BODY_CBOR | `AccessKey.MarshalBody`, `accesskey.DecodeBody` (hand-written `bodyWire` encode and decode) | `accesskey.TestFixtures`, `TestDecodeBodyStructure` |
| 42 | `credential_id` | `capsule.Encrypt` (16 bytes from `crypto/rand`) | `capsule.TestPortableKeysAreNeverReused` |
| 43 | `verification_metadata` | `accesskey.Verification`, `decodeVerification` (the closed map `{0: capsule_digest}`); `capsule.Open` (`checkCapsuleDigest`, seekable readers) | `accesskey.TestDecodeRejects` *empty verification map*, `TestDecodeBodyStructure`; mutation *capsule_digest of the .dkk does not match* |
| 44 | Application extensions in `.dkk` | `AccessKey.Critical/Noncritical`; `capsule.Open` (`checkAccessKey`, `Opened.UnusableAccessKeyExtensions`) | `accesskey.TestEncodeRejectsAbsenceAsEmptyMap`, `TestDecodeBodyExtensionRules`, `TestFixtureWithExtension`; `capsule.TestAccessKeyFixtureWithExtension`; mutation *known critical .dkk extension with invalid data* |
Docs at spec v0.11 and the draft v0.12: READMEs, SECURITY, traceability, CHANGELOG 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>
6 days ago
| 44.1 | The extension `datekeys.capsule` of a `.dkk`, noncritical: the note, the DateKey and an optional locator, an age file with one tlock stanza for the round of that DateKey, unusable for another round or chain; its plaintext, 1 to 8 addresses, `I_SOBRE`, the header of the envelope, the digest and the size of the rest, `capsule_digest` and a zero padding of at least one byte, of exactly 4096 bytes or the least multiple that holds it; the envelope, the `.dkc` in age split into the header and a rest without a mark, alone or inside a host at an offset; the addresses, ASCII of RFC 3986, read without decoding: `https` with a host of letters, digits and hyphens or a public IP outside the special-purpose blocks of IANA, no local name, no dot segment, or `ipfs` with a CID v1 in base32; a reader rejects each address that breaks them and uses the others; the rest and the `.dkc` checked by their digests; a writer never writes a rejected address and decodes what it writes | `locator` (`Info`, `Info.Extension`, `ParseInfo`, `Info.OpenLocator`, `Standard`; `Locator`, `Locator.Marshal`, `Unmarshal`, `Locator.Usable`, `PlaintextLength`, `Block`, `MaxAddresses`, `MaxURILen`, `MaxHeaderLen`; `Seal`, `Open`; `NewEnvelope`, `Locator.OpenEnvelope`, `Hide`, `Locator.RestIn`; `Address`, `Address.Host`, `CheckURI`), which downloads nothing; `extension.CapsuleID`, `extension.Standard` (`ValidateCapsule`); `accesskey.AccessKey.MarshalBody` (`extension.CheckWrite`); `spec/datekeys.cddl` (`capsule-locator`, `capsule-address`) | `locator.TestEnvelope`, `TestLocatorPlaintext`, `TestAddresses` (the blocks of IANA, NAT64, mapped and 6to4 addresses, local names, characters outside RFC 3986, dot segments, CIDs that do not decode), `TestSealedLocator` (another round or release: unusable), `TestInfo`, `TestUsableAddresses`, `TestLeastMultiple`, `TestPaddingBoundaries`, `TestLocatorVectors` (`testdata/vectors/locator.json`); `capsule.TestRegisteredExtensionsWhereRegistered` (never in a capsule); the cases of §64 of v0.11 and v0.12 for `datekeys.capsule` that `locator.json` lacks: *pending* |
| 45 | Release API | `provider.ReleaseSource` interface only (server out of scope, plan §2) | — |
| 46 | Release Queue | out of scope (server) | — |
| 47 | Release Cache | every release is verified again: `capsule.Open` step 10 and `agewrap.TimeIdentity` | mutations *release of another round* |
Spec v0.8.2: second-round corrections from the formal review 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>
1 week ago
| 48 | Multi-relay; a network source verifies every response with the rules of §63 step 10 and discards the invalid ones: none valid is `ERR_RELEASE_UNAVAILABLE` at step 9, and so is any other failure of a source, with no other code | `provider/drand.Client` (race, first *verified* release wins; the failure of each relay kept as text only, a context that ended detectable with `errors.Is`), the `provider.ReleaseSource` contract, `capsule.Open` (step 9 keeps only the text of a source error with another code or none) | `drand.TestRaceWaitsForAValidSignature`, `TestRejectMalformedRelayResponses`, `TestFetchErrorHasOneCode`, `TestUnavailabilityAndCancellation`; `capsule.TestReleaseFromANetworkSource`, `TestReleaseSourceErrorsAtStep9` |
| 49 | Direct recovery from the provider | `provider/drand` | `drand.TestLiveRelays`, `capsule.TestLiveLifecycle` (`-tags integration`) |
| 50 | Historical release dependency | documented in `README.md` | — |
Spec v0.8.2: corrections from the formal review 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>
2 weeks ago
| 51 | Quicknet release verification; order and codes of §63 step 10: the round (`ERR_ROUND_MISMATCH`), then the signature, the canonical encoding of a point of G1 other than the identity (§12.2) that verifies as the BLS signature of the round (`ERR_RELEASE_INVALID`); those codes for a release supplied directly, a network source discarding an invalid one at step 9 (`ERR_RELEASE_UNAVAILABLE`) | `provider.Verify` | `provider.TestVerifyPublishedReleases`, `TestVerifyRejects` (x + p, the point at infinity alone, with a payload or with the sort flag, no compression flag, the negated signature), `TestVerifyUsesThePinnedKeyOnly`; `capsule.TestReleaseFromANetworkSource`; mutations *DateKey A + release of round B*, *release of another round*, *release signature …* |
| 52 | DNS / MITM | `provider/drand` (no redirects, bounded responses, BLS) | `drand.TestRedirectsAreNotFollowed`, `TestRejectMalformedRelayResponses`, `TestRandomnessMustMatchWhenPresent` |
| 53 | Harvest now, decrypt later | `cmd/datekeys` warning beyond one year | `cmd/datekeys.TestLongHorizonWarning` |
Spec v0.8.2: second-round corrections from the formal review 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>
1 week ago
| 54 | Extensions: data absent or a non-empty opaque byte string, never decoded; 1 to 64 per array; `extension_id` of at least 1 byte; `extension_version` ≤ 2^32−1; elements in strictly ascending unsigned bytewise order of the UTF-8 bytes of `extension_id` (a proper prefix first, never UTF-16 code units or a collation), so one `extension_id` per array, and none in both arrays; a known extension in an object or array it is not registered for is unknown there, its data never interpreted | `extension.New`, `Canonical`, `EncodeArray` (refuses, through `codec.Encoder.Fail`, an array that `DecodeArray` rejects), `DecodeArray` (64 entries checked on the array head, explicit key 2 check), `CheckDisjoint` (linear merge), `CheckCritical`, `CheckNoncritical`, `Unusable`; `Object`, `Array`, the optional `Placement` of a `Registry`, `KnownIn`, and `CheckCriticalIn` and `CheckNoncriticalIn`, which `capsule` runs at steps 4, 9.a and 14 | `extension.TestNew`, `TestData`, `TestCanonicalSorts`, `TestOrderIsUnsignedBytewise`, `TestCanonicalRejects`, `TestEncodeArrayRejects`, `TestDecodeArrayRejects`, `TestCheckDisjoint`, `TestCheckDisjointIsLinear`, `TestCheckCritical`, `TestCheckNoncritical`, `TestPlacement`, `FuzzDecodeArray`; `capsule.TestKnownCriticalExtensions`, `TestUnusableNoncriticalExtensions`, `TestExtensionPlacement`; mutations *unknown critical … extension*, *known critical … extension with invalid data*, *extension_version above 2^32-1*, *null extension data* |
| 55 | Auxiliary integrity | `capsule_digest` treated as UX only | — |
Implement capsule format 2 of spec v0.9 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>
1 week ago
| 55.1 | Trust model: who writes each section, from which step and by what it is bound, what it never proves | no code of its own: `header_binding` (step 15), the age header MACs (steps 11, 13 and 17), `capsule_id` and `capsule_digest` (step 9.a) | `capsule.TestTrustModel` (a capsule forged from the public bytes of `time_only.dkc` or `format2_time_only.dkc` opens; a control that declares L + 1 over a zero padding byte opens to another content; edited PUBLIC_HEADER data passes steps 1 to 8 and fails step 15; other `.dkk` extension data opens the capsule) |
| 55.2 | Privacy: what a capsule reveals before and after the date and what format 2 hides; the reader reports the format | no code of its own: format 2 (rows 22, 29.1, 31 and 39); `Inspection.Prelude.Format`, `Opened.Format`, `datekeys inspect` (`format`), and `datekeys decrypt`, which warns about format 1 | `capsule.TestControlLengthIsConstant`, `TestSealedControlLength`, `TestFormatDispatch`; `cmd/datekeys.TestDecryptFixtures`, `TestEncryptDecryptRoundTrip`, `TestInspectJSONGoldens` |
| 56 | Atomic plaintext output; in format 2 the reader never writes the padding and never presents the content as valid before step 17 ends, and a reader that writes in streaming signals the error of step 17 so that what it wrote is discarded; in format 3 no file, head or verdict is presented before step 18, and a reader that writes to a file system stages the files in a place of its own, moves them at step 18 and removes them on failure, inside a folder it creates and that did not exist, following no link and overwriting nothing, and creates nothing for a capsule without files | `capsule.Open` contract (the first L bytes only, `checkPadding`); `capsule.Sink`, `ErrSinkRequired`; `cmd/datekeys.writeAtomic`, `cmd/datekeys.dirSink` (`os.Mkdir`, `os.OpenRoot`, `O_EXCL`, `Root.Rename`, `RemoveAll`) | `cmd/datekeys.TestOutputNotPublishedOnFailureOrOverwrite`, `TestDecryptFailuresLeaveNothing`, `TestDecryptFormat3LeavesNothing`, `TestDecryptFormat3Fixtures`; `capsule.TestPaddingChecksAtStep17` (at most the content is written), `TestPaddingAcrossChunks`, `TestOpen3Substeps`, `TestOpen3SinkFailures` |
| 57 | Parser limits, MUST for encoders and decoders; frame lengths of 0 or above the limits and objects above their frame → `ERR_INTEGRITY` on encode and decode, a field of the wrong CBOR type → `ERR_NON_CANONICAL_CBOR` before its own code, CDDL violations → `ERR_NON_CANONICAL_CBOR`, Provider Profile names and public key → `ERR_UNKNOWN_PROFILE`; implementation limits not normative; L and P are not frame lengths, and nothing is reserved according to them, nor according to `AREA_LEN`, `SECURITY_LEN`, `HEAD_LEN`, `size`, `start` or `end` in format 3 | `capsule.MaxPublicHeaderLen` (`ParsePrelude`, `EncodeHeader`, `DecodeHeader`), `MaxSealedControlLen` (`ParsePrelude`, `Encrypt`), `accesskey.MaxBodyLen` (`Decode`, `DecodeBody`, `MarshalBody`), `extension.MaxExtensions`, `MaxDataLen`; the bounds each schema passes to `codec.Decoder` (`Map`, `Array`, `Uint`, `Bstr`, `Text`), with lengths checked against the remaining input before any copy; `profile.Validate` | `capsule.TestDeclaredLengthIsNotAllocated`, `TestOpen3DeclaredLengths` (the reads of BODY, `readN`); mutations *…_LEN above the limit*, *65 extensions in one array*; `capsule.TestHeaderLimit`, `TestHugeExtensionArraysAreRejected`; `accesskey.TestDecodeRejects` *body length above the limit*, `TestBodyLimit`; `profile.TestValidateRejectsTamperedProfiles`, `TestIntegerRanges`; `codec.TestDecoderRejects` |
| 58 | Canonical CBOR and the protocol's CBOR profile (major types 0, 2, 3, 4, 5; unsigned integer keys; integers ≤ 2^53−1) | `codec`, without reflection or dependencies: `Encoder` (shortest heads, valid UTF-8, nil byte strings as empty, never `null`; a sticky first error, which `Fail` lets a schema encoder record), `Decoder` (strict cursor: profile major types only, shortest heads, definite lengths, strictly ascending unsigned keys per map, valid UTF-8, no trailing bytes), `Unmarshal` (re-encoding comparison), `Walk` (the profile only, for vectors, fuzzing and diagnostics); `codec.MaxSafeUint`; the profile covers the head of extension data only | `codec.TestDecoderAccepts`, `TestDecoderRejects` (negative integer, tag, float, simple values, indefinite lengths, non-shortest heads, text key, key order, UTF-8), `TestUnmarshalRejectsNonCanonical`, `TestWalk`, `TestEncoderAndWalkAgreeWithAReference` (against `internal/cbortest`), `TestSharedVectors`, `FuzzDecoder`, `FuzzUnmarshal`, `FuzzWalk`, `FuzzEncodeImpliesWalk`; `extension.TestData`; `internal/testkit.TestSchemaVectors`; `testdata/vectors/cbor.json` (generic vectors walked with `codec.Walk`, and one block per schema: Provider Profile, PUBLIC_HEADER, CONTROL_CBOR, `.dkk` body, `verification_metadata`, extension), generated by `internal/testkit.CBORVectors` |
| 58.1 | Absent optional fields are omitted; `h''` and `null` never stand for absence | `extension.Canonical` (nil for empty), `extension.DecodeArray` (empty array, empty data), re-encoding check, `accesskey` verification map | `codec` *empty optional array present*; `accesskey` *empty extension array*, *empty verification map*, *null verification*, *empty data*, *null data*; mutation *empty extension data (h'')* |
| 59 | Supply-chain security | pinned `go.mod`/`go.sum`, `.gitea/workflows`, `scripts/check.sh`, `.goreleaser.yaml`, `SECURITY.md` | CI jobs `vuln`, `sbom`, `verify` |
| 60 | Conceptual Go interfaces | `provider.ReleaseSource`, `provider.Verify`, `datekey.Resolve`, `datekey.RoundTime` | — |
| 61 | `time_only` encryption flow, format 3: the files measured, hashed, sealed and read again | `capsule.EncryptFiles` | `capsule.TestEncryptFilesRoundTrip`, `TestEncryptFilesLengths`, `TestEncryptFilesChangedFile`, `TestSealedControlLength`; the live test (`-tags integration`) |
| 62 | `time_and_key` encryption flow, format 3: 16 recipients, and the SEALED_CONTROL_LEN of an INNER_ACCESS_AGE of 16 stanzas | `capsule.EncryptFiles` | `capsule.TestEncryptFilesTimeAndKey`, `TestEncryptRoundTripBothPolicies`, `TestPortableKeysAreNeverReused`, `TestSealedControlLength` |
Docs at spec v0.11 and the draft v0.12: READMEs, SECURITY, traceability, CHANGELOG 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>
6 days ago
| 62.1 | Writer rules: format 3 only, formats 1 and 2 being written only by a generator of test vectors; an instant after the clock of the writer; from 1 to 16 credentials, none twice, canonical and not of low order; dummies and a random order; `capsule_id`, `I_PAYLOAD`, `I_ACCESS`, `credential_id`, dummies and order from a CSPRNG, `I_PAYLOAD` and dummies never reused or derived; L known before sealing, at most L_MAX, code 1 or 2; SEALED_CONTROL_LEN exact, measured by a provisional seal and checked; limits; on error, the output is discarded; in format 3, the area of 32768 bytes, signed or not, and 65536 only when the creator widens it once the signatures are made, without signing again for it, a capsule refused when they do not fit, `SECURITY_CBOR` always and empty without a signature or a seal, another area or `SECURITY_CBOR` only from a generator of test vectors (rule 13), a head with a fresh salt, a file or a comment, the order of R8, the layout and the SHA-256 of what is written, at most 16 MiB, paths and texts refused with the rule and the character, the mtime taken at load and omitted out of range, the three CBOR objects decoded with the rules of the reader (MUST), and files that must not change between the two readings; with a signature or a seal, each verified with the rules of the reader before writing, never one that gives F1, F2, F5, S1, S2 or S3 (rule 19), `AUTHOR_MESSAGE` given as text and its code shown before each signature, `SIGNERS` closed before the first (rule 20), a CAdES-T for each signer of `alg` 2 (rule 21), the seal of `seal_type` 2 over `SEAL_SUBJECT` after the signature (rule 22), and no secret on disk while waiting for them (rule 25); the public note only when asked for, with the rules of the declared author and a warning (rule 23); the rules of §38.1 for a key of words, and for a `.dkk` with a locator the rest stored before the `.dkk` is written (rule 24). SHOULD: code 2 by default, self-checks, wiping | `capsule.EncryptFiles` (`newHead`, `readSource`, `selfCheckHead`) and `capsule.Encrypt`, through their sealer (`EncryptOptions.Padding`, `accessRecipients`, `fillSlots`, `copyExactly`, `selfCheckHeader`, `selfCheckControl`, `selfCheckInner`, `selfCheckPayload`); `agewrap.CheckX25519Recipient`; `EncryptOptions.TestVectors` for `Encrypt` of format 2, and `internal/testkit.Build`, generators of test vectors (§70); in format 3, `capsule.AreaLen`, `LargeAreaLen` and `EncryptOptions.LargeArea`, the area decided by `EncryptFiles`, in the `prepare` that it gives `sealer.write`, once `sealer.security` has signed and sealed and checked both with `EvaluateSecurityIn` (rules 13, 19, 21 and 22), `EncryptOptions.AuthorKey`, `CMSSigner` and `Sealer`, a typed nil in one of them an error (`newSealer`, `isNil`), nothing written to `dst` before they return and the control and `I_PAYLOAD` kept in memory (rule 25); `EncryptOptions.TestAreaLen`, with `TestVectors`, the area of 512 bytes of the fixtures of v0.10 (rule 13); `EncryptOptions.PublicNote` and `extension.CheckWrite` (rule 23, §72); `EncryptOptions.Words` and `wordkey.Check`, and `locator.NewEnvelope`, `Locator.Marshal` and `Info.Extension` (rule 24); `cmd/datekeys`: `announced` (rule 20) and the warning of `-note` (rule 23) | `capsule.TestEncryptFilesRejects`, `TestEncryptFilesChangedFile`, `TestEncryptIsForTestVectors`, `TestEncryptFilesHeadCritical`, `TestEncryptRejectsInvalidOptions`, `TestCredentialBounds`, `TestEncryptSourceLength`, `TestEncryptSelfCheck`, `TestSealedControlLength`, `TestPayloadIdentityReuse`, `TestStanzaOrderIsUniform`, `TestDummyRecipients`, `TestPortableKeysAreNeverReused`; `cmd/datekeys.TestEncryptRefusesPaths`; rules 13 and 19 to 25: `capsule.TestEncryptFilesSigned`, `TestEncryptFilesSignatureChecked` (nothing written), `TestEncryptFilesCMSAndSeal` (a signature without a required signer, or without seals, is not written), `TestAreaChosenAfterSigning` (the area widened once signed, signing once; without a signature, 32 KiB with `LargeArea`), `TestWriterOptionsChecked`, `TestPublicNoteRules`, `TestRegisteredExtensionsWhereRegi
| 63 | Decryption flow; step 2 accepts the formats 1, 2 and 3, and the steps after it apply the rules of the format: in formats 2 and 3, 16 stanzas at step 12, a control of schema version 2 at step 14, L, the code and P at step 16, a plaintext of P bytes with a zero padding at step 17 (`ERR_INTEGRITY` whenever it is found), the first L bytes at step 18; in format 3, step 17 in its substeps, a failure of age or a plaintext whose length is not P prevailing and a code other than `ERR_INTEGRITY` reported only after reading to EOF, and a caller without a `Sink` stopped right after step 2; steps 4 and 14 validate critical extensions (unknown, then invalid data); step 5 reads SEALED_CONTROL, a MUST (`ERR_INTEGRITY`), and SHOULD inspect its age header; step 8 argument rules; step 9 order: the `.dkk` as an object (decoded there when still encoded), its `capsule_id` and `capsule_digest`, credentials (nil identities are none) before the clock, round time, request, and nothing of the credentials under `time_only`; a network source verifies each response with the rules of step 10 and discards the invalid ones (none valid: `ERR_RELEASE_UNAVAILABLE`, step 9), and any failure of a source is `ERR_RELEASE_UNAVAILABLE` alone, whatever code its error carries; step 10: round, then signature, a canonical point other than the identity (§12.2), the codes of a release supplied directly; step 11: the tlock stanza body `U \|\| V \|\| W` of \|U\| + 32 bytes (128 in Quicknet), U canonical and not the identity, the IBE check r·G == U, every failure `ERR_INTEGRITY`, H2, H3 and H4 those of drand/kyber `encrypt/ibe`, H2 over the element of GT serialized in the order of kilic/bls12-381 (c1 before c0 at every level of the tower), with the frozen vector H2(e(G1, G2)) = `cb87319f24560b5231579a09ad79f12e`; the codes of the identities at steps 11, 13 (malformed X25519 stanza `ERR_INTEGRITY`, an identity that unwraps two stanzas `ERR_POLICY_STRUCTURE_MISMATCH` whatever the order, none `ERR_ACCESS_INVALID`) and 17; step 15 `ERR_HEADER_BINDING` | `capsule.Inspect` (steps 1–8), `capsule.Open` (steps 9–18; `openBody`, `drain`, `ErrSinkRequired`; `OpenOptions.AccessKeyFile`, `checkAccessKey`, `checkCapsuleDigest`), the `provider.ReleaseSource` contract, `provider/drand.Client` and `capsule.sourceFailure` (step 9), `tlock.TimeUnlock` with the kyber-bls12381 pairing (step 11), MUST rules inside `agewrap` identities (`AccessIdentity` tries every identity on every stanza; `TimeIdentity` checks the length of the tlock stanza body and U before `tlock.TimeUnlock`); no error copies the text of an error of age, tlock, kyber or drand (`agewrap`, `capsule.classify`), since kyber's IBE error carries the candidate plaintext and r; `cmd/datekeys` hands the `.dkk` over encoded; `datekeys inspect -json` rendered by `internal/inspectview` | `capsule.TestConformanceFixtures` (stage by stage), `TestOpen3`, `TestOpen3Substeps`, `TestFormatDispatch`, `TestFormatRelabel`, `TestPaddingChecksAtStep17`, `TestTlockFailureDiagnosticsCarryNoSecrets`, `TestPlaintextWriterFailureKeepsItsText`, `TestAccessKeyCheckOrder`, `TestAccessKeyFileAtStep9`, `TestPrecedenceAcrossSteps`, `TestControlCriticalBeforeHeaderBinding`, `TestReleaseFromANetworkSource`, `TestReleaseSourceErrorsAtStep9`, `agewrap.TestAccessIdentityStrictness`, `TestMalformedX25519Stanzas`, `TestTlockH2Vector` (`testdata/vectors/tlock_ibe.json`, generated by `internal/testkit.IBEVectors`, and step 11 recomputed with H2 and H4 against the file key tlock unwraps), `cmd/datekeys.TestDecryptAccessKeyOrder`, `TestMutationCorpus`, `TestInspectDifferentialCorpus` (`testdata/vectors/inspect_differential.json`: 5110 deterministic mutations of 14 fixtures, two of them of format 3, the 1825 of the format 1 ones first with the verdict of steps 1–8, generated by `internal/testkit.InspectDifferential`); `cmd/datekeys.TestInspectJSONGoldens` (`testdata/fixtures/*.inspect.json`) |
Docs at spec v0.11 and the draft v0.12: READMEs, SECURITY, traceability, CHANGELOG 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>
6 days ago
| 64 | Mandatory mutation tests: the first two lists in the three formats, the list of format 2 in format 2, and that of format 3, four of whose cases open with their verdicts, F2 for a signature of `alg` 1 that does not verify among them; the lists of v0.11 and v0.12: the signature of `alg` 1 and of `alg` 2, the area widened after signing, the seal, `alg` and `seal_type` 4294967295, the same P with a signature and without, the public note, the key of words and `datekeys.capsule` | `internal/testkit.Mutations` (the corpus: `specMutations` for each format, `furtherMutations`, `format2Mutations`, `format3Mutations` with `LoadedFixture.WithBody`, among them those of the list of v0.11 from `format3_signed`, `format3_unsigned` and `format3_note`, `signed1`), `internal/testkit.MutationCorpus` (its export); the cases of formats 2 and 3 derived without randomness, by sealing the fixtures again with their known file keys and nonces (`internal/testkit/reseal.go`, `mutations3.go`), exported as edits of their fixture (`internal/testkit.Splice`); the cases of `alg` 2, `seal_type` 2 and `datekeys.capsule`, vectors of `security_cms.json` and `locator.json`, frozen once written (`internal/testkit/genfixtures`, `frozenVectors`) | `capsule.TestMutationCorpus`: the 178 listed mutations, 33 in each format, the 23 of the list of format 2, the 48 of that of format 3 and 8 of that of v0.11 (`internal/testkit.SpecMutationsPerFormat`, `Format2SpecMutations`, `Format3SpecMutations`, `V011SpecMutations`), plus 40 more, built afresh; `capsule.TestExportedMutationCorpus`: `testdata/vectors/mutations.json`, the same 218 cases as frozen data (capsule, `.dkk`, identities, recorded release, clock, registry, known extensions), replayed with the recorded error and step, or the recorded verdicts; `capsule.TestPointMutationsChangeOnlyTheEncoding`: the ten point mutations keep a valid header MAC, and a decoder that reduces coordinates modulo p opens the c0 + p and x + p cases; `internal/testkit.TestResealReproducesFixtures`, `TestFixedX25519Stanza`; the cases of the lists of v0.11 and v0.12 outside the corpus: `ed25519strict.TestVectors` (`ed25519_strict.json`), `capsule.TestCMSVectors` (`security_cms.json`), `locator.TestLocatorVectors` (`locator.json`), `wordkey.TestKeyVector`, `TestNormalize` and `TestCheck`, `testdata/vectors/note.json`, and the same P of the fixtures `format3_unsigned` and `format3_signed` (`capsule.TestConformanceFixtures`); those of `alg` 2, `seal_type` 2 and `datekeys.capsule` of the list of v0.12, and the ones of v0.11 that `security_cms.json` and `locator.json` lack: *pending* |
| 65 | Quicknet vectors | `internal/testkit.RoundVectors` | `datekey.TestGoldenRoundVectors` |
| 66 | `dk1_` vectors | `internal/testkit.DK1Vectors` | `datekey.TestGoldenDK1Vectors` |
Docs at spec v0.11 and the draft v0.12: READMEs, SECURITY, traceability, CHANGELOG 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>
6 days ago
| 67 | `.dkc` vectors: the format 1 fixtures of v0.8.2, kept for compatibility, and format 2 fixtures for both policies, both codes, L = 0, one, several and 16 credentials, and extensions; the format 3 fixtures: one file, a tree, a comment alone, both codes, `time_and_key` with a portable key, an area of 1024 bytes, security of version 2, a signature of `alg` 4294967295 and that with a seal of `seal_type` 4294967295, the area of 32 KiB without a signature, a signature of `alg` 1, that with a seal of `seal_type` 2 and a file whose mtime is later than the seal, and a signature of `alg` 2 of two signers, ECDSA P-256 and RSA 2048, each with its CAdES-T; the records give the format, L, the code, P and the stanza each credential opens, and in format 3 the head, security, each file and the verdicts with their lines, and for a signature or a seal the commitments, `AUTHOR_MESSAGE` and its code, the key or `SIGNERS` and the certificates, the result of each signer, `SEAL_SUBJECT` and the token; the padding vectors, and those of paths, keys of R7, heads, security, `security_cms.json`, `ed25519_strict.json`, `note.json` and `locator.json` | `testdata/fixtures/*.dkc` + `*.json`, `internal/testkit/genfixtures`, which never regenerates a format 1 fixture, gives the five fixtures that `EncryptFiles` wrote in v0.10 their area of 512 bytes (`EncryptOptions.TestAreaLen`) and recomputes the derived fields of every record; `format3_note`, with a public note, for the mutations of §64; the frozen `datekeys inspect -json` output of each, `*.inspect.json`; `testdata/vectors/padding.json`; `security_cms.json` and `locator.json`, frozen once written (`frozenVectors`); formats in `testdata/README.md` | `capsule.TestConformanceFixtures` (`checkBody3`, `checkSignature3`), `TestPaddingAcrossChunks` (a capsule generated at run time), `TestCMSVectors`; `locator.TestLocatorVectors`; `ed25519strict.TestVectors`; `internal/testkit.TestFormat3VectorFiles`, `TestVectorFilesAreCurrent`; `cmd/datekeys.TestInspectJSONGoldens`, `TestDecryptFixtures`, `TestDecryptFormat3Fixtures`, `TestMTimeAfterSeal` |
| 68 | `.dkk` vectors, with the exact extension data; one carries an extension with data, two accompany a format 2 capsule and one a format 3 capsule | `testdata/fixtures/*.dkk` + `*.dkk.json`; `time_and_key_portable_extension.dkk` derived by `genfixtures`; `format2_time_and_key_portable.dkk`, `format2_time_and_key_recipients.dkk`, `format3_time_and_key_portable.dkk` | `accesskey.TestFixtures`, `TestFixtureWithExtension`; `capsule.TestAccessKeyFixtureWithExtension` |
| 69 | Normative errors, including `ERR_EXTENSION_DATA_INVALID` and `ERR_HEAD_INVALID`; every error of the module wraps exactly one | `errors.go`; `provider/drand.Client` and step 9 of `capsule.Open` keep another code of a failure as text only | `datekeys.TestCatalogueMatchesSpec`, `TestCode`; `drand.TestFetchErrorHasOneCode`; `capsule.TestReleaseSourceErrorsAtStep9` |
Implement capsule format 2 of spec v0.9 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>
1 week ago
| 69.1 | Error precedence: the first failing layer of each object (frame, a truncated prelude before the version; type tag and schema version; CBOR profile and CDDL, except the rules with codes of their own; fields with codes of their own in ascending key order, an extension unknown in its object or array before invalid data), the step order of §63 across objects and steps; only the optional inspection of steps 5, 6 and 8 and the `capsule_digest` check can change the code; the codes of step 10 are those of a release supplied directly, one from a network source being discarded at step 9; in format 2, the 16 stanzas belong to step 12, the version of CONTROL_CBOR against the format to layer 2 of step 14, the rules of keys 6 and 7 to its layer 3, and the length and padding of the plaintext to step 17 | `capsule.ParsePrelude`, `capsule.DecodeHeader`, `capsule.DecodeControl`, `accesskey.Decode`, `accesskey.DecodeBody`, `profile.Decode`, `codec.CheckSchema`, `codec.Unmarshal`, `extension.CheckCriticalIn`, `capsule.checkAccessKey`, `OpenOptions.AccessKeyFile`, `agewrap.AccessIdentity`, `provider/drand.Client` | `capsule.TestPrecedenceWithinPublicHeader`, `TestPrecedenceAcrossSteps` (with the examples of format 2), `TestDecodeHeaderReportsTheCDDLFirst`, `TestAccessKeyCheckOrder`, `TestAccessKeyFileAtStep9`, `TestControlCriticalBeforeHeaderBinding`, `TestReleaseFromANetworkSource`, `TestExtensionPlacement`; `accesskey.TestDecodePrecedence`; `agewrap.TestAccessIdentityStrictness`; `profile.TestDecodePrecedence`, `TestPinPathMatchesDecode`; `cmd/datekeys.TestDecryptAccessKeyOrder`; `extension.TestCheckCritical`, `TestPlacement`; `codec.TestCheckSchemaVersionForms`; `testdata/vectors/cbor.json`, `inspect_differential.json` |
Docs at spec v0.11 and the draft v0.12: READMEs, SECURITY, traceability, CHANGELOG 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>
6 days ago
| 70 | Compatibility: a reader accepts the three formats and opens formats 1 and 2 with the semantics of v0.8.2 and v0.9; an implementation that writes capsules writes format 3, and only a generator of test vectors writes formats 1 and 2; a reader should report the format; the format is neither the version of the specification (`datekeys.SpecVersion`) nor that of the module (`datekeys.Version`, `datekeys version`); a reader of v0.10 opens the capsules of v0.11, with an area of 32 or 64 KiB, F1 for `alg` 1 and 2, S1 for `seal_type` 2, and the public note and `datekeys.capsule` ignored, and a reader of v0.11 those of v0.10, with their area of 512 bytes; v0.12 changes no format, only texts of the verdicts and how a certificate and a token are read | magic and version checks; `capsule.Format`; `codec.Peek` and `codec.CheckSchema` read keys 0 and 1 only, before strict decoding, with a type tag of at most `codec.MaxTypeTagLen` bytes; `Inspection.Prelude.Format`, `Opened.Format`; `internal/testkit.Build`; `version.go`, where `SpecVersion` stays 0.11 until v0.12 is approved; `capsule.EvaluateSecurity`, which reads security without a context, as a reader of v0.10; `ParseBodyFrame`, which accepts any area of the frame | mutations; `capsule.TestFormatDispatch`, `TestFormatRelabel`, `TestFormat1Compatibility`; `codec.TestPeek`, `TestCheckSchema`, `TestCheckSchemaVersionForms`, `FuzzPeek`; `capsule.TestDecodeSchemaVersion`; `datekeys.TestSpecVersionNamesTheSpecification`, `TestVersion`; `cmd/datekeys.TestVersion`; `capsule.TestEvaluateSecurityIn` and `TestEvaluateCMS` (no context: F1), `TestEvaluateSeal` (no context: S1), `TestCMSVectors` (the cases without a context); the fixtures of v0.10, with their area of 512 bytes, among those of v0.11 (`TestConformanceFixtures`) |
| 71 | Profile registry | `profile.Decode` + `profile.NewRegistry` with pinned hashes | `profile.TestRegistry` |
Docs at spec v0.11 and the draft v0.12: READMEs, SECURITY, traceability, CHANGELOG 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>
6 days ago
| 72 | Extension registry and registration rules, among them the objects and arrays where each extension may appear, and an encoder never writes one elsewhere; the encoder decodes its own output before sealing; security-relevant claims in CONTROL_CBOR or under a signature extension, `.dkk` extension data advisory; the registered extensions, `datekeys.note` in the noncritical array of PUBLIC_HEADER and `datekeys.capsule` in the noncritical array of a `.dkk`, both informative | `extension.Registry`, `extension.Set`, `extension.DataValidator`, `extension.Placement` (optional: a `Registry` without it knows its extensions in every object and array); `extension.Standard`, the registry of the extensions of the specification (`NoteID`, `CapsuleID`), and `locator.Standard`, which validates the data of `datekeys.capsule`; `extension.CheckWrite`, which the writers of capsules and `.dkk` files apply with `extension.Standard` (`capsule.newSealer`, `accesskey.AccessKey.MarshalBody`); self-checks in `capsule.Encrypt`, `capsule.EncryptFiles`, `accesskey.MarshalBody` and `locator.Info.Extension`; these writers take no `Registry`: the application writes each extension of its own only where it is registered | `capsule.TestKnownCriticalExtensions`, `TestUnusableNoncriticalExtensions`, `TestExtensionPlacement`, `TestNestedDataSealsAndOpens`, `TestRegisteredExtensionsWhereRegistered`, `TestPublicNoteRules`, `FuzzEncodeImpliesDecode`; `extension.TestPlacement`, `TestCheckWrite`; `locator.TestInfo` |
Spec v0.8.2 refinements: error precedence, trust model, strict order 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>
2 weeks ago
| 74 | Provisional aspects; the implementation limits of the reference (name lengths, `public_key`, `period`, maximum `extension_id` length, `dk1_` length, age parser limits, `ERR_POLICY_STRUCTURE_MISMATCH` for INNER_ACCESS_AGE) | `profile.ValidID`, `validName`, `maxPublicKeyLen`, `maxPeriod`; `extension.MaxIDLen`; `datekey.MaxEncodedLen`; `filippo.io/age` | `profile.TestValidateRejectsTamperedProfiles`, `TestIntegerRanges`; `extension.TestNew`; the vectors of `cbor.json` named after the implementation limit |
| 75 | Blocking requirements before v1.0 | items 1–9 and 11 above, with fixtures and mutations in the three formats; item 10 (external review) pending | — |
Docs at spec v0.11 and the draft v0.12: READMEs, SECURITY, traceability, CHANGELOG 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>
6 days ago
| 76 | Change policy; the normative changes of v0.8.2: the extension change and its reproducible cases; the refinements and theirs; the amendment on point canonicality and its case (a second implementation on `tlock-js` and `@noble/curves` 1.9.7 accepted U with c0 + p and a signature with x + p); the corrections of the formal review (an invalid release from a network source, the objects and arrays of each extension, the serialization of GT in H2) and their cases, and those of its second round (the encoder rule of §72, the codes of step 10 in §17 and §51 for a release supplied directly, one code for any failure of a source at step 9); the normative changes of v0.9, capsule format 2, and their cases; those of v0.10, capsule format 3, and theirs; those of v0.11, the area of 32 KiB, what is signed, the signatures of `alg` 1 and 2, the seal of `seal_type` 2, the key of words, the public note and `datekeys.capsule`, and theirs; and those of the draft v0.12, which change no format: the names of certificates and the seal of each signer of F6 in the verdicts, the holder without its identifier, the profile of the certificate, identifiers, repetitions and edge cases, the addresses and the padding of the locator, errata, and the vectors of v0.11 | `extension`, `codec`, fixture `time_only_extensions` regenerated; refinements: the order of `capsule.checkAccessKey`, `BODY_LEN` 0 in `accesskey.Decode`, CR and LF and invalid UTF-8 in `datekey.Parse`, the `.dkk` decoded at step 9.a (`OpenOptions.AccessKeyFile`, the CLI), nil identities in `capsule.Open`, every identity tried in `agewrap.AccessIdentity`, `profile.NewRegistry` through `Decode`, `Profile.Validate` rule 1 first; four new `dk1.json` vectors; corrections: `extension.Placement` and the object-aware checks, the `provider.ReleaseSource` contract, `testdata/vectors/tlock_ibe.json`; second round: the error of `provider/drand.Client` and of step 9 in `capsule.Open`; v0.9: rows 22, 29, 29.1, 31, 33, 36, 37, 39, 55.2, 56, 57, 61, 62, 62.1, 63 and 70; v0.10: rows 22, 23, 29 to 29.7, 31, 56, 57, 61 to 64 and 67 to 70; v0.11: rows 24.1, 29.2, 29.3, 29.7 to 29.12, 38.1, 44.1, 62.1, 64, 67, 70 and 72; v0.12: rows 29.3, 29.7, 29.10, 29.11, 44.1, 64, 67 and 70, and the sizes of the locator in `spec/datekeys.cddl` | case 2: `extension.TestNew`; case 3: `capsule.TestNaNKeyedDataHasOneVerdict`; case 4: `capsule.TestExtensionFixtureData`; case 5: `capsule.TestNestedDataSealsAndOpens`; case 6: `capsule.TestHugeExtensionArraysAreRejected`, `extension.TestCheckDisjointIsLinear`; refinements: the tests of rows 12.1, 15, 17, 19, 22, 28.1, 35, 36, 40, 51, 55.1, 63 and 69.1, and `extension.TestOrderIsUnsignedBytewise`; amendment: the tests of rows 12.2 and 64; corrections: `capsule.TestReleaseFromANetworkSource`, `TestExtensionPlacement`, `extension.TestPlacement`, `agewrap.TestTlockH2Vector`; second round: `capsule.TestReleaseSourceErrorsAtStep9`, `TestExtensionPlacement` (the noncritical array of a `.dkk`), `drand.TestFetchErrorHasOneCode`, `TestUnavailabilityAndCancellation`, `datekeys.TestCode`; v0.9: the tests that §76 names for each change, in rows 22, 29.1, 31, 37, 39, 55.2, 57, 62.1, 64 and 70; v0.10: those of the rows it changed; v0.11: those of the rows it added and changed; v0.12: changes 1 and 2, `capsule.TestEvaluateCMS`, `TestIssuerTextFiltered`, `cmd/datekeys.TestRows` and the record of `format3_signed_cms`; change 5, `der.TestSetOfSorted`, `TestCheck` and `cms.TestTSTInfoStrict`; changes 6 and 7, `locator.TestAddresses`, `TestUsableAddresses`, `TestLeastMultiple` and `TestPaddingBoundaries`; change 9, `testdata/vectors/security.json` and the fixture `format3_seal_unsupported`; the cases of changes 1 and 3 to 7 in `security_cms.json` and `locator.json`: *pending* |
## Error mapping
Spec v0.8.2 refinements: error precedence, trust model, strict order 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>
2 weeks ago
The error of each failure, with the section of the specification that names
it. Until the v0.8.2 refinements (§76) several of these were choices of the
reference; they are now normative. When bytes break several rules, §69.1
decides which code is reported.
Spec v0.8.2 refinements: error precedence, trust model, strict order 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>
2 weeks ago
| Failure | Error | Spec |
|---|---|---|
| Bytes that are not the deterministic encoding of a valid schema instance: malformed CBOR, non-canonical encoding (including a map head or type tag head not in its shortest form before the schema version), unknown key, missing key, wrong type (also for a field with a code of its own), `null`, wrong type tag (key 0, or one longer than `codec.MaxTypeTagLen` bytes), a schema version that is missing, not the second key, not an unsigned integer, not in its shortest form or above 2^53−1, wrong field length, undefined `access_policy` (any value other than 0 and 1), empty optional array or map, extension rules including the order of `extension_id` | `ERR_NON_CANONICAL_CBOR` | §54, §57, §58, §69.1 |
| Schema version other than 1, read as the second key, after a type tag within the profile, as an unsigned integer in its shortest form of at most 2^53−1; whatever follows it | `ERR_UNSUPPORTED_VERSION` | §69.1, §70 |
| Truncated framing, length fields of 0 or beyond the §57 limits, an object above its §57 frame on encode or decode, data after BODY_CBOR, a malformed OUTER_TIME_AGE or PAYLOAD_AGE (an age header against the C2SP grammar, without stanzas, or beyond the parser limits of `filippo.io/age`: 1024 stanzas, 128 arguments, 2 MiB), a tlock stanza body of a length other than \|U\| + 32, with a U that is not the canonical encoding of a point of the key group (§12.2) or is the identity, or that fails the IBE check r·G == U (step 11), a malformed X25519 stanza (steps 13 and 17), a failed header MAC, a truncated or modified STREAM, trailing data after PAYLOAD_AGE, a PAYLOAD_AGE that I_PAYLOAD cannot open (step 17); in format 3 at step 17, a violation of the frame of BODY or of its area, files that do not fill CONTENT, or a file whose SHA-256 is not that of the head | `ERR_INTEGRITY` | §22, §23, §28.1, §40, §57, §63 steps 11, 13 and 17, §74 |
Spec v0.8.2 refinements: error precedence, trust model, strict order 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>
2 weeks ago
| Stanza count or type violations in OUTER_TIME_AGE, PAYLOAD_AGE or INNER_ACCESS_AGE in a header that parses; a repeated X25519 ephemeral share in INNER_ACCESS_AGE (step 12); an offered identity that unwraps more than one INNER_ACCESS_AGE stanza, whatever the order of the identities (step 13); a malformed INNER_ACCESS_AGE, including one beyond the parser limits, or none, under `time_and_key`; an age intro line under `time_only`; a tlock stanza without exactly two arguments | `ERR_POLICY_STRUCTURE_MISMATCH` | §28.1, §36, §63 steps 8, 12 and 13 |
Spec v0.8.2: corrections from the formal review 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>
2 weeks ago
| tlock stanza round argument not exactly the canonical decimal DateKey round (steps 8 and 11); a release for another round supplied directly, checked before its signature (step 10) | `ERR_ROUND_MISMATCH` | §17, §63 steps 8, 10 and 11 |
| A release supplied directly whose signature is not the canonical encoding of a point of the signature group of the scheme (§12.2), is the identity, or does not verify under the pinned key for the DateKey round | `ERR_RELEASE_INVALID` | §12.2, §51, §63 step 10 |
Spec v0.8.2 refinements: error precedence, trust model, strict order 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>
2 weeks ago
| tlock stanza chain hash not exactly the lowercase hex chain hash of the pinned profile; profile whose parameters do not hash to its chain hash; a pinned profile with another `profile_hash` | `ERR_PROFILE_MISMATCH` | §12.1, §63 step 8 |
| Instant before the profile genesis or whose round time would be after 9999-12-31T23:59:59Z; `dk1_` round outside 1..2^53−1; round time after 9999-12-31T23:59:59Z under the pinned profile (step 7) | `ERR_DATEKEY_INVALID` | §15, §19 |
Spec v0.8.2 amendment: canonical point encoding; no library error text 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>
2 weeks ago
| Provider Profile that cannot be pinned: name alphabets (§12.1) and lengths (§74), public key size (§74), `period` above 2^32−1 (preceded in the reference by its 86400 s limit, `ERR_NON_CANONICAL_CBOR`), `genesis_time` outside 1..253402300798, `provider` other than `drand`, a scheme tlock does not support, a public key that is not the canonical encoding of a point of the scheme's key group (§12.2) or is the identity | `ERR_UNKNOWN_PROFILE` | §12.1, §12.2 |
Spec v0.8.2 refinements: error precedence, trust model, strict order 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>
2 weeks ago
| Unknown `access_type`, wrong material length, `.dkk` for another `capsule_id`, `capsule_digest` mismatch (step 9.a); no offered identity is a recipient of INNER_ACCESS_AGE (step 13) | `ERR_ACCESS_INVALID` | §57, §63 steps 9 and 13 |
| `time_and_key` and no credential offered (nil identities are none), before the clock is consulted | `ERR_ACCESS_REQUIRED` | §63 step 9 |
Spec v0.8.2: second-round corrections from the formal review 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>
1 week ago
| Round time not reached yet (no request is made), or no source delivered a release, whatever the cause: a network source discarded every response for breaking the rules of step 10, or the error of a source carries another code or none, of which only the text is kept | `ERR_RELEASE_UNAVAILABLE` | §63 step 9 |
| A head of format 3 that is well encoded but whose comment, declared author, paths, layout or tree break a rule of layer 4 (step 17) | `ERR_HEAD_INVALID` | §29.4–§29.6, §69.1 |
Spec v0.8.2: corrections from the formal review 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>
2 weeks ago
| An unknown critical extension, or a known one in an object or array it is not registered for, before any invalid data (steps 4, 9.a and 14) | `ERR_EXTENSION_CRITICAL_UNKNOWN` | §54, §63, §69.1, §72 |
Spec v0.8.2 refinements: error precedence, trust model, strict order 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>
2 weeks ago
## Implementation decisions
Spec v0.8.2 refinements: error precedence, trust model, strict order 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>
2 weeks ago
Choices the reference implementation made where v0.8.2 was silent or
provisional (§74). None changes the protocol semantics.
Spec v0.8.2: second-round corrections from the formal review 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>
1 week ago
### Settled in the specification by the refinements, the amendment and the corrections of v0.8.2
Spec v0.8.2 refinements: error precedence, trust model, strict order 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>
2 weeks ago
1. **Pre-genesis instants** are `ERR_DATEKEY_INVALID` instead of resolving to
round 1: §15.
2. **Round bounds.** `dk1_` accepts rounds in 1..2^53−1; a profile further
limits rounds to round times up to 9999-12-31T23:59:59Z (Quicknet:
83 903 165 811), checked at step 7: §15, §19.
3. **Name alphabets.** `[a-z0-9][a-z0-9:._-]*` for `profile_id`, also as
the `network` of `dk1_`, and `[a-z0-9][a-z0-9._-]*` for the other names:
normative, §12.1, §19. Their maximum lengths, 128 and 64 bytes, are
implementation limits listed in §74.
4. **Reading `dk1_`.** JSON numbers are compared by their exact decimal
value, and the Base64 decoding accepts padding, the standard alphabet and
non-zero trailing bits, which the final comparison rejects as
Spec v0.8.2: corrections from the formal review 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>
2 weeks ago
non-canonical; CR and LF, which the Go decoders skip, a byte order mark
and invalid UTF-8, which `encoding/json` replaces with U+FFFD, are
invalid: §19. Until the refinements CR and LF were
`ERR_DATEKEY_NON_CANONICAL`, and so was invalid UTF-8 in a member that a
repeated name overwrites until 692cf87.
Spec v0.8.2 refinements: error precedence, trust model, strict order 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>
2 weeks ago
5. **Strict tlock stanza arguments.** Exact string comparison; a round with
leading zeros or a sign, or an uppercase chain hash, is a mismatch: §63
step 8.
6. **Order of checks.** The first failing layer of each object, and the step
order of §63 across objects: §69.1. The decoder of each schema reads the
type tag and version first (keys 0 and 1), then the whole map with every
CDDL rule, and only then the fields with codes of their own. At step 9 an
offered `.dkk` is checked as an object (`access_type`, `access_material`,
critical extensions) before its `capsule_id` and `capsule_digest`; until
the refinements the reference checked the `capsule_id` first.
7. **Profile `period` and `genesis_time`.** `period` at most 86400 s, an
implementation limit (§74), below the normative 2^32−1 of §12.1;
`genesis_time` in 1..253402300798 to be pinned (§12.1). `NewRegistry`
encodes and decodes each profile, so a profile gets the same code pinned
or decoded; until the refinements it applied the field rules first.
8. **Frame lengths.** `PUBLIC_HEADER_LEN`, `SEALED_CONTROL_LEN` and
`BODY_LEN` of 0 are out of range (`ERR_INTEGRITY`): §22, §40, §57. Until
the refinements a `.dkk` with `BODY_LEN` 0 was `ERR_NON_CANONICAL_CBOR`.
9. **Malformed age headers.** A header against the C2SP grammar, including
one without stanzas or beyond the parser limits, is `ERR_INTEGRITY` for
OUTER_TIME_AGE and PAYLOAD_AGE and `ERR_POLICY_STRUCTURE_MISMATCH` for
INNER_ACCESS_AGE; only a header that parses is judged by the stanza rules:
§28.1, §36.
10. **Release verification order.** The release round before its signature:
§63 step 10.
11. **One stanza per recipient.** No repeated X25519 ephemeral share (step
12), and no offered identity that unwraps more than one stanza, whatever
the order of the identities (step 13): §36, §63. Until the refinements
the first identity that unwrapped exactly one stanza opened the file.
12. **Decoding a `.dkk`.** Its errors come at step 9.a, and never under
`time_only`, even when it is decoded earlier: §63. The CLI hands the file
to `capsule.Open` encoded (`OpenOptions.AccessKeyFile`); until the
refinements it decoded the `.dkk` before step 1. A caller of the API that
decodes a `.dkk` itself, with `accesskey.Decode`, gets its decoding errors
first.
Spec v0.8.2 amendment: canonical point encoding; no library error text 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>
2 weeks ago
13. **Point encodings.** Settled by the v0.8.2 amendment on point
canonicality: the public key, the release signature and U accept only the
canonical compressed encoding of drand, never the point at infinity, and
the tlock stanza body is `U || V || W`: §12.1, §12.2, §63 steps 10 and 11.
The reference already rejected every other encoding through the decoder
of `kilic/bls12-381`; a U at infinity, which only the IBE check used to
reject, is now refused before decryption, with the same code.
Spec v0.8.2: corrections from the formal review 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>
2 weeks ago
14. **Invalid releases from the network.** Settled by the corrections of the
formal review: `provider/drand.Client` verifies every relay response and
discards the invalid ones, so a relay that returns no valid release gives
`ERR_RELEASE_UNAVAILABLE` at step 9, and only a release supplied directly
Spec v0.8.2: second-round corrections from the formal review 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>
1 week ago
gets the codes of step 10: §63 steps 9 and 10, §69.1. Since the second
round of the review, the error of `Fetch` keeps the failure of each relay
as text only, and step 9 reports any failure of a source with
`ERR_RELEASE_UNAVAILABLE` alone, keeping only the text of an error with
another code or none. Until then, a relay whose only answer broke a rule
of step 10 left that rule's code in the error too (`errors.Is`), and a
caller's source that failed with `ERR_RELEASE_INVALID` got that code at
step 9.
Spec v0.8.2: corrections from the formal review 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>
2 weeks ago
15. **GT in H2.** Settled by the corrections of the formal review: H2 hashes
the element of GT as `kilic/bls12-381` serializes it, through tlock and
drand/kyber, with the frozen vector of `testdata/vectors/tlock_ibe.json`:
§63 step 11.
Spec v0.8.2 refinements: error precedence, trust model, strict order 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>
2 weeks ago
### Still implementation decisions
1. **Closed maps.** Unknown keys in core maps are rejected; applications use
extensions (§1, §54, §58).
2. **Extension data.** `extension.MaxDataLen` is 64 MiB, the largest frame,
the container frame being the effective bound; an application validates
the data of the extensions it knows through the optional
Spec v0.8.2: corrections from the formal review 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>
2 weeks ago
`extension.DataValidator` of its `Registry`, and declares the objects and
arrays each one is registered for (§72) through the optional
`extension.Placement`; a `Registry` without it knows its extensions in
Spec v0.8.2: second-round corrections from the formal review 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>
1 week ago
every object and array, as before the formal review. The writers,
`capsule.Encrypt` and `accesskey.Encode`, take no `Registry`: they write
the extensions they are given, and the application writes each one only
where it is registered, as §72 requires of an encoder.
Spec v0.8.2 refinements: error precedence, trust model, strict order 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>
2 weeks ago
3. **`capsule_digest`.** Written by `Encrypt` for every portable key and
checked at step 9 when the capsule reader is seekable; it remains a UX
shortcut and an optional check (§43, §69.1).
4. **Creation in the past.** `Encrypt` requires the unlock time to be
strictly after the injected clock.
5. **Clock injection.** No library package reads the wall clock; `Encrypt`
and `Open` require a `Now` function, and `Open` never requests a release
for a round whose time has not been reached (§63 step 9).
6. **The files of format 3.** `Open` hands them to a `capsule.Sink`, which
gets `Abort` once after any failure that follows a successful `Begin`,
a failure of `Commit` included; a `Begin` that fails cleans up after
itself. A failure of the `Sink` is the caller's own error with
`ERR_INTEGRITY`, as a failure of `dst` is in formats 1 and 2.
`EncryptFiles` sorts its `Source` list in the byte order of the paths,
and refuses a path given twice before the rules of the tree.
7. **The width of the terminal.** The CLI asks it through `syscall`
(`TIOCGWINSZ` on Unix, `GetConsoleScreenBufferInfo` on Windows) rather
than through a new dependency; elsewhere, and for any output that is not
a terminal, W is 80 (§29.7). The folder of `decrypt` keeps mode 0700 and
its files 0600, and the extracted files get their recorded mtime.
Spec v0.8.2 refinements: error precedence, trust model, strict order 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>
2 weeks ago
`testdata/README.md` documents the formats of the vectors and corpora and
points to these sections for the rules that decide each verdict.

Powered by TurnKey Linux.