最近翻到一篇讲私有稠密检索的,八月底投的第一版,这个月 3 号刚更了一版。这类东西被讲玄了很多年——一提就是全同态、安全多方那一套,好像不把整条链路加密封死就没资格谈隐私。
看完这篇我的感觉是:卡住的地方根本不在加密那一步。
先画开这个领域一直以来的形状。私有检索,之前就两条路:
路 A:全量加密扫
每条查询 × 整个语料(几百万条),全在密文里过一遍
→ 安全,但每来一次查询就重算一次全库
→ 语料涨一倍,开销涨一倍;查询量涨,开销再涨一倍
路 B:先粗糙聚类,只扫命中的那几个簇
→ 便宜,但一刀砍掉大半候选,召回哗哗掉
贵和准,二选一。这就是它卡了这么多年的形状。
等等等等,先别急着说"那就花钱买准"。服务商多烧的算力最后都折进调用价里,这不是加预算能绕过去的取舍。
然后我看这篇的做法,第一反应是它在耍赖。
它核心就一个动作:不藏"你查了什么",只让服务器知道"该去哪儿翻"。
画张图:
你的查询
│
┌───────────┴───────────┐
│ │
① 粗:学习出来的哈希码 ② 细:加密重排 + 不经意键传输
随机化二值码 (精确的这一步才配上密码学)
↓ ↓
服务器照码翻出一小叠候选 ──► 200~500 条 ──► 选出最终那一条
粗的那层不加密,靠"模糊指路"换便宜
两层。粗的那层便宜、泄一点;细的那层贵、藏死。整个设计的机灵全在"只把贵的那一步压在最后那几十上百条上"。
"指路"这个词我用得很认真。打个比方:你给分拣中心寄包裹,你不能告诉它里面是什么,但得给它一个能扫的条码,它才知道该丢哪个格口。条码不透露内容——但它透露了"这东西大概属于哪一类"。
这个比喻先放这儿,等会儿要回来标它漏风在哪儿。
我第一版画这张图的时候画错了。当时我以为是这样:
查询 ──► 加密 ──► 服务器全量搜 ──► 加密返回
一条直线,每一格都加着密。看着挺安全,毛病也很明显:这条线上每一格都贵,而且你没法只优化其中一格——任何一格做便宜了,整条链就破了。
我第一版会画成直线,是因为脑子里默认"隐私"是个均匀的属性:要么全藏,要么没藏。
重来。真正有用的问题不是"怎么把它全藏起来",是:哪一格必须藏死,哪一格只要别明说就行。
然后我在另一个地方卡了挺久。
哈希。哈希我熟——哈希是确定性的,同一个输入永远同一个输出,不然你没法拿它查表去重。可这篇讲的码是随机化的。随机的哈希还算哈希?我盯着那两行看了半天,脑内只有羊叫。
后来想通了:它不是拿来查表去重的,是拿来当"粗糙坐标"用的。同一个查询,每次算出来的码可以不一样,只要都还落在语义上差不多的地方。
关键在于——确定性在这里恰恰是个缺点。你的查询码要是每次都一模一样,服务器把日志攒几天,那个码的分布本身就是查询的指纹。随机化不是"顺手加的一点噪声",它是这个方案能不能成立的前提之一。
这也解释了它为什么专门强调要压住 embedding-inversion 和 property-inference 这两类泄露:粗糙那层一旦泄成稳定指纹,后面藏得再死都白搭。
顺便给个数。候选列表开到 200 到 500 条的时候,在五个语料上就基本贴住全量检索的结果了。这五个语料跨度挺大,从两万五千文档一路到五百四十万文档。NQ 全量那组是 268 万 passage,10-Gbps 链路上跑,整条 Qwen3-32B 的 RAG 流水线加 0.73 秒,大概 10%。
10% 这个量级,回去对着上面那张两层图看是能对上的。粗的那层几乎是白嫖——算一次二值码、扫一遍短列表;贵的只有最后那两百到五百条的重排,而加密重排本来就是按条数计费的。所以它没让成本消失,它是让成本不再跟着语料总量走。
这话我说得有点别扭,但意思你应该懂。
OK,回来标漏风。
我说"指路",听着好像服务器只拿到一个终点、其余一概不知。不是的。那个二值码本身就是查询的一个粗糙投影。投影比原查询泄得少得多,但绝不是零。同一个语义片区里的两个不同查询,可能落到同一个码上——服务器至少知道"这两回都落在这片"。
所以那 200~500 条的短列表不是免费的午餐,它是拿一点点泄露换来的。中间那条缝有多窄,才是这篇值不值得看的真正地方。粗糙层完全不泄,你就得退回全量加密;粗糙层泄多了,私有检索四个字当场作废。
有件事我得老实说:它的方向性度量差分隐私具体怎么定义、怎么证的,我还没画开,先搁这儿。等哪天想明白了再补。但至少"为什么要随机化"这一步,我现在能用自己的话讲出来了。
照例留个自检:现在你能不能给一个没接触过的人讲清楚——为什么这套东西不把加密放在第一步?
如果你讲到一半卡住,多半是因为你跟我一样,下意识觉得"隐私应该是个均匀的属性"。
它不是。
本期画开:藏得住,还得指得对,这两件事得分开办。💡