fhevm Host Contracts 实战指南:在宿主链上部署 FHEVM、DAO 升级与跨链 KMS 状态镜像
【免费下载链接】fhevmFHEVM, a full-stack framework for integrating Fully Homomorphic Encryption (FHE) with blockchain applications项目地址: https://gitcode.com/GitHub_Trending/fh/fhevm
本篇指南围绕开源仓库 fhevm 中host-contracts这一 Node 包展开,它承载了在任意宿主 EVM 区块链(Host EVM Blockchain)上部署一套完整 FHEVM 实例所需的全部核心 Solidity 合约。读者将掌握三件事:如何初始化依赖并通过 Forge 运行合约测试;如何以“仅准备不生效”的方式为FHEVMExecutor做 DAO 驱动升级;以及如何理解并操作 Ethereum(canonical 链)与 Polygon 等非 canonical 宿主链之间共享的ProtocolConfig状态——包括镜像方法、导出/审核/应用三步初始化流程。读完本文,你可以独立完成一条非 canonical 宿主链从零初始化、以及后续 KMS 上下文/epoch 轮转的镜像操作。
背景:host-contracts 在 FHEVM 架构中的位置
fhevm 是一个将全同态加密(FHE)能力与区块链应用集成的全栈框架。其中host-contracts(位于仓库根目录下的 host-contracts 目录)专门提供“宿主链侧”的后端合约,其核心职责是:
- 管理 FHE 密文的访问控制(
ACL)与密文输入验证(InputVerifier); - 校验 KMS 签名与节点信息(
KMSVerifier),并在 canonical 链上执行 KMS 上下文/epoch 的生成与确认(KMSGeneration); - 记录协议级配置与 KMS 上下文状态(
ProtocolConfig),并负责将该状态跨链同步到非 canonical 宿主链; - 承载用户合约执行入口(
FHEVMExecutor)与 HCU 资源限制(HCULimit)。
与面向应用开发者的 library-solidity(提供FHE.sol等用户侧库)不同,host-contracts 面向的是链的运营者:谁要部署一条新的 FHEVM 宿主链、谁要升级链上组件、谁要管理多链 KMS 状态,都需要直接与这一层打交道。
快速开始:安装依赖与运行测试
host-contracts同时使用 Hardhat(TypeScript 任务)与 Foundry(Forge 测试)。从仓库根目录进入该包后:
npm install安装完成后,如果之前未拉取 Soldeer 依赖(如 OpenZeppelin 合约),需要先执行:
npm run forge:soldeer npm run test:forge对应脚本定义在 host-contracts/package.json 中:forge:soldeer执行forge soldeer install,test:forge执行forge test。包内还提供了常用的 Hardhat 辅助脚本,例如:
npm run compile:先部署空的 UUPS 代理(含--with-kms-generation true路径与PauserSet),再执行 hardhat compile;npm run test:运行hardhat test;npm run test:gas:仅运行名字匹配Gas的测试用例;npm run mock:daemon/mock:query/mock:encrypt:启动本地 mock coprocessor 守护进程(用于本地链路联调)。
合约清单总览
host-contracts/contracts目录(contracts)下按职责划分为多个子目录与顶层合约:
| 文件 / 目录 | 职责 |
|---|---|
ACL.sol、ACLEvents.sol | 密文访问控制列表及其事件定义,管理谁能对密文执行操作 |
FHEVMExecutor.sol | 用户合约执行入口,负责将 FHE 操作调度到 coprocessor 网络 |
InputVerifier.sol | 验证 coprocessor 签名的密文输入 |
KMSVerifier.sol | 校验 KMS 签名、节点与上下文生命周期信息 |
KMSGeneration.sol | 仅在 canonical 宿主链部署,驱动 KMS 上下文/epoch 的生成与确认 |
ProtocolConfig.sol | 协议级配置与 KMS 上下文/epoch 状态的唯一事实来源,并提供跨链镜像入口 |
HCULimit.sol | 宿主链 HCU(Homomorphic Computation Unit)用量限制 |
bridge/ | 跨链桥(ConfidentialBridge)相关合约,基于 LayerZero |
emptyProxy/、immutable/ | 供首次部署与不可变参数使用的辅助代理与不可变合约 |
interfaces/ | 各合约的接口与事件定义(如IProtocolConfig.sol) |
本文后续聚焦 README 重点阐述的两个运维主题:FHEVMExecutor 的 DAO 驱动升级与ProtocolConfig 的多链状态镜像。
Prepare-only executor 升级:DAO 驱动的 FHEVMExecutor 升级
为什么需要“仅准备”模式
FHEVMExecutor是 UUPS 可升级合约,其代理(proxy)地址对用户合约固定不变。常规的task:upgradeFHEVMExecutor会一次性完成“导入代理 → 部署新实现 → 原地升级”。但治理驱动的升级(尤其是 DAO 提案)需要把“部署新实现”和“切换代理”两个动作解耦:先离线准备好实现地址与reinitializeV*调用数据,供多签/DAO 审核签名,再由治理提案最终执行。
task:prepareUpgradeFHEVMExecutor正是为此设计,其行为(定义见 host-contracts/tasks/upgradeContracts.ts)包括:
- 将现有代理
forceImport进 OpenZeppelin 的升级 manifest(使升级校验基于真实部署状态); - 通过
prepareUpgrade(kind: 'uups')部署新的实现合约,但不触碰代理; - 打印新实现地址、
reinitializeV*函数签名与 calldata,以及外层upgradeToAndCall(address,bytes)的完整 calldata; - 可选地等待 2 分钟后对实现合约执行区块浏览器源码验证。
执行前置条件:地址文件必须与目标环境一致
contracts/FHEVMExecutor.sol会importaddresses/FHEVMHostAddresses.sol,把 ACL、KMSVerifier 等兄弟合约地址编译进实现字节码。因此运行升级任务前,磁盘上的地址生成文件必须与当前正在升级的环境一致。若从零生成或切换环境,应按顺序执行以下 setter 任务(顺序很重要:setACLAddress会重写两份文件,其余任务在其基础上追加):
npx hardhat task:setACLAddress --address <acl> npx hardhat task:setFHEVMExecutorAddress --address <executor-proxy> npx hardhat task:setKMSVerifierAddress --address <kms> npx hardhat task:setInputVerifierAddress --address <input-verifier> npx hardhat task:setHCULimitAddress --address <hcu-limit> npx hardhat task:setPauserSetAddress --address <pauser-set>这些命令生成两份文件:
host-contracts/addresses/.env.hosthost-contracts/addresses/FHEVMHostAddresses.sol
喂给 setter 的地址值应来自当前线上环境的真实部署。一个实用的权威来源是代理背后现有实现合约的已验证源码包(verified source bundle)中的addresses/FHEVMHostAddresses.sol。
运行 prepare 任务
npx hardhat task:prepareUpgradeFHEVMExecutor \ --network sepolia \ --current-implementation previous-contracts/FHEVMExecutor.sol:FHEVMExecutor \ --new-implementation contracts/FHEVMExecutor.sol:FHEVMExecutor \ --verify-contract true参数说明:
--network:选择实现合约部署交易发往的链(如 sepolia);--current-implementation:磁盘上保存的旧实现源码路径与合约名(路径:合约名格式);--new-implementation:当前 checkout 中的新实现源码;--use-internal-proxy-address true(可选):代理地址改为从addresses/.env.host读取,而不是从环境变量读取;--verify-contract(默认true):部署完成后等待 2 分钟并对实现执行 Etherscan 类区块浏览器验证。
一个关键实现细节:任务在重新编译前会先执行hardhat clean(见 host-contracts/tasks/upgradeContracts.ts 中compileImplementations对compile:specific的调用及 README 的说明),确保实现不是针对另一环境编译出的过期 artifact 构建的。源码中还会校验新旧实现 artifact 的contractName与预期一致,且新实现必须包含reinitializeV*前缀的重初始化函数,否则直接报错拒绝执行。
与常规升级任务的对比
| 任务 | 是否修改代理 | 用途 |
|---|---|---|
task:upgradeFHEVMExecutor | 是(立即生效) | 私钥直接持有者快速升级 |
task:prepareUpgradeFHEVMExecutor | 否(仅打印 calldata) | DAO 提案:先部署实现、打印upgradeToAndCall载荷供签名 |
同模式的任务还覆盖ACL、KMSVerifier、ProtocolConfig、KMSGeneration、InputVerifier、HCULimit等全部可升级组件(task:prepareUpgrade*系列),其中ProtocolConfig升级时还会带上由环境变量构建的reinitializeV*参数(buildProtocolConfigReinitializeArgs)。另外首次部署ConfidentialBridge走task:prepareUpgradeConfidentialBridge,其内部调用initializeFromEmptyProxy并校验LZ_ENDPOINT_ADDRESS与--dst-eids/--dst-chain-ids配对。
事件消费者注意:KMSVerifier 生命周期事件已迁移
迁移到 canonical 的ProtocolConfig状态之后,KMSVerifier不再发射上下文生命周期事件。链下消费者(如 relayer、listener、KMS-connector)应改从ProtocolConfig合约订阅事件(地址见host-contracts/addresses/FHEVMHostAddresses.sol中的protocolConfigAdd条目):
旧:
KMSVerifier.NewContextSet(uint256,address[],uint256)新:
ProtocolConfig.NewKmsContext(uint256,uint256,KmsNodeParams[],KmsThresholds,string,PcrValues[])旧:
KMSVerifier.KMSContextDestroyed(uint256)新:
ProtocolConfig.KmsContextDestroyed(uint256)
新事件签名与 host-contracts/contracts/interfaces/IProtocolConfig.sol 中定义一致,额外携带 epochId、节点参数、阈值、软件版本与 PCR 值,信息量比旧事件更完整。
宿主部署角色:canonical 与非 canonical
task:deployAllHostContracts强制要求显式传入--with-kms-generation取值,以明确本次部署的宿主链角色:
npx hardhat task:deployAllHostContracts --with-kms-generation true # canonical 宿主链 npx hardhat task:deployAllHostContracts --with-kms-generation false # 非 canonical 宿主链同一份合约,多条链:canonical 是唯一事实来源
Ethereum 是 canonical 宿主链——KMS 上下文/epoch 状态的唯一事实来源,完整的生命周期只在其上运行:治理方开启一个上下文/epoch(defineNewKmsContextAndEpoch/defineNewEpochForCurrentKmsContext),KMS 签名者达成法定人数(confirmKmsContextCreation/confirmEpochActivation)后状态才激活。KMSGeneration只部署在 Ethereum 上。
其他每条宿主链上部署的是同一份ProtocolConfig合约(不存在单独的"多链合约"),但这些非 canonical 宿主链(如 Polygon)是只读副本(read-replica):它们不运行生命周期/法定人数路径,因为 KMS 重分片与远程证明只在 Ethereum 上发生一次。它们没有KMSGeneration,唯一的写入路径就是下文介绍的镜像方法。
镜像方法:非 canonical 链的写入路径
mirrorKmsContextAndEpoch与mirrorKmsEpoch是副本(replica)追踪 Ethereum 状态的方式。两者都是onlyACLOwner且绕过确认法定人数——副本无法重跑 MPC 远程证明,因此信任运营者导入 Ethereum 已最终确定的状态,并使其立即变为Active。
合约侧实现(host-contracts/contracts/ProtocolConfig.sol):
mirrorKmsContextAndEpoch(contextId, epochId, kmsNodeParams, thresholds, softwareVersion, pcrValues)——导入一个上下文及其首个 epoch 为 active 状态,发射MirrorKmsContextAndEpoch事件。内部先校验contextId > latestActiveKmsContextId(否则 revertNonIncreasingKmsContextId)、epochId > epochCounter(否则 revertNonIncreasingEpochId),再_storeAndActivateKmsContextAndEpoch落库。mirrorKmsEpoch(contextId, epochId)——推进已镜像上下文的 active epoch,发射MirrorKmsEpoch事件。要求contextId等于当前 active 上下文且该上下文仍存活,epoch 同样必须严格递增。
严格递增:唯一的链上防回滚守卫
ID 必须严格递增——这是唯一的链上守卫,防止状态回滚。间隔(gap)是允许的:在 Ethereum 上被中止或从未激活的上下文/epoch 只是永远不会被镜像而已。但没有任何机制阻止副本漂移:如果一次镜像调用被跳过或乱序应用,副本状态就会落后。按序将 Ethereum 的每次轮转回放到每个副本,是运营者的责任。
任务层(定义在 host-contracts/tasks/mirrorKmsContext.ts)提供了两组镜像任务,均遵循“build calldata(DAO 路径,从不广播)/ broadcast(devnet / test-suite 无 DAO 路径)”的约定:
- 上下文切换:
task:buildMirrorKmsContextAndEpochCalldata/task:mirrorKmsContextAndEpoch - 同节点集 epoch 轮转:
task:buildMirrorKmsEpochCalldata/task:mirrorKmsEpoch
这四者共享同一组 CLI 参数:--canonical-rpc-url(canonical 链 RPC)、--canonical-protocol-config-address、可选的--block-number(默认读取 latest finalized 区块)与可选的--use-internal-proxy-address。
上下文切换路径有一个额外的安全校验:它从 canonical 读取 active 上下文锚(getKmsContextAnchor)定位NewKmsContext事件的发射区块,读取该事件中的节点/软件版本/PCR 数据后,用 ABI 编码重新计算contextInfoHash并与链上锚比对;不一致直接抛错(tasks/mirrorKmsContext.ts中readCanonicalContextSwitch的computeContextInfoHash与哈希比对)。事件数据在 ETH 上不存在(MPC 字段不在存储内),因此节点数据只能从事件取回,这个哈希交叉校验保证了事件数据与链上锚一致。广播前还会断言副本确实需要切换/推进(assertReplicaNeedsContextSwitch/assertReplicaNeedsEpochMirror),给出明确的错误信息而不是裸 revert。
从 canonical 链初始化非 canonical 的 ProtocolConfig
Ethereum 的ProtocolConfig是协议状态的唯一事实来源,因此新的宿主链要从它播种副本。整个流程是 artifact 驱动的——每个环境都走同样的三步。
第 1 步:导出 canonical KMS 上下文为可审核的 JSON artifact
该步骤只需 RPC 访问,可从干净的 checkout 直接运行:
npx hardhat task:exportCanonicalProtocolConfig \ --canonical-rpc-url https://mainnet.example \ --canonical-protocol-config-address 0x... \ --out canonical-protocol-config-snapshot.jsonartifact 持有一个export对象:它是将快照展开为扁平KEY=value映射,bigint 序列化为十进制字符串。每个 key 会成为第 3 步 apply 任务读取的环境变量。其底层实现在 host-contracts/tasks/protocolConfigMirror.ts 的readCanonicalSnapshot:先做 RPC 握手(getNetwork+getBlock,默认钉在finalized区块),再做合约身份/版本前缀校验,随后一次性读取 active 上下文/epoch、节点集与四个阈值。
第 2 步:审核
所有读取都发生在同一个区块上,因此审核者(如 DAO 签名者)可以通过重新运行带--block-number <N>的导出并 diff 输出,逐字节复现artifact——即使之后发生了defineNewKmsContextAndEpoch轮转也不受影响。
第 3 步:应用
将审核通过的 artifact 应用到本地的ProtocolConfig代理上。两种环境运行相同的 prepare 步骤——部署实现并构建upgradeToAndCall(initializeFromCanonical(contextId, epochId, …))载荷,让副本落在 canonical 的 active 上下文/epoch 上,而不是从本地新计数器开始。两者的差异仅在谁执行该载荷:
| 环境 | 任务 | 签名者 |
|---|---|---|
| devnet / local | task:deployProtocolConfigFromCanonical | DEPLOYER_PRIVATE_KEY |
| testnet / mainnet | task:prepareDeployProtocolConfigFromCanonical | DAO 执行打印出的upgradeToAndCall载荷 |
# devnet:用部署者私钥直接升级 npx hardhat task:deployProtocolConfigFromCanonical # testnet/mainnet:部署实现并打印 DAO 载荷,不触碰代理 npx hardhat task:prepareDeployProtocolConfigFromCanonical导出 key 对照表
两个 apply 任务都从环境变量读取配置,不接受任何命令行 flag传入 canonical 状态——由部署平台把值注入部署容器。任务会在部署任何东西之前拒绝坏值,因此配置错误的环境不会动到代理。导出 key 如下:
| Variable | 类型 | 含义 |
|---|---|---|
CANONICAL_CHAIN_ID | decimal string | canonical 宿主链的 chain id。 |
CANONICAL_PROTOCOL_CONFIG_ADDRESS | address | 快照读取自的 canonicalProtocolConfig地址。 |
CANONICAL_BLOCK_NUMBER | decimal string | 快照钉住的区块号。 |
CANONICAL_BLOCK_HASH | 32-byte hex | 该区块的哈希。 |
CANONICAL_KMS_CONTEXT_ID | decimal string | 要镜像的 active KMS context id。 |
CANONICAL_EPOCH_ID | decimal string | 要镜像的 active KMS epoch id。 |
CANONICAL_KMS_NODES | JSON array | KMS 节点集,每个节点一个 JSON 对象。 |
CANONICAL_KMS_THRESHOLDS | JSON object | 四个阈值,每个为 decimal string。 |
context id、epoch id、节点集与阈值会成为initializeFromCanonical的 calldata,因此任务会对它们完整校验。chain id 与区块号属于溯源信息,任务按十进制字符串解析。区块哈希与地址同样是溯源信息,任务只检查存在性并打印。
合约侧的initializeFromCanonical(host-contracts/contracts/ProtocolConfig.sol)带有onlyFromEmptyProxy与reinitializer守卫,并校验canonicalContextId >= KMS_CONTEXT_COUNTER_BASE + 1、canonicalEpochId >= EPOCH_COUNTER_BASE + 1,随后_storeAndActivateKmsContextAndEpoch一次性落库并激活。注意该函数只接收四个入参(contextId、epochId、节点、阈值):MPC 元数据(partyId、mpcIdentity、caCert、storagePrefix)不会被存储,因此镜像路径用确定性占位符填充(partyId从 0 编号、mpcIdentity为空串、caCert为0x、storagePrefix为空串),见buildCanonicalUpgradeProposal中的注释说明——副本存储的节点集仍与 canonical 完全一致。
职责边界:任务只校验“环境给了什么”
只有第 1 步会访问 canonical 链。任务只检查环境变量给它们的值,不会证明这些值与审核过的 artifact 一致——这个绑定关系由部署平台负责。两个 apply 任务都会打印decodedArgs(载荷编码的 context id、epoch id、节点集与阈值),以及 chain id、区块号、区块哈希与 canonicalProtocolConfig地址。运营者需要把每个溯源值与 artifact 逐一比对;由于打印的节点集带占位 MPC 字段,需逐字段比对。
全栈部署与后续轮转
部署一整套非 canonical 宿主栈时,task:deployAllHostContracts --protocol-config-source canonical会把镜像与其余宿主合约串行执行,使用相同的环境变量(fhevm-cli 多链栈正是如此,因此 e2e 测试用与生产完全相同的方式播种非 canonical 链)。
后续的 canonical 轮转则分别用:
task:buildMirrorKmsContextAndEpochCalldata/task:mirrorKmsContextAndEpoch——上下文切换(更换签名者集合);task:buildMirrorKmsEpochCalldata/task:mirrorKmsEpoch——同节点集 epoch 轮转。
两者都通过--canonical-rpc-url/--canonical-protocol-config-address读取 canonical 的 active KMS 上下文/epoch(上下文切换对还会用恢复出的NewKmsContext事件数据与 canonical 的contextInfoHash锚交叉校验),然后调用副本的mirrorKmsContextAndEpoch/mirrorKmsEpoch(定义于 host-contracts/tasks/mirrorKmsContext.ts)。
小结:一张图看懂状态流
Ethereum(canonical)侧:治理defineNewKmsContextAndEpoch→ KMS 签名者confirmKmsContextCreation/confirmEpochActivation→ 状态Active,NewKmsContext/KmsContextDestroyed事件由ProtocolConfig发射。非 canonical 侧:mirrorKmsContextAndEpoch/mirrorKmsEpoch(onlyACLOwner、严格递增、跳过法定人数)直接把 Ethereum 已确定的状态导入为Active,发射MirrorKmsContextAndEpoch/MirrorKmsEpoch。新增宿主链则通过“导出 → 审核 → 应用”三步,以initializeFromCanonical直接播种 canonical 的 active 状态。
这套设计把多签共识集中到一条链上(MPC 重分片与远程证明只跑一次),其余宿主链只消费结果;代价是副本的最终一致性完全依赖运营者按序回放每一次轮转。理解“canonical 是唯一事实来源、副本是受信任的镜像”这一心智模型,是正确运维多链 FHEVM 的关键。
【免费下载链接】fhevmFHEVM, a full-stack framework for integrating Fully Homomorphic Encryption (FHE) with blockchain applications项目地址: https://gitcode.com/GitHub_Trending/fh/fhevm
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考