-
-
Notifications
You must be signed in to change notification settings - Fork 246
fix(multichain-account-service): prevent creating EVM account during discovery #6650
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
988ea6d
0b4c3be
8cfb0d7
36cade5
923231c
c17e874
a3c8ad4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,6 @@ | ||
import { publicToAddress } from '@ethereumjs/util'; | ||
import type { Bip44Account } from '@metamask/account-api'; | ||
import type { HdKeyring } from '@metamask/eth-hd-keyring'; | ||
import type { EntropySourceId, KeyringAccount } from '@metamask/keyring-api'; | ||
import { EthAccountType } from '@metamask/keyring-api'; | ||
import { KeyringTypes } from '@metamask/keyring-controller'; | ||
|
@@ -7,7 +9,7 @@ import type { | |
InternalAccount, | ||
} from '@metamask/keyring-internal-api'; | ||
import type { Provider } from '@metamask/network-controller'; | ||
import type { Hex } from '@metamask/utils'; | ||
import { add0x, assert, bytesToHex, type Hex } from '@metamask/utils'; | ||
import type { MultichainAccountServiceMessenger } from 'src/types'; | ||
|
||
import { | ||
|
@@ -169,6 +171,36 @@ export class EvmAccountProvider extends BaseBip44AccountProvider { | |
return parseInt(countHex, 16); | ||
} | ||
|
||
async #getAddressFromGroupIndex({ | ||
entropySource, | ||
groupIndex, | ||
}: { | ||
entropySource: EntropySourceId; | ||
groupIndex: number; | ||
}): Promise<Hex> { | ||
// NOTE: To avoid exposing this function at keyring level, we just re-use its internal state | ||
// and compute the derivation here. | ||
return await this.withKeyring<HdKeyring, Hex>( | ||
{ id: entropySource }, | ||
async ({ keyring }) => { | ||
// If the account already exist, do not re-derive and just re-use that account. | ||
cursor[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
const existing = await keyring.getAccounts(); | ||
if (groupIndex < existing.length) { | ||
return existing[groupIndex]; | ||
} | ||
|
||
// If not, then we just "peek" the next address to avoid creating the account. | ||
assert(keyring.root, 'Expected HD keyring.root to be set'); | ||
const hdKey = keyring.root.deriveChild(groupIndex); | ||
assert(hdKey.publicKey, 'Expected public key to be set'); | ||
|
||
return add0x( | ||
bytesToHex(publicToAddress(hdKey.publicKey, true)).toLowerCase(), | ||
); | ||
}, | ||
); | ||
} | ||
|
||
/** | ||
* Discover and create accounts for the EVM provider. | ||
* | ||
|
@@ -184,39 +216,29 @@ export class EvmAccountProvider extends BaseBip44AccountProvider { | |
const provider = this.getEvmProvider(); | ||
const { entropySource, groupIndex } = opts; | ||
|
||
const [address, didCreate] = await this.#createAccount({ | ||
const addressFromGroupIndex = await this.#getAddressFromGroupIndex({ | ||
entropySource, | ||
groupIndex, | ||
}); | ||
|
||
// We don't want to remove the account if it's the first one. | ||
const shouldCleanup = didCreate && groupIndex !== 0; | ||
try { | ||
const count = await this.#getTransactionCount(provider, address); | ||
|
||
if (count === 0 && shouldCleanup) { | ||
await this.withKeyring<EthKeyring>( | ||
{ id: entropySource }, | ||
async ({ keyring }) => { | ||
keyring.removeAccount?.(address); | ||
}, | ||
); | ||
return []; | ||
} | ||
} catch (error) { | ||
// If the RPC request fails and we just created this account for discovery, | ||
// remove it to avoid leaving a dangling account. | ||
if (shouldCleanup) { | ||
await this.withKeyring<EthKeyring>( | ||
{ id: entropySource }, | ||
async ({ keyring }) => { | ||
keyring.removeAccount?.(address); | ||
}, | ||
); | ||
} | ||
throw error; | ||
const count = await this.#getTransactionCount( | ||
provider, | ||
addressFromGroupIndex, | ||
); | ||
if (count === 0) { | ||
return []; | ||
} | ||
|
||
// We have some activity on this address, we try to create the account. | ||
const [address] = await this.#createAccount({ | ||
entropySource, | ||
groupIndex, | ||
}); | ||
assert( | ||
addressFromGroupIndex === address, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you remind me if the addresses are all lowercase or are they normalized? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We usually use lowercase for EVM yes. But I'm pretty sure it's bit of a mix today... I just know that most people re-sanitize their addresses before using them. JFYI, I just re-used the same code than here: |
||
'Created account does not match address from group index.', | ||
); | ||
|
||
const account = this.messenger.call( | ||
'AccountsController:getAccountByAddress', | ||
address, | ||
|
Uh oh!
There was an error while loading. Please reload this page.