@steve02081504/fount-p2p 0.0.35 → 0.0.36

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.
Files changed (51) hide show
  1. package/AGENTS.md +1 -1
  2. package/core/entity_id.mjs +6 -6
  3. package/core/entity_id_parse.mjs +7 -7
  4. package/core/hexIds.mjs +6 -18
  5. package/crypto/checkpoint_sign.mjs +4 -4
  6. package/dag/canonicalize_row.mjs +4 -7
  7. package/discovery/adverts.mjs +1 -2
  8. package/discovery/bt/index.mjs +11 -11
  9. package/discovery/bt/peer_hints.mjs +4 -7
  10. package/discovery/index.mjs +8 -12
  11. package/discovery/internal/signal_crypto.mjs +1 -2
  12. package/discovery/lan.mjs +7 -9
  13. package/discovery/lan_peer_hints.mjs +5 -8
  14. package/discovery/nostr.mjs +19 -20
  15. package/discovery/peer_clue.mjs +1 -4
  16. package/federation/entity_key_chain.mjs +10 -9
  17. package/federation/message_rate_limit.mjs +1 -3
  18. package/files/manifest/normalize.mjs +2 -2
  19. package/files/manifest/public.mjs +5 -5
  20. package/governance/branch.mjs +1 -2
  21. package/governance/join_pow.mjs +2 -4
  22. package/governance/owner_succession_ballot.mjs +5 -5
  23. package/infra/priority.mjs +1 -2
  24. package/link/handshake.mjs +21 -21
  25. package/link/pipe.mjs +1 -2
  26. package/link/providers/ble_gatt.mjs +2 -3
  27. package/link/providers/lan_tcp.mjs +2 -3
  28. package/link/providers/link_id_pipe.mjs +3 -3
  29. package/link/providers/nostr.mjs +6 -6
  30. package/mailbox/deliver_or_store.mjs +11 -11
  31. package/mailbox/prune.mjs +3 -4
  32. package/mailbox/rate.mjs +3 -2
  33. package/mailbox/store.mjs +6 -9
  34. package/node/denylist.mjs +25 -28
  35. package/node/identity.mjs +6 -6
  36. package/node/network.mjs +12 -12
  37. package/node/personal_block.mjs +4 -4
  38. package/node/reputation_store.mjs +13 -13
  39. package/node/reputation_sync.mjs +9 -10
  40. package/package.json +1 -1
  41. package/reputation/engine.mjs +14 -16
  42. package/schemas/discovery.mjs +5 -6
  43. package/schemas/federation_pull.mjs +7 -7
  44. package/schemas/mailbox.mjs +3 -3
  45. package/schemas/part_query.mjs +5 -5
  46. package/timeline/verify_remote.mjs +7 -7
  47. package/transport/link_registry.mjs +16 -19
  48. package/transport/mesh_keepalive.mjs +8 -11
  49. package/transport/offer_answer.mjs +3 -3
  50. package/transport/peer_health.mjs +12 -15
  51. package/trust_graph/send.mjs +3 -3
package/AGENTS.md CHANGED
@@ -33,7 +33,7 @@ Deno / native / BT: [runtime.md](docs/runtime.md).
33
33
  - **Heterogeneous backends:** normalize at the load boundary (e.g. `link/rtc/ice_local_hostname.mjs` wraps W3C RTC backends); call sites speak one contract.
34
34
  - **File naming:** parent directory is scope — short child names. Tunables default `<dir>/tunables.json` (exception: `schemas/part_query.tunables.json`). Subpath `package.json` exports mirror filenames.
35
35
  - **Import boundary:** `test/integration/p2p_shell_import_guard.test.mjs`.
36
- - **No scattered `trim` / `toLowerCase`:** hex IDs must already be lowercase; `normalizeHex64` strips an optional `0x` only — mixed case / whitespace is rejected. Exceptions: JSONL blank lines, SDP fingerprint, CLI/`scripts` parsing.
36
+ - **No scattered `trim` / `toLowerCase`:** hex IDs must already be lowercase and without a `0x` prefixa `0x`-prefixed, mixed-case, or whitespace value is rejected by `isHex64`/`isEntityHash128`, never cleaned. Exceptions: JSONL blank lines, SDP fingerprint, CLI/`scripts` parsing.
37
37
  - **No `String(x)` / `x || ''` on typed `string`:** if `@param {string}`, use it directly; `String(...)` / `|| ''` / `?? ''` only at optional / `unknown` / disk / inbound boundaries, or number→string.
38
38
  - **Optional methods:** `if (fn) return await fn(...)` / `if (fn) …` — never `typeof x === 'function'`.
39
39
 
@@ -7,7 +7,7 @@ import {
7
7
  isEntityHash128,
8
8
  parseEntityHash,
9
9
  } from './entity_id_parse.mjs'
10
- import { isHex64, normalizeHex64 } from './hexIds.mjs'
10
+ import { isHex64 } from './hexIds.mjs'
11
11
 
12
12
  /**
13
13
  * entityHash 编解码与校验(re-export)。
@@ -24,8 +24,8 @@ export {
24
24
  * @returns {string} 64 位 nodeHash / subjectHash(pubKeyHash)
25
25
  */
26
26
  export function hashFromPubKeyHex(pubKeyHex) {
27
- const hex = normalizeHex64(pubKeyHex)
28
- if (!isHex64(hex)) throw new Error('invalid pubKeyHex')
27
+ const hex = isHex64(pubKeyHex)
28
+ if (!hex) throw new Error('invalid pubKeyHex')
29
29
  const bytes = hexToBytes(hex)
30
30
  if (bytes.length !== 32) throw new Error('invalid pubKeyHex')
31
31
  return pubKeyHash(bytes)
@@ -46,8 +46,8 @@ export function entityHashFromRecoveryPubKeyHex(nodeHash, recoveryPubKeyHex) {
46
46
  * @returns {string} entityHash
47
47
  */
48
48
  export function entityHashFromSubjectHash(nodeHash, subjectHash) {
49
- const node = normalizeHex64(nodeHash)
50
- const subject = normalizeHex64(subjectHash)
51
- if (!isHex64(node) || !isHex64(subject)) throw new Error('invalid subject hash')
49
+ const node = isHex64(nodeHash)
50
+ const subject = isHex64(subjectHash)
51
+ if (!node || !subject) throw new Error('invalid subject hash')
52
52
  return encodeEntityHash(node, subject)
53
53
  }
@@ -1,14 +1,14 @@
1
- import { isHex64, normalizeHex64 } from './hexIds.mjs'
1
+ import { isHex64 } from './hexIds.mjs'
2
2
 
3
3
  /** 128 位小写 hex:`nodeHash(64)` + `subjectHash(64)`。 */
4
4
  export const ENTITY_HASH_RE = /^[\da-f]{128}$/u
5
5
 
6
6
  /**
7
7
  * @param {unknown} value 待校验值
8
- * @returns {boolean} 是否为合法 128 hex
8
+ * @returns {string | null} 合法时返回原 128 位实体hash,否则 null(含 0x 前缀/大写/空白)
9
9
  */
10
10
  export function isEntityHash128(value) {
11
- return ENTITY_HASH_RE.test(String(value ?? '').replace(/^0x/iu, ''))
11
+ return ENTITY_HASH_RE.test(value) ? value : null
12
12
  }
13
13
 
14
14
  /**
@@ -16,7 +16,7 @@ export function isEntityHash128(value) {
16
16
  * @returns {{ entityHash: string, nodeHash: string, subjectHash: string } | null} 解析结果;非法时 null
17
17
  */
18
18
  export function parseEntityHash(entityHash) {
19
- const raw = String(entityHash ?? '').replace(/^0x/iu, '')
19
+ const raw = String(entityHash ?? '')
20
20
  if (!ENTITY_HASH_RE.test(raw)) return null
21
21
  return {
22
22
  entityHash: raw,
@@ -31,9 +31,9 @@ export function parseEntityHash(entityHash) {
31
31
  * @returns {string} 128 位 entityHash
32
32
  */
33
33
  export function encodeEntityHash(nodeHash, subjectHash) {
34
- const node = normalizeHex64(nodeHash)
35
- const subject = normalizeHex64(subjectHash)
36
- if (!isHex64(node) || !isHex64(subject))
34
+ const node = isHex64(nodeHash)
35
+ const subject = isHex64(subjectHash)
36
+ if (!node || !subject)
37
37
  throw new Error('invalid entity hash parts')
38
38
  return node + subject
39
39
  }
package/core/hexIds.mjs CHANGED
@@ -10,21 +10,12 @@ export const BLOB_STORAGE_LOCATOR_RE = /^blob:([\da-f]{64})$/u
10
10
  /** `local:…/chunks/<64hex>.bin` 群分块路径。 */
11
11
  export const LOCAL_CHUNK_FILE_RE = /^local:[^/]+\/chunks\/([\da-f]{64})\.bin$/u
12
12
 
13
- /**
14
- * @param {unknown} value 原始字符串
15
- * @returns {string} 去可选 0x 前缀后的字符串(不修大小写/空白)
16
- */
17
- export function normalizeHex64(value) {
18
- return String(value ?? '').replace(/^0x/iu, '')
19
- }
20
-
21
13
  /**
22
14
  * @param {unknown} value 待校验值
23
- * @returns {string | null} 合法时返回规范化 64 位 hex,否则 null
15
+ * @returns {string | null} 合法时返回原 64 位 hex,否则 null(含 0x 前缀/大写/空白)
24
16
  */
25
17
  export function isHex64(value) {
26
- const normalized = normalizeHex64(value)
27
- return HEX_ID_64.test(normalized) ? normalized : null
18
+ return HEX_ID_64.test(value) ? value : null
28
19
  }
29
20
 
30
21
  /**
@@ -34,22 +25,19 @@ export function isHex64(value) {
34
25
  * @returns {number} 排序比较结果
35
26
  */
36
27
  export function compareHex64Asc(a, b) {
37
- const sa = normalizeHex64(a)
38
- const sb = normalizeHex64(b)
39
- return sa < sb ? -1 : sa > sb ? 1 : 0
28
+ return a < b ? -1 : a > b ? 1 : 0
40
29
  }
41
30
 
42
31
  /**
43
- * 外部入站专用:规范化并断言 64 位小写 hex
32
+ * 外部入站专用:断言小写 64 hex(不清理 0x 前缀,直接拒绝)。
44
33
  * @param {unknown} value 原始值
45
34
  * @param {string} [label='hex64'] 字段名(错误信息)
46
35
  * @returns {string} 小写 64 位 hex
47
36
  */
48
37
  export function assertHex64(value, label = 'hex64') {
49
- const normalized = normalizeHex64(value)
50
- if (!HEX_ID_64.test(normalized))
38
+ if (!HEX_ID_64.test(value))
51
39
  throw new Error(`${label} must be 64 hex characters`)
52
- return normalized
40
+ return value
53
41
  }
54
42
 
55
43
  /**
@@ -27,8 +27,8 @@ export async function signCheckpoint(payload, secretKey) {
27
27
  * @returns {Promise<boolean>} 合法为 true
28
28
  */
29
29
  export async function verifyCheckpointSignature(checkpoint, ownerPublicKey) {
30
- const raw = String(checkpoint.checkpoint_signature || '')
31
- if (!isSignatureHex128(raw)) return false
30
+ const raw = isSignatureHex128(checkpoint.checkpoint_signature)
31
+ if (!raw) return false
32
32
  const body = { ...checkpoint }
33
33
  delete body.checkpoint_signature
34
34
  const messageBytes = Buffer.from(canonicalStringify(body), 'utf8')
@@ -41,7 +41,7 @@ export async function verifyCheckpointSignature(checkpoint, ownerPublicKey) {
41
41
  * @returns {boolean} 签名格式合法为 true
42
42
  */
43
43
  export function isSignedCheckpoint(checkpoint) {
44
- return !!isSignatureHex128(String(checkpoint?.checkpoint_signature || ''))
44
+ return !!isSignatureHex128(checkpoint?.checkpoint_signature)
45
45
  }
46
46
 
47
47
  /**
@@ -60,7 +60,7 @@ export async function verifyRemoteCheckpoint(checkpoint) {
60
60
  const ownerHash = checkpoint.members_record?.delegatedOwnerPubKeyHash
61
61
  const owner = checkpoint.members_record?.members?.[ownerHash]
62
62
  const pubHex = owner?.pubKeyHex
63
- if (!pubHex || !isHex64(pubHex))
63
+ if (!isHex64(pubHex))
64
64
  return { valid: false, reason: 'delegated owner pubkey missing' }
65
65
  if (!await verifyCheckpointSignature(checkpoint, Buffer.from(pubHex, 'hex')))
66
66
  return { valid: false, reason: 'checkpoint signature invalid' }
@@ -2,17 +2,14 @@
2
2
  * DAG / 时间线签名行入库前的共用 hex 规范化(非权限校验)。
3
3
  */
4
4
  import { isEntityHash128 } from '../core/entity_id.mjs'
5
- import { assertHex64, HEX_ID_64, normalizeHex64 } from '../core/hexIds.mjs'
5
+ import { assertHex64 } from '../core/hexIds.mjs'
6
6
  /**
7
7
  * @param {Record<string, unknown>} obj 可变对象
8
8
  * @param {string} key 字段名
9
9
  */
10
10
  function canonicalizeHexField(obj, key) {
11
11
  if (!obj[key]) return
12
- const normalized = normalizeHex64(obj[key])
13
- if (!HEX_ID_64.test(normalized))
14
- throw new Error(`${key} must be 64 hex characters`)
15
- obj[key] = normalized
12
+ obj[key] = assertHex64(obj[key], key)
16
13
  }
17
14
 
18
15
  /**
@@ -28,8 +25,8 @@ export function canonicalizeRowContent(content, hexKeys, entityHashKeys = new Se
28
25
  canonicalizeHexField(out, key)
29
26
  for (const key of entityHashKeys) {
30
27
  if (!out[key]) continue
31
- const entityHash = String(out[key] || '')
32
- if (!isEntityHash128(entityHash))
28
+ const entityHash = isEntityHash128(out[key])
29
+ if (!entityHash)
33
30
  throw new Error(`${key} must be 128 hex characters`)
34
31
  out[key] = entityHash
35
32
  }
@@ -1,4 +1,3 @@
1
- import { normalizeHex64 } from '../core/hexIds.mjs'
2
1
  import { buildSignedAdvert, verifySignedAdvert } from '../link/handshake.mjs'
3
2
 
4
3
  import {
@@ -95,7 +94,7 @@ export async function ingestNetworkAdvert(bytes) {
95
94
  * @returns {Promise<{ verifiedNodeHash: string, body: object } | null>} 验签成功返回 nodeHash 与 advert body,否则 null
96
95
  */
97
96
  export async function ingestNodeAdvert(nodeHash, bytes) {
98
- return ingestEncryptedAdvert(nodeRendezvousKey(normalizeHex64(nodeHash)), bytes)
97
+ return ingestEncryptedAdvert(nodeRendezvousKey(nodeHash), bytes)
99
98
  }
100
99
 
101
100
  /**
@@ -1,6 +1,6 @@
1
1
  import { Buffer } from 'node:buffer'
2
2
 
3
- import { isHex64, normalizeHex64 } from '../../core/hexIds.mjs'
3
+ import { isHex64 } from '../../core/hexIds.mjs'
4
4
  import { nodeDebug, shortHash } from '../../node/log.mjs'
5
5
  import { noteAdvertPeerHints } from '../advert_peer_hints.mjs'
6
6
  import { ingestNetworkAdvert } from '../adverts.mjs'
@@ -31,8 +31,8 @@ const visibleByHash = new Map()
31
31
  * @returns {void}
32
32
  */
33
33
  export function noteBtVisibleNode(nodeHash, now = Date.now()) {
34
- const hash = normalizeHex64(nodeHash)
35
- if (!isHex64(hash)) return
34
+ const hash = isHex64(nodeHash)
35
+ if (!hash) return
36
36
  visibleByHash.set(hash, now)
37
37
  }
38
38
 
@@ -176,7 +176,7 @@ export function createBluetoothDiscoveryProvider() {
176
176
  onWriteRequest(_connection, data, _offset, _withoutResponse, callback) {
177
177
  try {
178
178
  const parsed = JSON.parse(Buffer.from(data).toString('utf8'))
179
- const to = normalizeHex64(parsed?.to)
179
+ const to = parsed?.to
180
180
  const bytes = Uint8Array.from(Buffer.from(String(parsed?.data || ''), 'base64'))
181
181
  if (isHex64(to) && bytes.byteLength)
182
182
  for (const listener of signalListeners.get(to) || [])
@@ -268,7 +268,7 @@ export function createBluetoothDiscoveryProvider() {
268
268
  * @returns {Promise<boolean>} 是否经 GATT 发出
269
269
  */
270
270
  async function sendNodeSignalViaGatt(toNodeHash, bytes) {
271
- const hash = normalizeHex64(toNodeHash)
271
+ const hash = toNodeHash
272
272
  const hint = getBtPeerHint(hash)
273
273
  if (!hint) return false
274
274
  const blob = Buffer.from(JSON.stringify({
@@ -353,8 +353,8 @@ export function createBluetoothDiscoveryProvider() {
353
353
  * @returns {Promise<boolean>} 有 BT hint 且 GATT 可达时为 true
354
354
  */
355
355
  async connectToNode(nodeHash) {
356
- const hash = normalizeHex64(nodeHash)
357
- if (!isHex64(hash)) return false
356
+ const hash = isHex64(nodeHash)
357
+ if (!hash) return false
358
358
  if (!getBtPeerHint(hash)) return false
359
359
  return await sendNodeSignalViaGatt(hash, new Uint8Array([0])).catch(() => false)
360
360
  },
@@ -370,8 +370,8 @@ export function createBluetoothDiscoveryProvider() {
370
370
  const refresh = async () => {
371
371
  const body = await getBeacon?.()
372
372
  if (!body?.nodeHash || !body.advertBytes?.byteLength) return
373
- const hash = normalizeHex64(body.nodeHash)
374
- if (!isHex64(hash)) return
373
+ const hash = isHex64(body.nodeHash)
374
+ if (!hash) return
375
375
  localNodeHash = hash
376
376
  localPresence.set(hash, Uint8Array.from(body.advertBytes))
377
377
  noteBtVisibleNode(hash)
@@ -401,8 +401,8 @@ export function createBluetoothDiscoveryProvider() {
401
401
  */
402
402
  async listenNodeSignals(localNodeHash, onSignal) {
403
403
  if (role === 'scan') return () => { }
404
- const hash = normalizeHex64(localNodeHash)
405
- if (!isHex64(hash)) throw new Error('p2p: invalid nodeHash')
404
+ const hash = isHex64(localNodeHash)
405
+ if (!hash) throw new Error('p2p: invalid nodeHash')
406
406
  await ensurePeripheralRuntime()
407
407
  if (!signalListeners.has(hash)) signalListeners.set(hash, new Set())
408
408
  signalListeners.get(hash).add(onSignal)
@@ -1,4 +1,3 @@
1
- import { normalizeHex64 } from '../../core/hexIds.mjs'
2
1
  import { createTtlMap } from '../../utils/ttl_map.mjs'
3
2
 
4
3
  /** BT peer hint 存活时间。 */
@@ -14,9 +13,8 @@ const hints = createTtlMap(BT_PEER_HINT_TTL_MS)
14
13
  * @returns {void}
15
14
  */
16
15
  export function noteBtPeerHint(nodeHash, peripheralId) {
17
- const hash = normalizeHex64(nodeHash)
18
- if (!hash || !peripheralId) return
19
- hints.set(hash, { peripheralId })
16
+ if (!nodeHash || !peripheralId) return
17
+ hints.set(nodeHash, { peripheralId })
20
18
  }
21
19
 
22
20
  /**
@@ -26,9 +24,8 @@ export function noteBtPeerHint(nodeHash, peripheralId) {
26
24
  * @returns {{ peripheralId: string } | null} hint 或 null
27
25
  */
28
26
  export function getBtPeerHint(nodeHash, now = Date.now()) {
29
- const hash = normalizeHex64(nodeHash)
30
- if (!hash) return null
31
- return hints.get(hash, now)
27
+ if (!nodeHash) return null
28
+ return hints.get(nodeHash, now)
32
29
  }
33
30
 
34
31
  /**
@@ -1,4 +1,3 @@
1
- import { normalizeHex64 } from '../core/hexIds.mjs'
2
1
  import { nodeDebug, shortHash } from '../node/log.mjs'
3
2
 
4
3
  import { ingestGroupAdvert, ingestNodeAdvert } from './adverts.mjs'
@@ -149,10 +148,9 @@ export async function listVisibleNodeHashes(options = {}) {
149
148
  * @returns {Promise<void>}
150
149
  */
151
150
  export async function prepareConnectToNode(nodeHash, options = {}) {
152
- const hash = normalizeHex64(nodeHash)
153
151
  for (const provider of listDiscoveryProviders()) {
154
152
  if (!provider.connectToNode) continue
155
- try { await provider.connectToNode(hash, options) }
153
+ try { await provider.connectToNode(nodeHash, options) }
156
154
  catch { /* prepare next medium */ }
157
155
  }
158
156
  }
@@ -164,20 +162,19 @@ export async function prepareConnectToNode(nodeHash, options = {}) {
164
162
  * @returns {Promise<boolean>} 是否建链成功
165
163
  */
166
164
  export async function connectToNode(nodeHash, options = {}) {
167
- const hash = normalizeHex64(nodeHash)
168
165
  if (!linkDialer) {
169
- await prepareConnectToNode(hash, options)
170
- nodeDebug('p2p:discovery connect skip', { peer: shortHash(hash), reason: 'no-dialer' })
166
+ await prepareConnectToNode(nodeHash, options)
167
+ nodeDebug('p2p:discovery connect skip', { peer: shortHash(nodeHash), reason: 'no-dialer' })
171
168
  return false
172
169
  }
173
170
  try {
174
- const ok = !!await linkDialer(hash)
175
- nodeDebug(ok ? 'p2p:discovery connect ok' : 'p2p:discovery connect miss', { peer: shortHash(hash) })
171
+ const ok = !!await linkDialer(nodeHash)
172
+ nodeDebug(ok ? 'p2p:discovery connect ok' : 'p2p:discovery connect miss', { peer: shortHash(nodeHash) })
176
173
  return ok
177
174
  }
178
175
  catch (error) {
179
176
  nodeDebug('p2p:discovery connect fail', {
180
- peer: shortHash(hash),
177
+ peer: shortHash(nodeHash),
181
178
  err: String(error?.message || error),
182
179
  })
183
180
  return false
@@ -271,8 +268,7 @@ export async function startGroupPresence(roomSecret, getBeacon) {
271
268
  * @returns {Promise<void>}
272
269
  */
273
270
  export async function sendNodeSignalPacket(toNodeHash, packet) {
274
- const hash = normalizeHex64(toNodeHash)
275
- await sendNodeSignal(hash, encryptSignalPacket(nodeRendezvousKey(hash), packet))
271
+ await sendNodeSignal(toNodeHash, encryptSignalPacket(nodeRendezvousKey(toNodeHash), packet))
276
272
  }
277
273
 
278
274
  /**
@@ -282,7 +278,7 @@ export async function sendNodeSignalPacket(toNodeHash, packet) {
282
278
  * @returns {object | null} 解密 JSON
283
279
  */
284
280
  export function decryptNodeSignalPacket(localNodeHash, bytes) {
285
- return decryptSignalPacket(nodeRendezvousKey(normalizeHex64(localNodeHash)), bytes)
281
+ return decryptSignalPacket(nodeRendezvousKey(localNodeHash), bytes)
286
282
  }
287
283
 
288
284
  /**
@@ -1,7 +1,6 @@
1
1
  import { Buffer } from 'node:buffer'
2
2
  import { createCipheriv, createDecipheriv, createHash, randomBytes } from 'node:crypto'
3
3
 
4
- import { normalizeHex64 } from '../../core/hexIds.mjs'
5
4
  import { sha256Hex } from '../../crypto/crypto.mjs'
6
5
  import { createLruMap } from '../../utils/lru.mjs'
7
6
 
@@ -19,7 +18,7 @@ export const SIGNAL_KEY_CACHE_MAX = 512
19
18
  * @returns {string} rendezvous 键
20
19
  */
21
20
  export function nodeRendezvousKey(nodeHash) {
22
- return sha256Hex(`${NODE_RENDEZVOUS_DOMAIN}${normalizeHex64(nodeHash)}`)
21
+ return sha256Hex(`${NODE_RENDEZVOUS_DOMAIN}${nodeHash}`)
23
22
  }
24
23
 
25
24
  /**
package/discovery/lan.mjs CHANGED
@@ -2,7 +2,7 @@ import { Buffer } from 'node:buffer'
2
2
  import dgram from 'node:dgram'
3
3
 
4
4
  import { base64ToBytes, bytesToBase64 } from '../core/bytes_codec.mjs'
5
- import { isHex64, normalizeHex64 } from '../core/hexIds.mjs'
5
+ import { isHex64 } from '../core/hexIds.mjs'
6
6
  import { nodeDebug, shortHash } from '../node/log.mjs'
7
7
 
8
8
  import { noteAdvertPeerHints } from './advert_peer_hints.mjs'
@@ -25,8 +25,8 @@ const visibleByHash = new Map()
25
25
  * @returns {void}
26
26
  */
27
27
  export function noteLanVisibleNode(nodeHash, now = Date.now()) {
28
- const hash = normalizeHex64(nodeHash)
29
- if (!isHex64(hash)) return
28
+ const hash = isHex64(nodeHash)
29
+ if (!hash) return
30
30
  visibleByHash.set(hash, now)
31
31
  }
32
32
 
@@ -61,7 +61,7 @@ export async function acceptLanPresenceAdvert(advertBytes, meta = {}) {
61
61
  if (!advertBytes?.byteLength) return null
62
62
  const ingested = await ingestNetworkAdvert(advertBytes)
63
63
  if (!ingested) return null
64
- const skipHash = meta.skipNodeHash ? normalizeHex64(meta.skipNodeHash) : null
64
+ const skipHash = isHex64(meta.skipNodeHash)
65
65
  if (skipHash && ingested.verifiedNodeHash === skipHash) return ingested
66
66
  const firstSeen = !visibleByHash.has(ingested.verifiedNodeHash)
67
67
  noteLanVisibleNode(ingested.verifiedNodeHash)
@@ -93,9 +93,8 @@ export function createLanDiscoveryProvider(options = {}) {
93
93
  let refs = 0
94
94
  /** @type {ReturnType<typeof setInterval> | null} */
95
95
  let beaconTimer = null
96
- const seededSelf = normalizeHex64(options.localNodeHash)
97
96
  /** @type {string | null} */
98
- let selfNodeHash = isHex64(seededSelf)
97
+ let selfNodeHash = isHex64(options.localNodeHash)
99
98
  /** @type {Set<string>} */
100
99
  const joinedAddresses = new Set()
101
100
 
@@ -249,8 +248,7 @@ export function createLanDiscoveryProvider(options = {}) {
249
248
  * @returns {Promise<boolean>} 存在 peer hint 时为 true
250
249
  */
251
250
  async connectToNode(nodeHash) {
252
- const hash = normalizeHex64(nodeHash)
253
- return isHex64(hash) && !!getLanPeerHint(hash)
251
+ return !!getLanPeerHint(nodeHash)
254
252
  },
255
253
  /**
256
254
  * @param {() => Promise<{ nodeHash?: string, tcpPort?: number, advertBytes?: Uint8Array, advertBody?: object } | null>} getBeacon 本机 beacon
@@ -264,7 +262,7 @@ export function createLanDiscoveryProvider(options = {}) {
264
262
  const send = async () => {
265
263
  const body = await getBeacon?.()
266
264
  if (!body) return
267
- if (body.nodeHash) selfNodeHash = normalizeHex64(body.nodeHash)
265
+ if (body.nodeHash) selfNodeHash = body.nodeHash
268
266
  const advertBytes = body.advertBytes?.byteLength
269
267
  ? body.advertBytes
270
268
  : null
@@ -1,4 +1,3 @@
1
- import { normalizeHex64 } from '../core/hexIds.mjs'
2
1
  import { normalizeTcpPort } from '../core/tcp_port.mjs'
3
2
  import { createTtlMap } from '../utils/ttl_map.mjs'
4
3
 
@@ -19,14 +18,13 @@ const hints = createTtlMap(LAN_PEER_HINT_TTL_MS)
19
18
  * @returns {void}
20
19
  */
21
20
  export function noteLanPeerHint(nodeHash, endpoint) {
22
- const hash = normalizeHex64(nodeHash)
23
21
  const host = String(endpoint?.host || '')
24
22
  const port = normalizeTcpPort(endpoint?.port)
25
- if (!hash || !host || !port) return
26
- const existing = hints.get(hash)?.endpoints ?? []
23
+ if (!nodeHash || !host || !port) return
24
+ const existing = hints.get(nodeHash)?.endpoints ?? []
27
25
  const next = existing.filter(item => !(item.host === host && item.port === port))
28
26
  next.unshift({ host, port })
29
- hints.set(hash, { endpoints: next.slice(0, MAX_ENDPOINTS) })
27
+ hints.set(nodeHash, { endpoints: next.slice(0, MAX_ENDPOINTS) })
30
28
  }
31
29
 
32
30
  /**
@@ -46,9 +44,8 @@ export function getLanPeerHint(nodeHash, now = Date.now()) {
46
44
  * @returns {{ host: string, port: number }[]} hint 列表
47
45
  */
48
46
  export function listLanPeerHints(nodeHash, now = Date.now()) {
49
- const hash = normalizeHex64(nodeHash)
50
- if (!hash) return []
51
- return hints.get(hash, now)?.endpoints ?? []
47
+ if (!nodeHash) return []
48
+ return hints.get(nodeHash, now)?.endpoints ?? []
52
49
  }
53
50
 
54
51
  /**
@@ -4,7 +4,7 @@ import { schnorr } from '@noble/curves/secp256k1.js'
4
4
  import WebSocket from 'ws'
5
5
 
6
6
  import { base64ToBytes, hexToBytes, bytesToBase64, bytesToHex } from '../core/bytes_codec.mjs'
7
- import { isHex64, normalizeHex64 } from '../core/hexIds.mjs'
7
+ import { isHex64 } from '../core/hexIds.mjs'
8
8
  import { sha256Hex } from '../crypto/crypto.mjs'
9
9
  import { getNodeTransportSettings } from '../node/identity.mjs'
10
10
  import { getSignalingRuntimeConfig } from '../node/instance.mjs'
@@ -80,8 +80,8 @@ function listPoolHashes(pool, now, ttlMs) {
80
80
  * @returns {void}
81
81
  */
82
82
  export function noteNostrVisibleNode(nodeHash, now = Date.now()) {
83
- const hash = normalizeHex64(nodeHash)
84
- if (!isHex64(hash)) return
83
+ const hash = isHex64(nodeHash)
84
+ if (!hash) return
85
85
  visibleByHash.set(hash, now)
86
86
  }
87
87
 
@@ -93,8 +93,8 @@ export function noteNostrVisibleNode(nodeHash, now = Date.now()) {
93
93
  * @returns {void}
94
94
  */
95
95
  export function noteNostrGroupVisibleNode(roomSecret, nodeHash, now = Date.now()) {
96
- const hash = normalizeHex64(nodeHash)
97
- if (!roomSecret || !isHex64(hash)) return
96
+ const hash = isHex64(nodeHash)
97
+ if (!roomSecret || !hash) return
98
98
  let pool = visibleByGroup.get(roomSecret)
99
99
  if (!pool) {
100
100
  pool = new Map()
@@ -137,7 +137,7 @@ export async function acceptNostrAdvert(rendezvousKey, bytes, options = {}) {
137
137
  const ingested = await ingestEncryptedAdvert(rendezvousKey, bytes)
138
138
  if (!ingested) return null
139
139
  const hash = ingested.verifiedNodeHash
140
- const skipHash = options.skipNodeHash ? normalizeHex64(options.skipNodeHash) : null
140
+ const skipHash = isHex64(options.skipNodeHash)
141
141
  if (skipHash && hash === skipHash) return hash
142
142
  const { roomSecret } = options
143
143
  let firstSeen = true
@@ -761,9 +761,8 @@ export function createNostrDiscoveryProvider(options = {}) {
761
761
  return dedupeRelayUrls(options.relayUrls)
762
762
  }
763
763
  const secretKey = randomBytes(32)
764
- const seededSelf = normalizeHex64(options.localNodeHash)
765
764
  /** @type {string | null} */
766
- let selfNodeHash = isHex64(seededSelf)
765
+ let selfNodeHash = isHex64(options.localNodeHash)
767
766
  const NETWORK_SUB_KEY = 'network'
768
767
  /**
769
768
  * @typedef {{ stop: () => void, held: boolean, listeners: Set<(bytes: Uint8Array, meta: object) => void> }} AdvertSubEntry
@@ -778,8 +777,8 @@ export function createNostrDiscoveryProvider(options = {}) {
778
777
  * @returns {void}
779
778
  */
780
779
  function noteSelfNodeHash(nodeHash) {
781
- const hash = normalizeHex64(nodeHash)
782
- if (isHex64(hash)) selfNodeHash = hash
780
+ const hash = isHex64(nodeHash)
781
+ if (hash) selfNodeHash = hash
783
782
  }
784
783
 
785
784
  /**
@@ -873,8 +872,8 @@ export function createNostrDiscoveryProvider(options = {}) {
873
872
  * @returns {() => void} 取消 listener
874
873
  */
875
874
  function ensureNodeAdvertSubscription(nodeHash, listener) {
876
- const hash = normalizeHex64(nodeHash)
877
- if (!isHex64(hash)) return () => { }
875
+ const hash = isHex64(nodeHash)
876
+ if (!hash) return () => { }
878
877
  return ensureAdvertSubscription('node:' + hash, {
879
878
  rendezvousKey: nodeRendezvousKey(hash),
880
879
  }, listener)
@@ -907,8 +906,8 @@ export function createNostrDiscoveryProvider(options = {}) {
907
906
  * @returns {Promise<boolean>} 是否已准备
908
907
  */
909
908
  async connectToNode(nodeHash) {
910
- const hash = normalizeHex64(nodeHash)
911
- if (!isHex64(hash)) return false
909
+ const hash = isHex64(nodeHash)
910
+ if (!hash) return false
912
911
  ensureNodeAdvertSubscription(hash)
913
912
  return true
914
913
  },
@@ -959,8 +958,8 @@ export function createNostrDiscoveryProvider(options = {}) {
959
958
  * @returns {Promise<void>}
960
959
  */
961
960
  async sendNodeSignal(toNodeHash, bytes) {
962
- const hash = normalizeHex64(toNodeHash)
963
- if (!isHex64(hash)) throw new Error('nostr: invalid nodeHash')
961
+ const hash = isHex64(toNodeHash)
962
+ if (!hash) throw new Error('nostr: invalid nodeHash')
964
963
  const rendezvousKey = nodeRendezvousKey(hash)
965
964
  const event = await signNostrEvent(
966
965
  NOSTR_SIGNAL_KIND,
@@ -976,8 +975,8 @@ export function createNostrDiscoveryProvider(options = {}) {
976
975
  * @returns {Promise<() => void>} 取消函数
977
976
  */
978
977
  async listenNodeSignals(localNodeHash, onSignal) {
979
- const hash = normalizeHex64(localNodeHash)
980
- if (!isHex64(hash)) throw new Error('nostr: invalid nodeHash')
978
+ const hash = isHex64(localNodeHash)
979
+ if (!hash) throw new Error('nostr: invalid nodeHash')
981
980
  noteSelfNodeHash(hash)
982
981
  const rendezvousKey = nodeRendezvousKey(hash)
983
982
  const existing = nodeSignalSubs.get(hash)
@@ -1001,8 +1000,8 @@ export function createNostrDiscoveryProvider(options = {}) {
1001
1000
  * @returns {Promise<() => void>} 取消函数
1002
1001
  */
1003
1002
  async watchNodeAdvert(nodeHash, onAdvert) {
1004
- const hash = normalizeHex64(nodeHash)
1005
- if (!isHex64(hash)) throw new Error('nostr: invalid nodeHash')
1003
+ const hash = isHex64(nodeHash)
1004
+ if (!hash) throw new Error('nostr: invalid nodeHash')
1006
1005
  return ensureNodeAdvertSubscription(hash, onAdvert)
1007
1006
  },
1008
1007
  /**
@@ -1,5 +1,3 @@
1
- import { normalizeHex64 } from '../core/hexIds.mjs'
2
-
3
1
  /** @type {((nodeHash: string) => void) | null} */
4
2
  let peerClueListener = null
5
3
 
@@ -18,6 +16,5 @@ export function setDiscoveryPeerClueListener(listener) {
18
16
  * @returns {void}
19
17
  */
20
18
  export function noteDiscoveryPeerClue(nodeHash) {
21
- const hash = normalizeHex64(nodeHash)
22
- if (hash) peerClueListener?.(hash)
19
+ if (nodeHash) peerClueListener?.(nodeHash)
23
20
  }