- 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
- This audit includes two Zenith Researchers (ZRs), who are designated as leads for the audit ("LZRs").
- Dark Horse wardens earn a portion of the Z pool by outperforming (or tying) the top-ranked LZR auditor based on Gatherer score.
- For more details, see Z Pool / Dark Horse bonus pool distribution rules
ℹ️ While there are no QA awards, QA reports are encouraged as a fallback in the event of no valid HMs.
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
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 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.
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).
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.
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.
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.
Recommended prerequisites for effectively auditing Kakarot include:
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: 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: An in-depth understanding of the EVM design is a must. Vast resources are available, including:
- the Ethereum Yellowpaper, which originally defined the EVM
- evm.codes, a handy reference for looking up instruction semantics
- the go-ethereum and py-evm implementations, officially sanctioned by the Ethereum Foundation
Kakarot consists of two major logical components: the core contract and the account contract.
The core contract handles transaction parsing and implements the interpreter which executes EVM bytecode. Only one instance of this contract is deployed.
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.
[^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
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 delegatecall
ing) 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:
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.
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!
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)
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!
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).
- Previous audits: Zellic Audit Report
- Documentation: https://docs.kakarot.org/
- Website: https://www.kakarot.org/
- X/Twitter: https://x.com/KakarotZkEvm
- Discord: https://discord.com/invite/kakarotzkevm
Contract | SLOC |
---|---|
src/CairoLib.sol | 124 |
TOTAL SLOC | 124 |
Contract | SLOC |
---|---|
crates/contracts/src/cairo1_helpers.cairo | 134 |
crates/evm/src/errors.cairo | 86 |
crates/evm/src/precompiles/ec_operations/ec_add.cairo | 142 |
crates/evm/src/precompiles/ec_operations/ec_mul.cairo | 115 |
crates/evm/src/precompiles/ec_operations.cairo | 72 |
crates/evm/src/precompiles/sha256.cairo | 141 |
crates/utils/src/helpers.cairo (Only fn load_word in scope) |
230 |
crates/utils/src/traits/bytes.cairo (Only trait ToBytes<T> , trait FromBytes , fn pad_right_with_zeroes in scope) |
552 |
crates/utils/src/math.cairo (Only trait Bitshift , trait Exponentiation in scope) |
451 |
TOTAL SLOC | 1923 |
All issues referred here and any files not listed in the scope tables are OOS.
Question | Answer |
---|---|
ERC20 used by the protocol | Starknet's ETH token - an ERC20 defined in https://github.com/starknet-io/starkgate-contracts |
Test coverage | N/A |
ERC721 used by the protocol | None |
ERC777 used by the protocol | None |
ERC1155 used by the protocol | None |
Chains the protocol will be deployed on | Starknet |
Question | Answer |
---|---|
Missing return values | In scope |
Fee on transfer | Out of scope |
Balance changes outside of transfers | Out of scope |
Upgradeability | In scope |
Flash minting | Out of scope |
Pausability | In scope |
Approval race protections | Out of scope |
Revert on approval to zero address | In scope |
Revert on zero value approvals | In scope |
Revert on zero value transfers | In scope |
Revert on transfer to the zero address | In scope |
Revert on large approvals and/or transfers | In scope |
Doesn't revert on failure | In scope |
Multiple token addresses | Out of scope |
Low decimals ( < 6) | Out of scope |
High decimals ( > 18) | Out of scope |
Blocklists | In scope |
Question | Answer |
---|---|
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 |
None
- Transactions can only be submitted to Kakarot by the Starknet account corresponding to an EVM account
• Underconstrained computations
• Overflows / Underflows
• Missing range checks
• DoS attacks
- Owner of the Kakarot Contract
N/A
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
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
- 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
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.