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
// Command datekeys encrypts, inspects and opens DateKeyCap (.dkc) files.
//
Format 3, step 5: the CLI
datekeys encrypt writes format 3 and datekeys decrypt writes its files
to a new folder, with the presentation of spec 29.7.
- encrypt: -in is repeatable and takes files and folders; a folder
gives its name as the first segment, as a browser does, and is
walked with Lstat, following no link, taking regular files only.
.DS_Store, Thumbs.db, desktop.ini, ._* and __MACOSX are left out of
folders, and each one left out is reported (62.1 rule 15). New
-comment, -author and -no-mtime; the mtimes are kept by default
(rule 16). A capsule may hold a comment alone. The copy of a pipe to
a temporary file goes, as only regular files are taken.
- decrypt: the prelude decides. Format 3 claims -out with os.Mkdir,
only when there are files, stages the tree in -out/.datekeys-*
through an os.Root with O_EXCL and mode 0600, sets the mtimes, and
moves each entry of the first level into place at step 18; any
failure removes the folder (spec 56). Formats 1 and 2 still write a
file.
- The presentation goes to stdout: the verdicts, the declared author
and the comment box with their labels, the paths, and the verdicts
again. Every line of the creator goes in pieces of at most W - 3
columns behind the prefix, counting 2 for anything but printable
ASCII, with its TABs expanded to multiples of 8; W is the width of
the terminal, asked with syscall on Unix and Windows, or 80. Risky
names get a warning: shortcuts, desktop.ini, .git, programs and a
leading dash, compared by their key of R7.
- Encrypt no longer runs in the CLI: only the test data generators
set TestVectors.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
// datekeys encrypt -at 2030-01-01T00:00:00Z -in fotos -in carta.txt -comment "Para Ana" -out regalo.dkc
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 encrypt -at 2030-01-01T00:00:00Z -policy time_and_key -dkk key.dkk -in secret.txt -out secret.dkc
Format 3, step 5: the CLI
datekeys encrypt writes format 3 and datekeys decrypt writes its files
to a new folder, with the presentation of spec 29.7.
- encrypt: -in is repeatable and takes files and folders; a folder
gives its name as the first segment, as a browser does, and is
walked with Lstat, following no link, taking regular files only.
.DS_Store, Thumbs.db, desktop.ini, ._* and __MACOSX are left out of
folders, and each one left out is reported (62.1 rule 15). New
-comment, -author and -no-mtime; the mtimes are kept by default
(rule 16). A capsule may hold a comment alone. The copy of a pipe to
a temporary file goes, as only regular files are taken.
- decrypt: the prelude decides. Format 3 claims -out with os.Mkdir,
only when there are files, stages the tree in -out/.datekeys-*
through an os.Root with O_EXCL and mode 0600, sets the mtimes, and
moves each entry of the first level into place at step 18; any
failure removes the folder (spec 56). Formats 1 and 2 still write a
file.
- The presentation goes to stdout: the verdicts, the declared author
and the comment box with their labels, the paths, and the verdicts
again. Every line of the creator goes in pieces of at most W - 3
columns behind the prefix, counting 2 for anything but printable
ASCII, with its TABs expanded to multiples of 8; W is the width of
the terminal, asked with syscall on Unix and Windows, or 80. Risky
names get a warning: shortcuts, desktop.ini, .git, programs and a
leading dash, compared by their key of R7.
- Encrypt no longer runs in the CLI: only the test data generators
set TestVectors.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
// datekeys inspect -in regalo.dkc
A key of words for the CLI: encrypt and decrypt with -words
The author asked for keys that people can keep without files. Package
wordkey derives the X25519 identity of words a person chooses, at least
six: their NFD by the tables of pathrule without the marks U+0300 to
U+036F, each code point in lower case by its simple mapping, split at
white space, joined by one space, and stretched with PBKDF2-HMAC-SHA256
of the standard library, 600 000 rounds, salted with the chain hash and
the round of the capsule. It is wordkey.ts of datekeys-ts byte for byte:
both check the same vector.
encrypt takes -words or -words-file for a time_and_key capsule, and adds
the key as one more recipient, derived for the round of -at; decrypt
takes them and adds the identity, for the round the capsule shows. The
format does not change. Checked: the CLI opened a capsule that the page
wrote with words, with the release from the public relays.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
7 days ago
// datekeys encrypt -at 2030-01-01T00:00:00Z -policy time_and_key -words-file palabras.txt -in carta.txt -out carta.dkc
// datekeys author keygen -out autor.key -pass-file clave.txt
// datekeys encrypt -at 2030-01-01T00:00:00Z -in carta.txt -sign autor.key -sign-pass-file clave.txt -out carta.dkc
A key of words for the CLI: encrypt and decrypt with -words
The author asked for keys that people can keep without files. Package
wordkey derives the X25519 identity of words a person chooses, at least
six: their NFD by the tables of pathrule without the marks U+0300 to
U+036F, each code point in lower case by its simple mapping, split at
white space, joined by one space, and stretched with PBKDF2-HMAC-SHA256
of the standard library, 600 000 rounds, salted with the chain hash and
the round of the capsule. It is wordkey.ts of datekeys-ts byte for byte:
both check the same vector.
encrypt takes -words or -words-file for a time_and_key capsule, and adds
the key as one more recipient, derived for the round of -at; decrypt
takes them and adds the identity, for the round the capsule shows. The
format does not change. Checked: the CLI opened a capsule that the page
wrote with words, with the release from the public relays.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
7 days ago
// datekeys decrypt -in regalo.dkc -out regalo [-dkk key.dkk] [-identity key.txt] [-words-file palabras.txt]
Spec v0.15 draft: remove the .dkr file
The author's decision of 7 October 2026. A release saved next to a capsule
cannot exist when the capsule is made, and once the date comes the capsule
can be opened: such a file only opens it again and does not cover the real
case, someone opening it decades later when drand is gone and nobody saved
anything. Long-term recovery rests instead on archives and cache services
that keep the releases of all rounds; a reader asks for its round and
verifies the signature against the pinned key.
Spec: the .dkr extension (section 20, back to v0.14), sections 1, 4, 8, 45,
47.1, 49, 50 (rewritten), 53, 62.1 (rule 28 removed, rule 26 reworded),
63, 70, 73, 74 (the datekeys.release .dkk extension dropped too), 76
(the v0.15 block, with the discarded design) and the annex 79. The
release object, the chain hash at step 10, step 9.c option B and the
archive format stay.
Code: decrypt -save-release and the command datekeys release are gone,
with writeRelease and their tests; decrypt -release FILE stays. The
release objects of testdata/releases are now <round>.cbor, and
TestVectorFilesAreCurrent fails on a file the generator no longer writes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
23 hours ago
// datekeys decrypt -in regalo.dkc -out regalo -release ronda.cbor
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 datekey resolve -at 2030-01-01T00:00:00Z
// datekeys profile hash
Version constants: the specification and the module
- datekeys.SpecVersion ("0.8.2") names the specification the module
implements. A test ties it to the spec file, its title and
spec/README.md, and TestCatalogueMatchesSpec and the vector files use
it (testkit.SpecVersion now aliases it), so the vectors regenerate
unchanged.
- datekeys.Version() is the version of the module as the go command
recorded it. That is a tag, or for a binary built in a checkout the
pseudo-version of its commit (for example
v0.0.0-20260928105528-9ac9cd952f04), or (devel) when it is unknown, as
in tests or under a replace directive to a directory. It works as the
main module and as a dependency, whatever the module path, which it
reads from the root package.
- `datekeys version` (also -version and --version) prints both and the
Go toolchain.
- README.md and README.es.md explain the three versions (format,
specification, module) and what the code on main covers.
traceability §70 and CHANGELOG follow.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
// datekeys version
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
//
// Encryption never touches the network. Decryption fetches the release from
Release object, release in hand and step 9.c option B (spec v0.15 draft)
The release of a round becomes a file, .dkr: a release object in
deterministic CBOR, {0: "datekeys-release", 1: 1, 2: chain_hash, 3: round,
4: signature}, which provider.EncodeRelease writes and DecodeRelease reads
with its layers (size, type and version, schema). provider.ParseRelease
also reads drand's JSON as the input of the caller. Verify checks the chain
hash a release names before its round and its signature, with
ERR_PROFILE_MISMATCH. provider.Archive reads a local release archive, the
informative format of the draft.
capsule.OpenOptions.Release takes a release in hand, a provider.Supplier,
exclusive with Source: Open does not compare it with the clock (step 9.c,
option B) and reports a clock behind it in Opened.ClockBehind; a network
source is still never asked before the round time. The CLI gains
decrypt -release FILE (.dkr, drand's JSON or a local archive),
decrypt -save-release FILE.dkr and the command release, which fetches,
verifies and saves the .dkr without opening the capsule.
Test data: vectors/release.json, releases/<round>.dkr for rounds 1000,
1001, 1004 and 2000, and a local archive of rounds 1000 to 1004. In
mutations.json every case says its source, "supplied" or "network"; the
case "round not reached yet", a release in hand, now opens, and four cases
are added: the same with a network source, a release of another round from
a network source, and two release objects of another chain. SpecVersion
stays 0.14 until the author approves the draft.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 day ago
// public drand relays, or reads the one the person has in hand with -release,
// and verifies it locally. Outputs are written to a
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
// temporary file in the destination directory and published only when
Format 3, step 5: the CLI
datekeys encrypt writes format 3 and datekeys decrypt writes its files
to a new folder, with the presentation of spec 29.7.
- encrypt: -in is repeatable and takes files and folders; a folder
gives its name as the first segment, as a browser does, and is
walked with Lstat, following no link, taking regular files only.
.DS_Store, Thumbs.db, desktop.ini, ._* and __MACOSX are left out of
folders, and each one left out is reported (62.1 rule 15). New
-comment, -author and -no-mtime; the mtimes are kept by default
(rule 16). A capsule may hold a comment alone. The copy of a pipe to
a temporary file goes, as only regular files are taken.
- decrypt: the prelude decides. Format 3 claims -out with os.Mkdir,
only when there are files, stages the tree in -out/.datekeys-*
through an os.Root with O_EXCL and mode 0600, sets the mtimes, and
moves each entry of the first level into place at step 18; any
failure removes the folder (spec 56). Formats 1 and 2 still write a
file.
- The presentation goes to stdout: the verdicts, the declared author
and the comment box with their labels, the paths, and the verdicts
again. Every line of the creator goes in pieces of at most W - 3
columns behind the prefix, counting 2 for anything but printable
ASCII, with its TABs expanded to multiples of 8; W is the width of
the terminal, asked with syscall on Unix and Windows, or 80. Risky
names get a warning: shortcuts, desktop.ini, .git, programs and a
leading dash, compared by their key of R7.
- Encrypt no longer runs in the CLI: only the test data generators
set TestVectors.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
// complete; existing files are never overwritten. The files of a format 3
// capsule go to a new folder, staged inside it and moved into place only
// when every check has passed.
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
package main
import (
Review fixes: author keys, the writer, the CLI, extensions and the locator
Fixes of the review of the session of 1 and 2 October that the text of
spec v0.11 already asks for:
- authorkey: String and GoString hide the secret key, which only Secret
returns; ParsePublic refuses a key that is not a point of the curve
(ed25519strict.OnCurve, checked against the square root of testkit).
- capsule: a typed nil in AuthorKey, CMSSigner or Sealer is an error, never
a capsule without the signature or the seal that was asked for. A panic
while evaluating the signature or the seal fails only that part, F1 or
S2, not both. OpenOptions.Accept sees the verdicts before step 18 and can
refuse to publish the files.
- extension.CheckWrite, the rule of encoders of spec 72: the writers of
capsules and .dkk files refuse datekeys.note and datekeys.capsule outside
the arrays where they are registered, or with invalid data.
- CLI: encrypt -sign shows the author key and the code of AUTHOR_MESSAGE
before it signs (rule 20); decrypt -expect-author compares the key of an
F4 and writes nothing unless it matches; decrypt notifies a public note
that it does not show; the lines of the verdicts break at the last space
that fits, each row after the first behind a mark, so that the terminal
never breaks them; L is the payload, not the content.
- locator: a reader rejects an address that breaks 44.1 and keeps the
others; addresses refuse the special-purpose blocks of IANA, IPv6 outside
2000::/3, localhost and local names, characters outside RFC 3986, dot
segments, and a CID that does not decode to version 1 and a multihash;
ParseInfo checks that the locator is an age file with one tlock stanza
for the round of its DateKey; Info.Extension reads what it writes; its
errors carry no normative code.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6 days ago
"bytes"
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
"context"
Dice: the words of five dice each, encrypt -dice and datekeys wordlist
For whoever does not trust the random numbers of a computer, as the author
decided: five dice for each word, read in a fixed order, give a number
from 11111 to 66666, the position of the word in a list of 7776 words, the
first die the most significant. wordkey.DiceNumber and DiceWord number the
words and give the word of a number; DiceWords takes several numbers, at
least 6 and never the same word twice, which is rolled again; DiceList is
the list numbered for dice, a line for each word with its dice and a tab,
as the EFF publishes its own: for en it is the file of the EFF, byte for
byte.
encrypt -dice TEXT and -dice-file FILE take the words from the dice of the
list -dic and show them: the words open the capsule, the numbers give them
only with that list. datekeys wordlist [-dic LIST] writes the numbered
list, to print it, and its SHA-256, which wordkey/lists/README.md records.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
10 hours ago
"crypto/sha256"
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
"encoding/hex"
"encoding/json"
"errors"
"flag"
"fmt"
"io"
"os"
"path/filepath"
Version constants: the specification and the module
- datekeys.SpecVersion ("0.8.2") names the specification the module
implements. A test ties it to the spec file, its title and
spec/README.md, and TestCatalogueMatchesSpec and the vector files use
it (testkit.SpecVersion now aliases it), so the vectors regenerate
unchanged.
- datekeys.Version() is the version of the module as the go command
recorded it. That is a tag, or for a binary built in a checkout the
pseudo-version of its commit (for example
v0.0.0-20260928105528-9ac9cd952f04), or (devel) when it is unknown, as
in tests or under a replace directive to a directory. It works as the
main module and as a dependency, whatever the module path, which it
reads from the root package.
- `datekeys version` (also -version and --version) prints both and the
Go toolchain.
- README.md and README.es.md explain the three versions (format,
specification, module) and what the code on main covers.
traceability §70 and CHANGELOG follow.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
"runtime"
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
"strings"
"time"
"filippo.io/age"
datekeys "g.activething.com/go/DateKeys"
"g.activething.com/go/DateKeys/accesskey"
"g.activething.com/go/DateKeys/authorkey"
"g.activething.com/go/DateKeys/capsule"
"g.activething.com/go/DateKeys/datekey"
"g.activething.com/go/DateKeys/internal/inspectview"
"g.activething.com/go/DateKeys/profile"
Release object, release in hand and step 9.c option B (spec v0.15 draft)
The release of a round becomes a file, .dkr: a release object in
deterministic CBOR, {0: "datekeys-release", 1: 1, 2: chain_hash, 3: round,
4: signature}, which provider.EncodeRelease writes and DecodeRelease reads
with its layers (size, type and version, schema). provider.ParseRelease
also reads drand's JSON as the input of the caller. Verify checks the chain
hash a release names before its round and its signature, with
ERR_PROFILE_MISMATCH. provider.Archive reads a local release archive, the
informative format of the draft.
capsule.OpenOptions.Release takes a release in hand, a provider.Supplier,
exclusive with Source: Open does not compare it with the clock (step 9.c,
option B) and reports a clock behind it in Opened.ClockBehind; a network
source is still never asked before the round time. The CLI gains
decrypt -release FILE (.dkr, drand's JSON or a local archive),
decrypt -save-release FILE.dkr and the command release, which fetches,
verifies and saves the .dkr without opening the capsule.
Test data: vectors/release.json, releases/<round>.dkr for rounds 1000,
1001, 1004 and 2000, and a local archive of rounds 1000 to 1004. In
mutations.json every case says its source, "supplied" or "network"; the
case "round not reached yet", a release in hand, now opens, and four cases
are added: the same with a network source, a release of another round from
a network source, and two release objects of another chain. SpecVersion
stays 0.14 until the author approves the draft.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 day ago
"g.activething.com/go/DateKeys/provider"
"g.activething.com/go/DateKeys/provider/drand"
A key of words for the CLI: encrypt and decrypt with -words
The author asked for keys that people can keep without files. Package
wordkey derives the X25519 identity of words a person chooses, at least
six: their NFD by the tables of pathrule without the marks U+0300 to
U+036F, each code point in lower case by its simple mapping, split at
white space, joined by one space, and stretched with PBKDF2-HMAC-SHA256
of the standard library, 600 000 rounds, salted with the chain hash and
the round of the capsule. It is wordkey.ts of datekeys-ts byte for byte:
both check the same vector.
encrypt takes -words or -words-file for a time_and_key capsule, and adds
the key as one more recipient, derived for the round of -at; decrypt
takes them and adds the identity, for the round the capsule shows. The
format does not change. Checked: the CLI opened a capsule that the page
wrote with words, with the release from the public relays.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
7 days ago
"g.activething.com/go/DateKeys/wordkey"
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
)
const usage = ` usage :
What the official SDK says when it seals: the recovery annex, §7.6 and §71
The SHOULDs of the official SDK that the CLI did not follow yet. encrypt
writes next to the .dkc the recovery annex, FILE.dkc.recuperacion.txt
(spec §62.1, rule 27): datekeys.RecoveryAnnex, annex/recovery.md, which is
§79 of the specification under a title with its version and SHA-256, the
same for every capsule; TestRecoveryAnnex checks it against the text of
SpecVersion. -no-recovery leaves it out. encrypt also says what opening the
capsule years later will take (rule 26): the .dkc, a credential of a
time_and_key capsule, and the release of its round, which an archive of
releases or a cache service must keep if drand no longer serves it; and
beyond one year it recommends time_and_key to a time_only capsule (§7.6).
profile.Status and StatusOf give the state of a pinned profile in the
registry of §71, which DateKeys does not publish yet: Quicknet is active.
encrypt writes no capsule with a profile that is not active, and decrypt
and inspect warn when the profile of a capsule is compromised.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
8 hours ago
datekeys encrypt - at TIME - in FILE | FOLDER ... - out FILE . dkc [ - comment TEXT ] [ - author TEXT ] [ - no - mtime ] [ - policy time_only | time_and_key ] [ - recipient age1 ... ] ... [ - dkk FILE . dkk ] [ - words TEXT | - words - file FILE | - new - words FILE [ - dic LIST ] [ - word - count N ] | - dice TEXT | - dice - file FILE [ - dic LIST ] ] [ - padding reforzado | bloque256 ] [ - note TEXT ] [ - sign KEY [ - sign - pass - file FILE ] ] [ - large - area ] [ - no - recovery ]
Spec v0.15 draft: remove the .dkr file
The author's decision of 7 October 2026. A release saved next to a capsule
cannot exist when the capsule is made, and once the date comes the capsule
can be opened: such a file only opens it again and does not cover the real
case, someone opening it decades later when drand is gone and nobody saved
anything. Long-term recovery rests instead on archives and cache services
that keep the releases of all rounds; a reader asks for its round and
verifies the signature against the pinned key.
Spec: the .dkr extension (section 20, back to v0.14), sections 1, 4, 8, 45,
47.1, 49, 50 (rewritten), 53, 62.1 (rule 28 removed, rule 26 reworded),
63, 70, 73, 74 (the datekeys.release .dkk extension dropped too), 76
(the v0.15 block, with the discarded design) and the annex 79. The
release object, the chain hash at step 10, step 9.c option B and the
archive format stay.
Code: decrypt -save-release and the command datekeys release are gone,
with writeRelease and their tests; decrypt -release FILE stays. The
release objects of testdata/releases are now <round>.cbor, and
TestVectorFilesAreCurrent fails on a file the generator no longer writes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
23 hours ago
datekeys decrypt - in FILE . dkc - out PATH [ - dkk FILE . dkk ] [ - identity FILE ] ... [ - words TEXT | - words - file FILE ] [ - expect - author dkauthor1 ... ] [ - relay URL ] ... [ - release FILE ]
datekeys author keygen - out FILE ( - pass - file FILE | - plain )
datekeys author public - key FILE [ - pass - file FILE ]
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 inspect - in FILE . dkc [ - json ]
datekeys datekey resolve - at TIME
datekeys profile hash [ - in PROFILE . cbor ]
Dice: the words of five dice each, encrypt -dice and datekeys wordlist
For whoever does not trust the random numbers of a computer, as the author
decided: five dice for each word, read in a fixed order, give a number
from 11111 to 66666, the position of the word in a list of 7776 words, the
first die the most significant. wordkey.DiceNumber and DiceWord number the
words and give the word of a number; DiceWords takes several numbers, at
least 6 and never the same word twice, which is rolled again; DiceList is
the list numbered for dice, a line for each word with its dice and a tab,
as the EFF publishes its own: for en it is the file of the EFF, byte for
byte.
encrypt -dice TEXT and -dice-file FILE take the words from the dice of the
list -dic and show them: the words open the capsule, the numbers give them
only with that list. datekeys wordlist [-dic LIST] writes the numbered
list, to print it, and its SHA-256, which wordkey/lists/README.md records.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
10 hours ago
datekeys wordlist [ - dic LIST ]
Version constants: the specification and the module
- datekeys.SpecVersion ("0.8.2") names the specification the module
implements. A test ties it to the spec file, its title and
spec/README.md, and TestCatalogueMatchesSpec and the vector files use
it (testkit.SpecVersion now aliases it), so the vectors regenerate
unchanged.
- datekeys.Version() is the version of the module as the go command
recorded it. That is a tag, or for a binary built in a checkout the
pseudo-version of its commit (for example
v0.0.0-20260928105528-9ac9cd952f04), or (devel) when it is unknown, as
in tests or under a replace directive to a directory. It works as the
main module and as a dependency, whatever the module path, which it
reads from the root package.
- `datekeys version` (also -version and --version) prints both and the
Go toolchain.
- README.md and README.es.md explain the three versions (format,
specification, module) and what the code on main covers.
traceability §70 and CHANGELOG follow.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
datekeys version
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
Implement capsule format 2 of spec v0.9
The reference moves to the DateKeys Protocol Specification v0.9, approved
by its author on 29 September 2026. Encrypt writes capsule format 2 only;
Open and Inspect read formats 1 and 2, and a format 1 capsule keeps the
verdict v0.8.2 gave it.
Format 2 (spec §22, §29.1, §31, §39):
- VERSION in the PRELUDE is the capsule format, capsule.Format; any other
value is ERR_UNSUPPORTED_VERSION at step 2.
- CONTROL_CBOR has the schema version of its format. Version 2 adds key 6,
payload_length (8 bytes, big-endian, at most L_MAX = 2^53 - 2^46), and
key 7, padding (1 bloque256, 2 reforzado); it is 103 bytes without
extensions, whatever L.
- The payload is the content padded with zeros to P = rule(L). Step 17
checks the length and the zeros, and Open writes only the first L bytes.
- INNER_ACCESS_AGE holds exactly 16 X25519 stanzas: 1 to 16 credentials,
and a dummy in each slot left, in a uniformly random order.
Writer rules (spec §62.1): EncryptOptions.Length is required and the
source must deliver exactly that many bytes; recipients that are not
canonical or of low order are rejected (agewrap.CheckX25519Recipient);
self-checks of the header, the control, INNER_ACCESS_AGE and PAYLOAD_AGE.
The CLI measures its input, takes -padding and reports the format.
Test data: seven format 2 fixtures, padding vectors checked against
math/big, format 2 CBOR vectors, and the mutation corpus in both formats
with the 22 cases of the third list of spec §64, built without randomness
by sealing the fixtures again with their known keys and nonces. The
format 1 fixtures are kept byte for byte and never regenerated; the
differential corpus keeps its 1825 cases and adds a block per format 2
fixture. The spec copy loses its "to be implemented" markers, and the
READMEs, CHANGELOG, traceability and testdata/README.md follow v0.9.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
TIME is RFC 3339 with a time zone , for example 2030 - 01 - 01 T00 : 00 : 00 Z .
Format 3, step 5: the CLI
datekeys encrypt writes format 3 and datekeys decrypt writes its files
to a new folder, with the presentation of spec 29.7.
- encrypt: -in is repeatable and takes files and folders; a folder
gives its name as the first segment, as a browser does, and is
walked with Lstat, following no link, taking regular files only.
.DS_Store, Thumbs.db, desktop.ini, ._* and __MACOSX are left out of
folders, and each one left out is reported (62.1 rule 15). New
-comment, -author and -no-mtime; the mtimes are kept by default
(rule 16). A capsule may hold a comment alone. The copy of a pipe to
a temporary file goes, as only regular files are taken.
- decrypt: the prelude decides. Format 3 claims -out with os.Mkdir,
only when there are files, stages the tree in -out/.datekeys-*
through an os.Root with O_EXCL and mode 0600, sets the mtimes, and
moves each entry of the first level into place at step 18; any
failure removes the folder (spec 56). Formats 1 and 2 still write a
file.
- The presentation goes to stdout: the verdicts, the declared author
and the comment box with their labels, the paths, and the verdicts
again. Every line of the creator goes in pieces of at most W - 3
columns behind the prefix, counting 2 for anything but printable
ASCII, with its TABs expanded to multiples of 8; W is the width of
the terminal, asked with syscall on Unix and Windows, or 80. Risky
names get a warning: shortcuts, desktop.ini, .git, programs and a
leading dash, compared by their key of R7.
- Encrypt no longer runs in the CLI: only the test data generators
set TestVectors.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
encrypt writes capsule format 3 : the files of each - in , a folder by its name
and the files below it , with an optional comment and declared author . The
content is padded , by default with the rule reforzado , and a time_and_key
capsule holds 16 slots , from 1 to 16 credentials and a dummy in each slot
left ( spec § 29 , § 39 ) . decrypt writes the files of a format 3 capsule to the
A key of words for the CLI: encrypt and decrypt with -words
The author asked for keys that people can keep without files. Package
wordkey derives the X25519 identity of words a person chooses, at least
six: their NFD by the tables of pathrule without the marks U+0300 to
U+036F, each code point in lower case by its simple mapping, split at
white space, joined by one space, and stretched with PBKDF2-HMAC-SHA256
of the standard library, 600 000 rounds, salted with the chain hash and
the round of the capsule. It is wordkey.ts of datekeys-ts byte for byte:
both check the same vector.
encrypt takes -words or -words-file for a time_and_key capsule, and adds
the key as one more recipient, derived for the round of -at; decrypt
takes them and adds the identity, for the round the capsule shows. The
format does not change. Checked: the CLI opened a capsule that the page
wrote with words, with the release from the public relays.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
7 days ago
new folder PATH , and the content of formats 1 and 2 to the new file PATH .
Release object, release in hand and step 9.c option B (spec v0.15 draft)
The release of a round becomes a file, .dkr: a release object in
deterministic CBOR, {0: "datekeys-release", 1: 1, 2: chain_hash, 3: round,
4: signature}, which provider.EncodeRelease writes and DecodeRelease reads
with its layers (size, type and version, schema). provider.ParseRelease
also reads drand's JSON as the input of the caller. Verify checks the chain
hash a release names before its round and its signature, with
ERR_PROFILE_MISMATCH. provider.Archive reads a local release archive, the
informative format of the draft.
capsule.OpenOptions.Release takes a release in hand, a provider.Supplier,
exclusive with Source: Open does not compare it with the clock (step 9.c,
option B) and reports a clock behind it in Opened.ClockBehind; a network
source is still never asked before the round time. The CLI gains
decrypt -release FILE (.dkr, drand's JSON or a local archive),
decrypt -save-release FILE.dkr and the command release, which fetches,
verifies and saves the .dkr without opening the capsule.
Test data: vectors/release.json, releases/<round>.dkr for rounds 1000,
1001, 1004 and 2000, and a local archive of rounds 1000 to 1004. In
mutations.json every case says its source, "supplied" or "network"; the
case "round not reached yet", a release in hand, now opens, and four cases
are added: the same with a network source, a release of another round from
a network source, and two release objects of another chain. SpecVersion
stays 0.14 until the author approves the draft.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 day ago
decrypt fetches the release of the round from drand relays , never before the
round time . - release FILE gives it instead , without any network request : a
Spec v0.15 draft: remove the .dkr file
The author's decision of 7 October 2026. A release saved next to a capsule
cannot exist when the capsule is made, and once the date comes the capsule
can be opened: such a file only opens it again and does not cover the real
case, someone opening it decades later when drand is gone and nobody saved
anything. Long-term recovery rests instead on archives and cache services
that keep the releases of all rounds; a reader asks for its round and
verifies the signature against the pinned key.
Spec: the .dkr extension (section 20, back to v0.14), sections 1, 4, 8, 45,
47.1, 49, 50 (rewritten), 53, 62.1 (rule 28 removed, rule 26 reworded),
63, 70, 73, 74 (the datekeys.release .dkk extension dropped too), 76
(the v0.15 block, with the discarded design) and the annex 79. The
release object, the chain hash at step 10, step 9.c option B and the
archive format stay.
Code: decrypt -save-release and the command datekeys release are gone,
with writeRelease and their tests; decrypt -release FILE stays. The
release objects of testdata/releases are now <round>.cbor, and
TestVectorFilesAreCurrent fails on a file the generator no longer writes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
23 hours ago
release object , drand ' s JSON or a local release archive . It is verified like
one from a relay , against the pinned key , so it does not matter who served
it ; and the clock does not stop it : a valid release proves that the round
was published ( spec v0 .15 , § 63 step 9. c ) .
Release object, release in hand and step 9.c option B (spec v0.15 draft)
The release of a round becomes a file, .dkr: a release object in
deterministic CBOR, {0: "datekeys-release", 1: 1, 2: chain_hash, 3: round,
4: signature}, which provider.EncodeRelease writes and DecodeRelease reads
with its layers (size, type and version, schema). provider.ParseRelease
also reads drand's JSON as the input of the caller. Verify checks the chain
hash a release names before its round and its signature, with
ERR_PROFILE_MISMATCH. provider.Archive reads a local release archive, the
informative format of the draft.
capsule.OpenOptions.Release takes a release in hand, a provider.Supplier,
exclusive with Source: Open does not compare it with the clock (step 9.c,
option B) and reports a clock behind it in Opened.ClockBehind; a network
source is still never asked before the round time. The CLI gains
decrypt -release FILE (.dkr, drand's JSON or a local archive),
decrypt -save-release FILE.dkr and the command release, which fetches,
verifies and saves the .dkr without opening the capsule.
Test data: vectors/release.json, releases/<round>.dkr for rounds 1000,
1001, 1004 and 2000, and a local archive of rounds 1000 to 1004. In
mutations.json every case says its source, "supplied" or "network"; the
case "round not reached yet", a release in hand, now opens, and four cases
are added: the same with a network source, a release of another round from
a network source, and two release objects of another chain. SpecVersion
stays 0.14 until the author approves the draft.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 day ago
A key of words for the CLI: encrypt and decrypt with -words
The author asked for keys that people can keep without files. Package
wordkey derives the X25519 identity of words a person chooses, at least
six: their NFD by the tables of pathrule without the marks U+0300 to
U+036F, each code point in lower case by its simple mapping, split at
white space, joined by one space, and stretched with PBKDF2-HMAC-SHA256
of the standard library, 600 000 rounds, salted with the chain hash and
the round of the capsule. It is wordkey.ts of datekeys-ts byte for byte:
both check the same vector.
encrypt takes -words or -words-file for a time_and_key capsule, and adds
the key as one more recipient, derived for the round of -at; decrypt
takes them and adds the identity, for the round the capsule shows. The
format does not change. Checked: the CLI opened a capsule that the page
wrote with words, with the release from the public relays.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
7 days ago
- words and - words - file give a key of words to a time_and_key capsule : at
least 6 different words of 3 or more letters that open it with decrypt ,
instead of a . dkk
A key of words for the CLI: encrypt and decrypt with -words
The author asked for keys that people can keep without files. Package
wordkey derives the X25519 identity of words a person chooses, at least
six: their NFD by the tables of pathrule without the marks U+0300 to
U+036F, each code point in lower case by its simple mapping, split at
white space, joined by one space, and stretched with PBKDF2-HMAC-SHA256
of the standard library, 600 000 rounds, salted with the chain hash and
the round of the capsule. It is wordkey.ts of datekeys-ts byte for byte:
both check the same vector.
encrypt takes -words or -words-file for a time_and_key capsule, and adds
the key as one more recipient, derived for the round of -at; decrypt
takes them and adds the identity, for the round the capsule shows. The
format does not change. Checked: the CLI opened a capsule that the page
wrote with words, with the release from the public relays.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
7 days ago
( wordkey ) . Case , accents and extra spaces do not matter . - words leaves them
in the shell history ; - words - file reads them from a file . Never use a
password used anywhere else : once the date has come , the capsule lets
whoever holds it test guesses of it .
- new - words FILE draws the words at random instead , and writes them to the new
The English list: the large wordlist of the EFF, and -dic en by default
wordkey/lists/en.txt is the large wordlist for passphrases of the EFF,
7776 words, CC BY 4.0 under its copyright policy, without the dice number
of each line and in its order, so that the position of a word still gives
its dice, 11111 for the first. encrypt -new-words takes it by default, as
the author decided; -dic es takes the Spanish one. The alphabet of en is a
to z and the ASCII hyphen of its four compound words, such as t-shirt,
kept so that no word loses its dice. lists/README.md records its source,
the SHA-256 of the download, the change and the license.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
11 hours ago
file FILE : - word - count words , 7 by default , of the list - dic of 7776 words ,
en by default , the list of the EFF , or es . Words a person chooses are weaker
than random ones : whoever holds the . dkc can try them offline once the date
has come ( spec § 38.1 ) .
Dice: the words of five dice each, encrypt -dice and datekeys wordlist
For whoever does not trust the random numbers of a computer, as the author
decided: five dice for each word, read in a fixed order, give a number
from 11111 to 66666, the position of the word in a list of 7776 words, the
first die the most significant. wordkey.DiceNumber and DiceWord number the
words and give the word of a number; DiceWords takes several numbers, at
least 6 and never the same word twice, which is rolled again; DiceList is
the list numbered for dice, a line for each word with its dice and a tab,
as the EFF publishes its own: for en it is the file of the EFF, byte for
byte.
encrypt -dice TEXT and -dice-file FILE take the words from the dice of the
list -dic and show them: the words open the capsule, the numbers give them
only with that list. datekeys wordlist [-dic LIST] writes the numbered
list, to print it, and its SHA-256, which wordkey/lists/README.md records.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
10 hours ago
- dice TEXT and - dice - file FILE take the words from dice instead , for whoever
does not trust the random numbers of a computer : five dice for each word ,
read in a fixed order , give a number from 11111 to 66666 , the position of a
word in the list - dic , at least 6 numbers and no word twice . datekeys
wordlist writes the list - dic numbered for dice , to print it , and its
SHA - 256 : for en it is the list of the EFF , byte for byte . encrypt shows the
words : keep the words , which open the capsule ; the numbers give them only
with that list .
What the official SDK says when it seals: the recovery annex, §7.6 and §71
The SHOULDs of the official SDK that the CLI did not follow yet. encrypt
writes next to the .dkc the recovery annex, FILE.dkc.recuperacion.txt
(spec §62.1, rule 27): datekeys.RecoveryAnnex, annex/recovery.md, which is
§79 of the specification under a title with its version and SHA-256, the
same for every capsule; TestRecoveryAnnex checks it against the text of
SpecVersion. -no-recovery leaves it out. encrypt also says what opening the
capsule years later will take (rule 26): the .dkc, a credential of a
time_and_key capsule, and the release of its round, which an archive of
releases or a cache service must keep if drand no longer serves it; and
beyond one year it recommends time_and_key to a time_only capsule (§7.6).
profile.Status and StatusOf give the state of a pinned profile in the
registry of §71, which DateKeys does not publish yet: Quicknet is active.
encrypt writes no capsule with a profile that is not active, and decrypt
and inspect warn when the profile of a capsule is compromised.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
8 hours ago
encrypt also writes FILE . dkc . recuperacion . txt next to the capsule , unless
- no - recovery : the annex of the specification , in Spanish , on how to open a
capsule without DateKeys software ( spec § 79 ) , the same for every capsule .
Opening it years later needs the . dkc , a credential of a time_and_key
capsule , and the release of its round , which drand publishes at the date :
if drand no longer serves it then , an archive of releases or a cache service
must have kept it ( spec § 50 ) . For a long horizon or a valuable content ,
time_and_key adds a credential that drand does not hold ( spec § 7.6 ) . A
profile that is not active writes no capsule , and decrypt and inspect warn
when the profile of a capsule is compromised ( spec § 71 ) .
- note puts a public note in the capsule , in clear : anyone who has the . dkc
reads it before the date , nobody can check who wrote it , and with the date it
can identify someone . decrypt shows it as text of the creator .
- sign signs the capsule with the author key in the file KEY , made by author
keygen ( spec v0 .11 , § 29.9 ) . A key encrypted with a passphrase needs
- sign - pass - file : a file with the passphrase , or - for the standard input . The
passphrase is never taken from the command line or the environment . A
signature proves that whoever has the secret key signed , not who that is .
Review fixes: the CMS reader, the area after the signature, and the issuer on screen
The TSTInfo is read field by field in DER, with accuracy from zero and
millis and micros from 1 to 999, genTime in UTC with Z, no default written
and nothing after the last field. The ContentInfo and the SignerInfo must be
SEQUENCEs, a SignerInfo version must match its sid, an attribute needs a
value and is counted by attribute and not by value, a signing-certificate
beside the v2 decides nothing, PSS parameters come in order without the
trailer, and der.Check refuses the end of contents and the universal tags
the profile does not use.
The writer signs before L is fixed: write asks prepare for the final L, so
the area grows to 64 KiB only when what was signed does not fit and LargeArea
allows it, and nobody signs twice for it. Typed nils are nil, the exclusions
are checked before a file is read, Encrypt refuses the signing options, and
EvaluateSecurityIn gives X if a parser panics. The issuer of a certificate is
filtered like its holder.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
6 days ago
- large - area lets the security area grow from 32 KiB to 64 KiB if a signature does not fit . decrypt always
shows the signature ; with - expect - author it fails , and writes no file , unless
the capsule is signed with that public key . `
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
// errUsage reports a malformed command line; main prints the usage text.
var stdin io . Reader = os . Stdin
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
var errUsage = errors . New ( "invalid command line; run 'datekeys help'" )
// longHorizon is the product policy threshold for the harvest-now,
// decrypt-later warning (spec §53).
const longHorizon = 365 * 24 * time . Hour
What the official SDK says when it seals: the recovery annex, §7.6 and §71
The SHOULDs of the official SDK that the CLI did not follow yet. encrypt
writes next to the .dkc the recovery annex, FILE.dkc.recuperacion.txt
(spec §62.1, rule 27): datekeys.RecoveryAnnex, annex/recovery.md, which is
§79 of the specification under a title with its version and SHA-256, the
same for every capsule; TestRecoveryAnnex checks it against the text of
SpecVersion. -no-recovery leaves it out. encrypt also says what opening the
capsule years later will take (rule 26): the .dkc, a credential of a
time_and_key capsule, and the release of its round, which an archive of
releases or a cache service must keep if drand no longer serves it; and
beyond one year it recommends time_and_key to a time_only capsule (§7.6).
profile.Status and StatusOf give the state of a pinned profile in the
registry of §71, which DateKeys does not publish yet: Quicknet is active.
encrypt writes no capsule with a profile that is not active, and decrypt
and inspect warn when the profile of a capsule is compromised.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
8 hours ago
// profileStatus is the state of a profile in the registry of profiles
// (spec §71), as this release of the module knows it; the tests replace it
// to see a profile that is not active.
var profileStatus = func ( p * profile . Profile ) profile . Status {
h , err := p . Hash ( )
if err != nil {
return profile . Active
}
s , _ := profile . StatusOf ( h )
return s
}
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
func main ( ) {
if err := run ( os . Args [ 1 : ] , os . Stdout , os . Stderr , time . Now ) ; err != nil {
if errors . Is ( err , errUsage ) {
fmt . Fprintln ( os . Stderr , usage )
os . Exit ( 2 )
}
fmt . Fprintln ( os . Stderr , "datekeys:" , err )
if code := datekeys . Code ( err ) ; code != "" {
fmt . Fprintln ( os . Stderr , "datekeys: error code" , code )
}
os . Exit ( 1 )
}
}
type multi [ ] string
func ( m * multi ) String ( ) string { return strings . Join ( * m , "," ) }
func ( m * multi ) Set ( v string ) error { * m = append ( * m , v ) ; return nil }
// run is the CLI; the clock is injected for tests (only the CLI reads the
// wall clock).
func run ( args [ ] string , stdout , stderr io . Writer , now func ( ) time . Time ) error {
if len ( args ) == 0 {
return errUsage
}
switch args [ 0 ] {
case "encrypt" :
return encrypt ( args [ 1 : ] , stderr , now )
case "decrypt" :
Format 3, step 5: the CLI
datekeys encrypt writes format 3 and datekeys decrypt writes its files
to a new folder, with the presentation of spec 29.7.
- encrypt: -in is repeatable and takes files and folders; a folder
gives its name as the first segment, as a browser does, and is
walked with Lstat, following no link, taking regular files only.
.DS_Store, Thumbs.db, desktop.ini, ._* and __MACOSX are left out of
folders, and each one left out is reported (62.1 rule 15). New
-comment, -author and -no-mtime; the mtimes are kept by default
(rule 16). A capsule may hold a comment alone. The copy of a pipe to
a temporary file goes, as only regular files are taken.
- decrypt: the prelude decides. Format 3 claims -out with os.Mkdir,
only when there are files, stages the tree in -out/.datekeys-*
through an os.Root with O_EXCL and mode 0600, sets the mtimes, and
moves each entry of the first level into place at step 18; any
failure removes the folder (spec 56). Formats 1 and 2 still write a
file.
- The presentation goes to stdout: the verdicts, the declared author
and the comment box with their labels, the paths, and the verdicts
again. Every line of the creator goes in pieces of at most W - 3
columns behind the prefix, counting 2 for anything but printable
ASCII, with its TABs expanded to multiples of 8; W is the width of
the terminal, asked with syscall on Unix and Windows, or 80. Risky
names get a warning: shortcuts, desktop.ini, .git, programs and a
leading dash, compared by their key of R7.
- Encrypt no longer runs in the CLI: only the test data generators
set TestVectors.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
return decrypt ( args [ 1 : ] , stdout , stderr , now )
case "author" :
return author ( args [ 1 : ] , stdout , stderr , stdin )
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
case "inspect" :
return inspect ( args [ 1 : ] , stdout )
case "datekey" :
if len ( args ) < 2 || args [ 1 ] != "resolve" {
return errUsage
}
return resolve ( args [ 2 : ] , stdout )
case "profile" :
if len ( args ) < 2 || args [ 1 ] != "hash" {
return errUsage
}
return profileHash ( args [ 2 : ] , stdout )
Dice: the words of five dice each, encrypt -dice and datekeys wordlist
For whoever does not trust the random numbers of a computer, as the author
decided: five dice for each word, read in a fixed order, give a number
from 11111 to 66666, the position of the word in a list of 7776 words, the
first die the most significant. wordkey.DiceNumber and DiceWord number the
words and give the word of a number; DiceWords takes several numbers, at
least 6 and never the same word twice, which is rolled again; DiceList is
the list numbered for dice, a line for each word with its dice and a tab,
as the EFF publishes its own: for en it is the file of the EFF, byte for
byte.
encrypt -dice TEXT and -dice-file FILE take the words from the dice of the
list -dic and show them: the words open the capsule, the numbers give them
only with that list. datekeys wordlist [-dic LIST] writes the numbered
list, to print it, and its SHA-256, which wordkey/lists/README.md records.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
10 hours ago
case "wordlist" :
return wordList ( args [ 1 : ] , stdout , stderr )
Version constants: the specification and the module
- datekeys.SpecVersion ("0.8.2") names the specification the module
implements. A test ties it to the spec file, its title and
spec/README.md, and TestCatalogueMatchesSpec and the vector files use
it (testkit.SpecVersion now aliases it), so the vectors regenerate
unchanged.
- datekeys.Version() is the version of the module as the go command
recorded it. That is a tag, or for a binary built in a checkout the
pseudo-version of its commit (for example
v0.0.0-20260928105528-9ac9cd952f04), or (devel) when it is unknown, as
in tests or under a replace directive to a directory. It works as the
main module and as a dependency, whatever the module path, which it
reads from the root package.
- `datekeys version` (also -version and --version) prints both and the
Go toolchain.
- README.md and README.es.md explain the three versions (format,
specification, module) and what the code on main covers.
traceability §70 and CHANGELOG follow.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
case "version" , "-version" , "--version" :
if len ( args ) != 1 {
return errUsage
}
// The module version (a tag, or the pseudo-version of the commit a
// checkout was built from), the specification it implements and the
// toolchain.
fmt . Fprintf ( stdout , "datekeys %s\nspecification %s\n%s %s/%s\n" , datekeys . Version ( ) , datekeys . SpecVersion , runtime . Version ( ) , runtime . GOOS , runtime . GOARCH )
return nil
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
case "-h" , "-help" , "--help" , "help" :
fmt . Fprintln ( stdout , usage )
return nil
}
return errUsage
}
func newFlags ( name string ) * flag . FlagSet {
fs := flag . NewFlagSet ( name , flag . ContinueOnError )
fs . SetOutput ( io . Discard )
return fs
}
func parse ( fs * flag . FlagSet , args [ ] string ) error {
if err := fs . Parse ( args ) ; err != nil {
return fmt . Errorf ( "%s: %w" , fs . Name ( ) , err )
}
if fs . NArg ( ) != 0 {
return fmt . Errorf ( "%s: unexpected arguments %q" , fs . Name ( ) , fs . Args ( ) )
}
return nil
}
func parseTime ( s string ) ( time . Time , error ) {
t , err := time . Parse ( time . RFC3339Nano , s )
if err != nil {
return time . Time { } , fmt . Errorf ( "invalid -at %q: RFC 3339 with a time zone is required" , s )
}
return t , nil
}
func encrypt ( args [ ] string , stderr io . Writer , now func ( ) time . Time ) error {
fs := newFlags ( "encrypt" )
at := fs . String ( "at" , "" , "unlock time, RFC 3339" )
Format 3, step 5: the CLI
datekeys encrypt writes format 3 and datekeys decrypt writes its files
to a new folder, with the presentation of spec 29.7.
- encrypt: -in is repeatable and takes files and folders; a folder
gives its name as the first segment, as a browser does, and is
walked with Lstat, following no link, taking regular files only.
.DS_Store, Thumbs.db, desktop.ini, ._* and __MACOSX are left out of
folders, and each one left out is reported (62.1 rule 15). New
-comment, -author and -no-mtime; the mtimes are kept by default
(rule 16). A capsule may hold a comment alone. The copy of a pipe to
a temporary file goes, as only regular files are taken.
- decrypt: the prelude decides. Format 3 claims -out with os.Mkdir,
only when there are files, stages the tree in -out/.datekeys-*
through an os.Root with O_EXCL and mode 0600, sets the mtimes, and
moves each entry of the first level into place at step 18; any
failure removes the folder (spec 56). Formats 1 and 2 still write a
file.
- The presentation goes to stdout: the verdicts, the declared author
and the comment box with their labels, the paths, and the verdicts
again. Every line of the creator goes in pieces of at most W - 3
columns behind the prefix, counting 2 for anything but printable
ASCII, with its TABs expanded to multiples of 8; W is the width of
the terminal, asked with syscall on Unix and Windows, or 80. Risky
names get a warning: shortcuts, desktop.ini, .git, programs and a
leading dash, compared by their key of R7.
- Encrypt no longer runs in the CLI: only the test data generators
set TestVectors.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
var ins multi
fs . Var ( & ins , "in" , "file or folder to encrypt (repeatable)" )
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
out := fs . String ( "out" , "" , "new .dkc file; never overwritten" )
Format 3, step 5: the CLI
datekeys encrypt writes format 3 and datekeys decrypt writes its files
to a new folder, with the presentation of spec 29.7.
- encrypt: -in is repeatable and takes files and folders; a folder
gives its name as the first segment, as a browser does, and is
walked with Lstat, following no link, taking regular files only.
.DS_Store, Thumbs.db, desktop.ini, ._* and __MACOSX are left out of
folders, and each one left out is reported (62.1 rule 15). New
-comment, -author and -no-mtime; the mtimes are kept by default
(rule 16). A capsule may hold a comment alone. The copy of a pipe to
a temporary file goes, as only regular files are taken.
- decrypt: the prelude decides. Format 3 claims -out with os.Mkdir,
only when there are files, stages the tree in -out/.datekeys-*
through an os.Root with O_EXCL and mode 0600, sets the mtimes, and
moves each entry of the first level into place at step 18; any
failure removes the folder (spec 56). Formats 1 and 2 still write a
file.
- The presentation goes to stdout: the verdicts, the declared author
and the comment box with their labels, the paths, and the verdicts
again. Every line of the creator goes in pieces of at most W - 3
columns behind the prefix, counting 2 for anything but printable
ASCII, with its TABs expanded to multiples of 8; W is the width of
the terminal, asked with syscall on Unix and Windows, or 80. Risky
names get a warning: shortcuts, desktop.ini, .git, programs and a
leading dash, compared by their key of R7.
- Encrypt no longer runs in the CLI: only the test data generators
set TestVectors.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
comment := fs . String ( "comment" , "" , "comment for whoever opens the capsule, shown as text of the creator" )
author := fs . String ( "author" , "" , "declared author, shown as text of the creator that proves nothing" )
noMTime := fs . Bool ( "no-mtime" , false , "leave out the modification times of the files" )
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
policy := fs . String ( "policy" , "time_only" , "time_only or time_and_key" )
dkk := fs . String ( "dkk" , "" , "time_and_key: new .dkk file for a portable access key" )
Implement capsule format 2 of spec v0.9
The reference moves to the DateKeys Protocol Specification v0.9, approved
by its author on 29 September 2026. Encrypt writes capsule format 2 only;
Open and Inspect read formats 1 and 2, and a format 1 capsule keeps the
verdict v0.8.2 gave it.
Format 2 (spec §22, §29.1, §31, §39):
- VERSION in the PRELUDE is the capsule format, capsule.Format; any other
value is ERR_UNSUPPORTED_VERSION at step 2.
- CONTROL_CBOR has the schema version of its format. Version 2 adds key 6,
payload_length (8 bytes, big-endian, at most L_MAX = 2^53 - 2^46), and
key 7, padding (1 bloque256, 2 reforzado); it is 103 bytes without
extensions, whatever L.
- The payload is the content padded with zeros to P = rule(L). Step 17
checks the length and the zeros, and Open writes only the first L bytes.
- INNER_ACCESS_AGE holds exactly 16 X25519 stanzas: 1 to 16 credentials,
and a dummy in each slot left, in a uniformly random order.
Writer rules (spec §62.1): EncryptOptions.Length is required and the
source must deliver exactly that many bytes; recipients that are not
canonical or of low order are rejected (agewrap.CheckX25519Recipient);
self-checks of the header, the control, INNER_ACCESS_AGE and PAYLOAD_AGE.
The CLI measures its input, takes -padding and reports the format.
Test data: seven format 2 fixtures, padding vectors checked against
math/big, format 2 CBOR vectors, and the mutation corpus in both formats
with the 22 cases of the third list of spec §64, built without randomness
by sealing the fixtures again with their known keys and nonces. The
format 1 fixtures are kept byte for byte and never regenerated; the
differential corpus keeps its 1825 cases and adds a block per format 2
fixture. The spec copy loses its "to be implemented" markers, and the
READMEs, CHANGELOG, traceability and testdata/README.md follow v0.9.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
padding := fs . String ( "padding" , "reforzado" , "padding rule of the content: reforzado or bloque256" )
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
var recipients multi
fs . Var ( & recipients , "recipient" , "time_and_key: X25519 recipient age1... (repeatable)" )
A key of words for the CLI: encrypt and decrypt with -words
The author asked for keys that people can keep without files. Package
wordkey derives the X25519 identity of words a person chooses, at least
six: their NFD by the tables of pathrule without the marks U+0300 to
U+036F, each code point in lower case by its simple mapping, split at
white space, joined by one space, and stretched with PBKDF2-HMAC-SHA256
of the standard library, 600 000 rounds, salted with the chain hash and
the round of the capsule. It is wordkey.ts of datekeys-ts byte for byte:
both check the same vector.
encrypt takes -words or -words-file for a time_and_key capsule, and adds
the key as one more recipient, derived for the round of -at; decrypt
takes them and adds the identity, for the round the capsule shows. The
format does not change. Checked: the CLI opened a capsule that the page
wrote with words, with the release from the public relays.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
7 days ago
words := fs . String ( "words" , "" , "time_and_key: at least 6 words that open the capsule; they stay in the shell history" )
wordsFile := fs . String ( "words-file" , "" , "time_and_key: file with the words that open the capsule" )
newWords := fs . String ( "new-words" , "" , "time_and_key: new file with words drawn at random from a list, that open the capsule" )
Dice: the words of five dice each, encrypt -dice and datekeys wordlist
For whoever does not trust the random numbers of a computer, as the author
decided: five dice for each word, read in a fixed order, give a number
from 11111 to 66666, the position of the word in a list of 7776 words, the
first die the most significant. wordkey.DiceNumber and DiceWord number the
words and give the word of a number; DiceWords takes several numbers, at
least 6 and never the same word twice, which is rolled again; DiceList is
the list numbered for dice, a line for each word with its dice and a tab,
as the EFF publishes its own: for en it is the file of the EFF, byte for
byte.
encrypt -dice TEXT and -dice-file FILE take the words from the dice of the
list -dic and show them: the words open the capsule, the numbers give them
only with that list. datekeys wordlist [-dic LIST] writes the numbered
list, to print it, and its SHA-256, which wordkey/lists/README.md records.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
10 hours ago
dice := fs . String ( "dice" , "" , "time_and_key: numbers of five dice, one for each word of the list -dic; they stay in the shell history" )
diceFile := fs . String ( "dice-file" , "" , "time_and_key: file with the numbers of five dice of -dice" )
dic := fs . String ( "dic" , "en" , "list of -new-words and -dice: " + strings . Join ( wordkey . Languages ( ) , ", " ) )
wordCount := fs . Int ( "word-count" , wordkey . DefaultCount , "number of words of -new-words, 6 or more" )
note := fs . String ( "note" , "" , "public note of the capsule: one line that anyone with the .dkc reads before the date, and that can identify someone with it" )
sign := fs . String ( "sign" , "" , "file with the author key that signs the capsule" )
signPass := fs . String ( "sign-pass-file" , "" , "file with the passphrase of the author key, or - for the standard input" )
What the official SDK says when it seals: the recovery annex, §7.6 and §71
The SHOULDs of the official SDK that the CLI did not follow yet. encrypt
writes next to the .dkc the recovery annex, FILE.dkc.recuperacion.txt
(spec §62.1, rule 27): datekeys.RecoveryAnnex, annex/recovery.md, which is
§79 of the specification under a title with its version and SHA-256, the
same for every capsule; TestRecoveryAnnex checks it against the text of
SpecVersion. -no-recovery leaves it out. encrypt also says what opening the
capsule years later will take (rule 26): the .dkc, a credential of a
time_and_key capsule, and the release of its round, which an archive of
releases or a cache service must keep if drand no longer serves it; and
beyond one year it recommends time_and_key to a time_only capsule (§7.6).
profile.Status and StatusOf give the state of a pinned profile in the
registry of §71, which DateKeys does not publish yet: Quicknet is active.
encrypt writes no capsule with a profile that is not active, and decrypt
and inspect warn when the profile of a capsule is compromised.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
8 hours ago
noRecovery := fs . Bool ( "no-recovery" , false , "do not write the recovery annex of the specification next to the .dkc (spec §79)" )
Review fixes: the CMS reader, the area after the signature, and the issuer on screen
The TSTInfo is read field by field in DER, with accuracy from zero and
millis and micros from 1 to 999, genTime in UTC with Z, no default written
and nothing after the last field. The ContentInfo and the SignerInfo must be
SEQUENCEs, a SignerInfo version must match its sid, an attribute needs a
value and is counted by attribute and not by value, a signing-certificate
beside the v2 decides nothing, PSS parameters come in order without the
trailer, and der.Check refuses the end of contents and the universal tags
the profile does not use.
The writer signs before L is fixed: write asks prepare for the final L, so
the area grows to 64 KiB only when what was signed does not fit and LargeArea
allows it, and nobody signs twice for it. Typed nils are nil, the exclusions
are checked before a file is read, Encrypt refuses the signing options, and
EvaluateSecurityIn gives X if a parser panics. The issuer of a certificate is
filtered like its holder.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
6 days ago
largeArea := fs . Bool ( "large-area" , false , "let the security area grow to 64 KiB if a signature does not fit in 32 KiB (an author key always fits)" )
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
if err := parse ( fs , args ) ; err != nil {
return err
}
unlock , err := parseTime ( * at )
if err != nil {
return err
}
pol , err := capsule . ParsePolicy ( * policy )
if err != nil {
return err
}
Implement capsule format 2 of spec v0.9
The reference moves to the DateKeys Protocol Specification v0.9, approved
by its author on 29 September 2026. Encrypt writes capsule format 2 only;
Open and Inspect read formats 1 and 2, and a format 1 capsule keeps the
verdict v0.8.2 gave it.
Format 2 (spec §22, §29.1, §31, §39):
- VERSION in the PRELUDE is the capsule format, capsule.Format; any other
value is ERR_UNSUPPORTED_VERSION at step 2.
- CONTROL_CBOR has the schema version of its format. Version 2 adds key 6,
payload_length (8 bytes, big-endian, at most L_MAX = 2^53 - 2^46), and
key 7, padding (1 bloque256, 2 reforzado); it is 103 bytes without
extensions, whatever L.
- The payload is the content padded with zeros to P = rule(L). Step 17
checks the length and the zeros, and Open writes only the first L bytes.
- INNER_ACCESS_AGE holds exactly 16 X25519 stanzas: 1 to 16 credentials,
and a dummy in each slot left, in a uniformly random order.
Writer rules (spec §62.1): EncryptOptions.Length is required and the
source must deliver exactly that many bytes; recipients that are not
canonical or of low order are rejected (agewrap.CheckX25519Recipient);
self-checks of the header, the control, INNER_ACCESS_AGE and PAYLOAD_AGE.
The CLI measures its input, takes -padding and reports the format.
Test data: seven format 2 fixtures, padding vectors checked against
math/big, format 2 CBOR vectors, and the mutation corpus in both formats
with the 22 cases of the third list of spec §64, built without randomness
by sealing the fixtures again with their known keys and nonces. The
format 1 fixtures are kept byte for byte and never regenerated; the
differential corpus keeps its 1825 cases and adds a block per format 2
fixture. The spec copy loses its "to be implemented" markers, and the
READMEs, CHANGELOG, traceability and testdata/README.md follow v0.9.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
var code capsule . Padding
switch * padding {
case capsule . Reforzado . String ( ) :
code = capsule . Reforzado
case capsule . Bloque256 . String ( ) :
code = capsule . Bloque256
default :
return fmt . Errorf ( "encrypt: unknown padding rule %q: reforzado or bloque256" , * padding )
}
Format 3, step 5: the CLI
datekeys encrypt writes format 3 and datekeys decrypt writes its files
to a new folder, with the presentation of spec 29.7.
- encrypt: -in is repeatable and takes files and folders; a folder
gives its name as the first segment, as a browser does, and is
walked with Lstat, following no link, taking regular files only.
.DS_Store, Thumbs.db, desktop.ini, ._* and __MACOSX are left out of
folders, and each one left out is reported (62.1 rule 15). New
-comment, -author and -no-mtime; the mtimes are kept by default
(rule 16). A capsule may hold a comment alone. The copy of a pipe to
a temporary file goes, as only regular files are taken.
- decrypt: the prelude decides. Format 3 claims -out with os.Mkdir,
only when there are files, stages the tree in -out/.datekeys-*
through an os.Root with O_EXCL and mode 0600, sets the mtimes, and
moves each entry of the first level into place at step 18; any
failure removes the folder (spec 56). Formats 1 and 2 still write a
file.
- The presentation goes to stdout: the verdicts, the declared author
and the comment box with their labels, the paths, and the verdicts
again. Every line of the creator goes in pieces of at most W - 3
columns behind the prefix, counting 2 for anything but printable
ASCII, with its TABs expanded to multiples of 8; W is the width of
the terminal, asked with syscall on Unix and Windows, or 80. Risky
names get a warning: shortcuts, desktop.ini, .git, programs and a
leading dash, compared by their key of R7.
- Encrypt no longer runs in the CLI: only the test data generators
set TestVectors.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
if * out == "" || len ( ins ) == 0 && * comment == "" {
return errors . New ( "encrypt: -out, and -in or -comment, are required" )
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
}
Format 3, step 5: the CLI
datekeys encrypt writes format 3 and datekeys decrypt writes its files
to a new folder, with the presentation of spec 29.7.
- encrypt: -in is repeatable and takes files and folders; a folder
gives its name as the first segment, as a browser does, and is
walked with Lstat, following no link, taking regular files only.
.DS_Store, Thumbs.db, desktop.ini, ._* and __MACOSX are left out of
folders, and each one left out is reported (62.1 rule 15). New
-comment, -author and -no-mtime; the mtimes are kept by default
(rule 16). A capsule may hold a comment alone. The copy of a pipe to
a temporary file goes, as only regular files are taken.
- decrypt: the prelude decides. Format 3 claims -out with os.Mkdir,
only when there are files, stages the tree in -out/.datekeys-*
through an os.Root with O_EXCL and mode 0600, sets the mtimes, and
moves each entry of the first level into place at step 18; any
failure removes the folder (spec 56). Formats 1 and 2 still write a
file.
- The presentation goes to stdout: the verdicts, the declared author
and the comment box with their labels, the paths, and the verdicts
again. Every line of the creator goes in pieces of at most W - 3
columns behind the prefix, counting 2 for anything but printable
ASCII, with its TABs expanded to multiples of 8; W is the width of
the terminal, asked with syscall on Unix and Windows, or 80. Risky
names get a warning: shortcuts, desktop.ini, .git, programs and a
leading dash, compared by their key of R7.
- Encrypt no longer runs in the CLI: only the test data generators
set TestVectors.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
opts := capsule . EncryptOptions { Profile : profile . Quicknet ( ) , UnlockAt : unlock , Policy : pol , NewPortableKey : * dkk != "" , Padding : code ,
Comment : * comment , Author : * author , Now : now }
What the official SDK says when it seals: the recovery annex, §7.6 and §71
The SHOULDs of the official SDK that the CLI did not follow yet. encrypt
writes next to the .dkc the recovery annex, FILE.dkc.recuperacion.txt
(spec §62.1, rule 27): datekeys.RecoveryAnnex, annex/recovery.md, which is
§79 of the specification under a title with its version and SHA-256, the
same for every capsule; TestRecoveryAnnex checks it against the text of
SpecVersion. -no-recovery leaves it out. encrypt also says what opening the
capsule years later will take (rule 26): the .dkc, a credential of a
time_and_key capsule, and the release of its round, which an archive of
releases or a cache service must keep if drand no longer serves it; and
beyond one year it recommends time_and_key to a time_only capsule (§7.6).
profile.Status and StatusOf give the state of a pinned profile in the
registry of §71, which DateKeys does not publish yet: Quicknet is active.
encrypt writes no capsule with a profile that is not active, and decrypt
and inspect warn when the profile of a capsule is compromised.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
8 hours ago
if s := profileStatus ( opts . Profile ) ; s != profile . Active {
return fmt . Errorf ( "encrypt: the profile %s is %s: no new capsule is written with it (spec §71)" , opts . Profile . ID , s )
}
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
for _ , r := range recipients {
x , err := age . ParseX25519Recipient ( r )
if err != nil {
return fmt . Errorf ( "encrypt: %w" , err )
}
opts . Recipients = append ( opts . Recipients , x )
}
// The key of words is one more credential: the writer derives it once it
// has drawn capsule_id, which salts it (spec §38.1).
A key of words for the CLI: encrypt and decrypt with -words
The author asked for keys that people can keep without files. Package
wordkey derives the X25519 identity of words a person chooses, at least
six: their NFD by the tables of pathrule without the marks U+0300 to
U+036F, each code point in lower case by its simple mapping, split at
white space, joined by one space, and stretched with PBKDF2-HMAC-SHA256
of the standard library, 600 000 rounds, salted with the chain hash and
the round of the capsule. It is wordkey.ts of datekeys-ts byte for byte:
both check the same vector.
encrypt takes -words or -words-file for a time_and_key capsule, and adds
the key as one more recipient, derived for the round of -at; decrypt
takes them and adds the identity, for the round the capsule shows. The
format does not change. Checked: the CLI opened a capsule that the page
wrote with words, with the release from the public relays.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
7 days ago
text , err := wordsText ( "encrypt" , * words , * wordsFile )
if err != nil {
return err
}
Dice: the words of five dice each, encrypt -dice and datekeys wordlist
For whoever does not trust the random numbers of a computer, as the author
decided: five dice for each word, read in a fixed order, give a number
from 11111 to 66666, the position of the word in a list of 7776 words, the
first die the most significant. wordkey.DiceNumber and DiceWord number the
words and give the word of a number; DiceWords takes several numbers, at
least 6 and never the same word twice, which is rolled again; DiceList is
the list numbered for dice, a line for each word with its dice and a tab,
as the EFF publishes its own: for en it is the file of the EFF, byte for
byte.
encrypt -dice TEXT and -dice-file FILE take the words from the dice of the
list -dic and show them: the words open the capsule, the numbers give them
only with that list. datekeys wordlist [-dic LIST] writes the numbered
list, to print it, and its SHA-256, which wordkey/lists/README.md records.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
10 hours ago
diceText , err := flagText ( "encrypt" , "dice" , * dice , * diceFile )
if err != nil {
return err
}
if diceText != "" && ( text != "" || * newWords != "" ) {
return errors . New ( "encrypt: -dice and -dice-file exclude -words, -words-file and -new-words" )
}
The alphabet of a word list and the strength of the words drawn
wordkey.CheckList takes the language of the list and refuses a word with a
character that is not a letter of its alphabet, which only the code gives:
for es, a to z, the five vowels with an acute accent, u with diaeresis and
n with tilde, in lower case and NFC. A Cyrillic letter that looks like a
Latin one, a capital, a digit or the carriage return of a file with CRLF
lines would be written down and typed again with another character, and
the capsule would not open. The Spanish list passes as it is; its SHA-256
does not change.
wordkey.Bits is the strength of the words that Generate draws, log2 of the
number of draws in order, and encrypt -new-words prints it: 90 bits for 7
words of 7776.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
12 hours ago
var listSize int
Dice: the words of five dice each, encrypt -dice and datekeys wordlist
For whoever does not trust the random numbers of a computer, as the author
decided: five dice for each word, read in a fixed order, give a number
from 11111 to 66666, the position of the word in a list of 7776 words, the
first die the most significant. wordkey.DiceNumber and DiceWord number the
words and give the word of a number; DiceWords takes several numbers, at
least 6 and never the same word twice, which is rolled again; DiceList is
the list numbered for dice, a line for each word with its dice and a tab,
as the EFF publishes its own: for en it is the file of the EFF, byte for
byte.
encrypt -dice TEXT and -dice-file FILE take the words from the dice of the
list -dic and show them: the words open the capsule, the numbers give them
only with that list. datekeys wordlist [-dic LIST] writes the numbered
list, to print it, and its SHA-256, which wordkey/lists/README.md records.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
10 hours ago
var fromDice [ ] string
if * newWords != "" {
if text != "" {
return errors . New ( "encrypt: -new-words excludes -words and -words-file" )
}
if err := checkNew ( * newWords ) ; err != nil {
return err
}
list , err := wordkey . List ( * dic )
if err != nil {
return fmt . Errorf ( "encrypt: %w" , err )
}
drawn , err := wordkey . Generate ( list , * wordCount , nil )
if err != nil {
return fmt . Errorf ( "encrypt: %w" , err )
}
text = strings . Join ( drawn , " " )
The alphabet of a word list and the strength of the words drawn
wordkey.CheckList takes the language of the list and refuses a word with a
character that is not a letter of its alphabet, which only the code gives:
for es, a to z, the five vowels with an acute accent, u with diaeresis and
n with tilde, in lower case and NFC. A Cyrillic letter that looks like a
Latin one, a capital, a digit or the carriage return of a file with CRLF
lines would be written down and typed again with another character, and
the capsule would not open. The Spanish list passes as it is; its SHA-256
does not change.
wordkey.Bits is the strength of the words that Generate draws, log2 of the
number of draws in order, and encrypt -new-words prints it: 90 bits for 7
words of 7776.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
12 hours ago
listSize = len ( list )
}
Dice: the words of five dice each, encrypt -dice and datekeys wordlist
For whoever does not trust the random numbers of a computer, as the author
decided: five dice for each word, read in a fixed order, give a number
from 11111 to 66666, the position of the word in a list of 7776 words, the
first die the most significant. wordkey.DiceNumber and DiceWord number the
words and give the word of a number; DiceWords takes several numbers, at
least 6 and never the same word twice, which is rolled again; DiceList is
the list numbered for dice, a line for each word with its dice and a tab,
as the EFF publishes its own: for en it is the file of the EFF, byte for
byte.
encrypt -dice TEXT and -dice-file FILE take the words from the dice of the
list -dic and show them: the words open the capsule, the numbers give them
only with that list. datekeys wordlist [-dic LIST] writes the numbered
list, to print it, and its SHA-256, which wordkey/lists/README.md records.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
10 hours ago
if diceText != "" {
list , err := wordkey . List ( * dic )
if err != nil {
return fmt . Errorf ( "encrypt: %w" , err )
}
if fromDice , err = wordkey . DiceWords ( list , diceText ) ; err != nil {
return fmt . Errorf ( "encrypt: %w" , err )
}
text = strings . Join ( fromDice , " " )
listSize = len ( list )
}
A key of words for the CLI: encrypt and decrypt with -words
The author asked for keys that people can keep without files. Package
wordkey derives the X25519 identity of words a person chooses, at least
six: their NFD by the tables of pathrule without the marks U+0300 to
U+036F, each code point in lower case by its simple mapping, split at
white space, joined by one space, and stretched with PBKDF2-HMAC-SHA256
of the standard library, 600 000 rounds, salted with the chain hash and
the round of the capsule. It is wordkey.ts of datekeys-ts byte for byte:
both check the same vector.
encrypt takes -words or -words-file for a time_and_key capsule, and adds
the key as one more recipient, derived for the round of -at; decrypt
takes them and adds the identity, for the round the capsule shows. The
format does not change. Checked: the CLI opened a capsule that the page
wrote with words, with the release from the public relays.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
7 days ago
if text != "" {
if pol != capsule . TimeAndKey {
Dice: the words of five dice each, encrypt -dice and datekeys wordlist
For whoever does not trust the random numbers of a computer, as the author
decided: five dice for each word, read in a fixed order, give a number
from 11111 to 66666, the position of the word in a list of 7776 words, the
first die the most significant. wordkey.DiceNumber and DiceWord number the
words and give the word of a number; DiceWords takes several numbers, at
least 6 and never the same word twice, which is rolled again; DiceList is
the list numbered for dice, a line for each word with its dice and a tab,
as the EFF publishes its own: for en it is the file of the EFF, byte for
byte.
encrypt -dice TEXT and -dice-file FILE take the words from the dice of the
list -dic and show them: the words open the capsule, the numbers give them
only with that list. datekeys wordlist [-dic LIST] writes the numbered
list, to print it, and its SHA-256, which wordkey/lists/README.md records.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
10 hours ago
return errors . New ( "encrypt: -words, -words-file, -new-words and -dice need -policy time_and_key" )
A key of words for the CLI: encrypt and decrypt with -words
The author asked for keys that people can keep without files. Package
wordkey derives the X25519 identity of words a person chooses, at least
six: their NFD by the tables of pathrule without the marks U+0300 to
U+036F, each code point in lower case by its simple mapping, split at
white space, joined by one space, and stretched with PBKDF2-HMAC-SHA256
of the standard library, 600 000 rounds, salted with the chain hash and
the round of the capsule. It is wordkey.ts of datekeys-ts byte for byte:
both check the same vector.
encrypt takes -words or -words-file for a time_and_key capsule, and adds
the key as one more recipient, derived for the round of -at; decrypt
takes them and adds the identity, for the round the capsule shows. The
format does not change. Checked: the CLI opened a capsule that the page
wrote with words, with the release from the public relays.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
7 days ago
}
w := wordkey . Normalize ( text )
if err := wordkey . Check ( w ) ; err != nil {
A key of words for the CLI: encrypt and decrypt with -words
The author asked for keys that people can keep without files. Package
wordkey derives the X25519 identity of words a person chooses, at least
six: their NFD by the tables of pathrule without the marks U+0300 to
U+036F, each code point in lower case by its simple mapping, split at
white space, joined by one space, and stretched with PBKDF2-HMAC-SHA256
of the standard library, 600 000 rounds, salted with the chain hash and
the round of the capsule. It is wordkey.ts of datekeys-ts byte for byte:
both check the same vector.
encrypt takes -words or -words-file for a time_and_key capsule, and adds
the key as one more recipient, derived for the round of -at; decrypt
takes them and adds the identity, for the round the capsule shows. The
format does not change. Checked: the CLI opened a capsule that the page
wrote with words, with the release from the public relays.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
7 days ago
return fmt . Errorf ( "encrypt: %w" , err )
}
opts . Words = w
A key of words for the CLI: encrypt and decrypt with -words
The author asked for keys that people can keep without files. Package
wordkey derives the X25519 identity of words a person chooses, at least
six: their NFD by the tables of pathrule without the marks U+0300 to
U+036F, each code point in lower case by its simple mapping, split at
white space, joined by one space, and stretched with PBKDF2-HMAC-SHA256
of the standard library, 600 000 rounds, salted with the chain hash and
the round of the capsule. It is wordkey.ts of datekeys-ts byte for byte:
both check the same vector.
encrypt takes -words or -words-file for a time_and_key capsule, and adds
the key as one more recipient, derived for the round of -at; decrypt
takes them and adds the identity, for the round the capsule shows. The
format does not change. Checked: the CLI opened a capsule that the page
wrote with words, with the release from the public relays.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
7 days ago
}
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
if * dkk != "" {
if err := checkNew ( * dkk ) ; err != nil {
return err
}
}
What the official SDK says when it seals: the recovery annex, §7.6 and §71
The SHOULDs of the official SDK that the CLI did not follow yet. encrypt
writes next to the .dkc the recovery annex, FILE.dkc.recuperacion.txt
(spec §62.1, rule 27): datekeys.RecoveryAnnex, annex/recovery.md, which is
§79 of the specification under a title with its version and SHA-256, the
same for every capsule; TestRecoveryAnnex checks it against the text of
SpecVersion. -no-recovery leaves it out. encrypt also says what opening the
capsule years later will take (rule 26): the .dkc, a credential of a
time_and_key capsule, and the release of its round, which an archive of
releases or a cache service must keep if drand no longer serves it; and
beyond one year it recommends time_and_key to a time_only capsule (§7.6).
profile.Status and StatusOf give the state of a pinned profile in the
registry of §71, which DateKeys does not publish yet: Quicknet is active.
encrypt writes no capsule with a profile that is not active, and decrypt
and inspect warn when the profile of a capsule is compromised.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
8 hours ago
annex := * out + datekeys . RecoveryAnnexSuffix
if ! * noRecovery {
if err := checkNew ( annex ) ; err != nil {
return err
}
}
if * signPass != "" && * sign == "" {
return errors . New ( "encrypt: -sign-pass-file needs -sign" )
}
if * sign != "" {
k , err := loadAuthorKey ( "encrypt" , * sign , * signPass , stdin )
if err != nil {
return err
}
defer k . Clear ( )
Review fixes: author keys, the writer, the CLI, extensions and the locator
Fixes of the review of the session of 1 and 2 October that the text of
spec v0.11 already asks for:
- authorkey: String and GoString hide the secret key, which only Secret
returns; ParsePublic refuses a key that is not a point of the curve
(ed25519strict.OnCurve, checked against the square root of testkit).
- capsule: a typed nil in AuthorKey, CMSSigner or Sealer is an error, never
a capsule without the signature or the seal that was asked for. A panic
while evaluating the signature or the seal fails only that part, F1 or
S2, not both. OpenOptions.Accept sees the verdicts before step 18 and can
refuse to publish the files.
- extension.CheckWrite, the rule of encoders of spec 72: the writers of
capsules and .dkk files refuse datekeys.note and datekeys.capsule outside
the arrays where they are registered, or with invalid data.
- CLI: encrypt -sign shows the author key and the code of AUTHOR_MESSAGE
before it signs (rule 20); decrypt -expect-author compares the key of an
F4 and writes nothing unless it matches; decrypt notifies a public note
that it does not show; the lines of the verdicts break at the last space
that fits, each row after the first behind a mark, so that the terminal
never breaks them; L is the payload, not the content.
- locator: a reader rejects an address that breaks 44.1 and keeps the
others; addresses refuse the special-purpose blocks of IANA, IPv6 outside
2000::/3, localhost and local names, characters outside RFC 3986, dot
segments, and a CID that does not decode to version 1 and a multihash;
ParseInfo checks that the locator is an age file with one tlock stanza
for the round of its DateKey; Info.Extension reads what it writes; its
errors carry no normative code.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6 days ago
opts . AuthorKey = announced { k , stderr }
}
opts . LargeArea = * largeArea
opts . PublicNote = * note
Review fixes: addresses, safe integers, the note on screen, -expect-author, author keys
CheckURI works on the raw authority, as an HTTP client reads it: no percent
signs, userinfo or backslashes, a host of letters, digits and hyphens or a
public IP literal, a port from 1 to 65535, and Host returns that host. The
integers of the locator stop at 2^53 - 1, and Info.Extension refuses what
ParseInfo would. extension.Standard validates datekeys.capsule through
locator.Standard, and Info.OpenLocator ties the locator to the round of its
own DateKey.
inspect shows the public note as text of the creator, with its prefix and
wrapping and the warning, and says when a note is unusable. encrypt -note
warns that it is public. decrypt -expect-author fails before the release
is requested when the capsule is not format 3. Author key files are read
with the work factor of the spec as their maximum, the passphrase is not
read from a terminal, and two copies of secrets are cleared.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
6 days ago
if * note != "" {
fmt . Fprintln ( stderr , "warning: the public note is in clear: anyone who has the .dkc reads it before the date, nobody can delete it from the copies that circulate, and with the date it can identify someone (spec §24.1)." )
}
Format 3, step 5: the CLI
datekeys encrypt writes format 3 and datekeys decrypt writes its files
to a new folder, with the presentation of spec 29.7.
- encrypt: -in is repeatable and takes files and folders; a folder
gives its name as the first segment, as a browser does, and is
walked with Lstat, following no link, taking regular files only.
.DS_Store, Thumbs.db, desktop.ini, ._* and __MACOSX are left out of
folders, and each one left out is reported (62.1 rule 15). New
-comment, -author and -no-mtime; the mtimes are kept by default
(rule 16). A capsule may hold a comment alone. The copy of a pipe to
a temporary file goes, as only regular files are taken.
- decrypt: the prelude decides. Format 3 claims -out with os.Mkdir,
only when there are files, stages the tree in -out/.datekeys-*
through an os.Root with O_EXCL and mode 0600, sets the mtimes, and
moves each entry of the first level into place at step 18; any
failure removes the folder (spec 56). Formats 1 and 2 still write a
file.
- The presentation goes to stdout: the verdicts, the declared author
and the comment box with their labels, the paths, and the verdicts
again. Every line of the creator goes in pieces of at most W - 3
columns behind the prefix, counting 2 for anything but printable
ASCII, with its TABs expanded to multiples of 8; W is the width of
the terminal, asked with syscall on Unix and Windows, or 80. Risky
names get a warning: shortcuts, desktop.ini, .git, programs and a
leading dash, compared by their key of R7.
- Encrypt no longer runs in the CLI: only the test data generators
set TestVectors.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
sources , skipped , err := collect ( ins , ! * noMTime )
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
if err != nil {
Format 3, step 5: the CLI
datekeys encrypt writes format 3 and datekeys decrypt writes its files
to a new folder, with the presentation of spec 29.7.
- encrypt: -in is repeatable and takes files and folders; a folder
gives its name as the first segment, as a browser does, and is
walked with Lstat, following no link, taking regular files only.
.DS_Store, Thumbs.db, desktop.ini, ._* and __MACOSX are left out of
folders, and each one left out is reported (62.1 rule 15). New
-comment, -author and -no-mtime; the mtimes are kept by default
(rule 16). A capsule may hold a comment alone. The copy of a pipe to
a temporary file goes, as only regular files are taken.
- decrypt: the prelude decides. Format 3 claims -out with os.Mkdir,
only when there are files, stages the tree in -out/.datekeys-*
through an os.Root with O_EXCL and mode 0600, sets the mtimes, and
moves each entry of the first level into place at step 18; any
failure removes the folder (spec 56). Formats 1 and 2 still write a
file.
- The presentation goes to stdout: the verdicts, the declared author
and the comment box with their labels, the paths, and the verdicts
again. Every line of the creator goes in pieces of at most W - 3
columns behind the prefix, counting 2 for anything but printable
ASCII, with its TABs expanded to multiples of 8; W is the width of
the terminal, asked with syscall on Unix and Windows, or 80. Risky
names get a warning: shortcuts, desktop.ini, .git, programs and a
leading dash, compared by their key of R7.
- Encrypt no longer runs in the CLI: only the test data generators
set TestVectors.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
return fmt . Errorf ( "encrypt: %w" , err )
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
}
var res * capsule . Result
err = writeAtomic ( * out , func ( w io . Writer ) error {
Format 3, step 5: the CLI
datekeys encrypt writes format 3 and datekeys decrypt writes its files
to a new folder, with the presentation of spec 29.7.
- encrypt: -in is repeatable and takes files and folders; a folder
gives its name as the first segment, as a browser does, and is
walked with Lstat, following no link, taking regular files only.
.DS_Store, Thumbs.db, desktop.ini, ._* and __MACOSX are left out of
folders, and each one left out is reported (62.1 rule 15). New
-comment, -author and -no-mtime; the mtimes are kept by default
(rule 16). A capsule may hold a comment alone. The copy of a pipe to
a temporary file goes, as only regular files are taken.
- decrypt: the prelude decides. Format 3 claims -out with os.Mkdir,
only when there are files, stages the tree in -out/.datekeys-*
through an os.Root with O_EXCL and mode 0600, sets the mtimes, and
moves each entry of the first level into place at step 18; any
failure removes the folder (spec 56). Formats 1 and 2 still write a
file.
- The presentation goes to stdout: the verdicts, the declared author
and the comment box with their labels, the paths, and the verdicts
again. Every line of the creator goes in pieces of at most W - 3
columns behind the prefix, counting 2 for anything but printable
ASCII, with its TABs expanded to multiples of 8; W is the width of
the terminal, asked with syscall on Unix and Windows, or 80. Risky
names get a warning: shortcuts, desktop.ini, .git, programs and a
leading dash, compared by their key of R7.
- Encrypt no longer runs in the CLI: only the test data generators
set TestVectors.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
res , err = capsule . EncryptFiles ( w , sources , opts )
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
return err
} )
if err != nil {
return err
}
if res . PortableKey != nil {
defer res . PortableKey . Wipe ( )
if err := writeAtomic ( * dkk , func ( w io . Writer ) error { return accesskey . Encode ( w , res . PortableKey ) } ) ; err != nil {
return fmt . Errorf ( "the capsule was written to %s but its .dkk could not be: %w" , * out , err )
}
}
if * newWords != "" {
if err := writeAtomic ( * newWords , func ( w io . Writer ) error { _ , err := io . WriteString ( w , text + "\n" ) ; return err } ) ; err != nil {
return fmt . Errorf ( "the capsule was written to %s but its words could not be: %w" , * out , err )
}
}
What the official SDK says when it seals: the recovery annex, §7.6 and §71
The SHOULDs of the official SDK that the CLI did not follow yet. encrypt
writes next to the .dkc the recovery annex, FILE.dkc.recuperacion.txt
(spec §62.1, rule 27): datekeys.RecoveryAnnex, annex/recovery.md, which is
§79 of the specification under a title with its version and SHA-256, the
same for every capsule; TestRecoveryAnnex checks it against the text of
SpecVersion. -no-recovery leaves it out. encrypt also says what opening the
capsule years later will take (rule 26): the .dkc, a credential of a
time_and_key capsule, and the release of its round, which an archive of
releases or a cache service must keep if drand no longer serves it; and
beyond one year it recommends time_and_key to a time_only capsule (§7.6).
profile.Status and StatusOf give the state of a pinned profile in the
registry of §71, which DateKeys does not publish yet: Quicknet is active.
encrypt writes no capsule with a profile that is not active, and decrypt
and inspect warn when the profile of a capsule is compromised.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
8 hours ago
if ! * noRecovery {
if err := writeAtomic ( annex , func ( w io . Writer ) error { _ , err := io . WriteString ( w , datekeys . RecoveryAnnex ) ; return err } ) ; err != nil {
return fmt . Errorf ( "the capsule was written to %s but its recovery annex could not be: %w" , * out , err )
}
}
Review fixes: author keys, the writer, the CLI, extensions and the locator
Fixes of the review of the session of 1 and 2 October that the text of
spec v0.11 already asks for:
- authorkey: String and GoString hide the secret key, which only Secret
returns; ParsePublic refuses a key that is not a point of the curve
(ed25519strict.OnCurve, checked against the square root of testkit).
- capsule: a typed nil in AuthorKey, CMSSigner or Sealer is an error, never
a capsule without the signature or the seal that was asked for. A panic
while evaluating the signature or the seal fails only that part, F1 or
S2, not both. OpenOptions.Accept sees the verdicts before step 18 and can
refuse to publish the files.
- extension.CheckWrite, the rule of encoders of spec 72: the writers of
capsules and .dkk files refuse datekeys.note and datekeys.capsule outside
the arrays where they are registered, or with invalid data.
- CLI: encrypt -sign shows the author key and the code of AUTHOR_MESSAGE
before it signs (rule 20); decrypt -expect-author compares the key of an
F4 and writes nothing unless it matches; decrypt notifies a public note
that it does not show; the lines of the verdicts break at the last space
that fits, each row after the first behind a mark, so that the terminal
never breaks them; L is the payload, not the content.
- locator: a reader rejects an address that breaks 44.1 and keeps the
others; addresses refuse the special-purpose blocks of IANA, IPv6 outside
2000::/3, localhost and local names, characters outside RFC 3986, dot
segments, and a CID that does not decode to version 1 and a multihash;
ParseInfo checks that the locator is an age file with one tlock stanza
for the round of its DateKey; Info.Extension reads what it writes; its
errors carry no normative code.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6 days ago
fmt . Fprintf ( stderr , "Encrypted locally for %s (round %d)\n datekey %s\n capsule_id %x\n format %d: %d files, a payload of %d bytes, padded to %d (%s)\n" ,
Format 3, step 5: the CLI
datekeys encrypt writes format 3 and datekeys decrypt writes its files
to a new folder, with the presentation of spec 29.7.
- encrypt: -in is repeatable and takes files and folders; a folder
gives its name as the first segment, as a browser does, and is
walked with Lstat, following no link, taking regular files only.
.DS_Store, Thumbs.db, desktop.ini, ._* and __MACOSX are left out of
folders, and each one left out is reported (62.1 rule 15). New
-comment, -author and -no-mtime; the mtimes are kept by default
(rule 16). A capsule may hold a comment alone. The copy of a pipe to
a temporary file goes, as only regular files are taken.
- decrypt: the prelude decides. Format 3 claims -out with os.Mkdir,
only when there are files, stages the tree in -out/.datekeys-*
through an os.Root with O_EXCL and mode 0600, sets the mtimes, and
moves each entry of the first level into place at step 18; any
failure removes the folder (spec 56). Formats 1 and 2 still write a
file.
- The presentation goes to stdout: the verdicts, the declared author
and the comment box with their labels, the paths, and the verdicts
again. Every line of the creator goes in pieces of at most W - 3
columns behind the prefix, counting 2 for anything but printable
ASCII, with its TABs expanded to multiples of 8; W is the width of
the terminal, asked with syscall on Unix and Windows, or 80. Risky
names get a warning: shortcuts, desktop.ini, .git, programs and a
leading dash, compared by their key of R7.
- Encrypt no longer runs in the CLI: only the test data generators
set TestVectors.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
res . UnlockAt . Format ( time . RFC3339 ) , res . DateKey . Round , res . DateKey . Compact ( ) , res . CapsuleID , res . Format , len ( res . Head . Files ) , res . Length , res . PaddedLength , res . Padding )
for _ , p := range skipped {
fmt . Fprintf ( stderr , " left out %s, which the system creates on its own\n" , p )
}
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
if res . PortableKey != nil {
fmt . Fprintf ( stderr , " access key %s: keep it secret; it is valid for this capsule only\n" , * dkk )
}
if * newWords != "" {
The alphabet of a word list and the strength of the words drawn
wordkey.CheckList takes the language of the list and refuses a word with a
character that is not a letter of its alphabet, which only the code gives:
for es, a to z, the five vowels with an acute accent, u with diaeresis and
n with tilde, in lower case and NFC. A Cyrillic letter that looks like a
Latin one, a capital, a digit or the carriage return of a file with CRLF
lines would be written down and typed again with another character, and
the capsule would not open. The Spanish list passes as it is; its SHA-256
does not change.
wordkey.Bits is the strength of the words that Generate draws, log2 of the
number of draws in order, and encrypt -new-words prints it: 90 bits for 7
words of 7776.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
12 hours ago
fmt . Fprintf ( stderr , " words %s: %d words of the list %s, %d bits; keep them secret, or write them down and delete the file\n" ,
* newWords , * wordCount , * dic , int ( wordkey . Bits ( listSize , * wordCount ) ) )
}
Dice: the words of five dice each, encrypt -dice and datekeys wordlist
For whoever does not trust the random numbers of a computer, as the author
decided: five dice for each word, read in a fixed order, give a number
from 11111 to 66666, the position of the word in a list of 7776 words, the
first die the most significant. wordkey.DiceNumber and DiceWord number the
words and give the word of a number; DiceWords takes several numbers, at
least 6 and never the same word twice, which is rolled again; DiceList is
the list numbered for dice, a line for each word with its dice and a tab,
as the EFF publishes its own: for en it is the file of the EFF, byte for
byte.
encrypt -dice TEXT and -dice-file FILE take the words from the dice of the
list -dic and show them: the words open the capsule, the numbers give them
only with that list. datekeys wordlist [-dic LIST] writes the numbered
list, to print it, and its SHA-256, which wordkey/lists/README.md records.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
10 hours ago
if fromDice != nil {
fmt . Fprintf ( stderr , " words %s: the %d words of the dice in the list %s, %d bits; keep these words, which open the capsule: the numbers give them only with this list\n" ,
text , len ( fromDice ) , * dic , int ( wordkey . Bits ( listSize , len ( fromDice ) ) ) )
}
What the official SDK says when it seals: the recovery annex, §7.6 and §71
The SHOULDs of the official SDK that the CLI did not follow yet. encrypt
writes next to the .dkc the recovery annex, FILE.dkc.recuperacion.txt
(spec §62.1, rule 27): datekeys.RecoveryAnnex, annex/recovery.md, which is
§79 of the specification under a title with its version and SHA-256, the
same for every capsule; TestRecoveryAnnex checks it against the text of
SpecVersion. -no-recovery leaves it out. encrypt also says what opening the
capsule years later will take (rule 26): the .dkc, a credential of a
time_and_key capsule, and the release of its round, which an archive of
releases or a cache service must keep if drand no longer serves it; and
beyond one year it recommends time_and_key to a time_only capsule (§7.6).
profile.Status and StatusOf give the state of a pinned profile in the
registry of §71, which DateKeys does not publish yet: Quicknet is active.
encrypt writes no capsule with a profile that is not active, and decrypt
and inspect warn when the profile of a capsule is compromised.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
8 hours ago
if ! * noRecovery {
fmt . Fprintf ( stderr , " recovery %s: how to open the capsule without DateKeys software (spec §79); keep it with the .dkc\n" , annex )
}
// What opening it years later will take (spec §62.1, rule 26).
needs := "the .dkc"
if pol == capsule . TimeAndKey {
needs = "the .dkc, one of its credentials"
}
fmt . Fprintf ( stderr , " to open %s and the release of round %d, which drand publishes at %s: years later, if drand no longer\n" +
" serves it, an archive of releases or a cache service must have kept it (spec §50)\n" ,
needs , res . DateKey . Round , res . UnlockAt . Format ( time . RFC3339 ) )
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
if res . UnlockAt . Sub ( now ( ) ) > longHorizon {
fmt . Fprintln ( stderr , "warning: Quicknet V1 timelock is not post-quantum. The ciphertext may stay available for years,\n" +
" and its future confidentiality depends on the provider and on the underlying cryptography (spec §53)." )
What the official SDK says when it seals: the recovery annex, §7.6 and §71
The SHOULDs of the official SDK that the CLI did not follow yet. encrypt
writes next to the .dkc the recovery annex, FILE.dkc.recuperacion.txt
(spec §62.1, rule 27): datekeys.RecoveryAnnex, annex/recovery.md, which is
§79 of the specification under a title with its version and SHA-256, the
same for every capsule; TestRecoveryAnnex checks it against the text of
SpecVersion. -no-recovery leaves it out. encrypt also says what opening the
capsule years later will take (rule 26): the .dkc, a credential of a
time_and_key capsule, and the release of its round, which an archive of
releases or a cache service must keep if drand no longer serves it; and
beyond one year it recommends time_and_key to a time_only capsule (§7.6).
profile.Status and StatusOf give the state of a pinned profile in the
registry of §71, which DateKeys does not publish yet: Quicknet is active.
encrypt writes no capsule with a profile that is not active, and decrypt
and inspect warn when the profile of a capsule is compromised.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
8 hours ago
if pol == capsule . TimeOnly {
fmt . Fprintln ( stderr , "note: for a horizon this long, or a valuable content, -policy time_and_key adds a credential that drand does\n" +
" not hold: an early signature of the round would not open the capsule (spec §7.6)." )
}
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
}
return nil
}
Format 3, step 5: the CLI
datekeys encrypt writes format 3 and datekeys decrypt writes its files
to a new folder, with the presentation of spec 29.7.
- encrypt: -in is repeatable and takes files and folders; a folder
gives its name as the first segment, as a browser does, and is
walked with Lstat, following no link, taking regular files only.
.DS_Store, Thumbs.db, desktop.ini, ._* and __MACOSX are left out of
folders, and each one left out is reported (62.1 rule 15). New
-comment, -author and -no-mtime; the mtimes are kept by default
(rule 16). A capsule may hold a comment alone. The copy of a pipe to
a temporary file goes, as only regular files are taken.
- decrypt: the prelude decides. Format 3 claims -out with os.Mkdir,
only when there are files, stages the tree in -out/.datekeys-*
through an os.Root with O_EXCL and mode 0600, sets the mtimes, and
moves each entry of the first level into place at step 18; any
failure removes the folder (spec 56). Formats 1 and 2 still write a
file.
- The presentation goes to stdout: the verdicts, the declared author
and the comment box with their labels, the paths, and the verdicts
again. Every line of the creator goes in pieces of at most W - 3
columns behind the prefix, counting 2 for anything but printable
ASCII, with its TABs expanded to multiples of 8; W is the width of
the terminal, asked with syscall on Unix and Windows, or 80. Risky
names get a warning: shortcuts, desktop.ini, .git, programs and a
leading dash, compared by their key of R7.
- Encrypt no longer runs in the CLI: only the test data generators
set TestVectors.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
func decrypt ( args [ ] string , stdout , stderr io . Writer , now func ( ) time . Time ) error {
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
fs := newFlags ( "decrypt" )
in := fs . String ( "in" , "" , ".dkc file" )
Format 3, step 5: the CLI
datekeys encrypt writes format 3 and datekeys decrypt writes its files
to a new folder, with the presentation of spec 29.7.
- encrypt: -in is repeatable and takes files and folders; a folder
gives its name as the first segment, as a browser does, and is
walked with Lstat, following no link, taking regular files only.
.DS_Store, Thumbs.db, desktop.ini, ._* and __MACOSX are left out of
folders, and each one left out is reported (62.1 rule 15). New
-comment, -author and -no-mtime; the mtimes are kept by default
(rule 16). A capsule may hold a comment alone. The copy of a pipe to
a temporary file goes, as only regular files are taken.
- decrypt: the prelude decides. Format 3 claims -out with os.Mkdir,
only when there are files, stages the tree in -out/.datekeys-*
through an os.Root with O_EXCL and mode 0600, sets the mtimes, and
moves each entry of the first level into place at step 18; any
failure removes the folder (spec 56). Formats 1 and 2 still write a
file.
- The presentation goes to stdout: the verdicts, the declared author
and the comment box with their labels, the paths, and the verdicts
again. Every line of the creator goes in pieces of at most W - 3
columns behind the prefix, counting 2 for anything but printable
ASCII, with its TABs expanded to multiples of 8; W is the width of
the terminal, asked with syscall on Unix and Windows, or 80. Risky
names get a warning: shortcuts, desktop.ini, .git, programs and a
leading dash, compared by their key of R7.
- Encrypt no longer runs in the CLI: only the test data generators
set TestVectors.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
out := fs . String ( "out" , "" , "new folder (format 3) or file (formats 1 and 2); never overwritten" )
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
dkk := fs . String ( "dkk" , "" , "portable access key (.dkk)" )
timeout := fs . Duration ( "timeout" , 30 * time . Second , "release request timeout" )
var identities , relays multi
fs . Var ( & identities , "identity" , "age identity file with X25519 keys (repeatable)" )
fs . Var ( & relays , "relay" , "drand relay base URL (repeatable); default: public relays" )
A key of words for the CLI: encrypt and decrypt with -words
The author asked for keys that people can keep without files. Package
wordkey derives the X25519 identity of words a person chooses, at least
six: their NFD by the tables of pathrule without the marks U+0300 to
U+036F, each code point in lower case by its simple mapping, split at
white space, joined by one space, and stretched with PBKDF2-HMAC-SHA256
of the standard library, 600 000 rounds, salted with the chain hash and
the round of the capsule. It is wordkey.ts of datekeys-ts byte for byte:
both check the same vector.
encrypt takes -words or -words-file for a time_and_key capsule, and adds
the key as one more recipient, derived for the round of -at; decrypt
takes them and adds the identity, for the round the capsule shows. The
format does not change. Checked: the CLI opened a capsule that the page
wrote with words, with the release from the public relays.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
7 days ago
words := fs . String ( "words" , "" , "the words of a key of words; they stay in the shell history" )
wordsFile := fs . String ( "words-file" , "" , "file with the words of a key of words" )
expect := fs . String ( "expect-author" , "" , "fail unless the capsule is signed with this public key, dkauthor1..." )
Spec v0.15 draft: remove the .dkr file
The author's decision of 7 October 2026. A release saved next to a capsule
cannot exist when the capsule is made, and once the date comes the capsule
can be opened: such a file only opens it again and does not cover the real
case, someone opening it decades later when drand is gone and nobody saved
anything. Long-term recovery rests instead on archives and cache services
that keep the releases of all rounds; a reader asks for its round and
verifies the signature against the pinned key.
Spec: the .dkr extension (section 20, back to v0.14), sections 1, 4, 8, 45,
47.1, 49, 50 (rewritten), 53, 62.1 (rule 28 removed, rule 26 reworded),
63, 70, 73, 74 (the datekeys.release .dkk extension dropped too), 76
(the v0.15 block, with the discarded design) and the annex 79. The
release object, the chain hash at step 10, step 9.c option B and the
archive format stay.
Code: decrypt -save-release and the command datekeys release are gone,
with writeRelease and their tests; decrypt -release FILE stays. The
release objects of testdata/releases are now <round>.cbor, and
TestVectorFilesAreCurrent fails on a file the generator no longer writes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
23 hours ago
release := fs . String ( "release" , "" , "the release in hand: a release object, drand's JSON or a local release archive; no network request" )
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
if err := parse ( fs , args ) ; err != nil {
return err
}
if * in == "" || * out == "" {
return errors . New ( "decrypt: -in and -out are required" )
}
Release object, release in hand and step 9.c option B (spec v0.15 draft)
The release of a round becomes a file, .dkr: a release object in
deterministic CBOR, {0: "datekeys-release", 1: 1, 2: chain_hash, 3: round,
4: signature}, which provider.EncodeRelease writes and DecodeRelease reads
with its layers (size, type and version, schema). provider.ParseRelease
also reads drand's JSON as the input of the caller. Verify checks the chain
hash a release names before its round and its signature, with
ERR_PROFILE_MISMATCH. provider.Archive reads a local release archive, the
informative format of the draft.
capsule.OpenOptions.Release takes a release in hand, a provider.Supplier,
exclusive with Source: Open does not compare it with the clock (step 9.c,
option B) and reports a clock behind it in Opened.ClockBehind; a network
source is still never asked before the round time. The CLI gains
decrypt -release FILE (.dkr, drand's JSON or a local archive),
decrypt -save-release FILE.dkr and the command release, which fetches,
verifies and saves the .dkr without opening the capsule.
Test data: vectors/release.json, releases/<round>.dkr for rounds 1000,
1001, 1004 and 2000, and a local archive of rounds 1000 to 1004. In
mutations.json every case says its source, "supplied" or "network"; the
case "round not reached yet", a release in hand, now opens, and four cases
are added: the same with a network source, a release of another round from
a network source, and two release objects of another chain. SpecVersion
stays 0.14 until the author approves the draft.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 day ago
if * release != "" && len ( relays ) > 0 {
return errors . New ( "decrypt: -release and -relay are exclusive" )
}
Review fixes: author keys, the writer, the CLI, extensions and the locator
Fixes of the review of the session of 1 and 2 October that the text of
spec v0.11 already asks for:
- authorkey: String and GoString hide the secret key, which only Secret
returns; ParsePublic refuses a key that is not a point of the curve
(ed25519strict.OnCurve, checked against the square root of testkit).
- capsule: a typed nil in AuthorKey, CMSSigner or Sealer is an error, never
a capsule without the signature or the seal that was asked for. A panic
while evaluating the signature or the seal fails only that part, F1 or
S2, not both. OpenOptions.Accept sees the verdicts before step 18 and can
refuse to publish the files.
- extension.CheckWrite, the rule of encoders of spec 72: the writers of
capsules and .dkk files refuse datekeys.note and datekeys.capsule outside
the arrays where they are registered, or with invalid data.
- CLI: encrypt -sign shows the author key and the code of AUTHOR_MESSAGE
before it signs (rule 20); decrypt -expect-author compares the key of an
F4 and writes nothing unless it matches; decrypt notifies a public note
that it does not show; the lines of the verdicts break at the last space
that fits, each row after the first behind a mark, so that the terminal
never breaks them; L is the payload, not the content.
- locator: a reader rejects an address that breaks 44.1 and keeps the
others; addresses refuse the special-purpose blocks of IANA, IPv6 outside
2000::/3, localhost and local names, characters outside RFC 3986, dot
segments, and a CID that does not decode to version 1 and a multihash;
ParseInfo checks that the locator is an age file with one tlock stanza
for the round of its DateKey; Info.Extension reads what it writes; its
errors carry no normative code.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6 days ago
var expected [ ] byte
if * expect != "" {
Review fixes: author keys, the writer, the CLI, extensions and the locator
Fixes of the review of the session of 1 and 2 October that the text of
spec v0.11 already asks for:
- authorkey: String and GoString hide the secret key, which only Secret
returns; ParsePublic refuses a key that is not a point of the curve
(ed25519strict.OnCurve, checked against the square root of testkit).
- capsule: a typed nil in AuthorKey, CMSSigner or Sealer is an error, never
a capsule without the signature or the seal that was asked for. A panic
while evaluating the signature or the seal fails only that part, F1 or
S2, not both. OpenOptions.Accept sees the verdicts before step 18 and can
refuse to publish the files.
- extension.CheckWrite, the rule of encoders of spec 72: the writers of
capsules and .dkk files refuse datekeys.note and datekeys.capsule outside
the arrays where they are registered, or with invalid data.
- CLI: encrypt -sign shows the author key and the code of AUTHOR_MESSAGE
before it signs (rule 20); decrypt -expect-author compares the key of an
F4 and writes nothing unless it matches; decrypt notifies a public note
that it does not show; the lines of the verdicts break at the last space
that fits, each row after the first behind a mark, so that the terminal
never breaks them; L is the payload, not the content.
- locator: a reader rejects an address that breaks 44.1 and keeps the
others; addresses refuse the special-purpose blocks of IANA, IPv6 outside
2000::/3, localhost and local names, characters outside RFC 3986, dot
segments, and a CID that does not decode to version 1 and a multihash;
ParseInfo checks that the locator is an age file with one tlock stanza
for the round of its DateKey; Info.Extension reads what it writes; its
errors carry no normative code.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6 days ago
k , err := authorkey . ParsePublic ( * expect )
if err != nil {
return fmt . Errorf ( "decrypt: -expect-author: %w" , err )
}
Review fixes: author keys, the writer, the CLI, extensions and the locator
Fixes of the review of the session of 1 and 2 October that the text of
spec v0.11 already asks for:
- authorkey: String and GoString hide the secret key, which only Secret
returns; ParsePublic refuses a key that is not a point of the curve
(ed25519strict.OnCurve, checked against the square root of testkit).
- capsule: a typed nil in AuthorKey, CMSSigner or Sealer is an error, never
a capsule without the signature or the seal that was asked for. A panic
while evaluating the signature or the seal fails only that part, F1 or
S2, not both. OpenOptions.Accept sees the verdicts before step 18 and can
refuse to publish the files.
- extension.CheckWrite, the rule of encoders of spec 72: the writers of
capsules and .dkk files refuse datekeys.note and datekeys.capsule outside
the arrays where they are registered, or with invalid data.
- CLI: encrypt -sign shows the author key and the code of AUTHOR_MESSAGE
before it signs (rule 20); decrypt -expect-author compares the key of an
F4 and writes nothing unless it matches; decrypt notifies a public note
that it does not show; the lines of the verdicts break at the last space
that fits, each row after the first behind a mark, so that the terminal
never breaks them; L is the payload, not the content.
- locator: a reader rejects an address that breaks 44.1 and keeps the
others; addresses refuse the special-purpose blocks of IANA, IPv6 outside
2000::/3, localhost and local names, characters outside RFC 3986, dot
segments, and a CID that does not decode to version 1 and a multihash;
ParseInfo checks that the locator is an age file with one tlock stanza
for the round of its DateKey; Info.Extension reads what it writes; its
errors carry no normative code.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6 days ago
expected = k
}
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
reg , err := profile . Default ( )
if err != nil {
return err
}
Release object, release in hand and step 9.c option B (spec v0.15 draft)
The release of a round becomes a file, .dkr: a release object in
deterministic CBOR, {0: "datekeys-release", 1: 1, 2: chain_hash, 3: round,
4: signature}, which provider.EncodeRelease writes and DecodeRelease reads
with its layers (size, type and version, schema). provider.ParseRelease
also reads drand's JSON as the input of the caller. Verify checks the chain
hash a release names before its round and its signature, with
ERR_PROFILE_MISMATCH. provider.Archive reads a local release archive, the
informative format of the draft.
capsule.OpenOptions.Release takes a release in hand, a provider.Supplier,
exclusive with Source: Open does not compare it with the clock (step 9.c,
option B) and reports a clock behind it in Opened.ClockBehind; a network
source is still never asked before the round time. The CLI gains
decrypt -release FILE (.dkr, drand's JSON or a local archive),
decrypt -save-release FILE.dkr and the command release, which fetches,
verifies and saves the .dkr without opening the capsule.
Test data: vectors/release.json, releases/<round>.dkr for rounds 1000,
1001, 1004 and 2000, and a local archive of rounds 1000 to 1004. In
mutations.json every case says its source, "supplied" or "network"; the
case "round not reached yet", a release in hand, now opens, and four cases
are added: the same with a network source, a release of another round from
a network source, and two release objects of another chain. SpecVersion
stays 0.14 until the author approves the draft.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 day ago
opts := capsule . OpenOptions { Registry : reg , Now : now }
if * release != "" {
supplier , closer , err := releaseInHand ( * release )
if err != nil {
return err
}
defer closer . Close ( )
opts . Release = supplier
} else {
opts . Source = drand . New ( relays ... )
}
Review fixes: author keys, the writer, the CLI, extensions and the locator
Fixes of the review of the session of 1 and 2 October that the text of
spec v0.11 already asks for:
- authorkey: String and GoString hide the secret key, which only Secret
returns; ParsePublic refuses a key that is not a point of the curve
(ed25519strict.OnCurve, checked against the square root of testkit).
- capsule: a typed nil in AuthorKey, CMSSigner or Sealer is an error, never
a capsule without the signature or the seal that was asked for. A panic
while evaluating the signature or the seal fails only that part, F1 or
S2, not both. OpenOptions.Accept sees the verdicts before step 18 and can
refuse to publish the files.
- extension.CheckWrite, the rule of encoders of spec 72: the writers of
capsules and .dkk files refuse datekeys.note and datekeys.capsule outside
the arrays where they are registered, or with invalid data.
- CLI: encrypt -sign shows the author key and the code of AUTHOR_MESSAGE
before it signs (rule 20); decrypt -expect-author compares the key of an
F4 and writes nothing unless it matches; decrypt notifies a public note
that it does not show; the lines of the verdicts break at the last space
that fits, each row after the first behind a mark, so that the terminal
never breaks them; L is the payload, not the content.
- locator: a reader rejects an address that breaks 44.1 and keeps the
others; addresses refuse the special-purpose blocks of IANA, IPv6 outside
2000::/3, localhost and local names, characters outside RFC 3986, dot
segments, and a CID that does not decode to version 1 and a multihash;
ParseInfo checks that the locator is an age file with one tlock stanza
for the round of its DateKey; Info.Extension reads what it writes; its
errors carry no normative code.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
6 days ago
if expected != nil {
// The expected key is not a key that the person saved, with a label
// she gave it: a signature with it is F4, which shows the whole key. A
// capsule that is not signed with it is refused before step 18, so
// that none of its files is ever written.
opts . Accept = func ( v capsule . Verdicts ) error {
if v . Signature == capsule . VerdictSignedOther && bytes . Equal ( v . AuthorKey [ : ] , expected ) {
return nil
}
writeVerdicts ( stdout , v . Lines ( ) , outputWidth ( stdout ) )
return fmt . Errorf ( "decrypt: the capsule is not signed with the expected key %s: nothing was written to %s" , * expect , * out )
}
}
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
for _ , path := range identities {
ids , err := readIdentities ( path )
if err != nil {
return err
}
opts . Identities = append ( opts . Identities , ids ... )
}
if * dkk != "" {
Spec v0.8.2 refinements: error precedence, trust model, strict order
Approved refinements, each recorded with its reproducible case in the
§76 v0.8.2 subsection:
- §69.1: layered error model with normative precedence (frame, type tag
and version, CBOR profile and CDDL, then fields with their own code in
ascending key order; across steps the §63 order decides), with a scope
paragraph for the optional steps 5, 6 and 8.
- §55.1: normative trust table per section (who can write it, from which
step it is bound, what it never proves); §72: security-relevant claims
go in CONTROL_CBOR or under a signature, .dkk data is advisory.
- §31/§54: extension arrays in strictly ascending unsigned byte order of
extension_id (one rule for order and uniqueness).
- Gaps a second implementation needed: §28.1 malformed age headers,
§15/§19 latest unlock time and dk1_ reading rules, §22/§23/§57 length
lower bounds, §63 step 8 tlock argument comparison and step 9 order,
§12.1 profile validation with the drand chain-hash formula, §74 table
of implementation limits.
Reference alignment: .dkk errors only at step 9.a (new
OpenOptions.AccessKeyFile, used by the CLI), CR/LF in dk1_ is
ERR_DATEKEY_INVALID, BODY_LEN 0 is ERR_INTEGRITY, nil identities are not
credentials, and AccessIdentity tries every identity on every stanza so
its verdict does not depend on their order. dk1.json gains three
vectors; every other testdata file is byte-identical.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 weeks ago
// Open decodes the .dkk at step 9.a, and only for a time_and_key
// capsule, so that its errors come in the order of spec §63.
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
f , err := os . Open ( * dkk )
if err != nil {
return err
}
Spec v0.8.2 refinements: error precedence, trust model, strict order
Approved refinements, each recorded with its reproducible case in the
§76 v0.8.2 subsection:
- §69.1: layered error model with normative precedence (frame, type tag
and version, CBOR profile and CDDL, then fields with their own code in
ascending key order; across steps the §63 order decides), with a scope
paragraph for the optional steps 5, 6 and 8.
- §55.1: normative trust table per section (who can write it, from which
step it is bound, what it never proves); §72: security-relevant claims
go in CONTROL_CBOR or under a signature, .dkk data is advisory.
- §31/§54: extension arrays in strictly ascending unsigned byte order of
extension_id (one rule for order and uniqueness).
- Gaps a second implementation needed: §28.1 malformed age headers,
§15/§19 latest unlock time and dk1_ reading rules, §22/§23/§57 length
lower bounds, §63 step 8 tlock argument comparison and step 9 order,
§12.1 profile validation with the drand chain-hash formula, §74 table
of implementation limits.
Reference alignment: .dkk errors only at step 9.a (new
OpenOptions.AccessKeyFile, used by the CLI), CR/LF in dk1_ is
ERR_DATEKEY_INVALID, BODY_LEN 0 is ERR_INTEGRITY, nil identities are not
credentials, and AccessIdentity tries every identity on every stanza so
its verdict does not depend on their order. dk1.json gains three
vectors; every other testdata file is byte-identical.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 weeks ago
defer f . Close ( )
opts . AccessKeyFile = f
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
}
src , err := os . Open ( * in )
if err != nil {
return err
}
defer src . Close ( )
A key of words for the CLI: encrypt and decrypt with -words
The author asked for keys that people can keep without files. Package
wordkey derives the X25519 identity of words a person chooses, at least
six: their NFD by the tables of pathrule without the marks U+0300 to
U+036F, each code point in lower case by its simple mapping, split at
white space, joined by one space, and stretched with PBKDF2-HMAC-SHA256
of the standard library, 600 000 rounds, salted with the chain hash and
the round of the capsule. It is wordkey.ts of datekeys-ts byte for byte:
both check the same vector.
encrypt takes -words or -words-file for a time_and_key capsule, and adds
the key as one more recipient, derived for the round of -at; decrypt
takes them and adds the identity, for the round the capsule shows. The
format does not change. Checked: the CLI opened a capsule that the page
wrote with words, with the release from the public relays.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
7 days ago
text , err := wordsText ( "decrypt" , * words , * wordsFile )
if err != nil {
return err
}
if text != "" {
// The words are salted with the chain and the round of the capsule:
// steps 1 to 8 give them. When they fail, Open reports why.
if insp , err := capsule . Inspect ( src , capsule . InspectOptions { Registry : reg } ) ; err == nil {
id , err := wordkey . Identity ( wordkey . Normalize ( text ) , insp . Profile . ChainHash [ : ] , insp . Header . DateKey . Round , insp . Header . CapsuleID [ : ] )
A key of words for the CLI: encrypt and decrypt with -words
The author asked for keys that people can keep without files. Package
wordkey derives the X25519 identity of words a person chooses, at least
six: their NFD by the tables of pathrule without the marks U+0300 to
U+036F, each code point in lower case by its simple mapping, split at
white space, joined by one space, and stretched with PBKDF2-HMAC-SHA256
of the standard library, 600 000 rounds, salted with the chain hash and
the round of the capsule. It is wordkey.ts of datekeys-ts byte for byte:
both check the same vector.
encrypt takes -words or -words-file for a time_and_key capsule, and adds
the key as one more recipient, derived for the round of -at; decrypt
takes them and adds the identity, for the round the capsule shows. The
format does not change. Checked: the CLI opened a capsule that the page
wrote with words, with the release from the public relays.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
7 days ago
if err != nil {
return fmt . Errorf ( "decrypt: %w" , err )
}
opts . Identities = append ( opts . Identities , id )
}
if _ , err := src . Seek ( 0 , io . SeekStart ) ; err != nil {
return err
}
}
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
ctx , cancel := context . WithTimeout ( context . Background ( ) , * timeout )
defer cancel ( )
Format 3, step 5: the CLI
datekeys encrypt writes format 3 and datekeys decrypt writes its files
to a new folder, with the presentation of spec 29.7.
- encrypt: -in is repeatable and takes files and folders; a folder
gives its name as the first segment, as a browser does, and is
walked with Lstat, following no link, taking regular files only.
.DS_Store, Thumbs.db, desktop.ini, ._* and __MACOSX are left out of
folders, and each one left out is reported (62.1 rule 15). New
-comment, -author and -no-mtime; the mtimes are kept by default
(rule 16). A capsule may hold a comment alone. The copy of a pipe to
a temporary file goes, as only regular files are taken.
- decrypt: the prelude decides. Format 3 claims -out with os.Mkdir,
only when there are files, stages the tree in -out/.datekeys-*
through an os.Root with O_EXCL and mode 0600, sets the mtimes, and
moves each entry of the first level into place at step 18; any
failure removes the folder (spec 56). Formats 1 and 2 still write a
file.
- The presentation goes to stdout: the verdicts, the declared author
and the comment box with their labels, the paths, and the verdicts
again. Every line of the creator goes in pieces of at most W - 3
columns behind the prefix, counting 2 for anything but printable
ASCII, with its TABs expanded to multiples of 8; W is the width of
the terminal, asked with syscall on Unix and Windows, or 80. Risky
names get a warning: shortcuts, desktop.ini, .git, programs and a
leading dash, compared by their key of R7.
- Encrypt no longer runs in the CLI: only the test data generators
set TestVectors.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
// The format decides the output: a new folder for the files of format 3,
// a new file for the content of formats 1 and 2. A prelude that does not
// parse goes the second way, and Open reports it at step 1 or 2.
var pre [ capsule . PreludeSize ] byte
n , _ := src . ReadAt ( pre [ : ] , 0 )
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
var opened * capsule . Opened
Review fixes: addresses, safe integers, the note on screen, -expect-author, author keys
CheckURI works on the raw authority, as an HTTP client reads it: no percent
signs, userinfo or backslashes, a host of letters, digits and hyphens or a
public IP literal, a port from 1 to 65535, and Host returns that host. The
integers of the locator stop at 2^53 - 1, and Info.Extension refuses what
ParseInfo would. extension.Standard validates datekeys.capsule through
locator.Standard, and Info.OpenLocator ties the locator to the round of its
own DateKey.
inspect shows the public note as text of the creator, with its prefix and
wrapping and the warning, and says when a note is unusable. encrypt -note
warns that it is public. decrypt -expect-author fails before the release
is requested when the capsule is not format 3. Author key files are read
with the work factor of the spec as their maximum, the passphrase is not
read from a terminal, and two copies of secrets are cleared.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
6 days ago
p , perr := capsule . ParsePrelude ( pre [ : n ] )
if * expect != "" && perr == nil && p . Format != capsule . Format3 {
Review fixes: addresses, safe integers, the note on screen, -expect-author, author keys
CheckURI works on the raw authority, as an HTTP client reads it: no percent
signs, userinfo or backslashes, a host of letters, digits and hyphens or a
public IP literal, a port from 1 to 65535, and Host returns that host. The
integers of the locator stop at 2^53 - 1, and Info.Extension refuses what
ParseInfo would. extension.Standard validates datekeys.capsule through
locator.Standard, and Info.OpenLocator ties the locator to the round of its
own DateKey.
inspect shows the public note as text of the creator, with its prefix and
wrapping and the warning, and says when a note is unusable. encrypt -note
warns that it is public. decrypt -expect-author fails before the release
is requested when the capsule is not format 3. Author key files are read
with the work factor of the spec as their maximum, the passphrase is not
read from a terminal, and two copies of secrets are cleared.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
6 days ago
// Only a capsule of format 3 has an author signature: fail before
// the release is requested and before anything is written. A prelude
// that does not parse is left to Open, which reports it at step 1 or
// 2 with its code.
Review fixes: addresses, safe integers, the note on screen, -expect-author, author keys
CheckURI works on the raw authority, as an HTTP client reads it: no percent
signs, userinfo or backslashes, a host of letters, digits and hyphens or a
public IP literal, a port from 1 to 65535, and Host returns that host. The
integers of the locator stop at 2^53 - 1, and Info.Extension refuses what
ParseInfo would. extension.Standard validates datekeys.capsule through
locator.Standard, and Info.OpenLocator ties the locator to the round of its
own DateKey.
inspect shows the public note as text of the creator, with its prefix and
wrapping and the warning, and says when a note is unusable. encrypt -note
warns that it is public. decrypt -expect-author fails before the release
is requested when the capsule is not format 3. Author key files are read
with the work factor of the spec as their maximum, the passphrase is not
read from a terminal, and two copies of secrets are cleared.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
6 days ago
return errors . New ( "decrypt: -expect-author: only a capsule of format 3 has an author signature, and this is not one" )
}
if perr == nil && p . Format == capsule . Format3 {
Format 3, step 5: the CLI
datekeys encrypt writes format 3 and datekeys decrypt writes its files
to a new folder, with the presentation of spec 29.7.
- encrypt: -in is repeatable and takes files and folders; a folder
gives its name as the first segment, as a browser does, and is
walked with Lstat, following no link, taking regular files only.
.DS_Store, Thumbs.db, desktop.ini, ._* and __MACOSX are left out of
folders, and each one left out is reported (62.1 rule 15). New
-comment, -author and -no-mtime; the mtimes are kept by default
(rule 16). A capsule may hold a comment alone. The copy of a pipe to
a temporary file goes, as only regular files are taken.
- decrypt: the prelude decides. Format 3 claims -out with os.Mkdir,
only when there are files, stages the tree in -out/.datekeys-*
through an os.Root with O_EXCL and mode 0600, sets the mtimes, and
moves each entry of the first level into place at step 18; any
failure removes the folder (spec 56). Formats 1 and 2 still write a
file.
- The presentation goes to stdout: the verdicts, the declared author
and the comment box with their labels, the paths, and the verdicts
again. Every line of the creator goes in pieces of at most W - 3
columns behind the prefix, counting 2 for anything but printable
ASCII, with its TABs expanded to multiples of 8; W is the width of
the terminal, asked with syscall on Unix and Windows, or 80. Risky
names get a warning: shortcuts, desktop.ini, .git, programs and a
leading dash, compared by their key of R7.
- Encrypt no longer runs in the CLI: only the test data generators
set TestVectors.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
if err := checkNew ( * out ) ; err != nil {
return err
}
// The folder is created at step 17, after the release is requested:
// its parent must be a folder before then.
if info , err := os . Stat ( filepath . Dir ( * out ) ) ; err != nil || ! info . IsDir ( ) {
return fmt . Errorf ( "decrypt: %s cannot be created: %s is not a folder" , * out , filepath . Dir ( * out ) )
}
Format 3, step 5: the CLI
datekeys encrypt writes format 3 and datekeys decrypt writes its files
to a new folder, with the presentation of spec 29.7.
- encrypt: -in is repeatable and takes files and folders; a folder
gives its name as the first segment, as a browser does, and is
walked with Lstat, following no link, taking regular files only.
.DS_Store, Thumbs.db, desktop.ini, ._* and __MACOSX are left out of
folders, and each one left out is reported (62.1 rule 15). New
-comment, -author and -no-mtime; the mtimes are kept by default
(rule 16). A capsule may hold a comment alone. The copy of a pipe to
a temporary file goes, as only regular files are taken.
- decrypt: the prelude decides. Format 3 claims -out with os.Mkdir,
only when there are files, stages the tree in -out/.datekeys-*
through an os.Root with O_EXCL and mode 0600, sets the mtimes, and
moves each entry of the first level into place at step 18; any
failure removes the folder (spec 56). Formats 1 and 2 still write a
file.
- The presentation goes to stdout: the verdicts, the declared author
and the comment box with their labels, the paths, and the verdicts
again. Every line of the creator goes in pieces of at most W - 3
columns behind the prefix, counting 2 for anything but printable
ASCII, with its TABs expanded to multiples of 8; W is the width of
the terminal, asked with syscall on Unix and Windows, or 80. Risky
names get a warning: shortcuts, desktop.ini, .git, programs and a
leading dash, compared by their key of R7.
- Encrypt no longer runs in the CLI: only the test data generators
set TestVectors.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
opts . Sink = & dirSink { dir : * out }
if opened , err = capsule . Open ( ctx , nil , src , opts ) ; err != nil {
return err
}
} else {
err = writeAtomic ( * out , func ( w io . Writer ) error {
opened , err = capsule . Open ( ctx , w , src , opts )
return err
} )
if err != nil {
return err
}
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
}
Format 3, step 5: the CLI
datekeys encrypt writes format 3 and datekeys decrypt writes its files
to a new folder, with the presentation of spec 29.7.
- encrypt: -in is repeatable and takes files and folders; a folder
gives its name as the first segment, as a browser does, and is
walked with Lstat, following no link, taking regular files only.
.DS_Store, Thumbs.db, desktop.ini, ._* and __MACOSX are left out of
folders, and each one left out is reported (62.1 rule 15). New
-comment, -author and -no-mtime; the mtimes are kept by default
(rule 16). A capsule may hold a comment alone. The copy of a pipe to
a temporary file goes, as only regular files are taken.
- decrypt: the prelude decides. Format 3 claims -out with os.Mkdir,
only when there are files, stages the tree in -out/.datekeys-*
through an os.Root with O_EXCL and mode 0600, sets the mtimes, and
moves each entry of the first level into place at step 18; any
failure removes the folder (spec 56). Formats 1 and 2 still write a
file.
- The presentation goes to stdout: the verdicts, the declared author
and the comment box with their labels, the paths, and the verdicts
again. Every line of the creator goes in pieces of at most W - 3
columns behind the prefix, counting 2 for anything but printable
ASCII, with its TABs expanded to multiples of 8; W is the width of
the terminal, asked with syscall on Unix and Windows, or 80. Risky
names get a warning: shortcuts, desktop.ini, .git, programs and a
leading dash, compared by their key of R7.
- Encrypt no longer runs in the CLI: only the test data generators
set TestVectors.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
fmt . Fprintf ( stderr , "Decrypted capsule %s (round %d, unlocked at %s); release verified locally\n" ,
opened . Inspection . Header . CapsuleIDHex ( ) , opened . Release . Round , opened . Inspection . UnlockAt . Format ( time . RFC3339 ) )
What the official SDK says when it seals: the recovery annex, §7.6 and §71
The SHOULDs of the official SDK that the CLI did not follow yet. encrypt
writes next to the .dkc the recovery annex, FILE.dkc.recuperacion.txt
(spec §62.1, rule 27): datekeys.RecoveryAnnex, annex/recovery.md, which is
§79 of the specification under a title with its version and SHA-256, the
same for every capsule; TestRecoveryAnnex checks it against the text of
SpecVersion. -no-recovery leaves it out. encrypt also says what opening the
capsule years later will take (rule 26): the .dkc, a credential of a
time_and_key capsule, and the release of its round, which an archive of
releases or a cache service must keep if drand no longer serves it; and
beyond one year it recommends time_and_key to a time_only capsule (§7.6).
profile.Status and StatusOf give the state of a pinned profile in the
registry of §71, which DateKeys does not publish yet: Quicknet is active.
encrypt writes no capsule with a profile that is not active, and decrypt
and inspect warn when the profile of a capsule is compromised.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
8 hours ago
if profileStatus ( opened . Inspection . Profile ) == profile . Compromised {
fmt . Fprintf ( stderr , "warning: the profile %s is compromised: the content of this capsule may have been read before its date (spec §71)\n" ,
opened . Inspection . Profile . ID )
}
Release object, release in hand and step 9.c option B (spec v0.15 draft)
The release of a round becomes a file, .dkr: a release object in
deterministic CBOR, {0: "datekeys-release", 1: 1, 2: chain_hash, 3: round,
4: signature}, which provider.EncodeRelease writes and DecodeRelease reads
with its layers (size, type and version, schema). provider.ParseRelease
also reads drand's JSON as the input of the caller. Verify checks the chain
hash a release names before its round and its signature, with
ERR_PROFILE_MISMATCH. provider.Archive reads a local release archive, the
informative format of the draft.
capsule.OpenOptions.Release takes a release in hand, a provider.Supplier,
exclusive with Source: Open does not compare it with the clock (step 9.c,
option B) and reports a clock behind it in Opened.ClockBehind; a network
source is still never asked before the round time. The CLI gains
decrypt -release FILE (.dkr, drand's JSON or a local archive),
decrypt -save-release FILE.dkr and the command release, which fetches,
verifies and saves the .dkr without opening the capsule.
Test data: vectors/release.json, releases/<round>.dkr for rounds 1000,
1001, 1004 and 2000, and a local archive of rounds 1000 to 1004. In
mutations.json every case says its source, "supplied" or "network"; the
case "round not reached yet", a release in hand, now opens, and four cases
are added: the same with a network source, a release of another round from
a network source, and two release objects of another chain. SpecVersion
stays 0.14 until the author approves the draft.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 day ago
if opened . ClockBehind {
fmt . Fprintf ( stderr , "warning: the release proves that round %d was published at %s, and this clock says %s: it may be behind\n" ,
opened . Release . Round , opened . Inspection . UnlockAt . Format ( time . RFC3339 ) , now ( ) . UTC ( ) . Format ( time . RFC3339 ) )
}
Format 3, step 5: the CLI
datekeys encrypt writes format 3 and datekeys decrypt writes its files
to a new folder, with the presentation of spec 29.7.
- encrypt: -in is repeatable and takes files and folders; a folder
gives its name as the first segment, as a browser does, and is
walked with Lstat, following no link, taking regular files only.
.DS_Store, Thumbs.db, desktop.ini, ._* and __MACOSX are left out of
folders, and each one left out is reported (62.1 rule 15). New
-comment, -author and -no-mtime; the mtimes are kept by default
(rule 16). A capsule may hold a comment alone. The copy of a pipe to
a temporary file goes, as only regular files are taken.
- decrypt: the prelude decides. Format 3 claims -out with os.Mkdir,
only when there are files, stages the tree in -out/.datekeys-*
through an os.Root with O_EXCL and mode 0600, sets the mtimes, and
moves each entry of the first level into place at step 18; any
failure removes the folder (spec 56). Formats 1 and 2 still write a
file.
- The presentation goes to stdout: the verdicts, the declared author
and the comment box with their labels, the paths, and the verdicts
again. Every line of the creator goes in pieces of at most W - 3
columns behind the prefix, counting 2 for anything but printable
ASCII, with its TABs expanded to multiples of 8; W is the width of
the terminal, asked with syscall on Unix and Windows, or 80. Risky
names get a warning: shortcuts, desktop.ini, .git, programs and a
leading dash, compared by their key of R7.
- Encrypt no longer runs in the CLI: only the test data generators
set TestVectors.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
if opened . Format == capsule . Format3 {
fmt . Fprintf ( stderr , " format 3, %d files\n" , len ( opened . Head . Files ) )
if len ( opened . Head . Files ) == 0 {
fmt . Fprintf ( stderr , " no files: %s was not created\n" , * out )
}
present ( stdout , opened , * out , outputWidth ( stdout ) )
return nil
}
fmt . Fprintf ( stderr , " format %d, %d bytes of content\n" , opened . Format , opened . PayloadLength )
if * expect != "" {
return fmt . Errorf ( "decrypt: -expect-author: a capsule of format %d has no author signature" , opened . Format )
}
Implement capsule format 2 of spec v0.9
The reference moves to the DateKeys Protocol Specification v0.9, approved
by its author on 29 September 2026. Encrypt writes capsule format 2 only;
Open and Inspect read formats 1 and 2, and a format 1 capsule keeps the
verdict v0.8.2 gave it.
Format 2 (spec §22, §29.1, §31, §39):
- VERSION in the PRELUDE is the capsule format, capsule.Format; any other
value is ERR_UNSUPPORTED_VERSION at step 2.
- CONTROL_CBOR has the schema version of its format. Version 2 adds key 6,
payload_length (8 bytes, big-endian, at most L_MAX = 2^53 - 2^46), and
key 7, padding (1 bloque256, 2 reforzado); it is 103 bytes without
extensions, whatever L.
- The payload is the content padded with zeros to P = rule(L). Step 17
checks the length and the zeros, and Open writes only the first L bytes.
- INNER_ACCESS_AGE holds exactly 16 X25519 stanzas: 1 to 16 credentials,
and a dummy in each slot left, in a uniformly random order.
Writer rules (spec §62.1): EncryptOptions.Length is required and the
source must deliver exactly that many bytes; recipients that are not
canonical or of low order are rejected (agewrap.CheckX25519Recipient);
self-checks of the header, the control, INNER_ACCESS_AGE and PAYLOAD_AGE.
The CLI measures its input, takes -padding and reports the format.
Test data: seven format 2 fixtures, padding vectors checked against
math/big, format 2 CBOR vectors, and the mutation corpus in both formats
with the 22 cases of the third list of spec §64, built without randomness
by sealing the fixtures again with their known keys and nonces. The
format 1 fixtures are kept byte for byte and never regenerated; the
differential corpus keeps its 1825 cases and adds a block per format 2
fixture. The spec copy loses its "to be implemented" markers, and the
READMEs, CHANGELOG, traceability and testdata/README.md follow v0.9.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
if opened . Format == capsule . Format1 {
// Spec §55.2, §70: format 1 hides neither the number of credentials
// nor the exact length of the content.
fmt . Fprintln ( stderr , " format 1 does not hide the number of credentials or the exact length of the content" )
}
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
return nil
}
Release object, release in hand and step 9.c option B (spec v0.15 draft)
The release of a round becomes a file, .dkr: a release object in
deterministic CBOR, {0: "datekeys-release", 1: 1, 2: chain_hash, 3: round,
4: signature}, which provider.EncodeRelease writes and DecodeRelease reads
with its layers (size, type and version, schema). provider.ParseRelease
also reads drand's JSON as the input of the caller. Verify checks the chain
hash a release names before its round and its signature, with
ERR_PROFILE_MISMATCH. provider.Archive reads a local release archive, the
informative format of the draft.
capsule.OpenOptions.Release takes a release in hand, a provider.Supplier,
exclusive with Source: Open does not compare it with the clock (step 9.c,
option B) and reports a clock behind it in Opened.ClockBehind; a network
source is still never asked before the round time. The CLI gains
decrypt -release FILE (.dkr, drand's JSON or a local archive),
decrypt -save-release FILE.dkr and the command release, which fetches,
verifies and saves the .dkr without opening the capsule.
Test data: vectors/release.json, releases/<round>.dkr for rounds 1000,
1001, 1004 and 2000, and a local archive of rounds 1000 to 1004. In
mutations.json every case says its source, "supplied" or "network"; the
case "round not reached yet", a release in hand, now opens, and four cases
are added: the same with a network source, a release of another round from
a network source, and two release objects of another chain. SpecVersion
stays 0.14 until the author approves the draft.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 day ago
// releaseInHand reads the release that the person has in hand, from path: a
// local release archive, which is read when Open asks for the round, or a
Spec v0.15 draft: remove the .dkr file
The author's decision of 7 October 2026. A release saved next to a capsule
cannot exist when the capsule is made, and once the date comes the capsule
can be opened: such a file only opens it again and does not cover the real
case, someone opening it decades later when drand is gone and nobody saved
anything. Long-term recovery rests instead on archives and cache services
that keep the releases of all rounds; a reader asks for its round and
verifies the signature against the pinned key.
Spec: the .dkr extension (section 20, back to v0.14), sections 1, 4, 8, 45,
47.1, 49, 50 (rewritten), 53, 62.1 (rule 28 removed, rule 26 reworded),
63, 70, 73, 74 (the datekeys.release .dkk extension dropped too), 76
(the v0.15 block, with the discarded design) and the annex 79. The
release object, the chain hash at step 10, step 9.c option B and the
archive format stay.
Code: decrypt -save-release and the command datekeys release are gone,
with writeRelease and their tests; decrypt -release FILE stays. The
release objects of testdata/releases are now <round>.cbor, and
TestVectorFilesAreCurrent fails on a file the generator no longer writes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
23 hours ago
// release object or drand's JSON, which Open decodes at step 10. A file larger
Release object, release in hand and step 9.c option B (spec v0.15 draft)
The release of a round becomes a file, .dkr: a release object in
deterministic CBOR, {0: "datekeys-release", 1: 1, 2: chain_hash, 3: round,
4: signature}, which provider.EncodeRelease writes and DecodeRelease reads
with its layers (size, type and version, schema). provider.ParseRelease
also reads drand's JSON as the input of the caller. Verify checks the chain
hash a release names before its round and its signature, with
ERR_PROFILE_MISMATCH. provider.Archive reads a local release archive, the
informative format of the draft.
capsule.OpenOptions.Release takes a release in hand, a provider.Supplier,
exclusive with Source: Open does not compare it with the clock (step 9.c,
option B) and reports a clock behind it in Opened.ClockBehind; a network
source is still never asked before the round time. The CLI gains
decrypt -release FILE (.dkr, drand's JSON or a local archive),
decrypt -save-release FILE.dkr and the command release, which fetches,
verifies and saves the .dkr without opening the capsule.
Test data: vectors/release.json, releases/<round>.dkr for rounds 1000,
1001, 1004 and 2000, and a local archive of rounds 1000 to 1004. In
mutations.json every case says its source, "supplied" or "network"; the
case "round not reached yet", a release in hand, now opens, and four cases
are added: the same with a network source, a release of another round from
a network source, and two release objects of another chain. SpecVersion
stays 0.14 until the author approves the draft.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 day ago
// than any of them is cut where it can no longer be valid, so that step 10
// rejects it with the code of its size.
func releaseInHand ( path string ) ( provider . Supplier , io . Closer , error ) {
f , err := os . Open ( path )
if err != nil {
return nil , nil , err
}
info , err := f . Stat ( )
if err != nil {
f . Close ( )
return nil , nil , err
}
head := make ( [ ] byte , min ( info . Size ( ) , 64 ) )
if _ , err := io . ReadFull ( f , head ) ; err != nil {
f . Close ( )
return nil , nil , err
}
if provider . IsArchive ( head ) {
return provider . NewArchive ( f , info . Size ( ) ) , f , nil
}
defer f . Close ( )
rest , err := io . ReadAll ( io . LimitReader ( f , provider . MaxReleaseJSONSize + 1 - int64 ( len ( head ) ) ) )
if err != nil {
return nil , nil , err
}
return provider . Encoded ( append ( head , rest ... ) ) , io . NopCloser ( nil ) , nil
}
A key of words for the CLI: encrypt and decrypt with -words
The author asked for keys that people can keep without files. Package
wordkey derives the X25519 identity of words a person chooses, at least
six: their NFD by the tables of pathrule without the marks U+0300 to
U+036F, each code point in lower case by its simple mapping, split at
white space, joined by one space, and stretched with PBKDF2-HMAC-SHA256
of the standard library, 600 000 rounds, salted with the chain hash and
the round of the capsule. It is wordkey.ts of datekeys-ts byte for byte:
both check the same vector.
encrypt takes -words or -words-file for a time_and_key capsule, and adds
the key as one more recipient, derived for the round of -at; decrypt
takes them and adds the identity, for the round the capsule shows. The
format does not change. Checked: the CLI opened a capsule that the page
wrote with words, with the release from the public relays.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
7 days ago
// wordsText is the text of the words of -words or of -words-file, at most
// 4 KiB, or "" when neither is given.
func wordsText ( cmd , words , file string ) ( string , error ) {
Dice: the words of five dice each, encrypt -dice and datekeys wordlist
For whoever does not trust the random numbers of a computer, as the author
decided: five dice for each word, read in a fixed order, give a number
from 11111 to 66666, the position of the word in a list of 7776 words, the
first die the most significant. wordkey.DiceNumber and DiceWord number the
words and give the word of a number; DiceWords takes several numbers, at
least 6 and never the same word twice, which is rolled again; DiceList is
the list numbered for dice, a line for each word with its dice and a tab,
as the EFF publishes its own: for en it is the file of the EFF, byte for
byte.
encrypt -dice TEXT and -dice-file FILE take the words from the dice of the
list -dic and show them: the words open the capsule, the numbers give them
only with that list. datekeys wordlist [-dic LIST] writes the numbered
list, to print it, and its SHA-256, which wordkey/lists/README.md records.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
10 hours ago
return flagText ( cmd , "words" , words , file )
}
// flagText is the text of -NAME or of the file of -NAME-file, at most 4 KiB,
// or "" when neither is given.
func flagText ( cmd , name , text , file string ) ( string , error ) {
if text != "" && file != "" {
return "" , fmt . Errorf ( "%s: -%s and -%s-file are exclusive" , cmd , name , name )
A key of words for the CLI: encrypt and decrypt with -words
The author asked for keys that people can keep without files. Package
wordkey derives the X25519 identity of words a person chooses, at least
six: their NFD by the tables of pathrule without the marks U+0300 to
U+036F, each code point in lower case by its simple mapping, split at
white space, joined by one space, and stretched with PBKDF2-HMAC-SHA256
of the standard library, 600 000 rounds, salted with the chain hash and
the round of the capsule. It is wordkey.ts of datekeys-ts byte for byte:
both check the same vector.
encrypt takes -words or -words-file for a time_and_key capsule, and adds
the key as one more recipient, derived for the round of -at; decrypt
takes them and adds the identity, for the round the capsule shows. The
format does not change. Checked: the CLI opened a capsule that the page
wrote with words, with the release from the public relays.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
7 days ago
}
if file == "" {
Dice: the words of five dice each, encrypt -dice and datekeys wordlist
For whoever does not trust the random numbers of a computer, as the author
decided: five dice for each word, read in a fixed order, give a number
from 11111 to 66666, the position of the word in a list of 7776 words, the
first die the most significant. wordkey.DiceNumber and DiceWord number the
words and give the word of a number; DiceWords takes several numbers, at
least 6 and never the same word twice, which is rolled again; DiceList is
the list numbered for dice, a line for each word with its dice and a tab,
as the EFF publishes its own: for en it is the file of the EFF, byte for
byte.
encrypt -dice TEXT and -dice-file FILE take the words from the dice of the
list -dic and show them: the words open the capsule, the numbers give them
only with that list. datekeys wordlist [-dic LIST] writes the numbered
list, to print it, and its SHA-256, which wordkey/lists/README.md records.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
10 hours ago
return text , nil
A key of words for the CLI: encrypt and decrypt with -words
The author asked for keys that people can keep without files. Package
wordkey derives the X25519 identity of words a person chooses, at least
six: their NFD by the tables of pathrule without the marks U+0300 to
U+036F, each code point in lower case by its simple mapping, split at
white space, joined by one space, and stretched with PBKDF2-HMAC-SHA256
of the standard library, 600 000 rounds, salted with the chain hash and
the round of the capsule. It is wordkey.ts of datekeys-ts byte for byte:
both check the same vector.
encrypt takes -words or -words-file for a time_and_key capsule, and adds
the key as one more recipient, derived for the round of -at; decrypt
takes them and adds the identity, for the round the capsule shows. The
format does not change. Checked: the CLI opened a capsule that the page
wrote with words, with the release from the public relays.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
7 days ago
}
f , err := os . Open ( file )
if err != nil {
return "" , err
}
defer f . Close ( )
b , err := io . ReadAll ( io . LimitReader ( f , 4 << 10 + 1 ) )
if err != nil {
return "" , err
}
if len ( b ) > 4 << 10 {
Dice: the words of five dice each, encrypt -dice and datekeys wordlist
For whoever does not trust the random numbers of a computer, as the author
decided: five dice for each word, read in a fixed order, give a number
from 11111 to 66666, the position of the word in a list of 7776 words, the
first die the most significant. wordkey.DiceNumber and DiceWord number the
words and give the word of a number; DiceWords takes several numbers, at
least 6 and never the same word twice, which is rolled again; DiceList is
the list numbered for dice, a line for each word with its dice and a tab,
as the EFF publishes its own: for en it is the file of the EFF, byte for
byte.
encrypt -dice TEXT and -dice-file FILE take the words from the dice of the
list -dic and show them: the words open the capsule, the numbers give them
only with that list. datekeys wordlist [-dic LIST] writes the numbered
list, to print it, and its SHA-256, which wordkey/lists/README.md records.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
10 hours ago
return "" , fmt . Errorf ( "%s: %s is longer than 4 KiB: it is not a list of %s" , cmd , file , name )
A key of words for the CLI: encrypt and decrypt with -words
The author asked for keys that people can keep without files. Package
wordkey derives the X25519 identity of words a person chooses, at least
six: their NFD by the tables of pathrule without the marks U+0300 to
U+036F, each code point in lower case by its simple mapping, split at
white space, joined by one space, and stretched with PBKDF2-HMAC-SHA256
of the standard library, 600 000 rounds, salted with the chain hash and
the round of the capsule. It is wordkey.ts of datekeys-ts byte for byte:
both check the same vector.
encrypt takes -words or -words-file for a time_and_key capsule, and adds
the key as one more recipient, derived for the round of -at; decrypt
takes them and adds the identity, for the round the capsule shows. The
format does not change. Checked: the CLI opened a capsule that the page
wrote with words, with the release from the public relays.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
7 days ago
}
return string ( b ) , nil
}
Dice: the words of five dice each, encrypt -dice and datekeys wordlist
For whoever does not trust the random numbers of a computer, as the author
decided: five dice for each word, read in a fixed order, give a number
from 11111 to 66666, the position of the word in a list of 7776 words, the
first die the most significant. wordkey.DiceNumber and DiceWord number the
words and give the word of a number; DiceWords takes several numbers, at
least 6 and never the same word twice, which is rolled again; DiceList is
the list numbered for dice, a line for each word with its dice and a tab,
as the EFF publishes its own: for en it is the file of the EFF, byte for
byte.
encrypt -dice TEXT and -dice-file FILE take the words from the dice of the
list -dic and show them: the words open the capsule, the numbers give them
only with that list. datekeys wordlist [-dic LIST] writes the numbered
list, to print it, and its SHA-256, which wordkey/lists/README.md records.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
10 hours ago
// wordList writes the list -dic numbered for dice to stdout, as the EFF
// publishes its list, to print it, and its SHA-256 to stderr, to compare it
// with the one that wordkey/lists/README.md records.
func wordList ( args [ ] string , stdout , stderr io . Writer ) error {
fs := newFlags ( "wordlist" )
dic := fs . String ( "dic" , "en" , "list: " + strings . Join ( wordkey . Languages ( ) , ", " ) )
if err := parse ( fs , args ) ; err != nil {
return err
}
list , err := wordkey . List ( * dic )
if err != nil {
return fmt . Errorf ( "wordlist: %w" , err )
}
text , err := wordkey . DiceList ( list )
if err != nil {
return fmt . Errorf ( "wordlist: %w" , err )
}
if _ , err := io . WriteString ( stdout , text ) ; err != nil {
return err
}
fmt . Fprintf ( stderr , "the list %s numbered for dice, %d words, SHA-256 %x\n" , * dic , len ( list ) , sha256 . Sum256 ( [ ] byte ( text ) ) )
return nil
}
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
func readIdentities ( path string ) ( [ ] age . Identity , error ) {
f , err := os . Open ( path )
if err != nil {
return nil , err
}
defer f . Close ( )
ids , err := age . ParseIdentities ( f )
if err != nil {
return nil , fmt . Errorf ( "%s: %w" , path , err )
}
return ids , nil
}
// inspect runs steps 1 to 8 only: it never requests a release and never uses
// a secret.
func inspect ( args [ ] string , stdout io . Writer ) error {
fs := newFlags ( "inspect" )
in := fs . String ( "in" , "" , ".dkc file" )
asJSON := fs . Bool ( "json" , false , "JSON output" )
if err := parse ( fs , args ) ; err != nil {
return err
}
if * in == "" {
return errors . New ( "inspect: -in is required" )
}
reg , err := profile . Default ( )
if err != nil {
return err
}
f , err := os . Open ( * in )
if err != nil {
return err
}
defer f . Close ( )
result , inspectErr := capsule . Inspect ( f , capsule . InspectOptions { Registry : reg } )
v := inspectview . New ( * in , result , inspectErr )
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
if * asJSON {
if err := v . WriteJSON ( stdout ) ; err != nil {
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
return err
}
} else {
v . WriteText ( stdout )
Review fixes: addresses, safe integers, the note on screen, -expect-author, author keys
CheckURI works on the raw authority, as an HTTP client reads it: no percent
signs, userinfo or backslashes, a host of letters, digits and hyphens or a
public IP literal, a port from 1 to 65535, and Host returns that host. The
integers of the locator stop at 2^53 - 1, and Info.Extension refuses what
ParseInfo would. extension.Standard validates datekeys.capsule through
locator.Standard, and Info.OpenLocator ties the locator to the round of its
own DateKey.
inspect shows the public note as text of the creator, with its prefix and
wrapping and the warning, and says when a note is unusable. encrypt -note
warns that it is public. decrypt -expect-author fails before the release
is requested when the capsule is not format 3. Author key files are read
with the work factor of the spec as their maximum, the passphrase is not
read from a terminal, and two copies of secrets are cleared.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
6 days ago
showNote ( stdout , result )
What the official SDK says when it seals: the recovery annex, §7.6 and §71
The SHOULDs of the official SDK that the CLI did not follow yet. encrypt
writes next to the .dkc the recovery annex, FILE.dkc.recuperacion.txt
(spec §62.1, rule 27): datekeys.RecoveryAnnex, annex/recovery.md, which is
§79 of the specification under a title with its version and SHA-256, the
same for every capsule; TestRecoveryAnnex checks it against the text of
SpecVersion. -no-recovery leaves it out. encrypt also says what opening the
capsule years later will take (rule 26): the .dkc, a credential of a
time_and_key capsule, and the release of its round, which an archive of
releases or a cache service must keep if drand no longer serves it; and
beyond one year it recommends time_and_key to a time_only capsule (§7.6).
profile.Status and StatusOf give the state of a pinned profile in the
registry of §71, which DateKeys does not publish yet: Quicknet is active.
encrypt writes no capsule with a profile that is not active, and decrypt
and inspect warn when the profile of a capsule is compromised.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
8 hours ago
if result != nil && result . Profile != nil && profileStatus ( result . Profile ) == profile . Compromised {
fmt . Fprintf ( stdout , "warning: the profile %s is compromised: the content of this capsule may have been read before its date (spec §71)\n" ,
result . Profile . ID )
}
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
}
return inspectErr
}
type resolveView struct {
DateKey string ` json:"datekey" `
Profile string ` json:"profile" `
Round uint64 ` json:"round" `
Requested string ` json:"requested" `
UnlockAt string ` json:"unlock_at" `
}
func resolve ( args [ ] string , stdout io . Writer ) error {
fs := newFlags ( "datekey resolve" )
at := fs . String ( "at" , "" , "instant, RFC 3339" )
if err := parse ( fs , args ) ; err != nil {
return err
}
t , err := parseTime ( * at )
if err != nil {
return err
}
p := profile . Quicknet ( )
d , err := datekey . Resolve ( p , t )
if err != nil {
return err
}
return json . NewEncoder ( stdout ) . Encode ( resolveView {
DateKey : d . Compact ( ) , Profile : d . ProfileID , Round : d . Round ,
Requested : t . Format ( time . RFC3339Nano ) , UnlockAt : d . UnlockAt ( p ) . Format ( time . RFC3339 ) ,
} )
}
func profileHash ( args [ ] string , stdout io . Writer ) error {
fs := newFlags ( "profile hash" )
in := fs . String ( "in" , "" , "Deterministic CBOR profile file; default: the pinned Quicknet profile" )
if err := parse ( fs , args ) ; err != nil {
return err
}
p := profile . Quicknet ( )
if * in != "" {
b , err := os . ReadFile ( * in )
if err != nil {
return err
}
if p , err = profile . Decode ( b ) ; err != nil {
return err
}
}
b , err := p . CanonicalCBOR ( )
if err != nil {
return err
}
h , err := p . Hash ( )
if err != nil {
return err
}
pinned := * in == "" || hex . EncodeToString ( h [ : ] ) == profile . QuicknetProfileHash
return json . NewEncoder ( stdout ) . Encode ( map [ string ] any {
"profile_id" : p . ID ,
"profile_hash" : hex . EncodeToString ( h [ : ] ) ,
"canonical_cbor" : hex . EncodeToString ( b ) ,
"pinned" : pinned ,
} )
}