@steve02081504/fount-p2p 0.0.26 → 0.0.28

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 (100) hide show
  1. package/README.md +3 -2
  2. package/core/bytes_codec.mjs +5 -6
  3. package/core/composite_key.mjs +0 -79
  4. package/core/entity_id_parse.mjs +2 -3
  5. package/core/evfs_logical_path.mjs +2 -3
  6. package/core/hexIds.mjs +5 -5
  7. package/core/logical_entity.mjs +3 -4
  8. package/crypto/channel.mjs +2 -25
  9. package/crypto/checkpoint_sign.mjs +5 -4
  10. package/crypto/crypto.mjs +0 -8
  11. package/crypto/key.mjs +9 -10
  12. package/dag/canonicalize_row.mjs +1 -1
  13. package/discovery/advert_peer_hints.mjs +1 -1
  14. package/discovery/adverts.mjs +7 -12
  15. package/discovery/bt/index.mjs +6 -11
  16. package/discovery/bt/peer_hints.mjs +2 -3
  17. package/discovery/bt/runtime.mjs +1 -1
  18. package/discovery/index.mjs +14 -14
  19. package/discovery/lan.mjs +3 -3
  20. package/discovery/lan_interfaces.mjs +2 -2
  21. package/discovery/lan_peer_hints.mjs +1 -1
  22. package/discovery/nostr.mjs +19 -24
  23. package/federation/entity_key_chain.mjs +5 -24
  24. package/federation/message_rate_limit.mjs +3 -2
  25. package/federation/part_query/runtime.mjs +9 -20
  26. package/files/assemble.mjs +3 -3
  27. package/files/assemble_stream.mjs +1 -1
  28. package/files/chunk/fetch.mjs +1 -2
  29. package/files/chunk/responder.mjs +4 -41
  30. package/files/chunk/store.mjs +2 -3
  31. package/files/chunk/verify.mjs +2 -3
  32. package/files/evfs.mjs +1 -1
  33. package/files/evfs_ref.mjs +3 -4
  34. package/files/fetch_fanout.mjs +3 -3
  35. package/files/manifest/fetch.mjs +2 -3
  36. package/files/manifest/normalize.mjs +9 -9
  37. package/files/manifest/pending.mjs +1 -4
  38. package/files/manifest/public.mjs +8 -8
  39. package/files/transfer_key.mjs +3 -3
  40. package/files/transfer_key_registry.mjs +6 -6
  41. package/governance/branch.mjs +2 -1
  42. package/governance/join_pow.mjs +7 -5
  43. package/governance/owner_succession_ballot.mjs +6 -6
  44. package/index.mjs +8 -2
  45. package/infra/default_node_dir.mjs +2 -2
  46. package/infra/priority.mjs +2 -2
  47. package/link/frame.mjs +2 -3
  48. package/link/handshake.mjs +6 -6
  49. package/link/pipe.mjs +69 -5
  50. package/link/providers/ble_gatt.mjs +1 -9
  51. package/link/providers/index.mjs +2 -2
  52. package/link/providers/nostr.mjs +1 -3
  53. package/link/providers/webrtc.mjs +2 -2
  54. package/link/rtc/ice_local_hostname.mjs +6 -5
  55. package/link/rtc/w3c_bridge.mjs +1 -1
  56. package/link/sdp_fingerprint.mjs +1 -1
  57. package/mailbox/consumer_registry.mjs +2 -2
  58. package/mailbox/deliver_or_store.mjs +4 -4
  59. package/mailbox/parse.mjs +1 -1
  60. package/mailbox/prune.mjs +2 -2
  61. package/mailbox/rate.mjs +3 -10
  62. package/mailbox/store.mjs +7 -7
  63. package/node/denylist.mjs +38 -38
  64. package/node/entity_store.mjs +5 -5
  65. package/node/identity.mjs +7 -8
  66. package/node/instance.mjs +1 -1
  67. package/node/log.mjs +1 -1
  68. package/node/network.mjs +14 -16
  69. package/node/personal_block.mjs +22 -20
  70. package/node/reputation_store.mjs +11 -19
  71. package/node/reputation_sync.mjs +0 -7
  72. package/node/signaling_config.mjs +1 -1
  73. package/node/storage_plugins.mjs +2 -2
  74. package/overlay/index.mjs +1 -1
  75. package/package.json +1 -1
  76. package/registries/event_type.mjs +2 -2
  77. package/registries/room_provider.mjs +2 -2
  78. package/reputation/engine.mjs +17 -15
  79. package/schemas/discovery.mjs +5 -5
  80. package/schemas/federation_pull.mjs +16 -24
  81. package/schemas/part_query.mjs +4 -4
  82. package/timeline/verify_remote.mjs +4 -4
  83. package/transport/group_link_set.mjs +3 -3
  84. package/transport/ice_servers.mjs +2 -2
  85. package/transport/link_registry.mjs +51 -20
  86. package/transport/mesh_keepalive.mjs +1 -1
  87. package/transport/node_scope/wire.mjs +1 -1
  88. package/transport/peer_health.mjs +127 -0
  89. package/transport/remote_user_room.mjs +13 -15
  90. package/transport/rtc_connection_budget.mjs +6 -6
  91. package/transport/runtime_bootstrap.mjs +12 -12
  92. package/trust_graph/cache.mjs +2 -3
  93. package/trust_graph/engine.mjs +1 -1
  94. package/trust_graph/registry.mjs +2 -2
  95. package/trust_graph/send.mjs +1 -1
  96. package/utils/async_mutex.mjs +1 -1
  97. package/wire/ingress.mjs +1 -1
  98. package/wire/part/ingress.mjs +6 -6
  99. package/wire/part/query.mjs +3 -3
  100. package/wire/volatile_signature.mjs +4 -9
@@ -17,7 +17,7 @@ export function normalizeLanHosts(input) {
17
17
  /** @type {string[]} */
18
18
  const out = []
19
19
  for (const item of arr) {
20
- const host = String(item || '').trim()
20
+ const host = String(item || '')
21
21
  if (!host || !IPV4_RE.test(host) || seen.has(host)) continue
22
22
  seen.add(host)
23
23
  out.push(host)
@@ -68,7 +68,7 @@ export function listMulticastIpv4Addresses() {
68
68
  if (!ifaces) continue
69
69
  for (const iface of ifaces) {
70
70
  if (iface.internal || !isIpv4Family(iface.family)) continue
71
- const addr = String(iface.address || '').trim()
71
+ const addr = String(iface.address || '')
72
72
  if (!addr || seen.has(addr)) continue
73
73
  seen.add(addr)
74
74
  addrs.push(addr)
@@ -20,7 +20,7 @@ const hints = createTtlMap(LAN_PEER_HINT_TTL_MS)
20
20
  */
21
21
  export function noteLanPeerHint(nodeHash, endpoint) {
22
22
  const hash = normalizeHex64(nodeHash)
23
- const host = String(endpoint?.host || '').trim()
23
+ const host = String(endpoint?.host || '')
24
24
  const port = normalizeTcpPort(endpoint?.port)
25
25
  if (!hash || !host || !port) return
26
26
  const existing = hints.get(hash)?.endpoints ?? []
@@ -82,13 +82,12 @@ export function noteNostrVisibleNode(nodeHash, now = Date.now()) {
82
82
  * @returns {void}
83
83
  */
84
84
  export function noteNostrGroupVisibleNode(roomSecret, nodeHash, now = Date.now()) {
85
- const key = String(roomSecret || '')
86
85
  const hash = normalizeHex64(nodeHash)
87
- if (!key || !isHex64(hash)) return
88
- let pool = visibleByGroup.get(key)
86
+ if (!roomSecret || !isHex64(hash)) return
87
+ let pool = visibleByGroup.get(roomSecret)
89
88
  if (!pool) {
90
89
  pool = new Map()
91
- visibleByGroup.set(key, pool)
90
+ visibleByGroup.set(roomSecret, pool)
92
91
  }
93
92
  pool.set(hash, now)
94
93
  }
@@ -109,11 +108,10 @@ export function listNostrVisibleNodeHashes(now = Date.now(), ttlMs = ADVERT_TTL_
109
108
  * @returns {string[]} 该群可见 nodeHash
110
109
  */
111
110
  export function listNostrGroupVisibleNodeHashes(roomSecret, now = Date.now(), ttlMs = ADVERT_TTL_MS) {
112
- const key = String(roomSecret || '')
113
- const pool = visibleByGroup.get(key)
111
+ const pool = visibleByGroup.get(roomSecret)
114
112
  if (!pool) return []
115
113
  const out = listPoolHashes(pool, now, ttlMs)
116
- if (!pool.size) visibleByGroup.delete(key)
114
+ if (!pool.size) visibleByGroup.delete(roomSecret)
117
115
  return out
118
116
  }
119
117
 
@@ -130,12 +128,11 @@ export async function acceptNostrAdvert(rendezvousKey, bytes, options = {}) {
130
128
  const hash = ingested.verifiedNodeHash
131
129
  const skipHash = options.skipNodeHash ? normalizeHex64(options.skipNodeHash) : null
132
130
  if (skipHash && hash === skipHash) return hash
133
- const roomSecret = options.roomSecret
131
+ const { roomSecret } = options
134
132
  let firstSeen = true
135
133
  if (roomSecret) {
136
- const key = String(roomSecret || '')
137
- firstSeen = !visibleByGroup.get(key)?.has(hash)
138
- noteNostrGroupVisibleNode(key, hash)
134
+ firstSeen = !visibleByGroup.get(roomSecret)?.has(hash)
135
+ noteNostrGroupVisibleNode(roomSecret, hash)
139
136
  }
140
137
  else {
141
138
  firstSeen = !visibleByHash.has(hash)
@@ -188,8 +185,8 @@ function dropWebSocket(ws) {
188
185
  function dedupeRelayUrls(urls) {
189
186
  const seen = new Set()
190
187
  return (urls || [])
191
- .map(url => url.trim())
192
- .filter(trimmed => trimmed && !seen.has(trimmed) && (seen.add(trimmed), true))
188
+ .map(url => String(url || ''))
189
+ .filter(url => url && !seen.has(url) && (seen.add(url), true))
193
190
  }
194
191
 
195
192
  /**
@@ -502,7 +499,7 @@ function releaseSharedRelaySub(relayUrl, subscriptionId) {
502
499
  const session = sharedRelaySessions.get(relayUrl)
503
500
  if (!session) return
504
501
  session.subs.delete(subscriptionId)
505
- const ws = session.ws
502
+ const { ws } = session
506
503
  if (ws?.readyState === WebSocket.OPEN)
507
504
  try { ws.send(JSON.stringify(['CLOSE', subscriptionId])) } catch { /* ignore */ }
508
505
  if (session.subs.size) return
@@ -521,7 +518,7 @@ function releaseSharedRelaySub(relayUrl, subscriptionId) {
521
518
  */
522
519
  function registerSharedRelaySub(session, subscriptionId, filter, onEvent) {
523
520
  session.subs.set(subscriptionId, { filter, onEvent })
524
- const ws = session.ws
521
+ const { ws } = session
525
522
  if (ws?.readyState !== WebSocket.OPEN) return
526
523
  try { ws.send(JSON.stringify(['REQ', subscriptionId, filter])) } catch { /* ignore */ }
527
524
  }
@@ -568,7 +565,7 @@ export function createNostrDiscoveryProvider(options = {}) {
568
565
  * @returns {string[]} 去重后的中继 URL 列表
569
566
  */
570
567
  const resolveRelayUrls = () => {
571
- if (typeof options.getRelayUrls === 'function') {
568
+ if (options.getRelayUrls) {
572
569
  const urls = options.getRelayUrls()
573
570
  return dedupeRelayUrls(urls == null ? DEFAULT_RELAY_URLS : urls)
574
571
  }
@@ -676,11 +673,10 @@ export function createNostrDiscoveryProvider(options = {}) {
676
673
  * @returns {() => void} 取消 listener
677
674
  */
678
675
  function ensureGroupSubscription(roomSecret, listener) {
679
- const key = String(roomSecret || '')
680
- if (!key) return () => { }
681
- return ensureAdvertSubscription('group:' + key, {
682
- rendezvousKey: groupRendezvousKey(key),
683
- roomSecret: key,
676
+ if (!roomSecret) return () => { }
677
+ return ensureAdvertSubscription('group:' + roomSecret, {
678
+ rendezvousKey: groupRendezvousKey(roomSecret),
679
+ roomSecret,
684
680
  }, listener)
685
681
  }
686
682
 
@@ -828,10 +824,9 @@ export function createNostrDiscoveryProvider(options = {}) {
828
824
  * @returns {Promise<() => void>} 停止群 presence 广播
829
825
  */
830
826
  async startGroupPresence(roomSecret, getBeacon) {
831
- const key = String(roomSecret || '')
832
- const rendezvousKey = groupRendezvousKey(key)
827
+ const rendezvousKey = groupRendezvousKey(roomSecret)
833
828
  const abortController = new AbortController()
834
- ensureGroupSubscription(key)
829
+ ensureGroupSubscription(roomSecret)
835
830
  /**
836
831
  * @returns {Promise<void>}
837
832
  */
@@ -23,28 +23,11 @@ export const ENTITY_KEY_REVOKE_DOMAIN = 'fount-entity-key-revoke'
23
23
  */
24
24
 
25
25
  /**
26
- * @param {unknown} recoveryPubKeyHex 64 位十六进制 recovery 公钥
27
- * @returns {string} 稳定 subjectHash(entityHash 后半)
28
- */
29
- export function recoverySubjectHashFromPubKeyHex(recoveryPubKeyHex) {
30
- return hashFromPubKeyHex(recoveryPubKeyHex)
31
- }
32
-
33
- /**
34
- * @param {unknown} activePubKeyHex 64 位十六进制 活跃公钥
35
- * @returns {string} 时间线 sender(pubKeyHash)
36
- */
37
- export function activeSenderHashFromPubKeyHex(activePubKeyHex) {
38
- return hashFromPubKeyHex(activePubKeyHex)
39
- }
40
-
41
- /**
42
- * @param {string} recoveryPubKeyHex recovery 公钥
43
26
  * @param {string} activePubKeyHex 初始活跃公钥
44
27
  * @param {number} [validFrom] 生效时间
45
28
  * @returns {EntityKeyHistoryEntry[]} 创世链
46
29
  */
47
- export function createGenesisKeyHistory(recoveryPubKeyHex, activePubKeyHex, validFrom = Date.now()) {
30
+ export function createGenesisKeyHistory(activePubKeyHex, validFrom = Date.now()) {
48
31
  return [{
49
32
  generation: 0,
50
33
  activePubKeyHex: normalizeHex64(activePubKeyHex),
@@ -76,17 +59,15 @@ export function isActiveGenerationRevoked(keyHistory, generation) {
76
59
 
77
60
  /**
78
61
  * @param {EntityKeyHistoryEntry[]} keyHistory 密钥历史
79
- * @param {string} recoveryPubKeyHex recovery 公钥
80
62
  * @param {string} senderPubKeyHash 事件 sender(64 hex pubKeyHash)
81
63
  * @returns {boolean} sender 是否为未吊销的活跃钥
82
64
  */
83
- export function isValidActiveSender(keyHistory, recoveryPubKeyHex, senderPubKeyHash) {
65
+ export function isValidActiveSender(keyHistory, senderPubKeyHash) {
84
66
  const sender = normalizeHex64(senderPubKeyHash)
85
67
  if (!isHex64(sender)) return false
86
- void recoveryPubKeyHex
87
68
  for (const entry of keyHistory || []) {
88
69
  if (isActiveGenerationRevoked(keyHistory, entry.generation)) continue
89
- if (activeSenderHashFromPubKeyHex(entry.activePubKeyHex) === sender)
70
+ if (hashFromPubKeyHex(entry.activePubKeyHex) === sender)
90
71
  return true
91
72
  }
92
73
  return false
@@ -98,7 +79,7 @@ export function isValidActiveSender(keyHistory, recoveryPubKeyHex, senderPubKeyH
98
79
  * @returns {boolean} 是否为 recovery 钥签名
99
80
  */
100
81
  export function isRecoverySender(recoveryPubKeyHex, senderPubKeyHash) {
101
- return recoverySubjectHashFromPubKeyHex(recoveryPubKeyHex) === normalizeHex64(senderPubKeyHash)
82
+ return hashFromPubKeyHex(recoveryPubKeyHex) === normalizeHex64(senderPubKeyHash)
102
83
  }
103
84
 
104
85
  /**
@@ -183,7 +164,7 @@ export function entityKeyRevokeSignBytes(revokeBody) {
183
164
  revokeGenerations: (revokeBody.revokeGenerations || []).map(Number),
184
165
  newGeneration: Number(revokeBody.newGeneration),
185
166
  activePubKeyHex: normalizeHex64(revokeBody.activePubKeyHex || ''),
186
- entityHash: String(revokeBody.entityHash || '').trim().toLowerCase(),
167
+ entityHash: String(revokeBody.entityHash || ''),
187
168
  }
188
169
  return Buffer.from(`${ENTITY_KEY_REVOKE_DOMAIN}\0${canonicalStringify(body)}`, 'utf8')
189
170
  }
@@ -1,15 +1,16 @@
1
1
  /**
2
2
  * 群级消息限速纯函数(无 I/O,供单测与 governance 复用)。
3
3
  */
4
+ import { normalizeHex64 } from '../core/hexIds.mjs'
4
5
 
5
6
  /**
6
7
  * @param {object} event DAG 事件
7
8
  * @returns {string} 限速实体键
8
9
  */
9
10
  export function messageRateEntityKey(event) {
10
- const charId = String(event?.charId || '').trim()
11
+ const charId = String(event?.charId || '')
11
12
  if (charId) return `char:${charId}`
12
- return String(event?.sender || '').trim().toLowerCase()
13
+ return normalizeHex64(event?.sender)
13
14
  }
14
15
 
15
16
  /**
@@ -102,15 +102,6 @@ export function resolvePartQueryState(dependencies = {}) {
102
102
  return dependencies.state || defaultState
103
103
  }
104
104
 
105
- /**
106
- * @param {string} partpath part 路径
107
- * @param {string} kind 查询标签
108
- * @returns {string} handler 键
109
- */
110
- function handlerKey(partpath, kind) {
111
- return compositeKey(partpath, kind)
112
- }
113
-
114
105
  /**
115
106
  * Shell 注册 kind 语义处理器(怎么匹配、返回什么)。
116
107
  * @param {string} partpath part 路径
@@ -120,7 +111,7 @@ function handlerKey(partpath, kind) {
120
111
  * @returns {void}
121
112
  */
122
113
  export function registerQueryInboundHandler(partpath, kind, handler, state = defaultState) {
123
- state.handlers.set(handlerKey(partpath, kind), handler)
114
+ state.handlers.set(compositeKey(partpath, kind), handler)
124
115
  }
125
116
 
126
117
  /**
@@ -144,12 +135,10 @@ export function resolvePartQueryHopTimeoutMs(ttl, tunables = partQueryTunables)
144
135
  export function mergeQueryRows(lists, maxHits, rowKey) {
145
136
  const out = []
146
137
  const seen = new Set()
147
- const keyOf = typeof rowKey === 'function'
148
- ? rowKey
149
- : row => {
150
- try { return JSON.stringify(row) }
151
- catch { return `\0${out.length}` }
152
- }
138
+ const keyOf = rowKey || (row => {
139
+ try { return JSON.stringify(row) }
140
+ catch { return `\0${out.length}` }
141
+ })
153
142
  for (const list of lists) {
154
143
  if (!Array.isArray(list)) continue
155
144
  for (const row of list) {
@@ -172,7 +161,7 @@ export function mergeQueryRows(lists, maxHits, rowKey) {
172
161
  * @returns {Promise<unknown[]>} 本地 rows
173
162
  */
174
163
  async function runLocalHandler(state, queryContext, partpath, kind, query) {
175
- const handler = state.handlers.get(handlerKey(partpath, kind))
164
+ const handler = state.handlers.get(compositeKey(partpath, kind))
176
165
  if (!handler) return []
177
166
  return await handler(queryContext, query) || []
178
167
  }
@@ -260,7 +249,7 @@ export async function processIncomingPartQueryRequest(wireContext, wire, request
260
249
  const state = resolvePartQueryState(dependencies)
261
250
  const nodeHashOf = dependencies.getNodeHash || getNodeHash
262
251
  const now = dependencies.now || Date.now
263
- const username = String(wireContext.replicaUsername || '')
252
+ const username = wireContext.replicaUsername || ''
264
253
 
265
254
  const cached = state.cache.get(request.partpath, request.kind, request.query, now())
266
255
  if (cached) {
@@ -284,7 +273,7 @@ export async function processIncomingPartQueryRequest(wireContext, wire, request
284
273
  }
285
274
 
286
275
  const selfHash = nodeHashOf()
287
- const exclude = new Set([selfHash, request.originNodeHash, String(peerId || '').trim().toLowerCase()].filter(Boolean))
276
+ const exclude = new Set([selfHash, request.originNodeHash, peerId].filter(Boolean))
288
277
  const forwardPayload = { ...request, ttl: nextTtl }
289
278
 
290
279
  /** @type {RelayPending} */
@@ -332,7 +321,7 @@ export async function processIncomingPartQueryRequest(wireContext, wire, request
332
321
  */
333
322
  export function handleIncomingPartQueryResponse(response, peerId = '', dependencies = {}) {
334
323
  const state = resolvePartQueryState(dependencies)
335
- const responderKey = String(peerId || response.fromNodeHash || '').trim().toLowerCase()
324
+ const responderKey = peerId || response.fromNodeHash
336
325
  const relay = state.relayPending.get(response.requestId)
337
326
  if (relay) {
338
327
  if (responderKey) {
@@ -171,7 +171,7 @@ export function buildFileManifest(parameters) {
171
171
  } = parameters
172
172
  const enc = encryptPlaintextToParts(plaintext, ceMode)
173
173
  const manifest = normalizeFileManifest({
174
- ownerEntityHash: ownerEntityHash.toLowerCase(),
174
+ ownerEntityHash,
175
175
  logicalPath: logicalPath.replace(/^\/+/, ''),
176
176
  name: name || logicalPath.split('/').pop() || 'file',
177
177
  mimeType,
@@ -204,7 +204,7 @@ export function buildFileManifestFromEnc(parameters, enc) {
204
204
  meta,
205
205
  } = parameters
206
206
  const manifest = normalizeFileManifest({
207
- ownerEntityHash: ownerEntityHash.toLowerCase(),
207
+ ownerEntityHash,
208
208
  logicalPath: logicalPath.replace(/^\/+/, ''),
209
209
  name: name || logicalPath.split('/').pop() || 'file',
210
210
  mimeType,
@@ -229,7 +229,7 @@ export function buildFileManifestFromEnc(parameters, enc) {
229
229
  export function vaultWrapDescriptor(entityHash, fileId, contentKey, H) {
230
230
  return {
231
231
  type: 'vault-wrap',
232
- entityHash: entityHash.toLowerCase(),
232
+ entityHash,
233
233
  fileId,
234
234
  wrappedKey: wrapContentKey(contentKey, H, fileId),
235
235
  }
@@ -165,7 +165,7 @@ export function createManifestPlaintextStream(manifest, partStreams, contentKey)
165
165
  }
166
166
  if (manifest.contentHash) {
167
167
  const got = digest.digest('hex')
168
- if (got !== String(manifest.contentHash).toLowerCase()) {
168
+ if (got !== manifest.contentHash) {
169
169
  this.destroy(new Error('contentHash mismatch'))
170
170
  return
171
171
  }
@@ -34,8 +34,7 @@ const chunkInflight = createInflightTable({
34
34
  * @returns {Promise<Uint8Array | null>} 密文块
35
35
  */
36
36
  export async function fetchChunk(context) {
37
- const hash = String(context.ciphertextHash || '').trim().toLowerCase()
38
- const { username } = context
37
+ const { username, ciphertextHash: hash } = context
39
38
  if (!hash || !username) return null
40
39
 
41
40
  if (await hasChunk(hash))
@@ -4,43 +4,6 @@ import { resolvePendingManifestFetch } from '../manifest/pending.mjs'
4
4
  import { handleIncomingChunkGet } from './fetch.mjs'
5
5
  import { resolvePendingChunkFetch } from './pending.mjs'
6
6
 
7
- /**
8
- * 群联邦 / TrustGraph 路径:响应带 requestId 的 fed_chunk_get。
9
- * @param {object} data 入站 fed_chunk_get
10
- * @param {string} peerId 对端 id
11
- * @param {(resp: object, peerId: string) => void} sendChunkData 发送 fed_chunk_data
12
- * @returns {Promise<void>}
13
- */
14
- export async function handleFedChunkGetIngress(data, peerId, sendChunkData) {
15
- await handleIncomingChunkGet(data, sendChunkData, peerId)
16
- }
17
-
18
- /**
19
- * @param {object} data 入站 fed_chunk_data(含 requestId 时 resolve pending fetch)
20
- * @returns {boolean} 是否命中 pending
21
- */
22
- export function handleFedChunkDataIngress(data) {
23
- return resolvePendingChunkFetch(data)
24
- }
25
-
26
- /**
27
- * @param {object} data 入站 fed_manifest_get
28
- * @param {string} peerId 对端 id
29
- * @param {(resp: object, peerId: string) => void} sendManifestData 发送 fed_manifest_data
30
- * @returns {Promise<void>}
31
- */
32
- export async function handleFedManifestGetIngress(data, peerId, sendManifestData) {
33
- await handleIncomingManifestGet(data, sendManifestData, peerId)
34
- }
35
-
36
- /**
37
- * @param {object} data 入站 fed_manifest_data
38
- * @returns {Promise<boolean>} 是否命中 pending
39
- */
40
- export function handleFedManifestDataIngress(data) {
41
- return resolvePendingManifestFetch(data)
42
- }
43
-
44
7
  /**
45
8
  * @param {(resp: object, peerId: string) => void} sendData 发送
46
9
  * @param {{ enqueue: (prio: number, cleanup: () => void) => void }} [fedOut] 出站限速队列
@@ -83,22 +46,22 @@ export function attachTrustGraphFedChunkResponder(room, fedOut, guardGet, rtcLim
83
46
  getChunkGet((data, peerId) => {
84
47
  if (guardGet && !guardGet(roomKey, 'fed_chunk_get', rtcLimits)) return
85
48
  if (!data?.requestId) return
86
- void handleFedChunkGetIngress(data, peerId, sendChunk)
49
+ void handleIncomingChunkGet(data, sendChunk, peerId)
87
50
  })
88
51
 
89
52
  getChunkData(data => {
90
53
  if (!data?.requestId) return
91
- handleFedChunkDataIngress(data)
54
+ resolvePendingChunkFetch(data)
92
55
  })
93
56
 
94
57
  getManifestGet((data, peerId) => {
95
58
  if (guardGet && !guardGet(roomKey, 'fed_manifest_get', rtcLimits)) return
96
59
  if (!data?.requestId) return
97
- void handleFedManifestGetIngress(data, peerId, sendManifest)
60
+ void handleIncomingManifestGet(data, sendManifest, peerId)
98
61
  })
99
62
 
100
63
  getManifestData(data => {
101
64
  if (!data?.requestId) return
102
- handleFedManifestDataIngress(data)
65
+ resolvePendingManifestFetch(data)
103
66
  })
104
67
  }
@@ -19,9 +19,8 @@ export function chunkStoreRoot() {
19
19
  * @returns {string} 块文件绝对路径
20
20
  */
21
21
  export function chunkStorePath(hash) {
22
- const chunkHash = String(hash).trim().toLowerCase()
23
- if (!isHex64(chunkHash)) throw new Error('invalid chunk hash')
24
- return join(chunkStoreRoot(), chunkHash.slice(0, 2), `${chunkHash}.bin`)
22
+ if (!isHex64(hash)) throw new Error('invalid chunk hash')
23
+ return join(chunkStoreRoot(), hash.slice(0, 2), `${hash}.bin`)
25
24
  }
26
25
 
27
26
  /**
@@ -7,9 +7,8 @@ import { sha256Hex } from '../../crypto/crypto.mjs'
7
7
  * @returns {boolean} 是否与 hash 一致
8
8
  */
9
9
  export function chunkBytesMatchHash(chunkHash, data) {
10
- const hash = String(chunkHash || '').trim().toLowerCase()
11
- if (!isHex64(hash) || !data?.byteLength) return false
12
- return sha256Hex(data) === hash
10
+ if (!isHex64(chunkHash) || !data?.byteLength) return false
11
+ return sha256Hex(data) === chunkHash
13
12
  }
14
13
 
15
14
  /**
package/files/evfs.mjs CHANGED
@@ -197,7 +197,7 @@ export async function putFileManifestFromStream(parameters) {
197
197
  const enc = await encryptReadableToParts(readable, ceMode, async part =>
198
198
  putChunk(part.hash, part.raw), plainSize)
199
199
  const manifest = normalizeFileManifest({
200
- ownerEntityHash: ownerEntityHash.toLowerCase(),
200
+ ownerEntityHash,
201
201
  logicalPath: logicalPath.replace(/^\/+/, ''),
202
202
  name: name || logicalPath.split('/').pop() || 'file',
203
203
  mimeType: mimeType || 'application/octet-stream',
@@ -10,9 +10,8 @@ export const EVFS_SCHEME = 'evfs:'
10
10
  * @returns {string} evfs URI 引用
11
11
  */
12
12
  export function formatEvfsRef(entityHash, logicalPath) {
13
- const eh = String(entityHash).trim().toLowerCase()
14
13
  const safePath = assertSafeEvfsLogicalPath(logicalPath)
15
- return `${EVFS_SCHEME}//${eh}/${safePath}`
14
+ return `${EVFS_SCHEME}//${entityHash}/${safePath}`
16
15
  }
17
16
 
18
17
  /**
@@ -24,8 +23,8 @@ export function parseEvfsRef(ref) {
24
23
  try {
25
24
  const url = new URL(ref)
26
25
  if (url.protocol !== 'evfs:') return null
27
- const entityHash = String(url.hostname || '').trim().toLowerCase()
28
- const logicalPath = String(url.pathname || '').replace(/^\/+/, '')
26
+ const entityHash = url.hostname
27
+ const logicalPath = url.pathname.replace(/^\/+/, '')
29
28
  if (!isEntityHash128(entityHash)) return null
30
29
  return { entityHash, logicalPath: assertSafeEvfsLogicalPath(logicalPath) }
31
30
  }
@@ -10,12 +10,12 @@ function fetchPeerTargets() {
10
10
  /** @type {Set<string>} */
11
11
  const targets = new Set()
12
12
  for (const { nodeHash } of listLinks())
13
- if (nodeHash) targets.add(String(nodeHash).toLowerCase())
13
+ if (nodeHash) targets.add(nodeHash)
14
14
  const net = loadNetwork()
15
15
  for (const nodeHash of [...net.trustedPeers || [], ...net.explorePeers || []])
16
- if (nodeHash) targets.add(String(nodeHash).toLowerCase())
16
+ if (nodeHash) targets.add(nodeHash)
17
17
  for (const hint of net.hints || [])
18
- if (hint?.nodeHash) targets.add(String(hint.nodeHash).toLowerCase())
18
+ if (hint?.nodeHash) targets.add(hint.nodeHash)
19
19
  return [...targets]
20
20
  }
21
21
 
@@ -31,9 +31,8 @@ const manifestInflight = createInflightTable({
31
31
  * @returns {Promise<import('./normalize.mjs').FileManifest | null>} 验签后的 manifest,失败为 null
32
32
  */
33
33
  export async function fetchPublicManifest(context) {
34
- const ownerEntityHash = String(context.ownerEntityHash || '').trim().toLowerCase()
35
- const logicalPath = String(context.logicalPath || '').trim().replace(/^\/+/, '')
36
- const { username } = context
34
+ const { ownerEntityHash, username } = context
35
+ const logicalPath = context.logicalPath.replace(/^\/+/, '')
37
36
  if (!ownerEntityHash || !logicalPath || !username) return null
38
37
 
39
38
  const timeoutMs = Number(context.timeoutMs) > 0
@@ -40,9 +40,9 @@ const TRANSFER_TYPES = new Set(['public', 'file-master-key-wrap', 'vault-wrap',
40
40
  */
41
41
  export function normalizeFileManifest(input) {
42
42
  if (!input || typeof input !== 'object') return null
43
- const ownerEntityHash = String(input.ownerEntityHash || '').trim().toLowerCase()
43
+ const ownerEntityHash = String(input.ownerEntityHash || '')
44
44
  if (!isEntityHash128(ownerEntityHash)) return null
45
- const logicalPath = String(input.logicalPath || '').trim().replace(/^\/+/, '').replace(/\\/g, '/')
45
+ const logicalPath = String(input.logicalPath || '').replace(/^\/+/, '').replace(/\\/g, '/')
46
46
  if (!logicalPath) return null
47
47
  const ceMode = String(input.ceMode || 'convergent')
48
48
  if (!CE_MODES.has(ceMode)) return null
@@ -51,10 +51,10 @@ export function normalizeFileManifest(input) {
51
51
  .map(part => {
52
52
  /** @type {ManifestPart} */
53
53
  const out = {
54
- hash: String(part?.hash || '').trim().toLowerCase(),
54
+ hash: String(part?.hash || ''),
55
55
  size: Number(part?.size) || 0,
56
56
  }
57
- const partContentHash = String(part?.contentHash || '').trim().toLowerCase()
57
+ const partContentHash = String(part?.contentHash || '')
58
58
  if (isHex64(partContentHash)) out.contentHash = partContentHash
59
59
  return out
60
60
  })
@@ -69,7 +69,7 @@ export function normalizeFileManifest(input) {
69
69
  name: String(input.name || logicalPath.split('/').pop() || 'file'),
70
70
  mimeType: String(input.mimeType || 'application/octet-stream'),
71
71
  size: Number(input.size) || 0,
72
- contentHash: String(input.contentHash || '').trim().toLowerCase(),
72
+ contentHash: String(input.contentHash || ''),
73
73
  ceMode: /** @type {CeMode} */ ceMode,
74
74
  parts,
75
75
  transferKeyDescriptor,
@@ -83,7 +83,7 @@ export function normalizeFileManifest(input) {
83
83
  */
84
84
  export function normalizeTransferKeyDescriptor(input) {
85
85
  if (!input || typeof input !== 'object') return null
86
- const type = String(input.type || '').trim()
86
+ const type = String(input.type || '')
87
87
  if (!TRANSFER_TYPES.has(type)) return null
88
88
  /** @type {TransferKeyDescriptor} */
89
89
  const out = { type: /** @type {TransferKeyDescriptor['type']} */ type }
@@ -93,9 +93,9 @@ export function normalizeTransferKeyDescriptor(input) {
93
93
  ciphertext: String(input.wrappedKey.ciphertext || ''),
94
94
  authTag: String(input.wrappedKey.authTag || ''),
95
95
  }
96
- if (input.groupId) out.groupId = String(input.groupId).trim()
97
- if (input.fileId) out.fileId = String(input.fileId).trim()
98
- if (input.entityHash) out.entityHash = String(input.entityHash).trim().toLowerCase()
96
+ if (input.groupId) out.groupId = String(input.groupId)
97
+ if (input.fileId) out.fileId = String(input.fileId)
98
+ if (input.entityHash) out.entityHash = String(input.entityHash)
99
99
  if (input.keyGeneration != null) out.keyGeneration = Number(input.keyGeneration) || 0
100
100
  return out
101
101
  }
@@ -17,10 +17,7 @@ export const pendingManifestFetches = table.pending
17
17
  * @returns {string} 期望键
18
18
  */
19
19
  export function manifestFetchExpectedKey(ownerEntityHash, logicalPath) {
20
- return compositeKey(
21
- String(ownerEntityHash || '').trim().toLowerCase(),
22
- String(logicalPath || '').trim().replace(/^\/+/, ''),
23
- )
20
+ return compositeKey(ownerEntityHash, logicalPath.replace(/^\/+/, ''))
24
21
  }
25
22
 
26
23
  /**
@@ -3,7 +3,7 @@ import { Buffer } from 'node:buffer'
3
3
  import { canonicalStringify } from '../../core/canonical_json.mjs'
4
4
  import { hashFromPubKeyHex, parseEntityHash } from '../../core/entity_id.mjs'
5
5
  import { assertSafeEvfsLogicalPath } from '../../core/evfs_logical_path.mjs'
6
- import { isHex64, normalizeHex64 } from '../../core/hexIds.mjs'
6
+ import { isHex64, isSignatureHex128, normalizeHex64 } from '../../core/hexIds.mjs'
7
7
  import { sign, verify } from '../../crypto/crypto.mjs'
8
8
  import { putFileManifest, saveFileManifest } from '../evfs.mjs'
9
9
 
@@ -19,14 +19,14 @@ export const ENTITY_PUBLIC_MANIFEST_DOMAIN = 'fount-entity-public-manifest'
19
19
  export function publicManifestSignBytes(fields) {
20
20
  return Buffer.from(canonicalStringify([
21
21
  ENTITY_PUBLIC_MANIFEST_DOMAIN,
22
- String(fields.ownerEntityHash || '').trim().toLowerCase(),
22
+ fields.ownerEntityHash || '',
23
23
  assertSafeEvfsLogicalPath(fields.logicalPath),
24
24
  Number(fields.publishedAt) || 0,
25
- String(fields.contentHash || '').trim().toLowerCase(),
25
+ fields.contentHash || '',
26
26
  Number(fields.size) || 0,
27
- String(fields.mimeType || ''),
28
- String(fields.name || ''),
29
- String(fields.ceMode || ''),
27
+ fields.mimeType || '',
28
+ fields.name || '',
29
+ fields.ceMode || '',
30
30
  fields.parts || [],
31
31
  ]), 'utf8')
32
32
  }
@@ -74,8 +74,8 @@ export async function verifySignedPublicManifest(input) {
74
74
  if (!publicSig || typeof publicSig !== 'object') return null
75
75
  const publishedAt = Number(publicSig.publishedAt) || 0
76
76
  const pubKeyHex = normalizeHex64(publicSig.pubKeyHex)
77
- const sigHex = String(publicSig.sigHex || '').trim().toLowerCase()
78
- if (!isHex64(pubKeyHex) || !/^[\da-f]{128}$/u.test(sigHex) || publishedAt <= 0) return null
77
+ const sigHex = String(publicSig.sigHex || '')
78
+ if (!isHex64(pubKeyHex) || !isSignatureHex128(sigHex) || publishedAt <= 0) return null
79
79
 
80
80
  const parsed = parseEntityHash(manifest.ownerEntityHash)
81
81
  if (!parsed) return null
@@ -26,7 +26,7 @@ export async function resolveContentKey(descriptor, manifest, dependencies = {})
26
26
  if (type === 'file-master-key-wrap') {
27
27
  const { groupId, fileId } = descriptor
28
28
  if (!groupId || !fileId || !descriptor.wrappedKey || !dependencies.getGroupFileMasterKey) return null
29
- const groupKey = await dependencies.getGroupFileMasterKey(String(groupId), descriptor.keyGeneration)
29
+ const groupKey = await dependencies.getGroupFileMasterKey(groupId, descriptor.keyGeneration)
30
30
  if (!groupKey) return null
31
31
  return unwrapContentKey(descriptor.wrappedKey, groupKey, fileId)
32
32
  }
@@ -34,7 +34,7 @@ export async function resolveContentKey(descriptor, manifest, dependencies = {})
34
34
  if (type === 'vault-wrap') {
35
35
  const { entityHash, fileId } = descriptor
36
36
  if (!entityHash || !fileId || !descriptor.wrappedKey || !dependencies.getVaultMasterKey) return null
37
- const vaultKey = await dependencies.getVaultMasterKey(String(entityHash))
37
+ const vaultKey = await dependencies.getVaultMasterKey(entityHash)
38
38
  if (!vaultKey) return null
39
39
  return unwrapContentKey(descriptor.wrappedKey, vaultKey, fileId)
40
40
  }
@@ -85,7 +85,7 @@ export async function assembleManifestPlaintext(manifest, partBytes, dependencie
85
85
  }
86
86
  const merged = Buffer.concat(plains)
87
87
  if (manifest.contentHash)
88
- if (createHash('sha256').update(merged).digest('hex') !== manifest.contentHash.toLowerCase()) return null
88
+ if (createHash('sha256').update(merged).digest('hex') !== manifest.contentHash) return null
89
89
 
90
90
  return merged
91
91
  }