rwadurian/backend/mpc-system/services/service-party-android/IMPLEMENTATION_PLAN.md

250 lines
8.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 2-of-3 服务器参与选项 - 纯新增实施方案
## 目标
允许 2-of-3 MPC 用户勾选"包含服务器备份"参与签名,以便在丢失一个设备时转出资产。
## 核心设计
### 安全限制
- **仅** 2-of-3 配置显示此选项
- 其他配置3-of-5, 4-of-7等不显示
### 实施范围
- ✅ 只修改 Android 客户端
- ❌ **不需要**修改后端account-service, message-router
- ✅ 纯新增代码,现有逻辑保持不变
## 修改文件清单
### 1. TssRepository.kt2处新增
#### 1.1 新增辅助方法private
```kotlin
// 位置3712行之前类内部末尾
/**
* 构建参与方列表(新增辅助方法)
* @param participants 所有参与方
* @param includeServerParties 是否包含服务器方(默认 false保持现有行为
*/
private fun buildSigningParticipantList(
participants: List<ParticipantStatusInfo>,
includeServerParties: Boolean = false
): List<Pair<String, Int>> {
val filtered = if (includeServerParties) {
// 包含所有参与方(含服务器)
participants
} else {
// 过滤掉服务器方(现有行为)
participants.filter { !it.partyId.startsWith("co-managed-party-") }
}
return filtered.map { Pair(it.partyId, it.partyIndex) }
}
```
#### 1.2 新增签名会话创建方法
```kotlin
// 位置buildSigningParticipantList 之后
/**
* 创建签名会话(支持选择是否包含服务器)
* @param includeServerBackup 是否包含服务器备份参与方(仅 2-of-3 时使用)
* 新增方法,不影响现有 createSignSession
*/
suspend fun createSignSessionWithOptions(
shareId: Long,
messageHash: String,
password: String,
initiatorName: String,
includeServerBackup: Boolean = false // 新增参数
): Result<SignSessionResult> {
return withContext(Dispatchers.IO) {
try {
val shareEntity = shareRecordDao.getShareById(shareId)
?: return@withContext Result.failure(Exception("Share not found"))
val signingPartyIdForEvents = shareEntity.partyId
android.util.Log.d("TssRepository", "[CO-SIGN-OPTIONS] Creating sign session with includeServerBackup=$includeServerBackup")
ensureSessionEventSubscriptionActive(signingPartyIdForEvents)
val keygenStatusResult = getSessionStatus(shareEntity.sessionId)
if (keygenStatusResult.isFailure) {
return@withContext Result.failure(Exception("无法获取 keygen 会话的参与者信息: ${keygenStatusResult.exceptionOrNull()?.message}"))
}
val keygenStatus = keygenStatusResult.getOrThrow()
// 使用新的辅助方法构建参与方列表
val signingParties = buildSigningParticipantList(
keygenStatus.participants,
includeServerBackup
)
android.util.Log.d("TssRepository", "[CO-SIGN-OPTIONS] Signing parties: ${signingParties.size} of ${keygenStatus.participants.size} (includeServer=$includeServerBackup)")
signingParties.forEach { (id, index) ->
android.util.Log.d("TssRepository", "[CO-SIGN-OPTIONS] party_id=${id.take(16)}, party_index=$index")
}
if (signingParties.size < shareEntity.thresholdT) {
return@withContext Result.failure(Exception(
"签名参与方不足: 需要 ${shareEntity.thresholdT} 个,但只有 ${signingParties.size} 个参与方"
))
}
// 后续逻辑与 createSignSession 相同
// ... 构建请求、创建session、加入gRPC等
// (复用现有 createSignSession 的代码)
// 调用现有方法的内部逻辑(需要提取)
createSignSessionInternal(
shareEntity,
signingParties,
messageHash,
password,
initiatorName
)
} catch (e: Exception) {
Result.failure(e)
}
}
}
```
### 2. MainViewModel.kt1处新增
```kotlin
// 位置initiateSignSession 方法之后
/**
* 创建签名会话(支持选择服务器参与)
* 新增方法,不影响现有 initiateSignSession
*/
fun initiateSignSessionWithOptions(
shareId: Long,
password: String,
initiatorName: String = "发起者",
includeServerBackup: Boolean = false // 新增参数
) {
viewModelScope.launch {
_uiState.update { it.copy(isLoading = true, error = null) }
val tx = _preparedTx.value
if (tx == null) {
_uiState.update { it.copy(isLoading = false, error = "交易未准备") }
return@launch
}
android.util.Log.d("MainViewModel", "[SIGN-OPTIONS] Initiating sign session with includeServerBackup=$includeServerBackup")
val result = repository.createSignSessionWithOptions(
shareId = shareId,
messageHash = tx.signHash,
password = password,
initiatorName = initiatorName,
includeServerBackup = includeServerBackup // 传递参数
)
result.fold(
onSuccess = { sessionResult ->
_signSessionId.value = sessionResult.sessionId
_signInviteCode.value = sessionResult.inviteCode
_signParticipants.value = listOf(initiatorName)
_uiState.update { it.copy(isLoading = false) }
pendingSignInitiatorInfo = PendingSignInitiatorInfo(
sessionId = sessionResult.sessionId,
shareId = shareId,
password = password
)
if (sessionResult.sessionAlreadyInProgress) {
startSigningProcess(sessionResult.sessionId, shareId, password)
}
},
onFailure = { e ->
_uiState.update { it.copy(isLoading = false, error = e.message) }
}
)
}
}
```
### 3. TransferScreen.ktUI 新增)
```kotlin
// 在交易确认界面新增复选框Step 2
// 位置:密码输入框之后
// 仅在 2-of-3 时显示
if (wallet.thresholdT == 2 && wallet.thresholdN == 3) {
Spacer(modifier = Modifier.height(16.dp))
var includeServerBackup by remember { mutableStateOf(false) }
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
verticalAlignment = Alignment.CenterVertically
) {
Checkbox(
checked = includeServerBackup,
onCheckedChange = { includeServerBackup = it }
)
Spacer(modifier = Modifier.width(8.dp))
Column {
Text(
text = "包含服务器备份参与签名",
style = MaterialTheme.typography.bodyMedium
)
Text(
text = "如果您丢失了一个设备,勾选此项以使用服务器备份完成签名",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
}
}
```
### 4. MainActivity.kt传递参数
```kotlin
// 修改 TransferScreen 的 onConfirmTransaction 回调
onConfirmTransaction = { includeServer ->
viewModel.initiateSignSessionWithOptions(
shareId = shareId,
password = "",
includeServerBackup = includeServer
)
}
```
## 测试场景
### 场景12-of-3 正常使用(不勾选)
- 设备A + 设备B 签名 ✅
- 服务器被过滤(现有行为)
### 场景22-of-3 设备丢失(勾选)
- 设备A + 服务器 签名 ✅
- 用户明确勾选"包含服务器备份"
### 场景33-of-5 配置
- 不显示复选框 ✅
- 保持现有行为
## 优势
1.**零后端修改**:后端只接收 parties 数组
2.**完全向后兼容**:默认行为不变
3.**安全限制**:仅 2-of-3 可用
4.**纯新增**:不修改现有方法
5.**用户明确选择**:需要主动勾选
## 实施顺序
1. TssRepository新增辅助方法
2. TssRepository新增 createSignSessionWithOptions
3. MainViewModel新增 initiateSignSessionWithOptions
4. TransferScreen新增 UI 复选框
5. MainActivity传递参数
6. 测试编译和功能