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 extension implements the single generic extension mechanism shared
|
|
|
|
|
// by PUBLIC_HEADER, CONTROL_CBOR and .dkk (spec §31, §44, §54, §72).
|
|
|
|
|
//
|
|
|
|
|
// Extension data is opaque bytes: the base protocol never decodes or
|
|
|
|
|
// validates its content, and the validity of the containing object never
|
|
|
|
|
// depends on it. The package enforces the structural rules only: valid UTF-8
|
|
|
|
|
// identifiers, extension_version at most 2^32-1, data that is absent or a
|
|
|
|
|
// non-empty byte string, 1 to 64 extensions per array, no identifier repeated
|
|
|
|
|
// within an object, no identifier in both the critical and the noncritical
|
|
|
|
|
// array, canonical order by the UTF-8 bytes of extension_id, rejection of
|
|
|
|
|
// unknown critical extensions, and omission of empty arrays (spec §58.1).
|
|
|
|
|
//
|
|
|
|
|
// Only an application that knows an extension interprets its data. A
|
|
|
|
|
// Registry that also implements DataValidator checks the data of the
|
|
|
|
|
// extensions it knows: invalid data rejects a critical extension with
|
|
|
|
|
// ErrExtensionDataInvalid and makes a noncritical one Unusable (spec §54).
|
Spec v0.8.2: corrections from the formal review
A formal review of the whole v0.8.2 text found it approvable after
these corrections, recorded in §76 ("Correcciones de la revisión
formal"):
- §27 no longer calls header_binding the authenticity of PUBLIC_HEADER:
it binds the header to the opened control, never authorship or date
(§55.1); the age MAC only protects against whoever lacks the file key.
- §63 steps 9 and 10: a network source (relay, Release API, cache) MUST
verify every response and gives ERR_RELEASE_UNAVAILABLE at step 9 when
none verifies; the step-10 codes are for a directly supplied release.
The reference already behaved so; TestReleaseFromANetworkSource pins
both paths.
- §54 and §72: registrations declare the objects and arrays where an
extension may appear, and a known extension out of place counts as
unknown there. The reference gains the optional extension.Placement
interface, used at steps 4, 9.a and 14.
- §63 step 11 fixes the GT serialization hashed by H2 (kilic/kyber order)
with the frozen vector H2(e(G1, G2))[:16] = cb87319f..., shared as
testdata/vectors/tlock_ibe.json; H2-H4 are cited to drand/kyber.
- Step 5 makes the SEALED_CONTROL read mandatory, step 15 names
ERR_HEADER_BINDING, §21 makes capsule_id 16 CSPRNG bytes a MUST, §76
is made accurate (four dk1.json vectors, the §36 time_only rule, two
cases rewritten against the texts that really existed), and editorial
fixes in §5, §36, §55.1, §69.1 and §77. §73 lists the three new
decisions.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 weeks ago
|
|
|
// A Registry that also implements Placement tells in which objects and
|
|
|
|
|
// arrays each extension is registered: elsewhere a known extension is
|
|
|
|
|
// treated as unknown (spec §54, §72).
|
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 extension
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"bytes"
|
|
|
|
|
"fmt"
|
|
|
|
|
"slices"
|
|
|
|
|
"strings"
|
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
|
|
|
"unicode/utf8"
|
|
|
|
|
|
|
|
|
|
datekeys "g.activething.com/go/DateKeys"
|
|
|
|
|
"g.activething.com/go/DateKeys/codec"
|
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
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// Limits of one extension array and of one extension (spec §31, §54, §57).
|
|
|
|
|
const (
|
|
|
|
|
// MaxIDLen bounds extension_id. It is an implementation limit (spec §74).
|
|
|
|
|
MaxIDLen = 256
|
|
|
|
|
// MaxExtensions is the largest number of extensions in one array.
|
|
|
|
|
MaxExtensions = 64
|
|
|
|
|
// MaxVersion is the largest extension_version, 2^32-1.
|
|
|
|
|
MaxVersion = 1<<32 - 1
|
|
|
|
|
// MaxDataLen is the largest data: the largest frame of spec §57,
|
|
|
|
|
// SEALED_CONTROL. The frame of the containing object is the effective
|
|
|
|
|
// bound.
|
|
|
|
|
MaxDataLen = 64 << 20
|
|
|
|
|
)
|
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
|
|
|
|
|
|
|
|
// Extension is one entry of an extension array.
|
|
|
|
|
type Extension struct {
|
|
|
|
|
ID string // key 0, extension_id
|
|
|
|
|
Version uint64 // key 1, extension_version
|
|
|
|
|
// Data is the opaque content of key 2, at least one byte, or nil when the
|
|
|
|
|
// extension carries no data and key 2 is omitted. An empty non-nil slice
|
|
|
|
|
// is invalid: an empty byte string never stands for absence (spec §58.1).
|
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
|
|
|
Data []byte
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// New returns an extension that carries data, of which it keeps a copy. data
|
|
|
|
|
// must hold at least one byte. An extension without data has no constructor:
|
|
|
|
|
// it is the literal Extension{ID: id, Version: version}, which omits key 2.
|
|
|
|
|
func New(id string, version uint64, data []byte) (Extension, error) {
|
|
|
|
|
if data == nil {
|
|
|
|
|
return Extension{}, fmt.Errorf("extension %q: New needs data; an extension without data is Extension{ID, Version}: %w", id, datekeys.ErrNonCanonicalCBOR)
|
|
|
|
|
}
|
|
|
|
|
e := Extension{ID: id, Version: version, Data: bytes.Clone(data)}
|
|
|
|
|
if err := validate(e); 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 Extension{}, err
|
|
|
|
|
}
|
|
|
|
|
return e, 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
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Registry tells which extensions the application implements. A nil Registry
|
|
|
|
|
// knows none, which is the state of the base protocol V1.
|
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
|
|
|
type Registry interface {
|
|
|
|
|
Known(id string, version uint64) bool
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// DataValidator is an optional interface of a Registry. ValidateData reports
|
|
|
|
|
// whether the data of e (nil when e carries none) follows the registered
|
|
|
|
|
// schema of (e.ID, e.Version) (spec §72). It is called only for extensions
|
|
|
|
|
// the Registry knows.
|
|
|
|
|
type DataValidator interface {
|
|
|
|
|
ValidateData(e Extension) error
|
|
|
|
|
}
|
|
|
|
|
|
Spec v0.8.2: corrections from the formal review
A formal review of the whole v0.8.2 text found it approvable after
these corrections, recorded in §76 ("Correcciones de la revisión
formal"):
- §27 no longer calls header_binding the authenticity of PUBLIC_HEADER:
it binds the header to the opened control, never authorship or date
(§55.1); the age MAC only protects against whoever lacks the file key.
- §63 steps 9 and 10: a network source (relay, Release API, cache) MUST
verify every response and gives ERR_RELEASE_UNAVAILABLE at step 9 when
none verifies; the step-10 codes are for a directly supplied release.
The reference already behaved so; TestReleaseFromANetworkSource pins
both paths.
- §54 and §72: registrations declare the objects and arrays where an
extension may appear, and a known extension out of place counts as
unknown there. The reference gains the optional extension.Placement
interface, used at steps 4, 9.a and 14.
- §63 step 11 fixes the GT serialization hashed by H2 (kilic/kyber order)
with the frozen vector H2(e(G1, G2))[:16] = cb87319f..., shared as
testdata/vectors/tlock_ibe.json; H2-H4 are cited to drand/kyber.
- Step 5 makes the SEALED_CONTROL read mandatory, step 15 names
ERR_HEADER_BINDING, §21 makes capsule_id 16 CSPRNG bytes a MUST, §76
is made accurate (four dk1.json vectors, the §36 time_only rule, two
cases rewritten against the texts that really existed), and editorial
fixes in §5, §36, §55.1, §69.1 and §77. §73 lists the three new
decisions.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 weeks ago
|
|
|
// Object names an object that carries extension arrays (spec §54).
|
|
|
|
|
type Object int
|
|
|
|
|
|
|
|
|
|
// The objects that carry extensions.
|
|
|
|
|
const (
|
|
|
|
|
PublicHeader Object = iota + 1 // PUBLIC_HEADER, keys 5 and 6 (spec §24)
|
|
|
|
|
Control // CONTROL_CBOR, keys 4 and 5 (spec §31)
|
|
|
|
|
AccessKey // the body of a .dkk, keys 7 and 8 (spec §41)
|
Format 3, step 2: the codec of BODY, security and the head
- Format3 and the control of schema version 3, with the keys of
version 2 (spec 31). The PRELUDE still rejects VERSION 3 until the
reader opens format 3, in step 3, with the test data that expect it.
- The frame of BODY (spec 29.2): AREA_LEN, SECURITY_LEN and HEAD_LEN,
their limits against L and the zeros of the area, all ERR_INTEGRITY.
- security (spec 29.3, 29.7): the outer map, with the signature and
the seal as separately encoded byte strings, and its verdicts X, F0,
F1, S0, S1 and S2, which never fail. The first row that holds
decides, so a seal that breaks its schema is S2 before its type is
read. Writers of this version write it empty, 22 bytes.
- The head (spec 29.4): layer 2 with its type tag and version 1;
layer 3 with the CDDL, R1 and R8; layer 4 in key order, the comment
and the declared author, each file with R2 to R6c, R10 and its
layout, R7 and R9 over the tree, all ERR_HEAD_INVALID, and then the
critical extensions of the new extension.Head object.
- ERR_HEAD_INVALID is declared; All lists it once SpecVersion moves to
0.10 with the test data, in step 6.
- The control tests take format 4 as the caller error that format 3
was, as section 76 of the spec anticipated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
Head // the head of a format 3 capsule, keys 6 and 7 (spec §29.4)
|
Spec v0.8.2: corrections from the formal review
A formal review of the whole v0.8.2 text found it approvable after
these corrections, recorded in §76 ("Correcciones de la revisión
formal"):
- §27 no longer calls header_binding the authenticity of PUBLIC_HEADER:
it binds the header to the opened control, never authorship or date
(§55.1); the age MAC only protects against whoever lacks the file key.
- §63 steps 9 and 10: a network source (relay, Release API, cache) MUST
verify every response and gives ERR_RELEASE_UNAVAILABLE at step 9 when
none verifies; the step-10 codes are for a directly supplied release.
The reference already behaved so; TestReleaseFromANetworkSource pins
both paths.
- §54 and §72: registrations declare the objects and arrays where an
extension may appear, and a known extension out of place counts as
unknown there. The reference gains the optional extension.Placement
interface, used at steps 4, 9.a and 14.
- §63 step 11 fixes the GT serialization hashed by H2 (kilic/kyber order)
with the frozen vector H2(e(G1, G2))[:16] = cb87319f..., shared as
testdata/vectors/tlock_ibe.json; H2-H4 are cited to drand/kyber.
- Step 5 makes the SEALED_CONTROL read mandatory, step 15 names
ERR_HEADER_BINDING, §21 makes capsule_id 16 CSPRNG bytes a MUST, §76
is made accurate (four dk1.json vectors, the §36 time_only rule, two
cases rewritten against the texts that really existed), and editorial
fixes in §5, §36, §55.1, §69.1 and §77. §73 lists the three new
decisions.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 weeks ago
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// String returns the name of the object in the specification.
|
|
|
|
|
func (o Object) String() string {
|
|
|
|
|
switch o {
|
|
|
|
|
case PublicHeader:
|
|
|
|
|
return "PUBLIC_HEADER"
|
|
|
|
|
case Control:
|
|
|
|
|
return "CONTROL_CBOR"
|
|
|
|
|
case AccessKey:
|
|
|
|
|
return ".dkk"
|
Format 3, step 2: the codec of BODY, security and the head
- Format3 and the control of schema version 3, with the keys of
version 2 (spec 31). The PRELUDE still rejects VERSION 3 until the
reader opens format 3, in step 3, with the test data that expect it.
- The frame of BODY (spec 29.2): AREA_LEN, SECURITY_LEN and HEAD_LEN,
their limits against L and the zeros of the area, all ERR_INTEGRITY.
- security (spec 29.3, 29.7): the outer map, with the signature and
the seal as separately encoded byte strings, and its verdicts X, F0,
F1, S0, S1 and S2, which never fail. The first row that holds
decides, so a seal that breaks its schema is S2 before its type is
read. Writers of this version write it empty, 22 bytes.
- The head (spec 29.4): layer 2 with its type tag and version 1;
layer 3 with the CDDL, R1 and R8; layer 4 in key order, the comment
and the declared author, each file with R2 to R6c, R10 and its
layout, R7 and R9 over the tree, all ERR_HEAD_INVALID, and then the
critical extensions of the new extension.Head object.
- ERR_HEAD_INVALID is declared; All lists it once SpecVersion moves to
0.10 with the test data, in step 6.
- The control tests take format 4 as the caller error that format 3
was, as section 76 of the spec anticipated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
case Head:
|
|
|
|
|
return "head"
|
Spec v0.8.2: corrections from the formal review
A formal review of the whole v0.8.2 text found it approvable after
these corrections, recorded in §76 ("Correcciones de la revisión
formal"):
- §27 no longer calls header_binding the authenticity of PUBLIC_HEADER:
it binds the header to the opened control, never authorship or date
(§55.1); the age MAC only protects against whoever lacks the file key.
- §63 steps 9 and 10: a network source (relay, Release API, cache) MUST
verify every response and gives ERR_RELEASE_UNAVAILABLE at step 9 when
none verifies; the step-10 codes are for a directly supplied release.
The reference already behaved so; TestReleaseFromANetworkSource pins
both paths.
- §54 and §72: registrations declare the objects and arrays where an
extension may appear, and a known extension out of place counts as
unknown there. The reference gains the optional extension.Placement
interface, used at steps 4, 9.a and 14.
- §63 step 11 fixes the GT serialization hashed by H2 (kilic/kyber order)
with the frozen vector H2(e(G1, G2))[:16] = cb87319f..., shared as
testdata/vectors/tlock_ibe.json; H2-H4 are cited to drand/kyber.
- Step 5 makes the SEALED_CONTROL read mandatory, step 15 names
ERR_HEADER_BINDING, §21 makes capsule_id 16 CSPRNG bytes a MUST, §76
is made accurate (four dk1.json vectors, the §36 time_only rule, two
cases rewritten against the texts that really existed), and editorial
fixes in §5, §36, §55.1, §69.1 and §77. §73 lists the three new
decisions.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 weeks ago
|
|
|
}
|
|
|
|
|
return fmt.Sprintf("Object(%d)", int(o))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Array names one of the two extension arrays of an object.
|
|
|
|
|
type Array int
|
|
|
|
|
|
|
|
|
|
// The two extension arrays of an object.
|
|
|
|
|
const (
|
|
|
|
|
Critical Array = iota + 1 // critical_extensions
|
|
|
|
|
Noncritical // noncritical_extensions
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// String returns the name of the array in the specification.
|
|
|
|
|
func (a Array) String() string {
|
|
|
|
|
switch a {
|
|
|
|
|
case Critical:
|
|
|
|
|
return "critical_extensions"
|
|
|
|
|
case Noncritical:
|
|
|
|
|
return "noncritical_extensions"
|
|
|
|
|
}
|
|
|
|
|
return fmt.Sprintf("Array(%d)", int(a))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Placement is an optional interface of a Registry. RegisteredIn reports
|
|
|
|
|
// whether (id, version) is registered for the array arr of the object obj:
|
|
|
|
|
// the registration of each extension declares the objects and arrays where
|
|
|
|
|
// it may appear (spec §72). It is called only for extensions the Registry
|
|
|
|
|
// knows. A known extension that appears in an object or array it is not
|
|
|
|
|
// registered for is treated there as unknown (spec §54): a critical one is
|
|
|
|
|
// rejected with ErrExtensionCriticalUnknown and a noncritical one is ignored,
|
Spec v0.8.2: second-round corrections from the formal review
The second round of the formal review confirmed the nine corrections of
c57ed48 and asked for these, recorded in §76 as corrections 4 to 6 and
an editorial note:
- §72: an encoder MUST NOT write a registered extension in an object or
array it is not registered for; §54: a reader MUST NOT interpret the
data of a noncritical one it ignores for that reason. capsule.Encrypt
and accesskey.Encode take no Registry, so the application applies the
rule; their documentation and extension.Placement say so.
- §17 and §51 give the step-10 codes only for a directly supplied
release, as step 10 does; a network source discards a failing one at
step 9.
- Step 9 reports ERR_RELEASE_UNAVAILABLE and no other code, whatever the
failure of the source. provider/drand.Client keeps each relay's failure
as text only (errors.Join made a relay's ERR_ROUND_MISMATCH match with
errors.Is), and capsule.Open keeps only the text of a source error that
carries another code (a caller's source failing with
ERR_RELEASE_INVALID gave that code at step 9). A context that ended
stays detectable: Fetch now has a single failure path, so the canceled
and deadline cases are deterministic.
- TestExtensionPlacement covers the noncritical array of a .dkk: with
the object-blind extension.CheckNoncritical at step 9.a it fails.
- Editorial: §28.1 "analizan solo la cabecera age", one arrow at step 9,
two §76 introductions; §73 lines for release sources and placement.
- testdata/README.md says the corpus registers its extensions in both
arrays of every object; traceability, CHANGELOG and both READMEs
(integrity holds against whoever lacks the file keys, §27, §55.1)
follow. Spec dated 28 September 2026; new SHA-256 in spec/README.md.
No fixture or vector changes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 week ago
|
|
|
// its data neither checked nor interpreted. A Registry that does not
|
|
|
|
|
// implement Placement knows each of its extensions in every object and array.
|
|
|
|
|
//
|
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
|
|
|
// An encoder must not write a registered extension where it is not
|
|
|
|
|
// registered (spec §72): CheckWrite is that rule, which the writers of this
|
|
|
|
|
// module apply with the Registry of the extensions that the specification
|
|
|
|
|
// itself registers.
|
Spec v0.8.2: corrections from the formal review
A formal review of the whole v0.8.2 text found it approvable after
these corrections, recorded in §76 ("Correcciones de la revisión
formal"):
- §27 no longer calls header_binding the authenticity of PUBLIC_HEADER:
it binds the header to the opened control, never authorship or date
(§55.1); the age MAC only protects against whoever lacks the file key.
- §63 steps 9 and 10: a network source (relay, Release API, cache) MUST
verify every response and gives ERR_RELEASE_UNAVAILABLE at step 9 when
none verifies; the step-10 codes are for a directly supplied release.
The reference already behaved so; TestReleaseFromANetworkSource pins
both paths.
- §54 and §72: registrations declare the objects and arrays where an
extension may appear, and a known extension out of place counts as
unknown there. The reference gains the optional extension.Placement
interface, used at steps 4, 9.a and 14.
- §63 step 11 fixes the GT serialization hashed by H2 (kilic/kyber order)
with the frozen vector H2(e(G1, G2))[:16] = cb87319f..., shared as
testdata/vectors/tlock_ibe.json; H2-H4 are cited to drand/kyber.
- Step 5 makes the SEALED_CONTROL read mandatory, step 15 names
ERR_HEADER_BINDING, §21 makes capsule_id 16 CSPRNG bytes a MUST, §76
is made accurate (four dk1.json vectors, the §36 time_only rule, two
cases rewritten against the texts that really existed), and editorial
fixes in §5, §36, §55.1, §69.1 and §77. §73 lists the three new
decisions.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 weeks ago
|
|
|
type Placement interface {
|
|
|
|
|
RegisteredIn(id string, version uint64, obj Object, arr Array) bool
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
// CheckWrite applies the rules of an encoder of spec §72 to the extensions
|
|
|
|
|
// exts that it writes in the array arr of obj: an extension that reg knows
|
|
|
|
|
// goes only where reg registers it, and with data that reg validates when it
|
|
|
|
|
// is a DataValidator. The extensions that reg does not know are the
|
|
|
|
|
// application's own, and the application answers for them.
|
|
|
|
|
func CheckWrite(reg Registry, obj Object, arr Array, exts []Extension) error {
|
|
|
|
|
if reg == nil {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
for _, e := range exts {
|
|
|
|
|
if !reg.Known(e.ID, e.Version) {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
if !registered(reg, e.ID, e.Version, obj, arr) {
|
|
|
|
|
return fmt.Errorf("extension: %s version %d is not registered for %s of %s: an encoder must not write it there (spec §72)", e.ID, e.Version, arr, obj)
|
|
|
|
|
}
|
|
|
|
|
if v, ok := reg.(DataValidator); ok {
|
|
|
|
|
if err := v.ValidateData(e); err != nil {
|
|
|
|
|
return fmt.Errorf("extension: %s version %d: %w", e.ID, e.Version, err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
Spec v0.8.2: corrections from the formal review
A formal review of the whole v0.8.2 text found it approvable after
these corrections, recorded in §76 ("Correcciones de la revisión
formal"):
- §27 no longer calls header_binding the authenticity of PUBLIC_HEADER:
it binds the header to the opened control, never authorship or date
(§55.1); the age MAC only protects against whoever lacks the file key.
- §63 steps 9 and 10: a network source (relay, Release API, cache) MUST
verify every response and gives ERR_RELEASE_UNAVAILABLE at step 9 when
none verifies; the step-10 codes are for a directly supplied release.
The reference already behaved so; TestReleaseFromANetworkSource pins
both paths.
- §54 and §72: registrations declare the objects and arrays where an
extension may appear, and a known extension out of place counts as
unknown there. The reference gains the optional extension.Placement
interface, used at steps 4, 9.a and 14.
- §63 step 11 fixes the GT serialization hashed by H2 (kilic/kyber order)
with the frozen vector H2(e(G1, G2))[:16] = cb87319f..., shared as
testdata/vectors/tlock_ibe.json; H2-H4 are cited to drand/kyber.
- Step 5 makes the SEALED_CONTROL read mandatory, step 15 names
ERR_HEADER_BINDING, §21 makes capsule_id 16 CSPRNG bytes a MUST, §76
is made accurate (four dk1.json vectors, the §36 time_only rule, two
cases rewritten against the texts that really existed), and editorial
fixes in §5, §36, §55.1, §69.1 and §77. §73 lists the three new
decisions.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 weeks ago
|
|
|
// KnownIn reports whether reg knows (id, version) in the array arr of obj:
|
|
|
|
|
// reg knows it and, when reg is a Placement, registers it there (spec §54,
|
|
|
|
|
// §72). A nil Registry knows none. It is the rule of CheckCriticalIn and
|
|
|
|
|
// CheckNoncriticalIn, for an application that interprets the extensions of
|
|
|
|
|
// an object it has read: an extension unknown there is not interpreted.
|
|
|
|
|
func KnownIn(reg Registry, id string, version uint64, obj Object, arr Array) bool {
|
|
|
|
|
return reg != nil && reg.Known(id, version) && registered(reg, id, version, obj, arr)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// registered reports whether reg, which knows (id, version), registers it for
|
|
|
|
|
// the array arr of obj: always when reg is not a Placement.
|
|
|
|
|
func registered(reg Registry, id string, version uint64, obj Object, arr Array) bool {
|
|
|
|
|
p, ok := reg.(Placement)
|
|
|
|
|
return !ok || p.RegisteredIn(id, version, obj, arr)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Set is a simple Registry. It does not validate data, and it knows each of
|
|
|
|
|
// its extensions in every object and array.
|
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
|
|
|
type Set map[string][]uint64
|
|
|
|
|
|
|
|
|
|
// Known reports whether (id, version) is in the set.
|
|
|
|
|
func (s Set) Known(id string, version uint64) bool { return slices.Contains(s[id], version) }
|
|
|
|
|
|
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
|
|
|
// compare orders extensions by the UTF-8 bytes of extension_id, the order of
|
|
|
|
|
// spec §54: strings.Compare compares the bytes as unsigned values, and a
|
|
|
|
|
// proper prefix sorts first. Within one object an identifier appears at most
|
|
|
|
|
// once.
|
|
|
|
|
func compare(a, b Extension) int { return strings.Compare(a.ID, b.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
|
|
|
|
|
|
|
|
func validate(e Extension) error {
|
|
|
|
|
if e.ID == "" || len(e.ID) > MaxIDLen || !utf8.ValidString(e.ID) {
|
|
|
|
|
return fmt.Errorf("extension: invalid extension_id %q: %w", e.ID, datekeys.ErrNonCanonicalCBOR)
|
|
|
|
|
}
|
|
|
|
|
if e.Version > MaxVersion {
|
|
|
|
|
return fmt.Errorf("extension %s: extension_version %d exceeds %d: %w", e.ID, e.Version, uint64(MaxVersion), datekeys.ErrNonCanonicalCBOR)
|
|
|
|
|
}
|
|
|
|
|
if e.Data != nil && (len(e.Data) == 0 || len(e.Data) > MaxDataLen) {
|
|
|
|
|
return fmt.Errorf("extension %s: data of %d bytes outside 1..%d: %w", e.ID, len(e.Data), MaxDataLen, datekeys.ErrNonCanonicalCBOR)
|
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
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Canonical validates one extension array and returns it in canonical order,
|
|
|
|
|
// sorted by the UTF-8 bytes of extension_id: 1 to 64 valid extensions, no
|
|
|
|
|
// identifier repeated. An empty input yields nil, so that the array key is
|
|
|
|
|
// omitted (spec §58.1).
|
|
|
|
|
func Canonical(exts []Extension) ([]Extension, 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
|
|
|
if len(exts) == 0 {
|
|
|
|
|
return nil, nil
|
|
|
|
|
}
|
|
|
|
|
if len(exts) > MaxExtensions {
|
|
|
|
|
return nil, errTooMany(len(exts))
|
|
|
|
|
}
|
|
|
|
|
out := slices.Clone(exts)
|
|
|
|
|
slices.SortFunc(out, compare)
|
|
|
|
|
if err := checkArray(out); err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
return out, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func errTooMany(n int) error {
|
|
|
|
|
return fmt.Errorf("extension: %d extensions in one array, at most %d: %w", n, MaxExtensions, datekeys.ErrNonCanonicalCBOR)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// checkArray applies the rules of DecodeArray to an array to be written: 1 to
|
|
|
|
|
// 64 valid extensions in canonical order, no identifier repeated.
|
|
|
|
|
func checkArray(exts []Extension) error {
|
|
|
|
|
switch {
|
|
|
|
|
case len(exts) == 0:
|
|
|
|
|
return fmt.Errorf("extension: empty array; an absent array omits its key: %w", datekeys.ErrNonCanonicalCBOR)
|
|
|
|
|
case len(exts) > MaxExtensions:
|
|
|
|
|
return errTooMany(len(exts))
|
|
|
|
|
}
|
|
|
|
|
for i, e := range exts {
|
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 := validate(e); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if i == 0 {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
switch c := compare(exts[i-1], e); {
|
|
|
|
|
case c == 0:
|
|
|
|
|
return fmt.Errorf("extension %s: appears more than once: %w", e.ID, datekeys.ErrNonCanonicalCBOR)
|
|
|
|
|
case c > 0:
|
|
|
|
|
return fmt.Errorf("extension %s: array is not in canonical order: %w", e.ID, datekeys.ErrNonCanonicalCBOR)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// EncodeArray writes a non-empty extension array as Canonical returns it:
|
|
|
|
|
// each extension is the map {0: extension_id, 1: extension_version} with
|
|
|
|
|
// key 2, the data as a byte string, only when the extension carries data
|
|
|
|
|
// (spec §54). An array that DecodeArray would reject is not written: its
|
|
|
|
|
// error is recorded in e, whose Out returns it.
|
|
|
|
|
func EncodeArray(e *codec.Encoder, exts []Extension) {
|
|
|
|
|
if err := checkArray(exts); err != nil {
|
|
|
|
|
e.Fail(err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
e.Array(len(exts))
|
|
|
|
|
for _, x := range exts {
|
|
|
|
|
if x.Data == nil {
|
|
|
|
|
e.Map(2)
|
|
|
|
|
} else {
|
|
|
|
|
e.Map(3)
|
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
|
|
|
}
|
|
|
|
|
e.Uint(0)
|
|
|
|
|
e.Text(x.ID)
|
|
|
|
|
e.Uint(1)
|
|
|
|
|
e.Uint(x.Version)
|
|
|
|
|
if x.Data != nil {
|
|
|
|
|
e.Uint(2)
|
|
|
|
|
e.Bstr(x.Data)
|
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
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// DecodeArray reads one extension array. The array holds 1 to 64 entries,
|
|
|
|
|
// which its head declares before any is read; each entry is a map with
|
|
|
|
|
// key 0, a non-empty UTF-8 extension_id of at most MaxIDLen bytes, key 1, an
|
|
|
|
|
// extension_version of at most MaxVersion, and optionally key 2, a byte
|
|
|
|
|
// string of at least one byte whose content is copied and never decoded
|
|
|
|
|
// (spec §54, §58.1). Entries are in canonical order with no identifier
|
|
|
|
|
// repeated. Every failure wraps ErrNonCanonicalCBOR.
|
|
|
|
|
func DecodeArray(d *codec.Decoder) ([]Extension, error) {
|
|
|
|
|
n, err := d.Array(MaxExtensions)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, fmt.Errorf("extension: %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
|
|
|
}
|
|
|
|
|
if n == 0 {
|
|
|
|
|
return nil, fmt.Errorf("extension: empty array; an absent array omits its key: %w", datekeys.ErrNonCanonicalCBOR)
|
|
|
|
|
}
|
|
|
|
|
out := make([]Extension, 0, n)
|
|
|
|
|
for i := range n {
|
|
|
|
|
e, err := decodeOne(d)
|
|
|
|
|
if 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 nil, err
|
|
|
|
|
}
|
|
|
|
|
if i > 0 {
|
|
|
|
|
switch c := compare(out[i-1], e); {
|
|
|
|
|
case c == 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
|
|
|
return nil, fmt.Errorf("extension %s: appears more than once: %w", e.ID, datekeys.ErrNonCanonicalCBOR)
|
|
|
|
|
case c > 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
|
|
|
return nil, fmt.Errorf("extension %s: array is not in canonical order: %w", e.ID, datekeys.ErrNonCanonicalCBOR)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
out = append(out, e)
|
|
|
|
|
}
|
|
|
|
|
return out, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// decodeOne reads the map of one extension. Key 2, when present, must be a
|
|
|
|
|
// byte string of at least one byte: the empty byte string and every other
|
|
|
|
|
// CBOR type are rejected explicitly (spec §54, §58.1).
|
|
|
|
|
func decodeOne(d *codec.Decoder) (Extension, error) {
|
|
|
|
|
var e Extension
|
|
|
|
|
pairs, err := d.Map(3)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return e, fmt.Errorf("extension: %w", err)
|
|
|
|
|
}
|
|
|
|
|
var seen [3]bool
|
|
|
|
|
for range pairs {
|
|
|
|
|
k, err := d.Key()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return e, fmt.Errorf("extension: %w", err)
|
|
|
|
|
}
|
|
|
|
|
switch k {
|
|
|
|
|
case 0:
|
|
|
|
|
e.ID, err = d.Text(MaxIDLen)
|
|
|
|
|
case 1:
|
|
|
|
|
e.Version, err = d.Uint(MaxVersion)
|
|
|
|
|
case 2:
|
|
|
|
|
if e.Data, err = d.Bstr(0, MaxDataLen); err == nil && len(e.Data) == 0 {
|
|
|
|
|
return e, fmt.Errorf("extension %q: data is present but empty; an extension without data omits key 2: %w", e.ID, datekeys.ErrNonCanonicalCBOR)
|
|
|
|
|
}
|
|
|
|
|
default:
|
|
|
|
|
return e, fmt.Errorf("extension %q: unknown key %d: %w", e.ID, k, datekeys.ErrNonCanonicalCBOR)
|
|
|
|
|
}
|
|
|
|
|
if err != nil {
|
|
|
|
|
return e, fmt.Errorf("extension %q: key %d: %w", e.ID, k, err)
|
|
|
|
|
}
|
|
|
|
|
seen[k] = true
|
|
|
|
|
}
|
|
|
|
|
if !seen[0] || !seen[1] {
|
|
|
|
|
return e, fmt.Errorf("extension %q: extension_id and extension_version are required: %w", e.ID, datekeys.ErrNonCanonicalCBOR)
|
|
|
|
|
}
|
|
|
|
|
if err := validate(e); err != nil {
|
|
|
|
|
return e, err
|
|
|
|
|
}
|
|
|
|
|
return e, d.EndMap()
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
// CheckDisjoint applies the cross-array rule of one object: an extension_id
|
|
|
|
|
// must not appear in both critical_extensions and noncritical_extensions
|
|
|
|
|
// (spec §31, §54). Arrays in canonical order, as Canonical and DecodeArray
|
|
|
|
|
// return them, are merged in one linear pass; other input is sorted first.
|
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 CheckDisjoint(critical, noncritical []Extension) error {
|
|
|
|
|
critical, noncritical = sorted(critical), sorted(noncritical)
|
|
|
|
|
for i, j := 0, 0; i < len(critical) && j < len(noncritical); {
|
|
|
|
|
switch c := compare(critical[i], noncritical[j]); {
|
|
|
|
|
case c == 0:
|
|
|
|
|
return fmt.Errorf("extension %s: both critical and noncritical: %w", critical[i].ID, datekeys.ErrNonCanonicalCBOR)
|
|
|
|
|
case c < 0:
|
|
|
|
|
i++
|
|
|
|
|
default:
|
|
|
|
|
j++
|
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
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// sorted returns exts itself when it is in canonical order, and a sorted copy
|
|
|
|
|
// otherwise.
|
|
|
|
|
func sorted(exts []Extension) []Extension {
|
|
|
|
|
if slices.IsSortedFunc(exts, compare) {
|
|
|
|
|
return exts
|
|
|
|
|
}
|
|
|
|
|
sorted := slices.Clone(exts)
|
|
|
|
|
slices.SortFunc(sorted, compare)
|
|
|
|
|
return sorted
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// CheckCritical rejects every critical extension unknown to reg with
|
|
|
|
|
// ErrExtensionCriticalUnknown and, when reg is a DataValidator, every known
|
|
|
|
|
// one whose data it rejects with ErrExtensionDataInvalid (spec §54, §70).
|
Spec v0.8.2: corrections from the formal review
A formal review of the whole v0.8.2 text found it approvable after
these corrections, recorded in §76 ("Correcciones de la revisión
formal"):
- §27 no longer calls header_binding the authenticity of PUBLIC_HEADER:
it binds the header to the opened control, never authorship or date
(§55.1); the age MAC only protects against whoever lacks the file key.
- §63 steps 9 and 10: a network source (relay, Release API, cache) MUST
verify every response and gives ERR_RELEASE_UNAVAILABLE at step 9 when
none verifies; the step-10 codes are for a directly supplied release.
The reference already behaved so; TestReleaseFromANetworkSource pins
both paths.
- §54 and §72: registrations declare the objects and arrays where an
extension may appear, and a known extension out of place counts as
unknown there. The reference gains the optional extension.Placement
interface, used at steps 4, 9.a and 14.
- §63 step 11 fixes the GT serialization hashed by H2 (kilic/kyber order)
with the frozen vector H2(e(G1, G2))[:16] = cb87319f..., shared as
testdata/vectors/tlock_ibe.json; H2-H4 are cited to drand/kyber.
- Step 5 makes the SEALED_CONTROL read mandatory, step 15 names
ERR_HEADER_BINDING, §21 makes capsule_id 16 CSPRNG bytes a MUST, §76
is made accurate (four dk1.json vectors, the §36 time_only rule, two
cases rewritten against the texts that really existed), and editorial
fixes in §5, §36, §55.1, §69.1 and §77. §73 lists the three new
decisions.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 weeks ago
|
|
|
// An unknown extension takes precedence over invalid data. It does not know
|
|
|
|
|
// the object of the array and consults no Placement: CheckCriticalIn does.
|
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 CheckCritical(critical []Extension, reg Registry) error {
|
Spec v0.8.2: corrections from the formal review
A formal review of the whole v0.8.2 text found it approvable after
these corrections, recorded in §76 ("Correcciones de la revisión
formal"):
- §27 no longer calls header_binding the authenticity of PUBLIC_HEADER:
it binds the header to the opened control, never authorship or date
(§55.1); the age MAC only protects against whoever lacks the file key.
- §63 steps 9 and 10: a network source (relay, Release API, cache) MUST
verify every response and gives ERR_RELEASE_UNAVAILABLE at step 9 when
none verifies; the step-10 codes are for a directly supplied release.
The reference already behaved so; TestReleaseFromANetworkSource pins
both paths.
- §54 and §72: registrations declare the objects and arrays where an
extension may appear, and a known extension out of place counts as
unknown there. The reference gains the optional extension.Placement
interface, used at steps 4, 9.a and 14.
- §63 step 11 fixes the GT serialization hashed by H2 (kilic/kyber order)
with the frozen vector H2(e(G1, G2))[:16] = cb87319f..., shared as
testdata/vectors/tlock_ibe.json; H2-H4 are cited to drand/kyber.
- Step 5 makes the SEALED_CONTROL read mandatory, step 15 names
ERR_HEADER_BINDING, §21 makes capsule_id 16 CSPRNG bytes a MUST, §76
is made accurate (four dk1.json vectors, the §36 time_only rule, two
cases rewritten against the texts that really existed), and editorial
fixes in §5, §36, §55.1, §69.1 and §77. §73 lists the three new
decisions.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 weeks ago
|
|
|
return checkCritical(critical, reg, 0)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// CheckCriticalIn is CheckCritical for the critical_extensions of obj: an
|
|
|
|
|
// extension that reg knows but, as a Placement, does not register for that
|
|
|
|
|
// array of obj is unknown there, ErrExtensionCriticalUnknown (spec §54,
|
|
|
|
|
// §72). The reading flow of package capsule checks every critical array
|
|
|
|
|
// with it.
|
|
|
|
|
func CheckCriticalIn(obj Object, critical []Extension, reg Registry) error {
|
|
|
|
|
return checkCritical(critical, reg, obj)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// checkCritical checks the critical extensions of obj, or of any object when
|
|
|
|
|
// obj is 0: every unknown one first, then the data of the known ones.
|
|
|
|
|
func checkCritical(critical []Extension, reg Registry, obj Object) 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
|
|
|
for _, c := range critical {
|
|
|
|
|
if reg == nil || !reg.Known(c.ID, c.Version) {
|
|
|
|
|
return fmt.Errorf("extension %s v%d: %w", c.ID, c.Version, datekeys.ErrExtensionCriticalUnknown)
|
|
|
|
|
}
|
Spec v0.8.2: corrections from the formal review
A formal review of the whole v0.8.2 text found it approvable after
these corrections, recorded in §76 ("Correcciones de la revisión
formal"):
- §27 no longer calls header_binding the authenticity of PUBLIC_HEADER:
it binds the header to the opened control, never authorship or date
(§55.1); the age MAC only protects against whoever lacks the file key.
- §63 steps 9 and 10: a network source (relay, Release API, cache) MUST
verify every response and gives ERR_RELEASE_UNAVAILABLE at step 9 when
none verifies; the step-10 codes are for a directly supplied release.
The reference already behaved so; TestReleaseFromANetworkSource pins
both paths.
- §54 and §72: registrations declare the objects and arrays where an
extension may appear, and a known extension out of place counts as
unknown there. The reference gains the optional extension.Placement
interface, used at steps 4, 9.a and 14.
- §63 step 11 fixes the GT serialization hashed by H2 (kilic/kyber order)
with the frozen vector H2(e(G1, G2))[:16] = cb87319f..., shared as
testdata/vectors/tlock_ibe.json; H2-H4 are cited to drand/kyber.
- Step 5 makes the SEALED_CONTROL read mandatory, step 15 names
ERR_HEADER_BINDING, §21 makes capsule_id 16 CSPRNG bytes a MUST, §76
is made accurate (four dk1.json vectors, the §36 time_only rule, two
cases rewritten against the texts that really existed), and editorial
fixes in §5, §36, §55.1, §69.1 and §77. §73 lists the three new
decisions.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 weeks ago
|
|
|
if obj != 0 && !registered(reg, c.ID, c.Version, obj, Critical) {
|
|
|
|
|
return fmt.Errorf("extension %s v%d: known, but not registered for the %s of %s: %w", c.ID, c.Version, Critical, obj, datekeys.ErrExtensionCriticalUnknown)
|
|
|
|
|
}
|
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 _, c := range critical {
|
|
|
|
|
if err := validateData(c, reg); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Unusable is a known noncritical extension whose data does not follow its
|
|
|
|
|
// registered schema. The object that carries it stays valid; the application
|
|
|
|
|
// must not use the extension, and the caller is told (spec §54).
|
|
|
|
|
type Unusable struct {
|
|
|
|
|
ID string
|
|
|
|
|
Version uint64
|
|
|
|
|
Err error // wraps datekeys.ErrExtensionDataInvalid
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// CheckNoncritical returns the noncritical extensions that reg knows and whose
|
|
|
|
|
// data it rejects. It never fails the object: unknown noncritical extensions
|
|
|
|
|
// are ignored, and a Registry that is not a DataValidator rejects no data
|
Spec v0.8.2: corrections from the formal review
A formal review of the whole v0.8.2 text found it approvable after
these corrections, recorded in §76 ("Correcciones de la revisión
formal"):
- §27 no longer calls header_binding the authenticity of PUBLIC_HEADER:
it binds the header to the opened control, never authorship or date
(§55.1); the age MAC only protects against whoever lacks the file key.
- §63 steps 9 and 10: a network source (relay, Release API, cache) MUST
verify every response and gives ERR_RELEASE_UNAVAILABLE at step 9 when
none verifies; the step-10 codes are for a directly supplied release.
The reference already behaved so; TestReleaseFromANetworkSource pins
both paths.
- §54 and §72: registrations declare the objects and arrays where an
extension may appear, and a known extension out of place counts as
unknown there. The reference gains the optional extension.Placement
interface, used at steps 4, 9.a and 14.
- §63 step 11 fixes the GT serialization hashed by H2 (kilic/kyber order)
with the frozen vector H2(e(G1, G2))[:16] = cb87319f..., shared as
testdata/vectors/tlock_ibe.json; H2-H4 are cited to drand/kyber.
- Step 5 makes the SEALED_CONTROL read mandatory, step 15 names
ERR_HEADER_BINDING, §21 makes capsule_id 16 CSPRNG bytes a MUST, §76
is made accurate (four dk1.json vectors, the §36 time_only rule, two
cases rewritten against the texts that really existed), and editorial
fixes in §5, §36, §55.1, §69.1 and §77. §73 lists the three new
decisions.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 weeks ago
|
|
|
// (spec §54). It does not know the object of the array and consults no
|
|
|
|
|
// Placement: CheckNoncriticalIn does.
|
|
|
|
|
func CheckNoncritical(noncritical []Extension, reg Registry) []Unusable {
|
Spec v0.8.2: corrections from the formal review
A formal review of the whole v0.8.2 text found it approvable after
these corrections, recorded in §76 ("Correcciones de la revisión
formal"):
- §27 no longer calls header_binding the authenticity of PUBLIC_HEADER:
it binds the header to the opened control, never authorship or date
(§55.1); the age MAC only protects against whoever lacks the file key.
- §63 steps 9 and 10: a network source (relay, Release API, cache) MUST
verify every response and gives ERR_RELEASE_UNAVAILABLE at step 9 when
none verifies; the step-10 codes are for a directly supplied release.
The reference already behaved so; TestReleaseFromANetworkSource pins
both paths.
- §54 and §72: registrations declare the objects and arrays where an
extension may appear, and a known extension out of place counts as
unknown there. The reference gains the optional extension.Placement
interface, used at steps 4, 9.a and 14.
- §63 step 11 fixes the GT serialization hashed by H2 (kilic/kyber order)
with the frozen vector H2(e(G1, G2))[:16] = cb87319f..., shared as
testdata/vectors/tlock_ibe.json; H2-H4 are cited to drand/kyber.
- Step 5 makes the SEALED_CONTROL read mandatory, step 15 names
ERR_HEADER_BINDING, §21 makes capsule_id 16 CSPRNG bytes a MUST, §76
is made accurate (four dk1.json vectors, the §36 time_only rule, two
cases rewritten against the texts that really existed), and editorial
fixes in §5, §36, §55.1, §69.1 and §77. §73 lists the three new
decisions.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 weeks ago
|
|
|
return checkNoncritical(noncritical, reg, 0)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// CheckNoncriticalIn is CheckNoncritical for the noncritical_extensions of
|
|
|
|
|
// obj: an extension that reg knows but, as a Placement, does not register
|
|
|
|
|
// for that array of obj is unknown there and ignored, its data unchecked
|
|
|
|
|
// (spec §54, §72). The reading flow of package capsule checks every
|
|
|
|
|
// noncritical array with it.
|
|
|
|
|
func CheckNoncriticalIn(obj Object, noncritical []Extension, reg Registry) []Unusable {
|
|
|
|
|
return checkNoncritical(noncritical, reg, obj)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// checkNoncritical checks the noncritical extensions of obj, or of any
|
|
|
|
|
// object when obj is 0.
|
|
|
|
|
func checkNoncritical(noncritical []Extension, reg Registry, obj Object) []Unusable {
|
|
|
|
|
var out []Unusable
|
|
|
|
|
for _, n := range noncritical {
|
Spec v0.8.2: corrections from the formal review
A formal review of the whole v0.8.2 text found it approvable after
these corrections, recorded in §76 ("Correcciones de la revisión
formal"):
- §27 no longer calls header_binding the authenticity of PUBLIC_HEADER:
it binds the header to the opened control, never authorship or date
(§55.1); the age MAC only protects against whoever lacks the file key.
- §63 steps 9 and 10: a network source (relay, Release API, cache) MUST
verify every response and gives ERR_RELEASE_UNAVAILABLE at step 9 when
none verifies; the step-10 codes are for a directly supplied release.
The reference already behaved so; TestReleaseFromANetworkSource pins
both paths.
- §54 and §72: registrations declare the objects and arrays where an
extension may appear, and a known extension out of place counts as
unknown there. The reference gains the optional extension.Placement
interface, used at steps 4, 9.a and 14.
- §63 step 11 fixes the GT serialization hashed by H2 (kilic/kyber order)
with the frozen vector H2(e(G1, G2))[:16] = cb87319f..., shared as
testdata/vectors/tlock_ibe.json; H2-H4 are cited to drand/kyber.
- Step 5 makes the SEALED_CONTROL read mandatory, step 15 names
ERR_HEADER_BINDING, §21 makes capsule_id 16 CSPRNG bytes a MUST, §76
is made accurate (four dk1.json vectors, the §36 time_only rule, two
cases rewritten against the texts that really existed), and editorial
fixes in §5, §36, §55.1, §69.1 and §77. §73 lists the three new
decisions.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 weeks ago
|
|
|
if reg == nil || !reg.Known(n.ID, n.Version) || obj != 0 && !registered(reg, n.ID, n.Version, obj, Noncritical) {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
if err := validateData(n, reg); err != nil {
|
|
|
|
|
out = append(out, Unusable{ID: n.ID, Version: n.Version, Err: err})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return out
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// validateData applies the optional DataValidator of reg to a known
|
|
|
|
|
// extension. The validator's own error is kept as text only, so that the
|
|
|
|
|
// result carries exactly one normative code.
|
|
|
|
|
func validateData(e Extension, reg Registry) error {
|
|
|
|
|
v, ok := reg.(DataValidator)
|
|
|
|
|
if !ok {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
if err := v.ValidateData(e); err != nil {
|
|
|
|
|
return fmt.Errorf("extension %s v%d: data: %v: %w", e.ID, e.Version, err, datekeys.ErrExtensionDataInvalid)
|
|
|
|
|
}
|
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
|
|
|
|
|
}
|