☰
Tendermint 轻客户端攻击者隔离(Light Client Attackers Isolation):三类攻击的链上举证与验证
2026/10/12 2:19:26 网站建设 项目流程
  • 区块链
  • 共识算法

【免费下载链接】tendermint

⟁ Tendermint Core (BFT Consensus) in Go

项目地址:https://gitcode.com/gh_mirrors/te/tendermint
点击查看免费下载

本文基于 Tendermint 规范 spec/light-client/attacks/isolate-attackers_002_reviewed.md,系统讲解全节点(full node)如何利用轻客户端提交的攻击证据(evidence),通过isolateMisbehavingProcesses函数从链上隔离出实际的作恶验证人集合。文章覆盖问题定义、输出契约、隔离协议、三类攻击(lunatic / equivocation / amnesia)的判定逻辑、TLA+ 完整性论证,并结合仓库 Go 实现(light/detector.go、types/evidence.go、evidence/verify.go)给出源码级印证。读完本文,你将掌握攻击证据的数据结构、全节点验证路径、隔离算法的完整调用链,以及"攻击类型穷尽性"的形式化论证方法。


一、背景:为什么需要"隔离攻击者"

1.1 攻击的本质是偏离共识协议签名

对抗性节点(adversarial nodes)有动机向轻客户端谎报 Tendermint 区块链的状态,这种尝试被称为攻击。轻客户端验证依赖所谓的commit——一组在执行 Tendermint 共识时产生的签名消息。因此,一次攻击本质上归结为:偏离 Tendermint 共识算法规则去创建并签名共识消息(见原规范第 3 行)。

由于 Tendermint 共识与轻客户端验证的安全性都建立在"每个区块拥有超过 2/3 正确投票权"的假设[[TMBC-FM-2THIRDS]]之上(见 verification_002_draft.md 中[TMBC-FM-2THIRDS.1],要求任意相邻区块间有超过 2/3 投票权、时间位于信任期内的验证者持续在线且正确执行协议),一旦发生攻击,必然意味着该假设被违反。由此可推出关键事实:

  • 某些验证者偏离了协议;
  • 这些验证者在某一区块中代表的投票权超过 1/3。

正是这"超过 1/3 投票权"的下界,为攻击者隔离提供了可行性:即便只有 1/3+ 的验证者作恶,也足以产生两个互相冲突的合法签名集合,而正确验证者不可能为非法区块签名,因此交集必然命中作恶者。

1.2 检测、证据与隔离的职责分工

在轻客户端攻击检测机制(detector)检测到攻击后,会计算被称为evidence的数据(定义见[[LC-DATA-EVIDENCE.1]]),其用途有二:

  1. 证明确实发生了攻击([[TMBC-LC-EVIDENCE-DATA.1]]);
  2. 作为找出实际偏离 Tendermint 协议节点的基础。

本规范(隔离规范)考虑的正是第二步:Tendermint 区块链上的全节点如何从证据中隔离出一组发动攻击的作恶验证者。隔离出的集合需要满足:

  • 集合中不包含任何正确验证者;
  • 集合中的验证者代表超过 1/3 的投票权,且对应区块仍处于unbonding period(解绑期)内。

这一约束非常关键:只有仍在解绑期内的验证者才可被惩罚(slash),这也与全节点在链上证据处理时"只向应用上报 bonded 验证者"的要求一致。


二、Part I:问题定义与输出契约

2.1 输入:攻击证据

检测机制规范定义了证据的数据格式([[LC-DATA-EVIDENCE.1]]):

type LightClientAttackEvidence struct { ConflictingBlock LightBlock CommonHeight int64 }
  • ConflictingBlock:与链上区块冲突的轻区块(由攻击者构造、经检测机制确认可从公共高度验证)。
  • CommonHeight:主链与冲突分支分叉前共同信任的高度。

该结构的 Go 实现位于 types/evidence.go,额外携带 ABCI 相关信息:

type LightClientAttackEvidence struct { ConflictingBlock *LightBlock CommonHeight int64 // abci specific information ByzantineValidators []*Validator // validators in the validator set that misbehaved in creating the conflicting block TotalVotingPower int64 // total voting power of the validator set at the common height Timestamp time.Time // timestamp of the block at the common height }

注意:Height()返回的是CommonHeight而非冲突高度,因为作恶验证者在该高度必定处于 bonded 状态,这对证据过期(evidence expiry)判断至关重要(见 types/evidence.go 的注释)。

2.2 隔离函数的输入输出契约

**isolator(隔离器)**是一个函数:输入为证据ev和区块链前缀bc(至少延伸到高度ev.ConflictingBlock.Header.Height + 1),输出为验证者的peerID 集合。规范假设全节点已与区块链同步并到达高度ev.ConflictingBlock.Header.Height + 1。

核心不变式[LCAI-INV-Output.1]定义了输出的合法条件:

  • 若满足以下条件:
    • bc[CommonHeight].bfttime相对全节点当前时间仍在解绑期内;
    • ev.ConflictingBlock.Header != bc[ev.ConflictingBlock.Header.Height](确实冲突);
    • ev.ConflictingBlock.Commit中的验证者在bc[ev.CommonHeight].NextValidators中代表超过 1/3 的投票权;
  • 则输出必须是bc[CommonHeight].NextValidators的一个子集,且该子集:
    • 代表超过 1/3 的投票权;
    • 确实在高度ev.ConflictingBlock.Header.Height签名了违反 Tendermint 共识协议的共识消息。
  • 否则输出空集。

从实现看,这一契约的 TLA+ 版本在 spec/light-client/attacks/Isolation_001_draft.tla 中被建模为两个不变式:DetectionCompleteness(攻击者投票权 > 1/3)与DetectionAccuracy(攻击者 ⊆ Faulty,即不含正确验证者)。


三、Part II:隔离协议

3.1 主函数isolateMisbehavingProcesses

规范的协议核心是主函数[LCAI-FUNC-MAIN.1]:

func isolateMisbehavingProcesses(ev LightClientAttackEvidence, bc Blockchain) []ValidatorAddress { reference := bc[ev.conflictingBlock.Header.Height].Header ev_header := ev.conflictingBlock.Header ref_commit := bc[ev.conflictingBlock.Header.Height + 1].Header.LastCommit // + 1 !! ev_commit := ev.conflictingBlock.Commit if violatesTMValidity(reference, ev_header) { // lunatic light client attack signatories := Signers(ev.ConflictingBlock.Commit) bonded_vals := Addresses(bc[ev.CommonHeight].NextValidators) return intersection(signatories, bonded_vals) } // If this point is reached the validator sets in reference and ev_header are identical else if RoundOf(ref_commit) == RoundOf(ev_commit) { // equivocation light client attack return intersection(Signers(ref_commit), Signers(ev_commit)) } else { // amnesia light client attack return IsolateAmnesiaAttacker(ev, bc) } }

整体逻辑分三步走(规范中的 Outline):

  1. 先验证冲突区块能否从公共高度验证(ValidAndVerifiedUnbonding,见 3.3 节);
  2. 再判定攻击类型:先检查是否为 lunatic(违反有效性);若不是,检查是否为 equivocation(同一轮次双重签名);若都不是,则进入链上accountability(问责)协议处理 amnesia 攻击。

3.2 前置条件、后置条件与错误条件

  • 前置条件:
    • length(bc) >= ev.conflictingBlock.Header.Height;
    • ValidAndVerifiedUnbonding(bc[ev.CommonHeight], ev.ConflictingBlock) == SUCCESS(公共高度块未过期且冲突块可验证);
    • ev.ConflictingBlock.Header != bc[ev.ConflictingBlock.Header.Height];
    • ev.conflictingBlock通过基本校验(尤其 commit 中所有签名消息必须来自同一轮次)。
  • 后置条件:[LCAI-INV-Output.1]成立。
  • 错误条件:前置条件被违反时返回错误。

3.3 辅助函数

ValidAndVerifiedUnbonding(trusted, untrusted)[LCAI-FUNC-VVU.1]

条件与验证规范的[LCV-FUNC-VALID.2](见 verification_002_draft.md 中ValidAndVerified)完全一致,唯一区别是把前置条件trusted.Header.Time > now - trustingPeriod替换为:

trusted.Header.Time > now - UnbondingPeriod

也就是说,全节点在受理证据时使用解绑期(而非轻客户端的信任期)作为时间窗口,保证上报的作恶验证者仍在押金锁定范围内。

violatesTMValidity(ref, ev)[LCAI-FUNC-NONVALID.1]

检查证据头ev是否违反 Tendermint 共识的有效性属性。前置条件ref.Height == ev.Height;后置条件返回以下析取式[LCAI-NONVALID-OUTPUT.1]的求值结果:

ref.ValidatorsHash != ev.ValidatorsHash or ref.NextValidatorsHash != ev.NextValidatorsHash or ref.ConsensusHash != ev.ConsensusHash or ref.AppHash != ev.AppHash or ref.LastResultsHash != ev.LastResultsHash

IsolateAmnesiaAttacker(ev, bc)

触发链上的query/response 协议(on-chain accountability protocol),后置条件为返回符合[LCAI-INV-Output.1]的攻击者集合。

RoundOf(commit)

前置条件:commit 格式良好,尤其所有投票来自同一轮次r;后置条件:返回 commit 中所有投票编码的轮次r;前置条件被违反时报错。

Signers(commit)

返回 commit 中所有验证者地址。

Addresses(vals)

返回验证者列表中所有验证者地址。

3.4 实现注释:+ 1的含义

代码中ref_commit取自bc[ev.conflictingBlock.Header.Height + 1].Header.LastCommit(注意+ 1)。原因是:全节点本地存储的、由正确验证者签名的高度H的 commit,实际上是高度H+1区块头中的LastCommit字段。若全节点只推进到高度ev.conflictingBlock.Header.Height,则该字段引用的是本地存储的该高度 commit(由length(bc)前置条件保证存在)。这一点与检测机制中getSigners(trustedCommit)使用bc[conflictingBlock.Header.Height+1].LastCommit的写法(见 notes-on-evidence-handling.md)完全一致。

另外规范明确指出:虽然前置条件已检查解绑期未过期,但时间在流动,因此在把验证者移交给 Cosmos SDK 之前需要再次检查时间,以满足"只上报 bonded 验证者"的契约——这一移交动作不在本规范范围内。


四、三类攻击的判定与源码印证

4.1 攻击类型归纳

规范将"错误签名的消息"归纳为三种类型:

类型含义判定条件
Lunatic(狂想)签名了非法区块(有效状态转换之外的内容)violatesTMValidity为真
Equivocation(双重签名)在同一共识轮次双重签名了不同合法区块violatesTMValidity为假且两 commit 轮次相同
Amnesia(失忆)在不同共识轮次签名冲突区块,却未见过允许其这样做的法定人数消息violatesTMValidity为假且两 commit 轮次不同

4.2 Go 实现的对应逻辑

规范中的violatesTMValidity在 types/evidence.go 中实现为ConflictingHeaderIsInvalid:

func (l *LightClientAttackEvidence) ConflictingHeaderIsInvalid(trustedHeader *Header) bool { return !bytes.Equal(trustedHeader.ValidatorsHash, l.ConflictingBlock.ValidatorsHash) || !bytes.Equal(trustedHeader.NextValidatorsHash, l.ConflictingBlock.NextValidatorsHash) || !bytes.Equal(trustedHeader.ConsensusHash, l.ConflictingBlock.ConsensusHash) || !bytes.Equal(trustedHeader.AppHash, l.ConflictingBlock.AppHash) || !bytes.Equal(trustedHeader.LastResultsHash, l.ConflictingBlock.LastResultsHash) }

而isolateMisbehavingProcesses的三分支结构对应GetByzantineValidators(types/evidence.go),注释与分支逻辑完全一致:无效头 → lunatic,取"公共验证者集合中给冲突区块投票者";轮次相同 → equivocation,取"两个 commit 都签名者";轮次不同 → amnesia,因无法仅凭证据推断,返回空验证者集合。

注意一个工程细节:判定轮次时,规范使用RoundOf(ref_commit)(引用块 commit 编码的轮次),Go 实现使用trusted.Commit.Round——两者语义相同,因为bc[ConflictingHeight+1].Header.LastCommit正是与冲突高度同高的被信任 commit。

4.3 检测端(轻客户端侧)的证据生成

隔离协议的输入端是检测机制的输出。在 light/detector.go 中,newLightClientAttackEvidence生成证据时也会做同样的类型判定(light/detector.go):

func newLightClientAttackEvidence(conflicted, trusted, common *types.LightBlock) *types.LightClientAttackEvidence { ev := &types.LightClientAttackEvidence{ConflictingBlock: conflicted} // if this is an equivocation or amnesia attack, i.e. the validator sets are the same, then we // return the height of the conflicting block else if it is a lunatic attack and the validator sets // are not the same then we send the height of the common header. if ev.ConflictingHeaderIsInvalid(trusted.Header) { ev.CommonHeight = common.Height ev.Timestamp = common.Time ev.TotalVotingPower = common.ValidatorSet.TotalVotingPower() } else { ev.CommonHeight = trusted.Height ev.Timestamp = trusted.Time ev.TotalVotingPower = trusted.ValidatorSet.TotalVotingPower() } ev.ByzantineValidators = ev.GetByzantineValidators(common.ValidatorSet, trusted.SignedHeader) return ev }

关键点:CommonHeight 的取值本身携带了攻击类型信息。若CommonHeight != ConflictingBlock.Height则按定义是 lunatic 攻击(因为验证者集合不同,只能从公共高度验证冲突块);若相等则是 equivocation/amnesia。这与检测规范CreateEvidenceForPeer中的注释一致(detection_003_reviewed.md)。

4.4 全节点侧的链上验证

全节点收到证据后,在 evidence/verify.go 的VerifyLightClientAttack中执行完整验证,对应规范的前置条件:

func VerifyLightClientAttack(e *types.LightClientAttackEvidence, commonHeader, trustedHeader *types.SignedHeader, commonVals *types.ValidatorSet, now time.Time, trustPeriod time.Duration) error { // In the case of lunatic attack there will be a different commonHeader height. Therefore the node perform a single // verification jump between the common header and the conflicting one if commonHeader.Height != e.ConflictingBlock.Height { err := commonVals.VerifyCommitLightTrusting(trustedHeader.ChainID, e.ConflictingBlock.Commit, light.DefaultTrustLevel) ... } else if e.ConflictingHeaderIsInvalid(trustedHeader.Header) { return errors.New("common height is the same as conflicting block height so expected the conflicting" + " block to be correctly derived yet it wasn't") } ... }

随后验证冲突验证者集合对冲突块的 2/3+ 提交、投票权一致、以及"前向 lunatic 攻击"的时间单调性检查。证据池入口Pool.verify(evidence/verify.go)还会先做过期检查:只有当ageDuration > MaxAgeDuration && ageNumBlocks > MaxAgeNumBlocks时才判定过期(evidence/verify.go)。默认证据参数MaxAgeNumBlocks: 100000、MaxAgeDuration: 48 * time.Hour定义在 types/params.go——这正是"解绑期窗口"在工程上的落地形态。

4.5 amnesia 攻击为何需要链上问责协议

从 types/evidence.go 的注释可以看到,amnesia 攻击无法仅凭证据数据推断作恶者:因为两个 commit 轮次不同,签名者集合相同也不能说明谁违规——违规的判定标准是"在未见过允许其这样做的法定人数消息的情况下跨轮次签名"(见 notes-on-evidence-handling.md 中的 amnesia 处理协议)。因此 amnesia 分支必须触发链上 query/response 协议:

  1. 监控者(monitor,可在分布式环境中以链上模块实现)向冲突高度的验证者请求 votesets;
  2. 验证者在超时内发送各自收到的投票集;超时未响应者判为 faulty;
  3. 预处理:每个合法投票被归入其发送者的 voteset,确保被至少一个正确验证者观察到的恶意投票不会被排除;
  4. 独立分析每个验证者的 voteset,判定其是否出现非法状态转换,例如:
    • 同一轮次发送多于一条 PREVOTE 或 PRECOMMIT;
    • 未收到 +2/3 投票权对应的 PREVOTE 就发送 PRECOMMIT;
    • 在轮次r'发送 PREVOTE(V') 之前曾在轮次r(r' > r)发送过 PRECOMMIT(V),且缺乏vr(vr ≥ 0 且 r < vr < r')轮次的 +2/3 PREVOTE(vr, V') 作为依据。

这也解释了为什么 amnesia 攻击只在多轮次高度(commit round > 0)才可能发生——单一轮次内不存在"跨轮次"的非法转换空间。


五、Part III:完整性论证(为什么三类攻击是穷尽的)

5.1 归约到固定成员资格下的二元共识

如文档开头所述,攻击归结为偏离共识规则签名消息。主函数区分的三种错误签名类型是否覆盖所有攻击?论证如下:

  1. 第一个检查violatesTMValidity处理 lunatic 攻击。若该检查通过(返回 FALSE),则[LCAI-NONVALID-OUTPUT.1]为假,意味着ref.ValidatorsHash = ev.ValidatorsHash,即冲突区块的验证者集合与链上一致;
  2. 因此只需分析**固定成员资格(固定验证者集合)**的单实例 Tendermint 共识;
  3. 又因为同一高度存在两个不同区块,只需考虑两个不同的共识值——即二元共识(binary consensus)。

5.2 TLA+ 与 Ivy 的交叉验证

对于固定成员资格,作者使用 Tendermint 共识的TLA+ 规范(对应仓库 spec/light-client/accountability 中的TendermintAcc_004_draft.tla及其不变式TendermintAccInv_004_draft.tla)进行了分析,检查确认:唯一可能导致 agreement 违反的场景就是 equivocation 和 amnesia。Galois 基于 Ivy 证明(spec/ivy-proofs)的独立研究得出相同结论。

Synopsis.md 总结了问责 TLA+ 模型的要点:简化模型假设单高度一次性共识、只关注安全性、时间用非确定性建模、每个进程投票权为 1、哈希为恒等映射;核心结论是"在集体证据下,至少f+1个拜占庭进程必然表现出 equivocation(同一轮次发送两个不同值)或 amnesia(锁定了过去锁定过的另一个值)"。

5.3 隔离模型的机械化验证

攻击隔离逻辑本身也有 TLA+ 模型 spec/light-client/attacks/Isolation_001_draft.tla。该模型用 Apalache 检查器验证两个不变式(注释中标注为[LCAI-INV-Output.1::TLA-DETECTION-COMPLETENESS.1]与[LCAI-INV-Output.1::TLA-DETECTION-ACCURACY.1]):

DetectionCompleteness == state /= "init" => 3 * Cardinality(attackers) > Cardinality(blockchain[CONFLICT_HEIGHT].VS) DetectionAccuracy == attackers \subseteq Faulty

其中Next动作正是isolateMisbehavingProcesses的机械化版本:ViolatesValidity时取NextVS ∩ evidenceCommit(lunatic);否则轮次相同取referenceCommit ∩ evidenceCommit(equivocation);轮次不同则进入 amnesia 分支并存在一个满足 >1/3 投票权的攻击者子集(该属性由TendermintAcc的 Accountability 性质保证)。这为本文第 2.2 节的输出契约提供了自动化验证依据。


六、端到端流程与工程落地点

综合规范与代码,完整的攻击处理链路如下:

  1. 检测:轻客户端以 primary 验证目标头,再用 witness 交叉比对(light/detector.go 的detectDivergence),发现冲突后examineConflictingHeaderAgainstTrace定位分叉点;
  2. 生成证据:newLightClientAttackEvidence判定攻击类型并填充CommonHeight、ByzantineValidators等字段;对 primary/witness 双向生成证据并发送(handleConflictingHeaders,light/detector.go);
  3. 链上受理:全节点经证据池Pool.verify做基础与过期校验(evidence/verify.go),VerifyLightClientAttack完成轻信任验证与类型一致性检查;
  4. 隔离:GetByzantineValidators依据类型计算作恶验证者(types/evidence.go);amnesia 场景则通过链上问责协议在后续区块中确定(当前实现中 amnesia 证据的ByzantineValidators为空,见 evidence/verify.go 的校验逻辑);
  5. 上报应用:ABCI()将每个作恶验证者转换为abci.Evidence(类型LIGHT_CLIENT_ATTACK,types/evidence.go),供应用层实施惩罚(slash)。

相关测试可参考 evidence/verify_test.go(TestVerifyLightClientAttack_Amnesia等用例)与 evidence/pool_test.go(TestLightClientAttackEvidenceLifecycle),它们验证了本文所述各类型攻击的隔离结果与生命周期。


七、关键要点总结

  • 攻击即偏离共识协议的签名:攻击能成立的前提是超过 1/3 投票权偏离协议,这保证了隔离集合的下界;
  • 证据是常量大小的二元组:ConflictingBlock + CommonHeight,同时满足轻客户端带宽约束与全节点可验证性;
  • 三类攻击穷尽:lunatic(非法块)、equivocation(同轮双签)、amnesia(跨轮无依据签名),其穷尽性由 TLA+ 分析与 Ivy 证明双重背书;
  • 隔离的输出契约:不含正确验证者、代表超过 1/3 投票权、且区块仍处于解绑期内,三者缺一不可;
  • amnesia 最复杂:无法仅凭证据定位作恶者,需链上 query/response 问责协议结合验证者本地投票集判定;
  • 工程实现完全对齐规范:ConflictingHeaderIsInvalid↔violatesTMValidity,GetByzantineValidators↔isolateMisbehavingProcesses三分支,EvidenceParams.MaxAgeDuration↔ unbonding period 窗口。
  • 区块链
  • 共识算法

【免费下载链接】tendermint

⟁ Tendermint Core (BFT Consensus) in Go

项目地址:https://gitcode.com/gh_mirrors/te/tendermint
点击查看免费下载

相关推荐

上一篇:CANN Runtime ACL 错误码 EH0002 排查指南:`Argument must not be null` 空指针参数报错的定位与处理
下一篇:ESP32 BLE Object Transfer Service(OTS)服务端示例详解:基于 esp-iot-solution 实现对象传输服务的 GATT 服务器

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询