ncc-02-js 0.2.5 → 0.3.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/README.md +10 -7
- package/dist/index.cjs +14 -12
- package/dist/index.mjs +14 -12
- package/dist/models.d.ts +4 -4
- package/dist/resolver.d.ts +4 -4
- package/package.json +1 -1
- package/src/models.js +10 -10
- package/src/resolver.js +19 -14
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ This library provides tools for service owners to publish records and for client
|
|
|
6
6
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
9
|
-
- **Service Discovery**: Resolve Kind 30059 service records.
|
|
9
|
+
- **Service Discovery**: Resolve Kind 30059 service records for both public and private services.
|
|
10
10
|
- **Verification**: Built-in signature and expiry validation.
|
|
11
11
|
- **Trust Policy**: Support for third-party attestations (Kind 30060) and revocations (Kind 30061).
|
|
12
12
|
- **Security**: Cross-validation of subject and service identifiers to prevent impersonation.
|
|
@@ -36,7 +36,11 @@ try {
|
|
|
36
36
|
requireAttestation: true,
|
|
37
37
|
minLevel: 'verified' // 'self', 'verified', 'hardened'
|
|
38
38
|
});
|
|
39
|
-
|
|
39
|
+
if(service.endpoint) {
|
|
40
|
+
console.log('Resolved endpoint:', service.endpoint);
|
|
41
|
+
} else {
|
|
42
|
+
console.log('Resolved private service, use NCC-05 for endpoint discovery.');
|
|
43
|
+
}
|
|
40
44
|
} catch (err) {
|
|
41
45
|
console.error('Resolution failed:', err.code, err.message);
|
|
42
46
|
}
|
|
@@ -50,7 +54,7 @@ import { NCC02Builder } from 'ncc-02-js';
|
|
|
50
54
|
// Initialize with private key (hex)
|
|
51
55
|
const builder = new NCC02Builder(privateKey);
|
|
52
56
|
|
|
53
|
-
// Example 1: IP-based Service
|
|
57
|
+
// Example 1: Public IP-based Service
|
|
54
58
|
const event = builder.createServiceRecord({
|
|
55
59
|
serviceId: 'media',
|
|
56
60
|
endpoint: 'https://203.0.113.45:8443',
|
|
@@ -58,10 +62,9 @@ const event = builder.createServiceRecord({
|
|
|
58
62
|
expiryDays: 14
|
|
59
63
|
});
|
|
60
64
|
|
|
61
|
-
// Example 2:
|
|
62
|
-
const
|
|
65
|
+
// Example 2: Private / Invite-Only Service
|
|
66
|
+
const privateEvent = builder.createServiceRecord({
|
|
63
67
|
serviceId: 'wallet',
|
|
64
|
-
endpoint: 'tcp://vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd.onion:80',
|
|
65
68
|
fingerprint: 'sha256:fingerprint',
|
|
66
69
|
expiryDays: 7
|
|
67
70
|
});
|
|
@@ -109,7 +112,7 @@ The library follows a fail-closed principle. If a policy requirement is not met
|
|
|
109
112
|
- `options.minLevel`: Minimum trust level required.
|
|
110
113
|
|
|
111
114
|
### `NCC02Builder(privateKey)`
|
|
112
|
-
- `createServiceRecord({ serviceId, endpoint
|
|
115
|
+
- `createServiceRecord({ serviceId, endpoint?, fingerprint?, expiryDays? })`
|
|
113
116
|
- `createAttestation({ subjectPubkey, serviceId, serviceEventId, level, validDays })`
|
|
114
117
|
- `createRevocation({ attestationId, reason })`
|
|
115
118
|
|
package/dist/index.cjs
CHANGED
|
@@ -2509,25 +2509,24 @@ var NCC02Builder = class {
|
|
|
2509
2509
|
* Creates a signed Service Record (Kind 30059).
|
|
2510
2510
|
* @param {Object} options
|
|
2511
2511
|
* @param {string} options.serviceId - The 'd' tag identifier.
|
|
2512
|
-
* @param {string} options.endpoint - The 'u' tag URI.
|
|
2513
|
-
* @param {string} options.fingerprint - The 'k' tag fingerprint.
|
|
2512
|
+
* @param {string} [options.endpoint] - The 'u' tag URI.
|
|
2513
|
+
* @param {string} [options.fingerprint] - The 'k' tag fingerprint.
|
|
2514
2514
|
* @param {number} [options.expiryDays=14] - Expiry in days.
|
|
2515
2515
|
*/
|
|
2516
2516
|
createServiceRecord(options) {
|
|
2517
2517
|
const { serviceId, endpoint, fingerprint, expiryDays = 14 } = options;
|
|
2518
2518
|
if (!serviceId) throw new Error("serviceId (d tag) is required");
|
|
2519
|
-
if (!endpoint) throw new Error("endpoint (u tag) is required");
|
|
2520
|
-
if (!fingerprint) throw new Error("fingerprint (k tag) is required");
|
|
2521
2519
|
const expiry = Math.floor(Date.now() / 1e3) + expiryDays * 24 * 60 * 60;
|
|
2520
|
+
const tags = [
|
|
2521
|
+
["d", serviceId],
|
|
2522
|
+
["exp", expiry.toString()]
|
|
2523
|
+
];
|
|
2524
|
+
if (endpoint) tags.push(["u", endpoint]);
|
|
2525
|
+
if (fingerprint) tags.push(["k", fingerprint]);
|
|
2522
2526
|
const event = {
|
|
2523
2527
|
kind: KINDS.SERVICE_RECORD,
|
|
2524
2528
|
created_at: Math.floor(Date.now() / 1e3),
|
|
2525
|
-
tags
|
|
2526
|
-
["d", serviceId],
|
|
2527
|
-
["u", endpoint],
|
|
2528
|
-
["k", fingerprint],
|
|
2529
|
-
["exp", expiry.toString()]
|
|
2530
|
-
],
|
|
2529
|
+
tags,
|
|
2531
2530
|
content: `NCC-02 Service Record for ${serviceId}`,
|
|
2532
2531
|
pubkey: this.pk
|
|
2533
2532
|
};
|
|
@@ -7752,8 +7751,11 @@ var NCC02Resolver = class {
|
|
|
7752
7751
|
}
|
|
7753
7752
|
const serviceTags = Object.fromEntries(serviceEvent.tags);
|
|
7754
7753
|
const now2 = Math.floor(Date.now() / 1e3);
|
|
7755
|
-
if (!serviceTags.
|
|
7756
|
-
throw new NCC02Error("MALFORMED_RECORD", "Service record is missing required
|
|
7754
|
+
if (!serviceTags.exp) {
|
|
7755
|
+
throw new NCC02Error("MALFORMED_RECORD", "Service record is missing required tag (exp)");
|
|
7756
|
+
}
|
|
7757
|
+
if (serviceTags.u && (serviceTags.u.startsWith("wss://") || serviceTags.u.startsWith("https://")) && !serviceTags.k) {
|
|
7758
|
+
throw new NCC02Error("MALFORMED_RECORD", "Service record with 'https' or 'wss' endpoint must have a 'k' tag");
|
|
7757
7759
|
}
|
|
7758
7760
|
const exp = parseInt(serviceTags.exp);
|
|
7759
7761
|
if (isNaN(exp)) {
|
package/dist/index.mjs
CHANGED
|
@@ -2475,25 +2475,24 @@ var NCC02Builder = class {
|
|
|
2475
2475
|
* Creates a signed Service Record (Kind 30059).
|
|
2476
2476
|
* @param {Object} options
|
|
2477
2477
|
* @param {string} options.serviceId - The 'd' tag identifier.
|
|
2478
|
-
* @param {string} options.endpoint - The 'u' tag URI.
|
|
2479
|
-
* @param {string} options.fingerprint - The 'k' tag fingerprint.
|
|
2478
|
+
* @param {string} [options.endpoint] - The 'u' tag URI.
|
|
2479
|
+
* @param {string} [options.fingerprint] - The 'k' tag fingerprint.
|
|
2480
2480
|
* @param {number} [options.expiryDays=14] - Expiry in days.
|
|
2481
2481
|
*/
|
|
2482
2482
|
createServiceRecord(options) {
|
|
2483
2483
|
const { serviceId, endpoint, fingerprint, expiryDays = 14 } = options;
|
|
2484
2484
|
if (!serviceId) throw new Error("serviceId (d tag) is required");
|
|
2485
|
-
if (!endpoint) throw new Error("endpoint (u tag) is required");
|
|
2486
|
-
if (!fingerprint) throw new Error("fingerprint (k tag) is required");
|
|
2487
2485
|
const expiry = Math.floor(Date.now() / 1e3) + expiryDays * 24 * 60 * 60;
|
|
2486
|
+
const tags = [
|
|
2487
|
+
["d", serviceId],
|
|
2488
|
+
["exp", expiry.toString()]
|
|
2489
|
+
];
|
|
2490
|
+
if (endpoint) tags.push(["u", endpoint]);
|
|
2491
|
+
if (fingerprint) tags.push(["k", fingerprint]);
|
|
2488
2492
|
const event = {
|
|
2489
2493
|
kind: KINDS.SERVICE_RECORD,
|
|
2490
2494
|
created_at: Math.floor(Date.now() / 1e3),
|
|
2491
|
-
tags
|
|
2492
|
-
["d", serviceId],
|
|
2493
|
-
["u", endpoint],
|
|
2494
|
-
["k", fingerprint],
|
|
2495
|
-
["exp", expiry.toString()]
|
|
2496
|
-
],
|
|
2495
|
+
tags,
|
|
2497
2496
|
content: `NCC-02 Service Record for ${serviceId}`,
|
|
2498
2497
|
pubkey: this.pk
|
|
2499
2498
|
};
|
|
@@ -7718,8 +7717,11 @@ var NCC02Resolver = class {
|
|
|
7718
7717
|
}
|
|
7719
7718
|
const serviceTags = Object.fromEntries(serviceEvent.tags);
|
|
7720
7719
|
const now2 = Math.floor(Date.now() / 1e3);
|
|
7721
|
-
if (!serviceTags.
|
|
7722
|
-
throw new NCC02Error("MALFORMED_RECORD", "Service record is missing required
|
|
7720
|
+
if (!serviceTags.exp) {
|
|
7721
|
+
throw new NCC02Error("MALFORMED_RECORD", "Service record is missing required tag (exp)");
|
|
7722
|
+
}
|
|
7723
|
+
if (serviceTags.u && (serviceTags.u.startsWith("wss://") || serviceTags.u.startsWith("https://")) && !serviceTags.k) {
|
|
7724
|
+
throw new NCC02Error("MALFORMED_RECORD", "Service record with 'https' or 'wss' endpoint must have a 'k' tag");
|
|
7723
7725
|
}
|
|
7724
7726
|
const exp = parseInt(serviceTags.exp);
|
|
7725
7727
|
if (isNaN(exp)) {
|
package/dist/models.d.ts
CHANGED
|
@@ -22,14 +22,14 @@ export class NCC02Builder {
|
|
|
22
22
|
* Creates a signed Service Record (Kind 30059).
|
|
23
23
|
* @param {Object} options
|
|
24
24
|
* @param {string} options.serviceId - The 'd' tag identifier.
|
|
25
|
-
* @param {string} options.endpoint - The 'u' tag URI.
|
|
26
|
-
* @param {string} options.fingerprint - The 'k' tag fingerprint.
|
|
25
|
+
* @param {string} [options.endpoint] - The 'u' tag URI.
|
|
26
|
+
* @param {string} [options.fingerprint] - The 'k' tag fingerprint.
|
|
27
27
|
* @param {number} [options.expiryDays=14] - Expiry in days.
|
|
28
28
|
*/
|
|
29
29
|
createServiceRecord(options: {
|
|
30
30
|
serviceId: string;
|
|
31
|
-
endpoint
|
|
32
|
-
fingerprint
|
|
31
|
+
endpoint?: string;
|
|
32
|
+
fingerprint?: string;
|
|
33
33
|
expiryDays?: number;
|
|
34
34
|
}): import("nostr-tools/core").VerifiedEvent;
|
|
35
35
|
/**
|
package/dist/resolver.d.ts
CHANGED
|
@@ -13,8 +13,8 @@ export class NCC02Error extends Error {
|
|
|
13
13
|
}
|
|
14
14
|
/**
|
|
15
15
|
* @typedef {Object} ResolvedService
|
|
16
|
-
* @property {string} endpoint
|
|
17
|
-
* @property {string} fingerprint
|
|
16
|
+
* @property {string|undefined} endpoint
|
|
17
|
+
* @property {string|undefined} fingerprint
|
|
18
18
|
* @property {number} expiry
|
|
19
19
|
* @property {any[]} attestations
|
|
20
20
|
* @property {string} eventId
|
|
@@ -87,8 +87,8 @@ export class NCC02Resolver {
|
|
|
87
87
|
verifyEndpoint(resolved: ResolvedService, actualFingerprint: string): boolean;
|
|
88
88
|
}
|
|
89
89
|
export type ResolvedService = {
|
|
90
|
-
endpoint: string;
|
|
91
|
-
fingerprint: string;
|
|
90
|
+
endpoint: string | undefined;
|
|
91
|
+
fingerprint: string | undefined;
|
|
92
92
|
expiry: number;
|
|
93
93
|
attestations: any[];
|
|
94
94
|
eventId: string;
|
package/package.json
CHANGED
package/src/models.js
CHANGED
|
@@ -27,26 +27,26 @@ export class NCC02Builder {
|
|
|
27
27
|
* Creates a signed Service Record (Kind 30059).
|
|
28
28
|
* @param {Object} options
|
|
29
29
|
* @param {string} options.serviceId - The 'd' tag identifier.
|
|
30
|
-
* @param {string} options.endpoint - The 'u' tag URI.
|
|
31
|
-
* @param {string} options.fingerprint - The 'k' tag fingerprint.
|
|
30
|
+
* @param {string} [options.endpoint] - The 'u' tag URI.
|
|
31
|
+
* @param {string} [options.fingerprint] - The 'k' tag fingerprint.
|
|
32
32
|
* @param {number} [options.expiryDays=14] - Expiry in days.
|
|
33
33
|
*/
|
|
34
34
|
createServiceRecord(options) {
|
|
35
35
|
const { serviceId, endpoint, fingerprint, expiryDays = 14 } = options;
|
|
36
36
|
if (!serviceId) throw new Error('serviceId (d tag) is required');
|
|
37
|
-
if (!endpoint) throw new Error('endpoint (u tag) is required');
|
|
38
|
-
if (!fingerprint) throw new Error('fingerprint (k tag) is required');
|
|
39
37
|
|
|
40
38
|
const expiry = Math.floor(Date.now() / 1000) + (expiryDays * 24 * 60 * 60);
|
|
39
|
+
const tags = [
|
|
40
|
+
['d', serviceId],
|
|
41
|
+
['exp', expiry.toString()]
|
|
42
|
+
];
|
|
43
|
+
if(endpoint) tags.push(['u', endpoint]);
|
|
44
|
+
if(fingerprint) tags.push(['k', fingerprint]);
|
|
45
|
+
|
|
41
46
|
const event = {
|
|
42
47
|
kind: KINDS.SERVICE_RECORD,
|
|
43
48
|
created_at: Math.floor(Date.now() / 1000),
|
|
44
|
-
tags:
|
|
45
|
-
['d', serviceId],
|
|
46
|
-
['u', endpoint],
|
|
47
|
-
['k', fingerprint],
|
|
48
|
-
['exp', expiry.toString()]
|
|
49
|
-
],
|
|
49
|
+
tags: tags,
|
|
50
50
|
content: `NCC-02 Service Record for ${serviceId}`,
|
|
51
51
|
pubkey: this.pk
|
|
52
52
|
};
|
package/src/resolver.js
CHANGED
|
@@ -19,8 +19,8 @@ export class NCC02Error extends Error {
|
|
|
19
19
|
|
|
20
20
|
/**
|
|
21
21
|
* @typedef {Object} ResolvedService
|
|
22
|
-
* @property {string} endpoint
|
|
23
|
-
* @property {string} fingerprint
|
|
22
|
+
* @property {string|undefined} endpoint
|
|
23
|
+
* @property {string|undefined} fingerprint
|
|
24
24
|
* @property {number} expiry
|
|
25
25
|
* @property {any[]} attestations
|
|
26
26
|
* @property {string} eventId
|
|
@@ -43,7 +43,7 @@ export class NCC02Resolver {
|
|
|
43
43
|
*/
|
|
44
44
|
constructor(relays, options = {}) {
|
|
45
45
|
if (!Array.isArray(relays)) {
|
|
46
|
-
|
|
46
|
+
throw new Error('NCC02Resolver expects an array of relay URLs.');
|
|
47
47
|
}
|
|
48
48
|
this.relays = relays;
|
|
49
49
|
this.pool = options.pool || new SimplePool();
|
|
@@ -57,16 +57,16 @@ export class NCC02Resolver {
|
|
|
57
57
|
* @returns {Promise<import('nostr-tools').Event[]>}
|
|
58
58
|
*/
|
|
59
59
|
async _query(filter) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
});
|
|
60
|
+
return new Promise((resolve) => {
|
|
61
|
+
/** @type {import('nostr-tools').Event[]} */
|
|
62
|
+
const events = [];
|
|
63
|
+
// subscribeMany(relays, filters, callbacks)
|
|
64
|
+
// @ts-ignore - subscribeMany filters parameter type mismatch with simple Object
|
|
65
|
+
const sub = this.pool.subscribeMany(this.relays, [filter], {
|
|
66
|
+
onevent(e) { events.push(e); },
|
|
67
|
+
oneose() { sub.close(); resolve(events); }
|
|
69
68
|
});
|
|
69
|
+
});
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
/**
|
|
@@ -117,8 +117,13 @@ export class NCC02Resolver {
|
|
|
117
117
|
const now = Math.floor(Date.now() / 1000);
|
|
118
118
|
|
|
119
119
|
// Security Fix: exp is REQUIRED by NCC-02 spec
|
|
120
|
-
if (!serviceTags.
|
|
121
|
-
throw new NCC02Error('MALFORMED_RECORD', 'Service record is missing required
|
|
120
|
+
if (!serviceTags.exp) {
|
|
121
|
+
throw new NCC02Error('MALFORMED_RECORD', 'Service record is missing required tag (exp)');
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// 'k' is required for TLS-based endpoints
|
|
125
|
+
if (serviceTags.u && (serviceTags.u.startsWith('wss://') || serviceTags.u.startsWith('https://')) && !serviceTags.k) {
|
|
126
|
+
throw new NCC02Error('MALFORMED_RECORD', 'Service record with \'https\' or \'wss\' endpoint must have a \'k\' tag');
|
|
122
127
|
}
|
|
123
128
|
|
|
124
129
|
const exp = parseInt(serviceTags.exp);
|