ncc-02-js 0.2.2 → 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 +15 -11
- package/dist/index.mjs +15 -11
- package/dist/models.d.ts +20 -9
- package/dist/resolver.d.ts +3 -3
- package/package.json +3 -3
- package/src/models.js +13 -13
- package/src/resolver.js +4 -2
package/README.md
CHANGED
|
@@ -25,12 +25,15 @@ npm install ncc-02-js
|
|
|
25
25
|
```javascript
|
|
26
26
|
import { NCC02Resolver } from 'ncc-02-js';
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
// Initialize with relay URLs and optional trusted CA pubkeys
|
|
29
|
+
const resolver = new NCC02Resolver(['wss://relay.damus.io'], {
|
|
30
|
+
trustedCAPubkeys: ['ca_pubkey_hex']
|
|
31
|
+
});
|
|
29
32
|
|
|
30
33
|
try {
|
|
31
34
|
const service = await resolver.resolve(ownerPubkey, 'api', {
|
|
32
35
|
requireAttestation: true,
|
|
33
|
-
minLevel: 'verified'
|
|
36
|
+
minLevel: 'verified' // 'self', 'verified', 'hardened'
|
|
34
37
|
});
|
|
35
38
|
console.log('Resolved endpoint:', service.endpoint);
|
|
36
39
|
} catch (err) {
|
|
@@ -44,20 +47,69 @@ try {
|
|
|
44
47
|
import { NCC02Builder } from 'ncc-02-js';
|
|
45
48
|
|
|
46
49
|
const builder = new NCC02Builder(privateKey);
|
|
47
|
-
|
|
48
|
-
//
|
|
50
|
+
|
|
51
|
+
// Example 1: IP-based Service
|
|
52
|
+
const event = builder.createServiceRecord({
|
|
53
|
+
serviceId: 'media',
|
|
54
|
+
endpoint: 'https://203.0.113.45:8443',
|
|
55
|
+
fingerprint: 'sha256:fingerprint',
|
|
56
|
+
expiryDays: 14
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// Example 2: Tor Onion Service
|
|
60
|
+
const onionEvent = builder.createServiceRecord({
|
|
61
|
+
serviceId: 'wallet',
|
|
62
|
+
endpoint: 'tcp://vww6ybal4bd7szmgncyruucpgfkqahzddi37ktceo3ah7ngmcopnpyyd.onion:80',
|
|
63
|
+
fingerprint: 'sha256:fingerprint',
|
|
64
|
+
expiryDays: 7
|
|
65
|
+
});
|
|
66
|
+
// publish events to relays...
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### 3. Issue an Attestation (CA)
|
|
70
|
+
|
|
71
|
+
```javascript
|
|
72
|
+
const caBuilder = new NCC02Builder(caPrivateKey);
|
|
73
|
+
const attestation = caBuilder.createAttestation({
|
|
74
|
+
subjectPubkey: ownerPubkey,
|
|
75
|
+
serviceId: 'api',
|
|
76
|
+
serviceEventId: serviceRecordEventId,
|
|
77
|
+
level: 'verified',
|
|
78
|
+
validDays: 30
|
|
79
|
+
});
|
|
49
80
|
```
|
|
50
81
|
|
|
51
|
-
##
|
|
82
|
+
## Trust Model & Security
|
|
83
|
+
|
|
84
|
+
### Trust Levels
|
|
85
|
+
- `self`: Asserted by the service owner (default if no attestation).
|
|
86
|
+
- `verified`: Attested by a trusted third party.
|
|
87
|
+
- `hardened`: Attested by a third party with stricter verification (e.g., physical proof or long-term history).
|
|
88
|
+
|
|
89
|
+
### Threat Model
|
|
90
|
+
- **Endpoint Impersonation**: Prevented by binding the endpoint URI to a public key fingerprint (`k` tag).
|
|
91
|
+
- **Man-in-the-Middle (MITM)**: Mitigated via cryptographic pinning of transport-level keys.
|
|
92
|
+
- **Stale Records**: Limited by required expiry (`exp`) and support for revocations.
|
|
93
|
+
- **Relay Censorship**: Mitigated by querying multiple relays (implemented via `SimplePool`).
|
|
94
|
+
|
|
95
|
+
### Fail-Closed Design
|
|
96
|
+
The library follows a fail-closed principle. If a policy requirement is not met (e.g., `requireAttestation: true` but no valid attestation is found), it throws an `NCC02Error` rather than returning a partially verified record.
|
|
97
|
+
|
|
98
|
+
## API Reference
|
|
99
|
+
|
|
100
|
+
### `NCC02Resolver(relays, options)`
|
|
101
|
+
- `relays`: Array of relay URLs.
|
|
102
|
+
- `options.pool`: (Optional) Existing `nostr-tools` SimplePool.
|
|
103
|
+
- `options.trustedCAPubkeys`: (Optional) Array of pubkeys trusted to issue attestations.
|
|
52
104
|
|
|
53
|
-
|
|
54
|
-
- `
|
|
55
|
-
- `
|
|
105
|
+
#### `resolve(pubkey, serviceId, options)`
|
|
106
|
+
- `options.requireAttestation`: Fails if no trusted attestation is found.
|
|
107
|
+
- `options.minLevel`: Minimum trust level required.
|
|
56
108
|
|
|
57
|
-
### `NCC02Builder`
|
|
58
|
-
- `createServiceRecord(
|
|
59
|
-
- `createAttestation(
|
|
60
|
-
- `createRevocation(attestationId, reason)`
|
|
109
|
+
### `NCC02Builder(privateKey)`
|
|
110
|
+
- `createServiceRecord({ serviceId, endpoint, fingerprint, expiryDays })`
|
|
111
|
+
- `createAttestation({ subjectPubkey, serviceId, serviceEventId, level, validDays })`
|
|
112
|
+
- `createRevocation({ attestationId, reason })`
|
|
61
113
|
|
|
62
114
|
## License
|
|
63
115
|
|
package/dist/index.cjs
CHANGED
|
@@ -2535,13 +2535,15 @@ var NCC02Builder = class {
|
|
|
2535
2535
|
}
|
|
2536
2536
|
/**
|
|
2537
2537
|
* Creates a signed Certificate Attestation (Kind 30060).
|
|
2538
|
-
* @param {
|
|
2539
|
-
* @param {string}
|
|
2540
|
-
* @param {string}
|
|
2541
|
-
* @param {string}
|
|
2542
|
-
* @param {
|
|
2538
|
+
* @param {Object} options
|
|
2539
|
+
* @param {string} options.subjectPubkey - The 'subj' tag pubkey.
|
|
2540
|
+
* @param {string} options.serviceId - The 'srv' tag identifier.
|
|
2541
|
+
* @param {string} options.serviceEventId - The 'e' tag referencing the Service Record.
|
|
2542
|
+
* @param {string} [options.level='verified'] - The 'lvl' tag level.
|
|
2543
|
+
* @param {number} [options.validDays=30] - Validity in days.
|
|
2543
2544
|
*/
|
|
2544
|
-
createAttestation(
|
|
2545
|
+
createAttestation(options) {
|
|
2546
|
+
const { subjectPubkey, serviceId, serviceEventId, level = "verified", validDays = 30 } = options;
|
|
2545
2547
|
if (!subjectPubkey) throw new Error("subjectPubkey is required");
|
|
2546
2548
|
if (!serviceId) throw new Error("serviceId is required");
|
|
2547
2549
|
if (!serviceEventId) throw new Error("serviceEventId (e tag) is required");
|
|
@@ -2566,10 +2568,12 @@ var NCC02Builder = class {
|
|
|
2566
2568
|
}
|
|
2567
2569
|
/**
|
|
2568
2570
|
* Creates a signed Revocation (Kind 30061).
|
|
2569
|
-
* @param {
|
|
2570
|
-
* @param {string}
|
|
2571
|
+
* @param {Object} options
|
|
2572
|
+
* @param {string} options.attestationId - The 'e' tag referencing the attestation.
|
|
2573
|
+
* @param {string} [options.reason=''] - Optional reason.
|
|
2571
2574
|
*/
|
|
2572
|
-
createRevocation(
|
|
2575
|
+
createRevocation(options) {
|
|
2576
|
+
const { attestationId, reason = "" } = options;
|
|
2573
2577
|
if (!attestationId) throw new Error("attestationId (e tag) is required");
|
|
2574
2578
|
const tags = [["e", attestationId]];
|
|
2575
2579
|
if (reason) tags.push(["reason", reason]);
|
|
@@ -7688,8 +7692,8 @@ var NCC02Resolver = class {
|
|
|
7688
7692
|
}
|
|
7689
7693
|
/**
|
|
7690
7694
|
* Internal query helper using SimplePool.subscribeMany (since list() is deprecated).
|
|
7691
|
-
* @param {
|
|
7692
|
-
* @returns {Promise<
|
|
7695
|
+
* @param {import('nostr-tools').Filter} filter
|
|
7696
|
+
* @returns {Promise<import('nostr-tools').Event[]>}
|
|
7693
7697
|
*/
|
|
7694
7698
|
async _query(filter) {
|
|
7695
7699
|
return new Promise((resolve) => {
|
package/dist/index.mjs
CHANGED
|
@@ -2501,13 +2501,15 @@ var NCC02Builder = class {
|
|
|
2501
2501
|
}
|
|
2502
2502
|
/**
|
|
2503
2503
|
* Creates a signed Certificate Attestation (Kind 30060).
|
|
2504
|
-
* @param {
|
|
2505
|
-
* @param {string}
|
|
2506
|
-
* @param {string}
|
|
2507
|
-
* @param {string}
|
|
2508
|
-
* @param {
|
|
2504
|
+
* @param {Object} options
|
|
2505
|
+
* @param {string} options.subjectPubkey - The 'subj' tag pubkey.
|
|
2506
|
+
* @param {string} options.serviceId - The 'srv' tag identifier.
|
|
2507
|
+
* @param {string} options.serviceEventId - The 'e' tag referencing the Service Record.
|
|
2508
|
+
* @param {string} [options.level='verified'] - The 'lvl' tag level.
|
|
2509
|
+
* @param {number} [options.validDays=30] - Validity in days.
|
|
2509
2510
|
*/
|
|
2510
|
-
createAttestation(
|
|
2511
|
+
createAttestation(options) {
|
|
2512
|
+
const { subjectPubkey, serviceId, serviceEventId, level = "verified", validDays = 30 } = options;
|
|
2511
2513
|
if (!subjectPubkey) throw new Error("subjectPubkey is required");
|
|
2512
2514
|
if (!serviceId) throw new Error("serviceId is required");
|
|
2513
2515
|
if (!serviceEventId) throw new Error("serviceEventId (e tag) is required");
|
|
@@ -2532,10 +2534,12 @@ var NCC02Builder = class {
|
|
|
2532
2534
|
}
|
|
2533
2535
|
/**
|
|
2534
2536
|
* Creates a signed Revocation (Kind 30061).
|
|
2535
|
-
* @param {
|
|
2536
|
-
* @param {string}
|
|
2537
|
+
* @param {Object} options
|
|
2538
|
+
* @param {string} options.attestationId - The 'e' tag referencing the attestation.
|
|
2539
|
+
* @param {string} [options.reason=''] - Optional reason.
|
|
2537
2540
|
*/
|
|
2538
|
-
createRevocation(
|
|
2541
|
+
createRevocation(options) {
|
|
2542
|
+
const { attestationId, reason = "" } = options;
|
|
2539
2543
|
if (!attestationId) throw new Error("attestationId (e tag) is required");
|
|
2540
2544
|
const tags = [["e", attestationId]];
|
|
2541
2545
|
if (reason) tags.push(["reason", reason]);
|
|
@@ -7654,8 +7658,8 @@ var NCC02Resolver = class {
|
|
|
7654
7658
|
}
|
|
7655
7659
|
/**
|
|
7656
7660
|
* Internal query helper using SimplePool.subscribeMany (since list() is deprecated).
|
|
7657
|
-
* @param {
|
|
7658
|
-
* @returns {Promise<
|
|
7661
|
+
* @param {import('nostr-tools').Filter} filter
|
|
7662
|
+
* @returns {Promise<import('nostr-tools').Event[]>}
|
|
7659
7663
|
*/
|
|
7660
7664
|
async _query(filter) {
|
|
7661
7665
|
return new Promise((resolve) => {
|
package/dist/models.d.ts
CHANGED
|
@@ -34,17 +34,28 @@ export class NCC02Builder {
|
|
|
34
34
|
}): import("nostr-tools/core").VerifiedEvent;
|
|
35
35
|
/**
|
|
36
36
|
* Creates a signed Certificate Attestation (Kind 30060).
|
|
37
|
-
* @param {
|
|
38
|
-
* @param {string}
|
|
39
|
-
* @param {string}
|
|
40
|
-
* @param {string}
|
|
41
|
-
* @param {
|
|
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.
|
|
42
43
|
*/
|
|
43
|
-
createAttestation(
|
|
44
|
+
createAttestation(options: {
|
|
45
|
+
subjectPubkey: string;
|
|
46
|
+
serviceId: string;
|
|
47
|
+
serviceEventId: string;
|
|
48
|
+
level?: string;
|
|
49
|
+
validDays?: number;
|
|
50
|
+
}): import("nostr-tools/core").VerifiedEvent;
|
|
44
51
|
/**
|
|
45
52
|
* Creates a signed Revocation (Kind 30061).
|
|
46
|
-
* @param {
|
|
47
|
-
* @param {string}
|
|
53
|
+
* @param {Object} options
|
|
54
|
+
* @param {string} options.attestationId - The 'e' tag referencing the attestation.
|
|
55
|
+
* @param {string} [options.reason=''] - Optional reason.
|
|
48
56
|
*/
|
|
49
|
-
createRevocation(
|
|
57
|
+
createRevocation(options: {
|
|
58
|
+
attestationId: string;
|
|
59
|
+
reason?: string;
|
|
60
|
+
}): import("nostr-tools/core").VerifiedEvent;
|
|
50
61
|
}
|
package/dist/resolver.d.ts
CHANGED
|
@@ -41,10 +41,10 @@ export class NCC02Resolver {
|
|
|
41
41
|
trustedCAPubkeys: Set<string>;
|
|
42
42
|
/**
|
|
43
43
|
* Internal query helper using SimplePool.subscribeMany (since list() is deprecated).
|
|
44
|
-
* @param {
|
|
45
|
-
* @returns {Promise<
|
|
44
|
+
* @param {import('nostr-tools').Filter} filter
|
|
45
|
+
* @returns {Promise<import('nostr-tools').Event[]>}
|
|
46
46
|
*/
|
|
47
|
-
_query(filter:
|
|
47
|
+
_query(filter: import("nostr-tools").Filter): Promise<import("nostr-tools").Event[]>;
|
|
48
48
|
/**
|
|
49
49
|
* Resolves a service for a given pubkey and service identifier.
|
|
50
50
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
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
5
|
"main": "dist/index.cjs",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
"type": "module",
|
|
9
9
|
"exports": {
|
|
10
10
|
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
11
12
|
"import": "./dist/index.mjs",
|
|
12
|
-
"require": "./dist/index.cjs"
|
|
13
|
-
"types": "./dist/index.d.ts"
|
|
13
|
+
"require": "./dist/index.cjs"
|
|
14
14
|
}
|
|
15
15
|
},
|
|
16
16
|
"sideEffects": false,
|
package/src/models.js
CHANGED
|
@@ -55,17 +55,15 @@ export class NCC02Builder {
|
|
|
55
55
|
|
|
56
56
|
/**
|
|
57
57
|
* Creates a signed Certificate Attestation (Kind 30060).
|
|
58
|
-
* @param {
|
|
59
|
-
* @param {string}
|
|
60
|
-
* @param {string}
|
|
61
|
-
* @param {string}
|
|
62
|
-
* @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.
|
|
63
64
|
*/
|
|
64
|
-
createAttestation(
|
|
65
|
-
|
|
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.
|
|
65
|
+
createAttestation(options) {
|
|
66
|
+
const { subjectPubkey, serviceId, serviceEventId, level = 'verified', validDays = 30 } = options;
|
|
69
67
|
if (!subjectPubkey) throw new Error('subjectPubkey is required');
|
|
70
68
|
if (!serviceId) throw new Error('serviceId is required');
|
|
71
69
|
if (!serviceEventId) throw new Error('serviceEventId (e tag) is required');
|
|
@@ -92,10 +90,12 @@ export class NCC02Builder {
|
|
|
92
90
|
|
|
93
91
|
/**
|
|
94
92
|
* Creates a signed Revocation (Kind 30061).
|
|
95
|
-
* @param {
|
|
96
|
-
* @param {string}
|
|
93
|
+
* @param {Object} options
|
|
94
|
+
* @param {string} options.attestationId - The 'e' tag referencing the attestation.
|
|
95
|
+
* @param {string} [options.reason=''] - Optional reason.
|
|
97
96
|
*/
|
|
98
|
-
createRevocation(
|
|
97
|
+
createRevocation(options) {
|
|
98
|
+
const { attestationId, reason = '' } = options;
|
|
99
99
|
if (!attestationId) throw new Error('attestationId (e tag) is required');
|
|
100
100
|
|
|
101
101
|
const tags = [['e', attestationId]];
|
package/src/resolver.js
CHANGED
|
@@ -50,13 +50,15 @@ export class NCC02Resolver {
|
|
|
50
50
|
|
|
51
51
|
/**
|
|
52
52
|
* Internal query helper using SimplePool.subscribeMany (since list() is deprecated).
|
|
53
|
-
* @param {
|
|
54
|
-
* @returns {Promise<
|
|
53
|
+
* @param {import('nostr-tools').Filter} filter
|
|
54
|
+
* @returns {Promise<import('nostr-tools').Event[]>}
|
|
55
55
|
*/
|
|
56
56
|
async _query(filter) {
|
|
57
57
|
return new Promise((resolve) => {
|
|
58
|
+
/** @type {import('nostr-tools').Event[]} */
|
|
58
59
|
const events = [];
|
|
59
60
|
// subscribeMany(relays, filters, callbacks)
|
|
61
|
+
// @ts-ignore - subscribeMany filters parameter type mismatch with simple Object
|
|
60
62
|
const sub = this.pool.subscribeMany(this.relays, [filter], {
|
|
61
63
|
onevent(e) { events.push(e); },
|
|
62
64
|
oneose() { sub.close(); resolve(events); }
|