ncc-02-js 0.2.0 → 0.2.2

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,50 @@
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 {string} subjectPubkey
38
+ * @param {string} serviceId
39
+ * @param {string} serviceEventId
40
+ * @param {string} [level='verified']
41
+ * @param {number} [validDays=30]
42
+ */
43
+ createAttestation(subjectPubkey: string, serviceId: string, serviceEventId: string, level?: string, validDays?: number): import("nostr-tools/core").VerifiedEvent;
44
+ /**
45
+ * Creates a signed Revocation (Kind 30061).
46
+ * @param {string} attestationId
47
+ * @param {string} [reason='']
48
+ */
49
+ createRevocation(attestationId: string, reason?: string): import("nostr-tools/core").VerifiedEvent;
50
+ }
@@ -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 {Object} filter
45
+ * @returns {Promise<any[]>}
46
+ */
47
+ _query(filter: any): Promise<any[]>;
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.2",
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
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.cjs",
13
+ "types": "./dist/index.d.ts"
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');
@@ -60,6 +62,10 @@ export class NCC02Builder {
60
62
  * @param {number} [validDays=30]
61
63
  */
62
64
  createAttestation(subjectPubkey, serviceId, serviceEventId, level = 'verified', validDays = 30) {
65
+ // Keeping signature backward compatible for now unless requested,
66
+ // but improving internal logic slightly if needed.
67
+ // Ideally this should also be options object but user only asked for createServiceRecord fix.
68
+ // I'll leave it to minimize breaking changes scope creep.
63
69
  if (!subjectPubkey) throw new Error('subjectPubkey is required');
64
70
  if (!serviceId) throw new Error('serviceId is required');
65
71
  if (!serviceEventId) throw new Error('serviceEventId (e tag) is required');
@@ -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,35 @@ 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 {Object} filter
54
+ * @returns {Promise<any[]>}
55
+ */
56
+ async _query(filter) {
57
+ return new Promise((resolve) => {
58
+ const events = [];
59
+ // subscribeMany(relays, filters, callbacks)
60
+ const sub = this.pool.subscribeMany(this.relays, [filter], {
61
+ onevent(e) { events.push(e); },
62
+ oneose() { sub.close(); resolve(events); }
63
+ });
64
+ });
42
65
  }
43
66
 
44
67
  /**
@@ -62,7 +85,7 @@ export class NCC02Resolver {
62
85
 
63
86
  let serviceEvents;
64
87
  try {
65
- serviceEvents = await this.relay.query({
88
+ serviceEvents = await this._query({
66
89
  kinds: [KINDS.SERVICE_RECORD],
67
90
  authors: [pubkey],
68
91
  '#d': [serviceId]
@@ -105,8 +128,8 @@ export class NCC02Resolver {
105
128
  let revocations;
106
129
  try {
107
130
  [attestations, revocations] = await Promise.all([
108
- this.relay.query({ kinds: [KINDS.ATTESTATION], '#e': [serviceEvent.id] }),
109
- this.relay.query({ kinds: [KINDS.REVOCATION] })
131
+ this._query({ kinds: [KINDS.ATTESTATION], '#e': [serviceEvent.id] }),
132
+ this._query({ kinds: [KINDS.REVOCATION] })
110
133
  ]);
111
134
  } catch (err) {
112
135
  throw new NCC02Error('RELAY_ERROR', 'Failed to query relay for attestations/revocations', err);
@@ -209,4 +232,4 @@ export class NCC02Resolver {
209
232
  verifyEndpoint(resolved, actualFingerprint) {
210
233
  return resolved.fingerprint === actualFingerprint;
211
234
  }
212
- }
235
+ }