ARTICLE DETAIL

建站实战干货

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

fhEVM 密文句柄(Handle)完全指南:opaque bytes32 的不变量、误区与正确用法

2026/9/12 12:36:47 拓冰建站 浏览量
fhEVM 密文句柄(Handle)完全指南:opaque bytes32 的不变量、误区与正确用法 fhEVM 密文句柄Handle完全指南opaque bytes32 的不变量、误区与正确用法【免费下载链接】fhevmFHEVM, a full-stack framework for integrating Fully Homomorphic Encryption (FHE) with blockchain applications项目地址: https://gitcode.com/GitHub_Trending/fh/fhevm导读在 fhEVMFully Homomorphic Encryption Virtual Machine中链上每一个加密值——euint8、ebool、eaddress等——都通过一个 32 字节的句柄handle被引用。句柄是 Solidity 合约中唯一能够持有并传递的加密对象形态FHE 运算以句柄为输入、以句柄为输出ACL访问控制列表也逐句柄实施权限。本文以 docs/solidity-guides/handles.md 为核心结合 host-contracts/lib/FHE.sol 与 host-contracts/lib/Impl.sol 等源码实现系统讲解句柄的定义、协议保证的不变量、常见编程误区以及如何在合约中安全地判断两个加密值是否相等。读完本文你将清楚哪些关于句柄的假设是安全的、哪些一定会出错并能写出不依赖句柄具体取值的高健壮性合约。句柄是什么链上链下的分层模型要理解句柄先要分清四个容易混淆的概念。它们分处不同位置职责完全不同术语是什么存在于哪里明文Plaintext真实的未加密值例如数字42decrypt(...)返回的就是它链下仅在获得授权的解密之后密文CiphertextFHE 产生的加密数据块协处理器存储并在此基础上计算任何时刻都可以被重新随机化re-randomize而不改变明文链下由协处理器持有句柄Handle一个 32 字节的链上标识符指向某个具体的明文密文对你的 Solidity 代码持有和传递的就是句柄链上计算Computation产生某个句柄的 FHE 操作序列例如FHE.add(a, b)两个不同的计算可能产生同一个句柄同一个计算在不同上下文中也可能产生不同句柄概念性存在并不以实体形式存储协议明确保证的是句柄与明文之间的关系它刻意不保证句柄与密文之间、句柄与产生它的计算之间的任何关系。从源码层面看这个分层非常直观在 host-contracts/lib/FHE.sol 中FHE库被定义为一个library而其底层实现全部委托给Impl.sol。例如Impl.add内部调用协处理器合约的fheAdd(lhs, rhs, scalarByte)入参和返回值类型都是bytes32——这正是一个句柄在 EVM 层面的真实形态function add(bytes32 lhs, bytes32 rhs, bool scalar) internal returns (bytes32 result) { bytes1 scalarByte; if (scalar) { scalarByte 0x01; } else { scalarByte 0x00; } CoprocessorConfig storage $ getCoprocessorConfig(); result IFHEVMExecutor($.CoprocessorAddress).fheAdd(lhs, rhs, scalarByte); }参见 Impl.sol 中的 add 实现。同样地FHE.sol中所有加密类型的操作eq、select、add等最终都以bytes32句柄为输入输出。这也是为什么句柄可以被当作普通bytes32处理——它们在 EVM 中本来就是一个bytes32。核心原则句柄是 opaque不透明的fhEVM 官方文档对句柄给出了一个强警告warning把句柄当成名牌name tag而不是事物本身。句柄的字节不会告诉你它是如何产生的也不会告诉你背后是哪个密文。你能从句柄上唯一读出的信息是它指向哪个明文——而且只有在解密之后才能读出。这句话的含义是双重的你可以把句柄当作任何其他bytes32使用用/!比较、存入状态变量、记录日志。ACL 本身就是这样工作的——它以bytes32 handle为参数进行权限校验见 ACL 合约中的isAllowed(bytes32 handle, address account)。你不可以从句柄的字节猜测它是由什么计算产生的也不可以猜测它背后对应哪个密文。之所以句柄能承载 ACL 语义正是因为句柄与明文之间存在确定性对应同一明文在所有合法句柄之间保持同一性权限系统才能围绕句柄展开授权、委托与撤销。而句柄字节本身不携带任何计算或密文信息保证了不会通过句柄泄露隐私。你可以依赖什么协议的不变量协议给出了一条规则并且这条规则是单一且可依赖的如果两个句柄相等那么它们指向同一个明文。这条规则还有一个同样成立的镜像命题如果两个明文不同那么它们的句柄一定不同。除此之外的一切都不能假设。你可以依赖你不能依赖相等句柄 → 相等明文不同句柄 → 不同明文不同明文 → 不同句柄相等明文 → 相等句柄相等句柄 → 两者由同一计算产生相等句柄 → 两者底下是同一密文最后两行你不能依赖的原因从密码学角度看非常自然相等句柄 → 同一计算协议当前会把前一个区块的哈希混入句柄的构建过程详见下文常见误区因此同一个计算在两个不同区块中就会得到不同句柄。未来协议还可能对句柄做优化重写同一计算产生同一句柄的假设随时可能被打破。相等句柄 → 同一密文协处理器持有的密文可以随时被重新随机化re-randomization而不改变明文。句柄作为指向明文密文对的标识符可以在密文更新后保持不变或者反之。句柄相等只能说明明文相同与底层密文无关。协议可能在部分相等明文的计算上产生相等句柄在另一部分上产生不同句柄——跨区块、跨链、密文重新随机化之后、乃至未来的优化版本中情况都可能变化。你的合约必须在这两种情况下都能正常工作这是编写 fhEVM 合约最基本的健壮性要求。常见误区与正确的相等性判断误区一假设相同操作 相同输入 → 相同句柄这是最常见的错误。今天协议会把前一个区块的哈希混入每个句柄的构建过程所以同样的FHE.add(h1, h2)在两个不同区块中执行得到的句柄就已经不同。而反过来——假设两个不同操作一定产生不同句柄——同样危险如果协议未来把某些计算优化合并到同一个句柄这类假设就会静默出错。文档给出了明确的错误示范// ❌ 不要依赖 h3 h4也不要依赖 h3 ! h4。 euint64 h3 FHE.add(h1, h2); euint64 h4 FHE.add(h1, h2);正确做法是永远不要把句柄的相等性当作逻辑判断的依据。如果你需要知道两个加密值在明文层面是否相等应该使用 FHE 运算符FHE.eq它返回一个ebool只有当底层值真正匹配时才解密为trueebool isEqual FHE.eq(a, b);FHE.eq在源码中的实现同样走协处理器调用链FHE.eq→Impl.eq(lhs, rhs, scalar)→IFHEVMExecutor(...).fheEq(lhs, rhs, scalarByte)见 Impl.sol 中的 eq 实现返回的是新的句柄。也就是说相等性判断本身也是加密计算结果仍然以句柄形式存在需要授权后才能解密。误区二混用来自不同来源的句柄一个从其他链桥接过来的句柄或者链下构造好、通过FHE.fromExternal(...)引入的句柄并不保证与链上计算产生的句柄相等——即使二者编码的是同一个明文。这一点从fromExternal的源码语义可以看得更清楚。在 FHE.sol 的 fromExternal 实现 中当传入inputProof时句柄要经过Impl.verify的密文校验当inputProof为空时句柄必须已经通过 ACL 的isAllowed(inputBytes32, msg.sender)检查否则会触发SenderNotAllowedToUseHandle回退function fromExternal(externalEuint8 inputHandle, bytes memory inputProof) internal returns (euint8) { if (inputProof.length ! 0) { return euint8.wrap(Impl.verify(externalEuint8.unwrap(inputHandle), inputProof, FheType.Uint8)); } else { bytes32 inputBytes32 externalEuint8.unwrap(inputHandle); if (inputBytes32 0) { return asEuint8(0); } if (!Impl.isAllowed(inputBytes32, msg.sender)) revert SenderNotAllowedToUseHandle(inputBytes32, msg.sender); return euint8.wrap(inputBytes32); } }外部句柄与本地计算句柄的生成路径完全不同一个来自输入验证与 ACL 放行一个来自协处理器的 FHE 运算因此不满足句柄相等 → 明文相等这一不变量的反向推理。跨链桥场景同样如此桥接协议重新注册句柄句柄与明文的映射关系由目标链上的桥接逻辑重新建立与原链的句柄取值没有可依赖的对应关系。关键提醒句柄不变量是单向的把两个误区放在一起可以得到一张完整的推理安全图✅ 相等句柄 → 相等明文可以依赖✅ 不同明文 → 不同句柄可以依赖❌ 不同句柄 → 不同明文不可依赖❌ 相等明文 → 相等句柄不可依赖❌ 句柄相等 → 同一计算不可依赖❌ 句柄相等 → 同一密文不可依赖协议只给出句柄到明文的正向确定性反向与跨维度的一切推测都是危险的。这条不变量是 fhEVM 合约开发中最重要的心智模型之一。句柄与加密类型、ACL 的协同加密类型是句柄的安全包装在 fhEVM 中euint8、ebool、eaddress等加密类型本质上是对bytes32句柄的类型安全包装。文档 docs/solidity-guides/types.md 明确指出fhEVM 中的加密整数以 FHE 密文表示并通过密文句柄进行抽象这些以e为前缀的类型例如euint64是密文句柄之上的安全包装。对应地FHE.sol中所有操作都围绕*_wrap/*_unwrap展开。例如and运算function and(ebool a, ebool b) internal returns (ebool) { if (!isInitialized(a)) { a asEbool(false); } if (!isInitialized(b)) { b asEbool(false); } return ebool.wrap(Impl.and(ebool.unwrap(a), ebool.unwrap(b), false)); }见 FHE.sol 中的 and 实现。isInitialized通过unwrap(v) ! 0判断句柄是否为 0即未初始化未初始化的句柄按默认值参与运算。这正是句柄可当作普通 bytes32 比较在实际代码中的体现——类型系统利用句柄的零值约定实现了初始化检查。ACL 以句柄为最小权限单元ACL访问控制列表合约 host-contracts/contracts/ACL.sol 的职责是控制谁能访问、计算或解密 fhEVM 中的加密值。它提供的核心接口都以bytes32句柄为参数allow(bytes32 handle, address account)允许某账户使用某句柄ACL.solallowTransient(bytes32 ciphertext, address account)仅限当前交易内的临时授权协处理器合约始终可以调用ACL.solisAllowed(bytes32 handle, address account)查询账户是否被允许使用句柄ACL.sol。因为句柄与明文存在确定性对应ACL 才能围绕句柄实施精确到值的权限控制又因为句柄本身不泄露任何信息ACL 的授权记录不会暴露加密数据的任何内容。二者相辅相成构成了 fhEVM 的隐私访问模型。实战建议编写不依赖句柄取值的安全合约综合文档与源码可以总结出以下可直接落地的编码规范把句柄当bytes32用但只做存储、传递、ACL 授权句柄可以作为状态变量持久化可以在函数间传递也可以作为allow/isAllowed等 ACL 调用的参数。这些用法不依赖句柄的具体取值。绝不把句柄相等性当业务逻辑无论是h3 h4还是h3 ! h4的依赖都应从代码中移除。同一操作在不同区块产生不同句柄是当前协议的既定行为未来优化还可能进一步改变句柄生成方式。用FHE.eq(a, b)判断加密值是否相等这是协议提供的、唯一正确的明文相等性判断方式。返回的ebool仍需通过授权的解密流程才能读出true/false。警惕跨来源句柄跨链桥接、FHE.fromExternal引入的句柄与本地计算句柄之间不存在可依赖的相等性关系。需要比较时同样使用FHE.eq。利用未初始化约定零值句柄0表示未初始化FHE.isInitialized系列函数见 FHE.sol以此为基础实现可用于防御性地校验输入句柄是否有效。小结句柄是 fhEVM 中连接链下密文世界与链上合约世界的唯一桥梁协议保证相等句柄 → 相等明文和不同明文 → 不同句柄两条不变量而其他一切关系——句柄与密文、句柄与计算、不同句柄与明文——都不在保证范围之内。理解并遵守这套不变量是编写健壮、可长期演进的 fhEVM 智能合约的前提。当你需要比较加密值时记住一句口诀不要比较句柄使用FHE.eq。【免费下载链接】fhevmFHEVM, a full-stack framework for integrating Fully Homomorphic Encryption (FHE) with blockchain applications项目地址: https://gitcode.com/GitHub_Trending/fh/fhevm创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考