Initial implementation of the DateKeys Protocol v0.8.1
Reference implementation in Go, built from the implementation plan
(milestones M0 to M5): datekey, profile, provider, codec, agewrap,
extension, capsule, accesskey, the datekeys CLI, official vectors and
fixtures, the mutation corpus, fuzz targets, interop and live tests,
CI workflows, traceability and policy documents.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2 weeks ago
|
|
|
|
# datekeys-go
|
|
|
|
|
|
|
|
|
|
|
|
Reference implementation in Go of the **DateKeys Protocol Specification
|
|
|
|
|
|
v0.8.1** ([`spec/`](spec/DateKeys_Protocol_Specification_v0.8.1.md)).
|
|
|
|
|
|
[Versión en español](README.es.md).
|
|
|
|
|
|
|
|
|
|
|
|
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`](datekey) |
|
|
|
|
|
|
| Provider Profile, pinned Quicknet profile, `profile_hash` | §10–§13 | [`profile`](profile) |
|
|
|
|
|
|
| Release sources, local BLS verification, drand relays | §45–§52 | [`provider`](provider), [`provider/drand`](provider/drand) |
|
|
|
|
|
|
| DateKeyCap `.dkc`: `time_only` and `time_and_key` | §20–§39, §61–§63 | [`capsule`](capsule) |
|
|
|
|
|
|
| DateKeys Access Key `.dkk` | §40–§44 | [`accesskey`](accesskey) |
|
|
|
|
|
|
| Extensions | §54 | [`extension`](extension) |
|
|
|
|
|
|
| Deterministic CBOR | §58 | [`codec`](codec) |
|
|
|
|
|
|
| Normative errors | §69 | [`errors.go`](errors.go) |
|
|
|
|
|
|
| CLI | — | [`cmd/datekeys`](cmd/datekeys) |
|
|
|
|
|
|
|
|
|
|
|
|
No cryptography is implemented here. Encryption is [age](https://age-encryption.org)
|
|
|
|
|
|
(`filippo.io/age`); the timelock is [tlock](https://github.com/drand/tlock)
|
|
|
|
|
|
(its exported core only); BLS verification is drand's. This module adds
|
|
|
|
|
|
framing, CBOR, 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, concrete extensions, and the TypeScript client (plan §2).
|
|
|
|
|
|
|
|
|
|
|
|
## A capsule, in one picture
|
|
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
|
.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(X25519 recipients → CONTROL_CBOR))
|
|
|
|
|
|
CONTROL_CBOR = { header_binding = SHA-256(PRELUDE || PUBLIC_HEADER), I_PAYLOAD, extensions }
|
|
|
|
|
|
PAYLOAD_AGE = age(X25519 R_PAYLOAD → your data), streamed
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
## CLI
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
go install github.com/datekeys/datekeys-go/cmd/datekeys@latest
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
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 -policy time_and_key -dkk letter.dkk -in letter.txt -out letter.dkc
|
|
|
|
|
|
datekeys inspect -in letter.dkc
|
|
|
|
|
|
datekeys decrypt -in letter.dkc -out letter.txt -dkk letter.dkk
|
|
|
|
|
|
datekeys profile hash
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
`encrypt` never touches the network. `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, verifies it locally
|
|
|
|
|
|
and publishes the plaintext only after age authenticated all of it. Outputs are
|
|
|
|
|
|
never overwritten.
|
|
|
|
|
|
|
|
|
|
|
|
## Library
|
|
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
|
reg, err := profile.Default() // pinned Quicknet profile, checked against its profile_hash
|
|
|
|
|
|
|
|
|
|
|
|
// Encrypt: no network, the round is resolved locally.
|
|
|
|
|
|
res, err := capsule.Encrypt(dst, src, 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
|
|
|
|
|
|
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.
|
|
|
|
|
|
opened, err := capsule.Open(ctx, tmp, f, capsule.OpenOptions{
|
|
|
|
|
|
Registry: reg,
|
|
|
|
|
|
Source: drand.New(),
|
|
|
|
|
|
AccessKey: key, // or Identities: []age.Identity{...}
|
|
|
|
|
|
Now: time.Now,
|
|
|
|
|
|
})
|
|
|
|
|
|
if errors.Is(err, datekeys.ErrReleaseUnavailable) { /* not yet */ }
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
`Open` streams the plaintext; on error, discard what was written (spec §56).
|
|
|
|
|
|
Every protocol failure wraps one of the 17 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 all authenticated;
|
|
|
|
|
|
any change makes opening fail (fixtures and the mutation corpus).
|
|
|
|
|
|
- **No authorship**: `time_only` gives internal coherence, not proof of who
|
|
|
|
|
|
created a capsule, before or after it matures; `time_and_key` adds an access
|
|
|
|
|
|
barrier, not a signature (spec §36.1).
|
|
|
|
|
|
- **Recovery years later** needs the historical release: from a drand relay
|
|
|
|
|
|
that still serves it or from any cache, re-verified locally (spec §50).
|
|
|
|
|
|
|
|
|
|
|
|
See [SECURITY.md](SECURITY.md).
|
|
|
|
|
|
|
|
|
|
|
|
## Conformance and tests
|
|
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
|
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).
|
|
|
|
|
|
- `testdata/fixtures`: official `.dkc`/`.dkk` fixtures over published rounds,
|
|
|
|
|
|
with the BLS signature embedded and every intermediate value (spec §67, §68);
|
|
|
|
|
|
they decrypt offline.
|
|
|
|
|
|
- `capsule/mutation_test.go`: the 20 mutations of spec §64 and 25 more, each
|
|
|
|
|
|
with its exact error and step, and a check that pre-unlock failures never
|
|
|
|
|
|
cause a release request.
|
|
|
|
|
|
- [`docs/traceability.md`](docs/traceability.md): spec section → code → test.
|
|
|
|
|
|
- [`spec/datekeys.cddl`](spec/datekeys.cddl): CBOR schemas.
|
|
|
|
|
|
|
|
|
|
|
|
## License
|
|
|
|
|
|
|
|
|
|
|
|
Code: Apache-2.0 ([LICENSE](LICENSE)). Specification: CC-BY-4.0
|
|
|
|
|
|
([spec/README.md](spec/README.md)). `codec/bech32` is copied from age under its
|
|
|
|
|
|
own license. "DateKeys" is a reserved name: see [TRADEMARKS.md](TRADEMARKS.md).
|