more conversions.
This commit is contained in:
@@ -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"},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
6
keyer.go
6
keyer.go
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
10
keyer/lib.go
10
keyer/lib.go
@@ -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"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/nbd-wtf/go-nostr"
|
"fiatjaf.com/nostr"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
"runtime"
|
"runtime"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
nostr "fiatjaf.com/nostrlib"
|
nostr "fiatjaf.com/nostr"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
@@ -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"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
131
nip19/nip19.go
131
nip19/nip19.go
@@ -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)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(
|
||||||
|
nostr.MustPubKeyFromHex("3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d"),
|
||||||
|
[]string{
|
||||||
"wss://r.x.com",
|
"wss://r.x.com",
|
||||||
"wss://djbas.sadkb.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)
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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":
|
||||||
|
|||||||
@@ -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},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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)
|
|
||||||
}
|
|
||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"fiatjaf.com/nostrlib"
|
"fiatjaf.com/nostr"
|
||||||
)
|
)
|
||||||
|
|
||||||
type GroupAddress struct {
|
type GroupAddress struct {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package nip29
|
|||||||
import (
|
import (
|
||||||
"slices"
|
"slices"
|
||||||
|
|
||||||
"fiatjaf.com/nostrlib"
|
"fiatjaf.com/nostr"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Role struct {
|
type Role struct {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"fiatjaf.com/nostrlib"
|
"fiatjaf.com/nostr"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Repository struct {
|
type Repository struct {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package nip34
|
|||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"fiatjaf.com/nostrlib"
|
"fiatjaf.com/nostr"
|
||||||
)
|
)
|
||||||
|
|
||||||
type RepositoryState struct {
|
type RepositoryState struct {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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] {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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{
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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=") {
|
||||||
|
|||||||
@@ -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]
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"fiatjaf.com/nostrlib"
|
"fiatjaf.com/nostr"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CalendarEventKind int
|
type CalendarEventKind int
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"fiatjaf.com/nostrlib"
|
"fiatjaf.com/nostr"
|
||||||
)
|
)
|
||||||
|
|
||||||
type LiveEvent struct {
|
type LiveEvent struct {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"fiatjaf.com/nostrlib"
|
"fiatjaf.com/nostr"
|
||||||
)
|
)
|
||||||
|
|
||||||
type HistoryEntry struct {
|
type HistoryEntry struct {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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)
|
|
||||||
}
|
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"fiatjaf.com/nostrlib"
|
"fiatjaf.com/nostr"
|
||||||
)
|
)
|
||||||
|
|
||||||
type IMeta []IMetaEntry
|
type IMeta []IMetaEntry
|
||||||
|
|||||||
@@ -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"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -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"})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|
||||||
|
|||||||
@@ -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()
|
|
||||||
}
|
|
||||||
@@ -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()
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -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
Reference in New Issue
Block a user