@unicitylabs/nostr-js-sdk 0.3.2 → 0.4.0-dev.1

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
@@ -272,25 +272,93 @@ PaymentRequestProtocol.parseAmount('1.5', 8); // BigInt(150_000_000) (8 decimals
272
272
 
273
273
  ### Nametag Bindings
274
274
 
275
- ```typescript
276
- import { NametagBinding, NametagUtils } from '@unicitylabs/nostr-sdk';
275
+ Nametags are human-readable identifiers (`[a-z0-9_-]`, 3–20 chars) or phone numbers (E.164). All input is normalized to lowercase before hashing. Use `isValidNametag(input)` to validate.
277
276
 
278
- // Hash a nametag (privacy-preserving)
279
- const hash = NametagUtils.hashNametag('+14155551234', 'US');
277
+ #### Publishing a Binding
280
278
 
281
- // Create binding event
282
- const event = await NametagBinding.createBindingEvent(
283
- keyManager,
284
- '+14155551234',
285
- 'unicity_address_...'
279
+ ```typescript
280
+ // Publish nametag binding with identity info
281
+ const success = await client.publishNametagBinding(
282
+ 'alice', // nametag
283
+ keyManager.getPublicKeyHex(), // Nostr pubkey (stored in event's address field)
284
+ { // optional extended identity
285
+ publicKey: '02abc...', // 33-byte compressed secp256k1 chain pubkey
286
+ l1Address: 'alpha1...', // L1 bech32 address
287
+ directAddress: 'DIRECT://...', // L3 direct address
288
+ proxyAddress: 'PROXY://...', // proxy address (derived from nametag)
289
+ },
286
290
  );
291
+ // Throws if nametag is already claimed by another pubkey.
292
+ // Returns true on success, false if publish fails.
293
+ ```
294
+
295
+ #### Resolving a Nametag
296
+
297
+ ```typescript
298
+ // Simple: get the owner pubkey
299
+ const pubkey = await client.queryPubkeyByNametag('alice');
287
300
 
288
- await client.publishEvent(event);
301
+ // Extended: get full binding info (addresses, nametag, etc.)
302
+ const info = await client.queryBindingByNametag('alice');
303
+ // info: { transportPubkey, publicKey?, l1Address?, directAddress?, proxyAddress?, nametag?, timestamp }
289
304
 
290
- // Query pubkey by nametag
291
- const pubkey = await client.queryPubkeyByNametag('+14155551234');
305
+ // Reverse lookup: address → binding
306
+ const info = await client.queryBindingByAddress('alpha1abc...');
292
307
  ```
293
308
 
309
+ #### Event Format (kind 30078)
310
+
311
+ Nametag bindings use NIP-78 parameterized replaceable events. Only the original author can update their own event (same pubkey + d-tag = replacement).
312
+
313
+ **Nametag binding event** (with identity):
314
+
315
+ ```json
316
+ {
317
+ "kind": 30078,
318
+ "pubkey": "<32-byte x-only Nostr pubkey>",
319
+ "tags": [
320
+ ["d", "<SHA256('unicity:nametag:' + normalizedNametag)>"],
321
+ ["nametag", "<hashed_nametag>"],
322
+ ["t", "<hashed_nametag>"],
323
+ ["address", "<nostr_pubkey>"],
324
+ ["t", "<SHA256('unicity:address:' + chainPubkey)>"],
325
+ ["pubkey", "<chainPubkey>"],
326
+ ["t", "<SHA256('unicity:address:' + l1Address)>"],
327
+ ["l1", "<l1Address>"],
328
+ ["t", "<SHA256('unicity:address:' + directAddress)>"],
329
+ ["t", "<SHA256('unicity:address:' + proxyAddress)>"]
330
+ ],
331
+ "content": "{\"nametag_hash\":\"...\",\"address\":\"...\",\"verified\":...,\"nametag\":\"alice\",\"encrypted_nametag\":\"...\",\"public_key\":\"02...\",\"l1_address\":\"alpha1...\",\"direct_address\":\"DIRECT://...\",\"proxy_address\":\"PROXY://...\"}"
332
+ }
333
+ ```
334
+
335
+ Key fields in content:
336
+ - `nametag` — plaintext nametag (for display)
337
+ - `encrypted_nametag` — AES-GCM encrypted nametag (for recovery by private key owner)
338
+ - `public_key`, `l1_address`, `direct_address`, `proxy_address` — identity addresses
339
+ - `nametag_hash` — `SHA256('unicity:nametag:' + normalizedNametag)`
340
+
341
+ Tags enable indexed lookups:
342
+ - `d` tag: makes event replaceable per nametag per author
343
+ - `t` tags: hashed nametag + hashed addresses for relay search (privacy-preserving)
344
+ - `pubkey`, `l1` tags: unhashed for backward-compatible lookups
345
+
346
+ #### Anti-Hijacking Resolution Strategy
347
+
348
+ All query methods use **first-seen-wins across authors, latest-wins for same author**:
349
+
350
+ 1. **First-seen-wins across authors** — if multiple pubkeys claim the same nametag, the author who published the earliest `created_at` event wins. Ties are broken deterministically by lexicographic pubkey comparison. This prevents hijacking.
351
+
352
+ 2. **Latest-wins for same author** — if the rightful owner publishes multiple events (e.g., initial binding without nametag, then updated binding with nametag), the most recent event is returned. This ensures queries return the most complete data.
353
+
354
+ 3. **Signature verification** — events with invalid signatures are silently skipped, preventing malicious relays from injecting forged events.
355
+
356
+ This two-level strategy is critical for the wallet workflow where a binding may be published first without a nametag, then updated later when the user registers one. Both events share address `#t` tags, so address-based lookups see both — the strategy ensures the latest (most complete) event from the original author is returned.
357
+
358
+ #### Privacy
359
+
360
+ Nametags are never stored as plaintext in tags. All `t` and `d` tags use `SHA256('unicity:nametag:' + name)` or `SHA256('unicity:address:' + address)` hashes. The plaintext nametag is only in the event content (JSON), and an encrypted copy (`encrypted_nametag`) allows the private key owner to recover it.
361
+
294
362
  ## Token Transfer Format
295
363
 
296
364
  Token transfers use Nostr event kind 31113 with NIP-04 encryption.