use xhex everywhere.

This commit is contained in:
fiatjaf
2025-11-21 21:16:34 -03:00
parent 61b9717c5c
commit 55a43e46b7
46 changed files with 185 additions and 177 deletions

View File

@@ -1,7 +1,6 @@
package nostr package nostr
import ( import (
"encoding/hex"
"errors" "errors"
"fmt" "fmt"
"strconv" "strconv"
@@ -10,6 +9,7 @@ import (
"github.com/mailru/easyjson" "github.com/mailru/easyjson"
jwriter "github.com/mailru/easyjson/jwriter" jwriter "github.com/mailru/easyjson/jwriter"
"github.com/templexxx/xhex"
"github.com/tidwall/gjson" "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 { 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 v.Count = countResult.Count
if len(countResult.HLL) == 512 { if len(countResult.HLL) == 512 {
v.HyperLogLog, err = hex.DecodeString(countResult.HLL) v.HyperLogLog, err = HexDecodeString(countResult.HLL)
if err != nil { if err != nil {
return fmt.Errorf("invalid \"hll\" value in COUNT message: %w", err) 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 { if v.HyperLogLog != nil {
w.RawString(`,"hll":"`) w.RawString(`,"hll":"`)
hllHex := make([]byte, 512) hllHex := make([]byte, 512)
hex.Encode(hllHex, v.HyperLogLog) xhex.Encode(hllHex, v.HyperLogLog)
w.Buffer.AppendBytes(hllHex) w.Buffer.AppendBytes(hllHex)
w.RawString(`"`) w.RawString(`"`)
} }
@@ -365,7 +365,7 @@ func (v *OKEnvelope) FromJSON(data string) error {
if len(arr) < 4 { if len(arr) < 4 {
return fmt.Errorf("failed to decode OK envelope: missing fields") 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 return err
} }
v.OK = arr[2].Raw == "true" v.OK = arr[2].Raw == "true"
@@ -377,7 +377,7 @@ func (v *OKEnvelope) FromJSON(data string) error {
func (v OKEnvelope) MarshalJSON() ([]byte, error) { func (v OKEnvelope) MarshalJSON() ([]byte, error) {
w := jwriter.Writer{NoEscapeHTML: true} w := jwriter.Writer{NoEscapeHTML: true}
w.RawString(`["OK","`) w.RawString(`["OK","`)
w.RawString(hex.EncodeToString(v.EventID[:])) w.RawString(HexEncodeToString(v.EventID[:]))
w.RawString(`",`) w.RawString(`",`)
ok := "false" ok := "false"
if v.OK { if v.OK {

View File

@@ -1,11 +1,11 @@
package nostr package nostr
import ( import (
"encoding/hex"
"testing" "testing"
"unsafe" "unsafe"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/templexxx/xhex"
) )
func TestParseMessage(t *testing.T) { func TestParseMessage(t *testing.T) {
@@ -139,6 +139,6 @@ func TestParseMessage(t *testing.T) {
func mustSigFromHex(sigStr string) [64]byte { func mustSigFromHex(sigStr string) [64]byte {
var sig [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 return sig
} }

View File

@@ -2,10 +2,10 @@ package nostr
import ( import (
"crypto/sha256" "crypto/sha256"
"encoding/hex"
"strconv" "strconv"
"github.com/mailru/easyjson" "github.com/mailru/easyjson"
"github.com/templexxx/xhex"
) )
// Event represents a Nostr event. // Event represents a Nostr event.
@@ -38,15 +38,12 @@ func (evt Event) CheckID() bool {
func (evt Event) Serialize() []byte { func (evt Event) Serialize() []byte {
// the serialization process is just putting everything into a JSON array // the serialization process is just putting everything into a JSON array
// so the order is kept. See NIP-01 // so the order is kept. See NIP-01
dst := make([]byte, 0, 100+len(evt.Content)+len(evt.Tags)*80) dst := make([]byte, 4+64, 100+len(evt.Content)+len(evt.Tags)*80)
return serializeEventInto(evt, dst)
}
func serializeEventInto(evt Event, dst []byte) []byte {
// the header portion is easy to serialize // the header portion is easy to serialize
// [0,"pubkey",created_at,kind,[ // [0,"pubkey",created_at,kind,[
dst = append(dst, `[0,"`...) copy(dst, `[0,"`)
dst = hex.AppendEncode(dst, evt.PubKey[:]) xhex.Encode(dst[4:4+64], evt.PubKey[:]) // there will always be such capacity
dst = append(dst, `",`...) dst = append(dst, `",`...)
dst = append(dst, strconv.FormatInt(int64(evt.CreatedAt), 10)...) dst = append(dst, strconv.FormatInt(int64(evt.CreatedAt), 10)...)
dst = append(dst, `,`...) dst = append(dst, `,`...)

View File

@@ -1,10 +1,9 @@
package nostr package nostr
import ( import (
"encoding/hex"
jlexer "github.com/mailru/easyjson/jlexer" jlexer "github.com/mailru/easyjson/jlexer"
jwriter "github.com/mailru/easyjson/jwriter" jwriter "github.com/mailru/easyjson/jwriter"
"github.com/templexxx/xhex"
) )
func easyjsonDecodeEvent(in *jlexer.Lexer, out *Event) { func easyjsonDecodeEvent(in *jlexer.Lexer, out *Event) {
@@ -29,12 +28,12 @@ func easyjsonDecodeEvent(in *jlexer.Lexer, out *Event) {
case "id": case "id":
b := in.UnsafeBytes() b := in.UnsafeBytes()
if len(b) == 64 { if len(b) == 64 {
hex.Decode(out.ID[:], b) xhex.Decode(out.ID[:], b)
} }
case "pubkey": case "pubkey":
b := in.UnsafeBytes() b := in.UnsafeBytes()
if len(b) == 64 { if len(b) == 64 {
hex.Decode(out.PubKey[:], b) xhex.Decode(out.PubKey[:], b)
} }
case "created_at": case "created_at":
out.CreatedAt = Timestamp(in.Int64()) out.CreatedAt = Timestamp(in.Int64())
@@ -73,7 +72,7 @@ func easyjsonDecodeEvent(in *jlexer.Lexer, out *Event) {
case "sig": case "sig":
b := in.UnsafeBytes() b := in.UnsafeBytes()
if len(b) == 128 { if len(b) == 128 {
hex.Decode(out.Sig[:], b) xhex.Decode(out.Sig[:], b)
} }
} }
in.WantComma() in.WantComma()
@@ -92,12 +91,12 @@ func easyjsonEncodeEvent(out *jwriter.Writer, in Event) {
if in.ID != ZeroID { if in.ID != ZeroID {
out.RawString(",\"id\":\"") out.RawString(",\"id\":\"")
out.RawString(hex.EncodeToString(in.ID[:]) + "\"") out.RawString(HexEncodeToString(in.ID[:]) + "\"")
} }
if in.PubKey != ZeroPK { if in.PubKey != ZeroPK {
out.RawString(",\"pubkey\":\"") out.RawString(",\"pubkey\":\"")
out.RawString(hex.EncodeToString(in.PubKey[:]) + "\"") out.RawString(HexEncodeToString(in.PubKey[:]) + "\"")
} }
out.RawString(",\"created_at\":") out.RawString(",\"created_at\":")
@@ -125,7 +124,7 @@ func easyjsonEncodeEvent(out *jwriter.Writer, in Event) {
if in.Sig != [64]byte{} { if in.Sig != [64]byte{} {
out.RawString(",\"sig\":\"") out.RawString(",\"sig\":\"")
out.RawString(hex.EncodeToString(in.Sig[:]) + "\"") out.RawString(HexEncodeToString(in.Sig[:]) + "\"")
} }
out.RawByte('}') out.RawByte('}')

View File

@@ -1,13 +1,13 @@
package nostr package nostr
import ( import (
"encoding/hex"
"fmt" "fmt"
"math/rand/v2" "math/rand/v2"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/templexxx/xhex"
) )
func TestEventParsingAndVerifying(t *testing.T) { func TestEventParsingAndVerifying(t *testing.T) {
@@ -36,7 +36,7 @@ func TestEventParsingAndVerifying(t *testing.T) {
func TestEventSerialization(t *testing.T) { func TestEventSerialization(t *testing.T) {
sig := [64]byte{} sig := [64]byte{}
hex.Decode(sig[:], []byte("ed08d2dd5b0f7b6a3cdc74643d4adee3158ddede9cc848e8cd97630c097001acc2d052d2d3ec2b7ac4708b2314b797106d1b3c107322e61b5e5cc2116e099b79")) xhex.Decode(sig[:], []byte("ed08d2dd5b0f7b6a3cdc74643d4adee3158ddede9cc848e8cd97630c097001acc2d052d2d3ec2b7ac4708b2314b797106d1b3c107322e61b5e5cc2116e099b79"))
events := []Event{ events := []Event{
{ {

View File

@@ -1,9 +1,8 @@
package bluge package bluge
import ( import (
"encoding/hex"
"fiatjaf.com/nostr" "fiatjaf.com/nostr"
"github.com/templexxx/xhex"
) )
const ( const (
@@ -23,6 +22,6 @@ func (id eventIdentifier) Field() string {
func (id eventIdentifier) Term() []byte { func (id eventIdentifier) Term() []byte {
idhex := make([]byte, 64) idhex := make([]byte, 64)
hex.Encode(idhex, id[:]) xhex.Encode(idhex, id[:])
return idhex return idhex
} }

View File

@@ -3,13 +3,13 @@ package boltdb
import ( import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"encoding/hex"
"slices" "slices"
"fiatjaf.com/nostr" "fiatjaf.com/nostr"
"fiatjaf.com/nostr/eventstore/codec/betterbinary" "fiatjaf.com/nostr/eventstore/codec/betterbinary"
"fiatjaf.com/nostr/nip45" "fiatjaf.com/nostr/nip45"
"fiatjaf.com/nostr/nip45/hyperloglog" "fiatjaf.com/nostr/nip45/hyperloglog"
"github.com/templexxx/xhex"
"go.etcd.io/bbolt" "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])) binary.BigEndian.PutUint16(cacheKey[0:2], uint16(filter.Kinds[0]))
switch filter.Kinds[0] { switch filter.Kinds[0] {
case 3: 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: 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: 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 var count uint32
@@ -204,7 +204,7 @@ func (b *BoltBackend) updateHyperLogLogCachedValues(txn *bbolt.Tx, evt nostr.Eve
for ref, offset := range nip45.HyperLogLogEventPubkeyOffsetsAndReferencesForEvent(evt) { for ref, offset := range nip45.HyperLogLogEventPubkeyOffsetsAndReferencesForEvent(evt) {
// setup cache key (reusing buffer) // 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 // fetch hll value from cache db
hll := hyperloglog.New(offset) hll := hyperloglog.New(offset)

View File

@@ -4,7 +4,6 @@ import (
"bytes" "bytes"
"crypto/md5" "crypto/md5"
"encoding/binary" "encoding/binary"
"encoding/hex"
"fmt" "fmt"
"iter" "iter"
"slices" "slices"
@@ -12,6 +11,7 @@ import (
"strings" "strings"
"fiatjaf.com/nostr" "fiatjaf.com/nostr"
"github.com/templexxx/xhex"
"go.etcd.io/bbolt" "go.etcd.io/bbolt"
) )
@@ -284,7 +284,7 @@ func (b *BoltBackend) getTagIndexPrefix(tagName string, tagValue string) (bucket
if len(tagValue) == 64 { if len(tagValue) == 64 {
// but we actually only use the first 8 bytes, with letter (tag name) prefix // but we actually only use the first 8 bytes, with letter (tag name) prefix
k = make([]byte, 1+8+4+8) 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 k[0] = letterPrefix
return indexTag32, k return indexTag32, k
} }
@@ -294,7 +294,7 @@ func (b *BoltBackend) getTagIndexPrefix(tagName string, tagValue string) (bucket
spl := strings.Split(tagValue, ":") spl := strings.Split(tagValue, ":")
if len(spl) == 3 && len(spl[1]) == 64 { if len(spl) == 3 && len(spl[1]) == 64 {
k = make([]byte, 1+2+8+30+4+8) 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 { if kind, err := strconv.ParseUint(spl[0], 10, 16); err == nil {
k[0] = letterPrefix k[0] = letterPrefix
k[1] = byte(kind >> 8) k[1] = byte(kind >> 8)

View File

@@ -3,7 +3,6 @@ package lmdb
import ( import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"encoding/hex"
"slices" "slices"
"fiatjaf.com/nostr" "fiatjaf.com/nostr"
@@ -11,6 +10,7 @@ import (
"fiatjaf.com/nostr/nip45" "fiatjaf.com/nostr/nip45"
"fiatjaf.com/nostr/nip45/hyperloglog" "fiatjaf.com/nostr/nip45/hyperloglog"
"github.com/PowerDNS/lmdb-go/lmdb" "github.com/PowerDNS/lmdb-go/lmdb"
"github.com/templexxx/xhex"
) )
func (b *LMDBBackend) CountEvents(filter nostr.Filter) (uint32, error) { 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])) binary.BigEndian.PutUint16(cacheKey[0:2], uint16(filter.Kinds[0]))
switch filter.Kinds[0] { switch filter.Kinds[0] {
case 3: 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: 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: 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 var count uint32
@@ -217,7 +217,7 @@ func (b *LMDBBackend) updateHyperLogLogCachedValues(txn *lmdb.Txn, evt nostr.Eve
for ref, offset := range nip45.HyperLogLogEventPubkeyOffsetsAndReferencesForEvent(evt) { for ref, offset := range nip45.HyperLogLogEventPubkeyOffsetsAndReferencesForEvent(evt) {
// setup cache key (reusing buffer) // 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 // fetch hll value from cache db
hll := hyperloglog.New(offset) hll := hyperloglog.New(offset)

View File

@@ -4,7 +4,6 @@ import (
"bytes" "bytes"
"crypto/md5" "crypto/md5"
"encoding/binary" "encoding/binary"
"encoding/hex"
"fmt" "fmt"
"iter" "iter"
"slices" "slices"
@@ -13,6 +12,7 @@ import (
"fiatjaf.com/nostr" "fiatjaf.com/nostr"
"github.com/PowerDNS/lmdb-go/lmdb" "github.com/PowerDNS/lmdb-go/lmdb"
"github.com/templexxx/xhex"
) )
type iterator struct { type iterator struct {
@@ -244,7 +244,7 @@ func (b *LMDBBackend) getIndexKeysForEvent(evt nostr.Event) iter.Seq[key] {
// now the p-tag+kind+date // now the p-tag+kind+date
if dbi == b.indexTag32 && tag[0] == "p" { if dbi == b.indexTag32 && tag[0] == "p" {
k := make([]byte, 8+2+4) 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.PutUint16(k[8:8+2], uint16(evt.Kind))
binary.BigEndian.PutUint32(k[8+2:8+2+4], uint32(evt.CreatedAt)) binary.BigEndian.PutUint32(k[8+2:8+2+4], uint32(evt.CreatedAt))
dbi := b.indexPTagKind dbi := b.indexPTagKind
@@ -276,7 +276,7 @@ func (b *LMDBBackend) getTagIndexPrefix(tagName string, tagValue string) (lmdb.D
if len(tagValue) == 64 { if len(tagValue) == 64 {
// but we actually only use the first 8 bytes, with letter (tag name) prefix // but we actually only use the first 8 bytes, with letter (tag name) prefix
k = make([]byte, 1+8+4) 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 k[0] = letterPrefix
offset = 1 + 8 offset = 1 + 8
dbi = b.indexTag32 dbi = b.indexTag32
@@ -288,7 +288,7 @@ func (b *LMDBBackend) getTagIndexPrefix(tagName string, tagValue string) (lmdb.D
spl := strings.Split(tagValue, ":") spl := strings.Split(tagValue, ":")
if len(spl) == 3 && len(spl[1]) == 64 { if len(spl) == 3 && len(spl[1]) == 64 {
k = make([]byte, 1+2+8+30+4) 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 { if kind, err := strconv.ParseUint(spl[0], 10, 16); err == nil {
k[0] = letterPrefix k[0] = letterPrefix
k[1] = byte(kind >> 8) k[1] = byte(kind >> 8)

View File

@@ -2,12 +2,12 @@ package lmdb
import ( import (
"encoding/binary" "encoding/binary"
"encoding/hex"
"fmt" "fmt"
"fiatjaf.com/nostr" "fiatjaf.com/nostr"
"fiatjaf.com/nostr/eventstore/internal" "fiatjaf.com/nostr/eventstore/internal"
"github.com/PowerDNS/lmdb-go/lmdb" "github.com/PowerDNS/lmdb-go/lmdb"
"github.com/templexxx/xhex"
) )
type query struct { type query struct {
@@ -76,7 +76,7 @@ func (b *LMDBBackend) prepareQueries(filter nostr.Filter) (
for _, kind := range filter.Kinds { for _, kind := range filter.Kinds {
k := make([]byte, 8+2) 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) return nil, nil, nil, "", nil, 0, fmt.Errorf("invalid 'p' tag '%s'", value)
} }
binary.BigEndian.PutUint16(k[8:8+2], uint16(kind)) binary.BigEndian.PutUint16(k[8:8+2], uint16(kind))
@@ -93,7 +93,7 @@ func (b *LMDBBackend) prepareQueries(filter nostr.Filter) (
} }
k := make([]byte, 8) 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) 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} queries[i] = query{i: i, dbi: b.indexPTagKind, prefix: k[0:8], keySize: 8 + 2 + 4}

View File

@@ -3,7 +3,6 @@ package mmm
import ( import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"encoding/hex"
"iter" "iter"
"slices" "slices"
"strconv" "strconv"
@@ -11,6 +10,7 @@ import (
"fiatjaf.com/nostr" "fiatjaf.com/nostr"
"github.com/PowerDNS/lmdb-go/lmdb" "github.com/PowerDNS/lmdb-go/lmdb"
"github.com/templexxx/xhex"
) )
type iterator struct { type iterator struct {
@@ -229,7 +229,7 @@ func (il *IndexingLayer) getIndexKeysForEvent(evt nostr.Event) iter.Seq[key] {
// now the p-tag+kind+date // now the p-tag+kind+date
if dbi == il.indexTag32 && tag[0] == "p" { if dbi == il.indexTag32 && tag[0] == "p" {
k := make([]byte, 8+2+4) 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.PutUint16(k[8:8+2], uint16(evt.Kind))
binary.BigEndian.PutUint32(k[8+2:8+2+4], uint32(evt.CreatedAt)) binary.BigEndian.PutUint32(k[8+2:8+2+4], uint32(evt.CreatedAt))
dbi := il.indexPTagKind dbi := il.indexPTagKind
@@ -261,7 +261,7 @@ func (il *IndexingLayer) getTagIndexPrefix(tagName string, tagValue string) (lmd
if len(tagValue) == 64 { if len(tagValue) == 64 {
// but we actually only use the first 8 bytes, with letter (tag name) prefix // but we actually only use the first 8 bytes, with letter (tag name) prefix
k = make([]byte, 1+8+4) 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 k[0] = letterPrefix
offset = 1 + 8 offset = 1 + 8
dbi = il.indexTag32 dbi = il.indexTag32
@@ -273,7 +273,7 @@ func (il *IndexingLayer) getTagIndexPrefix(tagName string, tagValue string) (lmd
spl := strings.Split(tagValue, ":") spl := strings.Split(tagValue, ":")
if len(spl) == 3 && len(spl[1]) == 64 { if len(spl) == 3 && len(spl[1]) == 64 {
k = make([]byte, 1+2+8+30+4) 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 { if kind, err := strconv.ParseUint(spl[0], 10, 16); err == nil {
k[0] = letterPrefix k[0] = letterPrefix
k[1] = byte(kind >> 8) k[1] = byte(kind >> 8)

View File

@@ -2,12 +2,12 @@ package mmm
import ( import (
"encoding/binary" "encoding/binary"
"encoding/hex"
"fmt" "fmt"
"fiatjaf.com/nostr" "fiatjaf.com/nostr"
"fiatjaf.com/nostr/eventstore/internal" "fiatjaf.com/nostr/eventstore/internal"
"github.com/PowerDNS/lmdb-go/lmdb" "github.com/PowerDNS/lmdb-go/lmdb"
"github.com/templexxx/xhex"
) )
type query struct { type query struct {
@@ -77,7 +77,7 @@ func (il *IndexingLayer) prepareQueries(filter nostr.Filter) (
for _, kind := range filter.Kinds { for _, kind := range filter.Kinds {
k := make([]byte, 8+2) 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) return nil, nil, nil, "", nil, 0, fmt.Errorf("invalid 'p' tag '%s'", value)
} }
binary.BigEndian.PutUint16(k[8:8+2], uint16(kind)) binary.BigEndian.PutUint16(k[8:8+2], uint16(kind))
@@ -94,7 +94,7 @@ func (il *IndexingLayer) prepareQueries(filter nostr.Filter) (
} }
k := make([]byte, 8) 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) 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} queries[i] = query{i: i, dbi: il.indexPTagKind, prefix: k[0:8], keySize: 8 + 2 + 4, timestampSize: 4}

View File

@@ -2,7 +2,6 @@ package test
import ( import (
"encoding/binary" "encoding/binary"
"encoding/hex"
"fmt" "fmt"
"os" "os"
"testing" "testing"
@@ -42,7 +41,7 @@ func runBenchmarkOn(b *testing.B, db eventstore.Store) {
Content: fmt.Sprintf("hello %d", i), Content: fmt.Sprintf("hello %d", i),
Tags: nostr.Tags{ Tags: nostr.Tags{
{"t", fmt.Sprintf("t%d", i)}, {"t", fmt.Sprintf("t%d", i)},
{"e", hex.EncodeToString(eTag)}, {"e", nostr.HexEncodeToString(eTag)},
{"p", ref.Hex()}, {"p", ref.Hex()},
}, },
Kind: nostr.Kind(i % 10), Kind: nostr.Kind(i % 10),
@@ -70,7 +69,7 @@ func runBenchmarkOn(b *testing.B, db eventstore.Store) {
for i := 0; i < 20; i++ { for i := 0; i < 20; i++ {
eTag := make([]byte, 32) eTag := make([]byte, 32)
binary.BigEndian.PutUint16(eTag, uint16(i)) 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{9}, Tags: nostr.TagMap{"e": eTags}})
filters = append(filters, nostr.Filter{Kinds: []nostr.Kind{5}, Tags: nostr.TagMap{"e": eTags, "t": []string{"t5"}}}) filters = append(filters, nostr.Filter{Kinds: []nostr.Kind{5}, Tags: nostr.TagMap{"e": eTags, "t": []string{"t5"}}})

View File

@@ -2,7 +2,6 @@ package test
import ( import (
"encoding/binary" "encoding/binary"
"encoding/hex"
"fmt" "fmt"
"slices" "slices"
"testing" "testing"
@@ -29,7 +28,7 @@ func runSecondTestOn(t *testing.T, db eventstore.Store) {
Content: fmt.Sprintf("hello %d", i), Content: fmt.Sprintf("hello %d", i),
Tags: nostr.Tags{ Tags: nostr.Tags{
{"t", fmt.Sprintf("t%d", i)}, {"t", fmt.Sprintf("t%d", i)},
{"e", hex.EncodeToString(eTag)}, {"e", nostr.HexEncodeToString(eTag)},
{"p", ref.Hex()}, {"p", ref.Hex()},
}, },
Kind: nostr.Kind(i % 10), Kind: nostr.Kind(i % 10),
@@ -49,7 +48,7 @@ func runSecondTestOn(t *testing.T, db eventstore.Store) {
for i := 0; i < 20; i++ { for i := 0; i < 20; i++ {
eTag := make([]byte, 32) eTag := make([]byte, 32)
binary.BigEndian.PutUint16(eTag, uint16(i)) binary.BigEndian.PutUint16(eTag, uint16(i))
eTags[i] = hex.EncodeToString(eTag) eTags[i] = nostr.HexEncodeToString(eTag)
} }
filters := []nostr.Filter{ filters := []nostr.Filter{

View File

@@ -1,10 +1,9 @@
package nostr package nostr
import ( import (
"encoding/hex"
jlexer "github.com/mailru/easyjson/jlexer" jlexer "github.com/mailru/easyjson/jlexer"
jwriter "github.com/mailru/easyjson/jwriter" jwriter "github.com/mailru/easyjson/jwriter"
"github.com/templexxx/xhex"
) )
func easyjsonDecodeFilter(in *jlexer.Lexer, out *Filter) { func easyjsonDecodeFilter(in *jlexer.Lexer, out *Filter) {
@@ -42,7 +41,7 @@ func easyjsonDecodeFilter(in *jlexer.Lexer, out *Filter) {
id := ID{} id := ID{}
b := in.UnsafeBytes() b := in.UnsafeBytes()
if len(b) == 64 { if len(b) == 64 {
hex.Decode(id[:], b) xhex.Decode(id[:], b)
} }
out.IDs = append(out.IDs, id) out.IDs = append(out.IDs, id)
in.WantComma() in.WantComma()
@@ -79,7 +78,7 @@ func easyjsonDecodeFilter(in *jlexer.Lexer, out *Filter) {
pk := PubKey{} pk := PubKey{}
b := in.UnsafeBytes() b := in.UnsafeBytes()
if len(b) == 64 { if len(b) == 64 {
hex.Decode(pk[:], b) xhex.Decode(pk[:], b)
} }
out.Authors = append(out.Authors, pk) out.Authors = append(out.Authors, pk)
in.WantComma() in.WantComma()
@@ -140,7 +139,7 @@ func easyjsonEncodeFilter(out *jwriter.Writer, in Filter) {
if i > 0 { if i > 0 {
out.RawByte(',') out.RawByte(',')
} }
out.RawString("\"" + hex.EncodeToString(id[:]) + "\"") out.RawString("\"" + HexEncodeToString(id[:]) + "\"")
} }
out.RawByte(']') out.RawByte(']')
} }
@@ -178,7 +177,7 @@ func easyjsonEncodeFilter(out *jwriter.Writer, in Filter) {
if i > 0 { if i > 0 {
out.RawByte(',') out.RawByte(',')
} }
out.RawString("\"" + hex.EncodeToString(pk[:]) + "\"") out.RawString("\"" + HexEncodeToString(pk[:]) + "\"")
} }
out.RawByte(']') out.RawByte(']')
} }

2
go.mod
View File

@@ -43,6 +43,8 @@ require (
require ( require (
github.com/dgraph-io/ristretto/v2 v2.3.0 github.com/dgraph-io/ristretto/v2 v2.3.0
github.com/go-git/go-git/v5 v5.16.3 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 ( require (

4
go.sum
View File

@@ -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 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 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/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 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=

View File

@@ -1,13 +1,13 @@
package nostr package nostr
import ( import (
"encoding/hex"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
"unsafe" "unsafe"
jsoniter "github.com/json-iterator/go" jsoniter "github.com/json-iterator/go"
"github.com/templexxx/xhex"
"golang.org/x/exp/constraints" "golang.org/x/exp/constraints"
) )
@@ -186,7 +186,7 @@ func extractEventID(jsonStr string) ID {
// get 64 characters of the id // get 64 characters of the id
var id [32]byte 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 return id
} }
@@ -203,7 +203,7 @@ func extractEventPubKey(jsonStr string) PubKey {
// get 64 characters of the pubkey // get 64 characters of the pubkey
var pk [32]byte 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 return pk
} }

26
keys.go
View File

@@ -2,7 +2,6 @@ package nostr
import ( import (
"crypto/rand" "crypto/rand"
"encoding/hex"
stdjson "encoding/json" stdjson "encoding/json"
"fmt" "fmt"
"io" "io"
@@ -11,6 +10,7 @@ import (
"github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/schnorr" "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} 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 type SecretKey [32]byte
func (sk SecretKey) String() string { return "sk::" + sk.Hex() } 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 (sk SecretKey) Public() PubKey { return GetPublicKey(sk) }
func (pk SecretKey) MarshalJSON() ([]byte, error) { func (pk SecretKey) MarshalJSON() ([]byte, error) {
res := make([]byte, 66) res := make([]byte, 66)
hex.Encode(res[1:], pk[:]) xhex.Encode(res[1:], pk[:])
res[0] = '"' res[0] = '"'
res[65] = '"' res[65] = '"'
return res, nil return res, nil
@@ -50,7 +50,7 @@ func (pk *SecretKey) UnmarshalJSON(buf []byte) error {
if len(buf) != 66 { if len(buf) != 66 {
return fmt.Errorf("must be a hex string of 64 characters") 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 err
} }
return nil 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) 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) 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 { func MustSecretKeyFromHex(idh string) SecretKey {
id := 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) panic(err)
} }
return id return id
@@ -107,10 +107,10 @@ var (
) )
func (pk PubKey) String() string { return "pk::" + pk.Hex() } 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) { func (pk PubKey) MarshalJSON() ([]byte, error) {
res := make([]byte, 66) res := make([]byte, 66)
hex.Encode(res[1:], pk[:]) xhex.Encode(res[1:], pk[:])
res[0] = '"' res[0] = '"'
res[65] = '"' res[65] = '"'
return res, nil return res, nil
@@ -120,7 +120,7 @@ func (pk *PubKey) UnmarshalJSON(buf []byte) error {
if len(buf) != 66 { if len(buf) != 66 {
return fmt.Errorf("must be a hex string of 64 characters") 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 err
} }
if _, err := schnorr.ParsePubKey(pk[:]); err != nil { if _, err := schnorr.ParsePubKey(pk[:]); err != nil {
@@ -134,7 +134,7 @@ func PubKeyFromHex(pkh string) (PubKey, error) {
if len(pkh) != 64 { if len(pkh) != 64 {
return pk, fmt.Errorf("pubkey should be 64-char hex, got '%s'", pkh) 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) return pk, fmt.Errorf("'%s' is not valid hex: %w", pkh, err)
} }
if _, err := schnorr.ParsePubKey(pk[:]); err != nil { if _, err := schnorr.ParsePubKey(pk[:]); err != nil {
@@ -148,7 +148,7 @@ func PubKeyFromHexCheap(pkh string) (PubKey, error) {
if len(pkh) != 64 { if len(pkh) != 64 {
return pk, fmt.Errorf("pubkey should be 64-char hex, got '%s'", pkh) 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) 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 { func MustPubKeyFromHex(pkh string) PubKey {
pk := 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 return pk
} }

View File

@@ -2,7 +2,6 @@ package blossom
import ( import (
"crypto/sha256" "crypto/sha256"
"encoding/hex"
"encoding/json" "encoding/json"
"io" "io"
"mime" "mime"
@@ -128,7 +127,7 @@ func (bs BlossomServer) handleUpload(w http.ResponseWriter, r *http.Request) {
} }
hash := sha256.Sum256(b) hash := sha256.Sum256(b)
hhash := hex.EncodeToString(hash[:]) hhash := nostr.HexEncodeToString(hash[:])
mimeType := mime.TypeByExtension(ext) mimeType := mime.TypeByExtension(ext)
if mimeType == "" { if mimeType == "" {
mimeType = "application/octet-stream" mimeType = "application/octet-stream"
@@ -443,7 +442,7 @@ func (bs BlossomServer) handleMirror(w http.ResponseWriter, r *http.Request) {
// calculate sha256 // calculate sha256
hash := sha256.Sum256(body) hash := sha256.Sum256(body)
hhash := hex.EncodeToString(hash[:]) hhash := nostr.HexEncodeToString(hash[:])
// verify hash against x tag // verify hash against x tag
if auth.Tags.FindWithValue("x", hhash) == nil { if auth.Tags.FindWithValue("x", hhash) == nil {

View File

@@ -3,7 +3,6 @@ package khatru
import ( import (
"context" "context"
"crypto/rand" "crypto/rand"
"encoding/hex"
"errors" "errors"
"net/http" "net/http"
"slices" "slices"
@@ -76,7 +75,7 @@ func (rl *Relay) HandleWebsocket(w http.ResponseWriter, r *http.Request) {
ws := &WebSocket{ ws := &WebSocket{
conn: conn, conn: conn,
Request: r, Request: r,
Challenge: rl.ChallengePrefix + hex.EncodeToString(challenge), Challenge: rl.ChallengePrefix + nostr.HexEncodeToString(challenge),
AuthedPublicKeys: make([]nostr.PubKey, 0), AuthedPublicKeys: make([]nostr.PubKey, 0),
negentropySessions: xsync.NewMapOf[string, *NegentropySession](), negentropySessions: xsync.NewMapOf[string, *NegentropySession](),
} }

View File

@@ -4,7 +4,6 @@ import (
"context" "context"
"crypto/sha256" "crypto/sha256"
"encoding/base64" "encoding/base64"
"encoding/hex"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
@@ -99,7 +98,7 @@ func (rl *Relay) HandleNIP86(w http.ResponseWriter, r *http.Request) {
goto respond 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" resp.Error = "invalid auth event payload hash"
goto respond goto respond
} }

View File

@@ -6,7 +6,6 @@ import (
"crypto/cipher" "crypto/cipher"
"crypto/rand" "crypto/rand"
"encoding/base64" "encoding/base64"
"encoding/hex"
"fmt" "fmt"
"strings" "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) // adding 02 to signal that this is a compressed public key (33 bytes)
pubKey, err := btcec.ParsePubKey(append([]byte{2}, pub[:]...)) pubKey, err := btcec.ParsePubKey(append([]byte{2}, pub[:]...))
if err != nil { 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 return btcec.GenerateSharedSecret(privKey, pubKey), nil

View File

@@ -1,7 +1,6 @@
package nip04 package nip04
import ( import (
"encoding/hex"
"strings" "strings"
"testing" "testing"
@@ -56,8 +55,8 @@ func TestEncryptionAndDecryptionWithMultipleLengths(t *testing.T) {
} }
func TestNostrToolsCompatibility(t *testing.T) { func TestNostrToolsCompatibility(t *testing.T) {
sk1, _ := hex.DecodeString("92996316beebf94171065a714cbf164d1f56d7ad9b35b329d9fc97535bf25352") sk1, _ := nostr.HexDecodeString("92996316beebf94171065a714cbf164d1f56d7ad9b35b329d9fc97535bf25352")
sk2, _ := hex.DecodeString("591c0c249adfb9346f8d37dfeed65725e2eea1d7a6e99fa503342f367138de84") sk2, _ := nostr.HexDecodeString("591c0c249adfb9346f8d37dfeed65725e2eea1d7a6e99fa503342f367138de84")
pk2 := nostr.GetPublicKey([32]byte(sk2)) pk2 := nostr.GetPublicKey([32]byte(sk2))
shared, _ := ComputeSharedSecret(pk2, [32]byte(sk1)) shared, _ := ComputeSharedSecret(pk2, [32]byte(sk1))
ciphertext := "A+fRnU4aXS4kbTLfowqAww==?iv=QFYUrl5or/n/qamY79ze0A==" ciphertext := "A+fRnU4aXS4kbTLfowqAww==?iv=QFYUrl5or/n/qamY79ze0A=="

View File

@@ -1,8 +1,7 @@
package nip06 package nip06
import ( import (
"encoding/hex" "fiatjaf.com/nostr"
"github.com/tyler-smith/go-bip32" "github.com/tyler-smith/go-bip32"
"github.com/tyler-smith/go-bip39" "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 { func ValidateWords(words string) bool {

View File

@@ -1,12 +1,12 @@
package nip19 package nip19
import ( import (
"encoding/hex"
"testing" "testing"
"fiatjaf.com/nostr" "fiatjaf.com/nostr"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/templexxx/xhex"
) )
func TestEncodeNpub(t *testing.T) { func TestEncodeNpub(t *testing.T) {
@@ -17,7 +17,7 @@ func TestEncodeNpub(t *testing.T) {
func TestEncodeNsec(t *testing.T) { func TestEncodeNsec(t *testing.T) {
skh := "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d" skh := "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d"
var sk [32]byte var sk [32]byte
hex.Decode(sk[:], []byte(skh)) xhex.Decode(sk[:], []byte(skh))
nsec := EncodeNsec(sk) nsec := EncodeNsec(sk)
assert.Equal(t, "nsec180cvv07tjdrrgpa0j7j7tmnyl2yr6yr7l8j4s3evf6u64th6gkwsgyumg0", nsec, "produced an unexpected nsec string") assert.Equal(t, "nsec180cvv07tjdrrgpa0j7j7tmnyl2yr6yr7l8j4s3evf6u64th6gkwsgyumg0", nsec, "produced an unexpected nsec string")
} }

View File

@@ -7,7 +7,6 @@ import (
"crypto/sha256" "crypto/sha256"
"encoding/base64" "encoding/base64"
"encoding/binary" "encoding/binary"
"encoding/hex"
"fmt" "fmt"
"io" "io"
"math" "math"
@@ -162,7 +161,7 @@ func Decrypt(b64ciphertextWrapped string, conversationKey [32]byte) (string, err
return string(unpadded), nil 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) { func GenerateConversationKey(pub nostr.PubKey, sk nostr.SecretKey) ([32]byte, error) {
var ck [32]byte 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[:]...)) pubKey, err := btcec.ParsePubKey(append([]byte{2}, pub[:]...))
if err != nil { if err != nil {
return sharedSecret, fmt.Errorf("error parsing receiver public key '%s': %w", 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 var point, result secp256k1.JacobianPoint

View File

@@ -2,12 +2,12 @@ package nip44
import ( import (
"crypto/sha256" "crypto/sha256"
"encoding/hex"
"strings" "strings"
"testing" "testing"
"fiatjaf.com/nostr" "fiatjaf.com/nostr"
"github.com/stretchr/testify/require" "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) { 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) 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) require.NoErrorf(t, err, "hex decode failed for salt: %v", err)
actual, err := Encrypt(plaintext, k1, WithCustomNonce(customNonce)) actual, err := Encrypt(plaintext, k1, WithCustomNonce(customNonce))
@@ -70,13 +70,13 @@ func assertMessageKeyGeneration(t *testing.T, conversationKey string, salt strin
convNonce, err := hexDecode32Array(salt) convNonce, err := hexDecode32Array(salt)
require.NoErrorf(t, err, "hex decode failed for nonce: %v", err) 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) 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) 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) require.NoErrorf(t, err, "hex decode failed for hmac key: %v", err)
actualChaChaKey, actualChaChaNonce, actualHmacKey, err := messageKeys(convKey, convNonce) 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) convKey, err := hexDecode32Array(conversationKey)
require.NoErrorf(t, err, "hex decode failed for convKey: %v", err) 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) require.NoErrorf(t, err, "hex decode failed for salt: %v", err)
plaintext := "" plaintext := ""
@@ -101,7 +101,7 @@ func assertCryptLong(t *testing.T, conversationKey string, salt string, pattern
} }
h := sha256.New() h := sha256.New()
h.Write([]byte(plaintext)) 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) require.Equalf(t, plaintextSha256, actualPlaintextSha256, "invalid plaintext sha256 hash: %v", err)
actualPayload, err := Encrypt(plaintext, convKey, WithCustomNonce(customNonce)) actualPayload, err := Encrypt(plaintext, convKey, WithCustomNonce(customNonce))
@@ -109,7 +109,7 @@ func assertCryptLong(t *testing.T, conversationKey string, salt string, pattern
h.Reset() h.Reset()
h.Write([]byte(actualPayload)) 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) 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) { func hexDecode32Array(hexString string) (res [32]byte, err error) {
_, err = hex.Decode(res[:], []byte(hexString)) err = xhex.Decode(res[:], []byte(hexString))
return res, err return res, err
} }

View File

@@ -2,7 +2,6 @@ package nip46
import ( import (
"context" "context"
"encoding/hex"
"fmt" "fmt"
"math/rand" "math/rand"
"net/url" "net/url"
@@ -56,7 +55,7 @@ func ConnectBunker(
pool, pool,
onAuth, 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 return bunker, err
} }

View File

@@ -2,7 +2,6 @@ package nip46
import ( import (
"context" "context"
"encoding/hex"
"fmt" "fmt"
"sync" "sync"
@@ -131,7 +130,7 @@ func (p *DynamicSigner) HandleRequest(ctx context.Context, event nostr.Event) (
result = "ack" result = "ack"
case "get_public_key": case "get_public_key":
result = hex.EncodeToString(session.PublicKey[:]) result = nostr.HexEncodeToString(session.PublicKey[:])
case "sign_event": case "sign_event":
if len(req.Params) != 1 { if len(req.Params) != 1 {
resultErr = fmt.Errorf("wrong number of arguments to 'sign_event'") resultErr = fmt.Errorf("wrong number of arguments to 'sign_event'")

View File

@@ -2,7 +2,6 @@ package nip46
import ( import (
"context" "context"
"encoding/hex"
"fmt" "fmt"
"sync" "sync"
@@ -90,7 +89,7 @@ func (p *StaticKeySigner) HandleRequest(_ context.Context, event nostr.Event) (
result = "ack" result = "ack"
harmless = true harmless = true
case "get_public_key": case "get_public_key":
result = hex.EncodeToString(session.PublicKey[:]) result = nostr.HexEncodeToString(session.PublicKey[:])
harmless = true harmless = true
case "sign_event": case "sign_event":
if len(req.Params) != 1 { if len(req.Params) != 1 {

View File

@@ -3,13 +3,13 @@ package nip60
import ( import (
"crypto/rand" "crypto/rand"
"crypto/sha256" "crypto/sha256"
"encoding/hex"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"strconv" "strconv"
"strings" "strings"
"fiatjaf.com/nostr"
"github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/schnorr" "github.com/btcsuite/btcd/btcec/v2/schnorr"
"github.com/decred/dcrd/dcrec/secp256k1/v4" "github.com/decred/dcrd/dcrec/secp256k1/v4"
@@ -66,7 +66,7 @@ func createBlindedMessages(
if _, err := rand.Read(secretBytes); err != nil { if _, err := rand.Read(secretBytes); err != nil {
return nil, nil, nil, err return nil, nil, nil, err
} }
secret = hex.EncodeToString(secretBytes) secret = nostr.HexEncodeToString(secretBytes)
} }
B_, r, err := crypto.BlindMessage(secret, r) B_, r, err := crypto.BlindMessage(secret, r)
@@ -92,7 +92,7 @@ func signInput(
return "", fmt.Errorf("failed to sign: %w", err) return "", fmt.Errorf("failed to sign: %w", err)
} }
witness, _ := json.Marshal(nut11.P2PKWitness{ witness, _ := json.Marshal(nut11.P2PKWitness{
Signatures: []string{hex.EncodeToString(signature.Serialize())}, Signatures: []string{nostr.HexEncodeToString(signature.Serialize())},
}) })
return string(witness), nil return string(witness), nil
} }
@@ -101,14 +101,14 @@ func signOutput(
privateKey *btcec.PrivateKey, privateKey *btcec.PrivateKey,
output cashu.BlindedMessage, output cashu.BlindedMessage,
) (string, error) { ) (string, error) {
msg, _ := hex.DecodeString(output.B_) msg, _ := nostr.HexDecodeString(output.B_)
hash := sha256.Sum256(msg) hash := sha256.Sum256(msg)
signature, err := schnorr.Sign(privateKey, hash[:]) signature, err := schnorr.Sign(privateKey, hash[:])
if err != nil { if err != nil {
return "", fmt.Errorf("failed to sign: %w", err) return "", fmt.Errorf("failed to sign: %w", err)
} }
witness, _ := json.Marshal(nut11.P2PKWitness{ witness, _ := json.Marshal(nut11.P2PKWitness{
Signatures: []string{hex.EncodeToString(signature.Serialize())}, Signatures: []string{nostr.HexEncodeToString(signature.Serialize())},
}) })
return string(witness), nil return string(witness), nil
} }
@@ -143,7 +143,7 @@ func constructProofs(
dleq = &cashu.DLEQProof{ dleq = &cashu.DLEQProof{
E: blindedSignature.DLEQ.E, E: blindedSignature.DLEQ.E,
S: blindedSignature.DLEQ.S, 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, string,
error, error,
) { ) {
C_bytes, err := hex.DecodeString(C_str) C_bytes, err := nostr.HexDecodeString(C_str)
if err != nil { if err != nil {
return "", err return "", err
} }
@@ -180,14 +180,14 @@ func unblindSignature(C_str string, r *secp256k1.PrivateKey, key *secp256k1.Publ
} }
C := crypto.UnblindSignature(C_, r, key) C := crypto.UnblindSignature(C_, r, key)
Cstr := hex.EncodeToString(C.SerializeCompressed()) Cstr := nostr.HexEncodeToString(C.SerializeCompressed())
return Cstr, nil return Cstr, nil
} }
func ParseKeysetKeys(keys nut01.KeysMap) (map[uint64]*btcec.PublicKey, error) { func ParseKeysetKeys(keys nut01.KeysMap) (map[uint64]*btcec.PublicKey, error) {
parsedKeys := make(map[uint64]*btcec.PublicKey) parsedKeys := make(map[uint64]*btcec.PublicKey)
for amount, pkh := range keys { for amount, pkh := range keys {
pkb, err := hex.DecodeString(pkh) pkb, err := nostr.HexDecodeString(pkh)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -2,7 +2,6 @@ package nip60
import ( import (
"context" "context"
"encoding/hex"
"fmt" "fmt"
"slices" "slices"
@@ -42,7 +41,7 @@ func (opts SendOptions) asSpendingCondition(refund *btcec.PublicKey) *nut10.Spen
return &nut10.SpendingCondition{ return &nut10.SpendingCondition{
Kind: nut10.HTLC, Kind: nut10.HTLC,
Data: hex.EncodeToString(opts.Hashlock[:]), Data: nostr.HexEncodeToString(opts.Hashlock[:]),
Tags: nut11.SerializeP2PKTags(tags), Tags: nut11.SerializeP2PKTags(tags),
} }
} else if opts.P2PK != nil { } else if opts.P2PK != nil {
@@ -62,7 +61,7 @@ func (opts SendOptions) asSpendingCondition(refund *btcec.PublicKey) *nut10.Spen
return &nut10.SpendingCondition{ return &nut10.SpendingCondition{
Kind: nut10.P2PK, Kind: nut10.P2PK,
Data: hex.EncodeToString(opts.P2PK.SerializeCompressed()), Data: nostr.HexEncodeToString(opts.P2PK.SerializeCompressed()),
Tags: nut11.SerializeP2PKTags(tags), Tags: nut11.SerializeP2PKTags(tags),
} }
} else { } else {

View File

@@ -2,7 +2,6 @@ package nip60
import ( import (
"context" "context"
"encoding/hex"
"encoding/json" "encoding/json"
"fmt" "fmt"
"slices" "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") return fmt.Errorf("can't do write operations: missing PublishUpdate function")
} }
skb, err := hex.DecodeString(privateKey) skb, err := nostr.HexDecodeString(privateKey)
if err != nil { if err != nil {
return err 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)) encryptedTags := make(nostr.Tags, 0, 1+len(w.Mints))
if w.PrivateKey != nil { 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 { 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": case "mint":
mints = append(mints, tag[1]) mints = append(mints, tag[1])
case "privkey": case "privkey":
skb, err := hex.DecodeString(tag[1]) skb, err := nostr.HexDecodeString(tag[1])
if err != nil { if err != nil {
return fmt.Errorf("failed to parse private key: %w", err) return fmt.Errorf("failed to parse private key: %w", err)
} }

View File

@@ -1,7 +1,6 @@
package nip61 package nip61
import ( import (
"encoding/hex"
"encoding/json" "encoding/json"
"fiatjaf.com/nostr" "fiatjaf.com/nostr"
@@ -52,7 +51,7 @@ func verifyProofDLEQ(
return false return false
} }
CBytes, err := hex.DecodeString(proof.C) CBytes, err := nostr.HexDecodeString(proof.C)
if err != nil { if err != nil {
return false return false
} }
@@ -88,7 +87,7 @@ func VerifyBlindSignatureDLEQ(
return false return false
} }
B_bytes, err := hex.DecodeString(B_str) B_bytes, err := nostr.HexDecodeString(B_str)
if err != nil { if err != nil {
return false return false
} }
@@ -97,7 +96,7 @@ func VerifyBlindSignatureDLEQ(
return false return false
} }
C_bytes, err := hex.DecodeString(C_str) C_bytes, err := nostr.HexDecodeString(C_str)
if err != nil { if err != nil {
return false return false
} }
@@ -115,13 +114,13 @@ func parseDLEQ(dleq cashu.DLEQProof) (
*btcec.PrivateKey, *btcec.PrivateKey,
error, error,
) { ) {
ebytes, err := hex.DecodeString(dleq.E) ebytes, err := nostr.HexDecodeString(dleq.E)
if err != nil { if err != nil {
return nil, nil, nil, err return nil, nil, nil, err
} }
e := secp256k1.PrivKeyFromBytes(ebytes) e := secp256k1.PrivKeyFromBytes(ebytes)
sbytes, err := hex.DecodeString(dleq.S) sbytes, err := nostr.HexDecodeString(dleq.S)
if err != nil { if err != nil {
return nil, nil, nil, err return nil, nil, nil, err
} }
@@ -131,7 +130,7 @@ func parseDLEQ(dleq cashu.DLEQProof) (
return e, s, nil, nil return e, s, nil, nil
} }
rbytes, err := hex.DecodeString(dleq.R) rbytes, err := nostr.HexDecodeString(dleq.R)
if err != nil { if err != nil {
return nil, nil, nil, err return nil, nil, nil, err
} }

View File

@@ -2,7 +2,6 @@ package negentropy
import ( import (
"bytes" "bytes"
"encoding/hex"
"fmt" "fmt"
"io" "io"
"math" "math"
@@ -72,12 +71,12 @@ func (n *Negentropy) Start() string {
output.WriteByte(protocolVersion) output.WriteByte(protocolVersion)
n.SplitRange(0, n.storage.Size(), InfiniteBound, output) 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) { func (n *Negentropy) Reconcile(msg string) (string, error) {
n.initialized = true n.initialized = true
msgb, err := hex.DecodeString(msg) msgb, err := nostr.HexDecodeString(msg)
if err != nil { if err != nil {
return "", err return "", err
} }
@@ -99,7 +98,7 @@ func (n *Negentropy) Reconcile(msg string) (string, error) {
return "", nil return "", nil
} }
return hex.EncodeToString(output), nil return nostr.HexEncodeToString(output), nil
} }
func (n *Negentropy) reconcileAux(reader *bytes.Reader) ([]byte, error) { func (n *Negentropy) reconcileAux(reader *bytes.Reader) ([]byte, error) {

View File

@@ -2,7 +2,6 @@ package blossom
import ( import (
"context" "context"
"encoding/hex"
"fmt" "fmt"
"fiatjaf.com/nostr" "fiatjaf.com/nostr"
@@ -16,7 +15,7 @@ func (c *Client) List(ctx context.Context) ([]BlobDescriptor, error) {
} }
bds := make([]BlobDescriptor, 0, 100) 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) { return c.authorizationHeader(ctx, func(evt *nostr.Event) {
evt.Tags = append(evt.Tags, nostr.Tag{"t", "list"}) evt.Tags = append(evt.Tags, nostr.Tag{"t", "list"})
}) })

View File

@@ -3,7 +3,6 @@ package blossom
import ( import (
"context" "context"
"crypto/sha256" "crypto/sha256"
"encoding/hex"
"fmt" "fmt"
"io" "io"
"mime" "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 { err = c.httpCall(ctx, "PUT", "upload", contentType, func() string {
return c.authorizationHeader(ctx, func(evt *nostr.Event) { return c.authorizationHeader(ctx, func(evt *nostr.Event) {
evt.Tags = append(evt.Tags, nostr.Tag{"t", "upload"}) 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) }, file, size, &bd)
if err != nil { if err != nil {

View File

@@ -1,7 +1,6 @@
package nostr package nostr
import ( import (
"encoding/hex"
"fmt" "fmt"
"strconv" "strconv"
"strings" "strings"
@@ -56,13 +55,13 @@ func ProfilePointerFromTag(refTag Tag) (ProfilePointer, error) {
// MatchesEvent checks if the pointer matches an event. // MatchesEvent checks if the pointer matches an event.
func (ep ProfilePointer) MatchesEvent(_ Event) bool { return false } func (ep ProfilePointer) MatchesEvent(_ Event) bool { return false }
func (ep ProfilePointer) AsFilter() Filter { return Filter{Authors: []PubKey{ep.PublicKey}} } 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 { func (ep ProfilePointer) AsTag() Tag {
if len(ep.Relays) > 0 { 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. // 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) MatchesEvent(evt Event) bool { return evt.ID == ep.ID }
func (ep EventPointer) AsFilter() Filter { return Filter{IDs: []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. // AsTag converts the pointer to a Tag.
func (ep EventPointer) AsTag() Tag { func (ep EventPointer) AsTag() Tag {
if len(ep.Relays) > 0 { if len(ep.Relays) > 0 {
if ep.Author != [32]byte{} { 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 { } 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). // EntityPointer represents a pointer to a nostr entity (addressable event).

View File

@@ -1,10 +1,9 @@
package nostr package nostr
import ( import (
"encoding/hex"
jlexer "github.com/mailru/easyjson/jlexer" jlexer "github.com/mailru/easyjson/jlexer"
jwriter "github.com/mailru/easyjson/jwriter" jwriter "github.com/mailru/easyjson/jwriter"
"github.com/templexxx/xhex"
) )
func easyjson33014d6eDecodeFiatjafComNostr(in *jlexer.Lexer, out *ProfilePointer) { func easyjson33014d6eDecodeFiatjafComNostr(in *jlexer.Lexer, out *ProfilePointer) {
@@ -30,7 +29,7 @@ func easyjson33014d6eDecodeFiatjafComNostr(in *jlexer.Lexer, out *ProfilePointer
if in.IsNull() { if in.IsNull() {
in.Skip() in.Skip()
} else { } else {
hex.Decode(out.PublicKey[:], in.UnsafeBytes()) xhex.Decode(out.PublicKey[:], in.UnsafeBytes())
} }
case "relays": case "relays":
if in.IsNull() { if in.IsNull() {
@@ -137,7 +136,7 @@ func easyjson33014d6eDecodeFiatjafComNostr1(in *jlexer.Lexer, out *EventPointer)
if in.IsNull() { if in.IsNull() {
in.Skip() in.Skip()
} else { } else {
hex.Decode(out.ID[:], in.UnsafeBytes()) xhex.Decode(out.ID[:], in.UnsafeBytes())
} }
case "relays": case "relays":
if in.IsNull() { if in.IsNull() {
@@ -164,7 +163,7 @@ func easyjson33014d6eDecodeFiatjafComNostr1(in *jlexer.Lexer, out *EventPointer)
if in.IsNull() { if in.IsNull() {
in.Skip() in.Skip()
} else { } else {
hex.Decode(out.Author[:], in.UnsafeBytes()) xhex.Decode(out.Author[:], in.UnsafeBytes())
} }
case "kind": case "kind":
out.Kind = Kind(in.Uint16()) out.Kind = Kind(in.Uint16())
@@ -262,7 +261,7 @@ func easyjson33014d6eDecodeFiatjafComNostr2(in *jlexer.Lexer, out *EntityPointer
if in.IsNull() { if in.IsNull() {
in.Skip() in.Skip()
} else { } else {
hex.Decode(out.PublicKey[:], in.UnsafeBytes()) xhex.Decode(out.PublicKey[:], in.UnsafeBytes())
} }
case "kind": case "kind":
out.Kind = Kind(in.Uint16()) out.Kind = Kind(in.Uint16())

View File

@@ -2,7 +2,6 @@ package schema
import ( import (
_ "embed" _ "embed"
"encoding/hex"
"fmt" "fmt"
"net/url" "net/url"
"slices" "slices"
@@ -12,6 +11,7 @@ import (
"fiatjaf.com/nostr" "fiatjaf.com/nostr"
"github.com/segmentio/encoding/json" "github.com/segmentio/encoding/json"
"github.com/templexxx/xhex"
"gopkg.in/yaml.v3" "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 { if len(tag[index]) != 40 {
return index, fmt.Errorf("invalid gitcommit at tag '%s', index %d", tag[0], index) 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) return index, fmt.Errorf("invalid gitcommit at tag '%s', index %d", tag[0], index)
} }
case "free": case "free":

View File

@@ -2,7 +2,6 @@ package sdk
import ( import (
"context" "context"
"encoding/hex"
"regexp" "regexp"
"strings" "strings"
"time" "time"
@@ -57,7 +56,7 @@ func (sys *System) PrepareNoteEvent(ctx context.Context, evt *nostr.Event) (targ
case nostr.ProfilePointer: case nostr.ProfilePointer:
pk = b.PublicKey pk = b.PublicKey
// add p tag if not already present // 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()) evt.Tags = append(evt.Tags, b.AsTag())
} }
case nostr.EventPointer: 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 // 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 { if len(tag) == 2 {
tag = append(tag, relay) // shove this relay hint here tag = append(tag, relay) // shove this relay hint here
} }

View File

@@ -1,10 +1,11 @@
package nostr package nostr
import ( import (
"encoding/hex"
stdjson "encoding/json" stdjson "encoding/json"
"fmt" "fmt"
"unsafe" "unsafe"
"github.com/templexxx/xhex"
) )
// RelayEvent represents an event received from a specific relay. // 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) 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) { func (id ID) MarshalJSON() ([]byte, error) {
res := make([]byte, 66) res := make([]byte, 66)
hex.Encode(res[1:], id[:]) xhex.Encode(res[1:], id[:])
res[0] = '"' res[0] = '"'
res[65] = '"' res[65] = '"'
return res, nil return res, nil
@@ -36,9 +37,9 @@ func (id ID) MarshalJSON() ([]byte, error) {
func (id *ID) UnmarshalJSON(buf []byte) error { func (id *ID) UnmarshalJSON(buf []byte) error {
if len(buf) != 66 { 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 return err
} }
@@ -48,7 +49,7 @@ func IDFromHex(idh string) (ID, error) {
if len(idh) != 64 { if len(idh) != 64 {
return id, fmt.Errorf("pubkey should be 64-char hex, got '%s'", idh) 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) 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 { func MustIDFromHex(idh string) ID {
id := 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) panic(err)
} }
return id return id

View File

@@ -4,6 +4,7 @@ import (
"testing" "testing"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/templexxx/cpu"
) )
func TestIDJSONEncoding(t *testing.T) { func TestIDJSONEncoding(t *testing.T) {
@@ -25,8 +26,10 @@ func TestIDJSONEncoding(t *testing.T) {
require.Error(t, err) require.Error(t, err)
// test unmarshaling invalid hex // test unmarshaling invalid hex
if !cpu.X86.HasAVX2 {
err = json.Unmarshal([]byte(`"zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz"`), &id2) err = json.Unmarshal([]byte(`"zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz"`), &id2)
require.Error(t, err) require.Error(t, err)
}
} }
func TestPubKeyJSONEncoding(t *testing.T) { func TestPubKeyJSONEncoding(t *testing.T) {

View File

@@ -3,9 +3,11 @@ package nostr
import ( import (
"bytes" "bytes"
"cmp" "cmp"
"encoding/hex"
"net/url" "net/url"
"slices" "slices"
"unsafe"
"github.com/templexxx/xhex"
) )
// IsValidRelayURL checks if a URL is a valid relay URL (ws:// or wss://). // IsValidRelayURL checks if a URL is a valid relay URL (ws:// or wss://).
@@ -20,6 +22,27 @@ func IsValidRelayURL(u string) bool {
return true 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. // IsValid32ByteHex checks if a string is a valid 32-byte hex string.
func IsValid32ByteHex(thing string) bool { func IsValid32ByteHex(thing string) bool {
if !isLowerHex(thing) { if !isLowerHex(thing) {
@@ -28,7 +51,7 @@ func IsValid32ByteHex(thing string) bool {
if len(thing) != 64 { if len(thing) != 64 {
return false return false
} }
_, err := hex.DecodeString(thing) _, err := HexDecodeString(thing)
return err == nil return err == nil
} }