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.
 
 
Go to file
dev fe50885491
Spec v0.15 approved: SpecVersion 0.15 and its SHA-256
20 hours ago
.gitea/workflows Host on Gitea, not GitHub 2 weeks ago
.githooks Run the local gate from a versioned pre-push hook 2 weeks ago
accesskey Review fixes: author keys, the writer, the CLI, extensions and the locator 6 days ago
agewrap Implement capsule format 2 of spec v0.9 1 week ago
authorkey Review fixes: author keys, the writer, the CLI, extensions and the locator 6 days ago
capsule Spec v0.15 draft: remove the .dkr file 20 hours ago
cmd/datekeys Spec v0.15 draft: remove the .dkr file 20 hours ago
codec Hand-written CBOR codec and shared test vectors (plan steps 2b and 3) 2 weeks ago
datekey Reject invalid UTF-8 in the dk1_ JSON at step 2 (spec §19) 2 weeks ago
docs Spec v0.15 approved: SpecVersion 0.15 and its SHA-256 20 hours ago
extension Review fixes: author keys, the writer, the CLI, extensions and the locator 6 days ago
internal Spec v0.15 draft: remove the .dkr file 20 hours ago
locator FuzzCheckResolvedIP: no NAT64 prefix lets a private IPv4 address through 1 day ago
profile Spec v0.14 draft: one scheme of drand, bls-unchained-g1-rfc9380 24 hours ago
provider Spec v0.15 draft: remove the .dkr file 20 hours ago
scripts Spec v0.15 draft: remove the .dkr file 20 hours ago
spec Spec v0.15 approved: SpecVersion 0.15 and its SHA-256 20 hours ago
testdata Spec v0.15 approved: SpecVersion 0.15 and its SHA-256 20 hours ago
wordkey Key of words v2: salted with capsule_id, and the checks of the writer 6 days ago
.gitattributes Initial implementation of the DateKeys Protocol v0.8.1 2 weeks ago
.gitignore Format 3, step 1: Unicode 18.0.0 tables and the path and text rules 1 week ago
.golangci.yml Host on Gitea, not GitHub 2 weeks ago
.goreleaser.yaml Host on Gitea, not GitHub 2 weeks ago
CHANGELOG.md Spec v0.15 approved: SpecVersion 0.15 and its SHA-256 20 hours ago
CONTRIBUTING.md Hand-written CBOR codec and shared test vectors (plan steps 2b and 3) 2 weeks ago
LICENSE Initial implementation of the DateKeys Protocol v0.8.1 2 weeks ago
README.es.md Spec v0.15 approved: SpecVersion 0.15 and its SHA-256 20 hours ago
README.md Spec v0.15 approved: SpecVersion 0.15 and its SHA-256 20 hours ago
SECURITY.md Spec v0.15 approved: SpecVersion 0.15 and its SHA-256 20 hours ago
TRADEMARKS.md Implement v0.8.2 extension data, limits and writer self-checks 2 weeks ago
errors.go Format 3, step 6a: specification 0.10 and ERR_HEAD_INVALID 1 week ago
errors_test.go Version constants: the specification and the module 1 week ago
go.mod Spec v0.8.2: corrections from the formal review 2 weeks ago
go.sum Hand-written CBOR codec and shared test vectors (plan steps 2b and 3) 2 weeks ago
version.go Spec v0.15 approved: SpecVersion 0.15 and its SHA-256 20 hours ago
version_test.go Version constants: the specification and the module 1 week ago

README.md

datekeys-go

Reference implementation in Go of the DateKeys Protocol Specification v0.15 (spec/), tagged spec-v0.15, on the long-term recovery of capsules: the release object, a release in hand that the clock does not stop, archives and cache services that keep the releases of all rounds, and an annex to open a capsule without DateKeys software. Versión en español.

DateKeys encrypts data so that it can only be opened after a chosen instant. The time condition comes from the drand Quicknet randomness beacon: data is sealed with timelock encryption to a future round, and the round's BLS signature, published by drand when the round arrives, is the key. Everything that can be verified locally is verified locally; relays, caches and APIs are untrusted transports.

Status: v0.x, pre-standard. The specification is a draft and the API may change before v1.0.0. The code has not had an external cryptographic review yet (spec §75). Do not rely on it for high-value secrets.

What it implements

Object Spec Package
DateKey: date → round, canonical dk1_… string §14–§19 datekey
Provider Profile, pinned Quicknet profile, profile_hash §10–§13 profile
Release sources, local BLS verification, drand relays §45–§52 provider, provider/drand
DateKeyCap .dkc: time_only and time_and_key, the files of format 3, the security area and its verdicts §20–§39, §61–§63 capsule
Paths and texts of a head, with the Unicode 18.0.0 and best-fit tables §29.5, §29.6 internal/pathrule
Author signature: what is signed, alg 1 and 2, the seal of seal_type 2 §29.8–§29.11 capsule
Author keys dkauthor1… and the strict Ed25519 of alg 1 §29.9, §29.12 authorkey, internal/ed25519strict
Signature with certificates (alg 2, CMS) and time seal (RFC 3161): DER, the profile of the certificate, the closed table of algorithms §29.10, §29.11 internal/cms, internal/der
The public note datekeys.note §24.1 extension, capsule
Key of words §38.1 wordkey
DateKeys Access Key .dkk §40–§44 accesskey
The extension datekeys.capsule: locator, envelope and addresses §44.1 locator
Extensions and their registry §54, §72 extension
Deterministic CBOR §58 codec
Normative errors §69 errors.go
CLI — cmd/datekeys

No cryptography is implemented here. Encryption is age (filippo.io/age); the timelock is tlock (its exported core only); BLS verification is drand's; author signatures and seals are verified with the standard library of Go, with the strict profile of Ed25519 checked around crypto/ed25519. This module adds framing, CBOR, DER, bindings, verification rules and the flow, and it enforces the stanza rules of the protocol inside the age identities, so that a file is never accepted just because age could unwrap a key.

Not implemented on purpose: the Release API server and queue, storage and delivery, the download of the rest of an envelope, and the TypeScript client (plan §2). Package locator checks the addresses of a locator and what a reader brings from them, and downloads nothing (spec §44.1). The signature with certificates and the seal check the cryptography, never who issued a certificate or a seal, or whether it was revoked: that is for an official validator (spec §29.10). Brainpool curves and national algorithms such as GOST or SM2 are outside the table.

Versions

Three version numbers, each with its own meaning:

Version Where Changes when
Format Inside the objects: the capsule format, the VERSION of the DKC1 prelude, 3 when written and 1 to 3 when read, which is also the schema version of CONTROL_CBOR; and 1 for the framing of DKK1 and the schema of the other objects, the head and security of format 3 included The format changes. A reader rejects a version it does not know (spec §22, §70)
Specification datekeys.SpecVersion, today 0.15, and the tag spec-v0.15. A draft, such as v0.15 was, has no tag and does not change it The normative text changes. Spec §76 records each change with its case
Module The tags of this Go module, vX.Y.Z, and datekeys.Version() The API or the behaviour changes. Semantic versioning, with no stability promise before v1.0.0

datekeys version prints the module version, the specification and the Go toolchain. A binary built in a checkout shows the pseudo-version of its commit, for example v0.0.0-20260928105528-9ac9cd952f04.

Each release states what it covers, here and in the CHANGELOG. The code of this branch, not yet released, covers:

  • specification 0.15: it writes capsule format 3 and reads formats 1, 2 and 3;
  • the pinned Quicknet profile, and any profile of the scheme bls-unchained-g1-rfc9380, the only one spec v0.14 admits;
  • encryption, inspection and opening, author signatures and seals, the public note, the key of words and the locator, and the CLI;
  • every shared vector and fixture of testdata/.

The first tag, v0.1.0, comes once go get works from a clean machine.

A capsule, in one picture

.dkc = PRELUDE (16 B) || PUBLIC_HEADER (CBOR) || SEALED_CONTROL (age) || PAYLOAD_AGE (age, to EOF)

time_only:     SEALED_CONTROL = age(tlock round R → CONTROL_CBOR)
time_and_key:  SEALED_CONTROL = age(tlock round R → age(16 X25519 stanzas → CONTROL_CBOR))
CONTROL_CBOR = { header_binding = SHA-256(PRELUDE || PUBLIC_HEADER), I_PAYLOAD, extensions, L, padding rule }
PAYLOAD_AGE  = age(X25519 R_PAYLOAD → BODY || zeros up to P = rule(L)), streamed

BODY = AREA_LEN || SECURITY_LEN || HEAD_LEN (3 × uint32)
    || SECURITY_CBOR, zeros up to AREA_LEN (32 KiB)   author signature and seal, or empty
    || HEAD_CBOR                                      salt, comment, declared author, the files
    || the files, one after another

This is format 3, the one encrypt writes (spec §22, §29.2). Its 16 stanzas hold from 1 to 16 credentials and a dummy in each slot left, in a random order, and its payload is padded to P, so that until the unlock date nobody learns the number of credentials, the exact length L, the names of the files or how many there are, beyond what P bounds (spec §29.1, §39, §55.2). The security area measures 32 KiB, signed or not, so that P does not tell whether a capsule is signed, and 64 KiB only when the creator asks for it because a signature does not fit (spec §29.2). The writers of v0.10 wrote 512 bytes, which a reader still accepts. Once open, it gives files with their paths, sizes, SHA-256 and modification times, a comment and a declared author, and the verdicts of its security area. Format 2, that of v0.9, holds one content; format 1, that of v0.8.2, has one stanza per credential and no padding. Readers still open both.

CLI

go install g.activething.com/go/DateKeys/cmd/datekeys@latest

The module is served by the project's own Gitea, whose certificate Go does not trust by default. Set GOPRIVATE=g.activething.com so that the Go proxy and checksum database are bypassed for it, and either install the server's certificate or, on a trusted network, set GOINSECURE=g.activething.com.

datekeys datekey resolve -at 2030-01-01T00:00:00Z
datekeys encrypt -at 2030-01-01T00:00:00Z -in letter.txt -out letter.dkc
datekeys encrypt -at 2030-01-01T00:00:00Z -in photos -in letter.txt -comment "For Ana" -author "Juan" -out gift.dkc
datekeys encrypt -at 2030-01-01T00:00:00Z -policy time_and_key -dkk gift.dkk -in photos -out gift.dkc
datekeys encrypt -at 2030-01-01T00:00:00Z -policy time_and_key -words-file words.txt -in letter.txt -out letter.dkc
datekeys encrypt -at 2030-01-01T00:00:00Z -padding bloque256 -no-mtime -in letter.txt -out letter.dkc
datekeys author keygen -out author.key -pass-file pass.txt
datekeys encrypt -at 2030-01-01T00:00:00Z -in letter.txt -note "Letters from Lisbon" -sign author.key -sign-pass-file pass.txt -out letter.dkc
datekeys decrypt -in letter.dkc -out letter -expect-author dkauthor1...
datekeys inspect -in gift.dkc
datekeys decrypt -in gift.dkc -out gift -dkk gift.dkk
datekeys decrypt -in gift.dkc -out gift -dkk gift.dkk -release round.cbor
datekeys profile hash
datekeys version

encrypt never touches the network. Each -in is a file or a folder; a folder gives its name as the first segment of its paths, as a browser does, and is walked without following links, taking regular files only and leaving out .DS_Store, Thumbs.db, desktop.ini, ._* and __MACOSX. A path or a text that breaks a rule of spec §29.5 or §29.6 is refused, naming the rule and the character. The files keep their modification times unless -no-mtime. The content is padded with the rule reforzado, or bloque256 if asked (spec §29.1). inspect runs only the pre-unlock checks (spec §63 steps 1–8): it never requests a release and never uses a secret. decrypt fetches the release from public drand relays and verifies it locally, never before the round time; with -release it takes the release in hand instead, a release object, drand's JSON or a local release archive, from any source, without any request and whatever the clock says (spec v0.15, §47.1, §63 step 9.c). The files of a format 3 capsule go to the new folder -out, staged inside it and moved into place only after every check passed (spec §56); a capsule with only a comment creates no folder. It shows the verdicts first and last, and the declared author, the comment and the paths as text of the creator that nobody has checked, each line behind a │ prefix and never wider than the terminal (spec §29.7). A line of the verdicts breaks at the last space that fits, and each row after its first starts with ↳ (two spaces, U+21B3 and a space): the terminal never breaks one, and no name of a certificate can start a row and pass for a verdict. Formats 1 and 2 still give one file. Outputs are never overwritten.

author keygen makes an Ed25519 author key (spec §29.12) in a file encrypted with a passphrase, or as text with -plain, and prints its public key, dkauthor1…; author public prints it again. The passphrase comes from a file, or from the standard input with -, never from the command line or the environment. encrypt -sign signs the capsule with it (alg 1): before it signs it shows the key and the code of AUTHOR_MESSAGE (spec §62.1 rule 20), and it checks the signature before writing anything. decrypt shows the signature; with -expect-author it fails, and writes nothing, unless the capsule has a valid signature of that public key, the verdict F4: it compares the key before step 18, when no file has been published.

encrypt -note puts a public note in clear in the capsule (spec §24.1), and warns that it is public. inspect and decrypt show it as text of the creator that nobody has checked; a note that breaks the rules of text is not shown, and both say so (public_note_unusable in inspect -json). -words and -words-file give a time_and_key capsule a key of words: at least six different words of three or more letters, which open it with decrypt -words-file instead of a .dkk (spec §38.1).

Library

reg, err := profile.Default() // pinned Quicknet profile, checked against its profile_hash

// EncryptFiles: no network, the round is resolved locally. It reads each
// file twice, and fails if a file changes between the two readings.
res, err := capsule.EncryptFiles(dst, []capsule.Source{{
	Path:    "photos/beach.jpg",
	Size:    info.Size(),
	ModTime: info.ModTime(),
	Open:    func() (io.ReadCloser, error) { return os.Open(name) },
}}, capsule.EncryptOptions{
	Profile:        profile.Quicknet(),
	UnlockAt:       time.Date(2030, 1, 1, 0, 0, 0, 0, time.UTC),
	Policy:         capsule.TimeAndKey,
	NewPortableKey: true, // res.PortableKey is the .dkk; encode it with accesskey.Encode
	Comment:        "For Ana",
	Now:            time.Now,
})

// Inspect: steps 1–8, no network, no secrets.
in, err := capsule.Inspect(f, capsule.InspectOptions{Registry: reg})

// Open: steps 1–18; the release is verified locally. A format 3 capsule
// gives its files to a Sink, which publishes them in Commit, at step 18.
opened, err := capsule.Open(ctx, nil, f, capsule.OpenOptions{
	Registry:  reg,
	Source:    drand.New(),
	AccessKey: key, // or Identities: []age.Identity{...}
	Sink:      sink,
	Now:       time.Now,
})
if errors.Is(err, datekeys.ErrReleaseUnavailable) { /* not yet */ }

A Sink gets Begin with the head, Create for each file and Commit only when step 17 has passed; after any failure it gets Abort, and nothing it received may be presented as valid (spec §56). opened.Head holds the paths, sizes, SHA-256 and modification times, the comment and the declared author, and opened.Verdicts.Lines() the verdicts to show before them (spec §29.7). EncryptOptions.AuthorKey or CMSSigner sign the capsule, and Sealer seals it (spec §29.9 to §29.11); OpenOptions.AuthorKeys are the keys that the person saved, which give F3, and OpenOptions.Accept sees the verdicts before step 18 and can refuse to publish the files. Open without a Sink stops right after step 2 with capsule.ErrSinkRequired, a caller error with no code. Capsules of formats 1 and 2 still stream their content to dst, never the padding. opened.Format is the format of the capsule: format 1 hides neither the number of credentials nor the exact length of the content, so show it (spec §70). Every protocol failure wraps one of the 19 normative errors of spec §69, so errors.Is and datekeys.Code(err) identify it.

Security properties and limits

  • Time confidentiality holds under drand's threshold assumption. The Quicknet timelock is not post-quantum: ciphertexts kept for years are exposed to harvest-now, decrypt-later (spec §7.7, §53).
  • No trust in servers: the profile is pinned in the binary, the round is computed locally, releases are BLS-verified locally, and a valid signature of another round is rejected (spec §13, §17, §51).
  • Integrity: framing, header, control and payload are authenticated against whoever does not know the file keys: a third party's change makes opening fail (fixtures and the mutation corpus). Once the round is published anyone can compute the time file key, and anyone can seal a new control for a public header; who can write each part, and from which step it is bound, is the trust model of spec §27 and §55.1.
  • Metadata privacy: until the date, the exact length of the content, the number of credentials, and the names, sizes and number of the files stay hidden, beyond what the padded size P bounds, and so does whether the capsule is signed, unless its area was widened; the date, the access policy, capsule_id and the header extensions, the public note among them, do not (spec §55.2).
  • Safe extraction: the paths of a head cannot leave the folder, collide on Windows, macOS or Linux, hide characters or change the direction of the text; the rules use fixed Unicode 18.0.0 tables, never those of the platform (spec §29.5, §29.6).
  • Authorship only by a signature: the declared author, the comment, the public note and the modification times are text of the creator that proves nothing (spec §24.1, §29.7, §36.1). A signature of alg 1 proves that whoever had the secret key signed, not who has it; for a signature with certificates and a seal, DateKeys checks the cryptography and the dates, never who issued them or whether they were revoked (spec §29.9 to §29.11). The verdicts never decide the opening (spec §29.3). time_and_key adds an access barrier, not a signature.
  • Recovery years later needs the historical release: from a drand relay that still serves it or from any cache, re-verified locally (spec §50). Spec v0.15 rests it on archives and cache services, of DateKeys or others, that keep the releases of all rounds, with no promise of hosting one, and its §79 says how to open a capsule without DateKeys software, which scripts/recovery_check.sh checks.

See SECURITY.md.

Conformance and tests

go test ./...                               # unit, golden vectors, fixtures, mutation corpus
go test -race -cover ./...
go test -fuzz=FuzzInspect ./capsule         # one fuzz target at a time
go test -tags interop ./capsule             # official age and tle CLIs open our files
go test -tags integration ./capsule ./provider/drand   # live Quicknet
  • testdata/vectors: profile hash, date→round and dk1_ vectors (spec §65, §66), CBOR profile and schema vectors, padding vectors (spec §29.1), the paths, the keys of R7, the heads and the security areas of format 3 (spec §67), the strict Ed25519 of alg 1, the signatures with certificates and the seals, the public note and the extension datekeys.capsule, the exported mutation corpus and a differential corpus of the pre-unlock checks; formats in testdata/README.md.
  • testdata/fixtures: official .dkc/.dkk fixtures over published rounds, with the BLS signature embedded and every intermediate value (spec §67, §68); they decrypt offline. Fourteen are of format 3, five of them with the area of 32 KiB of v0.11: unsigned, signed with alg 1, sealed, signed with certificates, and with a public note. The seven of format 2, from v0.9, and the five of format 1, from v0.8.2, are kept for compatibility. Each .dkc has its frozen datekeys inspect -json output.
  • internal/testkit.Mutations: the mutations of spec §64, the 33 of its first two lists in each format, the 23 of the list of format 2, the 48 of the list of format 3 and 8 of the list of v0.11, and 40 more, each with its exact error and step, or its verdicts when it opens, and a check that pre-unlock failures never cause a release request; exported to testdata/vectors/mutations.json.
  • docs/traceability.md: spec section → code → test.
  • spec/datekeys.cddl: CBOR schemas.

License

Code: Apache-2.0 (LICENSE). Specification: CC-BY-4.0 (spec/README.md). codec/bech32 is copied from age under its own license. "DateKeys" is a reserved name: see TRADEMARKS.md.

Powered by TurnKey Linux.