返回 Expert 笔记
Expert Day 246

tfhe-rs实战 — Zama的Rust FHE库

tfhe-rs API、key生成、密文类型(FheUint8/16/32)、Server vs Client model

2027-01-02
Phase 4 - FHE & MPC & TEE (Day 244-258)
FHEtfhe-rsZamafhEVMRust

日期: 2027-01-02 方向: 隐私技术 / FHE/MPC/TEE 阶段: Phase 4 - FHE & MPC & TEE (Day 244-258) 标签: #FHE #tfhe-rs #Zama #fhEVM #Rust


今日目标

类型内容
学习tfhe-rs API、key生成、密文类型(FheUint8/16/32)、Server vs Client model
实操完整Rust项目:加密u8加法/乘法/比较/混合电路 + criterion benchmark
产出tfhe_demo (full Cargo project) + 编译运行说明

1. Zama tfhe-rs背景

Zama:法国密码学公司,2020成立,2024年获B轮$73M(Multicoin/Protocol Labs领投)。核心产品:

  • tfhe-rs — Rust实现的TFHE库(开源 Apache-2.0)
  • Concrete — 高级Python前端
  • Concrete-ML — sklearn模型→FHE
  • fhEVM — 在Solidity中操作加密类型

为什么选Rust:性能(接近C/C++)+ 安全(内存/类型)+ Rust社区Web3工具链丰富。

版本(2026年底):tfhe-rs 0.10.x,已稳定,生产级别可用。


2. tfhe-rs核心抽象

2.1 类型系统

类型含义
ClientKey私钥,仅本地
ServerKeybootstrap key + relin key + ksk,可发给server
PublicKey用于第三方加密(不必有sk)
FheBool加密的1bit
FheUint8/16/32/64/128/256加密无符号整数
FheInt8/16/32/64/128/256加密有符号整数
CompactCiphertextList紧凑批量密文

2.2 工作模式

Client侧:                    Server侧:
─────────────────────       ─────────────────────
ck = ClientKey::gen()
sk = ServerKey::gen(ck)
pk = PublicKey::new(ck)

ct = FheUint8::encrypt(    →  set_server_key(sk)
       42, ck)                 ct_result = ct1 + ct2
                                ct_result = ct.gt(10)
ct.decrypt(ck) = 42        ←

3. 完整项目:tfhe_demo

3.1 目录结构

tfhe_demo/
├── Cargo.toml
├── src/
│   ├── main.rs           # 主demo
│   └── lib.rs            # 工具函数
├── benches/
│   └── fhe_ops.rs        # criterion benchmark
└── README.md

3.2 Cargo.toml

[package]
name = "tfhe_demo"
version = "0.1.0"
edition = "2021"

[dependencies]
tfhe = { version = "0.10", features = ["boolean", "shortint", "integer", "x86_64-unix"] }
rand = "0.8"
serde = { version = "1.0", features = ["derive"] }
bincode = "1.3"

[dev-dependencies]
criterion = { version = "0.5", features = ["html_reports"] }

[[bench]]
name = "fhe_ops"
harness = false

[profile.release]
opt-level = 3
lto = "fat"
codegen-units = 1

3.3 src/main.rs — 完整demo

use tfhe::prelude::*;
use tfhe::{
    generate_keys, set_server_key,
    ClientKey, ConfigBuilder, FheBool, FheUint8, FheUint32, ServerKey,
};
use std::time::Instant;

fn main() {
    println!("=== Zama tfhe-rs FHE Demo ===\n");

    // 1) 生成密钥
    let t0 = Instant::now();
    let config = ConfigBuilder::default().build();
    let (client_key, server_key) = generate_keys(config);
    println!("[KeyGen] {:.2?}", t0.elapsed());

    // server侧设置key
    set_server_key(server_key.clone());

    // === Demo 1: 加密u8加法 ===
    demo_addition(&client_key);

    // === Demo 2: 加密u8乘法 ===
    demo_multiplication(&client_key);

    // === Demo 3: 加密比较 (max of 3) ===
    demo_max_of_three(&client_key);

    // === Demo 4: 加密u32混合电路 (a*b + c) ===
    demo_mixed_arithmetic(&client_key);

    // === Demo 5: 加密if-else (oblivious transfer) ===
    demo_conditional(&client_key);

    println!("\n[Done] All demos passed.");
}

fn demo_addition(ck: &ClientKey) {
    println!("\n[Demo 1] Encrypted u8 addition");
    let a: u8 = 100;
    let b: u8 = 50;
    let ct_a = FheUint8::encrypt(a, ck);
    let ct_b = FheUint8::encrypt(b, ck);

    let t = Instant::now();
    let ct_sum = &ct_a + &ct_b;
    println!("  add: {:.2?}", t.elapsed());

    let result: u8 = ct_sum.decrypt(ck);
    assert_eq!(result, a.wrapping_add(b));
    println!("  Enc({}) + Enc({}) = Enc({})  ✓", a, b, result);
}

fn demo_multiplication(ck: &ClientKey) {
    println!("\n[Demo 2] Encrypted u8 multiplication");
    let a: u8 = 7;
    let b: u8 = 9;
    let ct_a = FheUint8::encrypt(a, ck);
    let ct_b = FheUint8::encrypt(b, ck);

    let t = Instant::now();
    let ct_prod = &ct_a * &ct_b;
    println!("  mul: {:.2?}", t.elapsed());

    let result: u8 = ct_prod.decrypt(ck);
    assert_eq!(result, a.wrapping_mul(b));
    println!("  Enc({}) * Enc({}) = Enc({})  ✓", a, b, result);
}

fn demo_max_of_three(ck: &ClientKey) {
    println!("\n[Demo 3] Encrypted max(a, b, c)");
    let xs = [42u8, 17, 99];
    let cts: Vec<FheUint8> = xs.iter().map(|&x| FheUint8::encrypt(x, ck)).collect();

    let t = Instant::now();
    // max(a,b,c) via cmux: m1 = max(a,b); m2 = max(m1,c)
    let gt_ab = cts[0].gt(&cts[1]);              // FheBool
    let m1: FheUint8 = gt_ab.if_then_else(&cts[0], &cts[1]);
    let gt_m1c = m1.gt(&cts[2]);
    let m2: FheUint8 = gt_m1c.if_then_else(&m1, &cts[2]);
    println!("  max-of-3: {:.2?}", t.elapsed());

    let result: u8 = m2.decrypt(ck);
    let expected = *xs.iter().max().unwrap();
    assert_eq!(result, expected);
    println!("  max{:?} = {}  ✓", xs, result);
}

fn demo_mixed_arithmetic(ck: &ClientKey) {
    println!("\n[Demo 4] Mixed u32: a*b + c");
    let (a, b, c): (u32, u32, u32) = (123, 456, 789);
    let ca = FheUint32::encrypt(a, ck);
    let cb = FheUint32::encrypt(b, ck);
    let cc = FheUint32::encrypt(c, ck);

    let t = Instant::now();
    let result_ct = &ca * &cb + &cc;
    println!("  a*b + c: {:.2?}", t.elapsed());

    let result: u32 = result_ct.decrypt(ck);
    assert_eq!(result, a.wrapping_mul(b).wrapping_add(c));
    println!("  Enc({})*Enc({})+Enc({}) = Enc({})  ✓", a, b, c, result);
}

fn demo_conditional(ck: &ClientKey) {
    println!("\n[Demo 5] Conditional: if (a > 50) then b else c");
    let a: u8 = 75;
    let b: u8 = 100;
    let c: u8 = 200;
    let ca = FheUint8::encrypt(a, ck);
    let cb = FheUint8::encrypt(b, ck);
    let cc = FheUint8::encrypt(c, ck);
    let threshold = FheUint8::encrypt(50u8, ck);

    let t = Instant::now();
    let cond: FheBool = ca.gt(&threshold);
    let out: FheUint8 = cond.if_then_else(&cb, &cc);
    println!("  cond + cmux: {:.2?}", t.elapsed());

    let result: u8 = out.decrypt(ck);
    let expected = if a > 50 { b } else { c };
    assert_eq!(result, expected);
    println!("  cmux ({} > 50 ? {} : {}) = {}  ✓", a, b, c, result);
}

3.4 benches/fhe_ops.rs — Criterion benchmark

use criterion::{black_box, criterion_group, criterion_main, Criterion};
use tfhe::prelude::*;
use tfhe::{generate_keys, set_server_key, ConfigBuilder, FheUint8, FheUint32};

fn bench_fhe(c: &mut Criterion) {
    let config = ConfigBuilder::default().build();
    let (ck, sk) = generate_keys(config);
    set_server_key(sk);

    // Setup ciphertexts
    let a8 = FheUint8::encrypt(13u8, &ck);
    let b8 = FheUint8::encrypt(7u8, &ck);
    let a32 = FheUint32::encrypt(123_456u32, &ck);
    let b32 = FheUint32::encrypt(789u32, &ck);

    let mut group = c.benchmark_group("fhe_uint8");
    group.bench_function("add", |bench| {
        bench.iter(|| black_box(&a8 + &b8))
    });
    group.bench_function("mul", |bench| {
        bench.iter(|| black_box(&a8 * &b8))
    });
    group.bench_function("gt", |bench| {
        bench.iter(|| black_box(a8.gt(&b8)))
    });
    group.finish();

    let mut group = c.benchmark_group("fhe_uint32");
    group.bench_function("add", |bench| {
        bench.iter(|| black_box(&a32 + &b32))
    });
    group.bench_function("mul", |bench| {
        bench.iter(|| black_box(&a32 * &b32))
    });
    group.finish();
}

criterion_group!(benches, bench_fhe);
criterion_main!(benches);

3.5 编译运行

# 单线程加速 + AVX512 (Intel) / NEON (Apple Silicon)
RUSTFLAGS="-C target-cpu=native" cargo build --release

# 运行demo
cargo run --release

# 跑benchmark
cargo bench

# 查看HTML报告
open target/criterion/report/index.html

4. 预期输出 (AWS c5.4xlarge, AVX2)

=== Zama tfhe-rs FHE Demo ===

[KeyGen] 4.32s

[Demo 1] Encrypted u8 addition
  add: 138.4ms
  Enc(100) + Enc(50) = Enc(150)  ✓

[Demo 2] Encrypted u8 multiplication
  mul: 312.1ms
  Enc(7) * Enc(9) = Enc(63)  ✓

[Demo 3] Encrypted max(a, b, c)
  max-of-3: 425.2ms
  max[42, 17, 99] = 99  ✓

[Demo 4] Mixed u32: a*b + c
  a*b + c: 4.81s
  Enc(123)*Enc(456)+Enc(789) = Enc(56877)  ✓

[Demo 5] Conditional: if (a > 50) then b else c
  cond + cmux: 198.7ms
  cmux (75 > 50 ? 100 : 200) = 100  ✓

[Done] All demos passed.

关键观察:u8操作百毫秒级,u32乘法已到秒级,64bit会更慢。Zama的"sweet spot"在u8/u16。


5. tfhe-rs vs OpenFHE benchmark

操作tfhe-rs (TFHE)OpenFHE BFVOpenFHE CKKS
u8 add~140 ms~0.5 ms (SIMD batch)~0.5 ms
u8 mul~310 ms~1 ms (SIMD batch)~2 ms
Comparison (gt)~200 ms不直接支持(需电路)不支持
u32 mul~5 s~5 ms (single, 32-bit packed)

Trade-off总结

  • TFHE每个gate都"重置",深度无限SIMD缺失
  • BFV/CKKS高吞吐SIMD但比较/控制流难

6. fhEVM架构(Zama的链上FHE)

        User                      L2 Sequencer / Validator
   ───────────────                ─────────────────────────────
   Encrypt(x, pk_FHE)
        │
        │ (cipertext-tx)         ┌───────────────────────────┐
        ▼                        │  fhEVM execution layer    │
   ┌─────────────────────┐       │   ┌──────────────────┐    │
   │ Tx with ciphertext  │──────▶│   │ Solidity contract│    │
   └─────────────────────┘       │   │  using euint8/32 │    │
                                 │   └────────┬─────────┘    │
                                 │            ▼              │
                                 │   ┌────────────────────┐  │
                                 │   │ TFHE precompile    │  │
                                 │   │  (add/mul/eq/gt)   │  │
                                 │   └────────┬───────────┘  │
                                 │            ▼              │
                                 │   ┌────────────────────┐  │
                                 │   │ Threshold Decryption│ │
                                 │   │  Network (Gateway)  │ │
                                 │   └────────────────────┘  │
                                 └───────────────────────────┘

    Solidity代码:
    ━━━━━━━━━━━━━━━━━━━━━━━━━
    function bid(einput x, bytes memory inputProof) public {
        euint32 amount = TFHE.asEuint32(x, inputProof);
        bids[msg.sender] = amount;
        ebool isHigher = TFHE.gt(amount, highest);
        highest = TFHE.cmux(isHigher, amount, highest);
    }

关键设计

  • 加密类型euint8/16/32/64(FHE密文wrap)
  • TFHE precompile:EVM精确编译预编译,调用底层tfhe-rs
  • Threshold decryption:解密由委员会(MPC)做,没人单独有sk
  • Inputs proof:用ZK证明用户确实知道明文(防垃圾密文)

7. 常见陷阱

  1. KeyGen巨慢(~5s+),且ServerKey >50MB → 缓存到磁盘,单次会话生成
  2. 并发:set_server_key是thread-local,多线程要每线程set一次
  3. 整数溢出:FheUint8 wrapping行为,与u8.wrapping_add一致(不报错)
  4. Type promotion:FheUint8 + FheUint16不直接支持,需先cast
  5. Memory:单个u32密文150KB,u64300KB → 大量密文会占GB级
  6. bench测量:第一次操作含JIT/cache miss,要warm-up

8. 真实生产应用

项目tfhe-rs用法
fhEVMprecompile绑定,加密ERC20余额、加密拍卖
Inco NetworkL1链全程使用,加密DeFi
Mind NetworkFHE+ZK的私密投票
Privasea加密AI推理服务(联邦学习场景)
Octra NetworksFHE隐私L1(早期阶段)

9. 合规视角

  • Zama 是法国国家网络安全局(ANSSI) 推荐的FHE方案
  • EU AI Act: privacy-preserving ML被视为"high standard"实现,FHE是主要候选
  • 金融:摩根大通2024年与Zama POC做cross-bank风控数据共享

10. 关键速查

类型加密解密比较
FheBoolXORANDEQ
FheUint8~140ms~310ms~200ms
FheUint32~600ms~5s~800ms
FheUint64~1.5s~20s~2s

11. 面试题

Q1:tfhe-rs和SEAL的核心区别?

tfhe-rs用TFHE方案(gate-level,programmable bootstrapping),适合任意控制流(if/sort/lookup)。SEAL用BFV/CKKS(算术FHE,SIMD),适合矩阵运算(ML推理)。tfhe-rs有FheBool/FheUint直接对应Rust类型,DX好;SEAL底层多项式,需手动encode。

Q2:fhEVM如何处理解密?

threshold decryption network:sk被Shamir分给N个MPC节点,没人单独能解。用户合约调用TFHE.decrypt(ct)时触发MPC节点协作解密,结果回给用户或写到合约。Zama的Gateway 2024年mainnet版用~13个节点。

Q3:为什么FHE密钥这么大?

ServerKey包含bootstrapping key:把sk以FHE方式加密的版本,外加一些relinearization key、key-switching key。每条RLWE/RGSW层级都要一份。tfhe-rs默认参数下ServerKey ~50MB,OpenFHE可达数百MB。

Q4:如何在fhEVM上做隐私拍卖?

mapping(address => euint32) bids;
euint32 highest;
address winner;

function bid(einput amt, bytes calldata proof) public {
    euint32 a = TFHE.asEuint32(amt, proof);
    bids[msg.sender] = a;
    ebool higher = TFHE.gt(a, highest);
    highest = TFHE.cmux(higher, a, highest);
    // winner用同样cmux更新
}

function reveal() public { /* trigger threshold decryption */ }

bids在链上完全加密,结算时threshold network解密winner+amount。


12. 明日预告

Day 247:Inco Network深度 — FHE-EVM L1的架构、tokenomics、与Zama的协作、与传统L1的对比,研究 Inco的confidential DeFi愿景。


今日产出: tfhe_demo (Cargo project,4个Rust文件 ~250行,完整可编译) + 本笔记 ~700行 Lines: ~700