ncc-02-js 0.3.0 → 0.4.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 +78 -44
- package/dist/index.cjs +220 -78
- package/dist/index.mjs +219 -78
- package/dist/models.d.ts +29 -7
- package/dist/resolver.d.ts +78 -14
- package/package.json +1 -1
- package/src/models.js +105 -21
- package/src/resolver.js +157 -79
package/dist/index.mjs
CHANGED
|
@@ -2464,12 +2464,73 @@ var KINDS = {
|
|
|
2464
2464
|
};
|
|
2465
2465
|
var NCC02Builder = class {
|
|
2466
2466
|
/**
|
|
2467
|
-
* @param {string | Uint8Array}
|
|
2467
|
+
* @param {string | Uint8Array | NostrSigner} signer - Raw private key or asynchronous signer.
|
|
2468
2468
|
*/
|
|
2469
|
-
constructor(
|
|
2470
|
-
if (!
|
|
2471
|
-
this.
|
|
2472
|
-
this.
|
|
2469
|
+
constructor(signer) {
|
|
2470
|
+
if (!signer) throw new Error("Signer or private key is required");
|
|
2471
|
+
this.signer = this._normalizeSigner(signer);
|
|
2472
|
+
this._pubkeyPromise = this.signer.getPublicKey();
|
|
2473
|
+
this._pubkey = void 0;
|
|
2474
|
+
}
|
|
2475
|
+
async _getPublicKey() {
|
|
2476
|
+
if (!this._pubkey) {
|
|
2477
|
+
this._pubkey = await this._pubkeyPromise;
|
|
2478
|
+
}
|
|
2479
|
+
return this._pubkey;
|
|
2480
|
+
}
|
|
2481
|
+
/**
|
|
2482
|
+
* @param {any} event
|
|
2483
|
+
*/
|
|
2484
|
+
async _finalizeEvent(event) {
|
|
2485
|
+
const pubkey = await this._getPublicKey();
|
|
2486
|
+
const eventWithPubkey = { ...event, pubkey };
|
|
2487
|
+
const signed = await this.signer.signEvent(eventWithPubkey);
|
|
2488
|
+
if (!signed || typeof signed.id !== "string" || typeof signed.sig !== "string") {
|
|
2489
|
+
throw new Error("Signer must return a signed event with id and sig");
|
|
2490
|
+
}
|
|
2491
|
+
return signed;
|
|
2492
|
+
}
|
|
2493
|
+
/**
|
|
2494
|
+
* @param {any} signer
|
|
2495
|
+
* @returns {NostrSigner}
|
|
2496
|
+
*/
|
|
2497
|
+
_normalizeSigner(signer) {
|
|
2498
|
+
if (typeof signer === "string" || signer instanceof Uint8Array) {
|
|
2499
|
+
const privateKey = typeof signer === "string" ? hexToBytes2(signer) : signer;
|
|
2500
|
+
const pubkey = getPublicKey(privateKey);
|
|
2501
|
+
return {
|
|
2502
|
+
getPublicKey: async () => pubkey,
|
|
2503
|
+
/** @param {any} event */
|
|
2504
|
+
signEvent: async (event) => {
|
|
2505
|
+
const clonedEvent = {
|
|
2506
|
+
...event,
|
|
2507
|
+
tags: Array.isArray(event.tags) ? event.tags.map((tag) => [...tag]) : []
|
|
2508
|
+
};
|
|
2509
|
+
return finalizeEvent(clonedEvent, privateKey);
|
|
2510
|
+
}
|
|
2511
|
+
};
|
|
2512
|
+
}
|
|
2513
|
+
if (typeof signer === "object" && signer !== null) {
|
|
2514
|
+
if (typeof signer.getPublicKey === "function" && typeof signer.signEvent === "function") {
|
|
2515
|
+
return {
|
|
2516
|
+
getPublicKey: async () => {
|
|
2517
|
+
const pubkey = await signer.getPublicKey();
|
|
2518
|
+
if (typeof pubkey !== "string") throw new Error("Signer.getPublicKey must return a hex string");
|
|
2519
|
+
return pubkey;
|
|
2520
|
+
},
|
|
2521
|
+
/** @param {any} event */
|
|
2522
|
+
signEvent: async (event) => {
|
|
2523
|
+
const signed = await signer.signEvent(event);
|
|
2524
|
+
if (!signed || typeof signed.id !== "string" || typeof signed.sig !== "string") {
|
|
2525
|
+
throw new Error("Signer.signEvent must return a signed event");
|
|
2526
|
+
}
|
|
2527
|
+
return signed;
|
|
2528
|
+
},
|
|
2529
|
+
decryptEvent: typeof signer.decryptEvent === "function" ? signer.decryptEvent.bind(signer) : void 0
|
|
2530
|
+
};
|
|
2531
|
+
}
|
|
2532
|
+
}
|
|
2533
|
+
throw new Error("Unsupported signer provided to NCC02Builder");
|
|
2473
2534
|
}
|
|
2474
2535
|
/**
|
|
2475
2536
|
* Creates a signed Service Record (Kind 30059).
|
|
@@ -2479,7 +2540,7 @@ var NCC02Builder = class {
|
|
|
2479
2540
|
* @param {string} [options.fingerprint] - The 'k' tag fingerprint.
|
|
2480
2541
|
* @param {number} [options.expiryDays=14] - Expiry in days.
|
|
2481
2542
|
*/
|
|
2482
|
-
createServiceRecord(options) {
|
|
2543
|
+
async createServiceRecord(options) {
|
|
2483
2544
|
const { serviceId, endpoint, fingerprint, expiryDays = 14 } = options;
|
|
2484
2545
|
if (!serviceId) throw new Error("serviceId (d tag) is required");
|
|
2485
2546
|
const expiry = Math.floor(Date.now() / 1e3) + expiryDays * 24 * 60 * 60;
|
|
@@ -2493,10 +2554,9 @@ var NCC02Builder = class {
|
|
|
2493
2554
|
kind: KINDS.SERVICE_RECORD,
|
|
2494
2555
|
created_at: Math.floor(Date.now() / 1e3),
|
|
2495
2556
|
tags,
|
|
2496
|
-
content: `NCC-02 Service Record for ${serviceId}
|
|
2497
|
-
pubkey: this.pk
|
|
2557
|
+
content: `NCC-02 Service Record for ${serviceId}`
|
|
2498
2558
|
};
|
|
2499
|
-
return
|
|
2559
|
+
return this._finalizeEvent(event);
|
|
2500
2560
|
}
|
|
2501
2561
|
/**
|
|
2502
2562
|
* Creates a signed Certificate Attestation (Kind 30060).
|
|
@@ -2507,7 +2567,7 @@ var NCC02Builder = class {
|
|
|
2507
2567
|
* @param {string} [options.level='verified'] - The 'lvl' tag level.
|
|
2508
2568
|
* @param {number} [options.validDays=30] - Validity in days.
|
|
2509
2569
|
*/
|
|
2510
|
-
createAttestation(options) {
|
|
2570
|
+
async createAttestation(options) {
|
|
2511
2571
|
const { subjectPubkey, serviceId, serviceEventId, level = "verified", validDays = 30 } = options;
|
|
2512
2572
|
if (!subjectPubkey) throw new Error("subjectPubkey is required");
|
|
2513
2573
|
if (!serviceId) throw new Error("serviceId is required");
|
|
@@ -2526,10 +2586,9 @@ var NCC02Builder = class {
|
|
|
2526
2586
|
["nbf", now2.toString()],
|
|
2527
2587
|
["exp", expiry.toString()]
|
|
2528
2588
|
],
|
|
2529
|
-
content: "NCC-02 Attestation"
|
|
2530
|
-
pubkey: this.pk
|
|
2589
|
+
content: "NCC-02 Attestation"
|
|
2531
2590
|
};
|
|
2532
|
-
return
|
|
2591
|
+
return this._finalizeEvent(event);
|
|
2533
2592
|
}
|
|
2534
2593
|
/**
|
|
2535
2594
|
* Creates a signed Revocation (Kind 30061).
|
|
@@ -2537,7 +2596,7 @@ var NCC02Builder = class {
|
|
|
2537
2596
|
* @param {string} options.attestationId - The 'e' tag referencing the attestation.
|
|
2538
2597
|
* @param {string} [options.reason=''] - Optional reason.
|
|
2539
2598
|
*/
|
|
2540
|
-
createRevocation(options) {
|
|
2599
|
+
async createRevocation(options) {
|
|
2541
2600
|
const { attestationId, reason = "" } = options;
|
|
2542
2601
|
if (!attestationId) throw new Error("attestationId (e tag) is required");
|
|
2543
2602
|
const tags = [["e", attestationId]];
|
|
@@ -2546,15 +2605,22 @@ var NCC02Builder = class {
|
|
|
2546
2605
|
kind: KINDS.REVOCATION,
|
|
2547
2606
|
created_at: Math.floor(Date.now() / 1e3),
|
|
2548
2607
|
tags,
|
|
2549
|
-
content: "NCC-02 Revocation"
|
|
2550
|
-
pubkey: this.pk
|
|
2608
|
+
content: "NCC-02 Revocation"
|
|
2551
2609
|
};
|
|
2552
|
-
return
|
|
2610
|
+
return this._finalizeEvent(event);
|
|
2553
2611
|
}
|
|
2554
2612
|
};
|
|
2555
2613
|
function verifyNCC02Event(event) {
|
|
2556
2614
|
return verifyEvent(event);
|
|
2557
2615
|
}
|
|
2616
|
+
function isExpired(event) {
|
|
2617
|
+
if (!event || !Array.isArray(event.tags)) return false;
|
|
2618
|
+
const expTag = event.tags.find((tag) => tag[0] === "exp");
|
|
2619
|
+
if (!expTag) return false;
|
|
2620
|
+
const expiry = parseInt(expTag[1], 10);
|
|
2621
|
+
if (Number.isNaN(expiry)) return false;
|
|
2622
|
+
return expiry <= Math.floor(Date.now() / 1e3);
|
|
2623
|
+
}
|
|
2558
2624
|
|
|
2559
2625
|
// node_modules/@scure/base/lib/esm/index.js
|
|
2560
2626
|
function assertNumber(n) {
|
|
@@ -7658,6 +7724,14 @@ var NCC02Resolver = class {
|
|
|
7658
7724
|
this.ownsPool = !options.pool;
|
|
7659
7725
|
this.trustedCAPubkeys = new Set(options.trustedCAPubkeys || []);
|
|
7660
7726
|
}
|
|
7727
|
+
/**
|
|
7728
|
+
* Closes the connection to the relays if the pool is owned by this resolver.
|
|
7729
|
+
*/
|
|
7730
|
+
close() {
|
|
7731
|
+
if (this.ownsPool && this.pool) {
|
|
7732
|
+
this.pool.close(this.relays);
|
|
7733
|
+
}
|
|
7734
|
+
}
|
|
7661
7735
|
/**
|
|
7662
7736
|
* Internal query helper using SimplePool.subscribeMany (since list() is deprecated).
|
|
7663
7737
|
* @param {import('nostr-tools').Filter} filter
|
|
@@ -7677,6 +7751,27 @@ var NCC02Resolver = class {
|
|
|
7677
7751
|
});
|
|
7678
7752
|
});
|
|
7679
7753
|
}
|
|
7754
|
+
/**
|
|
7755
|
+
* Returns the first event sorted by freshness (newest created_at, tie broken by id).
|
|
7756
|
+
* @param {import('nostr-tools').Event[]} events
|
|
7757
|
+
* @returns {import('nostr-tools').Event|null}
|
|
7758
|
+
*/
|
|
7759
|
+
_freshestEvent(events) {
|
|
7760
|
+
if (!events || !events.length) return null;
|
|
7761
|
+
return events.sort((a, b) => {
|
|
7762
|
+
if (b.created_at !== a.created_at) return b.created_at - a.created_at;
|
|
7763
|
+
return a.id.localeCompare(b.id);
|
|
7764
|
+
})[0];
|
|
7765
|
+
}
|
|
7766
|
+
/**
|
|
7767
|
+
* Query helper that returns only the freshest event matching the filter.
|
|
7768
|
+
* @param {import('nostr-tools').Filter} filter
|
|
7769
|
+
* @returns {Promise<import('nostr-tools').Event | null>}
|
|
7770
|
+
*/
|
|
7771
|
+
async _queryFreshest(filter) {
|
|
7772
|
+
const events = await this._query(filter);
|
|
7773
|
+
return this._freshestEvent(events);
|
|
7774
|
+
}
|
|
7680
7775
|
/**
|
|
7681
7776
|
* Resolves a service for a given pubkey and service identifier.
|
|
7682
7777
|
*
|
|
@@ -7686,8 +7781,8 @@ var NCC02Resolver = class {
|
|
|
7686
7781
|
* @param {boolean} [options.requireAttestation=false] - If true, fails if no trusted attestation is found.
|
|
7687
7782
|
* @param {string} [options.minLevel=null] - Minimum trust level ('self', 'verified', 'hardened').
|
|
7688
7783
|
* @param {string} [options.standard='nostr-service-trust-v0.1'] - Expected trust standard.
|
|
7689
|
-
|
|
7690
|
-
|
|
7784
|
+
* @throws {NCC02Error} If verification or policy checks fail.
|
|
7785
|
+
* @returns {Promise<ServiceStatus>} The service status including trust metadata.
|
|
7691
7786
|
*/
|
|
7692
7787
|
async resolve(pubkey, serviceId, options = {}) {
|
|
7693
7788
|
const {
|
|
@@ -7695,9 +7790,9 @@ var NCC02Resolver = class {
|
|
|
7695
7790
|
minLevel = null,
|
|
7696
7791
|
standard = "nostr-service-trust-v0.1"
|
|
7697
7792
|
} = options;
|
|
7698
|
-
let
|
|
7793
|
+
let serviceEvent;
|
|
7699
7794
|
try {
|
|
7700
|
-
|
|
7795
|
+
serviceEvent = await this._queryFreshest({
|
|
7701
7796
|
kinds: [KINDS.SERVICE_RECORD],
|
|
7702
7797
|
authors: [pubkey],
|
|
7703
7798
|
"#d": [serviceId]
|
|
@@ -7705,13 +7800,9 @@ var NCC02Resolver = class {
|
|
|
7705
7800
|
} catch (err) {
|
|
7706
7801
|
throw new NCC02Error("RELAY_ERROR", `Failed to query relay for ${serviceId}`, err);
|
|
7707
7802
|
}
|
|
7708
|
-
if (!
|
|
7803
|
+
if (!serviceEvent) {
|
|
7709
7804
|
throw new NCC02Error("NOT_FOUND", `No service record found for ${serviceId}`);
|
|
7710
7805
|
}
|
|
7711
|
-
const serviceEvent = serviceEvents.sort((a, b) => {
|
|
7712
|
-
if (b.created_at !== a.created_at) return b.created_at - a.created_at;
|
|
7713
|
-
return a.id.localeCompare(b.id);
|
|
7714
|
-
})[0];
|
|
7715
7806
|
if (!verifyEvent2(serviceEvent)) {
|
|
7716
7807
|
throw new NCC02Error("INVALID_SIGNATURE", "Service record signature verification failed");
|
|
7717
7808
|
}
|
|
@@ -7730,86 +7821,135 @@ var NCC02Resolver = class {
|
|
|
7730
7821
|
if (exp < now2) {
|
|
7731
7822
|
throw new NCC02Error("EXPIRED", "Service record has expired");
|
|
7732
7823
|
}
|
|
7733
|
-
let
|
|
7734
|
-
let revocations;
|
|
7824
|
+
let trustData;
|
|
7735
7825
|
try {
|
|
7736
|
-
|
|
7737
|
-
this._query({ kinds: [KINDS.ATTESTATION], "#e": [serviceEvent.id] }),
|
|
7738
|
-
this._query({ kinds: [KINDS.REVOCATION] })
|
|
7739
|
-
]);
|
|
7826
|
+
trustData = await this._buildTrustData(serviceEvent, { pubkey, serviceId, standard, minLevel });
|
|
7740
7827
|
} catch (err) {
|
|
7741
7828
|
throw new NCC02Error("RELAY_ERROR", "Failed to query relay for attestations/revocations", err);
|
|
7742
7829
|
}
|
|
7743
|
-
|
|
7744
|
-
for (const att of attestations) {
|
|
7745
|
-
if (this.trustedCAPubkeys.has(att.pubkey)) {
|
|
7746
|
-
const attTags = Object.fromEntries(att.tags);
|
|
7747
|
-
if (attTags.subj !== pubkey) continue;
|
|
7748
|
-
if (attTags.srv !== serviceId) continue;
|
|
7749
|
-
if (standard && attTags.std !== standard) continue;
|
|
7750
|
-
if (minLevel && !this._isLevelSufficient(attTags.lvl, minLevel)) continue;
|
|
7751
|
-
if (this._isAttestationValid(att, attTags, revocations)) {
|
|
7752
|
-
validAttestations.push({
|
|
7753
|
-
pubkey: att.pubkey,
|
|
7754
|
-
level: attTags.lvl,
|
|
7755
|
-
eventId: att.id
|
|
7756
|
-
});
|
|
7757
|
-
}
|
|
7758
|
-
}
|
|
7759
|
-
}
|
|
7760
|
-
if (requireAttestation && validAttestations.length === 0) {
|
|
7830
|
+
if (requireAttestation && trustData.validAttestations.length === 0) {
|
|
7761
7831
|
throw new NCC02Error("POLICY_FAILURE", `No trusted attestations meet the required policy for ${serviceId}`);
|
|
7762
7832
|
}
|
|
7763
7833
|
return {
|
|
7764
7834
|
endpoint: serviceTags.u,
|
|
7765
7835
|
fingerprint: serviceTags.k,
|
|
7766
7836
|
expiry: exp,
|
|
7767
|
-
attestations: validAttestations,
|
|
7837
|
+
attestations: trustData.validAttestations,
|
|
7838
|
+
attestationCount: trustData.validAttestations.length,
|
|
7839
|
+
isRevoked: trustData.isRevoked,
|
|
7768
7840
|
eventId: serviceEvent.id,
|
|
7769
|
-
pubkey: serviceEvent.pubkey
|
|
7841
|
+
pubkey: serviceEvent.pubkey,
|
|
7842
|
+
serviceEvent
|
|
7770
7843
|
};
|
|
7771
7844
|
}
|
|
7772
7845
|
/**
|
|
7773
|
-
* @param {
|
|
7774
|
-
* @param {
|
|
7846
|
+
* @param {any} serviceEvent
|
|
7847
|
+
* @param {Object} options
|
|
7848
|
+
* @param {string} options.pubkey
|
|
7849
|
+
* @param {string} options.serviceId
|
|
7850
|
+
* @param {string|null} options.standard
|
|
7851
|
+
* @param {string|null} options.minLevel
|
|
7775
7852
|
*/
|
|
7776
|
-
|
|
7777
|
-
const
|
|
7778
|
-
|
|
7779
|
-
|
|
7780
|
-
|
|
7853
|
+
async _buildTrustData(serviceEvent, options) {
|
|
7854
|
+
const attestations = await this._query({
|
|
7855
|
+
kinds: [KINDS.ATTESTATION],
|
|
7856
|
+
"#e": [serviceEvent.id]
|
|
7857
|
+
});
|
|
7858
|
+
const attestationIds = attestations.map((att) => att.id);
|
|
7859
|
+
let revocations = [];
|
|
7860
|
+
if (attestationIds.length) {
|
|
7861
|
+
revocations = await this._query({
|
|
7862
|
+
kinds: [KINDS.REVOCATION],
|
|
7863
|
+
"#e": attestationIds
|
|
7864
|
+
});
|
|
7865
|
+
}
|
|
7866
|
+
const revocationIndex = this._groupValidRevocations(revocations);
|
|
7867
|
+
const validAttestations = [];
|
|
7868
|
+
let isRevoked = false;
|
|
7869
|
+
for (const att of attestations) {
|
|
7870
|
+
if (!this.trustedCAPubkeys.has(att.pubkey)) continue;
|
|
7871
|
+
const attTags = Object.fromEntries(att.tags);
|
|
7872
|
+
if (attTags.subj !== options.pubkey) continue;
|
|
7873
|
+
if (attTags.srv !== options.serviceId) continue;
|
|
7874
|
+
if (options.standard && attTags.std !== options.standard) continue;
|
|
7875
|
+
const { valid, revoked } = this._evaluateAttestation(att, attTags, revocationIndex[att.id]);
|
|
7876
|
+
if (revoked) {
|
|
7877
|
+
isRevoked = true;
|
|
7878
|
+
continue;
|
|
7879
|
+
}
|
|
7880
|
+
if (!valid) continue;
|
|
7881
|
+
if (options.minLevel && !this._isLevelSufficient(attTags.lvl, options.minLevel)) continue;
|
|
7882
|
+
validAttestations.push({
|
|
7883
|
+
pubkey: att.pubkey,
|
|
7884
|
+
level: attTags.lvl,
|
|
7885
|
+
eventId: att.id
|
|
7886
|
+
});
|
|
7887
|
+
}
|
|
7888
|
+
return { validAttestations, isRevoked };
|
|
7889
|
+
}
|
|
7890
|
+
/**
|
|
7891
|
+
* @param {any[]} revocations
|
|
7892
|
+
* @returns {Record<string, any[]>}
|
|
7893
|
+
*/
|
|
7894
|
+
/**
|
|
7895
|
+
* @param {any[]} revocations
|
|
7896
|
+
* @returns {Record<string, any[]>}
|
|
7897
|
+
*/
|
|
7898
|
+
_groupValidRevocations(revocations) {
|
|
7899
|
+
const indexed = {};
|
|
7900
|
+
for (const rev of revocations) {
|
|
7901
|
+
if (!verifyEvent2(rev)) continue;
|
|
7902
|
+
const tags = Object.fromEntries(rev.tags);
|
|
7903
|
+
const targetId = tags.e;
|
|
7904
|
+
if (!targetId) continue;
|
|
7905
|
+
if (!indexed[targetId]) indexed[targetId] = [];
|
|
7906
|
+
indexed[targetId].push(rev);
|
|
7907
|
+
}
|
|
7908
|
+
return indexed;
|
|
7781
7909
|
}
|
|
7782
7910
|
/**
|
|
7783
|
-
* @param {any} att
|
|
7784
|
-
* @param {
|
|
7785
|
-
* @param {any[]} revocations
|
|
7911
|
+
* @param {any} att
|
|
7912
|
+
* @param {Record<string, string>} tags
|
|
7913
|
+
* @param {any[]} revocations
|
|
7914
|
+
*/
|
|
7915
|
+
/**
|
|
7916
|
+
* @param {any} att
|
|
7917
|
+
* @param {Record<string, string>} tags
|
|
7918
|
+
* @param {any[]} [revocations]
|
|
7786
7919
|
*/
|
|
7787
|
-
|
|
7788
|
-
|
|
7920
|
+
_evaluateAttestation(att, tags, revocations = []) {
|
|
7921
|
+
for (const rev of revocations) {
|
|
7922
|
+
if (rev.pubkey === att.pubkey) {
|
|
7923
|
+
return { valid: false, revoked: true };
|
|
7924
|
+
}
|
|
7925
|
+
}
|
|
7926
|
+
if (!verifyEvent2(att)) return { valid: false, revoked: false };
|
|
7789
7927
|
const now2 = Math.floor(Date.now() / 1e3);
|
|
7790
7928
|
if (tags.nbf) {
|
|
7791
|
-
const nbf = parseInt(tags.nbf);
|
|
7792
|
-
if (isNaN(nbf) || nbf > now2) return false;
|
|
7929
|
+
const nbf = parseInt(tags.nbf, 10);
|
|
7930
|
+
if (isNaN(nbf) || nbf > now2) return { valid: false, revoked: false };
|
|
7793
7931
|
}
|
|
7794
7932
|
if (tags.exp) {
|
|
7795
|
-
const exp = parseInt(tags.exp);
|
|
7796
|
-
if (isNaN(exp) || exp < now2) return false;
|
|
7797
|
-
}
|
|
7798
|
-
for (const rev of revocations) {
|
|
7799
|
-
const revTags = Object.fromEntries(rev.tags);
|
|
7800
|
-
if (revTags.e === att.id && rev.pubkey === att.pubkey) {
|
|
7801
|
-
if (verifyEvent2(rev)) {
|
|
7802
|
-
return false;
|
|
7803
|
-
}
|
|
7804
|
-
}
|
|
7933
|
+
const exp = parseInt(tags.exp, 10);
|
|
7934
|
+
if (isNaN(exp) || exp < now2) return { valid: false, revoked: false };
|
|
7805
7935
|
}
|
|
7806
|
-
return true;
|
|
7936
|
+
return { valid: true, revoked: false };
|
|
7937
|
+
}
|
|
7938
|
+
/**
|
|
7939
|
+
* @param {string | undefined} actual
|
|
7940
|
+
* @param {string} required
|
|
7941
|
+
*/
|
|
7942
|
+
_isLevelSufficient(actual, required) {
|
|
7943
|
+
const levels = { "self": 0, "verified": 1, "hardened": 2 };
|
|
7944
|
+
const actualVal = actual ? levels[actual] ?? -1 : -1;
|
|
7945
|
+
const requiredVal = levels[required] ?? 0;
|
|
7946
|
+
return actualVal >= requiredVal;
|
|
7807
7947
|
}
|
|
7808
7948
|
/**
|
|
7809
7949
|
* Verifies that the actual fingerprint found during transport-level connection
|
|
7810
7950
|
* matches the one declared in the signed service record.
|
|
7811
7951
|
*
|
|
7812
|
-
* @param {
|
|
7952
|
+
* @param {ServiceStatus} resolved - The object returned by resolve().
|
|
7813
7953
|
* @param {string} actualFingerprint - The fingerprint obtained from the service.
|
|
7814
7954
|
* @returns {boolean}
|
|
7815
7955
|
*/
|
|
@@ -7866,6 +8006,7 @@ export {
|
|
|
7866
8006
|
NCC02Builder,
|
|
7867
8007
|
NCC02Error,
|
|
7868
8008
|
NCC02Resolver,
|
|
8009
|
+
isExpired,
|
|
7869
8010
|
verifyNCC02Event
|
|
7870
8011
|
};
|
|
7871
8012
|
/*! Bundled license information:
|
package/dist/models.d.ts
CHANGED
|
@@ -3,6 +3,12 @@
|
|
|
3
3
|
* @param {any} event
|
|
4
4
|
*/
|
|
5
5
|
export function verifyNCC02Event(event: any): event is import("nostr-tools/core").VerifiedEvent;
|
|
6
|
+
/**
|
|
7
|
+
* Checks whether an NCC event has expired based on its 'exp' tag.
|
|
8
|
+
* @param {any} event
|
|
9
|
+
* @returns {boolean}
|
|
10
|
+
*/
|
|
11
|
+
export function isExpired(event: any): boolean;
|
|
6
12
|
export namespace KINDS {
|
|
7
13
|
let SERVICE_RECORD: number;
|
|
8
14
|
let ATTESTATION: number;
|
|
@@ -13,11 +19,22 @@ export namespace KINDS {
|
|
|
13
19
|
*/
|
|
14
20
|
export class NCC02Builder {
|
|
15
21
|
/**
|
|
16
|
-
* @param {string | Uint8Array}
|
|
22
|
+
* @param {string | Uint8Array | NostrSigner} signer - Raw private key or asynchronous signer.
|
|
23
|
+
*/
|
|
24
|
+
constructor(signer: string | Uint8Array | NostrSigner);
|
|
25
|
+
signer: NostrSigner;
|
|
26
|
+
_pubkeyPromise: Promise<string>;
|
|
27
|
+
_pubkey: string;
|
|
28
|
+
_getPublicKey(): Promise<string>;
|
|
29
|
+
/**
|
|
30
|
+
* @param {any} event
|
|
31
|
+
*/
|
|
32
|
+
_finalizeEvent(event: any): Promise<any>;
|
|
33
|
+
/**
|
|
34
|
+
* @param {any} signer
|
|
35
|
+
* @returns {NostrSigner}
|
|
17
36
|
*/
|
|
18
|
-
|
|
19
|
-
sk: Uint8Array<ArrayBufferLike>;
|
|
20
|
-
pk: string;
|
|
37
|
+
_normalizeSigner(signer: any): NostrSigner;
|
|
21
38
|
/**
|
|
22
39
|
* Creates a signed Service Record (Kind 30059).
|
|
23
40
|
* @param {Object} options
|
|
@@ -31,7 +48,7 @@ export class NCC02Builder {
|
|
|
31
48
|
endpoint?: string;
|
|
32
49
|
fingerprint?: string;
|
|
33
50
|
expiryDays?: number;
|
|
34
|
-
}):
|
|
51
|
+
}): Promise<any>;
|
|
35
52
|
/**
|
|
36
53
|
* Creates a signed Certificate Attestation (Kind 30060).
|
|
37
54
|
* @param {Object} options
|
|
@@ -47,7 +64,7 @@ export class NCC02Builder {
|
|
|
47
64
|
serviceEventId: string;
|
|
48
65
|
level?: string;
|
|
49
66
|
validDays?: number;
|
|
50
|
-
}):
|
|
67
|
+
}): Promise<any>;
|
|
51
68
|
/**
|
|
52
69
|
* Creates a signed Revocation (Kind 30061).
|
|
53
70
|
* @param {Object} options
|
|
@@ -57,5 +74,10 @@ export class NCC02Builder {
|
|
|
57
74
|
createRevocation(options: {
|
|
58
75
|
attestationId: string;
|
|
59
76
|
reason?: string;
|
|
60
|
-
}):
|
|
77
|
+
}): Promise<any>;
|
|
61
78
|
}
|
|
79
|
+
export type NostrSigner = {
|
|
80
|
+
getPublicKey: () => Promise<string>;
|
|
81
|
+
signEvent: (event: any) => Promise<any>;
|
|
82
|
+
decryptEvent?: (event: any) => Promise<any>;
|
|
83
|
+
};
|
package/dist/resolver.d.ts
CHANGED
|
@@ -12,13 +12,16 @@ export class NCC02Error extends Error {
|
|
|
12
12
|
cause: any;
|
|
13
13
|
}
|
|
14
14
|
/**
|
|
15
|
-
* @typedef {Object}
|
|
15
|
+
* @typedef {Object} ServiceStatus
|
|
16
16
|
* @property {string|undefined} endpoint
|
|
17
17
|
* @property {string|undefined} fingerprint
|
|
18
18
|
* @property {number} expiry
|
|
19
|
-
* @property {
|
|
19
|
+
* @property {boolean} isRevoked
|
|
20
|
+
* @property {number} attestationCount
|
|
21
|
+
* @property {Array<{eventId:string, level:string, pubkey:string}>} attestations
|
|
20
22
|
* @property {string} eventId
|
|
21
23
|
* @property {string} pubkey
|
|
24
|
+
* @property {any} serviceEvent
|
|
22
25
|
*/
|
|
23
26
|
/**
|
|
24
27
|
* Resolver for NCC-02 Service Records.
|
|
@@ -42,12 +45,28 @@ export class NCC02Resolver {
|
|
|
42
45
|
pool: SimplePool;
|
|
43
46
|
ownsPool: boolean;
|
|
44
47
|
trustedCAPubkeys: Set<string>;
|
|
48
|
+
/**
|
|
49
|
+
* Closes the connection to the relays if the pool is owned by this resolver.
|
|
50
|
+
*/
|
|
51
|
+
close(): void;
|
|
45
52
|
/**
|
|
46
53
|
* Internal query helper using SimplePool.subscribeMany (since list() is deprecated).
|
|
47
54
|
* @param {import('nostr-tools').Filter} filter
|
|
48
55
|
* @returns {Promise<import('nostr-tools').Event[]>}
|
|
49
56
|
*/
|
|
50
57
|
_query(filter: import("nostr-tools").Filter): Promise<import("nostr-tools").Event[]>;
|
|
58
|
+
/**
|
|
59
|
+
* Returns the first event sorted by freshness (newest created_at, tie broken by id).
|
|
60
|
+
* @param {import('nostr-tools').Event[]} events
|
|
61
|
+
* @returns {import('nostr-tools').Event|null}
|
|
62
|
+
*/
|
|
63
|
+
_freshestEvent(events: import("nostr-tools").Event[]): import("nostr-tools").Event | null;
|
|
64
|
+
/**
|
|
65
|
+
* Query helper that returns only the freshest event matching the filter.
|
|
66
|
+
* @param {import('nostr-tools').Filter} filter
|
|
67
|
+
* @returns {Promise<import('nostr-tools').Event | null>}
|
|
68
|
+
*/
|
|
69
|
+
_queryFreshest(filter: import("nostr-tools").Filter): Promise<import("nostr-tools").Event | null>;
|
|
51
70
|
/**
|
|
52
71
|
* Resolves a service for a given pubkey and service identifier.
|
|
53
72
|
*
|
|
@@ -57,41 +76,86 @@ export class NCC02Resolver {
|
|
|
57
76
|
* @param {boolean} [options.requireAttestation=false] - If true, fails if no trusted attestation is found.
|
|
58
77
|
* @param {string} [options.minLevel=null] - Minimum trust level ('self', 'verified', 'hardened').
|
|
59
78
|
* @param {string} [options.standard='nostr-service-trust-v0.1'] - Expected trust standard.
|
|
60
|
-
|
|
61
|
-
|
|
79
|
+
* @throws {NCC02Error} If verification or policy checks fail.
|
|
80
|
+
* @returns {Promise<ServiceStatus>} The service status including trust metadata.
|
|
62
81
|
*/
|
|
63
82
|
resolve(pubkey: string, serviceId: string, options?: {
|
|
64
83
|
requireAttestation?: boolean;
|
|
65
84
|
minLevel?: string;
|
|
66
85
|
standard?: string;
|
|
67
|
-
}): Promise<
|
|
86
|
+
}): Promise<ServiceStatus>;
|
|
68
87
|
/**
|
|
69
|
-
* @param {
|
|
70
|
-
* @param {
|
|
88
|
+
* @param {any} serviceEvent
|
|
89
|
+
* @param {Object} options
|
|
90
|
+
* @param {string} options.pubkey
|
|
91
|
+
* @param {string} options.serviceId
|
|
92
|
+
* @param {string|null} options.standard
|
|
93
|
+
* @param {string|null} options.minLevel
|
|
71
94
|
*/
|
|
72
|
-
|
|
95
|
+
_buildTrustData(serviceEvent: any, options: {
|
|
96
|
+
pubkey: string;
|
|
97
|
+
serviceId: string;
|
|
98
|
+
standard: string | null;
|
|
99
|
+
minLevel: string | null;
|
|
100
|
+
}): Promise<{
|
|
101
|
+
validAttestations: {
|
|
102
|
+
pubkey: string;
|
|
103
|
+
level: any;
|
|
104
|
+
eventId: string;
|
|
105
|
+
}[];
|
|
106
|
+
isRevoked: boolean;
|
|
107
|
+
}>;
|
|
108
|
+
/**
|
|
109
|
+
* @param {any[]} revocations
|
|
110
|
+
* @returns {Record<string, any[]>}
|
|
111
|
+
*/
|
|
112
|
+
/**
|
|
113
|
+
* @param {any[]} revocations
|
|
114
|
+
* @returns {Record<string, any[]>}
|
|
115
|
+
*/
|
|
116
|
+
_groupValidRevocations(revocations: any[]): Record<string, any[]>;
|
|
73
117
|
/**
|
|
74
118
|
* @param {any} att
|
|
75
|
-
* @param {
|
|
119
|
+
* @param {Record<string, string>} tags
|
|
76
120
|
* @param {any[]} revocations
|
|
77
121
|
*/
|
|
78
|
-
|
|
122
|
+
/**
|
|
123
|
+
* @param {any} att
|
|
124
|
+
* @param {Record<string, string>} tags
|
|
125
|
+
* @param {any[]} [revocations]
|
|
126
|
+
*/
|
|
127
|
+
_evaluateAttestation(att: any, tags: Record<string, string>, revocations?: any[]): {
|
|
128
|
+
valid: boolean;
|
|
129
|
+
revoked: boolean;
|
|
130
|
+
};
|
|
131
|
+
/**
|
|
132
|
+
* @param {string | undefined} actual
|
|
133
|
+
* @param {string} required
|
|
134
|
+
*/
|
|
135
|
+
_isLevelSufficient(actual: string | undefined, required: string): boolean;
|
|
79
136
|
/**
|
|
80
137
|
* Verifies that the actual fingerprint found during transport-level connection
|
|
81
138
|
* matches the one declared in the signed service record.
|
|
82
139
|
*
|
|
83
|
-
* @param {
|
|
140
|
+
* @param {ServiceStatus} resolved - The object returned by resolve().
|
|
84
141
|
* @param {string} actualFingerprint - The fingerprint obtained from the service.
|
|
85
142
|
* @returns {boolean}
|
|
86
143
|
*/
|
|
87
|
-
verifyEndpoint(resolved:
|
|
144
|
+
verifyEndpoint(resolved: ServiceStatus, actualFingerprint: string): boolean;
|
|
88
145
|
}
|
|
89
|
-
export type
|
|
146
|
+
export type ServiceStatus = {
|
|
90
147
|
endpoint: string | undefined;
|
|
91
148
|
fingerprint: string | undefined;
|
|
92
149
|
expiry: number;
|
|
93
|
-
|
|
150
|
+
isRevoked: boolean;
|
|
151
|
+
attestationCount: number;
|
|
152
|
+
attestations: Array<{
|
|
153
|
+
eventId: string;
|
|
154
|
+
level: string;
|
|
155
|
+
pubkey: string;
|
|
156
|
+
}>;
|
|
94
157
|
eventId: string;
|
|
95
158
|
pubkey: string;
|
|
159
|
+
serviceEvent: any;
|
|
96
160
|
};
|
|
97
161
|
import { SimplePool } from 'nostr-tools';
|