Skip to main content

难点:集成 Rust代码

下面是一份按 「从零 → 集成 Rust → 打包 iOS → UniFFI 重构」 时间线整理的难点总结,可直接用于面试或项目复盘。


项目背景(一句话)

React Native + Expo 钱包 App 中,把自研 MPC GG20 签名 / DKG 能力从 Rust 侧接入移动端:Rust → xcframework → UniFFI Swift 绑定 → Expo Native Module → TypeScript。

涉及模块:

模块Rust crate产物
expo-mpc(早期)mpc_clientlibmpc_client.xcframework
expo-mpc-stack(当前)mpc_ffilibmpc_ffi.xcframework
expo-age(加密)expo_age_uniffilibexpo_age_uniffi.xcframework

阶段一:从零开始 — 认知与架构

难点

  1. 技术栈跨度大:同时涉及 Rust、MPC 密码学、FFI、iOS 交叉编译、CocoaPods、Expo autolinking,没有现成「RN + Rust MPC」模板可抄。
  2. 调用链很长,任何一环出错都表现为「Native module is null」:
    TypeScript → Expo Module (Swift) → UniFFI 生成层 → Rust 静态库 (xcframework)
  3. Rust 代码在外部仓库mpc-stack),App 仓库只消费编译产物,需要建立「改 Rust → 重建 → 拷贝 → pod install」的协作流程。

收获

先画清整条链路,再逐段排查,而不是在 JS/Swift 业务层盲目改代码。


阶段二:集成 Rust — 把密码学能力接进 App

难点

  1. Expo Module 桥接层:Swift 要把 JS 的 [String: Any] 转成 Rust 侧的结构体(MpcConfigKeyShare 等),类型映射和错误传递都要手写。
  2. 异步与阻塞:Rust 侧 initialize() / signData() 可能长时间阻塞;Swift 侧加了 shutdown 超时、Task 隔离,TS 侧也要做 timeout 保护。
  3. 业务 API 演进
    • 早期 expo-mpccreateKeyShare(nParties, threshold, chain, accountId) — 按 chain 区分
    • 重构后 expo-mpc-stackcreateKeyShares(curves, ...) — 按 curve(ecdsa / ed25519)区分,一条 curve 可服务多链
  4. MPC 运行时依赖:客户端 DKG/签名需要 gateway + SSE 服务可达,调试时要区分「FFI 层问题」和「网络/协议层问题」。

关键代码位置

  • Swift 桥接:modules/expo-mpc-stack/ios/ExpoMpcStackModule.swift
  • TS 客户端:modules/expo-mpc-stack/src/

阶段三:Rust 打包成 iOS 可用 — 交叉编译与产物集成

难点(这一阶段通常最耗时间)

1. iOS 交叉编译 toolchain

  • 需要 rustup target add aarch64-apple-ios(真机 arm64)
  • 模拟器还要 aarch64-apple-ios-sim / x86_64-apple-ios
  • 外部仓库提供 build_ios.sh,输出 .a 静态库并打包成 xcframework

2. 原生依赖 GMP

MPC 大数运算依赖 GMP,不能直接用 macOS 的 brew install gmp,必须先为 iOS 单独编译:

# 在 mpc-stack 仓库
./scripts/build_gmp_ios.sh # → vendor/gmp-install-ios

# 在 horizonkey 项目
./scripts/rebuild-mpc-stack-ffi.sh # → 拷贝到 modules/expo-mpc-stack/ios/Frameworks/
npx pod-install

3. xcframework 集成到 CocoaPods

podspec 里要正确声明 vendored framework 和头文件搜索路径:

# ExpoMpcStack.podspec
s.vendored_frameworks = 'Frameworks/libmpc_ffi.xcframework'
s.preserve_paths = 'Frameworks/**/*', 'Frameworks/mpc_ffiFFI.modulemap'
s.pod_target_xcconfig = {
'SWIFT_INCLUDE_PATHS' => '$(PODS_TARGET_SRCROOT)/Frameworks $(PODS_TARGET_SRCROOT)',
}

4. iOS 最低版本多处不一致(典型坑)

Rust 库按 iOS 16 构建,但 Expo/RN 默认 15.1,导致 pod installExpoMpcStack 根本没进 Podfile.lock,JS 报 Native module cannot be null

需要对齐的位置:

配置位置作用
*.podspecs.platforms[:ios]Pod 声明最低版本
ios/Podfileplatform :iosCocoaPods 全局 platform
ios/Podfile.properties.jsonExpo prebuild 写入值
app.jsondeploymentTargetExpo 配置源
xcframework 构建时的 min OS二进制自带约束

Podfile 有 || '15.1' 兜底 — 若 Podfile.properties.json 缺失,全局 platform 会低于 podspec 要求,Pod 被 resolver 跳过。

最终方案:全部统一到 16.0,再 prebuild + pod install

5. 预编译 vs 按需重建

仓库里预置的 libmpc_ffi.xcframework 可能不含客户端 DKG;要启用 createKeyShare,必须从 mpc-stack 源码重建并拷贝产物。


阶段四:UniFFI 重构 — 从手工 FFI 到自动生成绑定

为什么要重构

早期手工维护 C FFI(cbindgen + 手写 Swift 封装)的问题:

  • 接口一改,Swift/ObjC 绑定要手工同步,容易 crash 或内存泄漏
  • 错误类型、字符串、字节数组的跨语言传递容易出错
  • 多模块(MPC、Age 加密)各自维护一套 FFI,成本高

UniFFI 方案

Rust 侧用 UniFFI 定义接口(.udl 或 proc-macro),自动生成:

生成物作用
mpc_ffi.swiftSwift 高层 API(MpcSignerMpcConfig 等)
mpc_ffiFFI.hC 层 FFI 头文件
mpc_ffiFFI.modulemapClang module 定义
libmpc_ffi.axcframeworkRust 静态库

App 侧 只消费生成物,不手改(文件头明确写着 "Trust me, you don't want to mess with it!")。

重构带来的变化

维度早期 expo-mpc重构后 expo-mpc-stack
Rust cratempc_clientmpc_ffi
外部仓库mpc-wallet-gg20mpc-stack
DKG API单 curve + chain 参数多 curve 数组(ecdsa/ed25519)
错误类型4 种新增 KeyError
目录结构Frameworks/同结构,命名空间换为 mpc_ffi

expo-age 也走同一套 UniFFI 模式(GeneratedUniffi/ + FrameworksUniffi/),形成可复用的集成模板。

UniFFI 集成的 podspec 要点

# expo-age 示例 — GeneratedUniffi 与 FrameworksUniffi 分离
s.source_files = "*.{h,m,mm,swift}", "GeneratedUniffi/*.{h,m,mm,swift}"
s.vendored_frameworks = 'FrameworksUniffi/libexpo_age_uniffi.xcframework'
s.public_header_files = "GeneratedUniffi/*.h"

重构后的维护流程

改 Rust (mpc-stack/mpc-ffi)
→ build_ios.sh 重建 xcframework + 生成 Swift/头文件
→ 拷贝到 modules/expo-mpc-stack/ios/Frameworks/
→ npx pod-install
→ 真机验证 DKG / 签名

整体架构图


STAR 口述模板

维度内容
SExpo 钱包需要接入 MPC 门限签名,核心算法在 Rust,要在 iOS 上跑通
T从零完成 Rust → iOS 集成,并重构为 UniFFI 自动生成绑定
A① 理清 TS→Swift→UniFFI→Rust 链路;② 交叉编译 + GMP + xcframework;③ 配置 CocoaPods podspec;④ 对齐 iOS deployment target;⑤ 迁移到 mpc-stack + UniFFI,统一 rebuild 脚本
RMPC 签名/DKG 在 iOS 16+ 真机跑通;形成可复用的 Rust-iOS-Expo 集成模板(MPC + Age 两个模块)

面试口述稿

30 秒版

钱包 App 的 MPC 签名核心在 Rust。我从零搭了 Rust 交叉编译 → xcframework → UniFFI 自动生成 Swift 绑定 → Expo Native Module 的整条链路。最难的是 iOS 交叉编译(GMP 依赖)和 CocoaPods 版本不一致导致 Pod 装不上。后来重构到 UniFFI,接口变更只需重建 Rust 产物,不再手写 FFI。

2 分钟版

项目要做 MPC 门限签名,算法在 Rust 的 mpc-stack 仓库。我一开始对 Rust 移动端集成完全没经验,调用链是 TypeScript → Expo Swift Module → UniFFI 生成的 Swift → Rust 静态库。

第一阶段是搞懂怎么把 Rust 编成 iOS 能用的 xcframework:需要 iOS 交叉编译 toolchain、先编 GMP 大数库、再用 build_ios.sh 产出 framework 和 Swift 绑定,通过 CocoaPods vendored_frameworks 链进 App。

第二阶段踩了 deployment target 的坑:Rust 库按 iOS 16 编译,Expo 默认 15.1,podspec 要求 16,Podfile 有 15.1 fallback,导致 ExpoMpcStack 根本没进 Podfile.lock。对齐 app.json、Podfile.properties.json 和所有 podspec 到 16.0 后解决。

第三阶段是把 FFI 层重构到 UniFFI:早期手工维护 C 绑定,接口一改就要同步改 Swift,还容易内存问题。UniFFI 从 Rust 接口定义自动生成 Swift + C 头文件 + modulemap,App 只消费产物。同时从 expo-mpc 迁移到 expo-mpc-stack,DKG API 从按 chain 改为按 curve 数组,支持 ecdsa/ed25519 多曲线。

收获是形成了一套可复用的 Rust-iOS-Expo 集成模式,Age 加密模块也复用了同一套 UniFFI + xcframework 方案。


简历一行(英文)

Built end-to-end Rust-to-iOS integration for MPC wallet signing: cross-compiled Rust with GMP into xcframework, wired UniFFI-generated Swift bindings through Expo Native Modules, resolved CocoaPods deployment-target mismatches, and migrated from manual FFI to UniFFI-based expo-mpc-stack.


常见追问

追问简答
为什么用 Rust 不用 Swift/Kotlin 写 MPC?MPC 协议复杂、已有 Rust 实现(multi-party-ecdsa),跨平台复用,性能和内存安全更好
UniFFI vs 手写 cbindgen?UniFFI 自动生成 Swift/Kotlin 绑定 + 类型安全 + 错误映射,接口变更成本低
xcframework 和 .a 的区别?xcframework 是 Apple 的多架构/多平台打包格式,CocoaPods 用 vendored_frameworks 直接链
为什么 iOS 16?Passkeys、Security API、Rust 构建目标、Skia 等依赖都要求 16
改 Rust 后怎么更新 App?rebuild-mpc-stack-ffi.sh → 拷贝 Frameworks → pod install → 重编 Dev Client
模拟器能跑吗?当前 xcframework 只有 ios-arm64(真机),模拟器需额外编译 sim slice

如果需要,我可以再单独压一版 「和 Android NDK/JNI 类比」「UniFFI 绑定文件清单 checklist」 方便背诵。