Release object, release in hand and step 9.c option B (spec v0.15 draft)
The release of a round becomes a file, .dkr: a release object in
deterministic CBOR, {0: "datekeys-release", 1: 1, 2: chain_hash, 3: round,
4: signature}, which provider.EncodeRelease writes and DecodeRelease reads
with its layers (size, type and version, schema). provider.ParseRelease
also reads drand's JSON as the input of the caller. Verify checks the chain
hash a release names before its round and its signature, with
ERR_PROFILE_MISMATCH. provider.Archive reads a local release archive, the
informative format of the draft.
capsule.OpenOptions.Release takes a release in hand, a provider.Supplier,
exclusive with Source: Open does not compare it with the clock (step 9.c,
option B) and reports a clock behind it in Opened.ClockBehind; a network
source is still never asked before the round time. The CLI gains
decrypt -release FILE (.dkr, drand's JSON or a local archive),
decrypt -save-release FILE.dkr and the command release, which fetches,
verifies and saves the .dkr without opening the capsule.
Test data: vectors/release.json, releases/<round>.dkr for rounds 1000,
1001, 1004 and 2000, and a local archive of rounds 1000 to 1004. In
mutations.json every case says its source, "supplied" or "network"; the
case "round not reached yet", a release in hand, now opens, and four cases
are added: the same with a network source, a release of another round from
a network source, and two release objects of another chain. SpecVersion
stays 0.14 until the author approves the draft.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 days ago
|
|
|
package provider
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"bytes"
|
|
|
|
|
"encoding/hex"
|
|
|
|
|
"encoding/json"
|
|
|
|
|
"fmt"
|
|
|
|
|
|
|
|
|
|
datekeys "g.activething.com/go/DateKeys"
|
|
|
|
|
"g.activething.com/go/DateKeys/codec"
|
|
|
|
|
"g.activething.com/go/DateKeys/profile"
|
|
|
|
|
)
|
|
|
|
|
|
Spec v0.15 draft: remove the .dkr file
The author's decision of 7 October 2026. A release saved next to a capsule
cannot exist when the capsule is made, and once the date comes the capsule
can be opened: such a file only opens it again and does not cover the real
case, someone opening it decades later when drand is gone and nobody saved
anything. Long-term recovery rests instead on archives and cache services
that keep the releases of all rounds; a reader asks for its round and
verifies the signature against the pinned key.
Spec: the .dkr extension (section 20, back to v0.14), sections 1, 4, 8, 45,
47.1, 49, 50 (rewritten), 53, 62.1 (rule 28 removed, rule 26 reworded),
63, 70, 73, 74 (the datekeys.release .dkk extension dropped too), 76
(the v0.15 block, with the discarded design) and the annex 79. The
release object, the chain hash at step 10, step 9.c option B and the
archive format stay.
Code: decrypt -save-release and the command datekeys release are gone,
with writeRelease and their tests; decrypt -release FILE stays. The
release objects of testdata/releases are now <round>.cbor, and
TestVectorFilesAreCurrent fails on a file the generator no longer writes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 days ago
|
|
|
// Schema constants of the release object (spec v0.15, §47.1): the answer of
|
|
|
|
|
// the Release API, an entry of a Release Cache, and a release the caller
|
|
|
|
|
// gives from a file or from an archive.
|
Release object, release in hand and step 9.c option B (spec v0.15 draft)
The release of a round becomes a file, .dkr: a release object in
deterministic CBOR, {0: "datekeys-release", 1: 1, 2: chain_hash, 3: round,
4: signature}, which provider.EncodeRelease writes and DecodeRelease reads
with its layers (size, type and version, schema). provider.ParseRelease
also reads drand's JSON as the input of the caller. Verify checks the chain
hash a release names before its round and its signature, with
ERR_PROFILE_MISMATCH. provider.Archive reads a local release archive, the
informative format of the draft.
capsule.OpenOptions.Release takes a release in hand, a provider.Supplier,
exclusive with Source: Open does not compare it with the clock (step 9.c,
option B) and reports a clock behind it in Opened.ClockBehind; a network
source is still never asked before the round time. The CLI gains
decrypt -release FILE (.dkr, drand's JSON or a local archive),
decrypt -save-release FILE.dkr and the command release, which fetches,
verifies and saves the .dkr without opening the capsule.
Test data: vectors/release.json, releases/<round>.dkr for rounds 1000,
1001, 1004 and 2000, and a local archive of rounds 1000 to 1004. In
mutations.json every case says its source, "supplied" or "network"; the
case "round not reached yet", a release in hand, now opens, and four cases
are added: the same with a network source, a release of another round from
a network source, and two release objects of another chain. SpecVersion
stays 0.14 until the author approves the draft.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 days ago
|
|
|
const (
|
|
|
|
|
ReleaseTypeTag = "datekeys-release"
|
|
|
|
|
ReleaseSchemaVersion = 1
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// Limits of the release object (spec v0.15, §47.1). The object has no frame:
|
|
|
|
|
// a file or an input of more than MaxReleaseObjectSize bytes is rejected
|
|
|
|
|
// before it is decoded, with ErrNonCanonicalCBOR, and no valid encoding comes
|
|
|
|
|
// close to it. MaxSignatureLen is the longest compressed point of
|
|
|
|
|
// BLS12-381, one of G2; Quicknet signs with 48 bytes, a point of G1.
|
|
|
|
|
const (
|
|
|
|
|
MaxReleaseObjectSize = 1024
|
|
|
|
|
MaxSignatureLen = 96
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// MaxReleaseJSONSize bounds drand's JSON, which a reader accepts too as the
|
|
|
|
|
// input of the caller (spec v0.15, §47.1). It is the bound of a relay
|
|
|
|
|
// response in provider/drand.
|
|
|
|
|
const MaxReleaseJSONSize = 8 << 10
|
|
|
|
|
|
|
|
|
|
// releaseKeys is the number of keys of the release object, all required.
|
|
|
|
|
const releaseKeys = 5
|
|
|
|
|
|
|
|
|
|
// releaseWire is the CBOR map of the release object, keys 2 to 4; keys 0 and
|
|
|
|
|
// 1 are the constants ReleaseTypeTag and ReleaseSchemaVersion.
|
|
|
|
|
type releaseWire struct {
|
|
|
|
|
ChainHash []byte // key 2, 32 bytes
|
|
|
|
|
Round uint64 // key 3, 1..2^53-1
|
|
|
|
|
Signature []byte // key 4, 1..MaxSignatureLen bytes
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (w *releaseWire) encode(e *codec.Encoder) {
|
|
|
|
|
e.Map(releaseKeys)
|
|
|
|
|
e.Uint(0)
|
|
|
|
|
e.Text(ReleaseTypeTag)
|
|
|
|
|
e.Uint(1)
|
|
|
|
|
e.Uint(ReleaseSchemaVersion)
|
|
|
|
|
e.Uint(2)
|
|
|
|
|
e.Bstr(w.ChainHash)
|
|
|
|
|
e.Uint(3)
|
|
|
|
|
e.Uint(w.Round)
|
|
|
|
|
e.Uint(4)
|
|
|
|
|
e.Bstr(w.Signature)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// decode reads the map with every CDDL rule of the release object, all of
|
|
|
|
|
// them ErrNonCanonicalCBOR: what each field means against the pinned profile
|
|
|
|
|
// and the DateKey is checked by Verify, at step 10.
|
|
|
|
|
func (w *releaseWire) decode(d *codec.Decoder) error {
|
|
|
|
|
pairs, err := d.Map(releaseKeys)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if pairs != releaseKeys {
|
|
|
|
|
return fmt.Errorf("%d keys, want all %d: %w", pairs, releaseKeys, datekeys.ErrNonCanonicalCBOR)
|
|
|
|
|
}
|
|
|
|
|
for want := range uint64(releaseKeys) {
|
|
|
|
|
k, err := d.Key()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
if k != want {
|
|
|
|
|
return fmt.Errorf("key %d where key %d was expected: %w", k, want, datekeys.ErrNonCanonicalCBOR)
|
|
|
|
|
}
|
|
|
|
|
switch k {
|
|
|
|
|
case 0:
|
|
|
|
|
_, err = d.Text(len(ReleaseTypeTag))
|
|
|
|
|
case 1:
|
|
|
|
|
_, err = d.Uint(ReleaseSchemaVersion)
|
|
|
|
|
case 2:
|
|
|
|
|
w.ChainHash, err = d.Bstr(32, 32)
|
|
|
|
|
case 3:
|
|
|
|
|
if w.Round, err = d.Uint(codec.MaxSafeUint); err == nil && w.Round == 0 {
|
|
|
|
|
err = fmt.Errorf("round 0: %w", datekeys.ErrNonCanonicalCBOR)
|
|
|
|
|
}
|
|
|
|
|
case 4:
|
|
|
|
|
w.Signature, err = d.Bstr(1, MaxSignatureLen)
|
|
|
|
|
}
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("key %d: %w", k, err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return d.EndMap()
|
|
|
|
|
}
|
|
|
|
|
|
Spec v0.15 draft: remove the .dkr file
The author's decision of 7 October 2026. A release saved next to a capsule
cannot exist when the capsule is made, and once the date comes the capsule
can be opened: such a file only opens it again and does not cover the real
case, someone opening it decades later when drand is gone and nobody saved
anything. Long-term recovery rests instead on archives and cache services
that keep the releases of all rounds; a reader asks for its round and
verifies the signature against the pinned key.
Spec: the .dkr extension (section 20, back to v0.14), sections 1, 4, 8, 45,
47.1, 49, 50 (rewritten), 53, 62.1 (rule 28 removed, rule 26 reworded),
63, 70, 73, 74 (the datekeys.release .dkk extension dropped too), 76
(the v0.15 block, with the discarded design) and the annex 79. The
release object, the chain hash at step 10, step 9.c option B and the
archive format stay.
Code: decrypt -save-release and the command datekeys release are gone,
with writeRelease and their tests; decrypt -release FILE stays. The
release objects of testdata/releases are now <round>.cbor, and
TestVectorFilesAreCurrent fails on a file the generator no longer writes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 days ago
|
|
|
// EncodeRelease returns the release object of r (spec v0.15, §47.1): its
|
|
|
|
|
// chain hash, its round and its signature. It does
|
Release object, release in hand and step 9.c option B (spec v0.15 draft)
The release of a round becomes a file, .dkr: a release object in
deterministic CBOR, {0: "datekeys-release", 1: 1, 2: chain_hash, 3: round,
4: signature}, which provider.EncodeRelease writes and DecodeRelease reads
with its layers (size, type and version, schema). provider.ParseRelease
also reads drand's JSON as the input of the caller. Verify checks the chain
hash a release names before its round and its signature, with
ERR_PROFILE_MISMATCH. provider.Archive reads a local release archive, the
informative format of the draft.
capsule.OpenOptions.Release takes a release in hand, a provider.Supplier,
exclusive with Source: Open does not compare it with the clock (step 9.c,
option B) and reports a clock behind it in Opened.ClockBehind; a network
source is still never asked before the round time. The CLI gains
decrypt -release FILE (.dkr, drand's JSON or a local archive),
decrypt -save-release FILE.dkr and the command release, which fetches,
verifies and saves the .dkr without opening the capsule.
Test data: vectors/release.json, releases/<round>.dkr for rounds 1000,
1001, 1004 and 2000, and a local archive of rounds 1000 to 1004. In
mutations.json every case says its source, "supplied" or "network"; the
case "round not reached yet", a release in hand, now opens, and four cases
are added: the same with a network source, a release of another round from
a network source, and two release objects of another chain. SpecVersion
stays 0.14 until the author approves the draft.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 days ago
|
|
|
// not verify the release: Verify does, against the pinned profile.
|
|
|
|
|
func EncodeRelease(r Release) ([]byte, error) {
|
|
|
|
|
switch {
|
|
|
|
|
case len(r.ChainHash) != 32:
|
|
|
|
|
return nil, fmt.Errorf("provider: release object: chain hash of %d bytes, want 32: %w", len(r.ChainHash), datekeys.ErrNonCanonicalCBOR)
|
|
|
|
|
case r.Round == 0 || r.Round > codec.MaxSafeUint:
|
|
|
|
|
return nil, fmt.Errorf("provider: release object: round %d outside 1..%d: %w", r.Round, uint64(codec.MaxSafeUint), datekeys.ErrNonCanonicalCBOR)
|
|
|
|
|
case len(r.Signature) == 0 || len(r.Signature) > MaxSignatureLen:
|
|
|
|
|
return nil, fmt.Errorf("provider: release object: signature of %d bytes outside 1..%d: %w", len(r.Signature), MaxSignatureLen, datekeys.ErrNonCanonicalCBOR)
|
|
|
|
|
}
|
|
|
|
|
w := releaseWire{ChainHash: r.ChainHash, Round: r.Round, Signature: r.Signature}
|
|
|
|
|
var e codec.Encoder
|
|
|
|
|
w.encode(&e)
|
|
|
|
|
return e.Out()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// DecodeRelease decodes a release object (spec v0.15, §47.1) with the layers
|
|
|
|
|
// of spec §69.1 that it has: its size, at most MaxReleaseObjectSize bytes;
|
|
|
|
|
// its type and schema version (ErrNonCanonicalCBOR, then
|
|
|
|
|
// ErrUnsupportedVersion); its encoding and schema (ErrNonCanonicalCBOR). The
|
|
|
|
|
// release it returns names its chain, and Verify checks it against the pinned
|
|
|
|
|
// profile at step 10 of spec §63: the chain hash, the round, the signature.
|
|
|
|
|
func DecodeRelease(b []byte) (Release, error) {
|
|
|
|
|
if len(b) == 0 || len(b) > MaxReleaseObjectSize {
|
|
|
|
|
return Release{}, fmt.Errorf("provider: release object of %d bytes, outside 1..%d: %w", len(b), MaxReleaseObjectSize, datekeys.ErrNonCanonicalCBOR)
|
|
|
|
|
}
|
|
|
|
|
if err := codec.CheckSchema(b, ReleaseTypeTag, ReleaseSchemaVersion); err != nil {
|
|
|
|
|
return Release{}, fmt.Errorf("provider: release object: %w", err)
|
|
|
|
|
}
|
|
|
|
|
var w releaseWire
|
|
|
|
|
if err := codec.Unmarshal(b, w.decode, w.encode); err != nil {
|
|
|
|
|
return Release{}, fmt.Errorf("provider: release object: %w", err)
|
|
|
|
|
}
|
|
|
|
|
return Release{Round: w.Round, Signature: w.Signature, ChainHash: w.ChainHash}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ParseRelease reads a release that the caller supplies: drand's JSON, when
|
|
|
|
|
// its first byte other than a JSON space is "{", or else a release object
|
|
|
|
|
// (spec v0.15, §47.1), with DecodeRelease. drand's JSON is the answer of a
|
|
|
|
|
// relay, {"round": …, "signature": "…"}, with an optional "randomness" that
|
|
|
|
|
// must be SHA-256 of the signature; it does not name its chain, so the
|
|
|
|
|
// release has no chain hash, and any failure to read it is
|
|
|
|
|
// ErrReleaseInvalid. It is accepted as input, never written.
|
|
|
|
|
func ParseRelease(b []byte) (Release, error) {
|
|
|
|
|
if t := bytes.TrimLeft(b, " \t\r\n"); len(t) > 0 && t[0] == '{' {
|
|
|
|
|
return parseDrandJSON(b)
|
|
|
|
|
}
|
|
|
|
|
return DecodeRelease(b)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// parseDrandJSON reads the JSON of a drand relay.
|
|
|
|
|
func parseDrandJSON(b []byte) (Release, error) {
|
|
|
|
|
if len(b) > MaxReleaseJSONSize {
|
|
|
|
|
return Release{}, fmt.Errorf("provider: drand JSON of %d bytes, larger than %d: %w", len(b), MaxReleaseJSONSize, datekeys.ErrReleaseInvalid)
|
|
|
|
|
}
|
|
|
|
|
var wire struct {
|
|
|
|
|
Round *uint64 `json:"round"`
|
|
|
|
|
Signature *string `json:"signature"`
|
|
|
|
|
Randomness string `json:"randomness"`
|
|
|
|
|
}
|
|
|
|
|
if err := json.Unmarshal(b, &wire); err != nil || wire.Round == nil || wire.Signature == nil {
|
|
|
|
|
return Release{}, fmt.Errorf("provider: drand JSON: malformed, or without round or signature: %w", datekeys.ErrReleaseInvalid)
|
|
|
|
|
}
|
|
|
|
|
sig, err := hex.DecodeString(*wire.Signature)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return Release{}, fmt.Errorf("provider: drand JSON: signature is not hex: %w", datekeys.ErrReleaseInvalid)
|
|
|
|
|
}
|
|
|
|
|
if wire.Randomness != "" && !randomnessMatches(wire.Randomness, sig) {
|
|
|
|
|
return Release{}, fmt.Errorf("provider: drand JSON: randomness does not match the signature: %w", datekeys.ErrReleaseInvalid)
|
|
|
|
|
}
|
|
|
|
|
return Release{Round: *wire.Round, Signature: sig}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Supplier hands over a release that the caller has in hand (spec v0.15,
|
Spec v0.15 draft: remove the .dkr file
The author's decision of 7 October 2026. A release saved next to a capsule
cannot exist when the capsule is made, and once the date comes the capsule
can be opened: such a file only opens it again and does not cover the real
case, someone opening it decades later when drand is gone and nobody saved
anything. Long-term recovery rests instead on archives and cache services
that keep the releases of all rounds; a reader asks for its round and
verifies the signature against the pinned key.
Spec: the .dkr extension (section 20, back to v0.14), sections 1, 4, 8, 45,
47.1, 49, 50 (rewritten), 53, 62.1 (rule 28 removed, rule 26 reworded),
63, 70, 73, 74 (the datekeys.release .dkk extension dropped too), 76
(the v0.15 block, with the discarded design) and the annex 79. The
release object, the chain hash at step 10, step 9.c option B and the
archive format stay.
Code: decrypt -save-release and the command datekeys release are gone,
with writeRelease and their tests; decrypt -release FILE stays. The
release objects of testdata/releases are now <round>.cbor, and
TestVectorFilesAreCurrent fails on a file the generator no longer writes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 days ago
|
|
|
// §49, §63 step 9.c): a release object read from a file, drand's JSON that
|
|
|
|
|
// the person saved, or an entry of a local archive. It makes no network
|
Release object, release in hand and step 9.c option B (spec v0.15 draft)
The release of a round becomes a file, .dkr: a release object in
deterministic CBOR, {0: "datekeys-release", 1: 1, 2: chain_hash, 3: round,
4: signature}, which provider.EncodeRelease writes and DecodeRelease reads
with its layers (size, type and version, schema). provider.ParseRelease
also reads drand's JSON as the input of the caller. Verify checks the chain
hash a release names before its round and its signature, with
ERR_PROFILE_MISMATCH. provider.Archive reads a local release archive, the
informative format of the draft.
capsule.OpenOptions.Release takes a release in hand, a provider.Supplier,
exclusive with Source: Open does not compare it with the clock (step 9.c,
option B) and reports a clock behind it in Opened.ClockBehind; a network
source is still never asked before the round time. The CLI gains
decrypt -release FILE (.dkr, drand's JSON or a local archive),
decrypt -save-release FILE.dkr and the command release, which fetches,
verifies and saves the .dkr without opening the capsule.
Test data: vectors/release.json, releases/<round>.dkr for rounds 1000,
1001, 1004 and 2000, and a local archive of rounds 1000 to 1004. In
mutations.json every case says its source, "supplied" or "network"; the
case "round not reached yet", a release in hand, now opens, and four cases
are added: the same with a network source, a release of another round from
a network source, and two release objects of another chain. SpecVersion
stays 0.14 until the author approves the draft.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 days ago
|
|
|
// request, so capsule.Open asks it for the release without comparing its
|
|
|
|
|
// clock with the round time: a valid signature proves that the round was
|
|
|
|
|
// published.
|
|
|
|
|
//
|
|
|
|
|
// Supply returns the encoding of the release of c, as it is: a release
|
|
|
|
|
// object or drand's JSON, which capsule.Open decodes and verifies at step 10
|
|
|
|
|
// with the codes of that step. Without a release for c it returns an error
|
|
|
|
|
// that wraps datekeys.ErrReleaseUnavailable, the code of step 9.
|
|
|
|
|
type Supplier interface {
|
|
|
|
|
Supply(p *profile.Profile, c Condition) ([]byte, error)
|
|
|
|
|
}
|
|
|
|
|
|
Spec v0.15 draft: remove the .dkr file
The author's decision of 7 October 2026. A release saved next to a capsule
cannot exist when the capsule is made, and once the date comes the capsule
can be opened: such a file only opens it again and does not cover the real
case, someone opening it decades later when drand is gone and nobody saved
anything. Long-term recovery rests instead on archives and cache services
that keep the releases of all rounds; a reader asks for its round and
verifies the signature against the pinned key.
Spec: the .dkr extension (section 20, back to v0.14), sections 1, 4, 8, 45,
47.1, 49, 50 (rewritten), 53, 62.1 (rule 28 removed, rule 26 reworded),
63, 70, 73, 74 (the datekeys.release .dkk extension dropped too), 76
(the v0.15 block, with the discarded design) and the annex 79. The
release object, the chain hash at step 10, step 9.c option B and the
archive format stay.
Code: decrypt -save-release and the command datekeys release are gone,
with writeRelease and their tests; decrypt -release FILE stays. The
release objects of testdata/releases are now <round>.cbor, and
TestVectorFilesAreCurrent fails on a file the generator no longer writes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 days ago
|
|
|
// Encoded is a release in hand, already read: the bytes of a release object
|
|
|
|
|
// or of drand's JSON. It supplies itself whatever the condition; step 10 compares
|
Release object, release in hand and step 9.c option B (spec v0.15 draft)
The release of a round becomes a file, .dkr: a release object in
deterministic CBOR, {0: "datekeys-release", 1: 1, 2: chain_hash, 3: round,
4: signature}, which provider.EncodeRelease writes and DecodeRelease reads
with its layers (size, type and version, schema). provider.ParseRelease
also reads drand's JSON as the input of the caller. Verify checks the chain
hash a release names before its round and its signature, with
ERR_PROFILE_MISMATCH. provider.Archive reads a local release archive, the
informative format of the draft.
capsule.OpenOptions.Release takes a release in hand, a provider.Supplier,
exclusive with Source: Open does not compare it with the clock (step 9.c,
option B) and reports a clock behind it in Opened.ClockBehind; a network
source is still never asked before the round time. The CLI gains
decrypt -release FILE (.dkr, drand's JSON or a local archive),
decrypt -save-release FILE.dkr and the command release, which fetches,
verifies and saves the .dkr without opening the capsule.
Test data: vectors/release.json, releases/<round>.dkr for rounds 1000,
1001, 1004 and 2000, and a local archive of rounds 1000 to 1004. In
mutations.json every case says its source, "supplied" or "network"; the
case "round not reached yet", a release in hand, now opens, and four cases
are added: the same with a network source, a release of another round from
a network source, and two release objects of another chain. SpecVersion
stays 0.14 until the author approves the draft.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 days ago
|
|
|
// its round with the DateKey.
|
|
|
|
|
type Encoded []byte
|
|
|
|
|
|
|
|
|
|
// Supply implements Supplier.
|
|
|
|
|
func (e Encoded) Supply(*profile.Profile, Condition) ([]byte, error) { return e, nil }
|
|
|
|
|
|
|
|
|
|
// NewReleaseObject returns the release object of a release of the profile p,
|
Spec v0.15 draft: remove the .dkr file
The author's decision of 7 October 2026. A release saved next to a capsule
cannot exist when the capsule is made, and once the date comes the capsule
can be opened: such a file only opens it again and does not cover the real
case, someone opening it decades later when drand is gone and nobody saved
anything. Long-term recovery rests instead on archives and cache services
that keep the releases of all rounds; a reader asks for its round and
verifies the signature against the pinned key.
Spec: the .dkr extension (section 20, back to v0.14), sections 1, 4, 8, 45,
47.1, 49, 50 (rewritten), 53, 62.1 (rule 28 removed, rule 26 reworded),
63, 70, 73, 74 (the datekeys.release .dkk extension dropped too), 76
(the v0.15 block, with the discarded design) and the annex 79. The
release object, the chain hash at step 10, step 9.c option B and the
archive format stay.
Code: decrypt -save-release and the command datekeys release are gone,
with writeRelease and their tests; decrypt -release FILE stays. The
release objects of testdata/releases are now <round>.cbor, and
TestVectorFilesAreCurrent fails on a file the generator no longer writes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 days ago
|
|
|
// with the chain hash of p: what a Release Cache or an archive stores, or
|
|
|
|
|
// the Release API serves, after verifying the release (spec v0.15, §45,
|
|
|
|
|
// §47, §47.1).
|
Release object, release in hand and step 9.c option B (spec v0.15 draft)
The release of a round becomes a file, .dkr: a release object in
deterministic CBOR, {0: "datekeys-release", 1: 1, 2: chain_hash, 3: round,
4: signature}, which provider.EncodeRelease writes and DecodeRelease reads
with its layers (size, type and version, schema). provider.ParseRelease
also reads drand's JSON as the input of the caller. Verify checks the chain
hash a release names before its round and its signature, with
ERR_PROFILE_MISMATCH. provider.Archive reads a local release archive, the
informative format of the draft.
capsule.OpenOptions.Release takes a release in hand, a provider.Supplier,
exclusive with Source: Open does not compare it with the clock (step 9.c,
option B) and reports a clock behind it in Opened.ClockBehind; a network
source is still never asked before the round time. The CLI gains
decrypt -release FILE (.dkr, drand's JSON or a local archive),
decrypt -save-release FILE.dkr and the command release, which fetches,
verifies and saves the .dkr without opening the capsule.
Test data: vectors/release.json, releases/<round>.dkr for rounds 1000,
1001, 1004 and 2000, and a local archive of rounds 1000 to 1004. In
mutations.json every case says its source, "supplied" or "network"; the
case "round not reached yet", a release in hand, now opens, and four cases
are added: the same with a network source, a release of another round from
a network source, and two release objects of another chain. SpecVersion
stays 0.14 until the author approves the draft.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 days ago
|
|
|
func NewReleaseObject(p *profile.Profile, r Release) ([]byte, error) {
|
|
|
|
|
r.ChainHash = p.ChainHash[:]
|
|
|
|
|
return EncodeRelease(r)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// chainHashHex is the chain hash of a release in the text of an error.
|
|
|
|
|
func chainHashHex(b []byte) string { return hex.EncodeToString(b) }
|