Files
dsh_shenxian/dsh-server-docs/04-调整方案/133-覆盖网络-低熵块治理方案-C域分离与D非确定性.md
T
admin d2ef362a98 feat(overlay): 覆盖网络线 序㊾ —— 探针观测面改「两台中继并集」(附 序㊽ 源码/文档补提交)
序㊾(本棒):
- scripts/overlay-probe.cjs:OBS-01 / OBS-08 / OBS-09 的数据源由「只读 47 中继」
  改为「按两台中继取并集」,消除 worker 归属漂移时的假红 / 假 SKIP
  · endpoints 以 network:hostId:port 为键合并,online 取「或」、localPort 取在线那一侧
  · used 按 network/hostId 去重计数(不求和,避免凭空放大在册数)
  · localPort 属中继机回环落点 ⇒ 按归属分机探活(106 侧落点由 106 机上探)
  · derived(OBS-11)保持 47 视角;阈值与判据一律未放宽
  · OBS-16 计数约束:对 47 /status 的读取仍为三次、Δ 只取 47 的 counters;
    对端 106 的采样为独立一次,落在第三次采样之后,不进 (status2, status3] 门窗口
  · 新增 --peer-status-fixture(并集的对端那一半)与「并集不可取证」强制留痕
- 交接单《覆盖网络-序45-低熵块治理-测熵与实现》§16 全节(§8 前前缀逐字未变)
- 参数表 §11.16 补记(§10 现算指纹未变,值格未动)

附(前几棒已完成并已部署、但尚未入仓的源码 / 文档):
- src/net/relay/content/*.ts、src/net/relay/index.ts、main.ts:块级寻址 C 域分离
- src/supervisor/orchestrator.ts、src/worker/agent.ts:日志采集与巡检(方案 C)
- test/overlay-content.test.mjs:随附用例(npm test = 200 pass / 0 fail / 1 skipped,Node 22)
- scripts/dshlog.mjs(跨机日志取证)、scripts/overlay-entropy.cjs(熵探针)
- dsh-server-docs/04-调整方案/129、133;INDEX.md / docs-manifest.json / 交接单 README 登记
2026-09-19 05:35:35 +08:00

18 KiB
Raw Blame History

覆盖网络 · 低熵块治理方案(C 域分离 + D 非确定性)

  • 版本:v1 规划稿(2026-09-18 · 规划棒 · ⛔ 零代码 / 零服务器改动)
  • 状态:📐 规划完成 · 待执行(第一个动作 = 补测「低熵块种类数 / 体积 / 占首屏包比例」,⛔ 从未测过)
  • 上游决策:用户 2026-09-18 21:28 拍板 —— 采纳 C + D;B(OPRF / SA-MLE)降级为可选加强、⛔ 本轮不立项
  • 权威来源(⛔ 不另起炉灶、⛔ 不重查文献、⛔ 不重新评估方案优劣):
    • 工作区根 调研_MLE加密去重最优方案_20260918.md(§3 六方案族 / §5 结构性发现 / §5.5 复杂度与收益)
    • .workbuddy/memory/2026-09-18.md 21:1x / 21:5x / 21:2x 三节(C / D / B 定义、量化读数、"收敛加密下持组密钥 ≠ 能解密"这一前提)
    • 阈值与判据面单一来源:工作区根 参数表_覆盖网络_20260917.md(⚠️ 现版 §10 指纹 = d408d640246a980f702fe7b0a2895219,以现算为准)
  • 执行载体:交接单/覆盖网络-序45-低熵块治理-测熵与实现.md

0. 一句话

分两件事治:C 把块 id 的收敛哈希 HA 从"裸 sha256(字节)"改成"每个 network 一把密钥的 keyed hash"(切断跨 network 的相关性推断,代价≈0);D 让低熵块不再确定性(首选 D-1「稀释域」 —— 低熵小字段与高熵内容拼在一起再加密;备选 D-2 每块随机密钥 + 密钥封装)。方案能否定量,取决于一个从未测过的读数 ⇒ 因此第一个动作不是改代码,是测熵。


1. 现状取证(源码事实 · 本轮新读)

# 事实 出处(文件:行) 含义
1 块 id = sha256(字节) 前 32 hex(只由字节决定,⛔ 无密钥) src/net/relay/content/chunker.ts:108-110(blockIdOf),BLOCK_ID_HEX_LEN = 32(同文件 :64) 这就是 C 要改的那一行;也是"相同明文 ⇒ 相同块 id ⇒ 跨节点共享"的唯一来源
2 整份内容的 id 同样 = sha256(全部落库字节) 同文件 :113-115(contentIdOf) C 必须同时改它,⛔ 只改 blockIdOf 会留下"内容指纹仍裸哈希"的缺口
3 块密钥含明文成分:iv = HMAC(key,"iv"‖plain) 前 12 B ⇒ k = HMAC(key,"k"‖iv) src/net/relay/content/crypto.ts:405-413(ivOf / keyOf) ⇒ 🔑 持组密钥 ≠ 能解密(本题的前提)。⇒ 低熵块之所以可枚举,是"猜明文 + 复算 iv/k + 比对",⛔ 不是"直接解密"
4 组密钥文件缺省 /etc/dshs/content-group-key.json 同文件 :47(DEFAULT_GROUP_KEY_FILE) C 的 per-network 密钥应从这条既有链路派生,⛔ 不新引入密钥来源
5 🔴 切分是「定长 1 MiB」(offset += blockSize 死循环) chunker.ts:137-158;DEFAULT_BLOCK_SIZE = 1024*1024(:60,⚠️ 常量而非配置) ⇒ 一份首屏包 10.8 MB ≈ 11 块,每块是 1 MiB 混合内容。⇒ 🔑 首屏包里的"纯低熵块"在结构上几乎不可能存在(除非整整 1 MiB 都是低熵)
6 块存储 = 纯内存,调用方没给 dir src/web/server.ts:909-911(new ContentStore({ maxBytes }),无 dir);runtime.ts:347-350 同 ⇒ 块不落盘 ⇒ "低熵块"无法从生产盘上"捞"出来 ⇒ 测熵只能对"真实内容"离线做
7 快照上限 = CONTENT_STORE_MAX_BYTES 64 MiB 参数表_覆盖网络_20260917.md:147 内存预算独立(⛔ 不挤 relay 的 MEM_PER_HOST_MB)⇒ D 若引入"合并加密",是否增加常驻字节要在这笔账里算

1.1 🔴 本轮最重要的结构发现(⛔ 尚无任何文档记录 · ⚠️ 待实测确认)

由事实 5 直接推出两条,方向相反,共同决定 D 的边界:

  1. 对"首屏包"而言,D-1 几乎是免费的 —— 定长 1 MiB 意味着低熵小字段已经被同块的高熵内容稀释了。⇒ D-1 在首屏包上代价≈0、收益≈0(本来就不暴露)。
  2. 🔴 D 的真实战场不在首屏包,而在「小于 1 MiB 的独立内容」 —— 任何整体小于 1 MiB 的一份内容只会切成 1 个块;若该内容本身低熵(配置 / 状态 / 清单 / 密钥交换载荷类),它就是一块纯低熵块 ⇒ 中继可数相等性、持钥者可枚举。

⇒ 推论(决定待测项的形状):测熵必须同时覆盖两个集合,只测首屏包会得出"没有低熵块"的假绿结论。 ⚠️ 本条为源码推导,⛔ 不是实测 ⇒ 待测项 M1 的第一条就是要证实或推翻它。


2. 第一个动作:补测「低熵块的种类数 / 体积 / 占首屏包比例」(M1)

2.1 为什么必须放第一个(⛔ 不是"顺手做的取证")

  • D 的边界由它决定 —— "什么算低熵、稀释单元取多大、要不要合并"全是它的函数(调研…§6 阻碍 4 已点明「什么算低熵本身无可靠判据」)。
  • 它从未被测过,而且历史上所有"低熵"读数都是合成字节 —— 三条硬证据:
# 证据 出处 说明
a perf 用的是 makeBytes(PACK_BYTES, SEED) _tmp_seq32/p01-perf.mjs:42 合成 11,363,655 B 缓冲,⛔ 不是真首屏包 —— 对 perf(尺寸驱动)无害,对熵统计致命
b 低熵定性读数用 blk-0000… 8 B 合成块、域 1000 _tmp_seq32/p03-lowentropy.txt;引用见 参数表_覆盖网络_20260917.md:617-622 结论是定性的("可枚举"),⛔ 从未给出"真实内容里低熵占多少"
c 块存储纯内存、不落盘 §1 事实 6 ⇒ 生产侧没有任何可回捞的低熵块样本

2.2 待测项定义(可判定 · 落 参数表 §11.3 补记区)

度量对象(两个集合,⛔ 都用真实内容)

  • S1 = 真实首屏包:门户 GET <portal>/plugins/(= 既有口径的 11,363,655 B)。⚠️ 只读 GET,⛔ 不重启、⛔ 不写盘。
  • S2 = 真实"独立小内容"集:覆盖网络实际分发的、非首屏的内容(⛔ 由执行棒先枚举来源并留痕;若确实不存在 ⇒ 报 SKIP + 说清"不存在",⛔ 不许用合成字节填充)。

方法(本机离线 · 纯函数 · 零生产触碰)

用代码常量 DEFAULT_BLOCK_SIZE(= 1 MiB)复刻 chunkify 的定长切分,再对每块统计。⚠️ 必须调用仓库里那份 chunkify(⛔ 不复刻一份算法 —— 复刻=双源,本线明令禁止)。

指标(每条都可机器断言)

编号 指标 定义 期望落点
M1-a 种类数 / 重复率 唯一块 id 数 ÷ 总块数;并给出完全重复块(同 id 出现 ≥ 2 次)的清单 数字
M1-b 低熵块数 / 体积 逐块经验 Shannon 熵(字节分布,bit/byte),阈值初值 H ≤ 4.0(⚠️ 阈值本身是待定项,须在报告里给出 H 的直方图,⛔ 不给单一阈值当结论) 块数 + 字节数 + 直方图
M1-c 占首屏包比例 低熵块字节 ÷ 首屏包字节 百分数(这就是"能不能定量"的那个数)
M1-d 🔑 反向腿 · 子窗口熵 整块高熵 ≠ 块内无低熵字段:按滑窗(如 4 KiB)扫整份内容,给出"低熵窗口"的尺寸分布 尺寸分布(决定 D-1 的稀释单元粒度)

🔴 M1-d 是防假绿的关键腿:只做整块统计,会因为 §1.1 事实 5 得出"全是高熵块"的假绿 —— 而那恰恰漏掉了低熵字段真实存在的形态(它们是块内的一小段)。本条同时同时交叉验证 §1.1 的两条推论。

落点与边界

  • 先可落 _tmp_seq*/ 一次性脚本取证,证据齐后固化为只读探针 scripts/overlay-entropy.cjs(⛔ 零第三方依赖,只 node:crypto + node:fs)。
  • 读数进 参数表_覆盖网络_20260917.md §11.3 补记区(⚠️ 属验收读数,⛔ 不进 §10 指纹口径 —— 沿用序㉛ 的既定做法,见该表 :506)。
  • ⛔ 不动 §10 指纹(除非同时新增判据键,那时按 §3.3 一并处理)。

3. 方案 C —— 域分离(per-network keyed hash)

3.1 改什么

把内容寻址的两个哈希从"裸哈希"改成"带 per-network 密钥的 keyed hash":

现状 目标 落点
blockIdOf(bytes) = sha256(bytes)[0:32] blockIdOf(bytes, netKey) = HMAC-SHA256(netKey, bytes)[0:32] chunker.ts:108-110
contentIdOf(bytes) = sha256(bytes)[0:32] contentIdOf(bytes, netKey) = HMAC-SHA256(netKey, bytes)[0:32] chunker.ts:113-115

🔑 密钥来源:复用既有组密钥链路从 network 维度派生(crypto.ts:47 的组密钥文件 + 既有的 network 标识),⛔ 不新增密钥文件、不新增 env。⚠️ per-network 密钥的派生写法(HMAC(groupKey, "overlay-block-id"‖network) 之类)属技术实现 ⇒ 由执行棒自决,⛔ 不上抛。

3.2 治什么 / 不治什么(口径必须写死)

  • ✅ 治:跨 network 的 COF / LRI / 离线枚举;中继从"同一块 id 同时出现在 A / B 两个 network"推出的跨租户相关性。
  • 🔴 不治:同一 network 内部持钥者枚举(85,878 条/秒 原样存在)⇒ C 是低成本加分项,⛔ 不是 §10-3 的解。
  • ✅ 代价≈0 的依据:跨 network 本来就不该去重(不同租户 / 不同覆盖网络)⇒ 去重域的收窄踩在不值得保留的地方。

3.3 🔴 C 的真实代价:块 id 口径换代(这是本方案最重的一笔)

  • 块 id 一变 ⇒ 全部既有块 id 失效(缓存全放弃、去重率归零重算)⇒ 直接触发组密钥单 §9-5 的回头条件。
  • ⇒ ⚠️ C 不能"悄悄上",必须与 E1 判据的基线重置同时做(见 §5)。
  • ⚠️ 换接口形状也要改:blockIdOf 从 1 参变 2 参 ⇒ 所有调用点(store.ts 校验路径、chunker.ts 内部、装配层)都要一起改。⇒ 执行前必须先出"调用点清单"(⛔ 漏一处 = 校验必红且极难定位)。

4. 方案 D —— 低熵块非确定性

4.1 首选 D-1「稀释域」合并加密

做法:把低熵小字段与高熵内容拼在一起再加密 ⇒ 明文域从 100 扩到 100 × 2³² ⇒ 不可枚举。 代价:这批块的去重失效(低熵块去重本无价值 ⇒ 代价踩在不值得保留的地方);块 id 口径不变(⚠️ 与 C 的方向相反 —— C 换 id,D-1 只换"这批块加不加密、怎么加密")。

🔴 D-1 的定义式设计约束(本方案必须写死,否则实现会做错):稀释源必须是"攻击者猜不到"的那个量。

  • 因为本题的攻击面是"持组密钥者 · 猜明文 + 复算"(§1 事实 3)⇒ 若稀释源是攻击者能自己算出来的量(如"由组密钥确定性派生的每块盐"、或块序号),稀释对持钥者完全无效(他能复算出同一份密文)。
  • ⇒ 稀释源必须包含真随机且不出现在明文可见面的字节;⚠️ 该随机量的存放位置(密文内 / 密钥封装体里 / 块外层)是技术实现,执行棒自决 —— 但判据必须能区分它是不是真随机(见 §5 判据面)。

⚠️ 与 §1.1 的联动:若 M1 证实"首屏包内不存在纯低熵块",D-1 在首屏包上的作用面 ≈ 0 ⇒ D 的实际工作范围将收窄到 S2(独立小内容),⛔ 那时不许为了"让 D 有用"而把块切小(切小 = 块数暴涨 = 控制面开销与回源字节都恶化 ⇒ 净变差,触 R11)。

4.2 备选 D-2 每块随机密钥 + 密钥封装

做法:彻底非确定性 —— 每块一把随机密钥,密钥随内容封装(用组密钥包裹)。 代价:该块不去重;新增密钥封装配送这一层(=新的出错面)。 定位:仅在 D-1 的稀释源无法满足 §4.1 约束时启用 —— 即"低熵内容确实是独立成块且没有任何高熵内容可与它同批"时。


5. 判据面 / 影响面 / 验收

5.1 判据面(⚠️ 这是"改完凭什么算改对")

项 要求
新增 OBS 行 现有 OBS 编号已到 OBS-28(参数表 第 264-270 行一带)⇒ 本单新增自 OBS-29 起,⛔ 不得跳号、不得复用
新增阈值键 若 D 需断言"稀释源是真随机"⇒ 需新增键(形如 CONTENT_ENTROPY_* / CONTENT_DILUTE_*)。⚠️ 键名与取值由执行棒定,⛔ 不上抛
🔴 必须有的负腿 「稀释源被换成确定性派生量 ⇒ 判据必红」—— ⛔ 否则"上了个无效的稀释"会全绿(本线老病根:装了但没生效 = 静默放行)

🆕 序㊻ 修订(2026-09-18 22:4x · 执行棒回填 · ⛔ 不改本方案的技术口径,只改判据面)

① D 本轮不实现(依据 = 序㊺ 实测:真实首屏包 23 块0 个低熵块、60 份真实独立小内容0 份低熵 ⇒ D-1 无对象可稀释、D-2 前提不成立)。⇒ 上表那条负腿失去对象(无稀释源可换)。 ② OBS-29 已重裁成 C 的判据(编号仍在 OBS-29,⛔ 不跳号不复用): 正腿 = 同字节 + 不同 network ⇒ 块 id 不同(且两侧都 ≠ 裸哈希);正腿 = 同 network + 同字节 ⇒ 块 id 相同(⛔ 只测前者会漏"去了重"); 装配面腿 = store 两处复算 + 重组位全过(⇒ 调用点无漏改); 🔴 负腿(真跑、具名) = 去掉 per-network 维度(域密钥取同值 / 取空)⇒ 谓词必红(flat-key-collapses / empty-key-falls-back-to-bare-hash)。 ③ E1 基线按 §5 的要求重取(C 换了 id 口径 ⇒ 旧读数作废);⚠️ E1 的定义不重估。 ④ §3.3 的口径不变:块 id 换代 ⇒ 缓存全清、去重率归零重算 —— ⛔ 回滚也不是无损。 | E1 基线重置 | C 换 id 口径 ⇒ CONTENT_TIER_HITS_MIN 等命中类阈值的前提变了;⚠️ E1 本身(回源字节 ≈ 1 份 × 组数)不重估,但基线读数必须重取(⛔ 不许拿旧读数当对照组) | | OBS-17 口径一致腿 | 该行断言 content.blockSize == CONTENT_BLOCK_SIZE 且 content.storeMaxBytes == CONTENT_STORE_MAX_BYTES(参数表:418)⇒ 若 D 引入额外常驻字节,需在此处补列,⛔ 不许绕过 |

5.2 影响面(执行前必须先出清单)

面 改动量级 是否触"超过 10 文件先出清单"
src/net/relay/content/chunker.ts 小(两个函数 + 调用点) 否
chunker.ts / store.ts / 装配层的 blockIdOf 调用点 待清点(⚠️ 数量未知 ⇒ 清点后若 > 10 ⇒ 先出清单再动) ⚠️ 可能触
src/net/relay/content/crypto.ts 中(D-1/D-2 的加密路径) 否
scripts/overlay-probe.cjs 小(新增 OBS) 否
参数表_覆盖网络_20260917.md 小(§11.3 + §6 判据行)|⚠️ §10 指纹会变(新增键才变) 否
生产 env 🔴 预期为 0 个新增 —

5.3 验收(三段式,沿用本线既有做法)

  1. 离线:M1 读数落盘 + 熵直方图 + 探针自检(夹具模式封闭 ⇒ ⛔ 不 ssh)。
  2. 本机:npm test(Node 22)全绿 + OBS-29 正腿绿、负腿红。
  3. 真机:/status 的 content 块 —— ⚠️ 只读,⛔ 不重启在线服务;⚠️ http2 on 环境 curl 必须 --http1.1。

5.4 回滚

  • 代码:C / D 必须是可独立回退的两处开关(C 的密钥可"传空 ⇒ 回落裸哈希";D 可"不启用稀释 ⇒ 回落确定性")⇒ ⛔ 不许做成"上了就下不来"。
  • 口径:C 回退后块 id 会再变一次(⇒ 缓存再清一次)—— ⚠️ 这条要写进回滚说明,⛔ 不许默认"回滚 = 无损"。
  • 判据:参数表 回退到本文件所记的指纹 d408d640246a980f702fe7b0a2895219(⚠️ 以现算为准)。

6. 边界(本方案不做的事)

  • ⛔ 不立项 B(OPRF / SA-MLE)—— 用户已拍板降级为可选加强;其接口位(Manager 做 KS)保留为将来可插,⛔ 本轮不写任何代码。
  • ⛔ 不做"按熵分层加盐"那条老路(它必须同时动块 id 与去重率 ⇒ 比 C+D 更重,调研…§5.5 已量化)。
  • ⛔ 不改 DEFAULT_BLOCK_SIZE(切小 = 净变差 ⇒ 触 R11)。
  • ⛔ 不引第三方依赖、⛔ 不动 package.json、⛔ 不改生产实例 / ⛔ 不重启在线服务(本轮规划棒另加:⛔ 零代码、⛔ 零服务器触碰)。
  • ⛔ 合规 / 数据主权不进本方案(口径:方案只做技术实现)。

7. 参考

  • 调研_MLE加密去重最优方案_20260918.md —— §3 方案族全景表 / §5.3 域分离 / §5.4 三选项 / §5.5 复杂度与收益 / §6 六条阻碍
  • .workbuddy/memory/2026-09-18.md —— 21:1x(三不兼得)/21:5x(文献印证 + 域分离发现)/21:2x(C/D/B 对比与"持钥者 ≠ 能解密")
  • 参数表_覆盖网络_20260917.md —— :146(CONTENT_BLOCK_SIZE)/:147(CONTENT_STORE_MAX_BYTES)/:418(OBS-17)/:506(§11.3 补记区用法)/:617-622(§10-3 低熵现状定性)
  • 源码 —— chunker.ts:60/64/108-115/137-158|crypto.ts:47/405-413|store.ts:1-40(模块定位与"不做加解密"边界)|server.ts:909-911|runtime.ts:347-350