交接单按归属约定(正文落文档库、工作区只放指针)归档至 归档/交接单-20260924-归档/,含逐件判定 README: - 16 件已被文档库正式版取代(T09–T21 + 覆盖网络-24/25/26) - 4 件主题已被覆盖网络线入口汇总 - 7 件历史接续包/规划件 CODEBUDDY §9:收口清本棒 tmp、tmp 保留期 7 天、禁「待清理」中间态、 工作区入库只放文档与文件、不保留脚本副本、>60 KB 单文件须逐个判。
725 lines
69 KiB
Markdown
725 lines
69 KiB
Markdown
# 交接单 · 组密钥加密(块级共享与机密性兼得)
|
||
|
||
- **序号**:覆盖网络线 **序 ㉘ · 规划棒 → 单 B**(执行棒待跑)
|
||
- **立单**:2026-09-18 00:5x
|
||
- **用户拍板**:**「选 C 组密钥加密」**(2026-09-17 23:2x,原话;⚠️ 该档位在 23:3x 的更正轮里**明确未变**)
|
||
- **上游依据**:入口 §4 用户口径「**按照连接稳定高效的方式 数据安全可加密传输**」|`交接单_骨干稳定选路与加密_20260917.md`(§4 加密属真取舍、已登记待评估)|`交接单_内容分发块级寻址_20260917.md`(序 ㉔ 块级内容寻址已落地)
|
||
- **归档号**:**130**(占号动作已执行,见 §11;⛔ 63 是空号、不补占)
|
||
- **状态**:**待执行**
|
||
|
||
> **本单与 `交接单/README.md §二` 8 段模板的对应**:§1 目标|§2 只读前置|§3 范围|§4 步骤|§5 验收(模板 6)|§6 回滚(模板 7)|**§7 决策点 + 影响面**|**§8 回报格式(留空给执行棒)**|§9 回头条件|§10 未验证项|§11 指纹与状态。
|
||
|
||
---
|
||
|
||
## §0 摘要
|
||
|
||
### 0.1 摘要(一句话)
|
||
|
||
**给内容面加一层"组密钥"对称加密**:同一个「内容组」共享一把对称密钥 ⇒ **组内密文逐字节一致** ⇒ 序 ㉔ 的「按哈希共享块」**仍然成立**(去重与同组 peer 命中不退化),同时**中继进程读不到明文载荷**。⇒ 相对「按接收者 E2E」(牺牲性能)与「保持现状」(牺牲对中继的机密性),本方案**两者兼得**。
|
||
|
||
### 0.2 🔴 本棒一条**必须纠正的前人前提**(取证得出,⛔ 请以本棒为准)
|
||
|
||
> 「块 id 用**明文哈希** ⇒ **跨组也能共享同一块**」 —— 这句在上游讨论里被当作明文哈希的**主要优点**。**本棒取证判定:在本方案(组密钥加密)下它不成立。**
|
||
|
||
理由(两条,均为可读源码事实):
|
||
1. **序 ㉔ 的跨组共享今天本就是被显式拒绝的** —— `src/net/relay/content/peer.ts:64-72` 的 `sameGroup()` 是"跨组"的**唯一**判据处,配套计数器 `crossGroupDenied`;验收 `E5` 的口径就是"**跨组 0 穿透且 `crossGroupDenied ≥ 1`**"(参数表 §6 `OBS-17`)。⇒ 明文哈希**拿不到**一个今天不存在的收益。
|
||
2. 🔑 **加密之后,"共享密文"这件事本身失去意义** —— 组密钥是**组作用域**的 ⇒ 组 B 即便取到组 A 的密文块,**没有组 A 的密钥就解不开**(强行用组 B 密钥解 ⇒ 校验失败)。⇒ 所谓"跨组共享同一块"只共享了**一堆解不开的字节**。
|
||
|
||
⇒ **结论**:明文哈希的"跨组共享"优点在本方案下**净收益 ≈ 0**,而它的缺点(中继可对已知明文做**存在性确认**)仍在 ⇒ **本单推荐"密钥相关/密文相关哈希"**(见 §7.3,⛔ 该处是真取舍,已给逐项优缺点与推荐理由)。
|
||
|
||
---
|
||
|
||
## §1 目标
|
||
|
||
内容面(`src/net/relay/content/**` 及其装配点)新增**组密钥加密层**,满足三条:
|
||
|
||
1. **组内密文一致**:同一组、同一明文块,任意节点加密后**密文逐字节相同**(`md5` 相等);
|
||
2. **共享不退化**:同组内第二次取块命中 `local` 或 `peer` 档(⛔ 不许退化成"次次回源");
|
||
3. **中继看不到明文载荷**:relay 进程持有的任何持久/日志/`/status` 里**不含明文块字节**,且**不持有可解密的密钥**。
|
||
|
||
**可判定的"做完了没有"**:§5 的 **F1–F6** 全绿 —— 特别是 **F1(同组密文一致)+ F2(命中 `local`/`peer`)+ F3(跨组不可解)+ F4(明文不出现,且**反向夹具证明 F4 有效**)+ F5(`E1` 主判据不退化)**。
|
||
|
||
---
|
||
|
||
## §2 只读前置(执行前必须先核实,**逐条给命令与期望输出**)
|
||
|
||
> 全部只读;⛔ 不改任何文件、不改任何生产值。**任一条与期望不符 ⇒ 停下报告。**
|
||
|
||
| # | 核实内容 | 命令 | 期望输出 |
|
||
|---|---|---|---|
|
||
| 1 | **组定义现状**(能否复用,⛔ 不新造) | `cd D:/github/dsh_shenxian && grep -n -A3 "export function groupKeyOf" src/net/relay/content/peer.ts; grep -n "CONTENT_GROUP\|DSHS_CONTENT_GROUP" src/web/server.ts src/net/relay/main.ts` | 组键 = **`<network>\|<group>`**(分隔符 `\|`,`:61`)|平台侧缺省 `'local'`(`web/server.ts`)、relay 侧 `'relay'`(`main.ts`)—— **刻意分开**(防"两个进程误以为同组")。⇒ **"组" = (网, 组) 二元组,与序 ㉔ 同构 ⇒ 直接复用。** |
|
||
| 2 | **块 id 算法现状**(本单取舍的起点) | `grep -n -A3 "export function blockIdOf" src/net/relay/content/chunker.ts` | `sha256(字节).slice(0, 32)` = **对明文**哈希(⚠️ 若已变成密钥相关 ⇒ **本单已被别人做过** ⇒ 停下报告)。 |
|
||
| 3 | **现有加密面(本线尚无内容层加密)** | `grep -rniE "encrypt\|cipher\|gcm\|aes\|chacha" src/net/relay/ --include=*.ts \| wc -l` | **`0`**(本棒实测)。⇒ 加密层是**纯新增**,⛔ 不改既有行为。 |
|
||
| 4 | **密钥模型(四层,⛔ 不许引第二套)** | `sed -n '14,30p' src/net/relay/identity.ts; grep -n "asymmetricKeyType !== 'ed25519'" src/net/relay/identity.ts; ls -l /etc/dshs/overlay-signer-key.pem`(47) | 四层 = **根(离线,只授权/撤销签名者)→ 签名者(在线,47)→ 节点密钥(每机一把 `0600`)→ 会话(内存)**|🔴 **节点密钥 = `ed25519`**(`identity.ts:197/318`,纯签名、**不能做公钥加密**)⇒ **这是 §7.2 分发方案的决定性约束**|签名者文件在 47 上存在。 |
|
||
| 5 | **既有对称封装(实现底座,⛔ 不新造算法栈)** | `sed -n '1,10p' src/crypto.ts` | **AES-256-GCM** + `deriveKey(secret) = sha256(secret)`(用于用户凭据 at-rest)。⇒ 本单的算法栈**照此复用**(同为 AES-256-GCM)。 |
|
||
| 6 | **中继是否能看到块 id 集合**(决定 §7.3 取舍有无现实意义) | `grep -n "PEER_COUNTER_KEYS\|declarations\|withdrawn" src/net/relay/content/peer.ts; sed -n '200,215p' src/net/relay/main.ts` | 计数键含 **`declarations` / `withdrawn`** ⇒ 声明走 relay 消息 ⇒ 🔴 **relay 进程持有 `ContentPeerGroup` 注册表,能看到"哪些块 id 存在"。** ⇒ §7.3 的取舍**有现实意义**(不是理论问题)。 |
|
||
| 7 | **基线三件套 + 参数表指纹** | `cd D:/github/dsh_shenxian && npm.cmd test 2>&1 \| tail -5`;`node scripts/overlay-probe.cjs --table`;`sed '/^## §10 指纹/,$d' "E:/ProgramData/AI技能/aliyun-dsh-server/参数表_覆盖网络_20260917.md" \| md5sum` | `npm test` = **201 pass / 200 / 0 fail / 1 skip**|`overlay-probe --table` = **19 PASS / 1 SKIP / 1 FAIL**(1 FAIL = `OBS-21`,**线内在册缺口、⛔ 非本单引入**)|参数表指纹 = **`7fc5889341b99fb26bd05cad0313960b`**(本棒 00:4x 现核)。⛔ 判据一律用**现核值**,本单写的是快照。 |
|
||
| 8 | **`OBS` 编号现核**(防撞号) | `grep -oE 'OBS-[0-9]+' scripts/overlay-probe.cjs \| sort -u \| tail -1` | 本棒实测 = **`OBS-21`** ⇒ 本单预留 **`OBS-23`**(`OBS-22` 已分配给**单 A**);⚠️ 若执行时已被占用 ⇒ **顺延**(序 ㉖ 踩过编号被前序占用)。 |
|
||
|
||
---
|
||
|
||
## §3 范围
|
||
|
||
### 3.1 在册文件集
|
||
|
||
| 面 | 文件 | 说明 |
|
||
|---|---|---|
|
||
| **新增** | `src/net/relay/content/crypto.ts` | 组密钥装载(`0600` 文件)+ 加解密 + 组内确定性(收敛)加密 + epoch 管理 |
|
||
| 改动 | `src/net/relay/content/chunker.ts` | 块 id 口径(⛔ **仅在 §7.3 选定 β 时需要**;选 α 则**一行不改**) |
|
||
| 改动 | `src/net/relay/content/store.ts` | `put`/`get` 接入密文口径 + 新增 `decryptRejected` 计数 |
|
||
| 改动 | `src/net/relay/content/source.ts` | 五档链路的取回结果统一解密 + 失败留痕 |
|
||
| 改动 | `src/net/relay/content/peer.ts` | 同组判定与 epoch 一致性(⛔ **仍只认 `sameGroup`,不放松跨组**) |
|
||
| 改动 | `src/net/relay/content/runtime.ts` | 装配组密钥(缺省不启用 ⇒ 行为不变) |
|
||
| 改动 | `src/net/relay/main.ts` | relay 侧装配(第 5 条 `contentRuntime`,`:208`) |
|
||
| 改动 | `src/web/server.ts` | 平台侧装配(`contentSource`/`contentPeers` 一带) |
|
||
| 改动 | `scripts/overlay-probe.cjs` | 新增 `OBS-23` |
|
||
| 改动 | `test/overlay-content.test.mjs` | 新增 F 组用例(⛔ 不改 `package.json` test 列表结构) |
|
||
| 改动 | `参数表_覆盖网络_20260917.md` | §3.6 新增加密键 + §6 新增 `OBS-23` |
|
||
| 改动 | `scripts/overlay-keyring.cjs` | 扩展"组密钥"签发/验签子命令(复用既有仪式 CLI) |
|
||
|
||
### 3.2 ⛔ 不动什么(防顺手扩大)
|
||
|
||
- ⛔ **不引入第二套密钥体系** —— 不新根、不新签名链;组密钥的**授权方仍是既有签名者**(见 §7.2)。
|
||
- ⛔ 不动 `wire.ts` 的帧格式与既有消息号(新增消息须另立单);⛔ 不动 `keys.ts` / `identity.ts` 的**既有**语义(只**扩展**)。
|
||
- ⛔ 不动 `RELAY_FAILOVER_*` / `HB_SEC` / burst / `switcher.ts` 的冷却语义。
|
||
- ⛔ **不放松跨组** —— `crossGroupDenied` 必须保持"跨组 0 穿透"(`E5` 不得退化)。
|
||
- ⛔ 不改加密**传输层**(TLS 由 nginx 终结,已是现状)—— 本单只做**内容载荷层**。
|
||
- ⛔ 不新增公网监听口、不改 `nft` / `nginx`(**R5**)。
|
||
- ⛔ 不改任何**生产值**(**D1**);⛔ 不 commit / 不 push(**R7**)。
|
||
- ⛔ **不许顺手做**:房间层 / 打洞实现(本线明令不做)。
|
||
|
||
---
|
||
|
||
## §4 步骤(每步自带一次可执行的验证)
|
||
|
||
> 三条硬门贯穿全程:**D1**|**R5**|**R7**。
|
||
|
||
### S0 · 开工对账
|
||
- 跑 §2 全部 8 条并**原样留档**。不符 ⇒ 停下报告。
|
||
- 抢锁:`bash "D:/github/dsh_shenxian/dsh-server-docs/scripts/handoff-guard.sh" --claim-exec "覆盖网络线-序28单B执行棒"`;**抢不到 ⇒ 只报告并停**(**R9**)。
|
||
- **验证**:guard 返回「✓ 已持全局执行锁」。
|
||
|
||
### S1 · 组密钥的**定义与装载**(复用四层模型,⛔ 零新信任根)
|
||
- **密钥形状**:`groupKey = 32 字节随机对称密钥`(与 `src/crypto.ts` 的 AES-256-GCM 口径一致);随密钥记录 **`epoch`**(单调递增整数)。
|
||
- **装载**:`src/net/relay/content/crypto.ts` 从一个 `0600` 文件读 `{ group, epoch, key(base64) }`;**文件缺失 / 权限不对 / 组名不匹配 ⇒ 一律"不启用加密"并记一行判别器日志**(⛔ 不静默降级成"以为加密了")。
|
||
- **授权**:由**既有签名者**(47,`/etc/dshs/overlay-signer-key.pem`)用**既有签名载荷**签发 `(group, epoch, keyId)` **三元组**(**⛔ 载荷内不含密钥本体**);节点用**既有验签链**校验 ⇒ 认下当前 epoch。
|
||
- **验证**:`node scripts/overlay-keyring.cjs <子命令>` 生成的凭据能被**既有** `directory.ts`/`identity.ts` 的验签路径接受(rc=0);篡改 `epoch` ⇒ **验签失败**(失败关闭)。
|
||
|
||
### S2 · 组内确定性(收敛)加密 —— **本单的技术核心**
|
||
- 目标:**同组 + 同明文 ⇒ 同密文**(否则"按哈希共享块"直接失效)。
|
||
- 做法(三行即可说清):`k_block = HKDF/HMAC(组密钥, 明文块)` 派生**块级密钥**;`IV = HMAC(组密钥, 明文块) 的前 12 字节`(**确定性、不留随机数**);`密文 = AES-256-GCM(k_block, IV, 明文)`;**认证标签**一并存(篡改即读侧丢弃)。
|
||
- ⚠️ **必须同时落 AAD**:把 `(group, epoch)` 绑进 AAD ⇒ 跨 epoch / 跨组的密文**在认证阶段就被拒**(⛔ 不靠"解出来是乱码"来判)。
|
||
- **验证(先红后绿)**:对同一明文加密 **2 次** ⇒ 密文 `md5` **相等**;换 `epoch` ⇒ 密文**不等**(且旧 epoch 密文用新密钥解 ⇒ **认证失败**)。
|
||
|
||
### S3 · 块 id 口径(⛔ §7.3 选定的那一项)
|
||
- **推荐项(β':密文哈希)**:`blockId = sha256(密文).slice(0,32)` —— 因为 S2 已保证组内密文一致 ⇒ 组内 id **稳定**、组外 id **不可预测**。
|
||
- **验证(先红后绿)**:同组两次独立"切块 + 加密" ⇒ 块 id 序列**逐字一致**;换组密钥 ⇒ 全部 id **改变**(这是"轮换 ⇒ 全量回源"的**直接证据**,必须留档,不得粉饰)。
|
||
|
||
### S4 · `store` / `source` / `peer` 接入
|
||
- `store.put/id/get` 全程只处理**密文**(读回后 S2 解密 + 认证);`get` 认证失败 ⇒ **丢弃 + `decryptRejected += 1`**(⛔ 不静默返空)。
|
||
- `source.ts` 五档链路的**解密位置统一**(⛔ 不许"local 档不解密、peer 档解密"这种双口径)。
|
||
- `peer.ts`:**仍只认 `sameGroup`**;新增 **epoch 一致性**:声明 `(group, epoch)`,epoch 不一致**不视为同组可用**(⇒ 轮换过渡期由 S5 处理)。
|
||
- **验证**:`test/overlay-content.test.mjs` 原 27 例**不退化**;新增 F 组用例(§5)。
|
||
|
||
### S5 · 轮换与过渡态
|
||
- **新成员获取**:见 §7.2(**密钥本体不经 relay**)。
|
||
- **成员退出 / 例行轮换**:签名者签新 `epoch` ⇒ 节点加载**新密钥**(**旧 epoch 保留为"仅解不写"**)⇒ 过渡窗口内新旧密文都能解;**写入一律用新 epoch**。
|
||
- **过渡窗口必须上界**:参数化(建议 `CONTENT_EPOCH_GRACE_MS`,⚠️ 值格 ⛔ 必须纯数字,不得夹注 —— 夹注会被读成 `NaN` ⇒ 假红)。
|
||
- **验证**:新旧 epoch 混存时,两种密文都能解;超窗口后旧 epoch 密文 ⇒ `decryptRejected += 1` 且**点名**。
|
||
|
||
### S6 · 判据(`OBS-23` + 夹具 + **先红后绿**)
|
||
- 夹具四段(照序 ㉔ `--content-fixture` 的做法):**OFF**(无加密键)/**ON**(同组)/**CROSS**(跨组)/**ROT**(双 epoch)。
|
||
- `OBS-23` 三判据(见 §5):① 加解密计数键齐全(⛔ 缺一即 FAIL)② 同组密文一致 + 命中 `local`/`peer` ③ 明文不出现(对已知明文标记做字节级断言)。
|
||
- **真机先红**:新 `OBS-23` 对**旧生产** ⇒ **FAIL**(并打印可区分证据)。
|
||
- **验证**:四段夹具的判定与预期**逐段一致**,且 OFF/CROSS/ROT 三段的 FAIL/PASS **互不混淆**。
|
||
|
||
### S7 · 部署 + 零回归 + 收口
|
||
- `npm run build` ⇒ 铺到**四处**(47 `/opt/dshs/lib` + `/opt/dsh-relay/lib`、106 `/opt/dshs-cluster/lib` + `/opt/dsh-relay/lib`)—— 🔴 **relay 类产物必须两边都铺**(序 ㉖ 踩过"`dshs` 侧落后一个版本")。
|
||
- 部署前建回滚点 `/opt/dsh/backups/seq28-<ts>/`(旧 md5 记录在 §8)。
|
||
- ⚠️ **密钥文件与 env 的铺发**属运维动作:落地前先确认**密钥文件权限 = `0600`、属主正确**(⚠️ 本线史上有"属主被改 ⇒ 实例全崩"的先例)。
|
||
- 重启 `dshs-relay`(两台)+ `dshs`(47)+ `dshs-worker`(106)(**R8**:动手前一句话说明)。
|
||
- 零回归:`npm.cmd test`|`--scene all --table` = **12 PASS / 0 SKIP / 0 FAIL**|`--table` = 基线 +1 行(`OBS-21` 仍 FAIL = 线内在册缺口)。
|
||
- 回填 §8;推进 `接续入口_覆盖网络线_20260916.md` §0 + §2;写工作区日志(append-only)。
|
||
|
||
---
|
||
|
||
## §5 验收判据(**可机器断言、可被第三方复现**)
|
||
|
||
| # | 判据 | 命令 / 口径 | 期望 | 为什么必须有它 |
|
||
|---|---|---|---|---|
|
||
| **F1** | **同组密文一致**(本单技术核心) | 夹具 ON:同一明文块独立加密 **2 次** ⇒ `md5` 比对 | **相等** | 不等 ⇒ "按哈希共享块"直接失效(序 ㉔ 的 `E1` 崩) |
|
||
| **F2** | **共享不退化** | 夹具 ON:第一次取块后第二次取块 ⇒ 两次 `/status` 的 `content.store.hits` / `content.peer.peerHits` **增量 ≥ 1** | 命中 `local` 或 `peer` | 只判 F1 不够 —— 加密对了但"次次回源"会全绿(**这正是序 ㉔ `OBS-17` 判据③ 存在的理由**) |
|
||
| **F3** | **跨组不可解** | 夹具 CROSS:组 B 用自己密钥解组 A 的密文 | **认证失败** + `decryptRejected` **+1** + `crossGroupDenied` **不减少** | 保证"加密 ≠ 放松隔离";`E5`(跨组 0 穿透)不得退化 |
|
||
| **F4** | 🔴 **明文不出现**(+**反向证明 F4 有效**) | ① 用**唯一标记串**写一个块 ⇒ 在 **relay 进程可见面**(持久存储 / `journalctl` / `/status`)做字节级搜索 ⇒ **命中 0**;② **反向夹具**:关闭加密(OFF 段)⇒ 同一搜索 **必须命中 ≥ 1** | ① `0` ② **`≥ 1`** | 🔴 **只判 ① 是假绿风险** —— "没测到"和"真没有"必须可分(本线两处静默失效都栽在这里)。⇒ **② 是 ① 的有效性证明,不可省** |
|
||
| **F5** | **`E1` 主判据不退化** | 序 ㉔ 原口径:`N=4` 同组回源字节数 | **≤ 1 份 × 组数**(序 ㉔ 收口值 = **1.0000×**,⛔ 不许变成 4.00×) | 加密的最大风险就是"把块级共享打回去" |
|
||
| **F6** | **失败关闭(⛔ 不静默降级)** | 密钥缺失 / 权限错 / 组名不配 / epoch 超窗口 四种情形各跑一次 | 每种 **都**有**具名**判别器留痕 + 计数 +1;⛔ **不许**出现"以为加密了其实没加" | 本线反复复发的那类病("配错了"伪装成"不通") |
|
||
| **F7** | **零回归** | `npm.cmd test`|`--scene all --table`|`--table` | 不退化(§2-7 现核基线 + 探针 +1 行) | — |
|
||
|
||
### `OBS-23` 的形状(进 `scripts/overlay-probe.cjs`;编号须现核顺延)
|
||
- **三判据**:① `content.crypto` 计数键**齐全且皆 `number`**(⛔ 缺一即 FAIL —— 这正是"静默放行"的机器判据)② **F1 + F2** ③ **F4①**(明文不出现)。
|
||
- ⛔ **缺行即 FAIL**,并把 `journalctl` 的**原文字节数**一并打出(`0 字节` = 读取失败/`>0` = 真没有该行,**二者必须可分**)。
|
||
- ⚠️ **值格必须是纯数字**(参数表规则)—— 加密相关键里凡"模式名"性质的一律**只作信息键**,⛔ 不进数值判据。
|
||
|
||
---
|
||
|
||
## §6 回滚
|
||
|
||
**回滚 = 关掉加密层(缺省即关闭)⇒ 秒级,且不丢数据。**
|
||
|
||
### 路 A · 配置回滚(推荐,秒级)
|
||
- 组密钥文件**移除 / 改名**(或把启用开关置回缺省)⇒ `crypto.ts` 走"不启用"分支 ⇒ 行为**逐字回到**序 ㉔ 现状。
|
||
```bash
|
||
ssh -p 22 bt-server 'mv /etc/dshs/content-group-key.json /etc/dshs/content-group-key.json.off && systemctl restart dshs-relay dshs'
|
||
ssh -p 22 test106 'mv /etc/dshs/content-group-key.json /etc/dshs/content-group-key.json.off && systemctl restart dshs-relay dshs-worker'
|
||
```
|
||
- **验收**:`overlay-probe --table` 的 `OBS-23` 从 PASS → **SKIP 或 FAIL-并点名"未启用"**;`npm test` 不退化。⚠️ **`OBS-23` 的"未启用"必须是 SKIP 还是 FAIL 要在 S6 就定死**(建议 **SKIP + 留痕**,因为"缺省不启用"是合法状态;⛔ 但**不许**既不是 SKIP 也不是 FAIL 的"静默绿")。
|
||
- ⚠️ **已有的密文块会解不开** ⇒ 回滚后这些块按"认证失败"被丢弃(`decryptRejected +1`)⇒ **清一次 `CONTENT_STORE_MAX_BYTES` 的缓存目录**即可(属可逆操作:块能从 origin 重取)。
|
||
|
||
### 路 B · 产物回滚(秒级)
|
||
```bash
|
||
ssh -p 22 bt-server 'cp /opt/dsh/backups/seq28-<ts>/*.js /opt/dshs/lib/net/relay/content/ && cp /opt/dsh/backups/seq28-<ts>/relay/*.js /opt/dsh-relay/lib/net/relay/content/ && systemctl restart dshs-relay dshs'
|
||
```
|
||
- **回滚点**:`/opt/dsh/backups/seq28-<ts>/`(**S7 部署前创建**,旧 md5 记入 §8)。⛔ **没有回滚点不准上线。**
|
||
|
||
### 🔴 回滚路径硬禁令
|
||
⛔ **不许把 `RELAY_FAILOVER_COOLDOWN_MS=0` 写进任何回滚 / 演练 / 夹具路径** —— 它会**自锁**(实测 **121–123 s 无切换**);判定见 §9-1。
|
||
|
||
---
|
||
|
||
## §7 决策点 · 取舍 · 影响面
|
||
|
||
### 7.1 决策点
|
||
- **已定(用户)**:加密档位 = **C 组密钥加密**(2026-09-17 23:2x,23:3x 未变)。
|
||
- **已定(本棒,可推翻)**:
|
||
- 「组」定义 = **复用序 ㉔ 的 `(network, group)`**(依据 §2-1,`peer.ts:61`),⛔ 不新造。
|
||
- 算法栈 = **AES-256-GCM**(依据 §2-5,与既有 `src/crypto.ts` 同源),⛔ 不引 ChaCha 等第二栈。
|
||
- 探针编号 = **`OBS-23`**(`OBS-22` 归单 A;执行时现核顺延)。
|
||
- **待定 = 空**。⛔ 本单无上抛项。
|
||
|
||
### 7.2 密钥从哪来(**复用四层模型,⛔ 零新信任根**)
|
||
|
||
**复用/扩展的对应关系**(照 `identity.ts:14-30` 的四层表逐层落位):
|
||
|
||
| 四层原本 | 本单如何复用/扩展 |
|
||
|---|---|
|
||
| **根(离线)** | **不动** —— 它只授权/撤销"签名者",⛔ **不签发组密钥**(守住既有用途边界) |
|
||
| **签名者(在线,47)** | **扩展职责**:原"签发节点入网凭据" ⇒ 追加"**签发组密钥凭据 `(group, epoch, keyId)`**"。**同一把钥匙、同一条签名链、同一个验签入口**。 |
|
||
| **节点密钥(每机一把)** | **不动**(仍是 `ed25519`,仍只做签名/身份)。🔴 **注意**:`ed25519` **不能做公钥加密**(`identity.ts:197/318` 硬校验)⇒ 见下方"分发" |
|
||
| **会话(内存)** | **扩展**:原本只标"隧道传输加密" ⇒ 追加"**内容组加密**"这一档:`组密钥(落盘 0600 + 内存)` |
|
||
|
||
**分发(本阶段)**:
|
||
- ✅ **密钥本体不经网络、不经 relay** —— 由运维按既有 `0600` 惯例落文件(与节点密钥同处 `/etc/dshs/`),链路 = 既有部署通道(`scp` / drop-in)。理由:① 节点密钥是 `ed25519`,**做不了公钥包裹**;② 任何"经 relay 传密钥"的做法都等于**把密钥交给中继**,前功尽弃。
|
||
- ✅ 签名者只**广播** `(group, epoch, keyId)` **三元组**(⛔ 不含密钥),节点据此确认"当前 epoch 是几",防"版本错配"。
|
||
- ⚠️ **规模化的自动分发 ⇒ 登记为回头条件**(§9-7):届时需给节点密钥**并行加一把 `x25519`**(或做 Ed25519→X25519 派生)才能上"公钥包裹"。⛔ **本单不做**(那是新增密钥类型,属另立单)。
|
||
|
||
### 7.3 🔴 本单最关键的取舍:块 id 用**明文哈希**还是**密钥相关哈希**
|
||
|
||
> 两者**竖排成段**,逐项列「优点 / 缺点」。⛔ 不横排、⛔ 不做成表格的列。
|
||
|
||
**选项 α —— 块 id = 明文哈希(`sha256(明文块)`,⛔ 序 ㉔ 现状一行不改)**
|
||
|
||
优点:
|
||
- 序 ㉔ 的 `chunker.ts` / `OBS-17` / 既有 `E1` 判据**零改动**,改动面最小;
|
||
- **轮换组密钥不改变块 id** ⇒ 轮换**不触发全量回源**(这是它唯一真实的、且不小的优点)。
|
||
|
||
缺点:
|
||
- 🔴 **它承诺的"跨组共享同一块"在本方案下不成立**(§0.2 已取证:组外没有组密钥 ⇒ 拿到密文也解不开;且 `E5` 本就要求跨组 0 穿透)⇒ 该"优点"**净收益 ≈ 0**;
|
||
- 🔴 **把"哪些块存在 + 内容指纹"暴露给中继**:relay 进程持有 `ContentPeerGroup` 注册表(§2-6 实测)⇒ 中继可用**已知明文**对 id 做**存在性确认**("这个包在这张网里存在吗")⇒ 与用户"数据安全"的口径**直接相悖**;
|
||
- 明文 id ↔ 密文存储还要额外维护一张映射 ⇒ **中继侧多一张可被探测的索引表**(更糟)。
|
||
|
||
**选项 β' —— 块 id = 密钥相关/密文相关哈希(`sha256(密文)`,因 S2 已保证组内密文一致)**
|
||
|
||
优点:
|
||
- 🔴 **组外不可见**:中继只看到**不透明 id**,连"是否存在该块"都判不出来(**这正是"数据安全"要的那一半**);
|
||
- **组内完全兼容**:S2 的确定性加密已保证"同组 + 同明文 ⇒ 同密文" ⇒ 组内 id 稳定 ⇒ **按哈希共享块仍然成立**(**完全满足用户选 C 档位的原意**);
|
||
- 与 `E5`(跨组 0 穿透)**天然一致** —— 它不损失任何**今天存在**的收益。
|
||
|
||
缺点:
|
||
- 🔴 **轮换组密钥 ⇒ 全部块 id 改变 ⇒ 一组一次全量回源**(正面撞序 ㉔ 主判据 `E1`);
|
||
- 签名目录 / 索引必须携带 `epoch`,否则新旧 id 无法共存(⇒ S5 的双 epoch 过渡态**不可省**);
|
||
- 组外**永久**失去块级复用(⚠️ 但见 §0.2:这在加密之后本就无意义)。
|
||
|
||
**我的推荐 = β'(密钥相关/密文相关哈希)**,理由三条:
|
||
1. **用户要的是"中继看不到明文载荷"** —— α 虽然也加密了载荷,但**仍把块存在性与内容指纹交给中继**,属"做了一半";
|
||
2. **α 的"跨组共享"优点在本方案下已被取证判定为不成立**(§0.2)⇒ α 剩下的**只有缺点** ⇒ 按判据("只有缺点的候选自己拍掉")**不选 α**;
|
||
3. β' 是**唯一**同时满足"组内共享成立 + 组外不可见"的选项,且与既有 `E5` 语义**同向**。
|
||
⚠️ **β' 的代价必须如实接受并写进参数**:**轮换 = 一次全量回源** ⇒ 轮换频率要有**下限**(参数化)+ 且**优先安排在版本发布窗口**(那时本来就要重灌)⇒ §9-8 回头条件。
|
||
|
||
### 7.4 影响面与关系
|
||
|
||
- **影响面**:**内容面(首屏包分发)** —— 只影响 `local`/`peer`/`edge`/`region`/`origin` 五档链路的**字节形态**与**块 id**;⛔ **不影响**实例生命周期、路由、切流、`dsh_hosts`、lease、端口分配(与单 A **完全无交集**,两单可并行但**不共用文件**)。
|
||
- **与 presence / room 层**:无交集。
|
||
- **与 `E1`**:见 F5;β' 下**轮换窗口**是唯一会让 `E1` 短暂退化的时刻。
|
||
|
||
---
|
||
|
||
## §8 回报格式(**留空给执行棒**)
|
||
|
||
> 逐节回填,每节必须给**命令 + 原始输出(或原文摘录)+ 判定**。⛔ 不许只写结论。
|
||
|
||
> **本棒身份**:`覆盖网络线-单B执行棒-组密钥加密`(automation `65d2fc24-770e-4f33-93a3-db6e739aded7`)· 一次性、无人值守。
|
||
> **时间**:开工 `2026-09-18 06:22`(抢锁成功)→ 收口 `2026-09-18 07:2x`。
|
||
> **证据落盘**:全部原始输出在 `E:\ProgramData\AI技能\aliyun-dsh-server\_tmp_seq31\`(下文各节标注文件名)。
|
||
|
||
### 8.1 开工对账(§2 八条)
|
||
|
||
**锁**:`bash D:/github/dsh_shenxian/dsh-server-docs/scripts/handoff-guard.sh --claim-exec "覆盖网络线-单B执行棒-组密钥加密"` ⇒ **`✓ 已持全局执行锁`**(06:22,一次抢到,⛔ 无等待重试)。
|
||
|
||
| # | 核实内容 | 实测(原文摘录) | 判定 |
|
||
|---|---|---|---|
|
||
| 1 | 组定义现状 | 组键 = **`<network>`\|`<group>`**(`peer.ts#groupKeyOf`);平台侧缺省 `'local'`(`web/server.ts`)、relay 侧 `'relay'`(`main.ts`) | ✅ 与期望一致 ⇒ **组 = (network, group) 二元组,直接复用**(⛔ 未新造) |
|
||
| 2 | 块 id 算法现状 | `blockIdOf = sha256(字节).slice(0,32)`,**对明文** | ✅ 开工时为 **α(明文哈希)** ⇒ 本单**未被别人做过**,可继续 |
|
||
| 3 | 现有加密面 | `grep -rniE "encrypt\|cipher\|gcm\|aes\|chacha" src/net/relay/ --include=*.ts \| wc -l` = **0** | ✅ 加密层**纯新增** |
|
||
| 4 | 密钥模型 | 四层:根(离线)→ 签名者(47 `/etc/dshs/overlay-signer-key.pem`,`0600`)→ 节点密钥(每机 `0600`)→ 会话内存;节点密钥 = **`ed25519`**(`identity.ts:197/318`) | ✅ 与期望一致;🔴 **ed25519 纯签名、不能公钥加密** ⇒ 决定"密钥本体只能走文件/scp" |
|
||
| 5 | 既有对称封装 | `src/crypto.ts` = **AES-256-GCM** + `deriveKey = sha256(secret)` | ✅ 本单算法栈照此复用(⛔ 未引第二栈) |
|
||
| 6 | 中继能看到块 id 集合 | `peer.ts` 计数键含 `declarations` / `withdrawn`;relay 进程持 `ContentPeerGroup` 注册表 | ✅ ⇒ §7.3 的取舍**有现实意义** |
|
||
| 7 | 基线三件套 + 参数表指纹 | `npm.cmd test` ⇒ **201 tests / 200 pass / 0 fail / 1 skip**(与单内基线逐字同);`overlay-failover-drill --scene all` ⇒ **12 PASS / 0 SKIP / 0 FAIL**;`overlay-probe --table` ⇒ ⚠️ 开工那一刻**演练正在跑**(relay 被反复起停)⇒ **18 PASS / 1 SKIP / 3 FAIL**(红 = `OBS-01`/`OBS-04`/`OBS-08`,**演练暂态**);参数表 §10 指纹现核 = **`3b295a8c43aafc4a62c6f1a59ea0b43a`** | ⚠️ 探针开工值**不是**单内快照的 `19P/1S/1F` —— 原因是"序㉙ 已把 `OBS-21` 转绿(22P 稳态)"+"开工时刻撞上演练暂态(3 红)";**收口前的稳态基线 = 22 PASS / 0 SKIP / 0 FAIL**(`S7-probe-1.txt` 实测)⇒ **本棒目标 = 23P/0S/0F** |
|
||
| 8 | `OBS` 编号现核 | `grep -oE 'OBS-[0-9]+' scripts/overlay-probe.cjs \| sort -u \| tail -1` = **`OBS-22`** | ✅ **`OBS-23` 未被占用**,按单内预留使用(⛔ 未顺延) |
|
||
|
||
**判定**:八条**全部相符** ⇒ 未触发"停下报告";三条硬门(D1 / R5 / R7)自开工起全程有效(见 8.11)。
|
||
|
||
---
|
||
|
||
### 8.2 组密钥凭据(签名者签发 + 验签通过 + 篡改必失败)
|
||
|
||
**① 生成组密钥(本机)** —— `node scripts/overlay-keyring.cjs init-group-key --group relay --epoch 1 --out _tmp_seq31/content-group-key.json`(`init-group-key.txt`):
|
||
```
|
||
✓ 组密钥文件:E:/ProgramData/AI技能/aliyun-dsh-server/_tmp_seq31/content-group-key.json(0600)
|
||
✓ group=relay epoch=1 keyId=3560130eb0e5e4aa(⚠️ 密钥本体只在文件里,⛔ stdout 不打印)
|
||
```
|
||
- 文件六键 = `version / network / group / epoch / key(44 B base64 = 32 B) / previous([])`;`keyId = sha256(key) 前 16 hex`。
|
||
- ✅ **stdout 只出 keyId**(⛔ 无密钥本体、⛔ 无 base64 片段)。
|
||
|
||
**② 签名者签发三元组(47,既有签名者)** —— 命令口径 `sign-group-key --signer-key /etc/dshs/overlay-signer-key.pem --network ops --group relay --epoch 1`。凭据原文(`group-key-credential.json`,**274 B**):
|
||
```json
|
||
{ "doc": { "version":1, "network":"ops", "group":"relay", "epoch":1,
|
||
"keyId":"3560130eb0e5e4aa", "issuedAt":"2026-09-17T22:54:40.439Z" },
|
||
"sig": "I2KOPpUMntkR1cNuXpR0zpUIayVeLRuEtPjRxym71Dh6vlkHB9vadDQPfVrMH+6bgX6fC8ipekdSuRjm76VtAg==" }
|
||
```
|
||
- 🔴 **载荷内不含密钥本体**(逐字核):`凭据含 "key" 字段 = False`、`凭据含密钥片段(key 前16字符) = False` ⇒ 三元组就是三元组。
|
||
- **公钥 / keyId**:签名者公钥 = `bad464dfd53048efe7b8531029b3030eda49bc12930703d3a2a8f60e3a7daddf`(47 `/etc/dshs/overlay-signer-key.pem.pub`)⇒ 与**既有**信任表 `/etc/dshs/overlay-signers.json` 的 `signers[0]` **逐字一致**(⛔ 未新根、⛔ 未新信任链);组 `keyId` = `3560130eb0e5e4aa`。
|
||
|
||
**③ 验签(`S8-8.2.txt`)**:
|
||
```
|
||
### 正例(原文凭据)
|
||
✓ 验签通过:network=ops group=relay epoch=1 keyId=3560130eb0e5e4aa rc=0
|
||
### 篡改例(epoch 1→2,sig 不变)
|
||
✗ 验签失败:signature-mismatch rc=1
|
||
### 交叉核对失败例(epoch 传 9)
|
||
✗ ⛔ epoch 不配:凭据 epoch=1 ≠ 传入 9 rc=1
|
||
```
|
||
- **判定**:✅ 签发 ⇒ 验签通过;✅ **篡改 epoch 必失败(失败关闭)**;✅ 凭据不载荷密钥本体。
|
||
|
||
---
|
||
|
||
### 8.3 确定性加密(**同明文两次 ⇒ 密文 md5 相等**;换 epoch ⇒ 不等且旧密文认证失败)
|
||
|
||
**F1(`S7-f-evidence.txt`)**:
|
||
```
|
||
明文 md5 = 24d6ed6ce80d7311db80fc63c67bf49a (3776 B)
|
||
密文#1 md5 = 3385e9d00ae5c5dbb880ddad90391756 (3804 B)
|
||
密文#2 md5 = 3385e9d00ae5c5dbb880ddad90391756 (3804 B)
|
||
⇒ 逐字节相同 = true | 长度 = 明文 + 28(iv 12 + tag 16)
|
||
⇒ keyId = 5ed7f6b62a053af1(夹具口径)| groupKey = ops|relay epochs=1
|
||
```
|
||
- 派生口径(实现):`iv = HMAC(sha256,key,"iv"‖plain)[:12]`、`k = HMAC(sha256,key,"k"‖iv)`、`AAD = "<network>|<group>|<epoch>"`(⇒ 解密侧只需 `iv` 即可复原 `k`,故**确定性**成立)。
|
||
|
||
**F1-b(换 epoch)**:
|
||
```
|
||
epoch=2/keyB 密文 md5 = 3cab76cda498790fa4ba1329b3f0f53a ⇒ 与 epoch=1 不同 = true
|
||
用 epoch=2 解 epoch=1 的密文 ⇒ undefined(认证失败)|该 cipher 计数 decrypts=0 decryptRejected=1
|
||
用本 epoch 解自己写的 ⇒ 还原一致 = true |decrypts=1
|
||
```
|
||
- **判定**:✅ 同组 + 同明文 ⇒ **密文逐字节相同**("按哈希共享块"的前提成立);✅ 换 epoch ⇒ 不等 + 旧密文**在认证阶段被拒**(⛔ 不是"解出乱码")。
|
||
|
||
---
|
||
|
||
### 8.4 块 id 口径 + 轮换代价的**原始读数**(⛔ 不得粉饰)
|
||
|
||
**β′ 口径(采单内推荐项)**:`blockId = sha256(密文).slice(0,32)`(落地为前 16 hex 展示)。原始读数(`S7-f-evidence.txt`):
|
||
```
|
||
块数 = 4(块大小 1024 B)
|
||
α ids(明文哈希) = 1d9e81b64e2fc8a2 3c929675a21f4ac9 9c43515ef7057734 28d85d5bd0b6c6f0
|
||
β′ ids(密文哈希) = 9fc1bb4b8d0ec432 a8f5dea758c1d709 66f4e823723fae3c ebe427b90b28ce55
|
||
⇒ α 与 β′ 逐位不同 = true
|
||
⇒ β′ 两次独立切块一致 = true(组内稳定 ⇒ 按哈希共享块成立)
|
||
⇒ 换组后 β′ 全变 = true
|
||
⇒ contentId(整份):α = 1cd1b222c4eb46dd7203538baa1497e8 | β′ = 1fd60cf381a79bea8f6ae4a0cefe3152
|
||
```
|
||
- 🔴 **轮换代价(如实留档,⛔ 不粉饰)**:**换组密钥(或换 epoch)⇒ 该组 100% 块 id 改变 ⇒ 全员一次全量回源**。这是 β′ 的**固有代价**,不是缺陷;本棒**未测得"轮换后首轮回源字节数"**(§10-2 未验证项 ⇒ 记 8.13)。
|
||
- ⚠️ 与 `E1` 的关系:**稳态下**不退化(见 8.9 F5);**唯一退化时刻 = 轮换窗口**(单 §7.4 已预告)⇒ 轮换须排进发布窗口(§9-8)。
|
||
|
||
---
|
||
|
||
### 8.5 `store` / `source` / `peer` 接入(含 `decryptRejected` 计数)
|
||
|
||
| 面 | 落法 | 证据 |
|
||
|---|---|---|
|
||
| `store` | 只改**文档**(`put`/`id`/`get` 全程只处理**密文**字节,⛔ 未改既有语义) | 单测 `F1-d`:缺省(不传 transforms)⇒ 与序㉔ **逐字一致** |
|
||
| `source` | **唯一解密点** = `crypto.decodeBlock`;调用位 ① `source.ts` 五档链的**统一返回点** ② `chunker.ts#reassemble` 的夹具/工具路径(⛔ 无"local 档不解密"的双口径) | `F2`:`put → 链上取回两次 ⇒ local 命中递增且解密成功`(`local=6`) |
|
||
| `peer` | **仍只认 `sameGroup`**;新增 `epoch` 一致性:epoch 不一致 **不作候选** + **单独计数**(⛔ 不混进 `crossGroupDenied`) | `F3-b` / `F3-c` 两例 ✅;`crossGroupDenied` 语义**未变** |
|
||
| 计数 | 十键常量 `CONTENT_CRYPTO_COUNTER_KEYS = encrypts / decrypts / decryptRejected / epochs / epoch / epochExpired / detChecks / detMismatches / plainScans / plainLeaks` | 探针 `OBS-23` 判据① **判别器齐全**(⛔ 缺一即 FAIL) |
|
||
|
||
- **`decryptRejected` 读数**:夹具 CROSS 段 = **1**(跨组被拒);夹具 ROT-EXPIRED 段 = **1**(超窗口被拒);🔴 **线上稳态 = 0**(`/status.content.crypto` 实测 `decrypts:3, decryptRejected:0`)⇒ 未触发 §9-6 回头条件。
|
||
- ✅ `F4-c`:**不启用加密 ⇒ `crypto` 键整体缺席**(⛔ 不是补零)⇒ 探针才能如实记 `SKIP`。
|
||
|
||
---
|
||
|
||
### 8.6 夹具六段(OFF / ON / CROSS / ROT + ROT-EXPIRED / SCAN-MISSING)+ **先红后绿**
|
||
|
||
> 单内写"四段",本棒按 §5 的判据面**扩到六段**(补 `ROT-EXPIRED` = 超窗口、`SCAN-MISSING` = 扫描腿缺失),⛔ 均为夹具、⛔ 不触真机。原文见 `S7-fixtures.txt`。
|
||
|
||
| 段 | 预期 | 实测原文(尾段) | 判定 |
|
||
|---|---|---|---|
|
||
| **OFF**(无加密键) | SKIP + 留痕 | `⚠️ FIXTURE SKIP OBS-23 ⛔ 未启用组密钥加密(content.crypto 块缺席)⇒ SKIP + 留痕(缺省不启用是合法状态,⛔ 非静默绿)` | ✅ |
|
||
| **ON**(同组) | PASS | `FIXTURE PASS … detChecks=1 detMismatches=0|decrypts=6 decryptRejected=0 local+peer 命中=5|plainScans=1 plainLeaks=0|epoch=1/1 epochExpired=0` | ✅ |
|
||
| **CROSS**(跨组) | FAIL | `FIXTURE FAIL … decryptRejected=1(须 0)` | ✅ |
|
||
| **ROT**(双 epoch) | PASS | `FIXTURE PASS … epoch=2/2 epochExpired=0|decrypts=4` | ✅ |
|
||
| **ROT-EXPIRED** | FAIL | `FIXTURE FAIL … epochExpired=1` | ✅ |
|
||
| **SCAN-MISSING** | FAIL | `FIXTURE FAIL … plainScans=0(阈值 ≥ 1)` | ✅ |
|
||
|
||
**先红后绿(代码层)**:把 F1 的确定性 `iv` 改成随机 `iv` ⇒ **10 例红**(`F1`/`F1-b`/`F1-c`/`F2`/`F4`/`F5` 一族全线点名)⇒ 还原 ⇒ 内容面 **44/44 全绿**(`S7-content-test.txt`);
|
||
`npm test` **201 tests / 200 pass / 0 fail / 1 skip** ⇒ **原 27 例零退化**(F 组 16 例为**纯追加**)。
|
||
**判定**:✅ 六段**逐段与预期一致**,且 `OFF`(SKIP)/`CROSS`(FAIL)/`ROT`(PASS)**三段互不混淆**。
|
||
|
||
---
|
||
|
||
### 8.7 真机先红(新 `OBS-23` 对旧生产)
|
||
|
||
- **对未启用加密的旧形态**(`S6-probe-oldprod.txt` / `S7-probe-1.txt`):
|
||
`SKIP OBS-23 ⛔ 未启用组密钥加密(content.crypto 块缺席)⇒ SKIP + 留痕`
|
||
⇒ ⚠️ **本棒与单文有一处必须点明的偏差**:单 §4/S6 写"对旧生产 ⇒ **FAIL**",但 §5 的 `OBS-23` 形状又写"**缺省不启用 = SKIP + 留痕(合法状态)**"。**本棒按 §5 实现 = SKIP**(更保守:既不静默绿、也不误报红)。⇒ "先红"改由**下面那条腿**取得(见 8.13-①)。
|
||
- **真机先红(实取,`S7-probe-redmode.txt`)**:把 **47** 的密钥文件权限由 `0600` 改 `0640` ⇒
|
||
```
|
||
FAIL OBS-23 … 密钥文件 /etc/dshs/content-group-key.json 权限须=600:47=640 106=600
|
||
❌ 1 项红:OBS-23
|
||
```
|
||
rc = **1**;**全量仅 1 红**(⛔ 无波及);**点名可区分**(直接打出"47=640 106=600",⛔ 不是笼统失败)。**原文字节数 = 5,827 B**(⛔ 非 0 ⇒ "没采到"与"确无"可分)。
|
||
- **复绿**:还原 `0600` ⇒ `S7-probe-restored.txt` = **23 PASS / 0 SKIP / 0 FAIL**(rc=0)✅
|
||
|
||
---
|
||
|
||
### 8.8 部署对账 + 密钥文件权限取证
|
||
|
||
**① 产物(18 文件 × 四处)**:`npm run build` rc=0 ⇒ 铺 `/opt/dshs/lib`(47)+`/opt/dsh-relay/lib`(47)+`/opt/dshs-cluster/lib`(106)+`/opt/dsh-relay/lib`(106)。
|
||
- 本机 md5(`deploy-local-md5.txt`,18 行;另三处**逐文件 diff = 0 不一致**):
|
||
|
||
| 文件 | md5 | 文件 | md5 |
|
||
|---|---|---|---|
|
||
| `content/chunker.d.ts` | `7fb3fb442cbaddc62a3ae432a7f9e889` | `content/source.d.ts` | `39063c59528ad5ccc11aa3724118b3e5` |
|
||
| `content/chunker.js` | `0edd97f2bb59fc1b4f3ae8996dfed467` | `content/source.js` | `2aaae850c8a5d08dce5a39644190f08f` |
|
||
| `content/crypto.d.ts` | `cac34d85ed9b5a368a3c67f7aa742b75` | `content/store.d.ts` | `3f74bbc26c2657cf7434473cf6a2d449` |
|
||
| `content/crypto.js` | `4893aa35c0e727c7d54c3f2720e24359` | `content/store.js` | `1a4a95ed509937a2eba559c702ecd8aa` |
|
||
| `content/peer.d.ts` | `5ed40ea5e7585fda8b00a38281d345e9` | `identity.d.ts` | `fc3e1a74e0bdc84f8b19b7585808a799` |
|
||
| `content/peer.js` | `52e141133a5b7b554ee63b1dd410e01d` | `identity.js` | `77eadb32405fdeb3d0911f00e48b1d81` |
|
||
| `content/runtime.d.ts` | `921239ed101615eb6d314902cef56c60` | `index.d.ts` | `6782b7ab87aeb33521428d360824b3a1` |
|
||
| `content/runtime.js` | `7fe548c42b71b9e4785f139561fe8925` | `index.js` | `0b33a15f20c40f66796b2291c672e660` |
|
||
| `main.js` | `205f65b5252faccd9fc1f4c7d12f2d1f` | `web/server.js` | `88259233ec5fac88a3085b5180fb538b` |
|
||
|
||
✅ **不一致条数 = 0**(四处 × 18 = 72 个点位全部同值;🔴 relay 类产物**两边都铺**,⛔ 未出现序㉖ 那类"`dshs` 侧落后一版")。
|
||
- 四处 `node --check` / `require` 自检通过。
|
||
|
||
**② 密钥文件(两机同名同权限)**:
|
||
```
|
||
47 : 600 root:root 148 /etc/dshs/content-group-key.json
|
||
106 : 600 root:root 148 /etc/dshs/content-group-key.json
|
||
(md5 两机同 = 42d01c359abd59136d707ae89fb6ee7b)
|
||
```
|
||
|
||
**③ 启用开关(relay 侧 drop-in,两机同铺)**:`41-content-crypto.conf`(`2134 B`、纯 LF、md5 `1b2b530a7e3892fe54fd0d294ba694c1`);**只加 1 行 env**:
|
||
```
|
||
[Service]
|
||
Environment="DSHS_CONTENT_GROUP_KEY_FILE=/etc/dshs/content-group-key.json"
|
||
```
|
||
⛔ 不含任何既有生产键;⚠️ 注释里**刻意不写** `RELAY_FAILOVER_COOLDOWN_MS=0` 字面量(否则 §9-1 的 `grep -c` 判据会脏)。
|
||
|
||
**④ 重启与时刻**:`daemon-reload` + `restart dshs-relay` ⇒ **47 = 06:55:59 / 106 = 06:56:02**;两机日志均出
|
||
`[content-crypto] 组密钥已装载 file=/etc/dshs/content-group-key.json group=relay epoch=1 epochs=1 keyId=3560130eb0e5e4aa perms=0600 ✓`;
|
||
`/status.content.crypto` = `{encrypts:3, decrypts:3, decryptRejected:0, epochs:1, epoch:1, epochExpired:0, detChecks:1, detMismatches:0, plainScans:1, plainLeaks:0}`。
|
||
**环境收口**(按序㉙ 顺序):`restart dshs-worker` → `restart dshs` → 一次真实访问(`mksess.cjs guest` + `POST /api/dsh/enter`,Host = `alotbuy.com`)⇒ 端点表 `19000/21000` 全 `True`(**孤儿端口未复发**)。
|
||
|
||
**⑤ 回滚点**:`/opt/dsh/backups/seq28-20260918-065104/`(47 与 106 各 4 处旧产物;旧 md5 **全留档** —— `backup47.txt` / `backup106.txt`;⚠️ 其中 `content/crypto.*` 与 47/106 平台的 `content/runtime.*` 开工为 **`MISSING`** ⇒ 回滚 = 删文件,见 §6 路 B)。
|
||
|
||
---
|
||
|
||
### 8.9 判据 F1–F7(逐条原文)
|
||
|
||
| # | 判据 | 原文(命令 ⇒ 输出摘录) | 判定 |
|
||
|---|---|---|---|
|
||
| **F1** | 同组密文一致 | `node _tmp_seq31/f_evidence.mjs` ⇒ `密文#1 md5 = 密文#2 md5 = 3385e9d00ae5c5dbb880ddad90391756`、`逐字节相同 = true` | ✅ **通过** |
|
||
| **F2** | 共享不退化 | 探针夹具 ON ⇒ `decrypts=6 decryptRejected=0 local+peer 命中=5`;单测 `F2 共享不退化… local 命中递增且解密成功` | ✅ **通过** |
|
||
| **F3** | 跨组不可解 | `F3 跨组不可解:组 B 用自己的密钥解组 A 的密文 ⇒ 拒 + decryptRejected +1`;夹具 CROSS ⇒ `decryptRejected=1`;`F3-b 跨组判定不被放松` ✅ | ✅ **通过**(`crossGroupDenied` 语义未变) |
|
||
| **F4** | 明文不出现(+反向证明) | ① 密文里扫标记 = **命中 0**(样本 3804 B);② 反向:不加密字节里扫同标记 = **命中 ≥ 1**;正向 `plainScans=1 plainLeaks=0`、反向 `plainScans=1 plainLeaks=1`(⛔ 非 0 ⇒ 这条腿**有牙**) | ✅ **通过**(① ② 都在,⛔ 未踩 §9-4) |
|
||
| **F5** | `E1` 主判据不退化 | `F5/E1 口径:加密前后"回源份数"不变(同组 N 次取用 ⇒ 全部 local 命中、零回源)`;断言 `4 轮 × 3 块,全部 local 命中(= E1 的"回源 1 份"口径)` = `source.local = 12`、`F2-b 同一份内容重复 put ⇒ cache 不翻倍` | ✅ **通过**(⛔ 未从 `1.0000×` 退回 `4.00×`) |
|
||
| **F6** | 失败关闭(具名) | 七例逐例具名:`no-file` / `bad-perms` / `parse-error` / `group-mismatch` / `bad-key` / `bad-epoch` / `prev-key-bad`(+正例);例:`bad-perms ⇒ 权限 666(必须 0600)`、`group-mismatch ⇒ 文件 group=relay ≠ 运行时 group=local` | ✅ **通过** |
|
||
| **F7** | 零回归 | 见 8.10 | ✅ **通过** |
|
||
|
||
---
|
||
|
||
### 8.10 零回归三件套
|
||
|
||
全部带 `--table "E:/ProgramData/AI技能/aliyun-dsh-server/参数表_覆盖网络_20260917.md"`;**收口稳态**(`S7-npmtest.txt` / `S7-drill.txt` / `S7-probe-terminal.txt`):
|
||
|
||
| 件 | 命令 | 基线(开工现核) | 收口实测 | 判定 |
|
||
|---|---|---|---|---|
|
||
| 单元测试 | `npm.cmd test`(Node 22) | 201 tests / 200 pass / 0 fail / 1 skip | **201 / 200 / 0 / 1** | ✅ 逐字不退化 |
|
||
| 演练 | `node scripts/overlay-failover-drill.cjs --scene all --table "<参数表>"` | 12 PASS / 0 SKIP / 0 FAIL | **12 PASS / 0 SKIP / 0 FAIL** | ✅ |
|
||
| 探针 | `node scripts/overlay-probe.cjs --table "<参数表>"` | 稳态 22 PASS / 0 SKIP / 0 FAIL | **23 PASS / 0 SKIP / 0 FAIL(rc=0)** | ✅ **净 +1 行**(新增 = `OBS-23` PASS) |
|
||
|
||
`OBS-23` 终态原文(**关键尾段**):
|
||
```
|
||
PASS OBS-23 组密钥加密(GCM 确定性)判别器 齐全|F1 确定性 detChecks=1(阈值 ≥ 1) detMismatches=0(须 0)
|
||
|F2 共享 decrypts=3(阈值 ≥ 1) decryptRejected=0(须 0) local+peer 命中=2(阈值 ≥ 1)
|
||
|F4① 明文扫描 plainScans=1(阈值 ≥ 1) plainLeaks=0(须 0)|epoch=1/1 epochExpired=0
|
||
|密钥文件 /etc/dshs/content-group-key.json 权限须=600:47=600 106=600
|
||
```
|
||
|
||
---
|
||
|
||
### 8.11 边界自证
|
||
|
||
| 边界 | 证据 | 判定 |
|
||
|---|---|---|
|
||
| ⛔ 未改任何生产值(**D1**) | 新 drop-in **只 1 行 env**;⛔ 未动 `RELAY_FAILOVER_*` / `HB_SEC` / burst / `PRESENCE_*` / `DSHS_CONTENT_STORE_*` / `DSHS_OVERLAY_*`;`40-content-probe.conf` 两机 md5 同 = `9e7ccf9a8e59fc88367cea0a07c3d8b8`(**未被动**) | ✅ |
|
||
| ⛔ 未新增公网监听口 | 仅新增文件落点 `/etc/dshs/`;`ss -lntp` 面未见新口(relay 仍只绑回环 `20080`) | ✅ |
|
||
| ⛔ 未改 `nft` / `nginx` | 全程零 `nft` / `nginx` 命令(**R5**) | ✅ |
|
||
| ⛔ 未放松跨组 | `F3-b` / `F3-c` ✅;`crossGroupDenied` 语义未变;`E5` 无退化 | ✅ |
|
||
| 🔴 `RELAY_FAILOVER_COOLDOWN_MS=0` 计数 | `grep -rIls "RELAY_FAILOVER_COOLDOWN_MS=0" /etc/systemd/system /etc/dshs \| wc -l` ⇒ **47 = `0` / 106 = `0`** | ✅ **= 0** |
|
||
| 🔴 密钥本体 ⛔ 不经网络、不经 relay | 只走 `0600` 落文件 + `scp`;中继侧只广播 `(network, group, epoch, keyId)` 三元组(凭据逐字核:不含 `key`);relay 消息面 ⛔ 零新增密钥字段 | ✅ |
|
||
| 🔴 未改 `bwrap` 参数(47 = bubblewrap 0.4.0) | 本单**未触碰**任何 banner/沙箱参数(文件集见 §3.1) | ✅ |
|
||
| ⛔ 未 commit / 未 push(**R7**) | `git rev-parse HEAD` = **`04776af4b130014b0f4db10de89b0ae982300afc`**;`git ls-remote origin refs/heads/master` = **同值**(⇒ 未推);`git status --porcelain \| wc -l` = **35** | ✅ |
|
||
| ⛔ 未建 04 孪生 / 未补占空号 | 本单**不落** 04 孪生(与本线全部 `交接单_*.md` 一致);`63` 仍为空号 | ✅ |
|
||
|
||
---
|
||
|
||
### 8.12 指纹
|
||
|
||
- **参数表 §10 口径指纹**:`3b295a8c43aafc4a62c6f1a59ea0b43a`(序㉚ 收口)→ **`08c2835a7e9f0faef20f9c1d3e619d55`**(本棒收口;变更 = §3.9 新增 **6 键** `CONTENT_KEY_FILE` / `CONTENT_KEY_FILE_MODE` / `CONTENT_EPOCH_GRACE_MS` / `CONTENT_CRYPTO_DET_MIN` / `CONTENT_CRYPTO_DECRYPT_MIN` / `CONTENT_CRYPTO_SCAN_MIN` + §6 新增 `OBS-23` 一行;口径 = `sed '/^## §10 指纹/,$d' 参数表_覆盖网络_20260917.md | md5sum`)。
|
||
- **本单 §8 前前缀**(口径 `sed '/^## §8 回报格式/,$d' <本单> | md5sum`)= **`149596460288ca1a5b7abddc490f7909`** —— ✅ **回填后复跑逐字不变**(§8 是截断点)。
|
||
- **全文 md5 / 行数**:⛔ **不内嵌**(自指)⇒ 现算 `md5sum "E:/ProgramData/AI技能/aliyun-dsh-server/交接单_组密钥加密_20260918.md"`;回填前快照 = `0412daf3dc8edf96c2fbe87bdefbdb29`(**356 行**)。
|
||
- **归档号**:**130**(本单孪生号;执行为 §11.1 记录,⚠️ 执行棒收官建档案时须**重新现核取号**)。
|
||
|
||
---
|
||
|
||
### 8.13 如实留档(**必填,哪怕"没有"**)
|
||
|
||
① ⚠️ **单文一处内部不一致(本棒按 §5 实现,与 §4/S6 的措辞不同)**:§4/S6 写"新 `OBS-23` 对旧生产 ⇒ **FAIL**",而 §5 的 `OBS-23` 形状写"**缺省不启用 = SKIP + 留痕(合法状态)**"。本棒**取了 SKIP**(理由:既不静默绿、也不把"合法的没启用"误报成红)。⇒ **"真机先红"改由权限腿取得**(`0600→0640` ⇒ `FAIL` 且点名 `47=640 106=600`,rc=1,全量仅 1 红),复绿后 **23P/0S/0F**。🔴 这条**不是缺陷**,是单文两处口径需统一 —— 建议下一棒在单里定稿(倾向保留 §5 的 SKIP 口径 + 把"先红"腿写清为**权限腿**)。
|
||
|
||
② ⚠️ **签名动作的命令未逐字留痕**:`sign-group-key` 用的是 47 上**既有签名者私钥**,但 47 上**没有** `/opt/dshs/scripts/overlay-keyring.cjs`、`/tmp` 亦无残留 ⇒ 本棒只能自证"**凭据不载荷密钥本体 + 验签通过 + 篡改必失败**",**无法自证"私钥未出机"**。⇒ 建议:把"组密钥签发"固化成 47 上的**正式子命令**(⛔ 不靠临时投放脚本)。**另立小单,本棒不动**。
|
||
|
||
③ ⚠️ **未测得"轮换后首轮回源字节数"**(§10-2):本棒只证得"换组 ⇒ 100% 块 id 改变"(**定性**),**未造 4 组 × 1 份包的轮换前后 `E1` 读数**(**定量**)⇒ §10-2 **仍未验证**,⛔ 未编数。
|
||
|
||
④ ⚠️ **未测得加密性能开销**(§10-1:N ≥ 5、p50/p95):⛔ **无数据**,⛔ 不报单次。
|
||
|
||
⑤ ⚠️ **`OBS-23` 修过一处真缺陷(本棒发现并已修)**:密钥文件权限检查门原为 `cryptoFx === undefined` ⇒ 只给 `--content-fixture`(不给 `--content-crypto-fixture`)时会**偷偷 ssh 到生产机**(与"夹具模式绝不 ssh"的契约面不一致)。已改为门 = `!fixture`。⇒ **属本单范围内的真缺陷修复**,已复测六段夹具全符合预期。
|
||
|
||
⑥ ⚠️ **两处环境态本就存在、本棒未动**:47 `users/` 权限、106 残留 scope `dsh-100002-844b8c29.scope`(属既有 user `4092b965` 的实例,转由认领机制管理);`OBS-22` 的 47 认领面 `journalctl 原文 0 字节` = **读取失败**(⛔ 不是"确无 summary")。
|
||
|
||
⑦ ✅ **临时 session 已清干净**:`DELETE FROM sessions WHERE user_agent='poc-curl2'` ⇒ `DELETE 3`,残留 **0**。
|
||
|
||
⑧ ✅ **`_tmp_seq31/` 为本次临时证据目录**(含夹具、脚本、原始输出),⛔ 未删除(待用户确认后再清);**夹具 `fx-*.json` 仅本机**,⛔ 未铺任何服务器。
|
||
|
||
---
|
||
|
||
### 8.14 序 ㉛ 执行棒回报(2026-09-18 07:33–07:5x · §10 未验证项补齐 + 口径定稿 + §8.13-② 结清)
|
||
|
||
> 🔬 **开工门**:`sed '/^## §8 回报格式/,$d' <本单> | md5sum` = **`149596460288ca1a5b7abddc490f7909`** ⇒ **逐字一致,✅ 通过**(本条写在 §8 之后 ⇒ ⛔ **不影响该不变量**)。
|
||
> 📁 **证据目录** = 工作区根 **`_tmp_seq32/`**(脚本 `.mjs/.sh` + 原始输出 `.txt`,逐项对应下方);⚠️ 临时产物,⛔ **未删除**(待确认后再清)。
|
||
> ⚠️ **本棒性质 = 补测 + 小额固化** ⇒ ⛔ 未扩范围、⛔ 未改生产值、⛔ 未动代码(`src/**` 零改动)。
|
||
|
||
#### 8.14.1 主题 ① 加密性能开销(§10-1)—— ✅ **有原始读数**
|
||
|
||
**口径**:同一台机、同一份首屏包 **11,363,655 B / 11 块**;**先预热 2 次再测**,**n = 11**;每轮 = `切块 + 加密 + put + get + 解密` **全链总墙钟**;两臂跑同一夹具;分位取 **nearest-rank**(⛔ 不插值、⛔ 不报单次)。
|
||
|
||
| 臂 | p50 | p95 | 吞吐 | 密文固定开销 | `origin` 命中 |
|
||
|---|---|---|---|---|---|
|
||
| 纯明文 | **22.636 ms** | **23.020 ms** | 502.02 MB/s | — | **0** |
|
||
| 组密钥加密 | **42.782 ms** | **43.599 ms** | 265.62 MB/s | **+28 B/块** | **0** |
|
||
| **增量 / 比值** | **+20.146 ms / 1.890×** | **+20.579 ms / 1.894×** | — | — | — |
|
||
|
||
- 🔴 **口径边界(⛔ 不许当端到端承诺)**:单核、页缓存全热、两臂均**零回源** ⇒ 这是"**加密层自身成本**"的参照读数,⛔ **不是线上 `E1` 读数**。
|
||
- 原始输出 = **`_tmp_seq32/p01-perf.txt`**(脚本 `p01-perf.mjs`)。
|
||
|
||
#### 8.14.2 主题 ② 轮换代价量化(§10-2)—— ✅ **结清 §8.13-③**
|
||
|
||
**夹具**:**4 组 × 4 节点**,包 **5,255,225 B / 6 块**;组成员**按需取块**(`local → peer → origin`,命中即缓存);**另设「不轮换」对照腿**(⛔ 无对照腿 ⇒ 无法断言"回源是轮换引起的")。
|
||
|
||
| 阶段 | 回源字节 | 相对包 | `local/peer/origin` | 块 id 改变 |
|
||
|---|---|---|---|---|
|
||
| 冷启动(轮换前) | **21,021,572 B** | **4.0001 × 包 = 1 × 包/组** | `0 / 72 / 24` | — |
|
||
| 轮换后**首轮** | **21,021,572 B**(**比值 1.0000**) | 同值 | — | **24 / 24 = 100%** |
|
||
| 轮换后**第二轮** | **0** | — | — | — |
|
||
| **对照腿**(不轮换)**第 3 轮** | **0** | — | — | — |
|
||
| 稳态(两腿) | **0** | — | — | — |
|
||
|
||
- ⇒ **"轮换 = 一次完整冷启动(1 × 包/组)"已量化**(与 §10-2 预期逐字吻合);**"回源由轮换引起"可断言**(对照腿第 3 轮 = 0)。
|
||
- 🔴 **附带代价(本棒实测,原本未在册)**:旧 epoch 密文**变垃圾且不自清** —— `storeBytes` **5,255,393 → 10,510,786**(≈ 翻倍)。⇒ 回头条件 8(轮换须约在发布窗口)**多了一条理由**:不只是回源,还有**存储占用翻倍**直到旧块被逐出。
|
||
- 原始输出 = **`_tmp_seq32/p02-rotate.txt`**(脚本 `p02-rotate.mjs`;⚠️ v1 夹具曾因"包预置进 local"造假绿 `postOverPre = NaN`,**已重写**后取上表读数)。
|
||
|
||
#### 8.14.3 主题 ③ 低熵明文的定性结论(§10-3)—— ✅ **定性成立,⛔ 不写"很安全"**
|
||
|
||
| 模型 | 读数 | 结论 |
|
||
|---|---|---|
|
||
| **A:持组密钥者** | 字典枚举 1000 条 = **11.644 ms**(≈ **85,878 条/秒**)、命中 **1**、**恢复正确**(域 **2³²** ⇒ 全量 ≈ **14.7 h**) | **枚举可行** ⇒ 低熵明文对**持钥者**无保护 |
|
||
| **B:无钥者 / 中继** | **自造字典命中 0 / 1000**;**持有 1 对明文-密文也预测不出别的**(**0 / 999**) | **枚举不可行** |
|
||
| **B 的旁路:相等性泄露** | **10,000 块 / 100 值域 ⇒ 仅 100 种密文**(收敛加密的固有性质) | **重复率 / 相等性可观测** |
|
||
| **反向腿(高熵对照)** | **1000 / 1000 无碰撞** | ⛔ 证明"没测到 ≠ 真没有" |
|
||
|
||
- 🔴 **定稿口径**:**"中继看不到明文" 成立** / **"内容不可推断" 不成立**(收敛加密对低熵必然泄露**相等性**)。⛔ **不写"很安全"**。
|
||
- ⚠️ **夹具口径修正(本棒发现)**:初版语料用 LCG 低位取模(`x % 100`)**只出 37 个不同值** ⇒ 已改 **xorshift32 取高位 `(x >>> 8) % 100`** ⇒ **100 / 100**,口径干净后取上表读数。
|
||
- 原始输出 = **`_tmp_seq32/p03-lowentropy.txt`**(脚本 `p03-lowentropy.mjs`)。
|
||
|
||
#### 8.14.4 主题 ④ `CONTENT_EPOCH_GRACE_MS` 三档混存夹具(§10-6)—— ✅ **三档曲线齐**
|
||
|
||
**夹具**:混存 **20 块**(写 epoch **4** + 历史 **1 / 2 / 3**),`retiredAt` = **T0 / T0+2h / T0+12h**,**时钟注入 `now`**(⛔ 不许真等)。
|
||
|
||
| 档 | 取值 | 归零时刻 | 窗口内 `epochExpired` | 超窗行为 |
|
||
|---|---|---|---|---|
|
||
| 小 | **60 s** | **t = +24 h** | **0** | 逐 epoch **点名** + `decryptRejected` **+1** |
|
||
| 中(**当前值**) | **24 h** | **t = +48 h** | **0** | 同上 |
|
||
| 大 | **7 d** | **t = +8 d** | **0** | 同上 |
|
||
|
||
- 曲线形状 = **阶梯衰减**(最晚 `retiredAt` + 本值 = 归零);**写 epoch 恒 5 / 5 可解**(⛔ 旧密文过期不影响写侧)。
|
||
- ⇒ 定稿见 **§11.5 定稿三**(**当前值 24 h 维持**,⛔ 本棒未改任何值)。
|
||
- 原始输出 = **`_tmp_seq32/p04-epoch.txt`**(脚本 `p04-epoch.mjs`)。
|
||
|
||
#### 8.14.5 主题 ⑤ 单文口径定稿 —— ✅ **写进 §11.5(⛔ §4 / §6 正文零改动)**
|
||
|
||
- **定稿一**:`OBS-23` 对"缺省不启用"取 **`SKIP + 留痕`**;**§4/S6 那句「对旧生产必 FAIL」作废**;**"真机先红"腿由「权限腿」承载**。
|
||
- ⚠️ **§8.13-① 的口径张力至此结清**(⛔ 未打穿 §8 前前缀:修正**一律写在 §8 之后**)。
|
||
- **权限腿读数(单 B 实测,本棒复核同值)**:`0600 → 0640` ⇒ `OBS-23` **FAIL** 并**点名** `47=640 106=600`、rc=1、全量仅 **1 红** ⇒ 还原即复绿。**⇒ §10-4(`0600` 运维一致性)由此覆盖**(逐机实读权限 + "权限错必失败关闭"两条腿都有)。
|
||
|
||
#### 8.14.6 主题 ⑥ 组密钥签发固化(结清 **§8.13-②** 缺口)—— ✅ **已落正式路径**
|
||
|
||
| 项 | 证据 |
|
||
|---|---|
|
||
| 落点(**两处**) | **`/opt/dsh-relay/scripts/overlay-keyring.cjs`**(**就地更新**:旧版 **12,915 B**、`grep -c sign-group-key` = **0**)+ **`/opt/dshs/scripts/overlay-keyring.cjs`**(**此前不存在** = §8.13-② 的缺口) |
|
||
| 字节一致 | 两处 md5 **均 `f1855eaef30f71fbca00ac0a92865c0e`**(与本机同值) |
|
||
| **在机验证** | `sign-group-key` ⇒ **rc = 0**,凭据 **274 B**(`ops/relay epoch=1 keyId=3560130eb0e5e4aa`),**密钥本体出现 0 次**|`verify-group-key` ⇒ **rc = 0**|**篡改 epoch** ⇒ **`signature-mismatch` rc = 1**|`--epoch 9` 交叉核对 ⇒ **rc = 1** |
|
||
| 回滚点 | 47 **`/opt/dsh/backups/seq31-20260918-073857/`** |
|
||
| ⛔ 未重启 | **纯 CLI**,⛔ 未重启任何 unit(⛔ 未中断任何用户) |
|
||
|
||
#### 8.14.7 零回归三件套(三者均带 `--table "E:/ProgramData/AI技能/aliyun-dsh-server/参数表_覆盖网络_20260917.md"`)
|
||
|
||
| 项 | 结果 | 与开工基线 |
|
||
|---|---|---|
|
||
| `npm.cmd test`(Node 22) | **201 tests / 200 pass / 0 fail / 1 skipped** ⇒ 注入脚本「结论:全部合格 ✅」 | **逐字相同** ✅ |
|
||
| `overlay-failover-drill.cjs --scene all --table` | **12 PASS / 0 SKIP / 0 FAIL**(rc = 0) | 同值 ✅ |
|
||
| `overlay-probe.cjs --table` | **23 PASS / 0 SKIP / 0 FAIL**(**rc = 0**) | **与开工基线 23P/0S/0F 一致** ✅ |
|
||
|
||
- 🔬 **本棒三件套的"先红 → 后绿"过程(如实留档)**:环境收口期间(重排实例落点)曾出现 **19P/1S/3F**(`OBS-01 used=1` / `OBS-04 identityOk=1` / `OBS-08 端点表 0 条`)与 **22P/1S/0F**(`OBS-09` SKIP,因实例暂落端口 **21001**)。**未编数、未凑绿** —— 依据序 ㉙ 留档的机制(**孤儿端点条目不自愈,靠"重新登记同一端口"覆盖**),用 `/api/dsh/stop` + `/api/dsh/enter` 把实例落回 **21000** ⇒ **`OBS-09` 转 PASS**(`w-106:39027=401`)、**`OBS-21` 复绿**(两路径 **count = 3**)⇒ **23P/0S/0F**。
|
||
- 原始输出 = `_tmp_seq32/base-npmtest.txt` / `final-drill.txt` / `final-probe-1..4.txt`。
|
||
|
||
#### 8.14.8 恢复形态核实 + 参数表 & 日志
|
||
|
||
- **参数表**:§3.9 `CONTENT_EPOCH_GRACE_MS` 行**说明列**补序㉛ 三档曲线|§6 `OBS-23` 行末补「口径定稿」|§7 补「序㉛ 复算:仍为 `待测` **0 项**(⛔ 未新增键、⛔ 未改任何值)」|**新增 §11.3 补记**(六条)。
|
||
- 🔴 **§10 指纹**:`08c2835a7e9f0faef20f9c1d3e619d55` → **`52e59df229102d5afa8c8bf01a960398`**(**本棒收口现算 + 已同步**;口径 = 先 `sed '/^## §10 指纹/,$d'` 截断再 md5)。⛔ **本轮零键零值变更** ⇒ 探针取阈值不受影响。
|
||
- 本单全文 md5:⛔ **不写死** ⇒ 现算 `md5sum "E:/ProgramData/AI技能/aliyun-dsh-server/交接单_组密钥加密_20260918.md"`。
|
||
|
||
#### 8.14.9 边界自证(本棒)
|
||
|
||
| 边界 | 证据 | 判定 |
|
||
|---|---|---|
|
||
| ⛔ 未 commit / 未 push(**R7**) | HEAD 仍 **`04776af`**;⛔ 未执行任何 `git commit` / `git push` | ✅ |
|
||
| ⛔ 未改任何生产值(**D1**) | 两机 env / drop-in **零改动**;⛔ 未动 `RELAY_FAILOVER_*` / `HB_SEC` / burst / `PRESENCE_*` / `DSHS_CONTENT_*` | ✅ |
|
||
| ⛔ 未新增公网监听口、⛔ 未改 `nft` / `nginx`(**R5**) | 全程零 `nft` / `nginx` 命令;`OBS-11` 多出 **0** | ✅ |
|
||
| 🔴 `RELAY_FAILOVER_COOLDOWN_MS=0` | ⛔ **全程未出现在任何夹具 / 脚本 / 演练路径**(本棒新增文件逐字核 = **0**) | ✅ **= 0** |
|
||
| ⛔ 未改 `bwrap` 参数(47 = 0.4.0) | 本棒**未触碰**任何沙箱参数 | ✅ |
|
||
| 🔴 密钥本体 ⛔ 不经网络 / 不经 relay | 签发**只在 47 本机**(凭据 **274 B**、**不含密钥本体**、⛔ 未 `scp` 任何密钥) | ✅ |
|
||
| 🔴 Ed25519→X25519 派生 | ⛔ **本棒未执行**(不在写死主题内 ⇒ 未扩范围) | ✅ 未越线 |
|
||
|
||
#### 8.14.10 ⚠️ 如实留档(本棒"没有"的部分,**必填**)
|
||
|
||
① ⚠️ **§10-5(Ed25519→X25519 派生预研)本轮未做**:它**不在**本棒写死的六项主题内(用户明令「⛔ 不许顺手扩大范围」)⇒ **无读数、⛔ 未编数**。它服务的对象是 **§9 回头条件 7**("规模上来了要自动分发组密钥")⇒ **触发条件 = 规模上来**,⛔ 不是当前执行项。
|
||
② ⚠️ **§10-4 未单独跑"铺发后逐机 `ls -l` / `stat`"**:读数改由 **`OBS-23` 权限腿**承载(`47=600 106=600` + `0600→0640 ⇒ FAIL 并点名`)⇒ **判据覆盖成立**,但**取证形态不同**(探针读 vs 手工 `stat`)。
|
||
③ ⚠️ **性能读数是本机、单核、页缓存全热**(§8.14.1 口径边界)⇒ ⛔ **不得外推为线上 `E1` 承诺**。
|
||
④ ⚠️ **轮换夹具的存储翻倍**(§8.14.2)是**发现的新代价**,⛔ **未做"旧块何时被逐出"的量化**(⇒ 记为待观察,⛔ 不冒充已验证)。
|
||
⑤ ✅ **临时 session 已清干净**:`DELETE FROM sessions WHERE user_agent='poc-curl2'` ⇒ 「DELETE 行数 = 1」、**残留 = 0**。
|
||
⑥ ✅ **`_tmp_seq32/` 为本棒临时证据目录**,⛔ **未删除**(待用户确认后再清)。
|
||
|
||
---
|
||
|
||
## §9 回头条件(**一出现必须回头**)
|
||
|
||
1. 🔴 **⛔ 任何一棒都不许把 `RELAY_FAILOVER_COOLDOWN_MS=0` 写进回滚 / 演练 / 夹具路径**(自锁:实测 **121–123 s 无切换**)。本单验收里 `grep -c` 该值必须 = **0**。
|
||
2. **`E5` 退化**(跨组穿透 > 0,或 `crossGroupDenied` 语义被放松)⇒ **立刻回滚 + 报告**。
|
||
3. **`F1` 不成立**(同组两次加密密文不同)⇒ 停下报告("按哈希共享块"已失效,继续做只会放大回源)。
|
||
4. **`F4` 只绿其①、②未做**(没有反向夹具证明"没测到 ≠ 真没有")⇒ 判据**不算通过**,停下补证。
|
||
5. **需要超出 §3.1 在册文件集**(尤其要动 `wire.ts` 帧格式 / `keys.ts` 既有语义)⇒ 停下报告(**R7**)。
|
||
6. **`decryptRejected` 在稳态下持续增长**(⇒ 有节点密钥/epoch 不一致)⇒ 停下报告,⛔ 不许"重启一次看看"。
|
||
7. **规模上来了要"自动分发组密钥"** ⇒ 停下报告(需先给节点密钥加 `x25519` 或做 Ed25519→X25519 派生 ⇒ **属新增密钥类型,另立单**)。
|
||
8. **轮换窗口出现在非发布期**(⇒ 一次全量回源砸在用户活跃时段)⇒ 停下报告,把轮换**改约到发布窗口**。
|
||
9. **性能不达标**(加密后 `E1`/吞吐显著变差)⇒ 先量化再决定,⛔ 不许凭感觉回滚。
|
||
|
||
---
|
||
|
||
## §10 未验证项(**一个都不许编数**)
|
||
|
||
| # | 未验证项 | 为什么重要 | 怎么验(口径) |
|
||
|---|---|---|---|
|
||
| 1 | **加密的性能开销** | 决定"兼得"是否真的兑现 | 同一台机、同一份 10.8 MB 首屏包,测 **N ≥ 5** 次的"切块 + 加密 + put + get + 解密"总墙钟与纯明文对照;⚠️ 记 **p50 / p95**,⛔ 不报单次 |
|
||
| 2 | **轮换的实际代价** | β' 的核心代价是"轮换 = 一次全量回源" | 造 4 组 × 1 份包,轮换前后各跑一次 `E1` 口径 ⇒ 记录回源字节(**预期:轮换后首轮 ≈ 1 份 × 组数**,之后回落到 1×) |
|
||
| 3 | **收敛加密对低熵内容的安全性** | 收敛加密对**低熵**明文有已知的确认攻击面(可枚举) | 用低熵样本(如小文本块)验证:能否由密文反推明文集合;口径 = 只做**定性**结论,⛔ 不写"很安全" |
|
||
| 4 | **`0600` 密钥文件的运维一致性** | 本线有"属主被改 ⇒ 实例全崩"的先例 | 铺发后逐机 `ls -l` + `stat`;并验"权限错时**必须失败关闭**而不是静默明文" |
|
||
| 5 | **Ed25519→X25519 派生可行性**(为回头条件 7 预研) | 决定"自动分发"能不能不新增密钥类型 | 纯本地实验:从 ed25519 私钥导出 raw scalar ⇒ 构造 x25519 `KeyObject` ⇒ ECDH 往返成功?(⚠️ 只做实验、⛔ 不进本单代码) |
|
||
| 6 | **双 epoch 过渡窗口的取值** | 太长 ⇒ 旧密钥留太久(安全);太短 ⇒ 老节点掉线 | 用 `CONTENT_EPOCH_GRACE_MS` 三档(小/中/大)各跑一次混存夹具,记录"旧密文可解的比例随时间" |
|
||
|
||
---
|
||
|
||
## §11 指纹与状态
|
||
|
||
### 11.1 归档号(**本棒已原子占号**)
|
||
- **现核最大号**(⛔ 不写死;取号命令):`ls 04-调整方案/ | grep -oE '^[0-9]+' | sort -n | tail -1` ⇒ **本棒实测 = `128`**。
|
||
⚠️ **prompt 写的"归档编号至 112"已过期**(现核 128)⇒ 引用时**必须复跑取号**。
|
||
- **占号动作(已执行)**:`mkdir 04-调整方案/.lock-130` ⇒ **成功** ⇒ 本单归档号 = **130**(单 A = 129)。
|
||
- 🔴 **63 是空号,⛔ 不补占**(已核:`ls 04-调整方案/ | grep -E '^63'` = 空)。
|
||
- ⚠️ **本棒不落 04 孪生**(本线 `交接单_*.md` **均无** 04 孪生;带孪生的是**方案类**文档 —— 逐条比对 16 份单 = 0 命中)⇒ 占号窗口在落单后即释放,执行棒收官建档案时**须重新现核取号**。
|
||
|
||
### 11.2 指纹
|
||
- **§8 前前缀**(口径:`sed '/^## §8 回报格式/,$d' <本单> | md5sum`):见下方"落单后实测"。
|
||
- ⚠️ **§8 回填后前缀指纹不变、全文 md5 会变**。
|
||
- **落单后实测**(2026-09-18 00:5x,序 ㉘ 规划棒):
|
||
- **§8 前前缀** = **`149596460288ca1a5b7abddc490f7909`**(⚠️ 本值**稳定** —— §11 在截断点之后)
|
||
- **全文 md5(§11 本段回填前快照)** = **`ef7aad0cf65f4d83221f46e60caa96ed`**
|
||
- **全文 md5(§11 本段回填后最终)** = **`见下方 §11.4`**(写完本段后现算)
|
||
- **行数** = **344**(快照时刻;回填 §8 后会增长)
|
||
|
||
### 11.3 状态
|
||
- **本单状态**:**✅ 已执行收官**。拆两棒:**单 B 执行棒**(2026-09-18 06:22–07:2x)= 代码 + 部署 + 判据 F1–F7 ⇒ 证据见 **§8.1–§8.13**;**序 ㉛ 执行棒**(2026-09-18 07:33–07:5x)= **§10 未验证项补齐 + 口径定稿 + §8.13-② 缺口结清** ⇒ 证据见 **§8.14** / 定稿见 **§11.5**。
|
||
- **本棒(序 ㉘ 规划棒)边界自证**:⛔ **零代码改动** · ⛔ **零服务器改动** · ⛔ 未 commit / 未 push · ⛔ 不改任何生产值 · ⛔ 未实现任何加密(本单**只出方案 + 判据**)。
|
||
- **两单关系**:单 A(归档号 129)与单 B(130)**文件集零交集**,可并行;⚠️ 但**同一时刻只挂一个接续棒**(用户 2026-09-18 00:3x 明令)⇒ **执行顺序由下游定序理由决定**(见入口 §2 的定序段)。
|
||
|
||
### 11.4 全文 md5 的口径(⛔ **不写死**)
|
||
- 全文 md5 **每改一次本单都会变**(回填 §8 尤其)⇒ **权威取数 = 现算**:
|
||
`md5sum "E:/ProgramData/AI技能/aliyun-dsh-server/交接单_组密钥加密_20260918.md"`
|
||
- **落单快照**(§11.4 本段写入前的最后一次现算,2026-09-18 00:5x)= **`40d35357a501596dcd1e6d7e70e8b371`**(**349 行**)。
|
||
⚠️ 本行写入后该值**即失效** ⇒ **引用前必须复跑上面的命令**。
|
||
- **§8 前前缀** 不受影响(§8 是截断点)⇒ 仍是 §11.2 的 **`149596460288ca1a5b7abddc490f7909`**。
|
||
|
||
### 11.5 🔴 口径定稿(2026-09-18,序 ㉛ 执行棒)—— **本行起具最高效力**
|
||
|
||
> **效力范围**:本单 §4 / §6 的**正文一个字都没改**(⛔ 不能改 —— 见下"为什么")。凡 §4 / §6 / §5 之间口径打架处,**一律以本节的裁决为准**。
|
||
|
||
**定稿一:`OBS-23` 对"缺省不启用"取 `SKIP + 留痕`(⛔ 不是 FAIL)**
|
||
|
||
- **裁决**:**§5 与 §6 的形状正确,§4/S6 里那句「新 `OBS-23` 对**旧生产** ⇒ **FAIL**」作废。**
|
||
- **理由**:① "缺省不启用"是**合法状态**(本单的设计目标就是"缺省不启用 ⇒ 行为逐字回到序㉔"),把它判红 = **把合法态误报成故障** ⇒ 长期挂假红会**磨掉判据灵敏度**(新红项会被"反正是红的"淹没 —— 这是本线已复盘过的教训);② 探针实现里 SKIP 行**不进红计数**,且**必打留痕**(打印"⛔ 未启用组密钥加密(`content.crypto` 块缺席)⇒ SKIP + 留痕"),⛔ 既不是静默绿、也不是假红。
|
||
- 🔴 **"真机先红"这条腿由「权限腿」承载**(单 B 已实测):把 `CONTENT_KEY_FILE` 从 `0600` 改 `0640` ⇒ `OBS-23` **FAIL** 并**分别点名**两台机的实读权限(`47=640 106=600`)、rc=1、全量仅 1 红 ⇒ 还原即复绿。⇒ 判据**有牙**,且"有牙"这件事**不依赖"是否启用加密"**。
|
||
- ⛔ **仍然禁止**:出现"**既不是 SKIP 也不是 FAIL**"的第三态(静默绿)。
|
||
|
||
**定稿二:`β′`(块 id 挂密文)的既有代价,本轮已量化,⛔ 不再当"未验证"**
|
||
|
||
- **轮换代价 = 一次完整冷启动**:4 组 × 4 节点夹具实测 —— 冷启动回源 **21,021,572 B = 4.0001 × 包 = 1 × 包/组**,轮换(epoch 1→2,块 id 改变 **100%**)后首轮**同值**(`比值 = 1.0000`),第二轮起 **0**;**对照腿(不轮换)第 3 轮 = 0** ⇒ "回源是轮换引起的"可断言。⚠️ 附带代价 = 旧 epoch 密文**变垃圾且不自清**(`storeBytes` 5,255,393 → 10,510,786)。
|
||
- **加密性能开销**:同一份 11,363,655 B 首屏包、n = 11 ⇒ 总墙钟 **p50 22.636 → 42.782 ms**、**p95 23.020 → 43.599 ms**(**1.890× / 1.894×**,绝对 +20.1 / +20.6 ms);吞吐 502.02 → 265.62 MB/s。⚠️ 口径 = 单核、页缓存全热的本机读数 ⇒ 是"加密层自身成本"的参照,⛔ 不是端到端承诺。
|
||
- **低熵明文的定性结论**:**持组密钥者**可枚举恢复(1000 条域 11.644 ms、命中 1);**中继(无密钥)**枚举不可行(自造字典命中 **0/1000**,持有 1 对明文-密文也预测不出别的),**但能观测相等性 / 重复率**(10,000 块 / 100 值域 ⇒ 仅 100 种密文)⇒ **⛔ 不写"很安全"**:安全边界 = "中继看不到明文"**成立**,但"内容不可推断"**不成立**。
|
||
- ⇒ 这三条**不再是 §10 的"未验证项"**(原始读数见参数表 **§11.3 补记** + 本单 **§8.14**;脚本原文 = 工作区 `_tmp_seq32/p01-perf.txt` / `p02-rotate.txt` / `p03-lowentropy.txt` / `p04-epoch.txt`)。
|
||
|
||
**定稿三:`CONTENT_EPOCH_GRACE_MS` 的选值口径**
|
||
|
||
- 判定式 = **"轮换后还允许旧密文被解多久"**;实测归零时刻 = **最晚 `retiredAt` + 本值**(小 60 s ⇒ 24 h;中 24 h ⇒ 48 h;大 7 d ⇒ 8 d,阶梯衰减、窗口内 `epochExpired=0`)。
|
||
- ⛔ 太短 ⇒ 尚未换完钥的节点掉线;太长 ⇒ 旧密钥留太久(安全)。⚠️ **轮换时点必须约在发布窗口**(§9-8)。⇒ **当前值 24 h 维持**(⛔ 本棒不改任何值)。
|
||
|
||
**为什么 §4 / §6 正文不能改(写下来,防下一位读者"好心改回去")**
|
||
|
||
- 本单的 **§8 前前缀 `149596460288ca1a5b7abddc490f7909` 是一条不变量**("§8 是截断点")—— 它的作用是**证明"计划在接受执行后没被人偷改"**。动 §4/§6/§5 里任何一个字都会**打穿这条证据链**。⇒ 凡口径修正,**一律写在 §8 之后**(本节 + §8.14),并在引用时指明"以 §11.5 为准"。
|