@unicitylabs/nostr-js-sdk 0.5.0-dev.3 → 0.6.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.
@@ -9250,6 +9250,16 @@
9250
9250
  */
9251
9251
  /** Default country code for phone number normalization (shared with NametagUtils) */
9252
9252
  const DEFAULT_COUNTRY = 'US';
9253
+ /**
9254
+ * UNIP-01 namespace label for nametag ownership bindings.
9255
+ *
9256
+ * A binding event carrying a NIP-32 ["L", UNICITY_NAMETAG_NAMESPACE] label opts
9257
+ * into relay-enforced single-owner semantics: the first author a UNIP-01 relay
9258
+ * accepts for a given identifier (d-tag) owns it, by relay receive order. The
9259
+ * marker is what makes ownership independent of the self-asserted `created_at`.
9260
+ * See the UNIP-01 spec (unicity-tokens-relay/docs/UNIP-01.md).
9261
+ */
9262
+ const UNICITY_NAMETAG_NAMESPACE = 'unicity:nametag';
9253
9263
  /**
9254
9264
  * Create a nametag binding event.
9255
9265
  *
@@ -9281,6 +9291,8 @@
9281
9291
  };
9282
9292
  const tags = [
9283
9293
  ['d', hashedNametag],
9294
+ // UNIP-01: opt this binding into relay-enforced single-owner semantics.
9295
+ ['L', UNICITY_NAMETAG_NAMESPACE],
9284
9296
  ['nametag', hashedNametag],
9285
9297
  ['t', hashedNametag],
9286
9298
  ['address', unicityAddress],
@@ -9484,6 +9496,18 @@
9484
9496
  return undefined;
9485
9497
  }
9486
9498
  }
9499
+ /**
9500
+ * UNIP-01: true if this binding event carries the single-owner namespace marker
9501
+ * (a NIP-32 ["L", UNICITY_NAMETAG_NAMESPACE] label). Such bindings are vetted by
9502
+ * UNIP-01 relays for single ownership and are preferred at resolution over
9503
+ * unmarked (legacy) bindings, independent of the self-asserted `created_at`.
9504
+ *
9505
+ * @param event Binding event
9506
+ * @returns true if the UNIP-01 nametag marker is present
9507
+ */
9508
+ function hasNametagOwnershipMarker(event) {
9509
+ return event.getTagValues('L').includes(UNICITY_NAMETAG_NAMESPACE);
9510
+ }
9487
9511
  /**
9488
9512
  * Verify that a binding event is valid.
9489
9513
  * Checks signature and structure.
@@ -9516,11 +9540,13 @@
9516
9540
 
9517
9541
  var NametagBinding = /*#__PURE__*/Object.freeze({
9518
9542
  __proto__: null,
9543
+ UNICITY_NAMETAG_NAMESPACE: UNICITY_NAMETAG_NAMESPACE,
9519
9544
  createAddressToBindingFilter: createAddressToBindingFilter,
9520
9545
  createBindingEvent: createBindingEvent,
9521
9546
  createIdentityBindingEvent: createIdentityBindingEvent,
9522
9547
  createNametagToPubkeyFilter: createNametagToPubkeyFilter,
9523
9548
  createPubkeyToNametagFilter: createPubkeyToNametagFilter,
9549
+ hasNametagOwnershipMarker: hasNametagOwnershipMarker,
9524
9550
  isValidBindingEvent: isValidBindingEvent,
9525
9551
  parseAddressFromEvent: parseAddressFromEvent,
9526
9552
  parseBindingInfo: parseBindingInfo,
@@ -10454,7 +10480,18 @@
10454
10480
  await this.publishEvent(event);
10455
10481
  return true;
10456
10482
  }
10457
- catch {
10483
+ catch (err) {
10484
+ // UNIP-01: a relay-enforced single-owner rejection (NIP-20 `blocked:`)
10485
+ // means the nametag is owned by another key — surface it as a hard
10486
+ // failure rather than a silent `false`, which a caller could mistake for
10487
+ // a transient publish error. Other (transient) errors keep returning
10488
+ // false, preserving the prior best-effort behavior.
10489
+ const msg = err instanceof Error ? err.message : String(err);
10490
+ // Match only the ownership-conflict message, not the generic NIP-20
10491
+ // `blocked:` prefix (which also covers spam/rate-limit/policy rejections).
10492
+ if (/owned by another key|already claimed/i.test(msg)) {
10493
+ throw new Error(`Nametag "${nametagId}" is already claimed by another key`);
10494
+ }
10458
10495
  return false;
10459
10496
  }
10460
10497
  }
@@ -10538,20 +10575,26 @@
10538
10575
  }
10539
10576
  }
10540
10577
  /**
10541
- * Query binding events with first-seen-wins anti-hijacking resolution.
10578
+ * Query binding events and select the owner.
10542
10579
  *
10543
- * Strategy: first-seen-wins across authors, latest-wins for same author.
10544
- * - Across authors: the pubkey that first published wins (earliest created_at)
10545
- * - Same author: the most recent event is used (latest created_at = most complete data)
10546
- * - Tie-breaking: deterministic by lexicographic pubkey comparison (lowest wins)
10580
+ * UNIP-01 (preferred): if any returned binding carries the single-owner
10581
+ * namespace marker (NIP-32 ["L", "unicity:nametag"]), ownership is taken from
10582
+ * the marked binding. These are vetted by UNIP-01 relays for single ownership
10583
+ * (first author by relay receive order owns the identifier), so the
10584
+ * self-asserted `created_at` is ignored entirely. With a UNIP-01 relay set
10585
+ * there is exactly one marked owner; observing more than one distinct marked
10586
+ * author (e.g. across relays in differing states) is treated as ambiguous and
10587
+ * resolves to null rather than guessing.
10547
10588
  *
10548
- * Events with invalid signatures are silently skipped to prevent relay injection attacks.
10589
+ * Legacy fallback (no marked binding present e.g. an identifier not yet
10590
+ * migrated): first-seen-wins across authors by `created_at`, latest-wins for
10591
+ * the same author, lexicographic pubkey tie-break. Retained for backward
10592
+ * compatibility during migration; superseded as soon as the owner republishes
10593
+ * a marked binding. (Self-asserted timestamps are not authoritative — this
10594
+ * path exists only so un-migrated identifiers keep resolving.)
10549
10595
  *
10550
- * Known limitations:
10551
- * - Timestamps are self-reported (NIP-01). An attacker can set created_at to 0.
10552
- * Chain-anchored proof of registration time is the only reliable defense.
10553
- * - TOCTOU: between conflict check and publish, another user can claim the same nametag.
10554
- * This is inherent to Nostr's eventually-consistent relay model.
10596
+ * Events with invalid signatures are silently skipped to prevent relay
10597
+ * injection of forged events.
10555
10598
  *
10556
10599
  * @param filter Subscription filter
10557
10600
  * @param extractResult Callback to extract the desired result from the winning event
@@ -10587,6 +10630,19 @@
10587
10630
  const authors = new Map();
10588
10631
  const allRelaysDone = (id) => this.allRelaysDoneFor(id);
10589
10632
  const pickWinner = () => {
10633
+ // UNIP-01: prefer marker-carrying (relay-vetted single-owner) bindings,
10634
+ // ignoring the self-asserted created_at. The marker is read from each
10635
+ // author's CURRENT (latest, per NIP-33 replaceable) event, so a newer
10636
+ // unmarked update correctly drops the author from the marked set, and a
10637
+ // marked owner resolves to their freshest binding.
10638
+ const marked = [...authors.values()].filter((e) => hasNametagOwnershipMarker(e.latestEvent));
10639
+ if (marked.length > 0) {
10640
+ // Exactly one marked owner is the relay-enforced norm. More than one
10641
+ // distinct marked author means cross-relay disagreement — do not guess.
10642
+ const owner = marked.length === 1 ? marked[0] : undefined;
10643
+ return owner ? extractResult(owner.latestEvent) : null;
10644
+ }
10645
+ // Legacy fallback: first-seen-wins by self-asserted created_at.
10590
10646
  let winnerEntry = null;
10591
10647
  let winnerPubkey = '';
10592
10648
  for (const [pubkey, entry] of authors) {
@@ -11435,6 +11491,7 @@
11435
11491
  exports.TEXT_NOTE = TEXT_NOTE;
11436
11492
  exports.TOKEN_TRANSFER = TOKEN_TRANSFER;
11437
11493
  exports.TokenTransferProtocol = TokenTransferProtocol;
11494
+ exports.UNICITY_NAMETAG_NAMESPACE = UNICITY_NAMETAG_NAMESPACE;
11438
11495
  exports.areSameNametag = areSameNametag;
11439
11496
  exports.createAddressToBindingFilter = createAddressToBindingFilter;
11440
11497
  exports.createBindingEvent = createBindingEvent;
@@ -11457,6 +11514,7 @@
11457
11514
  exports.getName = getName;
11458
11515
  exports.getPublicKey = getPublicKey;
11459
11516
  exports.getPublicKeyHex = getPublicKeyHex;
11517
+ exports.hasNametagOwnershipMarker = hasNametagOwnershipMarker;
11460
11518
  exports.hashAddressForTag = hashAddressForTag;
11461
11519
  exports.hashNametag = hashNametag;
11462
11520
  exports.isChatMessage = isChatMessage;