@steve02081504/fount-p2p 0.0.21 → 0.0.23
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.
- package/core/entity_id.mjs +5 -5
- package/core/logical_entity.mjs +1 -1
- package/core/random_id.mjs +4 -2
- package/crypto/crypto.mjs +26 -93
- package/files/manifest_fetch.mjs +17 -10
- package/package.json +2 -1
package/core/entity_id.mjs
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { Buffer } from 'node:buffer'
|
|
2
|
-
|
|
3
1
|
import { pubKeyHash } from '../crypto/crypto.mjs'
|
|
4
2
|
|
|
3
|
+
import { hexToBytes } from './bytes_codec.mjs'
|
|
5
4
|
import {
|
|
6
5
|
encodeEntityHash,
|
|
7
6
|
ENTITY_HASH_RE,
|
|
@@ -26,9 +25,10 @@ export {
|
|
|
26
25
|
*/
|
|
27
26
|
export function hashFromPubKeyHex(pubKeyHex) {
|
|
28
27
|
const hex = normalizeHex64(pubKeyHex)
|
|
29
|
-
if (!isHex64(hex)
|
|
30
|
-
|
|
31
|
-
|
|
28
|
+
if (!isHex64(hex)) throw new Error('invalid pubKeyHex')
|
|
29
|
+
const bytes = hexToBytes(hex)
|
|
30
|
+
if (bytes.length !== 32) throw new Error('invalid pubKeyHex')
|
|
31
|
+
return pubKeyHash(bytes)
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
/**
|
package/core/logical_entity.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { sha256TextHex } from '../crypto/crypto.mjs'
|
|
2
2
|
|
|
3
|
-
import { encodeEntityHash, parseEntityHash } from './
|
|
3
|
+
import { encodeEntityHash, parseEntityHash } from './entity_id_parse.mjs'
|
|
4
4
|
|
|
5
5
|
/** @type {string} 逻辑实体 sentinel nodeHash(非物理节点绑定) */
|
|
6
6
|
export const LOGICAL_ENTITY_SENTINEL_NODE_HASH = '0'.repeat(64)
|
package/core/random_id.mjs
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* 跨端随机 ID(Web Crypto / Node globalThis.crypto)。
|
|
3
|
+
*/
|
|
2
4
|
|
|
3
5
|
/**
|
|
4
6
|
* @param {string} prefix ID 前缀(如 channel_)
|
|
5
7
|
* @returns {string} 带前缀的随机 ID
|
|
6
8
|
*/
|
|
7
9
|
export function prefixedRandomId(prefix) {
|
|
8
|
-
return `${prefix}${randomUUID().replace(/-/g, '')}`
|
|
10
|
+
return `${prefix}${globalThis.crypto.randomUUID().replace(/-/g, '')}`
|
|
9
11
|
}
|
package/crypto/crypto.mjs
CHANGED
|
@@ -1,105 +1,65 @@
|
|
|
1
|
-
import { Buffer } from 'node:buffer'
|
|
2
|
-
import {
|
|
3
|
-
createHash,
|
|
4
|
-
createPrivateKey,
|
|
5
|
-
createPublicKey,
|
|
6
|
-
randomBytes,
|
|
7
|
-
sign as nodeSign,
|
|
8
|
-
verify as nodeVerify,
|
|
9
|
-
} from 'node:crypto'
|
|
10
|
-
|
|
11
|
-
/** 固定 DER 头(非秘密),让 Node 把 32 字节种子当成私钥 */
|
|
12
|
-
const PKCS8_RAW_SEED_PREFIX = Buffer.from('302e020100300506032b657004220420', 'hex')
|
|
13
|
-
/** 固定 DER 头(非秘密),让 Node 把 32 字节当成公钥 */
|
|
14
|
-
const SPKI_RAW_PUB_PREFIX = Buffer.from('302a300506032b6570032100', 'hex')
|
|
15
|
-
|
|
16
1
|
/**
|
|
17
|
-
*
|
|
18
|
-
* @returns {import('node:crypto').KeyObject} Node 私钥对象
|
|
2
|
+
* 跨端 Ed25519 / SHA-256(Node 与浏览器共用;不依赖 node:crypto)。
|
|
19
3
|
*/
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
key: Buffer.concat([PKCS8_RAW_SEED_PREFIX, Buffer.from(seed32).subarray(0, 32)]),
|
|
23
|
-
format: 'der',
|
|
24
|
-
type: 'pkcs8',
|
|
25
|
-
})
|
|
26
|
-
}
|
|
4
|
+
import { ed25519 } from '@noble/curves/ed25519.js'
|
|
5
|
+
import { sha256 } from '@noble/hashes/sha2.js'
|
|
27
6
|
|
|
28
|
-
|
|
29
|
-
* @param {Uint8Array|Buffer} rawPublicKey 32 字节公钥
|
|
30
|
-
* @returns {import('node:crypto').KeyObject} Node 公钥对象
|
|
31
|
-
*/
|
|
32
|
-
function publicKeyFromRaw(rawPublicKey) {
|
|
33
|
-
return createPublicKey({
|
|
34
|
-
key: Buffer.concat([SPKI_RAW_PUB_PREFIX, Buffer.from(rawPublicKey).subarray(0, 32)]),
|
|
35
|
-
format: 'der',
|
|
36
|
-
type: 'spki',
|
|
37
|
-
})
|
|
38
|
-
}
|
|
7
|
+
import { bytesToHex, toBytes } from '../core/bytes_codec.mjs'
|
|
39
8
|
|
|
40
9
|
/**
|
|
41
|
-
* @param {Uint8Array|
|
|
42
|
-
* @returns {Uint8Array}
|
|
10
|
+
* @param {Uint8Array|ArrayBufferView|ArrayBuffer|string} data 字节或 UTF-8 文本
|
|
11
|
+
* @returns {Uint8Array} 规范化字节
|
|
43
12
|
*/
|
|
44
|
-
function
|
|
45
|
-
|
|
46
|
-
return
|
|
13
|
+
function inputBytes(data) {
|
|
14
|
+
if (typeof data === 'string') return new TextEncoder().encode(data)
|
|
15
|
+
return toBytes(data)
|
|
47
16
|
}
|
|
48
17
|
|
|
49
18
|
/**
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
* @param {Uint8Array|Buffer} seed 任意长度;非 32 字节时 sha256 派生
|
|
19
|
+
* @param {Uint8Array|ArrayBufferView|ArrayBuffer} seed 任意长度;非 32 字节时 sha256 派生
|
|
53
20
|
* @returns {{ publicKey: Uint8Array, secretKey: Uint8Array }} 32 字节私钥与对应公钥
|
|
54
21
|
*/
|
|
55
22
|
export function keyPairFromSeed(seed) {
|
|
56
|
-
const u =
|
|
57
|
-
const sk = u.length === 32 ? u :
|
|
58
|
-
return { publicKey:
|
|
23
|
+
const u = toBytes(seed)
|
|
24
|
+
const sk = u.length === 32 ? u : sha256(u)
|
|
25
|
+
return { publicKey: ed25519.getPublicKey(sk), secretKey: new Uint8Array(sk) }
|
|
59
26
|
}
|
|
60
27
|
|
|
61
28
|
/**
|
|
62
|
-
* 随机生成密钥对
|
|
63
|
-
*
|
|
64
29
|
* @returns {Promise<{ publicKey: Uint8Array, secretKey: Uint8Array }>} 随机密钥对
|
|
65
30
|
*/
|
|
66
31
|
export async function randomKeyPair() {
|
|
67
|
-
const sk =
|
|
32
|
+
const sk = new Uint8Array(32)
|
|
33
|
+
globalThis.crypto.getRandomValues(sk)
|
|
68
34
|
return keyPairFromSeed(sk)
|
|
69
35
|
}
|
|
70
36
|
|
|
71
37
|
/**
|
|
72
|
-
* 由 32 字节私钥种子导出对应公钥
|
|
73
38
|
* @param {Uint8Array} secretKey 私钥种子
|
|
74
39
|
* @returns {Uint8Array} 公钥
|
|
75
40
|
*/
|
|
76
41
|
export function publicKeyFromSeed(secretKey) {
|
|
77
|
-
return
|
|
42
|
+
return ed25519.getPublicKey(toBytes(secretKey).subarray(0, 32))
|
|
78
43
|
}
|
|
79
44
|
|
|
80
45
|
/**
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
* @param {Uint8Array} message 待签名消息字节
|
|
46
|
+
* @param {Uint8Array|ArrayBufferView|ArrayBuffer|string} message 待签名消息
|
|
84
47
|
* @param {Uint8Array} secretKey 私钥(取前 32 字节为种子)
|
|
85
48
|
* @returns {Promise<Uint8Array>} 64 字节签名
|
|
86
49
|
*/
|
|
87
50
|
export async function sign(message, secretKey) {
|
|
88
|
-
|
|
89
|
-
return new Uint8Array(sig)
|
|
51
|
+
return ed25519.sign(inputBytes(message), toBytes(secretKey).subarray(0, 32))
|
|
90
52
|
}
|
|
91
53
|
|
|
92
54
|
/**
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
* @param {Uint8Array}
|
|
96
|
-
* @param {Uint8Array} message 原始消息字节
|
|
97
|
-
* @param {Uint8Array} publicKey 公钥
|
|
55
|
+
* @param {Uint8Array|ArrayBufferView|ArrayBuffer} signature 64 字节签名
|
|
56
|
+
* @param {Uint8Array|ArrayBufferView|ArrayBuffer|string} message 原始消息
|
|
57
|
+
* @param {Uint8Array|ArrayBufferView|ArrayBuffer} publicKey 公钥
|
|
98
58
|
* @returns {Promise<boolean>} 合法为 true;异常或失败为 false
|
|
99
59
|
*/
|
|
100
60
|
export async function verify(signature, message, publicKey) {
|
|
101
61
|
try {
|
|
102
|
-
return
|
|
62
|
+
return ed25519.verify(toBytes(signature), inputBytes(message), toBytes(publicKey))
|
|
103
63
|
}
|
|
104
64
|
catch {
|
|
105
65
|
return false
|
|
@@ -107,52 +67,25 @@ export async function verify(signature, message, publicKey) {
|
|
|
107
67
|
}
|
|
108
68
|
|
|
109
69
|
/**
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
* @param {Uint8Array} publicKey 公钥字节
|
|
70
|
+
* @param {Uint8Array|ArrayBufferView|ArrayBuffer} publicKey 公钥字节
|
|
113
71
|
* @returns {string} 64 字符 hex
|
|
114
72
|
*/
|
|
115
73
|
export function pubKeyHash(publicKey) {
|
|
116
|
-
return
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* 公钥 sha256 摘要
|
|
121
|
-
*
|
|
122
|
-
* @param {Uint8Array} publicKey 公钥字节
|
|
123
|
-
* @returns {Buffer} 32 字节 digest
|
|
124
|
-
*/
|
|
125
|
-
function hashPubKeyBytes(publicKey) {
|
|
126
|
-
return createHash('sha256').update(publicKey).digest()
|
|
74
|
+
return bytesToHex(sha256(toBytes(publicKey)))
|
|
127
75
|
}
|
|
128
76
|
|
|
129
77
|
/**
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
* @param {Uint8Array|Buffer|string} data 字节或 UTF-8 文本
|
|
78
|
+
* @param {Uint8Array|ArrayBufferView|ArrayBuffer|string} data 字节或 UTF-8 文本
|
|
133
79
|
* @returns {string} 64 字符 hex
|
|
134
80
|
*/
|
|
135
81
|
export function sha256Hex(data) {
|
|
136
|
-
|
|
137
|
-
return createHash('sha256').update(input).digest('hex')
|
|
82
|
+
return bytesToHex(sha256(inputBytes(data)))
|
|
138
83
|
}
|
|
139
84
|
|
|
140
85
|
/**
|
|
141
|
-
* UTF-8 文本 SHA-256 十六进制(小写、无 0x 前缀)。
|
|
142
|
-
*
|
|
143
86
|
* @param {string} text 文本
|
|
144
87
|
* @returns {string} 64 字符 hex
|
|
145
88
|
*/
|
|
146
89
|
export function sha256TextHex(text) {
|
|
147
90
|
return sha256Hex(String(text ?? ''))
|
|
148
91
|
}
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Buffer / Uint8Array → 小写 hex 字符串
|
|
152
|
-
*
|
|
153
|
-
* @param {Uint8Array|Buffer} buffer 二进制缓冲
|
|
154
|
-
* @returns {string} 小写十六进制文本
|
|
155
|
-
*/
|
|
156
|
-
function bufferToHexSimple(buffer) {
|
|
157
|
-
return Buffer.from(buffer).toString('hex')
|
|
158
|
-
}
|
package/files/manifest_fetch.mjs
CHANGED
|
@@ -26,8 +26,8 @@ const manifestInflight = createInflightTable({
|
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
28
|
* 拉取公开 manifest;默认不写盘。`cache: true` 或 `cachePublicManifest` 才缓存。
|
|
29
|
-
* 本地已有 publicSig
|
|
30
|
-
*
|
|
29
|
+
* 本地已有 publicSig 时立即返回,并后台 fanout;`cache: true` 时按 publishedAt 择新写盘。
|
|
30
|
+
* 冷 miss 仍等 fanout。同 key in-flight 去重;调用方外层超时不 abort,后台继续填缓存。
|
|
31
31
|
* @param {{ username: string, ownerEntityHash: string, logicalPath: string, cache?: boolean, timeoutMs?: number }} context - 拉取上下文
|
|
32
32
|
* @returns {Promise<import('./manifest.mjs').FileManifest | null>} 验签后的 manifest,失败为 null
|
|
33
33
|
*/
|
|
@@ -42,6 +42,7 @@ export async function fetchPublicManifest(context) {
|
|
|
42
42
|
: DEFAULT_MANIFEST_FETCH_TIMEOUT_MS
|
|
43
43
|
const expectedKey = manifestFetchExpectedKey(ownerEntityHash, logicalPath)
|
|
44
44
|
const inflightKey = `${username}\0${expectedKey}`
|
|
45
|
+
const wantCache = context.cache === true
|
|
45
46
|
|
|
46
47
|
// 先挂 in-flight(同步),再读本地 — 避免并发调用在 await 间隙各自 start
|
|
47
48
|
const localPromise = loadFileManifest(ownerEntityHash, logicalPath)
|
|
@@ -67,15 +68,21 @@ export async function fetchPublicManifest(context) {
|
|
|
67
68
|
const hasLocalPublic = local?.transferKeyDescriptor?.type === 'public' && !!local?.meta?.publicSig
|
|
68
69
|
if (!shared) return hasLocalPublic ? local : null
|
|
69
70
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
71
|
+
if (hasLocalPublic) {
|
|
72
|
+
// SWR:立刻回本地;后台 fanout 择新写盘(需 cache: true)
|
|
73
|
+
if (wantCache) {
|
|
74
|
+
void shared.then(async result => {
|
|
75
|
+
if (result && shouldPreferIncomingPublicManifest(local, result))
|
|
76
|
+
await cachePublicManifest(ownerEntityHash, logicalPath, result)
|
|
77
|
+
})
|
|
78
|
+
}
|
|
79
|
+
return local
|
|
76
80
|
}
|
|
77
|
-
|
|
78
|
-
|
|
81
|
+
|
|
82
|
+
const result = await shared
|
|
83
|
+
if (!result) return null
|
|
84
|
+
if (wantCache) await cachePublicManifest(ownerEntityHash, logicalPath, result)
|
|
85
|
+
return result
|
|
79
86
|
}
|
|
80
87
|
|
|
81
88
|
/**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@steve02081504/fount-p2p",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.23",
|
|
4
4
|
"description": "fount federation P2P layer — link, trust graph, mailbox, DAG, EVFS.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"network",
|
|
@@ -71,6 +71,7 @@
|
|
|
71
71
|
},
|
|
72
72
|
"dependencies": {
|
|
73
73
|
"@noble/curves": "latest",
|
|
74
|
+
"@noble/hashes": "latest",
|
|
74
75
|
"node-rtc-connection": "latest",
|
|
75
76
|
"on-shutdown": "latest",
|
|
76
77
|
"ws": "latest"
|