尧图精选

使用 fhEVM 构建加密计数器:从普通 Solidity Counter 到全同态加密 FHECounter 的完整实战

🕒 发布时间:2026/9/12 15:55:50 📁 来源:尧图网络
使用 fhEVM 构建加密计数器从普通 Solidity Counter 到全同态加密 FHECounter 的完整实战【免费下载链接】fhevmFHEVM, a full-stack framework for integrating Fully Homomorphic Encryption (FHE) with blockchain applications项目地址: https://gitcode.com/GitHub_Trending/fh/fhevm导读本文以 docs/examples/fhe-counter.md 为骨架逐步演示如何在 fhEVMFully Homomorphic Encryption Virtual Machine上将一个传统的、明文的Counter合约改造成一个全程密文运算的FHECounter合约。你将从零搭起可编译、可测试的 Hardhat 工程结构对比普通计数与加密计数在合约端与 TypeScript 测试端的差异并理解euint32、externalEuint32、FHE.fromExternal、FHE.add/sub、allowThis/allow这些核心 API 的真实语义。读完本文你将具备独立编写并验证一个基于 fhEVM 的密文状态机应用的基础能力。1. 示例背景为什么计数器也要加密普通智能合约中的状态变量例如uint32 _count以明文存储在所有节点上任何人通过区块链浏览器即可读取其当前值。当计数对象涉及敏感业务如投票数、竞价金额、用户积分、隐私偏好统计时明文存储就构成了数据泄露风险。fhEVM 通过全同态加密FHE让合约在不解密的前提下对密文执行加、减、乘、比较等运算。本文将普通计数器升级为加密计数器的过程完整展示了从明文可读状态到密文状态 受控解密的迁移路径是学习 fhEVM 最经典的入门案例。1.1 本示例在仓库中的位置关联文档 docs/examples/fhe-counter.md 位于仓库 docs 的 examples 目录下与其同级的还有 fheadd、fheifthenelse、heads-or-tails、sealed-bid-auction 等示例。本文用到的核心依赖都来自本仓库FHE 类型与运算库library-solidity/lib/FHE.sol含euint32、externalEuint32、fromExternal、add、sub、allow、allowThis等Zama 网络配置库library-solidity/config/ZamaConfig.sol仓库内另一个更简化的明文 Counter 示例library-solidity/examples/Counter.sol提示原文档与仓库中的 FHE 合约示例位于不同子项目本文以关联文档中的Counter.sol/FHECounter.sol为讲解主线仓库源码作为实现佐证。文中涉及的fhevm/solidity等包名与仓库实际发布形态可能存在细微差异请以你所安装的 fhEVM 工具链版本为准。2. 工程结构要求文件放对位置才能跑起来原文档在开头特别强调了一个极易踩坑的目录约束.sol合约文件 → 必须放在your-project-root-dir/contracts/.ts测试文件 → 必须放在your-project-root-dir/test/只有满足这一目录结构Hardhat 才能正常编译合约并发现测试。因此一个最小可运行的工程目录大致为your-project-root-dir/ ├── contracts/ │ ├── Counter.sol # 普通计数器 │ └── FHECounter.sol # FHE 加密计数器 ├── test/ │ ├── counter.ts # 普通计数器测试 │ └── fheCounter.ts # FHE 计数器测试 ├── hardhat.config.ts # 集成 fhevm 插件 └── package.json这个结构与仓库中 library-solidity 子项目examples/放合约、test/放 TS 测试以及 test-suite/e2econtracts/与test/分离的组织方式一致说明合约目录与测试目录分离是 fhEVM 官方示例的通用约定。3. 一个普通的 Counter明文版3.1 合约代码counter.sol// SPDX-License-Identifier: BSD-3-Clause-Clear pragma solidity ^0.8.24; /// title A simple counter contract contract Counter { uint32 private _count; /// notice Returns the current count function getCount() external view returns (uint32) { return _count; } /// notice Increments the counter by a specific value function increment(uint32 value) external { _count value; } /// notice Decrements the counter by a specific value function decrement(uint32 value) external { require(_count value, Counter: cannot decrement below zero); _count - value; } }关键点_count是uint32类型直接以明文存储在链上getCount()返回明文increment(value)/decrement(value)接收明文 uint32参数直接对状态变量做/-由于减法在无符号整数上可能下溢这里用require(_count value, ...)做了防御性检查。仓库中的 library-solidity/examples/Counter.sol 是一个更简化的同款示例increment()固定加 1、currentValue()读值可作为对照参考。3.2 测试代码counter.tsimport { Counter, Counter__factory } from ../types; import { HardhatEthersSigner } from nomicfoundation/hardhat-ethers/signers; import { expect } from chai; import { ethers } from hardhat; type Signers { deployer: HardhatEthersSigner; alice: HardhatEthersSigner; bob: HardhatEthersSigner; }; async function deployFixture() { const factory (await ethers.getContractFactory(Counter)) as Counter__factory; const counterContract (await factory.deploy()) as Counter; const counterContractAddress await counterContract.getAddress(); return { counterContract, counterContractAddress }; } describe(Counter, function () { let signers: Signers; let counterContract: Counter; before(async function () { const ethSigners: HardhatEthersSigner[] await ethers.getSigners(); signers { deployer: ethSigners[0], alice: ethSigners[1], bob: ethSigners[2] }; }); beforeEach(async () { ({ counterContract } await deployFixture()); }); it(count should be zero after deployment, async function () { const count await counterContract.getCount(); console.log(Counter.getCount() ${count}); // Expect initial count to be 0 after deployment expect(count).to.eq(0); }); it(increment the counter by 1, async function () { const countBeforeInc await counterContract.getCount(); const tx await counterContract.connect(signers.alice).increment(1); await tx.wait(); const countAfterInc await counterContract.getCount(); expect(countAfterInc).to.eq(countBeforeInc 1n); }); it(decrement the counter by 1, async function () { // First increment, count becomes 1 let tx await counterContract.connect(signers.alice).increment(1); await tx.wait(); // Then decrement, count goes back to 0 tx await counterContract.connect(signers.alice).decrement(1); await tx.wait(); const count await counterContract.getCount(); expect(count).to.eq(0); }); });这段测试完全使用标准 ethers.js / Hardhat 模式没有任何 FHE 相关 API。值得注意的是getCount()是view调用读回的即是真实明文断言直接比较数值用signers.alice连接合约执行写操作模拟不同账户的操作权限测试覆盖了三个典型场景部署后为 0、自增 1、自增后再自减回到 0。4. 一个 FHE 计数器密文版4.1 核心差异一览将普通计数器升级为 FHE 计数器本质变化有四处维度普通 CounterFHE Counter状态变量类型uint32明文euint32密文句柄写入参数uint32 value明文externalEuint32 inputEuint32 bytes inputProof外部密文 证明运算方式原生/-FHE.add(...)/FHE.sub(...)读取方式view直接返回明文返回euint32密文句柄需客户端解密权限控制无FHE.allowThis/FHE.allow授予解密/使用权限4.2 合约代码FHECounter.sol// SPDX-License-Identifier: BSD-3-Clause-Clear pragma solidity ^0.8.24; import { FHE, euint32, externalEuint32 } from fhevm/solidity/lib/FHE.sol; import { ZamaEthereumConfig } from fhevm/solidity/config/ZamaConfig.sol; /// title A simple FHE counter contract contract FHECounter is ZamaEthereumConfig { euint32 private _count; /// notice Returns the current count function getCount() external view returns (euint32) { return _count; } /// notice Increments the counter by a specified encrypted value. /// dev This example omits overflow/underflow checks for simplicity and readability. /// In a production contract, proper range checks should be implemented. function increment(externalEuint32 inputEuint32, bytes calldata inputProof) external { euint32 encryptedEuint32 FHE.fromExternal(inputEuint32, inputProof); _count FHE.add(_count, encryptedEuint32); FHE.allowThis(_count); FHE.allow(_count, msg.sender); } /// notice Decrements the counter by a specified encrypted value. /// dev This example omits overflow/underflow checks for simplicity and readability. /// In a production contract, proper range checks should be implemented. function decrement(externalEuint32 inputEuint32, bytes calldata inputProof) external { euint32 encryptedEuint32 FHE.fromExternal(inputEuint32, inputProof); _count FHE.sub(_count, encryptedEuint32); FHE.allowThis(_count); FHE.allow(_count, msg.sender); } }下面逐行拆解这个合约的每个关键环节并结合仓库源码印证其真实行为。4.2.1 继承ZamaEthereumConfig拿到当前链的 FHEVM 配置合约声明contract FHECounter is ZamaEthereumConfig。在仓库中ZamaConfig.sol 的getCoprocessorConfig()按block.chainid路由返回当前链上 ACL、Coprocessor、KMSVerifier 等核心合约地址Ethereum mainnetchainId1、Polygon137、Sepolia11155111、Polygon Amoy80002以及本地 Hardhat/Anvil 网络31337在其他链上会revert ZamaProtocolUnsupported()。也就是说同一个合约可以不改代码地在上述网络中部署运行前提是继承对应的 Zama 配置基类。4.2.2euint32 private _count密文状态变量euint32是32 位无符号整数的密文句柄类型。在 library-solidity/lib/FHE.sol 中euint32是type(uint256).wrap的用户定义值类型user-defined value type链上存储的其实是底层密文句柄bytes32形式真正的明文数值从未离开过加密环境。文档中的注释也点明部署后getCount()返回的是bytes32(0)即未初始化状态。4.2.3externalEuint32 inputProof外部密文输入与证明increment/decrement不接收明文而是接收externalEuint32 inputEuint32调用者本地加密后产生的密文句柄和bytes calldata inputProof证明该密文确实由合法的客户端加密密钥生成。这一设计保证了任何人都不能伪造一个看似加密的输入注入合约——密文必须通过 fhEVM 官方的加密库生成并附带可验证的证明。4.2.4FHE.fromExternal验证并接入外部密文euint32 encryptedEuint32 FHE.fromExternal(inputEuint32, inputProof);对照 library-solidity/lib/FHE.sol 中fromExternal(externalEuint32, bytes)的实现第 8608 行附近当inputProof非空时调用Impl.verify(...)用证明校验输入密文当inputProof为空时若句柄为 0 则视为明文 0否则要求该句柄已通过allow授权给msg.sender否则revert SenderNotAllowedToUseHandle这一路径为智能合约账户smart contract account集成 fhEVM 提供了可能。4.2.5FHE.add/FHE.sub在密文上做同态运算_count FHE.add(_count, encryptedEuint32); _count FHE.sub(_count, encryptedEuint32);在 library-solidity/lib/FHE.sol 中add(euint32, euint32)第 2528 行与sub(euint32, euint32)第 2541 行的实现会先将未初始化的操作数视为 0通过isInitialized检查后asEuint32(0)再调用底层Impl.add/Impl.sub生成新的密文句柄。也就是说密文是在链上、由 coprocessor 在加密域内完成加法/减法的任何人包括合约本身都无法看到中间数值。4.2.6FHE.allowThis/FHE.allow解密与使用授权FHE.allowThis(_count); FHE.allow(_count, msg.sender);FHE.allowThis(_count)把新生成的密文句柄授权给合约自身供后续合约内继续运算FHE.allow(_count, msg.sender)把句柄授权给当前调用者如 alice使 alice 可以在链下请求解密该值。对照 library-solidity/lib/FHE.sol 第 93529369 行allow与allowThis都会先对未初始化值做asEuint32(0)兜底再调用Impl.allow(...)写入授权关系。这是 fhEVM细粒度解密权限控制的体现谁被allow谁才有资格拿到解密后的明文状态本身永远不公开。4.2.7 关于溢出/下溢文档明确的取舍两个函数都带有dev注释原文明确写道This example omits overflow/underflow checks for simplicity and readability. In a production contract, proper range checks should be implemented.即示例为了可读性省略了溢出/下溢检查生产环境必须自行补充范围校验例如用FHE.gte等比较运算在密文域内做下溢保护。这一点应视为原文档对读者的显式安全提醒。4.3 测试代码FHECounter.tsimport { 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(); // Expect initial count to be bytes32(0) after deployment, // (meaning the encrypted count value is uninitialized) 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; // Encrypt constant 1 as a euint32 const clearOne 1; const encryptedOne await fhevm .createEncryptedInput(fheCounterContractAddress, signers.alice.address) .add32(clearOne) .encrypt(); 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); }); it(decrement the counter by 1, async function () { // Encrypt constant 1 as a euint32 const clearOne 1; const encryptedOne await fhevm .createEncryptedInput(fheCounterContractAddress, signers.alice.address) .add32(clearOne) .encrypt(); // First increment by 1, count becomes 1 let tx await fheCounterContract .connect(signers.alice) .increment(encryptedOne.handles[0], encryptedOne.inputProof); await tx.wait(); // Then decrement by 1, count goes back to 0 tx await fheCounterContract.connect(signers.alice).decrement(encryptedOne.handles[0], encryptedOne.inputProof); await tx.wait(); const encryptedCountAfterDec await fheCounterContract.getCount(); const clearCountAfterDec await fhevm.userDecryptEuint( FhevmType.euint32, encryptedCountAfterDec, fheCounterContractAddress, signers.alice, ); expect(clearCountAfterDec).to.eq(0); }); });4.3.1 从hardhat导入fhevm插件注入的 FHE 环境与普通测试相比这里额外从hardhat导入了fhevm对象并引入FhevmType枚举。这是 fhEVM Hardhat 插件为测试环境注入的客户端环境负责在测试进程内完成密文的创建、上链后的解密。仓库中 library-solidity/test/fhevmOperations/manual.ts 等测试同样使用createEncryptedInput(...)构造加密输入模式完全一致。4.3.2createEncryptedInput(...).add32(1).encrypt()客户端加密const encryptedOne await fhevm .createEncryptedInput(fheCounterContractAddress, signers.alice.address) .add32(clearOne) .encrypt();createEncryptedInput(contractAddress, accountAddress)创建针对目标合约、以指定账户身份加密的输入构造器.add32(clearOne)声明要加密一个uint32类型的明文值 1并生成对应的euint32密文.encrypt()完成本地加密返回{ handles, inputProof }encryptedOne.handles[0]是加密结果的密文句柄传给合约的externalEuint32参数encryptedOne.inputProof是对应证明传给bytes inputProof参数。4.3.3userDecryptEuint受控解密并断言明文const clearCountAfterInc await fhevm.userDecryptEuint( FhevmType.euint32, encryptedCountAfterInc, fheCounterContractAddress, signers.alice, );userDecryptEuint以用户身份发起解密请求本测试中即 alice配合合约内FHE.allow(_count, msg.sender)授予的权限把链上的密文句柄解密回明文。整个测试对明文的断言逻辑与普通 Counter 完全一致expect(clearCountAfterInc).to.eq(clearCountBeforeInc clearOne)但所有读写中间过程都发生在密文域。4.3.4 测试用例与普通版的对应关系普通 Counter 测试FHE Counter 测试差异点部署后getCount() 0部署后getCount() ethers.ZeroHash未初始化的密文句柄是bytes32(0)不能期望返回明文 0increment(1)后getCount() 1加密 1 → 调用increment(handle, proof)→ 解密后为 1入参从明文变成密文 证明increment(1)再decrement(1)回到 0同样步骤全程密文运算解密后为 0同态减在密文域完成注意第一行的差异非常重要在 fhEVM 中未初始化的密文状态读取到的是bytes32(0)句柄而非数值 0这是新手最容易困惑的地方。5. 原理解读这条密文路径上发生了什么结合合约与测试一次increment(1)的完整调用链如下客户端TS 测试fhevm.createEncryptedInput(addr, alice).add32(1).encrypt()在本地把明文1加密为密文输出handles[0]密文句柄与inputProof证明客户端 → 合约alice 调用FHECounter.increment(handles[0], inputProof)密文与证明上链合约内FHE.fromExternal(handle, proof)验证证明并转为euint32FHE.add(_count, encrypted)在加密域内完成加法生成新句柄FHE.allowThisFHE.allow(_count, msg.sender)授予合约与 alice 后续使用/解密权限链下解密alice 调用fhevm.userDecryptEuint(...)在拥有allow权限的前提下通过 KMS / coprocessor 服务将密文解密回明文1用于断言。从仓库源码看FHE.add/FHE.sub最终都落到 library-solidity/lib/Impl.sol 的底层实现Impl.add、Impl.sub、Impl.verify、Impl.allow由 fhEVM 的预编译/coprocessor 基础设施在链上执行同态运算与权限校验。6. 运行方式与前提条件环境前提需要配置好 fhEVM 的 Hardhat 开发环境含fhevm/hardhat-plugin等并在本地拉起支持 FHE 的节点如 Anvil coprocessor 或 fhEVM 测试网络chainId 31337 本地网络即可满足ZamaEthereumConfig的配置路由。文件放置严格按照第 2 节要求把Counter.sol、FHECounter.sol放入contracts/把counter.ts、fheCounter.ts放入test/。执行测试在工程根目录运行 Hardhat 测试命令如npx hardhat test应看到两个describe块共 6 个用例全部通过。验证标准普通 Counter 的getCount()直接返回数值FHECounter 的getCount()返回密文句柄初始为ethers.ZeroHash只有通过userDecryptEuint并拥有allow授权的账户才能拿到明文。仓库内 library-solidity、test-suite/e2e 等子项目提供了大量同模式的可运行测试如 library-solidity/test/fhevmOperations/manual.ts 中的createEncryptedInput 解密断言可作为进一步学习的参照。7. 小结与下一步通过本文你已经掌握了普通计数器与 FHE 计数器在状态类型、入参形态、运算方式、读取方式、权限模型上的五大差异euint32/externalEuint32的类型语义以及FHE.fromExternal的验证逻辑FHE.sol 第 8608 行起FHE.add/FHE.sub对未初始化值的兜底处理第 2528、2541 行与allow/allowThis的授权机制第 93529369 行客户端createEncryptedInputuserDecryptEuint的完整闭环测试写法。进阶方向在 docs/examples 中继续阅读fheadd、fheifthenelse、sealed-bid-auction、heads-or-tails等示例理解条件运算FHE.ifThenElse、比较运算在密封拍卖、掷骰子等真实场景中的应用生产化时务必参照 docs/solidity-guides 与 library-solidity/SECURITY.md补齐范围校验与访问控制。【免费下载链接】fhevmFHEVM, a full-stack framework for integrating Fully Homomorphic Encryption (FHE) with blockchain applications项目地址: https://gitcode.com/GitHub_Trending/fh/fhevm创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
上一篇/下一篇内容由系统自动关联 返回资讯列表 →