nip46: allow signers to prevent handling duplicate requests (happens sometimes when switching relays, depending on how stuff is done, it's harmless but still).

This commit is contained in:
fiatjaf
2026-01-21 22:46:16 -03:00
parent 061cf7f68f
commit 241959d1e3
3 changed files with 29 additions and 14 deletions

View File

@@ -1,7 +1,9 @@
package nip46 package nip46
import ( import (
"errors"
"fmt" "fmt"
"slices"
"fiatjaf.com/nostr" "fiatjaf.com/nostr"
"fiatjaf.com/nostr/nip44" "fiatjaf.com/nostr/nip44"
@@ -10,6 +12,9 @@ import (
type Session struct { type Session struct {
PublicKey nostr.PubKey PublicKey nostr.PubKey
ConversationKey [32]byte ConversationKey [32]byte
duplicatesBuf [6]string
serial int
} }
type RelayReadWrite struct { type RelayReadWrite struct {
@@ -17,7 +22,9 @@ type RelayReadWrite struct {
Write bool `json:"write"` Write bool `json:"write"`
} }
func (s Session) ParseRequest(event nostr.Event) (Request, error) { var AlreadyHandled = errors.New("already handled this request")
func (s *Session) ParseRequest(event nostr.Event) (Request, error) {
var req Request var req Request
plain, err := nip44.Decrypt(event.Content, s.ConversationKey) plain, err := nip44.Decrypt(event.Content, s.ConversationKey)
@@ -26,6 +33,15 @@ func (s Session) ParseRequest(event nostr.Event) (Request, error) {
} }
err = json.Unmarshal([]byte(plain), &req) err = json.Unmarshal([]byte(plain), &req)
// discard duplicates
if slices.Contains(s.duplicatesBuf[:], req.ID) {
return req, AlreadyHandled
}
s.duplicatesBuf[s.serial%len(s.duplicatesBuf)] = req.ID
s.serial++
return req, err return req, err
} }

View File

@@ -14,7 +14,7 @@ var _ Signer = (*DynamicSigner)(nil)
type DynamicSigner struct { type DynamicSigner struct {
// { [handlePubkey]: {[clientKey]: Session} } // { [handlePubkey]: {[clientKey]: Session} }
sessions map[nostr.PubKey]map[nostr.PubKey]Session sessions map[nostr.PubKey]map[nostr.PubKey]*Session
// used for switch_relays call // used for switch_relays call
DefaultRelays []string DefaultRelays []string
@@ -47,7 +47,7 @@ type DynamicSigner struct {
} }
func (p *DynamicSigner) Init() { func (p *DynamicSigner) Init() {
p.sessions = make(map[nostr.PubKey]map[nostr.PubKey]Session) p.sessions = make(map[nostr.PubKey]map[nostr.PubKey]*Session)
} }
func (p *DynamicSigner) HandleRequest(ctx context.Context, event nostr.Event) ( func (p *DynamicSigner) HandleRequest(ctx context.Context, event nostr.Event) (
@@ -85,14 +85,14 @@ func (p *DynamicSigner) HandleRequest(ctx context.Context, event nostr.Event) (
handlerSessions, exists := p.sessions[handlerPubkey] handlerSessions, exists := p.sessions[handlerPubkey]
if !exists { if !exists {
handlerSessions = make(map[nostr.PubKey]Session) handlerSessions = make(map[nostr.PubKey]*Session)
p.sessions[handlerPubkey] = handlerSessions p.sessions[handlerPubkey] = handlerSessions
} }
session, exists := handlerSessions[event.PubKey] session, exists := handlerSessions[event.PubKey]
if !exists { if !exists {
// create session if it doesn't exist // create session if it doesn't exist
session = Session{} session = &Session{}
session.ConversationKey, err = nip44.GenerateConversationKey(event.PubKey, handlerSecret) session.ConversationKey, err = nip44.GenerateConversationKey(event.PubKey, handlerSecret)
if err != nil { if err != nil {

View File

@@ -15,8 +15,8 @@ import (
var _ Signer = (*StaticKeySigner)(nil) var _ Signer = (*StaticKeySigner)(nil)
type StaticKeySigner struct { type StaticKeySigner struct {
secretKey [32]byte secretKey nostr.SecretKey
sessions map[nostr.PubKey]Session sessions map[nostr.PubKey]*Session
sync.Mutex sync.Mutex
@@ -29,11 +29,11 @@ type StaticKeySigner struct {
func NewStaticKeySigner(secretKey [32]byte) StaticKeySigner { func NewStaticKeySigner(secretKey [32]byte) StaticKeySigner {
return StaticKeySigner{ return StaticKeySigner{
secretKey: secretKey, secretKey: secretKey,
sessions: make(map[nostr.PubKey]Session), sessions: make(map[nostr.PubKey]*Session),
} }
} }
func (p *StaticKeySigner) getOrCreateSession(clientPubkey nostr.PubKey) (Session, error) { func (p *StaticKeySigner) getOrCreateSession(clientPubkey nostr.PubKey) (*Session, error) {
p.Lock() p.Lock()
defer p.Unlock() defer p.Unlock()
@@ -44,17 +44,16 @@ func (p *StaticKeySigner) getOrCreateSession(clientPubkey nostr.PubKey) (Session
ck, err := nip44.GenerateConversationKey(clientPubkey, p.secretKey) ck, err := nip44.GenerateConversationKey(clientPubkey, p.secretKey)
if err != nil { if err != nil {
return Session{}, fmt.Errorf("failed to compute shared secret: %w", err) return nil, fmt.Errorf("failed to compute shared secret: %w", err)
} }
pubkey := nostr.GetPublicKey(p.secretKey) session = &Session{
session = Session{ PublicKey: p.secretKey.Public(),
PublicKey: pubkey,
ConversationKey: ck, ConversationKey: ck,
} }
// add to pool // add to pool
p.sessions[pubkey] = session p.sessions[clientPubkey] = session
return session, nil return session, nil
} }