-
Notifications
You must be signed in to change notification settings - Fork 17
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* init: mailbox * feat: mailbox client + fix mailbox/message * fix: msg.value and view function * feat: router * feat: add nonce getter * fix + tests * feat:docs * Ism integration * feat: validator announce * feat: mock ism * fix: typo * fix: fmt * fix: remove test_multisig * fix: opp * fix: mailbox review * fix: comment multisig test * fix: fmt * corrections + tests * refactor * fix: fmt * fix: working dir * fix: ci working directory * fix: typo * fix: typo * fix: release dir --------- Co-authored-by: 0xevolve <[email protected]>
- Loading branch information
Showing
26 changed files
with
1,323 additions
and
108 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
File renamed without changes.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
94 changes: 94 additions & 0 deletions
94
contracts/src/contracts/isms/multisig/merkleroot_multisig_ism.cairo
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
#[starknet::contract] | ||
pub mod merkleroot_multisig_ism { | ||
use alexandria_bytes::{Bytes, BytesTrait, BytesStore}; | ||
|
||
|
||
use core::ecdsa::check_ecdsa_signature; | ||
use hyperlane_starknet::contracts::libs::message::{Message, MessageTrait}; | ||
use hyperlane_starknet::interfaces::{ | ||
IMultisigIsm, IMultisigIsmDispatcher, IMultisigIsmDispatcherTrait, ModuleType, | ||
IInterchainSecurityModule, IInterchainSecurityModuleDispatcher, | ||
IInterchainSecurityModuleDispatcherTrait, | ||
}; | ||
|
||
use starknet::ContractAddress; | ||
#[storage] | ||
struct Storage {} | ||
|
||
mod Errors { | ||
pub const NO_MULTISIG_THRESHOLD_FOR_MESSAGE: felt252 = 'No MultisigISM treshold present'; | ||
pub const VERIFICATION_FAILED_THRESHOLD_NOT_REACHED: felt252 = 'Verify failed, < threshold'; | ||
} | ||
|
||
#[abi(embed_v0)] | ||
impl IMerklerootMultisigIsmImpl of IInterchainSecurityModule<ContractState> { | ||
fn module_type(self: @ContractState) -> ModuleType { | ||
ModuleType::MERKLE_ROOT_MULTISIG(starknet::get_contract_address()) | ||
} | ||
|
||
fn verify( | ||
self: @ContractState, | ||
_metadata: Bytes, | ||
_message: Message, | ||
_validator_configuration: ContractAddress | ||
) -> bool { | ||
let digest = digest(_metadata.clone(), _message.clone()); | ||
let validator_configuration = IMultisigIsmDispatcher { | ||
contract_address: _validator_configuration | ||
}; | ||
let (validators, threshold) = validator_configuration | ||
.validators_and_threshold(_message); | ||
assert(threshold > 0, Errors::NO_MULTISIG_THRESHOLD_FOR_MESSAGE); | ||
let validator_count = validators.len(); | ||
let mut unmatched_signatures = 0; | ||
let mut matched_signatures = 0; | ||
let mut i = 0; | ||
|
||
// for each couple (sig_s, sig_r) extracted from the metadata | ||
loop { | ||
if (i == threshold) { | ||
break (); | ||
} | ||
let (signature_r, signature_s) = get_signature_at(_metadata.clone(), i); | ||
|
||
// we loop on the validators list public key in order to find a match | ||
let mut cur_idx = 0; | ||
let is_signer_in_list = loop { | ||
if (cur_idx == validators.len()) { | ||
break false; | ||
} | ||
let signer = *validators.at(cur_idx); | ||
if check_ecdsa_signature( | ||
digest, signer.try_into().unwrap(), signature_r, signature_s | ||
) { | ||
// we found a match | ||
break true; | ||
} | ||
cur_idx += 1; | ||
}; | ||
if (!is_signer_in_list) { | ||
unmatched_signatures += 1; | ||
} else { | ||
matched_signatures += 1; | ||
} | ||
assert( | ||
unmatched_signatures < validator_count - threshold, | ||
Errors::VERIFICATION_FAILED_THRESHOLD_NOT_REACHED | ||
); | ||
i += 1; | ||
}; | ||
assert( | ||
matched_signatures >= threshold, Errors::VERIFICATION_FAILED_THRESHOLD_NOT_REACHED | ||
); | ||
true | ||
} | ||
} | ||
|
||
fn digest(_metadata: Bytes, _message: Message) -> felt252 { | ||
return 0; | ||
} | ||
|
||
fn get_signature_at(_metadata: Bytes, index: u32) -> (felt252, felt252) { | ||
(0, 0) | ||
} | ||
} |
176 changes: 176 additions & 0 deletions
176
contracts/src/contracts/isms/multisig/messageid_multisig_ism.cairo
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,176 @@ | ||
#[starknet::contract] | ||
pub mod messageid_multisig_ism { | ||
use alexandria_bytes::{Bytes, BytesTrait, BytesStore}; | ||
use core::ecdsa::check_ecdsa_signature; | ||
use hyperlane_starknet::contracts::libs::checkpoint_lib::checkpoint_lib::CheckpointLib; | ||
use hyperlane_starknet::contracts::libs::message::{Message, MessageTrait}; | ||
use hyperlane_starknet::contracts::libs::multisig::message_id_ism_metadata::message_id_ism_metadata::MessageIdIsmMetadata; | ||
use hyperlane_starknet::interfaces::{ | ||
ModuleType, IInterchainSecurityModule, IInterchainSecurityModuleDispatcher, | ||
IInterchainSecurityModuleDispatcherTrait, | ||
}; | ||
use openzeppelin::access::ownable::OwnableComponent; | ||
use openzeppelin::upgrades::{interface::IUpgradeable, upgradeable::UpgradeableComponent}; | ||
use starknet::ContractAddress; | ||
use starknet::EthAddress; | ||
use starknet::eth_signature::is_eth_signature_valid; | ||
use starknet::secp256_trait::{Signature, signature_from_vrs}; | ||
component!(path: OwnableComponent, storage: ownable, event: OwnableEvent); | ||
component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent); | ||
#[abi(embed_v0)] | ||
impl OwnableImpl = OwnableComponent::OwnableImpl<ContractState>; | ||
impl OwnableInternalImpl = OwnableComponent::InternalImpl<ContractState>; | ||
impl UpgradeableInternalImpl = UpgradeableComponent::InternalImpl<ContractState>; | ||
#[storage] | ||
struct Storage { | ||
validators: LegacyMap<u32, EthAddress>, | ||
threshold: u32, | ||
#[substorage(v0)] | ||
ownable: OwnableComponent::Storage, | ||
#[substorage(v0)] | ||
upgradeable: UpgradeableComponent::Storage, | ||
} | ||
|
||
mod Errors { | ||
pub const NO_MULTISIG_THRESHOLD_FOR_MESSAGE: felt252 = 'No MultisigISM treshold present'; | ||
pub const NO_MATCH_FOR_SIGNATURE: felt252 = 'No match for given signature'; | ||
pub const EMPTY_METADATA: felt252 = 'Empty metadata'; | ||
pub const VALIDATOR_ADDRESS_CANNOT_BE_NULL: felt252 = 'Validator address cannot be 0'; | ||
} | ||
|
||
#[event] | ||
#[derive(Drop, starknet::Event)] | ||
pub enum Event { | ||
#[flat] | ||
OwnableEvent: OwnableComponent::Event, | ||
#[flat] | ||
UpgradeableEvent: UpgradeableComponent::Event, | ||
} | ||
|
||
|
||
#[constructor] | ||
fn constructor(ref self: ContractState, _owner: ContractAddress) { | ||
self.ownable.initializer(_owner); | ||
} | ||
|
||
#[abi(embed_v0)] | ||
impl IMessageidMultisigIsmImpl of IInterchainSecurityModule<ContractState> { | ||
fn module_type(self: @ContractState) -> ModuleType { | ||
ModuleType::MESSAGE_ID_MULTISIG(starknet::get_contract_address()) | ||
} | ||
|
||
fn verify(self: @ContractState, _metadata: Bytes, _message: Message,) -> bool { | ||
assert(_metadata.clone().data().len() > 0, Errors::EMPTY_METADATA); | ||
let digest = digest(_metadata.clone(), _message.clone()); | ||
let (validators, threshold) = self.validators_and_threshold(_message); | ||
assert(threshold > 0, Errors::NO_MULTISIG_THRESHOLD_FOR_MESSAGE); | ||
let mut matched_signatures = 0; | ||
let mut i = 0; | ||
|
||
// for each couple (sig_s, sig_r) extracted from the metadata | ||
loop { | ||
if (i == threshold) { | ||
break (); | ||
} | ||
let signature = get_signature_at(_metadata.clone(), i); | ||
// we loop on the validators list public key in order to find a match | ||
let mut cur_idx = 0; | ||
let is_signer_in_list = loop { | ||
if (cur_idx == validators.len()) { | ||
break false; | ||
} | ||
let signer = *validators.at(cur_idx); | ||
if bool_is_eth_signature_valid(digest, signature, signer) { | ||
// we found a match | ||
break true; | ||
} | ||
cur_idx += 1; | ||
}; | ||
assert(is_signer_in_list, Errors::NO_MATCH_FOR_SIGNATURE); | ||
i += 1; | ||
}; | ||
true | ||
} | ||
fn get_validators(self: @ContractState) -> Span<EthAddress> { | ||
build_validators_span(self) | ||
} | ||
|
||
fn get_threshold(self: @ContractState) -> u32 { | ||
self.threshold.read() | ||
} | ||
|
||
fn set_validators(ref self: ContractState, _validators: Span<EthAddress>) { | ||
self.ownable.assert_only_owner(); | ||
let mut cur_idx = 0; | ||
|
||
loop { | ||
if (cur_idx == _validators.len()) { | ||
break (); | ||
} | ||
let validator = *_validators.at(cur_idx); | ||
assert( | ||
validator != 0.try_into().unwrap(), Errors::VALIDATOR_ADDRESS_CANNOT_BE_NULL | ||
); | ||
self.validators.write(cur_idx.into(), validator); | ||
cur_idx += 1; | ||
} | ||
} | ||
|
||
fn set_threshold(ref self: ContractState, _threshold: u32) { | ||
self.ownable.assert_only_owner(); | ||
self.threshold.write(_threshold); | ||
} | ||
|
||
fn validators_and_threshold( | ||
self: @ContractState, _message: Message | ||
) -> (Span<EthAddress>, u32) { | ||
// USER CONTRACT DEFINITION HERE | ||
// USER CAN SPECIFY VALIDATORS SELECTION CONDITIONS | ||
let threshold = self.threshold.read(); | ||
(build_validators_span(self), threshold) | ||
} | ||
} | ||
|
||
fn digest(_metadata: Bytes, _message: Message) -> u256 { | ||
let origin_merkle_tree_hook = MessageIdIsmMetadata::origin_merkle_tree_hook( | ||
_metadata.clone() | ||
); | ||
let root = MessageIdIsmMetadata::root(_metadata.clone()); | ||
let index = MessageIdIsmMetadata::index(_metadata.clone()); | ||
CheckpointLib::digest( | ||
_message.origin, | ||
origin_merkle_tree_hook.into(), | ||
root.into(), | ||
index, | ||
MessageTrait::format_message(_message) | ||
) | ||
} | ||
|
||
fn get_signature_at(_metadata: Bytes, _index: u32) -> Signature { | ||
let (v, r, s) = MessageIdIsmMetadata::signature_at(_metadata, _index); | ||
signature_from_vrs(v.into(), r, s) | ||
} | ||
|
||
fn bool_is_eth_signature_valid( | ||
msg_hash: u256, signature: Signature, signer: EthAddress | ||
) -> bool { | ||
match is_eth_signature_valid(msg_hash, signature, signer) { | ||
Result::Ok(()) => true, | ||
Result::Err(_) => false | ||
} | ||
} | ||
|
||
fn build_validators_span(self: @ContractState) -> Span<EthAddress> { | ||
let mut validators = ArrayTrait::new(); | ||
let mut cur_idx = 0; | ||
loop { | ||
let validator = self.validators.read(cur_idx); | ||
if (validator == 0.try_into().unwrap()) { | ||
break (); | ||
} | ||
validators.append(validator); | ||
cur_idx += 1; | ||
}; | ||
validators.span() | ||
} | ||
} |
Oops, something went wrong.