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 +20 -1
- package/dist/index.js +32 -4
- package/dist/test.js +32 -1
- package/package.json +9 -3
- package/src/index.ts +42 -5
- package/src/test.ts +37 -1
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
|
|
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,
|
|
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.
|
|
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": [
|
|
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
|
|
196
|
+
const myPubkey = getPublicKey(secretKey);
|
|
197
|
+
const encryptionTarget = recipientPubkey || myPubkey;
|
|
162
198
|
|
|
163
199
|
// 1. Encrypt
|
|
164
|
-
const conversationKey = nip44.getConversationKey(secretKey,
|
|
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);
|