more conversions.

This commit is contained in:
fiatjaf
2025-04-15 00:00:03 -03:00
parent f9e4a5efa3
commit 376834cbf9
117 changed files with 450 additions and 1019 deletions

View File

@@ -13,7 +13,7 @@ import (
var defaultConnectionOptions = &ws.DialOptions{ var defaultConnectionOptions = &ws.DialOptions{
CompressionMode: ws.CompressionContextTakeover, CompressionMode: ws.CompressionContextTakeover,
HTTPHeader: http.Header{ HTTPHeader: http.Header{
textproto.CanonicalMIMEHeaderKey("User-Agent"): {"fiatjaf.com/nostrlib"}, textproto.CanonicalMIMEHeaderKey("User-Agent"): {"fiatjaf.com/nostr"},
}, },
} }

View File

@@ -9,8 +9,8 @@ import (
"time" "time"
jsoniter "github.com/json-iterator/go" jsoniter "github.com/json-iterator/go"
"fiatjaf.com/nostrlib" "fiatjaf.com/nostr"
"fiatjaf.com/nostrlib/nip19" "fiatjaf.com/nostr/nip19"
) )
func main() { func main() {

View File

@@ -18,7 +18,7 @@ type Keyer interface {
// User is an entity that has a public key (although they can't sign anything). // User is an entity that has a public key (although they can't sign anything).
type User interface { type User interface {
// GetPublicKey returns the public key associated with this user. // GetPublicKey returns the public key associated with this user.
GetPublicKey(ctx context.Context) (string, error) GetPublicKey(ctx context.Context) (PubKey, error)
} }
// Signer is a User that can also sign events. // Signer is a User that can also sign events.
@@ -35,9 +35,9 @@ type Signer interface {
type Cipher interface { type Cipher interface {
// Encrypt encrypts a plaintext message for a recipient. // Encrypt encrypts a plaintext message for a recipient.
// Returns the encrypted message as a base64-encoded string. // Returns the encrypted message as a base64-encoded string.
Encrypt(ctx context.Context, plaintext string, recipientPublicKey string) (base64ciphertext string, err error) Encrypt(ctx context.Context, plaintext string, recipient PubKey) (base64ciphertext string, err error)
// Decrypt decrypts a base64-encoded ciphertext from a sender. // Decrypt decrypts a base64-encoded ciphertext from a sender.
// Returns the decrypted plaintext. // Returns the decrypted plaintext.
Decrypt(ctx context.Context, base64ciphertext string, senderPublicKey string) (plaintext string, err error) Decrypt(ctx context.Context, base64ciphertext string, sender PubKey) (plaintext string, err error)
} }

View File

@@ -5,8 +5,8 @@ import (
"errors" "errors"
"time" "time"
"fiatjaf.com/nostrlib" "fiatjaf.com/nostr"
"fiatjaf.com/nostrlib/nip46" "fiatjaf.com/nostr/nip46"
) )
var _ nostr.Keyer = (*BunkerSigner)(nil) var _ nostr.Keyer = (*BunkerSigner)(nil)

View File

@@ -4,9 +4,9 @@ import (
"context" "context"
"fmt" "fmt"
"fiatjaf.com/nostrlib" "fiatjaf.com/nostr"
"fiatjaf.com/nostrlib/nip44" "fiatjaf.com/nostr/nip44"
"fiatjaf.com/nostrlib/nip49" "fiatjaf.com/nostr/nip49"
) )
var _ nostr.Keyer = (*EncryptedKeySigner)(nil) var _ nostr.Keyer = (*EncryptedKeySigner)(nil)

View File

@@ -7,11 +7,11 @@ import (
"strings" "strings"
"time" "time"
"fiatjaf.com/nostrlib" "fiatjaf.com/nostr"
"fiatjaf.com/nostrlib/nip05" "fiatjaf.com/nostr/nip05"
"fiatjaf.com/nostrlib/nip19" "fiatjaf.com/nostr/nip19"
"fiatjaf.com/nostrlib/nip46" "fiatjaf.com/nostr/nip46"
"fiatjaf.com/nostrlib/nip49" "fiatjaf.com/nostr/nip49"
"github.com/puzpuzpuz/xsync/v3" "github.com/puzpuzpuz/xsync/v3"
) )

View File

@@ -3,7 +3,7 @@ package keyer
import ( import (
"context" "context"
"fiatjaf.com/nostrlib" "fiatjaf.com/nostr"
) )
var _ nostr.Keyer = (*ManualSigner)(nil) var _ nostr.Keyer = (*ManualSigner)(nil)

View File

@@ -3,8 +3,8 @@ package keyer
import ( import (
"context" "context"
"fiatjaf.com/nostrlib" "fiatjaf.com/nostr"
"fiatjaf.com/nostrlib/nip44" "fiatjaf.com/nostr/nip44"
"github.com/puzpuzpuz/xsync/v3" "github.com/puzpuzpuz/xsync/v3"
) )

View File

@@ -4,7 +4,7 @@ import (
"context" "context"
"fmt" "fmt"
"github.com/nbd-wtf/go-nostr" "fiatjaf.com/nostr"
) )
var ( var (

View File

@@ -10,27 +10,20 @@ import (
"fmt" "fmt"
"strings" "strings"
"fiatjaf.com/nostr"
"github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2"
) )
// ComputeSharedSecret returns a shared secret key used to encrypt messages. // ComputeSharedSecret returns a shared secret key used to encrypt messages.
// The private and public keys should be hex encoded. // The private and public keys should be hex encoded.
// Uses the Diffie-Hellman key exchange (ECDH) (RFC 4753). // Uses the Diffie-Hellman key exchange (ECDH) (RFC 4753).
func ComputeSharedSecret(pub string, sk string) (sharedSecret []byte, err error) { func ComputeSharedSecret(pub nostr.PubKey, sk [32]byte) (sharedSecret []byte, err error) {
privKeyBytes, err := hex.DecodeString(sk) privKey, _ := btcec.PrivKeyFromBytes(sk[:])
if err != nil {
return nil, fmt.Errorf("error decoding sender private key: %w", err)
}
privKey, _ := btcec.PrivKeyFromBytes(privKeyBytes)
// 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)
pubKeyBytes, err := hex.DecodeString("02" + pub) pubKey, err := btcec.ParsePubKey(append([]byte{2}, pub[:]...))
if err != nil { if err != nil {
return nil, fmt.Errorf("error decoding hex string of receiver public key '%s': %w", "02"+pub, err) return nil, fmt.Errorf("error parsing receiver public key '%s': %w", "02"+hex.EncodeToString(pub[:]), err)
}
pubKey, err := btcec.ParsePubKey(pubKeyBytes)
if err != nil {
return nil, fmt.Errorf("error parsing receiver public key '%s': %w", "02"+pub, err)
} }
return btcec.GenerateSharedSecret(privKey, pubKey), nil return btcec.GenerateSharedSecret(privKey, pubKey), nil

View File

@@ -1,10 +1,11 @@
package nip04 package nip04
import ( import (
"encoding/hex"
"strings" "strings"
"testing" "testing"
"fiatjaf.com/nostrlib" "fiatjaf.com/nostr"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@@ -13,10 +14,8 @@ func TestSharedKeysAreTheSame(t *testing.T) {
sk1 := nostr.GeneratePrivateKey() sk1 := nostr.GeneratePrivateKey()
sk2 := nostr.GeneratePrivateKey() sk2 := nostr.GeneratePrivateKey()
pk1, err := nostr.GetPublicKey(sk1) pk1 := nostr.GetPublicKey(sk1)
require.NoError(t, err) pk2 := nostr.GetPublicKey(sk2)
pk2, err := nostr.GetPublicKey(sk2)
require.NoError(t, err)
ss1, err := ComputeSharedSecret(pk2, sk1) ss1, err := ComputeSharedSecret(pk2, sk1)
require.NoError(t, err) require.NoError(t, err)
@@ -57,10 +56,10 @@ func TestEncryptionAndDecryptionWithMultipleLengths(t *testing.T) {
} }
func TestNostrToolsCompatibility(t *testing.T) { func TestNostrToolsCompatibility(t *testing.T) {
sk1 := "92996316beebf94171065a714cbf164d1f56d7ad9b35b329d9fc97535bf25352" sk1, _ := hex.DecodeString("92996316beebf94171065a714cbf164d1f56d7ad9b35b329d9fc97535bf25352")
sk2 := "591c0c249adfb9346f8d37dfeed65725e2eea1d7a6e99fa503342f367138de84" sk2, _ := hex.DecodeString("591c0c249adfb9346f8d37dfeed65725e2eea1d7a6e99fa503342f367138de84")
pk2, _ := nostr.GetPublicKey(sk2) pk2 := nostr.GetPublicKey([32]byte(sk2))
shared, _ := ComputeSharedSecret(pk2, sk1) shared, _ := ComputeSharedSecret(pk2, [32]byte(sk1))
ciphertext := "A+fRnU4aXS4kbTLfowqAww==?iv=QFYUrl5or/n/qamY79ze0A==" ciphertext := "A+fRnU4aXS4kbTLfowqAww==?iv=QFYUrl5or/n/qamY79ze0A=="
plaintext, _ := Decrypt(ciphertext, shared) plaintext, _ := Decrypt(ciphertext, shared)
require.Equal(t, "hello", plaintext, "invalid decryption of nostr-tools payload") require.Equal(t, "hello", plaintext, "invalid decryption of nostr-tools payload")

View File

@@ -7,8 +7,8 @@ import (
"regexp" "regexp"
"strings" "strings"
"fiatjaf.com/nostr"
jsoniter "github.com/json-iterator/go" jsoniter "github.com/json-iterator/go"
"fiatjaf.com/nostrlib"
) )
var NIP05_REGEX = regexp.MustCompile(`^(?:([\w.+-]+)@)?([\w_-]+(\.[\w_-]+)+)$`) var NIP05_REGEX = regexp.MustCompile(`^(?:([\w.+-]+)@)?([\w_-]+(\.[\w_-]+)+)$`)
@@ -40,16 +40,17 @@ func QueryIdentifier(ctx context.Context, fullname string) (*nostr.ProfilePointe
return nil, err return nil, err
} }
pubkey, ok := result.Names[name] pubkeyh, ok := result.Names[name]
if !ok { if !ok {
return nil, fmt.Errorf("no entry for name '%s'", name) return nil, fmt.Errorf("no entry for name '%s'", name)
} }
if !nostr.IsValidPublicKey(pubkey) { pubkey, err := nostr.PubKeyFromHex(pubkeyh)
return nil, fmt.Errorf("got an invalid public key '%s'", pubkey) if err != nil {
return nil, fmt.Errorf("got an invalid public key '%s'", pubkeyh)
} }
relays, _ := result.Relays[pubkey] relays, _ := result.Relays[pubkeyh]
return &nostr.ProfilePointer{ return &nostr.ProfilePointer{
PublicKey: pubkey, PublicKey: pubkey,
Relays: relays, Relays: relays,

View File

@@ -1,6 +1,6 @@
package nip10 package nip10
import "fiatjaf.com/nostrlib" import "fiatjaf.com/nostr"
func GetThreadRoot(tags nostr.Tags) *nostr.EventPointer { func GetThreadRoot(tags nostr.Tags) *nostr.EventPointer {
for _, tag := range tags { for _, tag := range tags {

View File

@@ -7,7 +7,7 @@ import (
"time" "time"
jsoniter "github.com/json-iterator/go" jsoniter "github.com/json-iterator/go"
"fiatjaf.com/nostrlib" "fiatjaf.com/nostr"
) )
// Fetch fetches the NIP-11 metadata for a relay. // Fetch fetches the NIP-11 metadata for a relay.

View File

@@ -9,7 +9,7 @@ import (
"runtime" "runtime"
"strconv" "strconv"
nostr "fiatjaf.com/nostrlib" nostr "fiatjaf.com/nostr"
) )
var ( var (

View File

@@ -8,7 +8,7 @@ import (
"testing" "testing"
"time" "time"
nostr "fiatjaf.com/nostrlib" nostr "fiatjaf.com/nostr"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )

View File

@@ -1,6 +1,6 @@
package nip14 package nip14
import "fiatjaf.com/nostrlib" import "fiatjaf.com/nostr"
func GetSubject(tags nostr.Tags) string { func GetSubject(tags nostr.Tags) string {
for _, tag := range tags { for _, tag := range tags {

View File

@@ -5,8 +5,8 @@ import (
"fmt" "fmt"
"strings" "strings"
"fiatjaf.com/nostrlib" "fiatjaf.com/nostr"
"fiatjaf.com/nostrlib/nip59" "fiatjaf.com/nostr/nip59"
) )
func GetDMRelays(ctx context.Context, pubkey string, pool *nostr.SimplePool, relaysToQuery []string) []string { func GetDMRelays(ctx context.Context, pubkey string, pool *nostr.SimplePool, relaysToQuery []string) []string {

View File

@@ -3,11 +3,10 @@ package nip19
import ( import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"encoding/hex"
"fmt" "fmt"
"fiatjaf.com/nostr"
"github.com/btcsuite/btcd/btcutil/bech32" "github.com/btcsuite/btcd/btcutil/bech32"
"fiatjaf.com/nostrlib"
) )
func Decode(bech32string string) (prefix string, value any, err error) { func Decode(bech32string string) (prefix string, value any, err error) {
@@ -22,12 +21,21 @@ func Decode(bech32string string) (prefix string, value any, err error) {
} }
switch prefix { switch prefix {
case "npub", "nsec", "note": case "nsec":
if len(data) != 32 { if len(data) != 32 {
return prefix, nil, fmt.Errorf("data should be 32 bytes (%d)", len(data)) return prefix, nil, fmt.Errorf("nsec should be 32 bytes (%d)", len(data))
} }
return prefix, [32]byte(data[0:32]), nil
return prefix, hex.EncodeToString(data[0:32]), nil case "note":
if len(data) != 32 {
return prefix, nil, fmt.Errorf("note should be 32 bytes (%d)", len(data))
}
return prefix, [32]byte(data[0:32]), nil
case "npub":
if len(data) != 32 {
return prefix, nil, fmt.Errorf("npub should be 32 bytes (%d)", len(data))
}
return prefix, nostr.PubKey(data[0:32]), nil
case "nprofile": case "nprofile":
var result nostr.ProfilePointer var result nostr.ProfilePointer
curr := 0 curr := 0
@@ -35,7 +43,7 @@ func Decode(bech32string string) (prefix string, value any, err error) {
t, v := readTLVEntry(data[curr:]) t, v := readTLVEntry(data[curr:])
if v == nil { if v == nil {
// end here // end here
if result.PublicKey == "" { if result.PublicKey == nostr.ZeroPK {
return prefix, result, fmt.Errorf("no pubkey found for nprofile") return prefix, result, fmt.Errorf("no pubkey found for nprofile")
} }
@@ -47,7 +55,7 @@ func Decode(bech32string string) (prefix string, value any, err error) {
if len(v) != 32 { if len(v) != 32 {
return prefix, nil, fmt.Errorf("pubkey should be 32 bytes (%d)", len(v)) return prefix, nil, fmt.Errorf("pubkey should be 32 bytes (%d)", len(v))
} }
result.PublicKey = hex.EncodeToString(v) result.PublicKey = nostr.PubKey(v)
case TLVRelay: case TLVRelay:
result.Relays = append(result.Relays, string(v)) result.Relays = append(result.Relays, string(v))
default: default:
@@ -63,7 +71,7 @@ func Decode(bech32string string) (prefix string, value any, err error) {
t, v := readTLVEntry(data[curr:]) t, v := readTLVEntry(data[curr:])
if v == nil { if v == nil {
// end here // end here
if result.ID == "" { if result.ID == nostr.ZeroID {
return prefix, result, fmt.Errorf("no id found for nevent") return prefix, result, fmt.Errorf("no id found for nevent")
} }
@@ -75,19 +83,19 @@ func Decode(bech32string string) (prefix string, value any, err error) {
if len(v) != 32 { if len(v) != 32 {
return prefix, nil, fmt.Errorf("id should be 32 bytes (%d)", len(v)) return prefix, nil, fmt.Errorf("id should be 32 bytes (%d)", len(v))
} }
result.ID = hex.EncodeToString(v) result.ID = nostr.ID(v)
case TLVRelay: case TLVRelay:
result.Relays = append(result.Relays, string(v)) result.Relays = append(result.Relays, string(v))
case TLVAuthor: case TLVAuthor:
if len(v) != 32 { if len(v) != 32 {
return prefix, nil, fmt.Errorf("author should be 32 bytes (%d)", len(v)) return prefix, nil, fmt.Errorf("author should be 32 bytes (%d)", len(v))
} }
result.Author = hex.EncodeToString(v) result.Author = nostr.PubKey(v)
case TLVKind: case TLVKind:
if len(v) != 4 { if len(v) != 4 {
return prefix, nil, fmt.Errorf("invalid uint32 value for integer (%v)", v) return prefix, nil, fmt.Errorf("invalid uint32 value for integer (%v)", v)
} }
result.Kind = int(binary.BigEndian.Uint32(v)) result.Kind = uint16(binary.BigEndian.Uint32(v))
default: default:
// ignore // ignore
} }
@@ -101,7 +109,7 @@ func Decode(bech32string string) (prefix string, value any, err error) {
t, v := readTLVEntry(data[curr:]) t, v := readTLVEntry(data[curr:])
if v == nil { if v == nil {
// end here // end here
if result.Kind == 0 || result.Identifier == "" || result.PublicKey == "" { if result.Kind == 0 || result.Identifier == "" || result.PublicKey == nostr.ZeroPK {
return prefix, result, fmt.Errorf("incomplete naddr") return prefix, result, fmt.Errorf("incomplete naddr")
} }
@@ -117,9 +125,9 @@ func Decode(bech32string string) (prefix string, value any, err error) {
if len(v) != 32 { if len(v) != 32 {
return prefix, nil, fmt.Errorf("author should be 32 bytes (%d)", len(v)) return prefix, nil, fmt.Errorf("author should be 32 bytes (%d)", len(v))
} }
result.PublicKey = hex.EncodeToString(v) result.PublicKey = nostr.PubKey(v)
case TLVKind: case TLVKind:
result.Kind = int(binary.BigEndian.Uint32(v)) result.Kind = uint16(binary.BigEndian.Uint32(v))
default: default:
// ignore // ignore
} }
@@ -131,13 +139,8 @@ func Decode(bech32string string) (prefix string, value any, err error) {
return prefix, data, fmt.Errorf("unknown tag %s", prefix) return prefix, data, fmt.Errorf("unknown tag %s", prefix)
} }
func EncodePrivateKey(privateKeyHex string) (string, error) { func EncodeNsec(sk [32]byte) (string, error) {
b, err := hex.DecodeString(privateKeyHex) bits5, err := bech32.ConvertBits(sk[:], 8, 5, true)
if err != nil {
return "", fmt.Errorf("failed to decode private key hex: %w", err)
}
bits5, err := bech32.ConvertBits(b, 8, 5, true)
if err != nil { if err != nil {
return "", err return "", err
} }
@@ -145,79 +148,44 @@ func EncodePrivateKey(privateKeyHex string) (string, error) {
return bech32.Encode("nsec", bits5) return bech32.Encode("nsec", bits5)
} }
func EncodePublicKey(publicKeyHex string) (string, error) { func EncodeNpub(pk nostr.PubKey) string {
b, err := hex.DecodeString(publicKeyHex) bits5, _ := bech32.ConvertBits(pk[:], 8, 5, true)
if err != nil { npub, _ := bech32.Encode("npub", bits5)
return "", fmt.Errorf("failed to decode public key hex: %w", err) return npub
}
bits5, err := bech32.ConvertBits(b, 8, 5, true)
if err != nil {
return "", err
}
return bech32.Encode("npub", bits5)
} }
func EncodeNote(eventIDHex string) (string, error) { func EncodeNprofile(pk nostr.PubKey, relays []string) string {
b, err := hex.DecodeString(eventIDHex)
if err != nil {
return "", fmt.Errorf("failed to decode event id hex: %w", err)
}
bits5, err := bech32.ConvertBits(b, 8, 5, true)
if err != nil {
return "", err
}
return bech32.Encode("note", bits5)
}
func EncodeProfile(publicKeyHex string, relays []string) (string, error) {
buf := &bytes.Buffer{} buf := &bytes.Buffer{}
pubkey, err := hex.DecodeString(publicKeyHex) writeTLVEntry(buf, TLVDefault, pk[:])
if err != nil {
return "", fmt.Errorf("invalid pubkey '%s': %w", publicKeyHex, err)
}
writeTLVEntry(buf, TLVDefault, pubkey)
for _, url := range relays { for _, url := range relays {
writeTLVEntry(buf, TLVRelay, []byte(url)) writeTLVEntry(buf, TLVRelay, []byte(url))
} }
bits5, err := bech32.ConvertBits(buf.Bytes(), 8, 5, true) bits5, _ := bech32.ConvertBits(buf.Bytes(), 8, 5, true)
if err != nil {
return "", fmt.Errorf("failed to convert bits: %w", err)
}
return bech32.Encode("nprofile", bits5) nprofile, _ := bech32.Encode("nprofile", bits5)
return nprofile
} }
func EncodeEvent(eventIDHex string, relays []string, author string) (string, error) { func EncodeNevent(id nostr.ID, relays []string, author nostr.PubKey) string {
buf := &bytes.Buffer{} buf := &bytes.Buffer{}
id, err := hex.DecodeString(eventIDHex) writeTLVEntry(buf, TLVDefault, id[:])
if err != nil || len(id) != 32 {
return "", fmt.Errorf("invalid id '%s': %w", eventIDHex, err)
}
writeTLVEntry(buf, TLVDefault, id)
for _, url := range relays { for _, url := range relays {
writeTLVEntry(buf, TLVRelay, []byte(url)) writeTLVEntry(buf, TLVRelay, []byte(url))
} }
if pubkey, _ := hex.DecodeString(author); len(pubkey) == 32 { if author != nostr.ZeroPK {
writeTLVEntry(buf, TLVAuthor, pubkey) writeTLVEntry(buf, TLVAuthor, author[:])
} }
bits5, err := bech32.ConvertBits(buf.Bytes(), 8, 5, true) bits5, _ := bech32.ConvertBits(buf.Bytes(), 8, 5, true)
if err != nil { nevent, _ := bech32.Encode("nevent", bits5)
return "", fmt.Errorf("failed to convert bits: %w", err) return nevent
}
return bech32.Encode("nevent", bits5)
} }
func EncodeEntity(publicKey string, kind int, identifier string, relays []string) (string, error) { func EncodeNaddr(pk nostr.PubKey, kind uint16, identifier string, relays []string) string {
buf := &bytes.Buffer{} buf := &bytes.Buffer{}
writeTLVEntry(buf, TLVDefault, []byte(identifier)) writeTLVEntry(buf, TLVDefault, []byte(identifier))
@@ -226,20 +194,13 @@ func EncodeEntity(publicKey string, kind int, identifier string, relays []string
writeTLVEntry(buf, TLVRelay, []byte(url)) writeTLVEntry(buf, TLVRelay, []byte(url))
} }
pubkey, err := hex.DecodeString(publicKey) writeTLVEntry(buf, TLVAuthor, pk[:])
if err != nil {
return "", fmt.Errorf("invalid pubkey '%s': %w", pubkey, err)
}
writeTLVEntry(buf, TLVAuthor, pubkey)
kindBytes := make([]byte, 4) kindBytes := make([]byte, 4)
binary.BigEndian.PutUint32(kindBytes, uint32(kind)) binary.BigEndian.PutUint32(kindBytes, uint32(kind))
writeTLVEntry(buf, TLVKind, kindBytes) writeTLVEntry(buf, TLVKind, kindBytes)
bits5, err := bech32.ConvertBits(buf.Bytes(), 8, 5, true) bits5, _ := bech32.ConvertBits(buf.Bytes(), 8, 5, true)
if err != nil { naddr, _ := bech32.Encode("naddr", bits5)
return "", fmt.Errorf("failed to convert bits: %w", err) return naddr
}
return bech32.Encode("naddr", bits5)
} }

View File

@@ -1,21 +1,24 @@
package nip19 package nip19
import ( import (
"encoding/hex"
"testing" "testing"
"fiatjaf.com/nostrlib" "fiatjaf.com/nostr"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func TestEncodeNpub(t *testing.T) { func TestEncodeNpub(t *testing.T) {
npub, err := EncodePublicKey("3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d") npub := EncodeNpub(nostr.MustPubKeyFromHex("3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d"))
assert.NoError(t, err)
assert.Equal(t, "npub180cvv07tjdrrgpa0j7j7tmnyl2yr6yr7l8j4s3evf6u64th6gkwsyjh6w6", npub, "produced an unexpected npub string") assert.Equal(t, "npub180cvv07tjdrrgpa0j7j7tmnyl2yr6yr7l8j4s3evf6u64th6gkwsyjh6w6", npub, "produced an unexpected npub string")
} }
func TestEncodeNsec(t *testing.T) { func TestEncodeNsec(t *testing.T) {
nsec, err := EncodePrivateKey("3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d") skh := "3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d"
var sk [32]byte
hex.Decode(sk[:], []byte(skh))
nsec, err := EncodeNsec(sk)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, "nsec180cvv07tjdrrgpa0j7j7tmnyl2yr6yr7l8j4s3evf6u64th6gkwsgyumg0", nsec, "produced an unexpected nsec string") assert.Equal(t, "nsec180cvv07tjdrrgpa0j7j7tmnyl2yr6yr7l8j4s3evf6u64th6gkwsgyumg0", nsec, "produced an unexpected nsec string")
} }
@@ -63,20 +66,22 @@ func TestDecodeNprofile(t *testing.T) {
} }
func TestEncodeNprofile(t *testing.T) { func TestEncodeNprofile(t *testing.T) {
nprofile, err := EncodeProfile("3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d", []string{ nprofile := EncodeNprofile(
"wss://r.x.com", nostr.MustPubKeyFromHex("3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d"),
"wss://djbas.sadkb.com", []string{
}) "wss://r.x.com",
"wss://djbas.sadkb.com",
},
)
assert.NoError(t, err)
assert.Equal(t, assert.Equal(t,
"nprofile1qqsrhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8gpp4mhxue69uhhytnc9e3k7mgpz4mhxue69uhkg6nzv9ejuumpv34kytnrdaksjlyr9p", "nprofile1qqsrhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8gpp4mhxue69uhhytnc9e3k7mgpz4mhxue69uhkg6nzv9ejuumpv34kytnrdaksjlyr9p",
nprofile, "produced an unexpected nprofile string: %s", nprofile) nprofile, "produced an unexpected nprofile string: %s", nprofile)
} }
func TestEncodeDecodeNaddr(t *testing.T) { func TestEncodeDecodeNaddr(t *testing.T) {
naddr, err := EncodeEntity( naddr := EncodeNaddr(
"3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d", nostr.MustPubKeyFromHex("3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d"),
nostr.KindArticle, nostr.KindArticle,
"banana", "banana",
[]string{ []string{
@@ -84,7 +89,6 @@ func TestEncodeDecodeNaddr(t *testing.T) {
"wss://nostr.banana.com", "wss://nostr.banana.com",
}) })
assert.NoError(t, err)
assert.Equal(t, assert.Equal(t,
"naddr1qqrxyctwv9hxzqfwwaehxw309aex2mrp0yhxummnw3ezuetcv9khqmr99ekhjer0d4skjm3wv4uxzmtsd3jjucm0d5q3vamnwvaz7tmwdaehgu3wvfskuctwvyhxxmmdqgsrhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8grqsqqqa28a3lkds", "naddr1qqrxyctwv9hxzqfwwaehxw309aex2mrp0yhxummnw3ezuetcv9khqmr99ekhjer0d4skjm3wv4uxzmtsd3jjucm0d5q3vamnwvaz7tmwdaehgu3wvfskuctwvyhxxmmdqgsrhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8grqsqqqa28a3lkds",
naddr, "produced an unexpected naddr string: %s", naddr) naddr, "produced an unexpected naddr string: %s", naddr)
@@ -116,14 +120,12 @@ func TestDecodeNaddrWithoutRelays(t *testing.T) {
} }
func TestEncodeDecodeNEvent(t *testing.T) { func TestEncodeDecodeNEvent(t *testing.T) {
nevent, err := EncodeEvent( nevent := EncodeNevent(
"45326f5d6962ab1e3cd424e758c3002b8665f7b0d8dcee9fe9e288d7751ac194", nostr.MustIDFromHex("45326f5d6962ab1e3cd424e758c3002b8665f7b0d8dcee9fe9e288d7751ac194"),
[]string{"wss://banana.com"}, []string{"wss://banana.com"},
"7fa56f5d6962ab1e3cd424e758c3002b8665f7b0d8dcee9fe9e288d7751abb88", nostr.MustPubKeyFromHex("7fa56f5d6962ab1e3cd424e758c3002b8665f7b0d8dcee9fe9e288d7751abb88"),
) )
assert.NoError(t, err)
expectedNEvent := "nevent1qqsy2vn0t45k92c78n2zfe6ccvqzhpn977cd3h8wnl579zxhw5dvr9qpzpmhxue69uhkyctwv9hxztnrdaksygrl54h466tz4v0re4pyuavvxqptsejl0vxcmnhfl60z3rth2x4m3q04ndyp" expectedNEvent := "nevent1qqsy2vn0t45k92c78n2zfe6ccvqzhpn977cd3h8wnl579zxhw5dvr9qpzpmhxue69uhkyctwv9hxztnrdaksygrl54h466tz4v0re4pyuavvxqptsejl0vxcmnhfl60z3rth2x4m3q04ndyp"
assert.Equal(t, expectedNEvent, nevent) assert.Equal(t, expectedNEvent, nevent)

View File

@@ -3,7 +3,7 @@ package nip19
import ( import (
"fmt" "fmt"
"github.com/nbd-wtf/go-nostr" "fiatjaf.com/nostr"
) )
func EncodePointer(pointer nostr.Pointer) string { func EncodePointer(pointer nostr.Pointer) string {

View File

@@ -1,10 +1,9 @@
package nip19 package nip19
import ( import (
"github.com/nbd-wtf/go-nostr" "fiatjaf.com/nostr"
) )
func NeventFromRelayEvent(ie nostr.RelayEvent) string { func NeventFromRelayEvent(ie nostr.RelayEvent) string {
v, _ := EncodeEvent(ie.ID, []string{ie.Relay.URL}, ie.PubKey) return EncodeNevent(ie.ID, []string{ie.Relay.URL}, ie.PubKey)
return v
} }

View File

@@ -6,9 +6,9 @@ import (
"regexp" "regexp"
"strings" "strings"
"github.com/nbd-wtf/go-nostr" "fiatjaf.com/nostr"
"github.com/nbd-wtf/go-nostr/nip19" "fiatjaf.com/nostr/nip19"
"github.com/nbd-wtf/go-nostr/nip73" "fiatjaf.com/nostr/nip73"
) )
type Block struct { type Block struct {
@@ -54,7 +54,7 @@ func Parse(content string) iter.Seq[Block] {
var pointer nostr.Pointer var pointer nostr.Pointer
switch prefix { switch prefix {
case "npub": case "npub":
pointer = nostr.ProfilePointer{PublicKey: data.(string)} pointer = nostr.ProfilePointer{PublicKey: data.(nostr.PubKey)}
case "nprofile", "nevent", "naddr": case "nprofile", "nevent", "naddr":
pointer = data.(nostr.Pointer) pointer = data.(nostr.Pointer)
case "note", "nsec": case "note", "nsec":

View File

@@ -5,8 +5,8 @@ import (
"slices" "slices"
"testing" "testing"
"github.com/nbd-wtf/go-nostr" "fiatjaf.com/nostr"
"github.com/nbd-wtf/go-nostr/nip73" "fiatjaf.com/nostr/nip73"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@@ -19,9 +19,9 @@ func TestParse(t *testing.T) {
"hello, nostr:nprofile1qqsvc6ulagpn7kwrcwdqgp797xl7usumqa6s3kgcelwq6m75x8fe8yc5usxdg wrote nostr:nevent1qqsvc6ulagpn7kwrcwdqgp797xl7usumqa6s3kgcelwq6m75x8fe8ychxp5v4!", "hello, nostr:nprofile1qqsvc6ulagpn7kwrcwdqgp797xl7usumqa6s3kgcelwq6m75x8fe8yc5usxdg wrote nostr:nevent1qqsvc6ulagpn7kwrcwdqgp797xl7usumqa6s3kgcelwq6m75x8fe8ychxp5v4!",
[]Block{ []Block{
{Text: "hello, ", Start: 0}, {Text: "hello, ", Start: 0},
{Text: "nostr:nprofile1qqsvc6ulagpn7kwrcwdqgp797xl7usumqa6s3kgcelwq6m75x8fe8yc5usxdg", Start: 7, Pointer: nostr.ProfilePointer{PublicKey: "cc6b9fea033f59c3c39a0407c5f1bfee439b077508d918cfdc0d6fd431d39393"}}, {Text: "nostr:nprofile1qqsvc6ulagpn7kwrcwdqgp797xl7usumqa6s3kgcelwq6m75x8fe8yc5usxdg", Start: 7, Pointer: nostr.ProfilePointer{PublicKey: nostr.MustPubKeyFromHex("cc6b9fea033f59c3c39a0407c5f1bfee439b077508d918cfdc0d6fd431d39393")}},
{Text: " wrote ", Start: 83}, {Text: " wrote ", Start: 83},
{Text: "nostr:nevent1qqsvc6ulagpn7kwrcwdqgp797xl7usumqa6s3kgcelwq6m75x8fe8ychxp5v4", Start: 90, Pointer: nostr.EventPointer{ID: "cc6b9fea033f59c3c39a0407c5f1bfee439b077508d918cfdc0d6fd431d39393"}}, {Text: "nostr:nevent1qqsvc6ulagpn7kwrcwdqgp797xl7usumqa6s3kgcelwq6m75x8fe8ychxp5v4", Start: 90, Pointer: nostr.EventPointer{ID: nostr.MustIDFromHex("cc6b9fea033f59c3c39a0407c5f1bfee439b077508d918cfdc0d6fd431d39393")}},
{Text: "!", Start: 164}, {Text: "!", Start: 164},
}, },
}, },

View File

@@ -1,82 +0,0 @@
package nip27
import (
"iter"
"regexp"
"github.com/nbd-wtf/go-nostr"
"github.com/nbd-wtf/go-nostr/nip19"
)
type Reference struct {
Text string
Start int
End int
Pointer nostr.Pointer
}
var mentionRegex = regexp.MustCompile(`\bnostr:((note|npub|naddr|nevent|nprofile)1\w+)\b`)
// Deprecated: this is useless, use Parse() isntead (but the semantics is different)
func ParseReferences(evt nostr.Event) iter.Seq[Reference] {
return func(yield func(Reference) bool) {
for _, ref := range mentionRegex.FindAllStringSubmatchIndex(evt.Content, -1) {
reference := Reference{
Text: evt.Content[ref[0]:ref[1]],
Start: ref[0],
End: ref[1],
}
nip19code := evt.Content[ref[2]:ref[3]]
if prefix, data, err := nip19.Decode(nip19code); err == nil {
switch prefix {
case "npub":
pointer := nostr.ProfilePointer{
PublicKey: data.(string), Relays: []string{},
}
tag := evt.Tags.FindWithValue("p", pointer.PublicKey)
if tag != nil && len(tag) >= 3 {
pointer.Relays = []string{tag[2]}
}
if nostr.IsValidPublicKey(pointer.PublicKey) {
reference.Pointer = pointer
}
case "nprofile":
pointer := data.(nostr.ProfilePointer)
tag := evt.Tags.FindWithValue("p", pointer.PublicKey)
if tag != nil && len(tag) >= 3 {
pointer.Relays = append(pointer.Relays, tag[2])
}
if nostr.IsValidPublicKey(pointer.PublicKey) {
reference.Pointer = pointer
}
case "note":
// we don't even bother here because people using note1 codes aren't including relay hints anyway
reference.Pointer = nostr.EventPointer{ID: data.(string), Relays: nil}
case "nevent":
pointer := data.(nostr.EventPointer)
tag := evt.Tags.FindWithValue("e", pointer.ID)
if tag != nil && len(tag) >= 3 {
pointer.Relays = append(pointer.Relays, tag[2])
if pointer.Author == "" && len(tag) >= 5 && nostr.IsValidPublicKey(tag[4]) {
pointer.Author = tag[4]
}
}
reference.Pointer = pointer
case "naddr":
pointer := data.(nostr.EntityPointer)
tag := evt.Tags.FindWithValue("a", pointer.AsTagReference())
if tag != nil && len(tag) >= 3 {
pointer.Relays = append(pointer.Relays, tag[2])
}
reference.Pointer = pointer
}
}
if !yield(reference) {
return
}
}
}
}

View File

@@ -1,46 +0,0 @@
package nip27
import (
"slices"
"testing"
"github.com/nbd-wtf/go-nostr"
"github.com/stretchr/testify/require"
)
func TestParseReferences(t *testing.T) {
evt := nostr.Event{
Tags: nostr.Tags{
{"p", "cc6b9fea033f59c3c39a0407c5f1bfee439b077508d918cfdc0d6fd431d39393", "wss://xawr.com"},
{"e", "a84c5de86efc2ec2cff7bad077c4171e09146b633b7ad117fffe088d9579ac33", "wss://other.com", "reply"},
{"e", "cc6b9fea033f59c3c39a0407c5f1bfee439b077508d918cfdc0d6fd431d39393", "wss://nasdj.com"},
},
Content: "hello, nostr:nprofile1qqsvc6ulagpn7kwrcwdqgp797xl7usumqa6s3kgcelwq6m75x8fe8yc5usxdg wrote nostr:nevent1qqsvc6ulagpn7kwrcwdqgp797xl7usumqa6s3kgcelwq6m75x8fe8ychxp5v4!",
}
expected := []Reference{
{
Text: "nostr:nprofile1qqsvc6ulagpn7kwrcwdqgp797xl7usumqa6s3kgcelwq6m75x8fe8yc5usxdg",
Start: 7,
End: 83,
Pointer: nostr.ProfilePointer{
PublicKey: "cc6b9fea033f59c3c39a0407c5f1bfee439b077508d918cfdc0d6fd431d39393",
Relays: []string{"wss://xawr.com"},
},
},
{
Text: "nostr:nevent1qqsvc6ulagpn7kwrcwdqgp797xl7usumqa6s3kgcelwq6m75x8fe8ychxp5v4",
Start: 90,
End: 164,
Pointer: nostr.EventPointer{
ID: "cc6b9fea033f59c3c39a0407c5f1bfee439b077508d918cfdc0d6fd431d39393",
Relays: []string{"wss://nasdj.com"},
Author: "",
},
},
}
got := slices.Collect(ParseReferences(evt))
require.EqualValues(t, expected, got)
}

View File

@@ -6,7 +6,7 @@ import (
"slices" "slices"
"strings" "strings"
"fiatjaf.com/nostrlib" "fiatjaf.com/nostr"
) )
type GroupAddress struct { type GroupAddress struct {

View File

@@ -3,7 +3,7 @@ package nip29
import ( import (
"slices" "slices"
"fiatjaf.com/nostrlib" "fiatjaf.com/nostr"
) )
type Role struct { type Role struct {

View File

@@ -1,6 +1,6 @@
package nip31 package nip31
import "fiatjaf.com/nostrlib" import "fiatjaf.com/nostr"
func GetAlt(event nostr.Event) string { func GetAlt(event nostr.Event) string {
for _, tag := range event.Tags { for _, tag := range event.Tags {

View File

@@ -4,7 +4,7 @@ import (
"strings" "strings"
"github.com/bluekeyes/go-gitdiff/gitdiff" "github.com/bluekeyes/go-gitdiff/gitdiff"
"fiatjaf.com/nostrlib" "fiatjaf.com/nostr"
) )
type Patch struct { type Patch struct {

View File

@@ -4,7 +4,7 @@ import (
"context" "context"
"fmt" "fmt"
"fiatjaf.com/nostrlib" "fiatjaf.com/nostr"
) )
type Repository struct { type Repository struct {

View File

@@ -3,7 +3,7 @@ package nip34
import ( import (
"strings" "strings"
"fiatjaf.com/nostrlib" "fiatjaf.com/nostr"
) )
type RepositoryState struct { type RepositoryState struct {

View File

@@ -3,7 +3,7 @@ package nip40
import ( import (
"strconv" "strconv"
"fiatjaf.com/nostrlib" "fiatjaf.com/nostr"
) )
// GetExpiration returns the expiration timestamp for this event, or -1 if no "expiration" tag exists or // GetExpiration returns the expiration timestamp for this event, or -1 if no "expiration" tag exists or

View File

@@ -5,7 +5,7 @@ import (
"strings" "strings"
"time" "time"
"fiatjaf.com/nostrlib" "fiatjaf.com/nostr"
) )
// CreateUnsignedAuthEvent creates an event which should be sent via an "AUTH" command. // CreateUnsignedAuthEvent creates an event which should be sent via an "AUTH" command.

View File

@@ -12,6 +12,7 @@ import (
"io" "io"
"math" "math"
"fiatjaf.com/nostr"
"github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2"
"github.com/decred/dcrd/dcrec/secp256k1/v4" "github.com/decred/dcrd/dcrec/secp256k1/v4"
"golang.org/x/crypto/chacha20" "golang.org/x/crypto/chacha20"
@@ -153,10 +154,12 @@ func Decrypt(b64ciphertextWrapped string, conversationKey [32]byte) (string, err
return string(unpadded), nil return string(unpadded), nil
} }
func GenerateConversationKey(pub string, sk string) ([32]byte, error) { var maxThreshold, _ = hex.DecodeString("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141")
func GenerateConversationKey(pub nostr.PubKey, sk [32]byte) ([32]byte, error) {
var ck [32]byte var ck [32]byte
if sk >= "fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141" || sk == "0000000000000000000000000000000000000000000000000000000000000000" { if bytes.Compare(sk[:], maxThreshold) != -1 || sk == [32]byte{} {
return ck, fmt.Errorf("invalid private key: x coordinate %s is not on the secp256k1 curve", sk) return ck, fmt.Errorf("invalid private key: x coordinate %s is not on the secp256k1 curve", sk)
} }
@@ -220,20 +223,13 @@ func calcPadding(sLen int) int {
} }
// code adapted from nip04.ComputeSharedSecret() // code adapted from nip04.ComputeSharedSecret()
func computeSharedSecret(pub string, sk string) (sharedSecret [32]byte, err error) { func computeSharedSecret(pub nostr.PubKey, sk [32]byte) (sharedSecret [32]byte, err error) {
privKeyBytes, err := hex.DecodeString(sk) privKey, _ := btcec.PrivKeyFromBytes(sk[:])
if err != nil {
return sharedSecret, fmt.Errorf("error decoding sender private key: %w", err)
}
privKey, _ := btcec.PrivKeyFromBytes(privKeyBytes)
pubKeyBytes, err := hex.DecodeString("02" + pub) pubKey, err := btcec.ParsePubKey(append([]byte{2}, pub[:]...))
if err != nil { if err != nil {
return sharedSecret, fmt.Errorf("error decoding hex string of receiver public key '%s': %w", "02"+pub, err) return sharedSecret, fmt.Errorf("error parsing receiver public key '%s': %w",
} "02"+hex.EncodeToString(pub[:]), err)
pubKey, err := btcec.ParsePubKey(pubKeyBytes)
if err != nil {
return sharedSecret, fmt.Errorf("error parsing receiver public key '%s': %w", "02"+pub, err)
} }
var point, result secp256k1.JacobianPoint var point, result secp256k1.JacobianPoint

View File

@@ -8,14 +8,22 @@ import (
"strings" "strings"
"testing" "testing"
"fiatjaf.com/nostrlib" "fiatjaf.com/nostr"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func assertCryptPriv(t *testing.T, sk1 string, sk2 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) {
sk1 := [32]byte{}
hex.Decode(sk1[:], []byte(skh1))
sk2 := [32]byte{}
hex.Decode(sk2[:], []byte(skh2))
k1, err := hexDecode32Array(conversationKey) k1, err := hexDecode32Array(conversationKey)
require.NoErrorf(t, err, "hex decode failed for conversation key: %v", err) require.NoErrorf(t, err, "hex decode failed for conversation key: %v", err)
assertConversationKeyGenerationSec(t, sk1, sk2, conversationKey)
pub2 := nostr.GetPublicKey(sk2)
assertConversationKeyGenerationPub(t, skh1, hex.EncodeToString(pub2[:]), conversationKey)
customNonce, err := hex.DecodeString(salt) customNonce, err := hex.DecodeString(salt)
require.NoErrorf(t, err, "hex decode failed for salt: %v", err) require.NoErrorf(t, err, "hex decode failed for salt: %v", err)
@@ -39,7 +47,13 @@ func assertDecryptFail(t *testing.T, conversationKey string, _ string, ciphertex
} }
func assertConversationKeyFail(t *testing.T, priv string, pub string, msg string) { func assertConversationKeyFail(t *testing.T, priv string, pub string, msg string) {
_, err := GenerateConversationKey(pub, priv) var pub32 [32]byte
hex.Decode(pub32[:], []byte(pub))
var priv32 [32]byte
hex.Decode(priv32[:], []byte(priv))
_, err := GenerateConversationKey(pub32, priv32)
require.ErrorContains(t, err, msg) require.ErrorContains(t, err, msg)
} }
@@ -47,18 +61,18 @@ func assertConversationKeyGenerationPub(t *testing.T, priv string, pub string, c
expectedConversationKey, err := hexDecode32Array(conversationKey) expectedConversationKey, err := hexDecode32Array(conversationKey)
require.NoErrorf(t, err, "hex decode failed for conversation key: %v", err) require.NoErrorf(t, err, "hex decode failed for conversation key: %v", err)
actualConversationKey, err := GenerateConversationKey(pub, priv) var pub32 [32]byte
hex.Decode(pub32[:], []byte(pub))
var priv32 [32]byte
hex.Decode(priv32[:], []byte(priv))
actualConversationKey, err := GenerateConversationKey(pub32, priv32)
require.NoErrorf(t, err, "conversation key generation failed: %v", err) require.NoErrorf(t, err, "conversation key generation failed: %v", err)
require.Equalf(t, expectedConversationKey, actualConversationKey, "wrong conversation key") require.Equalf(t, expectedConversationKey, actualConversationKey, "wrong conversation key")
} }
func assertConversationKeyGenerationSec(t *testing.T, sk1 string, sk2 string, conversationKey string) {
pub2, err := nostr.GetPublicKey(sk2)
require.NoErrorf(t, err, "failed to derive pubkey from sk2: %v", err)
assertConversationKeyGenerationPub(t, sk1, pub2, conversationKey)
}
func assertMessageKeyGeneration(t *testing.T, conversationKey string, salt string, chachaKey string, chachaSalt string, hmacKey string) bool { func assertMessageKeyGeneration(t *testing.T, conversationKey string, salt string, chachaKey string, chachaSalt string, hmacKey string) bool {
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)
@@ -1061,7 +1075,7 @@ func TestMessageKeyGeneration033(t *testing.T) {
func TestMaxLength(t *testing.T) { func TestMaxLength(t *testing.T) {
sk1 := nostr.GeneratePrivateKey() sk1 := nostr.GeneratePrivateKey()
sk2 := nostr.GeneratePrivateKey() sk2 := nostr.GeneratePrivateKey()
pub2, _ := nostr.GetPublicKey(sk2) pub2 := nostr.GetPublicKey(sk2)
salt := make([]byte, 32) salt := make([]byte, 32)
rand.Read(salt) rand.Read(salt)
conversationKey, _ := GenerateConversationKey(pub2, sk1) conversationKey, _ := GenerateConversationKey(pub2, sk1)
@@ -1072,8 +1086,8 @@ func TestMaxLength(t *testing.T) {
} }
assertCryptPub(t, assertCryptPub(t,
sk1, hex.EncodeToString(sk1[:]),
pub2, hex.EncodeToString(pub2[:]),
fmt.Sprintf("%x", conversationKey), fmt.Sprintf("%x", conversationKey),
fmt.Sprintf("%x", salt), fmt.Sprintf("%x", salt),
plaintext, plaintext,

View File

@@ -4,7 +4,7 @@ import (
"iter" "iter"
"strconv" "strconv"
"fiatjaf.com/nostrlib" "fiatjaf.com/nostr"
) )
func HyperLogLogEventPubkeyOffsetsAndReferencesForEvent(evt *nostr.Event) iter.Seq2[string, int] { func HyperLogLogEventPubkeyOffsetsAndReferencesForEvent(evt *nostr.Event) iter.Seq2[string, int] {

View File

@@ -3,7 +3,7 @@ package nip45
import ( import (
"strconv" "strconv"
"fiatjaf.com/nostrlib" "fiatjaf.com/nostr"
) )
// HyperLogLogEventPubkeyOffsetForFilter returns the deterministic pubkey offset that will be used // HyperLogLogEventPubkeyOffsetForFilter returns the deterministic pubkey offset that will be used

View File

@@ -3,15 +3,13 @@ package nip46
import ( import (
"fmt" "fmt"
"fiatjaf.com/nostrlib" "fiatjaf.com/nostr"
"fiatjaf.com/nostrlib/nip04" "fiatjaf.com/nostr/nip44"
"fiatjaf.com/nostrlib/nip44"
) )
type Session struct { type Session struct {
PublicKey string PublicKey nostr.PubKey
SharedKey []byte // nip04 ConversationKey [32]byte
ConversationKey [32]byte // nip44
} }
type RelayReadWrite struct { type RelayReadWrite struct {
@@ -22,22 +20,18 @@ type RelayReadWrite struct {
func (s Session) ParseRequest(event *nostr.Event) (Request, error) { func (s Session) ParseRequest(event *nostr.Event) (Request, error) {
var req Request var req Request
plain, err1 := nip44.Decrypt(event.Content, s.ConversationKey) plain, err := nip44.Decrypt(event.Content, s.ConversationKey)
if err1 != nil { if err != nil {
var err2 error return req, fmt.Errorf("failed to decrypt event from %s: (nip44: %w)", event.PubKey, err)
plain, err2 = nip04.Decrypt(event.Content, s.SharedKey)
if err2 != nil {
return req, fmt.Errorf("failed to decrypt event from %s: (nip44: %w, nip04: %w)", event.PubKey, err1, err2)
}
} }
err := json.Unmarshal([]byte(plain), &req) err = json.Unmarshal([]byte(plain), &req)
return req, err return req, err
} }
func (s Session) MakeResponse( func (s Session) MakeResponse(
id string, id string,
requester string, requester nostr.PubKey,
result string, result string,
err error, err error,
) (resp Response, evt nostr.Event, error error) { ) (resp Response, evt nostr.Event, error error) {

View File

@@ -8,10 +8,10 @@ import (
"strconv" "strconv"
"sync/atomic" "sync/atomic"
"fiatjaf.com/nostr"
"fiatjaf.com/nostr/nip04"
"fiatjaf.com/nostr/nip44"
"github.com/mailru/easyjson" "github.com/mailru/easyjson"
"fiatjaf.com/nostrlib"
"fiatjaf.com/nostrlib/nip04"
"fiatjaf.com/nostrlib/nip44"
"github.com/puzpuzpuz/xsync/v3" "github.com/puzpuzpuz/xsync/v3"
) )
@@ -38,7 +38,7 @@ type BunkerClient struct {
// pool can be passed to reuse an existing pool, otherwise a new pool will be created. // pool can be passed to reuse an existing pool, otherwise a new pool will be created.
func ConnectBunker( func ConnectBunker(
ctx context.Context, ctx context.Context,
clientSecretKey string, clientSecretKey nostr.PubKey,
bunkerURLOrNIP05 string, bunkerURLOrNIP05 string,
pool *nostr.SimplePool, pool *nostr.SimplePool,
onAuth func(string), onAuth func(string),
@@ -51,7 +51,7 @@ func ConnectBunker(
// assume it's a bunker url (will fail later if not) // assume it's a bunker url (will fail later if not)
secret := parsed.Query().Get("secret") secret := parsed.Query().Get("secret")
relays := parsed.Query()["relay"] relays := parsed.Query()["relay"]
targetPublicKey := parsed.Host targetPublicKey, _ := nostr.PubKeyFromHex(parsed.Host)
if parsed.Scheme == "" { if parsed.Scheme == "" {
// could be a NIP-05 // could be a NIP-05
@@ -85,8 +85,8 @@ func ConnectBunker(
func NewBunker( func NewBunker(
ctx context.Context, ctx context.Context,
clientSecretKey string, clientSecretKey [32]byte,
targetPublicKey string, targetPublicKey nostr.PubKey,
relays []string, relays []string,
pool *nostr.SimplePool, pool *nostr.SimplePool,
onAuth func(string), onAuth func(string),
@@ -95,8 +95,7 @@ func NewBunker(
pool = nostr.NewSimplePool(ctx) pool = nostr.NewSimplePool(ctx)
} }
clientPublicKey, _ := nostr.GetPublicKey(clientSecretKey) clientPublicKey := nostr.GetPublicKey(clientSecretKey)
sharedSecret, _ := nip04.ComputeSharedSecret(targetPublicKey, clientSecretKey)
conversationKey, _ := nip44.GenerateConversationKey(targetPublicKey, clientSecretKey) conversationKey, _ := nip44.GenerateConversationKey(targetPublicKey, clientSecretKey)
bunker := &BunkerClient{ bunker := &BunkerClient{

View File

@@ -2,44 +2,41 @@ package nip46
import ( import (
"context" "context"
"encoding/hex"
"fmt" "fmt"
"slices"
"sync" "sync"
"fiatjaf.com/nostr"
"fiatjaf.com/nostr/nip44"
"github.com/mailru/easyjson" "github.com/mailru/easyjson"
"fiatjaf.com/nostrlib"
"fiatjaf.com/nostrlib/nip04"
"fiatjaf.com/nostrlib/nip44"
) )
var _ Signer = (*DynamicSigner)(nil) var _ Signer = (*DynamicSigner)(nil)
type DynamicSigner struct { type DynamicSigner struct {
sessionKeys []string sessions map[nostr.PubKey]Session
sessions []Session
sync.Mutex sync.Mutex
getHandlerSecretKey func(handlerPubkey string) (string, error) getHandlerSecretKey func(handlerPubkey nostr.PubKey) ([32]byte, error)
getUserKeyer func(handlerPubkey string) (nostr.Keyer, error) getUserKeyer func(handlerPubkey nostr.PubKey) (nostr.Keyer, error)
authorizeSigning func(event nostr.Event, from string, secret string) bool authorizeSigning func(event nostr.Event, from nostr.PubKey, secret string) bool
authorizeEncryption func(from string, secret string) bool authorizeEncryption func(from nostr.PubKey, secret string) bool
onEventSigned func(event nostr.Event) onEventSigned func(event nostr.Event)
getRelays func(pubkey string) map[string]RelayReadWrite
} }
func NewDynamicSigner( func NewDynamicSigner(
// the handler is the keypair we use to communicate with the NIP-46 client, decrypt requests, encrypt responses etc // the handler is the keypair we use to communicate with the NIP-46 client, decrypt requests, encrypt responses etc
getHandlerSecretKey func(handlerPubkey string) (string, error), getHandlerSecretKey func(handlerPubkey nostr.PubKey) ([32]byte, error),
// this should correspond to the actual user on behalf of which we will respond to requests // this should correspond to the actual user on behalf of which we will respond to requests
getUserKeyer func(handlerPubkey string) (nostr.Keyer, error), getUserKeyer func(handlerPubkey nostr.PubKey) (nostr.Keyer, error),
// this is called on every sign_event call, if it is nil it will be assumed that everything is authorized // this is called on every sign_event call, if it is nil it will be assumed that everything is authorized
authorizeSigning func(event nostr.Event, from string, secret string) bool, authorizeSigning func(event nostr.Event, from nostr.PubKey, secret string) bool,
// this is called on every encrypt or decrypt calls, if it is nil it will be assumed that everything is authorized // this is called on every encrypt or decrypt calls, if it is nil it will be assumed that everything is authorized
authorizeEncryption func(from string, secret string) bool, authorizeEncryption func(from nostr.PubKey, secret string) bool,
// unless it is nil, this is called after every event is signed // unless it is nil, this is called after every event is signed
onEventSigned func(event nostr.Event), onEventSigned func(event nostr.Event),
@@ -53,34 +50,28 @@ func NewDynamicSigner(
authorizeSigning: authorizeSigning, authorizeSigning: authorizeSigning,
authorizeEncryption: authorizeEncryption, authorizeEncryption: authorizeEncryption,
onEventSigned: onEventSigned, onEventSigned: onEventSigned,
getRelays: getRelays,
} }
} }
func (p *DynamicSigner) GetSession(clientPubkey string) (Session, bool) { func (p *DynamicSigner) GetSession(clientPubkey nostr.PubKey) (Session, bool) {
idx, exists := slices.BinarySearch(p.sessionKeys, clientPubkey) session, exists := p.sessions[clientPubkey]
if exists { if exists {
return p.sessions[idx], true return session, true
} }
return Session{}, false return Session{}, false
} }
func (p *DynamicSigner) setSession(clientPubkey string, session Session) { func (p *DynamicSigner) setSession(clientPubkey nostr.PubKey, session Session) {
p.Lock() p.Lock()
defer p.Unlock() defer p.Unlock()
idx, exists := slices.BinarySearch(p.sessionKeys, clientPubkey) _, exists := p.sessions[clientPubkey]
if exists { if exists {
return return
} }
// add to pool // add to pool
p.sessionKeys = append(p.sessionKeys, "") // bogus append just to increase the capacity p.sessions[clientPubkey] = session
p.sessions = append(p.sessions, Session{})
copy(p.sessionKeys[idx+1:], p.sessionKeys[idx:])
copy(p.sessions[idx+1:], p.sessions[idx:])
p.sessionKeys[idx] = clientPubkey
p.sessions[idx] = session
} }
func (p *DynamicSigner) HandleRequest(ctx context.Context, event *nostr.Event) ( func (p *DynamicSigner) HandleRequest(ctx context.Context, event *nostr.Event) (
@@ -99,7 +90,10 @@ func (p *DynamicSigner) HandleRequest(ctx context.Context, event *nostr.Event) (
return req, resp, eventResponse, fmt.Errorf("invalid \"p\" tag") return req, resp, eventResponse, fmt.Errorf("invalid \"p\" tag")
} }
handlerPubkey := handler[1] handlerPubkey, err := nostr.PubKeyFromHex(handler[1])
if err != nil {
return req, resp, eventResponse, fmt.Errorf("%x is invalid pubkey: %w", handler[1], err)
}
handlerSecret, err := p.getHandlerSecretKey(handlerPubkey) handlerSecret, err := p.getHandlerSecretKey(handlerPubkey)
if err != nil { if err != nil {
return req, resp, eventResponse, fmt.Errorf("no private key for %s: %w", handlerPubkey, err) return req, resp, eventResponse, fmt.Errorf("no private key for %s: %w", handlerPubkey, err)
@@ -109,18 +103,10 @@ func (p *DynamicSigner) HandleRequest(ctx context.Context, event *nostr.Event) (
return req, resp, eventResponse, fmt.Errorf("failed to get user keyer for %s: %w", handlerPubkey, err) return req, resp, eventResponse, fmt.Errorf("failed to get user keyer for %s: %w", handlerPubkey, err)
} }
var session Session session, exists := p.sessions[event.PubKey]
idx, exists := slices.BinarySearch(p.sessionKeys, event.PubKey) if !exists {
if exists {
session = p.sessions[idx]
} else {
session = Session{} session = Session{}
session.SharedKey, err = nip04.ComputeSharedSecret(event.PubKey, handlerSecret)
if err != nil {
return req, resp, eventResponse, fmt.Errorf("failed to compute shared secret: %w", err)
}
session.ConversationKey, err = nip44.GenerateConversationKey(event.PubKey, handlerSecret) session.ConversationKey, err = nip44.GenerateConversationKey(event.PubKey, handlerSecret)
if err != nil { if err != nil {
return req, resp, eventResponse, fmt.Errorf("failed to compute shared secret: %w", err) return req, resp, eventResponse, fmt.Errorf("failed to compute shared secret: %w", err)
@@ -150,7 +136,7 @@ func (p *DynamicSigner) HandleRequest(ctx context.Context, event *nostr.Event) (
} }
result = "ack" result = "ack"
case "get_public_key": case "get_public_key":
result = session.PublicKey result = hex.EncodeToString(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'")
@@ -174,21 +160,14 @@ func (p *DynamicSigner) HandleRequest(ctx context.Context, event *nostr.Event) (
} }
jrevt, _ := easyjson.Marshal(evt) jrevt, _ := easyjson.Marshal(evt)
result = string(jrevt) result = string(jrevt)
case "get_relays":
if p.getRelays == nil {
jrelays, _ := json.Marshal(p.getRelays(session.PublicKey))
result = string(jrelays)
} else {
result = "{}"
}
case "nip44_encrypt": case "nip44_encrypt":
if len(req.Params) != 2 { if len(req.Params) != 2 {
resultErr = fmt.Errorf("wrong number of arguments to 'nip04_encrypt'") resultErr = fmt.Errorf("wrong number of arguments to 'nip44_encrypt'")
break break
} }
thirdPartyPubkey := req.Params[0] thirdPartyPubkey, err := nostr.PubKeyFromHex(req.Params[0])
if !nostr.IsValidPublicKey(thirdPartyPubkey) { if err != nil {
resultErr = fmt.Errorf("first argument to 'nip04_encrypt' is not a pubkey string") resultErr = fmt.Errorf("first argument to 'nip44_encrypt' is not a valid pubkey string")
break break
} }
if p.authorizeEncryption != nil && !p.authorizeEncryption(event.PubKey, secret) { if p.authorizeEncryption != nil && !p.authorizeEncryption(event.PubKey, secret) {
@@ -208,9 +187,9 @@ func (p *DynamicSigner) HandleRequest(ctx context.Context, event *nostr.Event) (
resultErr = fmt.Errorf("wrong number of arguments to 'nip04_decrypt'") resultErr = fmt.Errorf("wrong number of arguments to 'nip04_decrypt'")
break break
} }
thirdPartyPubkey := req.Params[0] thirdPartyPubkey, err := nostr.PubKeyFromHex(req.Params[0])
if !nostr.IsValidPublicKey(thirdPartyPubkey) { if err != nil {
resultErr = fmt.Errorf("first argument to 'nip04_decrypt' is not a pubkey string") resultErr = fmt.Errorf("first argument to 'nip04_decrypt' is not a valid pubkey string")
break break
} }
if p.authorizeEncryption != nil && !p.authorizeEncryption(event.PubKey, secret) { if p.authorizeEncryption != nil && !p.authorizeEncryption(event.PubKey, secret) {

View File

@@ -5,8 +5,8 @@ import (
"net/url" "net/url"
"strings" "strings"
"fiatjaf.com/nostr"
jsoniter "github.com/json-iterator/go" jsoniter "github.com/json-iterator/go"
"fiatjaf.com/nostrlib"
) )
var json = jsoniter.ConfigFastest var json = jsoniter.ConfigFastest
@@ -34,7 +34,7 @@ func (r Response) String() string {
} }
type Signer interface { type Signer interface {
GetSession(clientPubkey string) (Session, bool) GetSession(client nostr.PubKey) (Session, bool)
HandleRequest(context.Context, *nostr.Event) (req Request, resp Response, eventResponse nostr.Event, err error) HandleRequest(context.Context, *nostr.Event) (req Request, resp Response, eventResponse nostr.Event, err error)
} }
@@ -46,7 +46,7 @@ func IsValidBunkerURL(input string) bool {
if p.Scheme != "bunker" { if p.Scheme != "bunker" {
return false return false
} }
if !nostr.IsValidPublicKey(p.Host) { if _, err := nostr.PubKeyFromHex(p.Host); err != nil {
return false return false
} }
if !strings.Contains(p.RawQuery, "relay=") { if !strings.Contains(p.RawQuery, "relay=") {

View File

@@ -2,57 +2,45 @@ package nip46
import ( import (
"context" "context"
"encoding/hex"
"fmt" "fmt"
"slices"
"sync" "sync"
"fiatjaf.com/nostr"
"fiatjaf.com/nostr/nip04"
"fiatjaf.com/nostr/nip44"
"github.com/mailru/easyjson" "github.com/mailru/easyjson"
"fiatjaf.com/nostrlib"
"fiatjaf.com/nostrlib/nip04"
"fiatjaf.com/nostrlib/nip44"
) )
var _ Signer = (*StaticKeySigner)(nil) var _ Signer = (*StaticKeySigner)(nil)
type StaticKeySigner struct { type StaticKeySigner struct {
secretKey string secretKey [32]byte
sessions map[nostr.PubKey]Session
sessionKeys []string
sessions []Session
sync.Mutex sync.Mutex
RelaysToAdvertise map[string]RelayReadWrite AuthorizeRequest func(harmless bool, from nostr.PubKey, secret string) bool
AuthorizeRequest func(harmless bool, from string, secret string) bool
} }
func NewStaticKeySigner(secretKey string) StaticKeySigner { func NewStaticKeySigner(secretKey [32]byte) StaticKeySigner {
return StaticKeySigner{ return StaticKeySigner{
secretKey: secretKey, secretKey: secretKey,
RelaysToAdvertise: make(map[string]RelayReadWrite),
} }
} }
func (p *StaticKeySigner) GetSession(clientPubkey string) (Session, bool) { func (p *StaticKeySigner) GetSession(clientPubkey nostr.PubKey) (Session, bool) {
idx, exists := slices.BinarySearch(p.sessionKeys, clientPubkey) session, ok := p.sessions[clientPubkey]
if exists { return session, ok
return p.sessions[idx], true
}
return Session{}, false
} }
func (p *StaticKeySigner) getOrCreateSession(clientPubkey string) (Session, error) { func (p *StaticKeySigner) getOrCreateSession(clientPubkey nostr.PubKey) (Session, error) {
p.Lock() p.Lock()
defer p.Unlock() defer p.Unlock()
idx, exists := slices.BinarySearch(p.sessionKeys, clientPubkey) session, exists := p.sessions[clientPubkey]
if exists { if exists {
return p.sessions[idx], nil return session, nil
}
shared, err := nip04.ComputeSharedSecret(clientPubkey, p.secretKey)
if err != nil {
return Session{}, fmt.Errorf("failed to compute shared secret: %w", err)
} }
ck, err := nip44.GenerateConversationKey(clientPubkey, p.secretKey) ck, err := nip44.GenerateConversationKey(clientPubkey, p.secretKey)
@@ -60,24 +48,14 @@ func (p *StaticKeySigner) getOrCreateSession(clientPubkey string) (Session, erro
return Session{}, fmt.Errorf("failed to compute shared secret: %w", err) return Session{}, fmt.Errorf("failed to compute shared secret: %w", err)
} }
pubkey, err := nostr.GetPublicKey(p.secretKey) pubkey := nostr.GetPublicKey(p.secretKey)
if err != nil { session = Session{
return Session{}, fmt.Errorf("failed to derive public key: %w", err)
}
session := Session{
PublicKey: pubkey, PublicKey: pubkey,
SharedKey: shared,
ConversationKey: ck, ConversationKey: ck,
} }
// add to pool // add to pool
p.sessionKeys = append(p.sessionKeys, "") // bogus append just to increase the capacity p.sessions[pubkey] = session
p.sessions = append(p.sessions, Session{})
copy(p.sessionKeys[idx+1:], p.sessionKeys[idx:])
copy(p.sessions[idx+1:], p.sessions[idx:])
p.sessionKeys[idx] = clientPubkey
p.sessions[idx] = session
return session, nil return session, nil
} }
@@ -116,7 +94,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 = session.PublicKey result = hex.EncodeToString(session.PublicKey[:])
harmless = true harmless = true
case "sign_event": case "sign_event":
if len(req.Params) != 1 { if len(req.Params) != 1 {
@@ -136,18 +114,14 @@ func (p *StaticKeySigner) HandleRequest(_ context.Context, event *nostr.Event) (
} }
jrevt, _ := easyjson.Marshal(evt) jrevt, _ := easyjson.Marshal(evt)
result = string(jrevt) result = string(jrevt)
case "get_relays":
jrelays, _ := json.Marshal(p.RelaysToAdvertise)
result = string(jrelays)
harmless = true
case "nip44_encrypt": case "nip44_encrypt":
if len(req.Params) != 2 { if len(req.Params) != 2 {
resultErr = fmt.Errorf("wrong number of arguments to 'nip04_encrypt'") resultErr = fmt.Errorf("wrong number of arguments to 'nip44_encrypt'")
break break
} }
thirdPartyPubkey := req.Params[0] thirdPartyPubkey, err := nostr.PubKeyFromHex(req.Params[0])
if !nostr.IsValidPublicKey(thirdPartyPubkey) { if err != nil {
resultErr = fmt.Errorf("first argument to 'nip04_encrypt' is not a pubkey string") resultErr = fmt.Errorf("first argument to 'nip04_encrypt' is not a valid pubkey string")
break break
} }
plaintext := req.Params[1] plaintext := req.Params[1]
@@ -168,9 +142,9 @@ func (p *StaticKeySigner) HandleRequest(_ context.Context, event *nostr.Event) (
resultErr = fmt.Errorf("wrong number of arguments to 'nip04_decrypt'") resultErr = fmt.Errorf("wrong number of arguments to 'nip04_decrypt'")
break break
} }
thirdPartyPubkey := req.Params[0] thirdPartyPubkey, err := nostr.PubKeyFromHex(req.Params[0])
if !nostr.IsValidPublicKey(thirdPartyPubkey) { if err != nil {
resultErr = fmt.Errorf("first argument to 'nip04_decrypt' is not a pubkey string") resultErr = fmt.Errorf("first argument to 'nip04_decrypt' is not a valid pubkey string")
break break
} }
ciphertext := req.Params[1] ciphertext := req.Params[1]
@@ -191,9 +165,9 @@ func (p *StaticKeySigner) HandleRequest(_ context.Context, event *nostr.Event) (
resultErr = fmt.Errorf("wrong number of arguments to 'nip04_encrypt'") resultErr = fmt.Errorf("wrong number of arguments to 'nip04_encrypt'")
break break
} }
thirdPartyPubkey := req.Params[0] thirdPartyPubkey, err := nostr.PubKeyFromHex(req.Params[0])
if !nostr.IsValidPublicKey(thirdPartyPubkey) { if err != nil {
resultErr = fmt.Errorf("first argument to 'nip04_encrypt' is not a pubkey string") resultErr = fmt.Errorf("first argument to 'nip04_encrypt' is not a valid pubkey string")
break break
} }
plaintext := req.Params[1] plaintext := req.Params[1]
@@ -214,9 +188,9 @@ func (p *StaticKeySigner) HandleRequest(_ context.Context, event *nostr.Event) (
resultErr = fmt.Errorf("wrong number of arguments to 'nip04_decrypt'") resultErr = fmt.Errorf("wrong number of arguments to 'nip04_decrypt'")
break break
} }
thirdPartyPubkey := req.Params[0] thirdPartyPubkey, err := nostr.PubKeyFromHex(req.Params[0])
if !nostr.IsValidPublicKey(thirdPartyPubkey) { if err != nil {
resultErr = fmt.Errorf("first argument to 'nip04_decrypt' is not a pubkey string") resultErr = fmt.Errorf("first argument to 'nip04_decrypt' is not a valid pubkey string")
break break
} }
ciphertext := req.Params[1] ciphertext := req.Params[1]

View File

@@ -4,22 +4,23 @@ import (
"context" "context"
"fmt" "fmt"
"fiatjaf.com/nostrlib/nip05" "fiatjaf.com/nostr"
"fiatjaf.com/nostr/nip05"
) )
func queryWellKnownNostrJson(ctx context.Context, fullname string) (pubkey string, relays []string, err error) { func queryWellKnownNostrJson(ctx context.Context, fullname string) (pubkey nostr.PubKey, relays []string, err error) {
result, name, err := nip05.Fetch(ctx, fullname) result, name, err := nip05.Fetch(ctx, fullname)
if err != nil { if err != nil {
return "", nil, err return nostr.ZeroPK, nil, err
} }
pubkey, ok := result.Names[name] pubkeyh, ok := result.Names[name]
if !ok { if !ok {
return "", nil, fmt.Errorf("no entry found for the '%s' name", name) return nostr.ZeroPK, nil, fmt.Errorf("no entry found for the '%s' name", name)
} }
relays, _ = result.NIP46[pubkey] relays, _ = result.NIP46[pubkeyh]
if !ok { if !ok {
return "", nil, fmt.Errorf("no bunker relays found for the '%s' name", name) return nostr.ZeroPK, nil, fmt.Errorf("no bunker relays found for the '%s' name", name)
} }
return pubkey, relays, nil return pubkey, relays, nil

View File

@@ -4,7 +4,7 @@ import (
"strconv" "strconv"
"time" "time"
"fiatjaf.com/nostrlib" "fiatjaf.com/nostr"
) )
type CalendarEventKind int type CalendarEventKind int

View File

@@ -4,7 +4,7 @@ import (
"strconv" "strconv"
"time" "time"
"fiatjaf.com/nostrlib" "fiatjaf.com/nostr"
) )
type LiveEvent struct { type LiveEvent struct {

View File

@@ -5,8 +5,8 @@ import (
"math/rand" "math/rand"
"github.com/mailru/easyjson" "github.com/mailru/easyjson"
"fiatjaf.com/nostrlib" "fiatjaf.com/nostr"
"fiatjaf.com/nostrlib/nip44" "fiatjaf.com/nostr/nip44"
) )
// GiftWrap takes a 'rumor', encrypts it with our own key, making a 'seal', then encrypts that with a nonce key and // GiftWrap takes a 'rumor', encrypts it with our own key, making a 'seal', then encrypts that with a nonce key and

View File

@@ -6,7 +6,7 @@ import (
"fmt" "fmt"
"strconv" "strconv"
"fiatjaf.com/nostrlib" "fiatjaf.com/nostr"
) )
type HistoryEntry struct { type HistoryEntry struct {

View File

@@ -9,7 +9,7 @@ import (
"github.com/elnosh/gonuts/cashu/nuts/nut02" "github.com/elnosh/gonuts/cashu/nuts/nut02"
"github.com/elnosh/gonuts/cashu/nuts/nut04" "github.com/elnosh/gonuts/cashu/nuts/nut04"
"github.com/elnosh/gonuts/cashu/nuts/nut05" "github.com/elnosh/gonuts/cashu/nuts/nut05"
"fiatjaf.com/nostrlib/nip60/client" "fiatjaf.com/nostr/nip60/client"
) )
type lightningSwapStatus int type lightningSwapStatus int

View File

@@ -7,8 +7,8 @@ import (
"github.com/elnosh/gonuts/cashu" "github.com/elnosh/gonuts/cashu"
"github.com/elnosh/gonuts/cashu/nuts/nut05" "github.com/elnosh/gonuts/cashu/nuts/nut05"
"fiatjaf.com/nostrlib" "fiatjaf.com/nostr"
"fiatjaf.com/nostrlib/nip60/client" "fiatjaf.com/nostr/nip60/client"
) )
func (w *Wallet) PayBolt11(ctx context.Context, invoice string, opts ...SendOption) (string, error) { func (w *Wallet) PayBolt11(ctx context.Context, invoice string, opts ...SendOption) (string, error) {

View File

@@ -7,8 +7,8 @@ import (
"github.com/elnosh/gonuts/cashu" "github.com/elnosh/gonuts/cashu"
"github.com/elnosh/gonuts/cashu/nuts/nut10" "github.com/elnosh/gonuts/cashu/nuts/nut10"
"fiatjaf.com/nostrlib" "fiatjaf.com/nostr"
"fiatjaf.com/nostrlib/nip60/client" "fiatjaf.com/nostr/nip60/client"
) )
type receiveSettings struct { type receiveSettings struct {

View File

@@ -6,7 +6,7 @@ import (
"github.com/elnosh/gonuts/cashu" "github.com/elnosh/gonuts/cashu"
"github.com/elnosh/gonuts/cashu/nuts/nut04" "github.com/elnosh/gonuts/cashu/nuts/nut04"
"fiatjaf.com/nostrlib/nip60/client" "fiatjaf.com/nostr/nip60/client"
) )
func (w *Wallet) SendExternal( func (w *Wallet) SendExternal(

View File

@@ -11,8 +11,8 @@ import (
"github.com/elnosh/gonuts/cashu/nuts/nut02" "github.com/elnosh/gonuts/cashu/nuts/nut02"
"github.com/elnosh/gonuts/cashu/nuts/nut10" "github.com/elnosh/gonuts/cashu/nuts/nut10"
"github.com/elnosh/gonuts/cashu/nuts/nut11" "github.com/elnosh/gonuts/cashu/nuts/nut11"
"fiatjaf.com/nostrlib" "fiatjaf.com/nostr"
"fiatjaf.com/nostrlib/nip60/client" "fiatjaf.com/nostr/nip60/client"
) )
type SendOption func(opts *sendSettings) type SendOption func(opts *sendSettings)

View File

@@ -10,7 +10,7 @@ import (
"github.com/elnosh/gonuts/cashu/nuts/nut02" "github.com/elnosh/gonuts/cashu/nuts/nut02"
"github.com/elnosh/gonuts/cashu/nuts/nut03" "github.com/elnosh/gonuts/cashu/nuts/nut03"
"github.com/elnosh/gonuts/cashu/nuts/nut10" "github.com/elnosh/gonuts/cashu/nuts/nut10"
"fiatjaf.com/nostrlib/nip60/client" "fiatjaf.com/nostr/nip60/client"
) )
type swapSettings struct { type swapSettings struct {

View File

@@ -6,7 +6,7 @@ import (
"fmt" "fmt"
"github.com/elnosh/gonuts/cashu" "github.com/elnosh/gonuts/cashu"
"fiatjaf.com/nostrlib" "fiatjaf.com/nostr"
) )
type Token struct { type Token struct {

View File

@@ -11,7 +11,7 @@ import (
"github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2"
"github.com/decred/dcrd/dcrec/secp256k1/v4" "github.com/decred/dcrd/dcrec/secp256k1/v4"
"fiatjaf.com/nostrlib" "fiatjaf.com/nostr"
) )
type Wallet struct { type Wallet struct {

View File

@@ -10,8 +10,8 @@ import (
"github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2"
"github.com/elnosh/gonuts/cashu" "github.com/elnosh/gonuts/cashu"
"fiatjaf.com/nostrlib" "fiatjaf.com/nostr"
"fiatjaf.com/nostrlib/keyer" "fiatjaf.com/nostr/keyer"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"golang.org/x/exp/rand" "golang.org/x/exp/rand"
) )

View File

@@ -5,7 +5,7 @@ import (
"slices" "slices"
"github.com/elnosh/gonuts/cashu" "github.com/elnosh/gonuts/cashu"
"fiatjaf.com/nostrlib" "fiatjaf.com/nostr"
) )
type Info struct { type Info struct {

View File

@@ -9,8 +9,8 @@ import (
"slices" "slices"
"strings" "strings"
"fiatjaf.com/nostrlib" "fiatjaf.com/nostr"
"fiatjaf.com/nostrlib/nip60" "fiatjaf.com/nostr/nip60"
) )
var NutzapsNotAccepted = errors.New("user doesn't accept nutzaps") var NutzapsNotAccepted = errors.New("user doesn't accept nutzaps")

View File

@@ -4,11 +4,11 @@ import (
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"fiatjaf.com/nostr"
"github.com/btcsuite/btcd/btcec/v2" "github.com/btcsuite/btcd/btcec/v2"
"github.com/decred/dcrd/dcrec/secp256k1/v4" "github.com/decred/dcrd/dcrec/secp256k1/v4"
"github.com/elnosh/gonuts/cashu" "github.com/elnosh/gonuts/cashu"
"github.com/elnosh/gonuts/crypto" "github.com/elnosh/gonuts/crypto"
"github.com/nbd-wtf/go-nostr"
) )
func VerifyNutzap( func VerifyNutzap(

View File

@@ -3,7 +3,7 @@ package nip70
import ( import (
"strings" "strings"
"github.com/nbd-wtf/go-nostr" "fiatjaf.com/nostr"
) )
func IsProtected(event nostr.Event) bool { func IsProtected(event nostr.Event) bool {

View File

@@ -8,7 +8,7 @@ import (
"github.com/mailru/easyjson" "github.com/mailru/easyjson"
jwriter "github.com/mailru/easyjson/jwriter" jwriter "github.com/mailru/easyjson/jwriter"
"fiatjaf.com/nostrlib" "fiatjaf.com/nostr"
"github.com/tidwall/gjson" "github.com/tidwall/gjson"
) )

View File

@@ -7,8 +7,8 @@ import (
"github.com/fiatjaf/eventstore" "github.com/fiatjaf/eventstore"
"github.com/fiatjaf/eventstore/slicestore" "github.com/fiatjaf/eventstore/slicestore"
"fiatjaf.com/nostrlib" "fiatjaf.com/nostr"
"fiatjaf.com/nostrlib/nip77" "fiatjaf.com/nostr/nip77"
) )
func main() { func main() {

View File

@@ -1,41 +0,0 @@
package nip77
import (
"sync"
)
type idlistpool struct {
initialsize int
pool [][]string
sync.Mutex
}
func newidlistpool(initialsize int) *idlistpool {
ilp := idlistpool{
initialsize: initialsize,
pool: make([][]string, 1, 2),
}
ilp.pool[0] = make([]string, 0, initialsize)
return &ilp
}
func (ilp *idlistpool) grab() []string {
ilp.Lock()
defer ilp.Unlock()
l := len(ilp.pool)
if l > 0 {
idlist := ilp.pool[l-1]
ilp.pool = ilp.pool[0 : l-1]
return idlist
}
idlist := make([]string, 0, ilp.initialsize)
return idlist
}
func (ilp *idlistpool) giveback(idlist []string) {
idlist = idlist[:0]
ilp.pool = append(ilp.pool, idlist)
}

View File

@@ -4,16 +4,16 @@ import (
"context" "context"
"fmt" "fmt"
"github.com/nbd-wtf/go-nostr" "fiatjaf.com/nostr"
"github.com/nbd-wtf/go-nostr/nip77/negentropy" "fiatjaf.com/nostr/nip77/negentropy"
"github.com/nbd-wtf/go-nostr/nip77/negentropy/storage/empty" "fiatjaf.com/nostr/nip77/negentropy/storage/empty"
) )
func FetchIDsOnly( func FetchIDsOnly(
ctx context.Context, ctx context.Context,
url string, url string,
filter nostr.Filter, filter nostr.Filter,
) (<-chan string, error) { ) (<-chan nostr.ID, error) {
id := "go-nostr-tmp" // for now we can't have more than one subscription in the same connection id := "go-nostr-tmp" // for now we can't have more than one subscription in the same connection
neg := negentropy.New(empty.Empty{}, 1024*1024) neg := negentropy.New(empty.Empty{}, 1024*1024)
@@ -56,7 +56,7 @@ func FetchIDsOnly(
return nil, fmt.Errorf("failed to write to relay: %w", err) return nil, fmt.Errorf("failed to write to relay: %w", err)
} }
ch := make(chan string) ch := make(chan nostr.ID)
go func() { go func() {
for id := range neg.HaveNots { for id := range neg.HaveNots {
ch <- id ch <- id

View File

@@ -3,7 +3,7 @@ package negentropy
import ( import (
"fmt" "fmt"
"fiatjaf.com/nostrlib" "fiatjaf.com/nostr"
) )
func (n *Negentropy) readTimestamp(reader *StringHexReader) (nostr.Timestamp, error) { func (n *Negentropy) readTimestamp(reader *StringHexReader) (nostr.Timestamp, error) {
@@ -42,12 +42,12 @@ func (n *Negentropy) readBound(reader *StringHexReader) (Bound, error) {
return Bound{}, fmt.Errorf("failed to decode bound length: %w", err) return Bound{}, fmt.Errorf("failed to decode bound length: %w", err)
} }
id, err := reader.ReadString(length * 2) pfb := make([]byte, length)
if err != nil { if err := reader.ReadHexBytes(pfb); err != nil {
return Bound{}, fmt.Errorf("failed to read bound id: %w", err) return Bound{}, fmt.Errorf("failed to read bound id: %w", err)
} }
return Bound{Item{timestamp, id}}, nil return Bound{timestamp, pfb}, nil
} }
func (n *Negentropy) writeTimestamp(w *StringHexWriter, timestamp nostr.Timestamp) { func (n *Negentropy) writeTimestamp(w *StringHexWriter, timestamp nostr.Timestamp) {
@@ -71,26 +71,25 @@ func (n *Negentropy) writeTimestamp(w *StringHexWriter, timestamp nostr.Timestam
func (n *Negentropy) writeBound(w *StringHexWriter, bound Bound) { func (n *Negentropy) writeBound(w *StringHexWriter, bound Bound) {
n.writeTimestamp(w, bound.Timestamp) n.writeTimestamp(w, bound.Timestamp)
writeVarInt(w, len(bound.ID)/2) writeVarInt(w, len(bound.IDPrefix))
w.WriteHex(bound.Item.ID) w.WriteBytes(bound.IDPrefix)
} }
func getMinimalBound(prev, curr Item) Bound { func getMinimalBound(prev, curr Item) Bound {
if curr.Timestamp != prev.Timestamp { if curr.Timestamp != prev.Timestamp {
return Bound{Item{curr.Timestamp, ""}} return Bound{curr.Timestamp, nil}
} }
sharedPrefixBytes := 0 sharedPrefixBytes := 0
for i := 0; i < 31; i++ {
for i := 0; i < 32; i += 2 { if curr.ID[i] != prev.ID[i] {
if curr.ID[i:i+2] != prev.ID[i:i+2] {
break break
} }
sharedPrefixBytes++ sharedPrefixBytes++
} }
// sharedPrefixBytes + 1 to include the first differing byte, or the entire ID if identical. // sharedPrefixBytes + 1 to include the first differing byte, or the entire ID if identical.
return Bound{Item{curr.Timestamp, curr.ID[:(sharedPrefixBytes+1)*2]}} return Bound{curr.Timestamp, curr.ID[:(sharedPrefixBytes + 1)]}
} }
func readVarInt(reader *StringHexReader) (int, error) { func readVarInt(reader *StringHexReader) (int, error) {

View File

@@ -9,9 +9,9 @@ import (
"sync" "sync"
"testing" "testing"
"fiatjaf.com/nostrlib" "fiatjaf.com/nostr"
"fiatjaf.com/nostrlib/nip77/negentropy" "fiatjaf.com/nostr/nip77/negentropy"
"fiatjaf.com/nostrlib/nip77/negentropy/storage/vector" "fiatjaf.com/nostr/nip77/negentropy/storage/vector"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )

View File

@@ -1,12 +1,13 @@
package negentropy package negentropy
import ( import (
"encoding/hex"
"fmt" "fmt"
"math" "math"
"strings" "strings"
"unsafe" "unsafe"
"fiatjaf.com/nostrlib" "fiatjaf.com/nostr"
) )
const ( const (
@@ -15,7 +16,7 @@ const (
buckets = 16 buckets = 16
) )
var InfiniteBound = Bound{Item: Item{Timestamp: maxTimestamp}} var InfiniteBound = Bound{Timestamp: maxTimestamp}
type Negentropy struct { type Negentropy struct {
storage Storage storage Storage
@@ -25,8 +26,8 @@ type Negentropy struct {
lastTimestampIn nostr.Timestamp lastTimestampIn nostr.Timestamp
lastTimestampOut nostr.Timestamp lastTimestampOut nostr.Timestamp
Haves chan string Haves chan nostr.ID
HaveNots chan string HaveNots chan nostr.ID
} }
func New(storage Storage, frameSizeLimit int) *Negentropy { func New(storage Storage, frameSizeLimit int) *Negentropy {
@@ -39,8 +40,8 @@ func New(storage Storage, frameSizeLimit int) *Negentropy {
return &Negentropy{ return &Negentropy{
storage: storage, storage: storage,
frameSizeLimit: frameSizeLimit, frameSizeLimit: frameSizeLimit,
Haves: make(chan string, buckets*4), Haves: make(chan nostr.ID, buckets*4),
HaveNots: make(chan string, buckets*4), HaveNots: make(chan nostr.ID, buckets*4),
} }
} }
@@ -158,9 +159,10 @@ func (n *Negentropy) reconcileAux(reader *StringHexReader) (string, error) {
} }
// what they have // what they have
theirItems := make(map[string]struct{}, numIds) theirItems := make(map[nostr.ID]struct{}, numIds)
for i := 0; i < numIds; i++ { for i := 0; i < numIds; i++ {
if id, err := reader.ReadString(64); err != nil { var id [32]byte
if err := reader.ReadHexBytes(id[:]); err != nil {
return "", fmt.Errorf("failed to read id (#%d/%d) in list: %w", i, numIds, err) return "", fmt.Errorf("failed to read id (#%d/%d) in list: %w", i, numIds, err)
} else { } else {
theirItems[id] = struct{}{} theirItems[id] = struct{}{}
@@ -203,11 +205,11 @@ func (n *Negentropy) reconcileAux(reader *StringHexReader) (string, error) {
for index, item := range n.storage.Range(lower, upper) { for index, item := range n.storage.Range(lower, upper) {
if n.frameSizeLimit-200 < fullOutput.Len()/2+responseIds.Len()/2 { if n.frameSizeLimit-200 < fullOutput.Len()/2+responseIds.Len()/2 {
endBound = Bound{item} endBound = Bound{item.Timestamp, item.ID[:]}
upper = index upper = index
break break
} }
responseIds.WriteString(item.ID) responseIds.WriteString(hex.EncodeToString(item.ID[:]))
responses++ responses++
} }
@@ -254,7 +256,7 @@ func (n *Negentropy) SplitRange(lower, upper int, upperBound Bound, output *Stri
writeVarInt(output, numElems) writeVarInt(output, numElems)
for _, item := range n.storage.Range(lower, upper) { for _, item := range n.storage.Range(lower, upper) {
output.WriteHex(item.ID) output.WriteBytes(item.ID[:])
} }
} else { } else {
itemsPerBucket := numElems / buckets itemsPerBucket := numElems / buckets

View File

@@ -5,7 +5,7 @@ import (
"encoding/binary" "encoding/binary"
"encoding/hex" "encoding/hex"
"fiatjaf.com/nostrlib/nip77/negentropy" "fiatjaf.com/nostr/nip77/negentropy"
) )
type Accumulator struct { type Accumulator struct {

View File

@@ -3,8 +3,8 @@ package empty
import ( import (
"iter" "iter"
"github.com/nbd-wtf/go-nostr/nip77/negentropy" "fiatjaf.com/nostr/nip77/negentropy"
"github.com/nbd-wtf/go-nostr/nip77/negentropy/storage" "fiatjaf.com/nostr/nip77/negentropy/storage"
) )
var acc storage.Accumulator var acc storage.Accumulator

View File

@@ -1,7 +1,6 @@
package vector package vector
import ( import (
"encoding/hex"
"fmt" "fmt"
"iter" "iter"
"slices" "slices"
@@ -24,7 +23,7 @@ func New() *Vector {
} }
} }
func (v *Vector) Insert(createdAt nostr.Timestamp, id string) { func (v *Vector) Insert(createdAt nostr.Timestamp, id nostr.ID) {
if len(id) != 64 { if len(id) != 64 {
panic(fmt.Errorf("bad id size for added item: expected %d bytes, got %d", 32, len(id)/2)) panic(fmt.Errorf("bad id size for added item: expected %d bytes, got %d", 32, len(id)/2))
} }
@@ -68,10 +67,8 @@ func (v *Vector) FindLowerBound(begin, end int, bound negentropy.Bound) int {
func (v *Vector) Fingerprint(begin, end int) string { func (v *Vector) Fingerprint(begin, end int) string {
v.acc.Reset() v.acc.Reset()
tmp := make([]byte, 32)
for _, item := range v.Range(begin, end) { for _, item := range v.Range(begin, end) {
hex.Decode(tmp, []byte(item.ID)) v.acc.AddBytes(item.ID[:])
v.acc.AddBytes(tmp)
} }
return v.acc.GetFingerprint(end - begin) return v.acc.GetFingerprint(end - begin)

View File

@@ -1,11 +1,11 @@
package negentropy package negentropy
import ( import (
"bytes"
"cmp" "cmp"
"fmt" "fmt"
"strings"
"fiatjaf.com/nostrlib" "fiatjaf.com/nostr"
) )
const FingerprintSize = 16 const FingerprintSize = 16
@@ -33,23 +33,26 @@ func (v Mode) String() string {
type Item struct { type Item struct {
Timestamp nostr.Timestamp Timestamp nostr.Timestamp
ID string ID nostr.ID
} }
func ItemCompare(a, b Item) int { func ItemCompare(a, b Item) int {
if a.Timestamp == b.Timestamp { if a.Timestamp == b.Timestamp {
return strings.Compare(a.ID, b.ID) return bytes.Compare(a.ID[:], b.ID[:])
} }
return cmp.Compare(a.Timestamp, b.Timestamp) return cmp.Compare(a.Timestamp, b.Timestamp)
} }
func (i Item) String() string { return fmt.Sprintf("Item<%d:%s>", i.Timestamp, i.ID) } func (i Item) String() string { return fmt.Sprintf("Item<%d:%x>", i.Timestamp, i.ID[:]) }
type Bound struct{ Item } type Bound struct {
Timestamp nostr.Timestamp
IDPrefix []byte
}
func (b Bound) String() string { func (b Bound) String() string {
if b.Timestamp == InfiniteBound.Timestamp { if b.Timestamp == InfiniteBound.Timestamp {
return "Bound<infinite>" return "Bound<infinite>"
} }
return fmt.Sprintf("Bound<%d:%s>", b.Timestamp, b.ID) return fmt.Sprintf("Bound<%d:%x>", b.Timestamp, b.IDPrefix)
} }

View File

@@ -6,9 +6,9 @@ import (
"sync" "sync"
"testing" "testing"
"fiatjaf.com/nostrlib" "fiatjaf.com/nostr"
"fiatjaf.com/nostrlib/nip77/negentropy" "fiatjaf.com/nostr/nip77/negentropy"
"fiatjaf.com/nostrlib/nip77/negentropy/storage/vector" "fiatjaf.com/nostr/nip77/negentropy/storage/vector"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )

View File

@@ -5,14 +5,14 @@ import (
"fmt" "fmt"
"sync" "sync"
"fiatjaf.com/nostrlib" "fiatjaf.com/nostr"
"fiatjaf.com/nostrlib/nip77/negentropy" "fiatjaf.com/nostr/nip77/negentropy"
"fiatjaf.com/nostrlib/nip77/negentropy/storage/vector" "fiatjaf.com/nostr/nip77/negentropy/storage/vector"
) )
type direction struct { type direction struct {
label string label string
items chan string items chan nostr.ID
source nostr.RelayStore source nostr.RelayStore
target nostr.RelayStore target nostr.RelayStore
} }
@@ -91,7 +91,9 @@ func NegentropySync(
}() }()
wg := sync.WaitGroup{} wg := sync.WaitGroup{}
pool := newidlistpool(50) pool := sync.Pool{
New: func() any { return make([]nostr.ID, 0, 50) },
}
// Define sync directions // Define sync directions
directions := [][]direction{ directions := [][]direction{
@@ -105,11 +107,11 @@ func NegentropySync(
go func(dir direction) { go func(dir direction) {
defer wg.Done() defer wg.Done()
seen := make(map[string]struct{}) seen := make(map[nostr.ID]struct{})
doSync := func(ids []string) { doSync := func(ids []nostr.ID) {
defer wg.Done() defer wg.Done()
defer pool.giveback(ids) defer pool.Put(ids)
if len(ids) == 0 { if len(ids) == 0 {
return return
@@ -124,7 +126,7 @@ func NegentropySync(
} }
} }
ids := pool.grab() ids := pool.Get().([]nostr.ID)
for item := range dir.items { for item := range dir.items {
if _, ok := seen[item]; ok { if _, ok := seen[item]; ok {
continue continue
@@ -135,7 +137,7 @@ func NegentropySync(
if len(ids) == 50 { if len(ids) == 50 {
wg.Add(1) wg.Add(1)
go doSync(ids) go doSync(ids)
ids = pool.grab() ids = pool.Get().([]nostr.ID)
} }
} }
wg.Add(1) wg.Add(1)

View File

@@ -5,7 +5,7 @@ import (
"math" "math"
"net" "net"
"fiatjaf.com/nostrlib" "fiatjaf.com/nostr"
) )
func DecodeRequest(req Request) (MethodParams, error) { func DecodeRequest(req Request) (MethodParams, error) {

View File

@@ -4,7 +4,7 @@ import (
"strconv" "strconv"
"strings" "strings"
"fiatjaf.com/nostrlib" "fiatjaf.com/nostr"
) )
type IMeta []IMetaEntry type IMeta []IMetaEntry

View File

@@ -3,7 +3,7 @@ package nip92
import ( import (
"testing" "testing"
"fiatjaf.com/nostrlib" "fiatjaf.com/nostr"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )

View File

@@ -3,7 +3,7 @@ package nip94
import ( import (
"strings" "strings"
"fiatjaf.com/nostrlib" "fiatjaf.com/nostr"
) )
func ParseFileMetadata(event nostr.Event) FileMetadata { func ParseFileMetadata(event nostr.Event) FileMetadata {

View File

@@ -14,7 +14,7 @@ import (
"strconv" "strconv"
jsoniter "github.com/json-iterator/go" jsoniter "github.com/json-iterator/go"
"fiatjaf.com/nostrlib" "fiatjaf.com/nostr"
) )
// Upload uploads a file to the provided req.Host. // Upload uploads a file to the provided req.Host.

View File

@@ -7,7 +7,7 @@ import (
"os" "os"
"testing" "testing"
"fiatjaf.com/nostrlib" "fiatjaf.com/nostr"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )

View File

@@ -5,7 +5,7 @@ import (
"io" "io"
"net/http" "net/http"
"fiatjaf.com/nostrlib" "fiatjaf.com/nostr"
) )
// UploadRequest is a NIP96 upload request. // UploadRequest is a NIP96 upload request.

View File

@@ -4,7 +4,7 @@ import (
"context" "context"
"fmt" "fmt"
"github.com/nbd-wtf/go-nostr" "fiatjaf.com/nostr"
) )
// Check checks if a file exists on the media server by its hash // Check checks if a file exists on the media server by its hash

View File

@@ -4,7 +4,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/nbd-wtf/go-nostr" "fiatjaf.com/nostr"
"github.com/valyala/fasthttp" "github.com/valyala/fasthttp"
) )

View File

@@ -4,7 +4,7 @@ import (
"context" "context"
"fmt" "fmt"
"github.com/nbd-wtf/go-nostr" "fiatjaf.com/nostr"
) )
// Delete deletes a file from the media server by its hash // Delete deletes a file from the media server by its hash

View File

@@ -7,7 +7,7 @@ import (
"net/http" "net/http"
"os" "os"
"github.com/nbd-wtf/go-nostr" "fiatjaf.com/nostr"
) )
// Download downloads a file from the media server by its hash // Download downloads a file from the media server by its hash

View File

@@ -8,7 +8,7 @@ import (
"io" "io"
"strconv" "strconv"
"github.com/nbd-wtf/go-nostr" "fiatjaf.com/nostr"
"github.com/valyala/fasthttp" "github.com/valyala/fasthttp"
) )

View File

@@ -2,9 +2,10 @@ package blossom
import ( import (
"context" "context"
"encoding/hex"
"fmt" "fmt"
"github.com/nbd-wtf/go-nostr" "fiatjaf.com/nostr"
) )
// List retrieves a list of blobs from a specific pubkey // List retrieves a list of blobs from a specific pubkey
@@ -15,7 +16,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/"+pubkey, "", func() string { err = c.httpCall(ctx, "GET", "list/"+hex.EncodeToString(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,7 @@ package blossom
import ( import (
"encoding/json" "encoding/json"
"github.com/nbd-wtf/go-nostr" "fiatjaf.com/nostr"
) )
// BlobDescriptor represents metadata about a blob stored on a media server // BlobDescriptor represents metadata about a blob stored on a media server

View File

@@ -10,7 +10,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"github.com/nbd-wtf/go-nostr" "fiatjaf.com/nostr"
) )
// UploadFile uploads a file to the media server // UploadFile uploads a file to the media server

View File

@@ -1,6 +1,6 @@
package hints package hints
import "fiatjaf.com/nostrlib" import "fiatjaf.com/nostr"
const END_OF_WORLD nostr.Timestamp = 2208999600 // 2040-01-01 const END_OF_WORLD nostr.Timestamp = 2208999600 // 2040-01-01

View File

@@ -2,15 +2,14 @@ package lmdbh
import ( import (
"bytes" "bytes"
"encoding/hex"
"fmt" "fmt"
"math" "math"
"os" "os"
"slices" "slices"
"fiatjaf.com/nostr"
"fiatjaf.com/nostr/sdk/hints"
"github.com/PowerDNS/lmdb-go/lmdb" "github.com/PowerDNS/lmdb-go/lmdb"
"github.com/nbd-wtf/go-nostr"
"github.com/nbd-wtf/go-nostr/sdk/hints"
) )
var _ hints.HintsDB = (*LMDBHints)(nil) var _ hints.HintsDB = (*LMDBHints)(nil)
@@ -63,7 +62,7 @@ func (lh *LMDBHints) Close() {
lh.env.Close() lh.env.Close()
} }
func (lh *LMDBHints) Save(pubkey string, relay string, hintkey hints.HintKey, ts nostr.Timestamp) { func (lh *LMDBHints) Save(pubkey nostr.PubKey, relay string, hintkey hints.HintKey, ts nostr.Timestamp) {
if now := nostr.Now(); ts > now { if now := nostr.Now(); ts > now {
ts = now ts = now
} }
@@ -91,7 +90,7 @@ func (lh *LMDBHints) Save(pubkey string, relay string, hintkey hints.HintKey, ts
} }
} }
func (lh *LMDBHints) TopN(pubkey string, n int) []string { func (lh *LMDBHints) TopN(pubkey nostr.PubKey, n int) []string {
type relayScore struct { type relayScore struct {
relay string relay string
score int64 score int64
@@ -107,11 +106,10 @@ func (lh *LMDBHints) TopN(pubkey string, n int) []string {
} }
defer cursor.Close() defer cursor.Close()
prefix, _ := hex.DecodeString(pubkey) k, v, err := cursor.Get(pubkey[:], nil, lmdb.SetRange)
k, v, err := cursor.Get(prefix, nil, lmdb.SetRange)
for ; err == nil; k, v, err = cursor.Get(nil, nil, lmdb.Next) { for ; err == nil; k, v, err = cursor.Get(nil, nil, lmdb.Next) {
// check if we're still in the prefix range // check if we're still in the prefix range
if len(k) < 32 || !bytes.Equal(k[:32], prefix) { if len(k) < 32 || !bytes.Equal(k[:32], pubkey[:]) {
break break
} }
@@ -182,7 +180,7 @@ func (lh *LMDBHints) PrintScores() {
} }
} }
func (lh *LMDBHints) GetDetailedScores(pubkey string, n int) []hints.RelayScores { func (lh *LMDBHints) GetDetailedScores(pubkey nostr.PubKey, n int) []hints.RelayScores {
type relayScore struct { type relayScore struct {
relay string relay string
tss timestamps tss timestamps
@@ -199,11 +197,10 @@ func (lh *LMDBHints) GetDetailedScores(pubkey string, n int) []hints.RelayScores
} }
defer cursor.Close() defer cursor.Close()
prefix, _ := hex.DecodeString(pubkey) k, v, err := cursor.Get(pubkey[:], nil, lmdb.SetRange)
k, v, err := cursor.Get(prefix, nil, lmdb.SetRange)
for ; err == nil; k, v, err = cursor.Get(nil, nil, lmdb.Next) { for ; err == nil; k, v, err = cursor.Get(nil, nil, lmdb.Next) {
// check if we're still in the prefix range // check if we're still in the prefix range
if len(k) < 32 || !bytes.Equal(k[:32], prefix) { if len(k) < 32 || !bytes.Equal(k[:32], pubkey[:]) {
break break
} }

View File

@@ -4,12 +4,12 @@ import (
"encoding/binary" "encoding/binary"
"encoding/hex" "encoding/hex"
"github.com/nbd-wtf/go-nostr" "fiatjaf.com/nostr"
) )
func encodeKey(pubhintkey, relay string) []byte { func encodeKey(pubhintkey nostr.PubKey, relay string) []byte {
k := make([]byte, 32+len(relay)) k := make([]byte, 32+len(relay))
hex.Decode(k[0:32], []byte(pubhintkey)) copy(k[0:32], pubhintkey[:])
copy(k[32:], relay) copy(k[32:], relay)
return k return k
} }

View File

@@ -6,15 +6,15 @@ import (
"slices" "slices"
"sync" "sync"
"fiatjaf.com/nostrlib" "fiatjaf.com/nostr"
"fiatjaf.com/nostrlib/sdk/hints" "fiatjaf.com/nostr/sdk/hints"
) )
var _ hints.HintsDB = (*HintDB)(nil) var _ hints.HintsDB = (*HintDB)(nil)
type HintDB struct { type HintDB struct {
RelayBySerial []string RelayBySerial []string
OrderedRelaysByPubKey map[string][]RelayEntry OrderedRelaysByPubKey map[nostr.PubKey][]RelayEntry
sync.Mutex sync.Mutex
} }
@@ -22,11 +22,11 @@ type HintDB struct {
func NewHintDB() *HintDB { func NewHintDB() *HintDB {
return &HintDB{ return &HintDB{
RelayBySerial: make([]string, 0, 100), RelayBySerial: make([]string, 0, 100),
OrderedRelaysByPubKey: make(map[string][]RelayEntry, 100), OrderedRelaysByPubKey: make(map[nostr.PubKey][]RelayEntry, 100),
} }
} }
func (db *HintDB) Save(pubkey string, relay string, key hints.HintKey, ts nostr.Timestamp) { func (db *HintDB) Save(pubkey nostr.PubKey, relay string, key hints.HintKey, ts nostr.Timestamp) {
if now := nostr.Now(); ts > now { if now := nostr.Now(); ts > now {
ts = now ts = now
} }
@@ -67,7 +67,7 @@ func (db *HintDB) Save(pubkey string, relay string, key hints.HintKey, ts nostr.
db.OrderedRelaysByPubKey[pubkey] = entries db.OrderedRelaysByPubKey[pubkey] = entries
} }
func (db *HintDB) TopN(pubkey string, n int) []string { func (db *HintDB) TopN(pubkey nostr.PubKey, n int) []string {
db.Lock() db.Lock()
defer db.Unlock() defer db.Unlock()
@@ -104,7 +104,7 @@ func (db *HintDB) PrintScores() {
} }
} }
func (db *HintDB) GetDetailedScores(pubkey string, n int) []hints.RelayScores { func (db *HintDB) GetDetailedScores(pubkey nostr.PubKey, n int) []hints.RelayScores {
db.Lock() db.Lock()
defer db.Unlock() defer db.Unlock()

View File

@@ -1,272 +0,0 @@
package sqlh
import (
"database/sql"
"fmt"
"strconv"
"strings"
"github.com/jmoiron/sqlx"
"fiatjaf.com/nostrlib"
"fiatjaf.com/nostrlib/sdk/hints"
)
type SQLHints struct {
*sqlx.DB
interop interop
saves [7]*sqlx.Stmt
topN *sqlx.Stmt
}
// NewSQLHints takes an sqlx.DB connection (db) and a database type name (driverName ).
// driverName must be either "postgres" or "sqlite3" -- this is so we can slightly change the queries.
func NewSQLHints(db *sql.DB, driverName string) (SQLHints, error) {
sh := SQLHints{DB: sqlx.NewDb(db, driverName)}
switch driverName {
case "sqlite3":
sh.interop = sqliteInterop
case "postgres":
sh.interop = postgresInterop
default:
return sh, fmt.Errorf("unknown database driver '%s'", driverName)
}
// db migrations
if txn, err := sh.Beginx(); err != nil {
return SQLHints{}, err
} else {
if _, err := txn.Exec(`CREATE TABLE IF NOT EXISTS nostr_sdk_db_version (version int)`); err != nil {
txn.Rollback()
return SQLHints{}, err
}
var version int
if err := txn.Get(&version, `SELECT version FROM nostr_sdk_db_version`); err != nil && err != sql.ErrNoRows {
txn.Rollback()
return SQLHints{}, err
}
if version == 0 {
if _, err := txn.Exec(`INSERT INTO nostr_sdk_db_version VALUES (0)`); err != nil {
txn.Rollback()
return SQLHints{}, err
}
version = 1
if _, err := txn.Exec(
`CREATE TABLE IF NOT EXISTS nostr_sdk_pubkey_relays (` +
`pubkey text, ` +
`relay text, ` +
`last_fetch_attempt integer, ` +
`most_recent_event_fetched integer, ` +
`last_in_relay_list integer, ` +
`last_in_tag integer, ` +
`last_in_nprofile integer, ` +
`last_in_nevent integer, ` +
`last_in_nip05 integer ` +
`)`,
); err != nil {
txn.Rollback()
return SQLHints{}, err
}
if _, err := txn.Exec(
`CREATE UNIQUE INDEX IF NOT EXISTS pkr ON nostr_sdk_pubkey_relays (pubkey, relay)`,
); err != nil {
txn.Rollback()
return SQLHints{}, err
}
if _, err := txn.Exec(
`CREATE INDEX IF NOT EXISTS bypk ON nostr_sdk_pubkey_relays (pubkey)`,
); err != nil {
txn.Rollback()
return SQLHints{}, err
}
}
if version == 1 {
version = 2
if _, err := txn.Exec(
`ALTER TABLE nostr_sdk_pubkey_relays DROP COLUMN last_in_tag`,
); err != nil {
txn.Rollback()
return SQLHints{}, err
}
if _, err := txn.Exec(
`ALTER TABLE nostr_sdk_pubkey_relays DROP COLUMN last_in_nprofile`,
); err != nil {
txn.Rollback()
return SQLHints{}, err
}
if _, err := txn.Exec(
`ALTER TABLE nostr_sdk_pubkey_relays DROP COLUMN last_in_nevent`,
); err != nil {
txn.Rollback()
return SQLHints{}, err
}
if _, err := txn.Exec(
`ALTER TABLE nostr_sdk_pubkey_relays DROP COLUMN last_in_nip05`,
); err != nil {
txn.Rollback()
return SQLHints{}, err
}
if _, err := txn.Exec(
`ALTER TABLE nostr_sdk_pubkey_relays ADD COLUMN last_in_hint integer`,
); err != nil {
txn.Rollback()
return SQLHints{}, err
}
}
if _, err := txn.Exec(
fmt.Sprintf(`UPDATE nostr_sdk_db_version SET version = %d`, version),
); err != nil {
txn.Rollback()
return SQLHints{}, err
}
if err := txn.Commit(); err != nil {
txn.Rollback()
return SQLHints{}, err
}
}
// prepare statements
for i := range hints.KeyBasePoints {
col := hints.HintKey(i).String()
stmt, err := sh.Preparex(
`INSERT INTO nostr_sdk_pubkey_relays (pubkey, relay, ` + col + `) VALUES (` + sh.interop.generateBindingSpots(0, 3) + `)
ON CONFLICT (pubkey, relay) DO UPDATE SET ` + col + ` = ` + sh.interop.maxFunc + `(` + sh.interop.generateBindingSpots(3, 1) + `, coalesce(excluded.` + col + `, 0))`,
)
if err != nil {
fmt.Println(
`INSERT INTO nostr_sdk_pubkey_relays (pubkey, relay, ` + col + `) VALUES (` + sh.interop.generateBindingSpots(0, 3) + `)
ON CONFLICT (pubkey, relay) DO UPDATE SET ` + col + ` = ` + sh.interop.maxFunc + `(` + sh.interop.generateBindingSpots(3, 1) + `, coalesce(excluded.` + col + `, 0))`,
)
return sh, fmt.Errorf("failed to prepare statement for %s: %w", col, err)
}
sh.saves[i] = stmt
}
{
stmt, err := sh.Preparex(
`SELECT relay FROM nostr_sdk_pubkey_relays WHERE pubkey = ` + sh.interop.generateBindingSpots(0, 1) + ` ORDER BY (` + sh.scorePartialQuery() + `) DESC LIMIT ` + sh.interop.generateBindingSpots(1, 1),
)
if err != nil {
return sh, fmt.Errorf("failed to prepare statement for querying: %w", err)
}
sh.topN = stmt
}
return sh, nil
}
func (sh SQLHints) TopN(pubkey string, n int) []string {
res := make([]string, 0, n)
err := sh.topN.Select(&res, pubkey, n)
if err != nil && err != sql.ErrNoRows {
nostr.InfoLogger.Printf("[sdk/hints/sql] unexpected error on query for %s: %s\n",
pubkey, err)
}
return res
}
func (sh SQLHints) Save(pubkey string, relay string, key hints.HintKey, ts nostr.Timestamp) {
if now := nostr.Now(); ts > now {
ts = now
}
_, err := sh.saves[key].Exec(pubkey, relay, ts, ts)
if err != nil {
nostr.InfoLogger.Printf("[sdk/hints/sql] unexpected error on insert for %s, %s, %d: %s\n",
pubkey, relay, ts, err)
}
}
func (sh SQLHints) PrintScores() {
fmt.Println("= print scores")
allpubkeys := make([]string, 0, 50)
if err := sh.Select(&allpubkeys, `SELECT DISTINCT pubkey FROM nostr_sdk_pubkey_relays`); err != nil {
panic(err)
}
allrelays := make([]struct {
PubKey string `db:"pubkey"`
Relay string `db:"relay"`
Score float64 `db:"score"`
}, 0, 20)
for _, pubkey := range allpubkeys {
fmt.Println("== relay scores for", pubkey)
if err := sh.Select(&allrelays,
`SELECT pubkey, relay, coalesce(`+sh.scorePartialQuery()+`, 0) AS score
FROM nostr_sdk_pubkey_relays WHERE pubkey = `+sh.interop.generateBindingSpots(0, 1)+` ORDER BY score DESC`, pubkey); err != nil {
panic(err)
}
for i, re := range allrelays {
fmt.Printf(" %3d :: %30s ::> %12d\n", i, re.Relay, int(re.Score))
}
}
}
func (sh SQLHints) GetDetailedScores(pubkey string, n int) []hints.RelayScores {
result := make([]hints.RelayScores, 0, n)
rows, err := sh.Queryx(
`SELECT relay, last_fetch_attempt, most_recent_event_fetched, last_in_relay_list, last_in_hint,
coalesce(`+sh.scorePartialQuery()+`, 0) AS score
FROM nostr_sdk_pubkey_relays
WHERE pubkey = `+sh.interop.generateBindingSpots(0, 1)+`
ORDER BY score DESC
LIMIT `+sh.interop.generateBindingSpots(1, 1),
pubkey, n)
if err != nil {
return nil
}
defer rows.Close()
for rows.Next() {
var rs hints.RelayScores
var scores [4]sql.NullInt64
err := rows.Scan(&rs.Relay, &scores[0], &scores[1], &scores[2], &scores[3], &rs.Sum)
if err != nil {
continue
}
for i, s := range scores {
if s.Valid {
rs.Scores[i] = nostr.Timestamp(s.Int64)
}
}
result = append(result, rs)
}
return result
}
func (sh SQLHints) scorePartialQuery() string {
calc := strings.Builder{}
calc.Grow(len(hints.KeyBasePoints) * (11 + 25 + 32 + 4 + 4 + 9 + 12 + 25 + 12 + 25 + 19 + 3))
for i, points := range hints.KeyBasePoints {
col := hints.HintKey(i).String()
multiplier := strconv.FormatInt(points, 10)
calc.WriteString(`(CASE WHEN `)
calc.WriteString(col)
calc.WriteString(` IS NOT NULL THEN 10000000000 * `)
calc.WriteString(multiplier)
calc.WriteString(` / power(`)
calc.WriteString(sh.interop.maxFunc)
calc.WriteString(`(1, (`)
calc.WriteString(sh.interop.getUnixEpochFunc)
calc.WriteString(` + 86400) - `)
calc.WriteString(col)
calc.WriteString(`), 1.3) ELSE 0 END)`)
if i != len(hints.KeyBasePoints)-1 {
calc.WriteString(` + `)
}
}
return calc.String()
}

View File

@@ -1,48 +0,0 @@
package sqlh
import (
"strconv"
"strings"
)
type interop struct {
maxFunc string
getUnixEpochFunc string
generateBindingSpots func(start, n int) string
}
var sqliteInterop = interop{
maxFunc: "max",
getUnixEpochFunc: "unixepoch()",
generateBindingSpots: func(_, n int) string {
b := strings.Builder{}
b.Grow(n * 2)
for i := range n {
if i == n-1 {
b.WriteString("?")
} else {
b.WriteString("?,")
}
}
return b.String()
},
}
var postgresInterop = interop{
maxFunc: "greatest",
getUnixEpochFunc: "extract(epoch from now())",
generateBindingSpots: func(start, n int) string {
b := strings.Builder{}
b.Grow(n * 2)
end := start + n
for i := start; i < end; i++ {
v := i + 1
b.WriteRune('$')
b.WriteString(strconv.Itoa(v))
if i != end-1 {
b.WriteRune(',')
}
}
return b.String()
},
}

View File

@@ -4,7 +4,7 @@ import (
"os" "os"
"testing" "testing"
"github.com/nbd-wtf/go-nostr/sdk/hints/badgerh" "fiatjaf.com/nostr/sdk/hints/badgerh"
) )
func TestBadgerHints(t *testing.T) { func TestBadgerHints(t *testing.T) {

View File

@@ -7,7 +7,7 @@ import (
"os" "os"
"testing" "testing"
"fiatjaf.com/nostrlib/sdk/hints/sqlh" "fiatjaf.com/nostr/sdk/hints/sqlh"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
_ "github.com/tursodatabase/go-libsql" _ "github.com/tursodatabase/go-libsql"
) )

View File

@@ -4,7 +4,7 @@ import (
"os" "os"
"testing" "testing"
"github.com/nbd-wtf/go-nostr/sdk/hints/lmdbh" "fiatjaf.com/nostr/sdk/hints/lmdbh"
) )
func TestLMDBHints(t *testing.T) { func TestLMDBHints(t *testing.T) {

View File

@@ -8,7 +8,7 @@ import (
"testing" "testing"
_ "github.com/mattn/go-sqlite3" _ "github.com/mattn/go-sqlite3"
"fiatjaf.com/nostrlib/sdk/hints/sqlh" "fiatjaf.com/nostr/sdk/hints/sqlh"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )

View File

@@ -3,7 +3,7 @@ package test
import ( import (
"testing" "testing"
"fiatjaf.com/nostrlib/sdk/hints/memoryh" "fiatjaf.com/nostr/sdk/hints/memoryh"
) )
func TestMemoryHints(t *testing.T) { func TestMemoryHints(t *testing.T) {

Some files were not shown because too many files have changed in this diff Show More