fix(tss-android): 修复备份恢复后钱包无法签名的问题
## 问题根因 备份恢复后的钱包在签名时失败,根本原因是 gRPC 通信使用了**设备的 partyId**, 而不是 **share 的原始 partyId**(keygen 时生成的 partyId)。 这导致: 1. 消息订阅使用错误的 partyId,无法接收其他参与方发送的消息 2. 消息发送使用错误的 fromParty,其他参与方无法正确路由消息 3. Session 事件订阅使用错误的 partyId,无法接收 session_started 等事件 4. API 调用使用错误的 partyId,服务端无法正确识别参与方 ## 修改内容 ### 1. 添加新的成员变量用于跟踪正确的 partyId - `currentMessageRoutingPartyId`: 消息路由使用的 partyId - `currentSessionEventPartyId`: Session 事件订阅使用的 partyId ### 2. 修改 startMessageRouting 方法 - 添加 `routingPartyId` 可选参数 - 签名流程中使用 signingPartyId(share 原始 partyId) - 消息发送 (routeMessage fromParty) 使用正确的 partyId - 消息订阅 (subscribeMessages) 使用正确的 partyId ### 3. 修改 startSessionEventSubscription 方法 - 添加 `subscriptionPartyId` 可选参数 - 签名流程中使用 signingPartyId ### 4. 修改 ensureSessionEventSubscriptionActive 方法 - 添加 `signingPartyId` 可选参数 - 支持动态切换订阅的 partyId ### 5. 修复所有签名流程中的调用 #### joinSignSessionViaGrpc 流程: - grpcClient.joinSession 使用 signingPartyId - startMessageRouting 使用 signingPartyId - ensureSessionEventSubscriptionActive 使用 signingPartyId #### joinSignSessionViaApiAndExecute 流程: - joinSignSessionViaApi HTTP 请求使用 signingPartyId - grpcClient.joinSession 使用 signingPartyId - startMessageRouting 使用 signingPartyId #### createSignSession 流程: - ensureSessionEventSubscriptionActive 使用 signingPartyId - join_tokens 查找使用 originalPartyId - grpcClient.joinSession 使用 signingPartyId - startMessageRouting 使用 signingPartyId #### startSigning 流程: - startMessageRouting 使用 signingPartyId ### 6. 修复 joinSignSessionViaApi 函数 - 添加 signingPartyId 参数 - HTTP 请求体中的 party_id 和 device_id 使用 signingPartyId ### 7. 修复重连恢复逻辑 (restoreStreamsAfterReconnect) - startMessageRouting 使用保存的 currentMessageRoutingPartyId - startSessionEventSubscription 使用保存的 currentSessionEventPartyId ## 测试场景 修复后应支持以下场景: 1. 原设备 keygen → 原设备签名 ✓ 2. 原设备 keygen → 备份 → 新设备恢复 → 新设备发起签名 ✓ 3. 原设备 keygen → 备份 → 新设备恢复 → 新设备参与签名 ✓ Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
37d3300b17
commit
2799eb5a3a
|
|
@ -108,6 +108,11 @@ class TssRepository @Inject constructor(
|
||||||
// Track current message routing params for reconnection recovery
|
// Track current message routing params for reconnection recovery
|
||||||
private var currentMessageRoutingSessionId: String? = null
|
private var currentMessageRoutingSessionId: String? = null
|
||||||
private var currentMessageRoutingPartyIndex: Int? = null
|
private var currentMessageRoutingPartyIndex: Int? = null
|
||||||
|
private var currentMessageRoutingPartyId: String? = null // The partyId used for message routing (may differ from device partyId for restored wallets)
|
||||||
|
|
||||||
|
// Track current session event subscription partyId for reconnection recovery
|
||||||
|
// This may differ from device partyId when signing with restored wallets
|
||||||
|
private var currentSessionEventPartyId: String? = null
|
||||||
|
|
||||||
// Account service URL (configurable via settings)
|
// Account service URL (configurable via settings)
|
||||||
private var accountServiceUrl: String = "https://rwaapi.szaiai.com"
|
private var accountServiceUrl: String = "https://rwaapi.szaiai.com"
|
||||||
|
|
@ -126,17 +131,19 @@ class TssRepository @Inject constructor(
|
||||||
private fun restoreStreamsAfterReconnect() {
|
private fun restoreStreamsAfterReconnect() {
|
||||||
val sessionId = currentMessageRoutingSessionId
|
val sessionId = currentMessageRoutingSessionId
|
||||||
val partyIndex = currentMessageRoutingPartyIndex
|
val partyIndex = currentMessageRoutingPartyIndex
|
||||||
|
val routingPartyId = currentMessageRoutingPartyId
|
||||||
|
|
||||||
// Restore message routing if we had an active session
|
// Restore message routing if we had an active session
|
||||||
if (sessionId != null && partyIndex != null) {
|
if (sessionId != null && partyIndex != null) {
|
||||||
android.util.Log.d("TssRepository", "Restoring message routing for session: $sessionId")
|
android.util.Log.d("TssRepository", "Restoring message routing for session: $sessionId, routingPartyId: $routingPartyId")
|
||||||
startMessageRouting(sessionId, partyIndex)
|
startMessageRouting(sessionId, partyIndex, routingPartyId)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Restore session event subscription
|
// Restore session event subscription with the correct partyId
|
||||||
if (grpcClient.wasEventStreamSubscribed()) {
|
if (grpcClient.wasEventStreamSubscribed()) {
|
||||||
android.util.Log.d("TssRepository", "Restoring session event subscription")
|
val eventPartyId = currentSessionEventPartyId
|
||||||
startSessionEventSubscription()
|
android.util.Log.d("TssRepository", "Restoring session event subscription with partyId: $eventPartyId")
|
||||||
|
startSessionEventSubscription(eventPartyId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -232,12 +239,19 @@ class TssRepository @Inject constructor(
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start session event subscription (called after registration)
|
* Start session event subscription (called after registration)
|
||||||
|
*
|
||||||
|
* @param subscriptionPartyId Optional partyId for subscription. If null, uses device partyId.
|
||||||
|
* CRITICAL: For signing with restored wallets, this should be the original partyId
|
||||||
|
* from keygen (shareEntity.partyId) so that session events are received correctly.
|
||||||
*/
|
*/
|
||||||
private fun startSessionEventSubscription() {
|
private fun startSessionEventSubscription(subscriptionPartyId: String? = null) {
|
||||||
sessionEventJob?.cancel()
|
sessionEventJob?.cancel()
|
||||||
android.util.Log.d("TssRepository", "Starting session event subscription for partyId: $partyId")
|
val effectivePartyId = subscriptionPartyId ?: partyId
|
||||||
|
// Save for reconnection recovery
|
||||||
|
currentSessionEventPartyId = effectivePartyId
|
||||||
|
android.util.Log.d("TssRepository", "Starting session event subscription for partyId: $effectivePartyId (device partyId: $partyId)")
|
||||||
sessionEventJob = repositoryScope.launch {
|
sessionEventJob = repositoryScope.launch {
|
||||||
grpcClient.subscribeSessionEvents(partyId).collect { event ->
|
grpcClient.subscribeSessionEvents(effectivePartyId).collect { event ->
|
||||||
android.util.Log.d("TssRepository", "=== Session event received ===")
|
android.util.Log.d("TssRepository", "=== Session event received ===")
|
||||||
android.util.Log.d("TssRepository", " eventType: ${event.eventType}")
|
android.util.Log.d("TssRepository", " eventType: ${event.eventType}")
|
||||||
android.util.Log.d("TssRepository", " sessionId: ${event.sessionId}")
|
android.util.Log.d("TssRepository", " sessionId: ${event.sessionId}")
|
||||||
|
|
@ -289,20 +303,32 @@ class TssRepository @Inject constructor(
|
||||||
* Ensure session event subscription is active
|
* Ensure session event subscription is active
|
||||||
* Called before critical operations (like joining sign session) to ensure
|
* Called before critical operations (like joining sign session) to ensure
|
||||||
* the event stream hasn't silently disconnected
|
* the event stream hasn't silently disconnected
|
||||||
|
*
|
||||||
|
* @param signingPartyId Optional partyId for signing operations. If provided,
|
||||||
|
* the subscription will use this partyId instead of the device partyId.
|
||||||
|
* CRITICAL: For signing with restored wallets, this should be the original
|
||||||
|
* partyId from keygen (shareEntity.partyId).
|
||||||
*/
|
*/
|
||||||
private fun ensureSessionEventSubscriptionActive() {
|
private fun ensureSessionEventSubscriptionActive(signingPartyId: String? = null) {
|
||||||
// Check if the session event job is still active
|
// Check if the session event job is still active
|
||||||
val isActive = sessionEventJob?.isActive == true
|
val isActive = sessionEventJob?.isActive == true
|
||||||
android.util.Log.d("TssRepository", "Checking session event subscription: isActive=$isActive")
|
val effectivePartyId = signingPartyId ?: currentSessionEventPartyId ?: partyId
|
||||||
|
android.util.Log.d("TssRepository", "Checking session event subscription: isActive=$isActive, effectivePartyId=$effectivePartyId")
|
||||||
|
|
||||||
if (!isActive) {
|
if (!isActive) {
|
||||||
android.util.Log.w("TssRepository", "Session event subscription is not active, restarting...")
|
android.util.Log.w("TssRepository", "Session event subscription is not active, restarting...")
|
||||||
startSessionEventSubscription()
|
startSessionEventSubscription(signingPartyId)
|
||||||
} else {
|
} else {
|
||||||
// Even if the job is "active", the gRPC stream may have silently disconnected
|
// Even if the job is "active", the gRPC stream may have silently disconnected
|
||||||
// Force a restart to ensure we have a fresh connection
|
// Force a restart to ensure we have a fresh connection
|
||||||
android.util.Log.d("TssRepository", "Refreshing session event subscription to ensure fresh connection")
|
// Also restart if we need to switch to a different partyId for signing
|
||||||
startSessionEventSubscription()
|
val needsRestart = signingPartyId != null && signingPartyId != currentSessionEventPartyId
|
||||||
|
if (needsRestart) {
|
||||||
|
android.util.Log.d("TssRepository", "Switching session event subscription to signingPartyId: $signingPartyId")
|
||||||
|
} else {
|
||||||
|
android.util.Log.d("TssRepository", "Refreshing session event subscription to ensure fresh connection")
|
||||||
|
}
|
||||||
|
startSessionEventSubscription(signingPartyId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1186,11 +1212,13 @@ class TssRepository @Inject constructor(
|
||||||
_sessionStatus.value = SessionStatus.WAITING
|
_sessionStatus.value = SessionStatus.WAITING
|
||||||
|
|
||||||
// Start message subscription (matching Electron's prepareForSign)
|
// Start message subscription (matching Electron's prepareForSign)
|
||||||
startMessageRouting(sessionId, myPartyIndex)
|
// CRITICAL: Use signingPartyId (original partyId from keygen) for message routing
|
||||||
|
startMessageRouting(sessionId, myPartyIndex, signingPartyId)
|
||||||
|
|
||||||
// CRITICAL: Ensure session event subscription is active
|
// CRITICAL: Ensure session event subscription is active with signingPartyId
|
||||||
// The event stream may have silently disconnected, so refresh it
|
// The event stream may have silently disconnected, so refresh it
|
||||||
ensureSessionEventSubscriptionActive()
|
// Use signingPartyId so that session events for restored wallets are received
|
||||||
|
ensureSessionEventSubscriptionActive(signingPartyId)
|
||||||
|
|
||||||
android.util.Log.d("TssRepository", "Sign session state set, waiting for session_started event or in_progress status")
|
android.util.Log.d("TssRepository", "Sign session state set, waiting for session_started event or in_progress status")
|
||||||
|
|
||||||
|
|
@ -1512,8 +1540,15 @@ class TssRepository @Inject constructor(
|
||||||
val shareEntity = shareRecordDao.getShareById(shareId)
|
val shareEntity = shareRecordDao.getShareById(shareId)
|
||||||
?: return@coroutineScope Result.failure(Exception("Share not found"))
|
?: return@coroutineScope Result.failure(Exception("Share not found"))
|
||||||
|
|
||||||
|
// CRITICAL: Define signingPartyId BEFORE calling API
|
||||||
|
// Use shareEntity.partyId (original partyId from keygen) for signing
|
||||||
|
val signingPartyId = shareEntity.partyId
|
||||||
|
currentSigningPartyId = signingPartyId // Save for later use in this flow
|
||||||
|
android.util.Log.d("TssRepository", "Using signingPartyId=$signingPartyId for API sign join (device partyId=$partyId)")
|
||||||
|
|
||||||
// Step 1: Call account-service API to join sign session and get party info
|
// Step 1: Call account-service API to join sign session and get party info
|
||||||
val joinApiResult = joinSignSessionViaApi(inviteCode, shareEntity.partyIndex)
|
// CRITICAL: Pass signingPartyId (original partyId from keygen) to the API
|
||||||
|
val joinApiResult = joinSignSessionViaApi(inviteCode, shareEntity.partyIndex, signingPartyId)
|
||||||
if (joinApiResult.isFailure) {
|
if (joinApiResult.isFailure) {
|
||||||
return@coroutineScope Result.failure(joinApiResult.exceptionOrNull()!!)
|
return@coroutineScope Result.failure(joinApiResult.exceptionOrNull()!!)
|
||||||
}
|
}
|
||||||
|
|
@ -1522,7 +1557,8 @@ class TssRepository @Inject constructor(
|
||||||
android.util.Log.d("TssRepository", "API sign join successful: sessionId=${apiJoinData.sessionId}, partyIndex=${apiJoinData.partyIndex}, messageHash=${apiJoinData.messageHash}")
|
android.util.Log.d("TssRepository", "API sign join successful: sessionId=${apiJoinData.sessionId}, partyIndex=${apiJoinData.partyIndex}, messageHash=${apiJoinData.messageHash}")
|
||||||
|
|
||||||
// Step 2: Join session via gRPC for message routing
|
// Step 2: Join session via gRPC for message routing
|
||||||
val joinResult = grpcClient.joinSession(apiJoinData.sessionId, partyId, apiJoinData.joinToken)
|
// CRITICAL: Use signingPartyId (original partyId from keygen) for backup/restore support
|
||||||
|
val joinResult = grpcClient.joinSession(apiJoinData.sessionId, signingPartyId, apiJoinData.joinToken)
|
||||||
if (joinResult.isFailure) {
|
if (joinResult.isFailure) {
|
||||||
android.util.Log.e("TssRepository", "gRPC join failed", joinResult.exceptionOrNull())
|
android.util.Log.e("TssRepository", "gRPC join failed", joinResult.exceptionOrNull())
|
||||||
return@coroutineScope Result.failure(joinResult.exceptionOrNull()!!)
|
return@coroutineScope Result.failure(joinResult.exceptionOrNull()!!)
|
||||||
|
|
@ -1547,10 +1583,7 @@ class TssRepository @Inject constructor(
|
||||||
_currentSession.value = session
|
_currentSession.value = session
|
||||||
_sessionStatus.value = SessionStatus.WAITING
|
_sessionStatus.value = SessionStatus.WAITING
|
||||||
|
|
||||||
// Add self to participants
|
// Add self to participants using signingPartyId
|
||||||
// CRITICAL: Use shareEntity.partyId (original partyId from keygen) for signing
|
|
||||||
val signingPartyId = shareEntity.partyId
|
|
||||||
currentSigningPartyId = signingPartyId // Save for later use in this flow
|
|
||||||
val allParticipants = sessionData.participants + Participant(signingPartyId, myPartyIndex)
|
val allParticipants = sessionData.participants + Participant(signingPartyId, myPartyIndex)
|
||||||
|
|
||||||
// Start TSS sign
|
// Start TSS sign
|
||||||
|
|
@ -1573,7 +1606,8 @@ class TssRepository @Inject constructor(
|
||||||
_sessionStatus.value = SessionStatus.IN_PROGRESS
|
_sessionStatus.value = SessionStatus.IN_PROGRESS
|
||||||
|
|
||||||
// Start message routing
|
// Start message routing
|
||||||
startMessageRouting(apiJoinData.sessionId, myPartyIndex)
|
// CRITICAL: Use signingPartyId (original partyId from keygen) for message routing
|
||||||
|
startMessageRouting(apiJoinData.sessionId, myPartyIndex, signingPartyId)
|
||||||
|
|
||||||
// Mark ready - use signingPartyId (original partyId from keygen)
|
// Mark ready - use signingPartyId (original partyId from keygen)
|
||||||
grpcClient.markPartyReady(apiJoinData.sessionId, signingPartyId)
|
grpcClient.markPartyReady(apiJoinData.sessionId, signingPartyId)
|
||||||
|
|
@ -1608,8 +1642,13 @@ class TssRepository @Inject constructor(
|
||||||
/**
|
/**
|
||||||
* Join sign session via account-service HTTP API
|
* Join sign session via account-service HTTP API
|
||||||
* Uses validateSignInviteCode to get session info, then joins
|
* Uses validateSignInviteCode to get session info, then joins
|
||||||
|
*
|
||||||
|
* @param inviteCode The invite code for the sign session
|
||||||
|
* @param partyIndex The party index for this participant
|
||||||
|
* @param signingPartyId The original partyId from keygen (shareEntity.partyId)
|
||||||
|
* CRITICAL: For backup/restore support, this must be the original partyId
|
||||||
*/
|
*/
|
||||||
private suspend fun joinSignSessionViaApi(inviteCode: String, partyIndex: Int): Result<ApiJoinSignSessionData> {
|
private suspend fun joinSignSessionViaApi(inviteCode: String, partyIndex: Int, signingPartyId: String): Result<ApiJoinSignSessionData> {
|
||||||
return withContext(Dispatchers.IO) {
|
return withContext(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
// First, get sign session info by invite code
|
// First, get sign session info by invite code
|
||||||
|
|
@ -1625,13 +1664,14 @@ class TssRepository @Inject constructor(
|
||||||
android.util.Log.d("TssRepository", "Got sign session info: sessionId=$sessionId, messageHash=${signSessionInfo.messageHash}, joinToken=$joinToken")
|
android.util.Log.d("TssRepository", "Got sign session info: sessionId=$sessionId, messageHash=${signSessionInfo.messageHash}, joinToken=$joinToken")
|
||||||
|
|
||||||
// Now call join API (same endpoint as keygen join, but for sign sessions)
|
// Now call join API (same endpoint as keygen join, but for sign sessions)
|
||||||
|
// CRITICAL: Use signingPartyId (original partyId from keygen) for backup/restore support
|
||||||
val jsonMediaType = "application/json; charset=utf-8".toMediaType()
|
val jsonMediaType = "application/json; charset=utf-8".toMediaType()
|
||||||
val requestBody = com.google.gson.JsonObject().apply {
|
val requestBody = com.google.gson.JsonObject().apply {
|
||||||
addProperty("party_id", partyId)
|
addProperty("party_id", signingPartyId) // Use original partyId from keygen
|
||||||
addProperty("join_token", joinToken)
|
addProperty("join_token", joinToken)
|
||||||
addProperty("party_index", partyIndex)
|
addProperty("party_index", partyIndex)
|
||||||
addProperty("device_type", "mobile")
|
addProperty("device_type", "mobile")
|
||||||
addProperty("device_id", partyId)
|
addProperty("device_id", signingPartyId) // Use original partyId from keygen
|
||||||
}.toString()
|
}.toString()
|
||||||
|
|
||||||
val request = okhttp3.Request.Builder()
|
val request = okhttp3.Request.Builder()
|
||||||
|
|
@ -1679,21 +1719,35 @@ class TssRepository @Inject constructor(
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start message routing between TSS and gRPC
|
* Start message routing between TSS and gRPC
|
||||||
|
*
|
||||||
|
* @param sessionId The session ID
|
||||||
|
* @param partyIndex The party index for this participant
|
||||||
|
* @param routingPartyId The party ID to use for message routing.
|
||||||
|
* CRITICAL: For signing with restored wallets, this MUST be the original partyId
|
||||||
|
* from keygen (shareEntity.partyId), not the current device's partyId.
|
||||||
|
* For keygen, this should be the device's partyId.
|
||||||
*/
|
*/
|
||||||
private fun startMessageRouting(sessionId: String, partyIndex: Int) {
|
private fun startMessageRouting(sessionId: String, partyIndex: Int, routingPartyId: String? = null) {
|
||||||
// Save params for reconnection recovery
|
// Save params for reconnection recovery
|
||||||
currentMessageRoutingSessionId = sessionId
|
currentMessageRoutingSessionId = sessionId
|
||||||
currentMessageRoutingPartyIndex = partyIndex
|
currentMessageRoutingPartyIndex = partyIndex
|
||||||
|
|
||||||
|
// Use provided routingPartyId, or fall back to device partyId for keygen
|
||||||
|
val effectivePartyId = routingPartyId ?: partyId
|
||||||
|
// Save for reconnection recovery
|
||||||
|
currentMessageRoutingPartyId = effectivePartyId
|
||||||
|
|
||||||
messageCollectionJob?.cancel()
|
messageCollectionJob?.cancel()
|
||||||
messageCollectionJob = repositoryScope.launch {
|
messageCollectionJob = repositoryScope.launch {
|
||||||
|
android.util.Log.d("TssRepository", "Starting message routing: sessionId=$sessionId, routingPartyId=$effectivePartyId")
|
||||||
|
|
||||||
// Collect outgoing messages from TSS and route via gRPC
|
// Collect outgoing messages from TSS and route via gRPC
|
||||||
launch {
|
launch {
|
||||||
tssNativeBridge.outgoingMessages.collect { message ->
|
tssNativeBridge.outgoingMessages.collect { message ->
|
||||||
val payload = Base64.decode(message.payload, Base64.NO_WRAP)
|
val payload = Base64.decode(message.payload, Base64.NO_WRAP)
|
||||||
grpcClient.routeMessage(
|
grpcClient.routeMessage(
|
||||||
sessionId = sessionId,
|
sessionId = sessionId,
|
||||||
fromParty = partyId,
|
fromParty = effectivePartyId, // Use the correct partyId for routing
|
||||||
toParties = message.toParties ?: emptyList(),
|
toParties = message.toParties ?: emptyList(),
|
||||||
roundNumber = 0,
|
roundNumber = 0,
|
||||||
messageType = if (message.isBroadcast) "broadcast" else "p2p",
|
messageType = if (message.isBroadcast) "broadcast" else "p2p",
|
||||||
|
|
@ -1704,7 +1758,7 @@ class TssRepository @Inject constructor(
|
||||||
|
|
||||||
// Collect incoming messages from gRPC and send to TSS
|
// Collect incoming messages from gRPC and send to TSS
|
||||||
launch {
|
launch {
|
||||||
grpcClient.subscribeMessages(sessionId, partyId).collect { message ->
|
grpcClient.subscribeMessages(sessionId, effectivePartyId).collect { message ->
|
||||||
// Find party index from party ID
|
// Find party index from party ID
|
||||||
val session = _currentSession.value
|
val session = _currentSession.value
|
||||||
val fromPartyIndex = session?.participants?.find { it.partyId == message.fromParty }?.partyIndex
|
val fromPartyIndex = session?.participants?.find { it.partyId == message.fromParty }?.partyIndex
|
||||||
|
|
@ -2195,15 +2249,19 @@ class TssRepository @Inject constructor(
|
||||||
): Result<SignSessionResult> {
|
): Result<SignSessionResult> {
|
||||||
return withContext(Dispatchers.IO) {
|
return withContext(Dispatchers.IO) {
|
||||||
try {
|
try {
|
||||||
// CRITICAL: Ensure session event subscription is active BEFORE creating sign session
|
// Get share record first to determine the signingPartyId
|
||||||
// This prevents race condition where session_started event arrives before subscription is ready
|
|
||||||
android.util.Log.d("TssRepository", "[CO-SIGN] Ensuring session event subscription is active before creating sign session")
|
|
||||||
ensureSessionEventSubscriptionActive()
|
|
||||||
|
|
||||||
// Get share record
|
|
||||||
val shareEntity = shareRecordDao.getShareById(shareId)
|
val shareEntity = shareRecordDao.getShareById(shareId)
|
||||||
?: return@withContext Result.failure(Exception("Share not found"))
|
?: return@withContext Result.failure(Exception("Share not found"))
|
||||||
|
|
||||||
|
// CRITICAL: Use shareEntity.partyId (original partyId from keygen) for session events
|
||||||
|
val signingPartyIdForEvents = shareEntity.partyId
|
||||||
|
|
||||||
|
// CRITICAL: Ensure session event subscription is active BEFORE creating sign session
|
||||||
|
// This prevents race condition where session_started event arrives before subscription is ready
|
||||||
|
// Use signingPartyId so that session events for restored wallets are received
|
||||||
|
android.util.Log.d("TssRepository", "[CO-SIGN] Ensuring session event subscription is active before creating sign session (signingPartyId=$signingPartyIdForEvents)")
|
||||||
|
ensureSessionEventSubscriptionActive(signingPartyIdForEvents)
|
||||||
|
|
||||||
android.util.Log.d("TssRepository", "[CO-SIGN] Creating sign session for share: ${shareEntity.sessionId}")
|
android.util.Log.d("TssRepository", "[CO-SIGN] Creating sign session for share: ${shareEntity.sessionId}")
|
||||||
|
|
||||||
// Step 1: Get keygen session status to get all participants with party_index
|
// Step 1: Get keygen session status to get all participants with party_index
|
||||||
|
|
@ -2296,20 +2354,26 @@ class TssRepository @Inject constructor(
|
||||||
val inviteCode = json.get("invite_code").asString
|
val inviteCode = json.get("invite_code").asString
|
||||||
val thresholdT = json.get("threshold_t")?.asInt ?: shareEntity.thresholdT
|
val thresholdT = json.get("threshold_t")?.asInt ?: shareEntity.thresholdT
|
||||||
|
|
||||||
|
// CRITICAL: Use shareEntity.partyId (original partyId from keygen) for join token lookup
|
||||||
|
// The server generates join_tokens keyed by original partyId, not device partyId
|
||||||
|
val originalPartyId = shareEntity.partyId
|
||||||
|
|
||||||
// Get join token - support both new format (join_tokens map) and old format (join_token string)
|
// Get join token - support both new format (join_tokens map) and old format (join_token string)
|
||||||
val joinToken = if (json.has("join_tokens") && json.get("join_tokens").isJsonObject) {
|
val joinToken = if (json.has("join_tokens") && json.get("join_tokens").isJsonObject) {
|
||||||
val joinTokens = json.getAsJsonObject("join_tokens")
|
val joinTokens = json.getAsJsonObject("join_tokens")
|
||||||
joinTokens.get(partyId)?.asString ?: joinTokens.get("*")?.asString
|
// CRITICAL: Use originalPartyId (from keygen) to look up join token
|
||||||
|
joinTokens.get(originalPartyId)?.asString ?: joinTokens.get("*")?.asString
|
||||||
} else {
|
} else {
|
||||||
json.get("join_token")?.asString
|
json.get("join_token")?.asString
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build participants list from signingParties
|
// Build participants list from signingParties
|
||||||
|
// CRITICAL: Use originalPartyId for name comparison (not device partyId)
|
||||||
val participants = signingParties.map { (pId, pIndex) ->
|
val participants = signingParties.map { (pId, pIndex) ->
|
||||||
Participant(
|
Participant(
|
||||||
partyId = pId,
|
partyId = pId,
|
||||||
partyIndex = pIndex,
|
partyIndex = pIndex,
|
||||||
name = if (pId == partyId) initiatorName else "参与方 ${pIndex + 1}"
|
name = if (pId == originalPartyId) initiatorName else "参与方 ${pIndex + 1}"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2334,22 +2398,31 @@ class TssRepository @Inject constructor(
|
||||||
_sessionStatus.value = SessionStatus.WAITING
|
_sessionStatus.value = SessionStatus.WAITING
|
||||||
|
|
||||||
// Step 4: Initiator auto-join via gRPC (matching Electron behavior)
|
// Step 4: Initiator auto-join via gRPC (matching Electron behavior)
|
||||||
|
// CRITICAL: Use shareEntity.partyId (original partyId from keygen) for signing
|
||||||
|
// This is essential for backup/restore to work correctly
|
||||||
|
val signingPartyId = shareEntity.partyId
|
||||||
|
currentSigningPartyId = signingPartyId // Save for later use in this flow
|
||||||
|
android.util.Log.d("TssRepository", "[CO-SIGN] Using signingPartyId=$signingPartyId (device partyId=$partyId)")
|
||||||
|
|
||||||
var sessionAlreadyInProgress = false
|
var sessionAlreadyInProgress = false
|
||||||
if (joinToken != null) {
|
if (joinToken != null) {
|
||||||
android.util.Log.d("TssRepository", "[CO-SIGN] Initiator auto-joining session...")
|
android.util.Log.d("TssRepository", "[CO-SIGN] Initiator auto-joining session...")
|
||||||
val myPartyIndex = signingParties.find { it.first == partyId }?.second ?: shareEntity.partyIndex
|
val myPartyIndex = signingParties.find { it.first == signingPartyId }?.second ?: shareEntity.partyIndex
|
||||||
|
|
||||||
val joinResult = grpcClient.joinSession(sessionId, partyId, joinToken)
|
// CRITICAL: Use signingPartyId (original partyId from keygen) for joining
|
||||||
|
val joinResult = grpcClient.joinSession(sessionId, signingPartyId, joinToken)
|
||||||
if (joinResult.isSuccess) {
|
if (joinResult.isSuccess) {
|
||||||
val joinData = joinResult.getOrThrow()
|
val joinData = joinResult.getOrThrow()
|
||||||
android.util.Log.d("TssRepository", "[CO-SIGN] Initiator joined: partyIndex=${joinData.partyIndex}, status=${joinData.sessionStatus}")
|
android.util.Log.d("TssRepository", "[CO-SIGN] Initiator joined: partyIndex=${joinData.partyIndex}, status=${joinData.sessionStatus}")
|
||||||
|
|
||||||
// Step 5: Start message routing (prepareForSign) BEFORE sign starts
|
// Step 5: Start message routing (prepareForSign) BEFORE sign starts
|
||||||
startMessageRouting(sessionId, myPartyIndex)
|
// CRITICAL: Use signingPartyId for message routing
|
||||||
|
startMessageRouting(sessionId, myPartyIndex, signingPartyId)
|
||||||
|
|
||||||
// CRITICAL: Ensure session event subscription is active for sign initiator
|
// CRITICAL: Ensure session event subscription is active for sign initiator
|
||||||
// The event stream may have silently disconnected
|
// The event stream may have silently disconnected
|
||||||
ensureSessionEventSubscriptionActive()
|
// Use signingPartyId so that session events for restored wallets are received
|
||||||
|
ensureSessionEventSubscriptionActive(signingPartyId)
|
||||||
|
|
||||||
// Step 6: Check if session already in_progress
|
// Step 6: Check if session already in_progress
|
||||||
if (joinData.sessionStatus == "in_progress") {
|
if (joinData.sessionStatus == "in_progress") {
|
||||||
|
|
@ -2456,7 +2529,8 @@ class TssRepository @Inject constructor(
|
||||||
// Note: Message routing is already started in createSignSession after auto-join
|
// Note: Message routing is already started in createSignSession after auto-join
|
||||||
// Only start if not already running (for backward compatibility with old flow)
|
// Only start if not already running (for backward compatibility with old flow)
|
||||||
if (messageCollectionJob == null || messageCollectionJob?.isActive != true) {
|
if (messageCollectionJob == null || messageCollectionJob?.isActive != true) {
|
||||||
startMessageRouting(sessionId, shareEntity.partyIndex)
|
// CRITICAL: Use signingPartyId for message routing
|
||||||
|
startMessageRouting(sessionId, shareEntity.partyIndex, signingPartyId)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mark ready - use signingPartyId (original partyId from keygen)
|
// Mark ready - use signingPartyId (original partyId from keygen)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue