Notes:
We decided to change the way the scope, context, and label are computed. The main reason is that those values are coputed in solidity hashing with a keccak256. This hash is not implemented in cairo, the implementation of non-native hashes is really expensive.
These hashes are not computed inside of the circuits so we can use another hash to compute them. We chose the poseidon hash implemented on starknet with the function core::poseidon::poseidon_hash_span.
The scope is computed in solidity as
SCOPE = uint256(keccak256(abi.encodePacked(address(this), block.chainid, _asset))) % Constants.SNARK_SCALAR_FIELD;We now compute it as follows,
fn compute_scope(self: @ContractState, asset: ContractAddress) -> u256 {
let CHAIN_ID = get_tx_info().unbox().chain_id;
let span: Span<felt252> = array![get_contract_address().into(), CHAIN_ID, asset.into()].span();
let scope: u256 = poseidon_hash_span(span).into();
return scope;
}In typescript this can be implemented with,
import { poseidonHashMany } from "@scure/starknet"
scope = poseidonHashMany([poolAddress, CHAIN_ID, ASSET])To test the implementation we can use
poolAddress: 0x123456;
CHAIN_ID: 0x534e5f5345504f4c4941; //sepolia
ASSET:: 0x4718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d; //STRK_ADDRESS
SCOPE: 694951935208482397787848015954714431929730211336803088452174886917016209636
Label is computed in solidity like,
LABEL = uint256(keccak256(abi.encodePacked(SCOPE, ++nonce))) % Constants.SNARK_SCALAR_FIELD;fn compute_label(ref self: ContractState) -> u256 {
let scope = self.scope.read();
let nonce = self.nonce.read();
let span = array![nonce.into(),scope.low.into(), scope.high.into()].span();
let label: u256 = poseidon_hash_span(span).into();
return label;
}import { poseidonHashMany } from "@scure/starknet"
label = poseidonHashMany([nonce, scope.low,scope.high])CONTEXT = uint256(keccak256(abi.encode(_withdrawal, SCOPE))) % Constants.SNARK_SCALAR_FIELD)In cairo we compute the context with a method of the Withdrawal struct,
fn compute_context(self: @Withdrawal, scope: u256) -> u256 {
let mut temp = array![];
Serde::serialize(self, ref temp);
temp.append(scope.low.into());
temp.append(scope.high.into());
let context = poseidon_hash_span(temp.span()).into()
return context;
}The Withdrawal struct is
pub struct Withdrawal {
processor: ContractAddress,
data: Span<felt252>,
}At low level, the span to hash consists of the serialization of the struct with the scope
span = [processor, data.len, ...data...,scope.low,scope.high]
import { poseidonHashMany } from "@scure/starknet"
context = poseidonHashMany([
processor,
data.len,
...data...,
scope.low,
scope.high,
])##CHAIN ID The chain_id of starknet
sepolia_id = 0x534e5f5345504f4c4941
mainet_id = 0x534e5f4d41494e