feat(target_chains/starknet): wormhole VAA verification and parsing
This commit is contained in:
parent
508de75839
commit
2d9c6d3028
|
@ -0,0 +1 @@
|
|||
scarb 2.5.4
|
|
@ -1,2 +1,3 @@
|
|||
mod pyth;
|
||||
mod wormhole;
|
||||
mod reader;
|
||||
|
|
|
@ -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'),
|
||||
}
|
||||
}
|
|
@ -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 set_value(ref self: ContractState, name: felt252) {
|
||||
self.name.write(name - 2);
|
||||
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![];
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue