卷 V · 落地CH 19深度 19/23

匿名集才是隐私的单位

第 7 章证明了协议本身一个比特都不泄露——真记录和假记录的分布距离精确为 0。但那个证明只覆盖了「协议里传的那几个数」。它管不了你什么时候点的按钮、转了多少钱、从哪个 IP 连的。本机模拟:一个 1000 人的混币池,你存完 10 分钟就取——有效匿名集只剩 1.6 人

1000 → 1.62^H隐私不是布尔值

◻ 本章先赊三条
第一条。隐私不是一个布尔值(「匿名 / 不匿名」),它是一个数:你藏在多少人里 第二条。这个数不等于池子里有多少人。它由观察者的后验分布决定,而时间、金额、网络这些「协议之外」的信息会把它砍掉一两个数量级。 第三条。零知识在隐私上最有前途的用法,可能不是「什么都不说」,而是「精确地只说该说的那一句」——包括向监管说。

先看这类系统长什么样

做隐私的那一类系统(Zcash 的屏蔽池、Tornado 那类混币器、Aztec)结构上都是同一个东西:

存入   任何人往池子里存一笔钱,同时公布一个「承诺」——
       它锁住了「这笔钱的所有者是谁」,但外人看不出来(第 4 章)。

取出   你出示一份零知识证明,内容是:
       「⟦ 我知道池子里某个承诺的秘密 ⟧,而且这笔钱还没被花过。」

       注意这句话里没有说是哪一个承诺。
       验证者只知道「是池子里的某一个」。

为了防止同一笔钱被花两次,证明里还要附一个作废标记(nullifier)——它由那笔存款的秘密算出来,看起来是个随机数,但同一笔钱算出来的永远一样。系统记下所有见过的作废标记,重复的直接拒绝。

这套结构在密码学上是干净的:第 7 章那个论证保证了证明本身不泄露你用的是哪个承诺。所以理论上,你藏在「池子里的所有人」中间。

那个数到底怎么量

◆ 匿名集的正确定义

不是「池子里有几个人」,而是:观察者看完他能看到的一切之后,对「这笔取款属于谁」这个问题的后验分布,有多分散。

匿名集 = 2^H

其中 H 是那个后验分布的熵(以比特计)。

  · 如果观察者完全分不清(1000 个人等概率),H = log₂1000 = 9.97,匿名集 = 1000。
  · 如果他能锁定到 4 个人等概率,H = 2,匿名集 = 4。
  · 如果他百分之百确定是你,H = 0,匿名集 = 1。

这个定义和这本书的姊妹篇《意外》里那个「信息就是意外」是同一套度量。隐私的单位是比特,而不是「有 / 没有」。

时间就够了

做一个模拟:1000 个人在 30 天里陆续存钱,取款时间等于存款时间加上一个平均值为 τ 的随机等待。观察者只知道每笔存款的时间这笔取款的时间,其他一无所知。算他的后验分布的熵:

你存完多久才取有效匿名集意味着
10 分钟1.6 人基本等于被点名
1 小时4.8 人缩到不足百分之一
1 天91.3 人还剩不到十分之一
1 周541.1 人过半
1 个月887.9 人接近名义上限

协议一个比特都没泄露,而「你什么时候点的按钮」把 1000 砍到了 1.6。

时间还只是最容易想到的那一条。真实的去匿名工作会同时用上:

侧信道为什么致命
金额存 13.37 个币、取 13.37 个币 → 匿名集 = 1。这就是为什么正经的混币器强制固定面额(比如只能存 0.1 / 1 / 10)
手续费与找零你用来付手续费的那个地址,往往和你的身份地址有关联
网络层IP、节点连接模式。不走 Tor 的话,观察者可能直接看到广播源
交易图取出来之后马上转给交易所 → 交易所的 KYC 记录接上了整条链
池子太小一个只有 5 个人用的隐私池,数学再完美,匿名集上限就是 5
◆ 一条判据

零知识保证的是「协议内的信息泄露为零」,而现实中的去匿名,几乎全部发生在协议之外。

这不是零知识的失败——它诚实地完成了它承诺的那件事。失败的是「用了零知识就等于匿名」这个推理。隐私是一个系统属性,而协议只是系统的一部分。

监管这一头

这类系统会遇到一个绕不开的问题:洗钱。而这本书前面所有的技术讨论,在这里会撞上法律和政治。

值得记住的一条时间线(截至 2026 年 8 月):

时间事件
2022 年 8 月美国财政部 OFAC 制裁了 Tornado Cash,把一组智能合约地址列入了制裁名单——这在历史上是第一次制裁一段代码而非一个实体
2024 年 11 月美国第五巡回上诉法院在 Van Loon 案中裁定:不可变的智能合约不属于制裁法条下的「财产」,OFAC 越权
2025 年 3 月OFAC 将 Tornado Cash 相关地址移出制裁名单

但把地址移出名单,不等于问题解决了。技术上更有意思的答案是这几年出现的两类工具,它们都是「用零知识来提供合规」,而不是「用零知识来对抗合规」

  • 查看密钥(viewing key)。Zcash 支持你把一把只读的密钥交给审计方或税务机关,让对方看到你的全部交易,而其他人仍然看不到。把「对谁公开」变成了一个可以精确设定的旋钮。
  • 关联集与「清白证明」(Privacy Pools 那一类,2023 年由 Buterin 等人形式化)。用户在取款时,不再只证明「我在池子里」,而是证明「我在池子的某个子集里,而这个子集里不包含任何已知的黑名单存款」于是守法用户可以主动把自己和赃款分开,而不必暴露自己是谁。

第二个想法很值得记住,因为它翻转了整个论述:以前的框架是「隐私 vs 监管」,二选一;而这里零知识让你能精确地证明一个否定命题——「我不属于那个集合」——从而两边都要到了一部分。第 20 章会把这个思路推到更日常的场景。

⌨ 自己跑一遍

那个「1000 → 1.6」的模拟只有二十行,而且你可以拧那个 τ 看曲线怎么塌:

import math, random

random.seed(42)
N = 1000
deposits = sorted(random.uniform(0, 30*24*3600) for _ in range(N))   # 30 天内陆续存入

def effective_set(tau, my_idx, trials=200):
    """观察者只知道各笔存款的时间和这一笔取款的时间"""
    total = 0.0
    for t in range(trials):
        wait = random.expovariate(1 / tau)                # 我等了多久
        w_time = deposits[(my_idx + t) % N] + wait        # 取款时刻
        # 后验:谁能在 w_time 取款?密度 ∝ exp(−Δ/τ),且 Δ>0
        post = [math.exp(-(w_time - d) / tau) / tau if w_time > d else 0.0
                for d in deposits]
        Z = sum(post)
        if Z == 0:
            total += 1; continue
        H = -sum((p/Z) * math.log2(p/Z) for p in post if p > 0)
        total += 2 ** H                                   # 匿名集 = 2^H
    return total / trials

for label, tau in [('10 分钟', 600), ('1 小时', 3600), ('1 天', 86400),
                   ('1 周', 604800), ('1 个月', 2592000)]:
    print('池子 %d 人,等 %-6s 再取 → 有效匿名集 %.1f 人'
          % (N, label, effective_set(tau, 500)))
池子 1000 人,等 10 分钟 再取 → 有效匿名集 1.7 人
池子 1000 人,等 1 小时  再取 → 有效匿名集 5.2 人
池子 1000 人,等 1 天    再取 → 有效匿名集 94.4 人
池子 1000 人,等 1 周    再取 → 有效匿名集 541.2 人
池子 1000 人,等 1 个月  再取 → 有效匿名集 894.0 人

这是蒙特卡洛模拟,每次跑都会浮动一点——上面这次是 1.7,书里那张表是另一次运行(1.6)。数量级和形状是稳定的:从 1000 掉到个位数,只需要「你等得不够久」。

两个值得动手的实验:(1)N 改成 100 或 10000,看等待时间需要怎么变才能保住同样的匿名集——你会发现池子大小和等待时间可以互相换。(2) 把存款时间从「30 天均匀」改成「集中在一个小时内」,匿名集会立刻变好——因为大家的行为变整齐了。这就是固定面额、定时批量提现这些设计存在的理由:它们在强行制造整齐。

python3 anon.py

在线跑:python.org/shell,把 trials 调到 50 会快很多。想看真实的分析方法,搜「Tornado Cash anonymity set analysis」这类公开研究,它们用的正是这套后验熵。

▸ 在现实里
  • Tor 的设计文档里就写着这一章。它明确声明不防「全局被动观察者」——一个能同时看到入口和出口流量的对手,可以靠时间相关性把你识别出来。和这一章那个 1.6 是完全相同的攻击。
  • 差分隐私是同一个思想的另一种形式化。它不问「有没有泄露」,而问「泄露了多少(ε)」。把隐私从布尔值变成一个可以定价、可以预算、可以累加的量——这是过去二十年隐私工程最重要的一次观念转变,而这一章的「匿名集 = 2^H」是它的一个特例。
  • k-匿名。医疗和人口数据发布领域的老概念:任何一条记录都必须和至少 k−1 条其他记录长得一样。它就是「匿名集 ≥ k」。而它出名的原因恰恰是它的失败——下一章那个「生日 + 性别 + 邮编就能识别 87% 的美国人」,正是 k-匿名被打破的经典案例。
  • 浏览器指纹。你的字体列表、屏幕分辨率、时区、显卡型号加起来是几十比特——足够在几十亿人里唯一定位你,即使你清空了所有 cookie。这是「匿名集 = 1」在日常里最常见的形态,而它同样发生在「协议之外」。
✗ 这个直觉是错的

「这个系统用了零知识证明,而且证明被数学证明是零泄露的,所以我在里面是匿名的。」

第 7 章那个「总变差距离 = 0」是真的,但它只覆盖协议里传的那几个数。而观察者能看到的远不止那几个数:时间、金额、gas、IP、之后的资金流向、你在推特上说过什么。

这是一个非常一般的模式,值得从这本书里带走:密码学证明的边界,就是它建模了的那个东西的边界。模型外的一切,它一个字都没保证。侧信道攻击、时序攻击、流量分析——全都住在模型之外。

实际的判据有三条,按重要性排:(1) 池子里有多少人在用(上限);(2) 你的行为和其他人有多像(时间、金额、频率);(3) 你离开池子之后做了什么。三条里只有第一条是系统给你的,另外两条得你自己保证——而绝大多数隐私工具的失败,都发生在第二和第三条。

✓ 结账

这一章的一句话

隐私的单位是「你藏在多少人里」,而这个数由观察者的后验分布决定;零知识把协议内的泄露压到了严格的零,但真实世界的去匿名几乎全部发生在协议之外——时间、金额、网络、以及你离开之后做了什么。

下一章讲这门技术在日常生活里最可能落地的一件事,而它的核心是一个非常朴素的算术:当你为了证明「我满 18 岁」而出示身份证时,你想说的是 1 比特,实际递过去的是 30 多比特——足够在十四亿人里唯一地定位你。那 1 比特要多少条约束?本机数出来是 2945 条,一台笔记本几秒钟的事。