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") } }) }