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 datekey_test
import (
"encoding/base64"
"errors"
"math/rand/v2"
"reflect"
"testing"
"time"
datekeys "g.activething.com/go/DateKeys"
"g.activething.com/go/DateKeys/datekey"
"g.activething.com/go/DateKeys/internal/testkit"
"g.activething.com/go/DateKeys/profile"
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 TestNormativeRoundVector ( t * testing . T ) {
// Spec §16, stated literally.
p := profile . Quicknet ( )
d , err := datekey . Resolve ( p , time . Date ( 2030 , 1 , 1 , 0 , 0 , 0 , 0 , time . UTC ) )
if err != nil || d . Round != 66884212 {
t . Fatalf ( "2030-01-01 resolved to %+v, %v" , d , err )
}
if got := d . UnlockAt ( p ) . Format ( time . RFC3339 ) ; got != "2030-01-01T00:00:00Z" {
t . Fatalf ( "round time %s" , got )
}
rt , err := datekey . RoundTime ( p , 66432123 )
if err != nil || rt . Format ( time . RFC3339 ) != "2029-12-16T07:15:33Z" {
t . Fatalf ( "round 66432123 at %s, %v" , rt , err )
}
if got := d . Compact ( ) ; got != "dk1_eyJ2ZXJzaW9uIjoxLCJuZXR3b3JrIjoiZGF0ZWtleXM6cXVpY2tuZXQ6djEiLCJyb3VuZCI6NjY4ODQyMTJ9" {
t . Fatalf ( "dk1_ %s" , got )
}
if got := string ( d . CanonicalJSON ( ) ) ; got != ` { "version":1,"network":"datekeys:quicknet:v1","round":66884212} ` {
t . Fatalf ( "canonical JSON %s" , got )
}
}
func TestGoldenRoundVectors ( t * testing . T ) {
var golden testkit . RoundVectorFile
if err := testkit . ReadJSON ( "../testdata/vectors/quicknet_rounds.json" , & golden ) ; err != nil {
t . Fatal ( err )
}
if got := testkit . RoundVectors ( ) ; ! reflect . DeepEqual ( got , golden ) {
t . Fatalf ( "round vectors changed:\n got %+v\nwant %+v" , got , golden )
}
p := profile . Quicknet ( )
for _ , v := range golden . Vectors {
at , err := time . Parse ( time . RFC3339Nano , v . Requested )
if err != nil {
t . Fatal ( err )
}
d , err := datekey . Resolve ( p , at )
if v . Error != "" {
if datekeys . Code ( err ) != v . Error {
t . Errorf ( "%s: got %v, want %s" , v . Name , err , v . Error )
}
continue
}
if err != nil || d . Round != v . Round || d . UnlockAt ( p ) . Format ( time . RFC3339Nano ) != v . Effective {
t . Errorf ( "%s: got %+v %v" , v . Name , d , err )
}
}
}
func TestGoldenDK1Vectors ( t * testing . T ) {
var golden testkit . DK1VectorFile
if err := testkit . ReadJSON ( "../testdata/vectors/dk1.json" , & golden ) ; err != nil {
t . Fatal ( err )
}
if got := testkit . DK1Vectors ( ) ; ! reflect . DeepEqual ( got , golden ) {
t . Fatalf ( "dk1_ vectors changed" )
}
for _ , v := range golden . Vectors {
if v . DK1 != "" {
d , err := datekey . Parse ( v . DK1 )
if err != nil || d . ProfileID != v . Network || d . Round != v . Round {
t . Errorf ( "%s: %+v %v" , v . Name , d , err )
}
if string ( d . CanonicalJSON ( ) ) != v . CanonicalJSON || base64 . RawURLEncoding . EncodeToString ( d . CanonicalJSON ( ) ) != v . Base64URL {
t . Errorf ( "%s: intermediate encodings differ" , v . Name )
}
continue
}
if v . Error == "accepted" {
t . Errorf ( "%s: a rejected-encoding vector is accepted" , v . Name )
}
if _ , err := datekey . Parse ( v . Input ) ; datekeys . Code ( err ) != v . Error {
t . Errorf ( "%s: got %v, want %s" , v . Name , err , v . Error )
}
}
}
// Kept from the prototype: resolution never picks a round that opens early.
func TestRoundNeverOpensEarly ( t * testing . T ) {
p := profile . Quicknet ( )
g := time . Unix ( p . GenesisTime , 0 ) . UTC ( )
for _ , tc := range [ ] struct {
offset time . Duration
want uint64
} {
{ 0 , 1 } , { time . Nanosecond , 2 } , { time . Second , 2 } , { 3 * time . Second , 2 } , { 3 * time . Second + time . Nanosecond , 3 } , { 2997 * time . Second , 1000 } ,
} {
requested := g . Add ( tc . offset )
d , err := datekey . Resolve ( p , requested )
if err != nil || d . Round != tc . want {
t . Fatalf ( "offset %s: %+v, %v" , tc . offset , d , err )
}
if u := d . UnlockAt ( p ) ; u . Before ( requested ) || u . Sub ( requested ) >= p . Period {
t . Fatal ( "unsafe rounding" )
}
}
}
func TestResolveProperty ( t * testing . T ) {
p := profile . Quicknet ( )
r := rand . New ( rand . NewPCG ( 3 , 4 ) )
for range 20000 {
secs := p . GenesisTime + r . Int64N ( profile . MaxUnixTime - p . GenesisTime - 3 )
at := time . Unix ( secs , r . Int64N ( int64 ( time . Second ) ) )
d , err := datekey . Resolve ( p , at )
if err != nil {
t . Fatalf ( "%s: %v" , at , err )
}
u := d . UnlockAt ( p )
// The first round whose time is >= at: never earlier, and the previous
// round is strictly earlier.
if u . Before ( at ) {
t . Fatalf ( "%s resolves to round %d at %s, before the request" , at , d . Round , u )
}
if d . Round > 1 {
prev , _ := datekey . RoundTime ( p , d . Round - 1 )
if ! prev . Before ( at ) {
t . Fatalf ( "%s: round %d at %s would already satisfy the request" , at , d . Round - 1 , prev )
}
}
parsed , err := datekey . Parse ( d . Compact ( ) )
if err != nil || parsed != d {
t . Fatalf ( "Parse(Compact(d)) != d for %+v: %v" , d , err )
}
}
}
func TestTimezoneIndependence ( t * testing . T ) {
p := profile . Quicknet ( )
a , _ := time . Parse ( time . RFC3339Nano , "2026-10-22T19:00:00.001+02:00" )
b , _ := time . Parse ( time . RFC3339Nano , "2026-10-22T17:00:00.001Z" )
da , _ := datekey . Resolve ( p , a )
db , _ := datekey . Resolve ( p , b )
if da != db {
t . Fatal ( "timezone changed the DateKey" )
}
}
func TestValidate ( t * testing . T ) {
p := profile . Quicknet ( )
if err := ( datekey . DateKey { ProfileID : "datekeys:evmnet:v1" , Round : 5 } ) . Validate ( p ) ; ! errors . Is ( err , datekeys . ErrProfileMismatch ) {
t . Fatalf ( "other profile: %v" , err )
}
for _ , r := range [ ] uint64 { 0 , p . MaxRound ( ) + 1 } {
if err := ( datekey . DateKey { ProfileID : p . ID , Round : r } ) . Validate ( p ) ; ! errors . Is ( err , datekeys . ErrDateKeyInvalid ) {
t . Fatalf ( "round %d: %v" , r , err )
}
}
if ! ( datekey . DateKey { ProfileID : p . ID , Round : 0 } ) . UnlockAt ( p ) . IsZero ( ) {
t . Fatal ( "invalid DateKey has an unlock time" )
}
if ( datekey . DateKey { ProfileID : ` bad"id ` , Round : 1 } ) . Compact ( ) != "" {
t . Fatal ( "invalid DateKey has a dk1_ form" )
}
if _ , err := datekey . Resolve ( p , time . Time { } ) ; ! errors . Is ( err , datekeys . ErrDateKeyInvalid ) {
t . Fatalf ( "zero time: %v" , err )
}
}
func TestNumberSpellings ( t * testing . T ) {
enc := func ( round string ) string {
return datekey . Prefix + base64 . RawURLEncoding . EncodeToString ( [ ] byte ( ` { "version":1,"network":"datekeys:quicknet:v1","round": ` + round + ` } ` ) )
}
for _ , s := range [ ] string { "1000.0" , "1e3" , "1E3" , "10e2" , "1000e0" , "100000e-2" , "0.1e4" } {
if _ , err := datekey . Parse ( enc ( s ) ) ; ! errors . Is ( err , datekeys . ErrDateKeyNonCanonical ) {
t . Errorf ( "%s: %v, want non-canonical" , s , err )
}
}
for _ , s := range [ ] string { "1.5" , "-1000" , "1e-3" , "1e400" , "18446744073709551616" , "0" , "-0" , "0e5" } {
if _ , err := datekey . Parse ( enc ( s ) ) ; ! errors . Is ( err , datekeys . ErrDateKeyInvalid ) {
t . Errorf ( "%s: %v, want invalid" , s , 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
// Spec §19, the reading rules of steps 1 to 3: step 1 accepts only the two
// Base64 alphabets, so CR and LF, which the Go decoders would skip, fail
// there; step 2 accepts one RFC 8259 JSON text holding an object, with JSON
// white space around it but no byte order mark; step 3 reads the exact
// decimal value of each number, never its IEEE 754 double.
func TestReadingRules ( t * testing . T ) {
canon := datekey . DateKey { ProfileID : profile . QuicknetID , Round : 66884212 } . Compact ( )
payload := canon [ len ( datekey . Prefix ) : ]
enc := func ( json string ) string {
return datekey . Prefix + base64 . RawURLEncoding . EncodeToString ( [ ] byte ( json ) )
}
for _ , tc := range [ ] struct {
name , in string
want error
} {
{ "LF inside the Base64" , datekey . Prefix + payload [ : 8 ] + "\n" + payload [ 8 : ] , datekeys . ErrDateKeyInvalid } ,
{ "CR inside the Base64" , datekey . Prefix + payload [ : 8 ] + "\r" + payload [ 8 : ] , datekeys . ErrDateKeyInvalid } ,
{ "CR LF after the Base64" , canon + "\r\n" , datekeys . ErrDateKeyInvalid } ,
{ "LF before the Base64" , datekey . Prefix + "\n" + payload , datekeys . ErrDateKeyInvalid } ,
{ "space inside the Base64" , datekey . Prefix + payload [ : 8 ] + " " + payload [ 8 : ] , datekeys . ErrDateKeyInvalid } ,
{ "both alphabets mixed" , datekey . Prefix + "-+" + payload [ 2 : ] , datekeys . ErrDateKeyInvalid } ,
{ "byte order mark" , enc ( "\ufeff" + ` { "version":1,"network":"datekeys:quicknet:v1","round":66884212} ` ) , datekeys . ErrDateKeyInvalid } ,
{ "JSON white space before the object" , enc ( " \t\r\n" + ` { "version":1,"network":"datekeys:quicknet:v1","round":66884212} ` ) , datekeys . ErrDateKeyNonCanonical } ,
{ "invalid UTF-8 in a member a repeated name overwrites" , enc ( ` { "version":1,"network":" ` + "\xff" + ` ","network":"datekeys:quicknet:v1","round":66884212} ` ) , datekeys . ErrDateKeyInvalid } ,
{ "invalid UTF-8 in a member name" , enc ( ` { "version":1,"netw ` + "\xff" + ` ork":"x","network":"datekeys:quicknet:v1","round":66884212} ` ) , datekeys . ErrDateKeyInvalid } ,
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
{ "a JSON array" , enc ( ` [ { "version":1,"network":"datekeys:quicknet:v1","round":66884212}] ` ) , datekeys . ErrDateKeyInvalid } ,
{ "version 1.0000000000000001, 1 as a double" , enc ( ` { "version":1.0000000000000001,"network":"datekeys:quicknet:v1","round":66884212} ` ) , datekeys . ErrDateKeyInvalid } ,
{ "round 66884212.00000000000001" , enc ( ` { "version":1,"network":"datekeys:quicknet:v1","round":66884212.00000000000001} ` ) , datekeys . ErrDateKeyInvalid } ,
{ "round 2^53+1, 2^53 as a double" , enc ( ` { "version":1,"network":"datekeys:quicknet:v1","round":9007199254740993} ` ) , datekeys . ErrDateKeyInvalid } ,
{ "version 1.0" , enc ( ` { "version":1.0,"network":"datekeys:quicknet:v1","round":66884212} ` ) , datekeys . ErrDateKeyNonCanonical } ,
{ "version 100e-2" , enc ( ` { "version":100e-2,"network":"datekeys:quicknet:v1","round":66884212} ` ) , datekeys . ErrDateKeyNonCanonical } ,
} {
if _ , err := datekey . Parse ( tc . in ) ; ! errors . Is ( err , tc . want ) {
t . Errorf ( "%s: %v, want %s" , tc . name , err , datekeys . Code ( tc . want ) )
}
}
}
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 FuzzParse ( f * testing . F ) {
d := datekey . DateKey { ProfileID : profile . QuicknetID , Round : 1000 }
f . Add ( d . Compact ( ) )
f . Add ( "dk1_invalid" )
f . Add ( datekey . Prefix + base64 . RawURLEncoding . EncodeToString ( [ ] byte ( ` { "version":1,"network":"a","round":1e3} ` ) ) )
f . Fuzz ( func ( t * testing . T , s string ) {
d , err := datekey . Parse ( s )
if err != nil {
if c := datekeys . Code ( err ) ; c != "ERR_DATEKEY_INVALID" && c != "ERR_DATEKEY_NON_CANONICAL" {
t . Fatalf ( "unexpected error %v" , err )
}
return
}
if d . Compact ( ) != s {
t . Fatal ( "accepted a non-canonical DateKey" )
}
} )
}