Selaa lähdekoodia

feat(target_chains/starknet): wormhole VAA verification and parsing

Pavel Strakhov 1 vuosi sitten
vanhempi
sitoutus
2d9c6d3028

+ 1 - 0
target_chains/starknet/contracts/.tool-versions

@@ -0,0 +1 @@
+scarb 2.5.4

+ 1 - 0
target_chains/starknet/contracts/src/lib.cairo

@@ -1,2 +1,3 @@
 mod pyth;
 mod wormhole;
+mod reader;

+ 333 - 0
target_chains/starknet/contracts/src/reader.cairo

@@ -0,0 +1,333 @@
+use core::option::OptionTrait;
+use core::array::ArrayTrait;
+use core::keccak::cairo_keccak;
+use core::integer::u128_byte_reverse;
+use core::fmt::{Debug, Formatter};
+
+pub const EOF: felt252 = 'unexpected end of input';
+pub const UNEXPECTED_OVERFLOW: felt252 = 'unexpected overflow';
+
+/// A byte array with storage format similar to `core::ByteArray`, but
+/// suitable for reading data from it.
+#[derive(Drop, Clone, Serde)]
+pub struct ByteArray {
+    // Number of bytes stored in the last item of `self.data` (or 0 if it's empty).
+    num_last_bytes: u8,
+    // Bytes in big endian. Each item except the last one stores 31 bytes.
+    // If `num_last_bytes < 31`, unused most significant bytes of the last item will be unused.
+    data: Array<bytes31>,
+}
+
+impl DebugByteArray of Debug<ByteArray> {
+    fn fmt(self: @ByteArray, ref f: Formatter) -> Result<(), core::fmt::Error> {
+        write!(f, "ByteArray {{ num_last_bytes: {}, data: [", self.num_last_bytes)?;
+        let mut data = self.data.clone();
+        loop {
+            match data.pop_front() {
+                Option::Some(v) => {
+                    let v: u256 = v.into();
+                    write!(f, "{:?}, ", v).unwrap();
+                },
+                Option::None => { break; },
+            }
+        };
+        write!(f, "]}}")
+    }
+}
+
+#[generate_trait]
+pub impl ByteArrayImpl of ByteArrayTrait {
+    /// Creates a byte array with the data.
+    fn new(data: Array<bytes31>, num_last_bytes: u8) -> ByteArray {
+        if data.len() == 0 {
+            assert!(num_last_bytes == 0);
+        } else {
+            assert!(num_last_bytes <= 31);
+        // TODO: check that unused bytes are zeroed.
+        }
+        ByteArray { num_last_bytes, data }
+    }
+
+    /// Removes 31 or less bytes from the start of the array.
+    /// Returns the value and the number of bytes.
+    fn pop_front(ref self: ByteArray) -> Option<(bytes31, u8)> {
+        let item = self.data.pop_front()?;
+        if self.data.is_empty() {
+            let num_bytes = self.num_last_bytes;
+            self.num_last_bytes = 0;
+            Option::Some((item, num_bytes))
+        } else {
+            Option::Some((item, 31))
+        }
+    }
+
+    fn len(self: @ByteArray) -> usize {
+        if self.data.is_empty() {
+            0
+        } else {
+            (self.data.len() - 1) * 31 + (*self.num_last_bytes).into()
+        }
+    }
+}
+
+/// Allows to read data from a byte array.
+/// Uses big endian unless specified otherwise.
+/// All methods return `EOF` error if attempted to
+/// read more bytes than is available.
+#[derive(Drop, Clone)]
+pub struct Reader {
+    // Input array.
+    array: ByteArray,
+    // Current value to read from (in big endian).
+    current: u128,
+    // Number of remaining bytes in `self.current`.
+    num_current_bytes: u8,
+    // Next value to read from (in big endian). This is needed because
+    // `array.pop_front()` returns up to 31 bytes which require two u128 to store.
+    next: Option<u128>,
+}
+
+#[generate_trait]
+pub impl ReaderImpl of ReaderTrait {
+    fn new(array: ByteArray) -> Reader {
+        Reader { array, current: 0, num_current_bytes: 0, next: Option::None }
+    }
+
+    /// Reads the specified number of bytes (up to 16) as a big endian unsigned integer.
+    fn read(ref self: Reader, num_bytes: u8) -> Result<u128, felt252> {
+        if num_bytes <= self.num_current_bytes {
+            let x = self.read_from_current(num_bytes);
+            return Result::Ok(x);
+        }
+        let num_low_bytes = num_bytes - self.num_current_bytes;
+        let high = self.current;
+        self.fetch_next()?;
+        let low = self.read(num_low_bytes)?;
+        let value = if num_low_bytes == 16 {
+            low
+        } else {
+            high * one_shift_left_bytes_u128(num_low_bytes) + low
+        };
+        Result::Ok(value)
+    }
+
+    fn read_u256(ref self: Reader) -> Result<u256, felt252> {
+        let high = self.read(16)?;
+        let low = self.read(16)?;
+        let value = u256 { high, low };
+        Result::Ok(value)
+    }
+    fn read_u128(ref self: Reader) -> Result<u128, felt252> {
+        self.read(16)
+    }
+    fn read_u64(ref self: Reader) -> Result<u64, felt252> {
+        let value = self.read(8)?.try_into().expect(UNEXPECTED_OVERFLOW);
+        Result::Ok(value)
+    }
+    fn read_u32(ref self: Reader) -> Result<u32, felt252> {
+        let value = self.read(4)?.try_into().expect(UNEXPECTED_OVERFLOW);
+        Result::Ok(value)
+    }
+    fn read_u16(ref self: Reader) -> Result<u16, felt252> {
+        let value = self.read(2)?.try_into().expect(UNEXPECTED_OVERFLOW);
+        Result::Ok(value)
+    }
+    fn read_u8(ref self: Reader) -> Result<u8, felt252> {
+        let value = self.read(1)?.try_into().expect(UNEXPECTED_OVERFLOW);
+        Result::Ok(value)
+    }
+
+    // TODO: skip without calculating values
+    fn skip(ref self: Reader, mut num_bytes: u8) -> Result<(), felt252> {
+        let mut result = Result::Ok(());
+        while num_bytes > 0 {
+            if num_bytes > 16 {
+                match self.read(16) {
+                    Result::Ok(_) => {},
+                    Result::Err(err) => {
+                        result = Result::Err(err);
+                        break;
+                    }
+                }
+                num_bytes -= 16;
+            } else {
+                match self.read(num_bytes) {
+                    Result::Ok(_) => {},
+                    Result::Err(err) => {
+                        result = Result::Err(err);
+                        break;
+                    }
+                }
+                break;
+            }
+        };
+        result
+    }
+
+    /// Reads the specified number of bytes as a new byte array.
+    fn read_bytes(ref self: Reader, num_bytes: usize) -> Result<ByteArray, felt252> {
+        let mut array: Array<bytes31> = array![];
+        let mut num_last_bytes = Option::None;
+        let mut num_remaining_bytes = num_bytes;
+        loop {
+            let r = self.read_bytes_iteration(num_remaining_bytes, ref array);
+            match r {
+                Result::Ok((
+                    num_read, eof
+                )) => {
+                    num_remaining_bytes -= num_read;
+                    if eof {
+                        num_last_bytes = Option::Some(Result::Ok(num_read));
+                        break;
+                    }
+                },
+                Result::Err(err) => {
+                    num_last_bytes = Option::Some(Result::Err(err));
+                    break;
+                }
+            }
+        };
+        // `num_last_bytes` is always set to Some before break.
+        let num_last_bytes = num_last_bytes.unwrap()?;
+        // num_last_bytes < 31
+        let num_last_bytes = num_last_bytes.try_into().expect(UNEXPECTED_OVERFLOW);
+        let array = ByteArrayImpl::new(array, num_last_bytes);
+        Result::Ok(array)
+    }
+
+    /// Returns number of remaining bytes to read.
+    fn len(ref self: Reader) -> usize {
+        let num_next_bytes = if self.next.is_some() {
+            16
+        } else {
+            0
+        };
+        self.num_current_bytes.into() + num_next_bytes + self.array.len()
+    }
+
+    /// Reads the specified number of bytes (up to 16) as a little endian unsigned integer.
+    fn read_le(ref self: Reader, num_bytes: u8) -> Result<u128, felt252> {
+        if num_bytes == 0 {
+            return Result::Ok(0);
+        }
+        let value = u128_byte_reverse(self.read(num_bytes)?)
+            / one_shift_left_bytes_u128(16 - num_bytes);
+        Result::Ok(value)
+    }
+
+    /// Reads and hashes all the remaining data.
+    fn keccak256(ref self: Reader) -> Result<u256, felt252> {
+        let mut data: Array<u64> = array![];
+
+        let mut result = Result::Ok(());
+        while self.len() >= 8 {
+            match self.read_le(8) {
+                Result::Ok(value) => { data.append(value.try_into().expect(UNEXPECTED_OVERFLOW)); },
+                Result::Err(err) => {
+                    result = Result::Err(err);
+                    break;
+                },
+            }
+        };
+        result?;
+
+        let last_len = self.len();
+        // last_len < 8
+        let last = self.read_le(last_len.try_into().expect(UNEXPECTED_OVERFLOW))?;
+        let last = last.try_into().expect(UNEXPECTED_OVERFLOW);
+        let hash = cairo_keccak(ref data, last, last_len);
+        Result::Ok(hash)
+    }
+}
+
+#[generate_trait]
+impl ReaderPrivateImpl of ReaderPrivateTrait {
+    /// Reads the specified number of bytes from `self.current`.
+    /// Panics if attempted to read more than `self.num_current_bytes`.
+    fn read_from_current(ref self: Reader, num_bytes: u8) -> u128 {
+        let num_remaining_bytes = self.num_current_bytes - num_bytes;
+        let divisor = one_shift_left_bytes_u128(num_remaining_bytes);
+        // divisor != 0
+        let (high, low) = DivRem::div_rem(self.current, divisor.try_into().unwrap());
+        self.current = low;
+        self.num_current_bytes = num_remaining_bytes;
+        high
+    }
+
+    /// Replenishes `self.current` and `self.num_current_bytes`.
+    /// This should only be called when all bytes from `self.current` has been read.
+    /// Returns `EOF` error if no more data is available.
+    fn fetch_next(ref self: Reader) -> Result<(), felt252> {
+        match self.next {
+            Option::Some(next) => {
+                self.next = Option::None;
+                self.current = next;
+                self.num_current_bytes = 16;
+            },
+            Option::None => {
+                let (value, bytes) = self.array.pop_front().ok_or(EOF)?;
+                let value: u256 = value.into();
+                if bytes > 16 {
+                    self.current = value.high;
+                    self.next = Option::Some(value.low);
+                    self.num_current_bytes = bytes - 16;
+                } else {
+                    self.current = value.low;
+                    self.num_current_bytes = bytes;
+                }
+            },
+        }
+        Result::Ok(())
+    }
+
+    // Moved out from `read_bytes` because we cannot use `return` or `?` within a loop.
+    fn read_bytes_iteration(
+        ref self: Reader, num_bytes: usize, ref array: Array<bytes31>
+    ) -> Result<(usize, bool), felt252> {
+        if num_bytes >= 31 {
+            let high = self.read(15)?;
+            let low = self.read(16)?;
+            let value: felt252 = u256 { high, low }.try_into().expect(UNEXPECTED_OVERFLOW);
+            array.append(value.try_into().expect(UNEXPECTED_OVERFLOW));
+            Result::Ok((31, false))
+        } else if num_bytes > 16 {
+            // num_bytes < 31
+            let high = self.read((num_bytes - 16).try_into().expect(UNEXPECTED_OVERFLOW))?;
+            let low = self.read(16)?;
+            let value: felt252 = u256 { high, low }.try_into().expect(UNEXPECTED_OVERFLOW);
+            array.append(value.try_into().expect(UNEXPECTED_OVERFLOW));
+            Result::Ok((num_bytes, true))
+        } else {
+            // bytes < 16
+            let low = self.read(num_bytes.try_into().expect(UNEXPECTED_OVERFLOW))?;
+            let value: felt252 = low.try_into().expect(UNEXPECTED_OVERFLOW);
+            array.append(value.try_into().expect(UNEXPECTED_OVERFLOW));
+            Result::Ok((num_bytes, true))
+        }
+    }
+}
+
+// Returns 1 << (8 * `n_bytes`) as u128, where `n_bytes` must be < BYTES_IN_U128.
+//
+// Panics if `n_bytes >= 16`.
+fn one_shift_left_bytes_u128(n_bytes: u8) -> u128 {
+    match n_bytes {
+        0 => 0x1,
+        1 => 0x100,
+        2 => 0x10000,
+        3 => 0x1000000,
+        4 => 0x100000000,
+        5 => 0x10000000000,
+        6 => 0x1000000000000,
+        7 => 0x100000000000000,
+        8 => 0x10000000000000000,
+        9 => 0x1000000000000000000,
+        10 => 0x100000000000000000000,
+        11 => 0x10000000000000000000000,
+        12 => 0x1000000000000000000000000,
+        13 => 0x100000000000000000000000000,
+        14 => 0x10000000000000000000000000000,
+        15 => 0x1000000000000000000000000000000,
+        _ => core::panic_with_felt252('n_bytes too big'),
+    }
+}

+ 304 - 11
target_chains/starknet/contracts/src/wormhole.cairo

@@ -1,29 +1,322 @@
+use pyth::reader::ByteArray;
+use core::starknet::secp256_trait::Signature;
+
 #[starknet::interface]
-trait IWormhole<T> {
-    fn get_value(self: @T) -> felt252;
-    fn set_value(ref self: T, name: felt252);
+pub trait IWormhole<T> {
+    fn submit_new_guardian_set(ref self: T, set_index: u32, guardians: Array<felt252>);
+    fn parse_and_verify_vm(ref self: T, encoded_vm: ByteArray) -> Result<VM, felt252>;
+}
+
+#[derive(Drop, Debug, Clone, Serde)]
+pub struct GuardianSignature {
+    pub guardian_index: u8,
+    pub signature: Signature,
+}
+
+#[derive(Drop, Debug, Clone, Serde)]
+pub struct VM {
+    pub version: u8,
+    pub guardian_set_index: u32,
+    pub signatures: Array<GuardianSignature>,
+    pub timestamp: u32,
+    pub nonce: u32,
+    pub emitter_chain_id: u16,
+    pub emitter_address: u256,
+    pub sequence: u64,
+    pub consistency_level: u8,
+    pub payload: ByteArray,
+}
+
+pub mod error_codes {
+    pub const NO_GUARDIANS_SPECIFIED: felt252 = 'no guardians specified';
+    pub const TOO_MANY_GUARDIANS: felt252 = 'too many guardians';
+    pub const INVALID_GUARDIAN_KEY: felt252 = 'invalid guardian key';
+    // guardian set index must increase in steps of 1
+    pub const INVALID_GUARDIAN_SET_SEQUENCE: felt252 = 'invalid guardian set sequence';
+    pub const ACCESS_DENIED: felt252 = 'access denied';
+
+    pub const VM_VERSION_INCOMPATIBLE: felt252 = 'VM version incompatible';
+    pub const INVALID_GUARDIAN_SET_INDEX: felt252 = 'invalid guardian set index';
+    pub const INVALID_SIGNATURE: felt252 = 'invalid signature';
+    pub const GUARDIAN_SET_EXPIRED: felt252 = 'guardian set expired';
+    pub const NO_QUORUM: felt252 = 'no quorum';
+    pub const INVALID_SIGNATURE_ORDER: felt252 = 'invalid signature order';
+    pub const INVALID_GUARDIAN_INDEX: felt252 = 'invalid guardian index';
+}
+
+pub fn quorum(num_guardians: usize) -> usize {
+    assert(num_guardians < 256, error_codes::TOO_MANY_GUARDIANS);
+    ((num_guardians * 2) / 3) + 1
 }
 
 #[starknet::contract]
 mod wormhole {
+    use core::box::BoxTrait;
+    use core::array::ArrayTrait;
+    use super::{VM, IWormhole, GuardianSignature, error_codes, quorum};
+    use pyth::reader::{Reader, ReaderImpl, ByteArray, UNEXPECTED_OVERFLOW};
+    use core::starknet::secp256_trait::{Signature, recover_public_key, Secp256PointTrait};
+    use core::starknet::secp256k1::Secp256k1Point;
+    use core::starknet::{
+        ContractAddress, get_execution_info, get_caller_address, get_block_timestamp
+    };
+    use core::keccak::cairo_keccak;
+    use core::integer::u128_byte_reverse;
+    use core::panic_with_felt252;
+
+    #[derive(Drop, Debug, Clone, Serde, starknet::Store)]
+    struct GuardianSet {
+        num_guardians: usize,
+        // XXX: storage doesn't work if we use Option here.
+        expiration_time: u64,
+    }
+
     #[storage]
     struct Storage {
-        name: felt252,
+        owner: ContractAddress,
+        current_guardian_set_index: u32,
+        guardian_sets: LegacyMap<u32, GuardianSet>,
+        // (guardian_set_index, guardian_index) => guardian_address
+        guardian_keys: LegacyMap<(u32, u8), u256>,
     }
 
     #[constructor]
-    fn constructor(ref self: ContractState, name: felt252) {
-        self.name.write(name);
+    fn constructor(
+        ref self: ContractState, owner: ContractAddress, initial_guardians: Array<felt252>
+    ) {
+        self.owner.write(owner);
+        let set_index = 0;
+        store_guardian_set(ref self, set_index, initial_guardians);
+    }
+
+    fn store_guardian_set(ref self: ContractState, set_index: u32, guardians: Array<felt252>) {
+        assert(guardians.len() > 0, error_codes::NO_GUARDIANS_SPECIFIED);
+        assert(guardians.len() < 256, error_codes::TOO_MANY_GUARDIANS);
+        let set = GuardianSet { num_guardians: guardians.len(), expiration_time: 0 };
+        self.guardian_sets.write(set_index, set);
+        let mut i = 0;
+        while i < guardians.len() {
+            let key = *guardians.at(i);
+            assert(key != 0, error_codes::INVALID_GUARDIAN_KEY);
+            // i < 256
+            self
+                .guardian_keys
+                .write((set_index, i.try_into().expect(UNEXPECTED_OVERFLOW)), key.into());
+            i += 1;
+        };
+        self.current_guardian_set_index.write(set_index);
+    }
+
+    fn expire_guardian_set(ref self: ContractState, set_index: u32, now: u64) {
+        let mut set = self.guardian_sets.read(set_index);
+        set.expiration_time = now + 86400;
+        self.guardian_sets.write(set_index, set);
     }
 
     #[abi(embed_v0)]
-    impl HelloImpl of super::IWormhole<ContractState> {
-        fn get_value(self: @ContractState) -> felt252 {
-            self.name.read() + 2
+    impl WormholeImpl of IWormhole<ContractState> {
+        fn submit_new_guardian_set(
+            ref self: ContractState, set_index: u32, guardians: Array<felt252>
+        ) {
+            let execution_info = get_execution_info().unbox();
+            assert(self.owner.read() == execution_info.caller_address, error_codes::ACCESS_DENIED);
+
+            let current_set_index = self.current_guardian_set_index.read();
+            assert(set_index == current_set_index + 1, error_codes::INVALID_GUARDIAN_SET_SEQUENCE);
+            expire_guardian_set(
+                ref self, current_set_index, execution_info.block_info.unbox().block_timestamp
+            );
+            store_guardian_set(ref self, set_index, guardians);
+        }
+
+        fn parse_and_verify_vm(
+            ref self: ContractState, encoded_vm: ByteArray
+        ) -> Result<VM, felt252> {
+            let (vm, body_hash) = parse_vm(encoded_vm)?;
+            let guardian_set = self.guardian_sets.read(vm.guardian_set_index);
+            if guardian_set.num_guardians == 0 {
+                return Result::Err(error_codes::INVALID_GUARDIAN_SET_INDEX);
+            }
+            if vm.guardian_set_index != self.current_guardian_set_index.read()
+                && guardian_set.expiration_time < get_block_timestamp() {
+                return Result::Err(error_codes::GUARDIAN_SET_EXPIRED);
+            }
+            if vm.signatures.len() < quorum(guardian_set.num_guardians) {
+                return Result::Err(error_codes::NO_QUORUM);
+            }
+            let mut signatures_clone = vm.signatures.clone();
+            let mut last_index = Option::None;
+
+            let mut result = Result::Ok(());
+            loop {
+                let signature = match signatures_clone.pop_front() {
+                    Option::Some(v) => { v },
+                    Option::None => { break; },
+                };
+
+                match last_index {
+                    Option::Some(last_index) => {
+                        if *(@signature).guardian_index <= last_index {
+                            result = Result::Err(error_codes::INVALID_SIGNATURE_ORDER);
+                            break;
+                        }
+                    },
+                    Option::None => {},
+                };
+                last_index = Option::Some(*(@signature).guardian_index);
+
+                if signature.guardian_index.into() >= guardian_set.num_guardians {
+                    result = Result::Err(error_codes::INVALID_GUARDIAN_INDEX);
+                    break;
+                }
+
+                let guardian_key = self
+                    .guardian_keys
+                    .read((vm.guardian_set_index, signature.guardian_index));
+
+                let r = verify_signature(body_hash, signature.signature, guardian_key);
+                if r.is_err() {
+                    result = r;
+                    break;
+                }
+            };
+            result?;
+
+            Result::Ok(vm)
         }
+    }
+
+    fn parse_signature(ref reader: Reader) -> Result<GuardianSignature, felt252> {
+        let guardian_index = reader.read_u8()?;
+        let r = reader.read_u256()?;
+        let s = reader.read_u256()?;
+        let recovery_id = reader.read_u8()?;
+        let y_parity = (recovery_id % 2) > 0;
+        let signature = GuardianSignature {
+            guardian_index, signature: Signature { r, s, y_parity }
+        };
+        Result::Ok(signature)
+    }
+
+    fn parse_vm(encoded_vm: ByteArray) -> Result<(VM, u256), felt252> {
+        let mut reader = ReaderImpl::new(encoded_vm);
+        let version = reader.read_u8()?;
+        if version != 1 {
+            return Result::Err(error_codes::VM_VERSION_INCOMPATIBLE);
+        }
+        let guardian_set_index = reader.read_u32()?;
+
+        let sig_count = reader.read_u8()?;
+        let mut i = 0;
+        let mut signatures = array![];
 
-        fn set_value(ref self: ContractState, name: felt252) {
-            self.name.write(name - 2);
+        let mut result = Result::Ok(());
+        while i < sig_count {
+            match parse_signature(ref reader) {
+                Result::Ok(signature) => { signatures.append(signature); },
+                Result::Err(err) => {
+                    result = Result::Err(err);
+                    break;
+                },
+            }
+            i += 1;
+        };
+        result?;
+
+        let mut reader_for_hash = reader.clone();
+        let body_hash1_le = reader_for_hash.keccak256()?;
+        let mut body_hash1_le_u64s = split_hash(body_hash1_le);
+        let body_hash2_le = cairo_keccak(ref body_hash1_le_u64s, 0, 0);
+        let body_hash2 = u256 {
+            low: u128_byte_reverse(body_hash2_le.high), high: u128_byte_reverse(body_hash2_le.low),
+        };
+
+        let timestamp = reader.read_u32()?;
+        let nonce = reader.read_u32()?;
+        let emitter_chain_id = reader.read_u16()?;
+        let emitter_address = reader.read_u256()?;
+        let sequence = reader.read_u64()?;
+        let consistency_level = reader.read_u8()?;
+        let payload_len = reader.len();
+        let payload = reader.read_bytes(payload_len)?;
+
+        let vm = VM {
+            version,
+            guardian_set_index,
+            signatures,
+            timestamp,
+            nonce,
+            emitter_chain_id,
+            emitter_address,
+            sequence,
+            consistency_level,
+            payload,
+        };
+        Result::Ok((vm, body_hash2))
+    }
+
+    fn verify_signature(
+        body_hash: u256, signature: Signature, guardian_key: u256,
+    ) -> Result<(), felt252> {
+        let point: Secp256k1Point = recover_public_key(body_hash, signature)
+            .ok_or(error_codes::INVALID_SIGNATURE)?;
+        let address = eth_address(point)?;
+        if guardian_key == 0 {
+            return Result::Err(error_codes::INVALID_GUARDIAN_KEY);
+        }
+        if address != guardian_key {
+            return Result::Err(error_codes::INVALID_SIGNATURE);
         }
+        Result::Ok(())
+    }
+
+    const ONE_SHIFT_64: u256 = 0x10000000000000000;
+    const ONE_SHIFT_160: u256 = 0x10000000000000000000000000000000000000000;
+
+    fn eth_address(point: Secp256k1Point) -> Result<u256, felt252> {
+        let (x, y) = match point.get_coordinates() {
+            Result::Ok(v) => { v },
+            Result::Err(_) => { return Result::Err(error_codes::INVALID_SIGNATURE); },
+        };
+
+        let mut array = array![];
+        push_reversed(ref array, x);
+        push_reversed(ref array, y);
+        let key_hash = cairo_keccak(ref array, 0, 0);
+        let reversed_key_hash = u256 {
+            low: u128_byte_reverse(key_hash.high), high: u128_byte_reverse(key_hash.low)
+        };
+        Result::Ok(reversed_key_hash % ONE_SHIFT_160)
+    }
+
+    fn split_hash(val: u256) -> Array<u64> {
+        let divisor = ONE_SHIFT_64.try_into().expect('not zero');
+        let (val, v1) = DivRem::div_rem(val, divisor);
+        let (val, v2) = DivRem::div_rem(val, divisor);
+        let (val, v3) = DivRem::div_rem(val, divisor);
+
+        array![
+            v1.try_into().expect(UNEXPECTED_OVERFLOW),
+            v2.try_into().expect(UNEXPECTED_OVERFLOW),
+            v3.try_into().expect(UNEXPECTED_OVERFLOW),
+            val.try_into().expect(UNEXPECTED_OVERFLOW),
+        ]
+    }
+
+    fn push_reversed(ref array: Array<u64>, val: u256) {
+        let divisor = ONE_SHIFT_64.try_into().expect('not zero');
+        let (val, v1) = DivRem::div_rem(val, divisor);
+        let (val, v2) = DivRem::div_rem(val, divisor);
+        let (val, v3) = DivRem::div_rem(val, divisor);
+
+        array.append(u64_byte_reverse(val.try_into().expect(UNEXPECTED_OVERFLOW)));
+        array.append(u64_byte_reverse(v3.try_into().expect(UNEXPECTED_OVERFLOW)));
+        array.append(u64_byte_reverse(v2.try_into().expect(UNEXPECTED_OVERFLOW)));
+        array.append(u64_byte_reverse(v1.try_into().expect(UNEXPECTED_OVERFLOW)));
+    }
+
+    fn u64_byte_reverse(value: u128) -> u64 {
+        let reversed = u128_byte_reverse(value) / ONE_SHIFT_64.try_into().expect('not zero');
+        reversed.try_into().expect(UNEXPECTED_OVERFLOW)
     }
 }