
上一篇27-1《三问三防权重、记录、出处》 下一篇27-3《端到端取证VLLM_ATTEST1 → 收证 → verify_attest.py》 **源码精读篇**本文为源码/方法论精读无独立实测文中数字均引述仓库 docs 的板端实测记录 **一句话导读**推理引擎的出证帧规范把证什么钉成字节级契约——拆 vatt_seal_json 的长度前缀帧 F(x)u32be(len)‖x 怎么把请求、参数、计数、输出文本、时间戳绑成一条 SM3 摘要讲 body_sha 为何让验证方无需 tokenizer 即可离线复算核验。 **关键词**手搓推理引擎、大模型推理、出证帧规范、body_sha、字节绑定、attestation、可验证推理1. 知识点帧规范——把语义翻译成字节序列attest 摘要schema3的规范写在 vllm_attest.h 8–26 行digest SM3( VLLM-AT-3 || F(model_fp) || F(model_id) || F(device_id) || F(user) || F(params) || F(n_prompt_tokens) || F(n_gen_tokens) || F(finish) || F(ts) || F(body_sha) || F(output_text) ) F(x) u32be(len(x)) || x # 按字节为什么必须 F(x)长度前缀 原字节而不是直接拼接因为裸拼接有歧义abcd和abcd会产生相同字节流字段边界丢失。长度前缀让每个字段自描述且字节级无歧义。这条规则一视同仁地作用于自由文本user、output_text按 UTF-8 原始字节喂入不做任何清洗/折叠——凭证绑定的是引擎真正输出的那串字节数字n_prompt_tokens / n_gen_tokens / ts先格式化成十进制 ASCII%d/%ld再入帧防止同一数值不同写法造成的复算分叉body_shaSM3(客户端原始请求体字节)的 64-hex 字符串。它让验证方用自己的请求原文复算 SM3 即可比对——不需要 tokenizer、不需要聊天模板、不需要引擎这正是字节绑定换来的可独立验证性。还有一个常被忽略的字段model_fp模型指纹。它在模型加载成功后一次性固化vatt_model_ready锚定 config.json 模型目录文件清单namesize——防响应声称来自模型 A、实际跑的是模型 B这种事后偷换。2. 对应代码vatt_seal_json 的帧组装vllm_attest.c 266–314#define DF(p, n) do { if (dump_safe dn (n) cap) memcpy(dbufdn,(p),(n)); dn (n); vc_sm3_update(c, (p), (n)); } while (0) #define DFLEN(p, n) do { uint8_t _h[4]; vatt_u32be(_h, (uint32_t)(n)); /* u32be(len) */ DF(_h, 4); DF((p), (n)); } while (0) /* || x */ DF(VATT_MAGIC, ...); /* VLLM-AT-3 */ DFLEN(g_ctx-attest_fp, ...); /* model_fp */ DFLEN(model_id, ...); DFLEN(device_id, ...); /* 模型/设备标识 */ DFLEN(user, ...); char params[128]; vatt_params_str(r, params, ...); /* t..,p..,m..,k..,mt..,th.. */ DFLEN(params, ...); snprintf(npt, ..., %d, r-n_prompt_tokens); /* 数字 → 十进制 ASCII */ DFLEN(npt, ...); DFLEN(ngt, ...); DFLEN(finish, ...); DFLEN(ts, ...); DFLEN(body_sha, ...); /* 请求体 SM3 的 64-hex */ DFLEN(r-text, r-text_len); /* 输出文本原始字节 */ vc_sm3_final(c, d); /* 314: digest 就绪 */DFLEN宏就是规范里的F(x)先喂 4 字节大端长度再喂内容本身。整个 seal 函数只有一件事——按固定顺序把 12 个字段的字节流喂进同一个 SM3。随后vatt_sign345–355用设备私钥对这个 digest 做 SM2 签名IDVLLM-ATTEST-1k 每次现取——25-3 的纪律在这里同样生效r‖s 与 digest 一起装进凭证 JSON 发给调用方。3. 改动后果为什么响应改一个字必然验签失败验证方verify_attest.py做的事与 seal 完全对称读凭证里的字段 → 用自己的--request复算 body_sha、用自己的--output喂最后一个字段 → 重算 digest → 与凭证里的 digest 比对 → SM2 验签。只要喂进去的任何一个字节与签名时不同digest 就不等验签必然失败。归档侧改动影响的帧字段验签结果输出文本改 1 字F(output_text)FAILdigest 变请求原文被替换F(body_sha) 复算不符FAILbody_sha 不一致时间戳被改F(ts)FAIL声称的 token 数与实际不符F(n_prompt_tokens)/F(n_gen_tokens)FAIL换一个模型目录的档案F(model_fp)FAIL把params里的ktop-k或ththinking改成别的档位F(params)FAILschema3 起推演改动后果若实现时图省事把output_text先做 JSON 转义再入帧vatt_seal_json的 JSON 字段确实转义但摘要入帧的是原始字节两处必须分清——验证方若不知道引擎对转义文本做摘要还是对原文做摘要复算必然分叉。所以帧规范必须写清入帧字节 原始字节且实现与验证脚本verify_attest.py的 14–26 行注释严格同口径。这也是 Day 18 分词器教训的延伸凡跨进程/跨实现比对一律按字节定契约。4. 学员调试任务A 档动手读verify_attest.py主验证函数复算 digest 部分对照vllm_attest.c266–314 逐字段核对顺序与编码手动把--output换成输出文本的转义版如把换行换成\n两字符重跑验证观察 FAIL体会字节契约的严格。B 档纯读源码回答①F(x)len‖x的长度前缀为什么能消除abcdvsabcd的歧义若没有长度前缀攻击者能否构造一个摘要不变但字段边界不同的替换提示SM3 抗碰撞下不能构造但规范歧义会导致合法验证失败② 数字字段为什么先转十进制 ASCII 再入帧而不是直接喂 4/8 字节定长提示两端语言无关、避免字节序歧义③body_sha字段本身是 64-hex 字符串为什么还要再套一层F()提示与其它字段统一帧格式防边界歧义预期输出字段序清单12 项 一次转义文本导致 FAIL的对照实验能讲清凭证强度 帧规范严格度。收尾本篇源码点名vllm_attest.c帧组装 266–314、签名 345–355、vllm_attest.hschema3 注释 8–26、verify_attest.py纯 Python 复算开源仓库Kestrel-LLM (Gitee)AGPL-3.0-or-later 或商业许可二选一下篇预告规范讲完了跑一次真的27-3 板端端到端取证——VLLM_ATTEST1启动 → 发一次请求 → 收下attest凭证 → 用verify_attest.py离线验签 PASS再把输出改一个字验出 FAIL。