This post outlines the goals and model of account abstraction to be used in Starknet. We look to collect feedback and implement the model, possibly in phases, in Starknet Alpha. This is the first of two posts describing current thoughts.
At a high level, account abstraction refers to the abstraction of the concept of accounts and related mechanisms from the core protocol of the chain. So while identifying users is key to the system’s operation, we look to allow flexibility and extensibility in aspects relating to how accounts are verified and operate. We expect this to enable innovation in areas we can’t anticipate when defining the core protocol.
We describe the design choices and their rationale, with the known tradeoffs. Familiarity with Starknet and its underlying technology is assumed.
Similar to other account abstraction models suggested in Ethereum and other systems, we aim to provide flexibility in the Starknet protocol. Specifically in some key components of the protocol:
- Signature abstraction: allow different account contracts to use different signature validation schemes.
- Payment abstraction:
- Allow different models of payment for transactions. For example, payment by another party/contract.
- Better UX: don’t mandate a specific token (native or contract-defined) to be used for paying for transactions.
Complete abstraction of the account, i.e., all transactions are sent to contracts that have complete freedom in how transactions are validated and executed, will achieve the above-stated goals. It will, however, result in confusing UX (e.g., hard to guarantee transaction uniqueness) and might open the door to attacks on sequencers.
We, therefore, maintain a model where accounts are still represented by contracts, so-called “account contracts”, but provide a more defined structure to this interaction. We follow, in spirit, the model proposed in EIP-4337 for Ethereum, with applicable changes made to accommodate the difference in Starknet and Cairo.
The gist of the mechanism is an account contract interface and a definition of its interaction with the Starknet OS and the sequencer node when handling a given transaction and including it in a block. The proposed interface requires an account contract to implement a
execute functions separately, with specific semantics and constraints on their operation. When used by the Starknet OS and sequencer, this allows the sequencer to validate a transaction and get paid for it with minimal risk.
Paying for transactions is done by a specific token known to the OS. This is either a native token or some other known ERC20 implementation. At this point (StarkNet Alpha), Starknet will use ETH, implemented as an ERC-20 contract, for paying for transactions.
The Starknet OS handles payment for transactions under the correct conditions (see below). It is not left to the account contract implementation. This allows the OS to guarantee payment and avoid some of the risks to the sequencer executing the transactions.
We see little value in abstracting the nonce itself from the protocol. Also, not guaranteeing a well-defined behavior (validation) of nonces creates issues with ensuring transaction uniqueness.
Therefore, nonces are defined as part of the contract state and part of the transaction structure and signature and behave similarly to how they are provided in the Ethereum protocol. In other words, transaction nonces are guaranteed to be sequentially increasing. The Starknet OS validates this.
A sequencer should limit the number of transactions per account (contract) it accepts into the pool. This is to prevent abuse of the pool as a means to DoS the sequencer or otherwise limit the resource usage per account.
We now describe a basic transaction flow, as depicted in the following diagram:
A transaction is sent to an address where an account contract is deployed to be executed and added to a block.
Note that the flow above describes the flow involving the OS. In practice, the sequencer doesn’t run the OS when executing each transaction. The OS is used to generate the proof and provides the necessary guarantees.
When a transaction is picked to be added to a block, the sequencer picks it and executes it. The execution of the transaction happens in two stages: the sequencer first asks the account contract to validate the transaction and then asks the account contract to execute it. These two stages are encoded in two separate functions in the account contract -
execute. The distinction between these stages allows Starknet OS to guarantee payment to the sequencer.
When an account contract successfully validates the transaction, the fee is collected (verified by the OS), so the sequencer is guaranteed to receive the payment, even if the transaction fails later during execution. When a transaction fails validation, no fee is paid; otherwise, this can be a potential attack vector by a malicious sequencer. Both validate and execute functions are part of the account contract code, encoded in Cairo, and therefore are proven as part of the block.
To prevent attacks by implementing a malicious account contract implementation, the validation function is constrained:
- The number of steps in the
validatefunction is limited.
validatefunction cannot access the storage of other contracts (outside the account contract).
The first constraint prevents abuse of the sequencer before successfully validating a transaction and getting paid for it. The second constraint addresses a potential risk where an attacker might fill the transaction pool (“mempool”) with many transactions and then invalidate them with a single operation that changes some storage in a 3rd party contract by making sure that transaction gets included first. Ensuring that validation depends only on local storage makes such an attack more expensive. It will require an attacker to invalidate storage separately per account, i.e., many storage updates. So the cost of such an attack is proportional to the potential damage.
Transactions to be included in a block are picked from the available transaction pool. To prevent a DoS attack using the mempool (the Starknet transaction pool) - filling the pool with apriori invalid transactions, we first mandate that a node accepting a transaction simulates locally against the known state before it adds it to the mempool and broadcasts it to other nodes, namely to sequencers.
When a node receives a transaction, it runs the validation function (a function call) by the designated account contract. This is analogous to the initial transaction verification (signature, nonce, etc.) done in the core Ethereum protocol. It allows validation of transaction information, signature, and the ability to pay. A transaction is not considered a failed transaction if it does not pass this validation; it’s simply not accepted in the network.
The simulation should verify that the validation function accesses the state it can access or otherwise doesn’t behave in a way that is not allowed by validation, e.g., running forbidden system calls.
Upon successfully completing the simulation, the transaction can be entered into the pool and propagated in the network.
So far we covered only the basic flow of adding a transaction. This achieves the goal of abstracting the signature. In the next part, we’ll address the topic of fee abstraction and potential extensions.