fix(tss-android): 修复备份恢复后无法签名的问题
问题原因: 备份数据中缺少 partyId 字段。恢复到新手机后,签名时使用的是新设备 生成的 partyId,而不是 keygen 时编码到 LocalPartySaveData 中的 原始 partyId,导致 TSS 签名协议无法正确匹配密钥数据而失败。 修复内容: 1. Models.kt: - ShareRecord 添加 partyId 字段 - ShareBackup 添加 partyId 字段,备份格式版本升级到 v2 - 更新 fromShareRecord() 和 toShareRecord() 方法 2. Database.kt: - ShareRecordEntity 添加 party_id 列 - 数据库版本升级到 3 3. AppModule.kt: - 添加 MIGRATION_2_3 数据库迁移脚本 4. TssRepository.kt: - 添加 currentSigningPartyId 成员变量跟踪当前签名使用的 partyId - keygen 保存时包含 partyId (3处) - 备份导入时保存原始 partyId - 签名流程使用 shareEntity.partyId 替代设备 partyId (3处) - gRPC 调用 (markPartyReady, reportCompletion) 使用原始 partyId 关键点: 签名时必须使用 keygen 时的原始 partyId,因为该 ID 被编码 到了 TSS 密钥数据结构中。现在备份会保存此关键字段,恢复后签名 将使用正确的 partyId。 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
b00de68b01
commit
a47b935bce
|
|
@ -29,6 +29,9 @@ data class ShareRecordEntity(
|
||||||
@ColumnInfo(name = "party_index")
|
@ColumnInfo(name = "party_index")
|
||||||
val partyIndex: Int,
|
val partyIndex: Int,
|
||||||
|
|
||||||
|
@ColumnInfo(name = "party_id")
|
||||||
|
val partyId: String, // The original partyId used during keygen - required for signing
|
||||||
|
|
||||||
@ColumnInfo(name = "address")
|
@ColumnInfo(name = "address")
|
||||||
val address: String,
|
val address: String,
|
||||||
|
|
||||||
|
|
@ -95,7 +98,7 @@ interface AppSettingDao {
|
||||||
*/
|
*/
|
||||||
@Database(
|
@Database(
|
||||||
entities = [ShareRecordEntity::class, AppSettingEntity::class],
|
entities = [ShareRecordEntity::class, AppSettingEntity::class],
|
||||||
version = 2,
|
version = 3, // Version 3: added party_id column to share_records
|
||||||
exportSchema = false
|
exportSchema = false
|
||||||
)
|
)
|
||||||
abstract class TssDatabase : RoomDatabase() {
|
abstract class TssDatabase : RoomDatabase() {
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,12 @@ class TssRepository @Inject constructor(
|
||||||
// partyId is loaded once from database in registerParty() and cached here
|
// partyId is loaded once from database in registerParty() and cached here
|
||||||
// This matches Electron's getOrCreatePartyId() pattern
|
// This matches Electron's getOrCreatePartyId() pattern
|
||||||
private lateinit var partyId: String
|
private lateinit var partyId: String
|
||||||
|
|
||||||
|
// currentSigningPartyId: The partyId to use for the current signing session
|
||||||
|
// This may differ from partyId when signing with a restored wallet backup
|
||||||
|
// CRITICAL: For backup/restore to work, signing must use the original partyId from keygen
|
||||||
|
private var currentSigningPartyId: String? = null
|
||||||
|
|
||||||
private var messageCollectionJob: Job? = null
|
private var messageCollectionJob: Job? = null
|
||||||
private var sessionEventJob: Job? = null
|
private var sessionEventJob: Job? = null
|
||||||
|
|
||||||
|
|
@ -1051,6 +1057,7 @@ class TssRepository @Inject constructor(
|
||||||
val address = AddressUtils.deriveEvmAddress(publicKeyBytes)
|
val address = AddressUtils.deriveEvmAddress(publicKeyBytes)
|
||||||
|
|
||||||
// Save share record (use actual thresholds and party index from backend)
|
// Save share record (use actual thresholds and party index from backend)
|
||||||
|
// CRITICAL: Save partyId - this is required for signing after backup/restore
|
||||||
val shareEntity = ShareRecordEntity(
|
val shareEntity = ShareRecordEntity(
|
||||||
sessionId = sessionId,
|
sessionId = sessionId,
|
||||||
publicKey = result.publicKey,
|
publicKey = result.publicKey,
|
||||||
|
|
@ -1058,6 +1065,7 @@ class TssRepository @Inject constructor(
|
||||||
thresholdT = actualThresholdT,
|
thresholdT = actualThresholdT,
|
||||||
thresholdN = actualThresholdN,
|
thresholdN = actualThresholdN,
|
||||||
partyIndex = actualPartyIndex,
|
partyIndex = actualPartyIndex,
|
||||||
|
partyId = partyId,
|
||||||
address = address
|
address = address
|
||||||
)
|
)
|
||||||
val id = shareRecordDao.insertShare(shareEntity)
|
val id = shareRecordDao.insertShare(shareEntity)
|
||||||
|
|
@ -1222,10 +1230,14 @@ class TssRepository @Inject constructor(
|
||||||
} else {
|
} else {
|
||||||
messageHash
|
messageHash
|
||||||
}
|
}
|
||||||
android.util.Log.d("TssRepository", "Starting TSS sign with cleanMessageHash=${cleanMessageHash.take(20)}...")
|
// CRITICAL: Use shareEntity.partyId (original partyId from keygen) for signing
|
||||||
|
// This is required for backup/restore to work - the partyId must match what was used during keygen
|
||||||
|
val signingPartyId = shareEntity.partyId
|
||||||
|
currentSigningPartyId = signingPartyId // Save for later use in this flow
|
||||||
|
android.util.Log.d("TssRepository", "Starting TSS sign with cleanMessageHash=${cleanMessageHash.take(20)}..., signingPartyId=$signingPartyId")
|
||||||
val startResult = tssNativeBridge.startSign(
|
val startResult = tssNativeBridge.startSign(
|
||||||
sessionId = sessionId,
|
sessionId = sessionId,
|
||||||
partyId = partyId,
|
partyId = signingPartyId,
|
||||||
partyIndex = partyIndex,
|
partyIndex = partyIndex,
|
||||||
thresholdT = thresholdT,
|
thresholdT = thresholdT,
|
||||||
thresholdN = shareEntity.thresholdN, // Use original N from keygen
|
thresholdN = shareEntity.thresholdN, // Use original N from keygen
|
||||||
|
|
@ -1243,8 +1255,8 @@ class TssRepository @Inject constructor(
|
||||||
// Start collecting progress from native bridge
|
// Start collecting progress from native bridge
|
||||||
startProgressCollection()
|
startProgressCollection()
|
||||||
|
|
||||||
// Mark ready
|
// Mark ready - use signingPartyId (original partyId from keygen)
|
||||||
grpcClient.markPartyReady(sessionId, partyId)
|
grpcClient.markPartyReady(sessionId, signingPartyId)
|
||||||
|
|
||||||
// Wait for sign result
|
// Wait for sign result
|
||||||
val signResult = tssNativeBridge.waitForSignResult()
|
val signResult = tssNativeBridge.waitForSignResult()
|
||||||
|
|
@ -1256,14 +1268,15 @@ class TssRepository @Inject constructor(
|
||||||
|
|
||||||
val result = signResult.getOrThrow()
|
val result = signResult.getOrThrow()
|
||||||
|
|
||||||
// Report completion
|
// Report completion - use signingPartyId (original partyId from keygen)
|
||||||
val signatureBytes = android.util.Base64.decode(result.signature, android.util.Base64.NO_WRAP)
|
val signatureBytes = android.util.Base64.decode(result.signature, android.util.Base64.NO_WRAP)
|
||||||
grpcClient.reportCompletion(sessionId, partyId, signature = signatureBytes)
|
grpcClient.reportCompletion(sessionId, signingPartyId, signature = signatureBytes)
|
||||||
|
|
||||||
stopProgressCollection()
|
stopProgressCollection()
|
||||||
_sessionStatus.value = SessionStatus.COMPLETED
|
_sessionStatus.value = SessionStatus.COMPLETED
|
||||||
pendingSessionId = null // Clear pending session ID on completion
|
pendingSessionId = null // Clear pending session ID on completion
|
||||||
messageCollectionJob?.cancel()
|
messageCollectionJob?.cancel()
|
||||||
|
currentSigningPartyId = null // Clear after signing completes
|
||||||
|
|
||||||
android.util.Log.d("TssRepository", "Sign as joiner completed: signature=${result.signature.take(20)}...")
|
android.util.Log.d("TssRepository", "Sign as joiner completed: signature=${result.signature.take(20)}...")
|
||||||
|
|
||||||
|
|
@ -1274,6 +1287,7 @@ class TssRepository @Inject constructor(
|
||||||
stopProgressCollection()
|
stopProgressCollection()
|
||||||
_sessionStatus.value = SessionStatus.FAILED
|
_sessionStatus.value = SessionStatus.FAILED
|
||||||
pendingSessionId = null // Clear pending session ID on failure
|
pendingSessionId = null // Clear pending session ID on failure
|
||||||
|
currentSigningPartyId = null // Clear on failure too
|
||||||
Result.failure(e)
|
Result.failure(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1366,6 +1380,7 @@ class TssRepository @Inject constructor(
|
||||||
val address = AddressUtils.deriveEvmAddress(publicKeyBytes)
|
val address = AddressUtils.deriveEvmAddress(publicKeyBytes)
|
||||||
|
|
||||||
// Save share record
|
// Save share record
|
||||||
|
// CRITICAL: Save partyId - this is required for signing after backup/restore
|
||||||
val shareEntity = ShareRecordEntity(
|
val shareEntity = ShareRecordEntity(
|
||||||
sessionId = apiJoinData.sessionId,
|
sessionId = apiJoinData.sessionId,
|
||||||
publicKey = result.publicKey,
|
publicKey = result.publicKey,
|
||||||
|
|
@ -1373,6 +1388,7 @@ class TssRepository @Inject constructor(
|
||||||
thresholdT = apiJoinData.thresholdT,
|
thresholdT = apiJoinData.thresholdT,
|
||||||
thresholdN = apiJoinData.thresholdN,
|
thresholdN = apiJoinData.thresholdN,
|
||||||
partyIndex = myPartyIndex,
|
partyIndex = myPartyIndex,
|
||||||
|
partyId = partyId,
|
||||||
address = address
|
address = address
|
||||||
)
|
)
|
||||||
val id = shareRecordDao.insertShare(shareEntity)
|
val id = shareRecordDao.insertShare(shareEntity)
|
||||||
|
|
@ -1516,12 +1532,15 @@ class TssRepository @Inject constructor(
|
||||||
_sessionStatus.value = SessionStatus.WAITING
|
_sessionStatus.value = SessionStatus.WAITING
|
||||||
|
|
||||||
// Add self to participants
|
// Add self to participants
|
||||||
val allParticipants = sessionData.participants + Participant(partyId, myPartyIndex)
|
// 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)
|
||||||
|
|
||||||
// Start TSS sign
|
// Start TSS sign
|
||||||
val startResult = tssNativeBridge.startSign(
|
val startResult = tssNativeBridge.startSign(
|
||||||
sessionId = apiJoinData.sessionId,
|
sessionId = apiJoinData.sessionId,
|
||||||
partyId = partyId,
|
partyId = signingPartyId,
|
||||||
partyIndex = myPartyIndex,
|
partyIndex = myPartyIndex,
|
||||||
thresholdT = apiJoinData.thresholdT,
|
thresholdT = apiJoinData.thresholdT,
|
||||||
thresholdN = shareEntity.thresholdN, // Use original N from keygen
|
thresholdN = shareEntity.thresholdN, // Use original N from keygen
|
||||||
|
|
@ -1540,8 +1559,8 @@ class TssRepository @Inject constructor(
|
||||||
// Start message routing
|
// Start message routing
|
||||||
startMessageRouting(apiJoinData.sessionId, myPartyIndex)
|
startMessageRouting(apiJoinData.sessionId, myPartyIndex)
|
||||||
|
|
||||||
// Mark ready
|
// Mark ready - use signingPartyId (original partyId from keygen)
|
||||||
grpcClient.markPartyReady(apiJoinData.sessionId, partyId)
|
grpcClient.markPartyReady(apiJoinData.sessionId, signingPartyId)
|
||||||
|
|
||||||
// Wait for sign result
|
// Wait for sign result
|
||||||
val signResult = tssNativeBridge.waitForSignResult()
|
val signResult = tssNativeBridge.waitForSignResult()
|
||||||
|
|
@ -1552,18 +1571,20 @@ class TssRepository @Inject constructor(
|
||||||
|
|
||||||
val result = signResult.getOrThrow()
|
val result = signResult.getOrThrow()
|
||||||
|
|
||||||
// Report completion
|
// Report completion - use signingPartyId (original partyId from keygen)
|
||||||
val signatureBytes = Base64.decode(result.signature, Base64.NO_WRAP)
|
val signatureBytes = Base64.decode(result.signature, Base64.NO_WRAP)
|
||||||
grpcClient.reportCompletion(apiJoinData.sessionId, partyId, signature = signatureBytes)
|
grpcClient.reportCompletion(apiJoinData.sessionId, signingPartyId, signature = signatureBytes)
|
||||||
|
|
||||||
_sessionStatus.value = SessionStatus.COMPLETED
|
_sessionStatus.value = SessionStatus.COMPLETED
|
||||||
messageCollectionJob?.cancel()
|
messageCollectionJob?.cancel()
|
||||||
|
currentSigningPartyId = null // Clear after signing completes
|
||||||
|
|
||||||
Result.success(result)
|
Result.success(result)
|
||||||
|
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
android.util.Log.e("TssRepository", "Join sign session failed", e)
|
android.util.Log.e("TssRepository", "Join sign session failed", e)
|
||||||
_sessionStatus.value = SessionStatus.FAILED
|
_sessionStatus.value = SessionStatus.FAILED
|
||||||
|
currentSigningPartyId = null // Clear on failure too
|
||||||
Result.failure(e)
|
Result.failure(e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1785,6 +1806,7 @@ class TssRepository @Inject constructor(
|
||||||
val address = AddressUtils.deriveEvmAddress(publicKeyBytes)
|
val address = AddressUtils.deriveEvmAddress(publicKeyBytes)
|
||||||
|
|
||||||
// Save share record (use actual thresholds from backend)
|
// Save share record (use actual thresholds from backend)
|
||||||
|
// CRITICAL: Save partyId - this is required for signing after backup/restore
|
||||||
val shareEntity = ShareRecordEntity(
|
val shareEntity = ShareRecordEntity(
|
||||||
sessionId = sessionId,
|
sessionId = sessionId,
|
||||||
publicKey = result.publicKey,
|
publicKey = result.publicKey,
|
||||||
|
|
@ -1792,6 +1814,7 @@ class TssRepository @Inject constructor(
|
||||||
thresholdT = actualThresholdT,
|
thresholdT = actualThresholdT,
|
||||||
thresholdN = actualThresholdN,
|
thresholdN = actualThresholdN,
|
||||||
partyIndex = myPartyIndex,
|
partyIndex = myPartyIndex,
|
||||||
|
partyId = partyId,
|
||||||
address = address
|
address = address
|
||||||
)
|
)
|
||||||
val id = shareRecordDao.insertShare(shareEntity)
|
val id = shareRecordDao.insertShare(shareEntity)
|
||||||
|
|
@ -1900,6 +1923,7 @@ class TssRepository @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert to entity and save
|
// Convert to entity and save
|
||||||
|
// CRITICAL: Preserve the original partyId from backup - this is required for signing
|
||||||
val shareRecord = backup.toShareRecord()
|
val shareRecord = backup.toShareRecord()
|
||||||
val entity = ShareRecordEntity(
|
val entity = ShareRecordEntity(
|
||||||
sessionId = shareRecord.sessionId,
|
sessionId = shareRecord.sessionId,
|
||||||
|
|
@ -1908,6 +1932,7 @@ class TssRepository @Inject constructor(
|
||||||
thresholdT = shareRecord.thresholdT,
|
thresholdT = shareRecord.thresholdT,
|
||||||
thresholdN = shareRecord.thresholdN,
|
thresholdN = shareRecord.thresholdN,
|
||||||
partyIndex = shareRecord.partyIndex,
|
partyIndex = shareRecord.partyIndex,
|
||||||
|
partyId = shareRecord.partyId,
|
||||||
address = shareRecord.address,
|
address = shareRecord.address,
|
||||||
createdAt = shareRecord.createdAt
|
createdAt = shareRecord.createdAt
|
||||||
)
|
)
|
||||||
|
|
@ -1915,7 +1940,7 @@ class TssRepository @Inject constructor(
|
||||||
val newId = shareRecordDao.insertShare(entity)
|
val newId = shareRecordDao.insertShare(entity)
|
||||||
val savedShare = shareRecord.copy(id = newId)
|
val savedShare = shareRecord.copy(id = newId)
|
||||||
|
|
||||||
android.util.Log.d("TssRepository", "Imported share backup for address: ${backup.address}")
|
android.util.Log.d("TssRepository", "Imported share backup for address: ${backup.address}, partyId: ${backup.partyId}")
|
||||||
Result.success(savedShare)
|
Result.success(savedShare)
|
||||||
} catch (e: com.google.gson.JsonSyntaxException) {
|
} catch (e: com.google.gson.JsonSyntaxException) {
|
||||||
android.util.Log.e("TssRepository", "Invalid JSON format in backup", e)
|
android.util.Log.e("TssRepository", "Invalid JSON format in backup", e)
|
||||||
|
|
@ -2312,8 +2337,12 @@ class TssRepository @Inject constructor(
|
||||||
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 signing
|
||||||
|
// This is required for backup/restore to work - the partyId must match what was used during keygen
|
||||||
|
val signingPartyId = shareEntity.partyId
|
||||||
|
currentSigningPartyId = signingPartyId // Save for waitForSignature
|
||||||
android.util.Log.d("TssRepository", "[CO-SIGN] startSigning: participants=${session.participants.size}")
|
android.util.Log.d("TssRepository", "[CO-SIGN] startSigning: participants=${session.participants.size}")
|
||||||
android.util.Log.d("TssRepository", "[CO-SIGN] startSigning: sessionId=$sessionId, partyId=$partyId, partyIndex=${shareEntity.partyIndex}")
|
android.util.Log.d("TssRepository", "[CO-SIGN] startSigning: sessionId=$sessionId, signingPartyId=$signingPartyId, partyIndex=${shareEntity.partyIndex}")
|
||||||
android.util.Log.d("TssRepository", "[CO-SIGN] startSigning: thresholdT=${session.thresholdT}, thresholdN=${shareEntity.thresholdN}")
|
android.util.Log.d("TssRepository", "[CO-SIGN] startSigning: thresholdT=${session.thresholdT}, thresholdN=${shareEntity.thresholdN}")
|
||||||
android.util.Log.d("TssRepository", "[CO-SIGN] startSigning: messageHash=${session.messageHash?.take(20)}...")
|
android.util.Log.d("TssRepository", "[CO-SIGN] startSigning: messageHash=${session.messageHash?.take(20)}...")
|
||||||
session.participants.forEachIndexed { idx, p ->
|
session.participants.forEachIndexed { idx, p ->
|
||||||
|
|
@ -2328,10 +2357,10 @@ class TssRepository @Inject constructor(
|
||||||
} else {
|
} else {
|
||||||
rawMessageHash
|
rawMessageHash
|
||||||
}
|
}
|
||||||
android.util.Log.d("TssRepository", "[CO-SIGN] Calling tssNativeBridge.startSign with cleanMessageHash=${cleanMessageHash.take(20)}...")
|
android.util.Log.d("TssRepository", "[CO-SIGN] Calling tssNativeBridge.startSign with cleanMessageHash=${cleanMessageHash.take(20)}..., signingPartyId=$signingPartyId")
|
||||||
val startResult = tssNativeBridge.startSign(
|
val startResult = tssNativeBridge.startSign(
|
||||||
sessionId = sessionId,
|
sessionId = sessionId,
|
||||||
partyId = partyId,
|
partyId = signingPartyId,
|
||||||
partyIndex = shareEntity.partyIndex,
|
partyIndex = shareEntity.partyIndex,
|
||||||
thresholdT = session.thresholdT,
|
thresholdT = session.thresholdT,
|
||||||
thresholdN = shareEntity.thresholdN,
|
thresholdN = shareEntity.thresholdN,
|
||||||
|
|
@ -2359,8 +2388,8 @@ class TssRepository @Inject constructor(
|
||||||
startMessageRouting(sessionId, shareEntity.partyIndex)
|
startMessageRouting(sessionId, shareEntity.partyIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mark ready
|
// Mark ready - use signingPartyId (original partyId from keygen)
|
||||||
grpcClient.markPartyReady(sessionId, partyId)
|
grpcClient.markPartyReady(sessionId, signingPartyId)
|
||||||
|
|
||||||
Result.success(Unit)
|
Result.success(Unit)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
|
@ -2386,16 +2415,18 @@ class TssRepository @Inject constructor(
|
||||||
|
|
||||||
val result = signResult.getOrThrow()
|
val result = signResult.getOrThrow()
|
||||||
|
|
||||||
// Report completion
|
// Report completion - use currentSigningPartyId (original partyId from keygen)
|
||||||
val signatureBytes = Base64.decode(result.signature, Base64.NO_WRAP)
|
val signatureBytes = Base64.decode(result.signature, Base64.NO_WRAP)
|
||||||
val session = _currentSession.value
|
val session = _currentSession.value
|
||||||
|
val signingPartyId = currentSigningPartyId ?: partyId
|
||||||
if (session != null) {
|
if (session != null) {
|
||||||
grpcClient.reportCompletion(session.sessionId, partyId, signature = signatureBytes)
|
grpcClient.reportCompletion(session.sessionId, signingPartyId, signature = signatureBytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
stopProgressCollection()
|
stopProgressCollection()
|
||||||
_sessionStatus.value = SessionStatus.COMPLETED
|
_sessionStatus.value = SessionStatus.COMPLETED
|
||||||
messageCollectionJob?.cancel()
|
messageCollectionJob?.cancel()
|
||||||
|
currentSigningPartyId = null // Clear after signing completes
|
||||||
|
|
||||||
Result.success(result)
|
Result.success(result)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
|
@ -2759,6 +2790,7 @@ private fun ShareRecordEntity.toShareRecord() = ShareRecord(
|
||||||
thresholdT = thresholdT,
|
thresholdT = thresholdT,
|
||||||
thresholdN = thresholdN,
|
thresholdN = thresholdN,
|
||||||
partyIndex = partyIndex,
|
partyIndex = partyIndex,
|
||||||
|
partyId = partyId,
|
||||||
address = address,
|
address = address,
|
||||||
createdAt = createdAt
|
createdAt = createdAt
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,17 @@ object AppModule {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Migration from version 2 to 3: add party_id column to share_records
|
||||||
|
// This is critical for backup/restore - the partyId must be preserved for signing to work
|
||||||
|
private val MIGRATION_2_3 = object : Migration(2, 3) {
|
||||||
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
|
// Add party_id column with empty default (existing records will need to be re-exported)
|
||||||
|
database.execSQL(
|
||||||
|
"ALTER TABLE `share_records` ADD COLUMN `party_id` TEXT NOT NULL DEFAULT ''"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideGson(): Gson {
|
fun provideGson(): Gson {
|
||||||
|
|
@ -48,7 +59,7 @@ object AppModule {
|
||||||
TssDatabase::class.java,
|
TssDatabase::class.java,
|
||||||
"tss_party.db"
|
"tss_party.db"
|
||||||
)
|
)
|
||||||
.addMigrations(MIGRATION_1_2)
|
.addMigrations(MIGRATION_1_2, MIGRATION_2_3)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -86,6 +86,7 @@ data class ShareRecord(
|
||||||
val thresholdT: Int,
|
val thresholdT: Int,
|
||||||
val thresholdN: Int,
|
val thresholdN: Int,
|
||||||
val partyIndex: Int,
|
val partyIndex: Int,
|
||||||
|
val partyId: String, // The original partyId used during keygen - required for signing
|
||||||
val address: String,
|
val address: String,
|
||||||
val createdAt: Long = System.currentTimeMillis()
|
val createdAt: Long = System.currentTimeMillis()
|
||||||
)
|
)
|
||||||
|
|
@ -165,7 +166,7 @@ data class WalletBalance(
|
||||||
*/
|
*/
|
||||||
data class ShareBackup(
|
data class ShareBackup(
|
||||||
@SerializedName("version")
|
@SerializedName("version")
|
||||||
val version: Int = 1, // Backup format version for future compatibility
|
val version: Int = 2, // Version 2: added partyId field for proper backup/restore
|
||||||
|
|
||||||
@SerializedName("sessionId")
|
@SerializedName("sessionId")
|
||||||
val sessionId: String,
|
val sessionId: String,
|
||||||
|
|
@ -185,6 +186,9 @@ data class ShareBackup(
|
||||||
@SerializedName("partyIndex")
|
@SerializedName("partyIndex")
|
||||||
val partyIndex: Int,
|
val partyIndex: Int,
|
||||||
|
|
||||||
|
@SerializedName("partyId")
|
||||||
|
val partyId: String, // The original partyId used during keygen - CRITICAL for signing after restore
|
||||||
|
|
||||||
@SerializedName("address")
|
@SerializedName("address")
|
||||||
val address: String,
|
val address: String,
|
||||||
|
|
||||||
|
|
@ -209,6 +213,7 @@ data class ShareBackup(
|
||||||
thresholdT = share.thresholdT,
|
thresholdT = share.thresholdT,
|
||||||
thresholdN = share.thresholdN,
|
thresholdN = share.thresholdN,
|
||||||
partyIndex = share.partyIndex,
|
partyIndex = share.partyIndex,
|
||||||
|
partyId = share.partyId,
|
||||||
address = share.address,
|
address = share.address,
|
||||||
createdAt = share.createdAt
|
createdAt = share.createdAt
|
||||||
)
|
)
|
||||||
|
|
@ -227,6 +232,7 @@ data class ShareBackup(
|
||||||
thresholdT = thresholdT,
|
thresholdT = thresholdT,
|
||||||
thresholdN = thresholdN,
|
thresholdN = thresholdN,
|
||||||
partyIndex = partyIndex,
|
partyIndex = partyIndex,
|
||||||
|
partyId = partyId,
|
||||||
address = address,
|
address = address,
|
||||||
createdAt = createdAt
|
createdAt = createdAt
|
||||||
)
|
)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue