fix(co-sign): convert user-friendly threshold to tss-lib format

- Rename thresholdT/thresholdN to requiredSigners/totalParties in Create.tsx
- Add parameter conversion in main.ts: threshold_t = requiredSigners - 1
- In tss-lib, threshold t means t+1 parties needed to sign
- For 3-of-5: requiredSigners=3 → threshold_t=2 (t+1=3 signers)
- externalCount = requiredSigners (user parties)
- persistentCount = totalParties - requiredSigners (server parties)
- Backward compatible with legacy thresholdT/thresholdN format

BREAKING: Existing co-managed wallets need re-keygen with new params

🤖 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-31 07:44:17 -08:00
parent ca69ebc839
commit e81757ad83
3 changed files with 88 additions and 36 deletions

View File

@ -1226,16 +1226,54 @@ function setupIpcHandlers() {
return { success: false, error: '请先连接到消息路由器' }; return { success: false, error: '请先连接到消息路由器' };
} }
// 动态计算 server-party 数量: persistent = n - t // Support both old format (thresholdT, thresholdN) and new format (requiredSigners, totalParties)
// 例如: 2-of-3 -> persistent=1, 3-of-5 -> persistent=2, 4-of-7 -> persistent=3 // New format uses user-friendly "required signers" which needs conversion to tss-lib threshold
// 这样平台备份方数量等于"允许丢失的份额数",确保用户丢失密钥后仍可恢复 let thresholdT: number;
const persistentCount = params.thresholdN - params.thresholdT; let thresholdN: number;
const externalCount = params.thresholdT; // 用户持有的份额数量 let externalCount: number;
let persistentCount: number;
if (params.requiredSigners !== undefined && params.totalParties !== undefined) {
// New format: Convert user-friendly parameters to tss-lib parameters
// In tss-lib, threshold_t means you need (t+1) parties to sign
// So if user wants 3 signers, threshold_t = 3 - 1 = 2
const requiredSigners = params.requiredSigners;
const totalParties = params.totalParties;
thresholdT = requiredSigners - 1; // tss-lib threshold
thresholdN = totalParties;
externalCount = requiredSigners; // user parties = required signers
persistentCount = totalParties - requiredSigners; // server backup parties
console.log('[CreateSession] Converting user-friendly params to tss-lib:', {
requiredSigners,
totalParties,
'tss-lib threshold_t': thresholdT,
'tss-lib threshold_n': thresholdN,
externalCount,
persistentCount,
});
} else {
// Old format: Use thresholdT and thresholdN directly (backward compatible)
thresholdT = params.thresholdT;
thresholdN = params.thresholdN;
// 动态计算 server-party 数量: persistent = n - t
// 例如: 2-of-3 -> persistent=1, 3-of-5 -> persistent=2, 4-of-7 -> persistent=3
persistentCount = thresholdN - thresholdT;
externalCount = thresholdT;
console.log('[CreateSession] Using legacy params (backward compatible):', {
thresholdT,
thresholdN,
externalCount,
persistentCount,
});
}
const result = await accountClient?.createKeygenSession({ const result = await accountClient?.createKeygenSession({
wallet_name: params.walletName, wallet_name: params.walletName,
threshold_t: params.thresholdT, threshold_t: thresholdT,
threshold_n: params.thresholdN, threshold_n: thresholdN,
initiator_party_id: partyId, initiator_party_id: partyId,
initiator_name: params.initiatorName || '发起者', initiator_name: params.initiatorName || '发起者',
persistent_count: persistentCount, persistent_count: persistentCount,
@ -1271,8 +1309,8 @@ function setupIpcHandlers() {
partyIndex: joinResult.party_index, partyIndex: joinResult.party_index,
participants: participants, participants: participants,
threshold: { threshold: {
t: params.thresholdT, t: thresholdT,
n: params.thresholdN, n: thresholdN,
}, },
walletName: params.walletName, walletName: params.walletName,
encryptionPassword: '', // 不使用加密密码 encryptionPassword: '', // 不使用加密密码

View File

@ -13,8 +13,11 @@ export default function Create() {
const navigate = useNavigate(); const navigate = useNavigate();
const [walletName, setWalletName] = useState(''); const [walletName, setWalletName] = useState('');
const [thresholdT, setThresholdT] = useState(3); // requiredSigners: 用户选择的"需要几人签名"(用户直观理解)
const [thresholdN, setThresholdN] = useState(5); // 例如:用户选择 3 表示需要 3 人签名
// 在 tss-lib 中threshold_t = requiredSigners - 1因为 tss-lib 需要 t+1 人签名)
const [requiredSigners, setRequiredSigners] = useState(3);
const [totalParties, setTotalParties] = useState(5);
const [participantName, setParticipantName] = useState(''); const [participantName, setParticipantName] = useState('');
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
@ -30,15 +33,15 @@ export default function Create() {
setError('请输入您的名称'); setError('请输入您的名称');
return; return;
} }
if (thresholdT > thresholdN) { if (requiredSigners > totalParties) {
setError('签名阈值不能大于参与方总数'); setError('签名人数不能大于参与方总数');
return; return;
} }
if (thresholdT < 1) { if (requiredSigners < 2) {
setError('签名阈值至少为 1'); setError('签名人数至少为 2');
return; return;
} }
if (thresholdN < 2) { if (totalParties < 2) {
setError('参与方总数至少为 2'); setError('参与方总数至少为 2');
return; return;
} }
@ -48,10 +51,17 @@ export default function Create() {
setError(null); setError(null);
try { try {
// Pass user-friendly parameters directly
// main.ts will convert to tss-lib parameters
console.log('[Create] Sending parameters:', {
requiredSigners,
totalParties,
});
const createResult = await window.electronAPI.grpc.createSession({ const createResult = await window.electronAPI.grpc.createSession({
walletName: walletName.trim(), walletName: walletName.trim(),
thresholdT, requiredSigners,
thresholdN, totalParties,
initiatorName: participantName.trim(), initiatorName: participantName.trim(),
}); });
@ -122,23 +132,23 @@ export default function Create() {
</div> </div>
<div className={styles.inputGroup}> <div className={styles.inputGroup}>
<label className={styles.label}> (T-of-N)</label> <label className={styles.label}></label>
<div className={styles.thresholdConfig}> <div className={styles.thresholdConfig}>
<div className={styles.thresholdItem}> <div className={styles.thresholdItem}>
<span className={styles.thresholdLabel}> (T)</span> <span className={styles.thresholdLabel}></span>
<div className={styles.numberInput}> <div className={styles.numberInput}>
<button <button
className={styles.numberButton} className={styles.numberButton}
onClick={() => setThresholdT(Math.max(1, thresholdT - 1))} onClick={() => setRequiredSigners(Math.max(2, requiredSigners - 1))}
disabled={thresholdT <= 1} disabled={requiredSigners <= 2}
> >
- -
</button> </button>
<span className={styles.numberValue}>{thresholdT}</span> <span className={styles.numberValue}>{requiredSigners}</span>
<button <button
className={styles.numberButton} className={styles.numberButton}
onClick={() => setThresholdT(Math.min(thresholdN, thresholdT + 1))} onClick={() => setRequiredSigners(Math.min(totalParties, requiredSigners + 1))}
disabled={thresholdT >= thresholdN} disabled={requiredSigners >= totalParties}
> >
+ +
</button> </button>
@ -146,24 +156,24 @@ export default function Create() {
</div> </div>
<div className={styles.thresholdDivider}>of</div> <div className={styles.thresholdDivider}>of</div>
<div className={styles.thresholdItem}> <div className={styles.thresholdItem}>
<span className={styles.thresholdLabel}> (N)</span> <span className={styles.thresholdLabel}></span>
<div className={styles.numberInput}> <div className={styles.numberInput}>
<button <button
className={styles.numberButton} className={styles.numberButton}
onClick={() => { onClick={() => {
const newN = Math.max(2, thresholdN - 1); const newN = Math.max(3, totalParties - 1);
setThresholdN(newN); setTotalParties(newN);
if (thresholdT > newN) setThresholdT(newN); if (requiredSigners > newN) setRequiredSigners(newN);
}} }}
disabled={thresholdN <= 2} disabled={totalParties <= 3}
> >
- -
</button> </button>
<span className={styles.numberValue}>{thresholdN}</span> <span className={styles.numberValue}>{totalParties}</span>
<button <button
className={styles.numberButton} className={styles.numberButton}
onClick={() => setThresholdN(thresholdN + 1)} onClick={() => setTotalParties(totalParties + 1)}
disabled={thresholdN >= 10} disabled={totalParties >= 10}
> >
+ +
</button> </button>
@ -171,7 +181,7 @@ export default function Create() {
</div> </div>
</div> </div>
<p className={styles.hint}> <p className={styles.hint}>
{thresholdT} ( 2 ) {requiredSigners} ( {totalParties - requiredSigners} )
</p> </p>
</div> </div>

View File

@ -112,8 +112,12 @@ interface Settings {
interface CreateSessionParams { interface CreateSessionParams {
walletName: string; walletName: string;
thresholdT: number; // New format: user-friendly parameters (preferred)
thresholdN: number; requiredSigners?: number; // Number of signers needed (e.g., 3 for "3-of-5")
totalParties?: number; // Total parties (e.g., 5 for "3-of-5")
// Legacy format: tss-lib parameters (backward compatible)
thresholdT?: number; // tss-lib threshold (requiredSigners - 1)
thresholdN?: number; // Same as totalParties
initiatorName: string; initiatorName: string;
} }