)
EIP-3670 深度解析EOF 格式合约的部署期代码校验EOF Code Validation【免费下载链接】EIPsThe Ethereum Improvement Proposal repository项目地址: https://gitcode.com/GitHub_Trending/ei/EIPsEIP-3670EOF – Code Validation是 EVM 对象格式EOF系列提案中负责“部署期代码校验”的核心 EIP它为 EIP-3540 定义的 EOF1 容器引入了在合约创建时执行的字节码合法性检查拒绝包含未定义指令或截断PUSH立即数immediate data的合约。本文以 EIPS/eip-3670.md 为主体结合本仓库中 EIP-3540、EIP-3541、EIP-7620、EIP-5450 等相关提案逐条拆解其校验规则、设计权衡、参考实现与测试用例帮助你理解 EOF 如何把“字节码合法性”纳入共识层。为什么需要把代码校验纳入共识在今天的主网上已部署的 EVM 字节码没有任何预定义的结构。客户端通常依赖运行时反复进行的JUMPDEST分析来“容忍”截断的PUSH数据和未定义指令EVM 实现可以自行决定如何处理这些畸形字节码。这种现状带来两方面问题推理困难同一段字节码在不同客户端中的行为可能不一致难以对链上字节码做静态推理演进受限如果未来想在不提升 EOF 版本的前提下引入新指令链上已存在的“未定义指令”可能被重新赋予语义从而破坏既有合约。EIP-3670 的目标是把“代码有效性”变成共识的一部分——所有通过校验的 EOF 合约在部署后必然是良构的well-formedEVM 实现因此在判断“当前执行上下文中哪个指令合法”时只需要更少的执行路径。EOF1 的前向兼容性保障在 EIP-3540 定义的 EOF1 格式下部署期校验带来了三方面前向兼容能力新指令可定义在未分配的 opcode 上且可以携带立即数immediate values强制性的 EOF 段section可以改为可选可以引入新的可选 EOF 段且这些新段与既有段的相对顺序不受限制。正是因为部署时已经拒绝了所有未定义指令未来这些“未分配 opcode”才能安全地获得新语义而不必担心与链上旧合约冲突。EOF 容器格式与校验的定位EIP-3670 的校验规则只对 EOF 格式的字节码生效。要理解它需要先了解 EIP-3540 定义的容器布局magic, version, (section_kind, section_size_or_sizes), 0, section contentsEOF1 容器的具体结构是container : header, body header : magic, version, kind_type, type_size, kind_code, num_code_sections, code_size, [kind_container, num_container_sections, container_size,] kind_data, data_size, terminator body : types_section, code_section, container_section*, data_section其中关键字段包括2 字节 magic0xEF000xEF由 EIP-3541 保留、1 字节 versionEOF1 为0x01、type 段每 4 字节描述一个代码段的inputs/outputs/max_stack_increase、若干个 code 段、可选的 container 段以及一个 data 段。代码段存放任意字节码数据段存放任意字节序列——代码与数据在容器级别就被显式分离这是 EOF 带来的第一个切实收益。EIP-3540 明确说明代码校验在合约创建contract creation期间执行其详细规则由独立的 EIP 规定而legacy 代码非 EOF 格式不受任何影响。EIP-3670 正是承担“代码段内容校验”这一职责的提案并且它与 EIP-3540 在同一区块激活因此所有 EOF1 兼容字节码都必须按 EIP-3670 的规则校验。规范两条核心校验规则EIP-3670 的规范非常精简围绕“每个代码段”执行两条检查此前已废弃的指令CALLCODE0xf2和SELFDESTRUCT0xff以及 EIP-3540 中废弃的指令其 opcode 均视为未定义invalid。注意EOF 中还有更多被废弃/拒绝的指令由其他 EIP 另行规定。合约创建时对 EOF 容器的每个代码段执行代码校验任一检查失败即视为代码无效。对每条指令检查 opcode 是否已定义INVALID0xfe视为已定义检查指令的全部立即数字节是否都存在于代码中代码不能在指令中间结束。规则一废弃指令被拒绝参考实现中的rejected_in_eof列表给出了被拒绝的完整 opcode 集合十六进制助记符废弃来源0x38CODESIZEEIP-3540 废弃0x39CODECOPYEIP-3540 废弃0x3bEXTCODECOPYEIP-3540 废弃0x3cEXTCODEHASHEIP-3540 废弃0x3fEXTCODESIZEEIP-3540 废弃0x5aGASEIP-3540 废弃0xf1CALLEIP-3540 废弃0xf2CALLCODEEIP-3670 废弃0xf4DELEGATECALLEIP-3540 废弃0xfaSTATICCALLEIP-3540 废弃0xffSELFDESTRUCTEIP-3670 废弃这与 EIP-3540 的“执行语义变更”一节相互印证CODESIZE、CODECOPY、EXTCODESIZE、EXTCODECOPY、EXTCODEHASH、GAS在 EOF 合约中被校验拒绝且无替代CALL、DELEGATECALL、STATICCALL同样被拒绝替代指令由 EIP-7069Revamped CALL instructions另行引入。值得注意的是CALLCODE与SELFDESTRUCT的废弃意义。正如 EIP-7069 的 Rationale 所述传统合约可以通过三种方式自我销毁——直接执行SELFDESTRUCT、通过CALLCODE间接执行、以及通过DELEGATECALL间接执行。EIP-3670 关闭了前两种可能禁止SELFDESTRUCT与CALLCODE而 EIP-3540 进一步要求 EOF1 合约只能对 EOF1 合约执行DELEGATECALL在 EIP-7069 中为EXTDELEGATECALL从而可以得出一个强结论EOF1 合约永远不会被销毁基于SELFDESTRUCT的攻击如 Parity 多签库合约被销毁事件对 EOF1 合约完全消失。规则二逐指令校验立即数完整性第二条规则确保代码段的指令边界是良构的opcode 必须在valid_opcodes集合中且其立即数如PUSH1..PUSH32携带的 1~32 字节数据必须完整存在——代码不允许“在一条指令的中间结束”。设计权衡Rationale立即数为何必须显式存在一个自然的疑问是能否允许PUSH指令的立即数缺省、按零补全EIP-3670 明确否决了这种“隐式零立即数”设计它会给 EVM 实现带来不必要的低效而且没有任何实际用例——代码结尾处PUSH指令的值在 EVM 中根本不可能被观察到无法执行到它。因此本 EIP 要求所有立即数字节都显式存在于代码中。废弃指令的处理方式CALLCODE0xf2与SELFDESTRUCT0xff被从valid_opcodes列表中移除目的是防止它们在将来被重新使用。这也是前面“前向兼容性”讨论的具体落地被拒绝的 opcode 未来要么保持未定义要么在明确的版本策略下才可能重新启用。BLOCKHASH 为何保留BLOCKHASH指令已经有了很好的替代品——EIP-2935 引入的系统合约。然而尽管替代品已存在该 opcode 尚未被废弃。EIP-3670 的 Rationale 指出为避免与 legacy 字节码产生行为差异BLOCKHASH在 EOF 中仍然保持有效。这体现了 EOF 校验的一个总体原则在合理范围内与 legacy 语义保持一致减少分化。参考实现逐行解读EIP-3670 给出了一个面向 Shanghai 执行规格的 Python 参考实现完整如下# The ranges below are as specified by Execution Specs for Shanghai. # Note: range(s, e) excludes e, hence the 1 shanghai_opcodes [ *range(0x00, 0x0b 1), *range(0x10, 0x1d 1), 0x20, *range(0x30, 0x3f 1), *range(0x40, 0x48 1), *range(0x50, 0x5b 1), 0x5f, *range(0x60, 0x6f 1), *range(0x70, 0x7f 1), *range(0x80, 0x8f 1), *range(0x90, 0x9f 1), *range(0xa0, 0xa4 1), # Note: 0xfe is considered assigned. 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xfa, 0xfd, 0xfe, 0xff ] # Drop the opcodes deprecated and rejected in here and in EIP-3540 rejected_in_eof [ 0x38, 0x39, 0x3b, 0x3c, 0x3f, 0x5a, 0xf1, 0xf2, 0xf4, 0xfa, 0xff ] valid_opcodes [op for op in shanghai_opcodes not in rejected_in_eof] immediate_sizes 256 * [0] immediate_sizes[0x60:0x7f 1] range(1, 32 1) # PUSH1..PUSH32 # Raises ValidationException on invalid code def validate_instructions(code: bytes): # Note that EOF1 already asserts this with the code section requirements assert len(code) 0 pos 0 while pos len(code): # Ensure the opcode is valid opcode code[pos] if opcode not in valid_opcodes: raise ValidationException(undefined opcode) # Skip immediate data pos 1 immediate_sizes[opcode] # Ensure last instructions immediate doesnt go over code end if pos ! len(code): raise ValidationException(truncated immediate)其核心逻辑可以拆解为四个部分定义 opcode 全集shanghai_opcodes以 Shanghai 执行规格的合法指令集合为基准。注意0xfeINVALID被显式视为已分配assigned因此代码段中可以包含INVALID指令剔除被拒绝的指令得到valid_opcodes从全集中去掉rejected_in_eof中的 11 个 opcode即上文的废弃指令表建立immediate_sizes表256 个条目的数组记录每个 opcode 的立即数长度0x60..0x7fPUSH1..PUSH32分别映射到 1..32其余均为 0单次线性扫描validate_instructions从pos 0开始每遇到一个 opcode 先检查其是否在valid_opcodes中否则抛undefined opcode然后按1 immediate_sizes[opcode]跳过指令整体循环结束后若pos ! len(code)说明最后一条指令的立即数越过了代码末尾抛truncated immediate。这个实现清晰体现了 EIP-3670 校验的线性时间复杂度特性——单遍扫描、无递归、无复杂状态这也是 EIP-3540 安全考量中所说“校验预期具有线性的计算与空间复杂度”的直接来源。测试用例EIP-3670 的测试用例围绕 EOF 合约创建由独立 EIP 规定的 EOF 创建流程展开要求把 EOF 容器提交给 EOF 合约创建流程进行测试并且每个用例都应把代码放在不同索引的代码段中测试覆盖多代码段场景。合法代码Valid codes包含INVALID0xfe指令的 EOF 代码——因为INVALID被视为已定义指令数据段中包含未定义指令字节的 EOF 代码——数据段不做指令解析其内容可以是任意字节序列。非法代码Invalid codes包含未定义指令的 EOF 代码——例如使用了不在valid_opcodes中的 opcode以不完整的PUSH指令结尾的 EOF 代码——即最后一条PUSHn的立即数不足 n 字节validate_instructions中的truncated immediate分支会命中。注意第二类“合法”用例体现了 EOF 代码/数据分离的关键价值未定义字节放在数据段中完全合法而放在代码段中则会被拒绝。这也是 EIP-3540 Motivation 中提到的、对链上代码校验器如 Optimism 等 L2 工具友好的特性来源。向后兼容性EIP-3670 对向后兼容不构成任何风险原因有二它与 EIP-3540 在同一区块引入而 EIP-3540 本身是一个破坏性变更此前以0xEF开头的代码无法部署二者天然同步校验规则不覆盖 legacy 字节码非 EOF 格式的代码链上既有合约的行为完全不受影响。从部署链路看EIP-3541已在 London 分叉部署早已禁止新合约代码以0xEF开头这保证了链上不可能出现“以 EOF magic 开头但未经校验”的旧合约从而让“EOF 前缀 ⇒ 已通过校验”这一强性质成立。安全考量EIP-3670 自身的安全考量直接引用 EIP-3540 的安全考量章节。EIP-3540 指出考虑到 EOF 的后续扩展校验预期具有线性的计算与空间复杂度校验成本已充分覆盖因为initcode 的部署成本由 EIP-3860 按字节计费部署 code 本身就有较高的每字节成本GAS_CODE_DEPOSIT见 EIP-7620 中定义的 200 gas/字节。换句话说恶意方无法通过构造超长或病态的 EOF 容器来对节点发起低成本 DoS 攻击——校验本身的成本结构是防御性的。在 EOFv1 校验体系中的位置EIP-3670 只是 EOF 多阶段校验流水线的第一阶段。作为佐证EIP-5450EOF – Stack Validation的正文明确写道在 EIP-3670 定义的第一校验阶段中并由 EIP-4200 与 EIP-4750 扩展指令被独立检查以确认其 opcode 与立即数是否有效。整个 EOFv1又称 Mega EOF提案清单由 EIP-7692EOFv1 Meta EIP汇总其中 EIP-3670 位列 eof-devnet-0 引入的第一批 EIP。围绕代码段校验各 EIP 的分工如下EIP-4200静态相对跳转扩展 EIP-3670 的校验算法验证每个RJUMP/RJUMPI/RJUMPV的relative_offset必须指向一条指令不能指向PUSHn/RJUMP*的立即数中间不能越出代码边界允许但不要求指向JUMPDESTEIP-4750函数规定 EIP-3670 的代码校验规则应用于每一个代码段EIP-5450栈校验在第一阶段EIP-3670 的指令级校验之上叠加第二阶段的栈高度/类型分析EIP-7620EOF 合约创建进一步扩展代码段校验规则例如EOFCREATE的initcontainer_index必须小于num_container_sections、被引用的子容器不得包含RETURN/STOP等EIP-7069重构 CALL 指令承接 EIP-3670 对CALL/DELEGATECALL/STATICCALL的拒绝为 EOF 提供替代调用指令。由此可见EIP-3670 奠定了 EOF 整个校验体系的地基先把“每个指令本身是否合法、立即数是否完整”这件最基础的事钉死在共识层后续的跳转校验、栈校验、容器子段校验才能放心地在其上层层叠加。小结EIP-3670 用两条简洁的校验规则——拒绝废弃/未定义指令、拒绝截断的立即数——把 EOF 代码的“指令级良构性”固化到部署期。它与 EIP-3540 在同一区块生效通过 EIP-3541 对0xEF字节的保留保证链上不存在未经验证的伪 EOF 合约并以线性复杂度的单遍扫描实现低成本校验。理解 EIP-3670是理解整个 EOF 提案族EIP-7692 汇总的第一步也是阅读后续栈校验EIP-5450、跳转校验EIP-4200与 EOF 合约创建EIP-7620等提案的必备基础。【免费下载链接】EIPsThe Ethereum Improvement Proposal repository项目地址: https://gitcode.com/GitHub_Trending/ei/EIPs创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考