From 55a43e46b7360fe664be4274bf4a512689ec1c80 Mon Sep 17 00:00:00 2001 From: fiatjaf Date: Fri, 21 Nov 2025 21:16:34 -0300 Subject: [PATCH] use xhex everywhere. --- envelopes.go | 10 +++++----- envelopes_test.go | 4 ++-- event.go | 11 ++++------- event_easyjson.go | 15 +++++++-------- event_test.go | 4 ++-- eventstore/bluge/helpers.go | 5 ++--- eventstore/boltdb/count.go | 10 +++++----- eventstore/boltdb/helpers.go | 6 +++--- eventstore/lmdb/count.go | 10 +++++----- eventstore/lmdb/helpers.go | 8 ++++---- eventstore/lmdb/query_planner.go | 6 +++--- eventstore/mmm/helpers.go | 8 ++++---- eventstore/mmm/query_planner.go | 6 +++--- eventstore/test/benchmark_test.go | 5 ++--- eventstore/test/second_test.go | 5 ++--- filter_easyjson.go | 11 +++++------ go.mod | 2 ++ go.sum | 4 ++++ helpers.go | 6 +++--- keys.go | 26 ++++++++++++++------------ khatru/blossom/handlers.go | 5 ++--- khatru/handlers.go | 3 +-- khatru/nip86.go | 3 +-- nip04/nip04.go | 3 +-- nip04/nip04_test.go | 5 ++--- nip06/nip06.go | 5 ++--- nip19/nip19_test.go | 4 ++-- nip44/nip44.go | 5 ++--- nip44/nip44_test.go | 18 +++++++++--------- nip46/client.go | 3 +-- nip46/dynamic-signer.go | 3 +-- nip46/static-key-signer.go | 3 +-- nip60/helpers.go | 18 +++++++++--------- nip60/send.go | 5 ++--- nip60/wallet.go | 7 +++---- nip61/verify.go | 13 ++++++------- nip77/negentropy/negentropy.go | 7 +++---- nipb0/blossom/list.go | 3 +-- nipb0/blossom/upload.go | 3 +-- pointers.go | 15 +++++++-------- pointers_easyjson.go | 11 +++++------ schema/schema.go | 4 ++-- sdk/note.go | 5 ++--- types.go | 15 ++++++++------- types_test.go | 7 +++++-- utils.go | 27 +++++++++++++++++++++++++-- 46 files changed, 185 insertions(+), 177 deletions(-) diff --git a/envelopes.go b/envelopes.go index 2284e50..444546c 100644 --- a/envelopes.go +++ b/envelopes.go @@ -1,7 +1,6 @@ package nostr import ( - "encoding/hex" "errors" "fmt" "strconv" @@ -10,6 +9,7 @@ import ( "github.com/mailru/easyjson" jwriter "github.com/mailru/easyjson/jwriter" + "github.com/templexxx/xhex" "github.com/tidwall/gjson" ) @@ -187,7 +187,7 @@ func (v *CountEnvelope) FromJSON(data string) error { 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) + v.HyperLogLog, err = HexDecodeString(countResult.HLL) if err != nil { return fmt.Errorf("invalid \"hll\" value in COUNT message: %w", err) } @@ -214,7 +214,7 @@ func (v CountEnvelope) MarshalJSON() ([]byte, error) { if v.HyperLogLog != nil { w.RawString(`,"hll":"`) hllHex := make([]byte, 512) - hex.Encode(hllHex, v.HyperLogLog) + xhex.Encode(hllHex, v.HyperLogLog) w.Buffer.AppendBytes(hllHex) w.RawString(`"`) } @@ -365,7 +365,7 @@ func (v *OKEnvelope) FromJSON(data string) error { if len(arr) < 4 { return fmt.Errorf("failed to decode OK envelope: missing fields") } - if _, err := hex.Decode(v.EventID[:], []byte(arr[1].Str)); err != nil { + if err := xhex.Decode(v.EventID[:], []byte(arr[1].Str)); err != nil { return err } v.OK = arr[2].Raw == "true" @@ -377,7 +377,7 @@ func (v *OKEnvelope) FromJSON(data string) error { func (v OKEnvelope) MarshalJSON() ([]byte, error) { w := jwriter.Writer{NoEscapeHTML: true} w.RawString(`["OK","`) - w.RawString(hex.EncodeToString(v.EventID[:])) + w.RawString(HexEncodeToString(v.EventID[:])) w.RawString(`",`) ok := "false" if v.OK { diff --git a/envelopes_test.go b/envelopes_test.go index b3fb9ab..08c19b8 100644 --- a/envelopes_test.go +++ b/envelopes_test.go @@ -1,11 +1,11 @@ package nostr import ( - "encoding/hex" "testing" "unsafe" "github.com/stretchr/testify/require" + "github.com/templexxx/xhex" ) func TestParseMessage(t *testing.T) { @@ -139,6 +139,6 @@ func TestParseMessage(t *testing.T) { func mustSigFromHex(sigStr string) [64]byte { var sig [64]byte - hex.Decode(sig[:], unsafe.Slice(unsafe.StringData(sigStr), 128)) + xhex.Decode(sig[:], unsafe.Slice(unsafe.StringData(sigStr), 128)) return sig } diff --git a/event.go b/event.go index 9348680..51ad0b1 100644 --- a/event.go +++ b/event.go @@ -2,10 +2,10 @@ package nostr import ( "crypto/sha256" - "encoding/hex" "strconv" "github.com/mailru/easyjson" + "github.com/templexxx/xhex" ) // Event represents a Nostr event. @@ -38,15 +38,12 @@ func (evt Event) CheckID() bool { func (evt Event) Serialize() []byte { // the serialization process is just putting everything into a JSON array // so the order is kept. See NIP-01 - dst := make([]byte, 0, 100+len(evt.Content)+len(evt.Tags)*80) - return serializeEventInto(evt, dst) -} + dst := make([]byte, 4+64, 100+len(evt.Content)+len(evt.Tags)*80) -func serializeEventInto(evt Event, dst []byte) []byte { // the header portion is easy to serialize // [0,"pubkey",created_at,kind,[ - dst = append(dst, `[0,"`...) - dst = hex.AppendEncode(dst, evt.PubKey[:]) + copy(dst, `[0,"`) + xhex.Encode(dst[4:4+64], evt.PubKey[:]) // there will always be such capacity dst = append(dst, `",`...) dst = append(dst, strconv.FormatInt(int64(evt.CreatedAt), 10)...) dst = append(dst, `,`...) diff --git a/event_easyjson.go b/event_easyjson.go index 6284f67..1371ff2 100644 --- a/event_easyjson.go +++ b/event_easyjson.go @@ -1,10 +1,9 @@ package nostr import ( - "encoding/hex" - jlexer "github.com/mailru/easyjson/jlexer" jwriter "github.com/mailru/easyjson/jwriter" + "github.com/templexxx/xhex" ) func easyjsonDecodeEvent(in *jlexer.Lexer, out *Event) { @@ -29,12 +28,12 @@ func easyjsonDecodeEvent(in *jlexer.Lexer, out *Event) { case "id": b := in.UnsafeBytes() if len(b) == 64 { - hex.Decode(out.ID[:], b) + xhex.Decode(out.ID[:], b) } case "pubkey": b := in.UnsafeBytes() if len(b) == 64 { - hex.Decode(out.PubKey[:], b) + xhex.Decode(out.PubKey[:], b) } case "created_at": out.CreatedAt = Timestamp(in.Int64()) @@ -73,7 +72,7 @@ func easyjsonDecodeEvent(in *jlexer.Lexer, out *Event) { case "sig": b := in.UnsafeBytes() if len(b) == 128 { - hex.Decode(out.Sig[:], b) + xhex.Decode(out.Sig[:], b) } } in.WantComma() @@ -92,12 +91,12 @@ func easyjsonEncodeEvent(out *jwriter.Writer, in Event) { if in.ID != ZeroID { out.RawString(",\"id\":\"") - out.RawString(hex.EncodeToString(in.ID[:]) + "\"") + out.RawString(HexEncodeToString(in.ID[:]) + "\"") } if in.PubKey != ZeroPK { out.RawString(",\"pubkey\":\"") - out.RawString(hex.EncodeToString(in.PubKey[:]) + "\"") + out.RawString(HexEncodeToString(in.PubKey[:]) + "\"") } out.RawString(",\"created_at\":") @@ -125,7 +124,7 @@ func easyjsonEncodeEvent(out *jwriter.Writer, in Event) { if in.Sig != [64]byte{} { out.RawString(",\"sig\":\"") - out.RawString(hex.EncodeToString(in.Sig[:]) + "\"") + out.RawString(HexEncodeToString(in.Sig[:]) + "\"") } out.RawByte('}') diff --git a/event_test.go b/event_test.go index 278a201..93d643d 100644 --- a/event_test.go +++ b/event_test.go @@ -1,13 +1,13 @@ package nostr import ( - "encoding/hex" "fmt" "math/rand/v2" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/templexxx/xhex" ) func TestEventParsingAndVerifying(t *testing.T) { @@ -36,7 +36,7 @@ func TestEventParsingAndVerifying(t *testing.T) { func TestEventSerialization(t *testing.T) { sig := [64]byte{} - hex.Decode(sig[:], []byte("ed08d2dd5b0f7b6a3cdc74643d4adee3158ddede9cc848e8cd97630c097001acc2d052d2d3ec2b7ac4708b2314b797106d1b3c107322e61b5e5cc2116e099b79")) + xhex.Decode(sig[:], []byte("ed08d2dd5b0f7b6a3cdc74643d4adee3158ddede9cc848e8cd97630c097001acc2d052d2d3ec2b7ac4708b2314b797106d1b3c107322e61b5e5cc2116e099b79")) events := []Event{ { diff --git a/eventstore/bluge/helpers.go b/eventstore/bluge/helpers.go index 726be7a..46900e2 100644 --- a/eventstore/bluge/helpers.go +++ b/eventstore/bluge/helpers.go @@ -1,9 +1,8 @@ package bluge import ( - "encoding/hex" - "fiatjaf.com/nostr" + "github.com/templexxx/xhex" ) const ( @@ -23,6 +22,6 @@ func (id eventIdentifier) Field() string { func (id eventIdentifier) Term() []byte { idhex := make([]byte, 64) - hex.Encode(idhex, id[:]) + xhex.Encode(idhex, id[:]) return idhex } diff --git a/eventstore/boltdb/count.go b/eventstore/boltdb/count.go index 36b0440..f52d5e7 100644 --- a/eventstore/boltdb/count.go +++ b/eventstore/boltdb/count.go @@ -3,13 +3,13 @@ package boltdb import ( "bytes" "encoding/binary" - "encoding/hex" "slices" "fiatjaf.com/nostr" "fiatjaf.com/nostr/eventstore/codec/betterbinary" "fiatjaf.com/nostr/nip45" "fiatjaf.com/nostr/nip45/hyperloglog" + "github.com/templexxx/xhex" "go.etcd.io/bbolt" ) @@ -174,11 +174,11 @@ func (b *BoltBackend) countEventsHLLCached(filter nostr.Filter) (uint32, *hyperl binary.BigEndian.PutUint16(cacheKey[0:2], uint16(filter.Kinds[0])) switch filter.Kinds[0] { case 3: - hex.Decode(cacheKey[2:2+8], []byte(filter.Tags["p"][0][0:8*2])) + xhex.Decode(cacheKey[2:2+8], []byte(filter.Tags["p"][0][0:8*2])) case 7: - hex.Decode(cacheKey[2:2+8], []byte(filter.Tags["e"][0][0:8*2])) + xhex.Decode(cacheKey[2:2+8], []byte(filter.Tags["e"][0][0:8*2])) case 1111: - hex.Decode(cacheKey[2:2+8], []byte(filter.Tags["E"][0][0:8*2])) + xhex.Decode(cacheKey[2:2+8], []byte(filter.Tags["E"][0][0:8*2])) } var count uint32 @@ -204,7 +204,7 @@ func (b *BoltBackend) updateHyperLogLogCachedValues(txn *bbolt.Tx, evt nostr.Eve for ref, offset := range nip45.HyperLogLogEventPubkeyOffsetsAndReferencesForEvent(evt) { // setup cache key (reusing buffer) - hex.Decode(cacheKey[2:2+8], []byte(ref[0:8*2])) + xhex.Decode(cacheKey[2:2+8], []byte(ref[0:8*2])) // fetch hll value from cache db hll := hyperloglog.New(offset) diff --git a/eventstore/boltdb/helpers.go b/eventstore/boltdb/helpers.go index 8f0296b..6589320 100644 --- a/eventstore/boltdb/helpers.go +++ b/eventstore/boltdb/helpers.go @@ -4,7 +4,6 @@ import ( "bytes" "crypto/md5" "encoding/binary" - "encoding/hex" "fmt" "iter" "slices" @@ -12,6 +11,7 @@ import ( "strings" "fiatjaf.com/nostr" + "github.com/templexxx/xhex" "go.etcd.io/bbolt" ) @@ -284,7 +284,7 @@ func (b *BoltBackend) getTagIndexPrefix(tagName string, tagValue string) (bucket if len(tagValue) == 64 { // but we actually only use the first 8 bytes, with letter (tag name) prefix k = make([]byte, 1+8+4+8) - if _, err := hex.Decode(k[1:1+8], []byte(tagValue[0:8*2])); err == nil { + if err := xhex.Decode(k[1:1+8], []byte(tagValue[0:8*2])); err == nil { k[0] = letterPrefix return indexTag32, k } @@ -294,7 +294,7 @@ func (b *BoltBackend) getTagIndexPrefix(tagName string, tagValue string) (bucket spl := strings.Split(tagValue, ":") if len(spl) == 3 && len(spl[1]) == 64 { k = make([]byte, 1+2+8+30+4+8) - if _, err := hex.Decode(k[1+2:1+2+8], []byte(spl[1][0:8*2])); err == nil { + if err := xhex.Decode(k[1+2:1+2+8], []byte(spl[1][0:8*2])); err == nil { if kind, err := strconv.ParseUint(spl[0], 10, 16); err == nil { k[0] = letterPrefix k[1] = byte(kind >> 8) diff --git a/eventstore/lmdb/count.go b/eventstore/lmdb/count.go index 3ba8624..e955802 100644 --- a/eventstore/lmdb/count.go +++ b/eventstore/lmdb/count.go @@ -3,7 +3,6 @@ package lmdb import ( "bytes" "encoding/binary" - "encoding/hex" "slices" "fiatjaf.com/nostr" @@ -11,6 +10,7 @@ import ( "fiatjaf.com/nostr/nip45" "fiatjaf.com/nostr/nip45/hyperloglog" "github.com/PowerDNS/lmdb-go/lmdb" + "github.com/templexxx/xhex" ) func (b *LMDBBackend) CountEvents(filter nostr.Filter) (uint32, error) { @@ -185,11 +185,11 @@ func (b *LMDBBackend) countEventsHLLCached(filter nostr.Filter) (uint32, *hyperl binary.BigEndian.PutUint16(cacheKey[0:2], uint16(filter.Kinds[0])) switch filter.Kinds[0] { case 3: - hex.Decode(cacheKey[2:2+8], []byte(filter.Tags["p"][0][0:8*2])) + xhex.Decode(cacheKey[2:2+8], []byte(filter.Tags["p"][0][0:8*2])) case 7: - hex.Decode(cacheKey[2:2+8], []byte(filter.Tags["e"][0][0:8*2])) + xhex.Decode(cacheKey[2:2+8], []byte(filter.Tags["e"][0][0:8*2])) case 1111: - hex.Decode(cacheKey[2:2+8], []byte(filter.Tags["E"][0][0:8*2])) + xhex.Decode(cacheKey[2:2+8], []byte(filter.Tags["E"][0][0:8*2])) } var count uint32 @@ -217,7 +217,7 @@ func (b *LMDBBackend) updateHyperLogLogCachedValues(txn *lmdb.Txn, evt nostr.Eve for ref, offset := range nip45.HyperLogLogEventPubkeyOffsetsAndReferencesForEvent(evt) { // setup cache key (reusing buffer) - hex.Decode(cacheKey[2:2+8], []byte(ref[0:8*2])) + xhex.Decode(cacheKey[2:2+8], []byte(ref[0:8*2])) // fetch hll value from cache db hll := hyperloglog.New(offset) diff --git a/eventstore/lmdb/helpers.go b/eventstore/lmdb/helpers.go index 9e51c69..c1a4f43 100644 --- a/eventstore/lmdb/helpers.go +++ b/eventstore/lmdb/helpers.go @@ -4,7 +4,6 @@ import ( "bytes" "crypto/md5" "encoding/binary" - "encoding/hex" "fmt" "iter" "slices" @@ -13,6 +12,7 @@ import ( "fiatjaf.com/nostr" "github.com/PowerDNS/lmdb-go/lmdb" + "github.com/templexxx/xhex" ) type iterator struct { @@ -244,7 +244,7 @@ func (b *LMDBBackend) getIndexKeysForEvent(evt nostr.Event) iter.Seq[key] { // now the p-tag+kind+date if dbi == b.indexTag32 && tag[0] == "p" { k := make([]byte, 8+2+4) - hex.Decode(k[0:8], []byte(tag[1][0:8*2])) + xhex.Decode(k[0:8], []byte(tag[1][0:8*2])) binary.BigEndian.PutUint16(k[8:8+2], uint16(evt.Kind)) binary.BigEndian.PutUint32(k[8+2:8+2+4], uint32(evt.CreatedAt)) dbi := b.indexPTagKind @@ -276,7 +276,7 @@ func (b *LMDBBackend) getTagIndexPrefix(tagName string, tagValue string) (lmdb.D if len(tagValue) == 64 { // but we actually only use the first 8 bytes, with letter (tag name) prefix k = make([]byte, 1+8+4) - if _, err := hex.Decode(k[1:1+8], []byte(tagValue[0:8*2])); err == nil { + if err := xhex.Decode(k[1:1+8], []byte(tagValue[0:8*2])); err == nil { k[0] = letterPrefix offset = 1 + 8 dbi = b.indexTag32 @@ -288,7 +288,7 @@ func (b *LMDBBackend) getTagIndexPrefix(tagName string, tagValue string) (lmdb.D spl := strings.Split(tagValue, ":") if len(spl) == 3 && len(spl[1]) == 64 { k = make([]byte, 1+2+8+30+4) - if _, err := hex.Decode(k[1+2:1+2+8], []byte(spl[1][0:8*2])); err == nil { + if err := xhex.Decode(k[1+2:1+2+8], []byte(spl[1][0:8*2])); err == nil { if kind, err := strconv.ParseUint(spl[0], 10, 16); err == nil { k[0] = letterPrefix k[1] = byte(kind >> 8) diff --git a/eventstore/lmdb/query_planner.go b/eventstore/lmdb/query_planner.go index f8534e5..284cb97 100644 --- a/eventstore/lmdb/query_planner.go +++ b/eventstore/lmdb/query_planner.go @@ -2,12 +2,12 @@ package lmdb import ( "encoding/binary" - "encoding/hex" "fmt" "fiatjaf.com/nostr" "fiatjaf.com/nostr/eventstore/internal" "github.com/PowerDNS/lmdb-go/lmdb" + "github.com/templexxx/xhex" ) type query struct { @@ -76,7 +76,7 @@ func (b *LMDBBackend) prepareQueries(filter nostr.Filter) ( for _, kind := range filter.Kinds { k := make([]byte, 8+2) - if _, err := hex.Decode(k[0:8], []byte(value[0:8*2])); err != nil { + if err := xhex.Decode(k[0:8], []byte(value[0:8*2])); err != nil { return nil, nil, nil, "", nil, 0, fmt.Errorf("invalid 'p' tag '%s'", value) } binary.BigEndian.PutUint16(k[8:8+2], uint16(kind)) @@ -93,7 +93,7 @@ func (b *LMDBBackend) prepareQueries(filter nostr.Filter) ( } k := make([]byte, 8) - if _, err := hex.Decode(k[0:8], []byte(value[0:8*2])); err != nil { + if err := xhex.Decode(k[0:8], []byte(value[0:8*2])); err != nil { return nil, nil, nil, "", nil, 0, fmt.Errorf("invalid 'p' tag '%s'", value) } queries[i] = query{i: i, dbi: b.indexPTagKind, prefix: k[0:8], keySize: 8 + 2 + 4} diff --git a/eventstore/mmm/helpers.go b/eventstore/mmm/helpers.go index 7c24e6c..49614de 100644 --- a/eventstore/mmm/helpers.go +++ b/eventstore/mmm/helpers.go @@ -3,7 +3,6 @@ package mmm import ( "bytes" "encoding/binary" - "encoding/hex" "iter" "slices" "strconv" @@ -11,6 +10,7 @@ import ( "fiatjaf.com/nostr" "github.com/PowerDNS/lmdb-go/lmdb" + "github.com/templexxx/xhex" ) type iterator struct { @@ -229,7 +229,7 @@ func (il *IndexingLayer) getIndexKeysForEvent(evt nostr.Event) iter.Seq[key] { // now the p-tag+kind+date if dbi == il.indexTag32 && tag[0] == "p" { k := make([]byte, 8+2+4) - hex.Decode(k[0:8], []byte(tag[1][0:8*2])) + xhex.Decode(k[0:8], []byte(tag[1][0:8*2])) binary.BigEndian.PutUint16(k[8:8+2], uint16(evt.Kind)) binary.BigEndian.PutUint32(k[8+2:8+2+4], uint32(evt.CreatedAt)) dbi := il.indexPTagKind @@ -261,7 +261,7 @@ func (il *IndexingLayer) getTagIndexPrefix(tagName string, tagValue string) (lmd if len(tagValue) == 64 { // but we actually only use the first 8 bytes, with letter (tag name) prefix k = make([]byte, 1+8+4) - if _, err := hex.Decode(k[1:1+8], []byte(tagValue[0:8*2])); err == nil { + if err := xhex.Decode(k[1:1+8], []byte(tagValue[0:8*2])); err == nil { k[0] = letterPrefix offset = 1 + 8 dbi = il.indexTag32 @@ -273,7 +273,7 @@ func (il *IndexingLayer) getTagIndexPrefix(tagName string, tagValue string) (lmd spl := strings.Split(tagValue, ":") if len(spl) == 3 && len(spl[1]) == 64 { k = make([]byte, 1+2+8+30+4) - if _, err := hex.Decode(k[1+2:1+2+8], []byte(spl[1][0:8*2])); err == nil { + if err := xhex.Decode(k[1+2:1+2+8], []byte(spl[1][0:8*2])); err == nil { if kind, err := strconv.ParseUint(spl[0], 10, 16); err == nil { k[0] = letterPrefix k[1] = byte(kind >> 8) diff --git a/eventstore/mmm/query_planner.go b/eventstore/mmm/query_planner.go index 831b0a7..932cdae 100644 --- a/eventstore/mmm/query_planner.go +++ b/eventstore/mmm/query_planner.go @@ -2,12 +2,12 @@ package mmm import ( "encoding/binary" - "encoding/hex" "fmt" "fiatjaf.com/nostr" "fiatjaf.com/nostr/eventstore/internal" "github.com/PowerDNS/lmdb-go/lmdb" + "github.com/templexxx/xhex" ) type query struct { @@ -77,7 +77,7 @@ func (il *IndexingLayer) prepareQueries(filter nostr.Filter) ( for _, kind := range filter.Kinds { k := make([]byte, 8+2) - if _, err := hex.Decode(k[0:8], []byte(value[0:8*2])); err != nil { + if err := xhex.Decode(k[0:8], []byte(value[0:8*2])); err != nil { return nil, nil, nil, "", nil, 0, fmt.Errorf("invalid 'p' tag '%s'", value) } binary.BigEndian.PutUint16(k[8:8+2], uint16(kind)) @@ -94,7 +94,7 @@ func (il *IndexingLayer) prepareQueries(filter nostr.Filter) ( } k := make([]byte, 8) - if _, err := hex.Decode(k[0:8], []byte(value[0:8*2])); err != nil { + if err := xhex.Decode(k[0:8], []byte(value[0:8*2])); err != nil { return nil, nil, nil, "", nil, 0, fmt.Errorf("invalid 'p' tag '%s'", value) } queries[i] = query{i: i, dbi: il.indexPTagKind, prefix: k[0:8], keySize: 8 + 2 + 4, timestampSize: 4} diff --git a/eventstore/test/benchmark_test.go b/eventstore/test/benchmark_test.go index efea30d..09d927d 100644 --- a/eventstore/test/benchmark_test.go +++ b/eventstore/test/benchmark_test.go @@ -2,7 +2,6 @@ package test import ( "encoding/binary" - "encoding/hex" "fmt" "os" "testing" @@ -42,7 +41,7 @@ func runBenchmarkOn(b *testing.B, db eventstore.Store) { Content: fmt.Sprintf("hello %d", i), Tags: nostr.Tags{ {"t", fmt.Sprintf("t%d", i)}, - {"e", hex.EncodeToString(eTag)}, + {"e", nostr.HexEncodeToString(eTag)}, {"p", ref.Hex()}, }, Kind: nostr.Kind(i % 10), @@ -70,7 +69,7 @@ func runBenchmarkOn(b *testing.B, db eventstore.Store) { for i := 0; i < 20; i++ { eTag := make([]byte, 32) binary.BigEndian.PutUint16(eTag, uint16(i)) - eTags[i] = hex.EncodeToString(eTag) + eTags[i] = nostr.HexEncodeToString(eTag) } filters = append(filters, nostr.Filter{Kinds: []nostr.Kind{9}, Tags: nostr.TagMap{"e": eTags}}) filters = append(filters, nostr.Filter{Kinds: []nostr.Kind{5}, Tags: nostr.TagMap{"e": eTags, "t": []string{"t5"}}}) diff --git a/eventstore/test/second_test.go b/eventstore/test/second_test.go index 64c1580..19f397b 100644 --- a/eventstore/test/second_test.go +++ b/eventstore/test/second_test.go @@ -2,7 +2,6 @@ package test import ( "encoding/binary" - "encoding/hex" "fmt" "slices" "testing" @@ -29,7 +28,7 @@ func runSecondTestOn(t *testing.T, db eventstore.Store) { Content: fmt.Sprintf("hello %d", i), Tags: nostr.Tags{ {"t", fmt.Sprintf("t%d", i)}, - {"e", hex.EncodeToString(eTag)}, + {"e", nostr.HexEncodeToString(eTag)}, {"p", ref.Hex()}, }, Kind: nostr.Kind(i % 10), @@ -49,7 +48,7 @@ func runSecondTestOn(t *testing.T, db eventstore.Store) { for i := 0; i < 20; i++ { eTag := make([]byte, 32) binary.BigEndian.PutUint16(eTag, uint16(i)) - eTags[i] = hex.EncodeToString(eTag) + eTags[i] = nostr.HexEncodeToString(eTag) } filters := []nostr.Filter{ diff --git a/filter_easyjson.go b/filter_easyjson.go index 690d3a9..afc8429 100644 --- a/filter_easyjson.go +++ b/filter_easyjson.go @@ -1,10 +1,9 @@ package nostr import ( - "encoding/hex" - jlexer "github.com/mailru/easyjson/jlexer" jwriter "github.com/mailru/easyjson/jwriter" + "github.com/templexxx/xhex" ) func easyjsonDecodeFilter(in *jlexer.Lexer, out *Filter) { @@ -42,7 +41,7 @@ func easyjsonDecodeFilter(in *jlexer.Lexer, out *Filter) { id := ID{} b := in.UnsafeBytes() if len(b) == 64 { - hex.Decode(id[:], b) + xhex.Decode(id[:], b) } out.IDs = append(out.IDs, id) in.WantComma() @@ -79,7 +78,7 @@ func easyjsonDecodeFilter(in *jlexer.Lexer, out *Filter) { pk := PubKey{} b := in.UnsafeBytes() if len(b) == 64 { - hex.Decode(pk[:], b) + xhex.Decode(pk[:], b) } out.Authors = append(out.Authors, pk) in.WantComma() @@ -140,7 +139,7 @@ func easyjsonEncodeFilter(out *jwriter.Writer, in Filter) { if i > 0 { out.RawByte(',') } - out.RawString("\"" + hex.EncodeToString(id[:]) + "\"") + out.RawString("\"" + HexEncodeToString(id[:]) + "\"") } out.RawByte(']') } @@ -178,7 +177,7 @@ func easyjsonEncodeFilter(out *jwriter.Writer, in Filter) { if i > 0 { out.RawByte(',') } - out.RawString("\"" + hex.EncodeToString(pk[:]) + "\"") + out.RawString("\"" + HexEncodeToString(pk[:]) + "\"") } out.RawByte(']') } diff --git a/go.mod b/go.mod index aba6e89..2382bff 100644 --- a/go.mod +++ b/go.mod @@ -43,6 +43,8 @@ require ( require ( github.com/dgraph-io/ristretto/v2 v2.3.0 github.com/go-git/go-git/v5 v5.16.3 + github.com/templexxx/cpu v0.0.1 + github.com/templexxx/xhex v0.0.0-20200614015412-aed53437177b ) require ( diff --git a/go.sum b/go.sum index 847afc6..8d7bebf 100644 --- a/go.sum +++ b/go.sum @@ -247,6 +247,10 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc= +github.com/templexxx/cpu v0.0.1 h1:hY4WdLOgKdc8y13EYklu9OUTXik80BkxHoWvTO6MQQY= +github.com/templexxx/cpu v0.0.1/go.mod h1:w7Tb+7qgcAlIyX4NhLuDKt78AHA5SzPmq0Wj6HiEnnk= +github.com/templexxx/xhex v0.0.0-20200614015412-aed53437177b h1:XeDLE6c9mzHpdv3Wb1+pWBaWv/BlHK0ZYIu/KaL6eHg= +github.com/templexxx/xhex v0.0.0-20200614015412-aed53437177b/go.mod h1:7rwmCH0wC2fQvNEvPZ3sKXukhyCTyiaZ5VTZMQYpZKQ= github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= diff --git a/helpers.go b/helpers.go index 0546e91..ba5154a 100644 --- a/helpers.go +++ b/helpers.go @@ -1,13 +1,13 @@ package nostr import ( - "encoding/hex" "strconv" "strings" "sync" "unsafe" jsoniter "github.com/json-iterator/go" + "github.com/templexxx/xhex" "golang.org/x/exp/constraints" ) @@ -186,7 +186,7 @@ func extractEventID(jsonStr string) ID { // get 64 characters of the id var id [32]byte - hex.Decode(id[:], unsafe.Slice(unsafe.StringData(jsonStr[start:start+64]), 64)) + xhex.Decode(id[:], unsafe.Slice(unsafe.StringData(jsonStr[start:start+64]), 64)) return id } @@ -203,7 +203,7 @@ func extractEventPubKey(jsonStr string) PubKey { // get 64 characters of the pubkey var pk [32]byte - hex.Decode(pk[:], unsafe.Slice(unsafe.StringData(jsonStr[start:start+64]), 64)) + xhex.Decode(pk[:], unsafe.Slice(unsafe.StringData(jsonStr[start:start+64]), 64)) return pk } diff --git a/keys.go b/keys.go index 9abcb30..c850526 100644 --- a/keys.go +++ b/keys.go @@ -2,7 +2,6 @@ package nostr import ( "crypto/rand" - "encoding/hex" stdjson "encoding/json" "fmt" "io" @@ -11,6 +10,7 @@ import ( "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/schnorr" + "github.com/templexxx/xhex" ) var KeyOne = SecretKey{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1} @@ -36,11 +36,11 @@ func Generate() SecretKey { type SecretKey [32]byte func (sk SecretKey) String() string { return "sk::" + sk.Hex() } -func (sk SecretKey) Hex() string { return hex.EncodeToString(sk[:]) } +func (sk SecretKey) Hex() string { return HexEncodeToString(sk[:]) } func (sk SecretKey) Public() PubKey { return GetPublicKey(sk) } func (pk SecretKey) MarshalJSON() ([]byte, error) { res := make([]byte, 66) - hex.Encode(res[1:], pk[:]) + xhex.Encode(res[1:], pk[:]) res[0] = '"' res[65] = '"' return res, nil @@ -50,7 +50,7 @@ func (pk *SecretKey) UnmarshalJSON(buf []byte) error { if len(buf) != 66 { return fmt.Errorf("must be a hex string of 64 characters") } - if _, err := hex.Decode(pk[:], buf[1:65]); err != nil { + if err := xhex.Decode(pk[:], buf[1:65]); err != nil { return err } return nil @@ -65,7 +65,7 @@ func SecretKeyFromHex(skh string) (SecretKey, error) { return sk, fmt.Errorf("secret key should be at most 64-char hex, got '%s'", skh) } - if _, err := hex.Decode(sk[:], unsafe.Slice(unsafe.StringData(skh), 64)); err != nil { + if err := xhex.Decode(sk[:], unsafe.Slice(unsafe.StringData(skh), 64)); err != nil { return sk, fmt.Errorf("'%s' is not valid hex: %w", skh, err) } @@ -78,7 +78,7 @@ func SecretKeyFromHex(skh string) (SecretKey, error) { func MustSecretKeyFromHex(idh string) SecretKey { id := SecretKey{} - if _, err := hex.Decode(id[:], unsafe.Slice(unsafe.StringData(idh), 64)); err != nil { + if err := xhex.Decode(id[:], unsafe.Slice(unsafe.StringData(idh), 64)); err != nil { panic(err) } return id @@ -107,10 +107,10 @@ var ( ) func (pk PubKey) String() string { return "pk::" + pk.Hex() } -func (pk PubKey) Hex() string { return hex.EncodeToString(pk[:]) } +func (pk PubKey) Hex() string { return HexEncodeToString(pk[:]) } func (pk PubKey) MarshalJSON() ([]byte, error) { res := make([]byte, 66) - hex.Encode(res[1:], pk[:]) + xhex.Encode(res[1:], pk[:]) res[0] = '"' res[65] = '"' return res, nil @@ -120,7 +120,7 @@ func (pk *PubKey) UnmarshalJSON(buf []byte) error { if len(buf) != 66 { return fmt.Errorf("must be a hex string of 64 characters") } - if _, err := hex.Decode(pk[:], buf[1:65]); err != nil { + if err := xhex.Decode(pk[:], buf[1:65]); err != nil { return err } if _, err := schnorr.ParsePubKey(pk[:]); err != nil { @@ -134,7 +134,7 @@ func PubKeyFromHex(pkh string) (PubKey, error) { if len(pkh) != 64 { return pk, fmt.Errorf("pubkey should be 64-char hex, got '%s'", pkh) } - if _, err := hex.Decode(pk[:], unsafe.Slice(unsafe.StringData(pkh), 64)); err != nil { + if err := xhex.Decode(pk[:], unsafe.Slice(unsafe.StringData(pkh), 64)); err != nil { return pk, fmt.Errorf("'%s' is not valid hex: %w", pkh, err) } if _, err := schnorr.ParsePubKey(pk[:]); err != nil { @@ -148,7 +148,7 @@ func PubKeyFromHexCheap(pkh string) (PubKey, error) { if len(pkh) != 64 { return pk, fmt.Errorf("pubkey should be 64-char hex, got '%s'", pkh) } - if _, err := hex.Decode(pk[:], unsafe.Slice(unsafe.StringData(pkh), 64)); err != nil { + if err := xhex.Decode(pk[:], unsafe.Slice(unsafe.StringData(pkh), 64)); err != nil { return pk, fmt.Errorf("'%s' is not valid hex: %w", pkh, err) } @@ -157,7 +157,9 @@ func PubKeyFromHexCheap(pkh string) (PubKey, error) { func MustPubKeyFromHex(pkh string) PubKey { pk := PubKey{} - hex.Decode(pk[:], unsafe.Slice(unsafe.StringData(pkh), 64)) + if err := xhex.Decode(pk[:], unsafe.Slice(unsafe.StringData(pkh), 64)); err != nil { + panic(err) + } return pk } diff --git a/khatru/blossom/handlers.go b/khatru/blossom/handlers.go index 9a84ba6..0784c37 100644 --- a/khatru/blossom/handlers.go +++ b/khatru/blossom/handlers.go @@ -2,7 +2,6 @@ package blossom import ( "crypto/sha256" - "encoding/hex" "encoding/json" "io" "mime" @@ -128,7 +127,7 @@ func (bs BlossomServer) handleUpload(w http.ResponseWriter, r *http.Request) { } hash := sha256.Sum256(b) - hhash := hex.EncodeToString(hash[:]) + hhash := nostr.HexEncodeToString(hash[:]) mimeType := mime.TypeByExtension(ext) if mimeType == "" { mimeType = "application/octet-stream" @@ -443,7 +442,7 @@ func (bs BlossomServer) handleMirror(w http.ResponseWriter, r *http.Request) { // calculate sha256 hash := sha256.Sum256(body) - hhash := hex.EncodeToString(hash[:]) + hhash := nostr.HexEncodeToString(hash[:]) // verify hash against x tag if auth.Tags.FindWithValue("x", hhash) == nil { diff --git a/khatru/handlers.go b/khatru/handlers.go index 50e3055..72d8266 100644 --- a/khatru/handlers.go +++ b/khatru/handlers.go @@ -3,7 +3,6 @@ package khatru import ( "context" "crypto/rand" - "encoding/hex" "errors" "net/http" "slices" @@ -76,7 +75,7 @@ func (rl *Relay) HandleWebsocket(w http.ResponseWriter, r *http.Request) { ws := &WebSocket{ conn: conn, Request: r, - Challenge: rl.ChallengePrefix + hex.EncodeToString(challenge), + Challenge: rl.ChallengePrefix + nostr.HexEncodeToString(challenge), AuthedPublicKeys: make([]nostr.PubKey, 0), negentropySessions: xsync.NewMapOf[string, *NegentropySession](), } diff --git a/khatru/nip86.go b/khatru/nip86.go index c2a2540..fab0b0a 100644 --- a/khatru/nip86.go +++ b/khatru/nip86.go @@ -4,7 +4,6 @@ import ( "context" "crypto/sha256" "encoding/base64" - "encoding/hex" "encoding/json" "fmt" "io" @@ -99,7 +98,7 @@ func (rl *Relay) HandleNIP86(w http.ResponseWriter, r *http.Request) { goto respond } - if pht := evt.Tags.FindWithValue("payload", hex.EncodeToString(payloadHash[:])); pht == nil { + if pht := evt.Tags.FindWithValue("payload", nostr.HexEncodeToString(payloadHash[:])); pht == nil { resp.Error = "invalid auth event payload hash" goto respond } diff --git a/nip04/nip04.go b/nip04/nip04.go index 1ddf902..487f3b2 100644 --- a/nip04/nip04.go +++ b/nip04/nip04.go @@ -6,7 +6,6 @@ import ( "crypto/cipher" "crypto/rand" "encoding/base64" - "encoding/hex" "fmt" "strings" @@ -23,7 +22,7 @@ func ComputeSharedSecret(pub nostr.PubKey, sk [32]byte) (sharedSecret []byte, er // adding 02 to signal that this is a compressed public key (33 bytes) pubKey, err := btcec.ParsePubKey(append([]byte{2}, pub[:]...)) if err != nil { - return nil, fmt.Errorf("error parsing receiver public key '%s': %w", "02"+hex.EncodeToString(pub[:]), err) + return nil, fmt.Errorf("error parsing receiver public key '%s': %w", "02"+nostr.HexEncodeToString(pub[:]), err) } return btcec.GenerateSharedSecret(privKey, pubKey), nil diff --git a/nip04/nip04_test.go b/nip04/nip04_test.go index af6395b..966cb38 100644 --- a/nip04/nip04_test.go +++ b/nip04/nip04_test.go @@ -1,7 +1,6 @@ package nip04 import ( - "encoding/hex" "strings" "testing" @@ -56,8 +55,8 @@ func TestEncryptionAndDecryptionWithMultipleLengths(t *testing.T) { } func TestNostrToolsCompatibility(t *testing.T) { - sk1, _ := hex.DecodeString("92996316beebf94171065a714cbf164d1f56d7ad9b35b329d9fc97535bf25352") - sk2, _ := hex.DecodeString("591c0c249adfb9346f8d37dfeed65725e2eea1d7a6e99fa503342f367138de84") + sk1, _ := nostr.HexDecodeString("92996316beebf94171065a714cbf164d1f56d7ad9b35b329d9fc97535bf25352") + sk2, _ := nostr.HexDecodeString("591c0c249adfb9346f8d37dfeed65725e2eea1d7a6e99fa503342f367138de84") pk2 := nostr.GetPublicKey([32]byte(sk2)) shared, _ := ComputeSharedSecret(pk2, [32]byte(sk1)) ciphertext := "A+fRnU4aXS4kbTLfowqAww==?iv=QFYUrl5or/n/qamY79ze0A==" diff --git a/nip06/nip06.go b/nip06/nip06.go index 455c771..ab72861 100644 --- a/nip06/nip06.go +++ b/nip06/nip06.go @@ -1,8 +1,7 @@ package nip06 import ( - "encoding/hex" - + "fiatjaf.com/nostr" "github.com/tyler-smith/go-bip32" "github.com/tyler-smith/go-bip39" ) @@ -47,7 +46,7 @@ func PrivateKeyFromSeed(seed []byte) (string, error) { } } - return hex.EncodeToString(next.Key), nil + return nostr.HexEncodeToString(next.Key), nil } func ValidateWords(words string) bool { diff --git a/nip19/nip19_test.go b/nip19/nip19_test.go index d07fccc..56e0573 100644 --- a/nip19/nip19_test.go +++ b/nip19/nip19_test.go @@ -1,12 +1,12 @@ package nip19 import ( - "encoding/hex" "testing" "fiatjaf.com/nostr" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "github.com/templexxx/xhex" ) func TestEncodeNpub(t *testing.T) { @@ -17,7 +17,7 @@ func TestEncodeNpub(t *testing.T) { func TestEncodeNsec(t *testing.T) { skh := "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d" var sk [32]byte - hex.Decode(sk[:], []byte(skh)) + xhex.Decode(sk[:], []byte(skh)) nsec := EncodeNsec(sk) assert.Equal(t, "nsec180cvv07tjdrrgpa0j7j7tmnyl2yr6yr7l8j4s3evf6u64th6gkwsgyumg0", nsec, "produced an unexpected nsec string") } diff --git a/nip44/nip44.go b/nip44/nip44.go index 9a8d52c..d2c4925 100644 --- a/nip44/nip44.go +++ b/nip44/nip44.go @@ -7,7 +7,6 @@ import ( "crypto/sha256" "encoding/base64" "encoding/binary" - "encoding/hex" "fmt" "io" "math" @@ -162,7 +161,7 @@ func Decrypt(b64ciphertextWrapped string, conversationKey [32]byte) (string, err return string(unpadded), nil } -var maxThreshold, _ = hex.DecodeString("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141") +var maxThreshold, _ = nostr.HexDecodeString("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141") func GenerateConversationKey(pub nostr.PubKey, sk nostr.SecretKey) ([32]byte, error) { var ck [32]byte @@ -236,7 +235,7 @@ func computeSharedSecret(pub nostr.PubKey, sk [32]byte) (sharedSecret [32]byte, pubKey, err := btcec.ParsePubKey(append([]byte{2}, pub[:]...)) if err != nil { return sharedSecret, fmt.Errorf("error parsing receiver public key '%s': %w", - "02"+hex.EncodeToString(pub[:]), err) + "02"+nostr.HexEncodeToString(pub[:]), err) } var point, result secp256k1.JacobianPoint diff --git a/nip44/nip44_test.go b/nip44/nip44_test.go index a1b93c9..a112b76 100644 --- a/nip44/nip44_test.go +++ b/nip44/nip44_test.go @@ -2,12 +2,12 @@ package nip44 import ( "crypto/sha256" - "encoding/hex" "strings" "testing" "fiatjaf.com/nostr" "github.com/stretchr/testify/require" + "github.com/templexxx/xhex" ) func assertCryptPriv(t *testing.T, skh1 string, skh2 string, conversationKey string, salt string, plaintext string, expected string) { @@ -19,7 +19,7 @@ func assertCryptPriv(t *testing.T, skh1 string, skh2 string, conversationKey str assertConversationKeyGenerationPub(t, skh1, pub2.Hex(), conversationKey) - customNonce, err := hex.DecodeString(salt) + customNonce, err := nostr.HexDecodeString(salt) require.NoErrorf(t, err, "hex decode failed for salt: %v", err) actual, err := Encrypt(plaintext, k1, WithCustomNonce(customNonce)) @@ -70,13 +70,13 @@ func assertMessageKeyGeneration(t *testing.T, conversationKey string, salt strin convNonce, err := hexDecode32Array(salt) require.NoErrorf(t, err, "hex decode failed for nonce: %v", err) - expectedChaChaKey, err := hex.DecodeString(chachaKey) + expectedChaChaKey, err := nostr.HexDecodeString(chachaKey) require.NoErrorf(t, err, "hex decode failed for chacha key: %v", err) - expectedChaChaNonce, err := hex.DecodeString(chachaSalt) + expectedChaChaNonce, err := nostr.HexDecodeString(chachaSalt) require.NoErrorf(t, err, "hex decode failed for chacha nonce: %v", err) - expectedHmacKey, err := hex.DecodeString(hmacKey) + expectedHmacKey, err := nostr.HexDecodeString(hmacKey) require.NoErrorf(t, err, "hex decode failed for hmac key: %v", err) actualChaChaKey, actualChaChaNonce, actualHmacKey, err := messageKeys(convKey, convNonce) @@ -92,7 +92,7 @@ func assertCryptLong(t *testing.T, conversationKey string, salt string, pattern convKey, err := hexDecode32Array(conversationKey) require.NoErrorf(t, err, "hex decode failed for convKey: %v", err) - customNonce, err := hex.DecodeString(salt) + customNonce, err := nostr.HexDecodeString(salt) require.NoErrorf(t, err, "hex decode failed for salt: %v", err) plaintext := "" @@ -101,7 +101,7 @@ func assertCryptLong(t *testing.T, conversationKey string, salt string, pattern } h := sha256.New() h.Write([]byte(plaintext)) - actualPlaintextSha256 := hex.EncodeToString(h.Sum(nil)) + actualPlaintextSha256 := nostr.HexEncodeToString(h.Sum(nil)) require.Equalf(t, plaintextSha256, actualPlaintextSha256, "invalid plaintext sha256 hash: %v", err) actualPayload, err := Encrypt(plaintext, convKey, WithCustomNonce(customNonce)) @@ -109,7 +109,7 @@ func assertCryptLong(t *testing.T, conversationKey string, salt string, pattern h.Reset() h.Write([]byte(actualPayload)) - actualPayloadSha256 := hex.EncodeToString(h.Sum(nil)) + actualPayloadSha256 := nostr.HexEncodeToString(h.Sum(nil)) require.Equalf(t, payloadSha256, actualPayloadSha256, "invalid payload sha256 hash: %v", err) } @@ -1051,7 +1051,7 @@ func TestMessageKeyGeneration033(t *testing.T) { } func hexDecode32Array(hexString string) (res [32]byte, err error) { - _, err = hex.Decode(res[:], []byte(hexString)) + err = xhex.Decode(res[:], []byte(hexString)) return res, err } diff --git a/nip46/client.go b/nip46/client.go index d7b108a..a762f86 100644 --- a/nip46/client.go +++ b/nip46/client.go @@ -2,7 +2,6 @@ package nip46 import ( "context" - "encoding/hex" "fmt" "math/rand" "net/url" @@ -56,7 +55,7 @@ func ConnectBunker( pool, onAuth, ) - _, err = bunker.RPC(ctx, "connect", []string{hex.EncodeToString(parsed.HostPubKey[:]), parsed.Secret}) + _, err = bunker.RPC(ctx, "connect", []string{nostr.HexEncodeToString(parsed.HostPubKey[:]), parsed.Secret}) return bunker, err } diff --git a/nip46/dynamic-signer.go b/nip46/dynamic-signer.go index 7a63ba8..6beea76 100644 --- a/nip46/dynamic-signer.go +++ b/nip46/dynamic-signer.go @@ -2,7 +2,6 @@ package nip46 import ( "context" - "encoding/hex" "fmt" "sync" @@ -131,7 +130,7 @@ func (p *DynamicSigner) HandleRequest(ctx context.Context, event nostr.Event) ( result = "ack" case "get_public_key": - result = hex.EncodeToString(session.PublicKey[:]) + result = nostr.HexEncodeToString(session.PublicKey[:]) case "sign_event": if len(req.Params) != 1 { resultErr = fmt.Errorf("wrong number of arguments to 'sign_event'") diff --git a/nip46/static-key-signer.go b/nip46/static-key-signer.go index 8816a81..90e5008 100644 --- a/nip46/static-key-signer.go +++ b/nip46/static-key-signer.go @@ -2,7 +2,6 @@ package nip46 import ( "context" - "encoding/hex" "fmt" "sync" @@ -90,7 +89,7 @@ func (p *StaticKeySigner) HandleRequest(_ context.Context, event nostr.Event) ( result = "ack" harmless = true case "get_public_key": - result = hex.EncodeToString(session.PublicKey[:]) + result = nostr.HexEncodeToString(session.PublicKey[:]) harmless = true case "sign_event": if len(req.Params) != 1 { diff --git a/nip60/helpers.go b/nip60/helpers.go index a30ca0f..8f2679e 100644 --- a/nip60/helpers.go +++ b/nip60/helpers.go @@ -3,13 +3,13 @@ package nip60 import ( "crypto/rand" "crypto/sha256" - "encoding/hex" "encoding/json" "errors" "fmt" "strconv" "strings" + "fiatjaf.com/nostr" "github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/decred/dcrd/dcrec/secp256k1/v4" @@ -66,7 +66,7 @@ func createBlindedMessages( if _, err := rand.Read(secretBytes); err != nil { return nil, nil, nil, err } - secret = hex.EncodeToString(secretBytes) + secret = nostr.HexEncodeToString(secretBytes) } B_, r, err := crypto.BlindMessage(secret, r) @@ -92,7 +92,7 @@ func signInput( return "", fmt.Errorf("failed to sign: %w", err) } witness, _ := json.Marshal(nut11.P2PKWitness{ - Signatures: []string{hex.EncodeToString(signature.Serialize())}, + Signatures: []string{nostr.HexEncodeToString(signature.Serialize())}, }) return string(witness), nil } @@ -101,14 +101,14 @@ func signOutput( privateKey *btcec.PrivateKey, output cashu.BlindedMessage, ) (string, error) { - msg, _ := hex.DecodeString(output.B_) + msg, _ := nostr.HexDecodeString(output.B_) hash := sha256.Sum256(msg) signature, err := schnorr.Sign(privateKey, hash[:]) if err != nil { return "", fmt.Errorf("failed to sign: %w", err) } witness, _ := json.Marshal(nut11.P2PKWitness{ - Signatures: []string{hex.EncodeToString(signature.Serialize())}, + Signatures: []string{nostr.HexEncodeToString(signature.Serialize())}, }) return string(witness), nil } @@ -143,7 +143,7 @@ func constructProofs( dleq = &cashu.DLEQProof{ E: blindedSignature.DLEQ.E, S: blindedSignature.DLEQ.S, - R: hex.EncodeToString(prep.rs[i].Serialize()), + R: nostr.HexEncodeToString(prep.rs[i].Serialize()), } } } @@ -170,7 +170,7 @@ func unblindSignature(C_str string, r *secp256k1.PrivateKey, key *secp256k1.Publ string, error, ) { - C_bytes, err := hex.DecodeString(C_str) + C_bytes, err := nostr.HexDecodeString(C_str) if err != nil { return "", err } @@ -180,14 +180,14 @@ func unblindSignature(C_str string, r *secp256k1.PrivateKey, key *secp256k1.Publ } C := crypto.UnblindSignature(C_, r, key) - Cstr := hex.EncodeToString(C.SerializeCompressed()) + Cstr := nostr.HexEncodeToString(C.SerializeCompressed()) return Cstr, nil } func ParseKeysetKeys(keys nut01.KeysMap) (map[uint64]*btcec.PublicKey, error) { parsedKeys := make(map[uint64]*btcec.PublicKey) for amount, pkh := range keys { - pkb, err := hex.DecodeString(pkh) + pkb, err := nostr.HexDecodeString(pkh) if err != nil { return nil, err } diff --git a/nip60/send.go b/nip60/send.go index 64ff4e7..668601c 100644 --- a/nip60/send.go +++ b/nip60/send.go @@ -2,7 +2,6 @@ package nip60 import ( "context" - "encoding/hex" "fmt" "slices" @@ -42,7 +41,7 @@ func (opts SendOptions) asSpendingCondition(refund *btcec.PublicKey) *nut10.Spen return &nut10.SpendingCondition{ Kind: nut10.HTLC, - Data: hex.EncodeToString(opts.Hashlock[:]), + Data: nostr.HexEncodeToString(opts.Hashlock[:]), Tags: nut11.SerializeP2PKTags(tags), } } else if opts.P2PK != nil { @@ -62,7 +61,7 @@ func (opts SendOptions) asSpendingCondition(refund *btcec.PublicKey) *nut10.Spen return &nut10.SpendingCondition{ Kind: nut10.P2PK, - Data: hex.EncodeToString(opts.P2PK.SerializeCompressed()), + Data: nostr.HexEncodeToString(opts.P2PK.SerializeCompressed()), Tags: nut11.SerializeP2PKTags(tags), } } else { diff --git a/nip60/wallet.go b/nip60/wallet.go index 251236e..ee5864b 100644 --- a/nip60/wallet.go +++ b/nip60/wallet.go @@ -2,7 +2,6 @@ package nip60 import ( "context" - "encoding/hex" "encoding/json" "fmt" "slices" @@ -344,7 +343,7 @@ func (w *Wallet) SetPrivateKey(ctx context.Context, privateKey string) error { return fmt.Errorf("can't do write operations: missing PublishUpdate function") } - skb, err := hex.DecodeString(privateKey) + skb, err := nostr.HexDecodeString(privateKey) if err != nil { return err } @@ -378,7 +377,7 @@ func (w *Wallet) toEvent(ctx context.Context, kr nostr.Keyer, evt *nostr.Event) encryptedTags := make(nostr.Tags, 0, 1+len(w.Mints)) if w.PrivateKey != nil { - encryptedTags = append(encryptedTags, nostr.Tag{"privkey", hex.EncodeToString(w.PrivateKey.Serialize())}) + encryptedTags = append(encryptedTags, nostr.Tag{"privkey", nostr.HexEncodeToString(w.PrivateKey.Serialize())}) } for _, mint := range w.Mints { @@ -433,7 +432,7 @@ func (w *Wallet) parse(ctx context.Context, kr nostr.Keyer, evt *nostr.Event) er case "mint": mints = append(mints, tag[1]) case "privkey": - skb, err := hex.DecodeString(tag[1]) + skb, err := nostr.HexDecodeString(tag[1]) if err != nil { return fmt.Errorf("failed to parse private key: %w", err) } diff --git a/nip61/verify.go b/nip61/verify.go index 865f33f..2be6a92 100644 --- a/nip61/verify.go +++ b/nip61/verify.go @@ -1,7 +1,6 @@ package nip61 import ( - "encoding/hex" "encoding/json" "fiatjaf.com/nostr" @@ -52,7 +51,7 @@ func verifyProofDLEQ( return false } - CBytes, err := hex.DecodeString(proof.C) + CBytes, err := nostr.HexDecodeString(proof.C) if err != nil { return false } @@ -88,7 +87,7 @@ func VerifyBlindSignatureDLEQ( return false } - B_bytes, err := hex.DecodeString(B_str) + B_bytes, err := nostr.HexDecodeString(B_str) if err != nil { return false } @@ -97,7 +96,7 @@ func VerifyBlindSignatureDLEQ( return false } - C_bytes, err := hex.DecodeString(C_str) + C_bytes, err := nostr.HexDecodeString(C_str) if err != nil { return false } @@ -115,13 +114,13 @@ func parseDLEQ(dleq cashu.DLEQProof) ( *btcec.PrivateKey, error, ) { - ebytes, err := hex.DecodeString(dleq.E) + ebytes, err := nostr.HexDecodeString(dleq.E) if err != nil { return nil, nil, nil, err } e := secp256k1.PrivKeyFromBytes(ebytes) - sbytes, err := hex.DecodeString(dleq.S) + sbytes, err := nostr.HexDecodeString(dleq.S) if err != nil { return nil, nil, nil, err } @@ -131,7 +130,7 @@ func parseDLEQ(dleq cashu.DLEQProof) ( return e, s, nil, nil } - rbytes, err := hex.DecodeString(dleq.R) + rbytes, err := nostr.HexDecodeString(dleq.R) if err != nil { return nil, nil, nil, err } diff --git a/nip77/negentropy/negentropy.go b/nip77/negentropy/negentropy.go index 89f034b..7c8b707 100644 --- a/nip77/negentropy/negentropy.go +++ b/nip77/negentropy/negentropy.go @@ -2,7 +2,6 @@ package negentropy import ( "bytes" - "encoding/hex" "fmt" "io" "math" @@ -72,12 +71,12 @@ func (n *Negentropy) Start() string { output.WriteByte(protocolVersion) n.SplitRange(0, n.storage.Size(), InfiniteBound, output) - return hex.EncodeToString(output.Bytes()) + return nostr.HexEncodeToString(output.Bytes()) } func (n *Negentropy) Reconcile(msg string) (string, error) { n.initialized = true - msgb, err := hex.DecodeString(msg) + msgb, err := nostr.HexDecodeString(msg) if err != nil { return "", err } @@ -99,7 +98,7 @@ func (n *Negentropy) Reconcile(msg string) (string, error) { return "", nil } - return hex.EncodeToString(output), nil + return nostr.HexEncodeToString(output), nil } func (n *Negentropy) reconcileAux(reader *bytes.Reader) ([]byte, error) { diff --git a/nipb0/blossom/list.go b/nipb0/blossom/list.go index 7bb83af..00bc3dd 100644 --- a/nipb0/blossom/list.go +++ b/nipb0/blossom/list.go @@ -2,7 +2,6 @@ package blossom import ( "context" - "encoding/hex" "fmt" "fiatjaf.com/nostr" @@ -16,7 +15,7 @@ func (c *Client) List(ctx context.Context) ([]BlobDescriptor, error) { } bds := make([]BlobDescriptor, 0, 100) - err = c.httpCall(ctx, "GET", "list/"+hex.EncodeToString(pubkey[:]), "", func() string { + err = c.httpCall(ctx, "GET", "list/"+nostr.HexEncodeToString(pubkey[:]), "", func() string { return c.authorizationHeader(ctx, func(evt *nostr.Event) { evt.Tags = append(evt.Tags, nostr.Tag{"t", "list"}) }) diff --git a/nipb0/blossom/upload.go b/nipb0/blossom/upload.go index f051528..775e8b6 100644 --- a/nipb0/blossom/upload.go +++ b/nipb0/blossom/upload.go @@ -3,7 +3,6 @@ package blossom import ( "context" "crypto/sha256" - "encoding/hex" "fmt" "io" "mime" @@ -47,7 +46,7 @@ func (c *Client) UploadBlob(ctx context.Context, file io.ReadSeeker, contentType err = c.httpCall(ctx, "PUT", "upload", contentType, func() string { return c.authorizationHeader(ctx, func(evt *nostr.Event) { evt.Tags = append(evt.Tags, nostr.Tag{"t", "upload"}) - evt.Tags = append(evt.Tags, nostr.Tag{"x", hex.EncodeToString(hash[:])}) + evt.Tags = append(evt.Tags, nostr.Tag{"x", nostr.HexEncodeToString(hash[:])}) }) }, file, size, &bd) if err != nil { diff --git a/pointers.go b/pointers.go index 33f39a1..a992086 100644 --- a/pointers.go +++ b/pointers.go @@ -1,7 +1,6 @@ package nostr import ( - "encoding/hex" "fmt" "strconv" "strings" @@ -56,13 +55,13 @@ func ProfilePointerFromTag(refTag Tag) (ProfilePointer, error) { // MatchesEvent checks if the pointer matches an event. func (ep ProfilePointer) MatchesEvent(_ Event) bool { return false } func (ep ProfilePointer) AsFilter() Filter { return Filter{Authors: []PubKey{ep.PublicKey}} } -func (ep ProfilePointer) AsTagReference() string { return hex.EncodeToString(ep.PublicKey[:]) } +func (ep ProfilePointer) AsTagReference() string { return HexEncodeToString(ep.PublicKey[:]) } func (ep ProfilePointer) AsTag() Tag { if len(ep.Relays) > 0 { - return Tag{"p", hex.EncodeToString(ep.PublicKey[:]), ep.Relays[0]} + return Tag{"p", HexEncodeToString(ep.PublicKey[:]), ep.Relays[0]} } - return Tag{"p", hex.EncodeToString(ep.PublicKey[:])} + return Tag{"p", HexEncodeToString(ep.PublicKey[:])} } // EventPointer represents a pointer to a nostr event. @@ -98,18 +97,18 @@ func EventPointerFromTag(refTag Tag) (EventPointer, error) { func (ep EventPointer) MatchesEvent(evt Event) bool { return evt.ID == ep.ID } func (ep EventPointer) AsFilter() Filter { return Filter{IDs: []ID{ep.ID}} } -func (ep EventPointer) AsTagReference() string { return hex.EncodeToString(ep.ID[:]) } +func (ep EventPointer) AsTagReference() string { return HexEncodeToString(ep.ID[:]) } // AsTag converts the pointer to a Tag. func (ep EventPointer) AsTag() Tag { if len(ep.Relays) > 0 { if ep.Author != [32]byte{} { - return Tag{"e", hex.EncodeToString(ep.ID[:]), ep.Relays[0], hex.EncodeToString(ep.Author[:])} + return Tag{"e", HexEncodeToString(ep.ID[:]), ep.Relays[0], HexEncodeToString(ep.Author[:])} } else { - return Tag{"e", hex.EncodeToString(ep.ID[:]), ep.Relays[0]} + return Tag{"e", HexEncodeToString(ep.ID[:]), ep.Relays[0]} } } - return Tag{"e", hex.EncodeToString(ep.ID[:])} + return Tag{"e", HexEncodeToString(ep.ID[:])} } // EntityPointer represents a pointer to a nostr entity (addressable event). diff --git a/pointers_easyjson.go b/pointers_easyjson.go index 1973e24..7c99e80 100644 --- a/pointers_easyjson.go +++ b/pointers_easyjson.go @@ -1,10 +1,9 @@ package nostr import ( - "encoding/hex" - jlexer "github.com/mailru/easyjson/jlexer" jwriter "github.com/mailru/easyjson/jwriter" + "github.com/templexxx/xhex" ) func easyjson33014d6eDecodeFiatjafComNostr(in *jlexer.Lexer, out *ProfilePointer) { @@ -30,7 +29,7 @@ func easyjson33014d6eDecodeFiatjafComNostr(in *jlexer.Lexer, out *ProfilePointer if in.IsNull() { in.Skip() } else { - hex.Decode(out.PublicKey[:], in.UnsafeBytes()) + xhex.Decode(out.PublicKey[:], in.UnsafeBytes()) } case "relays": if in.IsNull() { @@ -137,7 +136,7 @@ func easyjson33014d6eDecodeFiatjafComNostr1(in *jlexer.Lexer, out *EventPointer) if in.IsNull() { in.Skip() } else { - hex.Decode(out.ID[:], in.UnsafeBytes()) + xhex.Decode(out.ID[:], in.UnsafeBytes()) } case "relays": if in.IsNull() { @@ -164,7 +163,7 @@ func easyjson33014d6eDecodeFiatjafComNostr1(in *jlexer.Lexer, out *EventPointer) if in.IsNull() { in.Skip() } else { - hex.Decode(out.Author[:], in.UnsafeBytes()) + xhex.Decode(out.Author[:], in.UnsafeBytes()) } case "kind": out.Kind = Kind(in.Uint16()) @@ -262,7 +261,7 @@ func easyjson33014d6eDecodeFiatjafComNostr2(in *jlexer.Lexer, out *EntityPointer if in.IsNull() { in.Skip() } else { - hex.Decode(out.PublicKey[:], in.UnsafeBytes()) + xhex.Decode(out.PublicKey[:], in.UnsafeBytes()) } case "kind": out.Kind = Kind(in.Uint16()) diff --git a/schema/schema.go b/schema/schema.go index dbec4a7..0b8a476 100644 --- a/schema/schema.go +++ b/schema/schema.go @@ -2,7 +2,6 @@ package schema import ( _ "embed" - "encoding/hex" "fmt" "net/url" "slices" @@ -12,6 +11,7 @@ import ( "fiatjaf.com/nostr" "github.com/segmentio/encoding/json" + "github.com/templexxx/xhex" "gopkg.in/yaml.v3" ) @@ -186,7 +186,7 @@ func (v *Validator) validateNext(tag nostr.Tag, index int, this *nextSpec) (fail if len(tag[index]) != 40 { return index, fmt.Errorf("invalid gitcommit at tag '%s', index %d", tag[0], index) } - if _, err := hex.Decode(gitcommitdummydecoder, unsafe.Slice(unsafe.StringData(tag[index]), 40)); err != nil { + if err := xhex.Decode(gitcommitdummydecoder, unsafe.Slice(unsafe.StringData(tag[index]), 40)); err != nil { return index, fmt.Errorf("invalid gitcommit at tag '%s', index %d", tag[0], index) } case "free": diff --git a/sdk/note.go b/sdk/note.go index 62e4382..952ce59 100644 --- a/sdk/note.go +++ b/sdk/note.go @@ -2,7 +2,6 @@ package sdk import ( "context" - "encoding/hex" "regexp" "strings" "time" @@ -57,7 +56,7 @@ func (sys *System) PrepareNoteEvent(ctx context.Context, evt *nostr.Event) (targ case nostr.ProfilePointer: pk = b.PublicKey // add p tag if not already present - if tag := evt.Tags.FindWithValue("p", hex.EncodeToString(b.PublicKey[:])); tag == nil { + if tag := evt.Tags.FindWithValue("p", nostr.HexEncodeToString(b.PublicKey[:])); tag == nil { evt.Tags = append(evt.Tags, b.AsTag()) } case nostr.EventPointer: @@ -77,7 +76,7 @@ func (sys *System) PrepareNoteEvent(ctx context.Context, evt *nostr.Event) (targ } // add e tag if not already present - if tag := evt.Tags.FindWithValue("q", hex.EncodeToString(b.ID[:])); tag != nil { + if tag := evt.Tags.FindWithValue("q", nostr.HexEncodeToString(b.ID[:])); tag != nil { if len(tag) == 2 { tag = append(tag, relay) // shove this relay hint here } diff --git a/types.go b/types.go index 6149f97..2a4111f 100644 --- a/types.go +++ b/types.go @@ -1,10 +1,11 @@ package nostr import ( - "encoding/hex" stdjson "encoding/json" "fmt" "unsafe" + + "github.com/templexxx/xhex" ) // RelayEvent represents an event received from a specific relay. @@ -24,11 +25,11 @@ var ( ) func (id ID) String() string { return "id::" + id.Hex() } -func (id ID) Hex() string { return hex.EncodeToString(id[:]) } +func (id ID) Hex() string { return HexEncodeToString(id[:]) } func (id ID) MarshalJSON() ([]byte, error) { res := make([]byte, 66) - hex.Encode(res[1:], id[:]) + xhex.Encode(res[1:], id[:]) res[0] = '"' res[65] = '"' return res, nil @@ -36,9 +37,9 @@ func (id ID) MarshalJSON() ([]byte, error) { func (id *ID) UnmarshalJSON(buf []byte) error { if len(buf) != 66 { - return fmt.Errorf("must be a hex string of 64 characters") + return fmt.Errorf("must be a quoted hex string of 64 characters") } - _, err := hex.Decode(id[:], buf[1:65]) + err := xhex.Decode(id[:], buf[1:65]) return err } @@ -48,7 +49,7 @@ func IDFromHex(idh string) (ID, error) { if len(idh) != 64 { return id, fmt.Errorf("pubkey should be 64-char hex, got '%s'", idh) } - if _, err := hex.Decode(id[:], unsafe.Slice(unsafe.StringData(idh), 64)); err != nil { + if err := xhex.Decode(id[:], unsafe.Slice(unsafe.StringData(idh), 64)); err != nil { return id, fmt.Errorf("'%s' is not valid hex: %w", idh, err) } @@ -57,7 +58,7 @@ func IDFromHex(idh string) (ID, error) { func MustIDFromHex(idh string) ID { id := ID{} - if _, err := hex.Decode(id[:], unsafe.Slice(unsafe.StringData(idh), 64)); err != nil { + if err := xhex.Decode(id[:], unsafe.Slice(unsafe.StringData(idh), 64)); err != nil { panic(err) } return id diff --git a/types_test.go b/types_test.go index d95fa64..df82b0c 100644 --- a/types_test.go +++ b/types_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/stretchr/testify/require" + "github.com/templexxx/cpu" ) func TestIDJSONEncoding(t *testing.T) { @@ -25,8 +26,10 @@ func TestIDJSONEncoding(t *testing.T) { require.Error(t, err) // test unmarshaling invalid hex - err = json.Unmarshal([]byte(`"zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz"`), &id2) - require.Error(t, err) + if !cpu.X86.HasAVX2 { + err = json.Unmarshal([]byte(`"zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz"`), &id2) + require.Error(t, err) + } } func TestPubKeyJSONEncoding(t *testing.T) { diff --git a/utils.go b/utils.go index 0a63417..e8bea0a 100644 --- a/utils.go +++ b/utils.go @@ -3,9 +3,11 @@ package nostr import ( "bytes" "cmp" - "encoding/hex" "net/url" "slices" + "unsafe" + + "github.com/templexxx/xhex" ) // IsValidRelayURL checks if a URL is a valid relay URL (ws:// or wss://). @@ -20,6 +22,27 @@ func IsValidRelayURL(u string) bool { return true } +// HexEncodeToString encodes src into a hex string. +func HexEncodeToString(src []byte) string { + dst := make([]byte, len(src)*2) + xhex.Encode(dst, src) + return unsafe.String(unsafe.SliceData(dst), len(dst)) +} + +// HexDecodeString decodes a hex string into bytes. +func HexDecodeString(s string) ([]byte, error) { + src := unsafe.Slice(unsafe.StringData(s), len(s)) + if len(src)%2 != 0 { + return nil, xhex.ErrLength + } + dst := make([]byte, len(src)/2) + err := xhex.Decode(dst, src) + if err != nil { + return nil, err + } + return dst, nil +} + // IsValid32ByteHex checks if a string is a valid 32-byte hex string. func IsValid32ByteHex(thing string) bool { if !isLowerHex(thing) { @@ -28,7 +51,7 @@ func IsValid32ByteHex(thing string) bool { if len(thing) != 64 { return false } - _, err := hex.DecodeString(thing) + _, err := HexDecodeString(thing) return err == nil }