ncc-02-js 0.2.0 → 0.2.3
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 +64 -12
- package/dist/index.cjs +7920 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.mjs +5465 -340
- package/dist/mockRelay.d.ts +21 -0
- package/dist/models.d.ts +61 -0
- package/dist/resolver.d.ts +94 -0
- package/package.json +13 -3
- package/src/models.js +21 -15
- package/src/resolver.js +35 -10
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple in-memory mock relay for testing NCC-02 resolution.
|
|
3
|
+
* Adheres to standard Nostr relay filtering rules.
|
|
4
|
+
*/
|
|
5
|
+
export class MockRelay {
|
|
6
|
+
/** @type {any[]} */
|
|
7
|
+
events: any[];
|
|
8
|
+
/**
|
|
9
|
+
* Publishes an event to the relay.
|
|
10
|
+
* Mimics a standard relay by verifying signatures before acceptance.
|
|
11
|
+
* @param {any} event
|
|
12
|
+
*/
|
|
13
|
+
publish(event: any): Promise<boolean>;
|
|
14
|
+
/**
|
|
15
|
+
* Queries events using standard Nostr filters.
|
|
16
|
+
* Supports kinds, authors, ids, and tag filters (e.g. #d, #e).
|
|
17
|
+
* @param {any} filter
|
|
18
|
+
* @returns {Promise<any[]>}
|
|
19
|
+
*/
|
|
20
|
+
query(filter: any): Promise<any[]>;
|
|
21
|
+
}
|
package/dist/models.d.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Verifies a Nostr event signature.
|
|
3
|
+
* @param {any} event
|
|
4
|
+
*/
|
|
5
|
+
export function verifyNCC02Event(event: any): event is import("nostr-tools/core").VerifiedEvent;
|
|
6
|
+
export namespace KINDS {
|
|
7
|
+
let SERVICE_RECORD: number;
|
|
8
|
+
let ATTESTATION: number;
|
|
9
|
+
let REVOCATION: number;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Utility for building and signing NCC-02 events.
|
|
13
|
+
*/
|
|
14
|
+
export class NCC02Builder {
|
|
15
|
+
/**
|
|
16
|
+
* @param {string | Uint8Array} privateKey - The private key to sign events with.
|
|
17
|
+
*/
|
|
18
|
+
constructor(privateKey: string | Uint8Array);
|
|
19
|
+
sk: Uint8Array<ArrayBufferLike>;
|
|
20
|
+
pk: string;
|
|
21
|
+
/**
|
|
22
|
+
* Creates a signed Service Record (Kind 30059).
|
|
23
|
+
* @param {Object} options
|
|
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.
|
|
27
|
+
* @param {number} [options.expiryDays=14] - Expiry in days.
|
|
28
|
+
*/
|
|
29
|
+
createServiceRecord(options: {
|
|
30
|
+
serviceId: string;
|
|
31
|
+
endpoint: string;
|
|
32
|
+
fingerprint: string;
|
|
33
|
+
expiryDays?: number;
|
|
34
|
+
}): import("nostr-tools/core").VerifiedEvent;
|
|
35
|
+
/**
|
|
36
|
+
* Creates a signed Certificate Attestation (Kind 30060).
|
|
37
|
+
* @param {Object} options
|
|
38
|
+
* @param {string} options.subjectPubkey - The 'subj' tag pubkey.
|
|
39
|
+
* @param {string} options.serviceId - The 'srv' tag identifier.
|
|
40
|
+
* @param {string} options.serviceEventId - The 'e' tag referencing the Service Record.
|
|
41
|
+
* @param {string} [options.level='verified'] - The 'lvl' tag level.
|
|
42
|
+
* @param {number} [options.validDays=30] - Validity in days.
|
|
43
|
+
*/
|
|
44
|
+
createAttestation(options: {
|
|
45
|
+
subjectPubkey: string;
|
|
46
|
+
serviceId: string;
|
|
47
|
+
serviceEventId: string;
|
|
48
|
+
level?: string;
|
|
49
|
+
validDays?: number;
|
|
50
|
+
}): import("nostr-tools/core").VerifiedEvent;
|
|
51
|
+
/**
|
|
52
|
+
* Creates a signed Revocation (Kind 30061).
|
|
53
|
+
* @param {Object} options
|
|
54
|
+
* @param {string} options.attestationId - The 'e' tag referencing the attestation.
|
|
55
|
+
* @param {string} [options.reason=''] - Optional reason.
|
|
56
|
+
*/
|
|
57
|
+
createRevocation(options: {
|
|
58
|
+
attestationId: string;
|
|
59
|
+
reason?: string;
|
|
60
|
+
}): import("nostr-tools/core").VerifiedEvent;
|
|
61
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom error class for NCC-02 specific failures.
|
|
3
|
+
*/
|
|
4
|
+
export class NCC02Error extends Error {
|
|
5
|
+
/**
|
|
6
|
+
* @param {string} code
|
|
7
|
+
* @param {string} message
|
|
8
|
+
* @param {any} [cause]
|
|
9
|
+
*/
|
|
10
|
+
constructor(code: string, message: string, cause?: any);
|
|
11
|
+
code: string;
|
|
12
|
+
cause: any;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* @typedef {Object} ResolvedService
|
|
16
|
+
* @property {string} endpoint
|
|
17
|
+
* @property {string} fingerprint
|
|
18
|
+
* @property {number} expiry
|
|
19
|
+
* @property {any[]} attestations
|
|
20
|
+
* @property {string} eventId
|
|
21
|
+
* @property {string} pubkey
|
|
22
|
+
*/
|
|
23
|
+
/**
|
|
24
|
+
* Resolver for NCC-02 Service Records.
|
|
25
|
+
* Implements the client-side resolution and trust verification algorithm.
|
|
26
|
+
*/
|
|
27
|
+
export class NCC02Resolver {
|
|
28
|
+
/**
|
|
29
|
+
* @param {string[]} relays - List of relay URLs.
|
|
30
|
+
* @param {Object} [options={}]
|
|
31
|
+
* @param {SimplePool} [options.pool] - Shared SimplePool instance.
|
|
32
|
+
* @param {string[]} [options.trustedCAPubkeys=[]] - List of trusted CA pubkeys.
|
|
33
|
+
*/
|
|
34
|
+
constructor(relays: string[], options?: {
|
|
35
|
+
pool?: SimplePool;
|
|
36
|
+
trustedCAPubkeys?: string[];
|
|
37
|
+
});
|
|
38
|
+
relays: string[];
|
|
39
|
+
pool: SimplePool;
|
|
40
|
+
ownsPool: boolean;
|
|
41
|
+
trustedCAPubkeys: Set<string>;
|
|
42
|
+
/**
|
|
43
|
+
* Internal query helper using SimplePool.subscribeMany (since list() is deprecated).
|
|
44
|
+
* @param {import('nostr-tools').Filter} filter
|
|
45
|
+
* @returns {Promise<import('nostr-tools').Event[]>}
|
|
46
|
+
*/
|
|
47
|
+
_query(filter: import("nostr-tools").Filter): Promise<import("nostr-tools").Event[]>;
|
|
48
|
+
/**
|
|
49
|
+
* Resolves a service for a given pubkey and service identifier.
|
|
50
|
+
*
|
|
51
|
+
* @param {string} pubkey - The pubkey of the service owner.
|
|
52
|
+
* @param {string} serviceId - The 'd' tag identifier of the service (e.g., 'api').
|
|
53
|
+
* @param {Object} [options={}] - Policy options.
|
|
54
|
+
* @param {boolean} [options.requireAttestation=false] - If true, fails if no trusted attestation is found.
|
|
55
|
+
* @param {string} [options.minLevel=null] - Minimum trust level ('self', 'verified', 'hardened').
|
|
56
|
+
* @param {string} [options.standard='nostr-service-trust-v0.1'] - Expected trust standard.
|
|
57
|
+
* @throws {NCC02Error} If verification or policy checks fail.
|
|
58
|
+
* @returns {Promise<ResolvedService>} The verified service details.
|
|
59
|
+
*/
|
|
60
|
+
resolve(pubkey: string, serviceId: string, options?: {
|
|
61
|
+
requireAttestation?: boolean;
|
|
62
|
+
minLevel?: string;
|
|
63
|
+
standard?: string;
|
|
64
|
+
}): Promise<ResolvedService>;
|
|
65
|
+
/**
|
|
66
|
+
* @param {string | undefined} actual
|
|
67
|
+
* @param {string} required
|
|
68
|
+
*/
|
|
69
|
+
_isLevelSufficient(actual: string | undefined, required: string): boolean;
|
|
70
|
+
/**
|
|
71
|
+
* @param {any} att
|
|
72
|
+
* @param {any} tags
|
|
73
|
+
* @param {any[]} revocations
|
|
74
|
+
*/
|
|
75
|
+
_isAttestationValid(att: any, tags: any, revocations: any[]): boolean;
|
|
76
|
+
/**
|
|
77
|
+
* Verifies that the actual fingerprint found during transport-level connection
|
|
78
|
+
* matches the one declared in the signed service record.
|
|
79
|
+
*
|
|
80
|
+
* @param {ResolvedService} resolved - The object returned by resolve().
|
|
81
|
+
* @param {string} actualFingerprint - The fingerprint obtained from the service.
|
|
82
|
+
* @returns {boolean}
|
|
83
|
+
*/
|
|
84
|
+
verifyEndpoint(resolved: ResolvedService, actualFingerprint: string): boolean;
|
|
85
|
+
}
|
|
86
|
+
export type ResolvedService = {
|
|
87
|
+
endpoint: string;
|
|
88
|
+
fingerprint: string;
|
|
89
|
+
expiry: number;
|
|
90
|
+
attestations: any[];
|
|
91
|
+
eventId: string;
|
|
92
|
+
pubkey: string;
|
|
93
|
+
};
|
|
94
|
+
import { SimplePool } from 'nostr-tools';
|
package/package.json
CHANGED
|
@@ -1,13 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ncc-02-js",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "Nostr-native service discovery and trust implementation (NCC-02)",
|
|
5
|
-
"main": "dist/index.
|
|
5
|
+
"main": "dist/index.cjs",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
7
8
|
"type": "module",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.mjs",
|
|
13
|
+
"require": "./dist/index.cjs"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"sideEffects": false,
|
|
8
17
|
"scripts": {
|
|
9
18
|
"test": "node tests/test.js",
|
|
10
|
-
"build": "esbuild src/index.js --bundle --platform=node --format=cjs --outfile=dist/index.
|
|
19
|
+
"build": "esbuild src/index.js --bundle --platform=node --format=cjs --outfile=dist/index.cjs && esbuild src/index.js --bundle --platform=node --format=esm --outfile=dist/index.mjs && tsc --emitDeclarationOnly",
|
|
20
|
+
"prepublishOnly": "npm run build",
|
|
11
21
|
"lint": "eslint src/**/*.js",
|
|
12
22
|
"type-check": "tsc -p jsconfig.json"
|
|
13
23
|
},
|
package/src/models.js
CHANGED
|
@@ -25,12 +25,14 @@ export class NCC02Builder {
|
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
27
|
* Creates a signed Service Record (Kind 30059).
|
|
28
|
-
* @param {
|
|
29
|
-
* @param {string}
|
|
30
|
-
* @param {string}
|
|
31
|
-
* @param {
|
|
28
|
+
* @param {Object} options
|
|
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.
|
|
32
|
+
* @param {number} [options.expiryDays=14] - Expiry in days.
|
|
32
33
|
*/
|
|
33
|
-
createServiceRecord(
|
|
34
|
+
createServiceRecord(options) {
|
|
35
|
+
const { serviceId, endpoint, fingerprint, expiryDays = 14 } = options;
|
|
34
36
|
if (!serviceId) throw new Error('serviceId (d tag) is required');
|
|
35
37
|
if (!endpoint) throw new Error('endpoint (u tag) is required');
|
|
36
38
|
if (!fingerprint) throw new Error('fingerprint (k tag) is required');
|
|
@@ -53,13 +55,15 @@ export class NCC02Builder {
|
|
|
53
55
|
|
|
54
56
|
/**
|
|
55
57
|
* Creates a signed Certificate Attestation (Kind 30060).
|
|
56
|
-
* @param {
|
|
57
|
-
* @param {string}
|
|
58
|
-
* @param {string}
|
|
59
|
-
* @param {string}
|
|
60
|
-
* @param {
|
|
58
|
+
* @param {Object} options
|
|
59
|
+
* @param {string} options.subjectPubkey - The 'subj' tag pubkey.
|
|
60
|
+
* @param {string} options.serviceId - The 'srv' tag identifier.
|
|
61
|
+
* @param {string} options.serviceEventId - The 'e' tag referencing the Service Record.
|
|
62
|
+
* @param {string} [options.level='verified'] - The 'lvl' tag level.
|
|
63
|
+
* @param {number} [options.validDays=30] - Validity in days.
|
|
61
64
|
*/
|
|
62
|
-
createAttestation(
|
|
65
|
+
createAttestation(options) {
|
|
66
|
+
const { subjectPubkey, serviceId, serviceEventId, level = 'verified', validDays = 30 } = options;
|
|
63
67
|
if (!subjectPubkey) throw new Error('subjectPubkey is required');
|
|
64
68
|
if (!serviceId) throw new Error('serviceId is required');
|
|
65
69
|
if (!serviceEventId) throw new Error('serviceEventId (e tag) is required');
|
|
@@ -86,10 +90,12 @@ export class NCC02Builder {
|
|
|
86
90
|
|
|
87
91
|
/**
|
|
88
92
|
* Creates a signed Revocation (Kind 30061).
|
|
89
|
-
* @param {
|
|
90
|
-
* @param {string}
|
|
93
|
+
* @param {Object} options
|
|
94
|
+
* @param {string} options.attestationId - The 'e' tag referencing the attestation.
|
|
95
|
+
* @param {string} [options.reason=''] - Optional reason.
|
|
91
96
|
*/
|
|
92
|
-
createRevocation(
|
|
97
|
+
createRevocation(options) {
|
|
98
|
+
const { attestationId, reason = '' } = options;
|
|
93
99
|
if (!attestationId) throw new Error('attestationId (e tag) is required');
|
|
94
100
|
|
|
95
101
|
const tags = [['e', attestationId]];
|
|
@@ -112,4 +118,4 @@ export class NCC02Builder {
|
|
|
112
118
|
*/
|
|
113
119
|
export function verifyNCC02Event(event) {
|
|
114
120
|
return verifyEvent(event);
|
|
115
|
-
}
|
|
121
|
+
}
|
package/src/resolver.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { verifyEvent } from 'nostr-tools
|
|
1
|
+
import { SimplePool, verifyEvent } from 'nostr-tools';
|
|
2
2
|
import { KINDS } from './models.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -33,12 +33,37 @@ export class NCC02Error extends Error {
|
|
|
33
33
|
*/
|
|
34
34
|
export class NCC02Resolver {
|
|
35
35
|
/**
|
|
36
|
-
* @param {
|
|
37
|
-
* @param {
|
|
36
|
+
* @param {string[]} relays - List of relay URLs.
|
|
37
|
+
* @param {Object} [options={}]
|
|
38
|
+
* @param {SimplePool} [options.pool] - Shared SimplePool instance.
|
|
39
|
+
* @param {string[]} [options.trustedCAPubkeys=[]] - List of trusted CA pubkeys.
|
|
38
40
|
*/
|
|
39
|
-
constructor(
|
|
40
|
-
|
|
41
|
-
|
|
41
|
+
constructor(relays, options = {}) {
|
|
42
|
+
if (!Array.isArray(relays)) {
|
|
43
|
+
throw new Error("NCC02Resolver expects an array of relay URLs.");
|
|
44
|
+
}
|
|
45
|
+
this.relays = relays;
|
|
46
|
+
this.pool = options.pool || new SimplePool();
|
|
47
|
+
this.ownsPool = !options.pool;
|
|
48
|
+
this.trustedCAPubkeys = new Set(options.trustedCAPubkeys || []);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Internal query helper using SimplePool.subscribeMany (since list() is deprecated).
|
|
53
|
+
* @param {import('nostr-tools').Filter} filter
|
|
54
|
+
* @returns {Promise<import('nostr-tools').Event[]>}
|
|
55
|
+
*/
|
|
56
|
+
async _query(filter) {
|
|
57
|
+
return new Promise((resolve) => {
|
|
58
|
+
/** @type {import('nostr-tools').Event[]} */
|
|
59
|
+
const events = [];
|
|
60
|
+
// subscribeMany(relays, filters, callbacks)
|
|
61
|
+
// @ts-ignore - subscribeMany filters parameter type mismatch with simple Object
|
|
62
|
+
const sub = this.pool.subscribeMany(this.relays, [filter], {
|
|
63
|
+
onevent(e) { events.push(e); },
|
|
64
|
+
oneose() { sub.close(); resolve(events); }
|
|
65
|
+
});
|
|
66
|
+
});
|
|
42
67
|
}
|
|
43
68
|
|
|
44
69
|
/**
|
|
@@ -62,7 +87,7 @@ export class NCC02Resolver {
|
|
|
62
87
|
|
|
63
88
|
let serviceEvents;
|
|
64
89
|
try {
|
|
65
|
-
serviceEvents = await this.
|
|
90
|
+
serviceEvents = await this._query({
|
|
66
91
|
kinds: [KINDS.SERVICE_RECORD],
|
|
67
92
|
authors: [pubkey],
|
|
68
93
|
'#d': [serviceId]
|
|
@@ -105,8 +130,8 @@ export class NCC02Resolver {
|
|
|
105
130
|
let revocations;
|
|
106
131
|
try {
|
|
107
132
|
[attestations, revocations] = await Promise.all([
|
|
108
|
-
this.
|
|
109
|
-
this.
|
|
133
|
+
this._query({ kinds: [KINDS.ATTESTATION], '#e': [serviceEvent.id] }),
|
|
134
|
+
this._query({ kinds: [KINDS.REVOCATION] })
|
|
110
135
|
]);
|
|
111
136
|
} catch (err) {
|
|
112
137
|
throw new NCC02Error('RELAY_ERROR', 'Failed to query relay for attestations/revocations', err);
|
|
@@ -209,4 +234,4 @@ export class NCC02Resolver {
|
|
|
209
234
|
verifyEndpoint(resolved, actualFingerprint) {
|
|
210
235
|
return resolved.fingerprint === actualFingerprint;
|
|
211
236
|
}
|
|
212
|
-
}
|
|
237
|
+
}
|