Photo by Markus Spiske on Unsplash
Starknet Byte Directory: A Simple Implementation Selector Directory
Table of contents
- Function Selectors: An Overview
- Function Selectors in Solidity (Ethereum)
- Function Selectors in Cairo (Starknet)
- Starknet Byte4Directory Implementation
- Why Cairo Doesn't Support Function Overloading
- Benefits of a Starknet Byte Directory
- Repository and Further Exploration
- Summary Table: Solidity vs. Cairo Function Selectors
- Conclusion
Starknet is a validity rollup (Layer 2) built on Ethereum to help scale the network. Unlike Ethereum, which uses Solidity for smart contract development, Starknet leverages Cairo, a programming language designed to enable provable computation using STARKs (Succinct Transparent Arguments of Knowledge). This article explores the concept of function selectors in Starknet, compares it to Ethereum's approach, and demonstrates a practical implementation of a Starknet byte directory.
Function Selectors: An Overview
What is a Function Selector?
A function selector is a unique identifier that specifies which function in a smart contract should be executed. In Ethereum, this is typically a 4-byte value derived from the function signature. On Starknet, the concept is similar but implemented differently due to Cairo's unique characteristics.
Function Selectors in Solidity (Ethereum)
In Solidity, function selectors are generated using the function signature, which includes the function name and parameter types. The selector is computed as the first 4 bytes of the Keccak-256 hash of the function signature.
Example: Generating a Function Selector in Solidity
function point(uint256 x, uint256 y) external {}
// Generating the function selector:
bytes4(keccak256(abi.encodePacked("point(uint256,uint256)")));
Here, the function signature point(uint256,uint256)
is hashed using Keccak-256, and the first 4 bytes of the hash become the function selector.
Function Selectors in Cairo (Starknet)
Cairo, Starknet's programming language, takes a different approach to function selectors. Unlike Solidity, Cairo does not support function overloading, so the selector is derived solely from the function name without including parameter types.
Generating Function Selectors in Cairo
Starknet provides a utility function called get_selector_from_name
to compute the selector. This function uses a variant of Keccak-256 called starknet_keccak
, which ensures the result fits within a Starknet field element.
Code Example:
pub fn get_selector_from_name(func_name: &str) -> Result<Felt, NonAsciiNameError> {
if func_name == DEFAULT_ENTRY_POINT_NAME || func_name == DEFAULT_L1_ENTRY_POINT_NAME {
Ok(Felt::ZERO)
} else {
let name_bytes = func_name.as_bytes();
if name_bytes.is_ascii() {
Ok(starknet_keccak(name_bytes))
} else {
Err(NonAsciiNameError)
}
}
}
pub fn starknet_keccak(data: &[u8]) -> Felt {
let mut hasher = Keccak256::new();
hasher.update(data);
let mut hash = hasher.finalize();
// Remove the first 6 bits to fit within a Starknet field element
hash[0] &= 0b00000011;
// Convert the hash to a field element
Felt::from_bytes_be(unsafe { &*(hash[..].as_ptr() as *const [u8; 32]) })
}
Key Differences:
No Parameter Types: Cairo selectors are derived only from the function name.
Field Element Compatibility: The
starknet_keccak
function ensures the result fits within a Starknet field element by modifying the first 6 bits of the hash.
Starknet Byte4Directory Implementation
To create a robust database for function selectors in the Starknet ecosystem, we can implement a Starknet Byte4Directory. This directory stores function names, their corresponding selectors, and the first 4 bytes of the selector for easy reference.
Implementation Details
The implementation involves:
Generating the function selector using
get_selector_from_name
.Storing the selector and its metadata in a database.
Providing an API endpoint to submit and retrieve function selectors.
Code Example: /submit
Endpoint
#[post("/submit")]
async fn submit(req_body: web::Json<FunctionNameRequest>) -> impl Responder {
let mut connection = db_connect();
let func_name = req_body.function_name.clone();
// Check if the function name already exists in the database
match selectors::table
.filter(selectors::function_name.eq(&func_name))
.first::<CreateSelector>(&mut connection)
{
Ok(_) => {
// Function name already exists, return an error
return HttpResponse::BadRequest().body("Error: Function name already exists");
}
Err(diesel::result::Error::NotFound) => {
// Function name does not exist, proceed to insert
match get_selector_from_name(&func_name) {
Ok(felt) => {
let felt_hex = format!("{:#x}", felt);
let first_4_bytes = &felt_hex[0..10];
let new_data = CreateSelector {
id: Uuid::new_v4(),
function_name: func_name.clone(),
felt_selector: felt_hex.clone(),
selector: first_4_bytes.to_string(),
};
// Insert the new data into the database
diesel::insert_into(selectors::table)
.values(&new_data)
.execute(&mut connection)
.expect("Error inserting new selector");
HttpResponse::Ok().json(new_data)
}
Err(e) => HttpResponse::BadRequest().body(format!("Error: {:?}", e)),
}
}
Err(e) => {
HttpResponse::InternalServerError().body(format!("Database error: {:?}", e))
}
}
}
How It Works:
The
/submit
endpoint accepts a function name as input.It checks if the function name already exists in the database.
If the name is unique, it generates the selector using
get_selector_from_name
, extracts the first 4 bytes, and stores the data in the database.If the name already exists, it returns an error.
Why Cairo Doesn't Support Function Overloading
Cairo's design prioritizes simplicity and efficiency for STARK proofs. Function overloading, which allows multiple functions with the same name but different parameter types, is not supported because:
Determinism: STARK proofs require deterministic computation, and overloading introduces ambiguity.
Simplicity: Avoiding overloading simplifies the compiler and runtime, making generating and verifying proofs easier.
Benefits of a Starknet Byte Directory
A Starknet byte directory provides several benefits:
Centralized Reference: Developers can quickly look up function selectors for their contracts.
Efficiency: Reduces the need to recompute selectors, saving computational resources.
Repository and Further Exploration
To explore the implementation in detail, check out the repository here
Summary Table: Solidity vs. Cairo Function Selectors
Feature | Solidity (Ethereum) | Cairo (Starknet) |
Selector Generation | bytes4(keccak256(abi.encodePacked(...))) | get_selector_from_name(func_name) |
Includes Parameter Types | Yes | No |
Function Overloading | Supported | Not Supported |
Hash Function | Keccak-256 | Starknet Keccak |
Conclusion
The Starknet byte directory is a tool for managing function selectors in the Starknet ecosystem. The implementation is a starting point for creating a robust function selector directory.
Repositories: