diff --git a/backend/mpc-system/services/service-party-android/app/build.gradle.kts b/backend/mpc-system/services/service-party-android/app/build.gradle.kts index 2d6dceef..937ab756 100644 --- a/backend/mpc-system/services/service-party-android/app/build.gradle.kts +++ b/backend/mpc-system/services/service-party-android/app/build.gradle.kts @@ -39,8 +39,9 @@ android { } // NDK configuration for TSS native library + // Only include ARM ABIs for real devices (x86_64 is for emulators only) ndk { - abiFilters += listOf("arm64-v8a", "armeabi-v7a", "x86_64") + abiFilters += listOf("arm64-v8a", "armeabi-v7a") } } diff --git a/backend/mpc-system/services/service-party-android/app/src/main/java/com/durian/tssparty/data/repository/TssRepository.kt b/backend/mpc-system/services/service-party-android/app/src/main/java/com/durian/tssparty/data/repository/TssRepository.kt index 120c1b94..4ac5e70b 100644 --- a/backend/mpc-system/services/service-party-android/app/src/main/java/com/durian/tssparty/data/repository/TssRepository.kt +++ b/backend/mpc-system/services/service-party-android/app/src/main/java/com/durian/tssparty/data/repository/TssRepository.kt @@ -1126,14 +1126,26 @@ class TssRepository @Inject constructor( // Note: Password is verified during actual sign execution, same as Electron + // CRITICAL: Use the original partyId from the share (keygen time) for signing + // This is essential for backup/restore - the partyId must match what was used during keygen + // If shareEntity.partyId is empty (legacy data), fall back to current device's partyId + val signingPartyId = if (shareEntity.partyId.isNotEmpty()) { + shareEntity.partyId + } else { + android.util.Log.w("TssRepository", "Share has no partyId (legacy data), using current device partyId") + partyId + } + currentSigningPartyId = signingPartyId // Save for later use in this flow + android.util.Log.d("TssRepository", "Using signingPartyId=$signingPartyId (current device partyId=$partyId)") + // CRITICAL: Set pendingSessionId BEFORE joinSession to avoid race condition // This ensures session_started events can be matched even if they arrive // before _currentSession is set pendingSessionId = sessionId android.util.Log.d("TssRepository", "Set pendingSessionId=$sessionId for event matching (sign joiner)") - // Join session via gRPC (matching Electron's grpcClient.joinSession) - val joinResult = grpcClient.joinSession(sessionId, partyId, joinToken) + // Join session via gRPC using the original partyId from keygen (CRITICAL for backup/restore) + val joinResult = grpcClient.joinSession(sessionId, signingPartyId, joinToken) if (joinResult.isFailure) { android.util.Log.e("TssRepository", "gRPC sign join failed", joinResult.exceptionOrNull()) return@withContext Result.failure(joinResult.exceptionOrNull()!!) @@ -1148,12 +1160,13 @@ class TssRepository @Inject constructor( // Build participants list (matching Electron's logic) // Prefer using parties from validateInviteCode (complete list) + // CRITICAL: Use signingPartyId (original partyId from keygen) for participant identification val participants = if (parties.isNotEmpty()) { parties.toMutableList() } else { // Fallback: use other_parties + self val list = sessionData.participants.toMutableList() - list.add(Participant(partyId, myPartyIndex, "我")) + list.add(Participant(signingPartyId, myPartyIndex, "我")) list.sortBy { it.partyIndex } list } diff --git a/backend/mpc-system/services/service-party-app/electron/main.ts b/backend/mpc-system/services/service-party-app/electron/main.ts index 278a01a2..2f22e630 100644 --- a/backend/mpc-system/services/service-party-app/electron/main.ts +++ b/backend/mpc-system/services/service-party-app/electron/main.ts @@ -821,6 +821,21 @@ async function handleCoSignStart(event: { // 标记签名开始 signInProgressSessionId = event.sessionId; + // CRITICAL: Get the original partyId from keygen (stored in share) for signing + // This is essential for backup/restore - the partyId must match what was used during keygen + const share = database?.getShare(activeCoSignSession.shareId, activeCoSignSession.sharePassword); + if (!share) { + debugLog.error('main', 'Failed to get share data'); + mainWindow?.webContents.send(`cosign:events:${event.sessionId}`, { + type: 'failed', + error: 'Failed to get share data', + }); + signInProgressSessionId = null; + return; + } + const signingPartyId = share.party_id || grpcClient?.getPartyId() || ''; + debugLog.info('main', `Using signingPartyId=${signingPartyId} (currentDevicePartyId=${grpcClient?.getPartyId()})`); + // 打印当前 activeCoSignSession.participants 状态 console.log('[CO-SIGN] Current activeCoSignSession.participants before update:', activeCoSignSession.participants.map(p => ({ @@ -832,8 +847,9 @@ async function handleCoSignStart(event: { // 从 event.selectedParties 更新参与者列表 // 优先使用 activeCoSignSession.participants 中的 partyIndex(来自 signingParties 或 other_parties) + // CRITICAL: Use signingPartyId (original from keygen) for identification if (event.selectedParties && event.selectedParties.length > 0) { - const myPartyId = grpcClient?.getPartyId(); + const myPartyId = signingPartyId; const updatedParticipants: Array<{ partyId: string; partyIndex: number; name: string }> = []; event.selectedParties.forEach((partyId) => { @@ -869,21 +885,11 @@ async function handleCoSignStart(event: { }))); } - // 获取 share 数据 - const share = database?.getShare(activeCoSignSession.shareId, activeCoSignSession.sharePassword); - if (!share) { - debugLog.error('main', 'Failed to get share data'); - mainWindow?.webContents.send(`cosign:events:${event.sessionId}`, { - type: 'failed', - error: 'Failed to get share data', - }); - signInProgressSessionId = null; - return; - } + // Note: share already fetched above for getting signingPartyId console.log('[CO-SIGN] Calling tssHandler.participateSign with:', { sessionId: activeCoSignSession.sessionId, - partyId: grpcClient?.getPartyId(), + partyId: signingPartyId, // CRITICAL: Use signingPartyId (original from keygen) partyIndex: activeCoSignSession.partyIndex, participants: activeCoSignSession.participants.map(p => ({ partyId: p.partyId.substring(0, 8), partyIndex: p.partyIndex })), threshold: activeCoSignSession.threshold, @@ -892,9 +898,10 @@ async function handleCoSignStart(event: { debugLog.info('tss', `Starting sign for session ${event.sessionId}...`); try { + // CRITICAL: Use signingPartyId (original partyId from keygen) for signing const result = await (tssHandler as TSSHandler).participateSign( activeCoSignSession.sessionId, - grpcClient?.getPartyId() || '', + signingPartyId, // CRITICAL: Use original partyId from keygen for backup/restore to work activeCoSignSession.partyIndex, activeCoSignSession.participants, activeCoSignSession.threshold, @@ -1613,9 +1620,9 @@ function setupIpcHandlers() { initiatorName?: string; }) => { try { - // 获取当前 party ID - const partyId = grpcClient?.getPartyId(); - if (!partyId) { + // 获取当前 party ID (用于检查连接状态) + const currentDevicePartyId = grpcClient?.getPartyId(); + if (!currentDevicePartyId) { return { success: false, error: '请先连接到消息路由器' }; } @@ -1625,6 +1632,11 @@ function setupIpcHandlers() { return { success: false, error: 'Share 不存在或密码错误' }; } + // CRITICAL: Use the original partyId from keygen (stored in share) for signing + // This is essential for backup/restore - the partyId must match what was used during keygen + const partyId = share.party_id || currentDevicePartyId; + debugLog.info('main', `Initiator using partyId=${partyId} (currentDevicePartyId=${currentDevicePartyId})`); + // 从后端获取 keygen 会话的参与者信息(包含正确的 party_index) const keygenStatus = await accountClient?.getSessionStatus(share.session_id); if (!keygenStatus?.participants || keygenStatus.participants.length === 0) { @@ -1810,8 +1822,8 @@ function setupIpcHandlers() { parties?: Array<{ party_id: string; party_index: number }>; }) => { try { - const partyId = grpcClient?.getPartyId(); - if (!partyId) { + const currentDevicePartyId = grpcClient?.getPartyId(); + if (!currentDevicePartyId) { return { success: false, error: '请先连接到消息路由器' }; } @@ -1821,9 +1833,12 @@ function setupIpcHandlers() { return { success: false, error: 'Share 不存在或密码错误' }; } - debugLog.info('grpc', `Joining co-sign session: sessionId=${params.sessionId}, partyId=${partyId}`); + // CRITICAL: Use the original partyId from keygen (stored in share) for signing + // This is essential for backup/restore - the partyId must match what was used during keygen + const signingPartyId = share.party_id || currentDevicePartyId; + debugLog.info('grpc', `Joining co-sign session: sessionId=${params.sessionId}, signingPartyId=${signingPartyId} (currentDevicePartyId=${currentDevicePartyId})`); - const result = await grpcClient?.joinSession(params.sessionId, partyId, params.joinToken); + const result = await grpcClient?.joinSession(params.sessionId, signingPartyId, params.joinToken); if (result?.success) { // 设置活跃的 Co-Sign 会话 // 优先使用 params.parties(来自 validateInviteCode,包含所有预期参与者) @@ -1832,10 +1847,11 @@ function setupIpcHandlers() { if (params.parties && params.parties.length > 0) { // 使用完整的 parties 列表 + // CRITICAL: Use signingPartyId (original from keygen) for identification participants = params.parties.map(p => ({ partyId: p.party_id, partyIndex: p.party_index, - name: p.party_id === partyId ? '我' : `参与方 ${p.party_index + 1}`, + name: p.party_id === signingPartyId ? '我' : `参与方 ${p.party_index + 1}`, })); console.log('[CO-SIGN] Participant using params.parties (complete list):', participants.map(p => ({ partyId: p.partyId.substring(0, 8), @@ -1850,9 +1866,9 @@ function setupIpcHandlers() { name: `参与方 ${idx + 1}`, })) || []; - // 添加自己 + // 添加自己 - CRITICAL: Use signingPartyId (original from keygen) participants.push({ - partyId: partyId, + partyId: signingPartyId, partyIndex: result.party_index, name: '我', }); @@ -1886,11 +1902,11 @@ function setupIpcHandlers() { messageHash: params.messageHash, }); - // 预订阅消息流 + // 预订阅消息流 - CRITICAL: Use signingPartyId (original from keygen) if (tssHandler && 'prepareForSign' in tssHandler) { try { - debugLog.info('tss', `Preparing for sign: subscribing to messages for session ${params.sessionId}`); - (tssHandler as TSSHandler).prepareForSign(params.sessionId, partyId); + debugLog.info('tss', `Preparing for sign: subscribing to messages for session ${params.sessionId}, signingPartyId=${signingPartyId}`); + (tssHandler as TSSHandler).prepareForSign(params.sessionId, signingPartyId); } catch (prepareErr) { debugLog.error('tss', `Failed to prepare for sign: ${(prepareErr as Error).message}`); return { success: false, error: `消息订阅失败: ${(prepareErr as Error).message}` };