Files
boris/Amber.md
Gigi 94d5089e33 docs: clarify Amethyst bookmark structure in Amber.md
Updated documentation to explicitly state that:
- Amethyst bookmarks are stored in a SINGLE kind:30001 event with d-tag 'bookmark'
- This one event contains BOTH public (in tags) and private (in encrypted content) bookmarks
- When processed, it produces separate items with different isPrivate flags
- Example: 76 public + 416 private = 492 total bookmarks from one event

Added sections on:
- Event structure with d-tag requirement
- Processing flow showing how items are tagged
- UI grouping logic with setName check
- Why both public and private come from the same event
2025-10-18 00:22:23 +02:00

9.3 KiB
Raw Blame History

Boris ↔ Amber bunker: current findings

  • Environment
    • Client: Boris (web) using applesauce stack (NostrConnectSigner, RelayPool).
    • Bunker: Amber (mobile).
    • We restored a nostr-connect account from localStorage and re-wired the signer to the app RelayPool before use.

What we changed client-side

  • Signer wiring

    • Bound NostrConnectSigner.subscriptionMethod/publishMethod to the app RelayPool at startup.
    • After deserialization, recreated the signer with pool context and merged its relays with app RELAYS (includes local relays).
    • Opened the signer subscription and performed a guarded connect() with default permissions including nip04_encrypt/decrypt and nip44_encrypt/decrypt.
  • Account queue disabling (CRITICAL)

    • applesauce-accounts BaseAccount queues requests by default - each request waits for the previous one to complete before being sent.
    • This caused batch decrypt operations to hang: first request would timeout waiting for user interaction, blocking all subsequent requests in the queue.
    • Solution: Set accounts.disableQueue = true globally on the AccountManager in App.tsx during initialization. This applies to all accounts.
    • Without this, Amber never sees decrypt requests because they're stuck in the account's internal queue.
    • Reference: https://hzrd149.github.io/applesauce/typedoc/classes/applesauce-accounts.BaseAccount.html#disablequeue
  • Probes and timeouts

    • Initial probe tried decrypt('invalid-ciphertext') → timed out.
    • Switched to roundtrip probes: encrypt(self, ... ) then decrypt(self, cipher) for both nip-44 and nip-04.
    • Increased probe timeout from 3s → 10s; increased bookmark decrypt timeout from 15s → 30s.
  • Logging

    • Added logs for publish/subscribe and parsed the NIP-46 request content length.
    • Confirmed NIP46 request events are kind 24133 with a single p tag (expected). The method is inside the encrypted content, so it prints as method: undefined (expected).

Evidence from logs (client)

[bunker] ✅ Wired NostrConnectSigner to RelayPool publish/subscription
[bunker] 🔗 Signer relays merged with app RELAYS: (19) [...]
[bunker] subscribe via signer: { relays: [...], filters: [...] }
[bunker] ✅ Signer subscription opened
[bunker] publish via signer: { relays: [...], kind: 24133, tags: [['p', <remote>]], contentLength: 260|304|54704 }
[bunker] 🔎 Probe nip44 roundtrip (encrypt→decrypt)… → probe timeout after 10000ms
[bunker] 🔎 Probe nip04 roundtrip (encrypt→decrypt)… → probe timeout after 10000ms
bookmarkProcessing.ts: ❌ nip44.decrypt failed: Decrypt timeout after 30000ms
bookmarkProcessing.ts: ❌ nip04.decrypt failed: Decrypt timeout after 30000ms

Notes:

  • Final signer status shows listening: true, isConnected: true, and requests are published to 19 relays (includes Ambers).

Evidence from Amber (device)

  • Activity screen shows multiple entries for: “Encrypt data using nip 4” and “Encrypt data using nip 44” with green checkmarks.
  • No entries for “Decrypt data using nip 4” or “Decrypt data using nip 44”.

Interpretation

  • Transport and publish paths are working: Boris is publishing NIP46 requests (kind 24133) and Amber receives them (ENCRYPT activity visible).
  • The persistent failure is specific to DECRYPT handling: Amber does not show any DECRYPT activity and Boris receives no decrypt responses within 1030s windows.
  • Client-side wiring is likely correct (subscription open, permissions requested, relays merged). The remaining issue appears provider-side in Ambers NIP46 decrypt handling or permission gating.

Repro steps (quick)

  1. Revoke Boris in Amber.
  2. Reconnect with a fresh bunker URI; approve signing and both encrypt/decrypt scopes for nip04 and nip44.
  3. Keep Amber unlocked and foregrounded.
  4. Reload Boris; observe:
    • Logs showing publish via signer for kind 24133.
    • In Amber, activity should include “Decrypt data using nip 4/44”.

If DECRYPT entries still dont appear:

  • This points to Ambers NIP46 provider not executing/authorizing nip04_decrypt/nip44_decrypt methods, or not publishing responses.

Suggestions for Amber-side debugging

  • Verify permission gating allows nip04_decrypt and nip44_decrypt (not just encrypt).
  • Confirm the provider recognizes NIP46 methods nip04_decrypt and nip44_decrypt in the decrypted payload and routes them to decrypt routines.
  • Ensure the response event is published back to the same relays and correctly addressed to the client (p tag set and content encrypted back to client pubkey).
  • Add activity logging for “Decrypt …” attempts and failures to surface denial/exception states.

Performance improvements (post-debugging)

Non-blocking publish wiring

  • Problem: Awaiting pool.publish() completion blocks until all relay sends finish (can take 30s+ with timeouts).
  • Solution: Wrapped NostrConnectSigner.publishMethod at app startup to fire-and-forget publish Observable/Promise; responses still arrive via signer subscription.
  • Result: Encrypt/decrypt operations complete in <2s as seen in /debug page (NIP-44: ~900ms enc, ~700ms dec; NIP-04: ~1s enc, ~2s dec).

Bookmark decryption optimization

  • Problem #1: Sequential decrypt of encrypted bookmark events blocks UI and takes long with multiple events.
  • Problem #2: 30-second timeouts on nip44.decrypt meant waiting 30s per event if bunker didn't support nip44.
  • Problem #3: Account request queue blocked all decrypt requests until first one completed (waiting for user interaction).
  • Solution:
    • Removed all artificial timeouts - let decrypt fail naturally like debug page does.
    • Added smart encryption detection (NIP-04 has ?iv=, NIP-44 doesn't) to try the right method first.
    • Disabled account queue globally (accounts.disableQueue = true) in App.tsx so all requests are sent immediately.
    • Process sequentially (removed concurrent mapWithConcurrency hack).
  • Result: Bookmark decryption is near-instant, limited only by bunker response time and user approval speed.

Amethyst-style bookmarks (kind:30001)

Important: Amethyst bookmarks are stored in a SINGLE kind:30001 event with d-tag "bookmark" that contains BOTH public AND private bookmarks in different parts of the event.

Event structure:

  • Event kind: 30001 (NIP-51 bookmark set)
  • d-tag: "bookmark" (identifies this as the Amethyst bookmark list)
  • Public bookmarks: Stored in event tags (e.g., ["e", "..."], ["a", "..."])
  • Private bookmarks: Stored in encrypted content field (NIP-04 or NIP-44)

Example event:

{
  "kind": 30001,
  "tags": [
    ["d", "bookmark"],       // Identifies this as Amethyst bookmarks
    ["e", "102a2fe..."],     // Public bookmark (76 total)
    ["a", "30023:..."]       // Public bookmark
  ],
  "content": "lvOfl7Qb...?iv=5KzDXv09..."  // NIP-04 encrypted (416 private bookmarks)
}

Processing:

When this single event is processed:

  1. Public tags → 76 bookmark items with sourceKind: 30001, isPrivate: false, setName: "bookmark"
  2. Encrypted content → 416 bookmark items with sourceKind: 30001, isPrivate: true, setName: "bookmark"
  3. Total: 492 bookmarks from one event

Encryption detection:

  • The encrypted content field contains a JSON array of private bookmark tags
  • Helpers.hasHiddenContent() from applesauce-core only detects NIP-44 encrypted content
  • NIP-04 encrypted content must be detected explicitly by checking for ?iv= in the content string
  • Both detection methods are needed in:
    1. Display logic (Debug.tsx - hasEncryptedContent()) - to show padlock emoji and decrypt button
    2. Decryption logic (bookmarkProcessing.ts) - to schedule decrypt jobs

Grouping:

In the UI, these are separated into two groups:

  • Amethyst Lists: sourceKind === 30001 && !isPrivate && setName === 'bookmark' (public items)
  • Amethyst Private: sourceKind === 30001 && isPrivate && setName === 'bookmark' (private items)

Both groups come from the same event, separated by whether they were in public tags or encrypted content.

Why this matters:

This dual-storage format (public + private in one event) is why we need explicit NIP-04 detection. Without it, Helpers.hasHiddenContent() returns false and the encrypted content is never decrypted, resulting in 0 private bookmarks despite having encrypted data.

Current conclusion

  • Client is configured and publishing requests correctly; encryption proves endtoend path is alive.
  • Non-blocking publish keeps operations fast (~1-2s for encrypt/decrypt).
  • Account queue is GLOBALLY DISABLED - this was the primary cause of hangs/timeouts.
  • Smart encryption detection (both NIP-04 and NIP-44) and no artificial timeouts make operations instant.
  • Sequential processing is cleaner and more predictable than concurrent hacks.
  • Relay queries now trust EOSE signals instead of arbitrary timeouts, completing in 1-2s instead of 6s.
  • The missing DECRYPT activity in Amber was partially due to requests never being sent (stuck in queue). With queue disabled globally, Amber receives all decrypt requests immediately.
  • Amethyst-style bookmarks require explicit NIP-04 detection (?iv= check) since Helpers.hasHiddenContent() only detects NIP-44.