首页
/ RuView BFLD 隐私分级与 BLAKE3 哈希轮换机制解析:从 `privacy_class` 字节到跨站点不可关联的架构级实现

RuView BFLD 隐私分级与 BLAKE3 哈希轮换机制解析:从 `privacy_class` 字节到跨站点不可关联的架构级实现

2026-09-07 14:08:10作者:俞予舒Fleming

导读

本文是 RuView 项目 BFLD(Beamforming Feedback Layer for Detection,基于 802.11ac/ax 波束赋形反馈信息 BFI 的无线感知层)中隐私分级(privacy class)与身份哈希轮换(hash rotation)技术方案 ADR-120 的完整解读。BFLD 通过普通 WiFi 信号实现存在感知、人数统计与生物特征级身份信号分析,而 ADR-120 解决的核心问题,是如何让"同一物理个体在不同站点、不同日期产生不同rf_signature_hash",使跨站点身份关联在密码学层面不可计算。读完本文,你将掌握 BFLD 四档隐私分级(raw/derived/anonymous/restricted)的字段暴露规则、sink marker trait 的静态强制机制、基于 BLAKE3 密钥哈希的 site_salt + day_epoch 轮换算法,以及 identity_embedding 从生成、驻留到零化销毁的完整生命周期,并可在 v2/crates/wifi-densepose-bfld/ 中对照源码逐行验证。


1. 背景:BFLD 的三条结构不变量与隐私缺口

BFLD 框架(见 ADR-118)为其数据面声明了三条结构性不变量:

  • I1:原始 BFI 永不离开节点(raw BFI never exits the node);
  • I2:身份嵌入向量(identity embedding)仅存于内存(in-RAM-only);
  • I3:跨站点身份关联在密码学上不可实现(cross-site identity correlation is cryptographically impossible)。

I1/I2 由 sink 类型化与模块可见性约束强制(见 ADR-119 第 2.3 节)。而 I3 需要一个哈希轮换方案:让同一个物理个体在跨站点、跨日边界时产生不同的 rf_signature_hash,且站点之间无需任何带外协调(out-of-band coordination)。

在此之前的 ADR-115 中已有 HA-PRIVACY 模式,可在"full"与"anonymous"两种表面之间切换,但那是事件粒度(per-event),而非字节字段粒度(per-byte-field)。BFLD 之所以需要后者,是因为其 BfldFrame 载荷在同一结构体中混装了"可发布"的感知数据(presence、motion)与"不可发布"的身份衍生数据(identity embedding、rf_signature_hash)。

ADR-120 同时引用了学术界证据作为动机:KIT 的 BFId 论文(ACM CCS 2025)表明,在同一站点仅需数分钟 BFI 采集即可构建持久性生物特征(persistent biometric);NDSS LeakyBeam(2025)研究亦佐证了该风险面。因此缓解手段必须是结构性的(structural),而非仅靠策略或约定(policy-dependent)。


2. 核心决策一:四档 privacy_class 分级体系

2.1 分级表与默认值

BfldFrame 头部的单个 privacy_class: u8 字节(位置见 ADR-119 第 2.1 节)从四档中选择其一,crate 通过 marker 类型在编译期强制字段可用性。在源码中该枚举定义于 v2/crates/wifi-densepose-bfld/src/lib.rs#L106-L119,并配套 #[repr(u8)]TryFrom<u8> 反序列化(非法字节返回 BfldError::InvalidPrivacyClass)。

Class 名称 使用场景 可用字段
0 raw 仅本地研究,永不联网 全部字段:全精度 BFI 矩阵、身份嵌入向量
1 derived 操作者确认后的局域网研究 降采样角度矩阵、完整特征、identity_risk_scoreidentity_embedding
2 anonymous默认 生产部署 仅聚合感知:presence、motion、person_count、zone_id、confidence
3 restricted 养老院 / 受监管部署 Class 2 再减去 identity_risk_scorerf_signature_hash

新 RuView 节点的默认档位为 2。操作者必须通过 ADR-115 第 7 节所述的既有 --research-mode 标志显式"降级"到 class 1;class 0 仅保留给 cargo test,从 wifi-densepose-sensing-server 不可达。

关于 class 数值序的语义,源码有精确定义:数字越大信息密度越低,PrivacyClass::allows_network()(class 0 之外)与 allows_matter()(仅 class 2/3)这两个关联函数分别编码了网络边界与 Matter 边界(ADR-122)的准入规则。

各档位逐字段的发布/抑制明细可进一步对照 docs/research/BFLD/04-privacy-gating.md 第 1 节的数据暴露表,其中逐字段标注了哪个档位零化/删除哪一块载荷区。

2.2 借助 marker trait 的静态强制

ADR-120 给出如下关键设计——sink 按可承载的信息密度分层,class 0 帧无法传入网络 sink:

pub trait Sink {}

pub trait LocalSink: Sink {}     // Allowed: classes 0,1,2,3
pub trait NetworkSink: Sink {}   // Allowed: classes 1,2,3 (NOT class 0)
pub trait MatterSink: NetworkSink {}  // Allowed: class 2,3 + cluster-filter (ADR-122)

impl Emitter {
    pub fn publish<S: NetworkSink>(&self, sink: &S, frame: BfldFrame)
        -> Result<(), BfldError>
    {
        if frame.header.privacy_class == 0 {
            return Err(BfldError::PrivacyViolation {
                reason: "class 0 to NetworkSink",
            });
        }
        // ... serialize and write
    }
}

实际实现位于 v2/crates/wifi-densepose-bfld/src/sink.rs,与 ADR 文档的 trait 继承略有演化但意图一致:每个 sink 都实现 Sink 并声明 const MIN_CLASS: PrivacyClassconst KIND: &'static strLocalSinkNetworkSinkMatterSink 逐层收窄。运行时闸门 check_class<S: Sink>(class) 用一次简单的 >= 字节比较完成判定(class 数值越大越安全,因而接受更高数值即接受更低信息密度),不通过则返回携带 sink 种类的 BfldError::PrivacyViolation。仓库同时提供了三个零尺寸"kind tag"(LocalKind/NetworkKind/MatterKind)作为默认 sink 类型,供单元测试与编译期表使用。

2.3 档位与 sink 的映射表(源码确认)

Sink trait MIN_CLASS 接受的档位
LocalSink PrivacyClass::Raw 0, 1, 2, 3
NetworkSink PrivacyClass::Derived 1, 2, 3
MatterSink PrivacyClass::Anonymous 2, 3

MatterSink: NetworkSink——每个 Matter sink 同时是网络 sink。


3. 核心决策二:rf_signature_hash 的 BLAKE3 密钥哈希轮换

3.1 算法与源码逐行对照

ADR-120 给出的参考实现如下:

pub fn rf_signature_hash(
    site_salt: &[u8; 32],       // generated on first boot, persisted in TPM/KMS
    day_epoch: u32,             // floor(unix_time_utc / 86400)
    features: &IdentityFeatures,
) -> Hash {
    let mut hasher = blake3::Hasher::new_keyed(site_salt);
    hasher.update(&day_epoch.to_le_bytes());
    hasher.update(&features.canonical_bytes());
    hasher.finalize()
}

其落地代码在 v2/crates/wifi-densepose-bfld/src/signature_hasher.rs,以 SignatureHasher 结构体封装:

  • 常量:SECONDS_PER_DAY = 86_400(轮换模数)、SITE_SALT_LEN = 32(BLAKE3 keyed 模式固定密钥长度)、RF_SIGNATURE_LEN = 32(BLAKE3 默认输出长度);
  • SignatureHasher::new(site_salt):从持久秘密存储(TPM/KMS 或严格权限文件)读取盐后构建,盐从此永不传输
  • day_epoch_from_unix_secs(unix_secs) -> u32unix_secs / 86_400,UTC 午夜翻转;
  • compute(day_epoch, features_bytes)Hasher::new_keyed(&site_salt)update(day_epoch.to_le_bytes())update(features)finalize(),与 ADR 伪代码完全一致;
  • 便捷方法 compute_at(unix_secs, features) 免去手动换算。

其中"确定性序列化"由 v2/crates/wifi-densepose-bfld/src/identity_features.rsIdentityFeatures 枚举负责,它把两种特征源统一到一个规范字节路径之下:

  • 首选IdentityFeatures::Embedding(&IdentityEmbedding)——借用的 AETHER 嵌入向量,直接承载聚类身份,输出 EMBEDDING_DIM × 4 = 512 字节(全部 little-endian f32);
  • 回退IdentityFeatures::RiskFactors { sep, stab, consist, conf }——四元风险因子,输出固定 16 字节(RISK_FACTOR_BYTES),在编码器离线时仍可用。

v2/crates/wifi-densepose-bfld/src/emitter.rsBfldEmitter::emit_with_oracle 中,哈希在把嵌入向量移入环形缓冲之前先行计算(timestamp_ns / NS_PER_SEC 换算 unix 秒再求 day_epoch),随后通过 BfldEvent::with_privacy_gating(...) 构造事件;class 3 会在事件构造器内再次剥离风险分与哈希,形成纵深防御。

3.2 跨站点隔离为什么是"结构性"的

site_salt 是一个每节点唯一、256-bit 随机、永不在网络上传输的秘密。因此站点 A 与站点 B 即使观测到同一个物理个体,产出的 rf_signature_hash 也是互不相关的——不存在操作者(或攻破单个节点的攻击者)能用来跨站关联的"桥接密钥"。

这里要强调的是:BLAKE3 keyed 模式是一个伪随机函数族(PRF,pseudorandom function family),当且仅当 site_salt 保密时成立。这与"策略上禁止分享"有本质区别——桥接在计算上不可能(the bridge cannot be computed),正如 docs/research/BFLD/04-privacy-gating.md 第 2 节所论证:攻破单站无法推断 hash_Ahash_B 是否对应同一人。

3.3 站内、日内跟踪为何安全

站内同一天内,同一人的两帧会产生相似但不相同的临时特征摘要,从而形成同一 (site, day) 范围内的"会话内可聚类、身份不可恢复"的假名(pseudonym)。这是刻意设计:它允许操作者聚合同一会话的事件,而不泄露身份。rf_signature_hash 不是身份——它是限定在 (站点, 日期) 作用域内的假名;同一个人连续两天访问同一站点,将得到两个不同的假名。

3.4 日轮换节奏

轮换以 UTC 午夜为界(epoch k 对应 k * 86_400_000_000_000 纳秒)。一旦轮换发生,此前所有 rf_signature_hash 与未来值在密码学上断开关联——轮换前的日志与轮换后的日志即使由节点操作者本人也无法关联。这与 ADR-120 选型一节互相印证(见第 6 节 Alt 3/Alt 4)。


4. 核心决策三:PrivacyGate 单向降级转换器

BfldFrame 载荷中混装了感知数据与身份衍生数据,因此仅有"事件级"开关不够,必须提供唯一合法的降级通道PrivacyGate::demote(frame, target_class)。其语义(ADR-120 第 2.4 节)为:

  1. 断言目标档位的数值严格大于或等于输入档位(数字越大信息越少);
  2. subtle::Zeroize 语义零化该档位禁止的字段;
  3. 重算 payload_crc32
  4. 返回新帧。

不存在 promote 操作——class-2 帧不可能变回 class-1 帧,因为被丢弃的字段在闸门可达的范围内没有被保留。

源码实现在 v2/crates/wifi-densepose-bfld/src/privacy_gate.rs。需要说明,实际实现为追求跨平台与 no_std 兼容,采用了"手工零化循环 + core::hint::black_box"而非 subtle crate 调用(zeroize_then_clear 先逐字节覆写再截断,black_box(v.as_ptr()) 阻止编译器死存储消除 DCE 吞掉写操作),并额外处理了载荷段:

  • 目标 ≥ class 2(Anonymous):丢弃 compressed_angle_matrix(身份表面),若存在可选 csi_delta 段一并零化后置 Nonefrom_payload 会依据 csi_delta.is_some() 反推 flag 位,保证 HAS_CSI_DELTA 位同步清除);
  • 目标 ≥ class 3(Restricted):再丢弃 amplitude_proxyphase_proxy
  • 帧头 privacy_classpayload_crc32 统一重同步;对无法干净解析的畸形载荷,类字节与 CRC 仍会更新(帧字节原样保留)。

逆向调用会返回 BfldError::InvalidDemote { from, to }(见 lib.rsBfldError 枚举)。此外,事件侧也有对应实现:BfldEvent::with_privacy_gating(...) 在构造时按 privacy_class 剥离身份字段,与帧级闸门形成纵深防御


5. 核心决策四:identity_embedding 的内存生命周期

5.1 类型系统的三重锁

身份嵌入是 AETHER 对比学习编码器(ADR-024)的输出。在 v2/crates/wifi-densepose-bfld/src/embedding.rs 中,IdentityEmbedding 被刻意实现为"不可逃逸"类型:

  • Serialize impl:对任何 MQTT/磁盘/网络 I/O sink 均不可序列化;Debug 只输出维度与 L2 范数(values 显示为 <redacted>);
  • Copy、无 Clone:唯一取值通道是借用的 as_slice(),生命周期绑定 &self
  • Drop 零化:析构时用 0.0 覆写全部 [f32; 128] 存储,并以 black_box 阻止 DCE。

这些约束被编译期断言固化:static_assertions::assert_impl_all!(IdentityEmbedding: Drop)assert_not_impl_any!(IdentityEmbedding: Copy, Clone)——一旦未来某次 PR 误加派生,构建直接失败,这正是 I2"结构性而非文档化"的落地方式。此外文档还提到 #[forbid(serde::Serialize)] lint 作为纵深防线。

5.2 FIFO 环形缓冲

嵌入向量存放于 EmbeddingRingembedding_ring.rs):固定容量 RING_CAPACITY = 64(ADR-120 第 2.5 节"约 30KB"与 64×128×f32 相符),采用无堆分配的 no_std 环形数组实现。行为:

  1. 编码器在每个采集窗口写入(push);
  2. identity_risk_score 计算(ADR-121)消费——注意文档说明实际在 emitter.rs 中嵌入向量先于风险门评估被推入环形缓冲;
  3. 永不写盘、永不发 MQTT;
  4. FIFO 溢出时逐出最旧条目,其 Drop 随即运行、零化 f32 存储。

drain() 是 coherence-gate 的 Recalibrate 动作触发的显式清空钩子(ADR-121 第 2.4 节),一次性 drop 所有槽位。注意 docs/research/BFLD/04-privacy-gating.md 第 3 节的早期版本描述为"600 帧默认容量",当前仓库源码以 64 为准——本文以实际实现为准。


6. 工程护栏:#[must_classify] 默认拒绝式字段分类

ADR-120 的默认拒绝(default-deny)机制针对一个常见事故模式——"新字段先上线,六个月后才被发现身份泄漏"。其设计为:

  • 每个新增到 BfldFrame / BfldEvent 的字段必须标注 #[must_classify](自定义属性宏);
  • 若字段未出现在逐档允许列表中,编译失败;
  • 文档描述的落地形态是属性注解(如 #[privacy_class(minimum = N)])加上 CI 阶段"PR 无分类注解即不能通过"的强制;对字段分类流程的运行时半对应物,可见 privacy_gate.rs 中按 target.as_u8() 阈值分段的零化逻辑。

它强制未来每一位贡献者在新增字段时做出显式隐私决策。


7. 双 ID 空间契约:Soul Signature 部署模式

Soul Signature(见 docs/research/soul/)是一个基于同意(consent-based)的生物特征系统,其刻意为已登记人员产出长期持久的个人身份。因此它无法运行在默认 class 2 下——它所需的 identity_embedding 在该档位上结构性缺失。ADR-120 第 2.7 节定义了契约:

部署模式 privacy_class 未登记旁观者的 ID 空间 已登记人员的 ID 空间
默认 BFLD-only 2 (anonymous) 日轮换 rf_signature_hash 不适用——无登记
Soul Signature 显式选择 1 (derived) 日轮换 rf_signature_hash(不变) 来自 Soul Signature 图的长期不透明 person_id
Restricted / 养老院 3 (restricted) 抑制 不适用——class 3 下 Soul Signature 禁用

两个 ID 空间无碰撞地共存:轮换哈希是"不在同意名册上所有人"的隐私保护标识;稳定 person_id 仅在本人 GDPR/HIPAA 模式下为已登记受试者保留。Soul Signature 的 match_against_enrolled() 只消费内存中的 identity_embedding(I2 依然成立),输出 person_id 加一个校准后的相似度分数,永不把嵌入写盘或上线。class-1 要求在编译期强制:匹配 API 接受 &IdentityEmbedding 参数,该类型只有在 BFLD crate 以 --features soul-signature 编译并针对 class-1 帧时方可构造。仓库中相关实现可查 soul_match.rs 与 soul_channels.rs 模块及对应测试,模块注释明确标注了身份锁定的数据门控状态(真实 AETHER 登记 + 体共振通道数据尚未供给前,仅凭心肺通道身份不可分离)。


8. 方案权衡与后果分析

8.1 备选方案与拒绝理由

  • Alt 1:单一布尔 privacy_mode 标志ADR-115 现状)——拒绝。粒度不足:帧内混装可发布感知数据与不可发布身份数据,闸门必须工作在字段级而非事件级。
  • Alt 2:SHA-256 替代 BLAKE3——拒绝。BLAKE3 keyed-hash 模式在 ESP32-S3 / Cortex-M 核心上约快 5 倍,且对本场景安全余量相当;SHA-256 无密钥模式(须退而求其次用 HMAC-SHA256,可用但更慢)。
  • Alt 3:按小时而非按天轮换——拒绝。小时级轮换会破坏操作者可能需要"此人早上来过、下午又来了"的合法用例。日边界是折中。
  • Alt 4:每事件 nonce 替代日 epoch——拒绝。每事件 nonce 会迫使消费方跟踪会话内哪些事件来自同一人,反而按结构泄漏身份信息;日 epoch 保留粗粒度时间分组却不泄漏更细粒度身份。

8.2 积极后果

  • 跨站点身份关联计算上不可能,而不仅仅是"策略上禁止"——这是无 TEE 前提下可获得的最强隐私保证形式;
  • #[must_classify] 默认拒绝机制截断了"字段上线数月后才暴露泄漏"的常见事故链;
  • identity_embedding 不会被意外序列化——类型系统承载该约束(compile-time assert_not_impl_any);
  • 类转换器让数据生命周期显式化、可审计。

8.3 消极后果

  • site_salt 存储依赖 TPM(rvCSI 平台特性缺口见 ADR-095/ADR-096)或严格权限的秘密文件;丢失 site_salt 将导致历史 witness 比较不可能——这是设计使然,却构成文档性隐患;
  • #[must_classify] 是自定义 proc-macro,是构建中的又一个活动部件;
  • 需要多日分析的运维只能工作在聚合层,而非个体签名层。

8.4 中性后果

class 0 仅供 cargo test;部分 CI runner 可能需要显式 feature flag 才能编译 class-0 路径。


9. 验收标准(AC)与源码/测试对照

ADR-120 列出七条验收标准,绝大多数可在仓库中找到对应实现或测试锚点:

  • AC1Emitter::publishNetworkSink 上遇到 privacy_class = 0 帧返回 BfldError::PrivacyViolation。源码锚点:sink.rs 的 check_classBfldError::PrivacyViolation 变体;
  • AC2:不同 site_salt 的两个节点对同一模拟人产出的 rf_signature_hash 在 100 次试验中 Hamming 距离 ≥ 120 bits。signature_hasher.rs 模块注释指明由 tests/signature_hasher.rs 直接覆盖;
  • AC3privacy_class = 3 帧的序列化载荷中 identity_risk_scorerf_signature_hash 均缺席。由 privacy_gate.rs 的阈值零化与事件构造器双重保证;
  • AC4PrivacyGate::demote(class_1_frame, target=0) 编译失败(compile-fail test)。对应运行时拒绝为 BfldError::InvalidDemote
  • AC5:向 BfldEvent 新增字段而不加 #[must_classify] 即构建失败(CI 强制);
  • AC6IdentityEmbedding 无任何公开可达的 Serialize impl。源码锚点:embedding.rsassert_not_impl_any
  • AC7:drop 一个 IdentityEmbedding 会零化其内存(cargo test --features zeroize-validation 下的可调试验证)。对应 Drop 实现的 black_box 可观测零化。

测试目录位于 v2/crates/wifi-densepose-bfld/tests/,其中 event_gating_irreversibility.rsevent_privacy_gating.rsidentity_embedding.rsframe_roundtrip.rs 等直接围绕本 ADR 的分级、不可逆性与零化语义。


10. 参考与延伸阅读

注:ADR 状态为 Proposed(2026-05-24,决策者 ruv),本文所述能力均以当前仓库中 wifi-densepose-bfld crate 的实际实现为准;运行 cargo test -p wifi-densepose-bfld 可在本地验证各隐私测试用例。

登录后查看全文
热门项目推荐
相关项目推荐