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 drand fetches Quicknet releases directly from public drand relays
|
|
|
|
|
// (spec §48, §49). HTTP is an untrusted transport: every response is verified
|
|
|
|
|
// locally with provider.Verify against the pinned profile before it is
|
|
|
|
|
// returned, and authenticity comes from the BLS signature, never from the
|
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
|
|
|
// hostname (spec §48, §52). A response that fails is discarded; when no relay
|
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
|
|
|
// returns a valid release, the error of Fetch wraps
|
|
|
|
|
// datekeys.ErrReleaseUnavailable, the code of spec §63 step 9, and no other
|
|
|
|
|
// normative error: the failure of each relay is kept in its text only, for
|
|
|
|
|
// diagnosis.
|
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 drand
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"fmt"
|
|
|
|
|
"io"
|
|
|
|
|
"net/http"
|
|
|
|
|
"strconv"
|
|
|
|
|
"strings"
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
datekeys "g.activething.com/go/DateKeys"
|
|
|
|
|
"g.activething.com/go/DateKeys/profile"
|
|
|
|
|
"g.activething.com/go/DateKeys/provider"
|
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 a single relay exchange.
|
|
|
|
|
const (
|
|
|
|
|
DefaultTimeout = 6 * time.Second
|
|
|
|
|
maxResponseSize = 8 << 10
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// DefaultRelays returns the public drand relays used when none are given.
|
|
|
|
|
func DefaultRelays() []string {
|
|
|
|
|
return []string{"https://api.drand.sh", "https://api2.drand.sh", "https://api3.drand.sh"}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Client races independent relays and returns the first release that passes
|
|
|
|
|
// local verification. It implements provider.ReleaseSource.
|
|
|
|
|
type Client struct {
|
|
|
|
|
http *http.Client
|
|
|
|
|
relays []string
|
|
|
|
|
timeout time.Duration
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var _ provider.ReleaseSource = (*Client)(nil)
|
|
|
|
|
|
|
|
|
|
// New returns a client for the given relay base URLs, or DefaultRelays.
|
|
|
|
|
// Redirects are not followed.
|
|
|
|
|
func New(relays ...string) *Client {
|
|
|
|
|
return NewWithHTTPClient(&http.Client{
|
|
|
|
|
Timeout: DefaultTimeout,
|
|
|
|
|
CheckRedirect: func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse },
|
|
|
|
|
}, relays...)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NewWithHTTPClient is New with a caller-supplied HTTP client.
|
|
|
|
|
func NewWithHTTPClient(hc *http.Client, relays ...string) *Client {
|
|
|
|
|
if len(relays) == 0 {
|
|
|
|
|
relays = DefaultRelays()
|
|
|
|
|
}
|
|
|
|
|
return &Client{http: hc, relays: append([]string(nil), relays...), timeout: DefaultTimeout}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Fetch implements provider.ReleaseSource. Only a cryptographically valid
|
|
|
|
|
// release for exactly the requested round wins the race.
|
|
|
|
|
func (c *Client) Fetch(ctx context.Context, p *profile.Profile, cond provider.Condition) (provider.Release, error) {
|
|
|
|
|
if p.Provider != profile.ProviderDrand {
|
|
|
|
|
return provider.Release{}, fmt.Errorf("drand: profile %s is not a drand profile: %w", p.ID, datekeys.ErrUnknownProfile)
|
|
|
|
|
}
|
|
|
|
|
if cond.Round == 0 || cond.Round > p.MaxRound() {
|
|
|
|
|
return provider.Release{}, fmt.Errorf("drand: round %d outside the range of %s: %w", cond.Round, p.ID, datekeys.ErrDateKeyInvalid)
|
|
|
|
|
}
|
|
|
|
|
ctx, cancel := context.WithTimeout(ctx, c.timeout)
|
|
|
|
|
defer cancel()
|
|
|
|
|
type result struct {
|
|
|
|
|
release provider.Release
|
|
|
|
|
err error
|
|
|
|
|
}
|
|
|
|
|
ch := make(chan result, len(c.relays))
|
|
|
|
|
for _, relay := range c.relays {
|
|
|
|
|
go func(relay string) {
|
|
|
|
|
r, err := c.fetch(ctx, relay, p, cond)
|
|
|
|
|
ch <- result{r, err}
|
|
|
|
|
}(relay)
|
|
|
|
|
}
|
|
|
|
|
var failures []error
|
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
|
|
|
wait:
|
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 range c.relays {
|
|
|
|
|
select {
|
|
|
|
|
case <-ctx.Done():
|
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
|
|
|
break wait
|
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 r := <-ch:
|
|
|
|
|
if r.err == nil {
|
|
|
|
|
return r.release, nil
|
|
|
|
|
}
|
|
|
|
|
failures = append(failures, r.err)
|
|
|
|
|
}
|
|
|
|
|
}
|
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
|
|
|
err := fmt.Errorf("drand: no relay returned a verified release for round %d%s: %w",
|
|
|
|
|
cond.Round, reasons(failures), datekeys.ErrReleaseUnavailable)
|
|
|
|
|
if ctx.Err() != nil {
|
|
|
|
|
// The context ended, before or after the relays failed because of
|
|
|
|
|
// it: that stays detectable with errors.Is.
|
|
|
|
|
return provider.Release{}, fmt.Errorf("%w: %w", err, ctx.Err())
|
|
|
|
|
}
|
|
|
|
|
return provider.Release{}, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// reasons keeps the failure of each relay as text only. A relay's answer may
|
|
|
|
|
// break a rule of spec §63 step 10, whose code its error wraps, but Fetch
|
|
|
|
|
// reports ErrReleaseUnavailable alone: every error of this module wraps
|
|
|
|
|
// exactly one normative code.
|
|
|
|
|
func reasons(failures []error) string {
|
|
|
|
|
if len(failures) == 0 {
|
|
|
|
|
return ""
|
|
|
|
|
}
|
|
|
|
|
s := make([]string, len(failures))
|
|
|
|
|
for i, err := range failures {
|
|
|
|
|
s[i] = err.Error()
|
|
|
|
|
}
|
|
|
|
|
return ": " + strings.Join(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 (c *Client) fetch(ctx context.Context, relay string, p *profile.Profile, cond provider.Condition) (provider.Release, error) {
|
|
|
|
|
url := strings.TrimRight(relay, "/") + "/v2/chains/" + p.ChainHashHex() + "/rounds/" + strconv.FormatUint(cond.Round, 10)
|
|
|
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return provider.Release{}, fmt.Errorf("%s: %w", relay, err)
|
|
|
|
|
}
|
|
|
|
|
req.Header.Set("Accept", "application/json")
|
|
|
|
|
res, err := c.http.Do(req)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return provider.Release{}, fmt.Errorf("%s: %w", relay, err)
|
|
|
|
|
}
|
|
|
|
|
defer res.Body.Close()
|
|
|
|
|
if res.StatusCode != http.StatusOK {
|
|
|
|
|
return provider.Release{}, fmt.Errorf("%s: HTTP %d", relay, res.StatusCode)
|
|
|
|
|
}
|
|
|
|
|
b, err := io.ReadAll(io.LimitReader(res.Body, maxResponseSize+1))
|
|
|
|
|
if err != nil {
|
|
|
|
|
return provider.Release{}, fmt.Errorf("%s: %w", relay, err)
|
|
|
|
|
}
|
|
|
|
|
if len(b) > maxResponseSize {
|
|
|
|
|
return provider.Release{}, fmt.Errorf("%s: response larger than %d bytes: %w", relay, maxResponseSize, datekeys.ErrReleaseInvalid)
|
|
|
|
|
}
|
|
|
|
|
// The answer is read as a release the caller gives, with the strict
|
|
|
|
|
// rules of spec v0.16, §47.1, randomness included.
|
|
|
|
|
release, err := provider.ParseDrandJSON(b)
|
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 {
|
|
|
|
|
return provider.Release{}, fmt.Errorf("%s: %w", relay, 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 err := provider.Verify(p, cond, release); err != nil {
|
|
|
|
|
return provider.Release{}, fmt.Errorf("%s: %w", relay, err)
|
|
|
|
|
}
|
|
|
|
|
return release, nil
|
|
|
|
|
}
|