本文介绍了一种开源的基准测试框架,用于比较现有开源zkTLS库的性能。研究表明,Primus提出的garble-then-prove系统在速度上领先于其他MPC-TLS解决方案,同时发现一些Proxy-TLS实现存在安全缺陷。此外,文章详细探讨了zkTLS的TLS协议概述、两种主要方法(MPC-TLS和Proxy-TLS)的实现细节及其性能测试。
作者: 谢翔, 小王
精华 本文提供了一个开源基准框架,用于跨各种平台和网络条件对现有开源 zkTLS 库进行基准测试。我们发现,Primus 提出的 garble-then-prove 系统比其他 MPC-TLS 解决方案快 一个数量级。此外,基于 Primus 的 QuickSilver 的 Proxy-TLS 协议比替代方案快 30倍 和 145倍。我们欢迎其他团队提交 PR 加入 开源基准 努力。此外,我们还指出一些 Proxy-TLS 实现中的安全缺陷,这可能使恶意客户端在某些情况下证明虚假陈述。我们已对相关团队进行了必要的负责任披露,并想澄清我们并没有对他们的代码进行实际攻击。
zkTLS,也称为 Web 证明,是一种加密协议,确保通过传输层安全(TLS)协议传输的数据的真实性和隐私,例如在所有基于 HTTPS 的 Web 应用程序中。zkTLS 为可验证地将 Web2 数据迁移到 Web3 开辟了可能性,同时保持隐私。
在深入 zkTLS 的详细协议之前,首先回顾 TLS 的高层流程是有帮助的,因为这将帮助我们理解 zkTLS 方法的不同之处。
TLS 协议涉及两个参与方:客户端和数据源(我们将其称为服务器,以便与 zkTLS 一致)。TLS 在两个主要阶段中操作:握手阶段和记录阶段。
在握手阶段,客户端和数据源协商一个共享的秘密密钥,称为预主密钥(pms
)。该预主密钥随后用于生成会话密钥,这些密钥将在通信过程中用于加密数据。
为了生成这些会话密钥,使用了密钥派生函数(KDF)。KDF 取输入预主密钥(pms
)和其他公共信息(例如客户端和服务器随机值)来推导会话密钥。这个过程通常通过一系列 HMAC 函数完成。
正式而言,会话密钥可以如下推导:
keys←KDF(pms,public_info)
在记录阶段,客户端和数据源使用会话密钥来加密和解密消息。大多数 zkTLS 协议使用 AES-GCM 进行数据传输。以下是 AES-GCM 的高层概述。
给定一个密钥
K 和消息
(m0,...mℓ),其中每个
mi 为 16 字节(假设使用 AES-128),密文为以下形式
AES(K,ctr0)⊕m0,...,AES(K,ctrℓ)⊕mℓ,τ
这里
ctri 为计数器,且
τ 是根据前面的密文和
K 计算的标签。使用密钥
K 解密密文时,系统首先检查
τ,然后解密以恢复
(m0,...,mℓ)。我们参考 RFC 5288。
在 zkTLS 协议中,引入了一个名为 attestor 或 notary 的第三方,用于验证客户端从数据源接收的数据,同时确保没有私密信息(例如密码)泄露给验证者。
业界中 zkTLS 的两种主要方法是 MPC-TLS 和 Proxy-TLS,这是本文的主要焦点。
在 MPC-TLS 方法中,验证者和客户端运行一个二方计算(2PC)协议来模拟 TLS 握手的客户端部分。这意味着客户端在握手阶段后并不会接收到完整的会话密钥。只有在验证者收到响应密文后,验证者才会将秘密密钥份额发送给客户端,从而使客户端能够解密所有密文。
MPC-TLS 的高层流程如下。
请注意,客户端和验证者不运行 2PC 协议来解密响应密文。
DECO 解决方案在 MPC-TLS 方法中使用了经过认证的混淆电路和 zkSNARKs。然而,这种方法在通信大小和计算时间上都很耗资源,难以在实际产品中部署。
TLS Notary 通过使用半诚实的混淆电路和基于混淆电路的交互式零知识证明来优化此方法。虽然这减少了一些开销,但成本仍然很大。
Primus 在 MPC-TLS 设置中引入了 garble-then-prove 协议,将半诚实的混淆电路与高效的交互式零知识证明系统 QuickSilver 相结合,并进行了其他优化。这导致了一种在计算和通信上都高效的解决方案。
在 Proxy-TLS 方法中,验证者作为客户端与数据源之间的代理,转发所有 TLS 记录。验证者可以记录客户端与数据源之间交换的握手记录和密文。在协议结束时,客户端在零知识中向验证者证明密文的有效性。
Proxy-TLS 方法的动机是消除在 MPC-TLS 中使用的 2PC 协议,这是计算上最密集的部分。然而,这引入了更强的网络假设:验证者必须确保与真实数据源之间的直接连接。如果不满足此条件,恶意客户端可能会在验证者和数据源之间进行劫持,从而允许其伪造数据并证明任意主张。虽然许多人认为这一假设合理,但仍然很重要要强调。
根据客户端向验证者证明的陈述,不同项目采用了两个选项。
在这种情况下,客户端证明 AES 加密中的密钥是由 pms
派生的,并且消息是在验证者记录的密文下用相同的密钥加密的。有关更多详细信息,请参见 这篇论文。
此选项适用于 HTTPS,其中响应中有固定的公共填充,例如数据源返回的 200 OK 状态。客户端证明某些密文加密公共填充,而其他密文则加密消息,所有这些均使用相同的密钥。更多细节见 此处。
这两个选项旨在解决 AES-GCM 密码套件的非承诺问题。此外,证实 AES 密钥在不同块之间的一致性,并与 KDF 函数的输出相匹配至关重要。我们发现一些实现未能证明这一属性,允许恶意客户端提出虚假主张。我们将在接下来的部分中对此进行详细讨论。
Primus 和 Pluto 在其 Proxy-TLS 方法中利用选项 1,Primus 使用 QuickSilver,而 Pluto 则利用增量可验证计算(IVC)系统。
另一方面,Reclaim 采用选项 2,使用 Groth16 作为基础证明系统。
我们对现有的开源 zkTLS 核心库进行了基准测试,包括 Primus、TLS Notary、Reclaim 和 Pluto(Origo)。为了进行基准测试,我们构建了一个开源框架,测试在不同平台上的性能,包括 x86、ARM 和浏览器(WASM)。该框架还允许自定义各种网络环境。我们参考此 GitHub 仓库。
为了模拟现实场景——因为大多数应用程序通过浏览器访问网站,需要通过 TLS 发送 cookies——我们将请求大小设置为 1KB 或 2KB。响应大小从 16 字节到 2KB 不等。值得注意的是,实际性能可能因网络和平台配置而异。
我们提供了几个具有代表性的环境的性能指标。
响应大小(字节) | 上传大小(K字节) | 下载大小(K字节) | 原生(x86)运行时间(秒) | 原生(arm)运行时间(秒) | 浏览器运行时间(秒) | 内存(M字节) |
---|---|---|---|---|---|---|
16 | 34,888 | 1,141 | 2.56 | 2.64 | 5.05 | 146 |
256 | 34,901 | 1,141 | 2.57 | 2.62 | 5.09 | 146 |
1024 | 34,930 | 1,141 | 2.57 | 2.64 | 5.04 | 146 |
2048 | 34,971 | 1,141 | 2.58 | 2.65 | 5.11 | 146 |
响应大小(字节) | 上传大小(K字节) | 下载大小(K字节) | 原生(x86)运行时间(秒) | 原生(arm)运行时间(秒) | 浏览器运行时间(秒) | 内存(M字节) |
---|---|---|---|---|---|---|
16 | 61,089 | 62,144 | 20 | 32 | 47 | 147 |
256 | 61,121 | 65,309 | 20 | 33 | 48 | 148 |
1024 | 61,222 | 75,437 | 21 | 35 | 49 | 148 |
2048 | 61,356 | 88,940 | 23 | 39 | 53 | 148 |
在 Proxy-TLS 解决方案中,为了简化基准测试并尽可能确保公平,我们将请求大小设置为零。这意味着没有实现证明请求数据,重点仅集中在响应数据的大小上。
响应大小(字节) | 上传大小(K字节) | 下载大小(K字节) | 原生(x86)运行时间(秒) | 原生(arm)运行时间(秒) | 浏览器运行时间(秒) | 内存(M字节) |
---|---|---|---|---|---|---|
16 | 375 | 788 | 0.45 | 0.48 | 2.31 | 71 |
256 | 383 | 788 | 0.46 | 0.47 | 2.34 | 75 |
1024 | 416 | 788 | 0.47 | 0.49 | 2.48 | 91 |
2048 | 461 | 788 | 0.48 | 0.52 | 2.66 | 111 |
响应大小(字节) | 上传大小(1.2/1.3 K字节) | 下载大小(1.2/1.3 K字节) | 原生(x86)运行时间(1.2/1.3 秒) | 原生(arm)运行时间(1.2/1.3 秒) | 浏览器运行时间(1.2/1.3 秒) | 内存(M字节) |
---|---|---|---|---|---|---|
16 | 32/23 | 31/22 | 4.09/3.62 | 8.79/8.16 | 13.88/13.05 | 356 |
256 | 34/24 | 32/23 | 5.09/4.76 | 11.06/10.64 | 18.76/21.43 | 313 |
1024 | 38/29 | 38/29 | 8.53/8.43 | 18.71/19.10 | 36.63/49.21 | 298 |
2048 | 44/35 | 44/36 | 12.92/13.38 | 28.61/29.83 | 60.22/86.21 | 306 |
响应大小(字节) | 上传大小(K字节) | 下载大小(K字节) | 原生(x86)运行时间(秒) | 原生(arm)运行时间(秒) | 浏览器运行时间(秒) | 内存(M字节) |
---|---|---|---|---|---|---|
16 | 62 | 14 | 41 | 56 | 193 | 1,395 |
256 | 62 | 14 | 41 | 57 | 206 | 1,395 |
1024 | 63 | 15 | 58 | 89 | 293 | 1,391 |
2048 | 65 | 16 | 76 | 119 | 390 | 1,412 |
在对 Reclaim 的实现进行基准测试时,我们发现了一些反直觉的事情。Groth16 是一个依赖电路的证明系统,意味着每个独特电路都需要一个单独的可信设置。然而,在 Proxy-TLS 设置中,电路取决于响应长度,即 AES 块的数量。然而,我们发现,在 Reclaim 的系统中,用户并不需要每次响应长度变化时都重新运行设置,这与预期行为相悖。
在与团队确认后,我们发现 Reclaim 实际上将每个 AES 块分别进行证明,而不确保加密密钥在所有块之间保持一致。因此,对于一个 AES 块的单一设置过程就足够。然而,这种实现可能允许恶意客户端提出虚假主张。以下是对此潜在问题的简单解释。也就是说,我们想强调,我们并没有对他们的代码进行实际攻击,而只是指出这个可能的安全缺陷。
假设验证者/公证人获得两个 AES 密文,如下所示。
c0=AES(K,ctr0)⊕m0,c1=AES(K,ctr1)⊕m
其中
m0 是一些公共填充信息,且
m 是客户端希望证明的数据。
在 Reclaim 的实现中,客户端为
c0 和
c1 生成两个单独的 Groth16 证明,而不确保加密密钥
K 在这两个密文之间保持一致。这允许恶意客户端实施以下攻击。
客户端选择某些
K′,并重写
c1 为
c1=AES(K′,ctr1)⊕AES(K′,ctr1)⊕AES(K,ctr1)⊕m
因此,
c1 的密文为
m′=AES(K′,ctr1)⊕AES(K,ctr1)⊕m,使用密钥
K′ 和
ctr1。然后客户端可以声称来自数据源的消息是
m′。
在这种情况下,如果
m 是公共数据,如 Reclaim 团队所解释的那样,验证者将对消息执行格式检查——例如,确保
m 仅由数字字符组成。由于随机选择的
m′ 极不可能是有效的数字字符串,因此验证者最终会拒绝证明。
然而,如果
m 代表私有数据——例如银行账户余额——并且目标是证明关于其的陈述(例如,余额大于 100),潜在攻击可能会出现。这是因为验证者无法直接访问
m,因此无法验证其格式。在这种情况下,恶意选择的
m′ 可能表示一个非常大的数字,导致无效证明被接受。
总之,我们发现,在 zkTLS 的背景下,零知识证明对于 1) 确保参与方的诚实行为和 2) 证明 TLS 负载中的内容至关重要。然而,我们观察到像 QuickSilver 这样交互式协议由于其可扩展性总是输出替代解决方案。
根据开放的基准测试结果,我们相信像 QuickSilver 这样的交互式 ZK 目前是 zkTLS 最适合的方法,尤其是在我们将技术推向浏览器时。
- 原文链接: hackmd.io/@-fI_Eu_rR8qs0...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!