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_test
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"bytes"
|
|
|
|
|
"errors"
|
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
|
|
datekeys "g.activething.com/go/DateKeys"
|
|
|
|
|
"g.activething.com/go/DateKeys/internal/testkit"
|
|
|
|
|
"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
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func TestVerifyPublishedReleases(t *testing.T) {
|
|
|
|
|
p := profile.Quicknet()
|
|
|
|
|
for _, round := range testkit.Rounds {
|
|
|
|
|
if err := provider.Verify(p, provider.Condition{Round: round}, testkit.Release(round)); err != nil {
|
|
|
|
|
t.Fatalf("round %d: %v", round, err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestVerifyRejects(t *testing.T) {
|
|
|
|
|
p := profile.Quicknet()
|
|
|
|
|
r1000, r1001 := testkit.Release(1000), testkit.Release(1001)
|
Spec v0.8.2 amendment: canonical point encoding; no library error text
Amendment of the unreleased v0.8.2, recorded in §76 with its case: the
second implementation's phase-2 research found that tlock-js over
@noble/curves 1.9.7 accepts U re-encoded as c0 + p and a signature
x + p and returns the same file key, while the reference rejects both
(noble 1.9.7 differed from kilic on 5,615 of 41,686 encodings), and the
spec did not say which encodings are valid.
- §12.2 defines the canonical encoding of a BLS12-381 point (drand's
compressed ZCash form) and requires decoders to reject every other
byte string; §12.1 applies it to public_key.
- §63 step 10 applies it to the release signature (ERR_RELEASE_INVALID)
and step 11 defines the tlock stanza body U || V || W (96 + 16 + 16
bytes for Quicknet) with a canonical, non-infinity U (ERR_INTEGRITY).
- §64 gains ten mutations, exported to mutations.json (65 cases). The
signature x + p case uses published Quicknet round 1004, the first
after 1000 whose x allows x + p < 2^381. The reference already gave
every stated code and step.
Errors no longer copy text from tlock, kyber, age, drand or
kyber-bls12381. kyber's IBE error carried the candidate plaintext and r,
and with one bit of W flipped the message disclosed the real tlock file
key with that bit flipped. Every such place now uses a fixed reason with
its normative sentinel; TestTlockFailureDiagnosticsCarryNoSecrets fails
with the old wrapping.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 weeks ago
|
|
|
xPlusP, err := testkit.AddModulus(testkit.Release(testkit.XPlusPRound).Signature, 0)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatal(err)
|
|
|
|
|
}
|
|
|
|
|
uncompressed := bytes.Clone(r1000.Signature)
|
|
|
|
|
uncompressed[0] &^= testkit.FlagCompressed
|
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 _, tc := range []struct {
|
|
|
|
|
name string
|
|
|
|
|
cond uint64
|
|
|
|
|
rel provider.Release
|
|
|
|
|
want error
|
|
|
|
|
}{
|
|
|
|
|
// Spec §17: a valid signature of another round is not enough.
|
|
|
|
|
{"valid release of another round", 1000, r1001, datekeys.ErrRoundMismatch},
|
Spec v0.8.2 refinements: error precedence, trust model, strict order
Approved refinements, each recorded with its reproducible case in the
§76 v0.8.2 subsection:
- §69.1: layered error model with normative precedence (frame, type tag
and version, CBOR profile and CDDL, then fields with their own code in
ascending key order; across steps the §63 order decides), with a scope
paragraph for the optional steps 5, 6 and 8.
- §55.1: normative trust table per section (who can write it, from which
step it is bound, what it never proves); §72: security-relevant claims
go in CONTROL_CBOR or under a signature, .dkk data is advisory.
- §31/§54: extension arrays in strictly ascending unsigned byte order of
extension_id (one rule for order and uniqueness).
- Gaps a second implementation needed: §28.1 malformed age headers,
§15/§19 latest unlock time and dk1_ reading rules, §22/§23/§57 length
lower bounds, §63 step 8 tlock argument comparison and step 9 order,
§12.1 profile validation with the drand chain-hash formula, §74 table
of implementation limits.
Reference alignment: .dkk errors only at step 9.a (new
OpenOptions.AccessKeyFile, used by the CLI), CR/LF in dk1_ is
ERR_DATEKEY_INVALID, BODY_LEN 0 is ERR_INTEGRITY, nil identities are not
credentials, and AccessIdentity tries every identity on every stanza so
its verdict does not depend on their order. dk1.json gains three
vectors; every other testdata file is byte-identical.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 weeks ago
|
|
|
// Spec §63 step 10: the round is compared before the signature.
|
|
|
|
|
{"another round and a short signature", 1000, provider.Release{Round: 1001, Signature: r1001.Signature[:47]}, datekeys.ErrRoundMismatch},
|
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
|
|
|
// A signature of round 1001 relabelled as round 1000.
|
|
|
|
|
{"signature of another round relabelled", 1000, provider.Release{Round: 1000, Signature: r1001.Signature}, datekeys.ErrReleaseInvalid},
|
|
|
|
|
{"all-zero signature", 1000, provider.Release{Round: 1000, Signature: make([]byte, 48)}, datekeys.ErrReleaseInvalid},
|
|
|
|
|
{"flipped bit", 1000, provider.Release{Round: 1000, Signature: flip(r1000.Signature)}, datekeys.ErrReleaseInvalid},
|
|
|
|
|
{"short signature", 1000, provider.Release{Round: 1000, Signature: r1000.Signature[:47]}, datekeys.ErrReleaseInvalid},
|
|
|
|
|
{"G2-sized signature", 1000, provider.Release{Round: 1000, Signature: bytes.Repeat(r1000.Signature, 2)}, datekeys.ErrReleaseInvalid},
|
Spec v0.8.2 amendment: canonical point encoding; no library error text
Amendment of the unreleased v0.8.2, recorded in §76 with its case: the
second implementation's phase-2 research found that tlock-js over
@noble/curves 1.9.7 accepts U re-encoded as c0 + p and a signature
x + p and returns the same file key, while the reference rejects both
(noble 1.9.7 differed from kilic on 5,615 of 41,686 encodings), and the
spec did not say which encodings are valid.
- §12.2 defines the canonical encoding of a BLS12-381 point (drand's
compressed ZCash form) and requires decoders to reject every other
byte string; §12.1 applies it to public_key.
- §63 step 10 applies it to the release signature (ERR_RELEASE_INVALID)
and step 11 defines the tlock stanza body U || V || W (96 + 16 + 16
bytes for Quicknet) with a canonical, non-infinity U (ERR_INTEGRITY).
- §64 gains ten mutations, exported to mutations.json (65 cases). The
signature x + p case uses published Quicknet round 1004, the first
after 1000 whose x allows x + p < 2^381. The reference already gave
every stated code and step.
Errors no longer copy text from tlock, kyber, age, drand or
kyber-bls12381. kyber's IBE error carried the candidate plaintext and r,
and with one bit of W flipped the message disclosed the real tlock file
key with that bit flipped. Every such place now uses a fixed reason with
its normative sentinel; TestTlockFailureDiagnosticsCarryNoSecrets fails
with the old wrapping.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 weeks ago
|
|
|
// Spec §12.2, §63 step 10: the canonical encoding of a point of G1
|
|
|
|
|
// other than the point at infinity. For a decoder that reduces x
|
|
|
|
|
// modulo p, x + p is the published signature of its round.
|
|
|
|
|
{"signature re-encoded with x + p", testkit.XPlusPRound, provider.Release{Round: testkit.XPlusPRound, Signature: xPlusP}, datekeys.ErrReleaseInvalid},
|
|
|
|
|
{"signature the point at infinity", 1000, provider.Release{Round: 1000, Signature: testkit.Infinity(48)}, datekeys.ErrReleaseInvalid},
|
|
|
|
|
{"infinity flag and a payload", 1000, provider.Release{Round: 1000, Signature: testkit.InfinityWithPayload(r1000.Signature)}, datekeys.ErrReleaseInvalid},
|
|
|
|
|
{"point at infinity with the sort flag", 1000, provider.Release{Round: 1000, Signature: testkit.Negated(testkit.Infinity(48))}, datekeys.ErrReleaseInvalid},
|
|
|
|
|
{"compression flag cleared", 1000, provider.Release{Round: 1000, Signature: uncompressed}, datekeys.ErrReleaseInvalid},
|
|
|
|
|
{"negated signature", 1000, provider.Release{Round: 1000, Signature: testkit.Negated(r1000.Signature)}, datekeys.ErrReleaseInvalid},
|
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
|
|
|
{"round zero", 0, provider.Release{Round: 0, Signature: r1000.Signature}, datekeys.ErrDateKeyInvalid},
|
|
|
|
|
{"round beyond the profile", p.MaxRound() + 1, provider.Release{Round: p.MaxRound() + 1, Signature: r1000.Signature}, datekeys.ErrDateKeyInvalid},
|
|
|
|
|
} {
|
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
|
|
|
if err := provider.Verify(p, provider.Condition{Round: tc.cond}, tc.rel); !errors.Is(err, tc.want) {
|
|
|
|
|
t.Fatalf("got %v, want %v", err, tc.want)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestVerifyUsesThePinnedKeyOnly(t *testing.T) {
|
|
|
|
|
// A profile with another public key rejects the genuine signature.
|
|
|
|
|
p := profile.Quicknet()
|
|
|
|
|
p.PublicKey = append([]byte(nil), p.PublicKey...)
|
|
|
|
|
p.PublicKey[len(p.PublicKey)-1] ^= 1
|
|
|
|
|
err := provider.Verify(p, provider.Condition{Round: 1000}, testkit.Release(1000))
|
|
|
|
|
if err == nil {
|
|
|
|
|
t.Fatal("verified under a different key")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func flip(b []byte) []byte {
|
|
|
|
|
c := append([]byte(nil), b...)
|
|
|
|
|
c[10] ^= 0x01
|
|
|
|
|
return c
|
|
|
|
|
}
|