Open raulk opened 2 years ago
TL;DR:
The third element is to add an sself::change_code_cid privileged syscall that enables an actor to change its CodeCID. For M2, it would only be allowable from the account actor.
(note: I'd like this to not be privileged to avoid users using the proxy actor upgrade pattern)
The v9? account actor would offer a new method EnableEthTx to enable Ethereum compatibility. It's only invokable once, and on f1 addresses only (secp pubkeys). It changes its CodeCid to the one for the eaccount builtin actor (Ethereum-compatible acount) —whose bytecode offers the authenticate entrypoint— and adds the corresponding address mapping to the EVM address registry.
I like the ability to use f1 addresses here, but I'm not a fan of adding an Eth specific endpoint to accounts.
But I think we can generalize this, and make it really useful elsewhere by adding a general-purpose "upgrade" method to account actors that let the owner replace the account actor's code with some other type of actor. In this case, you'd replace the actor with an EVM proxy, eaccount, etc.
This would also allow one to start out with an account actor, then upgrade to a multisig. I can see this being used all the time in practice where a service starts out by generating an HD Wallet that may never be used, then transitions it into a multisig when/if it appears on-chain. It's similar to the CREATE2 counterfactual, but it works with wallets and keys.
It would also allow users to "upgrade" accounts later if they find that they need additional security, even if that account has already been hard-coded into other actors.
Overhead of loading Wasm bytecode to validate transactions in the mpool. An attacker could send millions of invalid "delegated" transactions to force Wasm code to run for free.
We can't run arbitrary wasm bytecode when validating messages. That's not a mild inconvenience, it's a trival DoS vector.
But that's not a show-stopper:
authenticate
and include it in the message's gas limit.authenticate
). That way we don't execute arbitrary wasm in the message pool validator.authenticate
function:
authenticate
function succeeds, the actor is charged for gas as usual.The show-stopper is tipsets because a message can go from authenticating to not authenticating. Examples:
authenticate
to behave differently.If this happens, we need to charge someone for the cost of trying to validate the message and failing (otherwise this is an attack vector). But we:
authenticate
may have succeeded when executing their block alone.Ethereum doesn't have this issue because they don't have tipsets. If an Ethereum miner includes a message in a block, they can know the exact result of executing that message before actually executing it. Unfortunately, that's not the case in Filecoin (at the moment).
Proposal:
There is a lot of overlap here with an account abstraction proposal that I am soon to make – especially the delegated signature validation. Account abstraction is a very general capability that would be powerful for lots of things. I had not yet realised that it might be part of a solution to smooth hosted VM workflows. I'll write more about that shortly, and then we can look for the common ground.
Edit: and contract upgradeability is another general capability we want to add (but I don't have a proposal for).
Note: @anorth pointed out (on slack) that we already do charge the miner in some cases like this. Specifically, we charge the miner if:
This can happen in one of two cases:
We need someone to cover the gas, so we charge the miner.
We could take the same attitude here and say that a miner is effectively taking responsibility for the message validation.
The main difference is...
authenticate
call, which could be substantial?But I guess that's up to the miner.
The second element is to omitting Filecoin signatures in messages where the authentication will be performed by the sender. This requires introducing a new signature type "delegated", signifying that the authentication is delegated to the sender actor via authenticate.
I'm wondering if this opens an attack vector where a malicious actor would upload an actor with an authenticate
implementation that authenticates everything successfully and that would let them impersonate any account on filecoin.
All known blockchain use ed25519 signatures for single singer transactions (Solana, Eth, Cosmos, etc.), and in all those instances we essentially have a transaction, a public key and a signature. We can have a generic mechanism that allows authenticating any arbitrary account by validating a signature over an arbitrary byte string:
let (account_pubkey, valid) = validate(pubkey, message_bytes, signature);
Without being specific to a particular chain, then when an account is impersonated using this method, we can "translate" a message with delegated
signature type to a native FileCoin message by unwrapping the inner message.
This, however, opens a very trivial and dangerous attack vector: an attacker can grab an arbitrary old transaction belonging to an account on another chain, and use its bytestring and signature to impersonate that transaction's equivalent on FileCoin.
This can be mitigated to an extend in Ethereum transactions by having a unique ChainID
for FileCoin, that would reject transactions with invalid ids.
On Solana this can be mitigated to a certain extend by rejecting transactions that have invalid recent_blockhash
.
So this leads me to the conclusion that we could have a two step process:
let (account_pubkey, valid) = validate(pubkey, message_bytes, signature);
for initial impersonation authentication.I'm wondering if this opens an attack vector where a malicious actor would upload an actor with an authenticate implementation that authenticates everything successfully and that would let them impersonate any account on filecoin.
To switch to that actor, you'd need to first have the account's key.
All known blockchain use ed25519 signatures for single singer transactions (Solana, Eth, Cosmos, etc.), and in all those instances we essentially have a transaction, a public key and a signature. We can have a generic mechanism that allows authenticating any arbitrary account by validating a signature over an arbitrary byte string:
The "translation" component would have the power to do whatever it wants.
- Let's generalize this. If we're willing to add EVM specific functionality, there are likely simpler solutions (adding a new signature/message type is annoying, but not that hard).
In our previous convos, we expressly decided to avoid cross-chain coupling at the protocol layer. The rationale is that we don't want to couple/marry our layer 1 with other layer 1 as that will create many problems down the road. Multiply this for every blockchain that we eventually want to support, and it quickly becomes unwieldly. There are already 3 Ethereum transaction types, and they are likely to add more going forward. That's why we settled on supporting foreign transactions at the actor level.
That's why we settled on supporting foreign transactions at the actor level
There are three levels:
With the current proposal, we'd still have to have chain level logic to verify the type of the sending account. In that case, we have two choices:
authenticate
endpoint. If we're going that way, I'd rather generalize the entire thing and allow other account types (rather than hard-code eaccounts into our account model).I guess the core problem here is that everything in the system interacts with "accounts". Any changes to accounts will affect chain verification, so any EVM specific changes will push EVM related logic down into chain verification.
Verify that it has an authenticate endpoint. If we're going that way, I'd rather generalize the entire thing and allow other account types (rather than hard-code eaccounts into our account model).
This proposal introduces this. In other words, there is no EVM-specific chain level logic required by this proposal; that would be a different proposal.
I 100% agree with the complete generalization. @anorth's proposal likely generalises even further. My goal with this sketch was to orient us towards one particular direction, and to gauge our appetite to introduce account abstraction in the protocol and abandon the bridge/proxy idea (the sooner we settle on a direction, the better).
A further generalisation here would consist of allowing accounts to always switch to any other actor code, as long as it exposes an authenticate
entrypoint. (Dangerous, that's why I opened with allowing account actors to transition only to eaccount
, as a form of a poor man allowlist, and a stepping stone until we generalise further).
Tipsets really do make any solution without a separate proxy service impossible.
In this proposal, messages with delegated
signature type still need to include the nonce. Wouldn't that solve your concern?
@anorth's account abstraction proposal is being incubated here (working doc): https://pl-strflt.notion.site/Account-abstraction-for-FVM-ebd15c2331d44c9aae8838bddac9e8c8
In this proposal, messages with delegated signature type still need to include the nonce. Wouldn't that solve your concern?
The opposite.
If a message is included but can't be executed for some reason, we charge the miner the "inclusion" cost of the message. This cost covers both the signature verification, and the data-storage cost of the message. At the moment, there are two cases where that can happen (assuming the miner didn't misbehave):
With this proposal:
authenticate
method. This could make these cases more expensive.authenticate
could cause messages to fail for arbitrary reasons.However, as long as:
authenticate
methods).authenticate
method is relatively cheap.We should be fine.
This proposal introduces this. In other words, there is no EVM-specific chain level logic required by this proposal; that would be a different proposal.
Then I'm totally on-board. My concern was with the fact that we were baking EVM-specific logic into account actors.
@anorth's account abstraction proposal is being incubated here (working doc): https://pl-strflt.notion.site/Account-abstraction-for-FVM-ebd15c2331d44c9aae8838bddac9e8c8
This addresses most of my concerns by limiting state changes. With that version, miners don't even need to whitelist specific validation methods.
I've posted the basic account abstraction proposal for FIP discussion now in https://github.com/filecoin-project/FIPs/discussions/388.
I didn't include details of how we might use it for extensible addressing, I think we still have some bits to figure out there.
(This is a very early idea sketch; I just wanted to get it out there after brainstorming with @karim-agha around this. It's still missing a proper reflection on pros, cons, risks, security properties, and other opportunities this scheme enables.)
Context
Filecoin needs the ability to intake signed Ethereum transactions, often issued from wallets like MetaMask. This is an integral part of the Ethereum compatibility story.
Forwarders/bridge services and Ethereum proxy actors may be a solution to frame and sponsor/chaperone Filecoin messages out of Ethereum transactions. However, they feels clunky, lazy and non-native. They also summon complexity and challenges:
Proposal (conceptual design)
Key elements
The first element of the proposal is to introduce a mechanism to enable actors to promote themselves as message senders. By offering a new Wasm entrypoint
authenticate
, actor code can advise the FVM that it supplies custom logic to authenticate a message payload and, optionally, hoist itself as the sender.The second element is to omitting Filecoin signatures in messages where the authentication will be performed by the sender. This requires introducing a new signature type
"delegated"
, signifying that the authentication is delegated to the sender actor viaauthenticate
.The third element is to add an
sself::change_code_cid
privileged syscall that enables an actor to change its CodeCID. For M2, it would only be allowable from the account actor. The v9? account actor would offer a new methodEnableEthTx
to enable Ethereum compatibility. It's only invokable once, and on f1 addresses only (secp pubkeys). It changes its CodeCid to the one for theeaccount
builtin actor (Ethereum-compatible acount) —whose bytecode offers theauthenticate
entrypoint— and adds the corresponding address mapping to the EVM address registry.Note that calling the
EnableEthTx
method is necessary because we need to ecrecover the pubkey to generate the Ethereum address for the EVM address registry. We could do away with this step if we drop some requirements though.Processing EVM transactions
Wallets like MetaMask submit signed Ethereum transactions through the
eth_sendSignedTransaction
JSON-RPC operation, offered by Filecoin clients. This method would:delegated
signature typefrom
is computed by performing ecrecover on the Ethereum signature and computing the f1 address for the pubkey.to
,nonce
,value
, gas params, etc.) is copied from the EVM tx.The FVM is now responsible for realising that the tx type is
"delegated"
, loading the bytecode offrom
and invoking itsauthenticate
entrypoint. Details on params, return value and what syscalls are allowed TBD.eaccount
'sauthenticate
validates the signature on the inner EVM message as well as the consistency with the outer message. If successful, it returns without aborting, thus instructing the FVM to adopt theeaccount
actor as the sender, using the nonce, value, etc. from the outer Filecoin message.The FVM will now invoke the receiver (an Ethereum contract that will understand the EVM tx) using the context of the authenticated sender.
We have now managed to intake an Ethereum transaction without infecting the core protocol and without requiring clunky app-level bridges or extra actors (e.g. indirection).
Supporting other chains
This mechanism allows us to support native transactions with their relevant signature types (e.g. ecdsa with curve25519) from other chains by authenticating them in user/semi-privileged land, without leaking transaction and signature scheme details of every chain to the core protocol.
Open questions
"delegated"
transactions to force Wasm code to run for free.eaccount
type and thesself::change_code_cid
syscall, or can we add theauthenticate
entrypoint to the account actor itself, and track when Eth has been bound/enabled in state?