nostr-bray 1.14.0 → 1.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,42 @@
1
+ /**
2
+ * BunkerContext — an IdentityContext-compatible interface backed by a NIP-46 bunker.
3
+ *
4
+ * Instead of holding the secret key locally, all signing is delegated to a
5
+ * remote bunker via encrypted Nostr relay messages.
6
+ *
7
+ * Usage:
8
+ * BUNKER_URI=bunker://pk?relay=wss://...
9
+ * or
10
+ * BUNKER_URI=bunker://pk?relay=wss://...&secret=clienthex
11
+ */
12
+ import type { PublicIdentity, SignFn } from './types.js';
13
+ export interface BunkerConfig {
14
+ pubkey: string;
15
+ relay: string;
16
+ secret?: string;
17
+ }
18
+ /** Parse a bunker:// URI */
19
+ export declare function parseBunkerUri(uri: string): BunkerConfig;
20
+ export declare class BunkerContext {
21
+ private signer;
22
+ private pool;
23
+ private pubkeyHex;
24
+ private clientSk;
25
+ private constructor();
26
+ /** Connect to a remote bunker. Blocks until the connection is established. */
27
+ static connect(uri: string, timeoutMs?: number): Promise<BunkerContext>;
28
+ /** The remote identity's npub */
29
+ get activeNpub(): string;
30
+ /** The remote identity's hex pubkey */
31
+ get activePublicKeyHex(): string;
32
+ /** Sign an event via the remote bunker */
33
+ getSigningFunction(): SignFn;
34
+ /** List identities — bunker mode only has one (the remote key) */
35
+ listIdentities(): PublicIdentity[];
36
+ /** NIP-44 encrypt via the remote bunker */
37
+ nip44Encrypt(recipientPubkey: string, plaintext: string): Promise<string>;
38
+ /** NIP-44 decrypt via the remote bunker */
39
+ nip44Decrypt(senderPubkey: string, ciphertext: string): Promise<string>;
40
+ /** Clean up */
41
+ destroy(): void;
42
+ }
@@ -0,0 +1,87 @@
1
+ /**
2
+ * BunkerContext — an IdentityContext-compatible interface backed by a NIP-46 bunker.
3
+ *
4
+ * Instead of holding the secret key locally, all signing is delegated to a
5
+ * remote bunker via encrypted Nostr relay messages.
6
+ *
7
+ * Usage:
8
+ * BUNKER_URI=bunker://pk?relay=wss://...
9
+ * or
10
+ * BUNKER_URI=bunker://pk?relay=wss://...&secret=clienthex
11
+ */
12
+ import { BunkerSigner } from 'nostr-tools/nip46';
13
+ import { useWebSocketImplementation, SimplePool } from 'nostr-tools/pool';
14
+ import { generateSecretKey } from 'nostr-tools/pure';
15
+ import { npubEncode } from 'nostr-tools/nip19';
16
+ import WebSocket from 'ws';
17
+ useWebSocketImplementation(WebSocket);
18
+ /** Parse a bunker:// URI */
19
+ export function parseBunkerUri(uri) {
20
+ // bunker://<pubkey>?relay=<url>&secret=<hex>
21
+ const url = new URL(uri);
22
+ const pubkey = url.hostname || url.pathname.replace('//', '');
23
+ const relay = url.searchParams.get('relay');
24
+ const secret = url.searchParams.get('secret') ?? undefined;
25
+ if (!pubkey || !relay) {
26
+ throw new Error('Invalid bunker URI: missing pubkey or relay');
27
+ }
28
+ return { pubkey, relay, secret };
29
+ }
30
+ export class BunkerContext {
31
+ signer;
32
+ pool;
33
+ pubkeyHex;
34
+ clientSk;
35
+ constructor(signer, pool, clientSk) {
36
+ this.signer = signer;
37
+ this.pool = pool;
38
+ this.clientSk = clientSk;
39
+ }
40
+ /** Connect to a remote bunker. Blocks until the connection is established. */
41
+ static async connect(uri, timeoutMs = 15_000) {
42
+ const config = parseBunkerUri(uri);
43
+ const clientSk = config.secret
44
+ ? Buffer.from(config.secret, 'hex')
45
+ : generateSecretKey();
46
+ const pool = new SimplePool();
47
+ const signer = BunkerSigner.fromBunker(clientSk, { pubkey: config.pubkey, relays: [config.relay], secret: null }, { pool });
48
+ // Connect and verify
49
+ await signer.connect();
50
+ await signer.ping();
51
+ const ctx = new BunkerContext(signer, pool, clientSk);
52
+ ctx.pubkeyHex = await signer.getPublicKey();
53
+ return ctx;
54
+ }
55
+ /** The remote identity's npub */
56
+ get activeNpub() {
57
+ return npubEncode(this.pubkeyHex);
58
+ }
59
+ /** The remote identity's hex pubkey */
60
+ get activePublicKeyHex() {
61
+ return this.pubkeyHex;
62
+ }
63
+ /** Sign an event via the remote bunker */
64
+ getSigningFunction() {
65
+ return async (template) => {
66
+ return this.signer.signEvent(template);
67
+ };
68
+ }
69
+ /** List identities — bunker mode only has one (the remote key) */
70
+ listIdentities() {
71
+ return [{ npub: this.activeNpub, purpose: 'bunker', index: 0 }];
72
+ }
73
+ /** NIP-44 encrypt via the remote bunker */
74
+ async nip44Encrypt(recipientPubkey, plaintext) {
75
+ return this.signer.nip44Encrypt(recipientPubkey, plaintext);
76
+ }
77
+ /** NIP-44 decrypt via the remote bunker */
78
+ async nip44Decrypt(senderPubkey, ciphertext) {
79
+ return this.signer.nip44Decrypt(senderPubkey, ciphertext);
80
+ }
81
+ /** Clean up */
82
+ destroy() {
83
+ this.signer.close();
84
+ this.pool.destroy();
85
+ this.clientSk.fill(0);
86
+ }
87
+ }
package/dist/cli.js CHANGED
@@ -178,6 +178,7 @@ Modes:
178
178
  Environment:
179
179
  NOSTR_SECRET_KEY nsec, hex, or BIP-39 mnemonic
180
180
  NOSTR_SECRET_KEY_FILE Path to secret key file
181
+ BUNKER_URI / BUNKER_URI_FILE bunker:// URI (use INSTEAD of secret key)
181
182
  NOSTR_RELAYS Comma-separated relay URLs
182
183
  NWC_URI / NWC_URI_FILE Nostr Wallet Connect URI
183
184
  TOR_PROXY SOCKS5h proxy URL
package/dist/config.js CHANGED
@@ -36,14 +36,21 @@ export function loadConfig() {
36
36
  const keyFilePath = process.env.NOSTR_SECRET_KEY_FILE;
37
37
  const keyEnvVar = process.env.NOSTR_SECRET_KEY;
38
38
  let secretKey;
39
+ const bunkerUri = process.env.BUNKER_URI ?? process.env.BUNKER_URI_FILE
40
+ ? (process.env.BUNKER_URI_FILE ? readSecretFile(process.env.BUNKER_URI_FILE) : process.env.BUNKER_URI)
41
+ : undefined;
39
42
  if (keyFilePath) {
40
43
  secretKey = readSecretFile(keyFilePath);
41
44
  }
42
45
  else if (keyEnvVar) {
43
46
  secretKey = keyEnvVar;
44
47
  }
48
+ else if (bunkerUri) {
49
+ // Bunker mode — no local secret needed
50
+ secretKey = '';
51
+ }
45
52
  else {
46
- throw new Error('No secret key provided: set NOSTR_SECRET_KEY or NOSTR_SECRET_KEY_FILE');
53
+ throw new Error('No secret key provided: set NOSTR_SECRET_KEY, NOSTR_SECRET_KEY_FILE, or BUNKER_URI');
47
54
  }
48
55
  const secretFormat = detectKeyFormat(secretKey);
49
56
  // --- NWC URI ---
@@ -72,10 +79,13 @@ export function loadConfig() {
72
79
  delete process.env.NOSTR_SECRET_KEY_FILE;
73
80
  delete process.env.NWC_URI;
74
81
  delete process.env.NWC_URI_FILE;
82
+ delete process.env.BUNKER_URI;
83
+ delete process.env.BUNKER_URI_FILE;
75
84
  return {
76
85
  secretKey,
77
86
  secretFormat,
78
87
  relays,
88
+ bunkerUri: bunkerUri ?? undefined,
79
89
  nwcUri,
80
90
  torProxy,
81
91
  allowClearnetWithTor,
package/dist/index.d.ts CHANGED
@@ -1,9 +1,8 @@
1
1
  #!/usr/bin/env node
2
- import { IdentityContext } from './context.js';
3
2
  import { RelayPool } from './relay-pool.js';
4
3
  import { Nip65Manager } from './nip65.js';
5
4
  export declare const deps: {
6
- ctx: IdentityContext;
5
+ ctx: any;
7
6
  pool: RelayPool;
8
7
  nip65: Nip65Manager;
9
8
  nwcUri: string | undefined;
package/dist/index.js CHANGED
@@ -18,10 +18,20 @@ const pool = new RelayPool({
18
18
  defaultRelays: config.relays,
19
19
  });
20
20
  const nip65 = new Nip65Manager(pool, config.relays);
21
- const ctx = new IdentityContext(config.secretKey, config.secretFormat);
22
- export const deps = { ctx, pool, nip65, nwcUri: config.nwcUri };
21
+ // Connect to bunker or use local key
22
+ let ctx;
23
+ if (config.bunkerUri) {
24
+ const { BunkerContext } = await import('./bunker-context.js');
25
+ ctx = await BunkerContext.connect(config.bunkerUri);
26
+ console.error(`Connected to bunker — signing as ${ctx.activeNpub}`);
27
+ }
28
+ else {
29
+ ctx = new IdentityContext(config.secretKey, config.secretFormat);
30
+ }
31
+ export const deps = { ctx: ctx, pool, nip65, nwcUri: config.nwcUri };
23
32
  config.secretKey = '';
24
33
  config.nwcUri = undefined;
34
+ config.bunkerUri = undefined;
25
35
  // Load master identity relay list
26
36
  const masterRelays = await nip65.loadForIdentity(ctx.activeNpub);
27
37
  pool.reconfigure(ctx.activeNpub, masterRelays);
package/dist/types.d.ts CHANGED
@@ -31,6 +31,7 @@ export interface BrayConfig {
31
31
  readonly secretKey: string;
32
32
  readonly secretFormat: 'nsec' | 'hex' | 'mnemonic';
33
33
  readonly relays: string[];
34
+ readonly bunkerUri?: string;
34
35
  readonly nwcUri?: string;
35
36
  readonly torProxy?: string;
36
37
  readonly allowClearnetWithTor: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nostr-bray",
3
- "version": "1.14.0",
3
+ "version": "1.16.0",
4
4
  "description": "MCP server giving AI agents sovereign Nostr identities — key derivation, personas, attestations, duress detection, zaps",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",