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/dist/index.mjs CHANGED
@@ -2464,12 +2464,73 @@ var KINDS = {
2464
2464
  };
2465
2465
  var NCC02Builder = class {
2466
2466
  /**
2467
- * @param {string | Uint8Array} privateKey - The private key to sign events with.
2467
+ * @param {string | Uint8Array | NostrSigner} signer - Raw private key or asynchronous signer.
2468
2468
  */
2469
- constructor(privateKey) {
2470
- if (!privateKey) throw new Error("Private key is required");
2471
- this.sk = typeof privateKey === "string" ? hexToBytes2(privateKey) : privateKey;
2472
- this.pk = getPublicKey(this.sk);
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 finalizeEvent(event, this.sk);
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 finalizeEvent(event, this.sk);
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 finalizeEvent(event, this.sk);
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
- * @throws {NCC02Error} If verification or policy checks fail.
7698
- * @returns {Promise<ResolvedService>} The verified service details.
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 serviceEvents;
7793
+ let serviceEvent;
7707
7794
  try {
7708
- serviceEvents = await this._query({
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 (!serviceEvents || !serviceEvents.length) {
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
- const validAttestations = [];
7742
- if (requireAttestation || minLevel === "verified" || minLevel === "hardened") {
7743
- let attestations;
7744
- let revocations;
7745
- try {
7746
- [attestations, revocations] = await Promise.all([
7747
- this._query({ kinds: [KINDS.ATTESTATION], "#e": [serviceEvent.id] }),
7748
- this._query({ kinds: [KINDS.REVOCATION] })
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 {string | undefined} actual
7784
- * @param {string} required
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
- _isLevelSufficient(actual, required) {
7787
- const levels = { "self": 0, "verified": 1, "hardened": 2 };
7788
- const actualVal = actual ? levels[actual] ?? -1 : -1;
7789
- const requiredVal = levels[required] ?? 0;
7790
- return actualVal >= requiredVal;
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} att
7794
- * @param {any} tags
7795
- * @param {any[]} revocations
7891
+ * @param {any[]} revocations
7892
+ * @returns {Record<string, any[]>}
7796
7893
  */
7797
- _isAttestationValid(att, tags, revocations) {
7798
- if (!verifyEvent2(att)) return false;
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
- for (const rev of revocations) {
7809
- const revTags = Object.fromEntries(rev.tags);
7810
- if (revTags.e === att.id && rev.pubkey === att.pubkey) {
7811
- if (verifyEvent2(rev)) {
7812
- return false;
7813
- }
7814
- }
7815
- }
7816
- 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;
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 {ResolvedService} resolved - The object returned by resolve().
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} privateKey - The private key to sign events with.
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
- constructor(privateKey: string | Uint8Array);
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
- }): import("nostr-tools/core").VerifiedEvent;
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
- }): import("nostr-tools/core").VerifiedEvent;
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
- }): import("nostr-tools/core").VerifiedEvent;
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
+ };
@@ -12,13 +12,16 @@ export class NCC02Error extends Error {
12
12
  cause: any;
13
13
  }
14
14
  /**
15
- * @typedef {Object} ResolvedService
15
+ * @typedef {Object} ServiceStatus
16
16
  * @property {string|undefined} endpoint
17
17
  * @property {string|undefined} fingerprint
18
18
  * @property {number} expiry
19
- * @property {any[]} attestations
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
- * @throws {NCC02Error} If verification or policy checks fail.
65
- * @returns {Promise<ResolvedService>} The verified service details.
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<ResolvedService>;
86
+ }): Promise<ServiceStatus>;
72
87
  /**
73
- * @param {string | undefined} actual
74
- * @param {string} required
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
- _isLevelSufficient(actual: string | undefined, required: string): boolean;
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 {any} tags
119
+ * @param {Record<string, string>} tags
80
120
  * @param {any[]} revocations
81
121
  */
82
- _isAttestationValid(att: any, tags: any, revocations: any[]): boolean;
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 {ResolvedService} resolved - The object returned by resolve().
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: ResolvedService, actualFingerprint: string): boolean;
144
+ verifyEndpoint(resolved: ServiceStatus, actualFingerprint: string): boolean;
92
145
  }
93
- export type ResolvedService = {
146
+ export type ServiceStatus = {
94
147
  endpoint: string | undefined;
95
148
  fingerprint: string | undefined;
96
149
  expiry: number;
97
- attestations: any[];
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';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ncc-02-js",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "description": "Nostr-native service discovery and trust implementation (NCC-02)",
5
5
  "main": "dist/index.cjs",
6
6
  "module": "dist/index.mjs",