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 provider defines conditions, releases and release sources (spec §9,
|
|
|
|
|
// §45-§52) and the local verification every release must pass.
|
|
|
|
|
//
|
|
|
|
|
// A release is never trusted because of where it came from: the DateKeys API,
|
|
|
|
|
// a cache or a relay are untrusted transports (spec §3, §48, §52). A remote
|
|
|
|
|
// "verified: true" has no security value (spec §51).
|
|
|
|
|
package provider
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"fmt"
|
|
|
|
|
|
|
|
|
|
"github.com/drand/drand/v2/common"
|
|
|
|
|
|
|
|
|
|
datekeys "g.activething.com/go/DateKeys"
|
|
|
|
|
"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
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// Condition is the time condition of a Quicknet-style profile: a round.
|
|
|
|
|
type Condition struct {
|
|
|
|
|
Round uint64
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Release is the material that satisfies a condition. For drand it is the
|
|
|
|
|
// BLS signature of the round.
|
|
|
|
|
type Release struct {
|
|
|
|
|
Round uint64
|
|
|
|
|
Signature []byte
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ReleaseSource fetches the release of a condition. Implementations need not
|
|
|
|
|
// verify it; callers always do, with Verify.
|
|
|
|
|
//
|
|
|
|
|
// Errors should wrap datekeys.ErrReleaseUnavailable when the release cannot be
|
|
|
|
|
// obtained, for example because the round is not published yet.
|
|
|
|
|
type ReleaseSource interface {
|
|
|
|
|
Fetch(ctx context.Context, p *profile.Profile, c Condition) (Release, error)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ReleaseSourceFunc adapts a function to ReleaseSource.
|
|
|
|
|
type ReleaseSourceFunc func(ctx context.Context, p *profile.Profile, c Condition) (Release, error)
|
|
|
|
|
|
|
|
|
|
// Fetch calls f.
|
|
|
|
|
func (f ReleaseSourceFunc) Fetch(ctx context.Context, p *profile.Profile, c Condition) (Release, error) {
|
|
|
|
|
return f(ctx, p, c)
|
|
|
|
|
}
|
|
|
|
|
|
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
|
|
|
// Verify checks a release locally against the pinned profile (spec §17, §51),
|
|
|
|
|
// in the order of spec §63 step 10: the expected round (ErrRoundMismatch),
|
|
|
|
|
// then the signature length of the scheme and a valid BLS signature under the
|
|
|
|
|
// pinned public key (ErrReleaseInvalid). The chain hash is covered because
|
Initial implementation of the DateKeys Protocol v0.8.1
Reference implementation in Go, built from the implementation plan
(milestones M0 to M5): datekey, profile, provider, codec, agewrap,
extension, capsule, accesskey, the datekeys CLI, official vectors and
fixtures, the mutation corpus, fuzz targets, interop and live tests,
CI workflows, traceability and policy documents.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2 weeks ago
|
|
|
// the pinned public key is bound to it by profile.Validate.
|
|
|
|
|
func Verify(p *profile.Profile, c Condition, r Release) error {
|
|
|
|
|
if c.Round == 0 || c.Round > p.MaxRound() {
|
|
|
|
|
return fmt.Errorf("provider: round %d outside the range of %s: %w", c.Round, p.ID, datekeys.ErrDateKeyInvalid)
|
|
|
|
|
}
|
|
|
|
|
if r.Round != c.Round {
|
|
|
|
|
return fmt.Errorf("provider: release for round %d, expected %d: %w", r.Round, c.Round, datekeys.ErrRoundMismatch)
|
|
|
|
|
}
|
|
|
|
|
scheme, err := p.DrandScheme()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if want := scheme.SigGroup.PointLen(); len(r.Signature) != want {
|
|
|
|
|
return fmt.Errorf("provider: signature is %d bytes, %s uses %d: %w", len(r.Signature), scheme.Name, want, datekeys.ErrReleaseInvalid)
|
|
|
|
|
}
|
|
|
|
|
key := scheme.KeyGroup.Point()
|
|
|
|
|
if err := key.UnmarshalBinary(p.PublicKey); err != nil {
|
|
|
|
|
return fmt.Errorf("provider: pinned public key of %s: %v: %w", p.ID, err, datekeys.ErrUnknownProfile)
|
|
|
|
|
}
|
|
|
|
|
beacon := &common.Beacon{Round: r.Round, Signature: r.Signature}
|
|
|
|
|
if err := scheme.VerifyBeacon(beacon, key); err != nil {
|
|
|
|
|
return fmt.Errorf("provider: BLS signature of round %d does not verify under %s: %w", r.Round, p.ID, datekeys.ErrReleaseInvalid)
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|