@hashtree/nostr 0.2.3 → 0.2.6

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.
@@ -16,7 +16,7 @@ import type {
16
16
  PublishResult,
17
17
  RefResolverSubscriptionMetadata,
18
18
  } from '@hashtree/core';
19
- import { fromHex, toHex, cid } from '@hashtree/core';
19
+ import { fromHex, toHex, cid, objectKeyFromHex } from '@hashtree/core';
20
20
  import {
21
21
  encryptKeyForLink,
22
22
  computeKeyId,
@@ -195,6 +195,9 @@ function parseHashAndVisibility(event: NostrEvent): ParsedTreeVisibility | null
195
195
  const selfEncryptedLinkKeyTag = event.tags.find(t => t[0] === 'selfEncryptedLinkKey')?.[1];
196
196
 
197
197
  const key = keyTag ?? legacyContent?.key;
198
+ if (key !== undefined) {
199
+ try { objectKeyFromHex(key); } catch { return null; }
200
+ }
198
201
  const encryptedKey = encryptedKeyTag ?? legacyContent?.encryptedKey;
199
202
  const keyId = keyIdTag ?? legacyContent?.keyId;
200
203
  const selfEncryptedKey = selfEncryptedKeyTag ?? legacyContent?.selfEncryptedKey;
@@ -341,7 +344,7 @@ export function createNostrRefResolver(config: NostrRefResolverConfig): RefResol
341
344
  const doResolve = () => {
342
345
  unsubscribe();
343
346
  if (latestData) {
344
- resolve(cid(fromHex(latestData.hash), latestData.key ? fromHex(latestData.key) : undefined));
347
+ resolve(cid(fromHex(latestData.hash), latestData.key ? objectKeyFromHex(latestData.key) : undefined));
345
348
  } else {
346
349
  resolve(null);
347
350
  }
@@ -416,7 +419,7 @@ export function createNostrRefResolver(config: NostrRefResolverConfig): RefResol
416
419
  currentVisibility: sub.currentVisibility ? JSON.stringify(sub.currentVisibility) : 'null'
417
420
  });
418
421
  if (sub.currentHash) {
419
- const keyBytes = sub.currentKey ? fromHex(sub.currentKey) : undefined;
422
+ const keyBytes = sub.currentKey ? objectKeyFromHex(sub.currentKey) : undefined;
420
423
  callback(
421
424
  cid(fromHex(sub.currentHash), keyBytes),
422
425
  sub.currentVisibility ?? undefined,
@@ -430,7 +433,7 @@ export function createNostrRefResolver(config: NostrRefResolverConfig): RefResol
430
433
  // Helper to notify all callbacks
431
434
  const notifyCallbacks = (subEntry: SubscriptionEntry) => {
432
435
  if (!subEntry.currentHash) return;
433
- const keyBytes = subEntry.currentKey ? fromHex(subEntry.currentKey) : undefined;
436
+ const keyBytes = subEntry.currentKey ? objectKeyFromHex(subEntry.currentKey) : undefined;
434
437
  const visibilityInfo = subEntry.currentVisibility ?? undefined;
435
438
  const metadata = {
436
439
  updatedAt: subEntry.latestCreatedAt,
@@ -558,7 +561,7 @@ export function createNostrRefResolver(config: NostrRefResolverConfig): RefResol
558
561
 
559
562
  // Fire callback immediately with cached value if available
560
563
  if (cachedEntry?.hash) {
561
- const keyBytes = cachedEntry.key ? fromHex(cachedEntry.key) : undefined;
564
+ const keyBytes = cachedEntry.key ? objectKeyFromHex(cachedEntry.key) : undefined;
562
565
  callback(
563
566
  cid(fromHex(cachedEntry.hash), keyBytes),
564
567
  sub.currentVisibility ?? undefined,
@@ -651,8 +654,8 @@ export function createNostrRefResolver(config: NostrRefResolverConfig): RefResol
651
654
  break;
652
655
 
653
656
  case 'link-visible': {
654
- // Encrypt key with link key (XOR one-time pad)
655
- const linkKey = options?.linkKey ?? generateLinkKey();
657
+ // Mask the object key with the link secret.
658
+ const linkKey = options?.linkKey ?? generateLinkKey(chkKey.length === 32 ? 32 : 33);
656
659
  resultLinkKey = linkKey;
657
660
  const encryptedKey = encryptKeyForLink(chkKey, linkKey);
658
661
  const keyId = await computeKeyId(linkKey);
@@ -672,7 +675,6 @@ export function createNostrRefResolver(config: NostrRefResolverConfig): RefResol
672
675
  }
673
676
 
674
677
  // Encrypt linkKey to self for URL recovery (so owner can share URLs easily)
675
- // Owner can also derive linkKey from XOR(encryptedKey, contentKey)
676
678
  try {
677
679
  const selfEncryptedLinkKey = await visibility.encrypt(linkKey, pubkey);
678
680
  tags.push(['selfEncryptedLinkKey', selfEncryptedLinkKey]);
@@ -771,7 +773,7 @@ export function createNostrRefResolver(config: NostrRefResolverConfig): RefResol
771
773
  if (!entry.hash) continue; // Skip deleted trees
772
774
  result.push({
773
775
  key: `${npubStr}/${dTag}`,
774
- cid: cid(fromHex(entry.hash), entry.key ? fromHex(entry.key) : undefined),
776
+ cid: cid(fromHex(entry.hash), entry.key ? objectKeyFromHex(entry.key) : undefined),
775
777
  labels: entry.labels,
776
778
  visibility: entry.visibility,
777
779
  encryptedKey: entry.encryptedKey,
@@ -853,7 +855,7 @@ export function createNostrRefResolver(config: NostrRefResolverConfig): RefResol
853
855
  }
854
856
  result.push({
855
857
  key: `${npubStr}/${dTag}`,
856
- cid: cid(fromHex(entry.hash), entry.key ? fromHex(entry.key) : undefined),
858
+ cid: cid(fromHex(entry.hash), entry.key ? objectKeyFromHex(entry.key) : undefined),
857
859
  labels: entry.labels,
858
860
  visibility: entry.visibility,
859
861
  encryptedKey: entry.encryptedKey,
@@ -895,7 +897,7 @@ export function createNostrRefResolver(config: NostrRefResolverConfig): RefResol
895
897
  if (!entry.hash) continue;
896
898
  result.push({
897
899
  key: `${npubStr}/${dTag}`,
898
- cid: cid(fromHex(entry.hash), entry.key ? fromHex(entry.key) : undefined),
900
+ cid: cid(fromHex(entry.hash), entry.key ? objectKeyFromHex(entry.key) : undefined),
899
901
  labels: entry.labels,
900
902
  visibility: entry.visibility,
901
903
  encryptedKey: entry.encryptedKey,
@@ -1074,7 +1076,7 @@ export function createNostrRefResolver(config: NostrRefResolverConfig): RefResol
1074
1076
  if (!entry.hash) continue;
1075
1077
  result.push({
1076
1078
  key: `${npubStr}/${dTag}`,
1077
- cid: cid(fromHex(entry.hash), entry.key ? fromHex(entry.key) : undefined),
1079
+ cid: cid(fromHex(entry.hash), entry.key ? objectKeyFromHex(entry.key) : undefined),
1078
1080
  labels: entry.labels,
1079
1081
  visibility: entry.visibility,
1080
1082
  encryptedKey: entry.encryptedKey,
@@ -1186,7 +1188,7 @@ export function createNostrRefResolver(config: NostrRefResolverConfig): RefResol
1186
1188
  if (!e.hash) continue; // Skip deleted trees
1187
1189
  result.push({
1188
1190
  key: `${npubStr}/${dTag}`,
1189
- cid: cid(fromHex(e.hash), e.key ? fromHex(e.key) : undefined),
1191
+ cid: cid(fromHex(e.hash), e.key ? objectKeyFromHex(e.key) : undefined),
1190
1192
  labels: e.labels,
1191
1193
  visibility: e.visibility,
1192
1194
  encryptedKey: e.encryptedKey,
package/src/snapshot.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { objectKeyFromHex } from '@hashtree/core';
1
2
  import { HashTree, cid, fromHex, type CID, type Store, type TreeVisibility } from '@hashtree/core';
2
3
  import type { StoredNostrEvent } from './events.js';
3
4
 
@@ -199,7 +200,7 @@ export function parseHashtreeRootEvent(event: StoredNostrEvent): ParsedHashtreeR
199
200
  return {
200
201
  event: normalized,
201
202
  treeName,
202
- rootCid: cid(fromHex(hashHex), visibility === 'public' && keyHex ? fromHex(keyHex) : undefined),
203
+ rootCid: cid(fromHex(hashHex), visibility === 'public' && keyHex !== undefined ? objectKeyFromHex(keyHex) : undefined),
203
204
  visibility,
204
205
  labels,
205
206
  encryptedKey,
@@ -1,7 +1,7 @@
1
1
  import { nhashDecode } from '@hashtree/core';
2
2
 
3
3
  const NHASH_PATTERN = /^nhash1[a-z0-9]+$/;
4
- const LINK_KEY_PATTERN = /^[a-f0-9]{64}$/i;
4
+ const LINK_KEY_PATTERN = /^(?:[a-f0-9]{2}){32,33}$/i;
5
5
 
6
6
  export interface TreeEventSnapshotPermalink {
7
7
  snapshotNhash: string;
@@ -79,7 +79,7 @@ function normalizeTreeEventSnapshotPermalink(
79
79
 
80
80
  const linkKey = normalizeTreeEventSnapshotLinkKey(value.linkKey);
81
81
  if (value.linkKey?.trim() && !linkKey) {
82
- throw new Error(`Invalid tree event snapshot link key: ${value.linkKey}`);
82
+ throw new Error('Invalid tree event snapshot link key');
83
83
  }
84
84
 
85
85
  return {
@@ -6,6 +6,7 @@ import {
6
6
  type CID,
7
7
  } from '@hashtree/core';
8
8
  import type { StoredNostrEvent } from './events.js';
9
+ import { normalizeTreeEventSnapshotLinkKey } from './treeEventSnapshotPermalinks.js';
9
10
  import { compareReplaceableEvents } from './eventKeys.js';
10
11
  import type { Nip19Like, NostrFilter } from './resolver/nostr.js';
11
12
  import {
@@ -68,7 +69,9 @@ function normalizeLinkKey(linkKey?: string | Uint8Array | null): Uint8Array | nu
68
69
  if (!linkKey) {
69
70
  return null;
70
71
  }
71
- return typeof linkKey === 'string' ? fromHex(linkKey) : linkKey;
72
+ if (typeof linkKey !== 'string') return linkKey;
73
+ const hex = normalizeTreeEventSnapshotLinkKey(linkKey);
74
+ return hex ? fromHex(hex) : null;
72
75
  }
73
76
 
74
77
  function isValidTreeEventFor(