From a0acdf57c8a36a6aad67ca9d89d71e2ae0334814 Mon Sep 17 00:00:00 2001 From: oth-body Date: Mon, 14 Sep 2026 21:22:11 -0400 Subject: [PATCH] fix: address 11 bugs found in codebase review 1. Fix eventCache shadowing with := in main() 2. Fix getDMs() nip04.Decrypt using raw key instead of shared secret 3. Fix text input not updated for cursor states 51 and 200 4. Fix NIP-89 "a" tag format (kind:pubkey:d instead of pubkey:d:kind) 5. Fix NIP-89 handler registration kind 1984 -> 31990 with "d" tag 6. Add bounds check before accessing tag[1] in findHandlers 7. Capture withLoading errors for DMs and Replies actions 8. Fix context leak from defer cancel() inside relay loop 9. Add upper bound clamp for Home screen cursor 10. Fix publishToAllRelays returning error on first failure 11. Check GetPublicKey and Sign errors in publishPostTUI --- hoot.go | 35 ++++++++++++++++++++++------------- nip46/nip46.go | 9 ++++++++- tui/tui.go | 8 ++++++-- 3 files changed, 36 insertions(+), 16 deletions(-) diff --git a/hoot.go b/hoot.go index 24b2d09..a10ccd6 100644 --- a/hoot.go +++ b/hoot.go @@ -698,7 +698,8 @@ func getDMs(privateKey string) ([]tui.FeedPost, error) { } // Decrypt the DM content - decrypted, err := nip04.Decrypt(ev.Content, []byte(privateKey)) + ss, _ := nip04.ComputeSharedSecret(ev.PubKey, privateKey) + decrypted, err := nip04.Decrypt(ev.Content, ss) if err != nil { continue // Skip if we can't decrypt } @@ -787,6 +788,7 @@ func getReactions(eventID string) ([]tui.FeedPost, error) { // publishPostTUI publishes a note using either NIP-46 or local key func publishPostTUI(content string) error { var event nostr.Event + var err error event.Kind = 1 event.Content = content event.CreatedAt = nostr.Now() @@ -807,22 +809,28 @@ func publishPostTUI(content string) error { if localPrivateKey == "" { return fmt.Errorf("no active session or local key") } - event.PubKey, _ = nostr.GetPublicKey(localPrivateKey) - event.Sign(localPrivateKey) + event.PubKey, err = nostr.GetPublicKey(localPrivateKey) + if err != nil { + return fmt.Errorf("failed to get public key: %w", err) + } + if err := event.Sign(localPrivateKey); err != nil { + return fmt.Errorf("failed to sign event: %w", err) + } } success := 0 for _, url := range relays { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() relay, err := nostr.RelayConnect(ctx, url) if err != nil { + cancel() continue } if err := relay.Publish(ctx, event); err == nil { success++ } relay.Close() + cancel() } if success == 0 { @@ -904,12 +912,12 @@ func editProfile(privateKey, newContent, pubKey string, relays []string) { // registerAsHandler registers the app as a handler for specific kinds. func registerAsHandler(privateKey, publicKey string, kinds []int, platforms map[string]string) error { - // Create a kind 1984 event for handler registration + // Create a kind 31990 event for handler registration (NIP-89) event := nostr.Event{ PubKey: publicKey, CreatedAt: nostr.Timestamp(time.Now().Unix()), - Kind: 1984, - Tags: nostr.Tags{}, + Kind: 31990, + Tags: nostr.Tags{{"d", "hoot"}}, Content: "", } @@ -955,7 +963,7 @@ func recommendApp(privateKey, publicKey, handlerPubKey, handlerDIdentifier strin CreatedAt: nostr.Timestamp(time.Now().Unix()), Kind: 1985, Tags: nostr.Tags{ - {"a", fmt.Sprintf("%s:%s:%d", handlerPubKey, handlerDIdentifier, kind)}, + {"a", fmt.Sprintf("%d:%s:%s", kind, handlerPubKey, handlerDIdentifier)}, {"p", platform}, }, Content: relayHint, @@ -996,7 +1004,7 @@ func findHandlers(kind int) ([]struct { // Use relay list from file or default relays := getRelayList() filter := nostr.Filter{ - Kinds: []int{1984}, + Kinds: []int{31990}, Tags: nostr.TagMap{"k": []string{fmt.Sprintf("%d", kind)}}, } ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) @@ -1033,7 +1041,7 @@ func findHandlers(kind int) ([]struct { } for _, tag := range ev.Tags { - if tag[0] == "k" { + if len(tag) > 1 && tag[0] == "k" { kind, err := strconv.Atoi(tag[1]) if err == nil { handler.SupportedKinds = append(handler.SupportedKinds, kind) @@ -1303,7 +1311,8 @@ func extractLud16(pubKey string, relays []string) (string, error) { func main() { // Initialize cache for improved performance configDir := getConfigDir() - eventCache, err := cache.New(configDir) + var err error + eventCache, err = cache.New(configDir) if err != nil { log.Printf("Warning: failed to initialize cache: %v", err) } else { @@ -1769,7 +1778,7 @@ func main() { // Handle DMs action with loading. if *dmsPtr { - _ = withLoading("Loading DMs", func() error { + err = withLoading("Loading DMs", func() error { dms, err := getDMs(sk) if err != nil { return fmt.Errorf("failed to load DMs: %w", err) @@ -1788,7 +1797,7 @@ func main() { // Handle replies action with loading. if *repliesPtr != "" { - _ = withLoading("Loading replies", func() error { + err = withLoading("Loading replies", func() error { replies, err := getReactions(*repliesPtr) if err != nil { return fmt.Errorf("failed to load replies: %w", err) diff --git a/nip46/nip46.go b/nip46/nip46.go index a29607e..b23c5bc 100644 --- a/nip46/nip46.go +++ b/nip46/nip46.go @@ -405,11 +405,18 @@ func (s *Session) publishToAllRelays(ctx context.Context, event *nostr.Event) er } wg.Wait() close(errCh) + successes := 0 + var lastErr error for err := range errCh { if err != nil { - return err + lastErr = err + } else { + successes++ } } + if successes == 0 { + return fmt.Errorf("failed to publish to any relay: %w", lastErr) + } return nil } diff --git a/tui/tui.go b/tui/tui.go index 5269661..af2522a 100644 --- a/tui/tui.go +++ b/tui/tui.go @@ -303,6 +303,10 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { if m.cursor < limit { m.cursor++ } + case ScreenHome: + if m.cursor < 7 { + m.cursor++ + } default: m.cursor++ } @@ -343,7 +347,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } // Update text input - if (m.screen == ScreenLogin && (m.cursor == 50 || m.cursor == 100)) || m.screen == ScreenPost || m.screen == ScreenTip || m.screen == ScreenReplies || (m.screen == ScreenRelays && m.addingRelay) { + if (m.screen == ScreenLogin && (m.cursor == 50 || m.cursor == 51 || m.cursor == 100 || m.cursor == 200)) || m.screen == ScreenPost || m.screen == ScreenTip || m.screen == ScreenReplies || (m.screen == ScreenRelays && m.addingRelay) { // Only update input if we are in input mode (not menu selection mode) // For Login: if hasKey (Input mode) OR if !hasKey and cursor=0 (Nsec Entry? No, cursor 0 is menu choice) // Wait, if !hasKey: @@ -378,7 +382,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { // The previous code reused cursor=0 for menu. This is confusing. // Using cursor=50 for Nsec Entry, 100 for Password/Save choice. - if m.cursor == 50 || m.cursor == 100 { + if m.cursor == 50 || m.cursor == 51 || m.cursor == 100 || m.cursor == 200 { m.textInput, cmd = m.textInput.Update(msg) } }