niko_trust/internal/identity/invariants_test.go
Niko Marmeladkov e23a3cab59 refactor: drop PoW, checkpoint chain, gossip, light node and BFT; keep WS
The decentralization stack overcomplicated the project. Removed:
internal/pow, internal/smt, internal/checkpoint, internal/bft,
internal/lightnode, cmd/lightnode, relay gossip/checkpoint/proof/BFT
endpoints, their docs, vectors and the blake3 dependency.

Kept: WebSocket streaming on the relay, the full protocol v1 object set
including DelegationClaim (0x07) and KeyRotation request/confirm
(0x08/0x09) with chain resolution in verify.Graph, TrustedIssuers,
batch fetch, stable cursor pagination, per-type metrics.

INV-1 reverts to its original form: the relay holds no keys again.
Everything removed remains reachable at commit 20cc52c.
2026-08-26 00:42:53 +03:00

204 lines
6.7 KiB
Go

package identity_test
import (
"os"
"os/exec"
"strings"
"testing"
)
// Invariant tests.
//
// These check properties of the source tree itself rather than of a running
// program. They exist because INV-1 and INV-9 are structural claims: they
// cannot be established by testing behaviour, only by showing that certain
// code is absent and that certain packages are not linked together.
//
// Expressing them as tests means a violation fails the build instead of
// silently contradicting a comment.
// requireToolchain skips the calling test when the Go toolchain and the module
// source are not available.
//
// These tests inspect the source tree, so they are meaningful only when run
// from a checkout with a working toolchain. A cross-compiled test binary
// executed on another machine, for example the Windows build run under an
// emulator, has neither, and must skip rather than report a failure that says
// nothing about the code.
func requireToolchain(t *testing.T) {
t.Helper()
if _, err := exec.LookPath("go"); err != nil {
t.Skip("go toolchain not available; source-tree invariants are checked on the build host")
}
if _, err := os.Stat("invariants_test.go"); err != nil {
t.Skip("module source not available; source-tree invariants are checked on the build host")
}
}
// goList runs `go list` and returns its output lines.
func goList(t *testing.T, args ...string) []string {
t.Helper()
cmd := exec.Command("go", append([]string{"list"}, args...)...)
cmd.Env = append(cmd.Environ(), "GOPROXY=off", "GOFLAGS=-mod=mod")
out, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("go list %v: %v\n%s", args, err, out)
}
var lines []string
for _, l := range strings.Split(string(out), "\n") {
if l = strings.TrimSpace(l); l != "" {
lines = append(lines, l)
}
}
return lines
}
const modulePath = "git.n1ko.dev/Niko/niko_trust"
// TestProtocolLayerImports enforces INV-9.
//
// The protocol layer must depend on nothing but the standard library and
// vetted cryptographic primitives. If a protocol package ever imported the
// storage, transport or application layer, application semantics would leak
// into the definition of what a signed object means, and the layering that the
// whole design rests on would be gone.
func TestProtocolLayerImports(t *testing.T) {
requireToolchain(t)
// Only vetted cryptographic and encoding primitives are permitted, along
// with their own subpackages.
allowedExternalPrefixes := []string{
"github.com/btcsuite/btcd/btcutil/bech32",
"filippo.io/edwards25519",
}
allowedExternal := func(dep string) bool {
for _, p := range allowedExternalPrefixes {
if dep == p || strings.HasPrefix(dep, p+"/") {
return true
}
}
// Standard library internal packages are versioned in recent
// toolchains (for example crypto/internal/entropy/v1.0.0), which puts
// a dot in the path even though they are part of the stdlib.
return strings.HasPrefix(dep, "crypto/internal/") ||
strings.HasPrefix(dep, "internal/")
}
protocolPackages := []string{
modulePath + "/internal/address",
modulePath + "/internal/identity",
}
for _, pkg := range protocolPackages {
t.Run(pkg, func(t *testing.T) {
for _, dep := range goList(t, "-deps", pkg) {
switch {
case !strings.Contains(dep, "."):
// No dot in the first path element means stdlib.
continue
case allowedExternal(dep):
continue
case strings.HasPrefix(dep, modulePath+"/internal/address"),
strings.HasPrefix(dep, modulePath+"/internal/identity"):
// Protocol packages may depend on each other.
continue
case strings.HasPrefix(dep, "internal/"),
strings.HasPrefix(dep, "vendor/"),
strings.HasPrefix(dep, "golang.org/x/sys"),
strings.HasPrefix(dep, "golang.org/x/crypto"):
// Toolchain-internal and transitive runtime support.
continue
default:
t.Errorf("protocol package %s depends on %s, which is outside the protocol layer", pkg, dep)
}
}
})
}
}
// TestProtocolDoesNotImportSigner enforces the client/server split behind
// INV-1.
//
// The signer package is the only place a private key exists. Nothing in the
// protocol layer may reach it, so that a server binary built from the protocol
// and transport layers cannot contain signing capability even by accident.
func TestProtocolDoesNotImportSigner(t *testing.T) {
requireToolchain(t)
signerPkg := modulePath + "/internal/identity/signer"
for _, pkg := range []string{
modulePath + "/internal/address",
modulePath + "/internal/identity",
} {
for _, dep := range goList(t, "-deps", pkg) {
if dep == signerPkg {
t.Errorf("%s imports %s: the protocol layer must not have signing capability", pkg, signerPkg)
}
}
}
}
// TestNoSigningOutsideSigner enforces INV-1 at the source level: the ability
// to produce an Ed25519 signature must exist in exactly one package: the
// client-only signer. A relay that starts signing anything fails here the
// moment the code is written.
func TestNoSigningOutsideSigner(t *testing.T) {
requireToolchain(t)
files := goList(t, "-f",
`{{$d := .Dir}}{{range .GoFiles}}{{$d}}/{{.}}{{"\n"}}{{end}}`,
modulePath+"/...")
signingAPIs := []string{
"ed25519.Sign(",
"ed25519.GenerateKey(",
"ed25519.NewKeyFromSeed(",
}
for _, file := range files {
if strings.Contains(file, "/internal/identity/signer/") {
continue
}
data, err := readFile(file)
if err != nil {
t.Fatalf("read %s: %v", file, err)
}
for _, api := range signingAPIs {
if strings.Contains(data, api) {
t.Errorf("%s uses %s outside the signer package; "+
"private key operations must stay in internal/identity/signer", file, api)
}
}
}
}
// TestNoApplicationSemantics enforces INV-5 and INV-9 from the other
// direction: the protocol layer must not know what any claim key means.
//
// The trust server is a relay for opaque statements. The moment a package
// branches on "minecraft.op" or grants a "permission", it has started making
// authorization decisions, which is exactly what the design forbids.
func TestNoApplicationSemantics(t *testing.T) {
requireToolchain(t)
files := goList(t, "-f",
`{{$d := .Dir}}{{range .GoFiles}}{{$d}}/{{.}}{{"\n"}}{{end}}`,
modulePath+"/...")
forbidden := []string{
"minecraft", "ssh.login", "server.restart",
"isAdmin", "IsAdmin", "hasPermission", "HasPermission",
"isAuthorized", "IsAuthorized", "checkPermission", "CheckPermission",
}
for _, file := range files {
data, err := readFile(file)
if err != nil {
t.Fatalf("read %s: %v", file, err)
}
lower := strings.ToLower(data)
for _, term := range forbidden {
if strings.Contains(lower, strings.ToLower(term)) {
t.Errorf("%s mentions %q: the trust layer must not contain "+
"application-specific semantics or authorization logic", file, term)
}
}
}
}