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.
@@ -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
+ }
@@ -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.0",
3
+ "version": "0.2.3",
4
4
  "description": "Nostr-native service discovery and trust implementation (NCC-02)",
5
- "main": "dist/index.js",
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.js && esbuild src/index.js --bundle --platform=node --format=esm --outfile=dist/index.mjs",
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 {string} serviceId
29
- * @param {string} endpoint
30
- * @param {string} fingerprint
31
- * @param {number} [expiryDays=14]
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(serviceId, endpoint, fingerprint, expiryDays = 14) {
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 {string} subjectPubkey
57
- * @param {string} serviceId
58
- * @param {string} serviceEventId
59
- * @param {string} [level='verified']
60
- * @param {number} [validDays=30]
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(subjectPubkey, serviceId, serviceEventId, level = 'verified', validDays = 30) {
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 {string} attestationId
90
- * @param {string} [reason='']
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(attestationId, reason = '') {
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/pure';
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 {any} relay - A relay client providing a `query` method.
37
- * @param {string[]} [trustedCAPubkeys=[]] - List of CA pubkeys trusted by this client.
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(relay, trustedCAPubkeys = []) {
40
- this.relay = relay;
41
- this.trustedCAPubkeys = new Set(trustedCAPubkeys);
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.relay.query({
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.relay.query({ kinds: [KINDS.ATTESTATION], '#e': [serviceEvent.id] }),
109
- this.relay.query({ kinds: [KINDS.REVOCATION] })
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
+ }