以太坊预编译合约

本文详细介绍了以太坊预编译合约的九种类型及其应用场景,包括椭圆曲线数字签名恢复、哈希方法、内存复制和椭圆曲线数学运算等,并提供了如何使用Solidity调用这些预编译合约的示例代码。

Ethereum 预编译的行为类似于内置于以太坊协议中的智能合约。这九个预编译分布在地址 0x01 到 0x09。

预编译的用途分为四类: – 椭圆曲线数字签名恢复 – 与比特币和 Zcash 交互的哈希方法 – 内存复制 – 使椭圆曲线数学用于零知识证明的方法

这些操作被认为足够重要,因此有了高效的气体机制来执行它们。在 Solidity 中实现这些算法的气体效率将大大降低。

预编译不会在智能合约内部执行,它们是以太坊客户端规范的一部分。你可以在 Geth 客户端 中查看它们的列表。由于它们是协议规范,因此在 以太坊黄皮书(附录 E)中列出。

使用 Solidity 调用预编译智能合约

大多数预编译没有 Solidity 包装(唯一的例外是 ecRecover)。你需要直接调用地址,例如使用 addressOfPrecompile.staticcall(…) 或使用汇编。

虽然没有任何预编译合约会改变状态,但调用它们的 Solidity 函数不能是 pure,因为 Solidity 编译器无法推断出 staticcall 不会改变状态。

地址 0x01: ecRecover

ECRecover 是用于从哈希及其数字签名恢复地址的预编译,例如,确定签名是否有效。(在我们的教程中了解如何使用 solidity 数字签名)。

示例:

function recoverSignature(bytes32 hash, uint8 v, bytes32 r, bytes32 s) public view returns (address) {
    address r = ecrecover(hash, v, r, s);
    require(r != address(0), "签名无效");
}

注意:当签名未能验证哈希时,ecrecover 不会回退。它返回零地址。你应该始终明确检查这一点,或者更好地使用处理此问题的 Openzeppelin 库。如果你不知道自己在做什么,签名可能会出错!

地址 0x02 和 0x03: SHA-256 和 RIPEMD-160

这两个预编译将对提供的字节进行哈希。这里是使用 SHA256 的示例。为了简单起见,我们将对 uint256 进行哈希:

function hashSha256(uint256 numberToHash) public view returns (bytes32 h) {
    (bool ok, bytes memory out) = address(2).staticcall(abi.encode(numberToHash));
    require(ok);
    h = abi.decode(out, (bytes32));
}

以下是使用 RIPEMD-160 的示例:

function hashRIPEMD160(bytes calldata data) public view returns (bytes20 h) {
    (bool ok, bytes memory out) = address(3).staticcall(data);
    require(ok);
    h = bytes20(abi.decode(out, (bytes32)) << 96);
}

虽然 RIPEMD-160 返回 20 字节,但 EVM 只能按 32 字节增量工作,因此在上述示例代码中使用了位移和类型转换。

以太坊为什么支持 SHA-256 和 RIPEMD-160?比特币大量使用 SHA256,就像以太坊大量使用 keccak256 一样。然而,比特币地址使用 RIPEMD-160 哈希公钥,以使公钥地址更紧凑。这与以太坊获取 keccak256 的最后 20 字节(160 位,类似于 RIPEMD)公钥相当。

使用 Yul 汇编

由于返回大小是提前已知的,所以无需使用 returndatasize 操作码。在 yul(和操作码)中,staticcall 有六个参数: – args – 要转发的气体 – 在内存中查找要哈希的数据的位置 – 要哈希的数据大小(32 字节) – 要写入输出的位置 – 输出的大小

在下面的代码中,我们将 uint256 写入内存,然后将其传递给地址 2 进行哈希。

function hashSha256Yul(uint256 numberToHash) public view returns (bytes32) {
    assembly {
        mstore(0, numberToHash) // 将数字存储在第零个内存字中

        let ok := staticcall(gas(), 2, 0, 32, 0, 32)
        if iszero(ok) {
            revert(0, 0)
        }
        return(0, 32)
    }
}

地址 0x04: Identity

Identity 预编译将一个内存区域复制到另一个区域。以太坊没有 "memcopy" 操作码(用于将一个内存区域复制到另一个)。通常,你必须首先 MLOAD 一字内存到堆栈,然后 MSTORE 复制它,这样你必须逐字复制。使用 Identity 预编译,你可以一次复制一组连续的 32 字节。

地址 0x05: Modexp

ECDSA 不支持公共加密。如果应用程序对此有用例,则必须使用传统的 RSA 加密。从高层次来看,RSA 通过将消息提升到接收者公钥的幂,以某个非常大的数字进行取模运算来工作。生成的数字是加密后的消息。由于这大大限制了消息的长度,典型的消息交换是通过加密对称密钥(如 AES-256)并将其发送给接收方。然后接收方可以使用 AES-256 密钥来解密消息。

使用 RSA 签名消息的过程是相反的。发送方将消息的哈希提升到其私钥的幂,对一个大数字取模(该数字是公开已知的)。结果是消息的签名。接收者可以通过将签名的幂提升到公钥并取模大数字来验证签名,以查看结果是否为消息哈希。

以太坊没有 RSA 的公钥基础设施。但是,以太坊地址可以通过 RSA 签名其以太坊地址来证明拥有 RSA 公钥。请注意,这不能反向操作。用 ECDSA 签名 RSA 公钥是不安全的,因为任何人都可以对包含 RSA 公钥的任意字符串进行 ECDSA 签名。

你可以在我们的另一篇文章中查看有关 Solidity 中 RSA 的应用

以下是使用 Solidity 中的 modExp 的示例:

function modExp(uint256 base, uint256 exp, uint256 mod) public view returns (uint256) {
    bytes memory precompileData = abi.encode(32, 32, 32, base, exp, mod);
    (bool ok, bytes memory data) = address(5).staticcall(precompileData);
    require(ok, "expMod 失败");
    return abi.decode(data, (uint256));
}

地址 0x06 和 0x07 和 0x08: ecAdd, ecMul 和 ecPairing (EIP-196 和 EIP-197)

这些预编译用于使 零知识证明加密 更高效。实际上,你可以看到这三项预编译在 Tornado Cash 零知识证明验证器中被使用:

椭圆曲线相加: staticcall to address(6)
椭圆曲线相乘: staticcall to address(7)
椭圆曲线配对: static call to address(8)

这些操作仅支持 BN-128 Barreto-Naehrig 椭圆曲线。这些曲线与用于数字签名的椭圆曲线不同。

Ecadd 和 ecMul 在 EIP-196 中添加,ecPairing 在 EIP-197 中添加。

你可以在我们的其他教程中了解这些预编译的工作原理:

有限域中的椭圆曲线
双线性配对

ecAdd, ecMul 和 ecPairing 的Gas成本

这些预编译的Gas成本在引入 EIP-1108 时比原始规范降低。用户应参考该 EIP 获取有关其Gas成本的最新信息,而不是各自 EIP 规范。

地址 0x09: Blake2 (EIP-152)

Blake2 哈希是 zcash 的首选哈希。与 SHA256 和 RIPEMD-160 类似,Blake2 的添加使以太坊能够验证该区块链上交易的声明。此预编译是在 EIP-152 中添加的,并在该提案中提供了一些示例代码。

地址 0xa: 点评估预编译 (EIP-4844)

Decun 硬分叉在地址 10(地址 0xa)上添加了一个预编译 预编译在地址 10(地址 0xa),用于验证 KZG 承诺。也就是说,给定一个 blob 承诺和一个零知识证明,如果证明无效,则该预编译回退。

其他链上的预编译

智能合约开发人员在将 Solidity 代码复制到其他 EVM 兼容链时应该小心,因为这些链上的预编译可能与以太坊的不同。例如,自 ZKSync 上不支持 ecrecover 和其他加密预编译。(其技术原因是,大多数加密算法并不符合 SNARK,且从零知识证明的角度来看,它们的验证成本很高)。

了解更多

此材料是我们 Solidity 训练营的一部分。你还可以通过我们的免费 Solidity 课程 免费学习 Solidity。

最初发布于 2023 年 4 月 16 日

  • 原文链接: rareskills.io/post/solid...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
RareSkills
RareSkills
https://www.rareskills.io/