FHEVM 用户解密委托怎么做:EOA 直连 ACL 与合约委托两种模式怎么选
2026/9/13 8:04:23 网站建设 项目流程

FHEVM 用户解密委托怎么做:EOA 直连 ACL 与合约委托两种模式怎么选

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

在 FHEVM 里,用户解密(user decryption)是把链上密文在客户端重加密到用户自己的 NaCl 公钥下,从而让用户在不上链的情况下读取自己的私密数据(如余额、计数器)。这套流程由 Relayer 和 KMS 完成,前提是 ACL 中已经正确记录了「谁可以在哪个合约的上下文中解密哪个 handle」。

当你想让另一个账户(比如后端服务、relayer、钱包所有者对应的 EOA)替你执行用户解密时,就需要用户解密委托(user decryption delegation):它把(delegator, contractAddress)这对用户解密权限转给(delegate, contractAddress)。FHEVM 对委托入口有明确区分——EOA 和智能合约走的是两条不同的 API,选错入口调用会直接 revert。本文基于仓库文档说明两种模式各自怎么调用、怎么选,以及如何验证委托是否生效。

两种模式:delegator 是谁,决定你该调哪个 API

委托的关键变量是delegator(权限的持有者),它取决于你调用哪个 API(见 docs/solidity-guides/acl/delegation.md):

调用方使用的 API实际 delegator(ACL 眼中的msg.sender
EOA(Externally Owned Account)直接调用 ACL 合约上的IACL.delegateForUserDecryption这个 EOA 自己
智能合约在合约函数内部调用FHE.delegateUserDecryptionaddress(this),即调用委托函数的合约

由此得到选择规则:

  • 用户(EOA)想把「自己的」解密权委托出去→ 必须走 EOA 直连 ACL。文档明确指出:FHE.delegateUserDecryption不能用于 EOA 委托自己的权限——EOA 必须直接调用 ACL。
  • 合约持有密文 handle,想把「该合约名下的」解密权委托出去→ 用FHE.delegateUserDecryption。注意contractAddress参数必须是一个别的合约,其 handle 已被允许被当前合约访问。

模式一:EOA 直接委托给后端服务

用户在客户端/脚本中直接对 ACL 合约发起调用(docs/solidity-guides/acl/delegation.md 的 Pattern 1):

import { IACL } from "@fhevm/solidity/lib/Impl.sol"; IACL(aclAddress).delegateForUserDecryption(relayer, vault, expirationDate);

三个参数分别是:被委托方relayer、密文所在的合约vault、过期时间戳expirationDate。委托生效后,relayer 就能对任何带有(EOA, vault)ACL 权限对的 handle 执行用户解密。

SDK 侧对应的委托提交实现可参考 delegation.ts,其中delegateForUserDecryption通过 viem 的simulateContract+ 发送交易完成同一笔 ACL 调用。

模式二:合约委托自己的权限

合约可以把自己已被授予的用户解密权限转给 delegate。文档给出的完整示例(Aggregator合约):

import { FHE } from "@fhevm/solidity/lib/FHE.sol"; import { ZamaEthereumConfig } from "@fhevm/solidity/config/ZamaConfig.sol"; contract Aggregator is ZamaEthereumConfig { address public immutable vault; constructor(address vault_) { vault = vault_; } function authorizeRelayer(address relayer, uint64 expirationDate) external { FHE.delegateUserDecryption(relayer, vault, expirationDate); } function revokeRelayer(address relayer) external { FHE.revokeUserDecryptionDelegation(relayer, vault); } }

FHE.delegateUserDecryption会把调用合约(address(this))针对contractAddress的用户解密权限授予delegaterevokeUserDecryptionDelegation用于撤销。

常见错误:合约里想"替 EOA 委托"

文档特别标注了一个高频踩坑点:在合约内部调用FHE.delegateUserDecryption(relayer, address(this), expiration),希望替调用它的用户委托权限——这会一直 revert,因为此时msg.sender == contractAddress,违反下文约束之一。用户的权限只能由用户自己通过模式一(EOA 直连 ACL)委托。

ACL 对委托注册的硬性约束

ACL 在注册委托时会强制以下不变量,任何一条不满足都会 revert(错误名见 docs/solidity-guides/functions.md):

  • contractAddress != address(this)—— 违反时 revert 为IACL-SenderCannotBeContractAddress(即 EOA 调用方对应的"sender 不能是密文所在合约"约束)。
  • delegate != address(this)—— revert 为IACL-SenderCannotBeDelegate
  • delegate != contractAddress—— revert 为IACL-DelegateCannotBeContractAddress
  • expirationDate > block.timestamp—— revert 为IACL-ExpirationDateInThePast
  • 对每个(delegator, delegate, contractAddress)元组,每块最多执行一次委托或撤销(one-delegate-or-revoke-per-block)。

因此:

  • expirationDate时必须用未来时间戳;若不需要过期,用delegateUserDecryptionWithoutExpiration
  • 连续批量注册/撤销多个委托时,要间隔开块,不能塞进同一块。
  • e2e 测试中还提到一种线上部署的差异:部分 live v0.11 部署的 ACL 要求过期时间距当前超过 1 小时(测试通过探测ExpirationDateBeforeOneHour()的 revert 选择器来识别并适配,见 delegatedUserDecryption.ts)。如果你面向已上线部署,先用staticCall探测短过期是否被接受,再决定expirationDate取值。

完整的 API 清单

delegation.md 与 functions.md 汇总的委托相关 API:

// Granting (caller-contract side) FHE.delegateUserDecryption(delegate, contractAddress, expirationDate); FHE.delegateUserDecryptionWithoutExpiration(delegate, contractAddress); FHE.delegateUserDecryptions(delegate, contractAddresses, expirationDate); // batch FHE.delegateUserDecryptionsWithoutExpiration(delegate, contractAddresses); // batch // Revoking FHE.revokeUserDecryptionDelegation(delegate, contractAddress); FHE.revokeUserDecryptionDelegations(delegate, contractAddresses); // batch // Querying FHE.isDelegatedForUserDecryption(delegator, delegate, contractAddress, handle); // active for handle? FHE.getDelegatedUserDecryptionExpirationDate(delegator, delegate, contractAddress); // 0 = none, max = permanent FHE.isUserDecryptable(handle, user, contractAddress); // raw ACL check, ignores delegation

注意区分最后两个查询:isDelegatedForUserDecryption检查委托链路对该 handle 是否生效;isUserDecryptable只做原始 ACL 检查(用户和合约都必须持有该 handle 的持久权限),不考虑委托

执行与验证:委托后如何确认生效

端到端的验证路径在 e2e 套件 delegatedUserDecryption.ts 中可以直接照做,主路径如下:

  1. 发起委托交易并等待回执。例如合约所有者让 Bob 的 EOA 获得解密智能钱包余额的权限:

    const expirationTimestamp = Math.floor(Date.now() / 1000) + ONE_DAY_SECONDS; const delegateTx = await smartWallet .connect(signers.bob) .delegateUserDecryption(signers.bob.address, tokenAddress, expirationTimestamp); await delegateTx.wait();
  2. 等待 coprocessor 吸收 ACL 变更。e2e 测试在委托/撤销交易之后等待约 15 个 host-chain 区块(PROPAGATION_BLOCKS = 15)再尝试解密。如果你刚提交委托就立刻解密失败,先确认 ACL 变更是否已经被 coprocessor 处理,而不是急着判定委托失败。

  3. 用委托方的签名调用 SDK 的委托用户解密,成功即证明链路打通:

    const decryptedBalance = await instances.bob.delegatedUserDecryptSingleHandle({ handle: balanceHandle, contractAddress: tokenAddress, delegatorAddress: smartWalletAddress, // 权限持有者(合约/EOA),不是请求方 signer: signers.bob, // delegate 的签名者 });

    成功判据:返回的明文与密文实际值一致(e2e 中通过对照解密后的期望值断言)。delegatorAddress必须填权限持有者——合约场景就是持有权的合约地址,EOA 场景就是发起delegateForUserDecryption的那个 EOA。

  4. 链上查询兜底:用FHE.getDelegatedUserDecryptionExpirationDate(delegator, delegate, contractAddress)确认记录存在且未过期(返回0表示不存在,max表示永久),或直接用FHE.isDelegatedForUserDecryption(delegator, delegate, contractAddress, handle)检查某个具体 handle。

失败场景与错误含义

e2e 套件覆盖了四类会被 ACL/relayer 拒绝的情况,可用于排查「委托了却解不了密」:

  • 委托已被撤销:先revokeUserDecryptionDelegation并等待传播,再用原 delegate 调用delegatedUserDecryptSingleHandle会失败,错误信息对应 SDK 的getDelegatedUserDecryptErrorMessagerevocation类型。
  • 委托不存在:从未注册过委托的账户调用,报delegation-does-not-exist类错误。
  • 委托给了错误的合约contractAddress与密文实际所在合约不一致,报contract-unauthorized类错误。
  • 委托已过期expirationDate过去后同样被拒,错误形态与错误合约场景一致(contract-unauthorized类)。

另有一条硬边界来自 ACL 本身的语义(同样由 e2e 用例验证):通配/按合约委托都不绕过 ownership——delegator 必须对该 handle 有 ACL 权限,密文所在合约也必须有权限,且委托不能传递性地二次转授。

选型小结

你的情况正确入口要点
用户自己的 EOA 想把解密权交给后端/relayerIACL(aclAddress).delegateForUserDecryption(...)EOA 亲自签名;FHE.delegateUserDecryption帮不了你
合约持有 handle,想让 relayer 代解合约内FHE.delegateUserDecryption(delegate, vault, expiration)vault必须是别的合约;撤销用FHE.revokeUserDecryptionDelegation
需要多个合约一次性委托FHE.delegateUserDecryptions/ 批量撤销每个元组每块一次,批量时注意跨块
只想读状态getDelegatedUserDecryptionExpirationDate/isDelegatedForUserDecryptionisUserDecryptable不反映委托,别用它验证委托

如果委托后解密仍失败,按 e2e 的失败矩阵依次核对:委托记录是否存在(链上查询)→ 是否已过期/被撤销 →contractAddress是否与密文所在合约一致 → ACL 变更是否已传播(等待约 15 个区块)。更多委托 API 的参数细节见 User decryption delegation,委托与 ACL 权限授予(FHE.allow)的配合关系见 Access Control List 概览 与 用户解密。

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

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

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

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

立即咨询