本文是关于Rate Limiting Nullifier(RLN)零知识证明协议的安全审核报告,详细介绍了审核期间发现的问题和建议,以及RLN的应用场景和相关原理。审核发现了低影响的漏洞,并提供了详细的修复建议。总体而言,代码实现了良好的数学操作和基本功能,但在文档和测试验证方面仍可提高。
我最近有幸对一个 Zero Knowledge Proofs (ZKP) 协议进行首次安全审计。此协议是速率限制无效化器 (RLN) Zero Knowledge Proofs 协议,其电路使用 Circom 编写。我的审查主要集中在 Zero Knowledge Proofs 小工具的 Circom 电路上。让我告诉你,这真是一场冒险!我是说,谁知道数学可以如此令人兴奋?
在审查 RLN Zero Knowledge Proofs 协议期间,我学到了很多关于 RLN 规范、验证和如何识别约束不足系统的复杂知识。我的数学背景在帮助我理解这些复杂概念时证明是无价的。人们总是说,“数学是宇宙的语言”!但说真的,这些东西是谁想出来的?此外,我对 ZKP 原语攻击向量和系统中其他潜在脆弱性的理解也更深刻了。这就像一个侦探,但面对的是 ZKP 系统!来告诉你,这并不像看起来那么简单。
审查资源:
审计员:
速率限制无效化器
速率限制无效化器 (RLN) 是一个基于零知识证明的构造(有时称为 ZKP 小工具),它提供了一种适用于去中心化(和中心化)环境的匿名速率限制信号/消息框架,使用的是秘密沙密尔共享 (SSS) 方案。
动机 在匿名去中心化网络中,速率限制无效化器的一些应用包括:
RLN V2 RLN V2 协议是一个更为通用的构造,允许为一个 Epoch 设置不同的限制(在 RLN-V1 中是每个 Epoch 1 条消息),同时仍保持与其前身一样简单。此外,它还允许根据某些公共数据(例如股份)为不同的 RLN 应用用户设置不同的速率限制。
RLN Circom 电路的审查持续了 13 天。从 2023 年 5 月 31 日到 6 月 12 日进行了代码审查。在审查期间,RLN 仓库在积极开发中,但审查限制在最新提交 37073131b9 开始时。
RLN 电路的官方文档位于 rate-limiting-nullifier.github.io。
流程
这是 RLN 协议的完整流程图,用于 Circom 电路。
graph TD
ext_null>外部无效化器] --> h1(哈希)
secret{{秘密密码与无效化器}} --> h0(哈希) --> a_0
a_0{{秘密哈希 a_0}} --> h1
msg_id>消息_ID `k`] --> h1
msg_id --> limit_check(消息限制检查)
msg_limit>消息限制] --> limit_check
h1 --> a_1 --> h2(哈希) --> int_null([内部无效化器])
a_1 --> times
m>消息] --> h3(哈希) --> times(*) --> plus(+)
a_0 --> plus --> sss([沙密尔的分享 y_share])
a_0 --> h4(哈希) --> id_com([id_承诺])
h4 --> merkle(Merkle证明)
审查的范围包括指定提交中的以下电路:
在发现结果呈现给 RLN 团队之后,进行了一些修复并包含在几个 PR 中。
此审查是代码审查,以识别代码中的潜在漏洞。审查员未调查安全实践或操作安全,并假定可以信任特权帐户。审查员没有根据某个标准或规范评估代码的安全性。审查可能没有发现所有潜在的攻击向量或漏洞领域。
yAcademy 和审计员对代码的安全性不做任何保证,并且不保证代码没有缺陷。yAcademy 和审计员不向第三方表示或暗示代码已被审计或代码没有缺陷。通过部署或使用代码,RLN 和合同的用户同意自担风险使用该代码。
分类 | 评分 | 描述 |
---|---|---|
访问控制 | 不适用 | 代码没有明确实现访问控制机制。它没有针对谁可以访问或修改数据的具体检查或限制。 |
数学 | 良好 | 代码包括数学运算,如加法、乘法和使用 Poseidon 函数的哈希。它还包括范围检查和位操作。 |
复杂性 | 良好 | 代码的复杂性相对较低。它由基本数学运算组成,并包括Merkle树的包含证明和范围检查。 |
库 | 一般 | 代码包括 Circomlib 库,特别是用于哈希的 Poseidon 电路。 |
去中心化 | 不适用 | 代码没有明确处理去中心化。它没有包括用于分布式共识或与去中心化网络交互的机制。 |
代码稳定性 | 良好 | 代码似乎结构良好,并遵循 Circom 语法。它没有包含任何明显的错误或影响其稳定性的问题。 |
文档 | 低 | 代码没有包括广泛的文档。有些评论解释了某些组件的目的,但更详细的文档将是有益的。 |
监控 | 一般 | 代码没有包括具体的监控机制。它没有内置的日志记录或事件或性能指标的跟踪。 |
测试与验证 | 一般 | 代码包括一些基本的范围检查和Merkle树的包含证明,这些对于确保代码的正确性很重要。然而,它没有包括全面的测试或验证程序。 |
发现分为以下几个部分,按其影响分类:
x
(消息的哈希),identitySecret
被泄露当输入信号 x
(消息的哈希)为 0
模素数 p
(标量场使用的)时,identitySecret
被泄露。当 x
为 0
或 21888242871839275222246405745257275088548364400416034343698204186575808495617
(以太坊)或素数 p
时,标量场的 $Fp$ 算术电路的乘积 a1 * x
变为零,从而泄露了 identitySecret
。
p
是 $BN254$ 曲线的标量场的阶。52435875175126190479447740508185965837690552500527637822603658699938581184513
18446744069414584321
,最初用在 $Plonky2$ 中。推荐解决方案
对输入信号 x
和乘积 a1 * x
施加约束,确保二者都在模素数 p
下非零。
isZero(x).out === 0
isZero(a1*x).out === 0
无。
无。
在 RLN.sol 中,messageLimit 最多可以取到 2256 - 1 的值,而电路中的 messageId 和 userMessageLimit 的值限制为 216 - 1。
template RLN(DEPTH, LIMIT_BIT_SIZE) {
...
// messageId 范围检查
RangeCheck(LIMIT_BIT_SIZE)(messageId, userMessageLimit);
...
}
component main { public [x, externalNullifier] } = RLN(20, 16);
uint256 messageLimit = amount / MINIMAL_DEPOSIT;
推荐解决方案
更新 rln.sol 中的相关代码,如下所示:
function register(uint256 identityCommitment, uint256 amount) external {
...
uint256 messageLimit = amount / MINIMAL_DEPOSIT;
require( messageLimit <= type(uint16).max , "最大消息限制长度为 65535");
...
}
address
输入信号输入信号 address
被声明但是未在输出计算中使用。
signal input address;
推荐解决方案
为未使用的输入信号 address
分配一个本地计算。例如:
signal addressDoubled <== address + address;
Circom 电路缺少对多个输入参数(如 DEPTH
、address
、LIMIT_BIT_SIZE
等)的显式范围检查。
推荐解决方案 执行显式范围检查,并约束数据输入参数,以提高 ZKP 系统的可靠性。
Circom 电路进一步使用来自 0xParc 的 Ecne 工具 测试 Weak Verification
可证明性属性。这测试了在 QAP(R1CS 约束)中,给定输入变量时,输出变量是否具有唯一确定的值。一种约束不足的电路允许在给定相同输入的情况下为多个不同的输出生成有效证明。最糟糕的情况下,攻击者可以针对任何输出生成约束不足电路的有效证明——这意味着攻击者能够说服一个验证者(错误地)相信该电路是适当约束的,从而攻击者知道任意输出的原像。
Circom 电路编译为非优化的 R1CS 约束系统,然后进行了 Weak Verification
测试,以检查是否存在不良的约束或约束不足。所有电路在 Ecne 测试中均通过,没有不良或约束不足的情况。这验证了给定电路的 R1CS 方程在给定输入时唯一确定输出(即,这些约束是有效的)。
考虑添加以下错误处理,以检查特定条件,并在不满足这些条件时抛出错误或返回错误代码。这有助于提供有意义的错误消息或以可控的方式处理异常情况。
rln.circom
// 为Merkle树包含证明添加错误处理
root <== MerkleTreeInclusionProof(DEPTH)(rateCommitment, identityPathIndex, pathElements);
assert(root !== 0, "无效的Merkle树包含证明"); // 如果Merkle树包含证明无效,则抛出错误
withdraw.circom
// 为地址长度检查添加错误处理
assert(address.length == EXPECTED_ADDRESS_LENGTH, "无效的地址长度"); // 如果地址长度不符合预期,则抛出错误
utils.circom
// 为长度检查添加错误处理
assert(leaf.length == EXPECTED_LEAF_LENGTH, "无效的叶子长度"); // 如果叶子长度不符合预期,则抛出错误
POSEIDON
和一些附加说明Poseidon
)是:
identitySecret
信号的保密性,假定由用户保管。identitySecret
信号。identitySecret
信号,他们就可以计算该用户的 identityCommitment
、rateCommitment
、a1
和 y
信号,并利用这些信息破坏 RLN 电路的安全性。总体而言,代码在数学运算和基本功能的实现上表现良好。然而,它可以受益于更广泛的文档和额外的测试及验证程序。
- 原文链接: github.com/thogiti/thogi...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!