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

89 KiB

Traceability: DateKeys Protocol Specification v0.15 ↔ 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 spec/DateKeys_Protocol_Specification_v0.15.md, tagged spec-v0.15; v0.14, v0.13, v0.12 and v0.11 number their sections the same, but for §7.10, new in v0.14, and §47.1 and §79, new in v0.15. 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
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; §7.6, SHOULD: recommend time_and_key for long horizons or valuable content creator model in internal/testkit.Build, RewriteAge; third-party edits in the mutation corpus; cmd/datekeys recommends -policy time_and_key beyond one year with time_only (TestRecoveryAndNotices) 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
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 scheme bls-unchained-g1-rfc9380 alone since v0.14, a public key that is the canonical encoding of a point of G2 (§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
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/*
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
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
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; v0.15: no extension for the release object, identified by its type tag (provider.DecodeRelease, provider.IsArchive to tell an archive apart in cmd/datekeys) mutation a .dkk offered as a .dkc; accesskey.TestDecodeRejects a .dkc; provider.TestReleaseVectors
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
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
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
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
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, and with -ts and -dart the same tables for datekeys-ts and datekeys-dart; 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
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 when both have text that is not empty, the issuer by commonName or, without one that has text, 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
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
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
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)
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
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
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)
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
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; SHOULD: random words of a public list by default, at least 6 of 2048 or more wordkey (Normalize, Check, Key, Identity, Rounds, MinWords, MinLetters; Generate, List, CheckList with the alphabet of each language, Bits, DefaultCount, MinListSize, the lists lists/en.txt and lists/es.txt; the dice: DiceListSize, DiceNumber, DiceWord, DiceWords, DiceList), 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), -new-words, -dic, -word-count, -dice and -dice-file of encrypt, the command wordlist, the words of decrypt salted with what capsule.Inspect gives wordkey.TestNormalize, TestCheck, TestKeyVector (the vector of §38.1), TestBuiltInLists, TestCheckList, TestGenerate, TestBits, TestGenerateUniform, TestDiceNumber, TestDiceWord, TestDiceWords, TestDiceList; testdata/vectors/wordkey.json (internal/testkit.WordKeyVectors, TestVectorFilesAreCurrent): the words of a text, what a writer refuses and the identities, the cases of §64 of v0.11; capsule.TestEncryptFilesWords (the words of another capsule_id do not open); cmd/datekeys.TestKeyOfWords, TestNewWords, TestDice, TestWordList
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)
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
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, the scheme in lower case: https with a host of labels of 1 to 63 letters, digits and hyphens that neither start nor end with a hyphen, or a public IP outside the special-purpose blocks of IANA, an IPv4 without leading zeros, a port of 1 to 65535 without leading zeros, no local name in either case, no dot segment, or ipfs with a CID v1 of at most 128 characters in canonical 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; CheckResolvedIP, the address a name resolves to, NAT64 included, of v0.13), 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, TestInfoSealedForm (the round, the chain and the body of the sealed locator), TestUsableAddresses, TestLeastMultiple, TestPaddingBoundaries, TestLocatorVectors (testdata/vectors/locator.json); TestResolvedIPVectors (testdata/vectors/resolved_ip.json, internal/testkit.ResolvedIPVectors), TestResolvedIPWellKnownPrefix; capsule.TestRegisteredExtensionsWhereRegistered (never in a capsule); the cases of §64 of v0.11 and v0.12 for datekeys.capsule in locator.json: the addresses, a locator with a rejected address and a usable one, the resources of the rest, the data of the extension and the plaintexts of the locator
45 Release API: its answer is the release object of §47.1 (v0.15), and its user a network source; the HTTP form is informative the object: provider.EncodeRelease, provider.DecodeRelease; the API itself is out of scope (server, plan §2) provider.TestReleaseVectors
46 Release Queue out of scope (server) —
47 Release Cache every release is verified again: capsule.Open step 10 and agewrap.TimeIdentity; release_material is the release object (v0.15): provider.EncodeRelease mutations release of another round
47.1 Release object (v0.15): deterministic CBOR without a frame, {0: "datekeys-release", 1: 1, 2: chain_hash, 3: round, 4: signature}; size from 1 to 1024 bytes, then type and version, then schema (ERR_NON_CANONICAL_CBOR, a version other than 1 ERR_UNSUPPORTED_VERSION); the chain hash, the round and the signature at step 10; drand's JSON accepted as the input of the caller, ERR_RELEASE_INVALID when unreadable; a Release Cache and the Release API keep and serve the object provider/release.go: EncodeRelease, DecodeRelease (releaseWire with codec.CheckSchema and codec.Unmarshal), ParseRelease, NewReleaseObject, MaxReleaseObjectSize, MaxReleaseJSONSize; cmd/datekeys releaseInHand provider.TestReleaseVectors, TestEncodeRelease, FuzzDecodeRelease; testdata/vectors/release.json, testdata/releases/<round>.cbor; internal/testkit.TestVectorFilesAreCurrent
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; v0.15: a release in hand, provider.Supplier (provider.Encoded, provider.Archive) in capsule.OpenOptions.Release; datekeys decrypt -release drand.TestLiveRelays, capsule.TestLiveLifecycle (-tags integration); capsule.TestReleaseInHand, TestReleaseInHandErrors; cmd/datekeys.TestDecryptWithReleaseInHand
50 Historical release dependency; v0.15: long-term recovery on archives of all rounds and on cache services that serve them, with no hosting promise, the release archive as an informative format documented in README.md; provider.Archive, provider.EncodeArchiveHeader; datekeys decrypt -release with a local archive provider.TestArchive; cmd/datekeys.TestDecryptWithReleaseInHand (a local archive); the archive block of testdata/vectors/release.json, testdata/releases/archive_1000_1004.bin
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; v0.15: first the chain hash a release object names, ERR_PROFILE_MISMATCH (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 …; the objects of testdata/vectors/release.json; mutations release object of another chain
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; v0.15: the release among what a long horizon needs (text) cmd/datekeys.TestLongHorizonWarning, TestRecoveryAndNotices
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 —
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
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); v0.15, rules 26 and 27 (SHOULD of the SDK): the warning and the annex next to the .dkc are not implemented by the CLI 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, TestRegisteredExtensionsWhereRegistered, TestEncryptFilesWords; wordkey.TestCheck; locator.TestUsableAddresses, TestInfo; cmd/datekeys.TestAuthorSignRoundTrip (the key and the code before the signature), TestPublicNoteCLI, TestKeyOfWords; rule 25 has no test of its own
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; v0.15: step 9.c only for OpenOptions.Source, a release in hand (OpenOptions.Release) not compared with the clock and Opened.ClockBehind; step 10 starts with provider.ParseRelease, then provider.Verify with the chain hash 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); capsule.TestReleaseInHand, TestReleaseInHandErrors; mutations round not reached yet (opens) and round not reached yet, from a network source
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 44 more, built afresh; capsule.TestExportedMutationCorpus: testdata/vectors/mutations.json, the same 222 cases as frozen data (capsule, .dkk, identities, recorded release and its source, 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 lists of v0.11 and v0.12, in security_cms.json and locator.json
65 Quicknet vectors internal/testkit.RoundVectors datekey.TestGoldenRoundVectors
66 dk1_ vectors internal/testkit.DK1Vectors datekey.TestGoldenDK1Vectors
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
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
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; v0.15: a valid release in hand opens a capsule with a clock behind its round time 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); mutation round not reached yet
71 Profile registry; the states activo, solo lectura and comprometido; SHOULD: warn of a compromised profile profile.Decode + profile.NewRegistry with pinned hashes; profile.Status, StatusOf (the states that this release of the module knows, by profile_hash: Quicknet active); cmd/datekeys: encrypt writes no capsule with a profile that is not active, and decrypt and inspect warn when the profile of a capsule is compromised profile.TestRegistry, TestStatus; cmd/datekeys.TestProfileStatus
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
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 —
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 5 in security_cms.json, and those of changes 6 and 7 in locator.json (TestLocatorVectors); the changes of v0.15, each with its case: the release object (release.json, testdata/releases), its chain hash at step 10 and step 9.c (mutations.json, field source), the recovery (scripts/recovery_check.sh)
79 Informative annex: recovery without DateKeys software (v0.15); §62.1 rule 27, the annex next to each .dkc datekeys.RecoveryAnnex (annex/recovery.md, §79 under a title with the version and the SHA-256 of the specification) and RecoveryAnnexSuffix; encrypt writes it as FILE.dkc.recuperacion.txt unless -no-recovery; scripts/recovery (no package of this module, tlock or drand: Go, golang.org/x/crypto, filippo.io/age, drand/kyber-bls12381), scripts/recovery_check.sh, run by scripts/check.sh scripts/recovery.TestImports, TestRecoverFixtures and the other tests of the package, over eight fixtures of the three formats; datekeys.TestRecoveryAnnex (the annex is §79 of the specification of SpecVersion), cmd/datekeys.TestRecoveryAndNotices; scripts/recovery_check.sh on format3_single and format3_time_and_key_portable

Error mapping

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.

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
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
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
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
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
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
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
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

Implementation decisions

Choices the reference implementation made where v0.8.2 was silent or provisional (§74). None changes the protocol semantics.

Settled in the specification by the refinements, the amendment and the corrections of v0.8.2

  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 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.
  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.
  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.
  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 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.
  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.

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 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 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.
  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.

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.