ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

基于 FHE 与 OpenZeppelin 实现机密代币:fhevm 仓库中 ERC7984 完整开发指南

2026/9/12 3:41:41 拓冰建站 浏览量
基于 FHE 与 OpenZeppelin 实现机密代币:fhevm 仓库中 ERC7984 完整开发指南 基于 FHE 与 OpenZeppelin 实现机密代币fhevm 仓库中 ERC7984 完整开发指南【免费下载链接】fhevmFHEVM, a full-stack framework for integrating Fully Homomorphic Encryption (FHE) with blockchain applications项目地址: https://gitcode.com/GitHub_Trending/fh/fhevm本指南以 fhevm 仓库中 docs/examples/openzeppelin/erc7984.md 为核心讲解如何借助全同态加密FHE与 OpenZeppelin 机密合约库从零构建一个余额与转账全程加密、同时保持完整代币功能的机密同质化代币。你将掌握ERC7984Example合约的编写、Hardhat 测试的组织与运行以及可见/机密铸造、销毁、总供应量可见性等高级扩展的落地方式并理解FHE.asEuint64、FHE.fromExternal、FHE.allow等底层库函数在 library-solidity/lib/FHE.sol 中的真实实现机制。为什么机密代币需要 FHE在传统 ERC-20 模型中余额与转账金额以明文存储在链上任何人都可以监控大户持仓、交易流向与资金规模。而机密代币confidential token在许多真实场景中具有明确价值隐私保护Privacy用户可以在不暴露精确余额与交易金额的前提下完成转账合规Regulatory Compliance在保持隐私的同时允许按需进行选择性披露商业情报Business Intelligence企业可以避免竞争对手窥探其代币持仓个人隐私Personal Privacy个人可以在 DeFi 中参与交易而不暴露财务状况审计追溯Audit Trail所有交易仍然完整记录在链上只是以加密形式存在。FHE 之所以能同时满足以上需求是因为它允许在密文上直接进行计算而无需解密——链上节点看到的始终是密文但合约内部可以执行余额扣减、转账校验等逻辑在保持区块链安全性与透明性的同时实现了隐私。项目环境搭建开始本教程前需要完成两步准备工作安装FHEVM Hardhat 模板zama-ai/fhevm-hardhat-template安装OpenZeppelin 机密合约库openzeppelin/confidential-contracts环境搭建的完整步骤可参考同目录下的 OpenZeppelin 机密合约入门指南。按 docs/examples/openzeppelin/README.md 中的说明推荐的开发环境为Node.js 20与Hardhat ^2.24并需要能够访问 FHEVM 启用的网络以及 Zama 网关/中继器relayer。关键安装命令如下git clone https://github.com/zama-ai/fhevm-hardhat-template conf-token cd conf-token npm ci npm i openzeppelin/confidential-contracts npm run compile理解合约架构我们的机密代币合约将继承三个关键组件ERC7984—— OpenZeppelin 机密代币标准的基础实现提供加密余额记账与机密转账逻辑Ownable2Step—— 为铸造与治理函数提供两步式安全访问控制ZamaEthereumConfig—— FHE 网络配置面向Ethereum 主网与Ethereum Sepolia 测试网。其中ZamaEthereumConfig的定义位于仓库 library-solidity/config/ZamaConfig.sol它是一个可继承的抽象合约构造时会调用FHE.setCoprocessor(ZamaConfig.getEthereumCoprocessorConfig())把 ACL、协处理器Coprocessor与 KMS 验证器地址注入 FHE 库abstract contract ZamaEthereumConfig { constructor() { FHE.setCoprocessor(ZamaConfig.getEthereumCoprocessorConfig()); } function confidentialProtocolId() public view returns (uint256) { return ZamaConfig.getConfidentialProtocolId(); } }从源码结构看ZamaConfig库内部通过block.chainid路由配置Ethereum 主网chainId 1与 SepoliachainId 11155111走getEthereumCoprocessorConfig()本地 Hardhat/Anvil 网络chainId 31337会回落到本地配置若部署在不支持的链上getCoprocessorConfig()将抛出ZamaProtocolUnsupported错误。这意味着本教程合约默认面向以太坊系网络部署如需 Polygon 请改用ZamaPolygonConfig或ZamaMultiChainConfig。基础智能合约ERC7984Example在contracts/ERC7984Example.sol中创建我们的机密代币合约。该实现有以下关键设计点构造函数中以明文非加密金额铸造初始供应量初始铸造仅在构造时执行一次确立代币的总供应量此后所有转账将全程加密保护隐私合约同时继承ERC7984机密代币能力与Ownable2Step安全访问控制。// SPDX-License-Identifier: BSD-3-Clause-Clear pragma solidity ^0.8.27; import {Ownable2Step, Ownable} from openzeppelin/contracts/access/Ownable2Step.sol; import {FHE, externalEuint64, euint64} from fhevm/solidity/lib/FHE.sol; import {ZamaEthereumConfig} from fhevm/solidity/config/ZamaConfig.sol; import {ERC7984} from openzeppelin/confidential-contracts/token/ERC7984/ERC7984.sol; contract ERC7984Example is ZamaEthereumConfig, ERC7984, Ownable2Step { constructor( address owner, uint64 amount, string memory name_, string memory symbol_, string memory contractURI_ ) ERC7984(name_, symbol_, contractURI_) Ownable(owner) { euint64 encryptedAmount FHE.asEuint64(amount); _mint(owner, encryptedAmount); } }值得注意构造函数中的FHE.asEuint64(amount)将明文uint64转换为加密的euint64句柄。在 library-solidity/lib/FHE.sol 第 8672 行可以看到其实现是Impl.trivialEncrypt(uint256(value), FheType.Uint64)——即平凡加密trivial encrypt这种方式下明文本体仍然能被链上观察到所以本示例特意在部署阶段使用它来确立公开的总供应量而后续所有转账走真正的密文运算路径。虽然示例出于简化使用了明文的初始铸造生产环境中可以考虑使用加密铸造实现从创世块起的完全隐私实现更复杂的铸造计划minting schedule覆盖部分隐私假设。运行提示本示例要正常运行文件必须放置在对应目录下——.sol文件 →your-project-root-dir/contracts/.ts文件 →your-project-root-dir/test/这样才能保证 Hardhat 按预期完成编译与测试。完整测试套件confToken.test.ts测试文件test/confToken.test.ts使用 Hardhat 的fhevm插件生成加密输入createEncryptedInputadd64encrypt覆盖了初始化、转账、超额转账与零地址转账四条关键路径import { expect } from chai; import { ethers, fhevm } from hardhat; describe(ERC7984Example, function () { let token: any; let owner: any; let recipient: any; let other: any; const INITIAL_AMOUNT 1000; const TRANSFER_AMOUNT 100; beforeEach(async function () { [owner, recipient, other] await ethers.getSigners(); // Deploy ERC7984Example contract token await ethers.deployContract(ERC7984Example, [ owner.address, INITIAL_AMOUNT, Confidential Token, CTKN, https://example.com/token ]); }); describe(Initialization, function () { it(should set the correct name, async function () { expect(await token.name()).to.equal(Confidential Token); }); it(should set the correct symbol, async function () { expect(await token.symbol()).to.equal(CTKN); }); it(should set the correct contract URI, async function () { expect(await token.contractURI()).to.equal(https://example.com/token); }); it(should mint initial amount to owner, async function () { // Verify that the owner has a balance (without decryption for now) const balanceHandle await token.confidentialBalanceOf(owner.address); expect(balanceHandle).to.not.be.undefined; }); }); describe(Transfer Process, function () { it(should transfer tokens from owner to recipient, async function () { // Create encrypted input for transfer amount const encryptedInput await fhevm .createEncryptedInput(await token.getAddress(), owner.address) .add64(TRANSFER_AMOUNT) .encrypt(); // Perform the transfer await expect(token .connect(owner) confidentialTransfer(address,bytes32,bytes)).to.not.be.reverted; // Check that both addresses have balance handles (without decryption for now) const recipientBalanceHandle await token.confidentialBalanceOf(recipient.address); const ownerBalanceHandle await token.confidentialBalanceOf(owner.address); expect(recipientBalanceHandle).to.not.be.undefined; expect(ownerBalanceHandle).to.not.be.undefined; }); it(should allow recipient to transfer received tokens, async function () { // First transfer from owner to recipient const encryptedInput1 await fhevm .createEncryptedInput(await token.getAddress(), owner.address) .add64(TRANSFER_AMOUNT) .encrypt(); await expect(token .connect(owner) confidentialTransfer(address,bytes32,bytes)).to.not.be.reverted; // Second transfer from recipient to other const encryptedInput2 await fhevm .createEncryptedInput(await token.getAddress(), recipient.address) .add64(50) // Transfer half of what recipient received .encrypt(); await expect(token .connect(recipient) confidentialTransfer(address,bytes32,bytes)).to.not.be.reverted; // Check that all addresses have balance handles (without decryption for now) const otherBalanceHandle await token.confidentialBalanceOf(other.address); const recipientBalanceHandle await token.confidentialBalanceOf(recipient.address); expect(otherBalanceHandle).to.not.be.undefined; expect(recipientBalanceHandle).to.not.be.undefined; }); it(should revert when trying to transfer more than balance, async function () { const excessiveAmount INITIAL_AMOUNT 100; const encryptedInput await fhevm .createEncryptedInput(await token.getAddress(), recipient.address) .add64(excessiveAmount) .encrypt(); await expect( token .connect(recipient) confidentialTransfer(address,bytes32,bytes) ).to.be.revertedWithCustomError(token, ERC7984ZeroBalance) .withArgs(recipient.address); }); it(should revert when transferring to zero address, async function () { const encryptedInput await fhevm .createEncryptedInput(await token.getAddress(), owner.address) .add64(TRANSFER_AMOUNT) .encrypt(); await expect( token .connect(owner) confidentialTransfer(address,bytes32,bytes) ).to.be.revertedWithCustomError(token, ERC7984InvalidReceiver) .withArgs(ethers.ZeroAddress); }); }); });测试中传递的encryptedInput.handles[0]与encryptedInput.inputProof就是外部加密输入externalEuint64 证明的典型形态。合约侧在 library-solidity/lib/FHE.sol 第 8645 行的fromExternal中将其还原为可用的euint64当inputProof非空时走Impl.verify(...)完成证明验证当inputProof为空时则要求该句柄已经通过isAllowed检查被授权给msg.sender否则抛出SenderNotAllowedToUseHandle。运行测试在项目根目录执行以下命令即可运行测试npx hardhat test test/ERC7984Example.test.ts测试命令与 docs/examples/openzeppelin/README.md 中npm test的完整回归流程互补前者针对单个示例文件快速迭代后者在整个套件层面验证所有 OpenZeppelin 机密合约示例包括 ERC-20 包装、ERC7984↔ERC20 互换与机密 vesting 钱包详见 README 的 Available Guides 列表。高级功能与扩展基础ERC7984Example已经提供核心能力在此基础上可以进一步扩展。铸造函数Minting functions可见铸造Visible Mint—— 允许 owner 以明文金额铸造function mint(address to, uint64 amount) external onlyOwner { _mint(to, FHE.asEuint64(amount)); }适用场景公开/代币经济驱动的铸造需要透明性时例如计划性排放 scheduled emissions隐私注意铸造金额在 calldata 与事件中是可见的需要隐私时请改用confidentialMint访问控制可考虑用AccessControl例如MINTER_ROLE替代onlyOwner以适配多签工作流供应上限如需硬顶在_mint前添加检查并对可见与机密两条铸造路径一致地强制。机密铸造Confidential Mint—— 以加密金额铸造增强隐私function confidentialMint( address to, externalEuint64 encryptedAmount, bytes calldata inputProof ) external onlyOwner returns (euint64 transferred) { return _mint(to, FHE.fromExternal(encryptedAmount, inputProof)); }输入encryptedAmount与inputProof由链下 SDK 产生必须对格式错误的输入进行校验并回滚Gas 考量机密运算消耗更多 gas谨慎批量铸造优先少量大额铸造以降低开销审计金额保持私密但铸造的审计轨迹时间戳、发送者、接收者仍然可验证Hardhat SDK 调用示例const enc await fhevm .createEncryptedInput(await token.getAddress(), owner.address) .add64(1_000) .encrypt(); await token.confidentialMint(recipient.address, enc.handles[0], enc.inputProof);销毁函数Burning functions可见销毁Visible Burn—— 允许 owner 以明文金额销毁function burn(address from, uint64 amount) external onlyOwner { _burn(from, FHE.asEuint64(amount)); }机密销毁Confidential Burn—— 以加密金额销毁function confidentialBurn( address from, externalEuint64 encryptedAmount, bytes calldata inputProof ) external onlyOwner returns (euint64 transferred) { return _burn(from, FHE.fromExternal(encryptedAmount, inputProof)); }授权从任意账户销毁权限较强建议引入更严格的控制角色、多签、时间锁或采用用户同意式销毁事件策略决定是否发出仅表达意图不暴露金额的自定义事件以改善可观测性与链下索引错误面当加密金额超过余额时可能触发类似余额/授权不足的失败务必同时测试成功与回滚路径Hardhat SDK 调用示例const enc await fhevm .createEncryptedInput(await token.getAddress(), owner.address) .add64(250) .encrypt(); await token.confidentialBurn(holder.address, enc.handles[0], enc.inputProof);总供应量可见性Total supply visibility若希望 owner 能够查看总供应量便于管理用途可以覆盖_update钩子function _update(address from, address to, euint64 amount) internal virtual override returns (euint64 transferred) { transferred super._update(from, to, amount); FHE.allow(confidentialTotalSupply(), owner()); }作用在每次状态变更后将最新总供应量句柄的解密权限授予owner运行模型owner 调用confidentialTotalSupply()后使用其链下密钥材料解密返回的句柄即可读取数值安全性考量若所有权发生变更需确保只有新任 owner 能继续解密。由于使用Ownable2Step该函数会自动授权当前的owner()注意合规问题向某方开放供应量可见性属于特权访问应记录谁持有密钥及原因替代方案如需组织级访问可通过一个持有解密权限的专用管理合约来授予而不是单一 EOA。此处FHE.allow(confidentialTotalSupply(), owner())对应 library-solidity/lib/FHE.sol 第 9417 行的实现当句柄未初始化时先回退为asEuint64(0)再调用Impl.allow(...)在 ACL 中为该账户登记使用权限。这也是 fhevm 授权模型ACL KMS 验证在合约层最直接的体现——只有被显式allow的账户才能用其密钥材料解密对应句柄。小结通过本指南你已经掌握了在 fhevm 生态中构建 ERC7984 机密代币的完整路径从架构选择ERC7984Ownable2StepZamaEthereumConfig、合约实现、SDK 加密输入生成与 Hardhat 测试到可见/机密铸造、销毁与总供应量可见性等扩展模式。继续深入时可以参考仓库中 swapERC7984ToERC20.md机密代币解密换回 ERC-20 的两步流程、ERC7984ERC20WrapperMock.mdERC-20 包装为机密代币以及 vesting-wallet.md机密 vesting 钱包构建更完整的机密资产体系。【免费下载链接】fhevmFHEVM, a full-stack framework for integrating Fully Homomorphic Encryption (FHE) with blockchain applications项目地址: https://gitcode.com/GitHub_Trending/fh/fhevm创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考