全同态加密 + 智能合约实践:透明账本上的私密计算
Contents
介绍 #
提到区块链智能合约,可能第一反应是透明公开——它怎么会和私密计算联系起来呢?其实区块链上的隐私性也不是什么新鲜事,很早就出现了类似应用:如 ZCash 通过零知识证明技术隐藏交易地址和金额。本文介绍的是另一种叫做「全同态加密」的技术,使用它能让数据在加密状态下参与运算。
SalmonFlip 项目是个人开发的 Web3 项目,借助 Fhenix 全同态加密实现加密投票,目前部署在 Arbitrum Sepolia 测试网,可通过站内指引免费体验。

https://www.salmonFlip.com
智能合约 GitHub 仓库:https://github.com/SalmonFlip/contracts
全同态加密(FHE) #
全同态加密(Fully Homomorphic Encryption, FHE) 通常会跟 零知识证明(ZKPs) 和 安全多方计算(MPC) 一起讨论。这些技术都属于当前密码学前沿方向;它们近年来发展迅速,一方面得益于硬件性能的提升,另一方面也与在区块链等行业中的落地需求密切相关。据我所知,在现有项目中,大多是这三项技术相互组合使用——例如早期以太坊 Layer2 的核心技术是 ZKPs,但在其初始阶段也会用 MPC 生成秘密参数。零知识证明与多方计算在区块链中已有不少成熟应用,而全同态加密总体上仍在探索阶段。
全同态加密是一种强大的加密方法,它允许用户直接在密文上执行任意计算,并且不用解密数据。最终计算结果的密文可以被解密得到与对原始明文执行同样操作所得的结果完全一致。这种特性被称为“同态性”。
同态就是「相同形态」,指某种加密在特定运算下仍保留明文的结构:密文在做加法时,明文也在做加法;密文在做乘法时,明文也在做乘法。
假设你将数字 $a$ 和 $b$ 进行了加密,得到 $E(a)$ 和 $E(b)$。在普通加密方案下,如果你要计算 $a + b$,你需要先解密。但在同态加密体系下,你可以直接对密文进行加法操作:
$$ E(a) \oplus E(b) = E(a + b) $$
同理,对于乘法,如果加密体系支持:
$$ E(a) \otimes E(b) = E(a \times b) $$
一般来说,全同态加密、部分同态加密、有限同态加密都属于 同态加密。
- 部分同态加密:只支持某一类同态运算,比如仅支持加法或仅支持乘法。
- 有限同态加密:能支持少量(有限个)加法和乘法操作,但不能无限次混合运算。
- 全同态加密:任意次数地支持加法和乘法等运算,甚至可实现任意电路逻辑,但技术难度最高,性能消耗最大。
提示:经典公钥体制 RSA 本质上具有部分同态性质:$E(a) \cdot E(b) = E(a \times b)$,即密文相乘、解密后等于对应明文的乘积。
FHE 在区块链的现状 #
目前研究全同态加密方向的团队有很多,而在区块链行业中 ZAMA 绝对是核心团队之一,它的产品(如 fhEVM)是底层基础设施与核心技术。另一个团队 Fhenix 基于 ZAMA 的核心技术开发 CoFHE 协处理器、阈值网络等项目,将 FHE 技术作为主流 EVM 链上的隐私插件,实现加密运算。
当前,Fhenix 仍在测试阶段,我们只能在 Arbitrum Sepolia 测试链使用它的功能。
Fhenix 与 CoFHE 的系统架构 #
Fhenix 对外提供的能力核心是 CoFHE(Fully Homomorphic Encryption 的 Co-processor,可理解为面向 EVM 的 FHE 协处理器层):链上合约只编排「要什么运算」与权限,真正把密文搬进搬出、在密文上做 TFHE(如 ZAMA 开源栈一类)计算的,是一套链下服务;需要把结果交给用户可读时,再通过 阈值网络(Threshold Network) 做解密/MPC,避免单点掌握全局解密密钥。
简单理解:Fhenix 在 EVM 链上部署了 Task Manager 合约,你的合约通过 call 调用它完成加密运算,例如 FHE.add(密文A, 密文B)。Fhenix 链下系统通过日志监听请求并完成实际运算。
整个 Fhenix 架构分层理解:
1)链上层(宿主链 + 合约库)
- 业务合约通过 FHE.sol(Solidity 库,包名为
cofhe-contracts)对「加密类型」做运算编排,例如euint32上的加法、乘法、比较等。 - 合约里不会直接保存完整密文载荷(因为远超过256位),而是用 句柄(handle)(可以理解为指向链下密文资源的引用)。链上可读的是这个引用,机密性主要来自链下密文本身与访问控制。
- ACL(访问控制) 配合 客户端 SDK(
@cofhe/sdk) 里的许可(permit)等机制,决定谁可以对哪些加密句柄继续运算或申请解密。
2)网关与编排
- Task Manager 合约是链上网关:校验请求格式、结合 ACL 校验调用方是否有权使用相关输入句柄,为「即将产生的输出密文」分配 唯一句柄,并通过 事件 把具体操作(算子类型、输入句柄等)暴露给索引方。
- 合约在一次调用里先得到可用的输出句柄引用,异步计算完成后链下状态与后续运算才一致。
3)链下执行
- Slim Listener 监听 Task Manager(及相关)事件,把 FHE 作业投递给真正的计算节点。
- fheOS Server(或等价执行服务)装载与句柄对应的密文数据,在同态语义下执行加、乘、CMP 等原生 FHE 算子,维护句柄与密文结果的对应关系。
- Ciphertext Registry 维护链下密文寄存与 ACL 侧的引用一致性,便于多步运算衔接。
- Result Processor 把执行结果中与链上状态相关的部分写回宿主链(具体形态随版本可能是注册表更新、事件或配置的回调合约等),以便后续步骤继续仅以句柄编程。
4)解密
- 若要将某句柄解密给用户可读,需要通过 阈值网络,它是一个多方节点参与 MPC/门限解密的去中心化网络,没有任何一个节点能单独进行解密。这里就是一个典型的全同态加密与多方计算互补案例。
FHE 与智能合约的结合 #
首先讲解一下 SalmonFlip 项目的投票与揭示流程:
-
有三个选项可选。用户 A 在前端通过 MetaMask 登录后投票(选项可为 1、2、3)。投票内容经加密上链,他人无法直接得知其选择,后续只能在密文上做运算。
-
用户 B 同样进行加密投票;合约对双方投票数据做同态运算,得到新的密文句柄并写回链上。
-
投票截止后,合约在密文状态下进行比较,找出得票最少的选项,生成新的密文句柄并写回链上,同时公示该句柄(或相关可验证信息)。
-
投中该选项的用户胜出;任何人都可以按规则验证结果。
以下是投票函数的部分代码:
import {InEuint8} from "@fhenixprotocol/cofhe-contracts/ICofhe.sol";
import {FHE, euint8, euint64, ebool} from "@fhenixprotocol/cofhe-contracts/FHE.sol";
function vote(uint256 gameId, bytes calldata encryptedOption) external payable nonReentrant {
...
InEuint8 memory input = abi.decode(encryptedOption, (InEuint8));
euint8 vote = FHE.asEuint8(input);
FHE.allowThis(vote);
FHE.allowSender(vote);
euint64 one = FHE.asEuint64(1);
euint64 zero = FHE.asEuint64(0);
euint64 inc0 = FHE.select(FHE.eq(vote, FHE.asEuint8(0)), one, zero);
euint64 inc1 = FHE.select(FHE.eq(vote, FHE.asEuint8(1)), one, zero);
euint64 inc2 = FHE.select(FHE.eq(vote, FHE.asEuint8(2)), one, zero);
...
}先来了解一下 Fhenix 特有的两种数据类型:输入类型 InEuint* 和计算类型 euint*。
- InEuint8:用于输入的 8 位无符号整型,可转换为计算类型。
- euint8:用于计算的 8 位无符号整型;任何需要参与全同态加密运算的数据都要先转换成计算类型。
最后,FHE.select 其实是三目选择:以 inc0 为例,若 vote 等于 $0$ 则取 $1$,否则取 $0$。这样,若用户投的是当前选项,inc0 在明文语义下为 $1$,对应密文为 $E(1)$,其余计数为 $E(0)$。
代码中 encryptedOption 是前端经 Fhenix 客户端库加密后的数据,经 abi.decode 转为输入类型,再经 Fhenix 合约路径得到句柄并赋给变量 vote(euint 系列)。
allowThis 与 allowSender 会调用 Fhenix 的 ACL,表示允许本合约(SalmonFlip)与调用者(msg.sender)对该密文句柄继续读写、运算或申请解密。Fhenix 要求相关运算链上通常需显式授权,否则会无权继续使用该句柄。
这时候inc0在链上存储的值大概是这样0x6650fc2167c16eb1a0b735b7535ea182c4f1db11318617fb97b19443ee670500,这就是它的句柄。
每一次调用 FHE 相关接口通常会产生事件或日志,Fhenix 据此在链下执行真实运算;下图是一次投票调用所产生的约 32 条日志。
下面用简单的序列图说明链上合约与 Fhenix 链下如何协作完成密文运算——例如在合约里调用 FHE.add(a, b) 时大致会发生什么:
sequenceDiagram
participant SC as 业务合约/FHE.sol
participant TM as Task Manager / ACL
participant L as Slim Listener
participant OS as fheOS
participant RP as Result Processor
SC->>TM: add(a,b) 请求
TM->>TM: 校验结构与权限
TM-->>SC: 返回输出句柄 + 发射事件
L->>L: 监听事件
L->>OS: 投递 FHE.add 请求
OS->>OS: 读取密文,执行同态加运算
OS->>RP: 产出与句柄对齐的结果密文
RP->>TM: 更新链上可验证锚点