nostr-bray 1.26.0 → 1.27.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 +2 -0
- package/dist/bunker-context.js +17 -3
- package/dist/bunker.d.ts +1 -0
- package/dist/bunker.js +24 -6
- package/dist/nip17-wrap.d.ts +43 -0
- package/dist/nip17-wrap.js +91 -0
- package/dist/social/dm.js +25 -10
- package/dist/state.d.ts +4 -0
- package/dist/state.js +23 -0
- package/dist/trust/handlers.js +2 -2
- package/package.json +1 -1
package/dist/bunker-context.d.ts
CHANGED
|
@@ -20,6 +20,8 @@ export interface BunkerConfig {
|
|
|
20
20
|
}
|
|
21
21
|
/** Parse a bunker:// URI */
|
|
22
22
|
export declare function parseBunkerUri(uri: string): BunkerConfig;
|
|
23
|
+
/** Resolve the client secret key: URI secret > cached > generate & persist. */
|
|
24
|
+
export declare function resolveClientKey(config: BunkerConfig, stateDir?: string): Uint8Array;
|
|
23
25
|
export declare class BunkerContext implements SigningContext {
|
|
24
26
|
protected signer: BunkerSigner;
|
|
25
27
|
protected pool: SimplePool;
|
package/dist/bunker-context.js
CHANGED
|
@@ -14,6 +14,7 @@ import { useWebSocketImplementation, SimplePool } from 'nostr-tools/pool';
|
|
|
14
14
|
import { generateSecretKey } from 'nostr-tools/pure';
|
|
15
15
|
import { npubEncode } from 'nostr-tools/nip19';
|
|
16
16
|
import WebSocket from 'ws';
|
|
17
|
+
import { readStateFile, writeStateFile } from './state.js';
|
|
17
18
|
useWebSocketImplementation(WebSocket);
|
|
18
19
|
/** Parse a bunker:// URI */
|
|
19
20
|
export function parseBunkerUri(uri) {
|
|
@@ -27,6 +28,21 @@ export function parseBunkerUri(uri) {
|
|
|
27
28
|
}
|
|
28
29
|
return { pubkey, relays, secret };
|
|
29
30
|
}
|
|
31
|
+
const CLIENT_KEYS_FILE = 'client-keys.json';
|
|
32
|
+
/** Resolve the client secret key: URI secret > cached > generate & persist. */
|
|
33
|
+
export function resolveClientKey(config, stateDir) {
|
|
34
|
+
if (config.secret) {
|
|
35
|
+
return Buffer.from(config.secret, 'hex');
|
|
36
|
+
}
|
|
37
|
+
const cache = readStateFile(CLIENT_KEYS_FILE, stateDir);
|
|
38
|
+
if (cache[config.pubkey]) {
|
|
39
|
+
return Buffer.from(cache[config.pubkey], 'hex');
|
|
40
|
+
}
|
|
41
|
+
const sk = generateSecretKey();
|
|
42
|
+
cache[config.pubkey] = Buffer.from(sk).toString('hex');
|
|
43
|
+
writeStateFile(CLIENT_KEYS_FILE, cache, stateDir);
|
|
44
|
+
return sk;
|
|
45
|
+
}
|
|
30
46
|
export class BunkerContext {
|
|
31
47
|
signer;
|
|
32
48
|
pool;
|
|
@@ -40,9 +56,7 @@ export class BunkerContext {
|
|
|
40
56
|
/** Connect to a remote bunker. Blocks until the connection is established. */
|
|
41
57
|
static async connect(uri, timeoutMs = 15_000) {
|
|
42
58
|
const config = parseBunkerUri(uri);
|
|
43
|
-
const clientSk = config
|
|
44
|
-
? Buffer.from(config.secret, 'hex')
|
|
45
|
-
: generateSecretKey();
|
|
59
|
+
const clientSk = resolveClientKey(config);
|
|
46
60
|
const pool = new SimplePool();
|
|
47
61
|
const signer = BunkerSigner.fromBunker(clientSk, { pubkey: config.pubkey, relays: config.relays, secret: null }, { pool });
|
|
48
62
|
// Connect and verify
|
package/dist/bunker.d.ts
CHANGED
package/dist/bunker.js
CHANGED
|
@@ -14,17 +14,26 @@ import { finalizeEvent, getPublicKey, generateSecretKey } from 'nostr-tools/pure
|
|
|
14
14
|
import { npubEncode } from 'nostr-tools/nip19';
|
|
15
15
|
import { useWebSocketImplementation, SimplePool } from 'nostr-tools/pool';
|
|
16
16
|
import WebSocket from 'ws';
|
|
17
|
+
import { readStateFile, writeStateFile } from './state.js';
|
|
17
18
|
useWebSocketImplementation(WebSocket);
|
|
18
19
|
export function startBunker(opts) {
|
|
19
20
|
const { ctx, relays, quiet } = opts;
|
|
20
|
-
const authorizedKeys = new Set(opts.authorizedKeys ?? []);
|
|
21
21
|
const log = quiet ? () => { } : (...args) => console.error('[bunker]', ...args);
|
|
22
|
-
//
|
|
22
|
+
// Bunker keypair — must be computed first so we can load persisted approvals
|
|
23
23
|
const bunkerSk = opts.bunkerKeyHex
|
|
24
24
|
? Buffer.from(opts.bunkerKeyHex, 'hex')
|
|
25
25
|
: generateSecretKey();
|
|
26
26
|
const bunkerPk = getPublicKey(bunkerSk);
|
|
27
27
|
const bunkerNpub = npubEncode(bunkerPk);
|
|
28
|
+
// Authorised clients: CLI flag controls the gate, persisted approvals supplement
|
|
29
|
+
const APPROVALS_FILE = 'approved-clients.json';
|
|
30
|
+
const persisted = readStateFile(APPROVALS_FILE, opts.stateDir);
|
|
31
|
+
const bunkerApprovals = persisted[bunkerPk] ?? [];
|
|
32
|
+
const cliAuthorizedKeys = new Set(opts.authorizedKeys ?? []);
|
|
33
|
+
const authorizedKeys = new Set([
|
|
34
|
+
...cliAuthorizedKeys,
|
|
35
|
+
...bunkerApprovals,
|
|
36
|
+
]);
|
|
28
37
|
const pool = new SimplePool();
|
|
29
38
|
// Subscribe to kind 24133 events addressed to us
|
|
30
39
|
const sub = pool.subscribeMany(relays, { kinds: [24133], '#p': [bunkerPk] }, {
|
|
@@ -39,8 +48,8 @@ export function startBunker(opts) {
|
|
|
39
48
|
});
|
|
40
49
|
async function handleRequest(event) {
|
|
41
50
|
const clientPk = event.pubkey;
|
|
42
|
-
// Check authorization
|
|
43
|
-
if (
|
|
51
|
+
// Check authorization — gate only activates when CLI --authorized-keys were set
|
|
52
|
+
if (cliAuthorizedKeys.size > 0 && !authorizedKeys.has(clientPk)) {
|
|
44
53
|
log(`Rejected request from unauthorized key: ${clientPk.slice(0, 12)}...`);
|
|
45
54
|
return;
|
|
46
55
|
}
|
|
@@ -60,6 +69,15 @@ export function startBunker(opts) {
|
|
|
60
69
|
let error;
|
|
61
70
|
switch (request.method) {
|
|
62
71
|
case 'connect':
|
|
72
|
+
// Persist newly-approved client
|
|
73
|
+
if (!bunkerApprovals.includes(clientPk)) {
|
|
74
|
+
bunkerApprovals.push(clientPk);
|
|
75
|
+
authorizedKeys.add(clientPk);
|
|
76
|
+
const current = readStateFile(APPROVALS_FILE, opts.stateDir);
|
|
77
|
+
current[bunkerPk] = bunkerApprovals;
|
|
78
|
+
writeStateFile(APPROVALS_FILE, current, opts.stateDir);
|
|
79
|
+
log(`Approved and persisted client: ${clientPk.slice(0, 12)}...`);
|
|
80
|
+
}
|
|
63
81
|
result = 'ack';
|
|
64
82
|
break;
|
|
65
83
|
case 'ping':
|
|
@@ -188,8 +206,8 @@ export function startBunker(opts) {
|
|
|
188
206
|
log(`Bunker started: ${bunkerNpub}`);
|
|
189
207
|
log(`URI: ${bunkerUrl}`);
|
|
190
208
|
log(`Signing as: ${ctx.activeNpub}`);
|
|
191
|
-
if (
|
|
192
|
-
log(`Authorized keys: ${authorizedKeys.size}`);
|
|
209
|
+
if (cliAuthorizedKeys.size > 0) {
|
|
210
|
+
log(`Authorized keys: ${authorizedKeys.size} (${cliAuthorizedKeys.size} CLI + ${bunkerApprovals.length} persisted)`);
|
|
193
211
|
}
|
|
194
212
|
else {
|
|
195
213
|
log('WARNING: No authorized keys set — accepting requests from anyone');
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NIP-17 gift-wrap helpers that work with any SigningContext (local keys OR remote bunker).
|
|
3
|
+
*
|
|
4
|
+
* nostr-tools' wrapEvent/unwrapEvent require raw private key bytes, which are
|
|
5
|
+
* unavailable when signing is delegated to a NIP-46 bunker. These helpers
|
|
6
|
+
* reimplement the NIP-59 gift-wrap flow using the SigningContext interface
|
|
7
|
+
* (nip44Encrypt, nip44Decrypt, getSigningFunction) so that both local-key
|
|
8
|
+
* and bunker modes work identically.
|
|
9
|
+
*/
|
|
10
|
+
import type { Event as NostrEvent } from 'nostr-tools';
|
|
11
|
+
import type { SigningContext } from './signing-context.js';
|
|
12
|
+
interface Recipient {
|
|
13
|
+
publicKey: string;
|
|
14
|
+
relayUrl?: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Wrap a DM as a NIP-17 gift-wrapped event using the SigningContext.
|
|
18
|
+
*
|
|
19
|
+
* Flow (NIP-59):
|
|
20
|
+
* 1. Build unsigned rumor (kind 14) with sender pubkey + message
|
|
21
|
+
* 2. Create seal (kind 13): NIP-44 encrypt rumor with sender key, sign with sender key
|
|
22
|
+
* 3. Create gift wrap (kind 1059): NIP-44 encrypt seal with random ephemeral key
|
|
23
|
+
*
|
|
24
|
+
* The sender's private key is never accessed directly -- all crypto operations
|
|
25
|
+
* go through ctx.nip44Encrypt() and ctx.getSigningFunction(), which delegate
|
|
26
|
+
* to the bunker in NIP-46 mode.
|
|
27
|
+
*/
|
|
28
|
+
export declare function wrapEventAsync(ctx: SigningContext, recipient: Recipient, message: string): Promise<NostrEvent>;
|
|
29
|
+
/**
|
|
30
|
+
* Unwrap a NIP-17 gift-wrapped event using the SigningContext.
|
|
31
|
+
*
|
|
32
|
+
* Flow:
|
|
33
|
+
* 1. Decrypt the gift wrap (kind 1059) content to get the seal
|
|
34
|
+
* 2. Decrypt the seal (kind 13) content to get the rumor
|
|
35
|
+
* 3. Return the rumor (the actual DM content)
|
|
36
|
+
*/
|
|
37
|
+
export declare function unwrapEventAsync(ctx: SigningContext, wrap: NostrEvent): Promise<{
|
|
38
|
+
pubkey: string;
|
|
39
|
+
content: string;
|
|
40
|
+
created_at: number;
|
|
41
|
+
tags: string[][];
|
|
42
|
+
}>;
|
|
43
|
+
export {};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NIP-17 gift-wrap helpers that work with any SigningContext (local keys OR remote bunker).
|
|
3
|
+
*
|
|
4
|
+
* nostr-tools' wrapEvent/unwrapEvent require raw private key bytes, which are
|
|
5
|
+
* unavailable when signing is delegated to a NIP-46 bunker. These helpers
|
|
6
|
+
* reimplement the NIP-59 gift-wrap flow using the SigningContext interface
|
|
7
|
+
* (nip44Encrypt, nip44Decrypt, getSigningFunction) so that both local-key
|
|
8
|
+
* and bunker modes work identically.
|
|
9
|
+
*/
|
|
10
|
+
import { generateSecretKey, finalizeEvent, getEventHash } from 'nostr-tools/pure';
|
|
11
|
+
const TWO_DAYS = 2 * 24 * 60 * 60;
|
|
12
|
+
const now = () => Math.round(Date.now() / 1000);
|
|
13
|
+
const randomNow = () => Math.round(now() - Math.random() * TWO_DAYS);
|
|
14
|
+
/** Build the unsigned kind-14 rumor (inner DM event). */
|
|
15
|
+
function createRumor(senderPubkeyHex, recipient, message) {
|
|
16
|
+
const recipients = Array.isArray(recipient) ? recipient : [recipient];
|
|
17
|
+
const tags = [];
|
|
18
|
+
for (const r of recipients) {
|
|
19
|
+
tags.push(r.relayUrl ? ['p', r.publicKey, r.relayUrl] : ['p', r.publicKey]);
|
|
20
|
+
}
|
|
21
|
+
const rumor = {
|
|
22
|
+
created_at: Math.ceil(Date.now() / 1000),
|
|
23
|
+
kind: 14,
|
|
24
|
+
tags,
|
|
25
|
+
content: message,
|
|
26
|
+
pubkey: senderPubkeyHex,
|
|
27
|
+
};
|
|
28
|
+
rumor.id = getEventHash(rumor);
|
|
29
|
+
return rumor;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Wrap a DM as a NIP-17 gift-wrapped event using the SigningContext.
|
|
33
|
+
*
|
|
34
|
+
* Flow (NIP-59):
|
|
35
|
+
* 1. Build unsigned rumor (kind 14) with sender pubkey + message
|
|
36
|
+
* 2. Create seal (kind 13): NIP-44 encrypt rumor with sender key, sign with sender key
|
|
37
|
+
* 3. Create gift wrap (kind 1059): NIP-44 encrypt seal with random ephemeral key
|
|
38
|
+
*
|
|
39
|
+
* The sender's private key is never accessed directly -- all crypto operations
|
|
40
|
+
* go through ctx.nip44Encrypt() and ctx.getSigningFunction(), which delegate
|
|
41
|
+
* to the bunker in NIP-46 mode.
|
|
42
|
+
*/
|
|
43
|
+
export async function wrapEventAsync(ctx, recipient, message) {
|
|
44
|
+
const senderPubkeyHex = ctx.activePublicKeyHex;
|
|
45
|
+
// 1. Build the rumor (unsigned kind-14)
|
|
46
|
+
const rumor = createRumor(senderPubkeyHex, recipient, message);
|
|
47
|
+
// 2. Create the seal (kind 13) -- encrypted with sender's key, signed by sender
|
|
48
|
+
const sealContent = await ctx.nip44Encrypt(recipient.publicKey, JSON.stringify(rumor));
|
|
49
|
+
const sign = ctx.getSigningFunction();
|
|
50
|
+
const seal = await sign({
|
|
51
|
+
kind: 13,
|
|
52
|
+
content: sealContent,
|
|
53
|
+
created_at: randomNow(),
|
|
54
|
+
tags: [],
|
|
55
|
+
});
|
|
56
|
+
// 3. Create the gift wrap (kind 1059) -- encrypted with random ephemeral key
|
|
57
|
+
const randomSk = generateSecretKey();
|
|
58
|
+
const { getConversationKey, encrypt } = await import('nostr-tools/nip44');
|
|
59
|
+
const ck = getConversationKey(randomSk, recipient.publicKey);
|
|
60
|
+
const wrapContent = encrypt(JSON.stringify(seal), ck);
|
|
61
|
+
const wrap = finalizeEvent({
|
|
62
|
+
kind: 1059,
|
|
63
|
+
content: wrapContent,
|
|
64
|
+
created_at: randomNow(),
|
|
65
|
+
tags: [['p', recipient.publicKey]],
|
|
66
|
+
}, randomSk);
|
|
67
|
+
// Zeroise the ephemeral key
|
|
68
|
+
randomSk.fill(0);
|
|
69
|
+
return wrap;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Unwrap a NIP-17 gift-wrapped event using the SigningContext.
|
|
73
|
+
*
|
|
74
|
+
* Flow:
|
|
75
|
+
* 1. Decrypt the gift wrap (kind 1059) content to get the seal
|
|
76
|
+
* 2. Decrypt the seal (kind 13) content to get the rumor
|
|
77
|
+
* 3. Return the rumor (the actual DM content)
|
|
78
|
+
*/
|
|
79
|
+
export async function unwrapEventAsync(ctx, wrap) {
|
|
80
|
+
// 1. Decrypt outer layer (gift wrap -> seal)
|
|
81
|
+
// The wrap was encrypted by a random ephemeral key; we decrypt using
|
|
82
|
+
// our key + the wrap's pubkey (the ephemeral key's public half).
|
|
83
|
+
const sealJson = await ctx.nip44Decrypt(wrap.pubkey, wrap.content);
|
|
84
|
+
const seal = JSON.parse(sealJson);
|
|
85
|
+
// 2. Decrypt inner layer (seal -> rumor)
|
|
86
|
+
// The seal was encrypted by the sender's key; we decrypt using
|
|
87
|
+
// our key + the seal's pubkey (the sender's public key).
|
|
88
|
+
const rumorJson = await ctx.nip44Decrypt(seal.pubkey, seal.content);
|
|
89
|
+
const rumor = JSON.parse(rumorJson);
|
|
90
|
+
return rumor;
|
|
91
|
+
}
|
package/dist/social/dm.js
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
1
|
-
import { wrapEvent } from 'nostr-tools/nip17';
|
|
2
1
|
import * as nip04 from 'nostr-tools/nip04';
|
|
3
2
|
import { decode, npubEncode } from 'nostr-tools/nip19';
|
|
4
3
|
import { toAnnotation } from '../trust-context.js';
|
|
4
|
+
import { wrapEventAsync, unwrapEventAsync } from '../nip17-wrap.js';
|
|
5
5
|
/** Send a DM. Uses NIP-17 gift wrap by default. NIP-04 only if explicitly requested and enabled. */
|
|
6
6
|
export async function handleDmSend(ctx, pool, args) {
|
|
7
7
|
if (args.nip04) {
|
|
8
8
|
if (!args.nip04Enabled) {
|
|
9
9
|
throw new Error('NIP-04 is not enabled. Set NIP04_ENABLED=1 to use legacy DMs.');
|
|
10
10
|
}
|
|
11
|
-
// NIP-04 legacy DM (kind 4)
|
|
12
|
-
const
|
|
11
|
+
// NIP-04 legacy DM (kind 4) — requires local key (not available in bunker mode)
|
|
12
|
+
const localCtx = ctx;
|
|
13
|
+
if (!localCtx.activePrivateKey) {
|
|
14
|
+
throw new Error('NIP-04 is not supported in bunker mode. Use NIP-17 (the default) instead.');
|
|
15
|
+
}
|
|
16
|
+
const encrypted = await nip04.encrypt(localCtx.activePrivateKey, args.recipientPubkeyHex, args.message);
|
|
13
17
|
const sign = ctx.getSigningFunction();
|
|
14
18
|
const event = await sign({
|
|
15
19
|
kind: 4,
|
|
@@ -34,7 +38,7 @@ export async function handleDmSend(ctx, pool, args) {
|
|
|
34
38
|
}
|
|
35
39
|
const recipientRelayHint = args.recipientRelay ?? recipientRelays[0];
|
|
36
40
|
// Wrap for recipient
|
|
37
|
-
const event =
|
|
41
|
+
const event = await wrapEventAsync(ctx, { publicKey: args.recipientPubkeyHex, relayUrl: recipientRelayHint }, args.message);
|
|
38
42
|
// Publish to recipient's relays (primary) and sender's relays (fallback)
|
|
39
43
|
let publish;
|
|
40
44
|
if (recipientRelays.length > 0) {
|
|
@@ -47,10 +51,10 @@ export async function handleDmSend(ctx, pool, args) {
|
|
|
47
51
|
publish = await pool.publish(ctx.activeNpub, event);
|
|
48
52
|
}
|
|
49
53
|
// Sender copy — wrap the same message addressed to ourselves so our client shows sent DMs.
|
|
50
|
-
// The inner rumor's p-tag (set by
|
|
54
|
+
// The inner rumor's p-tag (set by wrapEventAsync) points to us, but clients use the
|
|
51
55
|
// outer gift-wrap's p-tag to route, and the inner content to display.
|
|
52
56
|
const senderHex = decode(ctx.activeNpub).data;
|
|
53
|
-
const senderCopy =
|
|
57
|
+
const senderCopy = await wrapEventAsync(ctx, { publicKey: senderHex, relayUrl: recipientRelayHint }, args.message);
|
|
54
58
|
const senderCopyPublish = await pool.publish(ctx.activeNpub, senderCopy);
|
|
55
59
|
const result = { event, senderCopy, protocol: 'nip17', publish, senderCopyPublish };
|
|
56
60
|
if (recipientRelays.length > 0 && publish.accepted.length === 0) {
|
|
@@ -97,8 +101,7 @@ async function decryptDmEvents(ctx, events) {
|
|
|
97
101
|
if (event.kind === 1059) {
|
|
98
102
|
// NIP-17 gift wrap — try to unwrap
|
|
99
103
|
try {
|
|
100
|
-
const
|
|
101
|
-
const unwrapped = unwrapEvent(event, ctx.activePrivateKey);
|
|
104
|
+
const unwrapped = await unwrapEventAsync(ctx, event);
|
|
102
105
|
results.push({
|
|
103
106
|
id: event.id,
|
|
104
107
|
from: unwrapped.pubkey,
|
|
@@ -120,9 +123,21 @@ async function decryptDmEvents(ctx, events) {
|
|
|
120
123
|
}
|
|
121
124
|
}
|
|
122
125
|
else if (event.kind === 4) {
|
|
123
|
-
// NIP-04 legacy DM
|
|
126
|
+
// NIP-04 legacy DM — needs local key; silently skip in bunker mode
|
|
127
|
+
const localCtx = ctx;
|
|
128
|
+
if (!localCtx.activePrivateKey) {
|
|
129
|
+
results.push({
|
|
130
|
+
id: event.id,
|
|
131
|
+
from: event.pubkey,
|
|
132
|
+
createdAt: event.created_at,
|
|
133
|
+
protocol: 'nip04-deprecated',
|
|
134
|
+
decrypted: false,
|
|
135
|
+
error: 'NIP-04 decryption not available in bunker mode',
|
|
136
|
+
});
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
124
139
|
try {
|
|
125
|
-
const decrypted = await nip04.decrypt(
|
|
140
|
+
const decrypted = await nip04.decrypt(localCtx.activePrivateKey, event.pubkey, event.content);
|
|
126
141
|
results.push({
|
|
127
142
|
id: event.id,
|
|
128
143
|
from: event.pubkey,
|
package/dist/state.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Read and parse a JSON state file. Returns `{}` if missing or corrupt. */
|
|
2
|
+
export declare function readStateFile<T = Record<string, unknown>>(name: string, stateDir?: string): T;
|
|
3
|
+
/** Write a JSON state file with 0600 permissions. Creates directory if needed. */
|
|
4
|
+
export declare function writeStateFile(name: string, data: unknown, stateDir?: string): void;
|
package/dist/state.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, mkdirSync, chmodSync, existsSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
const DEFAULT_STATE_DIR = join(process.env.XDG_CONFIG_HOME ?? join(homedir(), '.config'), 'bray');
|
|
5
|
+
/** Read and parse a JSON state file. Returns `{}` if missing or corrupt. */
|
|
6
|
+
export function readStateFile(name, stateDir = DEFAULT_STATE_DIR) {
|
|
7
|
+
const path = join(stateDir, name);
|
|
8
|
+
try {
|
|
9
|
+
return JSON.parse(readFileSync(path, 'utf-8'));
|
|
10
|
+
}
|
|
11
|
+
catch {
|
|
12
|
+
return {};
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
/** Write a JSON state file with 0600 permissions. Creates directory if needed. */
|
|
16
|
+
export function writeStateFile(name, data, stateDir = DEFAULT_STATE_DIR) {
|
|
17
|
+
if (!existsSync(stateDir)) {
|
|
18
|
+
mkdirSync(stateDir, { recursive: true });
|
|
19
|
+
}
|
|
20
|
+
const path = join(stateDir, name);
|
|
21
|
+
writeFileSync(path, JSON.stringify(data, null, 2), { mode: 0o600 });
|
|
22
|
+
chmodSync(path, 0o600);
|
|
23
|
+
}
|
package/dist/trust/handlers.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { createAttestation, createRevocation } from 'nostr-attestations';
|
|
2
2
|
import { validateAttestation } from 'nostr-attestations';
|
|
3
3
|
import { attestationFilter } from 'nostr-attestations';
|
|
4
|
-
import { wrapEvent } from 'nostr-tools/nip17';
|
|
5
4
|
import { toUnsignedEvent } from 'nsec-tree/event';
|
|
6
5
|
import { decode } from 'nostr-tools/nip19';
|
|
7
6
|
import { hasExtendedIdentity } from '../signing-context.js';
|
|
7
|
+
import { wrapEventAsync } from '../nip17-wrap.js';
|
|
8
8
|
/** Create and publish a kind 31000 attestation */
|
|
9
9
|
export async function handleTrustAttest(ctx, pool, args) {
|
|
10
10
|
if (!args.type && !args.assertionId) {
|
|
@@ -88,7 +88,7 @@ export async function handleTrustRequest(ctx, pool, args) {
|
|
|
88
88
|
attestation_type: args.attestationType,
|
|
89
89
|
message: args.message,
|
|
90
90
|
});
|
|
91
|
-
const event =
|
|
91
|
+
const event = await wrapEventAsync(ctx, { publicKey: args.recipientPubkeyHex }, payload);
|
|
92
92
|
const publish = await pool.publish(ctx.activeNpub, event);
|
|
93
93
|
return { event, publish };
|
|
94
94
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nostr-bray",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.27.0",
|
|
4
4
|
"description": "Trust-aware Nostr for AI and humans. Three dimensions of trust -- verification, proximity, and access -- woven into every interaction.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|