@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/node/denylist.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { compositeKey } from '../core/composite_key.mjs'
2
2
  import { isEntityHash128 } from '../core/entity_id.mjs'
3
- import { isHex64, normalizeHex64 } from '../core/hexIds.mjs'
3
+ import { isHex64 } from '../core/hexIds.mjs'
4
4
  import { withAsyncMutex } from '../utils/async_mutex.mjs'
5
5
 
6
6
  import { readNodeJsonSync, writeNodeJsonSync } from './storage.mjs'
@@ -79,15 +79,15 @@ export function normalizeDenylist(raw) {
79
79
  const groupId = String(entry.groupId || '')
80
80
  if (!scope) continue
81
81
  if (scope === 'entity') {
82
- const value = String(entry?.value || '')
83
- if (isEntityHash128(value))
82
+ const value = isEntityHash128(entry?.value)
83
+ if (value)
84
84
  blocked.push({ scope: 'entity', value })
85
85
  continue
86
86
  }
87
- const value = normalizeHex64(entry?.value)
88
- if (scope === 'node' && isHex64(value))
87
+ const value = isHex64(entry?.value)
88
+ if (scope === 'node' && value)
89
89
  blocked.push({ scope: 'node', value, ...groupId ? { groupId } : {} })
90
- else if (scope === 'subject' && isHex64(value))
90
+ else if (scope === 'subject' && value)
91
91
  blocked.push({ scope: 'subject', value, ...groupId ? { groupId } : {} })
92
92
  }
93
93
  return { blocked }
@@ -116,12 +116,12 @@ export function saveDenylist(list) {
116
116
  * @returns {boolean} 是否命中群级 ban 集合
117
117
  */
118
118
  export function isSubjectBannedByState(state, subject) {
119
- const pk = normalizeHex64(subject?.pubKeyHash)
120
- if (isHex64(pk) && state?.bannedMembers?.has?.(pk)) return true
121
- const entity = String(subject?.entityHash || '')
122
- if (isEntityHash128(entity) && state?.bannedEntities?.has?.(entity)) return true
123
- const node = normalizeHex64(subject?.nodeHash)
124
- if (isHex64(node) && state?.bannedNodes?.has?.(node)) return true
119
+ const pk = isHex64(subject?.pubKeyHash)
120
+ if (pk && state?.bannedMembers?.has?.(pk)) return true
121
+ const entity = isEntityHash128(subject?.entityHash)
122
+ if (entity && state?.bannedEntities?.has?.(entity)) return true
123
+ const node = isHex64(subject?.nodeHash)
124
+ if (node && state?.bannedNodes?.has?.(node)) return true
125
125
  return false
126
126
  }
127
127
 
@@ -132,17 +132,17 @@ export function isSubjectBannedByState(state, subject) {
132
132
  * @returns {boolean} 是否命中
133
133
  */
134
134
  function matchesDenylistIndex(index, subject, groupId = '') {
135
- const pk = normalizeHex64(subject?.pubKeyHash)
135
+ const pk = isHex64(subject?.pubKeyHash)
136
136
  const entity = String(subject?.entityHash || '')
137
- const node = normalizeHex64(subject?.nodeHash)
137
+ const node = isHex64(subject?.nodeHash)
138
138
  const { keys } = index
139
139
 
140
140
  if (entity && keys.has(denyKey('entity', '*', entity))) return true
141
- if (isHex64(node) && keys.has(denyKey('node', '*', node))) return true
142
- if (isHex64(pk) && keys.has(denyKey('subject', '*', pk))) return true
141
+ if (node && keys.has(denyKey('node', '*', node))) return true
142
+ if (pk && keys.has(denyKey('subject', '*', pk))) return true
143
143
  if (!groupId) return false
144
- if (isHex64(pk) && keys.has(denyKey('subject', groupId, pk))) return true
145
- if (isHex64(node) && keys.has(denyKey('node', groupId, node))) return true
144
+ if (pk && keys.has(denyKey('subject', groupId, pk))) return true
145
+ if (node && keys.has(denyKey('node', groupId, node))) return true
146
146
  return false
147
147
  }
148
148
 
@@ -161,8 +161,8 @@ export function isSubjectBlocked(subject, groupId = '') {
161
161
  * @returns {boolean} 是否拉黑
162
162
  */
163
163
  export function isPeerKeyBlocked(groupId, peerKey) {
164
- const key = normalizeHex64(peerKey)
165
- if (!isHex64(key)) return false
164
+ const key = isHex64(peerKey)
165
+ if (!key) return false
166
166
  const index = getDenylistIndex()
167
167
  const { keys } = index
168
168
  if (keys.has(denyKey('subject', '*', key))) return true
@@ -200,9 +200,7 @@ export function addDenylistEntry(entry) {
200
200
  throw new Error('scope and value required')
201
201
  if (scope === 'entity' && entry.groupId)
202
202
  throw new Error('entity scope does not use groupId')
203
- const normValue = scope === 'node' || scope === 'subject'
204
- ? normalizeHex64(entry.value)
205
- : entry.value
203
+ const normValue = entry.value
206
204
  if (!normValue)
207
205
  throw new Error('scope and value required')
208
206
  if (scope === 'subject' && !isHex64(normValue))
@@ -234,8 +232,8 @@ export async function addDenylistFromBanContent(banContent, groupId) {
234
232
  await addDenylistEntry({ scope: 'entity', value: banContent.targetEntityHash })
235
233
  if (scope === 'node' && banContent?.targetNodeHash)
236
234
  await addDenylistEntry({ scope: 'node', value: banContent.targetNodeHash })
237
- const pk = normalizeHex64(banContent?.targetPubKeyHash)
238
- if (isHex64(pk))
235
+ const pk = isHex64(banContent?.targetPubKeyHash)
236
+ if (pk)
239
237
  await addDenylistEntry({ scope: 'subject', value: pk, ...sourceGroupId ? { groupId: sourceGroupId } : {} })
240
238
  }
241
239
 
@@ -259,12 +257,11 @@ export function addGroupBlockedPeer(groupId, scope, value) {
259
257
  * @returns {Promise<void>}
260
258
  */
261
259
  export function removeGroupBlockedPeer(groupId, scope, value) {
262
- const id = scope === 'entity' ? value : normalizeHex64(value)
263
- if (!scope || !id) return Promise.resolve()
260
+ if (!scope || !value) return Promise.resolve()
264
261
  return mutateDenylist(() => {
265
262
  const list = loadDenylist()
266
263
  list.blocked = list.blocked.filter(entry => {
267
- if (entry.scope !== scope || entry.value !== id) return true
264
+ if (entry.scope !== scope || entry.value !== value) return true
268
265
  if (scope === 'entity') return false
269
266
  return entry.groupId !== groupId
270
267
  })
package/node/identity.mjs CHANGED
@@ -2,7 +2,7 @@ import { Buffer } from 'node:buffer'
2
2
  import { randomBytes } from 'node:crypto'
3
3
 
4
4
  import { entityHashFromRecoveryPubKeyHex, parseEntityHash } from '../core/entity_id.mjs'
5
- import { isHex64, normalizeHex64 } from '../core/hexIds.mjs'
5
+ import { isHex64 } from '../core/hexIds.mjs'
6
6
  import { keyPairFromSeed, pubKeyHash } from '../crypto/crypto.mjs'
7
7
  import { normalizeMailboxSettings } from '../mailbox/settings.mjs'
8
8
 
@@ -17,7 +17,7 @@ const NODE_JSON = 'node'
17
17
  * @returns {string} 节点哈希
18
18
  */
19
19
  export function nodeHashFromSeed(seedHex) {
20
- const seed = Buffer.from(normalizeHex64(seedHex), 'hex')
20
+ const seed = Buffer.from(seedHex, 'hex')
21
21
  if (seed.length !== 32) throw new Error('invalid node seed')
22
22
  const { publicKey } = keyPairFromSeed(seed)
23
23
  return pubKeyHash(publicKey)
@@ -46,8 +46,8 @@ function saveNodeFile(patch) {
46
46
  */
47
47
  export function ensureNodeSeed() {
48
48
  const data = loadNodeFile()
49
- const existing = normalizeHex64(data.nodeSeedHex)
50
- if (isHex64(existing)) return existing
49
+ const existing = isHex64(data.nodeSeedHex)
50
+ if (existing) return existing
51
51
  const nodeSeedHex = randomBytes(32).toString('hex')
52
52
  saveNodeFile({ nodeSeedHex })
53
53
  return nodeSeedHex
@@ -105,8 +105,8 @@ export function ensureNodeDefaults() {
105
105
  * @returns {string | null} entityHash(非法 hex 时 null)
106
106
  */
107
107
  export function entityHashFromKeys(nodeHash, recoveryPubKeyHex) {
108
- const pub = normalizeHex64(recoveryPubKeyHex)
109
- if (!isHex64(nodeHash) || !isHex64(pub)) return null
108
+ const pub = isHex64(recoveryPubKeyHex)
109
+ if (!isHex64(nodeHash) || !pub) return null
110
110
  return entityHashFromRecoveryPubKeyHex(nodeHash, pub)
111
111
  }
112
112
 
package/node/network.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { isEntityHash128 } from '../core/entity_id.mjs'
2
- import { isHex64, normalizeHex64 } from '../core/hexIds.mjs'
2
+ import { isHex64 } from '../core/hexIds.mjs'
3
3
 
4
4
  import { loadDenylist } from './denylist.mjs'
5
5
  import { getNodeDir, isNodeInitialized } from './instance.mjs'
@@ -57,7 +57,7 @@ export function normalizeNetwork(raw) {
57
57
  )]
58
58
  const hints = (file.hints || [])
59
59
  .map(hint => ({
60
- nodeHash: normalizeHex64(hint.nodeHash) || '',
60
+ nodeHash: hint.nodeHash || '',
61
61
  source: String(hint.source || ''),
62
62
  kind: String(hint.kind || ''),
63
63
  weight: Number.isFinite(Number(hint.weight)) ? Number(hint.weight) : 0.1,
@@ -126,8 +126,8 @@ export function saveNetwork(data) {
126
126
  * @returns {void}
127
127
  */
128
128
  export function applyNetworkHint(hint) {
129
- const nodeHash = normalizeHex64(hint?.nodeHash)
130
- if (!isHex64(nodeHash)) return
129
+ const nodeHash = isHex64(hint?.nodeHash)
130
+ if (!nodeHash) return
131
131
  const net = loadNetwork()
132
132
  const now = Date.now()
133
133
  const ttlMs = Number.isFinite(hint.ttlMs) ? hint.ttlMs : DEFAULT_EXPLORE_TTL_MS
@@ -160,8 +160,8 @@ export function widenExploreFromTrustedAnchors() {
160
160
  const net = loadNetwork()
161
161
  const now = Date.now()
162
162
  for (const raw of net.trustedPeers.slice(0, 12)) {
163
- const nodeHash = normalizeHex64(raw)
164
- if (!isHex64(nodeHash)) continue
163
+ const nodeHash = isHex64(raw)
164
+ if (!nodeHash) continue
165
165
  if (!net.explorePeers.includes(nodeHash))
166
166
  net.explorePeers.push(nodeHash)
167
167
  net.hints.push({
@@ -185,12 +185,12 @@ export function widenExploreFromTrustedAnchors() {
185
185
  export function mergeNetworkPeerPools(patch = {}) {
186
186
  const net = loadNetwork()
187
187
  for (const raw of patch.trustedPeers || []) {
188
- const id = normalizeHex64(raw)
189
- if (isHex64(id) && !net.trustedPeers.includes(id)) net.trustedPeers.push(id)
188
+ const id = isHex64(raw)
189
+ if (id && !net.trustedPeers.includes(id)) net.trustedPeers.push(id)
190
190
  }
191
191
  for (const raw of patch.explorePeers || []) {
192
- const id = normalizeHex64(raw)
193
- if (isHex64(id) && !net.explorePeers.includes(id)) net.explorePeers.push(id)
192
+ const id = isHex64(raw)
193
+ if (id && !net.explorePeers.includes(id)) net.explorePeers.push(id)
194
194
  }
195
195
  net.lastRosterAt = Date.now()
196
196
  saveNetwork(net)
@@ -203,8 +203,8 @@ export function mergeNetworkPeerPools(patch = {}) {
203
203
  */
204
204
  export function promoteExplorePeer(nodeHash) {
205
205
  const net = loadNetwork()
206
- const id = normalizeHex64(nodeHash)
207
- if (!isHex64(id)) return
206
+ const id = isHex64(nodeHash)
207
+ if (!id) return
208
208
  net.explorePeers = net.explorePeers.filter(peer => peer !== id)
209
209
  if (!net.trustedPeers.includes(id)) net.trustedPeers.push(id)
210
210
  net.lastRosterAt = Date.now()
@@ -4,7 +4,7 @@
4
4
  * **hide**:纯本地隐藏(personal_hide.json,永不联邦)。
5
5
  */
6
6
  import { parseEntityHash } from '../core/entity_id.mjs'
7
- import { isHex64, normalizeHex64 } from '../core/hexIds.mjs'
7
+ import { isHex64 } from '../core/hexIds.mjs'
8
8
 
9
9
  import { isWritableLocalEntity } from './identity.mjs'
10
10
  import { isNodeInitialized, getEntityStore } from './instance.mjs'
@@ -45,8 +45,8 @@ export function normalizePersonalListEntries(raw) {
45
45
  byKey.set(`entity:${parsed.entityHash}`, { scope: 'entity', value: parsed.entityHash })
46
46
  }
47
47
  else if (scope === 'subject') {
48
- const value = normalizeHex64(entry?.value)
49
- if (isHex64(value))
48
+ const value = isHex64(entry?.value)
49
+ if (value)
50
50
  byKey.set(`subject:${value}`, { scope: 'subject', value })
51
51
  }
52
52
  }
@@ -64,7 +64,7 @@ export function normalizePersonalListEntries(raw) {
64
64
  */
65
65
  export function matchesPersonalListEntries(entries, subject) {
66
66
  const entity = subject?.entityHash || ''
67
- const pk = normalizeHex64(subject?.pubKeyHash || subject?.subjectHash || '')
67
+ const pk = subject?.pubKeyHash || subject?.subjectHash || ''
68
68
  if (entity) {
69
69
  for (const entry of entries)
70
70
  if (entry.scope === 'entity' && entry.value === entity) return true
@@ -1,4 +1,4 @@
1
- import { assertHex64, isHex64, normalizeHex64 } from '../core/hexIds.mjs'
1
+ import { assertHex64, isHex64 } from '../core/hexIds.mjs'
2
2
  import { recordWantIdsBackoff, wantIdsPeerKey } from '../federation/want_ids.mjs'
3
3
  import {
4
4
  applyDecayCollusionAfterSlashPure,
@@ -120,7 +120,7 @@ export function recordGossipAllUnknownWant(groupId, peerNodeHash) {
120
120
  * @returns {Promise<void>}
121
121
  */
122
122
  export function recordMessageRateViolation(peerNodeHash, excessRatio = 1) {
123
- const id = normalizeHex64(peerNodeHash)
123
+ const id = peerNodeHash
124
124
  if (!id) return Promise.resolve()
125
125
  return mutateReputation(data => {
126
126
  recordMessageRateViolationPure(data, id, undefined, excessRatio)
@@ -134,7 +134,7 @@ export function recordMessageRateViolation(peerNodeHash, excessRatio = 1) {
134
134
  * @returns {Promise<boolean>} 是否触发异常隔离
135
135
  */
136
136
  export async function observePeerBehavior(peerNodeHash, sample) {
137
- const id = normalizeHex64(peerNodeHash)
137
+ const id = peerNodeHash
138
138
  if (!id) return false
139
139
  let anomaly = false
140
140
  await mutateReputation(data => {
@@ -149,7 +149,7 @@ export async function observePeerBehavior(peerNodeHash, sample) {
149
149
  * @returns {void}
150
150
  */
151
151
  export function bumpChunkStorageReputation(storagePeerKey) {
152
- const id = normalizeHex64(storagePeerKey)
152
+ const id = storagePeerKey
153
153
  if (!id) return
154
154
  void mutateReputation(data => {
155
155
  bumpChunkStorageReputationPure(data, id)
@@ -161,7 +161,7 @@ export function bumpChunkStorageReputation(storagePeerKey) {
161
161
  * @returns {void}
162
162
  */
163
163
  export function penalizeChunkStorageFailure(blamePeerKey) {
164
- const id = normalizeHex64(blamePeerKey)
164
+ const id = blamePeerKey
165
165
  if (!id) return
166
166
  void mutateReputation(data => {
167
167
  penalizeChunkStorageFailurePure(data, id)
@@ -173,7 +173,7 @@ export function penalizeChunkStorageFailure(blamePeerKey) {
173
173
  * @returns {void}
174
174
  */
175
175
  export function penalizeArchiveServeMismatch(peerNodeHash) {
176
- const id = normalizeHex64(peerNodeHash)
176
+ const id = peerNodeHash
177
177
  if (!id) return
178
178
  void mutateReputation(data => {
179
179
  penalizeArchiveServeMismatchPure(data, id)
@@ -187,9 +187,9 @@ export function penalizeArchiveServeMismatch(peerNodeHash) {
187
187
  export async function applyVolatileSlashAlert(alert) {
188
188
  const expiresAt = Number(alert?.expiresAt)
189
189
  if (Number.isFinite(expiresAt) && Date.now() > expiresAt) return false
190
- const target = normalizeHex64(alert?.targetPubKeyHash)
191
- const sender = normalizeHex64(alert?.sender)
192
- if (!isHex64(target) || !isHex64(sender)) return false
190
+ const target = isHex64(alert?.targetPubKeyHash)
191
+ const sender = isHex64(alert?.sender)
192
+ if (!target || !sender) return false
193
193
  const claim = Number.isFinite(Number(alert?.claim)) ? Number(alert.claim) : reputationTunables.slashDefaultClaim
194
194
  await mutateReputation(data => {
195
195
  applySubjectiveSlashPure(data, target, sender, claim, false)
@@ -227,8 +227,8 @@ export function buildUnverifiedSlashAlert(senderPubKeyHash, content, groupSettin
227
227
  export async function applySubjectiveSlashFromEvent(username, groupId, event, readEvents) {
228
228
  if (event.type !== 'reputation_slash') return
229
229
  const { content } = event
230
- const target = normalizeHex64(content.targetPubKeyHash)
231
- const sender = normalizeHex64(event.sender)
230
+ const target = content.targetPubKeyHash
231
+ const sender = event.sender
232
232
 
233
233
  await mutateReputation(async data => {
234
234
  const verified = content.verified && await verifySlashProof(username, groupId, content, readEvents)
@@ -245,7 +245,7 @@ export async function applySubjectiveSlashFromEvent(username, groupId, event, re
245
245
  * @returns {Promise<boolean>} 是否找到 proof 对应事件
246
246
  */
247
247
  async function verifySlashProof(username, groupId, content, readEvents) {
248
- const eventId = normalizeHex64(content?.proof?.eventId)
248
+ const eventId = content?.proof?.eventId
249
249
  if (!eventId) return false
250
250
  const events = await readEvents(username, groupId)
251
251
  return events.some(event => event.id === eventId)
@@ -261,7 +261,7 @@ export function applyDecayCollusionAfterSlash(targetPubKeyHash, inviteEdges) {
261
261
  const applied = applyDecayCollusionAfterSlashPure(data, targetPubKeyHash, inviteEdges)
262
262
  if (applied.length)
263
263
  getNodeLogger()?.warn?.('reputation: collusion decay after slash', {
264
- target: normalizeHex64(targetPubKeyHash),
264
+ target: targetPubKeyHash,
265
265
  upstreamCount: applied.length,
266
266
  hops: applied.map(row => row.hop),
267
267
  })
@@ -1,6 +1,6 @@
1
1
  import { randomUUID } from 'node:crypto'
2
2
 
3
- import { isHex64, normalizeHex64 } from '../core/hexIds.mjs'
3
+ import { isHex64 } from '../core/hexIds.mjs'
4
4
  import { sendToNodeLink } from '../transport/link_registry.mjs'
5
5
  import { attachNodeScopeFeature } from '../transport/node_scope/features.mjs'
6
6
  import { ensureNodeScope, getNodeScopeWire } from '../transport/node_scope/wire.mjs'
@@ -55,9 +55,8 @@ function loadSyncConfig() {
55
55
  ? raw.lockedMaxPrevByNodeHash
56
56
  : {}
57
57
  for (const [nodeHash, score] of Object.entries(prevRaw)) {
58
- const id = normalizeHex64(nodeHash)
59
58
  const n = Number(score)
60
- if (id && isHex64(id) && Number.isFinite(n)) lockedMaxPrevByNodeHash[id] = n
59
+ if (isHex64(nodeHash) && Number.isFinite(n)) lockedMaxPrevByNodeHash[nodeHash] = n
61
60
  }
62
61
  syncConfig = {
63
62
  trustSyncDonors: normalizeHashList(raw.trustSyncDonors),
@@ -73,7 +72,7 @@ function loadSyncConfig() {
73
72
  * @returns {string[]} 规范化去重后的 64-hex 列表
74
73
  */
75
74
  function normalizeHashList(list) {
76
- return [...new Set((Array.isArray(list) ? list : []).map(isHex64).filter(Boolean))]
75
+ return [...new Set(list?.map?.(isHex64)?.filter?.(Boolean) || [])]
77
76
  }
78
77
 
79
78
  /** 将当前 sync 配置写盘 */
@@ -91,8 +90,8 @@ export async function setReputationTable(table) {
91
90
  await mutateReputation(data => {
92
91
  data.byNodeHash = data.byNodeHash || {}
93
92
  for (const [nodeHash, row] of Object.entries(incoming)) {
94
- const id = normalizeHex64(nodeHash)
95
- if (!id || !isHex64(id)) continue
93
+ const id = isHex64(nodeHash)
94
+ if (!id) continue
96
95
  const score = Number(row?.score ?? row)
97
96
  if (!Number.isFinite(score)) continue
98
97
  data.byNodeHash[id] = { ...data.byNodeHash[id] || {}, score }
@@ -223,7 +222,7 @@ export function attachReputationSyncWire() {
223
222
  * @param {string} peerId 对端 nodeHash
224
223
  */
225
224
  rep_sync_req(payload, peerId) {
226
- const requester = normalizeHex64(peerId)
225
+ const requester = peerId
227
226
  if (!requester || !getReputationExportAllowlist().includes(requester)) return
228
227
  try {
229
228
  wire.send('rep_sync_res', {
@@ -241,7 +240,7 @@ export function attachReputationSyncWire() {
241
240
  const requestId = String(payload?.requestId || '')
242
241
  const pending = pendingPulls.get(requestId)
243
242
  if (!pending) return
244
- if (normalizeHex64(peerId) !== pending.donor) return
243
+ if (peerId !== pending.donor) return
245
244
  clearTimeout(pending.timer)
246
245
  pendingPulls.delete(requestId)
247
246
  pending.resolve(payload)
@@ -271,8 +270,8 @@ export function detachReputationSyncWire() {
271
270
  * @returns {Promise<object>} donor 返回的信誉表
272
271
  */
273
272
  export async function pullReputationFromNode(nodeHash) {
274
- const donor = normalizeHex64(nodeHash)
275
- if (!donor || !isHex64(donor)) throw new Error('p2p: pullReputationFromNode requires valid nodeHash')
273
+ const donor = isHex64(nodeHash)
274
+ if (!donor) throw new Error('p2p: pullReputationFromNode requires valid nodeHash')
276
275
  if (!getTrustSyncDonors().includes(donor))
277
276
  throw new Error('p2p: node not in trustSyncDonors')
278
277
  attachReputationSyncWire()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@steve02081504/fount-p2p",
3
- "version": "0.0.35",
3
+ "version": "0.0.36",
4
4
  "description": "fount federation P2P layer — link, trust graph, mailbox, DAG, EVFS.",
5
5
  "keywords": [
6
6
  "network",
@@ -1,8 +1,6 @@
1
1
  /**
2
2
  * 信誉纯内存算子(模拟器与磁盘 store 共用)。
3
3
  */
4
- import { normalizeHex64 } from '../core/hexIds.mjs'
5
-
6
4
  import {
7
5
  clampReputationScore,
8
6
  computeRepMaxEff,
@@ -178,7 +176,7 @@ export function pruneReputationFile(data, tunables = reputationTunables, now = D
178
176
  * @returns {boolean} 是否已加分
179
177
  */
180
178
  export function bumpReputationOnRelayPure(data, peerNodeHash, dedupeKey, now = Date.now(), tunables = reputationTunables) {
181
- const id = normalizeHex64(peerNodeHash)
179
+ const id = peerNodeHash
182
180
  if (!id) return false
183
181
  const key = dedupeKey || `conn:${id}`
184
182
  data.relayBumpSeen = data.relayBumpSeen.filter(hit => now - hit.t <= tunables.relayBumpDedupeMs)
@@ -215,7 +213,7 @@ export function recordGossipAllUnknownWantPure(data, peerNodeHash, now = Date.no
215
213
  * @returns {void}
216
214
  */
217
215
  export function recordMessageRateViolationPure(data, peerNodeHash, tunables = reputationTunables, excessRatio = 1) {
218
- const id = normalizeHex64(peerNodeHash)
216
+ const id = peerNodeHash
219
217
  if (!id) return
220
218
  const ratio = Math.max(0, Math.min(1, Number(excessRatio) || 0))
221
219
  if (ratio <= 0) return
@@ -229,7 +227,7 @@ export function recordMessageRateViolationPure(data, peerNodeHash, tunables = re
229
227
  * @returns {void}
230
228
  */
231
229
  export function bumpChunkStorageReputationPure(data, storagePeerKey, tunables = reputationTunables) {
232
- const id = normalizeHex64(storagePeerKey)
230
+ const id = storagePeerKey
233
231
  if (!id) return
234
232
  adjustNodeReputation(data, id, tunables.chunkStoreRepBump)
235
233
  }
@@ -241,7 +239,7 @@ export function bumpChunkStorageReputationPure(data, storagePeerKey, tunables =
241
239
  * @returns {void}
242
240
  */
243
241
  export function penalizeChunkStorageFailurePure(data, blamePeerKey, tunables = reputationTunables) {
244
- const id = normalizeHex64(blamePeerKey)
242
+ const id = blamePeerKey
245
243
  if (!id) return
246
244
  adjustNodeReputation(data, id, -tunables.chunkFetchFailPenalty)
247
245
  }
@@ -253,7 +251,7 @@ export function penalizeChunkStorageFailurePure(data, blamePeerKey, tunables = r
253
251
  * @returns {void}
254
252
  */
255
253
  export function penalizeArchiveServeMismatchPure(data, peerNodeHash, tunables = reputationTunables) {
256
- const id = normalizeHex64(peerNodeHash)
254
+ const id = peerNodeHash
257
255
  if (!id) return
258
256
  adjustNodeReputation(data, id, -tunables.archiveServeMismatchPenalty)
259
257
  }
@@ -282,7 +280,7 @@ export function applySubjectiveSlashPure(data, target, sender, claim, verified,
282
280
  * @returns {Array<{ hop: number, node: string, dRep: number }>} 已应用的上游惩罚
283
281
  */
284
282
  export function applyDecayCollusionAfterSlashPure(data, targetPubKeyHash, inviteEdges, tunables = reputationTunables) {
285
- const target = normalizeHex64(targetPubKeyHash)
283
+ const target = targetPubKeyHash
286
284
  /** @type {Array<{ hop: number, node: string, dRep: number }>} */
287
285
  const applied = []
288
286
  let frontier = new Set([target])
@@ -303,8 +301,8 @@ export function applyDecayCollusionAfterSlashPure(data, targetPubKeyHash, invite
303
301
  for (let hop = 1; hop <= effectiveMaxHop(); hop++) {
304
302
  const upstream = new Set()
305
303
  for (const edge of inviteEdges) {
306
- const to = normalizeHex64(edge.to)
307
- const from = normalizeHex64(edge.from)
304
+ const to = edge.to
305
+ const from = edge.from
308
306
  if (frontier.has(to) && from) upstream.add(from)
309
307
  }
310
308
  if (!upstream.size) break
@@ -333,7 +331,7 @@ export function applyDecayCollusionAfterSlashPure(data, targetPubKeyHash, invite
333
331
  * @returns {void}
334
332
  */
335
333
  export function applyReputationResetToScoresPure(data, targetPubKeyHash) {
336
- const t = normalizeHex64(targetPubKeyHash)
334
+ const t = targetPubKeyHash
337
335
  const row = repRow(data, t)
338
336
  row.score = 0
339
337
  delete row.offenseStreak
@@ -353,8 +351,8 @@ export function applyReputationResetToScoresPure(data, targetPubKeyHash) {
353
351
  * @returns {void}
354
352
  */
355
353
  export function seedMemberReputationFromIntroducerPure(data, memberPubKeyHash, introducerPubKeyHash, repEdge, tunables = reputationTunables, powBonus = 0) {
356
- const memberKey = normalizeHex64(memberPubKeyHash)
357
- const introducerKey = normalizeHex64(introducerPubKeyHash)
354
+ const memberKey = memberPubKeyHash
355
+ const introducerKey = introducerPubKeyHash
358
356
  if (data.byNodeHash[memberKey]) return
359
357
  const introducerReputation = introducerKey
360
358
  ? Number(data.byNodeHash[introducerKey]?.score ?? 0)
@@ -394,7 +392,7 @@ function baselineRow(row) {
394
392
  * @returns {{ z: number, anomaly: boolean }} 偏离度
395
393
  */
396
394
  export function observeBehaviorSamplePure(data, peerNodeHash, sample, now = Date.now(), tunables = reputationTunables) {
397
- const id = normalizeHex64(peerNodeHash)
395
+ const id = peerNodeHash
398
396
  if (!id) return { z: 0, anomaly: false }
399
397
  const row = repRow(data, id)
400
398
  const baseline = baselineRow(row)
@@ -429,7 +427,7 @@ export function observeBehaviorSamplePure(data, peerNodeHash, sample, now = Date
429
427
  * @returns {void}
430
428
  */
431
429
  export function applyQuarantinePure(data, peerNodeHash, now = Date.now(), tunables = reputationTunables) {
432
- const id = normalizeHex64(peerNodeHash)
430
+ const id = peerNodeHash
433
431
  if (!id) return
434
432
  const row = repRow(data, id)
435
433
  row.quarantinedUntil = now + Number(tunables.quarantineTtlMs ?? 900_000)
@@ -443,7 +441,7 @@ export function applyQuarantinePure(data, peerNodeHash, now = Date.now(), tunabl
443
441
  * @returns {boolean} 是否处于本地隔离
444
442
  */
445
443
  export function isQuarantinedPure(data, peerNodeHash, now = Date.now()) {
446
- const id = normalizeHex64(peerNodeHash)
444
+ const id = peerNodeHash
447
445
  const until = Number(data.byNodeHash?.[id]?.quarantinedUntil ?? 0)
448
446
  return Number.isFinite(until) && until > now
449
447
  }
@@ -1,4 +1,4 @@
1
- import { assertHex64, isHex64, isSignatureHex128, normalizeHex64 } from '../core/hexIds.mjs'
1
+ import { assertHex64, isHex64, isSignatureHex128 } from '../core/hexIds.mjs'
2
2
  import { isPlainObject } from '../core/object.mjs'
3
3
 
4
4
  const MAX_DISCOVERY_ADS = 64
@@ -10,16 +10,15 @@ const MAX_DISCOVERY_ADS = 64
10
10
  function sanitizeDiscoveryAdvertisement(ad) {
11
11
  if (!isPlainObject(ad)) return null
12
12
  const groupId = String(ad.groupId || '')
13
- const advertiserPubKeyHash = normalizeHex64(ad.advertiserPubKeyHash)
14
- const signature = String(ad.signature || '')
15
- if (!groupId || !isHex64(advertiserPubKeyHash) || !isSignatureHex128(signature)) return null
16
- const advertiserNodeHash = normalizeHex64(ad.advertiserNodeHash)
13
+ const advertiserPubKeyHash = isHex64(ad.advertiserPubKeyHash)
14
+ const signature = isSignatureHex128(ad.signature)
15
+ if (!groupId || !advertiserPubKeyHash || !signature) return null
17
16
  const body = {
18
17
  groupId,
19
18
  title: String(ad.title || '').slice(0, 200),
20
19
  blurb: String(ad.blurb || '').slice(0, 500),
21
20
  advertiserPubKeyHash,
22
- advertiserNodeHash: isHex64(advertiserNodeHash) || String(ad.advertiserNodeHash || ''),
21
+ advertiserNodeHash: isHex64(ad.advertiserNodeHash) || '',
23
22
  observedAt: Number(ad.observedAt) || 0,
24
23
  signature,
25
24
  }
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * 联邦补拉 attestation / HPKE 响应 wire 解析(无 attestation/envelope 即丢弃)。
3
3
  */
4
- import { isHex64, isSignatureHex128, normalizeHex64 } from '../core/hexIds.mjs'
4
+ import { isHex64, isSignatureHex128 } from '../core/hexIds.mjs'
5
5
  import { isPlainObject } from '../core/object.mjs'
6
6
  import { EVENT_ID_HEX } from '../dag/index.mjs'
7
7
 
@@ -23,15 +23,15 @@ function isEnvelopeBlob(value) {
23
23
  */
24
24
  export function parsePullAttestation(attestation) {
25
25
  if (!isPlainObject(attestation)) return null
26
- const requesterPubKeyHash = normalizeHex64(attestation.requesterPubKeyHash)
26
+ const requesterPubKeyHash = isHex64(attestation.requesterPubKeyHash)
27
27
  const groupId = String(attestation.groupId || '')
28
28
  const requestId = String(attestation.requestId || '')
29
29
  const timestamp = Number(attestation.timestamp)
30
- const signature = String(attestation.signature || '')
31
- if (!isHex64(requesterPubKeyHash) || !groupId || !Number.isFinite(timestamp) || !isSignatureHex128(signature))
30
+ const signature = isSignatureHex128(attestation.signature)
31
+ if (!requesterPubKeyHash || !groupId || !Number.isFinite(timestamp) || !signature)
32
32
  return null
33
33
  const wantIds = attestation.wantIds?.length
34
- ? [...new Set(attestation.wantIds.map(id => normalizeHex64(id)).filter(id => EVENT_ID_HEX.test(id)))]
34
+ ? [...new Set(attestation.wantIds.filter(id => EVENT_ID_HEX.test(id)))]
35
35
  : undefined
36
36
  return {
37
37
  requesterPubKeyHash,
@@ -50,13 +50,13 @@ export function parsePullAttestation(attestation) {
50
50
  export function parsePullResponseEnvelope(envelope) {
51
51
  if (!isPlainObject(envelope)) return null
52
52
  const requestId = String(envelope.requestId || '')
53
- const requesterPubKeyHash = normalizeHex64(envelope.requesterPubKeyHash)
53
+ const requesterPubKeyHash = isHex64(envelope.requesterPubKeyHash)
54
54
  const requesterNodeHash = String(envelope.requesterNodeHash || '')
55
55
  const ephemPub = String(envelope.ephemPub || '')
56
56
  const iv = String(envelope.iv || '')
57
57
  const ciphertext = String(envelope.ciphertext || '')
58
58
  const authTag = String(envelope.authTag || '')
59
- if (!requestId || !isHex64(requesterPubKeyHash) || !requesterNodeHash) return null
59
+ if (!requestId || !requesterPubKeyHash || !requesterNodeHash) return null
60
60
  if (!isEnvelopeBlob(ephemPub) || !isEnvelopeBlob(iv) || !isEnvelopeBlob(ciphertext) || !isEnvelopeBlob(authTag))
61
61
  return null
62
62
  return {
@@ -1,4 +1,4 @@
1
- import { normalizeHex64, HEX_ID_64 } from '../core/hexIds.mjs'
1
+ import { isHex64 } from '../core/hexIds.mjs'
2
2
 
3
3
  /**
4
4
  * @typedef {{ ok: true, value: string }} MailboxWireOk
@@ -17,8 +17,8 @@ import { normalizeHex64, HEX_ID_64 } from '../core/hexIds.mjs'
17
17
  * @returns {MailboxWireResult} 规范化 hex64 或结构化错误
18
18
  */
19
19
  export function assertMailboxPubKeyHash(value) {
20
- const normalized = normalizeHex64(value)
21
- if (!HEX_ID_64.test(normalized))
20
+ const normalized = isHex64(value)
21
+ if (!normalized)
22
22
  return { ok: false, code: 'invalid_hex64', field: 'toPubKeyHash' }
23
23
  return { ok: true, value: normalized }
24
24
  }
@@ -1,7 +1,7 @@
1
1
  import { Buffer } from 'node:buffer'
2
2
 
3
3
  import { canonicalStringify } from '../core/canonical_json.mjs'
4
- import { isHex64, normalizeHex64 } from '../core/hexIds.mjs'
4
+ import { isHex64 } from '../core/hexIds.mjs'
5
5
  import { isPlainObject } from '../core/object.mjs'
6
6
  import { parsePartpath } from '../core/partpath.mjs'
7
7
 
@@ -99,8 +99,8 @@ export function parsePartQueryReq(value, tunables = partQueryTunables) {
99
99
  if (!isPlainObject(value)) return null
100
100
  const requestId = normalizeRequestId(value.requestId)
101
101
  if (!requestId) return null
102
- const originNodeHash = normalizeHex64(value.originNodeHash)
103
- if (!isHex64(originNodeHash)) return null
102
+ const originNodeHash = isHex64(value.originNodeHash)
103
+ if (!originNodeHash) return null
104
104
  const partpath = parsePartpath(value.partpath)
105
105
  if (!partpath) return null
106
106
  const kind = String(value.kind || '')
@@ -130,8 +130,8 @@ export function parsePartQueryRes(value, tunables = partQueryTunables) {
130
130
  if (!isPlainObject(value)) return null
131
131
  const requestId = normalizeRequestId(value.requestId)
132
132
  if (!requestId) return null
133
- const fromNodeHash = normalizeHex64(value.fromNodeHash)
134
- if (!isHex64(fromNodeHash)) return null
133
+ const fromNodeHash = isHex64(value.fromNodeHash)
134
+ if (!fromNodeHash) return null
135
135
  const rows = clampPartQueryRows(value.rows, tunables.maxHits, tunables.maxRowsBytes)
136
136
  if (!rows) return null
137
137
  return { requestId, fromNodeHash, rows }