机制详解:ZKPoK 验证、Compact 密文列表与 KMS 签名流程)
fhEVM 加密输入Inputs机制详解ZKPoK 验证、Compact 密文列表与 KMS 签名流程【免费下载链接】fhevmFHEVM, a full-stack framework for integrating Fully Homomorphic Encryption (FHE) with blockchain applications项目地址: https://gitcode.com/GitHub_Trending/fh/fhevm导读本文基于 coprocessor/docs/fundamentals/fhevm/inputs.md 展开系统讲解 fhEVM 中加密输入的完整链路用户如何把明文加密为 FHE 密文、如何通过零知识证明ZKPoK绑定到特定合约与调用者、KMS 如何签发签名、以及智能合约如何通过FHE.fromExternal()将外部密文转换为可在链上参与 FHE 运算的句柄。阅读本文后你将掌握 fhEVM / fhEVM-coprocessor 中输入体系的全部概念einput、externalEuint*、CVerificationStructForKMS、InputVerifier并能直接写出可运行的接收加密输入的 Solidity 合约。什么是 fhEVM 中的输入Inputs在 fhEVM 语境下Inputs 特指用户发送给 fhEVM 原生区块链或 fhEVM-coprocessor 的加密数据。这些数据以 FHE 密文ciphertext形式存在。一个典型例子是调用 ERC20 的transfer函数时转账金额就是一个加密输入——链上任何节点、验证者、矿工都无法看到明文金额只能基于密文完成同态运算。与普通 EVM 的公开入参不同加密输入从诞生起就面临两个核心问题保密性密文不能被任何人解密也不能被绕过式地利用见下文 ZKPoK 章节的威胁模型可验证性链上合约必须能确认这份密文是合法产生的而不是攻击者手工构造的恶意密文。正是这两个问题催生了 fhEVM 输入体系中最重要的两个设计ZKPoK零知识证明与Compact Input Lists紧凑输入列表。ZKPoK为加密输入加上合法性证明为什么需要 ZKPoK如果没有额外的保护措施机密数据存在多种泄露路径任何人直接解密密文任何人在密文上做任意计算例如对别人的密文加 0产生一个新密文后再设法解密——这等于把其他用户的密文当作跳板进行恶意解密ciphertext malleability密文延展性攻击把密文塞进一个恶意合约由该合约触发解密流程。此外还有一个更底层的问题如果允许用户发送任意密文包括格式错误或恶意构造的密文在特定方案下可能泄露 FHE 私钥的相关信息。因此输入必须被约束为良构且可审计的密文。ZKPoK 保证的三件事为此fhEVM 采用零知识证明Zero-Knowledge Proof of KnowledgeZKPoK证明输入 FHE 密文满足密文是良构的well-formed——加密过程被正确执行密文不是随意拼凑的字节用户知道对应的明文值——即证明者确实持有该明文的知识输入密文只能被用于某个特定的智能合约——将密文与目标合约绑定防止跨合约搬运密文类攻击。从 host-contracts/contracts/InputVerifier.sol 的注释可以看到链上真正执行输入校验的是InputVerifier合约它被FHEVMExecutor内部的verifyInput函数调用负责对用户加密输入的签名进行验证。KMS 签名KMS_S与链上验证ZKPoK 的验证工作由KMSKey Management System完成。当 KMS 验证通过后会向用户返回一个签名KMS_S。此后当用户把输入字节数组传给FHE.fromExternal()函数、将密文转换为可参与 FHE 运算的句柄handle时链上只需要验证KMS_S即可——这比在链上重新跑一遍 ZKPoK 校验要快得多也是整个设计的关键性能取舍。这一点在源码中体现得非常直接host-contracts/lib/FHE.sol中每一种外部加密类型都对应一个fromExternal重载例如externalEuint32function fromExternal(externalEuint32 inputHandle, bytes memory inputProof) internal returns (euint32) { if (inputProof.length 0) { return euint32.wrap(Impl.verify(externalEuint32.unwrap(inputHandle), inputProof, FheType.Uint32)); } ... }见 host-contracts/lib/FHE.solfromExternal在收到非空inputProof时调用 Impl.verify其内部最终进入InputVerifier.verifyInput完成对 KMS 签名的 EIP-712 校验。同一组fromExternal重载也存在于 library-solidity/lib/FHE.sol覆盖externalEbool、externalEuint8/16/32/64/128/256与externalEaddress供不同仓库的合约工程引用。补充inputProof为空的fromExternal调用不会做任何验证仅把外部句柄重新包装为内部句柄用于链上合约之间传递已获验证的句柄toExternal/fromExternal成对使用详细说明可见 library-solidity/lib/FHE.sol。Compact Input Lists把多个密文打包成一个字节数组einput 与输入列表FHE 密文的体积远大于普通 calldata为显著缩减加密输入的大小fhEVM 引入了compact lists紧凑列表特性把多个密文值高效地打包pack在一起。即使调用只有一个输入也能受益当一次合约调用携带多个输入时收益更加明显。列表中的每一个密文由一个einput类型引用——einput本质上就是类型 索引eInput type index指向列表中的某一个密文。整个列表被序列化成一个字节数组bytes在合约调用时一并传入。实战示例Adder 合约原文档给出了一个完整的 Adder 合约示例其结构完全对应真实仓库中的用法// SPDX-License-Identifier: BSD-3-Clause-Clear pragma solidity ^0.8.24; import fhevm/lib/FHE.sol; contract Adder { euint32 result; function add(externalEuint32 inputA, externalEuint32 inputB, bytes calldata inputProof) public { euint32 a FHE.fromExternal(inputA, inputProof); euint32 b FHE.fromExternal(inputB, inputProof); result FHE.add(a, b); FHE.allow(result, address(this)); } }要点解读inputA、inputB是externalEuint32类型即指向紧凑列表中某两个密文的引用einput的具体体现序列化后的整个列表就是inputProof同一个inputProof被用于两次FHE.fromExternal调用说明一个证明字节数组可以同时证明列表中的多个密文inputProof中不仅包含序列化的密文列表还包含 ZKPoK以及 KMS 签名见下文InputVerifier的字节布局转换得到的euint32 a、euint32 b可以像普通加密类型一样参与FHE.add最后通过FHE.allow授权当前合约后续对result进行解密等操作。inputProof 的二进制布局源码级拆解紧凑列表最终落到链上时其inputProof字节布局可以在 host-contracts/contracts/InputVerifier.sol 的注释中看到精确描述inputProof numHandles numSigners handles coprocessorSignatures (1 1 32*numHandles 65*numSigners extraData 字节)即字段大小含义numHandles1 字节列表中句柄handle的数量numSigners1 字节提供签名的 KMS/协处理器签名者数量handles32 × numHandles 字节每个密文对应的句柄coprocessorSignatures65 × numSigners 字节每个签名者的 65 字节 ECDSA 签名r/s/vextraData剩余字节附加元数据而inputHandle本身也是一个精心编码的 32 字节值inputHandle keccak256(keccak256(bundleCiphertext)index)[0:20] index[21] chainId[22:29] type[30] version[31]其中前 20 字节由密文包哈希与索引共同派生其余字节携带索引、链 ID、FHE 类型、句柄版本等元信息。InputVerifier.verifyInput在 host-contracts/contracts/InputVerifier.sol 中会从inputHandle恢复chainId若与block.chainid不一致则revert InvalidChainId()——这正是密文只能用于特定链的实现从inputHandle提取indexHandle校验其合法范围numHandles indexHandle || indexHandle 254时报InvalidIndex校验列表中所有句柄的版本号HANDLE_VERSION用 EIP-712 结构化数据CiphertextVerification(ctHandles, userAddress, contractAddress, contractChainId, extraData)对签名做多签名阈值验证_verifyEIP712/_verifySignaturesDigest支持多个 KMS 签名者 阈值机制见 host-contracts/contracts/InputVerifier.sol将已验证的 proof 写入瞬态存储transient storage缓存同一交易内重复使用同一 proof 不再重复验签降低 gas 开销host-contracts/contracts/InputVerifier.sol。JS 侧的InputProof序列化与解析与之对应可见 sdk/js-sdk/src/core/coprocessor/InputProof-p.ts从原始字节中依次读出numHandles、numSignatures、各句柄、各签名与extraData。输入机制的完整流程处理加密输入分为三步① 从 Gateway 获取公钥材料 → ② 加密明文并计算 ZKPoK → ③ 像普通输入一样在合约中使用。第一步公钥材料与 CRS 的获取生成加密输入的第一步是获取与区块链关联的FHE 公钥材料。用户通过Gateway获取Gateway 暴露/keys端点在 relayer 实现中对应/v2/keyurl端点见 relayer/src/http/endpoints/v2/handlers/keyurl.rs返回FHE 公钥、CRSCommon Reference String以及对应的签名用户可以使用KMSVerifier 智能合约对这些材料进行验证确保拿到的公钥/CRS 确实来自可信的 KMS相关合约见 host-contracts/contracts/KMSVerifier.sol。/v2/keyurl响应中的 CRS 以参数规模如2048为键组织其类型定义在 relayer/src/http/endpoints/v2/types/keyurl.rs用户 SDK 正是据此获取与当前链参数匹配的 CRS 材料。第二步加密阶段Encryption Phase用户拿到公钥材料后执行用 FHE 公钥加密明文得到密文C在客户端计算证明ZkPoK将C绑定到contractAddress与callerAddress——即这份密文只能被特定合约、由特定调用者使用将(C, contractAddr, callerAddr, ZKPoK)提交给 Gateway由 KMS 验证并签名。原文档定义了以下记号C 密文使用区块链 FHE 公钥加密ZKPoK 零知识证明在用户侧计算eInput 类型 索引S 签名即 KMS_S。KMS 签名的数据结构即文档中的核心结构体也是 KMS 签名所覆盖的消息为struct CVerificationStructForKMS { address contractAddress; bytes32 hashOfCiphertext; address callerAddress; }即 KMS 签名的对象是目标合约地址 密文哈希 调用者地址三要素这从协议层面杜绝了把合法密文挪到别的合约/别的调用者名下的可能性。在 JS SDK 中这一步由encryptValue完成它接受{ value, contractAddress, userAddress }返回{ encryptedValue, inputProof }其中inputProof即为与合约调用配套的序列化证明见 sdk/js-sdk/src/core/actions/encrypt/encryptValue.ts。完整时序从加密到获得 KMS 签名下面是原文档给出的 KMS 验证与签发流程Gateway / KMS Blockchain / KMS Core 三方协作第三步使用阶段Usage当用户拿到 KMS 签名KMS_S时意味着ZKPoK 已由 KMS 离线验证通过该输入可以在 fhEVM 中直接使用。此后在 fhEVM 链上只验证 KMS 签名而不再重复验证 ZKPoK——这正是文档强调的更快的原因椭圆曲线签名ECDSA验签比完整的零知识证明验证廉价得多。链上执行路径在 host-contracts/contracts/FHEVMExecutor.sol 中可以看到FHEVMExecutor.verifyInput收到contextUserInputs、inputHandle、inputProof后调用INPUT_VERIFIER.verifyInput(...)验证通过才返回句柄其函数选择器verifyInput((address,address),bytes32,bytes)记录在 host-contracts/docs/contract_selectors.txt。安全设计小结与最佳实践综合原文档与源码可以总结出 fhEVM 加密输入体系的安全闭环防解密泄露明文只在用户侧出现链上只有密文防密文延展攻击ZKPoK 保证密文良构且用户知道明文杜绝对别人的密文加 0 再解密的攻击面防恶意密文KMS 是密文准入的唯一闸门任意/畸形密文在进入 FHEVM 前就会被拦截保护 FHE 私钥安全防跨合约/跨调用者滥用CVerificationStructForKMS将密文绑定到contractAddress与callerAddress链上InputVerifier同时校验链 ID、索引、句柄版本与签名阈值性能优化proof 瞬态缓存 链上只验 KMS 签名而非 ZKPoK配合 compact list 的批量打包让加密输入的成本大幅下降。在编写接收加密输入的合约时请遵循以下实践入参使用externalEuint*/externalEbool/externalEaddress类型并配套bytes calldata inputProof一律通过FHE.fromExternal(handle, inputProof)完成验证与句柄转换不要使用空的inputProof处理来自链下的输入转换后若合约需要后续解密或作为转账金额使用记得调用FHE.allow(result, 目标地址)显式授权如 Adder 示例所示若需进一步理解公钥/CRS 的链上存储与管理可参考 gateway-contracts/contracts/KMSGeneration.solGateway 链上的 view-only 版本以及 coprocessor/docs/fundamentals/overview.md 与 coprocessor/docs/fundamentals/glossary.md 中的术语定义。延伸阅读加密类型与FHE.fromExternal全部重载 host-contracts/lib/FHE.sol 与 library-solidity/lib/FHE.sol链上签名验证实现 host-contracts/contracts/InputVerifier.solKMS 签名者验证入口 host-contracts/contracts/KMSVerifier.solGateway/v2/keyurl端点 relayer/src/http/endpoints/v2/types/keyurl.rsJS SDK 加密与证明序列化 sdk/js-sdk/src/core/actions/encrypt/encryptValue.ts 与 sdk/js-sdk/src/core/coprocessor/InputProof-p.tsfhEVM 加密输入原文档 coprocessor/docs/fundamentals/fhevm/inputs.md【免费下载链接】fhevmFHEVM, a full-stack framework for integrating Fully Homomorphic Encryption (FHE) with blockchain applications项目地址: https://gitcode.com/GitHub_Trending/fh/fhevm创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考