fix(tss): 修复备份恢复后签名失败的问题
问题原因: - 备份恢复的钱包在签名时使用了当前设备的 partyId,而不是原始 keygen 时的 partyId - TSS 协议要求签名时使用的 partyId 必须与 keygen 时完全一致 修复内容: - Android: joinSignSessionViaGrpc() 使用 shareEntity.partyId 而非当前设备 partyId - Electron: cosign:joinSession 和 cosign:createSession 使用 share.party_id - Electron: handleCoSignStart() 使用 share.party_id 进行签名 - 所有 gRPC 通信和消息订阅都使用原始 partyId 关键修改点: - TssRepository.kt: joinSignSessionViaGrpc() 第 1136 行使用 signingPartyId - main.ts: cosign:joinSession 第 1826 行使用 signingPartyId - main.ts: cosign:createSession 第 1624-1633 行使用 share.party_id - main.ts: handleCoSignStart() 第 836 行使用 share.party_id 其他: - 移除 Android APK 中的 x86_64 ABI (仅用于模拟器) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
c5db77d23a
commit
94d283696f
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}` };
|
||||
|
|
|
|||
Loading…
Reference in New Issue