Table of contents
Introduction
Cairo provides a built-in hash2 function to calculate the Pedersen hash of two felts. In this challenge, we will recursively compute the hash of an array of felts.
The challenge
ex_hash_chain.cairo
// Task:
// Develop a function that is going to calculate Pedersen hash of an array of felts.
// Cairo's built in hash2 can calculate Pedersen hash on two field elements.
// To calculate hash of an array use hash chain algorithm where hash of [1, 2, 3, 4] is H(H(H(1, 2), 3), 4).
from starkware.cairo.common.cairo_builtins import HashBuiltin
from starkware.cairo.common.hash import hash2
// Computes the Pedersen hash chain on an array of size `arr_len` starting from `arr_ptr`.
func hash_chain{hash_ptr: HashBuiltin*}(arr_ptr: felt*, arr_len: felt) -> (result: felt) {
return (result=1);
}
test_ex_hash_chain.cairo
%lang starknet
from starkware.cairo.common.alloc import alloc
from starkware.cairo.common.cairo_builtins import HashBuiltin
from exercises.programs.ex_hash_chain import hash_chain
@external
func test_hash_chain{pedersen_ptr: HashBuiltin*}() {
alloc_locals;
let (local array: felt*) = alloc();
assert array[0] = 1;
assert array[1] = 4;
let (result) = hash_chain{hash_ptr=pedersen_ptr}(array, 2);
assert 1323616023845704258113538348000047149470450086307731200728039607710316625916 = result;
let (local array: felt*) = alloc();
assert array[0] = 2; // YES
assert array[1] = 100;
assert array[2] = 12;
assert array[3] = 2; // YES
assert array[4] = 422; // YES
assert array[5] = 898;
assert array[6] = 10;
assert array[7] = 31;
assert array[8] = 22;
assert array[9] = 5;
let (result) = hash_chain{hash_ptr=pedersen_ptr}(array, 10);
assert 2185907157710685805186499755291718313025201005027499629005977263373594646427 = result;
return ();
}
Solution
from starkware.cairo.common.cairo_builtins import HashBuiltin
from starkware.cairo.common.hash import hash2
func hash_chain{hash_ptr: HashBuiltin*}(arr_ptr: felt*, arr_len: felt) -> (result: felt) {
if (arr_len == 2) {
return hash2([arr_ptr], [arr_ptr + 1]);
}
let (hash) = hash_chain(arr_ptr, arr_len - 1);
return hash2(hash, [arr_ptr + arr_len - 1]);
}
Explain the solution
For a better explanation, I added another test case
let (local array: felt*) = alloc();
assert array[0] = 1;
assert array[1] = 2;
assert array[2] = 3;
assert array[3] = 4;
let (result) = hash_chain{hash_ptr=pedersen_ptr}(array, 4);
assert 2151680050850558576753658069693146429350618838199373217695410689374331200218 = result;
As usual, we handle the base case for our recursion function:
if (arr_len == 2) {
return hash2([arr_ptr], [arr_ptr + 1]);
}
The base case is to calculate the H(1, 2). Next, we handle the recursive case:
let (hash) = hash_chain(arr_ptr, arr_len - 1);
return hash2(hash, [arr_ptr + arr_len - 1]);
After the arr_len
reaches 2, the call stack starts to offload its box starting from the base case:
arr_len = 2
result = H(1, 2)
--------------------
arr_len = 3
hash = H(1, 2)
result = H(H(1 , 2), 3)
--------------------
arr_len = 4
hash = H(H(1 , 2), 3)
result = H(H(H(1 , 2), 3), 4)
done!
Conclusion
I don't often use recursion in other languages which have loop standards. However, since there is no loop in Cairo, things usually get done by recursion. I am getting used to it.
I hope you found this guide insightful. If you have any questions, feel free to reach out. Happy coding!
Here is the Github repo for the code in this article