{"content":{"title":"以太坊预编译合约","body":"Ethereum 预编译的行为类似于内置于以太坊协议中的智能合约。这九个预编译分布在地址 0x01 到 0x09。\r\n\r\n预编译的用途分为四类：\r\n– 椭圆曲线数字签名恢复\r\n– 与比特币和 Zcash 交互的哈希方法\r\n– 内存复制\r\n– 使椭圆曲线数学用于零知识证明的方法\r\n\r\n这些操作被认为足够重要，因此有了高效的气体机制来执行它们。在 Solidity 中实现这些算法的气体效率将大大降低。\r\n\r\n预编译不会在智能合约内部执行，它们是以太坊客户端规范的一部分。你可以在 [Geth 客户端](https://github.com/ethereum/go-ethereum/blob/master/core/vm/contracts.go#L81) 中查看它们的列表。由于它们是协议规范，因此在 [以太坊黄皮书](https://ethereum.github.io/yellowpaper/paper.pdf)（附录 E）中列出。\r\n\r\n## 使用 Solidity 调用预编译智能合约\r\n\r\n大多数预编译没有 Solidity 包装（唯一的例外是 ecRecover）。你需要直接调用地址，例如使用 addressOfPrecompile.staticcall(…) 或使用汇编。\r\n\r\n虽然没有任何预编译合约会改变状态，但调用它们的 Solidity 函数不能是 pure，因为 Solidity 编译器无法推断出 [staticcall](https://learnblockchain.cn/article/11233) 不会改变状态。\r\n\r\n## 地址 0x01: ecRecover\r\n\r\nECRecover 是用于从哈希及其数字签名恢复地址的预编译，例如，确定签名是否有效。（在我们的教程中了解如何使用 [solidity 数字签名](https://www.rareskills.io/post/openzeppelin-verify-signature)）。\r\n\r\n示例：\r\n```solidity\r\nfunction recoverSignature(bytes32 hash, uint8 v, bytes32 r, bytes32 s) public view returns (address) {\r\n    address r = ecrecover(hash, v, r, s);\r\n    require(r != address(0), \"签名无效\");\r\n}\r\n```\r\n\r\n**注意**：当签名未能验证哈希时，ecrecover 不会回退。它返回零地址。你应该始终明确检查这一点，或者更好地使用处理此问题的 Openzeppelin 库。如果你不知道自己在做什么，签名可能会出错！\r\n\r\n## 地址 0x02 和 0x03: SHA-256 和 RIPEMD-160\r\n\r\n这两个预编译将对提供的字节进行哈希。这里是使用 SHA256 的示例。为了简单起见，我们将对 uint256 进行哈希：\r\n```solidity\r\nfunction hashSha256(uint256 numberToHash) public view returns (bytes32 h) {\r\n    (bool ok, bytes memory out) = address(2).staticcall(abi.encode(numberToHash));\r\n    require(ok);\r\n    h = abi.decode(out, (bytes32));\r\n}\r\n```\r\n\r\n以下是使用 RIPEMD-160 的示例：\r\n```solidity\r\nfunction hashRIPEMD160(bytes calldata data) public view returns (bytes20 h) {\r\n    (bool ok, bytes memory out) = address(3).staticcall(data);\r\n    require(ok);\r\n    h = bytes20(abi.decode(out, (bytes32)) << 96);\r\n}\r\n```\r\n\r\n虽然 RIPEMD-160 返回 20 字节，但 EVM 只能按 32 字节增量工作，因此在上述示例代码中使用了位移和类型转换。\r\n\r\n以太坊为什么支持 SHA-256 和 RIPEMD-160？比特币大量使用 SHA256，就像以太坊大量使用 keccak256 一样。然而，比特币地址使用 RIPEMD-160 哈希公钥，以使公钥地址更紧凑。这与以太坊获取 keccak256 的最后 20 字节（160 位，类似于 RIPEMD）公钥相当。\r\n\r\n## 使用 Yul 汇编\r\n\r\n由于返回大小是提前已知的，所以无需使用 returndatasize 操作码。在 yul（和操作码）中，staticcall 有六个参数：\r\n– args\r\n– 要转发的气体\r\n– 在内存中查找要哈希的数据的位置\r\n– 要哈希的数据大小（32 字节）\r\n– 要写入输出的位置\r\n– 输出的大小\r\n\r\n在下面的代码中，我们将 [uint256](https://www.rareskills.io/post/uint-max-value-solidity) 写入内存，然后将其传递给地址 2 进行哈希。\r\n```solidity\r\nfunction hashSha256Yul(uint256 numberToHash) public view returns (bytes32) {\r\n    assembly {\r\n        mstore(0, numberToHash) // 将数字存储在第零个内存字中\r\n\r\n        let ok := staticcall(gas(), 2, 0, 32, 0, 32)\r\n        if iszero(ok) {\r\n            revert(0, 0)\r\n        }\r\n        return(0, 32)\r\n    }\r\n}\r\n```\r\n\r\n## 地址 0x04: Identity\r\n\r\nIdentity 预编译将一个内存区域复制到另一个区域。以太坊没有 \"memcopy\" 操作码（用于将一个内存区域复制到另一个）。通常，你必须首先 MLOAD 一字内存到堆栈，然后 MSTORE 复制它，这样你必须逐字复制。使用 Identity 预编译，你可以一次复制一组连续的 32 字节。\r\n\r\n## 地址 0x05: Modexp\r\n\r\nECDSA 不支持公共加密。如果应用程序对此有用例，则必须使用传统的 RSA 加密。从高层次来看，RSA 通过将消息提升到接收者公钥的幂，以某个非常大的数字进行取模运算来工作。生成的数字是加密后的消息。由于这大大限制了消息的长度，典型的消息交换是通过加密对称密钥（如 AES-256）并将其发送给接收方。然后接收方可以使用 AES-256 密钥来解密消息。\r\n\r\n使用 RSA 签名消息的过程是相反的。发送方将消息的哈希提升到其私钥的幂，对一个大数字取模（该数字是公开已知的）。结果是消息的签名。接收者可以通过将签名的幂提升到公钥并取模大数字来验证签名，以查看结果是否为消息哈希。\r\n\r\n以太坊没有 RSA 的公钥基础设施。但是，以太坊地址可以通过 RSA 签名其以太坊地址来证明拥有 RSA 公钥。请注意，这不能反向操作。用 ECDSA 签名 RSA 公钥是不安全的，因为任何人都可以对包含 RSA 公钥的任意字符串进行 ECDSA 签名。\r\n\r\n你可以在我们的另一篇文章中查看有关 [Solidity 中 RSA 的应用](https://www.rareskills.io/post/solidity-rsa-signatures-for-aidrops-and-presales-beating-ecdsa-and-merkle-trees-in-gas-efficiency)。\r\n\r\n以下是使用 Solidity 中的 `modExp` 的示例：\r\n```solidity\r\nfunction modExp(uint256 base, uint256 exp, uint256 mod) public view returns (uint256) {\r\n    bytes memory precompileData = abi.encode(32, 32, 32, base, exp, mod);\r\n    (bool ok, bytes memory data) = address(5).staticcall(precompileData);\r\n    require(ok, \"expMod 失败\");\r\n    return abi.decode(data, (uint256));\r\n}\r\n```\r\n\r\n## 地址 0x06 和 0x07 和 0x08: ecAdd, ecMul 和 ecPairing (EIP-196 和 EIP-197)\r\n\r\n这些预编译用于使 [零知识证明加密](https://www.rareskills.io/zk-book) 更高效。实际上，你可以看到这三项预编译在 [Tornado Cash](https://www.rareskills.io/post/how-does-tornado-cash-work) 零知识证明验证器中被使用：\r\n\r\n椭圆曲线相加: [staticcall to address(6)](https://github.com/tornadocash/tornado-core/blob/master/contracts/Verifier.sol#L79)  \r\n椭圆曲线相乘: [staticcall to address(7)](https://github.com/tornadocash/tornado-core/blob/master/contracts/Verifier.sol#L100)  \r\n椭圆曲线配对: [static call to address(8)](https://github.com/tornadocash/tornado-core/blob/master/contracts/Verifier.sol#L143)\r\n\r\n这些操作仅支持 BN-128 Barreto-Naehrig 椭圆曲线。这些曲线与用于数字签名的椭圆曲线不同。\r\n\r\nEcadd 和 ecMul 在 [EIP-196](https://eips.ethereum.org/EIPS/eip-196) 中添加，ecPairing 在 [EIP-197](https://eips.ethereum.org/EIPS/eip-197) 中添加。\r\n\r\n你可以在我们的其他教程中了解这些预编译的工作原理：\r\n\r\n[有限域中的椭圆曲线](https://www.rareskills.io/post/elliptic-curves-finite-fields)  \r\n[双线性配对](https://www.rareskills.io/post/bilinear-pairing)\r\n\r\n### ecAdd, ecMul 和 ecPairing 的Gas成本\r\n\r\n这些预编译的Gas成本在引入 [EIP-1108](https://eips.ethereum.org/EIPS/eip-1108) 时比原始规范降低。用户应参考该 EIP 获取有关其Gas成本的最新信息，而不是各自 EIP 规范。\r\n\r\n## 地址 0x09: Blake2 (EIP-152)\r\n\r\nBlake2 哈希是 [zcash](https://z.cash/) 的首选哈希。与 SHA256 和 RIPEMD-160 类似，Blake2 的添加使以太坊能够验证该区块链上交易的声明。此预编译是在 [EIP-152](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-152.md) 中添加的，并在该提案中提供了一些示例代码。\r\n\r\n## 地址 0xa: 点评估预编译 (EIP-4844)\r\n\r\nDecun 硬分叉在地址 10（地址 0xa）上添加了一个预编译 [预编译在地址 10（地址 0xa）](https://eips.ethereum.org/EIPS/eip-4844#point-evaluation-precompile)，用于验证 KZG 承诺。也就是说，给定一个 blob 承诺和一个零知识证明，如果证明无效，则该预编译回退。\r\n\r\n## 其他链上的预编译\r\n\r\n智能合约开发人员在将 Solidity 代码复制到其他 EVM 兼容链时应该小心，因为这些链上的预编译可能与以太坊的不同。例如，自 ZKSync 上不支持 ecrecover 和其他加密预编译。（其技术原因是，大多数加密算法并不符合 SNARK，且从零知识证明的角度来看，它们的验证成本很高）。\r\n\r\n## 了解更多\r\n\r\n此材料是我们 [Solidity 训练营](https://learnblockchain.cn/openspace/1)的一部分。你还可以通过我们的免费 [Solidity 课程](https://learnblockchain.cn/column/1) 免费学习 Solidity。\r\n\r\n**最初发布于 2023 年 4 月 16 日**\r\n\r\n>- 原文链接： [rareskills.io/post/solid...](https://www.rareskills.io/post/solidity-precompiles)\r\n>- 登链社区 AI 助手，为大家转译优秀英文文章，如有翻译不通的地方，还请包涵～"},"author":{"user":"https://learnblockchain.cn/people/20722","address":null},"history":null,"timestamp":1740546599,"version":1}