Kakarot

Efficient & Secure EVM Provability. Everywhere.

  • Start date27 Sep 2024
  • End date25 Oct 2024
  • Total awards$170,000 in USDC
  • Duration28 days

Kakarot audit details

  • Total Prize Pool: $170,000 in USDC
    • HM awards: $93,400 in USDC
    • Z Pool (Zenith side pool): $45,000 in USDC
    • Audit Catalyst awards: $12,500 in USDC
    • Judge awards: $11,200 in USDC
    • Validator awards: $7,400 in USDC
    • Scout awards: $500 in USDC
  • Read our guidelines for more details
  • Starts September 27, 2024 20:00 UTC
  • Ends October 25, 2024 20:00 UTC

Z Pool and Dark Horse Bonus Pool

ℹ️ While there are no QA awards, QA reports are encouraged as a fallback in the event of no valid HMs.

Automated Findings / Publicly Known Issues

Note for C4 wardens: Anything included in this Automated Findings / Publicly Known Issues section is considered a publicly known issue and is ineligible for awards.

• Front-end components
• Infrastructure relating to the project
• Key custody
• The owner of Kakarot has total admin rights to update classes and write values to account storages.
• EVM Gas not being equivalent to Starknet opcode pricing

Any documented difference between Kakarot and Ethereum in the docs: https://docs.kakarot.org/differences

Overview

Kakarot is a provable EVM built on the Cairo ZK-VM, Starkware's Turing complete and efficient ZK-VM.

The natural first use case for Kakarot is to be embedded into Starknet, thus making it a MultiVM environment. Starknet effectively becomes EVM compatible; allowing the use of both CairoVM and EVM for builders & users. Additionally, Kakarot Labs strives to push more innovations to the L2 space, participating in governance initiative such as Rollup Improvement Proposals (RIPs) and Rollcall.

For the Starknet community, Kakarot removes any kind of EVM-compatibility barrier to developers seeking to take advantage of Starknet’s scalability. For the broader Ethereum ecosystem, Kakarot accelerates the adoption of provable compute.

For developers on Starknet, this means being able to use EVM programming languages and tools in addition to existing tools on Starknet, therefore vastly expanding their options. Additionally, developers who want to launch an EVM appchain (a chain tailored to their specific app) with Kakarot benefit from the stack being highly auditable, maintainable, and modular!

For EVM native users, this means both access to faster and cheaper transactions on Starknet, as well as better interoperability with the broader Ethereum ecosystem.

Kakarot is not a rollup

  • Kakarot is not an appchain or a blockchain. Kakarot Labs is deploying an EVM environment on Starknet Mainnet. This EVM environment is fully enshrined & embedded into Starknet L2. Kakarot transactions and blocks are abstractions over Starknet transactions and block under the hood.
  • Kakarot is not an L3, nor an L2. Kakarot Labs is launching an EVM runtime inside Starknet. Long-term, Kakarot Labs will rename the Kakarot initiative on Starknet: "Starknet EVM" to reduce confusion.
  • Kakarot is not privacy preserving. Zero-knowledge technologies can be used for two (non-excluding) purposes, Scaling or Privacy. The underlying proof system used by Cairo is not privacy-preserving.

Kakarot is an implementation of the EVM in Cairo

Under the hood, Kakarot's core EVM is an implementation of the EVM instruction set in Cairo.

Cairo is the first Turing-complete language for creating provable programs for general computation.

Cairo is like any a programming language, but made for writing provable software. It means that whatever is written in Cairo is, by design, zk. Note that we use the term zk here in the sense that execution of provable software is "ZK-verifiable", not that it is privacy preserving, or so-called "zero-knowledge". Using Cairo means that we leverage the power of STARKs without having to think about it, it sort of "comes for free" just by using this language and not another high-level language, such as Rust or Python.

Kakarot on Starknet is composed of three parts: the Core EVM in Cairo, an RPC layer (RPC server and EVM indexer) and an underlying host CairoVM client (e.g. Starknet mainnet).

Code Walkthrough

The Kakarot Labs team gave a code walkthrough, which was recorded and will be published and linked here as soon as it's been edited.

Here's the key takeaways from the chat transcript <-- lots of links in there.

Kakarot zkEVM Audit Catalyst

This project includes an Audit Catalyst prepared by Zellic security researcher fcremo. This is an essential read for accelerating your work as an auditor and using your time most effectively in contributing to the security of the project.

Foundational knowledge

Kakarot zkEVM

Kakarot is a zkEVM written in Cairo0. The project is aiming for deployment on Starknet Mainnet in Q3 2024. However, it is written to be largely agnostic of Starknet-specific characteristics, and should be easily adapted to allow any CairoVM-based ecosystem to run EVM contracts with minimal to no modifications to existing contracts. The design also allows interoperability between contracts running in the zkEVM and native contracts running in the host CairoVM.

Fundamentals and recommended background

Recommended prerequisites for effectively auditing Kakarot include:

Cairo0

Cairo0: not to be confused with the more modern, rust-looking Cairo1, Cairo0 is the language used to develop Kakarot.

It is a relatively low-level language, and due to the atypical constraints posed by the zero-knowledge VM environment in which it runs (the CairoVM), it has several design choices that will be surprising to the uninitiated. Documentation about the language can be found at this link.

At times, you may need to reference the standard library souce code, which can be found in the starkware-libs/cairo-lang Github repo. You may find some concepts to be explained more clearly in the Cairo1 documentation. Be careful, however, as Cairo1 is significantly different from Cairo0.

Starknet

Starknet: some aspects of Kakarot are tied to Starknet implementation details. Useful readings include documentation about the transaction lifecycle, the account abstraction interface, and about system calls.

EVM

EVM: An in-depth understanding of the EVM design is a must. Vast resources are available, including:

Project overview

Kakarot consists of two major logical components: the core contract and the account contract.

Core contract

The core contract handles transaction parsing and implements the interpreter which executes EVM bytecode. Only one instance of this contract is deployed.

Account contract

As the name suggests, the account contract represents EVM accounts, both smart contracts and externally owner accounts (EOAs). Each EVM account is represented by a separate instance of the account contract (or more accurately, by an instance of a proxy contract, see the following section) which stores the state of the account, including the nonce, bytecode, and persistent storage. The account balance is not stored in the account contract, since Kakarot uses a Starknet ERC20 token as its EVM-native currency.

Note that while executing a transaction, information about the state of an account is usually read from the account contract and cached directly by the core contract. The account state is updated by the core contract only when required -- typically when a transaction has finished processing and changes to the account state need to be committed.

Account contract deployment

[^NOTE: some aspects of contract deployment changed since the code revision audited by Zellic. This description tries to match the current behavior]

One of the Kakarot design goals is to guarantee a deterministic Starknet address for each Kakarot account contract not influenced by the implementation of the account contract. This allows to upgrade the account contract implementation without affecting the Starknet address of a Kakarot EVM account, and to derive the Starknet address of an account contract before it is even deployed and/or off-chain.

It also allows the core contract to authenticate the source of a call and determine whether it originates from a legitimate Kakarot account contract.

To achieve this, Kakarot deploys an instance of a simple account proxy contract to represent each EVM account. When called, the proxy contract obtains the class hash of the actual account contract from the Kakarot core contract and performs a library call (essentially the equivalent of EVM delegatecall for Cairo).

The account proxy is always deployed by the core Kakarot contract, setting deploy_from_zero=FALSE. The constructor also receives the EVM address represented by the account contract. Therefore, the Starknet address of an account (proxy) contract depends on the following variables:

  • the class hash of the proxy contract
  • the address of the Kakarot core contract
  • the EVM address represented by the account contract

Transaction flow

Note: important details of the transaction flow changed since the code revision reviewed by Zellic. This includes changes to the account contract entrypoints and the separation of concerns between the core contract and account contract.

The flow of an EVM transaction into Kakarot is deep and could feel overwhelming at first. This section illustrates the execution path of a normal Ethereum transaction. Some simplifications and omissions needed to be made, but it should give you a good idea of the steps that are taken from the very entry point, right down to the EVM interpreter loop.

The journey starts with the account contract representing the EVM account sending the transaction; to be specific, the first entry point into Kakarot is the __default__ function of the proxy account contract. The proxy retrieves from the core Kakarot contract the class hash of the actual account contract implementation, and library calls if (Starknet equivalent of delegatecalling) forwarding the original calldata. This allows to upgrade the implementation of all account contracts at once. The diagram below shows this flow:

In the case of the Kakarot Starknet deployment, the Starknet transaction typically be initiated by a paymaster account, which will fund the Starknet gas required to process the transaction. Note however that anyone can call the account proxy contract to submit an EVM transaction to Kakarot.

The entry point into the account contract is its execute_from_outside function. This function performs several checks, including verification of the transaction signature, ensuring the transaction was signed by the private key associated to the public key represented by the account.

After verifying the transaction signature, the account contract calls the Kakarot core contract eth_rpc module, specifically the eth_send_raw_unsigned_tx function. This function verifies several other properties of the transaction (nonce, chain ID, gas parameters, account balance), and invokes eth_send_transaction.

eth_send_transaction performs another critical check, verifying that the Starknet address of the caller matches the expected Starknet address of the sender of the EVM transaction. This guarantees that the caller is a legitimate Kakarot account contract, and therefore that (modulo critical bugs) the transaction signature was validated correctly.

Execution continues in the Kakarot core eth_call function, which retrieves the bytecode of the contract being called from the corresponding contract account.

Finally, execution reaches the actual virtual machine implementation. The interpreter module execute function initializes all the structures needed to store the execution state (Message, Stack, Memory, State, EVM).

The interpreter loop is implemented using tail-recursion by the run function, and the individual opcodes are handled by the aptly-named exec_opcode.

When execution ends (successfully or not) the state of the accounts involved in the transaction need to be updated. This is mostly handled by a call to Starknet.commit(...), which performs some finalization on the state structures and then updates the state persisted in the account contracts (e.g. updating their nonce or storage), and also performs the actual Starknet ERC20 transfers needed to transfer the native currency used by Kakarot between accounts.

The following diagram summarizes the flow of a transaction from account contract to the interpreter loop and back:

Security considerations

Notable issue classes

Due to its very nature, issues in Kakarot are more likely to have a high impact. The core contract attack surface is inevitably large, as it includes an entire VM implementation and an EVM transaction parser, necessarily exposed to malicious interactions. There are also limited applicable defense-in-depth measures, since the core contract is a central trusted component controlling all the EVM state.

This section aims to give you some hints about potential severe issues that may be found in Kakarot. While we don't suggest to limit your search to these issue classes, most of these areas have caused issues in the past and are still a source of concern.

EVM equivalence issues

Kakarot aims for EVM equivalence -- almost any EVM contract should on Kakarot work as intended without modifications at the source (or even bytecode) level. Excluding some known edge cases, any difference in behavior between Kakarot and geth is likely to be a valid issue. Kakarot is already tested using the Ethereum Foundation testsuite, ensuring instruction semantics match the EVM spec. Despite being testsuite-compliant, Zellic manual review revealed a few edge cases not covered by the EF testsuite (see findings 3.6 and 3.7). Additionally, Kakarot compatibility extends beyond the VM to how Ethereum transactions are parsed. Zellic manual review found several critical issues in transaction parsing.

Differential fuzzing seems a promising approach for finding differences between geth and Kakarot, we encourage you to try!

Arithmetic issues

Cairo0 is a relatively low level language with several really unforgiving aspects. It operates on field elements (felts), which can represent values modulo a 252 bit prime. Arithmetic operations are not checked for overflows or underflows. Since the EVM works with 256 bit integers, Kakarot emulates them by making extensive use of the Uint256 type, which represents a 256 bit number using two 128 bit felts.

The Zellic review found several issues, including:

  • overflows and underflows when operating on two felts (e.g. finding 3.13)
  • overflows when converting between Uint256 and felts (e.g. finding 3.10)
  • unsafe Uint256 construction (using felts greater than 128 bits) (e.g. finding 3.17)

Prover hints without appropriate Cairo constraints

One of the most fundamental concepts in zero knowledge smart contract development is the role of the prover and the verifier. In ZK environments like Starknet, smart contracts define constraints which are -- with a huge simplification -- ultimately translated to mathematical equations. A prover is responsible for providing inputs (proofs) that satisfy these equations. Verifiers maintain the network consensus by checking that the proofs provided by the provers are valid.

Cairo allows to specify hints, snippets of code that help the prover to generate proofs. Hints are not part of the smart contract, and verifiers are completely unaware of their existence. A prover can follow a hint, but is not required to do so. Each hint typically requires appropriate assertions to ensure the prover is not acting maliciously.

Zellic audit discovered several issues in this class (findings 3.2, 3.4, 3.8, 3.20), which were patched by the Kakarot team. They also implemented convenient testing harnesses which allow to replace hints when running the Kakarot testsuite. We encourage you to experiment with it, you may find other similar issues!

Data packing/unpacking, RLP decoding, and transaction parsing

Kakarot typically stores bytestreams as an array of felts, efficiently packing multiple bytes (up to 31) into each felt. This includes the bytecode of a contract, or the bytestream of an incoming transaction. When needed, packed bytes felt arrays are unpacked to a longer array of felts where each element represents a single byte.

Once unpacked, transaction data needs to be decoded from Recursive Length Prefix format (RLP). The Kakarot RLP decoder and the packing/unpacking routines were sources of multiple issues (including 3.2, 3.3, 3.4, 3.12), especially related to length checks. The most common root causes of high severity issues were integer overflows and the lack of input length checks leading to out of bound reads/TOCTOUs.

Once the input bytestream is decoded into a stream of RLP items, their structure needs to be validated to ensure it represents a valid Ethereum transaction. This is another possible source of issues worth focusing on (ref. Zellic issues 3.5 and 3.16).

Links


Scope

Files in scope

Kakarot (Commit: 741a55)

ContractSLOC
src/utils/utils.cairo815
src/utils/eth_transaction.cairo247
src/utils/bytes.cairo237
src/utils/maths.cairo21
src/utils/dict.cairo104
src/utils/signature.cairo29
src/utils/rlp.cairo86
src/utils/uint256.cairo282
src/utils/array.cairo92
src/backend/starknet.cairo233
src/kakarot/storages.cairo35
src/kakarot/instructions/push_operations.cairo32
src/kakarot/instructions/stop_and_math_operations.cairo416
src/kakarot/instructions/environmental_information.cairo380
src/kakarot/instructions/sha3.cairo67
src/kakarot/instructions/logging_operations.cairo59
src/kakarot/instructions/block_information.cairo159
src/kakarot/instructions/duplication_operations.cairo22
src/kakarot/instructions/memory_operations.cairo432
src/kakarot/instructions/system_operations.cairo983
src/kakarot/instructions/exchange_operations.cairo24
src/kakarot/account.cairo602
src/kakarot/gas.cairo143
src/kakarot/errors.cairo750
src/kakarot/kakarot.cairo258
src/kakarot/interpreter.cairo990
src/kakarot/precompiles/blake2f.cairo434
src/kakarot/precompiles/ripemd160.cairo841
src/kakarot/precompiles/precompiles.cairo180
src/kakarot/precompiles/precompiles_helpers.cairo29
src/kakarot/precompiles/kakarot_precompiles.cairo97
src/kakarot/precompiles/ec_recover.cairo95
src/kakarot/precompiles/p256verify.cairo40
src/kakarot/precompiles/sha256.cairo42
src/kakarot/precompiles/identity.cairo22
src/kakarot/events.cairo7
src/kakarot/memory.cairo183
src/kakarot/accounts/uninitialized_account.cairo66
src/kakarot/accounts/account_contract.cairo239
src/kakarot/accounts/library.cairo445
src/kakarot/accounts/model.cairo18
src/kakarot/eth_rpc.cairo218
src/kakarot/state.cairo385
src/kakarot/evm.cairo272
src/kakarot/library.cairo307
src/kakarot/constants.cairo1293
src/kakarot/model.cairo126
src/kakarot/interfaces/interfaces.cairo172
src/kakarot/stack.cairo91
src/kakarot/solidity_contracts/src/CairoPrecompiles/DualVmToken.sol195
src/kakarot/solidity_contracts/src/L1L2Messaging/L1KakarotMessaging.sol34
src/kakarot/solidity_contracts/src/L1L2Messaging/L2KakarotMessaging.sol8
src/kakarot/solidity_contracts/src/L1L2Messaging/AddressAliasHelper.sol14
TOTAL SLOC13351

Kakarot-lib (Commit: c2c7cb4)

ContractSLOC
src/CairoLib.sol124
TOTAL SLOC124

Kakarot-ssj (Commit: 935c223)

Files out of scope

All issues referred here and any files not listed in the scope tables are OOS.

Scoping Q & A

General questions

QuestionAnswer
ERC20 used by the protocolStarknet's ETH token - an ERC20 defined in https://github.com/starknet-io/starkgate-contracts
Test coverageN/A
ERC721 used by the protocolNone
ERC777 used by the protocolNone
ERC1155 used by the protocolNone
Chains the protocol will be deployed onStarknet

ERC20 token behaviors in scope

External integrations (e.g., Uniswap) behavior in scope:

QuestionAnswer
Enabling/disabling fees (e.g. Blur disables/enables fees)No
Pausability (e.g. Uniswap pool gets paused)No
Upgradeability (e.g. Uniswap gets upgraded)No

EIP compliance

None

Additional context

Main invariants

  • Transactions can only be submitted to Kakarot by the Starknet account corresponding to an EVM account

Attack ideas (where to focus for bugs)

• Underconstrained computations

• Overflows / Underflows

• Missing range checks

• DoS attacks

All trusted roles in the protocol

  • Owner of the Kakarot Contract

Describe any novel or unique curve logic or mathematical models implemented in the contracts:

N/A

Running tests

Follow the instructions in the README.md and CONTRIBUTING.md guide for environment setup.

*** Make sure Rust 1.82 Beta release is installed and in use. ***

  • Clone the repo;
git clone --recurse https://github.com/code-423n4/2024-09-kakarot.git

For Kakarot (Commit: 7411a55)

cd 2024-09-kakarot/kakarot
make setup
make install-katana
make build-sol
cp .env.example .env
## this starts the chain instance, open another terminal within the same folder for the next function - make test
make run-katana
## this should be called in another terminal within the same folder 
make test
curl -L https://install.astral.sh | bash
make build
make build-sol
## this starts the chain instance, open another terminal within the same folder for the next function - make test-unit
make run-nodes
## this should be called in another terminal within the same folder
make test-unit

For Kakarot-ssj (Commit: 935c223)

  • Make sure the environment setup is completed as referred here
cd kakarot-ssj
scarb build
scarb test
  • For any Foundry tests, cd to the individual commit folder and;
forge build
forge test

Miscellaneous

Employees of Kakarot and employees' family members are ineligible to participate in this audit.

Code4rena's rules cannot be overridden by the contents of this README. In case of doubt, please check with C4 staff.