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' ;
124import {
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' ;
4639import {
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 ' ;
5447import type * as EthLikeCommon from '@ethereumjs/common' ;
55- import { EcdsaPaillierProof , EcdsaRangeProof , EcdsaTypes } from '@bitgo/sdk-lib-mpc ' ;
48+ import type * as EthLikeTxLib from '@ethereumjs/tx ' ;
5649import { FeeMarketEIP1559Transaction , Transaction as LegacyTransaction } from '@ethereumjs/tx' ;
57- import { addHexPrefix , stripHexPrefix } from 'ethereumjs-util' ;
5850import { 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' ;
6062import {
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