From cecc71cd81a8fb913a564af3e324d568112ea8ba Mon Sep 17 00:00:00 2001
From: fiatjaf <fiatjaf@gmail.com>
Date: Wed, 12 Mar 2025 00:17:01 -0300
Subject: [PATCH] fix and improve envelope stuff again, deal with messages as
 strings on all envelope parsing steps.

---
 envelopes.go                | 104 +++++++++++++++++-------------------
 envelopes_benchmark_test.go |  11 ++--
 envelopes_default.go        |  36 +++++++------
 envelopes_sonic.go          |   4 +-
 envelopes_test.go           |  44 +++++++--------
 helpers.go                  |  25 +++++----
 helpers_test.go             |   6 +--
 nip77/envelopes.go          |  36 +++++++------
 nip77/idsonly.go            |   2 +-
 nip77/nip77.go              |   2 +-
 relay.go                    |  21 ++++----
 11 files changed, 151 insertions(+), 140 deletions(-)

diff --git a/envelopes.go b/envelopes.go
index c75402c..c2a52c9 100644
--- a/envelopes.go
+++ b/envelopes.go
@@ -1,81 +1,75 @@
 package nostr
 
 import (
-	"bytes"
 	"encoding/hex"
 	"errors"
 	"fmt"
 	"strconv"
+	"strings"
+	"unsafe"
 
 	"github.com/mailru/easyjson"
 	jwriter "github.com/mailru/easyjson/jwriter"
 	"github.com/tidwall/gjson"
 )
 
-var (
-	labelEvent  = []byte("EVENT")
-	labelReq    = []byte("REQ")
-	labelCount  = []byte("COUNT")
-	labelNotice = []byte("NOTICE")
-	labelEose   = []byte("EOSE")
-	labelOk     = []byte("OK")
-	labelAuth   = []byte("AUTH")
-	labelClosed = []byte("CLOSED")
-	labelClose  = []byte("CLOSE")
-
-	UnknownLabel = errors.New("unknown envelope label")
-)
+var UnknownLabel = errors.New("unknown envelope label")
 
 type MessageParser interface {
 	// ParseMessage parses a message into an Envelope.
-	ParseMessage([]byte) (Envelope, error)
+	ParseMessage(string) (Envelope, error)
 }
 
 // Deprecated: use NewMessageParser instead
-func ParseMessage(message []byte) Envelope {
-	firstComma := bytes.Index(message, []byte{','})
-	if firstComma == -1 {
+func ParseMessage(message string) Envelope {
+	firstQuote := strings.IndexRune(message, '"')
+	if firstQuote == -1 {
 		return nil
 	}
-	label := message[0:firstComma]
+	secondQuote := strings.IndexRune(message[firstQuote+1:], '"')
+	if secondQuote == -1 {
+		return nil
+	}
+	label := message[firstQuote+1 : firstQuote+1+secondQuote]
 
 	var v Envelope
-	switch {
-	case bytes.Contains(label, labelEvent):
+	switch label {
+	case "EVENT":
 		v = &EventEnvelope{}
-	case bytes.Contains(label, labelReq):
+	case "REQ":
 		v = &ReqEnvelope{}
-	case bytes.Contains(label, labelCount):
+	case "COUNT":
 		v = &CountEnvelope{}
-	case bytes.Contains(label, labelNotice):
+	case "NOTICE":
 		x := NoticeEnvelope("")
 		v = &x
-	case bytes.Contains(label, labelEose):
+	case "EOSE":
 		x := EOSEEnvelope("")
 		v = &x
-	case bytes.Contains(label, labelOk):
+	case "OK":
 		v = &OKEnvelope{}
-	case bytes.Contains(label, labelAuth):
+	case "AUTH":
 		v = &AuthEnvelope{}
-	case bytes.Contains(label, labelClosed):
+	case "CLOSED":
 		v = &ClosedEnvelope{}
-	case bytes.Contains(label, labelClose):
+	case "CLOSE":
 		x := CloseEnvelope("")
 		v = &x
 	default:
 		return nil
 	}
 
-	if err := v.UnmarshalJSON(message); err != nil {
+	if err := v.FromJSON(message); err != nil {
 		return nil
 	}
+
 	return v
 }
 
 // Envelope is the interface for all nostr message envelopes.
 type Envelope interface {
 	Label() string
-	UnmarshalJSON([]byte) error
+	FromJSON(string) error
 	MarshalJSON() ([]byte, error)
 	String() string
 }
@@ -99,15 +93,15 @@ type EventEnvelope struct {
 
 func (_ EventEnvelope) Label() string { return "EVENT" }
 
-func (v *EventEnvelope) UnmarshalJSON(data []byte) error {
-	r := gjson.ParseBytes(data)
+func (v *EventEnvelope) FromJSON(data string) error {
+	r := gjson.Parse(data)
 	arr := r.Array()
 	switch len(arr) {
 	case 2:
-		return easyjson.Unmarshal([]byte(arr[1].Raw), &v.Event)
+		return easyjson.Unmarshal(unsafe.Slice(unsafe.StringData(arr[1].Raw), len(arr[1].Raw)), &v.Event)
 	case 3:
 		v.SubscriptionID = &arr[1].Str
-		return easyjson.Unmarshal([]byte(arr[2].Raw), &v.Event)
+		return easyjson.Unmarshal(unsafe.Slice(unsafe.StringData(arr[2].Raw), len(arr[2].Raw)), &v.Event)
 	default:
 		return fmt.Errorf("failed to decode EVENT envelope")
 	}
@@ -134,8 +128,8 @@ type ReqEnvelope struct {
 
 func (_ ReqEnvelope) Label() string { return "REQ" }
 
-func (v *ReqEnvelope) UnmarshalJSON(data []byte) error {
-	r := gjson.ParseBytes(data)
+func (v *ReqEnvelope) FromJSON(data string) error {
+	r := gjson.Parse(data)
 	arr := r.Array()
 	if len(arr) < 3 {
 		return fmt.Errorf("failed to decode REQ envelope: missing filters")
@@ -144,7 +138,7 @@ func (v *ReqEnvelope) UnmarshalJSON(data []byte) error {
 	v.Filters = make(Filters, len(arr)-2)
 	f := 0
 	for i := 2; i < len(arr); i++ {
-		if err := easyjson.Unmarshal([]byte(arr[i].Raw), &v.Filters[f]); err != nil {
+		if err := easyjson.Unmarshal(unsafe.Slice(unsafe.StringData(arr[i].Raw), len(arr[i].Raw)), &v.Filters[f]); err != nil {
 			return fmt.Errorf("%w -- on filter %d", err, f)
 		}
 		f++
@@ -180,8 +174,8 @@ func (c CountEnvelope) String() string {
 	return string(v)
 }
 
-func (v *CountEnvelope) UnmarshalJSON(data []byte) error {
-	r := gjson.ParseBytes(data)
+func (v *CountEnvelope) FromJSON(data string) error {
+	r := gjson.Parse(data)
 	arr := r.Array()
 	if len(arr) < 3 {
 		return fmt.Errorf("failed to decode COUNT envelope: missing filters")
@@ -192,7 +186,7 @@ func (v *CountEnvelope) UnmarshalJSON(data []byte) error {
 		Count *int64 `json:"count"`
 		HLL   string `json:"hll"`
 	}
-	if err := json.Unmarshal([]byte(arr[2].Raw), &countResult); err == nil && countResult.Count != nil {
+	if err := json.Unmarshal(unsafe.Slice(unsafe.StringData(arr[2].Raw), len(arr[2].Raw)), &countResult); err == nil && countResult.Count != nil {
 		v.Count = countResult.Count
 		if len(countResult.HLL) == 512 {
 			v.HyperLogLog, err = hex.DecodeString(countResult.HLL)
@@ -205,7 +199,7 @@ func (v *CountEnvelope) UnmarshalJSON(data []byte) error {
 
 	f := 0
 	for i := 2; i < len(arr); i++ {
-		item := []byte(arr[i].Raw)
+		item := unsafe.Slice(unsafe.StringData(arr[i].Raw), len(arr[i].Raw))
 
 		if err := easyjson.Unmarshal(item, &v.Filter); err != nil {
 			return fmt.Errorf("%w -- on filter %d", err, f)
@@ -249,8 +243,8 @@ func (n NoticeEnvelope) String() string {
 	return string(v)
 }
 
-func (v *NoticeEnvelope) UnmarshalJSON(data []byte) error {
-	r := gjson.ParseBytes(data)
+func (v *NoticeEnvelope) FromJSON(data string) error {
+	r := gjson.Parse(data)
 	arr := r.Array()
 	if len(arr) < 2 {
 		return fmt.Errorf("failed to decode NOTICE envelope")
@@ -276,8 +270,8 @@ func (e EOSEEnvelope) String() string {
 	return string(v)
 }
 
-func (v *EOSEEnvelope) UnmarshalJSON(data []byte) error {
-	r := gjson.ParseBytes(data)
+func (v *EOSEEnvelope) FromJSON(data string) error {
+	r := gjson.Parse(data)
 	arr := r.Array()
 	if len(arr) < 2 {
 		return fmt.Errorf("failed to decode EOSE envelope")
@@ -303,8 +297,8 @@ func (c CloseEnvelope) String() string {
 	return string(v)
 }
 
-func (v *CloseEnvelope) UnmarshalJSON(data []byte) error {
-	r := gjson.ParseBytes(data)
+func (v *CloseEnvelope) FromJSON(data string) error {
+	r := gjson.Parse(data)
 	arr := r.Array()
 	switch len(arr) {
 	case 2:
@@ -335,8 +329,8 @@ func (c ClosedEnvelope) String() string {
 	return string(v)
 }
 
-func (v *ClosedEnvelope) UnmarshalJSON(data []byte) error {
-	r := gjson.ParseBytes(data)
+func (v *ClosedEnvelope) FromJSON(data string) error {
+	r := gjson.Parse(data)
 	arr := r.Array()
 	switch len(arr) {
 	case 3:
@@ -370,8 +364,8 @@ func (o OKEnvelope) String() string {
 	return string(v)
 }
 
-func (v *OKEnvelope) UnmarshalJSON(data []byte) error {
-	r := gjson.ParseBytes(data)
+func (v *OKEnvelope) FromJSON(data string) error {
+	r := gjson.Parse(data)
 	arr := r.Array()
 	if len(arr) < 4 {
 		return fmt.Errorf("failed to decode OK envelope: missing fields")
@@ -411,14 +405,14 @@ func (a AuthEnvelope) String() string {
 	return string(v)
 }
 
-func (v *AuthEnvelope) UnmarshalJSON(data []byte) error {
-	r := gjson.ParseBytes(data)
+func (v *AuthEnvelope) FromJSON(data string) error {
+	r := gjson.Parse(data)
 	arr := r.Array()
 	if len(arr) < 2 {
 		return fmt.Errorf("failed to decode Auth envelope: missing fields")
 	}
 	if arr[1].IsObject() {
-		return easyjson.Unmarshal([]byte(arr[1].Raw), &v.Event)
+		return easyjson.Unmarshal(unsafe.Slice(unsafe.StringData(arr[1].Raw), len(arr[1].Raw)), &v.Event)
 	} else {
 		v.Challenge = &arr[1].Str
 	}
diff --git a/envelopes_benchmark_test.go b/envelopes_benchmark_test.go
index 2121221..04127b8 100644
--- a/envelopes_benchmark_test.go
+++ b/envelopes_benchmark_test.go
@@ -1,3 +1,5 @@
+//go:build sonic
+
 package nostr
 
 import (
@@ -6,6 +8,7 @@ import (
 	"math/rand/v2"
 	"testing"
 	"time"
+	"unsafe"
 )
 
 func BenchmarkParseMessage(b *testing.B) {
@@ -17,7 +20,7 @@ func BenchmarkParseMessage(b *testing.B) {
 				for i := 0; i < b.N; i++ {
 					for _, msg := range messages {
 						var v any
-						stdlibjson.Unmarshal(msg, &v)
+						stdlibjson.Unmarshal(unsafe.Slice(unsafe.StringData(msg), len(msg)), &v)
 					}
 				}
 			})
@@ -42,8 +45,8 @@ func BenchmarkParseMessage(b *testing.B) {
 	}
 }
 
-func generateTestMessages(typ string) [][]byte {
-	messages := make([][]byte, 0, 600)
+func generateTestMessages(typ string) []string {
+	messages := make([]string, 0, 600)
 
 	setup := map[string]map[int]func() []byte{
 		"client": {
@@ -62,7 +65,7 @@ func generateTestMessages(typ string) [][]byte {
 
 	for count, generator := range setup {
 		for range count {
-			messages = append(messages, generator())
+			messages = append(messages, string(generator()))
 		}
 	}
 
diff --git a/envelopes_default.go b/envelopes_default.go
index 379e647..089c038 100644
--- a/envelopes_default.go
+++ b/envelopes_default.go
@@ -3,8 +3,8 @@
 package nostr
 
 import (
-	"bytes"
 	"errors"
+	"strings"
 )
 
 func NewMessageParser() MessageParser {
@@ -13,41 +13,45 @@ func NewMessageParser() MessageParser {
 
 type messageParser struct{}
 
-func (messageParser) ParseMessage(message []byte) (Envelope, error) {
-	firstComma := bytes.Index(message, []byte{','})
-	if firstComma == -1 {
+func (messageParser) ParseMessage(message string) (Envelope, error) {
+	firstQuote := strings.IndexRune(message, '"')
+	if firstQuote == -1 {
 		return nil, errors.New("malformed json")
 	}
-	label := message[0:firstComma]
+	secondQuote := strings.IndexRune(message[firstQuote+1:], '"')
+	if secondQuote == -1 {
+		return nil, errors.New("malformed json")
+	}
+	label := message[firstQuote+1 : firstQuote+1+secondQuote]
 
 	var v Envelope
-	switch {
-	case bytes.Contains(label, labelEvent):
+	switch label {
+	case "EVENT":
 		v = &EventEnvelope{}
-	case bytes.Contains(label, labelReq):
+	case "REQ":
 		v = &ReqEnvelope{}
-	case bytes.Contains(label, labelCount):
+	case "COUNT":
 		v = &CountEnvelope{}
-	case bytes.Contains(label, labelNotice):
+	case "NOTICE":
 		x := NoticeEnvelope("")
 		v = &x
-	case bytes.Contains(label, labelEose):
+	case "EOSE":
 		x := EOSEEnvelope("")
 		v = &x
-	case bytes.Contains(label, labelOk):
+	case "OK":
 		v = &OKEnvelope{}
-	case bytes.Contains(label, labelAuth):
+	case "AUTH":
 		v = &AuthEnvelope{}
-	case bytes.Contains(label, labelClosed):
+	case "CLOSED":
 		v = &ClosedEnvelope{}
-	case bytes.Contains(label, labelClose):
+	case "CLOSE":
 		x := CloseEnvelope("")
 		v = &x
 	default:
 		return nil, UnknownLabel
 	}
 
-	if err := v.UnmarshalJSON(message); err != nil {
+	if err := v.FromJSON(message); err != nil {
 		return nil, err
 	}
 	return v, nil
diff --git a/envelopes_sonic.go b/envelopes_sonic.go
index 369cc9a..43c8c5a 100644
--- a/envelopes_sonic.go
+++ b/envelopes_sonic.go
@@ -551,11 +551,11 @@ func (smp *sonicMessageParser) doneWithIntSlice(slice []int) {
 // When an unexpected message (like ["NEG-OPEN", ...]) is found, the error UnknownLabel will be
 // returned. Other errors will be returned if the JSON is malformed or the objects are not exactly
 // as they should.
-func (smp sonicMessageParser) ParseMessage(message []byte) (Envelope, error) {
+func (smp sonicMessageParser) ParseMessage(message string) (Envelope, error) {
 	sv := &sonicVisitor{smp: &smp}
 	sv.whereWeAre = inEnvelope
 
-	err := ast.Preorder(unsafe.String(unsafe.SliceData(message), len(message)), sv, nil)
+	err := ast.Preorder(message, sv, nil)
 
 	return sv.mainEnvelope, err
 }
diff --git a/envelopes_test.go b/envelopes_test.go
index cb4b421..b9e54e8 100644
--- a/envelopes_test.go
+++ b/envelopes_test.go
@@ -1,3 +1,5 @@
+//go:build sonic
+
 package nostr
 
 import (
@@ -11,97 +13,97 @@ import (
 func TestParseMessage(t *testing.T) {
 	testCases := []struct {
 		Name             string
-		Message          []byte
+		Message          string
 		ExpectedEnvelope Envelope
 	}{
 		{
 			Name:             "nil",
-			Message:          nil,
+			Message:          "",
 			ExpectedEnvelope: nil,
 		},
 		{
 			Name:             "invalid string",
-			Message:          []byte("invalid input"),
+			Message:          "invalid input",
 			ExpectedEnvelope: nil,
 		},
 		{
 			Name:             "invalid string with a comma",
-			Message:          []byte("invalid, input"),
+			Message:          "invalid, input",
 			ExpectedEnvelope: nil,
 		},
 		{
 			Name:             "EVENT envelope with subscription id",
-			Message:          []byte(`["EVENT","_",{"kind":1,"id":"dc90c95f09947507c1044e8f48bcf6350aa6bff1507dd4acfc755b9239b5c962","pubkey":"3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d","created_at":1644271588,"tags":[],"content":"now that https://blueskyweb.org/blog/2-7-2022-overview was announced we can stop working on nostr?","sig":"230e9d8f0ddaf7eb70b5f7741ccfa37e87a455c9a469282e3464e2052d3192cd63a167e196e381ef9d7e69e9ea43af2443b839974dc85d8aaab9efe1d9296524"}]`),
+			Message:          `["EVENT","_",{"kind":1,"id":"dc90c95f09947507c1044e8f48bcf6350aa6bff1507dd4acfc755b9239b5c962","pubkey":"3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d","created_at":1644271588,"tags":[],"content":"now that https://blueskyweb.org/blog/2-7-2022-overview was announced we can stop working on nostr?","sig":"230e9d8f0ddaf7eb70b5f7741ccfa37e87a455c9a469282e3464e2052d3192cd63a167e196e381ef9d7e69e9ea43af2443b839974dc85d8aaab9efe1d9296524"}]`,
 			ExpectedEnvelope: &EventEnvelope{SubscriptionID: ptr("_"), Event: Event{Kind: 1, ID: "dc90c95f09947507c1044e8f48bcf6350aa6bff1507dd4acfc755b9239b5c962", PubKey: "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d", CreatedAt: 1644271588, Tags: Tags{}, Content: "now that https://blueskyweb.org/blog/2-7-2022-overview was announced we can stop working on nostr?", Sig: "230e9d8f0ddaf7eb70b5f7741ccfa37e87a455c9a469282e3464e2052d3192cd63a167e196e381ef9d7e69e9ea43af2443b839974dc85d8aaab9efe1d9296524"}},
 		},
 		{
 			Name:             "EVENT envelope without subscription id",
-			Message:          []byte(`["EVENT",{"kind":1,"id":"dc90c95f09947507c1044e8f48bcf6350aa6bff1507dd4acfc755b9239b5c962","pubkey":"3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d","created_at":1644271588,"tags":[],"content":"now that https://blueskyweb.org/blog/2-7-2022-overview was announced we can stop working on nostr?","sig":"230e9d8f0ddaf7eb70b5f7741ccfa37e87a455c9a469282e3464e2052d3192cd63a167e196e381ef9d7e69e9ea43af2443b839974dc85d8aaab9efe1d9296524"}]`),
+			Message:          `["EVENT",{"kind":1,"id":"dc90c95f09947507c1044e8f48bcf6350aa6bff1507dd4acfc755b9239b5c962","pubkey":"3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d","created_at":1644271588,"tags":[],"content":"now that https://blueskyweb.org/blog/2-7-2022-overview was announced we can stop working on nostr?","sig":"230e9d8f0ddaf7eb70b5f7741ccfa37e87a455c9a469282e3464e2052d3192cd63a167e196e381ef9d7e69e9ea43af2443b839974dc85d8aaab9efe1d9296524"}]`,
 			ExpectedEnvelope: &EventEnvelope{Event: Event{Kind: 1, ID: "dc90c95f09947507c1044e8f48bcf6350aa6bff1507dd4acfc755b9239b5c962", PubKey: "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d", CreatedAt: 1644271588, Tags: Tags{}, Content: "now that https://blueskyweb.org/blog/2-7-2022-overview was announced we can stop working on nostr?", Sig: "230e9d8f0ddaf7eb70b5f7741ccfa37e87a455c9a469282e3464e2052d3192cd63a167e196e381ef9d7e69e9ea43af2443b839974dc85d8aaab9efe1d9296524"}},
 		},
 		{
 			Name:             "EVENT envelope with tags",
-			Message:          []byte(`["EVENT",{"kind":3,"id":"9e662bdd7d8abc40b5b15ee1ff5e9320efc87e9274d8d440c58e6eed2dddfbe2","pubkey":"373ebe3d45ec91977296a178d9f19f326c70631d2a1b0bbba5c5ecc2eb53b9e7","created_at":1644844224,"tags":[["p","3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d"],["e","75fc5ac2487363293bd27fb0d14fb966477d0f1dbc6361d37806a6a740eda91e"],["p","46d0dfd3a724a302ca9175163bdf788f3606b3fd1bb12d5fe055d1e418cb60ea"]],"content":"{\"wss://nostr-pub.wellorder.net\":{\"read\":true,\"write\":true},\"wss://nostr.bitcoiner.social\":{\"read\":false,\"write\":true},\"wss://expensive-relay.fiatjaf.com\":{\"read\":true,\"write\":true},\"wss://relayer.fiatjaf.com\":{\"read\":true,\"write\":true},\"wss://relay.bitid.nz\":{\"read\":true,\"write\":true},\"wss://nostr.rocks\":{\"read\":true,\"write\":true}}","sig":"811355d3484d375df47581cb5d66bed05002c2978894098304f20b595e571b7e01b2efd906c5650080ffe49cf1c62b36715698e9d88b9e8be43029a2f3fa66be"}]`),
+			Message:          `["EVENT",{"kind":3,"id":"9e662bdd7d8abc40b5b15ee1ff5e9320efc87e9274d8d440c58e6eed2dddfbe2","pubkey":"373ebe3d45ec91977296a178d9f19f326c70631d2a1b0bbba5c5ecc2eb53b9e7","created_at":1644844224,"tags":[["p","3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d"],["e","75fc5ac2487363293bd27fb0d14fb966477d0f1dbc6361d37806a6a740eda91e"],["p","46d0dfd3a724a302ca9175163bdf788f3606b3fd1bb12d5fe055d1e418cb60ea"]],"content":"{\"wss://nostr-pub.wellorder.net\":{\"read\":true,\"write\":true},\"wss://nostr.bitcoiner.social\":{\"read\":false,\"write\":true},\"wss://expensive-relay.fiatjaf.com\":{\"read\":true,\"write\":true},\"wss://relayer.fiatjaf.com\":{\"read\":true,\"write\":true},\"wss://relay.bitid.nz\":{\"read\":true,\"write\":true},\"wss://nostr.rocks\":{\"read\":true,\"write\":true}}","sig":"811355d3484d375df47581cb5d66bed05002c2978894098304f20b595e571b7e01b2efd906c5650080ffe49cf1c62b36715698e9d88b9e8be43029a2f3fa66be"}]`,
 			ExpectedEnvelope: &EventEnvelope{Event: Event{Kind: 3, ID: "9e662bdd7d8abc40b5b15ee1ff5e9320efc87e9274d8d440c58e6eed2dddfbe2", PubKey: "373ebe3d45ec91977296a178d9f19f326c70631d2a1b0bbba5c5ecc2eb53b9e7", CreatedAt: 1644844224, Tags: Tags{Tag{"p", "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d"}, Tag{"e", "75fc5ac2487363293bd27fb0d14fb966477d0f1dbc6361d37806a6a740eda91e"}, Tag{"p", "46d0dfd3a724a302ca9175163bdf788f3606b3fd1bb12d5fe055d1e418cb60ea"}}, Content: "{\"wss://nostr-pub.wellorder.net\":{\"read\":true,\"write\":true},\"wss://nostr.bitcoiner.social\":{\"read\":false,\"write\":true},\"wss://expensive-relay.fiatjaf.com\":{\"read\":true,\"write\":true},\"wss://relayer.fiatjaf.com\":{\"read\":true,\"write\":true},\"wss://relay.bitid.nz\":{\"read\":true,\"write\":true},\"wss://nostr.rocks\":{\"read\":true,\"write\":true}}", Sig: "811355d3484d375df47581cb5d66bed05002c2978894098304f20b595e571b7e01b2efd906c5650080ffe49cf1c62b36715698e9d88b9e8be43029a2f3fa66be"}},
 		},
 		{
 			Name:             "NOTICE envelope",
-			Message:          []byte(`["NOTICE","kjasbdlasvdluiasvd\"kjasbdksab\\d"]`),
+			Message:          `["NOTICE","kjasbdlasvdluiasvd\"kjasbdksab\\d"]`,
 			ExpectedEnvelope: ptr(NoticeEnvelope("kjasbdlasvdluiasvd\"kjasbdksab\\d")),
 		},
 		{
 			Name:             "EOSE envelope",
-			Message:          []byte(`["EOSE","kjasbdlasvdluiasvd\"kjasbdksab\\d"]`),
+			Message:          `["EOSE","kjasbdlasvdluiasvd\"kjasbdksab\\d"]`,
 			ExpectedEnvelope: ptr(EOSEEnvelope("kjasbdlasvdluiasvd\"kjasbdksab\\d")),
 		},
 		{
 			Name:             "COUNT envelope",
-			Message:          []byte(`["COUNT","z",{"count":12}]`),
+			Message:          `["COUNT","z",{"count":12}]`,
 			ExpectedEnvelope: &CountEnvelope{SubscriptionID: "z", Count: ptr(int64(12))},
 		},
 		{
 			Name:             "COUNT envelope with HLL",
-			Message:          []byte(`["COUNT","sub1",{"count":42, "hll": "0100000101000000000000040000000001020000000002000000000200000003000002040000000101020001010000000000000007000004010000000200040000020400000000000102000002000004010000010000000301000102030002000301000300010000070000000001000004000102010000000400010002000000000103000100010001000001040100020001000000000000010000020000000000030100000001000400010000000000000901010100000000040000000b030000010100010000010000010000000003000000000000010003000100020000000000010000010100000100000104000200030001000300000001000101000102"}]`),
+			Message:          `["COUNT","sub1",{"count":42, "hll": "0100000101000000000000040000000001020000000002000000000200000003000002040000000101020001010000000000000007000004010000000200040000020400000000000102000002000004010000010000000301000102030002000301000300010000070000000001000004000102010000000400010002000000000103000100010001000001040100020001000000000000010000020000000000030100000001000400010000000000000901010100000000040000000b030000010100010000010000010000000003000000000000010003000100020000000000010000010100000100000104000200030001000300000001000101000102"}]`,
 			ExpectedEnvelope: &CountEnvelope{SubscriptionID: "sub1", Count: ptr(int64(42)), HyperLogLog: []byte{1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 1, 2, 0, 0, 0, 0, 2, 0, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 2, 4, 0, 0, 0, 1, 1, 2, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 4, 1, 0, 0, 0, 2, 0, 4, 0, 0, 2, 4, 0, 0, 0, 0, 0, 1, 2, 0, 0, 2, 0, 0, 4, 1, 0, 0, 1, 0, 0, 0, 3, 1, 0, 1, 2, 3, 0, 2, 0, 3, 1, 0, 3, 0, 1, 0, 0, 7, 0, 0, 0, 0, 1, 0, 0, 4, 0, 1, 2, 1, 0, 0, 0, 4, 0, 1, 0, 2, 0, 0, 0, 0, 1, 3, 0, 1, 0, 1, 0, 1, 0, 0, 1, 4, 1, 0, 2, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 2, 0, 0, 0, 0, 0, 3, 1, 0, 0, 0, 1, 0, 4, 0, 1, 0, 0, 0, 0, 0, 0, 9, 1, 1, 1, 0, 0, 0, 0, 4, 0, 0, 0, 11, 3, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 1, 0, 3, 0, 1, 0, 2, 0, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 4, 0, 2, 0, 3, 0, 1, 0, 3, 0, 0, 0, 1, 0, 1, 1, 0, 1, 2}},
 		},
 		{
 			Name:             "OK envelope success",
-			Message:          []byte(`["OK","3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefaaaaa",true,""]`),
+			Message:          `["OK","3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefaaaaa",true,""]`,
 			ExpectedEnvelope: &OKEnvelope{EventID: "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefaaaaa", OK: true, Reason: ""},
 		},
 		{
 			Name:             "OK envelope failure",
-			Message:          []byte(`["OK","3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefaaaaa",false,"error: could not connect to the database"]`),
+			Message:          `["OK","3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefaaaaa",false,"error: could not connect to the database"]`,
 			ExpectedEnvelope: &OKEnvelope{EventID: "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefaaaaa", OK: false, Reason: "error: could not connect to the database"},
 		},
 		{
 			Name:             "CLOSED envelope with underscore",
-			Message:          []byte(`["CLOSED","_","error: something went wrong"]`),
+			Message:          `["CLOSED","_","error: something went wrong"]`,
 			ExpectedEnvelope: &ClosedEnvelope{SubscriptionID: "_", Reason: "error: something went wrong"},
 		},
 		{
 			Name:             "CLOSED envelope with colon",
-			Message:          []byte(`["CLOSED",":1","auth-required: take a selfie and send it to the CIA"]`),
+			Message:          `["CLOSED",":1","auth-required: take a selfie and send it to the CIA"]`,
 			ExpectedEnvelope: &ClosedEnvelope{SubscriptionID: ":1", Reason: "auth-required: take a selfie and send it to the CIA"},
 		},
 		{
 			Name:             "AUTH envelope with challenge",
-			Message:          []byte(`["AUTH","kjsabdlasb aslkd kasndkad \"as.kdnbskadb"]`),
+			Message:          `["AUTH","kjsabdlasb aslkd kasndkad \"as.kdnbskadb"]`,
 			ExpectedEnvelope: &AuthEnvelope{Challenge: ptr("kjsabdlasb aslkd kasndkad \"as.kdnbskadb")},
 		},
 		{
 			Name:             "AUTH envelope with event",
-			Message:          []byte(`["AUTH",{"kind":1,"id":"ae1fc7154296569d87ca4663f6bdf448c217d1590d28c85d158557b8b43b4d69","pubkey":"79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798","created_at":1683660344,"tags":[],"content":"hello world","sig":"94e10947814b1ebe38af42300ecd90c7642763896c4f69506ae97bfdf54eec3c0c21df96b7d95daa74ff3d414b1d758ee95fc258125deebc31df0c6ba9396a51"}]`),
+			Message:          `["AUTH",{"kind":1,"id":"ae1fc7154296569d87ca4663f6bdf448c217d1590d28c85d158557b8b43b4d69","pubkey":"79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798","created_at":1683660344,"tags":[],"content":"hello world","sig":"94e10947814b1ebe38af42300ecd90c7642763896c4f69506ae97bfdf54eec3c0c21df96b7d95daa74ff3d414b1d758ee95fc258125deebc31df0c6ba9396a51"}]`,
 			ExpectedEnvelope: &AuthEnvelope{Event: Event{Kind: 1, ID: "ae1fc7154296569d87ca4663f6bdf448c217d1590d28c85d158557b8b43b4d69", PubKey: "79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", CreatedAt: 1683660344, Tags: Tags{}, Content: "hello world", Sig: "94e10947814b1ebe38af42300ecd90c7642763896c4f69506ae97bfdf54eec3c0c21df96b7d95daa74ff3d414b1d758ee95fc258125deebc31df0c6ba9396a51"}},
 		},
 		{
 			Name:             "REQ envelope",
-			Message:          []byte(`["REQ","million", {"kinds": [1]}, {"kinds": [30023 ], "#d": ["buteko",    "batuke"]}]`),
+			Message:          `["REQ","million", {"kinds": [1]}, {"kinds": [30023 ], "#d": ["buteko",    "batuke"]}]`,
 			ExpectedEnvelope: &ReqEnvelope{SubscriptionID: "million", Filters: Filters{{Kinds: []int{1}}, {Kinds: []int{30023}, Tags: TagMap{"d": []string{"buteko", "batuke"}}}}},
 		},
 		{
 			Name:             "CLOSE envelope",
-			Message:          []byte(`["CLOSE","subscription123"]`),
+			Message:          `["CLOSE","subscription123"]`,
 			ExpectedEnvelope: ptr(CloseEnvelope("subscription123")),
 		},
 	}
@@ -168,8 +170,8 @@ func TestParseMessagesFromFile(t *testing.T) {
 			continue
 		}
 
-		standardEnvelope := ParseMessage(line)
-		sonicEnvelope, err := smp.ParseMessage(line)
+		standardEnvelope := ParseMessage(string(line))
+		sonicEnvelope, err := smp.ParseMessage(string(line))
 
 		if standardEnvelope == nil {
 			require.Nil(t, sonicEnvelope, "line %d: standard parser returned nil but sonic parser didn't", lineNum)
diff --git a/helpers.go b/helpers.go
index fe68d8b..c0bd969 100644
--- a/helpers.go
+++ b/helpers.go
@@ -1,7 +1,6 @@
 package nostr
 
 import (
-	"bytes"
 	"strconv"
 	"strings"
 	"sync"
@@ -119,35 +118,39 @@ func isLowerHex(thing string) bool {
 	return true
 }
 
-func extractSubID(jsonStr []byte) string {
+func extractSubID(jsonStr string) string {
 	// look for "EVENT" pattern
-	start := bytes.Index(jsonStr, []byte(`"EVENT"`))
+	start := strings.Index(jsonStr, `"EVENT"`)
 	if start == -1 {
 		return ""
 	}
 
 	// move to the next quote
-	offset := bytes.Index(jsonStr[start+7:], []byte{'"'})
+	offset := strings.Index(jsonStr[start+7:], `"`)
+	if offset == -1 {
+		return ""
+	}
+
 	start += 7 + offset + 1
 
 	// find the ending quote
-	end := bytes.Index(jsonStr[start:], []byte{'"'})
+	end := strings.Index(jsonStr[start:], `"`)
 
 	// get the contents
-	return unsafe.String(unsafe.SliceData(jsonStr[start:start+end]), end)
+	return jsonStr[start : start+end]
 }
 
-func extractEventID(jsonStr []byte) string {
-	// look for "id": pattern
-	start := bytes.Index(jsonStr, []byte(`"id":`))
+func extractEventID(jsonStr string) string {
+	// look for "id" pattern
+	start := strings.Index(jsonStr, `"id"`)
 	if start == -1 {
 		return ""
 	}
 
 	// move to the next quote
-	offset := bytes.Index(jsonStr[start+4:], []byte{'"'})
+	offset := strings.IndexRune(jsonStr[start+4:], '"')
 	start += 4 + offset + 1
 
 	// get 64 characters of the id
-	return unsafe.String(unsafe.SliceData(jsonStr[start:start+64]), 64)
+	return jsonStr[start : start+64]
 }
diff --git a/helpers_test.go b/helpers_test.go
index 88d248e..1bf6fc0 100644
--- a/helpers_test.go
+++ b/helpers_test.go
@@ -27,18 +27,18 @@ func TestIsLower(t *testing.T) {
 
 func TestIDExtract(t *testing.T) {
 	{
-		data := []byte(`{"kind":1,"id":"6b5988e9471fa340880a40df815befc69c901420facfb670acd8308012088f16","pubkey":"67ada8e344532cbf82f0e702472e24c7896e0e1c96235eacbaaa4b8616052171","created_at":1736909072,"tags":[["e","cfdf18b78527455097515545be4ccbe17e9b88f64539a566c632e405e2c0d08a","","root"],["e","f1ec9c301383be082f1860f7e24e49164d855bfab67f8e5c3ed17f6f3f867cca","","reply"],["p","1afe0c74e3d7784eba93a5e3fa554a6eeb01928d12739ae8ba4832786808e36d"],["p","8aa642e26e65072139e10db59646a89aa7538a59965aab3ed89191d71967d6c3"],["p","f4d89779148ccd245c8d50914a284fd62d97cb0fb68b797a70f24a172b522db9"],["p","18905d0a5d623ab81a98ba98c582bd5f57f2506c6b808905fc599d5a0b229b08"],["p","9a0e2043afaa056a12b8bbe77ac4c3185c0e2bc46b12aac158689144323c0e3c"],["p","45f195cffcb8c9724efc248f0507a2fb65b579dfabe7cd35398598163cab7627"]],"content":"🫡","sig":"d21aaf43963b07a3cb5f85ac8809c2b2e4dd3269195f4d810e1b7650895178fe01cf685ab3ee93f193cdde1f8d17419ff05332c6e3fc7429bbbe3d70016b8638"}`)
+		data := `{"kind":1,"id":"6b5988e9471fa340880a40df815befc69c901420facfb670acd8308012088f16","pubkey":"67ada8e344532cbf82f0e702472e24c7896e0e1c96235eacbaaa4b8616052171","created_at":1736909072,"tags":[["e","cfdf18b78527455097515545be4ccbe17e9b88f64539a566c632e405e2c0d08a","","root"],["e","f1ec9c301383be082f1860f7e24e49164d855bfab67f8e5c3ed17f6f3f867cca","","reply"],["p","1afe0c74e3d7784eba93a5e3fa554a6eeb01928d12739ae8ba4832786808e36d"],["p","8aa642e26e65072139e10db59646a89aa7538a59965aab3ed89191d71967d6c3"],["p","f4d89779148ccd245c8d50914a284fd62d97cb0fb68b797a70f24a172b522db9"],["p","18905d0a5d623ab81a98ba98c582bd5f57f2506c6b808905fc599d5a0b229b08"],["p","9a0e2043afaa056a12b8bbe77ac4c3185c0e2bc46b12aac158689144323c0e3c"],["p","45f195cffcb8c9724efc248f0507a2fb65b579dfabe7cd35398598163cab7627"]],"content":"🫡","sig":"d21aaf43963b07a3cb5f85ac8809c2b2e4dd3269195f4d810e1b7650895178fe01cf685ab3ee93f193cdde1f8d17419ff05332c6e3fc7429bbbe3d70016b8638"}`
 		require.Equal(t, "6b5988e9471fa340880a40df815befc69c901420facfb670acd8308012088f16", extractEventID(data))
 	}
 	{
-		data := []byte(`{"kind":1,"pubkey":"67ada8e344532cbf82f0e702472e24c7896e0e1c96235eacbaaa4b8616052171","created_at":1736909072,"tags":[["e","cfdf18b78527455097515545be4ccbe17e9b88f64539a566c632e405e2c0d08a","","root"],["e","f1ec9c301383be082f1860f7e24e49164d855bfab67f8e5c3ed17f6f3f867cca","","reply"],["p","1afe0c74e3d7784eba93a5e3fa554a6eeb01928d12739ae8ba4832786808e36d"],["p","8aa642e26e65072139e10db59646a89aa7538a59965aab3ed89191d71967d6c3"],["p","f4d89779148ccd245c8d50914a284fd62d97cb0fb68b797a70f24a172b522db9"],["p","18905d0a5d623ab81a98ba98c582bd5f57f2506c6b808905fc599d5a0b229b08"],["p","9a0e2043afaa056a12b8bbe77ac4c3185c0e2bc46b12aac158689144323c0e3c"],["p","45f195cffcb8c9724efc248f0507a2fb65b579dfabe7cd35398598163cab7627"]],"content":"🫡","sig":"d21aaf43963b07a3cb5f85ac8809c2b2e4dd3269195f4d810e1b7650895178fe01cf685ab3ee93f193cdde1f8d17419ff05332c6e3fc7429bbbe3d70016b8638","id":  "6b5988e9471fa340880a40df815befc69c901420facfb670acd8308012088f16" }`)
+		data := `{"kind":1,"pubkey":"67ada8e344532cbf82f0e702472e24c7896e0e1c96235eacbaaa4b8616052171","created_at":1736909072,"tags":[["e","cfdf18b78527455097515545be4ccbe17e9b88f64539a566c632e405e2c0d08a","","root"],["e","f1ec9c301383be082f1860f7e24e49164d855bfab67f8e5c3ed17f6f3f867cca","","reply"],["p","1afe0c74e3d7784eba93a5e3fa554a6eeb01928d12739ae8ba4832786808e36d"],["p","8aa642e26e65072139e10db59646a89aa7538a59965aab3ed89191d71967d6c3"],["p","f4d89779148ccd245c8d50914a284fd62d97cb0fb68b797a70f24a172b522db9"],["p","18905d0a5d623ab81a98ba98c582bd5f57f2506c6b808905fc599d5a0b229b08"],["p","9a0e2043afaa056a12b8bbe77ac4c3185c0e2bc46b12aac158689144323c0e3c"],["p","45f195cffcb8c9724efc248f0507a2fb65b579dfabe7cd35398598163cab7627"]],"content":"🫡","sig":"d21aaf43963b07a3cb5f85ac8809c2b2e4dd3269195f4d810e1b7650895178fe01cf685ab3ee93f193cdde1f8d17419ff05332c6e3fc7429bbbe3d70016b8638","id":  "6b5988e9471fa340880a40df815befc69c901420facfb670acd8308012088f16" }`
 		require.Equal(t, "6b5988e9471fa340880a40df815befc69c901420facfb670acd8308012088f16", extractEventID(data))
 	}
 }
 
 func TestSubIdExtract(t *testing.T) {
 	{
-		data := []byte(`["EVENT", "xxz" ,{"kind":1,"id":"6b5988e9471fa340880a40df815befc69c901420facfb670acd8308012088f16","pubkey":"67ada8e344532cbf82f0e702472e24c7896e0e1c96235eacbaaa4b8616052171","created_at":1736909072,"tags":[["e","cfdf18b78527455097515545be4ccbe17e9b88f64539a566c632e405e2c0d08a","","root"],["e","f1ec9c301383be082f1860f7e24e49164d855bfab67f8e5c3ed17f6f3f867cca","","reply"],["p","1afe0c74e3d7784eba93a5e3fa554a6eeb01928d12739ae8ba4832786808e36d"],["p","8aa642e26e65072139e10db59646a89aa7538a59965aab3ed89191d71967d6c3"],["p","f4d89779148ccd245c8d50914a284fd62d97cb0fb68b797a70f24a172b522db9"],["p","18905d0a5d623ab81a98ba98c582bd5f57f2506c6b808905fc599d5a0b229b08"],["p","9a0e2043afaa056a12b8bbe77ac4c3185c0e2bc46b12aac158689144323c0e3c"],["p","45f195cffcb8c9724efc248f0507a2fb65b579dfabe7cd35398598163cab7627"]],"content":"🫡","sig":"d21aaf43963b07a3cb5f85ac8809c2b2e4dd3269195f4d810e1b7650895178fe01cf685ab3ee93f193cdde1f8d17419ff05332c6e3fc7429bbbe3d70016b8638"}]`)
+		data := `["EVENT", "xxz" ,{"kind":1,"id":"6b5988e9471fa340880a40df815befc69c901420facfb670acd8308012088f16","pubkey":"67ada8e344532cbf82f0e702472e24c7896e0e1c96235eacbaaa4b8616052171","created_at":1736909072,"tags":[["e","cfdf18b78527455097515545be4ccbe17e9b88f64539a566c632e405e2c0d08a","","root"],["e","f1ec9c301383be082f1860f7e24e49164d855bfab67f8e5c3ed17f6f3f867cca","","reply"],["p","1afe0c74e3d7784eba93a5e3fa554a6eeb01928d12739ae8ba4832786808e36d"],["p","8aa642e26e65072139e10db59646a89aa7538a59965aab3ed89191d71967d6c3"],["p","f4d89779148ccd245c8d50914a284fd62d97cb0fb68b797a70f24a172b522db9"],["p","18905d0a5d623ab81a98ba98c582bd5f57f2506c6b808905fc599d5a0b229b08"],["p","9a0e2043afaa056a12b8bbe77ac4c3185c0e2bc46b12aac158689144323c0e3c"],["p","45f195cffcb8c9724efc248f0507a2fb65b579dfabe7cd35398598163cab7627"]],"content":"🫡","sig":"d21aaf43963b07a3cb5f85ac8809c2b2e4dd3269195f4d810e1b7650895178fe01cf685ab3ee93f193cdde1f8d17419ff05332c6e3fc7429bbbe3d70016b8638"}]`
 		require.Equal(t, "xxz", extractSubID(data))
 	}
 }
diff --git a/nip77/envelopes.go b/nip77/envelopes.go
index b870c57..2b5603e 100644
--- a/nip77/envelopes.go
+++ b/nip77/envelopes.go
@@ -3,6 +3,8 @@ package nip77
 import (
 	"bytes"
 	"fmt"
+	"strings"
+	"unsafe"
 
 	"github.com/mailru/easyjson"
 	jwriter "github.com/mailru/easyjson/jwriter"
@@ -10,28 +12,28 @@ import (
 	"github.com/tidwall/gjson"
 )
 
-func ParseNegMessage(message []byte) nostr.Envelope {
-	firstComma := bytes.Index(message, []byte{','})
+func ParseNegMessage(message string) nostr.Envelope {
+	firstComma := strings.Index(message, ",")
 	if firstComma == -1 {
 		return nil
 	}
 	label := message[0:firstComma]
 
 	var v nostr.Envelope
-	switch {
-	case bytes.Contains(label, []byte("NEG-MSG")):
+	switch label {
+	case "NEG-MSG":
 		v = &MessageEnvelope{}
-	case bytes.Contains(label, []byte("NEG-OPEN")):
+	case "NEG-OPEN":
 		v = &OpenEnvelope{}
-	case bytes.Contains(label, []byte("NEG-ERR")):
+	case "NEG-ERR":
 		v = &ErrorEnvelope{}
-	case bytes.Contains(label, []byte("NEG-CLOSE")):
+	case "NEG-CLOSE":
 		v = &CloseEnvelope{}
 	default:
 		return nil
 	}
 
-	if err := v.UnmarshalJSON(message); err != nil {
+	if err := v.FromJSON(message); err != nil {
 		return nil
 	}
 	return v
@@ -56,8 +58,8 @@ func (v OpenEnvelope) String() string {
 	return string(b)
 }
 
-func (v *OpenEnvelope) UnmarshalJSON(data []byte) error {
-	r := gjson.ParseBytes(data)
+func (v *OpenEnvelope) FromJSON(data string) error {
+	r := gjson.Parse(data)
 	arr := r.Array()
 	if len(arr) != 4 {
 		return fmt.Errorf("failed to decode NEG-OPEN envelope")
@@ -65,7 +67,7 @@ func (v *OpenEnvelope) UnmarshalJSON(data []byte) error {
 
 	v.SubscriptionID = arr[1].Str
 	v.Message = arr[3].Str
-	return easyjson.Unmarshal([]byte(arr[2].Raw), &v.Filter)
+	return easyjson.Unmarshal(unsafe.Slice(unsafe.StringData(arr[2].Raw), len(arr[2].Raw)), &v.Filter)
 }
 
 func (v OpenEnvelope) MarshalJSON() ([]byte, error) {
@@ -97,8 +99,8 @@ func (v MessageEnvelope) String() string {
 	return string(b)
 }
 
-func (v *MessageEnvelope) UnmarshalJSON(data []byte) error {
-	r := gjson.ParseBytes(data)
+func (v *MessageEnvelope) FromJSON(data string) error {
+	r := gjson.Parse(data)
 	arr := r.Array()
 	if len(arr) < 3 {
 		return fmt.Errorf("failed to decode NEG-MSG envelope")
@@ -130,8 +132,8 @@ func (v CloseEnvelope) String() string {
 	return string(b)
 }
 
-func (v *CloseEnvelope) UnmarshalJSON(data []byte) error {
-	r := gjson.ParseBytes(data)
+func (v *CloseEnvelope) FromJSON(data string) error {
+	r := gjson.Parse(data)
 	arr := r.Array()
 	if len(arr) < 2 {
 		return fmt.Errorf("failed to decode NEG-CLOSE envelope")
@@ -159,8 +161,8 @@ func (v ErrorEnvelope) String() string {
 	return string(b)
 }
 
-func (v *ErrorEnvelope) UnmarshalJSON(data []byte) error {
-	r := gjson.ParseBytes(data)
+func (v *ErrorEnvelope) FromJSON(data string) error {
+	r := gjson.Parse(data)
 	arr := r.Array()
 	if len(arr) < 3 {
 		return fmt.Errorf("failed to decode NEG-ERROR envelope")
diff --git a/nip77/idsonly.go b/nip77/idsonly.go
index e50ee8f..0bd7781 100644
--- a/nip77/idsonly.go
+++ b/nip77/idsonly.go
@@ -20,7 +20,7 @@ func FetchIDsOnly(
 	result := make(chan error)
 
 	var r *nostr.Relay
-	r, err := nostr.RelayConnect(ctx, url, nostr.WithCustomHandler(func(data []byte) {
+	r, err := nostr.RelayConnect(ctx, url, nostr.WithCustomHandler(func(data string) {
 		envelope := ParseNegMessage(data)
 		if envelope == nil {
 			return
diff --git a/nip77/nip77.go b/nip77/nip77.go
index a6374af..a4e078c 100644
--- a/nip77/nip77.go
+++ b/nip77/nip77.go
@@ -49,7 +49,7 @@ func NegentropySync(
 	result := make(chan error)
 
 	var r *nostr.Relay
-	r, err = nostr.RelayConnect(ctx, url, nostr.WithCustomHandler(func(data []byte) {
+	r, err = nostr.RelayConnect(ctx, url, nostr.WithCustomHandler(func(data string) {
 		envelope := ParseNegMessage(data)
 		if envelope == nil {
 			return
diff --git a/relay.go b/relay.go
index 48e720e..d02d340 100644
--- a/relay.go
+++ b/relay.go
@@ -13,6 +13,7 @@ import (
 	"sync"
 	"sync/atomic"
 	"time"
+	"unsafe"
 
 	"github.com/puzpuzpuz/xsync/v3"
 )
@@ -35,7 +36,7 @@ type Relay struct {
 
 	challenge                     string       // NIP-42 challenge, we only keep the last
 	noticeHandler                 func(string) // NIP-01 NOTICEs
-	customHandler                 func([]byte) // nonstandard unparseable messages
+	customHandler                 func(string) // nonstandard unparseable messages
 	okCallbacks                   *xsync.MapOf[string, func(bool, string)]
 	writeQueue                    chan writeRequest
 	subscriptionChannelCloseQueue chan *Subscription
@@ -104,7 +105,7 @@ func (nh WithNoticeHandler) ApplyRelayOption(r *Relay) {
 
 // WithCustomHandler must be a function that handles any relay message that couldn't be
 // parsed as a standard envelope.
-type WithCustomHandler func(data []byte)
+type WithCustomHandler func(data string)
 
 func (ch WithCustomHandler) ApplyRelayOption(r *Relay) {
 	r.customHandler = ch
@@ -212,6 +213,7 @@ func (r *Relay) ConnectWithTLS(ctx context.Context, tlsConfig *tls.Config) error
 	// general message reader loop
 	go func() {
 		buf := new(bytes.Buffer)
+		mp := NewMessageParser()
 
 		for {
 			buf.Reset()
@@ -222,7 +224,8 @@ func (r *Relay) ConnectWithTLS(ctx context.Context, tlsConfig *tls.Config) error
 				break
 			}
 
-			message := buf.Bytes()
+			msgb := buf.Bytes()
+			message := unsafe.String(unsafe.SliceData(msgb), len(msgb))
 			debugLogf("{%s} received %v\n", r.URL, message)
 
 			// if this is an "EVENT" we will have this preparser logic that should speed things up a little
@@ -235,9 +238,9 @@ func (r *Relay) ConnectWithTLS(ctx context.Context, tlsConfig *tls.Config) error
 				}
 			}
 
-			envelope := ParseMessage(message)
+			envelope, err := mp.ParseMessage(message)
 			if envelope == nil {
-				if r.customHandler != nil {
+				if r.customHandler != nil && err == UnknownLabel {
 					r.customHandler(message)
 				}
 				continue
@@ -258,13 +261,13 @@ func (r *Relay) ConnectWithTLS(ctx context.Context, tlsConfig *tls.Config) error
 				r.challenge = *env.Challenge
 			case *EventEnvelope:
 				// we already have the subscription from the pre-check above, so we can just reuse it
-				if subscription == nil {
+				if sub == nil {
 					// InfoLogger.Printf("{%s} no subscription with id '%s'\n", r.URL, *env.SubscriptionID)
 					continue
 				} else {
 					// check if the event matches the desired filter, ignore otherwise
-					if !subscription.match(&env.Event) {
-						InfoLogger.Printf("{%s} filter does not match: %v ~ %v\n", r.URL, subscription.Filters, env.Event)
+					if !sub.match(&env.Event) {
+						InfoLogger.Printf("{%s} filter does not match: %v ~ %v\n", r.URL, sub.Filters, env.Event)
 						continue
 					}
 
@@ -277,7 +280,7 @@ func (r *Relay) ConnectWithTLS(ctx context.Context, tlsConfig *tls.Config) error
 					}
 
 					// dispatch this to the internal .events channel of the subscription
-					subscription.dispatchEvent(&env.Event)
+					sub.dispatchEvent(&env.Event)
 				}
 			case *EOSEEnvelope:
 				if subscription, ok := r.Subscriptions.Load(subIdToSerial(string(*env))); ok {