Add covenant name validation helpers

This commit is contained in:
Virgil 2026-04-02 01:29:54 +00:00
parent 6e2b4a125a
commit 9bfec37eca
2 changed files with 205 additions and 0 deletions

112
pkg/covenant/name.go Normal file
View file

@ -0,0 +1,112 @@
// SPDX-License-Identifier: EUPL-1.2
package covenant
import (
"crypto/sha3"
core "dappco.re/go/core"
"dappco.re/go/lns/pkg/primitives"
)
const maxNameSize = 63
var blacklist = map[string]struct{}{
"example": {},
"invalid": {},
"local": {},
"localhost": {},
"test": {},
}
// VerifyString reports whether a domain name meets the covenant rules.
func VerifyString(name string) bool {
if len(name) == 0 || len(name) > maxNameSize {
return false
}
for i := 0; i < len(name); i++ {
ch := name[i]
if ch&0x80 != 0 {
return false
}
switch {
case ch >= '0' && ch <= '9':
case ch >= 'A' && ch <= 'Z':
return false
case ch >= 'a' && ch <= 'z':
case ch == '-' || ch == '_':
if i == 0 || i == len(name)-1 {
return false
}
default:
return false
}
}
_, blocked := blacklist[name]
return !blocked
}
// VerifyBinary reports whether a binary domain name meets the covenant rules.
func VerifyBinary(name []byte) bool {
if len(name) == 0 || len(name) > maxNameSize {
return false
}
for i := 0; i < len(name); i++ {
ch := name[i]
if ch&0x80 != 0 {
return false
}
switch {
case ch >= '0' && ch <= '9':
case ch >= 'A' && ch <= 'Z':
return false
case ch >= 'a' && ch <= 'z':
case ch == '-' || ch == '_':
if i == 0 || i == len(name)-1 {
return false
}
default:
return false
}
}
_, blocked := blacklist[string(name)]
return !blocked
}
// VerifyName reports whether a name meets the covenant rules.
func VerifyName(name []byte) bool {
return VerifyBinary(name)
}
// HashString hashes a validated domain name.
func HashString(name string) (primitives.Hash, error) {
if !VerifyString(name) {
return primitives.Hash{}, core.E("covenant.HashString", "invalid name", nil)
}
sum := sha3.Sum256([]byte(name))
return primitives.Hash(sum), nil
}
// HashBinary hashes a validated domain name.
func HashBinary(name []byte) (primitives.Hash, error) {
if !VerifyBinary(name) {
return primitives.Hash{}, core.E("covenant.HashBinary", "invalid name", nil)
}
sum := sha3.Sum256(name)
return primitives.Hash(sum), nil
}
// HashName hashes a validated domain name.
func HashName(name []byte) (primitives.Hash, error) {
return HashBinary(name)
}

93
pkg/covenant/name_test.go Normal file
View file

@ -0,0 +1,93 @@
// SPDX-License-Identifier: EUPL-1.2
package covenant
import (
"crypto/sha3"
"testing"
)
func TestVerifyString(t *testing.T) {
cases := []struct {
name string
ok bool
}{
{name: "example", ok: false},
{name: "invalid", ok: false},
{name: "localhost", ok: false},
{name: "test", ok: false},
{name: "", ok: false},
{name: "foo", ok: true},
{name: "foo-bar", ok: true},
{name: "foo_bar", ok: true},
{name: "-foo", ok: false},
{name: "foo-", ok: false},
{name: "_foo", ok: false},
{name: "foo_", ok: false},
{name: "Foo", ok: false},
{name: "f\x7fo", ok: false},
}
for _, tc := range cases {
if got := VerifyString(tc.name); got != tc.ok {
t.Fatalf("VerifyString(%q) = %v, want %v", tc.name, got, tc.ok)
}
}
}
func TestVerifyBinary(t *testing.T) {
cases := []struct {
name []byte
ok bool
}{
{name: nil, ok: false},
{name: []byte("example"), ok: false},
{name: []byte("foo"), ok: true},
{name: []byte("foo-bar"), ok: true},
{name: []byte("foo_bar"), ok: true},
{name: []byte("-foo"), ok: false},
{name: []byte("foo-"), ok: false},
{name: []byte("Foo"), ok: false},
{name: []byte{0xff}, ok: false},
}
for _, tc := range cases {
if got := VerifyBinary(tc.name); got != tc.ok {
t.Fatalf("VerifyBinary(%q) = %v, want %v", tc.name, got, tc.ok)
}
}
}
func TestHashString(t *testing.T) {
got, err := HashString("foo-bar")
if err != nil {
t.Fatalf("HashString returned error: %v", err)
}
want := sha3.Sum256([]byte("foo-bar"))
if got != want {
t.Fatalf("HashString returned %x, want %x", got, want)
}
}
func TestHashBinary(t *testing.T) {
got, err := HashBinary([]byte("foo_bar"))
if err != nil {
t.Fatalf("HashBinary returned error: %v", err)
}
want := sha3.Sum256([]byte("foo_bar"))
if got != want {
t.Fatalf("HashBinary returned %x, want %x", got, want)
}
}
func TestHashRejectsInvalidName(t *testing.T) {
if _, err := HashString("Foo"); err == nil {
t.Fatal("HashString should reject invalid names")
}
if _, err := HashBinary([]byte("foo-")); err == nil {
t.Fatal("HashBinary should reject invalid names")
}
}