ncc-02-js 0.3.1 → 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 +89 -21
- package/dist/index.cjs +217 -85
- package/dist/index.mjs +216 -85
- package/dist/models.d.ts +29 -7
- package/dist/resolver.d.ts +74 -14
- package/package.json +1 -1
- package/src/models.js +105 -21
- package/src/resolver.js +153 -88
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) {
|
|
@@ -7685,6 +7751,27 @@ var NCC02Resolver = class {
|
|
|
7685
7751
|
});
|
|
7686
7752
|
});
|
|
7687
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
|
+
}
|
|
7688
7775
|
/**
|
|
7689
7776
|
* Resolves a service for a given pubkey and service identifier.
|
|
7690
7777
|
*
|
|
@@ -7694,8 +7781,8 @@ var NCC02Resolver = class {
|
|
|
7694
7781
|
* @param {boolean} [options.requireAttestation=false] - If true, fails if no trusted attestation is found.
|
|
7695
7782
|
* @param {string} [options.minLevel=null] - Minimum trust level ('self', 'verified', 'hardened').
|
|
7696
7783
|
* @param {string} [options.standard='nostr-service-trust-v0.1'] - Expected trust standard.
|
|
7697
|
-
|
|
7698
|
-
|
|
7784
|
+
* @throws {NCC02Error} If verification or policy checks fail.
|
|
7785
|
+
* @returns {Promise<ServiceStatus>} The service status including trust metadata.
|
|
7699
7786
|
*/
|
|
7700
7787
|
async resolve(pubkey, serviceId, options = {}) {
|
|
7701
7788
|
const {
|
|
@@ -7703,9 +7790,9 @@ var NCC02Resolver = class {
|
|
|
7703
7790
|
minLevel = null,
|
|
7704
7791
|
standard = "nostr-service-trust-v0.1"
|
|
7705
7792
|
} = options;
|
|
7706
|
-
let
|
|
7793
|
+
let serviceEvent;
|
|
7707
7794
|
try {
|
|
7708
|
-
|
|
7795
|
+
serviceEvent = await this._queryFreshest({
|
|
7709
7796
|
kinds: [KINDS.SERVICE_RECORD],
|
|
7710
7797
|
authors: [pubkey],
|
|
7711
7798
|
"#d": [serviceId]
|
|
@@ -7713,13 +7800,9 @@ var NCC02Resolver = class {
|
|
|
7713
7800
|
} catch (err) {
|
|
7714
7801
|
throw new NCC02Error("RELAY_ERROR", `Failed to query relay for ${serviceId}`, err);
|
|
7715
7802
|
}
|
|
7716
|
-
if (!
|
|
7803
|
+
if (!serviceEvent) {
|
|
7717
7804
|
throw new NCC02Error("NOT_FOUND", `No service record found for ${serviceId}`);
|
|
7718
7805
|
}
|
|
7719
|
-
const serviceEvent = serviceEvents.sort((a, b) => {
|
|
7720
|
-
if (b.created_at !== a.created_at) return b.created_at - a.created_at;
|
|
7721
|
-
return a.id.localeCompare(b.id);
|
|
7722
|
-
})[0];
|
|
7723
7806
|
if (!verifyEvent2(serviceEvent)) {
|
|
7724
7807
|
throw new NCC02Error("INVALID_SIGNATURE", "Service record signature verification failed");
|
|
7725
7808
|
}
|
|
@@ -7738,88 +7821,135 @@ var NCC02Resolver = class {
|
|
|
7738
7821
|
if (exp < now2) {
|
|
7739
7822
|
throw new NCC02Error("EXPIRED", "Service record has expired");
|
|
7740
7823
|
}
|
|
7741
|
-
|
|
7742
|
-
|
|
7743
|
-
|
|
7744
|
-
|
|
7745
|
-
|
|
7746
|
-
|
|
7747
|
-
|
|
7748
|
-
|
|
7749
|
-
]);
|
|
7750
|
-
} catch (err) {
|
|
7751
|
-
throw new NCC02Error("RELAY_ERROR", "Failed to query relay for attestations/revocations", err);
|
|
7752
|
-
}
|
|
7753
|
-
for (const att of attestations) {
|
|
7754
|
-
if (this.trustedCAPubkeys.has(att.pubkey)) {
|
|
7755
|
-
const attTags = Object.fromEntries(att.tags);
|
|
7756
|
-
if (attTags.subj !== pubkey) continue;
|
|
7757
|
-
if (attTags.srv !== serviceId) continue;
|
|
7758
|
-
if (standard && attTags.std !== standard) continue;
|
|
7759
|
-
if (minLevel && !this._isLevelSufficient(attTags.lvl, minLevel)) continue;
|
|
7760
|
-
if (this._isAttestationValid(att, attTags, revocations)) {
|
|
7761
|
-
validAttestations.push({
|
|
7762
|
-
pubkey: att.pubkey,
|
|
7763
|
-
level: attTags.lvl,
|
|
7764
|
-
eventId: att.id
|
|
7765
|
-
});
|
|
7766
|
-
}
|
|
7767
|
-
}
|
|
7768
|
-
}
|
|
7769
|
-
if (requireAttestation && validAttestations.length === 0) {
|
|
7770
|
-
throw new NCC02Error("POLICY_FAILURE", `No trusted attestations meet the required policy for ${serviceId}`);
|
|
7771
|
-
}
|
|
7824
|
+
let trustData;
|
|
7825
|
+
try {
|
|
7826
|
+
trustData = await this._buildTrustData(serviceEvent, { pubkey, serviceId, standard, minLevel });
|
|
7827
|
+
} catch (err) {
|
|
7828
|
+
throw new NCC02Error("RELAY_ERROR", "Failed to query relay for attestations/revocations", err);
|
|
7829
|
+
}
|
|
7830
|
+
if (requireAttestation && trustData.validAttestations.length === 0) {
|
|
7831
|
+
throw new NCC02Error("POLICY_FAILURE", `No trusted attestations meet the required policy for ${serviceId}`);
|
|
7772
7832
|
}
|
|
7773
7833
|
return {
|
|
7774
7834
|
endpoint: serviceTags.u,
|
|
7775
7835
|
fingerprint: serviceTags.k,
|
|
7776
7836
|
expiry: exp,
|
|
7777
|
-
attestations: validAttestations,
|
|
7837
|
+
attestations: trustData.validAttestations,
|
|
7838
|
+
attestationCount: trustData.validAttestations.length,
|
|
7839
|
+
isRevoked: trustData.isRevoked,
|
|
7778
7840
|
eventId: serviceEvent.id,
|
|
7779
|
-
pubkey: serviceEvent.pubkey
|
|
7841
|
+
pubkey: serviceEvent.pubkey,
|
|
7842
|
+
serviceEvent
|
|
7780
7843
|
};
|
|
7781
7844
|
}
|
|
7782
7845
|
/**
|
|
7783
|
-
* @param {
|
|
7784
|
-
* @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
|
|
7785
7852
|
*/
|
|
7786
|
-
|
|
7787
|
-
const
|
|
7788
|
-
|
|
7789
|
-
|
|
7790
|
-
|
|
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 };
|
|
7791
7889
|
}
|
|
7792
7890
|
/**
|
|
7793
|
-
* @param {any}
|
|
7794
|
-
* @
|
|
7795
|
-
* @param {any[]} revocations
|
|
7891
|
+
* @param {any[]} revocations
|
|
7892
|
+
* @returns {Record<string, any[]>}
|
|
7796
7893
|
*/
|
|
7797
|
-
|
|
7798
|
-
|
|
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;
|
|
7909
|
+
}
|
|
7910
|
+
/**
|
|
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]
|
|
7919
|
+
*/
|
|
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 };
|
|
7799
7927
|
const now2 = Math.floor(Date.now() / 1e3);
|
|
7800
7928
|
if (tags.nbf) {
|
|
7801
|
-
const nbf = parseInt(tags.nbf);
|
|
7802
|
-
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 };
|
|
7803
7931
|
}
|
|
7804
7932
|
if (tags.exp) {
|
|
7805
|
-
const exp = parseInt(tags.exp);
|
|
7806
|
-
if (isNaN(exp) || exp < now2) return false;
|
|
7933
|
+
const exp = parseInt(tags.exp, 10);
|
|
7934
|
+
if (isNaN(exp) || exp < now2) return { valid: false, revoked: false };
|
|
7807
7935
|
}
|
|
7808
|
-
|
|
7809
|
-
|
|
7810
|
-
|
|
7811
|
-
|
|
7812
|
-
|
|
7813
|
-
|
|
7814
|
-
|
|
7815
|
-
}
|
|
7816
|
-
|
|
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;
|
|
7817
7947
|
}
|
|
7818
7948
|
/**
|
|
7819
7949
|
* Verifies that the actual fingerprint found during transport-level connection
|
|
7820
7950
|
* matches the one declared in the signed service record.
|
|
7821
7951
|
*
|
|
7822
|
-
* @param {
|
|
7952
|
+
* @param {ServiceStatus} resolved - The object returned by resolve().
|
|
7823
7953
|
* @param {string} actualFingerprint - The fingerprint obtained from the service.
|
|
7824
7954
|
* @returns {boolean}
|
|
7825
7955
|
*/
|
|
@@ -7876,6 +8006,7 @@ export {
|
|
|
7876
8006
|
NCC02Builder,
|
|
7877
8007
|
NCC02Error,
|
|
7878
8008
|
NCC02Resolver,
|
|
8009
|
+
isExpired,
|
|
7879
8010
|
verifyNCC02Event
|
|
7880
8011
|
};
|
|
7881
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.
|
|
@@ -52,6 +55,18 @@ export class NCC02Resolver {
|
|
|
52
55
|
* @returns {Promise<import('nostr-tools').Event[]>}
|
|
53
56
|
*/
|
|
54
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>;
|
|
55
70
|
/**
|
|
56
71
|
* Resolves a service for a given pubkey and service identifier.
|
|
57
72
|
*
|
|
@@ -61,41 +76,86 @@ export class NCC02Resolver {
|
|
|
61
76
|
* @param {boolean} [options.requireAttestation=false] - If true, fails if no trusted attestation is found.
|
|
62
77
|
* @param {string} [options.minLevel=null] - Minimum trust level ('self', 'verified', 'hardened').
|
|
63
78
|
* @param {string} [options.standard='nostr-service-trust-v0.1'] - Expected trust standard.
|
|
64
|
-
|
|
65
|
-
|
|
79
|
+
* @throws {NCC02Error} If verification or policy checks fail.
|
|
80
|
+
* @returns {Promise<ServiceStatus>} The service status including trust metadata.
|
|
66
81
|
*/
|
|
67
82
|
resolve(pubkey: string, serviceId: string, options?: {
|
|
68
83
|
requireAttestation?: boolean;
|
|
69
84
|
minLevel?: string;
|
|
70
85
|
standard?: string;
|
|
71
|
-
}): Promise<
|
|
86
|
+
}): Promise<ServiceStatus>;
|
|
72
87
|
/**
|
|
73
|
-
* @param {
|
|
74
|
-
* @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
|
|
94
|
+
*/
|
|
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[]>}
|
|
75
111
|
*/
|
|
76
|
-
|
|
112
|
+
/**
|
|
113
|
+
* @param {any[]} revocations
|
|
114
|
+
* @returns {Record<string, any[]>}
|
|
115
|
+
*/
|
|
116
|
+
_groupValidRevocations(revocations: any[]): Record<string, any[]>;
|
|
77
117
|
/**
|
|
78
118
|
* @param {any} att
|
|
79
|
-
* @param {
|
|
119
|
+
* @param {Record<string, string>} tags
|
|
80
120
|
* @param {any[]} revocations
|
|
81
121
|
*/
|
|
82
|
-
|
|
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;
|
|
83
136
|
/**
|
|
84
137
|
* Verifies that the actual fingerprint found during transport-level connection
|
|
85
138
|
* matches the one declared in the signed service record.
|
|
86
139
|
*
|
|
87
|
-
* @param {
|
|
140
|
+
* @param {ServiceStatus} resolved - The object returned by resolve().
|
|
88
141
|
* @param {string} actualFingerprint - The fingerprint obtained from the service.
|
|
89
142
|
* @returns {boolean}
|
|
90
143
|
*/
|
|
91
|
-
verifyEndpoint(resolved:
|
|
144
|
+
verifyEndpoint(resolved: ServiceStatus, actualFingerprint: string): boolean;
|
|
92
145
|
}
|
|
93
|
-
export type
|
|
146
|
+
export type ServiceStatus = {
|
|
94
147
|
endpoint: string | undefined;
|
|
95
148
|
fingerprint: string | undefined;
|
|
96
149
|
expiry: number;
|
|
97
|
-
|
|
150
|
+
isRevoked: boolean;
|
|
151
|
+
attestationCount: number;
|
|
152
|
+
attestations: Array<{
|
|
153
|
+
eventId: string;
|
|
154
|
+
level: string;
|
|
155
|
+
pubkey: string;
|
|
156
|
+
}>;
|
|
98
157
|
eventId: string;
|
|
99
158
|
pubkey: string;
|
|
159
|
+
serviceEvent: any;
|
|
100
160
|
};
|
|
101
161
|
import { SimplePool } from 'nostr-tools';
|