本文提出了一种针对基于零知识证明(ZK)的 Rollup 的安全机制,旨在应对验证通过但实际上无效的提案。该机制利用证明系统的完备性,允许诚实参与者通过提交正确提案来挑战无效提案,从而发出链上信号,表明证明系统存在缺陷,并触发 Rollup 进入安全模式。此外,文章还提供了一个概念验证,并概述了在 OP Stack 中集成该机制的潜在路径。
基于 ZK 的 rollup 是使用零知识证明来链上证明状态转换的 rollup。由于这些系统很复杂,因此防止健全性错误(允许攻击者证明无效状态转换的缺陷)至关重要。本文提出了一种机制,以帮助rollup免受通过验证的无效提案的侵害,该机制使用了博客 ZK-rollups 的健全性警报功能 中介绍的概念,并在 Vitalik 的 Rollup 安全性的多重证明器 演讲中简要讨论过。我们利用证明系统的完备性属性(任何真陈述都可以被证明的保证)来创建一个“健全性警报”。通过此解决方案,如果攻击者提出一个通过验证的无效提案,诚实方可以通过证明同一区块的正确提案来挑战它。两个冲突但经过验证的证明的存在,可以作为证明系统已受到威胁的明确链上信号,从而允许 rollup 进入安全模式以保护自身。
我们认为这种机制是一个重要的安全层,应该集成到任何使用 ZK 证明的 rollup 中,包括多重证明器系统。下面,我们将详细介绍该机制,提供概念验证,并概述 OP Stack 的潜在集成路径。
本文档中提出的想法出现在博客 ZK-rollups 的健全性警报功能 以及 Vitalik 的演讲 Rollup 安全性的多重证明器 中。我们认为这些想法对于保护 rollup 非常重要,并决定对其进行扩展,特别是因为最近的披露表明为 rollup 设计证明系统是多么具有挑战性(例如,来自 Op-succinct 的 安全公告 和来自 Risc Zero 的这篇 帖子)。
ZK 的替代方案是一种我们称之为“交互式争议游戏”的方法,该方法发生在一方声称提案无效之后。交互式争议游戏是一种多轮协议,允许诚实的参与者保护有效的提案并消除无效的提案。在改进交互式争议游戏以对抗女巫攻击和资源耗尽攻击方面已经做了大量工作。不幸的是,尽管进行了这项研究,交互式争议游戏仍然具有很高的最终确定性延迟。
一种确保 rollup 安全并减少最终确定性延迟的方法是使用 ZK、TEE 和欺诈证明游戏的组合进行多重证明设计。有关此的提案和讨论可以在以下链接中找到:来自 Vitalik 的 L2 安全和最终确定路线图 和来自 Scroll 的 多重证明器实现。
在 rollup 的上下文中,ZK 证明系统是一种非交互式证明系统,需要同时具备完备性和健全性。完备性确保每个真陈述都可以被证明为真,而健全性确保攻击者无法证明一个假陈述。
由于 ZK 系统的复杂性,保护它们具有挑战性。单个缺陷(如缺少检查)可能会损害证明系统的健全性。如果证明系统不健全,攻击者可能会提供带有虚假证明的无效提案,从而导致其被接受。来自 SuccinctLabs 的最新 安全公告 以及来自 Risc Zero 的最新 披露 只是这个问题的一些例子。
一个潜在的解决方案是,在证明系统中构建一种机制,允许诚实方提供一个有效的提案来对抗一个无效的提案,即使该无效的提案通过了验证。
这个想法是,如果证明系统不健全但完备,则一方可以通过提供带有正确性证明的正确提案来提醒 rollup 该系统不健全。然后,rollup 应该丢弃所有未最终确定的提案(包括无效提案),并切换到不同的最终确定方法。
此解决方案在设计和实施中所需的开销最少。首先,此提案要求诚实方必须有时间为正确的提案生成证明,并有时间将其提交到 L1 链。这可以通过为 ZK 验证引入一个挑战期来完成(此挑战所需的最终确定延迟与交互式争议游戏所需的延迟相比将相形见绌)。其次,这要求必须实施备份最终确定方法。第三,我们需要实现一个用于提醒 rollup 证明系统不健全的函数。在此函数中,如果 rollup 接受证明系统存在缺陷,则它必须丢弃所有未最终确定的区块,并将最终确定切换到备份方法。
以下描述了如何向 rollup 提醒证明系统存在缺陷的步骤。
图:防止通过验证的无效提案。
在阈值证明系统的上下文中,此技术可用于消除有缺陷的组件。例如,在使用来自 Vitalik 的 2-out-of-3 提案 的 rollup 中,其中有三个组件(TEE、ZK、故障证明游戏),这个想法可以用于删除 ZK 组件,以便系统基本上变成具有 TEE 和故障证明游戏的 2-out-of-2。要恢复 ZK 组件,安全委员会必须继续升级 ZK 组件。
我们演示了如何将此提案应用于一个非常简单的 rollup 设计,其中 rollup 仅在当前区块最终确定后才要求下一个交易区块的哈希。对于此演示,一方将通过提交一个通过与当前提案相同的区块验证的提案来提醒 rollup 证明系统不健全。
如果 rollup 收到证明系统不健全的警报,则合约的状态将被修改,以便只有 backupProposer 才能提出提案。这将一直持续到 backupProposer 调用函数 useZKAgain()。在本节中,我们将仅提供 POC 中最重要的组件,并且还将省略一些代码以简化演示。完整的 POC 可在此处获得 here。
我们将 POC 分解为几个部分。
POC 的接口合约。
IZKVerifier
是用于验证证明的接口。
ITransactionsHash
是用于获取区块的下一个哈希的接口。
用于指定提案和状态的代码。
与提案直接相关的 SimpleRollup
合约的代码块,包括
变量
challengeProof
函数,其中一方质询所提议区块的证明
proposal
函数,如果证明失败,则只有 backupProposer
才能提出提案。
简单的测试,表明在成功发出警报后,只有备份提议者才能证明提案。
interface IZKVerifier {
function verifyStateProof(
uint256 blockNumber, // L2 区块号
bytes32 stateToProve, // 要证明的 L2 状态
bytes memory proof, // 证明
bytes32 transactionsHash // 交易哈希
) external view returns (bool);
}
interface ITransactionsHash {
function getTransactionsHash(
uint256 blockNumber // L2 区块号
) external view returns (bytes32);
function startingBlock() external view returns (uint256);
function updateTransactionsHash(
uint256 blockNumber, // L2 区块号
bytes32 transactionsHash // 交易哈希
) external;
}
struct Proposal {
// L2 block number
uint256 blockNumber; // L2 区块号
// L2 state
bytes32 state; // L2 状态
// When the proposal was proved
uint256 timestampProved; // 提案被证明的时间戳
}
struct State {
// L2 block number
uint256 blockNumber; // L2 区块号
// L2 state
bytes32 state; // L2 状态
}
contract SimpleRollup {
// Verifies ZK proofs
IZKVerifier public zkVerifier; // 验证 ZK 证明
// Fetches L2 transaction hashes
ITransactionHashes public transactionHashes; // 获取 L2 交易哈希
// Time to wait before finalizing a proposal
uint256 public zkFinalizationDelay; // 最终确定提案之前的等待时间
// Whether the ZK proof system has failed
bool public zkFailed; // ZK 证明系统是否已失败
address backupProposer; // 备份提议者地址
// code omitted for clarity
.......................... // 为了清晰起见,省略了代码
function challengeProof(
bytes32 alternateState, // 备用状态
bytes calldata proof // 证明
) external {
require(!zkFailed, "ZK has already failed."); // 需要未发出 ZK 失败的警报
require(
alternateState != currentProposal.state, // 需要备用状态与当前状态不同
"Alternate state is the same as the proposed state." // 备用状态与提议的状态相同
);
require(
block.timestamp - currentProposal.timestampProved <
zkFinalizationDelay, // 需要当前时间戳 - 时间戳证明小于 ZK 最终确定延迟
"Proposal is finalized." // 提案已最终确定
);
require(
currentProposal.timestampProved > 0, // 需要时间戳证明大于 0
"Proposal has not been proved yet." // 提案尚未被证明
);
Proposal memory proposal = currentProposal; // 从当前提案创建一个提案内存数据
require(
zkVerifier.verifyStateProof( // 如果 ZK 验证器验证了备用状态和交易哈希与区块编号的证明正确
proposal.blockNumber, // 区块编号
alternateState, // 备用状态
proof, // 证明
transactionHashes.getTransactionsHash(proposal.blockNumber) // 从交易哈希获取区块编号
),
"Proof is invalid." // 证明无效
);
zkFailed = true; // ZK 失败
delete currentProposal; // 删除当前提案
emit ZKFailed(proposal.blockNumber, proposal.state, alternateState); // 发出 ZK 失败事件
}
function propose(uint256 blockNumber, bytes32 state) public {
require(
!zkFailed || msg.sender == backupProposer, // 需要 ZK 没有失败,或者消息发送者是备份提议者
"ZK down. Only backup proposer can propose." // ZK 已关闭。只有备份提议者才能提出提案。
);
//code omitted for clarity
.......................... // 为了清晰起见,省略了代码
}
function useZKAgain() external {
require(zkFailed, "ZK has not failed yet."); // 需要 ZK 尚未失败
require(
msg.sender == backupProposer, // 需要消息发送者是备份提议者
"Only backup proposer can choose to use ZK again." // 只有备份提议者可以选择再次使用 ZK。
);
zkFailed = false; // zkFailed 设置为否
}
function testProveOnlyBackupProposer() public {
simpleRollup.propose(
startingL2Block + 1, // 起始 L2 区块 + 1
keccak256(abi.encode(startingL2Block + 1)) // 对起始 L2 区块 + 1 进行 keccak256 哈希处理
);
simpleRollup.prove(abi.encode(0x1234)); // 证明 0x1234
simpleRollup.challengeProof(
keccak256(abi.encode(0x1234)), // 对 0x1234 进行 keccak256 哈希处理
abi.encode(0x1234) // 证明 0x1234
);
assertEq(simpleRollup.zkFailed(), true); // 确认 ZK 失败
vm.prank(backupProposer); // 模拟为备份提议者
simpleRollup.propose(
startingL2Block + 1, // 起始 L2 区块 + 1
keccak256(abi.encode(startingL2Block + 1)) // 对起始 L2 区块 + 1 进行 keccak256 哈希处理
);
vm.expectRevert("ZK down. Only backup proposer can provide a proof."); // 预期恢复:ZK 已关闭。只有备份提议者才能提供证明。
simpleRollup.prove(abi.encode(0x1234)); // 证明 0x1234
vm.prank(backupProposer); // 模拟为备份提议者
simpleRollup.prove(abi.encode(0x1234)); // 证明 0x1234
(
uint256 blockNumber, // 区块编号
bytes32 state, // 状态
uint256 timestampProved // 时间戳证明
) = simpleRollup.currentProposal(); // 从简单rollup获取当前提案
assertEq(blockNumber, startingL2Block + 1); // 确认区块编号等于起始 L2 区块 + 1
assertEq(state, keccak256(abi.encode(startingL2Block + 1))); // 确认状态等于起始 L2 区块 + 1 的 keccak256 哈希
assertEq(timestampProved, block.timestamp); // 确认时间戳证明等于区块时间戳
}
基于 ZKVM 的 L2 rollup 应该研究此提案,并确定此提案是否可以提高其系统的安全性。如果研究得出结论,此提案可以提高其系统的安全性,则下一步将是弄清楚如何最好地集成健全性警报,并确定应该使用哪些后备方法进行最终确定(欺诈证明游戏、许可提议者等)。最后,应该在主网上启动之前,在测试网上集成和测试健全性警报和后备方法。
在下面的部分中,我们将展示如何将我们的提案集成到 OP Stack 链中。
以下是伪代码,说明如何为 OP Stack 链实现此系统。该功能已添加到 AnchorStateRegistry
合约中,该合约将被视为 L2 状态的真实来源。
每个提案都包含两条信息:L2 区块编号和 L2 输出根。因此,如果可以使用 ZK 证明验证具有相同 L2 区块编号但输出根不同的两个提案,则证明系统存在错误。
如果 AnchorStateRegistry 检测到健全性错误,它可以将游戏类型切换为默认游戏类型。我们相信这个想法可以通过合理的工作量应用于基于 Optimism 的链。
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;
import {AnchorStateRegistry} from "...";
contract ZKAnchorStateRegistry is AnchorStateRegistry {
// The game type for a ZK non-interactive game
GameType immutable ZK_GAME_TYPE; // ZK 非交互式游戏的游戏类型
// The amount of time for a resolved ZK game to be considered finalized
uint256 zkFinalizationDelay; // 被视为最终确定的已解决 ZK 游戏的时间量
event SoundnessError(Proposal proposal, IDisputeGame disputeGame); // 健全性错误事件
/// @notice Nullify a ZK dispute game // 取消 ZK 争议游戏
/// @param proposal The proposal to nullify the game with // 用于作废游戏的提案
/// @param zkProof The ZK proof to nullify the game with // 用于作废游戏的 ZK 证明
/// @param disputeGame The dispute game to nullify // 要作废的争议游戏
/// @param publicArgs Additional public arguments for the ZK proof // ZK 证明的附加公共参数
function alert(Proposal proposal, Proof zkProof, IDisputeGame disputeGame, bytes calldata publicArgs) public {
Proposal startingOutputRoot = disputeGame.startingOutputRoot(); // 从争议游戏中获取起始输出根
// Check that the proposal can be used to nullify the game // 检查该提案是否可以用于作废游戏
require(proposal.l2SequenceNumber == startingOutputRoot.l2SequenceNumber, "L2 block number must match"); // 需要 L2 区块编号匹配
require(proposal.root != startingOutputRoot.root, "Cannot contradict with same root"); // 需要输出根不同
// Checks on the public arguments // 检查公共参数
require(publicArgs ..., "Public arguments are incorrect"); // 公共参数不正确
// Check that the dispute game can be nullified // 检查是否可以作废争议游戏
require(isZKGameDisputable(disputeGame), "Game is not disputable"); // 游戏不可争议
// Verify the ZK proof // 验证 ZK 证明
require(prover.verify(proposal, zkProof, publicArgs), "ZK proof is invalid"); // ZK 证明无效
// There is a soundness error, so we have to change the game type and retire all current games // 存在健全性错误,因此我们必须更改游戏类型并放弃所有当前游戏
respectedGameType = GameType(1); // permissioned Cannon game type (or whichever game type is chosen to fall back to) // 许可的 Cannon 游戏类型(或选择作为后备的任何游戏类型)
retirementTimestamp = uint64(block.timestamp); // retire all games // 放弃所有游戏
emit SoundnessError(proposal, disputeGame); // 发出健全性错误事件
}
/// @notice Check if a ZK dispute game can be nullified // 检查是否可以撤销 ZK 争议游戏
/// @param disputeGame The dispute game to check // 要检查的争议游戏
/// @return Whether the game can be nullified // 游戏是否可以撤销
function isZKGameDisputable(IDisputeGame disputeGame) public view returns (bool) {
// Game must be a ZK game // 游戏必须是 ZK 游戏
if (disputeGame.gameType() != ZK_GAME_TYPE) return false; // 否则返回 false
// Game must be proper i.e. registered, not blacklisted, not retired, not paused // 游戏必须是正式的,即已注册、未列入黑名单、未放弃、未暂停
if (!isGameProper(disputeGame)) return false; // 否则返回 false
// Game must be respected i.e. correct game type when created // 游戏必须受到尊重,即创建时是正确的游戏类型
if (!isGameRespected(disputeGame)) return false; // 否则返回 false
// Game must be resolved // 游戏必须已解决
if (!isGameResolved(disputeGame)) return false; // 否则返回 false
// Game must be within the airgap period for a nullification // 游戏必须在撤销的气隙期内
if (block.timestamp - disputeGame.resolvedAt().raw() > zkFinalizationDelay) return false; // 否则返回 false
// Game must be resolved in favor of the defender. // 游戏必须以有利于防御者的方式解决。
if (disputeGame.status() != GameStatus.DefenderWins) return false; // 否则返回 false
return true; // 返回 true
}
- 原文链接: ethereum-magicians.org/t...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!