StarkNet Account Abstraction Model - Part 1

Overview

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.

Goals

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:

  1. Signature abstraction: allow different account contracts to use different signature validation schemes.
  2. 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.

Model

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 validate and 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

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.

Nonces

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.

A Simple Transaction Flow

We now describe a basic transaction flow, as depicted in the following diagram:

Simple Txn Flow

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.

Adding Transaction to a Block

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 - validate and 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:

  1. The number of steps in the validate function is limited.
  2. The validate function 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.

Simulation

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.

txn simulation

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.

14 Likes

Sounds great. As for the note about " The validate function cannot access the storage of other contracts (outside the account contract)" - what about other type of non-interactive invalidation, such as dependency on certain block number? So an attacker inputs a million valid transactions for validation, and the transactions get invalid only at certain block, before they are executed? Or are such transactions still considered ‘valid’?

This might be addressed in the next post you alluded to, but I’m curious about 2 things regarding TX cost:

  1. Does executing validate count towards TX fees?

  2. How does the OS know the TX cost before it executes it (the “Pay for txn” step is before the “Execute txn” step)?

1 Like

What do you mean by “Add txn failure to the batch”? Aren’t “failed” transactions unprovable?

We would like to avoid a situation where a transaction’s validity is defined by “outside” information; i.e. not intrinsic to the account or the transaction itself.

A block number is an example of such external information. So validate should not be able to rely on it.
An account contract is therefore limited also in the system calls it can make, one of which is getting the block number.

The whole topic of fees and the exact mechanism is still tbd.

In the current mechanism (being implemented for StarkNet Alpha), the main driver of cost is the information sent to L1, and the fee paid is sent as part of the transaction. So it’s less of an issue.

This assumes we can, as mentioned here.

Even if we don’t we’ll need to have some (proven) operation that is implied by the failure, e.g. the red fee.

1 Like

It is mentioned above that the validation function implemented by an account contract will be limited to some number of steps but couldnt find any exact description of this - in what way is this function limited ?

For instance, what if we have an account contract that implements IAccount interface as well as IERC721 interface - then we could issue an NFT to the public key initialising the account contract and is_valid_signature could check whether the signing public key is the owner of the said NFT which was minted on initialization. This could turn an account into a more general blockchain primitive where account ownership could be transferred independent of the wallet perhaps - and also transfer of ownership of an account could become as simple as transferring the NFT (transferring ownership could also be accomplished by simply changing the stored public key in the account contract but just trying to understand whether this is feasible). Is this right ? or have I misunderstood this concept

I guess you could do something like that.
But do you see here a problem with the validate function limited in steps?

No problem with limiting validation steps, infact I think having this constraint is actually a good decision. I went ahead and hacked a simple version of a token gated account here. I have another question though. How do you intend to collect the fees - like is the sequencer going to call some function in the Account contract to collect the fees (I am assuming its going to be an ERC20) ? I was thinking of an implementation of sponsored accounts and knowing the fee collection mechanism would help a lot.