# Smesh Build Plan One compiler: moxie. Two targets: native (relay), JS (browser). No Google Go toolchain. No npm. No Angular. No TypeScript. No Chrome. Moxie compiles Go to: - Static native binaries (linux/amd64, linux/arm64) via musl + Boehm GC - ES modules (.mjs) for browser via JS backend - Separate WASM module for heavy crypto (secp256k1) Extension: Firefox MV2. Persistent background page. No SW lifecycle. Signer UI: modal inside the sm3sh app. No extension popup. --- ## Part 1 — Relay: moxie compatibility (DONE) The relay compiles and runs under moxie with zero code changes. encoding/json and sync.Mutex work in moxie's stdlib overlay. Moxie fixes applied (in ../moxie): - goenv.go: isSourceDir checks .mx extension - loader/mxlist.go: stdlib vendor resolution (src/vendor/) - x/text/transform/transform.mx: removed fallthrough - x/text/unicode/norm/iter.mx: removed fallthrough - x/net/http2/hpack/huffman.mx: new() → &T{} - x/crypto/cryptobyte/{asn1,builder}.mx: new() → &T{}/&v - compileopts/target.go: futex C files in ExtraFiles - runtime/poll.mx: epoll-based netpoller - runtime/sync.mx: semaphore (SemacquireMutex/Semrelease) - scheduler_cooperative.mx: netpollCheck() in loop - runtime_unix.mx: netpollBlock() in waitForEvents() Build: `MOXIEROOT=../moxie ../moxie/moxie build -o smesh .` Test: relay starts, serves HTTP, handles WebSocket + Nostr REQ/EOSE. --- ## Part 2 — Frontend & signer ### Architecture ``` ┌──────────────────────────────────────────────────────┐ │ smesh relay (moxie → native binary) │ │ Serves static files: *.mjs, *.html, *.css │ │ WebSocket: /ws Blossom: /blossom/ │ └───────────────────────┬──────────────────────────────┘ │ HTTP/WS ┌───────────────────────▼──────────────────────────────┐ │ Browser │ │ │ │ ┌──────────────────────────────────────────────┐ │ │ │ sm3sh app (moxie → .mjs) │ │ │ │ Feed, messages, profiles, relay mgmt │ │ │ │ Signer modal overlay │ │ │ │ Calls window.nostr / window.nostr.smesh │ │ │ └───────┬──────────────────────────────────────┘ │ │ │ BroadcastChannel │ │ ┌───────▼─────────┐ ┌────────────────────────┐ │ │ │ Shell SW (.mjs) │ │ Core SW (.mjs) │ │ │ │ Lifecycle, cache │◄─┤ Relay proxy, IDB cache │ │ │ │ Version monitor │ │ Subscriptions, routing │ │ │ └──────────────────┘ └────────────────────────┘ │ │ │ │ ┌──────────────────────────────────────────────┐ │ │ │ Signer Extension (Firefox MV2) │ │ │ │ │ │ │ │ background page ── signer-bg (.mjs) │ │ │ │ Vault (Argon2id + AES-256-GCM) │ │ │ │ Key store, signing, NIP-04/44 │ │ │ │ Permission evaluation │ │ │ │ Management API (for sm3sh modal) │ │ │ │ │ │ │ │ content-script.js (~60 LOC, bridge) │ │ │ │ injected.js (~120 LOC, window.nostr) │ │ │ │ prompt.js (~100 LOC, shadow DOM) │ │ │ └──────────────────────────────────────────────┘ │ └──────────────────────────────────────────────────────┘ ``` ### Signer modal The signer's full UI lives inside the sm3sh app as a modal overlay. No extension popup. No separate unlock/options/prompt HTML pages. The modal communicates with the extension background via `window.nostr.smesh` (management API exposed by injected.js). Secret keys never leave the extension background. **Within sm3sh**: signing prompts appear in the modal. The app registers a prompt handler via `window.nostr.smesh.onPrompt(cb)`. When the extension needs approval, it sends a PROMPT message and the app shows the modal with the details. **On other sites**: content-script.js injects a shadow DOM prompt (prompt.js). Minimal: backdrop, card, allow/deny buttons. ### Trust boundary The extension background is the trust boundary. ``` TRUSTED (extension background): Secret keys, Argon2id derivation, AES-256-GCM vault, event signing, NIP-04/44, permission evaluation UNTRUSTED (page context): sm3sh app, signer modal, other Nostr apps All communicate via message passing, never see keys ``` ### Firefox MV2 Persistent background page. Loads moxie .mjs once, stays alive. No service worker termination. No READY handshakes. No message queuing for dead peers. No callback ID resurrection. ```json { "manifest_version": 2, "name": "Smesh Signer", "version": "2.0.0", "background": { "page": "bg/background.html", "persistent": true }, "content_scripts": [{ "matches": [""], "js": ["content-script.js"], "run_at": "document_start" }], "web_accessible_resources": ["injected.js", "prompt.js"], "permissions": ["storage", "activeTab"], "browser_specific_settings": { "gecko": { "id": "signer@smesh.lol" } } } ``` If Firefox ever drops MV2, fork the extension API surface. It's a frozen spec, not a moving target. Same approach as Cinnamon/GNOME. --- ## Directory layout ``` smesh/ go.mod # module smesh.lol (relay) main.go # relay entry point pkg/ # relay packages (existing) acl/ # access control blossom/ # file uploads crawler/ # relay discovery errors/ # error types find/ # search grapevine/ # WoT scoring lol/ # logging mode/ # runtime mode neterr/ # network error interface nostr/ # full Nostr protocol stack ec/ # secp256k1, schnorr, bech32, base58 envelope/ # 9 envelope types event/ # create, sign, verify, serialize filter/ # filter matching ... # hex, ints, keys, kind, signer, tag, text, etc. ws/ # WebSocket client relay/ # relay server graph/ # social graph queries pipeline/ # event processing pipeline ratelimit/ # rate limiter server/ # HTTP + WebSocket server store/ # storage engine index/ # sorted index keys serial/ # serial mapping sorted/ # sorted flat file wal/ # write-ahead log sync/negentropy/ # negentropy sync protocol typer/ # type interface web/ common/ # shared Go library for all browser targets go.mod # module common nostr/ # lightweight: event, filter, tags, parse helpers/ # hex, base64, bech32, json crypto/ # browser crypto (calls WASM secp256k1) secp256k1/ # Go API → jsruntime/crypto.mjs → WASM sha256/ # pure Go SHA-256 chacha20/ # ChaCha20 stream cipher hkdf/ # HKDF key derivation hmac/ # HMAC nip04/ # NIP-04 encrypt/decrypt nip44/ # NIP-44 encrypt/decrypt jsbridge/ # Go wrappers for moxie jsruntime dom/ # DOM manipulation via handles ws/ # WebSocket sw/ # Service Worker lifecycle + cache bc/ # BroadcastChannel idb/ # IndexedDB localstorage/ # localStorage registry/ # extension ↔ SW message routing signer/ # window.nostr calls from Go subtle/ # Web Crypto (AES-CBC, random) crypto/ # secp256k1 WASM bridge wasm/ # WASM bootstrap ext/ # NEW: browser.storage, browser.runtime relay/ # browser-side relay client conn.go # single relay WebSocket pool.go # connection pool sub.go # subscription app/ # sm3sh main app (moxie → .mjs) go.mod # requires common main.go # feed, messages, profiles, relay mgmt qr.go # QR code encoder signer.go # NEW: signer modal overlay sw/ # Shell Service Worker (moxie → .mjs) go.mod main.go # lifecycle, static cache, version monitor bus.go # BroadcastChannel to core SW router.go # subscription routing state.go # shared state identity.go # pubkey tracking (no secret key) decrypt.go # DM display records sw-core/ # Core Service Worker (moxie → .mjs) go.mod main.go # lifecycle cache.go # IndexedDB event storage client.go # relay pool / proxy identity.go # key management router.go # subscription dispatcher state.go # DM state, crypto callbacks signer-bg/ # Extension background (moxie → .mjs) go.mod # requires common main.go # entry point, message dispatcher vault.go # Argon2id + AES-256-GCM identity.go # multi-identity management nip07.go # NIP-07 method handler permissions.go # per-host/method permission store management.go # management API for sm3sh modal mls.go # Marmot/MLS (deferred) ext/ # Extension static files (JS, not compiled) manifest.json background.html #