@@ -40,6 +40,9 @@ import {
4040 VerifyAddressOptions as BaseVerifyAddressOptions ,
4141 VerifyTransactionOptions ,
4242 Wallet ,
43+ verifyMPCWalletAddress ,
44+ TssVerifyAddressOptions ,
45+ isTssVerifyAddressOptions ,
4346} from '@bitgo/sdk-core' ;
4447import { getDerivationPath } from '@bitgo/sdk-lib-mpc' ;
4548import { bip32 } from '@bitgo/secp256k1' ;
@@ -369,6 +372,7 @@ interface PresignTransactionOptions extends TransactionPrebuild, BasePresignTran
369372interface EthAddressCoinSpecifics extends AddressCoinSpecific {
370373 forwarderVersion : number ;
371374 salt ?: string ;
375+ feeAddress ?: string ;
372376}
373377
374378export const DEFAULT_SCAN_FACTOR = 20 ;
@@ -401,9 +405,12 @@ export interface EthConsolidationRecoveryOptions {
401405export interface VerifyEthAddressOptions extends BaseVerifyAddressOptions {
402406 baseAddress : string ;
403407 coinSpecific : EthAddressCoinSpecifics ;
404- forwarderVersion : number ;
408+ forwarderVersion ?: number ;
409+ walletVersion ?: number ;
405410}
406411
412+ export type TssVerifyEthAddressOptions = TssVerifyAddressOptions & VerifyEthAddressOptions ;
413+
407414const debug = debugLib ( 'bitgo:v2:ethlike' ) ;
408415
409416export const optionalDeps = {
@@ -2725,6 +2732,23 @@ export abstract class AbstractEthLikeNewCoins extends AbstractEthLikeCoin {
27252732 return { } ;
27262733 }
27272734
2735+ getFactoryAndImplContractAddresses ( walletVersion : number | undefined ) : {
2736+ forwarderFactoryAddress : string ;
2737+ forwarderImplementationAddress : string ;
2738+ } {
2739+ const ethNetwork = this . getNetwork ( ) ;
2740+ if ( walletVersion && ( walletVersion === 5 || walletVersion === 4 ) ) {
2741+ return {
2742+ forwarderFactoryAddress : ethNetwork ?. walletV4ForwarderFactoryAddress as string ,
2743+ forwarderImplementationAddress : ethNetwork ?. walletV4ForwarderImplementationAddress as string ,
2744+ } ;
2745+ }
2746+ return {
2747+ forwarderFactoryAddress : ethNetwork ?. forwarderFactoryAddress as string ,
2748+ forwarderImplementationAddress : ethNetwork ?. forwarderImplementationAddress as string ,
2749+ } ;
2750+ }
2751+
27282752 /**
27292753 * Make sure an address is a wallet address and throw an error if it's not.
27302754 * @param {Object } params
@@ -2736,45 +2760,57 @@ export abstract class AbstractEthLikeNewCoins extends AbstractEthLikeCoin {
27362760 * @throws {UnexpectedAddressError }
27372761 * @returns {boolean } True iff address is a wallet address
27382762 */
2739- async isWalletAddress ( params : VerifyEthAddressOptions ) : Promise < boolean > {
2763+ async isWalletAddress ( params : VerifyEthAddressOptions | TssVerifyEthAddressOptions ) : Promise < boolean > {
27402764 const ethUtil = optionalDeps . ethUtil ;
27412765
27422766 let expectedAddress ;
27432767 let actualAddress ;
27442768
2745- const { address, coinSpecific, baseAddress, impliedForwarderVersion = coinSpecific ?. forwarderVersion } = params ;
2769+ const { address, impliedForwarderVersion, coinSpecific } = params ;
2770+ const forwarderVersion = impliedForwarderVersion ?? coinSpecific ?. forwarderVersion ;
27462771
27472772 if ( address && ! this . isValidAddress ( address ) ) {
27482773 throw new InvalidAddressError ( `invalid address: ${ address } ` ) ;
27492774 }
2750-
2751- // base address is required to calculate the salt which is used in calculateForwarderV1Address method
2752- if ( _ . isUndefined ( baseAddress ) || ! this . isValidAddress ( baseAddress ) ) {
2753- throw new InvalidAddressError ( 'invalid base address' ) ;
2775+ // Forwarder version 0 addresses cannot be verified because we do not store the nonce value required for address derivation.
2776+ if ( forwarderVersion === 0 ) {
2777+ return true ;
27542778 }
2779+ // Verify MPC wallet address for wallet version 3 and 6
2780+ if ( isTssVerifyAddressOptions ( params ) && params . walletVersion !== 5 ) {
2781+ return verifyMPCWalletAddress ( { ...params , keyCurve : 'secp256k1' } , this . isValidAddress , ( pubKey ) => {
2782+ return new KeyPairLib ( { pub : pubKey } ) . getAddress ( ) ;
2783+ } ) ;
2784+ } else {
2785+ // Verify forwarder receive address
2786+ const { coinSpecific, baseAddress } = params ;
27552787
2756- if ( ! _ . isObject ( coinSpecific ) ) {
2757- throw new InvalidAddressVerificationObjectPropertyError (
2758- 'address validation failure: coinSpecific field must be an object'
2759- ) ;
2760- }
2788+ if ( _ . isUndefined ( baseAddress ) || ! this . isValidAddress ( baseAddress ) ) {
2789+ throw new InvalidAddressError ( 'invalid base address' ) ;
2790+ }
27612791
2762- if ( impliedForwarderVersion === 0 || impliedForwarderVersion === 3 || impliedForwarderVersion === 5 ) {
2763- return true ;
2764- } else {
2765- const ethNetwork = this . getNetwork ( ) ;
2766- const forwarderFactoryAddress = ethNetwork ?. forwarderFactoryAddress as string ;
2767- const forwarderImplementationAddress = ethNetwork ?. forwarderImplementationAddress as string ;
2792+ if ( ! _ . isObject ( coinSpecific ) ) {
2793+ throw new InvalidAddressVerificationObjectPropertyError (
2794+ 'address validation failure: coinSpecific field must be an object'
2795+ ) ;
2796+ }
27682797
2798+ const { forwarderFactoryAddress, forwarderImplementationAddress } = this . getFactoryAndImplContractAddresses (
2799+ params . walletVersion
2800+ ) ;
27692801 const initcode = getProxyInitcode ( forwarderImplementationAddress ) ;
27702802 const saltBuffer = ethUtil . setLengthLeft (
27712803 Buffer . from ( ethUtil . padToEven ( ethUtil . stripHexPrefix ( coinSpecific . salt || '' ) ) , 'hex' ) ,
27722804 32
27732805 ) ;
27742806
2775- // Hash the wallet base address with the given salt, so the address directly relies on the base address
2807+ const { createForwarderParams, createForwarderTypes } =
2808+ forwarderVersion === 4
2809+ ? getCreateForwarderParamsAndTypes ( baseAddress , saltBuffer , coinSpecific . feeAddress )
2810+ : getCreateForwarderParamsAndTypes ( baseAddress , saltBuffer ) ;
2811+
27762812 const calculationSalt = optionalDeps . ethUtil . bufferToHex (
2777- optionalDeps . ethAbi . soliditySHA3 ( [ 'address' , 'bytes32' ] , [ baseAddress , saltBuffer ] )
2813+ optionalDeps . ethAbi . soliditySHA3 ( createForwarderTypes , createForwarderParams )
27782814 ) ;
27792815
27802816 expectedAddress = calculateForwarderV1Address ( forwarderFactoryAddress , calculationSalt , initcode ) ;
@@ -3056,7 +3092,7 @@ export abstract class AbstractEthLikeNewCoins extends AbstractEthLikeCoin {
30563092 }
30573093 const typedDataRaw = JSON . parse ( typedData . typedDataRaw ) ;
30583094 const sanitizedData = TypedDataUtils . sanitizeData ( typedDataRaw as unknown as TypedMessage < any > ) ;
3059- const parts = [ Buffer . from ( '1901' , 'hex' ) ] ;
3095+ const parts : Buffer [ ] = [ Buffer . from ( '1901' , 'hex' ) ] ;
30603096 const eip712Domain = 'EIP712Domain' ;
30613097 parts . push ( TypedDataUtils . hashStruct ( eip712Domain , sanitizedData . domain , sanitizedData . types , version ) ) ;
30623098
0 commit comments