# ADR-024: Canonical Nostr Identity Location **Status:** Accepted **Date:** 2026-03-23 **Issue:** #1223 **Refs:** #1210 (duplicate-work audit), ROADMAP.md Phase 2 --- ## Context Nostr identity logic has been independently implemented in at least three repos (`replit/timmy-tower`, `replit/token-gated-economy`, `rockachopa/Timmy-time-dashboard`), each building keypair generation, event publishing, and NIP-07 browser-extension auth in isolation. This duplication causes: - Bug fixes applied in one repo but silently missed in others. - Diverging implementations of the same NIPs (NIP-01, NIP-07, NIP-44). - Agent time wasted re-implementing logic that already exists. ROADMAP.md Phase 2 already names `timmy-nostr` as the planned home for Nostr infrastructure. This ADR makes that decision explicit and prescribes how other repos consume it. --- ## Decision **The canonical home for all Nostr identity logic is `rockachopa/timmy-nostr`.** All other repos (`Timmy-time-dashboard`, `timmy-tower`, `token-gated-economy`) become consumers, not implementers, of Nostr identity primitives. ### What lives in `timmy-nostr` | Module | Responsibility | |--------|---------------| | `nostr_id/keypair.py` | Keypair generation, nsec/npub encoding, encrypted storage | | `nostr_id/identity.py` | Agent identity lifecycle (NIP-01 kind:0 profile events) | | `nostr_id/auth.py` | NIP-07 browser-extension signer; NIP-42 relay auth | | `nostr_id/event.py` | Event construction, signing, serialisation (NIP-01) | | `nostr_id/crypto.py` | NIP-44 encryption (XChaCha20-Poly1305 v2) | | `nostr_id/nip05.py` | DNS-based identifier verification | | `nostr_id/relay.py` | WebSocket relay client (publish / subscribe) | ### What does NOT live in `timmy-nostr` - Business logic that combines Nostr with application-specific concepts (e.g. "publish a task-completion event" lives in the application layer that calls `timmy-nostr`). - Reputation scoring algorithms (depends on application policy). - Dashboard UI components. --- ## How Other Repos Reference `timmy-nostr` ### Python repos (`Timmy-time-dashboard`, `timmy-tower`) Add to `pyproject.toml` dependencies: ```toml [tool.poetry.dependencies] timmy-nostr = {git = "https://gitea.hermes.local/rockachopa/timmy-nostr.git", tag = "v0.1.0"} ``` Import pattern: ```python from nostr_id.keypair import generate_keypair, load_keypair from nostr_id.event import build_event, sign_event from nostr_id.relay import NostrRelayClient ``` ### JavaScript/TypeScript repos (`token-gated-economy` frontend) Add to `package.json` (once published or via local path): ```json "dependencies": { "timmy-nostr": "rockachopa/timmy-nostr#v0.1.0" } ``` Import pattern: ```typescript import { generateKeypair, signEvent } from 'timmy-nostr'; ``` Until `timmy-nostr` publishes a JS package, use NIP-07 browser extension directly and delegate all key-management to the browser signer — never re-implement crypto in JS without the shared library. --- ## Migration Plan Existing duplicated code should be migrated in this order: 1. **Keypair generation** — highest duplication, clearest interface. 2. **NIP-01 event construction/signing** — used by all three repos. 3. **NIP-07 browser auth** — currently in `timmy-tower` and `token-gated-economy`. 4. **NIP-44 encryption** — lowest priority, least duplicated. Each step: implement in `timmy-nostr` → cut over one repo → delete the duplicate → repeat. --- ## Interface Contract `timmy-nostr` must expose a stable public API: ```python # Keypair keypair = generate_keypair() # -> NostrKeypair(nsec, npub, privkey_bytes, pubkey_bytes) keypair = load_keypair(encrypted_nsec, secret_key) # Events event = build_event(kind=0, content=profile_json, keypair=keypair) event = sign_event(event, keypair) # attaches .id and .sig # Relay async with NostrRelayClient(url) as relay: await relay.publish(event) async for msg in relay.subscribe(filters): ... ``` Breaking changes to this interface require a semver major bump and a migration note in `timmy-nostr`'s CHANGELOG. --- ## Consequences - **Positive:** Bug fixes in cryptographic or protocol code propagate to all repos via a version bump. - **Positive:** New NIPs are implemented once and adopted everywhere. - **Negative:** Adds a cross-repo dependency; version pinning discipline required. - **Negative:** `timmy-nostr` must be stood up and tagged before any migration can begin. --- ## Action Items - [ ] Create `rockachopa/timmy-nostr` repo with the module structure above. - [ ] Implement keypair generation + NIP-01 signing as v0.1.0. - [ ] Replace `Timmy-time-dashboard` inline Nostr code (if any) with `timmy-nostr` import once v0.1.0 is tagged. - [ ] Add `src/infrastructure/clients/nostr_client.py` as the thin application-layer wrapper (see ROADMAP.md §2.6). - [ ] File issues in `timmy-tower` and `token-gated-economy` to migrate their duplicate implementations.