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.2** ([`spec/`](spec/DateKeys_Protocol_Specification_v0.8.2.md)).
|
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
|
|
|
|
[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 g.activething.com/go/DateKeys/cmd/datekeys@latest
|
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
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
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`.
|
|
|
|
|
|
|
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
|
|
|
|
```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),
|
|
|
|
|
|
CBOR profile and schema vectors, the exported mutation corpus and a
|
|
|
|
|
|
differential corpus of the pre-unlock checks; formats in
|
|
|
|
|
|
[`testdata/README.md`](testdata/README.md).
|
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
|
|
|
|
- `testdata/fixtures`: official `.dkc`/`.dkk` fixtures over published rounds,
|
|
|
|
|
|
with the BLS signature embedded and every intermediate value (spec §67, §68);
|
|
|
|
|
|
they decrypt offline. Each `.dkc` has its frozen `datekeys inspect -json`
|
|
|
|
|
|
output.
|
Spec v0.8.2 amendment: canonical point encoding; no library error text
Amendment of the unreleased v0.8.2, recorded in §76 with its case: the
second implementation's phase-2 research found that tlock-js over
@noble/curves 1.9.7 accepts U re-encoded as c0 + p and a signature
x + p and returns the same file key, while the reference rejects both
(noble 1.9.7 differed from kilic on 5,615 of 41,686 encodings), and the
spec did not say which encodings are valid.
- §12.2 defines the canonical encoding of a BLS12-381 point (drand's
compressed ZCash form) and requires decoders to reject every other
byte string; §12.1 applies it to public_key.
- §63 step 10 applies it to the release signature (ERR_RELEASE_INVALID)
and step 11 defines the tlock stanza body U || V || W (96 + 16 + 16
bytes for Quicknet) with a canonical, non-infinity U (ERR_INTEGRITY).
- §64 gains ten mutations, exported to mutations.json (65 cases). The
signature x + p case uses published Quicknet round 1004, the first
after 1000 whose x allows x + p < 2^381. The reference already gave
every stated code and step.
Errors no longer copy text from tlock, kyber, age, drand or
kyber-bls12381. kyber's IBE error carried the candidate plaintext and r,
and with one bit of W flipped the message disclosed the real tlock file
key with that bit flipped. Every such place now uses a fixed reason with
its normative sentinel; TestTlockFailureDiagnosticsCarryNoSecrets fails
with the old wrapping.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 weeks ago
|
|
|
|
- `internal/testkit.Mutations`: the 33 mutations of spec §64 and 32 more, each
|
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
|
|
|
|
with its exact error and step, and a check that pre-unlock failures never
|
|
|
|
|
|
cause a release request; exported to `testdata/vectors/mutations.json`.
|
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
|
|
|
|
- [`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).
|