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/capsule/mutation_test.go

115 lines
3.4 KiB

package capsule_test
import (
"bytes"
"context"
"testing"
"g.activething.com/go/DateKeys/capsule"
"g.activething.com/go/DateKeys/extension"
"g.activething.com/go/DateKeys/internal/testkit"
)
const mutationsFile = "../testdata/vectors/mutations.json"
// build returns a capsule made by testkit.Build and the options to open it.
func build(t *testing.T, b testkit.Build) ([]byte, capsule.OpenOptions) {
t.Helper()
if b.Plaintext == nil {
b.Plaintext = []byte("malicious creator")
}
out, err := b.Make()
if err != nil {
t.Fatal(err)
}
return out.DKC, capsule.OpenOptions{Registry: testkit.Registry(), Source: testkit.NewSource(testkit.Release(1000)), Now: testkit.Fixed(testkit.Genesis().AddDate(1, 0, 0))}
}
// Spec §64: every mutation of the corpus (testkit.Mutations), built afresh,
// fails with its exact normative error at its exact step, and a failure
// before the release request causes no request.
func TestMutationCorpus(t *testing.T) {
e, err := testkit.NewMutationEnv(fixtureDir)
if err != nil {
t.Fatal(err)
}
n := 0
for _, m := range testkit.Mutations() {
if m.Spec {
n++
}
t.Run(m.Name, func(t *testing.T) {
in, err := m.Make(e)
if err != nil {
t.Fatal(err)
}
if err := m.Check(in); err != nil {
t.Fatal(err)
}
})
}
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
if n != 33 {
t.Fatalf("spec §64 lists 33 mutations, the corpus has %d", n)
}
}
// The exported corpus, testdata/vectors/mutations.json, holds every mutation
// of the corpus in order, and replaying each case from the file alone gives
// exactly the recorded error and step. The capsules that are not built with
// randomness are the ones the corpus derives from the fixtures.
func TestExportedMutationCorpus(t *testing.T) {
var f testkit.MutationFile
if err := testkit.ReadJSON(mutationsFile, &f); err != nil {
t.Fatal(err)
}
e, err := testkit.NewMutationEnv(fixtureDir)
if err != nil {
t.Fatal(err)
}
muts := testkit.Mutations()
if len(f.Cases) != len(muts) {
t.Fatalf("%d exported cases, the corpus has %d mutations", len(f.Cases), len(muts))
}
for i, c := range f.Cases {
m := muts[i]
t.Run(c.Name, func(t *testing.T) {
if c.Name != m.Name || c.Spec != m.Spec || c.Error != m.Want.Code() || c.Step != m.Step || c.Network != m.Network || c.Frozen != m.Random {
t.Fatalf("exported case %+v does not match mutation %q", c, m.Name)
}
if err := c.Check(fixtureDir); err != nil {
t.Fatal(err)
}
if m.Random {
if c.DKC.Base != "" {
t.Fatal("a frozen capsule must not depend on a fixture")
}
return
}
want, err := m.Make(e)
if err != nil {
t.Fatal(err)
}
got, err := c.Input(fixtureDir)
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(got.DKC, want.DKC) || !bytes.Equal(got.DKK, want.DKK) {
t.Fatal("the exported capsule or .dkk differs from the one the mutation derives")
}
})
}
}
// Known critical extensions are accepted when the application declares them.
func TestKnownCriticalExtensions(t *testing.T) {
crit := []extension.Extension{{ID: "org.example.must-understand", Version: 1}}
for _, b := range []testkit.Build{{HeaderCritical: crit}, {ControlCritical: crit}} {
dkc, o := build(t, b)
o.Extensions = extension.Set{"org.example.must-understand": {1}}
var out bytes.Buffer
if _, err := capsule.Open(context.Background(), &out, bytes.NewReader(dkc), o); err != nil || out.String() != "malicious creator" {
t.Fatalf("known critical extension rejected: %v", err)
}
}
}

Powered by TurnKey Linux.