EIP-7932 二次签名算法(Secondary Signature Algorithms):Ethereum 统一签名算法注册表与 sigrecover 预编译详解

EIP-7932 二次签名算法(Secondary Signature Algorithms):Ethereum 统一签名算法注册表与 sigrecover 预编译详解 EIP-7932 二次签名算法Secondary Signature AlgorithmsEthereum 统一签名算法注册表与 sigrecover 预编译详解【免费下载链接】EIPsThe Ethereum Improvement Proposal repository项目地址: https://gitcode.com/GitHub_Trending/ei/EIPs导读EIP-7932Secondary Signature Algorithms为 Ethereum 引入了一套统一的多签名算法注册表algorithm registry与标准化接口使得除 secp256k1 之外的后量子Post-QuantumPQ签名算法也能被用于账户地址推导与签名验证。本文以 EIP-7932 规范为主体结合仓库中 assets/eip-7932/ 下的参考实现与测试用例系统讲解注册表设计、算法类型规范、pubkey_to_address地址推导规则、sigrecover预编译的调用格式与 gas 模型并给出新算法接入的完整模板帮助读者理解如何在 EVM 层面扩展签名算法生态。背景为什么需要“二次签名算法”随着量子计算的发展一批后量子PQ签名算法被设计出来但它们普遍存在明显短板密钥体积大超过 1KiB、签名体积大、验证耗时长导致其计算与存储成本都远高于当前 Ethereum 使用的 secp256k1 椭圆曲线。若直接为每种算法单独引入一套地址推导和验证逻辑会造成大量重复且难以维护的协议代码。EIP-7932 的解决方案是创建一个统一注册表与标准化接口用于引入新的签名算法并基于它们推导账户地址在SIGRECOVER_PRECOMPILE_ADDRESS地址引入一个预编译合约precompile负责解码这些新引入的算法。由此各种算法包括未来的 PQ 算法都可以通过单一接口被 Ethereum 使用而无需为每种算法重写地址推导与验证框架。该提案属于 Standards Track / Core 类别当前状态为 Draft创建于 2025-04-12。规范总览与关键参数规范中的关键字MUST、SHOULD、MAY 等按 RFC 2119 与 RFC 8174 解释。除非显式注明整数编码一律使用大端序big-endian。| 常量 | 值 | | - | - | |SIGRECOVER_PRECOMPILE_ADDRESS|Bytes20(0x12)| |SIGRECOVER_PRECOMPILE_BASE_GAS|3000|预编译地址被安排在0x12静态基础 gas 为3000。注意该地址与 EIP-8051ML-DSA 预编译 的VERIFY_MLDSA地址0x12存在关联后者在其规范中声明requires: 7932体现了 EIP-7932 作为签名算法基础设施的地位。算法规范Algorithm Specification一个统一的 Trait 接口新算法除本 EIP 规定的 secp256k1 外必须通过独立的 EIP 来规定。每种算法类型必须指定以下字段与函数trait Algorithm { // The algorithm type byte ALG_TYPE: uint8, // The size of signatures. Signatures MUST be padded // to this size to be valid. Note that this does include // the ALG_TYPE byte prefix SIZE: uint32 // Get the gas cost of signing this data. This // SHOULD include a reasonable minimum and MUST // be relative to secp256k1, i.e. 0 gas is secp256k1. fn gas_cost(signing_data: Bytes) - Uint64; // Check whether the signature is valid. For some // algorithms, this may be a no-op. This function // will always be called before verify. fn validate(signature: Bytes) - None | Error; // Take the signature and signing_data and return the // public key of the signer. fn verify(signature: Bytes, signing_data: Bytes) - Bytes | Error; // Given a public key and corresponding signature, merge them into // a valid signature info container. This function MUST NOT check that // the provided public_key matches the signature. fn merge_detached_signature(detached_signature: Bytes, public_key: Bytes) - Bytes | Error; }字段语义要点ALG_TYPE单字节算法类型标识是注册表的索引键SIZE签名含ALG_TYPE前缀字节必须被填充到该固定大小才有效gas_cost签名数据的 gas 成本相对 secp256k1 计算即 secp256k1 为 0 gas 基准validate签名有效性预检总是在verify之前被调用verify给定签名与签名数据返回签名者公钥merge_detached_signature将分离签名与公钥合并为合法的签名信息容器不得校验公钥与签名是否匹配。规范要求每种算法的 EIP 必须包含针对该算法的安全分析、证明 gas 成本的基准测试benchmarks并且必须处理算法可能引入的可延展性malleability问题。完整的算法接入模板见 assets/eip-7932/template-eip.md.txt。从公钥推导地址pubkey_to_address从公钥推导地址必须使用如下函数def pubkey_to_address(public_key: Bytes, algorithm_id: uint8) - ExecutionAddress: if algorithm_id 0x00: # Compatibility shim to ensure backwards compatibility return ExecutionAddress(keccak(public_key[1:])[12:]) if len(public_key) 63: # Prevent collisions with legacy secp256k1 return ExecutionAddress(keccak(algorithm_id || 0x00 || public_key)[12:]) # with || being binary concatenation return ExecutionAddress(keccak(algorithm_id || public_key)[12:])规则解读算法 ID 为0x00secp256k1走兼容分支直接对公钥去掉首字节前缀 0x04后取 keccak 哈希的后 20 字节与现有 Ethereum 地址推导逻辑完全一致公钥长度恰为 63 字节为防止与 legacy secp256k1 地址碰撞在哈希前插入algorithm_id || 0x00两个字节其他情况直接拼接algorithm_id || public_key后取 keccak 后 20 字节。参考实现位于 assets/eip-7932/algorithm_registry/helpers.py其中ExecutionAddress被实现为ByteVector[20]并借助eth_hash.auto.keccak计算哈希。算法注册表Algorithm Registry注册表结构定义如下class AlgorithmEntry(): ALG_TYPE: uint8, SIZE: uint32, gas_cost: Callable[[Bytes], uint64], merge_detached_signature: Callable[[Bytes, Bytes], Bytes], validate: Callable[[Bytes], None | Error], verify: Callable[[Bytes, Bytes], Bytes | Error] algorithm_registry: Dict[uint8, AlgorithmEntry]该 EIP 使用algorithm_registry对象来标记已包含在某个硬分叉中的算法在 EIP 定稿后可以创建一个常驻livingEIP 来追踪各分叉当前活跃的算法算法类型0x7F被保留为无效/缺失所有算法类型必须小于 127。在参考实现 assets/eip-7932/algorithm_registry/registry.py 中AlgorithmEntry被实现为 Python 类algorithm_registry初始为空字典{}随后通过algorithm_registry[Secp256k1.ALG_TYPE] Secp256k1注册 secp256k1 算法。辅助函数Helper functions规范定义了三个辅助函数def calculate_penalty(algorithm: uint8, signing_data: Bytes) - uint: assert algorithm in algorithm_registry algorithm algorithm_registry[algorithm] return algorithm.gas_cost(signing_data) def validate_signature(signature: Bytes): assert len(signature) 0 assert signature[0] in algorithm_registry algorithm algorithm_registry[signature[0]] return algorithm.validate(signature) # This function cannot be called without prior calling validate_signature(signature) def verify_signature(signing_data: Bytes, signature: Bytes) - Bytes: algorithm algorithm_registry[signature[0]] return algorithm.verify(signature, signing_data)calculate_penalty查询算法的动态 gas 成本签名数据相关validate_signature以签名的首字节作为算法类型索引先断言签名非空且算法已注册再调用算法自身的validateverify_signature注意其调用前置条件——必须先调用validate_signature(signature)才能调用本函数从而保证索引有效。这些辅助函数在 assets/eip-7932/algorithm_registry/helpers.py 中有对应实现可直接对照阅读。首个内置算法secp256k1EIP-7932 内置了 secp256k1 作为基准算法ALG_TYPE 0x00 SIZE 66 SECP256K1_SIGNATURE_SIZE SIZE - 1 def secp256k1_unpack(signature: ByteVector[SECP256K1_SIGNATURE_SIZE]) - tuple[uint256, uint256, uint8]: r uint256.from_bytes(signature[0:32], big) s uint256.from_bytes(signature[32:64], big) y_parity signature[64] return (r, s, y_parity) def secp256k1_validate(signature: ByteVector[SECP256K1_SIGNATURE_SIZE]): SECP256K1N 0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141 r, s, y_parity secp256k1_unpack(signature) assert 0 r SECP256K1N assert 0 s SECP256K1N // 2 assert y_parity in (0, 1) def gas_cost(signing_data: Bytes) - Uint64: # This is an adaptation from the KECCAK256 opcode if len(signing_data) 32: return Uint64(0) else: minimum_word_size (len(signing_data) 31) // 32 return Uint64(30 (6 * minimum_word_size)) def validate(signature: Bytes) - None | Error: secp256k1_validate(signature[1:]) def verify(signature: Bytes, signing_data: Bytes) - Bytes | Error: # Another compatibility shim to ensure passing a 32 byte hash still works. if len(signing_data) ! 32: signing_data keccak256(signing_data) ecdsa ECDSA() recover_sig ecdsa.ecdsa_recoverable_deserialize(signature[1:65], signature[65]) public_key PublicKey(ecdsa.ecdsa_recover(signing_data, recover_sig, rawTrue)) uncompressed public_key.serialize(compressedFalse) return uncompressed def merge_detached_signature(detached_signature: bytes, _public_key: bytes) - bytes: # Secp256k1 uses recoverable signatures, this is a no-op. return detached_signature关键设计ALG_TYPE 0x00SIZE 661 字节类型前缀 65 字节可恢复签名32 字节 r 32 字节 s 1 字节 y_parityvalidate做范围检查0 r SECP256K1N且0 s SECP256K1N // 2低 s 值要求天然抑制签名可延展性y_parity只能是 0 或 1gas_cost是对 KECCAK256 操作码 gas 模型的改编签名数据恰好 32 字节时成本为 0因为无需额外哈希否则按30 6 * ceil(len/32)计费verify兼容性垫片若签名数据不是 32 字节先对其做 keccak256 再恢复公钥返回的是未压缩公钥65 字节含 0x04 前缀merge_detached_signature对 secp256k1 是 no-op因为其原生使用可恢复签名。仓库中的完整实现见 assets/eip-7932/algorithm_registry/registry.py与规范伪代码一一对应。sigrecover预编译EIP-7932 在SIGRECOVER_PRECOMPILE_ADDRESS0x12引入新预编译其行为约束执行前必须收取SIGRECOVER_PRECOMPILE_BASE_GAS3000静态 gas成功时输出签名者的 20 字节地址左填充至 32 字节返回失败时不得返回任何数据。伪代码定义def sigrecover_precompile(input: Bytes) - Bytes: assert len(input) 1 assert input[0] in algorithm_registry size algorithm_registry[input[0]].SIZE assert len(input) size signature input[:size] signing_data input[size:] charge_gas(calculate_penalty(input[0], signing_data)) # Run validate/verify function validate_signature(signature) pubkey verify_signature(signing_data, signature) # Return address left-padded to 32 bytes return (b\x00 * 12) pubkey_to_address(pubkey, input[0])输入/输出格式输入编码algorithm_type_byte || signature(padded to SIZE) || signing_data即首字节是算法类型紧随其后是固定SIZE字节的签名必须填充到该大小剩余部分全部作为签名数据signing_data。输出32 字节即0x00 × 12 signer_address(20 bytes)。参考实现与 gas 明细仓库中的参考实现位于 assets/eip-7932/precompile.py为便于测试被改造为同时返回 gasINVALID b SIGRECOVER_BASE_GAS 3000 def sigrecover_precompile(input: bytes) - tuple[bytes, int]: gas SIGRECOVER_BASE_GAS try: assert len(input) 1 assert input[0] in registry.algorithm_registry size registry.algorithm_registry[input[0]].SIZE assert len(input) size signature input[:size] signing_data input[size:] gas helpers.calculate_penalty(input[0], signing_data) # Run validate/verify function helpers.validate_signature(signature) pubkey helpers.verify_signature(signing_data, signature) # Return address left-padded to 32 bytes and gas return ((b\x00 * 12) helpers.pubkey_to_address(pubkey, input[0]), gas) except AssertionError as _: return (INVALID, gas)总 gas 3000基础静态 gas calculate_penalty 返回的动态部分。对于 secp256k1当签名数据为 32 字节时总 gas 为 3000当签名数据非 32 字节时总 gas 为3000 30 6 * ceil(len/32)。所有失败路径断言失败均返回空数据b体现了规范“失败时不返回任何数据”的要求。测试用例验证预编译行为的金标准测试文件 assets/eip-7932/precompile_test_cases.py 以input data - (output, gas_charged)的形式覆盖了关键场景| 输入 | 期望输出 | 期望 gas | | - | - | - | |b无数据 |INVALID空 | 3000 | |b\x7F保留的无效算法 |INVALID| 3000 | |b\x7F 65×0x01 32×0x00无效算法 数据 |INVALID| 3000 | |b\x00secp256k1 无数据 |INVALID| 3000 | |b\x00 64×0xFE 32×0x00数据过短 |INVALID| 3036 | |b\x00 67×0xFE 32×0x00签名超长 |INVALID| 3042 | |b\x00 65×0xFE 32×0x00无效签名 |INVALID| 3000 | |b\x00 zero_sig 32×0x00有效签名 |signer_address| 3000 | |b\x00 65×0xFE 30×0x00非 32 字节数据 无效签名 |INVALID| 3036 | |b\x00 deadbeef_sig deadbeef非 32 字节数据 有效签名 |signer_address| 3036 |测试使用固定私钥1f7627096fa44f0b850f5d9a859d271723ee856e526b947d0d4b011168bdcac1对应的期望地址为d3eF791e8a9c9BD26787D262e66e673FE8E7262A。从测试数据可以精确验证无效算法0x7F在任何输入下都失败并只收 3000 基础 gas数据长度为 30 字节时secp256k1 的 penalty 为30 6 * 1 36总 gas 为 3036数据长度为 32 字节时 penalty 为 0总 gas 恒为 3000非 32 字节的签名数据如deadbeef会被先 keccak 哈希再验签但 gas 仍按原始数据长度计费。该测试文件可以独立运行python precompile_test_cases.py输出Test cases pass表示全部断言通过是理解预编译行为最直接的入口。如何新增一种算法模板 EIP 实战新算法必须通过独立 EIP 引入模板见 assets/eip-7932/template-eip.md.txt。模板以“Generic algorithm”为例展示了必须填写的 5 个要素ALG_TYPE 0xFA SIZE 128 def gas_cost(signing_data: Bytes) - Uint64: return Uint64(128 len(signing_data)) def validate(signature: Bytes) - None | Error: # ... # Simple cryptography here # ... return None def verify(signature: Bytes, signing_data: Bytes) - Bytes | Error: # ... # Complicated cryptography here # ... return public_key def merge_detached_signature(detached_signature: bytes, public_key: bytes) - bytes: # ... # Either concatenation or a no-op here # ... return detached_signature public_key模板 EIP 头部需要声明requires: 7932见 assets/eip-7932/template-eip.md.txt并在 Specification 中声明“This EIP defines a new EIP-7932 algorithmic type”。仓库中已有两个真实示例可对照学习EIP-8030P256 算法支持——为 P256secp256r1定义ALG_TYPE 0x01、SIZE 129其 gas 模型以 EIP-7951 预编译成本减去 3000secp256k1 基准为起点是“gas 相对 secp256k1”要求的直接体现EIP-8051ML-DSA 预编译——引入后量子 ML-DSA 验签预编译地址0x12/0x13并声明requires: 7932是后量子算法接入本框架的代表案例。设计权衡Rationale与 ERC-4337 的互操作EIP-7932 的早期草案曾与 ERC-4337 存在竞争关系但当前版本通过 sigrecover 预编译对 ERC-4337 提供支持任何 ERC-4337 实现都可以为给定私钥复用同一套签名验证逻辑与地址推导逻辑且与具体推导地址的算法无关。为何用预编译而非原生 EVM 代码使用预编译可以让非 EVM 进程例如交易层的签名验证在不进入 EVM 的情况下直接访问注册表从而在协议更底层的地方完成签名校验。不透明Opaque的signature类型每种算法都有独特属性如是否支持签名恢复、密钥大小等因此签名对象需要容纳每种签名及潜在恢复信息的全部排列组合。一个由算法定义大小的字节数组bytearray即可实现这一目标——这正是SIZE字段存在的意义。向后兼容性EIP-7932不修改任何现有逻辑不引入任何向后兼容问题secp256k1 的地址推导保持原样0x00兼容分支预编译0x12为新增地址注册表只增不改。安全注意事项允许单一账户通过更多方式推导地址可能在整体上降低该账户的安全性不过攻击者需要穷举所有算法所需的处理能力提升在一定程度上缓解了该风险。即便如此添加新算法仍需充分讨论确保网络安全性不受损害。正因如此规范强制要求每种新算法必须附带安全分析、gas 基准测试并处理签名可延展性问题——这些要求也正是后量子算法大规模落地前必须跨越的门槛。延伸阅读规范原文EIPS/eip-7932.md参考实现注册表与 secp256k1assets/eip-7932/algorithm_registry/registry.py地址推导与辅助函数assets/eip-7932/algorithm_registry/helpers.py预编译实现assets/eip-7932/precompile.py测试用例assets/eip-7932/precompile_test_cases.py新算法接入模板assets/eip-7932/template-eip.md.txt依赖与配套提案EIP-8030P256 算法类型、EIP-8051ML-DSA 预编译【免费下载链接】EIPsThe Ethereum Improvement Proposal repository项目地址: https://gitcode.com/GitHub_Trending/ei/EIPs创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考