fix(sign): fetch partyIndex from Account Service instead of local storage

- Query keygen session status to get correct party_index for signing
- Keep original design: don't save partyIndex locally during keygen
- Add parties to createSignSession response
- Make partyIndex optional in database type definitions
- Transfer.tsx: use signParties from createSignSession response

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
hailin 2025-12-30 07:31:35 -08:00
parent 454e596d40
commit b79289bc29
4 changed files with 38 additions and 23 deletions

View File

@ -574,17 +574,18 @@ func (h *CoManagedHTTPHandler) CreateSignSession(c *gin.Context) {
zap.Int("num_parties", len(resp.SelectedParties))) zap.Int("num_parties", len(resp.SelectedParties)))
c.JSON(http.StatusCreated, gin.H{ c.JSON(http.StatusCreated, gin.H{
"session_id": resp.SessionID, "session_id": resp.SessionID,
"keygen_session_id": req.KeygenSessionID, "keygen_session_id": req.KeygenSessionID,
"wallet_name": req.WalletName, "wallet_name": req.WalletName,
"invite_code": inviteCode, "invite_code": inviteCode,
"join_token": wildcardToken, "join_token": wildcardToken,
"threshold_t": req.ThresholdT, "threshold_t": req.ThresholdT,
"selected_parties": resp.SelectedParties, "parties": req.Parties, // 返回完整的 parties (包含 party_id 和 party_index)
"status": "waiting_for_participants", "selected_parties": resp.SelectedParties,
"current_participants": 0, "status": "waiting_for_participants",
"current_participants": 0,
"required_participants": len(req.Parties), "required_participants": len(req.Parties),
"expires_at": resp.ExpiresAt, "expires_at": resp.ExpiresAt,
}) })
} }

View File

@ -564,10 +564,10 @@ async function handleKeygenComplete(result: KeygenResult) {
try { try {
// 1. 保存 share 到本地数据库 // 1. 保存 share 到本地数据库
const publicKeyHex = result.publicKey.toString('hex'); const publicKeyHex = result.publicKey.toString('hex');
// 转换 participants 格式:保存 partyId, partyIndex, name // 转换 participants 格式:从 { partyId, partyIndex, name } 到 { partyId, name }
// 注意partyIndex 不保存到本地,签名时需要从 Account Service 获取
const participantsForSave = activeKeygenSession.participants.map(p => ({ const participantsForSave = activeKeygenSession.participants.map(p => ({
partyId: p.partyId, partyId: p.partyId,
partyIndex: p.partyIndex,
name: p.name, name: p.name,
})); }));
const saved = database.saveShare({ const saved = database.saveShare({
@ -1219,11 +1219,16 @@ function setupIpcHandlers() {
return { success: false, error: 'Share not found or incorrect password' }; return { success: false, error: 'Share not found or incorrect password' };
} }
// 解析 participants_json 获取参与方列表 // 从 Account Service 查询 keygen session 获取正确的 participants包含 party_index
const participants = JSON.parse(share.participants_json || '[]'); const sessionStatus = await accountClient?.getSessionStatus(share.session_id);
const parties = participants.map((p: { partyId: string; partyIndex?: number }, index: number) => ({ if (!sessionStatus?.participants || sessionStatus.participants.length === 0) {
party_id: p.partyId, return { success: false, error: 'Failed to get participants from keygen session' };
party_index: p.partyIndex ?? index, // 优先使用保存的 partyIndex否则使用数组索引 }
// 使用后端返回的 participants包含正确的 party_index
const parties = sessionStatus.participants.map(p => ({
party_id: p.party_id,
party_index: p.party_index,
})); }));
const result = await accountClient?.createSignSession({ const result = await accountClient?.createSignSession({
@ -1241,6 +1246,7 @@ function setupIpcHandlers() {
inviteCode: result?.invite_code, inviteCode: result?.invite_code,
expiresAt: result?.expires_at, expiresAt: result?.expires_at,
joinToken: result?.join_token, joinToken: result?.join_token,
parties: result?.parties, // 包含正确的 party_id 和 party_index
}; };
} catch (error) { } catch (error) {
return { success: false, error: (error as Error).message }; return { success: false, error: (error as Error).message };

View File

@ -339,7 +339,7 @@ export class DatabaseManager {
thresholdN: number; thresholdN: number;
publicKeyHex: string; publicKeyHex: string;
rawShare: string; rawShare: string;
participants: Array<{ partyId: string; name: string }>; participants: Array<{ partyId: string; partyIndex?: number; name: string }>;
}, password: string): ShareRecord { }, password: string): ShareRecord {
if (!this.db) throw new Error('Database not initialized'); if (!this.db) throw new Error('Database not initialized');

View File

@ -60,6 +60,7 @@ export default function Transfer() {
// 签名会话 // 签名会话
const [inviteCode, setInviteCode] = useState(''); const [inviteCode, setInviteCode] = useState('');
const [signSessionId, setSignSessionId] = useState(''); const [signSessionId, setSignSessionId] = useState('');
const [signParties, setSignParties] = useState<Array<{ party_id: string; party_index: number }>>([]);
// 错误信息 // 错误信息
const [error, setError] = useState(''); const [error, setError] = useState('');
@ -215,6 +216,7 @@ export default function Transfer() {
setInviteCode(result.inviteCode || ''); setInviteCode(result.inviteCode || '');
setSignSessionId(result.sessionId || ''); setSignSessionId(result.sessionId || '');
setSignParties(result.parties || []);
setTxHash(messageHash); setTxHash(messageHash);
// 显示邀请码,等待用户通知其他参与方 // 显示邀请码,等待用户通知其他参与方
@ -231,11 +233,17 @@ export default function Transfer() {
if (!txParams || !share || !txHash || !signSessionId) return; if (!txParams || !share || !txHash || !signSessionId) return;
try { try {
// 构建 participants 列表 // 使用创建签名会话时返回的 parties包含正确的 party_index
const participants = (share.participants || []).map((p, index) => ({ // 如果没有,则 fallback 到本地 share 的 participants
partyId: p.partyId, const participants = signParties.length > 0
partyIndex: p.partyIndex ?? index, // 优先使用保存的 partyIndex ? signParties.map(p => ({
})); partyId: p.party_id,
partyIndex: p.party_index,
}))
: (share.participants || []).map((p, index) => ({
partyId: p.partyId,
partyIndex: p.partyIndex ?? index,
}));
// 执行 TSS 签名 // 执行 TSS 签名
if (window.electronAPI) { if (window.electronAPI) {