You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
DateKeys/provider/provider_test.go

84 lines
3.9 KiB

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"
)
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
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},
// 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},
{"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
}

Powered by TurnKey Linux.