ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

在 Hardhat 中编写 FHEVM 测试:使用 FHEVM Hardhat Plugin 实现加密输入与用户解密

在 Hardhat 中编写 FHEVM 测试:使用 FHEVM Hardhat Plugin 实现加密输入与用户解密 在 Hardhat 中编写 FHEVM 测试使用 FHEVM Hardhat Plugin 实现加密输入与用户解密【免费下载链接】fhevmFHEVM, a full-stack framework for integrating Fully Homomorphic Encryption (FHE) with blockchain applications项目地址: https://gitcode.com/GitHub_Trending/fh/fhevm本篇指南聚焦于在 Hardhat 测试工程中借助FHEVM Hardhat Plugin为全同态加密智能合约编写测试的核心技能如何在 TypeScript 测试代码中启用插件、通过fhevm运行时模块对输入值做本地加密、构造externalEuintXX密文句柄与零知识证明再调用合约方法并最终用userDecryptEuint系列 API 解密链上密文进行断言。读完本文你将能够在 fhevm 仓库所描述的 FHEVM 开发体系中从零写出可运行、可验证的加密合约测试并理解句柄handle、输入证明inputProof与 FHE 权限在其中的底层作用。前置条件启用 FHEVM Hardhat PluginFHEVM 的测试能力由一个独立的 Hardhat 插件提供其作用与任何普通 Hardhat 插件一致在加载阶段被引入从而把 FHEVM 相关能力注入 Hardhat 运行时环境Hardhat Runtime Environment简称 HRE。要启用它只需在hardhat.config.ts中添加一行 importimport fhevm/hardhat-plugin;⚠️注意如果没有这行 importHardhat 运行时环境中将不会存在 FHEVM API后续所有加密、解密调用都会因找不到fhevm模块而失败。因此这行 import 是编写一切 FHEVM 测试的前提。关于整个开发环境的初始化例如 Node.js 版本要求——建议使用偶数版本如v18.x、v20.x以及 FHEVM Hardhat 模板仓库的创建与npm install步骤可以参阅 Hardhat 环境搭建指南插件能力的整体介绍见 Hardhat 插件开发指南。访问 Hardhat FHEVM API插件在启用后会向标准 Hardhat Runtime Environment 扩展一个新的fhevm模块。在测试代码中有两种等价的方式拿到它import { fhevm } from hardhat;或import * as hre from hardhat; // Then access: hre.fhevm两种写法都指向同一个运行时单例。后续所有加密与解密操作例如fhevm.createEncryptedInput(...)、fhevm.userDecryptEuint(...)都从该模块发起。这也是整个 FHEVM Hardhat 测试 API 的入口。加密输入在测试中构造并提交密文FHEVM 的核心使用场景是测试方例如用户 Alice把明文值在本地加密成密文提交给链上合约处理。链上合约处理的是密文本身因此明文在链上任何环节都不会出现。Solidity 侧的函数签名假设被测合约有一个名为foo的函数接收一个加密的uint32。按 FHEVM 规范Solidity 侧应这样声明function foo(externalEuint32 value, bytes calldata inputProof);其中externalEuint32 value一个bytes32表示加密后的uint32即加密输入的句柄handle。externalEuint32是 FHEVM 对外部加密输入的专用类型与合约内部使用的euint32相区分——它表明该密文来自链下用户必须经过完整性验证后才能进入合约计算。bytes calldata inputProofbytes数组保存验证该加密有效性的零知识证明Zero-Knowledge Proof of KnowledgeZKPoK。关于externalEuintXX/externalEbool/externalEaddress类型与bytes inputProof参数的完整设计说明可进一步阅读 加密输入Encrypted Inputs文档。TypeScript 侧的加密四步流程在 TypeScript 测试中计算这两个参数需要准备两样东西目标合约的地址contractAddress签名者的地址即发送交易的账户如signers.alice.address随后按如下四步完成加密并调用第 1 步创建一个新的加密输入对象// use the fhevm API module from the Hardhat Runtime Environment const input fhevm.createEncryptedInput(contractAddress, signers.alice.address);createEncryptedInput返回一个加密输入构造器它把密文与「合约地址 用户地址」双重绑定生成的密文只能由该用户在指定合约中使用。第 2 步添加要加密的值input.add32(12345);add32对应加密一个uint32。FHEVM 还提供add8、add16、add64、addBool、addAddress等按位宽区分的追加方法用于在同一输入对象上打包多个不同类型的加密值。第 3 步执行本地加密const encryptedInputs await input.encrypt();encrypt()是异步操作它在本地完成 FHE 公钥加密并生成用于链上验证的零知识证明。第 4 步调用 Solidity 函数const externalUint32Value encryptedInputs.handles[0]; const inputProof encryptedInputs.inputProof; const tx await input.foo(externalUint32Value, inputProof); await tx.wait();encryptedInputs.handles是一个数组按add32等方法的调用顺序保存各个加密值的bytes32句柄encryptedInputs.inputProof则是对应整个打包结果的 ZKPoK。二者分别填入函数的两个参数即可。 补充说明多个加密值可以打包进同一个输入对象。例如 加密输入文档 中的例子依次调用addBool(...)、add64(...)、add8(...)加密结果通过handles[0]、handles[1]、handles[2]按添加顺序取出再分别传给 Solidity 函数的多个externalEbool/externalEuint64/externalEuint8参数。TypeScript 侧的添加顺序与 Solidity 函数参数的声明顺序没有强制对应关系开发时可以自由组织。仓库中的真实用法佐证仓库中大量真实测试都遵循上述模式。以 EncryptedERC20 测试 为例transfer测试先用createEncryptedInput为 Alice 构造加密转账金额再取出句柄与证明调用合约const input this.instances.alice.createEncryptedInput(this.contractAddress, this.signers.alice.address); input.add64(1337); const encryptedTransferAmount await input.encrypt(); const tx await this.erc20transfer(address,bytes32,bytes);同一测试文件中还对句柄的字节结构做了校验见 EncryptedERC20.ts 的 mint 测试句柄的字节 21 被置为0xff字节 2229 编码链 IDchainId字节 30 编码 FHE 类型如euint64对应05字节 31 为句柄版本。这说明一个bytes32句柄并非随机数而是携带了类型、链与版本信息的 FHEVM 内部引用。更多加密示例仓库文档目录还提供了从简单到完整的加密示例可与本节对照学习单值加密示例多值加密示例FHECounter 完整示例解密用 userDecryptEuint 系列 API 读取明文加密值进入合约参与计算后测试方需要把它读出来并解密成明文做断言。以用户Alice解密合约中一个euint32值为例合约需暴露如下view函数function getEncryptedUint32Value() public view returns (euint32) { returns _encryptedUint32Value; }⚠️权限前提为简化说明这里假设 Alice 的账户与目标合约都已经具备解密该值所需的 FHE 权限。FHE 权限的具体工作机制allow/ ACL请参阅 ACL 文档 以及 用户解密委托说明。如果目标合约或用户任一方没有 FHE 权限解密调用将直接失败。解密分两步进行第 1 步从合约读取加密值一个bytes32句柄const encryptedUint32Value await contract.getEncryptedUint32Value();第 2 步调用 FHEVM API 执行解密const clearUint32Value await fhevm.userDecryptEuint( FhevmType.euint32, // Encrypted type (must match the Solidity type) encryptedUint32Value, // bytes32 handle Alice wants to decrypt contractAddress, // Target contract address signers.alice, // Alice’s wallet );userDecryptEuint的四个参数含义分别为FhevmType加密值的整数类型必须与 Solidity 侧类型严格一致例如euint32对应FhevmType.euint32。类型不匹配会导致解密失败。加密句柄要解密的bytes32句柄。合约地址持有该句柄访问权限的目标合约地址。用户签名者拥有该句柄访问权限的用户钱包如signers.alice。支持的解密类型FHEVM 为每种加密类型提供了对应的解密函数使用时按下表选择类型函数euintXXXfhevm.userDecryptEuint(...)eboolfhevm.userDecryptEbool(...)eaddressfhevm.userDecryptEaddress(...)FHEVM 支持的加密类型全集ebool、euint8至euint256、eaddress等及各自的位宽与支持算子见 支持的加密类型文档。权限失败的真实表现权限约束在真实测试中是可以被验证的。EncryptedERC20.ts 专门断言了「Bob 无法解密密文」这一负向场景当 Bob 尝试对 Alice 的余额句柄执行解密/重加密时会抛出User is not authorized to reencrypt this handle!异常。这印证了文档中「权限缺失导致解密失败」的警告——测试编写者可以把这类断言纳入自己的测试以覆盖安全边界。更多解密示例单值用户解密示例多值用户解密示例从零搭建一个完整的 FHEVM 测试文件把加密与解密串起来一个完整的 FHEVM 测试文件骨架大致如下。以仓库文档 Test the FHEVM contract 中的FHECounter为例import { FHECounter, FHECounter__factory } from ../types; import { FhevmType } from fhevm/hardhat-plugin; import { HardhatEthersSigner } from nomicfoundation/hardhat-ethers/signers; import { expect } from chai; import { ethers, fhevm } from hardhat; type Signers { deployer: HardhatEthersSigner; alice: HardhatEthersSigner; bob: HardhatEthersSigner; }; async function deployFixture() { const factory (await ethers.getContractFactory(FHECounter)) as FHECounter__factory; const fheCounterContract (await factory.deploy()) as FHECounter; const fheCounterContractAddress await fheCounterContract.getAddress(); return { fheCounterContract, fheCounterContractAddress }; } describe(FHECounter, function () { let signers: Signers; let fheCounterContract: FHECounter; let fheCounterContractAddress: string; before(async function () { const ethSigners: HardhatEthersSigner[] await ethers.getSigners(); signers { deployer: ethSigners[0], alice: ethSigners[1], bob: ethSigners[2] }; }); beforeEach(async () { ({ fheCounterContract, fheCounterContractAddress } await deployFixture()); }); it(encrypted count should be uninitialized after deployment, async function () { const encryptedCount await fheCounterContract.getCount(); // 部署后初始加密计数应为 bytes32(0)表示尚未初始化 expect(encryptedCount).to.eq(ethers.ZeroHash); }); it(increment the counter by 1, async function () { const encryptedCountBeforeInc await fheCounterContract.getCount(); expect(encryptedCountBeforeInc).to.eq(ethers.ZeroHash); const clearCountBeforeInc 0; // 本地加密常量 1 为 euint32 const clearOne 1; const encryptedOne await fhevm .createEncryptedInput(fheCounterContractAddress, signers.alice.address) .add32(clearOne) .encrypt(); // 以加密参数调用 increment const tx await fheCounterContract.connect(signers.alice).increment(encryptedOne.handles[0], encryptedOne.inputProof); await tx.wait(); const encryptedCountAfterInc await fheCounterContract.getCount(); const clearCountAfterInc await fhevm.userDecryptEuint( FhevmType.euint32, encryptedCountAfterInc, fheCounterContractAddress, signers.alice, ); expect(clearCountAfterInc).to.eq(clearCountBeforeInc clearOne); }); });这段代码体现了与普通 Hardhat 测试的几个关键差异值得逐一理解句柄而非数值getCount()返回的不再是 TypeScriptnumber而是一个十六进制bytes32字符串FHEVM 句柄指向类型为euint32的加密原语。未初始化时它等于0x0000...0000即ethers.ZeroHash不引用任何加密值。加密输入绑定上下文fhevm.createEncryptedInput(contractAddress, signers.alice.address)生成的密文同时绑定合约地址与用户地址只能由 Alice 在该合约中使用不能被其他用户或其他合约复用从而保证数据机密性与上下文绑定。入参数量变化increment()由普通合约的单参数变为increment(encryptedOne.handles[0], encryptedOne.inputProof)双参数。这是因为 FHEVM 除密文句柄外还需要附带 ZKPoK 来证明该加密输入与调用者Alice以及目标合约绑定防止密文在异构上下文被重放。解密需要类型与权限双重匹配userDecryptEuint的FhevmType.euint32必须与 Solidity 类型一致句柄的访问权限由链上FHE.allow()等机制授权。在三种运行时模式下执行测试FHEVM Hardhat Plugin 提供三种运行时模式对应合约开发与测试的不同阶段在速度、加密强度与持久性之间取舍模式加密方式持久化链环境速度适用场景Hardhat默认模拟加密否内存网络非常快常规测试、CI 覆盖率、早期合约开发的快速反馈Hardhat Node模拟加密是本地服务器快前端联调、模拟用户流程、本地持久化测试Sepolia 测试网真实加密是链上服务器慢全栈验证唯一使用真实加密值的模式默认内存模式下直接执行npx hardhat test --network hardhat本地节点、Sepolia 部署与交互的完整操作步骤包括npx hardhat node、npx hardhat deploy --network localhost、npx hardhat fhevm check-fhevm-compatibility以及task:decrypt-count、task:increment等任务用法参见 部署合约并运行测试。补充提示如果你的测试逻辑需要在自定义 Hardhat Task而非test/compile内置任务中使用 FHEVM API必须在任务开头显式调用fhevm.initializeCLIApi()因为自定义任务不会像内置任务那样自动初始化 FHEVM 运行时环境。具体写法参见 编写 FHEVM Hardhat 任务。实战要点与常见陷阱忘记 import 插件hardhat.config.ts缺少import fhevm/hardhat-plugin;时hre.fhevm不存在所有加密解密调用都会抛错。这是最高频的入门错误。类型不匹配FhevmType.euint32与 Solidity 的euint32必须一一对应句柄的编码字节 30本身就携带类型信息类型错配时解密会失败或产生错误结果。权限缺失合约与用户都必须拥有句柄的 FHE 权限通过FHE.allow()等链上机制授予否则解密抛错。可参考仓库测试 EncryptedERC20.ts 的负向断言写法。未初始化句柄为 ZeroHash合约中尚未赋值的euint变量其句柄为全零bytes32在测试中应先用ethers.ZeroHash断言再进入加密计算流程。句柄与证明的配对handles[i]与inputProof来自同一次encrypt()调用跨输入对象混用会导致链上零知识验证失败。覆盖权限与非法调用等负向路径FHEVM 的安全边界如其他用户越权解密、密文跨合约复用本身就是重要测试点仓库测试中已有成熟范例可参考。通过以上步骤你就掌握了 FHEVM 合约测试从「启用插件 → 构造加密输入 → 调用合约 → 解密断言」的完整闭环。进一步深入学习可参阅 FHEVM Solidity 指南总目录、加密输入文档 与 FHEVM API 参考。【免费下载链接】fhevmFHEVM, a full-stack framework for integrating Fully Homomorphic Encryption (FHE) with blockchain applications项目地址: https://gitcode.com/GitHub_Trending/fh/fhevm创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表