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.
- package/dist/bunker-context.d.ts +42 -0
- package/dist/bunker-context.js +87 -0
- package/dist/cli.js +1 -0
- package/dist/config.js +11 -1
- package/dist/index.d.ts +1 -2
- package/dist/index.js +12 -2
- package/dist/types.d.ts +1 -0
- package/package.json +1 -1
|
@@ -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
|
|
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:
|
|
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
|
-
|
|
22
|
-
|
|
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