diff --git a/pkg/channels/buzz/buzz.go b/pkg/channels/buzz/buzz.go index 2510495c..370f6d2c 100644 --- a/pkg/channels/buzz/buzz.go +++ b/pkg/channels/buzz/buzz.go @@ -10,6 +10,7 @@ import ( "fmt" "strings" "sync" + "time" "github.com/nbd-wtf/go-nostr" "github.com/nbd-wtf/go-nostr/nip19" @@ -117,9 +118,18 @@ func (c *BuzzChannel) Start(ctx context.Context) error { } c.relay = relay - // NIP-42: the relay challenges, we sign the auth event with the bot identity. - // Buzz relays reject subscriptions from unauthenticated clients, so a failure - // here is fatal rather than advisory. + // NIP-42: the relay sends an AUTH challenge asynchronously after the + // WebSocket handshake completes. go-nostr stores the challenge in an + // unexported field populated by the background read loop. If we call + // Auth() before the challenge arrives, the auth event carries an empty + // challenge tag and the relay rejects it permanently ("verification + // failed" → "authentication already failed" on all subsequent attempts). + // + // We cannot inspect the challenge field directly (unexported), so we wait + // briefly to give the read loop time to receive and store it. The relay + // typically delivers the challenge within a few hundred milliseconds. + time.Sleep(2 * time.Second) + if err := relay.Auth(c.ctx, func(evt *nostr.Event) error { return evt.Sign(c.secretKey) }); err != nil {