more conversions.
This commit is contained in:
@@ -13,7 +13,7 @@ import (
|
||||
var defaultConnectionOptions = &ws.DialOptions{
|
||||
CompressionMode: ws.CompressionContextTakeover,
|
||||
HTTPHeader: http.Header{
|
||||
textproto.CanonicalMIMEHeaderKey("User-Agent"): {"fiatjaf.com/nostrlib"},
|
||||
textproto.CanonicalMIMEHeaderKey("User-Agent"): {"fiatjaf.com/nostr"},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -9,8 +9,8 @@ import (
|
||||
"time"
|
||||
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"fiatjaf.com/nostrlib"
|
||||
"fiatjaf.com/nostrlib/nip19"
|
||||
"fiatjaf.com/nostr"
|
||||
"fiatjaf.com/nostr/nip19"
|
||||
)
|
||||
|
||||
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).
|
||||
type User interface {
|
||||
// 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.
|
||||
@@ -35,9 +35,9 @@ type Signer interface {
|
||||
type Cipher interface {
|
||||
// Encrypt encrypts a plaintext message for a recipient.
|
||||
// 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.
|
||||
// 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"
|
||||
"time"
|
||||
|
||||
"fiatjaf.com/nostrlib"
|
||||
"fiatjaf.com/nostrlib/nip46"
|
||||
"fiatjaf.com/nostr"
|
||||
"fiatjaf.com/nostr/nip46"
|
||||
)
|
||||
|
||||
var _ nostr.Keyer = (*BunkerSigner)(nil)
|
||||
|
||||
@@ -4,9 +4,9 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"fiatjaf.com/nostrlib"
|
||||
"fiatjaf.com/nostrlib/nip44"
|
||||
"fiatjaf.com/nostrlib/nip49"
|
||||
"fiatjaf.com/nostr"
|
||||
"fiatjaf.com/nostr/nip44"
|
||||
"fiatjaf.com/nostr/nip49"
|
||||
)
|
||||
|
||||
var _ nostr.Keyer = (*EncryptedKeySigner)(nil)
|
||||
|
||||
10
keyer/lib.go
10
keyer/lib.go
@@ -7,11 +7,11 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"fiatjaf.com/nostrlib"
|
||||
"fiatjaf.com/nostrlib/nip05"
|
||||
"fiatjaf.com/nostrlib/nip19"
|
||||
"fiatjaf.com/nostrlib/nip46"
|
||||
"fiatjaf.com/nostrlib/nip49"
|
||||
"fiatjaf.com/nostr"
|
||||
"fiatjaf.com/nostr/nip05"
|
||||
"fiatjaf.com/nostr/nip19"
|
||||
"fiatjaf.com/nostr/nip46"
|
||||
"fiatjaf.com/nostr/nip49"
|
||||
"github.com/puzpuzpuz/xsync/v3"
|
||||
)
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ package keyer
|
||||
import (
|
||||
"context"
|
||||
|
||||
"fiatjaf.com/nostrlib"
|
||||
"fiatjaf.com/nostr"
|
||||
)
|
||||
|
||||
var _ nostr.Keyer = (*ManualSigner)(nil)
|
||||
|
||||
@@ -3,8 +3,8 @@ package keyer
|
||||
import (
|
||||
"context"
|
||||
|
||||
"fiatjaf.com/nostrlib"
|
||||
"fiatjaf.com/nostrlib/nip44"
|
||||
"fiatjaf.com/nostr"
|
||||
"fiatjaf.com/nostr/nip44"
|
||||
"github.com/puzpuzpuz/xsync/v3"
|
||||
)
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"fiatjaf.com/nostr"
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
@@ -10,27 +10,20 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"fiatjaf.com/nostr"
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
)
|
||||
|
||||
// ComputeSharedSecret returns a shared secret key used to encrypt messages.
|
||||
// The private and public keys should be hex encoded.
|
||||
// Uses the Diffie-Hellman key exchange (ECDH) (RFC 4753).
|
||||
func ComputeSharedSecret(pub string, sk string) (sharedSecret []byte, err error) {
|
||||
privKeyBytes, err := hex.DecodeString(sk)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error decoding sender private key: %w", err)
|
||||
}
|
||||
privKey, _ := btcec.PrivKeyFromBytes(privKeyBytes)
|
||||
func ComputeSharedSecret(pub nostr.PubKey, sk [32]byte) (sharedSecret []byte, err error) {
|
||||
privKey, _ := btcec.PrivKeyFromBytes(sk[:])
|
||||
|
||||
// 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 {
|
||||
return nil, fmt.Errorf("error decoding hex string of receiver public key '%s': %w", "02"+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 nil, fmt.Errorf("error parsing receiver public key '%s': %w", "02"+hex.EncodeToString(pub[:]), err)
|
||||
}
|
||||
|
||||
return btcec.GenerateSharedSecret(privKey, pubKey), nil
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
package nip04
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"fiatjaf.com/nostrlib"
|
||||
"fiatjaf.com/nostr"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@@ -13,10 +14,8 @@ func TestSharedKeysAreTheSame(t *testing.T) {
|
||||
sk1 := nostr.GeneratePrivateKey()
|
||||
sk2 := nostr.GeneratePrivateKey()
|
||||
|
||||
pk1, err := nostr.GetPublicKey(sk1)
|
||||
require.NoError(t, err)
|
||||
pk2, err := nostr.GetPublicKey(sk2)
|
||||
require.NoError(t, err)
|
||||
pk1 := nostr.GetPublicKey(sk1)
|
||||
pk2 := nostr.GetPublicKey(sk2)
|
||||
|
||||
ss1, err := ComputeSharedSecret(pk2, sk1)
|
||||
require.NoError(t, err)
|
||||
@@ -57,10 +56,10 @@ func TestEncryptionAndDecryptionWithMultipleLengths(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestNostrToolsCompatibility(t *testing.T) {
|
||||
sk1 := "92996316beebf94171065a714cbf164d1f56d7ad9b35b329d9fc97535bf25352"
|
||||
sk2 := "591c0c249adfb9346f8d37dfeed65725e2eea1d7a6e99fa503342f367138de84"
|
||||
pk2, _ := nostr.GetPublicKey(sk2)
|
||||
shared, _ := ComputeSharedSecret(pk2, sk1)
|
||||
sk1, _ := hex.DecodeString("92996316beebf94171065a714cbf164d1f56d7ad9b35b329d9fc97535bf25352")
|
||||
sk2, _ := hex.DecodeString("591c0c249adfb9346f8d37dfeed65725e2eea1d7a6e99fa503342f367138de84")
|
||||
pk2 := nostr.GetPublicKey([32]byte(sk2))
|
||||
shared, _ := ComputeSharedSecret(pk2, [32]byte(sk1))
|
||||
ciphertext := "A+fRnU4aXS4kbTLfowqAww==?iv=QFYUrl5or/n/qamY79ze0A=="
|
||||
plaintext, _ := Decrypt(ciphertext, shared)
|
||||
require.Equal(t, "hello", plaintext, "invalid decryption of nostr-tools payload")
|
||||
|
||||
@@ -7,8 +7,8 @@ import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"fiatjaf.com/nostr"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"fiatjaf.com/nostrlib"
|
||||
)
|
||||
|
||||
var NIP05_REGEX = regexp.MustCompile(`^(?:([\w.+-]+)@)?([\w_-]+(\.[\w_-]+)+)$`)
|
||||
@@ -40,16 +40,17 @@ func QueryIdentifier(ctx context.Context, fullname string) (*nostr.ProfilePointe
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pubkey, ok := result.Names[name]
|
||||
pubkeyh, ok := result.Names[name]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("no entry for name '%s'", name)
|
||||
}
|
||||
|
||||
if !nostr.IsValidPublicKey(pubkey) {
|
||||
return nil, fmt.Errorf("got an invalid public key '%s'", pubkey)
|
||||
pubkey, err := nostr.PubKeyFromHex(pubkeyh)
|
||||
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{
|
||||
PublicKey: pubkey,
|
||||
Relays: relays,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package nip10
|
||||
|
||||
import "fiatjaf.com/nostrlib"
|
||||
import "fiatjaf.com/nostr"
|
||||
|
||||
func GetThreadRoot(tags nostr.Tags) *nostr.EventPointer {
|
||||
for _, tag := range tags {
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"time"
|
||||
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"fiatjaf.com/nostrlib"
|
||||
"fiatjaf.com/nostr"
|
||||
)
|
||||
|
||||
// Fetch fetches the NIP-11 metadata for a relay.
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"runtime"
|
||||
"strconv"
|
||||
|
||||
nostr "fiatjaf.com/nostrlib"
|
||||
nostr "fiatjaf.com/nostr"
|
||||
)
|
||||
|
||||
var (
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
nostr "fiatjaf.com/nostrlib"
|
||||
nostr "fiatjaf.com/nostr"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package nip14
|
||||
|
||||
import "fiatjaf.com/nostrlib"
|
||||
import "fiatjaf.com/nostr"
|
||||
|
||||
func GetSubject(tags nostr.Tags) string {
|
||||
for _, tag := range tags {
|
||||
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"fiatjaf.com/nostrlib"
|
||||
"fiatjaf.com/nostrlib/nip59"
|
||||
"fiatjaf.com/nostr"
|
||||
"fiatjaf.com/nostr/nip59"
|
||||
)
|
||||
|
||||
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 (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
|
||||
"fiatjaf.com/nostr"
|
||||
"github.com/btcsuite/btcd/btcutil/bech32"
|
||||
"fiatjaf.com/nostrlib"
|
||||
)
|
||||
|
||||
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 {
|
||||
case "npub", "nsec", "note":
|
||||
case "nsec":
|
||||
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, hex.EncodeToString(data[0:32]), nil
|
||||
return prefix, [32]byte(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":
|
||||
var result nostr.ProfilePointer
|
||||
curr := 0
|
||||
@@ -35,7 +43,7 @@ func Decode(bech32string string) (prefix string, value any, err error) {
|
||||
t, v := readTLVEntry(data[curr:])
|
||||
if v == nil {
|
||||
// end here
|
||||
if result.PublicKey == "" {
|
||||
if result.PublicKey == nostr.ZeroPK {
|
||||
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 {
|
||||
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:
|
||||
result.Relays = append(result.Relays, string(v))
|
||||
default:
|
||||
@@ -63,7 +71,7 @@ func Decode(bech32string string) (prefix string, value any, err error) {
|
||||
t, v := readTLVEntry(data[curr:])
|
||||
if v == nil {
|
||||
// end here
|
||||
if result.ID == "" {
|
||||
if result.ID == nostr.ZeroID {
|
||||
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 {
|
||||
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:
|
||||
result.Relays = append(result.Relays, string(v))
|
||||
case TLVAuthor:
|
||||
if len(v) != 32 {
|
||||
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:
|
||||
if len(v) != 4 {
|
||||
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:
|
||||
// ignore
|
||||
}
|
||||
@@ -101,7 +109,7 @@ func Decode(bech32string string) (prefix string, value any, err error) {
|
||||
t, v := readTLVEntry(data[curr:])
|
||||
if v == nil {
|
||||
// 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")
|
||||
}
|
||||
|
||||
@@ -117,9 +125,9 @@ func Decode(bech32string string) (prefix string, value any, err error) {
|
||||
if len(v) != 32 {
|
||||
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:
|
||||
result.Kind = int(binary.BigEndian.Uint32(v))
|
||||
result.Kind = uint16(binary.BigEndian.Uint32(v))
|
||||
default:
|
||||
// ignore
|
||||
}
|
||||
@@ -131,13 +139,8 @@ func Decode(bech32string string) (prefix string, value any, err error) {
|
||||
return prefix, data, fmt.Errorf("unknown tag %s", prefix)
|
||||
}
|
||||
|
||||
func EncodePrivateKey(privateKeyHex string) (string, error) {
|
||||
b, err := hex.DecodeString(privateKeyHex)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to decode private key hex: %w", err)
|
||||
}
|
||||
|
||||
bits5, err := bech32.ConvertBits(b, 8, 5, true)
|
||||
func EncodeNsec(sk [32]byte) (string, error) {
|
||||
bits5, err := bech32.ConvertBits(sk[:], 8, 5, true)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@@ -145,79 +148,44 @@ func EncodePrivateKey(privateKeyHex string) (string, error) {
|
||||
return bech32.Encode("nsec", bits5)
|
||||
}
|
||||
|
||||
func EncodePublicKey(publicKeyHex string) (string, error) {
|
||||
b, err := hex.DecodeString(publicKeyHex)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to decode public key hex: %w", err)
|
||||
}
|
||||
|
||||
bits5, err := bech32.ConvertBits(b, 8, 5, true)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return bech32.Encode("npub", bits5)
|
||||
func EncodeNpub(pk nostr.PubKey) string {
|
||||
bits5, _ := bech32.ConvertBits(pk[:], 8, 5, true)
|
||||
npub, _ := bech32.Encode("npub", bits5)
|
||||
return npub
|
||||
}
|
||||
|
||||
func EncodeNote(eventIDHex string) (string, error) {
|
||||
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) {
|
||||
func EncodeNprofile(pk nostr.PubKey, relays []string) string {
|
||||
buf := &bytes.Buffer{}
|
||||
pubkey, err := hex.DecodeString(publicKeyHex)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("invalid pubkey '%s': %w", publicKeyHex, err)
|
||||
}
|
||||
writeTLVEntry(buf, TLVDefault, pubkey)
|
||||
writeTLVEntry(buf, TLVDefault, pk[:])
|
||||
|
||||
for _, url := range relays {
|
||||
writeTLVEntry(buf, TLVRelay, []byte(url))
|
||||
}
|
||||
|
||||
bits5, err := bech32.ConvertBits(buf.Bytes(), 8, 5, true)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to convert bits: %w", err)
|
||||
}
|
||||
bits5, _ := bech32.ConvertBits(buf.Bytes(), 8, 5, true)
|
||||
|
||||
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{}
|
||||
id, err := hex.DecodeString(eventIDHex)
|
||||
if err != nil || len(id) != 32 {
|
||||
return "", fmt.Errorf("invalid id '%s': %w", eventIDHex, err)
|
||||
}
|
||||
writeTLVEntry(buf, TLVDefault, id)
|
||||
writeTLVEntry(buf, TLVDefault, id[:])
|
||||
|
||||
for _, url := range relays {
|
||||
writeTLVEntry(buf, TLVRelay, []byte(url))
|
||||
}
|
||||
|
||||
if pubkey, _ := hex.DecodeString(author); len(pubkey) == 32 {
|
||||
writeTLVEntry(buf, TLVAuthor, pubkey)
|
||||
if author != nostr.ZeroPK {
|
||||
writeTLVEntry(buf, TLVAuthor, author[:])
|
||||
}
|
||||
|
||||
bits5, err := bech32.ConvertBits(buf.Bytes(), 8, 5, true)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to convert bits: %w", err)
|
||||
}
|
||||
|
||||
return bech32.Encode("nevent", bits5)
|
||||
bits5, _ := bech32.ConvertBits(buf.Bytes(), 8, 5, true)
|
||||
nevent, _ := bech32.Encode("nevent", bits5)
|
||||
return nevent
|
||||
}
|
||||
|
||||
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{}
|
||||
|
||||
writeTLVEntry(buf, TLVDefault, []byte(identifier))
|
||||
@@ -226,20 +194,13 @@ func EncodeEntity(publicKey string, kind int, identifier string, relays []string
|
||||
writeTLVEntry(buf, TLVRelay, []byte(url))
|
||||
}
|
||||
|
||||
pubkey, err := hex.DecodeString(publicKey)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("invalid pubkey '%s': %w", pubkey, err)
|
||||
}
|
||||
writeTLVEntry(buf, TLVAuthor, pubkey)
|
||||
writeTLVEntry(buf, TLVAuthor, pk[:])
|
||||
|
||||
kindBytes := make([]byte, 4)
|
||||
binary.BigEndian.PutUint32(kindBytes, uint32(kind))
|
||||
writeTLVEntry(buf, TLVKind, kindBytes)
|
||||
|
||||
bits5, err := bech32.ConvertBits(buf.Bytes(), 8, 5, true)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to convert bits: %w", err)
|
||||
}
|
||||
|
||||
return bech32.Encode("naddr", bits5)
|
||||
bits5, _ := bech32.ConvertBits(buf.Bytes(), 8, 5, true)
|
||||
naddr, _ := bech32.Encode("naddr", bits5)
|
||||
return naddr
|
||||
}
|
||||
|
||||
@@ -1,21 +1,24 @@
|
||||
package nip19
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
|
||||
"fiatjaf.com/nostrlib"
|
||||
"fiatjaf.com/nostr"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestEncodeNpub(t *testing.T) {
|
||||
npub, err := EncodePublicKey("3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d")
|
||||
assert.NoError(t, err)
|
||||
npub := EncodeNpub(nostr.MustPubKeyFromHex("3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d"))
|
||||
assert.Equal(t, "npub180cvv07tjdrrgpa0j7j7tmnyl2yr6yr7l8j4s3evf6u64th6gkwsyjh6w6", npub, "produced an unexpected npub string")
|
||||
}
|
||||
|
||||
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.Equal(t, "nsec180cvv07tjdrrgpa0j7j7tmnyl2yr6yr7l8j4s3evf6u64th6gkwsgyumg0", nsec, "produced an unexpected nsec string")
|
||||
}
|
||||
@@ -63,20 +66,22 @@ func TestDecodeNprofile(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestEncodeNprofile(t *testing.T) {
|
||||
nprofile, err := EncodeProfile("3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d", []string{
|
||||
"wss://r.x.com",
|
||||
"wss://djbas.sadkb.com",
|
||||
})
|
||||
nprofile := EncodeNprofile(
|
||||
nostr.MustPubKeyFromHex("3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d"),
|
||||
[]string{
|
||||
"wss://r.x.com",
|
||||
"wss://djbas.sadkb.com",
|
||||
},
|
||||
)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t,
|
||||
"nprofile1qqsrhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8gpp4mhxue69uhhytnc9e3k7mgpz4mhxue69uhkg6nzv9ejuumpv34kytnrdaksjlyr9p",
|
||||
nprofile, "produced an unexpected nprofile string: %s", nprofile)
|
||||
}
|
||||
|
||||
func TestEncodeDecodeNaddr(t *testing.T) {
|
||||
naddr, err := EncodeEntity(
|
||||
"3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d",
|
||||
naddr := EncodeNaddr(
|
||||
nostr.MustPubKeyFromHex("3bf0c63fcb93463407af97a5e5ee64fa883d107ef9e558472c4eb9aaaefa459d"),
|
||||
nostr.KindArticle,
|
||||
"banana",
|
||||
[]string{
|
||||
@@ -84,7 +89,6 @@ func TestEncodeDecodeNaddr(t *testing.T) {
|
||||
"wss://nostr.banana.com",
|
||||
})
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t,
|
||||
"naddr1qqrxyctwv9hxzqfwwaehxw309aex2mrp0yhxummnw3ezuetcv9khqmr99ekhjer0d4skjm3wv4uxzmtsd3jjucm0d5q3vamnwvaz7tmwdaehgu3wvfskuctwvyhxxmmdqgsrhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8grqsqqqa28a3lkds",
|
||||
naddr, "produced an unexpected naddr string: %s", naddr)
|
||||
@@ -116,14 +120,12 @@ func TestDecodeNaddrWithoutRelays(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestEncodeDecodeNEvent(t *testing.T) {
|
||||
nevent, err := EncodeEvent(
|
||||
"45326f5d6962ab1e3cd424e758c3002b8665f7b0d8dcee9fe9e288d7751ac194",
|
||||
nevent := EncodeNevent(
|
||||
nostr.MustIDFromHex("45326f5d6962ab1e3cd424e758c3002b8665f7b0d8dcee9fe9e288d7751ac194"),
|
||||
[]string{"wss://banana.com"},
|
||||
"7fa56f5d6962ab1e3cd424e758c3002b8665f7b0d8dcee9fe9e288d7751abb88",
|
||||
nostr.MustPubKeyFromHex("7fa56f5d6962ab1e3cd424e758c3002b8665f7b0d8dcee9fe9e288d7751abb88"),
|
||||
)
|
||||
|
||||
assert.NoError(t, err)
|
||||
|
||||
expectedNEvent := "nevent1qqsy2vn0t45k92c78n2zfe6ccvqzhpn977cd3h8wnl579zxhw5dvr9qpzpmhxue69uhkyctwv9hxztnrdaksygrl54h466tz4v0re4pyuavvxqptsejl0vxcmnhfl60z3rth2x4m3q04ndyp"
|
||||
assert.Equal(t, expectedNEvent, nevent)
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ package nip19
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"fiatjaf.com/nostr"
|
||||
)
|
||||
|
||||
func EncodePointer(pointer nostr.Pointer) string {
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
package nip19
|
||||
|
||||
import (
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"fiatjaf.com/nostr"
|
||||
)
|
||||
|
||||
func NeventFromRelayEvent(ie nostr.RelayEvent) string {
|
||||
v, _ := EncodeEvent(ie.ID, []string{ie.Relay.URL}, ie.PubKey)
|
||||
return v
|
||||
return EncodeNevent(ie.ID, []string{ie.Relay.URL}, ie.PubKey)
|
||||
}
|
||||
|
||||
@@ -6,9 +6,9 @@ import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/nbd-wtf/go-nostr/nip19"
|
||||
"github.com/nbd-wtf/go-nostr/nip73"
|
||||
"fiatjaf.com/nostr"
|
||||
"fiatjaf.com/nostr/nip19"
|
||||
"fiatjaf.com/nostr/nip73"
|
||||
)
|
||||
|
||||
type Block struct {
|
||||
@@ -54,7 +54,7 @@ func Parse(content string) iter.Seq[Block] {
|
||||
var pointer nostr.Pointer
|
||||
switch prefix {
|
||||
case "npub":
|
||||
pointer = nostr.ProfilePointer{PublicKey: data.(string)}
|
||||
pointer = nostr.ProfilePointer{PublicKey: data.(nostr.PubKey)}
|
||||
case "nprofile", "nevent", "naddr":
|
||||
pointer = data.(nostr.Pointer)
|
||||
case "note", "nsec":
|
||||
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/nbd-wtf/go-nostr/nip73"
|
||||
"fiatjaf.com/nostr"
|
||||
"fiatjaf.com/nostr/nip73"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@@ -19,9 +19,9 @@ func TestParse(t *testing.T) {
|
||||
"hello, nostr:nprofile1qqsvc6ulagpn7kwrcwdqgp797xl7usumqa6s3kgcelwq6m75x8fe8yc5usxdg wrote nostr:nevent1qqsvc6ulagpn7kwrcwdqgp797xl7usumqa6s3kgcelwq6m75x8fe8ychxp5v4!",
|
||||
[]Block{
|
||||
{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: "nostr:nevent1qqsvc6ulagpn7kwrcwdqgp797xl7usumqa6s3kgcelwq6m75x8fe8ychxp5v4", Start: 90, Pointer: nostr.EventPointer{ID: "cc6b9fea033f59c3c39a0407c5f1bfee439b077508d918cfdc0d6fd431d39393"}},
|
||||
{Text: "nostr:nevent1qqsvc6ulagpn7kwrcwdqgp797xl7usumqa6s3kgcelwq6m75x8fe8ychxp5v4", Start: 90, Pointer: nostr.EventPointer{ID: nostr.MustIDFromHex("cc6b9fea033f59c3c39a0407c5f1bfee439b077508d918cfdc0d6fd431d39393")}},
|
||||
{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"
|
||||
"strings"
|
||||
|
||||
"fiatjaf.com/nostrlib"
|
||||
"fiatjaf.com/nostr"
|
||||
)
|
||||
|
||||
type GroupAddress struct {
|
||||
|
||||
@@ -3,7 +3,7 @@ package nip29
|
||||
import (
|
||||
"slices"
|
||||
|
||||
"fiatjaf.com/nostrlib"
|
||||
"fiatjaf.com/nostr"
|
||||
)
|
||||
|
||||
type Role struct {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package nip31
|
||||
|
||||
import "fiatjaf.com/nostrlib"
|
||||
import "fiatjaf.com/nostr"
|
||||
|
||||
func GetAlt(event nostr.Event) string {
|
||||
for _, tag := range event.Tags {
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/bluekeyes/go-gitdiff/gitdiff"
|
||||
"fiatjaf.com/nostrlib"
|
||||
"fiatjaf.com/nostr"
|
||||
)
|
||||
|
||||
type Patch struct {
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"fiatjaf.com/nostrlib"
|
||||
"fiatjaf.com/nostr"
|
||||
)
|
||||
|
||||
type Repository struct {
|
||||
|
||||
@@ -3,7 +3,7 @@ package nip34
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"fiatjaf.com/nostrlib"
|
||||
"fiatjaf.com/nostr"
|
||||
)
|
||||
|
||||
type RepositoryState struct {
|
||||
|
||||
@@ -3,7 +3,7 @@ package nip40
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"fiatjaf.com/nostrlib"
|
||||
"fiatjaf.com/nostr"
|
||||
)
|
||||
|
||||
// GetExpiration returns the expiration timestamp for this event, or -1 if no "expiration" tag exists or
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"fiatjaf.com/nostrlib"
|
||||
"fiatjaf.com/nostr"
|
||||
)
|
||||
|
||||
// CreateUnsignedAuthEvent creates an event which should be sent via an "AUTH" command.
|
||||
|
||||
@@ -12,6 +12,7 @@ import (
|
||||
"io"
|
||||
"math"
|
||||
|
||||
"fiatjaf.com/nostr"
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||
"golang.org/x/crypto/chacha20"
|
||||
@@ -153,10 +154,12 @@ func Decrypt(b64ciphertextWrapped string, conversationKey [32]byte) (string, err
|
||||
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
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -220,20 +223,13 @@ func calcPadding(sLen int) int {
|
||||
}
|
||||
|
||||
// code adapted from nip04.ComputeSharedSecret()
|
||||
func computeSharedSecret(pub string, sk string) (sharedSecret [32]byte, err error) {
|
||||
privKeyBytes, err := hex.DecodeString(sk)
|
||||
if err != nil {
|
||||
return sharedSecret, fmt.Errorf("error decoding sender private key: %w", err)
|
||||
}
|
||||
privKey, _ := btcec.PrivKeyFromBytes(privKeyBytes)
|
||||
func computeSharedSecret(pub nostr.PubKey, sk [32]byte) (sharedSecret [32]byte, err error) {
|
||||
privKey, _ := btcec.PrivKeyFromBytes(sk[:])
|
||||
|
||||
pubKeyBytes, err := hex.DecodeString("02" + pub)
|
||||
pubKey, err := btcec.ParsePubKey(append([]byte{2}, pub[:]...))
|
||||
if err != nil {
|
||||
return sharedSecret, fmt.Errorf("error decoding hex string of receiver public key '%s': %w", "02"+pub, err)
|
||||
}
|
||||
pubKey, err := btcec.ParsePubKey(pubKeyBytes)
|
||||
if err != nil {
|
||||
return sharedSecret, fmt.Errorf("error parsing receiver public key '%s': %w", "02"+pub, err)
|
||||
return sharedSecret, fmt.Errorf("error parsing receiver public key '%s': %w",
|
||||
"02"+hex.EncodeToString(pub[:]), err)
|
||||
}
|
||||
|
||||
var point, result secp256k1.JacobianPoint
|
||||
|
||||
@@ -8,14 +8,22 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"fiatjaf.com/nostrlib"
|
||||
"fiatjaf.com/nostr"
|
||||
"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)
|
||||
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)
|
||||
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) {
|
||||
_, 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)
|
||||
}
|
||||
|
||||
@@ -47,18 +61,18 @@ func assertConversationKeyGenerationPub(t *testing.T, priv string, pub string, c
|
||||
expectedConversationKey, err := hexDecode32Array(conversationKey)
|
||||
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.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 {
|
||||
convKey, err := hexDecode32Array(conversationKey)
|
||||
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) {
|
||||
sk1 := nostr.GeneratePrivateKey()
|
||||
sk2 := nostr.GeneratePrivateKey()
|
||||
pub2, _ := nostr.GetPublicKey(sk2)
|
||||
pub2 := nostr.GetPublicKey(sk2)
|
||||
salt := make([]byte, 32)
|
||||
rand.Read(salt)
|
||||
conversationKey, _ := GenerateConversationKey(pub2, sk1)
|
||||
@@ -1072,8 +1086,8 @@ func TestMaxLength(t *testing.T) {
|
||||
}
|
||||
|
||||
assertCryptPub(t,
|
||||
sk1,
|
||||
pub2,
|
||||
hex.EncodeToString(sk1[:]),
|
||||
hex.EncodeToString(pub2[:]),
|
||||
fmt.Sprintf("%x", conversationKey),
|
||||
fmt.Sprintf("%x", salt),
|
||||
plaintext,
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"iter"
|
||||
"strconv"
|
||||
|
||||
"fiatjaf.com/nostrlib"
|
||||
"fiatjaf.com/nostr"
|
||||
)
|
||||
|
||||
func HyperLogLogEventPubkeyOffsetsAndReferencesForEvent(evt *nostr.Event) iter.Seq2[string, int] {
|
||||
|
||||
@@ -3,7 +3,7 @@ package nip45
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"fiatjaf.com/nostrlib"
|
||||
"fiatjaf.com/nostr"
|
||||
)
|
||||
|
||||
// HyperLogLogEventPubkeyOffsetForFilter returns the deterministic pubkey offset that will be used
|
||||
|
||||
@@ -3,15 +3,13 @@ package nip46
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"fiatjaf.com/nostrlib"
|
||||
"fiatjaf.com/nostrlib/nip04"
|
||||
"fiatjaf.com/nostrlib/nip44"
|
||||
"fiatjaf.com/nostr"
|
||||
"fiatjaf.com/nostr/nip44"
|
||||
)
|
||||
|
||||
type Session struct {
|
||||
PublicKey string
|
||||
SharedKey []byte // nip04
|
||||
ConversationKey [32]byte // nip44
|
||||
PublicKey nostr.PubKey
|
||||
ConversationKey [32]byte
|
||||
}
|
||||
|
||||
type RelayReadWrite struct {
|
||||
@@ -22,22 +20,18 @@ type RelayReadWrite struct {
|
||||
func (s Session) ParseRequest(event *nostr.Event) (Request, error) {
|
||||
var req Request
|
||||
|
||||
plain, err1 := nip44.Decrypt(event.Content, s.ConversationKey)
|
||||
if err1 != nil {
|
||||
var err2 error
|
||||
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)
|
||||
}
|
||||
plain, err := nip44.Decrypt(event.Content, s.ConversationKey)
|
||||
if err != nil {
|
||||
return req, fmt.Errorf("failed to decrypt event from %s: (nip44: %w)", event.PubKey, err)
|
||||
}
|
||||
|
||||
err := json.Unmarshal([]byte(plain), &req)
|
||||
err = json.Unmarshal([]byte(plain), &req)
|
||||
return req, err
|
||||
}
|
||||
|
||||
func (s Session) MakeResponse(
|
||||
id string,
|
||||
requester string,
|
||||
requester nostr.PubKey,
|
||||
result string,
|
||||
err error,
|
||||
) (resp Response, evt nostr.Event, error error) {
|
||||
|
||||
@@ -8,10 +8,10 @@ import (
|
||||
"strconv"
|
||||
"sync/atomic"
|
||||
|
||||
"fiatjaf.com/nostr"
|
||||
"fiatjaf.com/nostr/nip04"
|
||||
"fiatjaf.com/nostr/nip44"
|
||||
"github.com/mailru/easyjson"
|
||||
"fiatjaf.com/nostrlib"
|
||||
"fiatjaf.com/nostrlib/nip04"
|
||||
"fiatjaf.com/nostrlib/nip44"
|
||||
"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.
|
||||
func ConnectBunker(
|
||||
ctx context.Context,
|
||||
clientSecretKey string,
|
||||
clientSecretKey nostr.PubKey,
|
||||
bunkerURLOrNIP05 string,
|
||||
pool *nostr.SimplePool,
|
||||
onAuth func(string),
|
||||
@@ -51,7 +51,7 @@ func ConnectBunker(
|
||||
// assume it's a bunker url (will fail later if not)
|
||||
secret := parsed.Query().Get("secret")
|
||||
relays := parsed.Query()["relay"]
|
||||
targetPublicKey := parsed.Host
|
||||
targetPublicKey, _ := nostr.PubKeyFromHex(parsed.Host)
|
||||
|
||||
if parsed.Scheme == "" {
|
||||
// could be a NIP-05
|
||||
@@ -85,8 +85,8 @@ func ConnectBunker(
|
||||
|
||||
func NewBunker(
|
||||
ctx context.Context,
|
||||
clientSecretKey string,
|
||||
targetPublicKey string,
|
||||
clientSecretKey [32]byte,
|
||||
targetPublicKey nostr.PubKey,
|
||||
relays []string,
|
||||
pool *nostr.SimplePool,
|
||||
onAuth func(string),
|
||||
@@ -95,8 +95,7 @@ func NewBunker(
|
||||
pool = nostr.NewSimplePool(ctx)
|
||||
}
|
||||
|
||||
clientPublicKey, _ := nostr.GetPublicKey(clientSecretKey)
|
||||
sharedSecret, _ := nip04.ComputeSharedSecret(targetPublicKey, clientSecretKey)
|
||||
clientPublicKey := nostr.GetPublicKey(clientSecretKey)
|
||||
conversationKey, _ := nip44.GenerateConversationKey(targetPublicKey, clientSecretKey)
|
||||
|
||||
bunker := &BunkerClient{
|
||||
|
||||
@@ -2,44 +2,41 @@ package nip46
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"slices"
|
||||
"sync"
|
||||
|
||||
"fiatjaf.com/nostr"
|
||||
"fiatjaf.com/nostr/nip44"
|
||||
"github.com/mailru/easyjson"
|
||||
"fiatjaf.com/nostrlib"
|
||||
"fiatjaf.com/nostrlib/nip04"
|
||||
"fiatjaf.com/nostrlib/nip44"
|
||||
)
|
||||
|
||||
var _ Signer = (*DynamicSigner)(nil)
|
||||
|
||||
type DynamicSigner struct {
|
||||
sessionKeys []string
|
||||
sessions []Session
|
||||
sessions map[nostr.PubKey]Session
|
||||
|
||||
sync.Mutex
|
||||
|
||||
getHandlerSecretKey func(handlerPubkey string) (string, error)
|
||||
getUserKeyer func(handlerPubkey string) (nostr.Keyer, error)
|
||||
authorizeSigning func(event nostr.Event, from string, secret string) bool
|
||||
authorizeEncryption func(from string, secret string) bool
|
||||
getHandlerSecretKey func(handlerPubkey nostr.PubKey) ([32]byte, error)
|
||||
getUserKeyer func(handlerPubkey nostr.PubKey) (nostr.Keyer, error)
|
||||
authorizeSigning func(event nostr.Event, from nostr.PubKey, secret string) bool
|
||||
authorizeEncryption func(from nostr.PubKey, secret string) bool
|
||||
onEventSigned func(event nostr.Event)
|
||||
getRelays func(pubkey string) map[string]RelayReadWrite
|
||||
}
|
||||
|
||||
func NewDynamicSigner(
|
||||
// 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
|
||||
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
|
||||
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
|
||||
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
|
||||
onEventSigned func(event nostr.Event),
|
||||
@@ -53,34 +50,28 @@ func NewDynamicSigner(
|
||||
authorizeSigning: authorizeSigning,
|
||||
authorizeEncryption: authorizeEncryption,
|
||||
onEventSigned: onEventSigned,
|
||||
getRelays: getRelays,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *DynamicSigner) GetSession(clientPubkey string) (Session, bool) {
|
||||
idx, exists := slices.BinarySearch(p.sessionKeys, clientPubkey)
|
||||
func (p *DynamicSigner) GetSession(clientPubkey nostr.PubKey) (Session, bool) {
|
||||
session, exists := p.sessions[clientPubkey]
|
||||
if exists {
|
||||
return p.sessions[idx], true
|
||||
return session, true
|
||||
}
|
||||
return Session{}, false
|
||||
}
|
||||
|
||||
func (p *DynamicSigner) setSession(clientPubkey string, session Session) {
|
||||
func (p *DynamicSigner) setSession(clientPubkey nostr.PubKey, session Session) {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
idx, exists := slices.BinarySearch(p.sessionKeys, clientPubkey)
|
||||
_, exists := p.sessions[clientPubkey]
|
||||
if exists {
|
||||
return
|
||||
}
|
||||
|
||||
// add to pool
|
||||
p.sessionKeys = append(p.sessionKeys, "") // bogus append just to increase the capacity
|
||||
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
|
||||
p.sessions[clientPubkey] = session
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
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)
|
||||
if err != nil {
|
||||
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)
|
||||
}
|
||||
|
||||
var session Session
|
||||
idx, exists := slices.BinarySearch(p.sessionKeys, event.PubKey)
|
||||
if exists {
|
||||
session = p.sessions[idx]
|
||||
} else {
|
||||
session, exists := p.sessions[event.PubKey]
|
||||
if !exists {
|
||||
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)
|
||||
if err != nil {
|
||||
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"
|
||||
case "get_public_key":
|
||||
result = session.PublicKey
|
||||
result = hex.EncodeToString(session.PublicKey[:])
|
||||
case "sign_event":
|
||||
if len(req.Params) != 1 {
|
||||
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)
|
||||
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":
|
||||
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
|
||||
}
|
||||
thirdPartyPubkey := req.Params[0]
|
||||
if !nostr.IsValidPublicKey(thirdPartyPubkey) {
|
||||
resultErr = fmt.Errorf("first argument to 'nip04_encrypt' is not a pubkey string")
|
||||
thirdPartyPubkey, err := nostr.PubKeyFromHex(req.Params[0])
|
||||
if err != nil {
|
||||
resultErr = fmt.Errorf("first argument to 'nip44_encrypt' is not a valid pubkey string")
|
||||
break
|
||||
}
|
||||
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'")
|
||||
break
|
||||
}
|
||||
thirdPartyPubkey := req.Params[0]
|
||||
if !nostr.IsValidPublicKey(thirdPartyPubkey) {
|
||||
resultErr = fmt.Errorf("first argument to 'nip04_decrypt' is not a pubkey string")
|
||||
thirdPartyPubkey, err := nostr.PubKeyFromHex(req.Params[0])
|
||||
if err != nil {
|
||||
resultErr = fmt.Errorf("first argument to 'nip04_decrypt' is not a valid pubkey string")
|
||||
break
|
||||
}
|
||||
if p.authorizeEncryption != nil && !p.authorizeEncryption(event.PubKey, secret) {
|
||||
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"fiatjaf.com/nostr"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"fiatjaf.com/nostrlib"
|
||||
)
|
||||
|
||||
var json = jsoniter.ConfigFastest
|
||||
@@ -34,7 +34,7 @@ func (r Response) String() string {
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ func IsValidBunkerURL(input string) bool {
|
||||
if p.Scheme != "bunker" {
|
||||
return false
|
||||
}
|
||||
if !nostr.IsValidPublicKey(p.Host) {
|
||||
if _, err := nostr.PubKeyFromHex(p.Host); err != nil {
|
||||
return false
|
||||
}
|
||||
if !strings.Contains(p.RawQuery, "relay=") {
|
||||
|
||||
@@ -2,57 +2,45 @@ package nip46
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"slices"
|
||||
"sync"
|
||||
|
||||
"fiatjaf.com/nostr"
|
||||
"fiatjaf.com/nostr/nip04"
|
||||
"fiatjaf.com/nostr/nip44"
|
||||
"github.com/mailru/easyjson"
|
||||
"fiatjaf.com/nostrlib"
|
||||
"fiatjaf.com/nostrlib/nip04"
|
||||
"fiatjaf.com/nostrlib/nip44"
|
||||
)
|
||||
|
||||
var _ Signer = (*StaticKeySigner)(nil)
|
||||
|
||||
type StaticKeySigner struct {
|
||||
secretKey string
|
||||
|
||||
sessionKeys []string
|
||||
sessions []Session
|
||||
secretKey [32]byte
|
||||
sessions map[nostr.PubKey]Session
|
||||
|
||||
sync.Mutex
|
||||
|
||||
RelaysToAdvertise map[string]RelayReadWrite
|
||||
AuthorizeRequest func(harmless bool, from string, secret string) bool
|
||||
AuthorizeRequest func(harmless bool, from nostr.PubKey, secret string) bool
|
||||
}
|
||||
|
||||
func NewStaticKeySigner(secretKey string) StaticKeySigner {
|
||||
func NewStaticKeySigner(secretKey [32]byte) StaticKeySigner {
|
||||
return StaticKeySigner{
|
||||
secretKey: secretKey,
|
||||
RelaysToAdvertise: make(map[string]RelayReadWrite),
|
||||
secretKey: secretKey,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *StaticKeySigner) GetSession(clientPubkey string) (Session, bool) {
|
||||
idx, exists := slices.BinarySearch(p.sessionKeys, clientPubkey)
|
||||
if exists {
|
||||
return p.sessions[idx], true
|
||||
}
|
||||
return Session{}, false
|
||||
func (p *StaticKeySigner) GetSession(clientPubkey nostr.PubKey) (Session, bool) {
|
||||
session, ok := p.sessions[clientPubkey]
|
||||
return session, ok
|
||||
}
|
||||
|
||||
func (p *StaticKeySigner) getOrCreateSession(clientPubkey string) (Session, error) {
|
||||
func (p *StaticKeySigner) getOrCreateSession(clientPubkey nostr.PubKey) (Session, error) {
|
||||
p.Lock()
|
||||
defer p.Unlock()
|
||||
|
||||
idx, exists := slices.BinarySearch(p.sessionKeys, clientPubkey)
|
||||
session, exists := p.sessions[clientPubkey]
|
||||
if exists {
|
||||
return p.sessions[idx], nil
|
||||
}
|
||||
|
||||
shared, err := nip04.ComputeSharedSecret(clientPubkey, p.secretKey)
|
||||
if err != nil {
|
||||
return Session{}, fmt.Errorf("failed to compute shared secret: %w", err)
|
||||
return session, nil
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
pubkey, err := nostr.GetPublicKey(p.secretKey)
|
||||
if err != nil {
|
||||
return Session{}, fmt.Errorf("failed to derive public key: %w", err)
|
||||
}
|
||||
|
||||
session := Session{
|
||||
pubkey := nostr.GetPublicKey(p.secretKey)
|
||||
session = Session{
|
||||
PublicKey: pubkey,
|
||||
SharedKey: shared,
|
||||
ConversationKey: ck,
|
||||
}
|
||||
|
||||
// add to pool
|
||||
p.sessionKeys = append(p.sessionKeys, "") // bogus append just to increase the capacity
|
||||
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
|
||||
p.sessions[pubkey] = session
|
||||
|
||||
return session, nil
|
||||
}
|
||||
@@ -116,7 +94,7 @@ func (p *StaticKeySigner) HandleRequest(_ context.Context, event *nostr.Event) (
|
||||
result = "ack"
|
||||
harmless = true
|
||||
case "get_public_key":
|
||||
result = session.PublicKey
|
||||
result = hex.EncodeToString(session.PublicKey[:])
|
||||
harmless = true
|
||||
case "sign_event":
|
||||
if len(req.Params) != 1 {
|
||||
@@ -136,18 +114,14 @@ func (p *StaticKeySigner) HandleRequest(_ context.Context, event *nostr.Event) (
|
||||
}
|
||||
jrevt, _ := easyjson.Marshal(evt)
|
||||
result = string(jrevt)
|
||||
case "get_relays":
|
||||
jrelays, _ := json.Marshal(p.RelaysToAdvertise)
|
||||
result = string(jrelays)
|
||||
harmless = true
|
||||
case "nip44_encrypt":
|
||||
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
|
||||
}
|
||||
thirdPartyPubkey := req.Params[0]
|
||||
if !nostr.IsValidPublicKey(thirdPartyPubkey) {
|
||||
resultErr = fmt.Errorf("first argument to 'nip04_encrypt' is not a pubkey string")
|
||||
thirdPartyPubkey, err := nostr.PubKeyFromHex(req.Params[0])
|
||||
if err != nil {
|
||||
resultErr = fmt.Errorf("first argument to 'nip04_encrypt' is not a valid pubkey string")
|
||||
break
|
||||
}
|
||||
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'")
|
||||
break
|
||||
}
|
||||
thirdPartyPubkey := req.Params[0]
|
||||
if !nostr.IsValidPublicKey(thirdPartyPubkey) {
|
||||
resultErr = fmt.Errorf("first argument to 'nip04_decrypt' is not a pubkey string")
|
||||
thirdPartyPubkey, err := nostr.PubKeyFromHex(req.Params[0])
|
||||
if err != nil {
|
||||
resultErr = fmt.Errorf("first argument to 'nip04_decrypt' is not a valid pubkey string")
|
||||
break
|
||||
}
|
||||
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'")
|
||||
break
|
||||
}
|
||||
thirdPartyPubkey := req.Params[0]
|
||||
if !nostr.IsValidPublicKey(thirdPartyPubkey) {
|
||||
resultErr = fmt.Errorf("first argument to 'nip04_encrypt' is not a pubkey string")
|
||||
thirdPartyPubkey, err := nostr.PubKeyFromHex(req.Params[0])
|
||||
if err != nil {
|
||||
resultErr = fmt.Errorf("first argument to 'nip04_encrypt' is not a valid pubkey string")
|
||||
break
|
||||
}
|
||||
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'")
|
||||
break
|
||||
}
|
||||
thirdPartyPubkey := req.Params[0]
|
||||
if !nostr.IsValidPublicKey(thirdPartyPubkey) {
|
||||
resultErr = fmt.Errorf("first argument to 'nip04_decrypt' is not a pubkey string")
|
||||
thirdPartyPubkey, err := nostr.PubKeyFromHex(req.Params[0])
|
||||
if err != nil {
|
||||
resultErr = fmt.Errorf("first argument to 'nip04_decrypt' is not a valid pubkey string")
|
||||
break
|
||||
}
|
||||
ciphertext := req.Params[1]
|
||||
|
||||
@@ -4,22 +4,23 @@ import (
|
||||
"context"
|
||||
"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)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
return nostr.ZeroPK, nil, err
|
||||
}
|
||||
|
||||
pubkey, ok := result.Names[name]
|
||||
pubkeyh, ok := result.Names[name]
|
||||
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 {
|
||||
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
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"fiatjaf.com/nostrlib"
|
||||
"fiatjaf.com/nostr"
|
||||
)
|
||||
|
||||
type CalendarEventKind int
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"fiatjaf.com/nostrlib"
|
||||
"fiatjaf.com/nostr"
|
||||
)
|
||||
|
||||
type LiveEvent struct {
|
||||
|
||||
@@ -5,8 +5,8 @@ import (
|
||||
"math/rand"
|
||||
|
||||
"github.com/mailru/easyjson"
|
||||
"fiatjaf.com/nostrlib"
|
||||
"fiatjaf.com/nostrlib/nip44"
|
||||
"fiatjaf.com/nostr"
|
||||
"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
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"fiatjaf.com/nostrlib"
|
||||
"fiatjaf.com/nostr"
|
||||
)
|
||||
|
||||
type HistoryEntry struct {
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"github.com/elnosh/gonuts/cashu/nuts/nut02"
|
||||
"github.com/elnosh/gonuts/cashu/nuts/nut04"
|
||||
"github.com/elnosh/gonuts/cashu/nuts/nut05"
|
||||
"fiatjaf.com/nostrlib/nip60/client"
|
||||
"fiatjaf.com/nostr/nip60/client"
|
||||
)
|
||||
|
||||
type lightningSwapStatus int
|
||||
|
||||
@@ -7,8 +7,8 @@ import (
|
||||
|
||||
"github.com/elnosh/gonuts/cashu"
|
||||
"github.com/elnosh/gonuts/cashu/nuts/nut05"
|
||||
"fiatjaf.com/nostrlib"
|
||||
"fiatjaf.com/nostrlib/nip60/client"
|
||||
"fiatjaf.com/nostr"
|
||||
"fiatjaf.com/nostr/nip60/client"
|
||||
)
|
||||
|
||||
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/nuts/nut10"
|
||||
"fiatjaf.com/nostrlib"
|
||||
"fiatjaf.com/nostrlib/nip60/client"
|
||||
"fiatjaf.com/nostr"
|
||||
"fiatjaf.com/nostr/nip60/client"
|
||||
)
|
||||
|
||||
type receiveSettings struct {
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
|
||||
"github.com/elnosh/gonuts/cashu"
|
||||
"github.com/elnosh/gonuts/cashu/nuts/nut04"
|
||||
"fiatjaf.com/nostrlib/nip60/client"
|
||||
"fiatjaf.com/nostr/nip60/client"
|
||||
)
|
||||
|
||||
func (w *Wallet) SendExternal(
|
||||
|
||||
@@ -11,8 +11,8 @@ import (
|
||||
"github.com/elnosh/gonuts/cashu/nuts/nut02"
|
||||
"github.com/elnosh/gonuts/cashu/nuts/nut10"
|
||||
"github.com/elnosh/gonuts/cashu/nuts/nut11"
|
||||
"fiatjaf.com/nostrlib"
|
||||
"fiatjaf.com/nostrlib/nip60/client"
|
||||
"fiatjaf.com/nostr"
|
||||
"fiatjaf.com/nostr/nip60/client"
|
||||
)
|
||||
|
||||
type SendOption func(opts *sendSettings)
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"github.com/elnosh/gonuts/cashu/nuts/nut02"
|
||||
"github.com/elnosh/gonuts/cashu/nuts/nut03"
|
||||
"github.com/elnosh/gonuts/cashu/nuts/nut10"
|
||||
"fiatjaf.com/nostrlib/nip60/client"
|
||||
"fiatjaf.com/nostr/nip60/client"
|
||||
)
|
||||
|
||||
type swapSettings struct {
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/elnosh/gonuts/cashu"
|
||||
"fiatjaf.com/nostrlib"
|
||||
"fiatjaf.com/nostr"
|
||||
)
|
||||
|
||||
type Token struct {
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||
"fiatjaf.com/nostrlib"
|
||||
"fiatjaf.com/nostr"
|
||||
)
|
||||
|
||||
type Wallet struct {
|
||||
|
||||
@@ -10,8 +10,8 @@ import (
|
||||
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/elnosh/gonuts/cashu"
|
||||
"fiatjaf.com/nostrlib"
|
||||
"fiatjaf.com/nostrlib/keyer"
|
||||
"fiatjaf.com/nostr"
|
||||
"fiatjaf.com/nostr/keyer"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/exp/rand"
|
||||
)
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"slices"
|
||||
|
||||
"github.com/elnosh/gonuts/cashu"
|
||||
"fiatjaf.com/nostrlib"
|
||||
"fiatjaf.com/nostr"
|
||||
)
|
||||
|
||||
type Info struct {
|
||||
|
||||
@@ -9,8 +9,8 @@ import (
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"fiatjaf.com/nostrlib"
|
||||
"fiatjaf.com/nostrlib/nip60"
|
||||
"fiatjaf.com/nostr"
|
||||
"fiatjaf.com/nostr/nip60"
|
||||
)
|
||||
|
||||
var NutzapsNotAccepted = errors.New("user doesn't accept nutzaps")
|
||||
|
||||
@@ -4,11 +4,11 @@ import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
|
||||
"fiatjaf.com/nostr"
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||
"github.com/elnosh/gonuts/cashu"
|
||||
"github.com/elnosh/gonuts/crypto"
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
)
|
||||
|
||||
func VerifyNutzap(
|
||||
|
||||
@@ -3,7 +3,7 @@ package nip70
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"fiatjaf.com/nostr"
|
||||
)
|
||||
|
||||
func IsProtected(event nostr.Event) bool {
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
|
||||
"github.com/mailru/easyjson"
|
||||
jwriter "github.com/mailru/easyjson/jwriter"
|
||||
"fiatjaf.com/nostrlib"
|
||||
"fiatjaf.com/nostr"
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@ import (
|
||||
|
||||
"github.com/fiatjaf/eventstore"
|
||||
"github.com/fiatjaf/eventstore/slicestore"
|
||||
"fiatjaf.com/nostrlib"
|
||||
"fiatjaf.com/nostrlib/nip77"
|
||||
"fiatjaf.com/nostr"
|
||||
"fiatjaf.com/nostr/nip77"
|
||||
)
|
||||
|
||||
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"
|
||||
"fmt"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/nbd-wtf/go-nostr/nip77/negentropy"
|
||||
"github.com/nbd-wtf/go-nostr/nip77/negentropy/storage/empty"
|
||||
"fiatjaf.com/nostr"
|
||||
"fiatjaf.com/nostr/nip77/negentropy"
|
||||
"fiatjaf.com/nostr/nip77/negentropy/storage/empty"
|
||||
)
|
||||
|
||||
func FetchIDsOnly(
|
||||
ctx context.Context,
|
||||
url string,
|
||||
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
|
||||
|
||||
neg := negentropy.New(empty.Empty{}, 1024*1024)
|
||||
@@ -56,7 +56,7 @@ func FetchIDsOnly(
|
||||
return nil, fmt.Errorf("failed to write to relay: %w", err)
|
||||
}
|
||||
|
||||
ch := make(chan string)
|
||||
ch := make(chan nostr.ID)
|
||||
go func() {
|
||||
for id := range neg.HaveNots {
|
||||
ch <- id
|
||||
|
||||
@@ -3,7 +3,7 @@ package negentropy
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"fiatjaf.com/nostrlib"
|
||||
"fiatjaf.com/nostr"
|
||||
)
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
id, err := reader.ReadString(length * 2)
|
||||
if err != nil {
|
||||
pfb := make([]byte, length)
|
||||
if err := reader.ReadHexBytes(pfb); err != nil {
|
||||
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) {
|
||||
@@ -71,26 +71,25 @@ func (n *Negentropy) writeTimestamp(w *StringHexWriter, timestamp nostr.Timestam
|
||||
|
||||
func (n *Negentropy) writeBound(w *StringHexWriter, bound Bound) {
|
||||
n.writeTimestamp(w, bound.Timestamp)
|
||||
writeVarInt(w, len(bound.ID)/2)
|
||||
w.WriteHex(bound.Item.ID)
|
||||
writeVarInt(w, len(bound.IDPrefix))
|
||||
w.WriteBytes(bound.IDPrefix)
|
||||
}
|
||||
|
||||
func getMinimalBound(prev, curr Item) Bound {
|
||||
if curr.Timestamp != prev.Timestamp {
|
||||
return Bound{Item{curr.Timestamp, ""}}
|
||||
return Bound{curr.Timestamp, nil}
|
||||
}
|
||||
|
||||
sharedPrefixBytes := 0
|
||||
|
||||
for i := 0; i < 32; i += 2 {
|
||||
if curr.ID[i:i+2] != prev.ID[i:i+2] {
|
||||
for i := 0; i < 31; i++ {
|
||||
if curr.ID[i] != prev.ID[i] {
|
||||
break
|
||||
}
|
||||
sharedPrefixBytes++
|
||||
}
|
||||
|
||||
// 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) {
|
||||
|
||||
@@ -9,9 +9,9 @@ import (
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"fiatjaf.com/nostrlib"
|
||||
"fiatjaf.com/nostrlib/nip77/negentropy"
|
||||
"fiatjaf.com/nostrlib/nip77/negentropy/storage/vector"
|
||||
"fiatjaf.com/nostr"
|
||||
"fiatjaf.com/nostr/nip77/negentropy"
|
||||
"fiatjaf.com/nostr/nip77/negentropy/storage/vector"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
package negentropy
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
"unsafe"
|
||||
|
||||
"fiatjaf.com/nostrlib"
|
||||
"fiatjaf.com/nostr"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -15,7 +16,7 @@ const (
|
||||
buckets = 16
|
||||
)
|
||||
|
||||
var InfiniteBound = Bound{Item: Item{Timestamp: maxTimestamp}}
|
||||
var InfiniteBound = Bound{Timestamp: maxTimestamp}
|
||||
|
||||
type Negentropy struct {
|
||||
storage Storage
|
||||
@@ -25,8 +26,8 @@ type Negentropy struct {
|
||||
lastTimestampIn nostr.Timestamp
|
||||
lastTimestampOut nostr.Timestamp
|
||||
|
||||
Haves chan string
|
||||
HaveNots chan string
|
||||
Haves chan nostr.ID
|
||||
HaveNots chan nostr.ID
|
||||
}
|
||||
|
||||
func New(storage Storage, frameSizeLimit int) *Negentropy {
|
||||
@@ -39,8 +40,8 @@ func New(storage Storage, frameSizeLimit int) *Negentropy {
|
||||
return &Negentropy{
|
||||
storage: storage,
|
||||
frameSizeLimit: frameSizeLimit,
|
||||
Haves: make(chan string, buckets*4),
|
||||
HaveNots: make(chan string, buckets*4),
|
||||
Haves: make(chan nostr.ID, buckets*4),
|
||||
HaveNots: make(chan nostr.ID, buckets*4),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,9 +159,10 @@ func (n *Negentropy) reconcileAux(reader *StringHexReader) (string, error) {
|
||||
}
|
||||
|
||||
// what they have
|
||||
theirItems := make(map[string]struct{}, numIds)
|
||||
theirItems := make(map[nostr.ID]struct{}, numIds)
|
||||
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)
|
||||
} else {
|
||||
theirItems[id] = struct{}{}
|
||||
@@ -203,11 +205,11 @@ func (n *Negentropy) reconcileAux(reader *StringHexReader) (string, error) {
|
||||
|
||||
for index, item := range n.storage.Range(lower, upper) {
|
||||
if n.frameSizeLimit-200 < fullOutput.Len()/2+responseIds.Len()/2 {
|
||||
endBound = Bound{item}
|
||||
endBound = Bound{item.Timestamp, item.ID[:]}
|
||||
upper = index
|
||||
break
|
||||
}
|
||||
responseIds.WriteString(item.ID)
|
||||
responseIds.WriteString(hex.EncodeToString(item.ID[:]))
|
||||
responses++
|
||||
}
|
||||
|
||||
@@ -254,7 +256,7 @@ func (n *Negentropy) SplitRange(lower, upper int, upperBound Bound, output *Stri
|
||||
writeVarInt(output, numElems)
|
||||
|
||||
for _, item := range n.storage.Range(lower, upper) {
|
||||
output.WriteHex(item.ID)
|
||||
output.WriteBytes(item.ID[:])
|
||||
}
|
||||
} else {
|
||||
itemsPerBucket := numElems / buckets
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
|
||||
"fiatjaf.com/nostrlib/nip77/negentropy"
|
||||
"fiatjaf.com/nostr/nip77/negentropy"
|
||||
)
|
||||
|
||||
type Accumulator struct {
|
||||
|
||||
@@ -3,8 +3,8 @@ package empty
|
||||
import (
|
||||
"iter"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr/nip77/negentropy"
|
||||
"github.com/nbd-wtf/go-nostr/nip77/negentropy/storage"
|
||||
"fiatjaf.com/nostr/nip77/negentropy"
|
||||
"fiatjaf.com/nostr/nip77/negentropy/storage"
|
||||
)
|
||||
|
||||
var acc storage.Accumulator
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package vector
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"iter"
|
||||
"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 {
|
||||
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 {
|
||||
v.acc.Reset()
|
||||
|
||||
tmp := make([]byte, 32)
|
||||
for _, item := range v.Range(begin, end) {
|
||||
hex.Decode(tmp, []byte(item.ID))
|
||||
v.acc.AddBytes(tmp)
|
||||
v.acc.AddBytes(item.ID[:])
|
||||
}
|
||||
|
||||
return v.acc.GetFingerprint(end - begin)
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package negentropy
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"cmp"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"fiatjaf.com/nostrlib"
|
||||
"fiatjaf.com/nostr"
|
||||
)
|
||||
|
||||
const FingerprintSize = 16
|
||||
@@ -33,23 +33,26 @@ func (v Mode) String() string {
|
||||
|
||||
type Item struct {
|
||||
Timestamp nostr.Timestamp
|
||||
ID string
|
||||
ID nostr.ID
|
||||
}
|
||||
|
||||
func ItemCompare(a, b Item) int {
|
||||
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)
|
||||
}
|
||||
|
||||
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 {
|
||||
if b.Timestamp == InfiniteBound.Timestamp {
|
||||
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"
|
||||
"testing"
|
||||
|
||||
"fiatjaf.com/nostrlib"
|
||||
"fiatjaf.com/nostrlib/nip77/negentropy"
|
||||
"fiatjaf.com/nostrlib/nip77/negentropy/storage/vector"
|
||||
"fiatjaf.com/nostr"
|
||||
"fiatjaf.com/nostr/nip77/negentropy"
|
||||
"fiatjaf.com/nostr/nip77/negentropy/storage/vector"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
||||
@@ -5,14 +5,14 @@ import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"fiatjaf.com/nostrlib"
|
||||
"fiatjaf.com/nostrlib/nip77/negentropy"
|
||||
"fiatjaf.com/nostrlib/nip77/negentropy/storage/vector"
|
||||
"fiatjaf.com/nostr"
|
||||
"fiatjaf.com/nostr/nip77/negentropy"
|
||||
"fiatjaf.com/nostr/nip77/negentropy/storage/vector"
|
||||
)
|
||||
|
||||
type direction struct {
|
||||
label string
|
||||
items chan string
|
||||
items chan nostr.ID
|
||||
source nostr.RelayStore
|
||||
target nostr.RelayStore
|
||||
}
|
||||
@@ -91,7 +91,9 @@ func NegentropySync(
|
||||
}()
|
||||
|
||||
wg := sync.WaitGroup{}
|
||||
pool := newidlistpool(50)
|
||||
pool := sync.Pool{
|
||||
New: func() any { return make([]nostr.ID, 0, 50) },
|
||||
}
|
||||
|
||||
// Define sync directions
|
||||
directions := [][]direction{
|
||||
@@ -105,11 +107,11 @@ func NegentropySync(
|
||||
go func(dir direction) {
|
||||
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 pool.giveback(ids)
|
||||
defer pool.Put(ids)
|
||||
|
||||
if len(ids) == 0 {
|
||||
return
|
||||
@@ -124,7 +126,7 @@ func NegentropySync(
|
||||
}
|
||||
}
|
||||
|
||||
ids := pool.grab()
|
||||
ids := pool.Get().([]nostr.ID)
|
||||
for item := range dir.items {
|
||||
if _, ok := seen[item]; ok {
|
||||
continue
|
||||
@@ -135,7 +137,7 @@ func NegentropySync(
|
||||
if len(ids) == 50 {
|
||||
wg.Add(1)
|
||||
go doSync(ids)
|
||||
ids = pool.grab()
|
||||
ids = pool.Get().([]nostr.ID)
|
||||
}
|
||||
}
|
||||
wg.Add(1)
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"math"
|
||||
"net"
|
||||
|
||||
"fiatjaf.com/nostrlib"
|
||||
"fiatjaf.com/nostr"
|
||||
)
|
||||
|
||||
func DecodeRequest(req Request) (MethodParams, error) {
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"fiatjaf.com/nostrlib"
|
||||
"fiatjaf.com/nostr"
|
||||
)
|
||||
|
||||
type IMeta []IMetaEntry
|
||||
|
||||
@@ -3,7 +3,7 @@ package nip92
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"fiatjaf.com/nostrlib"
|
||||
"fiatjaf.com/nostr"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ package nip94
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"fiatjaf.com/nostrlib"
|
||||
"fiatjaf.com/nostr"
|
||||
)
|
||||
|
||||
func ParseFileMetadata(event nostr.Event) FileMetadata {
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
"strconv"
|
||||
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"fiatjaf.com/nostrlib"
|
||||
"fiatjaf.com/nostr"
|
||||
)
|
||||
|
||||
// Upload uploads a file to the provided req.Host.
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"fiatjaf.com/nostrlib"
|
||||
"fiatjaf.com/nostr"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
@@ -5,7 +5,7 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"fiatjaf.com/nostrlib"
|
||||
"fiatjaf.com/nostr"
|
||||
)
|
||||
|
||||
// UploadRequest is a NIP96 upload request.
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"fiatjaf.com/nostr"
|
||||
)
|
||||
|
||||
// Check checks if a file exists on the media server by its hash
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"fiatjaf.com/nostr"
|
||||
"github.com/valyala/fasthttp"
|
||||
)
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"fiatjaf.com/nostr"
|
||||
)
|
||||
|
||||
// Delete deletes a file from the media server by its hash
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"fiatjaf.com/nostr"
|
||||
)
|
||||
|
||||
// Download downloads a file from the media server by its hash
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"io"
|
||||
"strconv"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"fiatjaf.com/nostr"
|
||||
"github.com/valyala/fasthttp"
|
||||
)
|
||||
|
||||
|
||||
@@ -2,9 +2,10 @@ package blossom
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"fiatjaf.com/nostr"
|
||||
)
|
||||
|
||||
// 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)
|
||||
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) {
|
||||
evt.Tags = append(evt.Tags, nostr.Tag{"t", "list"})
|
||||
})
|
||||
|
||||
@@ -3,7 +3,7 @@ package blossom
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"fiatjaf.com/nostr"
|
||||
)
|
||||
|
||||
// BlobDescriptor represents metadata about a blob stored on a media server
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"fiatjaf.com/nostr"
|
||||
)
|
||||
|
||||
// UploadFile uploads a file to the media server
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package hints
|
||||
|
||||
import "fiatjaf.com/nostrlib"
|
||||
import "fiatjaf.com/nostr"
|
||||
|
||||
const END_OF_WORLD nostr.Timestamp = 2208999600 // 2040-01-01
|
||||
|
||||
|
||||
@@ -2,15 +2,14 @@ package lmdbh
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"math"
|
||||
"os"
|
||||
"slices"
|
||||
|
||||
"fiatjaf.com/nostr"
|
||||
"fiatjaf.com/nostr/sdk/hints"
|
||||
"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)
|
||||
@@ -63,7 +62,7 @@ func (lh *LMDBHints) 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 {
|
||||
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 {
|
||||
relay string
|
||||
score int64
|
||||
@@ -107,11 +106,10 @@ func (lh *LMDBHints) TopN(pubkey string, n int) []string {
|
||||
}
|
||||
defer cursor.Close()
|
||||
|
||||
prefix, _ := hex.DecodeString(pubkey)
|
||||
k, v, err := cursor.Get(prefix, nil, lmdb.SetRange)
|
||||
k, v, err := cursor.Get(pubkey[:], nil, lmdb.SetRange)
|
||||
for ; err == nil; k, v, err = cursor.Get(nil, nil, lmdb.Next) {
|
||||
// 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
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
relay string
|
||||
tss timestamps
|
||||
@@ -199,11 +197,10 @@ func (lh *LMDBHints) GetDetailedScores(pubkey string, n int) []hints.RelayScores
|
||||
}
|
||||
defer cursor.Close()
|
||||
|
||||
prefix, _ := hex.DecodeString(pubkey)
|
||||
k, v, err := cursor.Get(prefix, nil, lmdb.SetRange)
|
||||
k, v, err := cursor.Get(pubkey[:], nil, lmdb.SetRange)
|
||||
for ; err == nil; k, v, err = cursor.Get(nil, nil, lmdb.Next) {
|
||||
// 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
|
||||
}
|
||||
|
||||
|
||||
@@ -4,12 +4,12 @@ import (
|
||||
"encoding/binary"
|
||||
"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))
|
||||
hex.Decode(k[0:32], []byte(pubhintkey))
|
||||
copy(k[0:32], pubhintkey[:])
|
||||
copy(k[32:], relay)
|
||||
return k
|
||||
}
|
||||
|
||||
@@ -6,15 +6,15 @@ import (
|
||||
"slices"
|
||||
"sync"
|
||||
|
||||
"fiatjaf.com/nostrlib"
|
||||
"fiatjaf.com/nostrlib/sdk/hints"
|
||||
"fiatjaf.com/nostr"
|
||||
"fiatjaf.com/nostr/sdk/hints"
|
||||
)
|
||||
|
||||
var _ hints.HintsDB = (*HintDB)(nil)
|
||||
|
||||
type HintDB struct {
|
||||
RelayBySerial []string
|
||||
OrderedRelaysByPubKey map[string][]RelayEntry
|
||||
OrderedRelaysByPubKey map[nostr.PubKey][]RelayEntry
|
||||
|
||||
sync.Mutex
|
||||
}
|
||||
@@ -22,11 +22,11 @@ type HintDB struct {
|
||||
func NewHintDB() *HintDB {
|
||||
return &HintDB{
|
||||
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 {
|
||||
ts = now
|
||||
}
|
||||
@@ -67,7 +67,7 @@ func (db *HintDB) Save(pubkey string, relay string, key hints.HintKey, ts nostr.
|
||||
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()
|
||||
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()
|
||||
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"
|
||||
"testing"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr/sdk/hints/badgerh"
|
||||
"fiatjaf.com/nostr/sdk/hints/badgerh"
|
||||
)
|
||||
|
||||
func TestBadgerHints(t *testing.T) {
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"fiatjaf.com/nostrlib/sdk/hints/sqlh"
|
||||
"fiatjaf.com/nostr/sdk/hints/sqlh"
|
||||
"github.com/stretchr/testify/require"
|
||||
_ "github.com/tursodatabase/go-libsql"
|
||||
)
|
||||
|
||||
@@ -4,7 +4,7 @@ import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr/sdk/hints/lmdbh"
|
||||
"fiatjaf.com/nostr/sdk/hints/lmdbh"
|
||||
)
|
||||
|
||||
func TestLMDBHints(t *testing.T) {
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"testing"
|
||||
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
"fiatjaf.com/nostrlib/sdk/hints/sqlh"
|
||||
"fiatjaf.com/nostr/sdk/hints/sqlh"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ package test
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"fiatjaf.com/nostrlib/sdk/hints/memoryh"
|
||||
"fiatjaf.com/nostr/sdk/hints/memoryh"
|
||||
)
|
||||
|
||||
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