niko_trust/pkg/address/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

203 lines
6.1 KiB
Go

package address_test
import (
"crypto/ed25519"
"strings"
"testing"
"git.n1ko.dev/Niko/niko_trust/pkg/address"
)
func seedCorpus(f *testing.F) {
f.Helper()
seed := make([]byte, ed25519.SeedSize)
for i := 0; i < 4; i++ {
for j := range seed {
seed[j] = byte(i*7 + j)
}
pub := ed25519.NewKeyFromSeed(seed).Public().(ed25519.PublicKey)
a, err := address.FromPubKey(pub)
if err != nil {
f.Fatal(err)
}
f.Add(a.String())
}
for _, s := range []string{
"",
"trust",
"trust1",
"trust11",
"trust1qqqqqqqq",
"TRUST1QQQQQQQQ",
"trust1qqak5faue6m2gttz5w5dq2n0p4ek2vs4wuw7ysax8tqy3gvtt8dzj0yfahr",
"trust1qqak5faue6m2gttz5w5dq2n0p4ek2vs4wuw7ysax8tqy3gvtt8dzj0yfahR",
"bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4",
"trust1\x00\xff",
"trust1" + strings.Repeat("q", 200),
"1qqqqqq",
"trust1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq",
"\x00\x00\x00\x00\x00\x00\x00\x00",
} {
f.Add(s)
}
}
// FuzzParse asserts that Parse is total: for arbitrary input it returns
// either a fully valid Address or an error, and never panics, hangs or
// allocates unboundedly.
//
// Every Address it does return must satisfy the package's invariants, which is
// what makes "a non-zero Address is always well formed" a safe assumption for
// the rest of the program.
func FuzzParse(f *testing.F) {
seedCorpus(f)
f.Fuzz(func(t *testing.T, s string) {
a, err := address.Parse(s)
if err != nil {
if !a.IsZero() {
t.Fatalf("Parse returned an address alongside an error")
}
return
}
// Property 1: an accepted address is exactly the input. If Parse ever
// normalised its input, two strings would denote one identity and
// INV-8 would be violated.
if a.String() != s {
t.Fatalf("Parse normalised input: got %q, input %q", a.String(), s)
}
// Property 2: the key inside is always valid.
if err := address.ValidatePubKey(a.PubKey()); err != nil {
t.Fatalf("accepted address carries an invalid key: %v", err)
}
// Property 3: re-encoding the recovered key reproduces the address
// byte for byte. Encode and Decode are inverse on the accepted set.
re, err := address.FromPubKey(a.PubKey())
if err != nil {
t.Fatalf("could not re-encode a key recovered from a valid address: %v", err)
}
if re.String() != s {
t.Fatalf("encode(decode(x)) != x:\n got %q\nwant %q", re.String(), s)
}
// Property 4: parsing is idempotent and stable.
again, err := address.Parse(a.String())
if err != nil || !again.Equal(a) {
t.Fatalf("Parse is not idempotent: %v", err)
}
// Property 5: shape guarantees the rest of the code may rely on.
if len(s) != address.EncodedLen {
t.Fatalf("accepted address of length %d, want %d", len(s), address.EncodedLen)
}
if !strings.HasPrefix(s, address.HRP+"1") {
t.Fatalf("accepted address without the trust1 prefix")
}
if strings.ToLower(s) != s {
t.Fatalf("accepted a non-lowercase address")
}
if a.Version() != address.Version0 {
t.Fatalf("accepted unknown version %d", a.Version())
}
})
}
// FuzzRoundTrip asserts the property in the other direction: every valid key
// encodes to a string that parses back to the same key.
func FuzzRoundTrip(f *testing.F) {
f.Add(make([]byte, ed25519.SeedSize))
f.Add([]byte("0123456789abcdef0123456789abcdef"))
f.Fuzz(func(t *testing.T, seed []byte) {
if len(seed) != ed25519.SeedSize {
return
}
pub := ed25519.NewKeyFromSeed(seed).Public().(ed25519.PublicKey)
a, err := address.FromPubKey(pub)
if err != nil {
// Only a degenerate key may be refused, and a key derived from a
// seed through the standard construction is never degenerate.
t.Fatalf("valid derived key rejected: %v", err)
}
back, err := address.Parse(a.String())
if err != nil {
t.Fatalf("own output rejected by Parse: %v", err)
}
if !ed25519.PublicKey(back.PubKey()).Equal(pub) {
t.Fatalf("key not preserved through the round trip")
}
if back.KeyBytes() != a.KeyBytes() {
t.Fatalf("KeyBytes differ after round trip")
}
})
}
// FuzzValidatePubKey asserts that key validation is total and agrees with
// itself: it never panics on arbitrary bytes, and a key it accepts is always
// canonical, meaning it survives an encode/decode cycle unchanged.
func FuzzValidatePubKey(f *testing.F) {
f.Add(make([]byte, 32))
f.Add(make([]byte, 31))
f.Add([]byte{})
pub, _, _ := ed25519.GenerateKey(nil)
f.Add([]byte(pub))
f.Fuzz(func(t *testing.T, key []byte) {
err := address.ValidatePubKey(key)
if err != nil {
return
}
if len(key) != ed25519.PublicKeySize {
t.Fatalf("accepted a key of length %d", len(key))
}
// An accepted key must be usable as an address and recoverable.
a, aerr := address.FromPubKey(key)
if aerr != nil {
t.Fatalf("ValidatePubKey accepted a key FromPubKey rejected: %v", aerr)
}
got := a.PubKey()
for i := range key {
if key[i] != got[i] {
t.Fatalf("key altered by the address round trip at byte %d", i)
}
}
// Validation must be deterministic.
if err2 := address.ValidatePubKey(key); err2 != nil {
t.Fatalf("validation is not deterministic: %v", err2)
}
})
}
// FuzzNoAliasingBetweenAddresses asserts the injectivity property that matters
// for delivery: two different accepted addresses never denote the same key.
// If they could, an approval request addressed to one identity could be
// answered by another.
func FuzzNoAliasingBetweenAddresses(f *testing.F) {
a1, _ := address.FromPubKey(ed25519.NewKeyFromSeed(make([]byte, 32)).Public().(ed25519.PublicKey))
seed2 := make([]byte, 32)
seed2[0] = 1
a2, _ := address.FromPubKey(ed25519.NewKeyFromSeed(seed2).Public().(ed25519.PublicKey))
f.Add(a1.String(), a2.String())
f.Fuzz(func(t *testing.T, s1, s2 string) {
x, err1 := address.Parse(s1)
y, err2 := address.Parse(s2)
if err1 != nil || err2 != nil {
return
}
sameString := s1 == s2
sameKey := x.KeyBytes() == y.KeyBytes()
if sameKey != sameString {
t.Fatalf("address/key correspondence is not one to one:\n%q\n%q\nsameKey=%v sameString=%v",
s1, s2, sameKey, sameString)
}
if x.Equal(y) != sameString {
t.Fatalf("Equal disagrees with string identity")
}
})
}