tenex-eventd NIP-46 bunker spec
tenex-eventd NIP-46 bunker spec
Goal
Turn tenex-eventd into a remote signer that keeps the secret in local operator custody while exposing a shareable bunker:// URI to remote clients. The URI, not the secret key, is what remote sessions receive.
Current design
- Secret source:
TENEX_EVENTD_SECRET_KEY_HEX/NOSTR_SECRET_KEYfromsecret.env. - Public identity metadata stays in
nostr-identity.json. - Startup writes a local
bunker://<pubkey>?relay=...&secret=...URI to~/.config/tenex-eventd/bunker.uriwith0600permissions. - The URI is intentionally local-only; it is not published to relays.
Transport
- Incoming requests are kind
24133events tagged withp=<bunker-pubkey>. - The daemon polls relays directly for bunker requests.
- Responses are returned as kind
24133events encrypted back to the client. - Relay set currently matches the tenex relay set:
wss://relay.damus.iowss://nos.lolwss://relay.primal.net
Read / write path
- Read path tries Primal HTTP cache first for mentions/profile/thread reads.
- Because Primal currently returns
500for this identity, the daemon falls back to direct relay queries. - Write path signs locally and publishes directly to relays when
alby_tokenis unset.
Supported bunker RPC methods
connectget_public_keysign_eventnip44_encryptnip44_decryptget_relaysping
Permission model
connectrequires the shared secret embedded in the local bunker URI.sign_eventis allowlisted to kinds1and30023.- Initial intent is notes + long-form articles only.
- Unsupported or disallowed methods return an error and are not signed.
Runtime behavior
- Mention polling remains on the slower normal interval (
poll_interval = 60). - Bunker traffic is handled on a faster dedicated cadence (
bunker.poll_interval = 5). - This keeps remote-signing latency reasonable without forcing the whole daemon into a 5-second Primal loop.
Verification
Fact: local unit tests pass for connect/auth success, wrong-secret rejection, and disallowed-kind rejection.
Fact: live round-trip verification against the launchd daemon passed using the generated local bunker URI:
connectget_public_keysign_eventfor kind1nip44_encryptnip44_decrypt- rejection of disallowed kind
4
Known gaps
- No persisted per-client ACLs yet; authorization is currently shared-secret based.
- No session expiry / rotation policy yet beyond bunker URI regeneration.
- If
bunker.connect_secretis left empty, a new secret is generated on restart; copied old bunker URIs stop working. - No
switch_relaysflow yet. - No bunker-specific rate limiting yet.
- The Primal
500path is still degraded, so direct relay fallback is operationally required today.
Recommendation
Use this bunker mode as the immediate secure handoff layer. Remote sessions get a bunker:// URI, not an nsec. Keep the current scope narrow, then add ACL persistence, URI/secret lifecycle policy, and relay-management UX before treating this as general-purpose signing infrastructure.