test all dns nip-05 and fix bugs

This commit is contained in:
2025-06-26 17:16:47 +01:00
parent 1cf2094634
commit fc462031d7
4 changed files with 433 additions and 11 deletions

View File

@@ -5,6 +5,7 @@ package dns
import (
"encoding/json"
"fmt"
"io"
"net/http"
"regexp"
"strings"
@@ -118,12 +119,15 @@ func Fetch(c context.T, account string) (resp *WellKnownResponse,
}
defer func() { _ = res.Body.Close() }()
resp = NewWellKnownResponse()
b := make([]byte, 65535)
var n int
if n, err = res.Body.Read(b); chk.E(err) {
// Read the entire response body
var b []byte
if b, err = io.ReadAll(res.Body); chk.E(err) {
err = errorf.E("failed to read response body: %w", err)
return
}
b = b[:n]
// Unmarshal the JSON response
if err = json.Unmarshal(b, resp); chk.E(err) {
err = errorf.E("failed to decode json response: %w", err)
}

View File

@@ -0,0 +1,207 @@
package dns
import (
"bytes"
"testing"
)
func TestIsValidIdentifier(t *testing.T) {
tests := []struct {
name string
input string
expected bool
}{
{"Valid with name", "user@example.com", true},
{"Valid domain only", "example.com", true},
{"Valid with subdomain", "user@sub.example.com", true},
{"Valid with plus", "user+tag@example.com", true},
{"Valid with underscore", "user_name@example.com", true},
{"Invalid no domain", "user@", false},
{"Invalid special chars", "user!@example.com", false},
{"Invalid format", "not-an-email", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := IsValidIdentifier(tt.input)
if result != tt.expected {
t.Errorf("IsValidIdentifier(%q) = %v, want %v", tt.input, result, tt.expected)
}
})
}
}
func TestNormalizeIdentifier(t *testing.T) {
tests := []struct {
name string
input string
expected string
}{
{"Already normalized", "example.com", "example.com"},
{"With underscore prefix", "_@example.com", "example.com"},
{"With name", "user@example.com", "user@example.com"},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := NormalizeIdentifier(tt.input)
if result != tt.expected {
t.Errorf("NormalizeIdentifier(%q) = %q, want %q", tt.input, result, tt.expected)
}
})
}
}
func TestStringSliceToByteSlice(t *testing.T) {
tests := []struct {
name string
input []string
expected [][]byte
}{
{
"Empty slice",
[]string{},
[][]byte{},
},
{
"Single item",
[]string{"test"},
[][]byte{[]byte("test")},
},
{
"Multiple items",
[]string{"test1", "test2", "test3"},
[][]byte{[]byte("test1"), []byte("test2"), []byte("test3")},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := StringSliceToByteSlice(tt.input)
if len(result) != len(tt.expected) {
t.Fatalf("StringSliceToByteSlice(%v) returned slice of length %d, want %d",
tt.input, len(result), len(tt.expected))
}
for i, v := range result {
if !bytes.Equal(v, tt.expected[i]) {
t.Errorf("StringSliceToByteSlice(%v)[%d] = %v, want %v",
tt.input, i, v, tt.expected[i])
}
}
})
}
}
func TestParseIdentifierEdgeCases(t *testing.T) {
tests := []struct {
name string
input string
expectedName string
expectedDomain string
expectError bool
}{
{"Empty string", "", "", "", true},
{"Just @", "@", "", "", true},
{"Multiple @", "user@domain@example.com", "", "", true},
{"Invalid domain", "user@invalid", "", "", true},
{"Valid with hyphen", "user-name@example.com", "user-name", "example.com", false},
{"Valid with numbers", "user123@example123.com", "user123", "example123.com", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
name, domain, err := ParseIdentifier(tt.input)
if tt.expectError && err == nil {
t.Errorf("ParseIdentifier(%q) expected error, got nil", tt.input)
return
}
if !tt.expectError && err != nil {
t.Errorf("ParseIdentifier(%q) unexpected error: %v", tt.input, err)
return
}
if !tt.expectError {
if name != tt.expectedName {
t.Errorf("ParseIdentifier(%q) name = %q, want %q", tt.input, name, tt.expectedName)
}
if domain != tt.expectedDomain {
t.Errorf("ParseIdentifier(%q) domain = %q, want %q", tt.input, domain, tt.expectedDomain)
}
}
})
}
}
func TestNewWellKnownResponse(t *testing.T) {
resp := NewWellKnownResponse()
if resp == nil {
t.Fatal("NewWellKnownResponse() returned nil")
}
if resp.Names == nil {
t.Error("NewWellKnownResponse() Names map is nil")
}
if resp.Relays == nil {
t.Error("NewWellKnownResponse() Relays map is nil")
}
if resp.NIP46 == nil {
t.Error("NewWellKnownResponse() NIP46 map is nil")
}
// Test that we can add entries to the maps
resp.Names["test"] = "pubkey"
resp.Relays["pubkey"] = []string{"relay1", "relay2"}
resp.NIP46["pubkey"] = []string{"nip46url"}
if resp.Names["test"] != "pubkey" {
t.Error("Failed to add entry to Names map")
}
if len(resp.Relays["pubkey"]) != 2 {
t.Error("Failed to add entry to Relays map")
}
if len(resp.NIP46["pubkey"]) != 1 {
t.Error("Failed to add entry to NIP46 map")
}
}
func TestRegexPattern(t *testing.T) {
// Test the Nip05Regex pattern directly
tests := []struct {
name string
input string
expected bool
}{
{"Valid email format", "user@example.com", true},
{"Valid domain only", "example.com", true},
{"Valid subdomain", "user@sub.example.com", true},
{"Valid with underscore", "user_name@example.com", true},
{"Valid with hyphen", "user-name@example.com", true},
{"Valid with plus", "user+tag@example.com", true},
{"Valid with numbers", "user123@example123.com", true},
{"Invalid no TLD", "user@localhost", false},
{"Valid IP address", "user@127.0.0.1", true},
{"Invalid with port", "user@example.com:8080", false},
{"Invalid special chars", "user!@example.com", false},
{"Invalid multiple @", "user@domain@example.com", false},
{"Empty string", "", false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := Nip05Regex.MatchString(tt.input)
if result != tt.expected {
t.Errorf("Nip05Regex.MatchString(%q) = %v, want %v", tt.input, result, tt.expected)
}
})
}
}

View File

@@ -59,20 +59,34 @@ func SecretBytesToPubKeyHex(skb []byte) (pk string, err error) {
// IsValid32ByteHex checks that a hex string is a valid 32 bytes lower case hex encoded value as
// per nostr NIP-01 spec.
func IsValid32ByteHex[V []byte | string](pk V) bool {
if bytes.Equal(bytes.ToLower([]byte(pk)), []byte(pk)) {
pkBytes := []byte(pk)
// Check if the input is lowercase
if !bytes.Equal(bytes.ToLower(pkBytes), pkBytes) {
return false
}
var err error
dec := make([]byte, 32)
if _, err = hex.DecBytes(dec, []byte(pk)); chk.E(err) {
// Check if the input length is exactly 64 characters (32 bytes * 2)
if len(pkBytes) != 64 {
return false
}
return len(dec) == 32
// Try to decode the hex string
var err error
if _, err = hex.Dec(string(pkBytes)); err != nil {
return false
}
return true
}
// IsValidPublicKey checks that a hex encoded public key is a valid BIP-340 public key.
func IsValidPublicKey[V []byte | string](pk V) bool {
v, _ := hex.Dec(string(pk))
_, err := schnorr.ParsePubKey(v)
v, err := hex.Dec(string(pk))
if err != nil {
return false
}
_, err = schnorr.ParsePubKey(v)
return err == nil
}

197
keys/keys_test.go Normal file
View File

@@ -0,0 +1,197 @@
package keys
import (
"testing"
)
func TestIsValid32ByteHex(t *testing.T) {
tests := []struct {
name string
input string
expected bool
}{
{
"Valid lowercase hex 32 bytes",
"3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d",
true,
},
{
"Valid lowercase hex 32 bytes with different value",
"f9dd6a762506260b38a2d3e5b464213c2e47fa3877429fe9ee60e071a31a07d7",
true,
},
{
"Invalid uppercase hex",
"3BF0C63FCB93463407AF97A5E5EE64FA883D107EF9E558472C4EB9AAAEFA459D",
false,
},
{
"Invalid mixed case hex",
"3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459D",
false,
},
{
"Invalid too short",
"3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa45",
false,
},
{
"Invalid too long",
"3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d00",
false,
},
{
"Invalid non-hex characters",
"3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459g",
false,
},
{
"Empty string",
"",
false,
},
{
"Invalid special characters",
"3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa45!@",
false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := IsValid32ByteHex(tt.input)
if result != tt.expected {
t.Errorf("IsValid32ByteHex(%q) = %v, want %v", tt.input, result, tt.expected)
}
})
}
}
func TestIsValid32ByteHexWithBytes(t *testing.T) {
tests := []struct {
name string
input []byte
expected bool
}{
{
"Valid lowercase hex bytes",
[]byte("3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d"),
true,
},
{
"Invalid uppercase hex bytes",
[]byte("3BF0C63FCB93463407AF97A5E5EE64FA883D107EF9E558472C4EB9AAAEFA459D"),
false,
},
{
"Empty bytes",
[]byte(""),
false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := IsValid32ByteHex(tt.input)
if result != tt.expected {
t.Errorf("IsValid32ByteHex(%q) = %v, want %v", string(tt.input), result, tt.expected)
}
})
}
}
func TestIsValidPublicKey(t *testing.T) {
tests := []struct {
name string
input string
expected bool
}{
{
"Valid public key",
"3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d",
true,
},
{
"Another valid public key",
"f9dd6a762506260b38a2d3e5b464213c2e47fa3877429fe9ee60e071a31a07d7",
true,
},
{
"Invalid too short",
"3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa45",
false,
},
{
"Invalid too long",
"3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d00",
false,
},
{
"Invalid non-hex",
"not-a-hex-string",
false,
},
{
"Empty string",
"",
false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := IsValidPublicKey(tt.input)
if result != tt.expected {
t.Errorf("IsValidPublicKey(%q) = %v, want %v", tt.input, result, tt.expected)
}
})
}
}
func TestHexPubkeyToBytes(t *testing.T) {
tests := []struct {
name string
input string
expectError bool
expectedLen int
}{
{
"Valid hex string",
"3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d",
false,
32,
},
{
"Invalid hex string",
"not-hex",
true,
0,
},
{
"Empty string",
"",
true,
0,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := HexPubkeyToBytes(tt.input)
if tt.expectError && err == nil {
t.Errorf("HexPubkeyToBytes(%q) expected error, got nil", tt.input)
return
}
if !tt.expectError && err != nil {
t.Errorf("HexPubkeyToBytes(%q) unexpected error: %v", tt.input, err)
return
}
if !tt.expectError && len(result) != tt.expectedLen {
t.Errorf("HexPubkeyToBytes(%q) returned %d bytes, want %d", tt.input, len(result), tt.expectedLen)
}
})
}
}