# NSec Bunker Page — Architecture & Implementation Plan

## Overview

Transform `www/bunker.html` (currently a copy of `template.html`) into a **NIP-46 remote signer (bunker)** page. The user logs in normally via nostr-login-lite, and the page uses their nsec to run a bunker backend that signs events on behalf of remote client applications.

## Protocol: NIP-46 Remote Signing

NIP-46 defines a protocol where a **signer** (bunker) holds the private key and a **client** (any nostr app) sends signing requests over nostr relays using kind 24133 events encrypted with NIP-44.

### Connection Flow: `bunker://`

```mermaid
sequenceDiagram
    participant U as User
    participant B as Bunker Page
    participant R as Relay
    participant C as Client App

    U->>B: Logs in via nostr-login-lite
    B->>B: Extracts nsec from auth state
    B->>B: Creates NDKPrivateKeySigner
    B->>B: Instantiates NDKNip46Backend
    B->>B: Generates bunker:// URI
    B->>U: Displays URI + QR code
    U-->>C: Copies bunker:// URI to client app
    B->>R: Subscribes to kind 24133 tagged to pubkey
    C->>R: Sends connect request with secret
    R->>B: Delivers connect event
    B->>B: Validates secret, approves
    B->>R: Sends ack response
    Note over C,B: Connection established
    C->>R: sign_event request
    R->>B: Delivers request
    B->>B: Signs with nsec
    B->>R: Sends signed event response
    R->>C: Delivers signed event
```

## Architecture

### Key Decision: NDKNip46Backend over nostr-tools

**Why NDKNip46Backend:**
- Complete bunker server implementation already in `ndk-core.bundle.js`
- Handles all NIP-46 methods: `connect`, `sign_event`, `get_public_key`, `ping`, `nip04_encrypt/decrypt`, `nip44_encrypt/decrypt`
- Strategy pattern for handlers — extensible
- `permitCallback` for authorization decisions
- Uses NDK relay pool management
- Available via `window.NDK.NDKNip46Backend`

**Why not nostr-tools:**
- nostr-tools `nip46` module only has the **client side** — `BunkerSigner` connects *to* a bunker
- Building a bunker server from scratch with nostr-tools would require reimplementing the entire NIP-46 RPC protocol

### Dual NDK Architecture

The page uses **two separate NDK contexts**:

1. **SharedWorker NDK** (existing) — handles normal page operations via `init-ndk.mjs`, relay UI, blossom, etc.
2. **Standalone NDK instance** (new) — created directly in the page for `NDKNip46Backend`. This is necessary because the backend needs direct access to subscribe/publish, not through the worker message bridge.

```mermaid
flowchart TB
    subgraph Page[bunker.html]
        Auth[nostr-login-lite auth]
        Worker[SharedWorker NDK - page ops]
        BunkerNDK[Standalone NDK - bunker backend]
        Backend[NDKNip46Backend]
        Signer[NDKPrivateKeySigner - nsec]
        UI[Bunker UI - URI, QR, log]
    end

    Auth -->|nsec| Signer
    Auth -->|pubkey| Worker
    Signer --> Backend
    BunkerNDK --> Backend
    Backend -->|kind 24133| Relays[Nostr Relays]
    Relays -->|sign requests| Backend
    Backend -->|signed responses| Relays
    Worker -->|relay list| BunkerNDK
```

## Implementation Details

### 1. Script Loading

Add `ndk-core.bundle.js` to the page before `nostr-lite.js`:

```html
<script src="./nostr.bundle.js"></script>
<script src="./ndk-core.bundle.js"></script>
<script src="./nostr-lite.js"></script>
```

This makes `window.NDK` available with all exports including:
- `NDK` (default) — the main NDK class
- `NDKNip46Backend` — the bunker backend
- `NDKPrivateKeySigner` — signer from nsec
- `NDKNostrRpc` — RPC layer

### 2. Extracting the nsec

After authentication via nostr-login-lite, the secret key is stored in localStorage under key `nostr_login_lite_auth` as `authState.secret` in nsec or hex format.

```javascript
function getSecretKey() {
    const stored = localStorage.getItem('nostr_login_lite_auth');
    if (!stored) return null;
    const authState = JSON.parse(stored);
    if (authState.method !== 'local' || !authState.secret) return null;
    return authState.secret; // nsec1... or hex string
}
```

**Important:** The bunker only works when logged in via the "local" method (nsec/hex key). Extension and NIP-46 auth methods don't expose the secret key.

### 3. Standalone NDK Instance

```javascript
const { default: NDKClass, NDKPrivateKeySigner, NDKNip46Backend } = window.NDK;

// Get relay URLs from the SharedWorker NDK
const relayData = await getRelayData();
const relayUrls = relayData.relays.map(r => r.url);

// Create standalone NDK for the bunker
const bunkerNdk = new NDKClass({
    explicitRelayUrls: relayUrls
});
await bunkerNdk.connect();
```

### 4. NDKNip46Backend Setup

```javascript
const signer = new NDKPrivateKeySigner(secretKey);
const backend = new NDKNip46Backend(bunkerNdk, signer, async (params) => {
    // Auto-approve all requests, log them to UI
    logRequest(params);
    return true;
});
await backend.start();
```

### 5. Generating the bunker:// URI

```javascript
const user = await signer.user();
const secret = crypto.getRandomValues(new Uint8Array(32));
const secretHex = Array.from(secret).map(b => b.toString(16).padStart(2, '0')).join('');

const bunkerUrl = new URL(`bunker://${user.pubkey}`);
relayUrls.forEach(r => bunkerUrl.searchParams.append('relay', r));
bunkerUrl.searchParams.set('secret', secretHex);

const bunkerUri = bunkerUrl.toString();
```

### 6. UI Layout

The `divBody` section will contain:

- **Status indicator** — bunker running/stopped, connected clients count
- **bunker:// URI display** — copyable text field
- **QR code** — scannable QR of the bunker:// URI (using existing `qrcode-generator.min.js`)
- **Request log** — scrollable list showing incoming requests with timestamp, method, remote pubkey, and result
- **Start/Stop button** — toggle the bunker on/off

### 7. Lifecycle Management

- **Page load:** Auth → extract nsec → start bunker → display URI
- **Page close/navigate away:** `beforeunload` event to clean up subscriptions
- **Stop button:** Tear down backend, clear URI display
- **Auth requirement:** Must be logged in via "local" method. Show warning if using extension/NIP-46 auth since nsec is not available.

## Security Considerations

- The nsec is already in localStorage (nostr-login-lite stores it in plaintext for the "local" method). The bunker page does not introduce new storage of the key.
- The bunker:// secret is a one-time random value generated per session.
- Auto-approve means any client with the bunker:// URI can sign events. The user controls access by sharing the URI selectively.
- The page should warn users that the bunker is active and signing requests while the tab is open.

## Files Modified

| File | Changes |
|------|---------|
| `www/bunker.html` | Full page implementation: add ndk-core.bundle.js, bunker UI, backend logic |

No new JS modules needed — all logic fits in the page's inline script, using `window.NDK` and `window.NostrTools`.
