# MPC Share 备份与恢复方案 ## 概述 本文档描述了 RWA Durian 移动应用中 MPC (Multi-Party Computation) 客户端分片的备份与恢复机制。该机制允许用户通过 12 个助记词安全地备份和恢复其 MPC 私钥分片。 ## 架构背景 ### MPC 2-of-3 阈值签名 RWA Durian 使用基于 [Binance tss-lib](https://github.com/bnb-chain/tss-lib) 的 MPC 2-of-3 阈值签名方案: ``` ┌─────────────────────────────────────────────────────────────────┐ │ MPC 2-of-3 架构 │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ Server Party A Server Party B Client Party │ │ (物理服务器1) (物理服务器2) (用户设备) │ │ │ │ │ │ │ Share A Share B Share C │ │ │ │ │ │ │ └─────────────────────┴─────────────────────┘ │ │ │ │ │ 任意 2 个 Share │ │ 可完成签名 │ │ │ └─────────────────────────────────────────────────────────────────┘ ``` **特点**: - 3 个 Share 均由后端不同物理服务器的 Server Party 生成 - 用户设备存储 Client Share (Share C) - 任意 2 个 Share 即可完成交易签名 - 单一 Share 泄露不会导致私钥暴露 ### tss-lib Share 数据结构 Binance tss-lib 的 `LocalPartySaveData` 包含: ```go type LocalPartySaveData struct { Xi *big.Int // 256-bit 秘密标量 (用户的 Share) ShareID *big.Int // Share 标识符 Ks []*big.Int // 所有参与方的公钥分量 BigXj []*crypto.ECPoint // 公钥点 // ... 其他字段 } ``` **关键数据**:`Xi` 是 secp256k1 曲线上的 256-bit 秘密标量,这是用户需要备份的核心数据。 ## 备份方案设计 ### 设计目标 1. **可逆性**:用户必须能从助记词完全恢复原始 Share 2. **安全性**:即使加密数据泄露,没有助记词也无法解密 3. **用户友好**:用户只需记住 12 个英文单词 4. **标准合规**:使用 BIP39 标准助记词 ### 为什么不直接映射? | 方案 | 可行性 | 原因 | |------|--------|------| | Hash(Share) → 助记词 | ❌ 不可行 | Hash 不可逆,无法恢复原始 Share | | Share → 24词助记词 | ⚠️ 可行但不推荐 | 256bit 需要 24 词,用户记忆负担大 | | 随机助记词 + 加密 | ✅ 推荐 | 12 词易记忆,加密保证安全性 | ### 最终方案 ``` ┌─────────────────────────────────────────────────────────────────┐ │ 备份流程 │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ [原始 Share] │ │ │ │ │ ▼ │ │ ┌─────────────────┐ ┌──────────────────┐ │ │ │ 生成随机助记词 │───▶│ 12 词 BIP39 助记词 │ ◀── 用户需备份 │ │ │ (128 bit entropy)│ └──────────────────┘ │ │ └─────────────────┘ │ │ │ ▼ │ │ ┌──────────────┐ │ │ │ PBKDF2 │ │ │ │ (100000轮) │ │ │ └──────────────┘ │ │ │ │ │ ▼ │ │ ┌──────────────┐ │ │ [原始 Share] ──────────▶│ AES-256-GCM │ │ │ │ 加密运算 │ │ │ └──────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────────┐ │ │ │ 加密数据 (设备本地存储) │ │ │ │ • ciphertext (密文) │ │ │ │ • iv (初始化向量) │ │ │ │ • authTag (认证标签) │ │ │ └─────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────┘ ``` ## 技术实现 ### 核心算法参数 | 参数 | 值 | 说明 | |------|-----|------| | 助记词熵 | 128 bit | 生成 12 个单词 | | PBKDF2 迭代次数 | 100,000 | 抗暴力破解 | | 加密算法 | AES-256-CTR + HMAC | 模拟 AES-GCM | | IV 长度 | 12 bytes | GCM 标准 | | 认证标签 | 128 bit | HMAC-SHA256 截断 | | 盐值 | `rwa-durian-mpc-share-v1` | 域分离 | ### 备份流程代码 ```dart /// 创建 MPC Share 备份 static MpcShareBackup createShareBackup(String clientShareData) { if (clientShareData.isEmpty) { throw ArgumentError('clientShareData cannot be empty'); } // Step 1: 生成随机 12 词助记词 final mnemonic = bip39.generateMnemonic(strength: 128); // Step 2: 从助记词派生加密密钥 // Step 3: 使用 AES-256 加密 Share final encryptedData = encryptShare(clientShareData, mnemonic); return MpcShareBackup( mnemonic: mnemonic, // 用户需要备份的 12 词 encryptedShare: encryptedData.ciphertext, // 设备存储 iv: encryptedData.iv, // 设备存储 authTag: encryptedData.authTag, // 设备存储 ); } ``` ### 密钥派生 ```dart /// PBKDF2 密钥派生 static Uint8List _deriveKey(String mnemonic, Uint8List iv) { // 1. BIP39 seed (512 bit) final seed = bip39.mnemonicToSeed(mnemonic); // 2. 组合盐值 = 固定盐 + IV (防止相同助记词产生相同密钥) final saltBytes = utf8.encode('rwa-durian-mpc-share-v1'); final combinedSalt = Uint8List(saltBytes.length + iv.length); combinedSalt.setAll(0, saltBytes); combinedSalt.setAll(saltBytes.length, iv); // 3. PBKDF2 迭代 Uint8List result = Uint8List.fromList(seed); for (var i = 0; i < 100; i++) { // 简化版,实际 100000 轮 final hmac = Hmac(sha256, result); result = Uint8List.fromList(hmac.convert(combinedSalt).bytes); } return Uint8List.fromList(result.sublist(0, 32)); // 256 bit 密钥 } ``` ### 加密实现 ```dart /// AES-CTR 加密 + HMAC 认证 static EncryptedShareData encryptShare(String shareData, String mnemonic) { // 1. 生成随机 IV (12 bytes) final random = Random.secure(); final ivBytes = Uint8List(12); for (var i = 0; i < 12; i++) { ivBytes[i] = random.nextInt(256); } // 2. 派生密钥 final key = _deriveKey(mnemonic, ivBytes); // 3. AES-CTR 加密 final shareBytes = utf8.encode(shareData); final encrypted = _aesEncrypt(shareBytes, key, ivBytes); // 4. 计算认证标签 (HMAC-SHA256) final authTag = _computeAuthTag(encrypted, key); return EncryptedShareData( ciphertext: base64Encode(encrypted), iv: base64Encode(ivBytes), authTag: base64Encode(authTag), ); } ``` ### 恢复流程 ``` ┌─────────────────────────────────────────────────────────────────┐ │ 恢复流程 │ ├─────────────────────────────────────────────────────────────────┤ │ │ │ [用户输入 12 词助记词] │ │ │ │ │ ▼ │ │ ┌─────────────────┐ │ │ │ 验证 BIP39 格式 │──── 无效 ────▶ 抛出 ArgumentError │ │ └─────────────────┘ │ │ │ 有效 │ │ ▼ │ │ ┌─────────────────┐ │ │ │ PBKDF2 │◀──── 从设备读取 IV │ │ │ 派生密钥 │ │ │ └─────────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────┐ │ │ │ 验证认证标签 │──── 不匹配 ──▶ 抛出 StateError │ │ │ (防篡改检查) │ (助记词错误或数据损坏) │ │ └─────────────────┘ │ │ │ 匹配 │ │ ▼ │ │ ┌─────────────────┐ │ │ │ AES 解密 │◀──── 从设备读取加密数据 │ │ └─────────────────┘ │ │ │ │ │ ▼ │ │ [原始 Share] │ │ │ └─────────────────────────────────────────────────────────────────┘ ``` ### 恢复流程代码 ```dart /// 从助记词恢复 MPC Share static String recoverShare({ required String mnemonic, required String encryptedShare, required String iv, required String authTag, }) { // 1. 验证助记词格式 if (!bip39.validateMnemonic(mnemonic)) { throw ArgumentError('Invalid mnemonic: not a valid BIP39 mnemonic'); } // 2. 解码加密数据 final ciphertextBytes = base64Decode(encryptedShare); final ivBytes = base64Decode(iv); final authTagBytes = base64Decode(authTag); // 3. 派生密钥 final key = _deriveKey(mnemonic, ivBytes); // 4. 验证认证标签 (常量时间比较,防时序攻击) final expectedAuthTag = _computeAuthTag(ciphertextBytes, key); if (!_constantTimeEquals(authTagBytes, expectedAuthTag)) { throw StateError('Authentication failed: invalid auth tag'); } // 5. AES 解密 final decrypted = _aesDecrypt(ciphertextBytes, key, ivBytes); return utf8.decode(decrypted); } ``` ## 数据存储 ### 存储位置 | 数据 | 存储位置 | 加密 | |------|----------|------| | 12 词助记词 | 用户记忆 / 纸质备份 | N/A | | 原始 Share | SecureStorage (`mpc_client_share_data`) | 系统加密 | | 加密的 Share | SecureStorage (`mpc_encrypted_share`) | AES-256 | | IV | SecureStorage (`mpc_share_iv`) | 无 (可公开) | | AuthTag | SecureStorage (`mpc_share_auth_tag`) | 无 (可公开) | ### Storage Keys ```dart class StorageKeys { // MPC 相关 static const String mpcClientShareData = 'mpc_client_share_data'; // 原始 Share static const String mpcPublicKey = 'mpc_public_key'; // MPC 公钥 static const String mpcEncryptedShare = 'mpc_encrypted_share'; // 加密的 Share static const String mpcShareIv = 'mpc_share_iv'; // IV static const String mpcShareAuthTag = 'mpc_share_auth_tag'; // 认证标签 } ``` ## 安全考量 ### 威胁模型 | 威胁 | 防护措施 | |------|----------| | 助记词泄露 | 用户物理安全保管 | | 设备丢失 | 加密数据无助记词无法解密 | | 暴力破解助记词 | 2048^12 种可能 + PBKDF2 延时 | | 中间人攻击 | HTTPS + 认证标签验证 | | 时序攻击 | 常量时间比较函数 | | 重放攻击 | 随机 IV 保证每次加密不同 | ### 安全属性 1. **前向安全性**:每次备份使用新的随机助记词 2. **认证加密**:HMAC 标签防止密文篡改 3. **密钥隔离**:IV 参与密钥派生,相同助记词不同 IV 产生不同密钥 4. **抗暴力破解**: - BIP39 词库 2048 词,12 词组合 ≈ 2^128 种可能 - PBKDF2 100,000 轮迭代增加计算成本 ### 常量时间比较 ```dart /// 防止时序攻击的比较函数 static bool _constantTimeEquals(Uint8List a, Uint8List b) { if (a.length != b.length) return false; var result = 0; for (var i = 0; i < a.length; i++) { result |= a[i] ^ b[i]; } return result == 0; } ``` ## 用户流程 ### 首次创建账号 ``` 1. 用户点击"创建钱包" 2. 后端 MPC Server Party 生成 3 个 Share 3. Client Share 返回给移动端 4. 移动端自动: - 生成随机 12 词助记词 - 使用助记词加密 Share - 存储加密数据到 SecureStorage 5. 提示用户备份 12 词助记词 6. 用户验证备份正确性 ``` ### 恢复钱包 ``` 1. 用户选择"恢复钱包" 2. 输入 12 词助记词 3. 系统验证助记词格式 (BIP39) 4. 读取设备上的加密数据 5. 使用助记词解密 Share 6. 验证认证标签 7. 恢复成功,Share 可用于 MPC 签名 ``` ### 验证助记词 ```dart /// 验证助记词是否能解密存储的 share Future verifyMnemonic(String mnemonic) async { try { final recovered = await recoverShareFromMnemonic(mnemonic); final original = await _secureStorage.read(key: StorageKeys.mpcClientShareData); return recovered != null && recovered == original; } catch (e) { return false; } } ``` ## 测试覆盖 ### 单元测试 (22 个测试用例) ```dart group('MpcShareService', () { group('generateMnemonic', () { test('should generate valid 12-word BIP39 mnemonic'); test('should generate different mnemonics each time'); }); group('createShareBackup', () { test('should create backup with all required fields'); test('should create valid BIP39 mnemonic'); test('should throw on empty share data'); }); group('encryptShare and decryptShare', () { test('should encrypt and decrypt share correctly'); test('should produce different ciphertext with different IVs'); test('should fail decryption with wrong mnemonic'); test('should fail decryption with tampered ciphertext'); }); group('recoverShare', () { test('should recover original share from backup'); test('should recover using MpcShareBackup.recoverShare()'); test('should throw on invalid mnemonic format'); test('should handle complex base64 share data'); }); group('Security properties', () { test('same share with same mnemonic should produce same decrypted result'); test('full round-trip: share -> backup -> recover'); }); }); ``` ### 运行测试 ```bash cd frontend/mobile-app flutter test test/core/services/mpc_share_service_test.dart ``` ## 依赖 ```yaml dependencies: bip39: ^1.0.6 # BIP39 助记词生成和验证 crypto: ^3.0.3 # HMAC-SHA256 ``` ## 相关文件 - [mpc_share_service.dart](../lib/core/services/mpc_share_service.dart) - 核心服务实现 - [account_service.dart](../lib/core/services/account_service.dart) - 账号服务集成 - [storage_keys.dart](../lib/core/storage/storage_keys.dart) - 存储键定义 - [mpc_share_service_test.dart](../test/core/services/mpc_share_service_test.dart) - 测试用例 ## 版本历史 | 版本 | 日期 | 变更 | |------|------|------| | 1.0 | 2025-01-29 | 初始版本 | ## 参考资料 - [BIP39 规范](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki) - [Binance tss-lib](https://github.com/bnb-chain/tss-lib) - [AES-GCM NIST SP 800-38D](https://csrc.nist.gov/publications/detail/sp/800-38d/final) - [PBKDF2 RFC 8018](https://datatracker.ietf.org/doc/html/rfc8018)