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: '请先连接到消息路由器' };
}
// 动态计算 server-party 数量: persistent = n - t
// 例如: 2-of-3 -> persistent=1, 3-of-5 -> persistent=2, 4-of-7 -> persistent=3
// 这样平台备份方数量等于"允许丢失的份额数",确保用户丢失密钥后仍可恢复
const persistentCount = params.thresholdN - params.thresholdT;
const externalCount = params.thresholdT; // 用户持有的份额数量
// Support both old format (thresholdT, thresholdN) and new format (requiredSigners, totalParties)
// New format uses user-friendly "required signers" which needs conversion to tss-lib threshold
let thresholdT: number;
let thresholdN: number;
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({
wallet_name: params.walletName,
threshold_t: params.thresholdT,
threshold_n: params.thresholdN,
threshold_t: thresholdT,
threshold_n: thresholdN,
initiator_party_id: partyId,
initiator_name: params.initiatorName || '发起者',
persistent_count: persistentCount,
@ -1271,8 +1309,8 @@ function setupIpcHandlers() {
partyIndex: joinResult.party_index,
participants: participants,
threshold: {
t: params.thresholdT,
n: params.thresholdN,
t: thresholdT,
n: thresholdN,
},
walletName: params.walletName,
encryptionPassword: '', // 不使用加密密码

View File

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

View File

@ -112,8 +112,12 @@ interface Settings {
interface CreateSessionParams {
walletName: string;
thresholdT: number;
thresholdN: number;
// New format: user-friendly parameters (preferred)
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;
}