Skip to content

Commit 3292e9d

Browse files
committed
feat: add wallet address verification for eth
TICKET: WP-6461
1 parent 3b715f8 commit 3292e9d

File tree

5 files changed

+435
-183
lines changed

5 files changed

+435
-183
lines changed

modules/abstract-eth/src/abstractEthLikeNewCoins.ts

Lines changed: 57 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ import {
4040
VerifyAddressOptions as BaseVerifyAddressOptions,
4141
VerifyTransactionOptions,
4242
Wallet,
43+
verifyMPCWalletAddress,
44+
TssVerifyAddressOptions,
45+
isTssVerifyAddressOptions,
4346
} from '@bitgo/sdk-core';
4447
import { getDerivationPath } from '@bitgo/sdk-lib-mpc';
4548
import { bip32 } from '@bitgo/secp256k1';
@@ -369,6 +372,7 @@ interface PresignTransactionOptions extends TransactionPrebuild, BasePresignTran
369372
interface EthAddressCoinSpecifics extends AddressCoinSpecific {
370373
forwarderVersion: number;
371374
salt?: string;
375+
feeAddress?: string;
372376
}
373377

374378
export const DEFAULT_SCAN_FACTOR = 20;
@@ -401,9 +405,12 @@ export interface EthConsolidationRecoveryOptions {
401405
export 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+
407414
const debug = debugLib('bitgo:v2:ethlike');
408415

409416
export 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

Comments
 (0)