Skip to content

Commit 0fdbd5e

Browse files
committed
feat(abstract-eth): refactor eth wrw recovery tss
Ticket: WP-1885
1 parent 9025538 commit 0fdbd5e

1 file changed

Lines changed: 92 additions & 78 deletions

File tree

modules/abstract-eth/src/abstractEthLikeNewCoins.ts

Lines changed: 92 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,10 @@
11
/**
22
* @prettier
33
*/
4-
import debugLib from 'debug';
5-
import { bip32 } from '@bitgo/utxo-lib';
6-
import { BigNumber } from 'bignumber.js';
7-
import { randomBytes } from 'crypto';
8-
import Keccak from 'keccak';
9-
import _ from 'lodash';
10-
import secp256k1 from 'secp256k1';
11-
import BN from 'bn.js';
124
import {
135
AddressCoinSpecific,
146
BitGoBase,
7+
BuildNftTransferDataOptions,
158
common,
169
ECDSA,
1710
Ecdsa,
@@ -33,30 +26,39 @@ import {
3326
Recipient,
3427
SignTransactionOptions as BaseSignTransactionOptions,
3528
TransactionParams,
36-
TransactionRecipient,
3729
TransactionPrebuild as BaseTransactionPrebuild,
30+
TransactionRecipient,
3831
TypedData,
3932
UnexpectedAddressError,
4033
Util,
4134
VerifyAddressOptions as BaseVerifyAddressOptions,
4235
VerifyTransactionOptions,
4336
Wallet,
44-
BuildNftTransferDataOptions,
4537
} from '@bitgo/sdk-core';
38+
import { EcdsaPaillierProof, EcdsaRangeProof, EcdsaTypes } from '@bitgo/sdk-lib-mpc';
4639
import {
4740
BaseCoin as StaticsBaseCoin,
41+
CoinMap,
4842
coins,
4943
EthereumNetwork as EthLikeNetwork,
5044
ethGasConfigs,
51-
CoinMap,
5245
} from '@bitgo/statics';
53-
import type * as EthLikeTxLib from '@ethereumjs/tx';
46+
import { bip32 } from '@bitgo/utxo-lib';
5447
import type * as EthLikeCommon from '@ethereumjs/common';
55-
import { EcdsaPaillierProof, EcdsaRangeProof, EcdsaTypes } from '@bitgo/sdk-lib-mpc';
48+
import type * as EthLikeTxLib from '@ethereumjs/tx';
5649
import { FeeMarketEIP1559Transaction, Transaction as LegacyTransaction } from '@ethereumjs/tx';
57-
import { addHexPrefix, stripHexPrefix } from 'ethereumjs-util';
5850
import { SignTypedDataVersion, TypedDataUtils, TypedMessage } from '@metamask/eth-sig-util';
51+
import { BigNumber } from 'bignumber.js';
52+
import BN from 'bn.js';
53+
import { randomBytes } from 'crypto';
54+
import debugLib from 'debug';
55+
import { addHexPrefix, stripHexPrefix } from 'ethereumjs-util';
56+
import Keccak from 'keccak';
57+
import _ from 'lodash';
58+
import secp256k1 from 'secp256k1';
5959

60+
import { AbstractEthLikeCoin } from './abstractEthLikeCoin';
61+
import { EthLikeToken } from './ethLikeToken';
6062
import {
6163
calculateForwarderV1Address,
6264
ERC1155TransferBuilder,
@@ -68,8 +70,6 @@ import {
6870
TransactionBuilder,
6971
TransferBuilder,
7072
} from './lib';
71-
import { AbstractEthLikeCoin } from './abstractEthLikeCoin';
72-
import { EthLikeToken } from './ethLikeToken';
7373

7474
/**
7575
* The prebuilt hop transaction returned from the HSM
@@ -1902,56 +1902,79 @@ export abstract class AbstractEthLikeNewCoins extends AbstractEthLikeCoin {
19021902
*/
19031903
protected async recoverTSS(params: RecoverOptions): Promise<RecoveryInfo | OfflineVaultTxInfo> {
19041904
this.validateRecoveryParams(params);
1905-
const isUnsignedSweep = getIsUnsignedSweep(params);
1906-
19071905
// Clean up whitespace from entered values
19081906
const userPublicOrPrivateKeyShare = params.userKey.replace(/\s/g, '');
19091907
const backupPrivateOrPublicKeyShare = params.backupKey.replace(/\s/g, '');
19101908

1911-
// Set new eth tx fees (using default config values from platform)
19121909
const gasLimit = new optionalDeps.ethUtil.BN(this.setGasLimit(params.gasLimit));
19131910
const gasPrice = params.eip1559
19141911
? new optionalDeps.ethUtil.BN(params.eip1559.maxFeePerGas)
19151912
: new optionalDeps.ethUtil.BN(this.setGasPrice(params.gasPrice));
19161913

1917-
const [backupKeyAddress, userKeyCombined, backupKeyCombined] = ((): [
1918-
string,
1919-
ECDSAMethodTypes.KeyCombined | undefined,
1920-
ECDSAMethodTypes.KeyCombined | undefined
1921-
] => {
1922-
if (isUnsignedSweep) {
1923-
const backupKeyPair = new KeyPairLib({ pub: backupPrivateOrPublicKeyShare });
1924-
return [backupKeyPair.getAddress(), undefined, undefined];
1925-
} else {
1926-
const [userKeyCombined, backupKeyCombined] = this.getKeyCombinedFromTssKeyShares(
1927-
userPublicOrPrivateKeyShare,
1928-
backupPrivateOrPublicKeyShare,
1929-
params.walletPassphrase
1930-
);
1931-
const backupKeyPair = new KeyPairLib({ pub: backupKeyCombined.xShare.y });
1932-
return [backupKeyPair.getAddress(), userKeyCombined, backupKeyCombined];
1933-
}
1934-
})();
1914+
if (getIsUnsignedSweep(params)) {
1915+
const backupKeyPair = new KeyPairLib({ pub: backupPrivateOrPublicKeyShare });
1916+
const baseAddress = backupKeyPair.getAddress();
1917+
const { txInfo, tx, nonce } = await this.buildTssRecoveryTxn(baseAddress, gasPrice, gasLimit, params);
1918+
return this.formatForOfflineVaultTSS(
1919+
txInfo,
1920+
tx,
1921+
userPublicOrPrivateKeyShare,
1922+
backupPrivateOrPublicKeyShare,
1923+
gasPrice,
1924+
gasLimit,
1925+
nonce,
1926+
params.eip1559,
1927+
params.replayProtectionOptions
1928+
);
1929+
} else if (this.isGG18SigningMaterial(userPublicOrPrivateKeyShare, params.walletPassphrase)) {
1930+
const [userKeyCombined, backupKeyCombined] = this.getKeyCombinedFromTssKeyShares(
1931+
userPublicOrPrivateKeyShare,
1932+
backupPrivateOrPublicKeyShare,
1933+
params.walletPassphrase
1934+
);
1935+
const backupKeyPair = new KeyPairLib({ pub: backupKeyCombined.xShare.y });
1936+
const baseAddress = backupKeyPair.getAddress();
19351937

1936-
const backupKeyNonce = await this.getAddressNonce(backupKeyAddress);
1938+
let { tx } = await this.buildTssRecoveryTxn(baseAddress, gasPrice, gasLimit, params);
19371939

1938-
// get balance of backupKey to ensure funds are available to pay fees
1939-
const backupKeyBalance = await this.queryAddressBalance(backupKeyAddress);
1940+
const signableHex = tx.getMessageToSign(false).toString('hex');
1941+
if (!userKeyCombined || !backupKeyCombined) {
1942+
throw new Error('Missing key combined shares for user or backup');
1943+
}
1944+
const signature = await this.signRecoveryTSS(userKeyCombined, backupKeyCombined, signableHex);
1945+
const ethCommmon = AbstractEthLikeNewCoins.getEthLikeCommon(params.eip1559, params.replayProtectionOptions);
1946+
tx = this.getSignedTxFromSignature(ethCommmon, tx, signature);
19401947

1941-
const totalGasNeeded = gasPrice.mul(gasLimit);
1942-
const weiToGwei = 10 ** 9;
1943-
if (backupKeyBalance.lt(totalGasNeeded)) {
1944-
throw new Error(
1945-
`Backup key address ${backupKeyAddress} has balance ${(backupKeyBalance / weiToGwei).toString()} Gwei.` +
1946-
`This address must have a balance of at least ${(totalGasNeeded / weiToGwei).toString()}` +
1947-
` Gwei to perform recoveries. Try sending some ETH to this address then retry.`
1948-
);
1948+
return {
1949+
id: addHexPrefix(tx.hash().toString('hex')),
1950+
tx: addHexPrefix(tx.serialize().toString('hex')),
1951+
};
1952+
} else {
1953+
// DKLS
1954+
throw new Error('DKLS recovery is not implemented yet');
19491955
}
1956+
}
19501957

1951-
// get balance of wallet and deduct fees to get transaction amount, wallet contract address acts as base address for tss?
1952-
const txAmount = backupKeyBalance.sub(totalGasNeeded);
1958+
private isGG18SigningMaterial(keyShare: string, walletPassphrase: string | undefined): boolean {
1959+
const prv = this.bitgo.decrypt({
1960+
input: keyShare,
1961+
password: walletPassphrase,
1962+
});
1963+
try {
1964+
const signingMaterial = JSON.parse(prv);
1965+
return (
1966+
signingMaterial.pShare &&
1967+
signingMaterial.bitgoNShare &&
1968+
(signingMaterial.userNShare || signingMaterial.backupNShare)
1969+
);
1970+
} catch (error) {
1971+
return false;
1972+
}
1973+
}
19531974

1954-
// build recipients object
1975+
private async buildTssRecoveryTxn(baseAddress: string, gasPrice: any, gasLimit: any, params: RecoverOptions) {
1976+
const nonce = await this.getAddressNonce(baseAddress);
1977+
const txAmount = await this.validateBalanceAndGetTxAmount(baseAddress, gasPrice, gasLimit);
19551978
const recipients = [
19561979
{
19571980
address: params.recoveryDestination,
@@ -1966,44 +1989,35 @@ export abstract class AbstractEthLikeNewCoins extends AbstractEthLikeCoin {
19661989
};
19671990

19681991
const txParams = {
1969-
to: params.recoveryDestination, // no contract address, so this field should not be used anyways
1970-
nonce: backupKeyNonce,
1992+
to: params.recoveryDestination,
1993+
nonce: nonce,
19711994
value: txAmount,
19721995
gasPrice: gasPrice,
19731996
gasLimit: gasLimit,
1974-
data: Buffer.from('0x'), // no contract call
1997+
data: Buffer.from('0x'),
19751998
eip1559: params.eip1559,
19761999
replayProtectionOptions: params.replayProtectionOptions,
19772000
};
19782001

1979-
let tx = AbstractEthLikeNewCoins.buildTransaction(txParams);
2002+
const tx = AbstractEthLikeNewCoins.buildTransaction(txParams);
2003+
return { txInfo, tx, nonce };
2004+
}
19802005

1981-
if (isUnsignedSweep) {
1982-
return this.formatForOfflineVaultTSS(
1983-
txInfo,
1984-
tx,
1985-
userPublicOrPrivateKeyShare,
1986-
backupPrivateOrPublicKeyShare,
1987-
gasPrice,
1988-
gasLimit,
1989-
backupKeyNonce,
1990-
params.eip1559,
1991-
params.replayProtectionOptions
1992-
);
1993-
}
2006+
async validateBalanceAndGetTxAmount(baseAddress: string, gasPrice: BN, gasLimit: BN) {
2007+
const baseAddressBalance = await this.queryAddressBalance(baseAddress);
19942008

1995-
const signableHex = tx.getMessageToSign(false).toString('hex');
1996-
if (!userKeyCombined || !backupKeyCombined) {
1997-
throw new Error('Missing key combined shares for user or backup');
2009+
const totalGasNeeded = gasPrice.mul(gasLimit);
2010+
const weiToGwei = new BN(10 ** 9);
2011+
if (baseAddressBalance.lt(totalGasNeeded)) {
2012+
throw new Error(
2013+
`Backup key address ${baseAddress} has balance ${baseAddressBalance.div(weiToGwei).toString()} Gwei.` +
2014+
`This address must have a balance of at least ${totalGasNeeded.div(weiToGwei).toString()}` +
2015+
` Gwei to perform recoveries. Try sending some ETH to this address then retry.`
2016+
);
19982017
}
1999-
const signature = await this.signRecoveryTSS(userKeyCombined, backupKeyCombined, signableHex);
2000-
const ethCommmon = AbstractEthLikeNewCoins.getEthLikeCommon(params.eip1559, params.replayProtectionOptions);
2001-
tx = this.getSignedTxFromSignature(ethCommmon, tx, signature);
20022018

2003-
return {
2004-
id: addHexPrefix(tx.hash().toString('hex')),
2005-
tx: addHexPrefix(tx.serialize().toString('hex')),
2006-
};
2019+
const txAmount = baseAddressBalance.sub(totalGasNeeded);
2020+
return txAmount;
20072021
}
20082022

20092023
async recoveryBlockchainExplorerQuery(query: Record<string, string>): Promise<any> {

0 commit comments

Comments
 (0)