@steve02081504/fount-p2p 0.0.35 → 0.0.37

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 (67) hide show
  1. package/AGENTS.md +2 -2
  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 +17 -10
  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/census.mjs +281 -0
  15. package/discovery/nostr/census_math.mjs +54 -0
  16. package/discovery/nostr/constants.mjs +43 -0
  17. package/discovery/nostr/index.mjs +612 -0
  18. package/discovery/nostr/relays.mjs +1071 -0
  19. package/discovery/nostr/selection.mjs +224 -0
  20. package/discovery/nostr/session.mjs +584 -0
  21. package/discovery/peer_clue.mjs +1 -4
  22. package/docs/nostr_relay_discovery.md +92 -0
  23. package/federation/entity_key_chain.mjs +10 -9
  24. package/federation/message_rate_limit.mjs +1 -3
  25. package/files/manifest/fetch.mjs +4 -6
  26. package/files/manifest/normalize.mjs +2 -2
  27. package/files/manifest/public.mjs +5 -5
  28. package/governance/branch.mjs +1 -2
  29. package/governance/join_pow.mjs +2 -4
  30. package/governance/owner_succession_ballot.mjs +5 -5
  31. package/index.mjs +11 -4
  32. package/infra/priority.mjs +1 -2
  33. package/link/handshake.mjs +153 -41
  34. package/link/pipe.mjs +1 -2
  35. package/link/providers/ble_gatt.mjs +2 -3
  36. package/link/providers/lan_tcp.mjs +2 -3
  37. package/link/providers/link_id_pipe.mjs +3 -3
  38. package/link/providers/{nostr.mjs → nostr/index.mjs} +22 -17
  39. package/mailbox/deliver_or_store.mjs +11 -12
  40. package/mailbox/prune.mjs +3 -4
  41. package/mailbox/rate.mjs +3 -2
  42. package/mailbox/store.mjs +6 -9
  43. package/node/denylist.mjs +25 -28
  44. package/node/feature_config.mjs +30 -0
  45. package/node/identity.mjs +6 -6
  46. package/node/instance.mjs +21 -0
  47. package/node/network.mjs +12 -12
  48. package/node/personal_block.mjs +4 -4
  49. package/node/reputation_store.mjs +13 -14
  50. package/node/reputation_sync.mjs +9 -10
  51. package/node/storage.mjs +23 -0
  52. package/package.json +10 -4
  53. package/pages/app.mjs +172 -0
  54. package/pages/index.html +36 -0
  55. package/reputation/engine.mjs +13 -16
  56. package/schemas/discovery.mjs +5 -6
  57. package/schemas/federation_pull.mjs +7 -7
  58. package/schemas/mailbox.mjs +3 -3
  59. package/schemas/part_query.mjs +5 -5
  60. package/timeline/verify_remote.mjs +7 -7
  61. package/transport/link_registry.mjs +16 -19
  62. package/transport/mesh_keepalive.mjs +8 -11
  63. package/transport/offer_answer.mjs +3 -3
  64. package/transport/peer_health.mjs +12 -15
  65. package/transport/runtime_bootstrap.mjs +39 -3
  66. package/trust_graph/send.mjs +3 -3
  67. package/discovery/nostr.mjs +0 -1074
package/AGENTS.md CHANGED
@@ -14,7 +14,7 @@
14
14
 
15
15
  **Facade:** `index.mjs`; subpath exports mirror directories.
16
16
 
17
- Detail docs: [transports](docs/transports.md) · [mesh](docs/mesh.md) · [signaling](docs/signaling.md) · [runtime](docs/runtime.md) · [infra](docs/infra.md) · [wire](docs/wire.md) · [evfs](docs/evfs.md) · [reputation](docs/reputation.md)
17
+ Detail docs: [transports](docs/transports.md) · [mesh](docs/mesh.md) · [signaling](docs/signaling.md) · [nostr-relay-discovery](docs/nostr_relay_discovery.md) · [runtime](docs/runtime.md) · [infra](docs/infra.md) · [wire](docs/wire.md) · [evfs](docs/evfs.md) · [reputation](docs/reputation.md)
18
18
 
19
19
  ### Runtime: isomorphic vs Node
20
20
 
@@ -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 {
@@ -30,9 +29,10 @@ export function rendezvousKeyForScope(scope, selfNodeHash) {
30
29
  * @param {AdvertScope} scope advert 域
31
30
  * @param {{ nodeHash: string, nodePubKey: string, secretKey: Uint8Array }} localIdentity 本地身份
32
31
  * @param {number | null | undefined} [tcpPort] LAN TCP 端口
32
+ * @param {{ pool?: Array<{ url: string, rtt?: number }>, listen?: string[] }} [relayData] 已规范化并经 sanitize 裁剪的 relay 字段
33
33
  * @returns {Promise<object>} 签名 advert body
34
34
  */
35
- export async function buildSignedAdvertForScope(scope, localIdentity, tcpPort) {
35
+ export async function buildSignedAdvertForScope(scope, localIdentity, tcpPort, relayData) {
36
36
  const key = rendezvousKeyForScope(scope, localIdentity.nodeHash)
37
37
  const lanHosts = scope === 'network' && tcpPort != null
38
38
  ? listMulticastIpv4Addresses()
@@ -41,6 +41,8 @@ export async function buildSignedAdvertForScope(scope, localIdentity, tcpPort) {
41
41
  ...localIdentity,
42
42
  ...tcpPort != null ? { tcpPort } : {},
43
43
  ...lanHosts.length ? { lanHosts } : {},
44
+ ...relayData?.pool ? { nostrRelayPool: relayData.pool } : {},
45
+ ...relayData?.listen ? { listenNostrRelays: relayData.listen } : {},
44
46
  })
45
47
  }
46
48
 
@@ -69,20 +71,25 @@ export function encryptAdvertForScope(scope, localIdentity, advertBody) {
69
71
  * Untrusted ingress:解密并验签 advert;失败返回 null,不抛。不写入可见池 / peer hints。
70
72
  * @param {string} rendezvousKey rendezvous 键
71
73
  * @param {Uint8Array} bytes 加密 advert
72
- * @returns {Promise<{ verifiedNodeHash: string, body: object } | null>} 验签成功返回 nodeHashadvert body,否则 null
74
+ * @returns {Promise<{ verifiedNodeHash: string, body: object, relayPool: Array<{ url: string, rtt: number }>, listenRelays: string[] } | null>} 验签成功返回 nodeHashadvert body 与规范化 relay 字段,否则 null
73
75
  */
74
76
  export async function ingestEncryptedAdvert(rendezvousKey, bytes) {
75
77
  const packet = decryptSignalPacket(rendezvousKey, bytes)
76
78
  if (packet?.type !== 'advert' || !packet.body) return null
77
- const verifiedNodeHash = await verifySignedAdvert(rendezvousKey, packet.body)
78
- if (!verifiedNodeHash) return null
79
- return { verifiedNodeHash, body: packet.body }
79
+ const verified = await verifySignedAdvert(rendezvousKey, packet.body)
80
+ if (!verified) return null
81
+ return {
82
+ verifiedNodeHash: verified.nodeHash,
83
+ body: packet.body,
84
+ relayPool: verified.relayPool,
85
+ listenRelays: verified.listenRelays,
86
+ }
80
87
  }
81
88
 
82
89
  /**
83
90
  * Untrusted ingress:验签 network-scope advert;失败返回 null。不写盘 / 不写 hints。
84
91
  * @param {Uint8Array} bytes 加密 advert
85
- * @returns {Promise<{ verifiedNodeHash: string, body: object } | null>} 验签成功返回 nodeHashadvert body,否则 null
92
+ * @returns {Promise<{ verifiedNodeHash: string, body: object, relayPool: Array<{ url: string, rtt: number }>, listenRelays: string[] } | null>} 验签成功返回 nodeHashadvert body 与规范化 relay 字段,否则 null
86
93
  */
87
94
  export async function ingestNetworkAdvert(bytes) {
88
95
  return ingestEncryptedAdvert(networkRendezvousKey(), bytes)
@@ -92,17 +99,17 @@ export async function ingestNetworkAdvert(bytes) {
92
99
  * Untrusted ingress:验签 node-scope advert;失败返回 null。不写盘 / 不写 hints。
93
100
  * @param {string} nodeHash 目标 nodeHash
94
101
  * @param {Uint8Array} bytes 加密 advert
95
- * @returns {Promise<{ verifiedNodeHash: string, body: object } | null>} 验签成功返回 nodeHashadvert body,否则 null
102
+ * @returns {Promise<{ verifiedNodeHash: string, body: object, relayPool: Array<{ url: string, rtt: number }>, listenRelays: string[] } | null>} 验签成功返回 nodeHashadvert body 与规范化 relay 字段,否则 null
96
103
  */
97
104
  export async function ingestNodeAdvert(nodeHash, bytes) {
98
- return ingestEncryptedAdvert(nodeRendezvousKey(normalizeHex64(nodeHash)), bytes)
105
+ return ingestEncryptedAdvert(nodeRendezvousKey(nodeHash), bytes)
99
106
  }
100
107
 
101
108
  /**
102
109
  * Untrusted ingress:验签 group-scope advert;失败返回 null。不写盘 / 不写 hints。
103
110
  * @param {string} roomSecret 房间密钥
104
111
  * @param {Uint8Array} bytes 加密 advert
105
- * @returns {Promise<{ verifiedNodeHash: string, body: object } | null>} 验签成功返回 nodeHashadvert body,否则 null
112
+ * @returns {Promise<{ verifiedNodeHash: string, body: object, relayPool: Array<{ url: string, rtt: number }>, listenRelays: string[] } | null>} 验签成功返回 nodeHashadvert body 与规范化 relay 字段,否则 null
106
113
  */
107
114
  export async function ingestGroupAdvert(roomSecret, bytes) {
108
115
  return ingestEncryptedAdvert(groupRendezvousKey(roomSecret), bytes)
@@ -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
  /**