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

15 KiB

Traceability: DateKeys Protocol Specification v0.8.1 ↔ 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.8.1.md.

Section map

§ Topic Implementation Tests
3 Guiding principle: verify locally profile.Registry, datekey.Resolve, provider.Verify, capsule.Inspect capsule.TestMutationCorpus
4 Security goals whole module whole suite
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 Profile.CanonicalCBOR, Profile.Hash, profile.Decode profile.TestQuicknetMatchesGoldenVector, TestQuicknetCBORLayout, TestDecodeRoundTrip, FuzzDecode; testdata/vectors/profile_quicknet.json
12 Quicknet Provider Profile V1 profile.Quicknet, profile.Quicknet* constants profile.TestQuicknetMatchesGoldenVector
13 Root of trust profile.NewRegistry, profile.Pin, profile.Default, QuicknetProfileHash; chain-hash self-check in Profile.Validate profile.TestRegistry; mutations unknown profile, empty registry
14 DateKey datekey.DateKey datekey/*
15 Date → round resolution datekey.Resolve, datekey.RoundTime datekey.TestGoldenRoundVectors, TestRoundNeverOpensEarly, TestResolveProperty, TestTimezoneIndependence
16 Normative round vector — datekey.TestNormativeRoundVector; testdata/vectors/quicknet_rounds.json
17 Past-round attack provider.Verify (round equality), capsule.Encrypt (round time ≥ requested), agewrap.CheckTimeStanzas provider.TestVerifyRejects; mutations DateKey A + release of round B, tlock stanza round differs from DateKey.round
18 dk1_ representation DateKey.CanonicalJSON, DateKey.Compact datekey.TestGoldenDK1Vectors, TestNormativeRoundVector
19 dk1_ canonicality datekey.Parse datekey.TestGoldenDK1Vectors, TestNumberSpellings, FuzzParse; mutation non-canonical dk1_ JSON; testdata/vectors/dk1.json
20 File extensions and magic magic checks in capsule.ParsePrelude, accesskey.Decode mutation a .dkk offered as a .dkc; accesskey.TestDecodeRejects a .dkc
21 capsule_id capsule.Encrypt (16 bytes from crypto/rand), capsule.DecodeHeader capsule.TestPortableKeysAreNeverReused
22 .dkc framing capsule.Prelude, capsule.ParsePrelude mutations version changed, flags != 0, reserved != 0, magic, length limits; capsule.FuzzParsePrelude
23 PRELUDE Prelude.Bytes capsule.TestConformanceFixtures
24 PUBLIC_HEADER capsule.Header, EncodeHeader, DecodeHeader capsule.TestConformanceFixtures, FuzzDecodeHeader; mutations header schema version changed, unknown key in PUBLIC_HEADER
25 Declared access policy capsule.Policy; capsule.Open step 12 mutations access_policy=… with … structure (four cases), undefined access_policy
26 Header binding capsule.HeaderBinding; capsule.Open step 15 capsule.TestConformanceFixtures; mutation PUBLIC_HEADER_A + SEALED_CONTROL_B
27 Pre-unlock validation capsule.Inspect (steps 1–8), agewrap.Stanzas probe capsule.TestMutationCorpus (no release request for any pre-unlock failure), FuzzInspect
28 Three age files capsule.Encrypt, capsule.Open capsule.TestEncryptRoundTripBothPolicies
29 PAYLOAD_AGE capsule.Encrypt step 4; agewrap.PayloadIdentity, agewrap.CheckPayloadStanzas agewrap.TestPayloadIdentityStrictness; mutation extra stanza in PAYLOAD_AGE
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 agewrap.PayloadIdentity mutation SEALED_CONTROL_A + PAYLOAD_AGE_B; agewrap.TestPayloadIdentityStrictness
31 CONTROL_CBOR capsule.Control, EncodeControl, DecodeControl capsule.TestConformanceFixtures, FuzzDecodeControl; mutation unknown critical CONTROL_CBOR extension
32 time_only capsule.Encrypt; agewrap.TimeRecipient fixtures time_only*, empty_payload; capsule.TestInteropTleOpensSealedControl (-tags interop, official tle CLI)
33 time_and_key capsule.Encrypt (seal); agewrap.AccessIdentity fixtures time_and_key_*; capsule.TestEncryptRoundTripBothPolicies
34 SEALED_CONTROL capsule.Encrypt; capsule.Open step 11 capsule.TestConformanceFixtures
35 tlock strict mode agewrap.TimeRecipient, agewrap.TimeIdentity (pinned parameters only, exact stanza arguments) agewrap.TestTimeIdentityStrictness, TestInteroperabilityWithTlockLibrary, TestTimeIdentityRelease
36 Policy ↔ structure capsule.Open step 12, agewrap.CheckAccessStanzas mutations access_policy=… (four cases), non-X25519 stanza in INNER_ACCESS_AGE
36.1 Authenticity semantics documented in README.md, SECURITY.md — (a property the protocol does not provide)
37 X25519 recipient V1 age.X25519Recipient; agewrap.X25519IdentityFromRaw agewrap.TestRawKeys
38 Portable Access Key EncryptOptions.NewPortableKey (fresh I_ACCESS per capsule; no API accepts an existing one); accesskey.AccessKey capsule.TestPortableKeysAreNeverReused
39 Multiple recipients capsule.Encrypt; agewrap.AccessIdentity capsule.TestFixtureRecipients, TestEncryptRoundTripBothPolicies
40 .dkk framing accesskey.Encode, accesskey.Decode accesskey.TestDecodeRejects, FuzzDecode
41 .dkk BODY_CBOR AccessKey.MarshalBody, accesskey.DecodeBody accesskey.TestFixtures
42 credential_id capsule.Encrypt (16 bytes from crypto/rand) capsule.TestPortableKeysAreNeverReused
43 verification_metadata accesskey.Verification; capsule.Open (checkCapsuleDigest, seekable readers) accesskey.TestDecodeRejects empty verification map; mutation capsule_digest of the .dkk does not match
44 Application extensions in .dkk AccessKey.Critical/Noncritical; capsule.Open (checkAccessKey) accesskey.TestEncodeRejectsAbsenceAsEmptyMap
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
48 Multi-relay provider/drand.Client (race, first verified release wins) drand.TestRaceWaitsForAValidSignature
49 Direct recovery from the provider provider/drand drand.TestLiveRelays, capsule.TestLiveLifecycle (-tags integration)
50 Historical release dependency documented in README.md —
51 Quicknet release verification provider.Verify provider.TestVerifyPublishedReleases, TestVerifyRejects, TestVerifyUsesThePinnedKeyOnly
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
54 Extensions extension extension/*; mutations unknown critical … extension; capsule.TestKnownCriticalExtensions
55 Auxiliary integrity capsule_digest treated as UX only —
56 Atomic plaintext output capsule.Open contract; cmd/datekeys.writeAtomic cmd/datekeys.TestOutputNotPublishedOnFailureOrOverwrite, TestDecryptFailuresLeaveNothing
57 Parser limits capsule.MaxPublicHeaderLen, MaxSealedControlLen, accesskey.MaxBodyLen, codec limits mutations …_LEN above the limit; accesskey.TestDecodeRejects body length above the limit
58 Canonical CBOR codec.Marshal, codec.Unmarshal (re-encoding comparison), codec.Valid codec.TestUnmarshalRejectsNonCanonical, TestValid, TestRoundTripProperty, FuzzValid
58.1 Absent optional fields are omitted extension.Encode (nil for empty), re-encoding check, accesskey verification map codec empty optional array present; accesskey empty extension array, empty verification map, null verification
59 Supply-chain security pinned go.mod/go.sum, .github/workflows, .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 capsule.Encrypt (steps numbered in comments) capsule.TestEncryptRoundTripBothPolicies
62 time_and_key encryption flow capsule.Encrypt capsule.TestEncryptRoundTripBothPolicies, TestPortableKeysAreNeverReused
63 Decryption flow capsule.Inspect (steps 1–8), capsule.Open (steps 9–18), MUST rules inside agewrap identities capsule.TestConformanceFixtures (stage by stage), TestMutationCorpus
64 Mandatory mutation tests capsule/mutation_test.go capsule.TestMutationCorpus: the 20 listed mutations plus 25 more
65 Quicknet vectors internal/testkit.RoundVectors datekey.TestGoldenRoundVectors
66 dk1_ vectors internal/testkit.DK1Vectors datekey.TestGoldenDK1Vectors
67 .dkc vectors testdata/fixtures/*.dkc + *.json, internal/testkit/genfixtures capsule.TestConformanceFixtures
68 .dkk vectors testdata/fixtures/*.dkk + *.dkk.json accesskey.TestFixtures
69 Normative errors errors.go datekeys.TestCatalogueMatchesSpec, TestCode
70 Compatibility magic and version checks, codec.CheckSchema mutations; codec.TestCheckSchema
71 Profile registry profile.Decode + profile.NewRegistry with pinned hashes profile.TestRegistry
72 Extension registry extension.Registry, extension.Set capsule.TestKnownCriticalExtensions
75 Blocking requirements before v1.0 items 1–9 above; item 10 (external review) pending —

Error mapping

Where the specification does not name the error of a failure, the reference implementation uses the following mapping. Each entry is a reproducible case under the change policy of §76.

Failure Error
Bytes that are not the deterministic encoding of a valid schema instance: malformed CBOR, non-canonical encoding, unknown key, missing key, wrong type, wrong type tag (key 0), wrong field length, undefined access_policy, empty optional array or map, extension rules ERR_NON_CANONICAL_CBOR
Schema version (key 1) other than 1 ERR_UNSUPPORTED_VERSION
Truncated framing, length fields beyond the §57 limits, data after BODY_CBOR, malformed or unauthenticated age data, truncated or modified STREAM, trailing data after PAYLOAD_AGE, a PAYLOAD_AGE that I_PAYLOAD cannot open ERR_INTEGRITY
Stanza count or type violations in OUTER_TIME_AGE, PAYLOAD_AGE or INNER_ACCESS_AGE, including two stanzas for one recipient ERR_POLICY_STRUCTURE_MISMATCH
tlock stanza round argument not exactly the canonical decimal DateKey round ERR_ROUND_MISMATCH
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 ERR_PROFILE_MISMATCH
Instant before the profile genesis or after 9999-12-31T23:59:59Z; round outside the profile range ERR_DATEKEY_INVALID
Unknown access_type, wrong material length, .dkk for another capsule_id, capsule_digest mismatch, no supplied identity is a recipient ERR_ACCESS_INVALID
Round time not reached yet (no request is made), no source delivered the release ERR_RELEASE_UNAVAILABLE

Implementation decisions to confirm in the specification

These are choices the reference implementation had to make where v0.8.1 is silent or provisional (§74). None changes the protocol semantics; each is a candidate clarification under §76.

  1. Pre-genesis instants. §15 defines the candidate formula relative to genesis_time; instants before it are rejected with ERR_DATEKEY_INVALID instead of resolving to round 1.
  2. Round bounds. dk1_ accepts rounds in 1..2^53−1 so that JSON parsers based on IEEE 754 doubles read the same integer; a profile further limits rounds to round times up to 9999-12-31T23:59:59Z (Quicknet: 83 903 165 811).
  3. profile_id alphabet. [a-z0-9][a-z0-9:._-]{0,127}, which keeps the canonical dk1_ JSON free of escapes and makes its re-emission trivial.
  4. Number spellings in dk1_. JSON numbers are compared by value, so 1e3 or 1000.0 for 1000 are ERR_DATEKEY_NON_CANONICAL, while non-integers, negatives and out-of-range values are ERR_DATEKEY_INVALID. Padded or standard-alphabet Base64 and non-zero trailing bits are non-canonical.
  5. Closed maps. Unknown keys in core maps are rejected; applications use extensions (§1, §54).
  6. Extension data. Key 2 is optional and omitted when absent; data must be deterministic CBOR without tags. extension_id is 1 to 256 bytes of UTF-8. No V1 schema allows repeating an extension_id.
  7. One stanza per recipient. Enforced as far as a recipient can observe it: no repeated X25519 ephemeral share, and no identity that unwraps more than one stanza.
  8. Strict tlock stanza arguments. Exact string comparison, as the tlock library itself does for the chain hash; a round with leading zeros is a mismatch.
  9. capsule_digest. Written by Encrypt for every portable key and checked before any request when the capsule reader is seekable; it remains a UX shortcut (§43).
  10. Creation in the past. Encrypt requires the unlock time to be strictly after the injected clock.
  11. 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.

Powered by TurnKey Linux.