ncc-05 1.0.0 → 1.1.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/index.d.ts CHANGED
@@ -18,6 +18,24 @@ export interface ResolverOptions {
18
18
  timeout?: number;
19
19
  websocketImplementation?: any;
20
20
  }
21
+ /**
22
+ * Utility for managing shared group access to NCC-05 records.
23
+ */
24
+ export declare class NCC05Group {
25
+ /**
26
+ * Generate a new shared identity for a group.
27
+ * The nsec should be shared with all authorized members.
28
+ */
29
+ static createGroupIdentity(): {
30
+ nsec: `nsec1${string}`;
31
+ sk: Uint8Array<ArrayBufferLike>;
32
+ pk: string;
33
+ };
34
+ /**
35
+ * Resolve a record that was published using a group's shared identity.
36
+ */
37
+ static resolveAsGroup(resolver: NCC05Resolver, groupPubkey: string, groupSecretKey: Uint8Array, identifier?: string): Promise<NCC05Payload | null>;
38
+ }
21
39
  export declare class NCC05Resolver {
22
40
  private pool;
23
41
  private bootstrapRelays;
@@ -40,7 +58,8 @@ export declare class NCC05Publisher {
40
58
  });
41
59
  /**
42
60
  * Create and publish a locator record.
61
+ * @param recipientPubkey Optional hex pubkey of the recipient. If omitted, self-encrypts.
43
62
  */
44
- publish(relays: string[], secretKey: Uint8Array, payload: NCC05Payload, identifier?: string): Promise<Event>;
63
+ publish(relays: string[], secretKey: Uint8Array, payload: NCC05Payload, identifier?: string, recipientPubkey?: string): Promise<Event>;
45
64
  close(relays: string[]): void;
46
65
  }
package/dist/index.js CHANGED
@@ -1,4 +1,29 @@
1
- import { SimplePool, nip44, nip19, finalizeEvent, verifyEvent, getPublicKey } from 'nostr-tools';
1
+ import { SimplePool, nip44, nip19, finalizeEvent, verifyEvent, getPublicKey, generateSecretKey } from 'nostr-tools';
2
+ /**
3
+ * Utility for managing shared group access to NCC-05 records.
4
+ */
5
+ export class NCC05Group {
6
+ /**
7
+ * Generate a new shared identity for a group.
8
+ * The nsec should be shared with all authorized members.
9
+ */
10
+ static createGroupIdentity() {
11
+ const sk = generateSecretKey();
12
+ return {
13
+ nsec: nip19.nsecEncode(sk),
14
+ sk: sk,
15
+ pk: getPublicKey(sk)
16
+ };
17
+ }
18
+ /**
19
+ * Resolve a record that was published using a group's shared identity.
20
+ */
21
+ static async resolveAsGroup(resolver, groupPubkey, groupSecretKey, identifier = 'addr') {
22
+ // In group mode, we use the group's SK to decrypt a record
23
+ // that was self-encrypted by the group's PK.
24
+ return resolver.resolve(groupPubkey, groupSecretKey, identifier);
25
+ }
26
+ }
2
27
  export class NCC05Resolver {
3
28
  pool;
4
29
  bootstrapRelays;
@@ -97,16 +122,19 @@ export class NCC05Publisher {
97
122
  }
98
123
  /**
99
124
  * Create and publish a locator record.
125
+ * @param recipientPubkey Optional hex pubkey of the recipient. If omitted, self-encrypts.
100
126
  */
101
- async publish(relays, secretKey, payload, identifier = 'addr') {
102
- const pubkey = getPublicKey(secretKey);
127
+ async publish(relays, secretKey, payload, identifier = 'addr', recipientPubkey) {
128
+ const myPubkey = getPublicKey(secretKey);
129
+ const encryptionTarget = recipientPubkey || myPubkey;
103
130
  // 1. Encrypt
104
- const conversationKey = nip44.getConversationKey(secretKey, pubkey);
131
+ const conversationKey = nip44.getConversationKey(secretKey, encryptionTarget);
105
132
  const encryptedContent = nip44.encrypt(JSON.stringify(payload), conversationKey);
106
133
  // 2. Create and Finalize Event
107
134
  const eventTemplate = {
108
135
  kind: 30058,
109
136
  created_at: Math.floor(Date.now() / 1000),
137
+ pubkey: myPubkey,
110
138
  tags: [['d', identifier]],
111
139
  content: encryptedContent,
112
140
  };
package/dist/test.js CHANGED
@@ -1,4 +1,4 @@
1
- import { NCC05Publisher, NCC05Resolver } from './index.js';
1
+ import { NCC05Publisher, NCC05Resolver, NCC05Group } from './index.js';
2
2
  import { generateSecretKey, getPublicKey } from 'nostr-tools';
3
3
  async function test() {
4
4
  const sk = generateSecretKey();
@@ -73,6 +73,37 @@ async function test() {
73
73
  console.error('FAILED: npub resolution did not find record.');
74
74
  process.exit(1);
75
75
  }
76
+ // Test Friend-to-Friend resolution
77
+ console.log('Testing Friend-to-Friend resolution...');
78
+ const skA = generateSecretKey();
79
+ const pkA = getPublicKey(skA);
80
+ const skB = generateSecretKey();
81
+ const pkB = getPublicKey(skB);
82
+ const payloadFriend = {
83
+ v: 1, ttl: 60, updated_at: Math.floor(Date.now() / 1000),
84
+ endpoints: [{ type: 'tcp', uri: 'friend:7777', priority: 1, family: 'ipv4' }]
85
+ };
86
+ // User A publishes for User B
87
+ console.log('User A publishing for User B...');
88
+ await publisher.publish(relays, skA, payloadFriend, 'friend-test', pkB);
89
+ // Test Group Resolution Utility
90
+ console.log('Testing NCC05Group utility...');
91
+ const groupIdentity = NCC05Group.createGroupIdentity();
92
+ const payloadGroup = {
93
+ v: 1, ttl: 60, updated_at: Math.floor(Date.now() / 1000),
94
+ endpoints: [{ type: 'tcp', uri: 'group-service:8888', priority: 1, family: 'ipv4' }]
95
+ };
96
+ console.log('Publishing as Group...');
97
+ await publisher.publish(relays, groupIdentity.sk, payloadGroup, 'group-test');
98
+ console.log('Resolving as Group Member...');
99
+ const groupResult = await NCC05Group.resolveAsGroup(resolver, groupIdentity.pk, groupIdentity.sk, 'group-test');
100
+ if (groupResult && groupResult.endpoints[0].uri === 'group-service:8888') {
101
+ console.log('NCC05Group resolution successful.');
102
+ }
103
+ else {
104
+ console.error('FAILED: NCC05Group resolution.');
105
+ process.exit(1);
106
+ }
76
107
  publisher.close(relays);
77
108
  resolver.close();
78
109
  process.exit(0);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ncc-05",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Nostr Community Convention 05 - Identity-Bound Service Locator Resolution",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -9,7 +9,13 @@
9
9
  "build": "tsc",
10
10
  "prepublishOnly": "npm run build"
11
11
  },
12
- "keywords": ["nostr", "dns", "identity", "resolution", "privacy"],
12
+ "keywords": [
13
+ "nostr",
14
+ "dns",
15
+ "identity",
16
+ "resolution",
17
+ "privacy"
18
+ ],
13
19
  "author": "lostcause",
14
20
  "license": "MIT",
15
21
  "dependencies": {
@@ -19,4 +25,4 @@
19
25
  "typescript": "^5.0.0",
20
26
  "@types/node": "^20.0.0"
21
27
  }
22
- }
28
+ }
package/src/index.ts CHANGED
@@ -5,7 +5,8 @@ import {
5
5
  finalizeEvent,
6
6
  verifyEvent,
7
7
  Event,
8
- getPublicKey
8
+ getPublicKey,
9
+ generateSecretKey
9
10
  } from 'nostr-tools';
10
11
 
11
12
  export interface NCC05Endpoint {
@@ -30,6 +31,38 @@ export interface ResolverOptions {
30
31
  websocketImplementation?: any; // To support Tor/SOCKS5 proxies in Node.js
31
32
  }
32
33
 
34
+ /**
35
+ * Utility for managing shared group access to NCC-05 records.
36
+ */
37
+ export class NCC05Group {
38
+ /**
39
+ * Generate a new shared identity for a group.
40
+ * The nsec should be shared with all authorized members.
41
+ */
42
+ static createGroupIdentity() {
43
+ const sk = generateSecretKey();
44
+ return {
45
+ nsec: nip19.nsecEncode(sk),
46
+ sk: sk,
47
+ pk: getPublicKey(sk)
48
+ };
49
+ }
50
+
51
+ /**
52
+ * Resolve a record that was published using a group's shared identity.
53
+ */
54
+ static async resolveAsGroup(
55
+ resolver: NCC05Resolver,
56
+ groupPubkey: string,
57
+ groupSecretKey: Uint8Array,
58
+ identifier: string = 'addr'
59
+ ): Promise<NCC05Payload | null> {
60
+ // In group mode, we use the group's SK to decrypt a record
61
+ // that was self-encrypted by the group's PK.
62
+ return resolver.resolve(groupPubkey, groupSecretKey, identifier);
63
+ }
64
+ }
65
+
33
66
  export class NCC05Resolver {
34
67
  private pool: SimplePool;
35
68
  private bootstrapRelays: string[];
@@ -151,23 +184,27 @@ export class NCC05Publisher {
151
184
 
152
185
  /**
153
186
  * Create and publish a locator record.
187
+ * @param recipientPubkey Optional hex pubkey of the recipient. If omitted, self-encrypts.
154
188
  */
155
189
  async publish(
156
190
  relays: string[],
157
191
  secretKey: Uint8Array,
158
192
  payload: NCC05Payload,
159
- identifier: string = 'addr'
193
+ identifier: string = 'addr',
194
+ recipientPubkey?: string
160
195
  ): Promise<Event> {
161
- const pubkey = getPublicKey(secretKey);
196
+ const myPubkey = getPublicKey(secretKey);
197
+ const encryptionTarget = recipientPubkey || myPubkey;
162
198
 
163
199
  // 1. Encrypt
164
- const conversationKey = nip44.getConversationKey(secretKey, pubkey);
200
+ const conversationKey = nip44.getConversationKey(secretKey, encryptionTarget);
165
201
  const encryptedContent = nip44.encrypt(JSON.stringify(payload), conversationKey);
166
202
 
167
203
  // 2. Create and Finalize Event
168
204
  const eventTemplate = {
169
205
  kind: 30058,
170
206
  created_at: Math.floor(Date.now() / 1000),
207
+ pubkey: myPubkey,
171
208
  tags: [['d', identifier]],
172
209
  content: encryptedContent,
173
210
  };
@@ -183,4 +220,4 @@ export class NCC05Publisher {
183
220
  close(relays: string[]) {
184
221
  this.pool.close(relays);
185
222
  }
186
- }
223
+ }
package/src/test.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { NCC05Publisher, NCC05Resolver, NCC05Payload } from './index.js';
1
+ import { NCC05Publisher, NCC05Resolver, NCC05Payload, NCC05Group } from './index.js';
2
2
  import { generateSecretKey, getPublicKey } from 'nostr-tools';
3
3
 
4
4
  async function test() {
@@ -81,6 +81,42 @@ async function test() {
81
81
  process.exit(1);
82
82
  }
83
83
 
84
+ // Test Friend-to-Friend resolution
85
+ console.log('Testing Friend-to-Friend resolution...');
86
+ const skA = generateSecretKey();
87
+ const pkA = getPublicKey(skA);
88
+ const skB = generateSecretKey();
89
+ const pkB = getPublicKey(skB);
90
+
91
+ const payloadFriend: NCC05Payload = {
92
+ v: 1, ttl: 60, updated_at: Math.floor(Date.now() / 1000),
93
+ endpoints: [{ type: 'tcp', uri: 'friend:7777', priority: 1, family: 'ipv4' }]
94
+ };
95
+
96
+ // User A publishes for User B
97
+ console.log('User A publishing for User B...');
98
+ await publisher.publish(relays, skA, payloadFriend, 'friend-test', pkB);
99
+
100
+ // Test Group Resolution Utility
101
+ console.log('Testing NCC05Group utility...');
102
+ const groupIdentity = NCC05Group.createGroupIdentity();
103
+ const payloadGroup: NCC05Payload = {
104
+ v: 1, ttl: 60, updated_at: Math.floor(Date.now() / 1000),
105
+ endpoints: [{ type: 'tcp', uri: 'group-service:8888', priority: 1, family: 'ipv4' }]
106
+ };
107
+
108
+ console.log('Publishing as Group...');
109
+ await publisher.publish(relays, groupIdentity.sk, payloadGroup, 'group-test');
110
+
111
+ console.log('Resolving as Group Member...');
112
+ const groupResult = await NCC05Group.resolveAsGroup(resolver, groupIdentity.pk, groupIdentity.sk, 'group-test');
113
+ if (groupResult && groupResult.endpoints[0].uri === 'group-service:8888') {
114
+ console.log('NCC05Group resolution successful.');
115
+ } else {
116
+ console.error('FAILED: NCC05Group resolution.');
117
+ process.exit(1);
118
+ }
119
+
84
120
  publisher.close(relays);
85
121
  resolver.close();
86
122
  process.exit(0);