ncc-02-js 0.3.1 → 0.5.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 CHANGED
@@ -10,6 +10,7 @@ This library provides tools for service owners to publish records and for client
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.
13
+ - **Privacy Controls**: Required `private` tags and optional encrypted `privateRecipients` listings let you declare visibility and invite-only recipients.
13
14
  - **Fail-Closed**: Explicit error reporting for policy or verification failures.
14
15
 
15
16
  ## Installation
@@ -48,39 +49,110 @@ try {
48
49
  }
49
50
  ```
50
51
 
51
- ### 2. Publish a Service Record
52
- ...
53
- ### Trust Model & Security
52
+ ### 2. Publish Service Records and Attestations
54
53
 
55
- ### Trust Levels
56
- - `self`: Asserted by the service owner (default if no attestation).
57
- - `verified`: Attested by a trusted third party.
58
- - `hardened`: Attested by a third party with stricter verification (e.g., physical proof or long-term history).
54
+ ```javascript
55
+ import { NCC02Builder } from 'ncc-02-js';
56
+
57
+ const builder = new NCC02Builder(privateKey);
58
+
59
+ const serviceRecord = await builder.createServiceRecord({
60
+ serviceId: 'api',
61
+ endpoint: 'https://service.example.com',
62
+ fingerprint: '<spki fingerprint>',
63
+ expiryDays: 7
64
+ });
65
+
66
+ const attestation = await builder.createAttestation({
67
+ subjectPubkey: ownerPubkey,
68
+ serviceId: 'api',
69
+ serviceEventId: serviceRecord.id,
70
+ level: 'verified',
71
+ validDays: 30
72
+ });
73
+
74
+ await builder.createRevocation({
75
+ attestationId: attestation.id,
76
+ reason: 'Key rotation'
77
+ });
78
+ ```
79
+
80
+ The builder helpers emit the expected NCC-02 event kinds: Kind 30059 for service records, 30060 for attestations, and 30061 for revocations. `createServiceRecord` always includes the `d` and `exp` tags while optionally populating `u` and `k`. Attestations include `subj`, `srv`, `e`, `std`, `lvl`, `nbf`, and `exp`. Revocations only need the `e` tag and an optional reason. You can supply either a raw private key (hex string or `Uint8Array`) or a signer implementing `getPublicKey`/`signEvent`.
81
+
82
+ #### Private Service Metadata
83
+ Service records now require a boolean `private` tag (set via `isPrivate` when calling `createServiceRecord`). When private services should only be used by a curated set of users, you can provide pre-encrypted `privateRecipients` values that contain authorized `npub` identifiers. The helper utilities derive NIP-44 conversation keys: `await encryptPrivateRecipients(ownerPrivateKey, recipients)` when publishing so each recipient gets a ciphertext they can decrypt, and `await isPrivateRecipientAuthorized(privateRecipients, ownerPubkey, recipientPrivateKey)` on the client-side to verify if the signed-in key is on the allowlist (or `await decryptPrivateRecipient(...)` if you need the raw `npub`). The helpers accept either raw private keys or NIP-07/NIP-46 style signer objects (they will call `nip44Encrypt/nip44Decrypt` or fall back to legacy `nip04` if present). The resolver surfaces `isPrivate` and `privateRecipients` on the returned `ServiceStatus`.
84
+
85
+ ### 3. Trust Model & Security
86
+
87
+ The resolver treats the service owner’s pubkey as the root authority. Certificates and revocations are additive layers that you opt into via `requireAttestation` or `minLevel`. Validation failures surface as `NCC02Error` instances with codes such as `INVALID_SIGNATURE`, `EXPIRED`, or `POLICY_FAILURE`, helping clients react instead of defaulting to insecure fallbacks.
88
+
89
+ ### 4. Resolution Optimization
90
+
91
+ To avoid unnecessary relay traffic, attestation and revocation feeds are only fetched when the resolver’s policy requests them. Keep expiries short and rotate fingerprints with the `k` tag to reduce the blast radius of compromised keys.
59
92
 
60
- ### Resolution Optimization
61
- The resolver is designed to be network-efficient. It will only query for attestations (Kind 30060) and revocations (Kind 30061) if the provided policy requires them (e.g., when `requireAttestation` is set to `true` or a `minLevel` higher than `self` is requested).
93
+ ### 5. Threat Model
62
94
 
63
- ### Threat Model
64
- ...
65
- ### API Reference
95
+ The library defends against endpoint impersonation, MITM, stale records, and relay censorship by enforcing signed records, short expiries, revocation checks, and optional third-party attestations. It deliberately keeps scope focused on application-layer trust and does not replace TLS or browser PKI.
96
+
97
+ ### 6. Testing with MockRelay
98
+
99
+ ```javascript
100
+ import { MockRelay, NCC02Builder, NCC02Resolver } from 'ncc-02-js';
101
+
102
+ const mock = new MockRelay();
103
+ const builder = new NCC02Builder(privateKey);
104
+ const serviceRecord = await builder.createServiceRecord({ serviceId: 'api', endpoint: 'https://example', fingerprint: '<fp>' });
105
+ await mock.publish(serviceRecord);
106
+
107
+ const resolver = new NCC02Resolver(['mock://local'], { pool: mock });
108
+ await resolver.resolve(ownerPubkey, 'api');
109
+ ```
110
+
111
+ `MockRelay` mimics a Nostr relay by verifying signatures and honoring `kinds`, `authors`, `ids`, and `#tag` filters. It makes writing unit tests and integration suites deterministic without external dependencies.
112
+
113
+ ### 7. Endpoint Verification Helpers
114
+
115
+ After resolving a service, call `resolver.verifyEndpoint(serviceStatus, actualFingerprint)` to ensure the runtime transport fingerprint matches the declared `k` tag. Use `verifyNCC02Event` when you ingest raw events and `isExpired` to quickly skip expired records before attempting network validation.
116
+
117
+ ## API Reference
66
118
 
67
119
  ### `NCC02Resolver(relays, options)`
68
120
  - `relays`: Array of relay URLs.
69
- - `options.pool`: (Optional) Existing `nostr-tools` SimplePool.
70
- - `options.trustedCAPubkeys`: (Optional) Array of pubkeys trusted to issue attestations.
121
+ - `options.pool`: (Optional) Shared `nostr-tools` `SimplePool`.
122
+ - `options.trustedCAPubkeys`: Optional array of certifier pubkeys that may issue trusted attestations.
71
123
 
72
124
  #### `resolve(pubkey, serviceId, options)`
73
- - `options.requireAttestation`: Fails if no trusted attestation is found.
74
- - `options.minLevel`: Minimum trust level required.
125
+ - `options.requireAttestation`: Reject if no trusted attestation is available.
126
+ - `options.minLevel`: Minimum `lvl` tag level (`self`, `verified`, `hardened`).
127
+ - `options.standard`: Expected `std` tag value (defaults to `nostr-service-trust-v0.1`).
128
+ - Returns `ServiceStatus` including `endpoint`, `fingerprint`, `expiry`, `attestations`, `attestationCount`, `isRevoked`, `eventId`, `pubkey`, and the raw `serviceEvent`.
129
+
130
+ #### `verifyEndpoint(resolved, actualFingerprint)`
131
+ - Compares the resolved fingerprint with the observed transport fingerprint for an additional rejection guard.
75
132
 
76
133
  #### `close()`
77
- Closes WebSocket connections to all relays. If the resolver was initialized with an external `pool`, this will *not* close the pool; it only untracks the relays for this instance. If the resolver created its own internal pool, it will close it entirely.
134
+ - Closes relay subscriptions when the resolver owns the `SimplePool`.
135
+
136
+ ### `NCC02Builder(signer)`
137
+ - `signer`: Hex private key, `Uint8Array`, or custom signer with `getPublicKey`/`signEvent`.
138
+
139
+ #### `createServiceRecord({ serviceId, endpoint?, fingerprint?, expiryDays? })`
140
+ - Returns a signed Kind 30059 event. `endpoint` maps to `u`, `fingerprint` to `k`, and `expiryDays` controls `exp`.
141
+
142
+ #### `createAttestation({ subjectPubkey, serviceId, serviceEventId, level?, validDays? })`
143
+ - Emits Kind 30060 with `subj`, `srv`, `e`, `std`, `lvl`, `nbf`, and `exp`.
144
+
145
+ #### `createRevocation({ attestationId, reason? })`
146
+ - Emits Kind 30061 pointing to the revoked attestation.
78
147
 
148
+ ### `MockRelay`
149
+ - `publish(event)`: Verifies and stores the event (returns `true` if accepted).
150
+ - `query(filter)`: Filters stored events by `kinds`, `authors`, `ids`, and `#tag` criteria.
79
151
 
80
- ### `NCC02Builder(privateKey)`
81
- - `createServiceRecord({ serviceId, endpoint?, fingerprint?, expiryDays? })`
82
- - `createAttestation({ subjectPubkey, serviceId, serviceEventId, level, validDays })`
83
- - `createRevocation({ attestationId, reason })`
152
+ ### Helpers
153
+ - `verifyNCC02Event(event)`: Signature verification wrapper.
154
+ - `isExpired(event)`: Returns `true` when the `exp` tag is in the past.
155
+ - `KINDS`: Enum with `SERVICE_RECORD`, `ATTESTATION`, and `REVOCATION`.
84
156
 
85
157
  ## License
86
158