fhEVM Foundry 测试核心 API 速查:FhevmTest 基座合约的加密、解密与证明辅助函数全解析
2026/9/13 0:15:00 网站建设 项目流程

fhEVM Foundry 测试核心 API 速查:FhevmTest 基座合约的加密、解密与证明辅助函数全解析

【免费下载链接】fhevmFHEVM, a full-stack framework for integrating Fully Homomorphic Encryption (FHE) with blockchain applications项目地址: https://gitcode.com/GitHub_Trending/fh/fhevm

本文是 fhEVM(Fully Homomorphic Encryption EVM)项目中 forge-fhevm 测试库FhevmTest基座合约的 API 速查指南。你将在编写基于 Foundry 的 fhEVM 合约测试时,用FhevmTest在本地测试 EVM 中重建FHEVMExecutorACLInputVerifierKMSVerifier等主机合约栈,并通过encrypt*/decrypt/publicDecrypt/userDecrypt等辅助函数完成"加密输入 → 链上执行 → 解密断言"的完整测试闭环。读完本文,你将掌握FhevmTest的全部公开 API、常量语义及其背后的源码级原理,可直接上手编写与生产路径一致的 fhEVM 合约测试。

本文为docs/solidity-guides/foundry/api.md的展开版,补齐了源码级实现证据与完整用法说明。关于项目初始化与测试编写流程,可结合阅读 Foundry 入门、Setup Foundry、编写 fhEVM 测试 与 Foundry 部署指南。

1. FhevmTest 是什么:Foundry 环境中的 fhEVM 主机合约栈

FhevmTest是 forge-fhevm 提供的、面向 fhEVM 机密智能合约的 Foundry 原生测试基座合约。它的核心设计目标,是让被测合约在 Foundry 测试 EVM 中运行与生产环境完全一致的链上代码路径——输入证明验证(EIP-712)、ACL 权限强制、handle(句柄)生命周期管理都按主网真实逻辑执行;唯一的例外是 FHE 协处理器计算本身被模拟,明文值在本地被追踪,因此你可以直接对解密结果做assertEq断言。

从仓库源码可以印证这套"测试即生产"的思路:本仓库的 host-contracts/fhevm-foundry/HostContractsDeployerTestUtils.sol 就是一个在 Foundry 内重建主机合约栈的测试装配器——它把ACLFHEVMExecutorKMSVerifierInputVerifierHCULimitPauserSetProtocolConfigKMSGeneration等通过deployCodeTo部署到各自规范地址(canonical address)上的空代理中,再执行带 initializer payload 的特权升级调用,从而让跨合约权限检查(ACLOwnable、slot 读取等)与链上行为完全一致。FhevmTest.setUp()在语义上做的正是同类事情,只是封装成了开箱即用的基座合约。

FhevmTest开箱提供的能力包括:

  • 覆盖所有 FHE 类型的加密辅助函数(encryptBoolencryptUint8encryptUint256encryptAddress);
  • 三种解密模式:底层decrypt()publicDecrypt()userDecrypt()
  • EIP-712 证明辅助函数(signUserDecryptbuildDecryptionProof);
  • 一个在setUp()中部署好全部基础设施的FhevmTest基座合约。

与主网唯一的偏差,是输入签名者和 KMS 签名者使用 mock 私钥,从而保证测试中 EIP-712 证明的确定性生成。

2. 导入 FhevmTest

在测试合约中通过 Soldeer 安装的forge-fhevm依赖路径导入:

import {FhevmTest} from "forge-fhevm/FhevmTest.sol";

导入后,测试合约继承FhevmTest,并在setUp()中调用super.setUp()完成主机合约的部署:

// SPDX-License-Identifier: MIT pragma solidity ^0.8.27; import {FhevmTest} from "forge-fhevm/FhevmTest.sol"; contract MyTest is FhevmTest { function setUp() public override { super.setUp(); // 在规范地址上部署全部 fhEVM 主机合约 // 然后实例化被测合约…… } }

需要说明的是,被测合约必须继承 Zama 配置(例如ZamaEthereumConfig),使FHE.*调用路由到setUp()部署的 fhEVM 主机合约,否则加密类型与执行器无法正确接线。

3. setUp() 部署的状态变量

setUp()完成后,FhevmTest会暴露以下状态变量,供测试直接使用:

变量类型角色
_executorFHEVMExecutor处理 FHE 操作,并发出驱动明文追踪的事件
_aclACL按 handle 进行访问控制(瞬态与持久化权限)
_inputVerifierInputVerifier验证 EIP-712 输入证明(1 个 mock 签名者)
_kmsVerifierKMSVerifier验证 EIP-712 解密证明(1 个 mock 签名者)
MOCK_INPUT_SIGNERaddressmock 输入签名者地址
MOCK_KMS_SIGNERaddressmock KMS 签名者地址

这些角色与生产部署一一对应:FHEVMExecutor是 FHE 运算的执行入口,ACL维护每个密文 handle 的访问权限,InputVerifier校验FHE.fromExternal的输入证明,KMSVerifier校验解密证明的 KMS 门限签名。对照本仓库的部署装配器 HostContractsDeployerTestUtils.sol,_deployFullHostStack会把ACLPauserSetFHEVMExecutorHCULimitProtocolConfigKMSGenerationKMSVerifierInputVerifier全部拉起,并通过require断言执行器与 ACL、HCU 的地址接线、KMS 门限与输入签名者门限的配置均正确——这正是setUp()背后"生产同构"的工程保证。

4. 加密辅助函数:为被测合约构造 (handle, proof)

每个加密辅助函数都有两种重载:

  • 两参数重载:隐式用户为address(this)(即测试合约自身);
  • 三参数重载:显式指定用户。
function encryptBool(bool value, address target) returns (externalEbool, bytes memory); function encryptBool(bool value, address user, address target) returns (externalEbool, bytes memory); function encryptUint8(uint8 value, address target) returns (externalEuint8, bytes memory); function encryptUint8(uint8 value, address user, address target) returns (externalEuint8, bytes memory); // 相同形态:encryptUint16, encryptUint32, encryptUint64, // encryptUint128, encryptUint256, encryptAddress

其中target是最终调用FHE.fromExternal的合约地址,user是证明绑定的用户。完整的支持矩阵(源自write_test.md的表格)如下:

函数值类型返回的句柄
encryptBoolboolexternalEbool
encryptUint8uint8externalEuint8
encryptUint16uint16externalEuint16
encryptUint32uint32externalEuint32
encryptUint64uint64externalEuint64
encryptUint128uint128externalEuint128
encryptUint256uint256externalEuint256
encryptAddressaddressexternalEaddress

典型用法是先加密得到(handle, proof)对,再以vm.prank模拟用户调用被测合约:

// 隐式用户(address(this)) (externalEuint64 amount, bytes memory proof) = encryptUint64(100, address(myContract)); // 显式用户 address alice = address(0xA11CE); (externalEuint64 amount, bytes memory proof) = encryptUint64(100, alice, address(myContract)); vm.prank(alice); myContract.deposit(amount, proof);

实现细节提示:每次调用encrypt*都会使内部 nonce 递增,因此对同一值加密两次会得到不同的 handle。这一设计保证了测试中密文句柄的不可预测性,与生产环境每个输入产生唯一句柄的语义一致。

5. 解密辅助函数:三种与生产流程对齐的解密模式

FhevmTest提供三种解密模式,分别对应生产环境中的不同解密路径,按被测合约的交互模式选用。

5.1decrypt(handle)—— 底层直查

不做 ACL 检查、不做证明校验,直接返回 handle 对应的明文uint256,最适合单元断言:

function decrypt(bytes32 handle) returns (uint256);

同时提供针对每种加密类型的强类型重载,返回值是匹配的 Solidity 原生类型:

function decrypt(ebool value) returns (bool); function decrypt(euint8 value) returns (uint8); function decrypt(euint16 value) returns (uint16); function decrypt(euint32 value) returns (uint32); function decrypt(euint64 value) returns (uint64); function decrypt(euint128 value) returns (uint128); function decrypt(euint256 value) returns (uint256); function decrypt(eaddress value) returns (address);

用法示例:

euint64 balance = myContract.balanceHandle(alice); assertEq(decrypt(balance), 100); bool a = decrypt(myEbool); uint8 b = decrypt(myEuint8); uint64 c = decrypt(myEuint64); address d = decrypt(myEaddress);

5.2publicDecrypt(handles)—— KMS 签名的公开解密

适用于被测合约通过FHE.checkSignatures()在链上验证解密证明的回调式流程。返回明文数组与 KMS 签名的证明:

function publicDecrypt(bytes32[] memory handles) returns (uint256[] memory cleartexts, bytes memory proof);

用法示例:

bytes32[] memory handles = new bytes32[](1); handles[0] = euint64.unwrap(balance); (uint256[] memory cleartexts, bytes memory proof) = publicDecrypt(handles); FHE.checkSignatures(handles, abi.encode(cleartexts), proof); assertEq(cleartexts[0], 100);

注意:若被测合约没有对该 handle 调用FHE.makePubliclyDecryptable()publicDecrypt()会以HandleNotAllowedForPublicDecryption回滚。

从源码看,FHE.checkSignatures所校验的正是 KMS 对PublicDecryptVerification(bytes32[] ctHandles,bytes decryptedResult,bytes extraData)类型哈希的 EIP-712 门限签名——该类型哈希与DECRYPTION_RESULT_TYPEHASH定义在 library-solidity/lib/FHE.sol 中,底层由KMSVerifier.verifyDecryptionEIP712KMSSignatures(见 FHE.sol)执行多签名验证与门限判定。

5.3userDecrypt(handle, user, contract, signature)—— 面向用户的完整流程

实现带持久化 ACL 检查与 EIP-712 签名验证的完整用户解密流程:

function userDecrypt( bytes32 handle, address userAddress, address contractAddress, bytes memory userSignature ) returns (uint256);

用法示例(配合signUserDecrypt生成用户签名):

uint256 constant ALICE_PK = 0xA11CE; address alice = vm.addr(ALICE_PK); // (先通过业务逻辑的 mint/transfer 等把 ACL 授予 alice) bytes memory sig = signUserDecrypt(ALICE_PK, address(myContract)); uint256 cleartext = userDecrypt( euint64.unwrap(myContract.balanceHandle(alice)), alice, address(myContract), sig ); assertEq(cleartext, 100);

userDecrypt可能抛出的错误及其原因:

错误原因
UserAddressEqualsContractAddressuserAddress == contractAddress
UserNotAuthorizedForDecrypt用户缺少持久化ACL 权限
ContractNotAuthorizedForDecrypt合约缺少持久化ACL 权限
InvalidUserDecryptSignature签名无法恢复出userAddress

关键点:ACL 权限是由被测合约在业务逻辑中授予的(例如代币mint时调用FHE.allow(balance, owner)),测试中无需手动授权。这保证了userDecrypt测试的是真实业务权限流,而不是绕过 ACL 的桩逻辑。

6. 证明辅助函数:构建 EIP-712 签名与解密证明

6.1buildDecryptionProof—— 构建 KMS 签名的解密证明

用于回调式(callback-style)流程,不做 ACL 检查:

// 批量版本:一次构建多个 handle 的 KMS 签名解密证明 function buildDecryptionProof(bytes32[] memory handles, bytes memory abiEncodedCleartexts) view returns (bytes memory proof); // 单 handle 版本 function buildDecryptionProof(bytes32 handle, bytes memory abiEncodedCleartext) view returns (bytes memory proof);

该证明由 mock KMS 签名者按MOCK_KMS_SIGNER_PK生成,可在测试中直接作为FHE.checkSignatures的第三个参数,从而在不依赖真实 KMS 网络的前提下验证被测合约的链上解密校验逻辑。

6.2signUserDecrypt—— 生成 EIP-712 用户解密签名

模拟用户端为指定合约地址签署的解密授权:

// 简单版本:单个合约地址,使用默认有效期 function signUserDecrypt(uint256 userPk, address contractAddress) view returns (bytes memory signature); // 完整版本:多个合约地址 + 自定义有效期 function signUserDecrypt( uint256 userPk, address[] memory contractAddresses, uint256 startTimestamp, uint256 durationDays ) view returns (bytes memory signature);

其中userPk是测试中通过vm.addr(userPk)派生的用户私钥,与userDecrypt中的userAddress对应。默认有效期由常量DEFAULT_USER_DECRYPT_DURATION_DAYS(值为1)控制。

7. 常量一览

常量用途
MOCK_INPUT_SIGNER_PK硬编码 mock 密钥——见FhevmTest.sol签署输入证明(确定性、mock 签名者)
MOCK_KMS_SIGNER_PK硬编码 mock 密钥——见FhevmTest.sol签署 KMS 解密证明(确定性、mock 签名者)
EMPTY_EXTRA_DATAhex"00"附加到 EIP-712 证明上的默认 extra data
DEFAULT_USER_DECRYPT_DURATION_DAYS1用户解密签名的默认有效期(天)

重要提示:这两个 mock 签名者私钥是 Zama 特有的、固化在forge-fhevm/src/FhevmTest.sol中的值,并非Foundry 的标准测试私钥。它们存在的唯一目的,是让测试中的 EIP-712 证明具有确定性。任何依赖这些密钥的安全性假设,都只适用于本地测试环境。

8. 从速查到实战:API 在完整测试中的落地

FhevmTest的 API 组合起来,可以覆盖 fhEVM 合约测试的完整生命周期。一个端到端示例(计数器合约测试,对应文档write_test.md中的完整示例)如下:

contract FHECounterTest is FhevmTest { FHECounter counter; uint256 internal constant ALICE_PK = 0xA11CE; address alice; function setUp() public override { super.setUp(); counter = new FHECounter(); alice = vm.addr(ALICE_PK); } function test_incrementTheCounterByOne() public { (externalEuint32 encOne, bytes memory proof) = encryptUint32(1, alice, address(counter)); vm.prank(alice); counter.increment(encOne, proof); bytes memory sig = signUserDecrypt(ALICE_PK, address(counter)); uint256 clear = userDecrypt(euint32.unwrap(counter.getCount()), alice, address(counter), sig); assertEq(clear, 1); } }

运行测试:

forge test -vvv forge test --match-test test_incrementTheCounterByOne -vvv # 只跑单个测试

对应地,本仓库中的示例合约 library-solidity/examples/Counter.sol 展示了明态计数器(uint32 value+increment()/currentValue())的形态;而 fhEVM 场景下,value会被替换为euint32加密句柄,increment接收externalEuint32与证明并调用FHE.asEuint32,读取则通过解密辅助函数完成——FhevmTest的整套 API 正是为验证这类"加密输入 → 链上密态运算 → 授权解密"流程而设计的。

9. 进一步阅读

  • Foundry 测试库总览:forge-fhevm 的设计理念与目录导航;
  • Setup Foundry:从模板克隆项目、Soldeer 安装依赖、foundry.toml/remappings.txt配置;
  • 编写 fhEVM 测试:三种解密模式的完整用例与错误码对照表;
  • Foundry 部署指南:将合约部署到本地 Anvil 或 Sepolia;
  • FHE 交互库源码:FHE.fromExternalFHE.allowFHE.checkSignaturesFHE.makePubliclyDecryptable等链上函数的真实实现;
  • 主机合约测试装配器:FhevmTest.setUp()同构思想在仓库内的源码级印证。

【免费下载链接】fhevmFHEVM, a full-stack framework for integrating Fully Homomorphic Encryption (FHE) with blockchain applications项目地址: https://gitcode.com/GitHub_Trending/fh/fhevm

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询