From fe564931cb92ff3caff16e331bccc08e1f65a556 Mon Sep 17 00:00:00 2001 From: Virgil Date: Thu, 2 Apr 2026 03:38:34 +0000 Subject: [PATCH] feat(dns): add service-level name verification --- lns.go | 12 ++++++++++++ lns_test.go | 21 +++++++++++++++++++++ pkg/dns/resolve.go | 13 +++++++++++++ pkg/dns/resolve_test.go | 21 +++++++++++++++++++++ 4 files changed, 67 insertions(+) diff --git a/lns.go b/lns.go index e7fb3c6..4be643e 100644 --- a/lns.go +++ b/lns.go @@ -56,3 +56,15 @@ func (s *Service) Resolve(name any) (primitives.Hash, error) { return dns.NewService(opts...).Resolve(name) } + +// Verify reports whether a .lthn name is valid after canonicalisation. +// +// This keeps the service-level API aligned with the DNS resolver helpers. +func (s *Service) Verify(name any) bool { + opts := make([]dns.ServiceOption, 0, 1) + if s != nil && s.ServiceRuntime != nil { + opts = append(opts, dns.WithCore(s.Core())) + } + + return dns.NewService(opts...).Verify(name) +} diff --git a/lns_test.go b/lns_test.go index 4ddab4f..74d785a 100644 --- a/lns_test.go +++ b/lns_test.go @@ -54,3 +54,24 @@ func TestServiceResolveRejectsInvalidNames(t *testing.T) { t.Fatal("Resolve should reject unsupported input types") } } + +func TestServiceVerify(t *testing.T) { + svc := &Service{} + + cases := []struct { + name any + ok bool + }{ + {name: "Foo-Bar.lthn", ok: true}, + {name: []byte("Foo-Bar.lthn"), ok: true}, + {name: "foo.bar.lthn", ok: false}, + {name: "foo-", ok: false}, + {name: 123, ok: false}, + } + + for _, tc := range cases { + if got := svc.Verify(tc.name); got != tc.ok { + t.Fatalf("Verify(%v) = %v, want %v", tc.name, got, tc.ok) + } + } +} diff --git a/pkg/dns/resolve.go b/pkg/dns/resolve.go index 1db8ef1..1fc9def 100644 --- a/pkg/dns/resolve.go +++ b/pkg/dns/resolve.go @@ -73,6 +73,19 @@ func (s *Service) Resolve(name any) (primitives.Hash, error) { return covenant.HashString(normalized) } +// Verify reports whether a .lthn name is valid after canonicalisation. +// +// This mirrors Resolve's input handling but returns a boolean so callers can +// validate user input without allocating or hashing. +func (s *Service) Verify(name any) bool { + normalized, ok := canonicalizeName(name) + if !ok { + return false + } + + return covenant.VerifyString(normalized) +} + func canonicalizeName(name any) (string, bool) { var value string diff --git a/pkg/dns/resolve_test.go b/pkg/dns/resolve_test.go index 215b2fc..de6abce 100644 --- a/pkg/dns/resolve_test.go +++ b/pkg/dns/resolve_test.go @@ -54,3 +54,24 @@ func TestResolveRejectsInvalidNames(t *testing.T) { t.Fatal("Resolve should reject unsupported input types") } } + +func TestVerify(t *testing.T) { + svc := NewService() + + cases := []struct { + name any + ok bool + }{ + {name: "Foo-Bar.lthn", ok: true}, + {name: []byte("Foo-Bar.lthn"), ok: true}, + {name: "foo.bar.lthn", ok: false}, + {name: "foo-", ok: false}, + {name: 123, ok: false}, + } + + for _, tc := range cases { + if got := svc.Verify(tc.name); got != tc.ok { + t.Fatalf("Verify(%v) = %v, want %v", tc.name, got, tc.ok) + } + } +}