@unicitylabs/nostr-js-sdk 0.3.3 → 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,27 +272,93 @@ PaymentRequestProtocol.parseAmount('1.5', 8); // BigInt(150_000_000) (8 decimals
272
272
 
273
273
  ### Nametag Bindings
274
274
 
275
- Nametags are lowercase alphanumeric strings (`[a-z0-9_-]`, 3–20 chars) or phone numbers (E.164 format). All input is normalized to lowercase before hashing. Use `isValidNametag(input)` to validate.
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.
276
+
277
+ #### Publishing a Binding
276
278
 
277
279
  ```typescript
278
- import { NametagBinding, NametagUtils } from '@unicitylabs/nostr-sdk';
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
+ },
290
+ );
291
+ // Throws if nametag is already claimed by another pubkey.
292
+ // Returns true on success, false if publish fails.
293
+ ```
279
294
 
280
- // Hash a nametag (privacy-preserving)
281
- const hash = NametagUtils.hashNametag('+14155551234', 'US');
295
+ #### Resolving a Nametag
282
296
 
283
- // Create binding event
284
- const event = await NametagBinding.createBindingEvent(
285
- keyManager,
286
- '+14155551234',
287
- 'unicity_address_...'
288
- );
297
+ ```typescript
298
+ // Simple: get the owner pubkey
299
+ const pubkey = await client.queryPubkeyByNametag('alice');
289
300
 
290
- 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 }
291
304
 
292
- // Query pubkey by nametag
293
- const pubkey = await client.queryPubkeyByNametag('+14155551234');
305
+ // Reverse lookup: address → binding
306
+ const info = await client.queryBindingByAddress('alpha1abc...');
307
+ ```
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
+ }
294
333
  ```
295
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
+
296
362
  ## Token Transfer Format
297
363
 
298
364
  Token transfers use Nostr event kind 31113 with NIP-04 encryption.