Преглед на файлове

feat(target_chains/starknet): add multi-purpose keccak hasher

Pavel Strakhov преди 1 година
родител
ревизия
30c741ed49

+ 122 - 0
target_chains/starknet/contracts/src/hash.cairo

@@ -0,0 +1,122 @@
+use super::reader::{Reader, ReaderImpl};
+use core::cmp::min;
+use core::integer::u128_byte_reverse;
+use pyth::util::{
+    ONE_SHIFT_160, UNEXPECTED_OVERFLOW, UNEXPECTED_ZERO, ONE_SHIFT_64, one_shift_left_bytes_u64,
+    u64_byte_reverse,
+};
+
+/// Allows to push data as big endian to a buffer and apply
+/// the keccak256 hash.
+#[derive(Drop, Debug)]
+pub struct Hasher {
+    // Inputs in little endian.
+    inputs_le: Array<u64>,
+    // Last pushed bytes in big endian.
+    last_be: u64,
+    // Number of filled bytes in `self.last_be`.
+    num_last_bytes: u8,
+}
+
+#[generate_trait]
+pub impl HasherImpl of HasherTrait {
+    /// Creates an empty hasher.
+    fn new() -> Hasher {
+        Hasher { inputs_le: array![], last_be: 0, num_last_bytes: 0 }
+    }
+
+    fn push_u8(ref self: Hasher, value: u8) {
+        self.push_to_last(value.into(), 1);
+    }
+    fn push_u16(ref self: Hasher, value: u16) {
+        self.push_num_bytes(value.into(), 2);
+    }
+    fn push_u32(ref self: Hasher, value: u32) {
+        self.push_num_bytes(value.into(), 4);
+    }
+    fn push_u64(ref self: Hasher, value: u64) {
+        self.push_num_bytes(value, 8);
+    }
+    fn push_u128(ref self: Hasher, value: u128) {
+        let divisor = ONE_SHIFT_64.try_into().expect(UNEXPECTED_ZERO);
+        let (high, low) = DivRem::div_rem(value, divisor);
+        self.push_u64(high.try_into().expect(UNEXPECTED_OVERFLOW));
+        self.push_u64(low.try_into().expect(UNEXPECTED_OVERFLOW));
+    }
+    fn push_u160(ref self: Hasher, value: u256) {
+        assert!(value / ONE_SHIFT_160 == 0, "u160 value too big");
+        self.push_num_bytes(value.high.try_into().expect(UNEXPECTED_OVERFLOW), 4);
+        self.push_u128(value.low);
+    }
+    fn push_u256(ref self: Hasher, value: u256) {
+        self.push_u128(value.high);
+        self.push_u128(value.low);
+    }
+
+    /// Reads all remaining data from the reader and pushes it to
+    /// the hashing buffer.
+    fn push_reader(ref self: Hasher, ref reader: Reader) -> Result<(), felt252> {
+        let mut result = Result::Ok(());
+        while reader.len() > 0 {
+            let mut chunk_len = 8 - self.num_last_bytes;
+            if reader.len() < chunk_len.into() {
+                // reader.len() < 8
+                chunk_len = reader.len().try_into().expect(UNEXPECTED_OVERFLOW);
+            }
+            match reader.read_num_bytes(chunk_len) {
+                Result::Ok(value) => {
+                    // chunk_len <= 8 so value must fit in u64.
+                    self.push_to_last(value.try_into().expect(UNEXPECTED_OVERFLOW), chunk_len);
+                },
+                Result::Err(err) => {
+                    result = Result::Err(err);
+                    break;
+                },
+            }
+        };
+        result
+    }
+
+    /// Returns the keccak256 hash of the buffer. The output hash is interpreted
+    /// as a big endian unsigned integer.
+    fn finalize(ref self: Hasher) -> u256 {
+        let last_le = if self.num_last_bytes == 0 {
+            0
+        } else {
+            u64_byte_reverse(self.last_be) / one_shift_left_bytes_u64(8 - self.num_last_bytes)
+        };
+        let hash_le = core::keccak::cairo_keccak(
+            ref self.inputs_le, last_le, self.num_last_bytes.into()
+        );
+        u256 { low: u128_byte_reverse(hash_le.high), high: u128_byte_reverse(hash_le.low), }
+    }
+}
+
+#[generate_trait]
+impl HasherPrivateImpl of HasherPrivateTrait {
+    // Adds specified number of bytes to the buffer.
+    fn push_num_bytes(ref self: Hasher, value: u64, num_bytes: u8) {
+        assert!(num_bytes <= 8, "num_bytes too high in Hasher::push_num_bytes");
+        let num_high_bytes = min(num_bytes, 8 - self.num_last_bytes);
+        let num_low_bytes = num_bytes - num_high_bytes;
+        let divisor = one_shift_left_bytes_u64(num_low_bytes).try_into().expect(UNEXPECTED_ZERO);
+        let (high, low) = DivRem::div_rem(value, divisor);
+        self.push_to_last(high, num_high_bytes);
+        self.push_to_last(low, num_low_bytes);
+    }
+
+    fn push_to_last(ref self: Hasher, value: u64, num_bytes: u8) {
+        assert!(num_bytes <= 8 - self.num_last_bytes, "num_bytes too high in Hasher::push_to_last");
+        if num_bytes == 8 {
+            self.last_be = value;
+        } else {
+            self.last_be = self.last_be * one_shift_left_bytes_u64(num_bytes) + value;
+        };
+        self.num_last_bytes += num_bytes;
+        if self.num_last_bytes == 8 {
+            self.inputs_le.append(u64_byte_reverse(self.last_be));
+            self.last_be = 0;
+            self.num_last_bytes = 0;
+        }
+    }
+}

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

@@ -1,3 +1,5 @@
 pub mod pyth;
 pub mod wormhole;
 pub mod reader;
+pub mod hash;
+mod util;

+ 35 - 84
target_chains/starknet/contracts/src/reader.cairo

@@ -3,9 +3,11 @@ use core::array::ArrayTrait;
 use core::keccak::cairo_keccak;
 use core::integer::u128_byte_reverse;
 use core::fmt::{Debug, Formatter};
+use pyth::util::{UNEXPECTED_OVERFLOW, UNEXPECTED_ZERO, one_shift_left_bytes_u128};
 
-pub const EOF: felt252 = 'unexpected end of input';
-pub const UNEXPECTED_OVERFLOW: felt252 = 'unexpected overflow';
+pub mod error_codes {
+    pub const EOF: felt252 = 'unexpected end of input';
+}
 
 /// A byte array with storage format similar to `core::ByteArray`, but
 /// suitable for reading data from it.
@@ -70,8 +72,7 @@ pub impl ByteArrayImpl of ByteArrayTrait {
     }
 }
 
-/// Allows to read data from a byte array.
-/// Uses big endian unless specified otherwise.
+/// Allows to read data from a byte array as big endian integers.
 /// All methods return `EOF` error if attempted to
 /// read more bytes than is available.
 #[derive(Drop, Clone)]
@@ -94,7 +95,8 @@ pub impl ReaderImpl of ReaderTrait {
     }
 
     /// 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> {
+    fn read_num_bytes(ref self: Reader, num_bytes: u8) -> Result<u128, felt252> {
+        assert!(num_bytes <= 16, "Reader::read_num_bytes: num_bytes is too large");
         if num_bytes <= self.num_current_bytes {
             let x = self.read_from_current(num_bytes);
             return Result::Ok(x);
@@ -102,7 +104,7 @@ pub impl ReaderImpl of ReaderTrait {
         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 low = self.read_num_bytes(num_low_bytes)?;
         let value = if num_low_bytes == 16 {
             low
         } else {
@@ -112,28 +114,34 @@ pub impl ReaderImpl of ReaderTrait {
     }
 
     fn read_u256(ref self: Reader) -> Result<u256, felt252> {
-        let high = self.read(16)?;
-        let low = self.read(16)?;
+        let high = self.read_num_bytes(16)?;
+        let low = self.read_num_bytes(16)?;
+        let value = u256 { high, low };
+        Result::Ok(value)
+    }
+    fn read_u160(ref self: Reader) -> Result<u256, felt252> {
+        let high = self.read_num_bytes(4)?;
+        let low = self.read_num_bytes(16)?;
         let value = u256 { high, low };
         Result::Ok(value)
     }
     fn read_u128(ref self: Reader) -> Result<u128, felt252> {
-        self.read(16)
+        self.read_num_bytes(16)
     }
     fn read_u64(ref self: Reader) -> Result<u64, felt252> {
-        let value = self.read(8)?.try_into().expect(UNEXPECTED_OVERFLOW);
+        let value = self.read_num_bytes(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);
+        let value = self.read_num_bytes(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);
+        let value = self.read_num_bytes(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);
+        let value = self.read_num_bytes(1)?.try_into().expect(UNEXPECTED_OVERFLOW);
         Result::Ok(value)
     }
 
@@ -142,7 +150,7 @@ pub impl ReaderImpl of ReaderTrait {
         let mut result = Result::Ok(());
         while num_bytes > 0 {
             if num_bytes > 16 {
-                match self.read(16) {
+                match self.read_num_bytes(16) {
                     Result::Ok(_) => {},
                     Result::Err(err) => {
                         result = Result::Err(err);
@@ -151,7 +159,7 @@ pub impl ReaderImpl of ReaderTrait {
                 }
                 num_bytes -= 16;
             } else {
-                match self.read(num_bytes) {
+                match self.read_num_bytes(num_bytes) {
                     Result::Ok(_) => {},
                     Result::Err(err) => {
                         result = Result::Err(err);
@@ -165,7 +173,7 @@ pub impl ReaderImpl of ReaderTrait {
     }
 
     /// Reads the specified number of bytes as a new byte array.
-    fn read_bytes(ref self: Reader, num_bytes: usize) -> Result<ByteArray, felt252> {
+    fn read_byte_array(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;
@@ -204,40 +212,6 @@ pub impl ReaderImpl of ReaderTrait {
         };
         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]
@@ -246,9 +220,10 @@ impl ReaderPrivateImpl of ReaderPrivateTrait {
     /// 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());
+        let divisor = one_shift_left_bytes_u128(num_remaining_bytes)
+            .try_into()
+            .expect(UNEXPECTED_ZERO);
+        let (high, low) = DivRem::div_rem(self.current, divisor);
         self.current = low;
         self.num_current_bytes = num_remaining_bytes;
         high
@@ -265,7 +240,7 @@ impl ReaderPrivateImpl of ReaderPrivateTrait {
                 self.num_current_bytes = 16;
             },
             Option::None => {
-                let (value, bytes) = self.array.pop_front().ok_or(EOF)?;
+                let (value, bytes) = self.array.pop_front().ok_or(error_codes::EOF)?;
                 let value: u256 = value.into();
                 if bytes > 16 {
                     self.current = value.high;
@@ -285,49 +260,25 @@ impl ReaderPrivateImpl of ReaderPrivateTrait {
         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 high = self.read_num_bytes(15)?;
+            let low = self.read_num_bytes(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 high = self
+                .read_num_bytes((num_bytes - 16).try_into().expect(UNEXPECTED_OVERFLOW))?;
+            let low = self.read_num_bytes(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 low = self.read_num_bytes(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'),
-    }
-}

+ 56 - 0
target_chains/starknet/contracts/src/util.cairo

@@ -0,0 +1,56 @@
+use core::integer::u128_byte_reverse;
+
+pub const ONE_SHIFT_160: u256 = 0x10000000000000000000000000000000000000000;
+pub const ONE_SHIFT_96: u256 = 0x1000000000000000000000000;
+
+pub const ONE_SHIFT_64: u128 = 0x10000000000000000;
+
+pub const UNEXPECTED_OVERFLOW: felt252 = 'unexpected overflow';
+pub const UNEXPECTED_ZERO: felt252 = 'unexpected zero';
+
+// Returns 1 << (8 * `n_bytes`) as u128, where `n_bytes` must be < BYTES_IN_U128.
+//
+// Panics if `n_bytes >= 16`.
+pub 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'),
+    }
+}
+
+// Returns 1 << (8 * `n_bytes`) as u64.
+//
+// Panics if `n_bytes >= 8`.
+pub fn one_shift_left_bytes_u64(n_bytes: u8) -> u64 {
+    match n_bytes {
+        0 => 0x1,
+        1 => 0x100,
+        2 => 0x10000,
+        3 => 0x1000000,
+        4 => 0x100000000,
+        5 => 0x10000000000,
+        6 => 0x1000000000000,
+        7 => 0x100000000000000,
+        _ => core::panic_with_felt252('n_bytes too big'),
+    }
+}
+
+pub fn u64_byte_reverse(value: u64) -> u64 {
+    let reversed = u128_byte_reverse(value.into()) / ONE_SHIFT_64.try_into().expect('not zero');
+    reversed.try_into().unwrap()
+}

+ 15 - 50
target_chains/starknet/contracts/src/wormhole.cairo

@@ -54,7 +54,7 @@ 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 pyth::reader::{Reader, ReaderImpl, ByteArray};
     use core::starknet::secp256_trait::{Signature, recover_public_key, Secp256PointTrait};
     use core::starknet::secp256k1::Secp256k1Point;
     use core::starknet::{
@@ -63,6 +63,8 @@ mod wormhole {
     use core::keccak::cairo_keccak;
     use core::integer::u128_byte_reverse;
     use core::panic_with_felt252;
+    use pyth::hash::{Hasher, HasherImpl};
+    use pyth::util::{ONE_SHIFT_160, UNEXPECTED_OVERFLOW};
 
     #[derive(Drop, Debug, Clone, Serde, starknet::Store)]
     struct GuardianSet {
@@ -224,12 +226,12 @@ mod wormhole {
         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 mut hasher = HasherImpl::new();
+        hasher.push_reader(ref reader_for_hash)?;
+        let body_hash1 = hasher.finalize();
+        let mut hasher2 = HasherImpl::new();
+        hasher2.push_u256(body_hash1);
+        let body_hash2 = hasher2.finalize();
 
         let timestamp = reader.read_u32()?;
         let nonce = reader.read_u32()?;
@@ -238,7 +240,7 @@ mod wormhole {
         let sequence = reader.read_u64()?;
         let consistency_level = reader.read_u8()?;
         let payload_len = reader.len();
-        let payload = reader.read_bytes(payload_len)?;
+        let payload = reader.read_byte_array(payload_len)?;
 
         let vm = VM {
             version,
@@ -270,53 +272,16 @@ mod wormhole {
         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)
+        let mut hasher = HasherImpl::new();
+        hasher.push_u256(x);
+        hasher.push_u256(y);
+        let address = hasher.finalize() % ONE_SHIFT_160;
+        Result::Ok(address)
     }
 }