🤖

Account abstraction for FVM

Last edited
Jun 21, 2022 1:07 AM
Project
FVM Capabilities & Standards

The canonical version of this is now https://github.com/filecoin-project/FIPs/discussions/388. Please make comments there.

This is a proposal to introduce account abstraction into Filecoin. Account abstraction allows a user-programmed actor to be the top level entry point that initiates a transaction and pays fees.

Filecoin has a unique opportunity to implement account abstraction before a host of other actors and systems come to rely on the assumption that EOAs and actors are distinct.

Motivation

TODO fill in before public discussion.

Proposal

Message validation invokes the VM

The on-chain Message type is unchanged. All fields still have the same meanings, except that the gas values are a suggestion to the sending actor, rather than definitive. The sending actor will have the opportunity to specify different gas values during message validation.

Instead of validating a messages signature using a hardcoded algorithm (e.g. Secp256k1), message signature invokes the VM and delegates checking to the actor identified in a message’s From address.

Call sequence number checking remains unchanged: a message’s sequence number continues to be checked separately to signature validation, and the From actor’s CallSeqNum is incremented automatically after a message is validly included in the chain.

VM entry point for message validation

A new actor entry point is added to the VM (separate from invoke):

validate(Message, Signature)-> GasSpec

The message and signature parameters are the blockchain structures. An actor implementing this entry point is an account actor.

This entry point is invoked by the mempool and transaction relayers to validate a message, taking the place of existing message signature and gas limit validation. Validation either aborts (signalling the message is invalid) or returns the gas limit and price for the message.

type GasSpec struct {
	GasLimit   int64
	GasFeeCap  TokenAmount
	GasPremium TokenAmount
}

[TODO: work out mechanics of returning a tuple from entry point]

Validation is expected to verify that the signature data authorises the message for execution by the account actor. It may check a cryptographic signature but may also inspect the message and perform arbitrary local logic according to policies expressed in the actor’s code or state. Validation is subject to a fixed gas limit of VALIDATION_GAS_LIMIT = TODO.

If validate returns successfully, then prior to execution the VM sets the gas limit and deducts the returned GasLimit * GasFeeCap from the actor’s balance (failing if balance is insufficient) before proceeding. That the execution of validate itself incurs a gas cost, which consumes part of the message’s gas limit. The VM then invoke the receiver of the message as usual.

Note that the Message parameter to validate includes gas parameters specified from the outside. The actor may pass these values through or return different ones: those from the message may be thought of as advisory from the end user.

Restricted runtime

The validate method has a restricted runtime. It cannot inspect any environmental variables (e.g. current epoch), check its own balance, nor send a message to any other actor. Any attempt to do so must abort. The actor can read its own state, but must in effect be a pure function of that state and the message and signature.

Restrictions on receiving

A message to an account actor from any other actor is read-only, and cannot modify the account actor’s state. Any attempt to replace the actor’s state root must abort. The exception to this is that a message originating from an account actor can modify its own state (including through a call sequence via other actors).

Upgradeability

An account actor is expected to be upgradeable in-place via future FVM code upgradability mechanisms or proxy-delegate patterns. This compatibility should be confirmed prior to finalising either mechanism.

Block and message validity

Messages

The validity of a message with a particular sequence number depends purely on the actor state at that sequence number. Since that state is immutable except via calls originating from the actor itself, the validity for any message bearing that sequence number is fixed, and may be cached by mempools and message relayers. A node may use the cached validity result (including gas parameters and gas units used for validation) when executing the message in a block: it does not need to re-validate.

The validity of any message with a more distant sequence number is not fixed: an earlier message could arbitrarily alter the actor’s validation logic. Any change to the actor’s state would necessitate revalidation of all subsequent messages. Thus, in the basic case, a mempool should keep only one message originating from any actor at one time. Heuristics could support retaining longer message chains in some situations (e.g. the sending code CID is known to be an immutable, non-upgradeable contract; or analysis shows that earlier messages can’t invalidate later ones).

This restriction on message chains is mitigated by the introduction of multicalls (below).

Blocks

A block may include only valid messages, according to each actor’s validity method. Since message validity depends only on that actor’s state, which is immutable except via calls originating with the actor itself, the ordering of messages from different actors cannot affect the validity of any one of them.

Multiple messages from one actor may be included in one block. Each must be valid against the state after the immediately preceding message.

Note that a message may still fail due to the sending actor having insufficient balance to cover the gas limit (which the validation function cannot check). This results in a penalty to the block provider, as today.

Gas charges

The base message fee is reduced to reflect that no signature validation happens. But gas is charged for the validate method.

Upgrade the built-in account actor

The built-in account actor code is upgraded in place to become an account actor according to this proposal, with equivalent behaviour to today. The validate method checks the message’s signature via the VerifySignature syscall and passes through the message’s gas parameters.

The built-in account actor should also gain an upgradeability hook, so that it may be replaced with a different contract after initial creation.

This upgrade means that all account actors utilise account abstraction and may be upgraded. The only distinction that a built-in account actor has is that it has an address that was derived from a public key (but not necessarily the public key now in use for validation).

Discussion

Multicalls

A multicall is a single blockchain message that makes multiple calls to other actors, which can succeed or fail as a unit. Multicalls are great for the UX of doing multiple things without waiting for block inclusion (e.g. “approve token and then make a transfer”) and also more efficient, because message overheads (especially signature validation) are amortized over more work.

Account abstraction supports implementing multicalls within the account actors. We just add an exported method Execute(calls: List<Call>) and then send a message from the account to itself with the list of calls to execute. It’s also possible to parameterise policy around atomicity, etc. We can upgrade the built-in account actor to support this for everyone, retrospectively!

This Execute method should probably be specced as an FRC for more general delegated execution, too.

Hosted VMs (EVM)

TODO

Idea 1

A single EVM “account” actor as entry point for EVM txns. EVM message is wrapped in a Filecoin message, with the EVM account as both From and To address (thus allowing state mutation). EVM account actor state is the EVM’s state. EVM message is in the parameters (maybe the signature too). EVM actor inspects the message to validate signature and get gas values. Load the EVM contract’s nonce from state to validate the EVM message’s nonce. Dispatch to self, do EVM nonce increment as part of evaluating EVM state.

Problem: contention for EVM actor’s nonce, everyone needs to coordinate on it. Also conflicts with one txn per account in the mempool.

  • Theoretically solvable if we fully abstracted message validation, including nonce (then it could check the underlying EVM contract’s nonce instead).
  • A workaround is to allow miners to edit the Filecoin message nonces to put them in sequence, because the signature doesn’t check it. Tipsets would see collisions tho.
  • More than one tx in mempool is tricky, miners need to know about EVM nonces and track them.
  • The EVM actor would pay gas 🤔. Could we get the inner EVM message to pay gas to the hosting actor?
  • EVM actor essentially a bridge, holding bridged FIL.

Fixed set of VM entry point actors can be known and special-cased by block producers.

Precludes implementing AA inside the hosted EVM: EVM message validation must remain pure from EOAs. But could maybe do something like https://eips.ethereum.org/EIPS/eip-4337

Idea 2

From ‣

An account actor per EVM address.

“FVM will now invoke the receiver (an Ethereum contract that will understand the EVM tx)” is fuzzy. Alternative: EVM account actor invokes self, then calls a syscall to pass in the EVM txn. This means singleton EVM. Alt: call an EVM actor.

Use a method number to identify EVM wrapped txns, vs messages destined for the account actor itself?

Can an address have both a Filecoin and hosted EVM account? What happens to FIL sent to an EVM address? Or other assets (maybe preventable via token receiver hooks). Answer: the same pubkey will generate different addresses for Filecoin vs EVM, so could be separate accounts?

Open questions

TODO: how to handle verification of BLS aggregates in block header?

TODO: new abstract signature type to allow for multisigs, non-signatures etc. Can’t be a fixed whitelist of formats.

TODO: note the idea of paymasters or sponsored txns, but that we don’t get it with this scheme :-( (need authcall like EIP-3074)

TODO: note idea of paying fees with tokens, but we don’t get it :-(

Alternatives

Minimum gas bounds allowing state mutation from external calls

Rather than being immutable, allow mutation if a minimum gas amount is spent (some fraction of the fixed cost of re-validating the next message). See https://eips.ethereum.org/EIPS/eip-2938

Abstract over call sequence number (nonce)

Delegate nonce checking to validate() too, allowing more flexible schemes than linear (e.g. host an EVM inside an actor, and use EVM address nonces).

This can allow a message CID to be valid more than once, breaking uniqueness. Is that bad? We do deduplicate on CID within tipsets though (and can require unique within a block).

A message could become invalid in a tipset because a prior one bumped the nonce (each miner only held one in mempool, but different ones). This is analogous to situation today where we penalise miner for “wrong” nonce. But implication is that we need to re-run validate() as part of tipset execution, whereas original proposal doesn’t. Is that too bad?

Distinct AA entry point

  • Blockchain message from is a sentinel address, to is the account contract. Gas, value, method fields ignored. So AA messages are distinguishable from other ones.
  • Params opaque, but expected to be a sequence of Calls specifying to, methodnum, params
  • VM entry point validate(signature, nonce, params), returns gas limit and price
    • Note nonce is already checked, but needed for sig validation
  • Invoke the to actor, which then dispatches calls from an execute method or similar (or distinct entry point?)

Background reading

ERC-2938 Account abstraction (Sep 2020)

Account abstraction (AA) allows a contract to be the top-level account that pays fees and starts transaction execution
Account abstraction extends the validity conditions of transactions with the execution of arbitrary EVM bytecode (with some limits on what state may be accessed.) To signal validity, we propose a new EVM opcode PAYGAS, which also sets the gas price and gas limit the contract is willing to pay.
existing limitations preclude innovation in a number of important areas, particularly: - Smart contract wallets that use signature verification other than ECDSA (eg. Schnorr, BLS, post-quantum…) - Smart contract wallets that include features such as multisig verification or social recovery, reducing the highly prevalent risk of funds being lost or stolen - Privacy-preserving systems like tornado.cash - Attempts to improve gas efficiency of DeFi protocols by preventing transactions that don’t satisfy high-level conditions (eg. existence of a matching order) from being included on chain - Users being able to pay for transaction fees in a token other than ETH (eg. by converting that token into the ETH needed for fees inside the transaction in real-time)
Miners need to determine if a transaction will actually pay the fee if they include it after only a small amount of processing to avoid DoS attacks. Validating nodes need to perform an essentially identical verification to determine whether or not to rebroadcast the transaction.
In an account abstraction setup, the goal is to allow accounts to specify EVM code that can establish more flexible conditions for a transaction’s validity, but with the requirement that this EVM code can be quickly verified, with the same safety properties as the existing setup.
… mechanics introduced by AA already break the ability to easily verify that a chain longer than one transaction can be processed
An important property of traditional transactions is that activity happening as part of transactions that originate outside of some given account X cannot make transactions whose sender is X invalid.
AA transactions will always have origin == AA_ENTRY_POINT [can we fix that in FIL?]
Badly-designed single-tenant AA contracts will break the transaction non-malleability invariant. That is, it is possible to take an AA transaction in-flight, modify it, and have the modified version still be valid; AA account contracts can be designed in such a way as to make that not possible, but it is their responsibility.
AA contracts may not have replay protection unless they build it in explicitly; this can be done with the CHAINID (0x46)  opcode

ERC-4337 Account abstraction via entry point contract (Sep 2021)

Avoids any changes to Ethereum consensus layer via a separate mempool, bundler nodes, built-in entry point contract.

• Achieve the key goal of account abstraction: allow users to use smart contract wallets containing arbitrary verification logic instead of EOAs as their primary account. Completely remove any need at all for users to also have EOAs (as status quo SC wallets and EIP-3074 both require)
Try to support other use cases
  • Privacy-preserving applications
  • Atomic multi-operations (similar goal to EIP-3074)
  • Pay tx fees with ERC-20 tokens, allow developers to pay fees for their users, and EIP-3074like sponsored transaction use cases more generally
The main challenge with a purely smart contract wallet based account abstraction system is DoS safety: how can a miner including an operation make sure that it will actually pay fees, without having to first execute the entire operation?
It is an important design goal of this proposal to replicate the key property of EOAs that users do not need to perform some custom action or rely on an existing user to create their wallet; they can simply generate an address locally and immediately start accepting funds. This is accomplished by having the entry point itself create wallets using CREATE2.

In short, account abstraction means that not only the execution  of a transaction can be arbitrarily complex computation logic as specified by the EVM, but also the authorization  logic of a transaction would be opened up so that users could create accounts with whatever authorization logic they want. Currently, a transaction can only “start from” an externally owned account (EOA), which has one specific authorization policy: an ECDSA signature. With account abstraction, a transaction could start from accounts that have other kinds of authorization policies, including multisigs, other cryptographic algorithms, and more complex constructions such as ZK-SNARK verification.
Multisig wallets and other uses of smart contract wallets (eg. social recovery). Currently, such wallets require an awkward construction where there is a separate account that stores a small amount of ETH to pay for transaction fees, and that account gets refilled over time. Cryptography other than ECDSA. Schnorr signatures, BLS signatures, other curves and quantum-proof algorithms such as Lamport/Winternitz could all be implemented. Privacy solutions would no longer require “relayers”, as the verification of the proof would be moved into authorization logic and so would be a condition of fee payment. On-chain DEXes and similar systems where there’s often multiple transactions from many users trying to claim the same arbitrage opportunity; currently, this leads to inefficiency because many failed transactions are nevertheless published on chain, but with abstraction, it’s theoretically possible to make sure failed transactions do not get included on chain at all, improving gas efficiency.
The only-storage-dependent and non-writing rules are there to ensure that a transaction with some target T is guaranteed to continue being valid and having the same gasprice until either (i) that transaction is included, or (ii) another transaction with target T is included. This ensures that transactions have similar technical properties to what they have today. The “ENTRY_POINT only” guard requirement is there to ensure that this rule cannot be violated by transactions with other targets calling into the account.

The main challenge is the tradeoff between flexibility of account forwarding policy and safety of transaction processing and transmission. In general, it should not be possible to force other nodes in the network (miners or relaying nodes) to perform large amounts of processing work without at least some risk that the transaction actually will be included in the chain and the sender will be forced to pay a fee.

• currently, sending from a multisig wallet requires each operation to be ratified by the participants, and each ratification is a transaction. This could be simplified by having one ratification transaction include signatures from the other participants, but even still it introduces complexity because the participants' accounts all need to be stocked up with ETH. With this EIP, it will be possible to just have the contract store the ETH, send a transaction containing all signatures to the contract directly, and the contract can pay the fees. - custom cryptography - non-crypto modifications, like parallel nonces
Note that miners would need to have a strategy for accepting these transactions. This strategy would need to be very discriminating, because otherwise they run the risk of accepting transactions that do not pay them any fees, and possibly even transactions that have no effect (eg. because the transaction was already included and so the nonce is no longer current). One simple approach is to have a whitelist for the codehash of accounts that they accept transactions being sent to; approved code would include logic that pays miners transaction fees. However, this is arguably too restrictive; a looser but still effective strategy would be to accept any code that fits the same general format as the above, consuming only a limited amount of gas to perform nonce and signature checks and having a guarantee that transaction fees will be paid to the miner.

EIP-2718

There are multiple proposals in discussion that define new transaction types such as one that allows EOA accounts to execute code directly within their context, one that enables someone besides msg.sender  to pay for gas, and proposals related to layer 1 multi-sig transactions. These all need to be defined in a way that is mutually compatible, which quickly becomes burdensome to EIP authors and to clients who now have to follow complex rules for differentiating transaction type.

Discussion of some possible approaches in Ethereum: forced deployment, implicit default code, new tx type,

Not only do smart contract wallets improve efficiency and user experience, they provide a general way to mitigate cryptographic weaknesses (like ECDSA being vulnerable to quantum computing.)
All EOAs become proxies pointing by default to an ECDSA precompile with the same behavior as existing EOAs, and a setImplementation function which the user can call to switch to a different one in-place. Therefore the default implementation doesn’t need to do everything users need, just the basic functionality and an easy way to upgrade.

https://eips.ethereum.org/EIPS/eip-1271 standard signature verification method