fhevm 智能合约库核心特性全解析:从加密类型到 ACL 访问控制
【免费下载链接】fhevmFHEVM, a full-stack framework for integrating Fully Homomorphic Encryption (FHE) with blockchain applications项目地址: https://gitcode.com/GitHub_Trending/fh/fhevm
本文系统梳理 fhevm 智能合约库的五大核心特性:环境配置与初始化、加密数据类型、类型转换、机密计算(同态运算)与基于区块链的访问控制机制(ACL)。读者将掌握如何在 Solidity 合约中引入 FHE(全同态加密)能力,理解ebool、euintX、eaddress等加密句柄类型的使用规范,并能结合源码理解底层实现,最终编写出可正确运行、权限安全的隐私智能合约。
概述:fhevm 为 Solidity 合约带来什么
fhevm(Fully Homomorphic Encryption Virtual Machine)是一个将全同态加密与区块链应用相结合的全栈框架。在本仓库中,面向 Solidity 开发者的核心能力由 FHE.sol 这一库提供:它允许智能合约在链上直接对密文进行计算,而无需在任何环节解密明文——这就是所谓的"机密计算"(confidential computation)。
本文所依据的官方文档位于 docs/solidity-guides/getting-started/overview.md,它提纲挈领地总结了 FHEVM 智能合约库的关键特性。下面我们逐一展开,并结合仓库源码进行验证与深化。
配置与初始化:让合约接入 FHEVM 基础设施
任何使用加密计算的合约都必须完成三件事:导入并继承环境特定的配置合约、配置安全的 relayer(中继器)访问、在使用前校验加密变量已正确初始化。
环境配置:继承ZamaEthereumConfig
fhevm 官方文档(Configuration)指出:合约需要导入并继承环境特定的配置合约,由它自动完成 FHE 库与网络特定设置的接线。配置的核心是 ZamaConfig.sol,它以CoprocessorConfig结构体封装了 Zama FHEVM 基础设施在各类网络上的合约地址——包括ACL、FHEVMExecutor、KMSVerifier。注意:InputVerifier并不在继承的配置中,而是在运行时通过FHEVMExecutor.getInputVerifierAddress()动态解析。
从源码看,ZamaConfig.getCoprocessorConfig()依据block.chainid路由配置(见 ZamaConfig.sol),目前支持:
| chainId | 网络 |
|---|---|
| 1 | Ethereum 主网 |
| 137 | Polygon |
| 11155111 | Sepolia 测试网 |
| 80002 | Polygon Amoy |
| 31337 | 本地 Hardhat / Anvil |
在其他链上调用会回退(revert)ZamaProtocolUnsupported。此外还提供了getEthereumCoprocessorConfig()与getPolygonCoprocessorConfig()两个按生态路由的变体(ZamaConfig.sol)。
典型用法是让合约继承ZamaEthereumConfig,其构造函数会自动调用FHE.setCoprocessor(对应实现位于 FHE.sol)完成接线,抽象掉手工管理地址的负担:
// SPDX-License-Identifier: BSD-3-Clause-Clear pragma solidity ^0.8.24; import { ZamaEthereumConfig } from "@fhevm/solidity/config/ZamaConfig.sol"; contract MyERC20 is ZamaEthereumConfig { constructor() { // Additional initialization logic if needed } }Relayer 配置
加密操作(如用户解密、公钥获取等)需要依赖 off-chain 的 relayer 组件代为执行。文档要求为密码学操作配置安全的 relayer 访问。仓库中 relayer 的实现位于 relayer/src,它承载了输入证明、解密流程等网关交互;对于合约开发者而言,需要确保部署环境中 relayer 指向正确的 FHEVM 网关,且与链上CoprocessorConfig中登记的地址一致。
初始化检查:FHE.isInitialized
加密变量在未初始化时参与计算会产生未定义行为,因此 fhevm 提供isInitialized(T v) internal pure returns (bool)工具函数:
require(FHE.isInitialized(counter), "Counter not initialized!");该检查在合约执行入口处调用,能有效防止因未初始化密文句柄导致的逻辑错误。
加密数据类型:在 Solidity 中表示密文
fhevm 引入了一组与 Solidity 原生类型语义兼容的加密类型,它们本质上是**密文句柄(ciphertext handles)**的安全包装器,前缀e表示 encrypted。官方文档 Supported types 给出了完整清单:
| 类型 | 位宽 | 支持的算子 |
|---|---|---|
ebool | 2 | and, or, xor, eq, ne, not, select, rand |
euint8 | 8 | add, sub, mul, div, rem, and, or, xor, shl, shr, rotl, rotr, eq, ne, ge, gt, le, lt, min, max, neg, not, select, rand, randBounded |
euint16 | 16 | 同上 |
euint32 | 32 | 同上 |
euint64 | 64 | 同上 |
euint128 | 128 | 同上 |
euint160 | 160 | eq, ne, select(eaddress是它的别名,用于加密的以太坊地址) |
euint256 | 256 | and, or, xor, shl, shr, rotl, rotr, eq, ne, neg, not, select, rand, randBounded |
关键设计决策与注意点:
- 加密整数运算默认不检查溢出(unchecked):溢出时自动回绕。这是为了避免通过错误检测泄露信息——如果密文运算报错,攻击者可能推断出操作数的范围。文档同时预告:未来版本将提供带溢出检查的加密整数,代价是可能暴露操作数的部分信息。
div/rem仅支持明文除数(plaintext rhs):当右侧操作数是加密值时会导致 panic。这一限制保证了当前框架内计算的安全与正确。eaddress即euint160:FHE.asEaddress(address)可将明文地址转为加密地址。- 文档提到,更高精度的整数类型在
TFHE-rs库中已有,可按需引入。
另外,合约还通过externalEbool、externalEaddress、externalEuintXX这类external*输入类型接收来自链外的加密输入数据(配合输入证明inputProof使用),详情见 Encrypted inputs。
类型转换(Casting):在加密类型与明文之间穿梭
fhevm 提供三类转换函数(详见 types.md 与 operations/casting.md):
- 加密类型之间的转换:
FHE.asEbool将加密整数转换为加密布尔值。在 FHE.sol 中,asEbool对euint8至euint256均有重载实现。 - 明文转加密类型:
FHE.asEuintX(plaintext)将明文数值转换为对应位宽的加密类型,其中asEbool(bool)对应明文布尔(FHE.sol)。 - 明文地址转加密地址:
FHE.asEaddress(address)将 Solidity 地址转为eaddress(FHE.sol)。
// 明文 → 加密 euint8 age = FHE.asEuint8(25); eaddress encryptedOwner = FHE.asEaddress(msg.sender); // 加密整数 → 加密布尔(例如判断是否为 0) ebool isZero = FHE.asEbool(age);从源码结构看,FHE库对这些函数针对每种加密类型提供了完整的重载族,便于在编译期进行类型校验,杜绝把euint8误当euint256使用的类型错误。
机密计算:在密文上直接执行运算
fhevm 支持对加密操作进行符号化执行,官方文档 Operations on encrypted types 按类别给出了完整算子集合:
算术运算(euintX):
| 函数名 | 符号 | 说明 |
|---|---|---|
FHE.add | + | 二元加法 |
FHE.sub | - | 二元减法 |
FHE.mul | * | 二元乘法 |
FHE.div | — | 除法,仅限明文除数 |
FHE.rem | — | 取余,仅限明文除数 |
FHE.neg | - | 一元取负 |
FHE.min/FHE.max | — | 取最小 / 最大值 |
位运算:
| 函数名 | 符号 | 说明 |
|---|---|---|
FHE.and/FHE.or/FHE.xor | &|^ | 按位与 / 或 / 异或 |
FHE.not | ~ | 按位取反(一元) |
FHE.shl/FHE.shr | — | 左移 / 右移 |
FHE.rotl/FHE.rotr | — | 循环左移 / 循环右移 |
一个值得注意的细节:移位算子的第二个操作数可以是uint8或euint8,但它总是按第一个操作数的位宽取模。例如FHE.shr(euint64 x, 70)等价于FHE.shr(euint64 x, 6)(因为70 % 64 = 6)。这与 Solidity 原生移位不同——原生的uint64 >> 70会直接得到 0。
比较运算:FHE.eq、FHE.ne、FHE.ge、FHE.gt、FHE.le、FHE.lt,结果均为ebool。
高级算子:
FHE.select(condition, valueIfTrue, valueIfFalse):基于加密条件的三元选择,用于在密文上进行分支(详见下文与 Conditional logic in FHE)。FHE.randEuintX()/FHE.randEbool():链上生成密码学安全的加密随机数(详见 Generate Random Encrypted Numbers)。
使用FHE.select进行机密分支
在 fhevm 中,比较运算的结果是ebool,而加密布尔不能直接用于 Solidity 的if语句。条件逻辑必须通过FHE.select实现——它相当于加密版的三元运算符:
FHE.select(condition, valueIfTrue, valueIfFalse);其中condition是由比较产生的ebool,valueIfTrue/valueIfFalse是待选择的加密值。一个典型的拍卖出价示例:
function bid(externalEuint64 encryptedValue, bytes calldata inputProof) external onlyBeforeEnd { // 将加密输入转换为加密 64 位整数 euint64 bid = FHE.fromExternal(encryptedValue, inputProof); // 比较当前最高出价与新出价 ebool isAbove = FHE.lt(highestBid, bid); // 若新出价更高则更新最高出价 highestBid = FHE.select(isAbove, bid, highestBid); // 重新授权合约使用更新后的密文 FHE.allowThis(highestBid); }注意两个关键点:其一,每次FHE.select赋值都会生成新密文,即使明文值未变化——这是 FHE 保证机密性的固有行为,设计合约时应纳入考量;其二,每次赋值后都应调用FHE.allowThis重新授权,否则合约后续无法继续使用新句柄。
链上加密随机数
FHE.randEbool()/FHE.randEuintX()系列提供链上随机数生成(random.md):
ebool rb = FHE.randEbool(); // 随机加密布尔 euint8 r8 = FHE.randEuint8(); // 随机 8 位加密数 euint32 r32 = FHE.randEuint32(); // 随机 32 位加密数 euint64 r64 = FHE.randEuint64(); // 随机 64 位加密数也支持带上界的版本(上界必须是 2 的幂,结果落在[0, upperBound - 1]):
euint8 r8 = FHE.randEuint8(32); // 0~31 euint16 r16 = FHE.randEuint16(512); // 0~511 euint32 r32 = FHE.randEuint32(65536); // 0~65535重要限制:随机数生成必须在交易执行期间调用,因为它需要链上更新伪随机数生成器(PRNG)的状态,无法通过eth_callRPC 方式离线执行。生成的随机数由密码学安全伪随机数生成器(CSPRNG)产生并保持加密,在显式解密前任何第三方都无法预测或访问。
运算最佳实践
官方文档给出三条可直接落地的优化原则:
- 选用最小的合适类型:密文位宽越大,gas 成本越高。
euint8足够时不要用euint256:
// 不推荐:为小数值浪费 gas euint64 age = FHE.asEuint128(25); // 推荐:类型精确匹配数据范围 euint8 age = FHE.asEuint8(25);尽量使用标量操作数版本:多数算子存在"全密文"与"一密文一标量"两种重载。
FHE.add(x, 42)比FHE.add(x, FHE.asEuint(42))省 gas 得多,二者计算结果完全相同。警惕算术溢出:加密算术默认回绕。以加密 ERC20 的
mint为例,若不处理溢出则存在被操纵的风险,可用FHE.select在溢出时取消铸造:
function mint(externalEuint32 encryptedAmount, bytes calldata inputProof) public { euint32 mintedAmount = FHE.fromExternal(encryptedAmount, inputProof); euint32 tempTotalSupply = FHE.add(totalSupply, mintedAmount); ebool isOverflow = FHE.lt(tempTotalSupply, totalSupply); totalSupply = FHE.select(isOverflow, totalSupply, tempTotalSupply); euint32 tempBalanceOf = FHE.add(balances[msg.sender], mintedAmount); balances[msg.sender] = FHE.select(isOverflow, balances[msg.sender], tempBalanceOf); FHE.allowThis(balances[msg.sender]); FHE.allow(balances[msg.sender], msg.sender); }这里只需检测totalSupply的溢出即可,因为它是所有用户余额之和,只要它不溢出,单账户余额必然不溢出。
访问控制机制:基于区块链的 ACL
FHE 密文本身完全保密——即使是持有该密文的合约,未经授权也无法操作它。因此 fhevm 通过基于区块链的访问控制列表(ACL)来管理"谁能对哪个密文做什么",完整机制见 Access Control List。
授权类型:持久、瞬时与全局公开
| 授权方式 | 函数 | 存储 | 适用场景 |
|---|---|---|---|
| 持久授权 | FHE.allow(ciphertext, address) | 专用合约存储(持久) | 跨交易长期访问 |
| 瞬时授权 | FHE.allowTransient(ciphertext, address) | 瞬态存储(EIP-1153) | 单笔交易内临时访问,省 gas |
| 永久公开解密 | FHE.makePubliclyDecryptable(ciphertext) | 专用合约存储(持久) | 任何人都可离线解密该密文 |
语法糖:FHE.allowThis(ciphertext)等价于FHE.allow(ciphertext, address(this)),用于授权当前合约在后续交易中复用某密文句柄——在前面的bid、mint示例中,更新状态变量后都必须调用它。这些函数在 FHE.sol 中为每种加密类型均提供了重载。
权限校验
FHE.isAllowed(ciphertext, address):查询某地址是否有权限。FHE.isSenderAllowed(ciphertext):校验当前交易发送者是否有权,是常见的简化写法(FHE.sol)。FHE.isPubliclyDecryptable(ciphertext):校验密文是否已被永久公开解密。FHE.checkSignatures(cts, cleartexts, proof):校验提交回链上的明文确实来自对该密文句柄的合法公开解密操作,防止伪造解密结果。FHE.isAccountDenied(account):检查某账户是否被列入黑名单(deny list)。被拒绝的账户无法在 ACL 内执行allow*类调用,既不能授予也不能接收新的密文权限(FHE.sol)。
用户解密委托(User Decryption Delegation)
ACL 支持将用户解密权从一个账户委托给另一个账户(例如后端服务或 relayer),存在两种不可互换的模式:
- 合约作为委托方:合约内调用
FHE.delegateUserDecryption(delegate, contractAddress, expirationDate),此时委托方是address(this)(FHE.sol)。 - EOA 作为委托方:账户直接对 ACL 合约调用
IACL.delegateForUserDecryption。
配套函数还包括delegateUserDecryptionWithoutExpiration(无过期时间)、revokeUserDecryptionDelegation(撤销委托)与isUserDecryptable(查询句柄是否可在某合约上下文内被用户解密)。两种模式的完整约束与实例见 User decryption delegation,API 参考见 FHEVM API reference。
ACL 的典型应用
- 机密参数传递:在合约之间安全传递加密值,仅授权方可访问。
- 安全状态管理:存储加密状态变量,同时严格控制修改与读取权限。
- 隐私保护计算:确保计算仅在授权上下文内执行。
- 公开可验证的结果揭示:例如密封拍卖结束后公开最终价格,任何人都能验证。
从加密分支回到公开逻辑:异步解密路径
一个经常被忽略的关键约束(conditions.md):从加密路径分支到非加密(公开)业务逻辑,只有一种方式——离线的公开解密。因此任何"根据加密输入决定公开行为"的合约逻辑必然是异步的。典型流程是:合约将highestBidder设为makePubliclyDecryptable,off-chain 执行解密后,将明文结果与解密证明一起提交回链,由FHE.checkSignatures校验后继续公开业务逻辑(如发放奖品)。
小结
fhevm 智能合约库的核心特性可以概括为一条清晰的开发路径:
- 配置初始化——继承
ZamaEthereumConfig(底层是 ZamaConfig.sol 按 chainId 路由的CoprocessorConfig),配合 relayer 配置与FHE.isInitialized检查,完成环境就绪; - 选择加密类型——在
ebool、euint8~euint256、eaddress与externalE*输入类型中按数据范围选取最小合适的类型; - 执行机密计算——通过
FHE库的算术、位运算、比较、select与randEuintX在密文上直接运算,并遵循"标量优先、类型最小、防范溢出"的 gas 与安全最佳实践; - 管理访问权限——利用 ACL 的持久/瞬时/公开三种授权、
isSenderAllowed等校验函数与用户解密委托,确保密文始终只在授权范围内流动; - 设计异步公开出口——需要公开结果时,通过公开解密 +
checkSignatures的异步流程衔接机密逻辑与公开业务逻辑。
每一步都有对应的官方文档章节(overview、configure、types、operations、ACL)与仓库源码(FHE.sol、ZamaConfig.sol)相互印证。掌握了这条主线,你就可以开始在 Hardhat 快速上手教程 中动手编写第一个 FHEVM 隐私合约了。
【免费下载链接】fhevmFHEVM, a full-stack framework for integrating Fully Homomorphic Encryption (FHE) with blockchain applications项目地址: https://gitcode.com/GitHub_Trending/fh/fhevm
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考