niko_trust/pkg/identity/fuzz_test.go
Niko Marmeladkov 3bf13fa488 Public SDK packages, proxy-aware rate limits, service login recipe
- internal/{address,identity,protocol,tce,transport,verify} -> pkg/ so
  external Go projects can import the verified core; invariant tests
  updated for the new paths
- Config.TrustProxy: key rate limiting by X-Forwarded-For when the relay
  sits behind a reverse proxy (off by default, header never trusted
  otherwise)
- examples/service + examples/approve: complete passwordless login round
  trip (mint request -> wallet approves -> local verify), run live in CI
- docs/SERVICE-GUIDE.md: the integration recipe
2026-08-26 12:49:54 +03:00

124 lines
3.5 KiB
Go

package identity_test
import (
"crypto/ed25519"
"testing"
"git.n1ko.dev/Niko/niko_trust/pkg/identity"
"git.n1ko.dev/Niko/niko_trust/pkg/identity/signer"
)
// FuzzVerifyNeverPanics asserts that verification is total over arbitrary
// message and signature bytes, and that it never accepts anything the signer
// did not actually sign.
func FuzzVerifyNeverPanics(f *testing.F) {
s, err := signer.Generate()
if err != nil {
f.Fatal(err)
}
msg := []byte("seed message")
f.Add(msg, s.Sign(msg))
f.Add([]byte{}, []byte{})
f.Add([]byte("x"), make([]byte, 64))
f.Add(make([]byte, 1024), make([]byte, 63))
id := s.Identity()
f.Fuzz(func(t *testing.T, msg, sig []byte) {
ok := id.Verify(msg, sig)
if ok != (id.VerifyErr(msg, sig) == nil) {
t.Fatal("Verify and VerifyErr disagree")
}
if !ok {
return
}
// An accepted signature must be exactly 64 bytes and must be the one
// the signer produces for this message. Finding any other accepted
// pair would mean a forgery.
if len(sig) != ed25519.SignatureSize {
t.Fatalf("accepted a %d-byte signature", len(sig))
}
expected := s.Sign(msg)
if string(expected) != string(sig) {
t.Fatalf("accepted a signature the signer would not produce\nmsg=%x\nsig=%x", msg, sig)
}
})
}
// FuzzSignVerifyRoundTrip asserts that every message a signer signs verifies
// under its own identity and under no other.
func FuzzSignVerifyRoundTrip(f *testing.F) {
f.Add([]byte("hello"), make([]byte, ed25519.SeedSize))
f.Add([]byte(""), []byte("0123456789abcdef0123456789abcdef"))
other, err := signer.Generate()
if err != nil {
f.Fatal(err)
}
f.Fuzz(func(t *testing.T, msg, seed []byte) {
if len(seed) != ed25519.SeedSize {
return
}
s, err := signer.FromSeed(seed)
if err != nil {
t.Fatalf("FromSeed rejected a %d-byte seed: %v", len(seed), err)
}
sig := s.Sign(msg)
if !s.Identity().Verify(msg, sig) {
t.Fatal("a signature did not verify under its own identity")
}
if other.Identity().Verify(msg, sig) {
t.Fatal("a signature verified under a foreign identity")
}
// Signing is deterministic for Ed25519, which the protocol relies on
// when comparing objects for equality.
if string(s.Sign(msg)) != string(sig) {
t.Fatal("signing is not deterministic")
}
// Any single-bit change to the message must break verification.
if len(msg) > 0 {
bad := make([]byte, len(msg))
copy(bad, msg)
bad[0] ^= 0x80
if s.Identity().Verify(bad, sig) {
t.Fatal("verified a modified message")
}
}
})
}
// FuzzIdentityParse asserts that identity parsing inherits every guarantee of
// address parsing and never yields a usable identity from invalid text.
func FuzzIdentityParse(f *testing.F) {
s, _ := signer.Generate()
f.Add(s.Identity().String())
f.Add("")
f.Add("trust1")
f.Add("trust1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq")
f.Fuzz(func(t *testing.T, str string) {
id, err := identity.Parse(str)
if err != nil {
if !id.IsZero() {
t.Fatal("an error result carried a non-zero identity")
}
return
}
if id.IsZero() {
t.Fatal("a successful parse produced the zero identity")
}
if id.String() != str {
t.Fatalf("parse normalised input: %q -> %q", str, id.String())
}
// A parsed identity must never verify a signature that was not made
// for it. It has no private key anywhere in the process, so nothing
// should verify except by astronomically unlikely chance.
if id.Verify([]byte("arbitrary"), make([]byte, ed25519.SignatureSize)) {
t.Fatal("a parsed identity verified an all-zero signature")
}
})
}