Skip to content

Commit 4a743ac

Browse files
feat: retrofit during eth tss recovery
TICKET: WP-2959
1 parent e4dcbe1 commit 4a743ac

File tree

3 files changed

+173
-253
lines changed

3 files changed

+173
-253
lines changed

modules/abstract-eth/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@
4141
},
4242
"dependencies": {
4343
"@bitgo/sdk-core": "^28.12.0",
44-
"@bitgo/sdk-lib-mpc": "^10.1.0",
4544
"@bitgo/statics": "^50.5.0",
4645
"@bitgo/utxo-lib": "^11.0.1",
4746
"@ethereumjs/common": "^2.6.5",

modules/abstract-eth/src/abstractEthLikeNewCoins.ts

Lines changed: 11 additions & 214 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,13 @@ import {
66
BitGoBase,
77
BuildNftTransferDataOptions,
88
common,
9-
ECDSA,
109
Ecdsa,
1110
ECDSAMethodTypes,
1211
EthereumLibraryUnavailableError,
1312
FeeEstimateOptions,
1413
FullySignedTransaction,
1514
getIsUnsignedSweep,
1615
HalfSignedTransaction,
17-
hexToBigInt,
1816
InvalidAddressError,
1917
InvalidAddressVerificationObjectPropertyError,
2018
IWallet,
@@ -36,7 +34,6 @@ import {
3634
Wallet,
3735
ECDSAUtils,
3836
} from '@bitgo/sdk-core';
39-
import { EcdsaPaillierProof, EcdsaRangeProof, EcdsaTypes } from '@bitgo/sdk-lib-mpc';
4037
import {
4138
BaseCoin as StaticsBaseCoin,
4239
CoinMap,
@@ -1056,174 +1053,6 @@ export abstract class AbstractEthLikeNewCoins extends AbstractEthLikeCoin {
10561053
}
10571054
}
10581055

1059-
/**
1060-
* Method to sign tss recovery transaction
1061-
* @param {ECDSA.KeyCombined} userKeyCombined
1062-
* @param {ECDSA.KeyCombined} backupKeyCombined
1063-
* @param {string} txHex
1064-
* @param {Object} options
1065-
* @param {EcdsaTypes.SerializedNtilde} options.rangeProofChallenge
1066-
* @returns {Promise<ECDSAMethodTypes.Signature>}
1067-
*/
1068-
private async signRecoveryTSS(
1069-
userKeyCombined: ECDSA.KeyCombined,
1070-
backupKeyCombined: ECDSA.KeyCombined,
1071-
txHex: string,
1072-
openSSLBytes: Uint8Array,
1073-
{
1074-
rangeProofChallenge,
1075-
}: {
1076-
rangeProofChallenge?: EcdsaTypes.SerializedNtilde;
1077-
} = {}
1078-
): Promise<ECDSAMethodTypes.Signature> {
1079-
if (!userKeyCombined || !backupKeyCombined) {
1080-
throw new Error('Missing key combined shares for user or backup');
1081-
}
1082-
1083-
const MPC = new Ecdsa();
1084-
const signerOneIndex = userKeyCombined.xShare.i;
1085-
const signerTwoIndex = backupKeyCombined.xShare.i;
1086-
1087-
rangeProofChallenge =
1088-
rangeProofChallenge ?? EcdsaTypes.serializeNtildeWithProofs(await EcdsaRangeProof.generateNtilde(openSSLBytes));
1089-
1090-
const userToBackupPaillierChallenge = await EcdsaPaillierProof.generateP(
1091-
hexToBigInt(userKeyCombined.yShares[signerTwoIndex].n)
1092-
);
1093-
const backupToUserPaillierChallenge = await EcdsaPaillierProof.generateP(
1094-
hexToBigInt(backupKeyCombined.yShares[signerOneIndex].n)
1095-
);
1096-
1097-
const userXShare = MPC.appendChallenge(
1098-
userKeyCombined.xShare,
1099-
rangeProofChallenge,
1100-
EcdsaTypes.serializePaillierChallenge({ p: userToBackupPaillierChallenge })
1101-
);
1102-
const userYShare = MPC.appendChallenge(
1103-
userKeyCombined.yShares[signerTwoIndex],
1104-
rangeProofChallenge,
1105-
EcdsaTypes.serializePaillierChallenge({ p: backupToUserPaillierChallenge })
1106-
);
1107-
const backupXShare = MPC.appendChallenge(
1108-
backupKeyCombined.xShare,
1109-
rangeProofChallenge,
1110-
EcdsaTypes.serializePaillierChallenge({ p: backupToUserPaillierChallenge })
1111-
);
1112-
const backupYShare = MPC.appendChallenge(
1113-
backupKeyCombined.yShares[signerOneIndex],
1114-
rangeProofChallenge,
1115-
EcdsaTypes.serializePaillierChallenge({ p: userToBackupPaillierChallenge })
1116-
);
1117-
1118-
const signShares: ECDSA.SignShareRT = await MPC.signShare(userXShare, userYShare);
1119-
1120-
const signConvertS21 = await MPC.signConvertStep1({
1121-
xShare: backupXShare,
1122-
yShare: backupYShare, // YShare corresponding to the other participant signerOne
1123-
kShare: signShares.kShare,
1124-
});
1125-
const signConvertS12 = await MPC.signConvertStep2({
1126-
aShare: signConvertS21.aShare,
1127-
wShare: signShares.wShare,
1128-
});
1129-
const signConvertS21_2 = await MPC.signConvertStep3({
1130-
muShare: signConvertS12.muShare,
1131-
bShare: signConvertS21.bShare,
1132-
});
1133-
1134-
const [signCombineOne, signCombineTwo] = [
1135-
MPC.signCombine({
1136-
gShare: signConvertS12.gShare,
1137-
signIndex: {
1138-
i: signConvertS12.muShare.i,
1139-
j: signConvertS12.muShare.j,
1140-
},
1141-
}),
1142-
MPC.signCombine({
1143-
gShare: signConvertS21_2.gShare,
1144-
signIndex: {
1145-
i: signConvertS21_2.signIndex.i,
1146-
j: signConvertS21_2.signIndex.j,
1147-
},
1148-
}),
1149-
];
1150-
1151-
const MESSAGE = Buffer.from(txHex, 'hex');
1152-
1153-
const [signA, signB] = [
1154-
MPC.sign(MESSAGE, signCombineOne.oShare, signCombineTwo.dShare, Keccak('keccak256')),
1155-
MPC.sign(MESSAGE, signCombineTwo.oShare, signCombineOne.dShare, Keccak('keccak256')),
1156-
];
1157-
1158-
return MPC.constructSignature([signA, signB]);
1159-
}
1160-
1161-
/**
1162-
* Helper which combines key shares of user and backup
1163-
* @param {string} userPublicOrPrivateKeyShare
1164-
* @param {string} backupPrivateOrPublicKeyShare
1165-
* @param {string} walletPassphrase
1166-
* @returns {[ECDSAMethodTypes.KeyCombined, ECDSAMethodTypes.KeyCombined]}
1167-
*/
1168-
private getKeyCombinedFromTssKeyShares(
1169-
userPublicOrPrivateKeyShare: string,
1170-
backupPrivateOrPublicKeyShare: string,
1171-
walletPassphrase?: string
1172-
): [ECDSAMethodTypes.KeyCombined, ECDSAMethodTypes.KeyCombined] {
1173-
let backupPrv;
1174-
let userPrv;
1175-
try {
1176-
backupPrv = this.bitgo.decrypt({
1177-
input: backupPrivateOrPublicKeyShare,
1178-
password: walletPassphrase,
1179-
});
1180-
userPrv = this.bitgo.decrypt({
1181-
input: userPublicOrPrivateKeyShare,
1182-
password: walletPassphrase,
1183-
});
1184-
} catch (e) {
1185-
throw new Error(`Error decrypting backup keychain: ${e.message}`);
1186-
}
1187-
1188-
const userSigningMaterial = JSON.parse(userPrv) as ECDSAMethodTypes.SigningMaterial;
1189-
const backupSigningMaterial = JSON.parse(backupPrv) as ECDSAMethodTypes.SigningMaterial;
1190-
1191-
if (!userSigningMaterial.backupNShare) {
1192-
throw new Error('Invalid user key - missing backupNShare');
1193-
}
1194-
1195-
if (!backupSigningMaterial.userNShare) {
1196-
throw new Error('Invalid backup key - missing userNShare');
1197-
}
1198-
1199-
const MPC = new Ecdsa();
1200-
1201-
const userKeyCombined = MPC.keyCombine(userSigningMaterial.pShare, [
1202-
userSigningMaterial.bitgoNShare,
1203-
userSigningMaterial.backupNShare,
1204-
]);
1205-
const userSigningKeyDerived = MPC.keyDerive(
1206-
userSigningMaterial.pShare,
1207-
[userSigningMaterial.bitgoNShare, userSigningMaterial.backupNShare],
1208-
'm/0'
1209-
);
1210-
const userKeyDerivedCombined = {
1211-
xShare: userSigningKeyDerived.xShare,
1212-
yShares: userKeyCombined.yShares,
1213-
};
1214-
const backupKeyCombined = MPC.keyCombine(backupSigningMaterial.pShare, [
1215-
userSigningKeyDerived.nShares[2],
1216-
backupSigningMaterial.bitgoNShare,
1217-
]);
1218-
if (
1219-
userKeyDerivedCombined.xShare.y !== backupKeyCombined.xShare.y ||
1220-
userKeyDerivedCombined.xShare.chaincode !== backupKeyCombined.xShare.chaincode
1221-
) {
1222-
throw new Error('Common keychains do not match');
1223-
}
1224-
return [userKeyDerivedCombined, backupKeyCombined];
1225-
}
1226-
12271056
/**
12281057
* Helper which Adds signatures to tx object and re-serializes tx
12291058
* @param {EthLikeCommon.default} ethCommon
@@ -1289,7 +1118,7 @@ export abstract class AbstractEthLikeNewCoins extends AbstractEthLikeCoin {
12891118
*/
12901119
async recover(params: RecoverOptions): Promise<RecoveryInfo | OfflineVaultTxInfo> {
12911120
if (params.isTss === true) {
1292-
return this.recoverTSS(params, params.openSSLBytes);
1121+
return this.recoverTSS(params);
12931122
}
12941123
return this.recoverEthLike(params);
12951124
}
@@ -1974,10 +1803,7 @@ export abstract class AbstractEthLikeNewCoins extends AbstractEthLikeCoin {
19741803
* Recovers a tx with TSS key shares
19751804
* same expected arguments as recover method, but with TSS key shares
19761805
*/
1977-
protected async recoverTSS(
1978-
params: RecoverOptions,
1979-
openSSLBytes: Uint8Array
1980-
): Promise<RecoveryInfo | OfflineVaultTxInfo> {
1806+
protected async recoverTSS(params: RecoverOptions): Promise<RecoveryInfo | OfflineVaultTxInfo> {
19811807
this.validateRecoveryParams(params);
19821808
// Clean up whitespace from entered values
19831809
const userPublicOrPrivateKeyShare = params.userKey.replace(/\s/g, '');
@@ -2010,48 +1836,19 @@ export abstract class AbstractEthLikeNewCoins extends AbstractEthLikeCoin {
20101836
params.replayProtectionOptions
20111837
);
20121838
} else {
2013-
const isGG18SigningMaterial = ECDSAUtils.isGG18SigningMaterial(
1839+
const { userKeyShare, backupKeyShare, commonKeyChain } = await ECDSAUtils.getMpcV2RecoveryKeyShares(
20141840
userPublicOrPrivateKeyShare,
1841+
backupPrivateOrPublicKeyShare,
20151842
params.walletPassphrase
20161843
);
2017-
let signature: ECDSAMethodTypes.Signature;
2018-
let unsignedTx: EthLikeTxLib.Transaction | EthLikeTxLib.FeeMarketEIP1559Transaction;
2019-
if (isGG18SigningMaterial) {
2020-
const [userKeyCombined, backupKeyCombined] = this.getKeyCombinedFromTssKeyShares(
2021-
userPublicOrPrivateKeyShare,
2022-
backupPrivateOrPublicKeyShare,
2023-
params.walletPassphrase
2024-
);
2025-
const backupKeyPair = new KeyPairLib({ pub: backupKeyCombined.xShare.y });
2026-
const baseAddress = backupKeyPair.getAddress();
2027-
2028-
unsignedTx = (await this.buildTssRecoveryTxn(baseAddress, gasPrice, gasLimit, params)).tx;
2029-
2030-
signature = await this.signRecoveryTSS(
2031-
userKeyCombined,
2032-
backupKeyCombined,
2033-
unsignedTx.getMessageToSign(false).toString('hex'),
2034-
openSSLBytes
2035-
);
2036-
} else {
2037-
const { userKeyShare, backupKeyShare, commonKeyChain } = await ECDSAUtils.getMpcV2RecoveryKeyShares(
2038-
userPublicOrPrivateKeyShare,
2039-
backupPrivateOrPublicKeyShare,
2040-
params.walletPassphrase
2041-
);
2042-
2043-
const MPC = new Ecdsa();
2044-
const derivedCommonKeyChain = MPC.deriveUnhardened(commonKeyChain, 'm');
2045-
const backupKeyPair = new KeyPairLib({ pub: derivedCommonKeyChain.slice(0, 66) });
2046-
const baseAddress = backupKeyPair.getAddress();
2047-
2048-
unsignedTx = (await this.buildTssRecoveryTxn(baseAddress, gasPrice, gasLimit, params)).tx;
2049-
2050-
const messageHash = unsignedTx.getMessageToSign(true);
2051-
2052-
signature = await ECDSAUtils.signRecoveryMpcV2(messageHash, userKeyShare, backupKeyShare, commonKeyChain);
2053-
}
20541844

1845+
const MPC = new Ecdsa();
1846+
const derivedCommonKeyChain = MPC.deriveUnhardened(commonKeyChain, 'm/0');
1847+
const backupKeyPair = new KeyPairLib({ pub: derivedCommonKeyChain.slice(0, 66) });
1848+
const baseAddress = backupKeyPair.getAddress();
1849+
const unsignedTx = (await this.buildTssRecoveryTxn(baseAddress, gasPrice, gasLimit, params)).tx;
1850+
const messageHash = unsignedTx.getMessageToSign(true);
1851+
const signature = await ECDSAUtils.signRecoveryMpcV2(messageHash, userKeyShare, backupKeyShare, commonKeyChain);
20551852
const ethCommmon = AbstractEthLikeNewCoins.getEthLikeCommon(params.eip1559, params.replayProtectionOptions);
20561853
const signedTx = this.getSignedTxFromSignature(ethCommmon, unsignedTx, signature);
20571854

0 commit comments

Comments
 (0)