From 581ceecbf046f99b39885c74e2780a5320e5b15e Mon Sep 17 00:00:00 2001 From: bndw Date: Fri, 13 Feb 2026 17:35:32 -0800 Subject: feat: add Nostr protocol implementation (internal/nostr, internal/websocket) --- internal/nostr/bech32_test.go | 139 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 internal/nostr/bech32_test.go (limited to 'internal/nostr/bech32_test.go') diff --git a/internal/nostr/bech32_test.go b/internal/nostr/bech32_test.go new file mode 100644 index 0000000..fb1260b --- /dev/null +++ b/internal/nostr/bech32_test.go @@ -0,0 +1,139 @@ +package nostr + +import ( + "bytes" + "encoding/hex" + "testing" +) + +func TestBech32Encode(t *testing.T) { + // Test vector: 32 bytes of data + data, _ := hex.DecodeString("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef") + + encoded, err := Bech32Encode("npub", data) + if err != nil { + t.Fatalf("Bech32Encode() error = %v", err) + } + + if encoded[:5] != "npub1" { + t.Errorf("Encoded string should start with 'npub1', got %s", encoded[:5]) + } + + // Decode it back + hrp, decoded, err := Bech32Decode(encoded) + if err != nil { + t.Fatalf("Bech32Decode() error = %v", err) + } + + if hrp != "npub" { + t.Errorf("HRP = %s, want npub", hrp) + } + + if !bytes.Equal(decoded, data) { + t.Errorf("Round-trip failed: got %x, want %x", decoded, data) + } +} + +func TestBech32EncodeNsec(t *testing.T) { + data, _ := hex.DecodeString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef") + + encoded, err := Bech32Encode("nsec", data) + if err != nil { + t.Fatalf("Bech32Encode() error = %v", err) + } + + if encoded[:5] != "nsec1" { + t.Errorf("Encoded string should start with 'nsec1', got %s", encoded[:5]) + } + + // Decode it back + hrp, decoded, err := Bech32Decode(encoded) + if err != nil { + t.Fatalf("Bech32Decode() error = %v", err) + } + + if hrp != "nsec" { + t.Errorf("HRP = %s, want nsec", hrp) + } + + if !bytes.Equal(decoded, data) { + t.Errorf("Round-trip failed") + } +} + +func TestBech32DecodeErrors(t *testing.T) { + tests := []struct { + name string + input string + }{ + {"no separator", "npubabcdef"}, + {"empty data", "npub1"}, + {"invalid character", "npub1o"}, // 'o' is not in bech32 alphabet + {"invalid checksum", "npub1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqpqqqqq"}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + _, _, err := Bech32Decode(tt.input) + if err == nil { + t.Error("Bech32Decode() expected error, got nil") + } + }) + } +} + +func TestBech32KnownVectors(t *testing.T) { + // Test with known nostr npub/nsec values + // These can be verified with other nostr tools + + // Generate a key and verify round-trip + key, err := GenerateKey() + if err != nil { + t.Fatalf("GenerateKey() error = %v", err) + } + + npub := key.Npub() + nsec := key.Nsec() + + // Verify npub decodes to public key + hrp, pubBytes, err := Bech32Decode(npub) + if err != nil { + t.Fatalf("Bech32Decode(npub) error = %v", err) + } + if hrp != "npub" { + t.Errorf("npub HRP = %s, want npub", hrp) + } + if hex.EncodeToString(pubBytes) != key.Public() { + t.Error("npub does not decode to correct public key") + } + + // Verify nsec decodes to private key + hrp, privBytes, err := Bech32Decode(nsec) + if err != nil { + t.Fatalf("Bech32Decode(nsec) error = %v", err) + } + if hrp != "nsec" { + t.Errorf("nsec HRP = %s, want nsec", hrp) + } + if hex.EncodeToString(privBytes) != key.Private() { + t.Error("nsec does not decode to correct private key") + } +} + +func TestBech32CaseInsensitive(t *testing.T) { + data, _ := hex.DecodeString("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef") + encoded, _ := Bech32Encode("npub", data) + + // Test uppercase + upper := "NPUB1" + encoded[5:] + hrp, decoded, err := Bech32Decode(upper) + if err != nil { + t.Fatalf("Bech32Decode(uppercase) error = %v", err) + } + if hrp != "npub" { + t.Errorf("HRP = %s, want npub", hrp) + } + if !bytes.Equal(decoded, data) { + t.Error("Uppercase decode failed") + } +} -- cgit v1.2.3