EIP-7657: 同步委员会的罚没
恶意同步委员会消息的罚没条件
Authors | Etan Kissling (@etan-status) |
---|---|
Created | 2024-03-21 |
Discussion Link | https://ethereum-magicians.org/t/eip-7657-sync-committee-slashings/19288 |
摘要
本 EIP 定义了恶意同步委员会消息的罚没条件。
动机
不诚实的同步委员会成员的绝对多数能够说服依赖以太坊轻客户端同步协议的应用程序接受非规范的最终确定头,并有可能接管未来 SyncCommitteePeriod
的同步权限。通过签署恶意的信标区块根,可以形成恶意的(但有效的!)LightClientUpdate
消息,并随后用于例如利用基于轻客户端同步协议的最小信任桥合约。
引入了一种额外的罚没类型,以阻止作为同步委员会成员签署非规范信标区块根。与 ProposerSlashing
和 AttesterSlashing
一样,只有恶意行为才会被罚没。这包括跨多个链分支的同时矛盾参与,但仅仅是被诱骗同步到不正确的检查点的验证者不应被罚没,即使它参与了非规范链。请注意,即使没有访问历史记录,也必须可以验证罚没,例如,通过检查点同步的信标节点。
请注意,无论采用何种罚没机制,罚没只能在攻击发生后追溯应用。保护金额大于 SYNC_COMMITTEE_SIZE * MAX_EFFECTIVE_BALANCE
= 512 * 32 ETH
= 16384 ETH
(在主网上)的用例应将轻客户端同步协议与其他已建立的方法(如多重签名)相结合,或者可能需要发布额外的抵押品才有资格提交更新。其他方法不属于本 EIP 的范围。
规范
本文档中的关键词“必须 (MUST)”,“禁止 (MUST NOT)”,“需要 (REQUIRED)”,“应该 (SHALL)”,“不应该 (SHALL NOT)”,“推荐 (SHOULD)”,“不推荐 (SHOULD NOT)”,“可以 (MAY)”和“可选 (OPTIONAL)”应按照 RFC 2119 和 RFC 8174 中的描述进行解释。
状态转换检查
注意:这仍然允许在证明/提案和同步委员会消息之间存在矛盾。此外,按照设计,这允许验证者根本不参与诚实的同步委员会消息,而仅参与不诚实的同步委员会消息。
名称 | 值 |
---|---|
BLOCK_STATE_ROOT_INDEX |
get_generalized_index(BeaconBlock, 'state_root') (= 11) |
STATE_BLOCK_ROOTS_INDEX |
get_generalized_index(BeaconState, 'block_roots') (= 37) |
STATE_HISTORICAL_ROOTS_INDEX |
get_generalized_index(BeaconState, 'historical_roots') (= 39) |
HISTORICAL_BATCH_BLOCK_ROOTS_INDEX |
get_generalized_index(HistoricalBatch, 'block_roots') (= 2) |
MAX_SYNC_COMMITTEE_SLASHINGS |
2**0 (= 1) |
class SyncCommitteeSlashingEvidence(Container):
attested_header: BeaconBlockHeader
next_sync_committee: SyncCommittee
next_sync_committee_branch: Vector[Root, floorlog2(NEXT_SYNC_COMMITTEE_INDEX)]
finalized_header: BeaconBlockHeader
finality_branch: Vector[Root, floorlog2(FINALIZED_ROOT_INDEX)]
sync_aggregate: SyncAggregate
signature_slot: Slot
sync_committee_pubkeys: Vector[BLSPubkey, SYNC_COMMITTEE_SIZE]
actual_finalized_block_root: Root
actual_finalized_branch: List[Root, (
floorlog2(BLOCK_STATE_ROOT_INDEX)
+ floorlog2(STATE_HISTORICAL_ROOTS_INDEX)
+ 1 + floorlog2(HISTORICAL_ROOTS_LIMIT)
+ floorlog2(HISTORICAL_BATCH_BLOCK_ROOTS_INDEX)
+ 1 + floorlog2(SLOTS_PER_HISTORICAL_ROOT))]
class SyncCommitteeSlashing(Container):
slashable_validators: List[ValidatorIndex, SYNC_COMMITTEE_SIZE]
evidence_1: SyncCommitteeSlashingEvidence
evidence_2: SyncCommitteeSlashingEvidence
recent_finalized_block_root: Root
recent_finalized_slot: Slot
def sync_committee_slashing_evidence_has_sync_committee(evidence: SyncCommitteeSlashingEvidence) -> bool:
return evidence.next_sync_committee_branch != [Root() for _ in range(floorlog2(NEXT_SYNC_COMMITTEE_INDEX))]
def sync_committee_slashing_evidence_has_finality(evidence: SyncCommitteeSlashingEvidence) -> bool:
return evidence.finality_branch != [Root() for _ in range(floorlog2(FINALIZED_ROOT_INDEX))]
def is_valid_sync_committee_slashing_evidence(evidence: SyncCommitteeSlashingEvidence,
recent_finalized_block_root: Root,
recent_finalized_slot: Slot,
genesis_validators_root: Root) -> bool:
# 验证同步委员会是否有足够的参与者
sync_aggregate = evidence.sync_aggregate
if sum(sync_aggregate.sync_committee_bits) < MIN_SYNC_COMMITTEE_PARTICIPANTS:
return False
# 验证 `finality_branch`(如果存在)确认 `finalized_header`
# 以匹配保存在 `attested_header` 状态中的最终确定检查点根。
# 请注意,创世最终确定的检查点根表示为零哈希。
if not sync_committee_slashing_evidence_has_finality(evidence):
if evidence.actual_finalized_block_root != Root():
return False
if evidence.finalized_header != BeaconBlockHeader():
return False
else:
if evidence.finalized_header.slot == GENESIS_SLOT:
if evidence.actual_finalized_block_root != Root():
return False
if evidence.finalized_header != BeaconBlockHeader():
return False
finalized_root = Root()
else:
finalized_root = hash_tree_root(evidence.finalized_header)
if not is_valid_merkle_branch(
leaf=finalized_root,
branch=evidence.finality_branch,
depth=floorlog2(FINALIZED_ROOT_INDEX),
index=get_subtree_index(FINALIZED_ROOT_INDEX),
root=evidence.attested_header.state_root,
):
return False
# 验证 `next_sync_committee`(如果存在)实际上是保存在
# `attested_header` 状态中的下一个同步委员会
if not sync_committee_slashing_evidence_has_sync_committee(evidence):
if evidence.next_sync_committee != SyncCommittee():
return False
else:
if not is_valid_merkle_branch(
leaf=hash_tree_root(evidence.next_sync_committee),
branch=evidence.next_sync_committee_branch,
depth=floorlog2(NEXT_SYNC_COMMITTEE_INDEX),
index=get_subtree_index(NEXT_SYNC_COMMITTEE_INDEX),
root=evidence.attested_header.state_root,
):
return False
# 验证 `actual_finalized_block_root`(如果存在)是否被 `actual_finalized_branch` 确认
# 是相对于 `recent_finalized_block_root` 的 slot `finalized_header.slot` 处的区块根
if recent_finalized_block_root == Root():
if evidence.actual_finalized_block_root != Root():
return False
if evidence.actual_finalized_block_root == Root():
if len(evidence.actual_finalized_branch) != 0:
return False
else:
finalized_slot = evidence.finalized_header.slot
if recent_finalized_slot < finalized_slot:
return False
distance = recent_finalized_slot - finalized_slot
if distance == 0:
gindex = GeneralizedIndex(1)
else:
gindex = BLOCK_STATE_ROOT_INDEX
if distance <= SLOTS_PER_HISTORICAL_ROOT:
gindex = (gindex << floorlog2(STATE_BLOCK_ROOTS_INDEX)) + STATE_BLOCK_ROOTS_INDEX
else:
gindex = (gindex << floorlog2(STATE_HISTORICAL_ROOTS_INDEX)) + STATE_HISTORICAL_ROOTS_INDEX
gindex = (gindex << uint64(1)) + 0 # `mix_in_length`
historical_batch_index = finalized_slot // SLOTS_PER_HISTORICAL_ROOT
gindex = (gindex << floorlog2(HISTORICAL_ROOTS_LIMIT)) + historical_batch_index
gindex = (gindex << floorlog2(HISTORICAL_BATCH_BLOCK_ROOTS_INDEX)) + HISTORICAL_BATCH_BLOCK_ROOTS_INDEX
gindex = (gindex << uint64(1)) + 0 # `mix_in_length`
block_root_index = finalized_slot % SLOTS_PER_HISTORICAL_ROOT
gindex = (gindex << floorlog2(SLOTS_PER_HISTORICAL_ROOT)) + block_root_index
if len(evidence.actual_finalized_branch) != floorlog2(gindex):
return False
if not is_valid_merkle_branch(
leaf=evidence.actual_finalized_block_root,
branch=evidence.actual_finalized_branch,
depth=floorlog2(gindex),
index=get_subtree_index(gindex),
root=recent_finalized_block_root,
):
return False
# 验证同步委员会聚合签名
sync_committee_pubkeys = evidence.sync_committee_pubkeys
participant_pubkeys = [
pubkey for (bit, pubkey) in zip(sync_aggregate.sync_committee_bits, sync_committee_pubkeys)
if bit
]
fork_version = compute_fork_version(compute_epoch_at_slot(evidence.signature_slot))
domain = compute_domain(DOMAIN_SYNC_COMMITTEE, fork_version, genesis_validators_root)
signing_root = compute_signing_root(evidence.attested_header, domain)
return bls.FastAggregateVerify(participant_pubkeys, signing_root, sync_aggregate.sync_committee_signature)
def process_sync_committee_slashing(state: BeaconState, sync_committee_slashing: SyncCommitteeSlashing) -> None:
is_slashable = False
# 检查证据是否按 `attested_header.slot` 降序排列且不是来自未来
evidence_1 = sync_committee_slashing.evidence_1
evidence_2 = sync_committee_slashing.evidence_2
assert state.slot >= evidence_1.signature_slot > evidence_1.attested_header.slot >= evidence_1.finalized_header.slot
assert state.slot >= evidence_2.signature_slot > evidence_2.attested_header.slot >= evidence_2.finalized_header.slot
assert evidence_1.attested_header.slot >= evidence_2.attested_header.slot
# 只有当前和之前同步委员会期间之间的冲突数据才是可罚没的;
# 在新周期中,同步委员会最初会签署之前同步委员会期间的区块。
# 这允许与恶意检查点同步的验证者在未来的周期中再次做出贡献
evidence_1_attested_period = compute_sync_committee_period_at_slot(evidence_1.attested_header.slot)
evidence_2_attested_period = compute_sync_committee_period_at_slot(evidence_2.attested_header.slot)
assert evidence_1_attested_period <= evidence_2_attested_period + 1
# 不允许为给定的 slot 签署冲突的 `attested_header`
if evidence_1.attested_header.slot == evidence_2.attested_header.slot:
if evidence_1.attested_header != evidence_2.attested_header:
is_slashable = True
# 不允许签署冲突的已最终确定的 `next_sync_committee`
evidence_1_finalized_period = compute_sync_committee_period_at_slot(evidence_1.finalized_header.slot)
evidence_2_finalized_period = compute_sync_committee_period_at_slot(evidence_2.finalized_header.slot)
if (
evidence_1_attested_period == evidence_2_attested_period
and evidence_1_finalized_period == evidence_1_attested_period
and evidence_2_finalized_period == evidence_2_attested_period
and sync_committee_slashing_evidence_has_finality(evidence_1)
and sync_committee_slashing_evidence_has_finality(evidence_2)
and sync_committee_slashing_evidence_has_sync_committee(evidence_1)
and sync_committee_slashing_evidence_has_sync_committee(evidence_2)
):
if evidence_1.next_sync_committee != evidence_2.next_sync_committee:
is_slashable = True
# 不允许签署非线性的最终确定历史记录
recent_finalized_slot = sync_committee_slashing.recent_finalized_slot
recent_finalized_block_root = sync_committee_slashing.recent_finalized_block_root
if (
not sync_committee_slashing_evidence_has_finality(evidence_1)
or not sync_committee_slashing_evidence_has_finality(evidence_2)
):
assert recent_finalized_block_root == Root()
if recent_finalized_block_root == Root():
assert recent_finalized_slot == 0
else:
# 可以包括 Merkle 证明,以表明 `finalized_header` 与
# 相对于给定 `recent_finalized_block_root` 的 `actual_finalized_block_root` 不匹配。
# 最终确定的历史记录是线性的。因此,不匹配表示在不相关的链上签名。
# 请注意,仅签署替代历史记录是不可罚没的,只要它是一致的即可。
# 这允许与恶意检查点同步的验证者在未来的周期中再次做出贡献
linear_1 = (evidence_1.actual_finalized_block_root == hash_tree_root(evidence_1.finalized_header))
linear_2 = (evidence_2.actual_finalized_block_root == hash_tree_root(evidence_2.finalized_header))
assert not linear_1 or not linear_2
assert linear_1 or linear_2 # 不要在仅签署替代历史记录时进行罚没
# `actual_finalized_branch` 可能以提供的具有最高 slot 的 `finalized_header` 为根
rooted_in_evidence_1 = (
evidence_1.finalized_header.slot >= evidence_2.finalized_header.slot
and recent_finalized_slot == evidence_1.finalized_header.slot
and recent_finalized_block_root == evidence_1.actual_finalized_block_root and linear_1
)
rooted_in_evidence_2 = (
evidence_2.finalized_header.slot >= evidence_1.finalized_header.slot
and recent_finalized_slot == evidence_2.finalized_header.slot
and recent_finalized_block_root == evidence_2.actual_finalized_block_root and linear_2
)
# 或者,如果无法直接从攻击中获得有关非线性的证据,
# 则可以证明其中一个 `finalized_header` 是我们的信标节点
# 同步到的规范最终确定链的一部分,而另一个 `finalized_header` 是不相关的。
rooted_in_canonical = (
recent_finalized_slot < state.slot <= recent_finalized_slot + SLOTS_PER_HISTORICAL_ROOT
and recent_finalized_slot <= compute_start_slot_at_epoch(state.finalized_checkpoint.epoch)
and recent_finalized_block_root == state.state_roots[recent_finalized_slot % SLOTS_PER_HISTORICAL_ROOT]
)
assert rooted_in_evidence_1 or rooted_in_evidence_2 or rooted_in_canonical
is_slashable = True
assert is_slashable
# 检查可罚没的验证者是否已排序、已知且参与了两个签名
will_slash_any = False
sync_aggregate_1 = evidence_1.sync_aggregate
sync_aggregate_2 = evidence_2.sync_aggregate
sync_committee_pubkeys_1 = evidence_1.sync_committee_pubkeys
sync_committee_pubkeys_2 = evidence_2.sync_committee_pubkeys
participant_pubkeys_1 = [
pubkey for (bit, pubkey) in zip(sync_aggregate_1.sync_committee_bits, sync_committee_pubkeys_1)
if bit
]
participant_pubkeys_2 = [
pubkey for (bit, pubkey) in zip(sync_aggregate_2.sync_committee_bits, sync_committee_pubkeys_2)
if bit
]
slashable_validators = sync_committee_slashing.slashable_validators
num_validators = len(state.validators)
for i, index in enumerate(slashable_validators):
assert (
index < num_validators
and (i == 0 or index > slashable_validators[i - 1])
)
assert state.validators[index].pubkey in participant_pubkeys_1
assert state.validators[index].pubkey in participant_pubkeys_2
if is_slashable_validator(state.validators[index], get_current_epoch(state)):
will_slash_any = True
assert will_slash_any
# 验证证据,包括签名
assert is_valid_sync_committee_slashing_evidence(
evidence_1,
recent_finalized_block_root,
recent_finalized_slot,
state.genesis_validator_root,
)
assert is_valid_sync_committee_slashing_evidence(
evidence_2,
recent_finalized_block_root,
recent_finalized_slot,
state.genesis_validator_root,
)
# 执行罚没
for index in slashable_validators:
if is_slashable_validator(state.validators[index], get_current_epoch(state)):
slash_validator(state, index)
理由
用例是什么?
如果没有罚没,轻客户端同步协议会受到一些限制。虽然钱包应用程序可能会从中受益(风险在于显示不正确的数据),并且新的信标节点可能会使用它来加速链同步,但其他有趣的用例(如桥、代币分发或其他需要证明的系统)取决于提供更高安全保证的机制。
通过使同步委员会成员的攻击可罚没,可以提供足够高的威慑力。即使在最简单的情况下,也必须贿赂大多数同步委员会才能成功发起攻击,这代表了相当大的可罚没余额。
向后兼容性
本 EIP 需要进行硬分叉,因为它引入了新的共识验证规则。
一旦共识验证规则到位,就可以单独引入支持基础设施,包括但不限于:
- 罚没保护 DB 更新,以保证诚实的验证者不会在重组时被罚没
- 验证者客户端/远程签名者 API,以传递与罚没保护相关的信息
- 用于在信标节点之间交换罚没证据的 libp2p 网格
- Slasher,用于监控潜在目标并构建罚没证据
- 信标 API,用于提交和监控罚没证据
测试用例
待定
参考实现
待定
安全考虑
待定
版权
通过 CC0 放弃版权和相关权利。
Citation
Please cite this document as:
Etan Kissling (@etan-status), "EIP-7657: 同步委员会的罚没 [DRAFT]," Ethereum Improvement Proposals, no. 7657, March 2024. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7657.