Skip to content
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

Audit Address mapping #6723

Closed
1 task done
Tracked by #15
athei opened this issue Dec 1, 2024 · 5 comments · Fixed by #7662
Closed
1 task done
Tracked by #15

Audit Address mapping #6723

athei opened this issue Dec 1, 2024 · 5 comments · Fixed by #7662
Assignees

Comments

@athei
Copy link
Member

athei commented Dec 1, 2024

The EVM uses 20 byte addresses. Polkadot uses 32 byte addresses. In order to make those two components work together we needed to design and implement a mapping between the two. Our design works slightly different from frontier or Acala.

We need to get this audited as soon as possible as it might incur bigger changes we are not able to make easily once the chain is live.

Tasks

Preview Give feedback
  1. athei
@athei athei converted this from a draft issue Dec 1, 2024
@athei athei self-assigned this Dec 11, 2024
@boboskii
Copy link

A set of mnemonic words can generate either Ethereum's h160 address or Polkadot's SS58 address. As long as the bip44 path is determined, can't these two addresses be directly mapped? In this way, users only need to manage a set of mnemonic words.

@athei
Copy link
Member Author

athei commented Jan 20, 2025

The problem is that we need a mapping that maps addresses. Not mnemonics or private keys. Not even a mapping between public keys is sufficient. A contract can pass an H160 as argument and you need to derive the account id from it. You don't even know the public key of that H160.

@boboskii
Copy link

Thank you for your answer. I am developing a polkavm wallet. The problem I am encountering now is whether I should create an ecdsa key pair or a sr25519 key pair for the user. Users coming from evm are more familiar with h160, but ecdsa seems to need to pass precompile to interact with the asset hub. maybe i need to create two accounts, ecdsa and sr25519, at the same time, but these two accounts are not related. Sorry, I'm not very familiar with how polkavm works yet.

@athei
Copy link
Member Author

athei commented Jan 20, 2025

This is a tricky problem with a lot of nuance to it. Let me try to explain.

Users coming from evm are more familiar with h160, but ecdsa seems to need to pass precompile to interact with the asset hub.

You might think that you need to send Eth transactions through our Eth RPC interface when you have an ethereum private key. However, that is not true. You can sign a normal AssetHub transaction with your Ethereum key. AssetHub (as most other Substrate based chain) accept multiple different signature types. One of them the ecdsa curve Ethereum uses. However, the problem with that is that the address this transaction will perceived to be coming from is based off the public key.

The problem with that derivation is that it doesn't allow you to know somebodies AccountID32 from just looking at their H160. You would need the public key which is not available in all cases can't can't be recovered from the H160. This problem arises when running Eth contracts on a AccountId32 chain. This is why we introduced a new address to address mapping. Cross chain interactions with chains using H160 addresses (like Mythical or Moonbeam) face similar issues.

To remedy the situation we should add another MultiSigner variant that derives the AccountId32 for the ecdsa key in the same way as our account mapping (by deriving the H160 and then padding which is what we do in contracts).

Please note that you could sign a transaction with a H160 key today. The downside is the AccountID32 your would be acting as would be different that as if you would signed a Eth tx with the same key. A suboptimal solution.

The problem I am encountering now is whether I should create an ecdsa key pair or a sr25519 key pair for the user.

Our goal is that both keys can be used for both transaction type:

  1. Derive the address for normal transactions as described above.
  2. Accept sr/ed25519 signed Eth transactions.

Then a wallet could allow using both flavors of transactions (and hence both flavors of Dapps) to be used from any key type. And your address would not change depending on which transaction type you use.

All the wallets would need to add is allowing cross injecting of accounts: Allow injecting Eth keys into Polkadot native Dapps and injecting Polkadot Keys into Eth Dapps. The wallet would apply the canonical address mapping so the Dapps won't even be aware of whar is happening.

@boboskii
Copy link

Thank you again for your reply. It is true that I misunderstood the signature verification method of the substrate chain. Looks like I need to spend more time studying the documentation. This is not an audit question, sorry for taking up your time.

github-merge-queue bot pushed a commit that referenced this issue Feb 28, 2025
Fixes #6723

## Motivation

Internal auditors recommended to not truncate Polkadot Addresses when
deriving Ethereum addresses from it. Reasoning is that they are raw
public keys where truncating could lead to collisions when weaknesses in
those curves are discovered in the future. Additionally, some pallets
generate account addresses in a way where only the suffix we were
truncating contains any entropy. The changes in this PR act as a safe
guard against those two points.

## Changes made

We change the `to_address` function to first hash the AccountId32 and
then use trailing 20 bytes as `AccountId20`. If the `AccountId32` ends
with 12x 0xEE we keep our current behaviour of just truncating those
trailing bytes.

## Security Discussion

This will allow us to still recover the original `AccountId20` because
those are constructed by just adding those 12 bytes. Please note that
generating an ed25519 key pair where the trailing 12 bytes are 0xEE is
theoretically possible as 96bits is not a huge search space. However,
this cannot be used as an attack vector. It will merely allow this
address to interact with `pallet_revive` without registering as the
fallback account is the same as the actual address. The ultimate vanity
address. In practice, this is not relevant since the 0xEE addresses are
not valid public keys for sr25519 which is used almost everywhere.

tl:dr: We keep truncating in case of an Ethereum address derived account
id. This is safe as those are already derived via keccak. In every other
case where we have to assume that the account id might be a public key.
Therefore we first hash and then take the trailing bytes.

## Do we need a Migration for Westend

No. We changed the name of the mapping. This means the runtime will not
try to read the old data. Ethereum keys are unaffected by this change.
We just advise people to re-register their AccountId32 in case they need
to use it as it is a very small circle of users (just 3 addresses
registered). This will not cause disturbance on Westend.

---------

Co-authored-by: cmd[bot] <41898282+github-actions[bot]@users.noreply.github.com>
athei added a commit that referenced this issue Mar 3, 2025
Fixes #6723

Internal auditors recommended to not truncate Polkadot Addresses when
deriving Ethereum addresses from it. Reasoning is that they are raw
public keys where truncating could lead to collisions when weaknesses in
those curves are discovered in the future. Additionally, some pallets
generate account addresses in a way where only the suffix we were
truncating contains any entropy. The changes in this PR act as a safe
guard against those two points.

We change the `to_address` function to first hash the AccountId32 and
then use trailing 20 bytes as `AccountId20`. If the `AccountId32` ends
with 12x 0xEE we keep our current behaviour of just truncating those
trailing bytes.

This will allow us to still recover the original `AccountId20` because
those are constructed by just adding those 12 bytes. Please note that
generating an ed25519 key pair where the trailing 12 bytes are 0xEE is
theoretically possible as 96bits is not a huge search space. However,
this cannot be used as an attack vector. It will merely allow this
address to interact with `pallet_revive` without registering as the
fallback account is the same as the actual address. The ultimate vanity
address. In practice, this is not relevant since the 0xEE addresses are
not valid public keys for sr25519 which is used almost everywhere.

tl:dr: We keep truncating in case of an Ethereum address derived account
id. This is safe as those are already derived via keccak. In every other
case where we have to assume that the account id might be a public key.
Therefore we first hash and then take the trailing bytes.

No. We changed the name of the mapping. This means the runtime will not
try to read the old data. Ethereum keys are unaffected by this change.
We just advise people to re-register their AccountId32 in case they need
to use it as it is a very small circle of users (just 3 addresses
registered). This will not cause disturbance on Westend.

---------

Co-authored-by: cmd[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: Minimal Feature Launch
Development

Successfully merging a pull request may close this issue.

2 participants