RuView 传感器身份认证与射频证据链追踪:ADR-305 签名测量信封协议及 ruview-attest 实现解析
本文以 RuView 仓库中的架构决策记录 ADR-305: Authenticated sensor identity — RF chain of custody 为骨架,系统讲解 RuView 如何为基于商用 WiFi 的 CSI/CIR 射频感知引入"逐设备加密身份",使每一条测量都能沿着 设备 → 签名测量 → 序列号 → 时间戳 → 标定 → 推理 → 签名事件 的完整链路被追溯与离线复验。读者读完后将理解 ADR-305 解决的威胁模型、签名测量信封的设计细节、单调序列与新鲜度窗口的重放防护机制,以及 ruview-attest 中与验证流水线一一对应的源码级实现,可作为安全部署、二次开发或评审相邻 ADR(296/295/297/318/319)的起点。
ADR-305 在 RuView 感知基座中的定位
RuView 的核心承诺是用商业 WiFi 信号实现室内空间感知与生命体征监测,而推理输出的可信度上限取决于产生它的测量是否可信。ADR-305 是 ADR-300(感知基座项目) 的子文档,拥有其 5 号原语 authenticated sensor identity(认证的传感器身份)。在 ADR-300 的依赖 DAG 中,它与 ADR-306(规范空间本体) 一起作为"脊梁根节点",向下喂养 ADR-301(自动域标定证书) 与 ADR-319(见证链)。
在进入设计之前需要先理解一个关键前提:该 ADR 解决的不是"缺少新原语",而是缺少端到端的证据链(chain of custody)。仓库中已经存在相当数量的可复用类型,文档明确要求"复用而非重建":
wifi-densepose-rufield提供DeviceId、Signature、SignatureBlock、FrameProvenance、ProvenanceClass、SignatureVerifyError等类型词汇——即"一条被签名的帧"所需的类型语言;wifi-densepose-bfld提供设备侧 attestation 表面(对应 ADR-141,其PrivacyAttestationProof实际定义于 privacy_mode.rs);- ADR-295 定义了来源溯源状态机与新鲜度概念(
SpatialStateFreshness),单调序列与新鲜度窗口应当嵌入该状态机而非另起炉灶。
此外,该 ADR 给出的实现必须遵守仓库 CLAUDE.md 的两条铁律:"在每个网络、硬件、FFI 边界校验不可信输入,默认最小权限" 与 "未在真实硅片上验证前不得声称防伪造能力"——后者在文章后续的验证章节中反复出现。
威胁模型:为什么 IP 白名单不够(ADR-296 的遗留风险)
ADR-305 的直接上游是 ADR-296: Sensor data-plane hardening。ADR-296 解决的问题是:CSI UDP 接收端此前无条件绑定 0.0.0.0:{udp_port}(对应 HTTP 侧 --bind-addr 默认 127.0.0.1 的行为缺失),任何可达该 UDP 端口的宿主机都能注入一个"形状合法"的帧,将自动检测中的服务器切换到实时数据源状态,进而影响存在性/生命体征/自动化输出。
作为"第一步",ADR-296 落地了以下机制:
| 配置项 | 语义 |
|---|---|
--udp-bind(环境变量 RUVIEW_UDP_BIND) |
UDP 绑定地址,默认 127.0.0.1;绑定到可路由地址成为显式操作者选择,桌面/家电默认仍保持 loopback |
--udp-allow |
可选源 IP/CIDR 白名单;未命中来源的帧被丢弃并计数,loopback 始终放行 |
| 启动安全日志 | 记录绑定范围与白名单是否生效;拒绝"无可信白名单的可路由绑定",除非显式传入 --udp-insecure-lan 覆盖(对应既有 Docker HTTP 拒绝逻辑) |
但 ADR-296 在文档中明确承认并记录了这个缺口:IP 白名单无法阻止局域网(LAN)内的 IP 欺骗。同一子网内的任意主机都可以冒用被允许的源 IP 发送帧。它把"每设备预置密钥、MAC/AEAD、设备标识、单调序列号、新鲜度窗口与重放拒绝"整体推迟到后续 ADR——这个后续就是 ADR-305。
三个候选方案的取舍
ADR-305 评估了三条路线并记录了拒绝理由:
- 停在 ADR-296(绑定 + IP 白名单):被拒绝。ADR-296 自身已明确指出这在可信 LAN 上不足够,子网内任何主机仍可冒用设备身份。
- 仅做 TLS/DTLS 传输层认证:被拒绝。它认证的是信道而非测量本身;无法在存储转发(store-and-forward)后存续,无法把序列号绑定进被签名对象,也无法给下游证据/见证层提供可离线复核的东西。
- 逐设备签名密钥 + 签名测量信封 + 单调序列 + 新鲜度窗口,复用 RuField/BFLD 既有类型:被采纳。
决策:贯穿传感服务器的"认证帧信封"
ADR-305 的决策核心是引入一个认证帧信封(authenticated frame envelope),由既有 RuField/BFLD 类型组装而成,并贯穿传感服务器全链路。以下四个小节按决策原文编号逐条展开,并与源码实现对照。
1. 逐设备预置身份(Per-device provisioned identity)
设计要点:
- 每台射频节点(ESP32-S3/C6 节点或适配器)预置一对密钥:设备持有私钥,服务器持有已登记的公钥并绑定到某个
DeviceId; - 预置是一个显式、被授权的登记(enrollment)步骤——在操作者登记其公钥之前,设备一律视为不可信;
- 私钥永不写入日志或提交进仓库(遵循 CLAUDE.md 凭据规则);ESP32 侧遵循 firmware/esp32-csi-node 的密钥处理说明;
- 登记记录把
DeviceId → 公钥 → 能力(capabilities)绑定起来(经由 ADR-141 的 attestation 表面),因此设备只能对其被证明有能力感知的现象断言测量;这正是 ADR-318(能力证书) 后续消费的输入。
这一"未登记即不可信"的语义在实现中是显式的一等状态:ruview-attest 的登记表是一个以 DeviceId 为键、保存"验证器 + 最后接受序列号"的映射,只有调用 enroll() 之后的设备才可能通过校验(见下文第 3 节)。
2. 签名测量信封(Signed measurement envelope)
线缆上传输的帧从裸测量变为一个签名信封:
签名覆盖
{DeviceId, sequence, timestamp, measurement-hash}的规范化序列化;测量本身(CSI/CIR 载荷)由哈希覆盖,因此篡改可被检出,同时不必把整个载荷二次嵌入信封。
校验复用 RuField 的 Signature/SignatureVerifyError:签名验证失败的帧被丢弃并计数——正如 ADR-296 丢弃白名单外来源一样,这是边界上的 Err,绝不会降级为"继续处理的警告"。
这一决策在 ruview-attest/src/lib.rs 中得到了逐字实现(该 crate 的模块文档开头即声明 "This crate implements ADR-305 (authenticated sensor identity), phase 1 of the ADR-300 perception substrate")。核心类型形成一个清晰的洋葱结构:
// 被签名的内容:验证者必须能逐字节重建它才能验签
pub struct MeasurementContent {
pub device: DeviceId, // 认证的来源设备
pub sequence: u64, // 逐设备严格单调序列号(重放防御)
pub timestamp: Timestamp, // 设备声称的采集时间戳
pub payload_hash: PayloadHash, // 测量载荷的哈希(篡改检测)
pub calibration_ref: Option<CalibrationRef>, // 生效中的标定证书引用
}
// 线上传输对象,也是 ADR-319 见证链序列化的单元
pub struct SignedMeasurement {
pub content: MeasurementContent,
pub signature: Signature, // 覆盖 canonical_bytes() 的标签
}
实现细节中有三个值得注意的工程决策:
- 域分离(domain separation):签名输入前置固定前缀
b"ruview-attest/v1\x00signed-measurement\x00",确保本协议产生的标签永远不会与其他用途的哈希混淆; - 长度前缀规范化序列化:
canonical_bytes()对设备 ID、载荷哈希、标定引用等变长字段统一采用u32小端长度前缀 + 字节内容,且不依赖任何 serde 格式——编码无歧义,任一字段都不可能与其他字段混淆,验证者可以稳定重建签名输入; - 固定宽度常量时间比较:验签时使用
constant_time_eq进行常量时间相等比较,规避时序侧信道。
MeasurementContent 的签名输入由 canonical_bytes() 确定性生成(先域分离前缀,再按固定顺序写入 device 字段、sequence(LE u64)、timestamp(LE i64)、长度前缀化的 payload_hash,最后以 1 字节标记区分有无 calibration_ref)。SignedMeasurement::sign() 负责把签名过程封装为"内容构造 → 载荷哈希 → 签名"三步。
3. 单调序列 + 新鲜度窗口(重放防御)
重放(replay)防御由两个互补机制构成:
- 单调序列:每台设备维护严格递增的逐设备序列号;服务器按
DeviceId追踪最后接受的序列号,非递增的序列被当作重放拒绝; - 新鲜度窗口:以服务器时钟偏差预算为界约束
timestamp与服务器时钟的差距,过期帧被拒绝。
该设计明确复用 ADR-295 的 SpatialStateFreshness 而非发明一套平行的"陈旧"概念,并与 ADR-297 的陈旧节点处理组合工作。
实现侧最核心的部件是 AttestationVerifier(泛型于 Verifier 特征),其 verify() 按严格顺序执行五道关卡,对应着文档要求的每一条拒绝规则:
1. 设备是否已登记?(UnknownDevice)
2. 签名是否有效?(BadSignature)
3. 呈现的载荷是否与签名的 payload_hash 一致?(Tampered)
4. 序列号是否严格大于该设备上次接受的序列?(Replay)
5. 时间戳是否落在新鲜度窗口内?(FreshnessPolicy::check)
关键正确性设计是逐设备序列状态只在全部通过后才推进(entry.last_sequence = Some(content.sequence)),因此"被拒绝的帧永远不会消耗一个序列号"——攻击者无法通过发送垃圾帧把合法设备的序列号推到未来、进而合法拒绝其后续帧。同时,verify() 返回的 VerifiedMeasurement 携带已验证的 device/sequence/timestamp/payload_hash/calibration_ref,作为可信链记录继续向后传递,供标定、推理与见证链消费。
一个必须诚实指出的实现事实(与 ADR 的验证纪律一致):仓库当前随附的参考实现是 Blake3MacSigner——一个 keyed-BLAKE3 MAC(对称 MAC),签名者与验证者共享同一把秘密,因此它能演示端到端证据链逻辑,却不提供不可否认性与公钥信任边界。crate 文档明确标注其为 SYNTHETIC-grade reference:使用它得到的任何防伪造保证都是合成级证据,而非现场设备证据;投入部署级使用前需要换用 Ed25519 非对称签名器。架构上这一替换是廉价的——只要为新签名器实现 Signer/Verifier 两个特征(分别基于私钥签名、基于登记公钥验证),信封、序列、新鲜度与防篡改逻辑完全不变。
4. 把证据链带进最终事件
设计上,验证成功后帧的 FrameProvenance 记录已验证的 DeviceId、序列号与时间戳;标定(ADR-301)与推理为各自的变换附加注释;最终发出的空间事件(ADR-306 本体)携带一条已签名的来源谱系(signed provenance lineage)。ProvenanceClass 仍强制执行来自 ADR-282/ADR-279(不变量 6)的约束:实测证据链永远不能被别名化为合成,反之亦然。这条端到端已签名谱系正是 ADR-319 见证链要序列化、ADR-318 能力证书要作为证据指向的底层材料。
在统一帧模型 frame.rs 中,这一不变量已经具象化为类型层面的强约束:
ProvenanceClass只有两个变体Measured/Synthetic,注释明确"两类永不对齐:没有第三个变体,也没有默认值"(frame.rs);FrameProvenance把class(实测/合成)、evidence(证据阶梯等级)、device_id、firmware与receipt_id打包在一起(frame.rs),其中receipt_id把推理结果回链到具体帧;EvidenceLevel定义了从L0Simulation到L5Production的六级公开证据阶梯(frame.rs);- 权威原生帧
RfFrameV2内置provenance字段,且其构造函数强制一条一致规则:Synthetic ⇒ 恰好 L0Simulation,Measured ⇒ 至少 L1CapturedReplay——合成证据永远无法伪装成实测(frame.rs)。
因此 ADR-305 的"签名证据链"不是游离在外的附属物,而是直接落在仓库统一帧模型的不变量体系里。
兼容性策略:默认保持可选(opt-in)
框架级安全特性最容易破坏存量部署,因此 ADR-305 明确规定信封为按部署可选、在登记时协商(negotiated at enrollment):
- 未登记的桌面单节点部署保持现状——在 ADR-296 的 loopback 默认背后以"未认证"方式继续工作,行为不变;
- 可路由、多节点或车队(fleet)部署(对应 ADR-316)则必须登记身份;
- ADR-296 的启动安全日志被扩展:明确打印"帧认证是否已启用",让运维人员一眼看清当前部署的安全姿态。
这种"默认安全、进阶可选、日志可读"的渐进路径,是它与 ADR-296 的"loopback 默认 + --udp-insecure-lan 显式覆盖"哲学一脉相承的体现。
影响与权衡
ADR-305 如实记录了采纳后的代价与边界:
- 收益:ADR-296 明示的遗留风险——LAN 欺骗与重放——对已登记部署被关闭;被认证的是测量本身而非仅信道,因此保证能够在存储转发进入见证链后仍然存续。
- 运维责任:登记/密钥管理(预置、轮换、吊销)成为部署阶段的操作责任,文档中给出定义;但其车队级分发由 ADR-316 承担,ADR-305 不越界实现。
- 性能成本:签名验证在摄取边界带来每帧 CPU 开销,文档承认这是"换取可验证证据链的刻意代价",并要求在验证章节中以基准测量上界。
- 合约演进:帧合约增加 schema 字段;未登记部署不受影响,迁移访问器效仿 ADR-297 的方式。
- 诚实的证据边界:任何防伪造声明在真实硅片上验证之前都不算被实测——一套通过的单元/集成测试只证明逻辑正确,不证明现场设备路径可用。
验证计划与验收门槛
ADR-305 的验证矩阵(对应文档 "Validation" 章节)本身就是一套清晰的验收标准,与 ruview-attest 的校验关卡一一对应:
| 层级 | 覆盖场景 |
|---|---|
单元测试(cargo test -p wifi-densepose-sensing-server、-p wifi-densepose-rufield) |
合法信封被接受;坏签名被拒绝并计数;非单调序列被当作重放拒绝;超出新鲜度窗口的时间戳被拒绝;未登记 DeviceId 被拒绝;实测/合成来源别名被拒绝(ADR-279 不变量 6) |
| 集成测试 | 捕获/合成的多帧流产出可验证的 device → … → signed event 谱系,ADR-319 可离线序列化并复验 |
基准(cargo bench) |
测量每帧验证成本,为摄取开销设定上界 |
| 真实硅片证据(硬性要求) | 部署级认证声明前必须提供"已登记 ESP32 节点端到端签名帧"的启动/运行日志;成功的构建或模拟器运行不算硬件证据 |
值得注意:ADR 正文引用的 crate 名(wifi-densepose-rufield)与当前实现 crate 名(v2/crates/ruview-attest)存在演进差异——ruview-attest 的模块文档自述实现 ADR-305,因此在阅读测试命令时建议同时对照两个包名,以仓库实际目录结构为准。
结语:与相邻 ADR 一起读
ADR-305 是 RuView 感知基座安全脉络上承前启后的关键一环。若要完整理解它的设计,建议沿下列文档线索继续阅读(均以仓库根目录为基准):
- 威胁来源:ADR-296 Sensor data-plane hardening——loopback 默认绑定与 IP 白名单(第一步);
- 消费方与上游:ADR-300 Perception substrate program、ADR-301 自动域标定、ADR-306 规范空间本体、ADR-319 见证链、ADR-318 能力证书;
- 复用的既有机制:ADR-295 来源溯源状态机与新鲜度、ADR-141 BFLD 隐私控制面与 attestation、ADR-279 原生帧合约;
- 源码锚点:认证信封与验证流水线 ruview-attest/src/lib.rs、统一帧与来源类型 frame.rs、BFLD 隐私证明 privacy_mode.rs、ESP32 设备侧密钥处理 firmware/esp32-csi-node。
对一个把"无摄像头也能感知世界"作为目标的系统而言,ADR-305 回答的是一个根本问题:当系统开始影响存在性判断与生命体征告警时,你如何证明进来的每条测量真的来自你信任的那台设备? 从绑定地址到 IP 白名单,再到逐设备加密身份与端到端签名证据链,这条演进路径本身就是"默认最小权限 + 在边界验证不可信输入"原则在射频数据平面上的完整落地示范。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0629
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python07
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00