@steve02081504/fount-p2p 0.0.34 → 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 +9 -19
  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 +22 -23
  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 +15 -17
  37. package/node/personal_block.mjs +4 -4
  38. package/node/reputation_store.mjs +13 -13
  39. package/node/reputation_sync.mjs +9 -12
  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
@@ -5,7 +5,7 @@ import { Buffer } from 'node:buffer'
5
5
 
6
6
  import { canonicalStringify } from '../core/canonical_json.mjs'
7
7
  import { hashFromPubKeyHex } from '../core/entity_id.mjs'
8
- import { isHex64, normalizeHex64 } from '../core/hexIds.mjs'
8
+ import { isHex64 } from '../core/hexIds.mjs'
9
9
 
10
10
  /**
11
11
  * entity 密钥撤销签名的域分隔前缀。
@@ -28,9 +28,10 @@ export const ENTITY_KEY_REVOKE_DOMAIN = 'fount-entity-key-revoke'
28
28
  * @returns {EntityKeyHistoryEntry[]} 创世链
29
29
  */
30
30
  export function createGenesisKeyHistory(activePubKeyHex, validFrom = Date.now()) {
31
+ if (!activePubKeyHex) return []
31
32
  return [{
32
33
  generation: 0,
33
- activePubKeyHex: normalizeHex64(activePubKeyHex),
34
+ activePubKeyHex,
34
35
  attestedBy: 'recovery',
35
36
  validFrom,
36
37
  }]
@@ -63,8 +64,8 @@ export function isActiveGenerationRevoked(keyHistory, generation) {
63
64
  * @returns {boolean} sender 是否为未吊销的活跃钥
64
65
  */
65
66
  export function isValidActiveSender(keyHistory, senderPubKeyHash) {
66
- const sender = normalizeHex64(senderPubKeyHash)
67
- if (!isHex64(sender)) return false
67
+ const sender = isHex64(senderPubKeyHash)
68
+ if (!sender) return false
68
69
  for (const entry of keyHistory || []) {
69
70
  if (isActiveGenerationRevoked(keyHistory, entry.generation)) continue
70
71
  if (hashFromPubKeyHex(entry.activePubKeyHex) === sender)
@@ -79,7 +80,7 @@ export function isValidActiveSender(keyHistory, senderPubKeyHash) {
79
80
  * @returns {boolean} 是否为 recovery 钥签名
80
81
  */
81
82
  export function isRecoverySender(recoveryPubKeyHex, senderPubKeyHash) {
82
- return hashFromPubKeyHex(recoveryPubKeyHex) === normalizeHex64(senderPubKeyHash)
83
+ return hashFromPubKeyHex(recoveryPubKeyHex) === senderPubKeyHash
83
84
  }
84
85
 
85
86
  /**
@@ -89,8 +90,8 @@ export function isRecoverySender(recoveryPubKeyHex, senderPubKeyHash) {
89
90
  */
90
91
  export function reduceEntityKeyRotate(state, event) {
91
92
  const generation = Number(event.content?.generation)
92
- const activePubKeyHex = normalizeHex64(event.content?.activePubKeyHex || '')
93
- if (!Number.isFinite(generation) || generation < 0 || !isHex64(activePubKeyHex))
93
+ const activePubKeyHex = isHex64(event.content?.activePubKeyHex || '')
94
+ if (!Number.isFinite(generation) || generation < 0 || !activePubKeyHex)
94
95
  return state
95
96
  state.entityKeyHistory = state.entityKeyHistory || []
96
97
  if (state.entityKeyHistory.some(row => row.generation === generation))
@@ -111,7 +112,7 @@ export function reduceEntityKeyRotate(state, event) {
111
112
  */
112
113
  export function reduceEntityKeyRevoke(state, event) {
113
114
  const newGeneration = Number(event.content?.newGeneration)
114
- const activePubKeyHex = normalizeHex64(event.content?.activePubKeyHex || '')
115
+ const activePubKeyHex = event.content?.activePubKeyHex
115
116
  const revokeGenerations = (event.content?.revokeGenerations || [])
116
117
  .map(Number).filter(Number.isFinite)
117
118
  if (!Number.isFinite(newGeneration) || newGeneration < 0 || !isHex64(activePubKeyHex))
@@ -163,7 +164,7 @@ export function entityKeyRevokeSignBytes(revokeBody) {
163
164
  const body = {
164
165
  revokeGenerations: (revokeBody.revokeGenerations || []).map(Number),
165
166
  newGeneration: Number(revokeBody.newGeneration),
166
- activePubKeyHex: normalizeHex64(revokeBody.activePubKeyHex || ''),
167
+ activePubKeyHex: revokeBody.activePubKeyHex || '',
167
168
  entityHash: String(revokeBody.entityHash || ''),
168
169
  }
169
170
  return Buffer.from(`${ENTITY_KEY_REVOKE_DOMAIN}\0${canonicalStringify(body)}`, 'utf8')
@@ -1,8 +1,6 @@
1
1
  /**
2
2
  * 群级消息限速纯函数(无 I/O,供单测与 governance 复用)。
3
3
  */
4
- import { normalizeHex64 } from '../core/hexIds.mjs'
5
-
6
4
  /**
7
5
  * @param {object} event DAG 事件
8
6
  * @returns {string} 限速实体键
@@ -10,7 +8,7 @@ import { normalizeHex64 } from '../core/hexIds.mjs'
10
8
  export function messageRateEntityKey(event) {
11
9
  const charId = String(event?.charId || '')
12
10
  if (charId) return `char:${charId}`
13
- return normalizeHex64(event?.sender)
11
+ return event?.sender || ''
14
12
  }
15
13
 
16
14
  /**
@@ -40,8 +40,8 @@ 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 || '')
44
- if (!isEntityHash128(ownerEntityHash)) return null
43
+ const ownerEntityHash = isEntityHash128(input.ownerEntityHash)
44
+ if (!ownerEntityHash) return null
45
45
  const logicalPath = String(input.logicalPath || '').replace(/^\/+/, '').replace(/\\/g, '/')
46
46
  if (!logicalPath) return null
47
47
  const ceMode = String(input.ceMode || 'convergent')
@@ -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, isSignatureHex128, normalizeHex64 } from '../../core/hexIds.mjs'
6
+ import { isHex64, isSignatureHex128 } from '../../core/hexIds.mjs'
7
7
  import { sign, verify } from '../../crypto/crypto.mjs'
8
8
  import { putFileManifest, saveFileManifest } from '../evfs.mjs'
9
9
 
@@ -39,7 +39,7 @@ export function publicManifestSignBytes(fields) {
39
39
  * @returns {Promise<import('./normalize.mjs').FileManifest>} 带 publicSig 的清单
40
40
  */
41
41
  export async function attachPublicManifestSig(manifest, publishedAt, entitySecretKey, entityPubKeyHex) {
42
- const pubKeyHex = normalizeHex64(entityPubKeyHex)
42
+ const pubKeyHex = entityPubKeyHex
43
43
  const message = publicManifestSignBytes({
44
44
  ownerEntityHash: manifest.ownerEntityHash,
45
45
  logicalPath: manifest.logicalPath,
@@ -73,9 +73,9 @@ export async function verifySignedPublicManifest(input) {
73
73
  const publicSig = input?.meta?.publicSig
74
74
  if (!publicSig || typeof publicSig !== 'object') return null
75
75
  const publishedAt = Number(publicSig.publishedAt) || 0
76
- const pubKeyHex = normalizeHex64(publicSig.pubKeyHex)
77
- const sigHex = String(publicSig.sigHex || '')
78
- if (!isHex64(pubKeyHex) || !isSignatureHex128(sigHex) || publishedAt <= 0) return null
76
+ const pubKeyHex = isHex64(publicSig.pubKeyHex)
77
+ const sigHex = isSignatureHex128(publicSig.sigHex)
78
+ if (!pubKeyHex || !sigHex || publishedAt <= 0) return null
79
79
 
80
80
  const parsed = parseEntityHash(manifest.ownerEntityHash)
81
81
  if (!parsed) return null
@@ -1,4 +1,3 @@
1
- import { normalizeHex64 } from '../core/hexIds.mjs'
2
1
  import { sortedPrevEventIds } from '../dag/index.mjs'
3
2
  import { getGovernanceAuthzTypes } from '../registries/event_type.mjs'
4
3
 
@@ -105,7 +104,7 @@ function governanceAuthzScoreForTip(tipId, byId, reputationBySender) {
105
104
  for (const eventId of ancestorClosureFromTip(tipId, byId)) {
106
105
  const event = byId.get(eventId)
107
106
  if (!event || !getGovernanceAuthzTypes().has(event.type)) continue
108
- const sender = normalizeHex64(event.sender)
107
+ const sender = event.sender
109
108
  if (sender) score += Number(reputationBySender[sender] ?? 0)
110
109
  }
111
110
  return score
@@ -3,8 +3,6 @@
3
3
  */
4
4
  import { createHash } from 'node:crypto'
5
5
 
6
- import { normalizeHex64 } from '../core/hexIds.mjs'
7
-
8
6
  import admissionTunables from './tunables.json' with { type: 'json' }
9
7
 
10
8
  /** 默认 epoch 窗口(1 小时) */
@@ -105,8 +103,8 @@ export function verifyJoinPow(powSolution, options) {
105
103
  const anchorRef = String(powSolution.anchorRef ?? '')
106
104
  const { nonce } = powSolution
107
105
  const epoch = Number(powSolution.epoch)
108
- const joinerNodeHash = normalizeHex64(powSolution.joinerNodeHash ?? options.senderNodeHash)
109
- const senderNodeHash = normalizeHex64(options.senderNodeHash)
106
+ const joinerNodeHash = powSolution.joinerNodeHash ?? options.senderNodeHash
107
+ const senderNodeHash = options.senderNodeHash
110
108
 
111
109
  if (!anchorRef || nonce == null || !Number.isFinite(epoch)) return { ok: false, achievedBits: 0 }
112
110
  if (!joinerNodeHash || joinerNodeHash !== senderNodeHash) return { ok: false, achievedBits: 0 }
@@ -4,7 +4,7 @@
4
4
  import { Buffer } from 'node:buffer'
5
5
 
6
6
  import { canonicalStringify } from '../core/canonical_json.mjs'
7
- import { isHex64, isSignatureHex128, normalizeHex64 } from '../core/hexIds.mjs'
7
+ import { isHex64, isSignatureHex128 } from '../core/hexIds.mjs'
8
8
  import { pubKeyHash, verify } from '../crypto/crypto.mjs'
9
9
 
10
10
  const BALLOT_DOMAIN = 'fount-owner-succession'
@@ -15,7 +15,7 @@ const BALLOT_DOMAIN = 'fount-owner-succession'
15
15
  */
16
16
  export function ownerSuccessionBallotSignBytes(ballot) {
17
17
  const body = {
18
- proposedOwnerPubKeyHash: normalizeHex64(ballot.proposedOwnerPubKeyHash),
18
+ proposedOwnerPubKeyHash: ballot.proposedOwnerPubKeyHash,
19
19
  groupId: ballot.groupId,
20
20
  ballotId: ballot.ballotId,
21
21
  }
@@ -42,9 +42,9 @@ export async function verifyOwnerSuccessionThreshold(ballot, adminPubKeyHashes,
42
42
  let valid = 0
43
43
 
44
44
  for (const entry of ballot.adminSignatures || []) {
45
- const pubKeyHex = normalizeHex64(entry.pubKeyHex)
46
- const signatureHex = entry.signature
47
- if (!isHex64(pubKeyHex) || !isSignatureHex128(signatureHex)) continue
45
+ const pubKeyHex = isHex64(entry.pubKeyHex)
46
+ const signatureHex = isSignatureHex128(entry.signature)
47
+ if (!pubKeyHex || !signatureHex) continue
48
48
 
49
49
  const hash = pubKeyHash(Buffer.from(pubKeyHex, 'hex'))
50
50
  if (!admins.has(hash) || seen.has(hash)) continue
@@ -1,4 +1,3 @@
1
- import { normalizeHex64 } from '../core/hexIds.mjs'
2
1
  import { loadReputation } from '../node/reputation_store.mjs'
3
2
  import { pickNodeScoreFromReputation } from '../reputation/pick_score.mjs'
4
3
  import { getLinkRegistry } from '../transport/link_registry.mjs'
@@ -38,7 +37,7 @@ export function applyPriorityToRegistry() {
38
37
  registry.setPriorityWeightFunction(nodeHash => {
39
38
  const score = pickNodeScoreFromReputation(
40
39
  loadReputation(),
41
- normalizeHex64(nodeHash) || nodeHash,
40
+ nodeHash,
42
41
  )
43
42
  return Math.floor(score * PRIORITY_BOOST)
44
43
  })
@@ -1,7 +1,7 @@
1
1
  import { Buffer } from 'node:buffer'
2
2
  import { randomBytes } from 'node:crypto'
3
3
 
4
- import { isHex64, isSignatureHex128, normalizeHex64 } from '../core/hexIds.mjs'
4
+ import { isHex64, isSignatureHex128 } from '../core/hexIds.mjs'
5
5
  import { normalizeTcpPort } from '../core/tcp_port.mjs'
6
6
  import { keyPairFromSeed, pubKeyHash, sign, verify } from '../crypto/crypto.mjs'
7
7
  import { normalizeLanHosts } from '../discovery/lan_interfaces.mjs'
@@ -22,8 +22,7 @@ export const LINK_HANDSHAKE_DOMAIN = 'fount-link'
22
22
  export function normalizeLinkBinding(value) {
23
23
  const dtls = normalizeDtlsFingerprint(value)
24
24
  if (dtls) return dtls
25
- const hex = normalizeHex64(value)
26
- return isHex64(hex) ? hex : null
25
+ return isHex64(value)
27
26
  }
28
27
 
29
28
  /**
@@ -34,9 +33,9 @@ export function normalizeLinkBinding(value) {
34
33
  * @returns {Uint8Array} 待签名消息字节
35
34
  */
36
35
  export function buildAuthMessage(peerNonce, localBinding, localNodeHash) {
37
- const nonce = normalizeHex64(peerNonce)
36
+ const nonce = peerNonce
38
37
  const binding = normalizeLinkBinding(localBinding)
39
- const nodeHash = normalizeHex64(localNodeHash)
38
+ const nodeHash = localNodeHash
40
39
  if (!/^[\da-f]{64}$/u.test(nonce))
41
40
  throw new Error('p2p: auth nonce must be 64 hex characters')
42
41
  if (!binding)
@@ -57,10 +56,10 @@ export function buildHello(options = {}) {
57
56
  const derived = keyPairFromSeed(Buffer.from(ensureNodeSeed(), 'hex'))
58
57
  publicKey = derived.publicKey
59
58
  }
60
- const nodeHash = normalizeHex64(options.nodeHash || getNodeHash())
61
- const nodePubKey = normalizeHex64(options.nodePubKey || Buffer.from(publicKey).toString('hex'))
62
- const nonce = normalizeHex64(options.nonce || randomBytes(32).toString('hex'))
63
- if (!isHex64(nodeHash) || !isHex64(nodePubKey) || !isHex64(nonce))
59
+ const nodeHash = isHex64(options.nodeHash || getNodeHash())
60
+ const nodePubKey = isHex64(options.nodePubKey || Buffer.from(publicKey).toString('hex'))
61
+ const nonce = isHex64(options.nonce || randomBytes(32).toString('hex'))
62
+ if (!nodeHash || !nodePubKey || !nonce)
64
63
  throw new Error('p2p: invalid hello fields')
65
64
  if (pubKeyHash(Buffer.from(nodePubKey, 'hex')) !== nodeHash)
66
65
  throw new Error('p2p: hello nodePubKey does not match nodeHash')
@@ -79,7 +78,7 @@ export async function buildAuth(peerNonce, localBinding, options = {}) {
79
78
  ? Buffer.from(options.secretKey)
80
79
  : Buffer.from(ensureNodeSeed(), 'hex')
81
80
  const { publicKey, secretKey } = keyPairFromSeed(seed)
82
- const nodeHash = normalizeHex64(options.nodeHash || pubKeyHash(publicKey))
81
+ const nodeHash = options.nodeHash || pubKeyHash(publicKey)
83
82
  if (nodeHash !== pubKeyHash(publicKey))
84
83
  throw new Error('p2p: auth nodeHash does not match secretKey')
85
84
  const message = buildAuthMessage(peerNonce, localBinding, nodeHash)
@@ -93,10 +92,10 @@ export async function buildAuth(peerNonce, localBinding, options = {}) {
93
92
  * @returns {{ nodeHash: string, nodePubKey: string, nonce: string } | null} 规范化 hello 或 null
94
93
  */
95
94
  export function parseHello(hello) {
96
- const nodeHash = normalizeHex64(hello?.nodeHash)
97
- const nodePubKey = normalizeHex64(hello?.nodePubKey)
98
- const nonce = normalizeHex64(hello?.nonce)
99
- if (!isHex64(nodeHash) || !isHex64(nodePubKey) || !isHex64(nonce)) return null
95
+ const nodeHash = isHex64(hello?.nodeHash)
96
+ const nodePubKey = isHex64(hello?.nodePubKey)
97
+ const nonce = isHex64(hello?.nonce)
98
+ if (!nodeHash || !nodePubKey || !nonce) return null
100
99
  try {
101
100
  if (pubKeyHash(Buffer.from(nodePubKey, 'hex')) !== nodeHash) return null
102
101
  }
@@ -117,11 +116,11 @@ export function parseHello(hello) {
117
116
  export async function verifyAuth(hello, auth, expectedNonce, remoteBinding) {
118
117
  const parsedHello = parseHello(hello)
119
118
  if (!parsedHello) return null
120
- const signatureHex = String(auth?.sig ?? '')
119
+ const signatureHex = isSignatureHex128(auth?.sig)
121
120
  const binding = normalizeLinkBinding(remoteBinding)
122
- if (!isSignatureHex128(signatureHex) || !binding) return null
123
- const normalizedNonce = normalizeHex64(expectedNonce)
124
- if (!isHex64(normalizedNonce)) return null
121
+ if (!signatureHex || !binding) return null
122
+ const normalizedNonce = isHex64(expectedNonce)
123
+ if (!normalizedNonce) return null
125
124
  const message = buildAuthMessage(normalizedNonce, binding, parsedHello.nodeHash)
126
125
  const ok = await verify(
127
126
  Buffer.from(signatureHex, 'hex'),
@@ -141,7 +140,7 @@ export async function verifyAuth(hello, auth, expectedNonce, remoteBinding) {
141
140
  * @returns {Uint8Array} 待签名消息字节
142
141
  */
143
142
  export function buildAdvertMessage(rendezvousKey, ts, nodeHash, tcpPort = null, lanHosts = null) {
144
- const base = `fount-advert\0${rendezvousKey}\0${ts}\0${normalizeHex64(nodeHash)}`
143
+ const base = `fount-advert\0${rendezvousKey}\0${ts}\0${nodeHash}`
145
144
  const port = normalizeTcpPort(tcpPort)
146
145
  let message = port ? `${base}\0${port}` : base
147
146
  const hosts = normalizeLanHosts(lanHosts)
@@ -161,8 +160,8 @@ export async function buildSignedAdvert(rendezvousKey, ts = Date.now(), options
161
160
  ? Buffer.from(options.secretKey)
162
161
  : Buffer.from(ensureNodeSeed(), 'hex')
163
162
  const { publicKey, secretKey } = keyPairFromSeed(seed)
164
- const nodeHash = normalizeHex64(options?.nodeHash || pubKeyHash(publicKey))
165
- const nodePubKey = normalizeHex64(options?.nodePubKey || Buffer.from(publicKey).toString('hex'))
163
+ const nodeHash = options?.nodeHash || pubKeyHash(publicKey)
164
+ const nodePubKey = options?.nodePubKey || Buffer.from(publicKey).toString('hex')
166
165
  if (pubKeyHash(Buffer.from(nodePubKey, 'hex')) !== nodeHash)
167
166
  throw new Error('p2p: advert nodePubKey does not match nodeHash')
168
167
  const tcpPort = normalizeTcpPort(options?.tcpPort)
@@ -194,8 +193,8 @@ export async function verifySignedAdvert(rendezvousKey, advert, now = Date.now()
194
193
  const parsedHello = parseHello({ nodeHash: advert?.nodeHash, nodePubKey: advert?.nodePubKey, nonce: '0'.repeat(64) })
195
194
  if (!parsedHello) return null
196
195
  const ts = Number(advert?.ts)
197
- const sig = String(advert?.sig ?? '')
198
- if (!Number.isFinite(ts) || Math.abs(now - ts) > maxSkewMs || !isSignatureHex128(sig)) return null
196
+ const sig = isSignatureHex128(advert?.sig)
197
+ if (!Number.isFinite(ts) || Math.abs(now - ts) > maxSkewMs || !sig) return null
199
198
  const hasTcpPortField = !!advert?.tcpPort
200
199
  const tcpPort = normalizeTcpPort(advert?.tcpPort)
201
200
  if (hasTcpPortField && !tcpPort) return null
package/link/pipe.mjs CHANGED
@@ -1,5 +1,4 @@
1
1
  import { toBytes } from '../core/bytes_codec.mjs'
2
- import { normalizeHex64 } from '../core/hexIds.mjs'
3
2
  import { ms } from '../utils/duration.mjs'
4
3
  import { emitSafe } from '../utils/emit_safe.mjs'
5
4
  import { createLruMap } from '../utils/lru.mjs'
@@ -92,7 +91,7 @@ export function createLinkPipe(options) {
92
91
  const heartbeatMs = Number(options.heartbeatMs) || ms('15s')
93
92
  const idleTimeoutMs = Number(options.idleTimeoutMs) || ms('45s')
94
93
  const handshakeTimeoutMs = Number(options.handshakeTimeoutMs) || ms('10s')
95
- const targetNodeHash = normalizeHex64(options.nodeHash || '')
94
+ const targetNodeHash = options.nodeHash || ''
96
95
  let closed = false
97
96
  let ready = false
98
97
  let closeReason = 'closed'
@@ -1,7 +1,6 @@
1
1
  import { Buffer } from 'node:buffer'
2
2
  import { randomBytes } from 'node:crypto'
3
3
 
4
- import { normalizeHex64 } from '../../core/hexIds.mjs'
5
4
  import { getBtPeerHint } from '../../discovery/bt/peer_hints.mjs'
6
5
  import { canUseBluetoothRuntime, loadBleno, loadNoble, resolveBtRole, waitPoweredOn } from '../../discovery/bt/runtime.mjs'
7
6
  import { asLinkHandle } from '../pipe.mjs'
@@ -55,7 +54,7 @@ async function openGattPipe(options) {
55
54
 
56
55
  if (options.initiator)
57
56
  await Promise.resolve(options.write(Buffer.from(
58
- buildLinkOpen(normalizeHex64(options.linkId), options.localIdentity?.nodeHash),
57
+ buildLinkOpen(options.linkId, options.localIdentity?.nodeHash),
59
58
  'utf8',
60
59
  )))
61
60
 
@@ -69,7 +68,7 @@ async function openGattPipe(options) {
69
68
  * @returns {Promise<import('./index.mjs').LinkHandle>} 已就绪的 link
70
69
  */
71
70
  async function dialBleGatt(options) {
72
- const remoteNodeHash = normalizeHex64(options.nodeHash)
71
+ const remoteNodeHash = options.nodeHash
73
72
  const hint = getBtPeerHint(remoteNodeHash)
74
73
  if (!hint) throw new Error('p2p: ble_gatt no peer hint')
75
74
  const noble = await loadNoble()
@@ -2,7 +2,6 @@ import { Buffer } from 'node:buffer'
2
2
  import { randomBytes } from 'node:crypto'
3
3
  import net from 'node:net'
4
4
 
5
- import { normalizeHex64 } from '../../core/hexIds.mjs'
6
5
  import { getLanPeerHint, listLanPeerHints } from '../../discovery/lan_peer_hints.mjs'
7
6
  import { asLinkHandle } from '../pipe.mjs'
8
7
 
@@ -186,7 +185,7 @@ async function openTcpPipe(options) {
186
185
  pipe.handleInbound(payload)
187
186
 
188
187
  if (options.initiator)
189
- codec.write(buildLinkOpen(normalizeHex64(options.linkId), options.localIdentity?.nodeHash))
188
+ codec.write(buildLinkOpen(options.linkId, options.localIdentity?.nodeHash))
190
189
 
191
190
  await pipe.startHandshake()
192
191
  return asLinkHandle(pipe)
@@ -198,7 +197,7 @@ async function openTcpPipe(options) {
198
197
  * @returns {Promise<import('./index.mjs').LinkHandle>} 已就绪的 link
199
198
  */
200
199
  async function dialLanTcp(options) {
201
- const remoteNodeHash = normalizeHex64(options.nodeHash)
200
+ const remoteNodeHash = options.nodeHash
202
201
  const hints = listLanPeerHints(remoteNodeHash)
203
202
  if (!hints.length) throw new Error('p2p: lan_tcp no peer hint')
204
203
 
@@ -1,6 +1,6 @@
1
1
  import { Buffer } from 'node:buffer'
2
2
 
3
- import { normalizeHex64 } from '../../core/hexIds.mjs'
3
+ import { isHex64 } from '../../core/hexIds.mjs'
4
4
  import { createLinkPipe } from '../pipe.mjs'
5
5
 
6
6
  /**
@@ -25,7 +25,7 @@ export function parseLinkOpen(raw) {
25
25
  }
26
26
  catch { return null }
27
27
  if (parsed?.type !== 'link-open' || !parsed.linkId) return null
28
- return { linkId: String(parsed.linkId), from: normalizeHex64(parsed.from) || null }
28
+ return { linkId: String(parsed.linkId), from: isHex64(parsed.from) }
29
29
  }
30
30
 
31
31
  /**
@@ -34,7 +34,7 @@ export function parseLinkOpen(raw) {
34
34
  * @returns {ReturnType<typeof createLinkPipe>} link pipe
35
35
  */
36
36
  export function createLinkIdBoundPipe(options) {
37
- const linkId = normalizeHex64(options.linkId)
37
+ const linkId = options.linkId
38
38
  if (!linkId) throw new Error(`p2p: ${options.providerId || 'link'} linkId required`)
39
39
  return createLinkPipe({
40
40
  ...options,
@@ -2,7 +2,7 @@ import { Buffer } from 'node:buffer'
2
2
  import { randomBytes } from 'node:crypto'
3
3
 
4
4
  import { base64ToBytes, bytesToBase64 } from '../../core/bytes_codec.mjs'
5
- import { normalizeHex64 } from '../../core/hexIds.mjs'
5
+ import { isHex64 } from '../../core/hexIds.mjs'
6
6
  import { getDiscoveryProvider, sendNodeSignalPacket } from '../../discovery/index.mjs'
7
7
  import { NOSTR_SIGNAL_KIND, resolveNostrRelayUrls } from '../../discovery/nostr.mjs'
8
8
  import { ms } from '../../utils/duration.mjs'
@@ -317,9 +317,9 @@ export function createNostrLinkProvider(options = {}) {
317
317
  */
318
318
  async function deliverPacket(packet) {
319
319
  if (packet?.type !== 'link') return
320
- const linkId = normalizeHex64(packet.linkId)
321
- const from = normalizeHex64(packet.from)
322
- if (!linkId || !from) return
320
+ const linkId = packet.linkId
321
+ const from = packet.from
322
+ if (!isHex64(linkId) || !isHex64(from)) return
323
323
  if (localIdentity?.nodeHash && from === localIdentity.nodeHash) return
324
324
 
325
325
  const op = String(packet.op || '')
@@ -375,8 +375,8 @@ export function createNostrLinkProvider(options = {}) {
375
375
  */
376
376
  async dial(dialOptions) {
377
377
  if (!isAvailable()) return null
378
- const remoteNodeHash = normalizeHex64(dialOptions.nodeHash)
379
- if (!remoteNodeHash) return null
378
+ const remoteNodeHash = dialOptions.nodeHash
379
+ if (!isHex64(remoteNodeHash)) return null
380
380
  localIdentity = dialOptions.localIdentity || localIdentity
381
381
  if (!localIdentity?.nodeHash) throw new Error('p2p: nostr dial requires localIdentity')
382
382
  await refreshPayloadCap(resolveRelayUrls())
@@ -1,4 +1,4 @@
1
- import { normalizeHex64 } from '../core/hexIds.mjs'
1
+ import { isHex64 } from '../core/hexIds.mjs'
2
2
  import { getNodeTransportSettings, getNodeHash } from '../node/identity.mjs'
3
3
  import { activeLinkRoster, deliverToUserRoomPeers } from '../transport/user_room.mjs'
4
4
  import { DEFAULT_TRUST_GRAPH_OWNER, requireTrustGraphProvider } from '../trust_graph/registry.mjs'
@@ -25,7 +25,7 @@ import {
25
25
  async function resolveRemoteNodeHashForPeer(username, peerId) {
26
26
  if (!peerId) return null
27
27
  const entry = activeLinkRoster().find(row => row.peerId === peerId)
28
- const remote = normalizeHex64(entry?.remoteNodeHash)
28
+ const remote = entry?.remoteNodeHash
29
29
  return remote || null
30
30
  }
31
31
 
@@ -62,8 +62,8 @@ async function resolveRouting(username) {
62
62
  */
63
63
  export async function deliverOrStoreMailboxPut(username, options) {
64
64
  const routing = await resolveRouting(username)
65
- const toPubKeyHash = normalizeHex64(options.toPubKeyHash)
66
- if (!toPubKeyHash) return { stored: false, delivered: false, relayed: 0 }
65
+ const toPubKeyHash = options.toPubKeyHash
66
+ if (!isHex64(toPubKeyHash)) return { stored: false, delivered: false, relayed: 0 }
67
67
  const hop = normalizeMailboxHop(options.hop)
68
68
  if (hop >= routing.maxHop) return { stored: false, delivered: false, relayed: 0 }
69
69
  const tier = mailboxTierFromHop(hop)
@@ -76,8 +76,8 @@ export async function deliverOrStoreMailboxPut(username, options) {
76
76
  fromNodeHash: options.record?.fromNodeHash || nodeHash,
77
77
  }
78
78
  const stored = await storeMailboxRecord(record)
79
- const toNodeHash = normalizeHex64(options.toNodeHash)
80
- const delivered = toNodeHash && isMailboxRecordWithinSizeLimit(record)
79
+ const toNodeHash = options.toNodeHash
80
+ const delivered = isHex64(toNodeHash) && isMailboxRecordWithinSizeLimit(record)
81
81
  ? await requireTrustGraphProvider(DEFAULT_TRUST_GRAPH_OWNER).sendToNode(username, toNodeHash, 'mailbox_put', { nodeHash, record })
82
82
  : false
83
83
 
@@ -114,8 +114,8 @@ export async function publishMailboxRecord(username, toPubKeyHash, record, toNod
114
114
  export async function ingestMailboxPut(wireContext, put, peerId = '') {
115
115
  const { record } = put
116
116
  if (!record?.envelope || !record?.toPubKeyHash) return
117
- const fromNode = normalizeHex64(put.nodeHash)
118
- if (!fromNode || !takeIncomingMailboxPutSlot(fromNode)) return
117
+ const fromNode = put.nodeHash
118
+ if (!isHex64(fromNode) || !takeIncomingMailboxPutSlot(fromNode)) return
119
119
  const username = wireContext.replicaUsername || ''
120
120
  if (!username) return
121
121
  if (peerId) {
@@ -143,8 +143,8 @@ export async function ingestMailboxPut(wireContext, put, peerId = '') {
143
143
  */
144
144
  export async function respondMailboxWant(want, sendGive, peerId) {
145
145
  const { getMailboxRecords, takeMailboxForRecipient } = await import('./store.mjs')
146
- const recipient = normalizeHex64(want.toPubKeyHash)
147
- if (!recipient) return
146
+ const recipient = want.toPubKeyHash
147
+ if (!isHex64(recipient)) return
148
148
  const ids = want.ids || []
149
149
  const rows = (ids.length
150
150
  ? await getMailboxRecords(ids)
@@ -179,7 +179,7 @@ export async function ingestMailboxGive(wireContext, give) {
179
179
  export async function requestMailboxFromNetwork(username, toPubKeyHash) {
180
180
  const routing = await resolveRouting(username)
181
181
  const { listMailboxIdsForRecipient } = await import('./store.mjs')
182
- const recipient = normalizeHex64(toPubKeyHash)
182
+ const recipient = toPubKeyHash
183
183
  if (!recipient) return
184
184
  await deliverToUserRoomPeers(username, 'mailbox_want', {
185
185
  toPubKeyHash: recipient,
package/mailbox/prune.mjs CHANGED
@@ -1,7 +1,6 @@
1
1
  /**
2
2
  * Mailbox 公平淘汰(纯函数,供 store 与单测复用)。
3
3
  */
4
- import { normalizeHex64 } from '../core/hexIds.mjs'
5
4
 
6
5
  /** 单收件人×发送者桶:条数上限 */
7
6
  export const MAX_BUCKET_ENTRIES = 10
@@ -31,9 +30,9 @@ export function mailboxRecordBytes(record) {
31
30
  * @returns {string} 公平淘汰桶键
32
31
  */
33
32
  export function mailboxBucketKey(record) {
34
- const to = normalizeHex64(record.toPubKeyHash) || ''
35
- const from = normalizeHex64(record.fromNodeHash)
36
- || normalizeHex64(record.envelope?.sender)
33
+ const to = record.toPubKeyHash || ''
34
+ const from = record.fromNodeHash
35
+ || record.envelope?.sender
37
36
  || 'unknown'
38
37
  return `${to}\0${from}`
39
38
  }
package/mailbox/rate.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * Mailbox 入站 put 限速(按来源节点,节点级单例)。
3
3
  */
4
- import { normalizeHex64 } from '../core/hexIds.mjs'
4
+ import { isHex64 } from '../core/hexIds.mjs'
5
5
 
6
6
  const DEFAULT_WINDOW_MS = 60_000
7
7
  const DEFAULT_MAX_PUTS = 20
@@ -62,7 +62,8 @@ function evictLruIfNeeded(key) {
62
62
  */
63
63
  export function takeIncomingMailboxPutSlot(fromNodeHash, limits) {
64
64
  const { windowMs, maxPuts } = resolveMailboxRateLimits(limits)
65
- const key = normalizeHex64(fromNodeHash)
65
+ const key = fromNodeHash
66
+ if (!isHex64(key)) return false
66
67
  const now = Date.now()
67
68
  if (inboundByKey.size >= MAX_KEYS) sweepExpiredEntries(now)
68
69
  evictLruIfNeeded(key)
package/mailbox/store.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import { mkdir, readFile } from 'node:fs/promises'
2
2
  import { dirname } from 'node:path'
3
3
 
4
- import { isHex64, normalizeHex64 } from '../core/hexIds.mjs'
4
+ import { isHex64 } from '../core/hexIds.mjs'
5
5
  import { jsonlMutexKey, writeJsonl } from '../dag/storage.mjs'
6
6
  import { mailboxStorePath } from '../node/user_paths.mjs'
7
7
  import { withAsyncMutex } from '../utils/async_mutex.mjs'
@@ -150,7 +150,7 @@ export function mailboxEnvelopeId(envelope) {
150
150
  */
151
151
  export async function storeMailboxRecord(record) {
152
152
  if (!isMailboxRecordWithinSizeLimit(record)) return false
153
- const toPubKeyHash = normalizeHex64(record.toPubKeyHash)
153
+ const toPubKeyHash = record.toPubKeyHash
154
154
  if (!isHex64(toPubKeyHash)) return false
155
155
  const hop = normalizeMailboxHop(record.hop)
156
156
  const { tier } = record
@@ -178,7 +178,7 @@ export async function storeMailboxRecord(record) {
178
178
  envelope: record.envelope,
179
179
  storedAt: Date.now(),
180
180
  expiresAt: Date.now() + ttlMs,
181
- fromNodeHash: normalizeHex64(record.fromNodeHash),
181
+ fromNodeHash: record.fromNodeHash,
182
182
  hop,
183
183
  tier,
184
184
  importance: Number.isFinite(Number(record.importance)) ? Number(record.importance) : undefined,
@@ -193,9 +193,8 @@ export async function storeMailboxRecord(record) {
193
193
  * @returns {Promise<string[]>} record id 列表
194
194
  */
195
195
  export async function listMailboxIdsForRecipient(toPubKeyHash) {
196
- const recipient = normalizeHex64(toPubKeyHash)
197
196
  return (await readAll())
198
- .filter(record => record.toPubKeyHash === recipient)
197
+ .filter(record => record.toPubKeyHash === toPubKeyHash)
199
198
  .map(record => record.id)
200
199
  }
201
200
 
@@ -224,8 +223,7 @@ export async function deleteMailboxRecords(ids) {
224
223
  * @returns {Promise<MailboxRecord[]>} 待发记录(不删除)
225
224
  */
226
225
  export async function takeMailboxForRecipient(toPubKeyHash) {
227
- const recipient = normalizeHex64(toPubKeyHash)
228
- return (await readAll()).filter(record => record.toPubKeyHash === recipient)
226
+ return (await readAll()).filter(record => record.toPubKeyHash === toPubKeyHash)
229
227
  }
230
228
 
231
229
  /**
@@ -233,9 +231,8 @@ export async function takeMailboxForRecipient(toPubKeyHash) {
233
231
  * @returns {Promise<number>} 未过期条数
234
232
  */
235
233
  export async function countMailboxPendingForRecipient(toPubKeyHash) {
236
- const recipient = normalizeHex64(toPubKeyHash)
237
234
  const now = Date.now()
238
- return (await readAll()).filter(record => record.toPubKeyHash === recipient && record.expiresAt > now).length
235
+ return (await readAll()).filter(record => record.toPubKeyHash === toPubKeyHash && record.expiresAt > now).length
239
236
  }
240
237
 
241
238
  /**