// Ensure we're `no_std` when compiling for Wasm. #![cfg_attr(not(feature = "std"), no_std)] use frame_support::{ pallet_prelude::*, traits::{ Currency, DisabledValidators, ValidatorSet, ValidatorSetWithIdentification, OneSessionHandler, WithdrawReasons, ExistenceRequirement, }, }; use frame_system::{ offchain::{SubmitTransaction, SendTransactionTypes}, pallet_prelude::*, }; use sp_runtime::{ offchain::{ self as rt_offchain, storage::StorageValueRef, storage_lock::{StorageLock, Time}, }, traits::UniqueSaturatedInto, Saturating, Perbill, }; use sp_std::{vec, vec::Vec, collections::btree_map::BTreeMap}; use sp_application_crypto::RuntimeAppPublic; use sp_runtime::traits::BlockNumberProvider; use sp_io::hashing::blake2_256; use ghost_helpers::{ get_byzantium_threshold, SubstrateBlake2Hasher, hash_chain::sequential_hash, networks::{NetworkData, NetworkCurve, NetworkType}, bounded_bitmap::{validate_bitmap_sizes, BoundedBitmap}, }; #[cfg(test)] use ghost_traits::exodus::IdentifierConverter; use ghost_traits::{ hashing::GhostHasher, bounded_bitmap::{ BoundedBitmapGenerator, BoundedBitmapWriter, BoundedBitmapReader, BoundedBitmapIterable, }, exodus::{ MerkleTreeBuilder, DistributedKeyGeneration, EllipticCurveDiffieHellman, FlexibleRoundOptimizedSchnorrThresholdSignature, }, networks::{ NetworkDataBasicHandler, NetworkDataInspectHandler, NetworkDataMutateHandler, }, }; use rand_chacha::{ChaCha20Rng, rand_core::SeedableRng}; #[cfg(feature = "runtime-benchmarks")] mod benchmarking; #[cfg(any(test))] mod mock; #[cfg(test)] mod tests; #[cfg_attr(feature = "runtime-benchmarks", macro_use)] mod impls; mod error; mod types; pub mod weights; pub use types::*; pub use error::ExodusError; pub use weights::WeightInfo; pub mod sr25519 { mod app_sr25519 { use sp_application_crypto::{app_crypto, sr25519, KeyTypeId}; const EXODUS: KeyTypeId = KeyTypeId(*b"exds"); app_crypto!(sr25519, EXODUS); } sp_application_crypto::with_pair! { pub type AuthorityPair = app_sr25519::Pair; } pub type AuthoritySignature = app_sr25519::Signature; pub type AuthorityId = app_sr25519::Public; } const MIN_LOCK_GUARD_PERIOD: u64 = 15_000; const DB_STORAGE_PREFIX : &[u8] = b"exodus::"; const PALLET_LOG_TARGET : &str = "time:ghost-exodus"; const OFFCHAIN_INDEX_KEY : &[u8] = b"dkg-round2-encrypted"; const DKG_ROUND0_PREFIX: &[u8] = b"dkg-round0"; const DKG_ROUND1_PREFIX: &[u8] = b"dkg-round1"; const DKG_ROUND2_PREFIX: &[u8] = b"dkg-round2"; const DKG_ROUND3_PREFIX: &[u8] = b"dkg-round3"; const DKG_ROUND4_PREFIX: &[u8] = b"dkg-round4"; const DKG_SECRET_PREFIX: &[u8] = b"dkg-secret"; const EXODUS_PREFIX_NONCES: &[u8] = b"exodus-nonces"; const EXODUS_PREFIX_SHARES: &[u8] = b"exodus-shares"; const EXODUS_PREFIX_BLENDS: &[u8] = b"exodus-blends"; const EXODUS_PREFIX_EXILES: &[u8] = b"exodus-exiles"; const MAX_MESSAGE_SIZE : u32 = 420; const HEADER_MAX_BYTES : u32 = 1 + 4; const ELEMENT_MAX_BYTES : u32 = 33; const SCALAR_MAX_BYTES : u32 = 32; const ENCRYPTION_NONCE_MAX_BYTES : u32 = 12; const AUTHENTICATION_TAG_MAX_BYTES : u32 = 16; const ROUND_NUMBER_0: u8 = 0u8; const ROUND_NUMBER_1: u8 = 1u8; const ROUND_NUMBER_2: u8 = 2u8; const ROUND_NUMBER_3: u8 = 3u8; const ROUND_NUMBER_4: u8 = 4u8; const ROUND_NUMBER_5: u8 = 5u8; const ROUND_NUMBER_6: u8 = 6u8; const fn postcard_varint_len(len: usize) -> usize { if len <= 127 { 1 } else if len <= 16383 { 2 } else { 3 } } const fn signature_bytes_len( element_bytes_len: usize, scalar_bytes_len: usize, )-> usize { let signature_bytes_len = element_bytes_len.saturating_add(scalar_bytes_len); let signature_vec_prefix = postcard_varint_len(signature_bytes_len); signature_bytes_len.saturating_add(signature_vec_prefix) } const fn round1_package_size( commitments_count: usize, element_bytes_len: usize, scalar_bytes_len: usize, header_bytes_len: usize ) -> usize { let commitments_bytes_len = commitments_count.saturating_mul(element_bytes_len); let commitment_vec_prefix = postcard_varint_len(commitments_count); signature_bytes_len(element_bytes_len, scalar_bytes_len) .saturating_add(header_bytes_len) .saturating_add(commitment_vec_prefix) .saturating_add(commitments_bytes_len) } const fn encrypted_ciphertext_bytes_len( scalar_bytes_len: usize, header_bytes_len: usize, ) -> usize { let ciphertext_len = header_bytes_len .saturating_add(scalar_bytes_len) .saturating_add(AUTHENTICATION_TAG_MAX_BYTES as usize); let ciphertext_vec_prefix = postcard_varint_len(ciphertext_len); ciphertext_len.saturating_add(ciphertext_vec_prefix) } const fn round2_encrypted_package_size( participants: usize, scalar_bytes_len: usize, header_bytes_len: usize, ) -> usize { let encrypted_ciphertext_bytes_len = encrypted_ciphertext_bytes_len( scalar_bytes_len, header_bytes_len, ); let ciphertext_len = encrypted_ciphertext_bytes_len .saturating_add(ENCRYPTION_NONCE_MAX_BYTES as usize); let ciphertext_vec_prefix = postcard_varint_len(ciphertext_len); let size = ciphertext_len.saturating_add(ciphertext_vec_prefix); participants.saturating_mul(size) } const fn round2_package_size( participants: usize, scalar_bytes_len: usize, header_bytes_len: usize, ) -> usize { let signing_share_len = header_bytes_len.saturating_add(scalar_bytes_len); let signing_share_vec_prefix = postcard_varint_len(signing_share_len); let size = signing_share_vec_prefix.saturating_add(signing_share_len); participants.saturating_mul(size) } const fn nonce_commitment_package_size( element_max_bytes: usize, header_max_bytes: usize, ) -> usize { header_max_bytes .saturating_add(element_max_bytes) .saturating_add(element_max_bytes) } const fn signature_share_bytes_len( scalar_bytes_len: usize, header_bytes_len: usize, ) -> usize { header_bytes_len.saturating_add(scalar_bytes_len) } type DkgIndex = ghost_helpers::DkgIndexU32; type AuthIndex = ghost_helpers::AuthIndexU16; type BitmapChunk = ghost_helpers::BitmapChunkU32; type RoastSession = u16; type ExodusSession = u64; type EvmAddress = sp_core::H160; type ExodusHash = sp_core::H256; type EvmBytes32 = sp_core::H256; type ParticipantsBitmap = BoundedBitmap::MaxAuthoritiesChunks>; type BitmapByAuthority = BoundedBTreeMap, ::MaxAuthorities>; type QualifyingState = PendingDkgAuthorities, BlockNumberFor>; type ActivatedState = ReadyDkgAuthorities>; pub type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; pub type NetworkIdOf = <::NetworkDataHandler as NetworkDataBasicHandler>::NetworkId; pub type ValidatorId = <::ValidatorSet as ValidatorSet< ::AccountId, >>::ValidatorId; pub type IdentificationTuple = ( ValidatorId, <::ValidatorSet as ValidatorSetWithIdentification< ::AccountId, >>::Identification, ); type ExodusResult = Result; pub struct MaxAuthoritiesBitmaskSize(sp_std::marker::PhantomData); impl Get for MaxAuthoritiesBitmaskSize { fn get() -> u32 { let max_authorities = T::MaxAuthorities::get(); max_authorities.div_ceil(8) } } pub struct Round1MaxBytes(sp_std::marker::PhantomData); impl Get for Round1MaxBytes { fn get() -> u32 { let max_authorities = T::MaxAuthorities::get(); let min_authorities = get_byzantium_threshold(max_authorities); round1_package_size( min_authorities as usize, ELEMENT_MAX_BYTES as usize, SCALAR_MAX_BYTES as usize, HEADER_MAX_BYTES as usize, ) as u32 } } pub struct Round2BlobMaxBytes(sp_std::marker::PhantomData); impl Get for Round2BlobMaxBytes { fn get() -> u32 { let max_authorities = T::MaxAuthorities::get(); round2_package_size( max_authorities as usize, SCALAR_MAX_BYTES as usize, HEADER_MAX_BYTES as usize ) as u32 } } pub struct EncryptedRound2BlobMaxBytes(sp_std::marker::PhantomData); impl Get for EncryptedRound2BlobMaxBytes { fn get() -> u32 { let max_authorities = T::MaxAuthorities::get(); round2_encrypted_package_size( max_authorities as usize, SCALAR_MAX_BYTES as usize, HEADER_MAX_BYTES as usize ) as u32 } } pub struct AccusedIndicesMaxBytes(sp_std::marker::PhantomData); impl Get for AccusedIndicesMaxBytes { fn get() -> u32 { let max_authorities = T::MaxAuthorities::get(); max_authorities.saturating_add(7).div_ceil(8) } } pub struct MerkleProofMaxSize(sp_std::marker::PhantomData); impl Get for MerkleProofMaxSize { fn get() -> u32 { let max_authorities = T::MaxAuthorities::get(); max_authorities .next_power_of_two() .trailing_zeros() .saturating_mul(ExodusHash::len_bytes() as u32) } } pub struct BindingFactorsProof(sp_std::marker::PhantomData); impl Get for BindingFactorsProof { fn get() -> u32 { let max_authorities = T::MaxAuthorities::get(); get_byzantium_threshold(max_authorities) .next_power_of_two() .trailing_zeros() } } pub struct NonceCommitmentMaxBytes(sp_std::marker::PhantomData); impl Get for NonceCommitmentMaxBytes { fn get() -> u32 { nonce_commitment_package_size( ELEMENT_MAX_BYTES as usize, HEADER_MAX_BYTES as usize, ) as u32 } } pub struct CiphertextMaxBytes(sp_std::marker::PhantomData); impl Get for CiphertextMaxBytes { fn get() -> u32 { encrypted_ciphertext_bytes_len( SCALAR_MAX_BYTES as usize, HEADER_MAX_BYTES as usize, ) as u32 } } pub struct SignatureShareMaxBytes(sp_std::marker::PhantomData); impl Get for SignatureShareMaxBytes { fn get() -> u32 { signature_share_bytes_len( SCALAR_MAX_BYTES as usize, HEADER_MAX_BYTES as usize, ) as u32 } } pub struct SignatureMaxBytes(sp_std::marker::PhantomData); impl Get for SignatureMaxBytes { fn get() -> u32 { signature_bytes_len( ELEMENT_MAX_BYTES as usize, SCALAR_MAX_BYTES as usize, ) as u32 } } pub struct ZeroScalarDefault(sp_std::marker::PhantomData); impl Get>> for ZeroScalarDefault { fn get() -> BoundedVec> { let zero_vec = vec![0u8; SCALAR_MAX_BYTES as usize]; BoundedVec::try_from(zero_vec).unwrap_or_default() } } pub use pallet::*; #[frame_support::pallet] pub mod pallet { use super::*; const STORAGE_VERSION: StorageVersion = StorageVersion::new(0); #[pallet::pallet] #[pallet::storage_version(STORAGE_VERSION)] #[pallet::without_storage_info] pub struct Pallet(_); #[pallet::config] pub trait Config: SendTransactionTypes> + frame_system::Config + core::fmt::Debug { type RuntimeEvent: From> + IsType<::RuntimeEvent>; type AuthorityId: Member + Parameter + RuntimeAppPublic + Ord + MaybeSerializeDeserialize + MaxEncodedLen; type ValidatorSet: ValidatorSetWithIdentification; type Currency: Currency; type NetworkDataHandler: NetworkDataInspectHandler + NetworkDataMutateHandler> + NetworkDataBasicHandler; type BlockNumberProvider: BlockNumberProvider>; type DisabledValidators: DisabledValidators; #[pallet::constant] type UnsignedPriority: Get; #[pallet::constant] type UnsignedLongevity: Get; #[pallet::constant] type MaxAuthorities: Get; #[pallet::constant] type MaxAuthoritiesChunks: Get; #[pallet::constant] type DkgRoundPeriod: Get; #[pallet::constant] type RemovalLimit: Get; #[pallet::constant] type MaxCursorLen: Get; type WeightInfo: WeightInfo; } #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { Round0PackageRegistered { authority_index: AuthIndex, network_curve: NetworkCurve, hash: ExodusHash, }, Round1PackageRegistered { authority_index: AuthIndex, coefficients_count: AuthIndex, network_curve: NetworkCurve, }, Round2PackagesRegistered { authority_index: AuthIndex, encrypted_count: AuthIndex, network_curve: NetworkCurve, }, ComplaintsRegistered { complaints_count: AuthIndex, authority_index: AuthIndex, network_curve: NetworkCurve, }, Round4JustificatPackage { justifications_count: AuthIndex, authority_index: AuthIndex, network_curve: NetworkCurve, }, Round5PublicPackageRegistered { authority_index: AuthIndex, network_curve: NetworkCurve, verifying_hash: ExodusHash, }, Round6VerifyingShareRegistered { authority_index: AuthIndex, network_curve: NetworkCurve, }, NonceCommitmentRegistered { authority_index: AuthIndex, exodus_session: ExodusSession, network_curve: NetworkCurve, roast_session: RoastSession, }, GroupCommitmentRegistered { authority_index: AuthIndex, exodus_session: ExodusSession, network_curve: NetworkCurve, roast_session: RoastSession, }, PartialSignatureRegistered { authority_index: AuthIndex, exodus_session: ExodusSession, network_curve: NetworkCurve, roast_session: RoastSession, }, EvmBridgeOutRegistered { who: T::AccountId, network_id: NetworkIdOf, amount: BalanceOf, bounty: Perbill, receiver: EvmAddress, } } #[pallet::error] pub enum Error { DkgWrongRound, TooManyEntries, TooManyPackages, WrongNetworkType, InvalidMerkleProof, NetworkDoesNotExist, NoActiveAuthorities, InvalidSignatureShare, VerifyingShareNotFound, DkgAuthoritiesInProgress, PackagesAlreadyRegistered, DkgAuthoritiesNotInitialized, Round0PackageInvalidProof, Round0PackageAlreadyRegistered, Round1PackageBadHash, Round1PackageInvalidProof, Round1PackageAlreadyRegistered, Round1PackageInvalidLength, Round2PackagesInvalidProof, Round2PackagesAlreadyRegistered, Round2PackagesWrongCoefficients, Round3PackageInvalidProof, Round3PackagesAlreadyRegistere, Round4PackageInvalidProof, Round4PackagesAlreadyRegistered, Round5PackageInvalidProof, Round5PackagesAlreadyRegistered, Round6PackageInvalidProof, Round6PackagesAlreadyRegistered, ExodusMessageTooBig, ExodusRequestNotFound, ExodusAccumulationFailed, ExodusIncorrectRoastStatus, ExodusInvalidSignatureShare, ExodusAlreadyInRoastSession, ExodusSignedMessageAlreadyExists, ExodusNonceNotRegistered, ExodusNonceAlreadyRegistered, ExodusGroupAlreadyRegistered, ExodusCommitmentNotRegistered, ExodusSignedMessageAlreadyRegistered, ExodusNoncePackageInvalidProof, ExodusGroupPackageInvalidProof, ExodusSharePackageInvalidProof, } #[pallet::storage] #[pallet::getter(fn current_exodus)] pub(super) type CurrentExodus = StorageValue<_, ExodusSession, ValueQuery>; #[pallet::storage] #[pallet::getter(fn exodus_requests)] pub(super) type ExodusRequests = StorageDoubleMap< _, Twox64Concat, NetworkCurve, Twox64Concat, ExodusSession, ExodusRequest, BalanceOf>, OptionQuery, >; #[pallet::storage] #[pallet::getter(fn roast_states)] pub(super) type RoastSessionStates = StorageDoubleMap< _, Twox64Concat, ExodusSession, Twox64Concat, RoastSession, RoastSessionState>, ValueQuery, >; #[pallet::storage] #[pallet::getter(fn nonce_commitments)] pub(super) type NonceCommitments = StorageDoubleMap< _, Twox64Concat, (ExodusSession, RoastSession), Twox64Concat, AuthIndex, ExodusSeparatedNonce, OptionQuery, >; #[pallet::storage] #[pallet::getter(fn group_committers)] pub(super) type GroupCommitters = StorageNMap< _, ( NMapKey, NMapKey, NMapKey, NMapKey>>, ), ParticipantsBitmap, ValueQuery, >; #[pallet::storage] #[pallet::getter(fn group_commitments_consensus)] pub(super) type GroupCommitmentsConsensus = StorageDoubleMap< _, Twox64Concat, ExodusSession, Twox64Concat, RoastSession, ConsensusState>, ValueQuery, >; #[pallet::storage] #[pallet::getter(fn signature_scalars)] pub(super) type SignatureScalars = StorageDoubleMap< _, Twox64Concat, ExodusSession, Twox64Concat, RoastSession, BoundedVec>, ValueQuery, ZeroScalarDefault, >; #[pallet::storage] #[pallet::getter(fn roast_sessions)] pub(super) type RoastSessions = StorageDoubleMap< _, Twox64Concat, ExodusSession, Twox64Concat, AuthIndex, RoastSession, OptionQuery, >; #[pallet::storage] #[pallet::getter(fn exodus_signatured_rotations)] pub(super) type ExodusSignedRotations = StorageDoubleMap< _, Twox64Concat, (ExodusSession, NetworkType), Twox64Concat, DkgIndex, ExodusSignedMessage, BalanceOf>, OptionQuery, >; #[pallet::storage] #[pallet::getter(fn exodus_signed_bridges)] pub(super) type ExodusSignedBridges = StorageDoubleMap< _, Twox64Concat, (ExodusSession, NetworkIdOf), Twox64Concat, DkgIndex, ExodusSignedMessage, BalanceOf>, OptionQuery, >; #[pallet::storage] #[pallet::getter(fn exodus_signed_governance)] pub(super) type ExodusSignedGovernance = StorageDoubleMap< _, Twox64Concat, (ExodusSession, NetworkIdOf), Twox64Concat, DkgIndex, ExodusSignedMessage, BalanceOf>, OptionQuery, >; #[pallet::storage] #[pallet::getter(fn dkg_maybe_remove_cursor)] pub(super) type DkgMaybeCursor = StorageMap< _, Twox64Concat, NetworkCurve, BoundedVec, OptionQuery, >; #[pallet::storage] #[pallet::getter(fn exodus_activity)] pub(super) type ExodusActivity = StorageMap< _, Twox64Concat, AuthIndex, BlockNumberFor, ValueQuery, >; #[pallet::storage] #[pallet::getter(fn round0_packages)] pub(super) type Round0Packages = StorageDoubleMap< _, Twox64Concat, NetworkCurve, Twox64Concat, AuthIndex, ExodusHash, ValueQuery, >; #[pallet::storage] #[pallet::getter(fn round1_packages)] pub(super) type Round1Packages = StorageMap< _, Twox64Concat, NetworkCurve, ParticipantsBitmap, ValueQuery, >; #[pallet::storage] #[pallet::getter(fn encrypted_round2_packages)] pub(super) type Round2Packages = StorageMap< _, Twox64Concat, NetworkCurve, ParticipantsBitmap, ValueQuery, >; #[pallet::storage] #[pallet::getter(fn complaints)] pub(super) type Complaints = StorageMap< _, Twox64Concat, NetworkCurve, BitmapByAuthority, ValueQuery, >; #[pallet::storage] #[pallet::getter(fn justifications)] pub(super) type Justifications = StorageMap< _, Twox64Concat, NetworkCurve, BitmapByAuthority, ValueQuery, >; #[pallet::storage] #[pallet::getter(fn verifications)] pub(super) type Verifications = StorageNMap< _, ( NMapKey, NMapKey, NMapKey, ), ParticipantsBitmap, ValueQuery, >; #[pallet::storage] #[pallet::getter(fn verifying_key_consensus)] pub(super) type VerifyingKeyConsensus = StorageDoubleMap< _, Twox64Concat, NetworkCurve, Twox64Concat, DkgIndex, ConsensusState>, ValueQuery, >; #[pallet::storage] #[pallet::getter(fn verifying_shares)] pub(super) type VerifyingShares = StorageNMap< _, ( NMapKey, NMapKey, NMapKey, ), BoundedVec>, OptionQuery, >; #[pallet::storage] #[pallet::getter(fn verifying_shares_participants)] pub(super) type VerifyingSharesParticipants = StorageDoubleMap< _, Twox64Concat, NetworkCurve, Twox64Concat, DkgIndex, ParticipantsBitmap, ValueQuery, >; #[pallet::storage] #[pallet::getter(fn active_verifying_key)] pub(super) type ActiveVerifyingKey = StorageMap< _, Twox64Concat, NetworkCurve, BoundedVec>, ValueQuery, >; #[pallet::storage] #[pallet::getter(fn active_authorities)] pub(super) type ActiveAuthorities = StorageMap< _, Twox64Concat, NetworkCurve, WeakBoundedVec<::AuthorityId, ::MaxAuthorities>, ValueQuery, >; #[pallet::storage] #[pallet::getter(fn active_dkg_authority)] pub(super) type ActiveDkgAuthorities = StorageMap< _, Twox64Concat, NetworkCurve, ActivatedState, ValueQuery, >; #[pallet::storage] #[pallet::getter(fn qualification_authorities)] pub(super) type QualificationAuthorities = StorageMap< _, Twox64Concat, NetworkCurve, WeakBoundedVec<::AuthorityId, ::MaxAuthorities>, ValueQuery, >; #[pallet::storage] #[pallet::getter(fn qualification_dkg_state)] pub(super) type QualificationDkgState = StorageMap< _, Twox64Concat, NetworkCurve, QualifyingState, ValueQuery, >; #[pallet::genesis_config] #[derive(frame_support::DefaultNoBound)] pub struct GenesisConfig { pub authorities: Vec, } #[pallet::genesis_build] impl BuildGenesisConfig for GenesisConfig { fn build(&self) { use strum::IntoEnumIterator; // NOTE: would be nice to use same logic during forkless // upgrade in order to prevent any issues down the road. let block_longevity: BlockNumberFor = T::UnsignedLongevity::get().unique_saturated_into(); let converted_round_period: BlockNumberFor = T::DkgRoundPeriod::get().unique_saturated_into(); assert!( block_longevity < converted_round_period, "CRITICAL CONFIG ERROR: T::DKGRoundPeriod could not be less than expected block inclusion!", ); let max_authorities = T::MaxAuthorities::get(); validate_bitmap_sizes::>(max_authorities); for network_curve in NetworkCurve::iter() { assert!( network_curve.element_bytes_len() <= ELEMENT_MAX_BYTES as usize, "Curve {:?} requires {} bytes for elements but ELEMENT_MAX_BYTES is {}.", network_curve, network_curve.element_bytes_len(), ELEMENT_MAX_BYTES, ); assert!( network_curve.scalar_bytes_len() <= SCALAR_MAX_BYTES as usize, "Curve {:?} requires {} bytes for scalar but SCALAR_MAX_BYTES is {}.", network_curve, network_curve.scalar_bytes_len(), SCALAR_MAX_BYTES, ); for network_type in T::NetworkDataHandler::iter_types_by_curve(&network_curve) { let rotation_message_size = match network_type { NetworkType::Evm => { let dummy_public_address = EvmBytes32::repeat_byte(69u8); let dummy_exodus_request = ExodusRequest::, BalanceOf>::evm_rotation( 0, dummy_public_address, 0, ); let mut message_buffer = [0u8; MAX_MESSAGE_SIZE as usize]; dummy_exodus_request .get_message(&mut message_buffer) .map(|message| message.len() as u32) .unwrap_or(u32::MAX) }, _ => 0, }; assert!( rotation_message_size <= MAX_MESSAGE_SIZE, "Curve {:?} requires {} bytes for rotation message but MAX_MESSAGE_SIZE is {}", network_curve, rotation_message_size, MAX_MESSAGE_SIZE, ); } } } } #[pallet::call] impl Pallet { #[pallet::call_index(0)] #[pallet::weight(( T::WeightInfo::register_round0_package(), DispatchClass::Normal, Pays::No, ))] pub fn register_round0_package( origin: OriginFor, dkg_package: DkgPackage, signature: ::Signature, ) -> DispatchResult { ensure_none(origin)?; let network_curve = dkg_package.get_network_curve(); let authority_index = dkg_package.get_authority_index(); Self::validate_round0_package_size(&dkg_package, &signature) .map_err(|_| Error::::Round0PackageInvalidProof)?; Self::register_latest_activity(&dkg_package)?; let DkgPackage::Round0(round0) = dkg_package else { return Err(Error::::Round0PackageInvalidProof.into()); }; QualificationDkgState::::try_mutate(&network_curve, |qualification_state| -> DispatchResult { ensure!( qualification_state.is_zero_phase(), Error::::DkgWrongRound, ); ensure!( !qualification_state.contains_index(authority_index), Error::::Round0PackageAlreadyRegistered, ); qualification_state.insert_index(authority_index); Ok(()) })?; Round0Packages::::insert( &network_curve, authority_index, round0.package_hash, ); Self::deposit_event(Event::::Round0PackageRegistered { authority_index, network_curve, hash: round0.package_hash, }); Ok(()) } #[pallet::call_index(1)] #[pallet::weight(( T::WeightInfo::register_round1_package( dkg_package.bytes_len(), ), DispatchClass::Normal, Pays::No, ))] pub fn register_round1_package( origin: OriginFor, dkg_package: DkgPackage, signature: ::Signature, ) -> DispatchResult { ensure_none(origin)?; let network_curve = dkg_package.get_network_curve(); let authority_index = dkg_package.get_authority_index(); Self::validate_round1_package_size(&dkg_package, &signature) .map_err(|_| Error::::Round1PackageInvalidProof)?; Self::register_latest_activity(&dkg_package)?; let DkgPackage::Round1(round1) = dkg_package else { return Err(Error::::Round1PackageInvalidProof.into()); }; let hash1 = Round0Packages::::get(&network_curve, authority_index); let hash2 = ExodusHash::from(blake2_256(&round1.package)); ensure!(!hash1.is_zero() && hash1 == hash2, Error::::Round1PackageBadHash); let estimated_commitment_count = network_curve.dkg_verify_proof_of_knowledge( authority_index, &round1.package, ).map_err(|_| Error::::Round1PackageInvalidProof)?; let state = QualificationDkgState::::get(&network_curve); ensure!(state.is_first_phase(), Error::::DkgWrongRound); state.count_ones::().checked_sub(1) .map(|needed_commitment_count| { estimated_commitment_count .eq(&needed_commitment_count) .then(|| ()) }) .ok_or(Error::::Round1PackageInvalidLength)?; Round1Packages::::try_mutate( &network_curve, |round1_packages| -> DispatchResult { ensure!( !round1_packages.contains(authority_index), Error::::Round1PackageAlreadyRegistered, ); round1_packages.insert(authority_index); Ok(()) })?; Self::deposit_event(Event::::Round1PackageRegistered { authority_index, network_curve, coefficients_count: estimated_commitment_count, }); let index_key = Self::create_offchain_dkg_key( network_curve, authority_index, ROUND_NUMBER_1, ); sp_io::offchain_index::set( &index_key, &round1.package.encode(), ); Ok(()) } #[pallet::call_index(2)] #[pallet::weight(( T::WeightInfo::register_encrypted_round2_packages( dkg_package.bytes_len(), ), DispatchClass::Normal, Pays::No, ))] pub fn register_encrypted_round2_packages( origin: OriginFor, dkg_package: DkgPackage, signature: ::Signature, ) -> DispatchResult { ensure_none(origin)?; let network_curve = dkg_package.get_network_curve(); let authority_index = dkg_package.get_authority_index(); Self::validate_round2_package_size(&dkg_package, &signature) .map_err(|_| Error::::Round2PackagesInvalidProof)?; Self::register_latest_activity(&dkg_package)?; let DkgPackage::Round2(round2_bundle) = dkg_package else { return Err(Error::::Round2PackagesInvalidProof.into()); }; let state = QualificationDkgState::::get(&network_curve); ensure!(state.is_second_phase(), Error::::DkgWrongRound); let encrypted_indexes = ParticipantsBitmap::::from_bitmask( &round2_bundle.bundle.bitmask, ); let diff = state.get_indexes() ^ &encrypted_indexes; let only_one_lost = diff.count_ones::() == 1; let only_authority_index = diff.contains(authority_index); ensure!( only_one_lost && only_authority_index, Error::::Round2PackagesWrongCoefficients, ); Round2Packages::::try_mutate(&network_curve, |round2_packages| -> DispatchResult { ensure!( !round2_packages.contains(authority_index), Error::::Round2PackagesAlreadyRegistered, ); round2_packages.insert(authority_index); Ok(()) })?; Self::deposit_event(Event::::Round2PackagesRegistered { encrypted_count: encrypted_indexes.count_ones::(), authority_index, network_curve, }); let index_key = Self::create_offchain_dkg_key( network_curve, authority_index, ROUND_NUMBER_2, ); sp_io::offchain_index::set( &index_key, &round2_bundle.bundle.encode(), ); Ok(()) } #[pallet::call_index(3)] #[pallet::weight(( T::WeightInfo::register_round3_complaints( dkg_package.bytes_len(), ), DispatchClass::Normal, Pays::No, ))] pub fn register_round3_complaints( origin: OriginFor, dkg_package: DkgPackage, signature: ::Signature, ) -> DispatchResult { ensure_none(origin)?; let network_curve = dkg_package.get_network_curve(); let authority_index = dkg_package.get_authority_index(); Self::validate_round3_complaints_size(&dkg_package, &signature) .map_err(|_| Error::::Round3PackageInvalidProof)?; Self::register_latest_activity(&dkg_package)?; let DkgPackage::Round3(round3_complaints) = dkg_package else { return Err(Error::::Round3PackageInvalidProof.into()); }; let complaints_bitmap = ParticipantsBitmap::::from_bitmask( &round3_complaints.indices ); let complaints_count: AuthIndex = complaints_bitmap.count_ones(); let state = QualificationDkgState::::get(&network_curve); ensure!(state.is_third_phase(), Error::::DkgWrongRound); Complaints::::try_mutate( &network_curve, |complaints| -> DispatchResult { ensure!( !complaints.contains_key(&authority_index), Error::::Round3PackagesAlreadyRegistere, ); complaints.try_insert(authority_index, complaints_bitmap) .map_err(|_| Error::::TooManyPackages)?; Ok(()) })?; Self::deposit_event(Event::::ComplaintsRegistered { authority_index, complaints_count, network_curve, }); Ok(()) } #[pallet::call_index(4)] #[pallet::weight(( T::WeightInfo::register_round4_justifications( dkg_package.bytes_len(), ), DispatchClass::Normal, Pays::No, ))] pub fn register_round4_justifications( origin: OriginFor, dkg_package: DkgPackage, signature: ::Signature, ) -> DispatchResult { ensure_none(origin)?; let network_curve = dkg_package.get_network_curve(); let authority_index = dkg_package.get_authority_index(); Self::validate_round4_justifications_size(&dkg_package, &signature) .map_err(|_| Error::::Round4PackageInvalidProof)?; Self::register_latest_activity(&dkg_package)?; let DkgPackage::Round4(round4_bundle) = dkg_package else { return Err(Error::::Round4PackageInvalidProof.into()); }; let justifications_bitmap = ParticipantsBitmap::::from_bitmask( &round4_bundle.bundle.bitmask ); let justifications_count: AuthIndex = justifications_bitmap.count_ones(); let state = QualificationDkgState::::get(&network_curve); ensure!(state.is_fourth_phase(), Error::::DkgWrongRound); Justifications::::try_mutate( &network_curve, |justifications| -> DispatchResult { ensure!( !justifications.contains_key(&authority_index), Error::::Round4PackagesAlreadyRegistered, ); justifications.try_insert(authority_index, justifications_bitmap) .map_err(|_| Error::::TooManyPackages)?; Ok(()) })?; Self::deposit_event(Event::::Round4JustificatPackage { justifications_count, authority_index, network_curve, }); let index_key = Self::create_offchain_dkg_key( network_curve, authority_index, ROUND_NUMBER_4, ); sp_io::offchain_index::set( &index_key, &round4_bundle.bundle.encode(), ); Ok(()) } #[pallet::call_index(5)] #[pallet::weight(( T::WeightInfo::register_round5_verifying_package(), DispatchClass::Normal, Pays::No, ))] pub fn register_round5_verifying_package( origin: OriginFor, dkg_package: DkgPackage, signature:::Signature, ) -> DispatchResult { ensure_none(origin)?; let network_curve = dkg_package.get_network_curve(); let authority_index = dkg_package.get_authority_index(); Self::validate_round5_package_size(&dkg_package, &signature) .map_err(|_| Error::::Round5PackageInvalidProof)?; Self::register_latest_activity(&dkg_package)?; let DkgPackage::Round5(round5) = dkg_package else { return Err(Error::::Round5PackageInvalidProof.into()); }; let metadata_hashes = [ SubstrateBlake2Hasher::hash(round5.merkle_root.as_ref()), SubstrateBlake2Hasher::hash(round5.verifying_key.as_ref()), ]; let verifying_hash = sequential_hash::(metadata_hashes); let state = QualificationDkgState::::get(&network_curve); ensure!(state.is_fifth_phase(), Error::::DkgWrongRound); let dkg_index = state.get_dkg_index(); let verification_key = (network_curve, dkg_index, verifying_hash); let latest_participants = Verifications::::try_mutate( &verification_key, |bitmap| -> Result, DispatchError> { ensure!( !bitmap.contains(authority_index), Error::::Round5PackagesAlreadyRegistered, ); if bitmap.is_empty() { *bitmap = ParticipantsBitmap::::empty_from( state.get_indexes(), ); } bitmap.insert(authority_index); Ok(bitmap.clone()) })?; VerifyingKeyConsensus::::mutate(&network_curve, &dkg_index, |state| { let state_participants_count = state.participants.count_ones::(); let latest_participants_count = latest_participants.count_ones::(); if state_participants_count < latest_participants_count { *state = ConsensusState::new( round5.verifying_key, latest_participants, round5.merkle_root, ); } }); Self::deposit_event(Event::::Round5PublicPackageRegistered { authority_index, verifying_hash, network_curve, }); Ok(()) } #[pallet::call_index(6)] #[pallet::weight(( T::WeightInfo::register_round6_public_share_package( dkg_package.bytes_len(), ), DispatchClass::Normal, Pays::No, ))] pub fn register_round6_public_share_package( origin: OriginFor, dkg_package: DkgPackage, signature:::Signature, ) -> DispatchResult { ensure_none(origin)?; let network_curve = dkg_package.get_network_curve(); let authority_index = dkg_package.get_authority_index(); Self::validate_round6_package_size(&dkg_package, &signature) .map_err(|_| Error::::Round6PackageInvalidProof)?; Self::register_latest_activity(&dkg_package)?; let DkgPackage::Round6(round6) = dkg_package else { return Err(Error::::Round6PackageInvalidProof.into()); }; let expected_share_len = network_curve.element_bytes_len(); ensure!( round6.verifying_share.len() == expected_share_len, Error::::Round6PackageInvalidProof, ); let state = QualificationDkgState::::get(&network_curve); ensure!(state.is_sixth_phase(), Error::::DkgWrongRound); let dkg_index = state.get_dkg_index(); let consensus = VerifyingKeyConsensus::::get(&network_curve, &dkg_index); network_curve.verify_merkle_proof( &round6.verifying_share, &round6.merkle_proof, consensus.merkle_root, authority_index, ).map_err(|_| Error::::InvalidMerkleProof)?; let verifying_share_key = (network_curve, dkg_index, authority_index); ensure!( !VerifyingShares::::contains_key(&verifying_share_key), Error::::Round6PackagesAlreadyRegistered ); VerifyingSharesParticipants::::try_mutate( &network_curve, &dkg_index, |participants| -> DispatchResult { ensure!( !participants.contains(authority_index), Error::::Round6PackagesAlreadyRegistered ); if participants.is_empty() { *participants = ParticipantsBitmap::::empty_from( &state.get_indexes() ); } participants.insert(authority_index); Ok(()) })?; VerifyingShares::::insert(&verifying_share_key, round6.verifying_share); Self::deposit_event(Event::::Round6VerifyingShareRegistered { authority_index, network_curve, }); Ok(()) } #[pallet::call_index(7)] #[pallet::weight(( T::WeightInfo::register_nonce_commitment(), DispatchClass::Normal, Pays::No, ))] pub fn register_nonce_commitment( origin: OriginFor, exodus_package: ExodusPackage, signature:::Signature, ) -> DispatchResult { ensure_none(origin)?; let network_curve = exodus_package.get_network_curve(); let authority_index = exodus_package.get_authority_index(); let (mut exodus_request, roast_session) = Self::validate_exodus_nonce_commitment_size_and_get_metadata( &exodus_package, &signature, ).map_err(|_| Error::::ExodusNoncePackageInvalidProof)?; Self::register_latest_activity(&exodus_package)?; let ExodusPackage::NonceCommitment(package) = exodus_package else { return Err(Error::::ExodusNoncePackageInvalidProof.into()); }; let threshold = ActiveAuthorities::::decode_len(&network_curve) .map(|max_participants| get_byzantium_threshold(max_participants)) .ok_or(Error::::DkgAuthoritiesNotInitialized)?; let exodus_session = package.session; ensure!( !RoastSessions::::contains_key(&exodus_session, authority_index), Error::::ExodusAlreadyInRoastSession, ); let mut roast_state = RoastSessionStates::::get(&exodus_session, roast_session); ensure!( roast_state.status == RoastStatus::NonceCommitments, Error::::ExodusIncorrectRoastStatus, ); ensure!( !roast_state.nonce_committers.contains(authority_index), Error::::ExodusNonceAlreadyRegistered, ); ensure!( !roast_state.group_committers.contains(authority_index), Error::::ExodusGroupAlreadyRegistered, ); ensure!( !roast_state.partial_signers.contains(authority_index), Error::::ExodusSignedMessageAlreadyRegistered, ); roast_state.nonce_committers.insert(authority_index); if roast_state.nonce_committers.count_ones::() == threshold { roast_state.status = RoastStatus::GroupCommitments; exodus_request.next_roast_session = roast_session .saturating_add(1); ExodusRequests::::insert(&network_curve, &exodus_session, exodus_request); } RoastSessions::::insert(&exodus_session, &authority_index, roast_session); RoastSessionStates::::insert(&exodus_session, &roast_session, roast_state); NonceCommitments::::insert( (exodus_session, roast_session), authority_index, ExodusSeparatedNonce::new( package.hiding_commitment, package.binding_commitment, ), ); Self::deposit_event(Event::::NonceCommitmentRegistered { authority_index, exodus_session, network_curve, roast_session, }); Ok(()) } #[pallet::call_index(8)] #[pallet::weight(( T::WeightInfo::register_group_commitment(), DispatchClass::Normal, Pays::No, ))] pub fn register_group_commitment( origin: OriginFor, exodus_package: ExodusPackage, signature:::Signature, ) -> DispatchResult { ensure_none(origin)?; let network_curve = exodus_package.get_network_curve(); let authority_index = exodus_package.get_authority_index(); let roast_session = Self::validate_exodus_group_commitment_size_and_get_metadata( &exodus_package, &signature, ).map_err(|_| Error::::ExodusGroupPackageInvalidProof)?; Self::register_latest_activity(&exodus_package)?; let ExodusPackage::GroupCommitment(package) = exodus_package else { return Err(Error::::ExodusGroupPackageInvalidProof.into()); }; let max_authorities = ActiveAuthorities::::decode_len(&network_curve) .ok_or(Error::::DkgAuthoritiesNotInitialized)?; let threshold = get_byzantium_threshold(max_authorities); let exodus_session = package.session; ensure!( ExodusRequests::::contains_key(&network_curve, &exodus_session), Error::::ExodusRequestNotFound ); let group_commitment_key =( exodus_session, roast_session, package.binding_factors_root, &package.group_commitment ); let mut group_participants = GroupCommitters::::try_mutate( &group_commitment_key, |participants| -> Result, DispatchError> { ensure!( !participants.contains(authority_index), Error::::ExodusGroupAlreadyRegistered, ); if participants.is_empty() { *participants = ParticipantsBitmap::::empty(max_authorities); } participants.insert(authority_index); Ok(participants.clone()) })?; let consensus_reached = group_participants .count_ones::() .ge(&threshold.div_ceil(2)); let mut roast_state = RoastSessionStates::::get(&exodus_session, roast_session); ensure!( consensus_reached || roast_state.status == RoastStatus::GroupCommitments, Error::::ExodusIncorrectRoastStatus, ); ensure!( roast_state.nonce_committers.contains(authority_index), Error::::ExodusNonceNotRegistered, ); ensure!( !roast_state.partial_signers.contains(authority_index), Error::::ExodusSignedMessageAlreadyRegistered, ); group_participants.insert(authority_index); roast_state.group_committers.insert(authority_index); let group_participants_count = group_participants.count_ones::(); if group_participants_count >= threshold.div_ceil(2) { roast_state.group_committers |= &roast_state.nonce_committers; roast_state.status = RoastStatus::PartialSignatures; } RoastSessionStates::::insert(&exodus_session, &roast_session, roast_state); GroupCommitters::::insert(&group_commitment_key, group_participants.clone()); GroupCommitmentsConsensus::::mutate(&exodus_session, &roast_session, |consensus| { let consensus_count = consensus.participants .count_ones::(); if consensus_count < group_participants_count { *consensus = ConsensusState::new( package.group_commitment, group_participants, package.binding_factors_root, ); } }); Self::deposit_event(Event::::GroupCommitmentRegistered { network_curve, exodus_session, authority_index, roast_session }); Ok(()) } #[pallet::call_index(9)] #[pallet::weight(( T::WeightInfo::register_signature_share(), DispatchClass::Normal, Pays::No, ))] pub fn register_signature_share( origin: OriginFor, exodus_package: ExodusPackage, signature:::Signature, ) -> DispatchResult { ensure_none(origin)?; let network_curve = exodus_package.get_network_curve(); let authority_index = exodus_package.get_authority_index(); let (roast_session, active_dkg_index) = Self::validate_exodus_signature_share_size_and_get_metadata( &exodus_package, &signature, ).map_err(|_| Error::::ExodusSharePackageInvalidProof)?; Self::register_latest_activity(&exodus_package)?; let ExodusPackage::SignatureShare(package) = exodus_package else { return Err(Error::::ExodusSharePackageInvalidProof.into()); }; let threshold = ActiveAuthorities::::decode_len(&network_curve) .map(|max_participants| get_byzantium_threshold(max_participants)) .ok_or(Error::::DkgAuthoritiesNotInitialized)?; let exodus_session = package.session; let commitment_consensus = GroupCommitmentsConsensus::::get(&exodus_session, &roast_session); let consensus_participants_count = commitment_consensus .participants .count_ones::(); ensure!( consensus_participants_count >= threshold.div_ceil(2), Error::::ExodusIncorrectRoastStatus, ); let exodus_request = ExodusRequests::::get(&network_curve, &exodus_session) .ok_or(Error::::ExodusRequestNotFound)?; ensure!( match exodus_request.r#type { ExodusRequestType::EvmRotation { .. } => { !ExodusSignedRotations::::contains_key( (exodus_session, NetworkType::Evm), active_dkg_index, ) }, ExodusRequestType::UtxoRotation => { !ExodusSignedRotations::::contains_key( (exodus_session, NetworkType::Utxo), active_dkg_index, ) } ExodusRequestType::EvmBridgeOut { network_id, .. } | ExodusRequestType::UtxoBridgeOut { network_id, .. } => { !ExodusSignedBridges::::contains_key( (exodus_session, network_id), active_dkg_index, ) } ExodusRequestType::EvmGovernance { network_id, .. } => { !ExodusSignedGovernance::::contains_key( (exodus_session, network_id), active_dkg_index, ) } }, Error::::ExodusSignedMessageAlreadyExists, ); let mut roast_state = RoastSessionStates::::get(&exodus_session, roast_session); ensure!( roast_state.status == RoastStatus::PartialSignatures, Error::::ExodusIncorrectRoastStatus, ); ensure!( roast_state.nonce_committers.contains(authority_index), Error::::ExodusNonceNotRegistered, ); ensure!( roast_state.group_committers.contains(authority_index), Error::::ExodusCommitmentNotRegistered, ); ensure!( !roast_state.partial_signers.contains(authority_index), Error::::ExodusSignedMessageAlreadyRegistered, ); network_curve.verify_merkle_proof( &package.self_binding_factor, &package.binding_factors_proof, commitment_consensus.merkle_root, authority_index, ).map_err(|_| Error::::InvalidMerkleProof)?; let nonce_commitment = NonceCommitments::::get((exodus_session, roast_session), authority_index) .ok_or(Error::::ExodusNonceNotRegistered)?; let verifying_share = VerifyingShares::::get((network_curve, active_dkg_index, authority_index)) .ok_or(Error::::VerifyingShareNotFound)?; let verifying_key = ActiveVerifyingKey::::get(network_curve); let mut message_buffer = [0u8; MAX_MESSAGE_SIZE as usize]; let message = exodus_request.get_message(&mut message_buffer) .ok_or(Error::::ExodusMessageTooBig)?; let invalid_signature_share = network_curve.verify_signature_share( authority_index, roast_state.group_committers.iter(), &package.signature_share, &package.self_binding_factor, &nonce_commitment.hiding, &nonce_commitment.binding, &commitment_consensus.element_bytes, &verifying_share, &verifying_key, message, ).is_err(); if invalid_signature_share { #[cfg(not(feature = "runtime-benchmarks"))] return Err(Error::::ExodusInvalidSignatureShare.into()); } let signature_scalar = SignatureScalars::::get(exodus_session, roast_session); let new_signature_scalar = network_curve .accumulate_signature_scalar( &signature_scalar[..network_curve.scalar_bytes_len()], &package.signature_share, ) .map_err(|_| Error::::ExodusAccumulationFailed)?; roast_state.partial_signers.insert(authority_index); let bounded_signature_scalar = BoundedVec::>::try_from(new_signature_scalar) .map_err(|_| Error::::TooManyEntries)?; let exodus_signed_message = ExodusSignedMessage::new( commitment_consensus.element_bytes.iter().copied::(), bounded_signature_scalar.iter().copied::(), exodus_request.r#type.clone(), ).ok_or(Error::::TooManyEntries)?; Self::deposit_event(Event::::PartialSignatureRegistered { authority_index, exodus_session, network_curve, roast_session }); RoastSessions::::remove(&exodus_session, authority_index); if roast_state.partial_signers.count_ones::() < threshold { RoastSessionStates::::insert(&exodus_session, &roast_session, roast_state); SignatureScalars::::insert( exodus_session, roast_session, bounded_signature_scalar, ); return Ok(()); } RoastSessionStates::::remove(&exodus_session, &roast_session); SignatureScalars::::remove(&exodus_session, &roast_session); GroupCommitmentsConsensus::::remove(&exodus_session, &roast_session); GroupCommitters::::remove(( &exodus_session, &roast_session, &commitment_consensus.merkle_root, &commitment_consensus.element_bytes, )); let invalid_aggregated_signature = network_curve .is_signature_valid( &verifying_key, &commitment_consensus.element_bytes, &bounded_signature_scalar, message, ) .is_err(); if invalid_aggregated_signature { #[cfg(not(feature = "runtime-benchmarks"))] return Ok(()); } ExodusRequests::::remove(&network_curve, &exodus_session); match exodus_request.r#type { ExodusRequestType::EvmRotation { .. } => { ExodusSignedRotations::::insert( (exodus_session, NetworkType::Evm), active_dkg_index, exodus_signed_message, ); }, ExodusRequestType::UtxoRotation => { ExodusSignedRotations::::insert( (exodus_session, NetworkType::Utxo), active_dkg_index, exodus_signed_message, ); } ExodusRequestType::EvmBridgeOut { network_id, .. } | ExodusRequestType::UtxoBridgeOut { network_id, .. } => { ExodusSignedBridges::::insert( (exodus_session, network_id), active_dkg_index, exodus_signed_message, ); } ExodusRequestType::EvmGovernance { network_id, .. } => { ExodusSignedGovernance::::insert( (exodus_session, network_id), active_dkg_index, exodus_signed_message, ); } } Ok(()) } #[pallet::call_index(10)] #[pallet::weight(T::WeightInfo::register_evm_bridge_out_exodus())] pub fn register_evm_bridge_out_exodus( origin: OriginFor, network_id: NetworkIdOf, amount: BalanceOf, bounty: Perbill, receiver: EvmAddress, ) -> DispatchResult { let who = ensure_signed(origin)?; let _imbalance = T::Currency::withdraw( &who, amount, WithdrawReasons::TRANSFER, ExistenceRequirement::AllowDeath, )?; Self::do_register_evm_bridge_out_exodus( network_id, amount, bounty, receiver, )?; Self::deposit_event(Event::::EvmBridgeOutRegistered { who, network_id, amount, bounty, receiver, }); Ok(()) } } #[pallet::hooks] impl Hooks> for Pallet { fn on_initialize(current_block: BlockNumberFor) -> Weight { let mut weight = T::DbWeight::get().reads(1); let converted_block: usize = current_block.unique_saturated_into(); let network_curve = match T::NetworkDataHandler::network_for_block(converted_block) { Some((_, network)) => network.curve, None => return weight, }; weight.saturating_accrue(T::DbWeight::get().reads(1)); let mut dkg_qualification = QualificationDkgState::::get(&network_curve); if !dkg_qualification.is_dkg_finalized() { let qualification_len = match QualificationAuthorities::::decode_len(&network_curve) { Some(total_authorities) => total_authorities as AuthIndex, None => return weight, }; let min_threshold = get_byzantium_threshold(qualification_len); weight.saturating_accrue(T::DbWeight::get().reads(1)); let maybe_cursor = DkgMaybeCursor::::get(&network_curve); let converted_round_period: BlockNumberFor = T::DkgRoundPeriod::get().unique_saturated_into(); let release_block = dkg_qualification .get_block() .saturating_add(converted_round_period); if maybe_cursor.is_none() && current_block < release_block { return weight; } let removal_result = match dkg_qualification.get_phase() { DkgPhase::Vacant => DkgRemovalResult::new(|| Self::prepare_round0_storage(network_curve, &mut dkg_qualification, maybe_cursor)), DkgPhase::Round0 => DkgRemovalResult::new(|| Self::prepare_round1_storage(network_curve, &mut dkg_qualification, maybe_cursor)), DkgPhase::Round1 => DkgRemovalResult::new(|| Self::prepare_round2_storage(network_curve, &mut dkg_qualification, maybe_cursor)), DkgPhase::Round2 => DkgRemovalResult::new(|| Self::prepare_round3_storage(network_curve, &mut dkg_qualification, maybe_cursor)), DkgPhase::Round3 => DkgRemovalResult::new(|| Self::prepare_round4_storage(network_curve, min_threshold, &mut dkg_qualification, maybe_cursor)), DkgPhase::Round4 => DkgRemovalResult::new(|| Self::prepare_round5_storage(network_curve, min_threshold, &mut dkg_qualification, maybe_cursor)), DkgPhase::Round5 => DkgRemovalResult::new(|| Self::prepare_round6_storage(network_curve, &mut dkg_qualification, maybe_cursor)), DkgPhase::Round6 => DkgRemovalResult::new(|| Self::prepare_round7_storage(network_curve, min_threshold, &mut dkg_qualification, maybe_cursor)), DkgPhase::Round7 => DkgRemovalResult::new(|| Self::prepare_finalization_storage(network_curve, &mut dkg_qualification, maybe_cursor)), DkgPhase::Finalized => return weight, }; weight.saturating_accrue(removal_result.used_weight()); if !removal_result.fully_cleared() { return weight; } let threshold_reached = min_threshold <= dkg_qualification.count_ones::(); if threshold_reached || dkg_qualification.is_dkg_vacant() { dkg_qualification.next_phase(); } else { dkg_qualification.restart_dkg(); } dkg_qualification.set_block(current_block); QualificationDkgState::::insert(&network_curve, dkg_qualification); weight.saturating_accrue(T::DbWeight::get().writes(1)) } weight } fn offchain_worker(current_block: BlockNumberFor) { if !sp_io::offchain::is_validator() { return; } log::warn!(target: PALLET_LOG_TARGET, "🗺️ Exodus starting at block #{:?}", current_block); match Self::start_exodus(current_block) { Ok(inner_results) => { for result in inner_results { match result { Ok(action) => log::warn!(target: PALLET_LOG_TARGET, "🗺️ Exodus action '{}' at block #{:?} finished.", action, current_block), Err(error) => log::warn!(target: PALLET_LOG_TARGET, "🗺️ Exodus failed at block #{:?}: {:?}.", current_block, error), } } }, Err(err) => { log::error!(target: PALLET_LOG_TARGET, "🗺️ Exodus skipped or failed initialization at {:?}: {:?}", current_block, err); } } } } #[pallet::validate_unsigned] impl ValidateUnsigned for Pallet { type Call = Call; fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity { match call { Call::register_round0_package { dkg_package, signature } => { let network_curve = dkg_package.get_network_curve(); let authority_index = dkg_package.get_authority_index(); Self::validate_round0_package_size(dkg_package, signature)?; ValidTransaction::with_tag_prefix("GhostExodus") .priority(T::UnsignedPriority::get()) .longevity(T::UnsignedLongevity::get()) .and_provides((network_curve, authority_index)) .propagate(true) .build() }, Call::register_round1_package { dkg_package, signature } => { let network_curve = dkg_package.get_network_curve(); let authority_index = dkg_package.get_authority_index(); Self::validate_round1_package_size(dkg_package, signature)?; ValidTransaction::with_tag_prefix("GhostExodus") .priority(T::UnsignedPriority::get()) .longevity(T::UnsignedLongevity::get()) .and_provides((network_curve, authority_index)) .propagate(true) .build() }, Call::register_encrypted_round2_packages { dkg_package, signature } => { let network_curve = dkg_package.get_network_curve(); let authority_index = dkg_package.get_authority_index(); Self::validate_round2_package_size( dkg_package, signature, )?; ValidTransaction::with_tag_prefix("GhostExodus") .priority(T::UnsignedPriority::get()) .longevity(T::UnsignedLongevity::get()) .and_provides((network_curve, authority_index)) .propagate(true) .build() }, Call::register_round3_complaints { dkg_package, signature } => { let network_curve = dkg_package.get_network_curve(); let authority_index = dkg_package.get_authority_index(); Self::validate_round3_complaints_size(dkg_package, signature)?; ValidTransaction::with_tag_prefix("GhostExodus") .priority(T::UnsignedPriority::get()) .longevity(T::UnsignedLongevity::get()) .and_provides((network_curve, authority_index)) .propagate(true) .build() }, Call::register_round4_justifications { dkg_package, signature } => { let network_curve = dkg_package.get_network_curve(); let authority_index = dkg_package.get_authority_index(); Self::validate_round4_justifications_size(dkg_package, signature)?; ValidTransaction::with_tag_prefix("GhostExodus") .priority(T::UnsignedPriority::get()) .longevity(T::UnsignedLongevity::get()) .and_provides((network_curve, authority_index)) .propagate(true) .build() }, Call::register_round5_verifying_package { dkg_package, signature } => { let network_curve = dkg_package.get_network_curve(); let authority_index = dkg_package.get_authority_index(); Self::validate_round5_package_size(dkg_package, signature)?; ValidTransaction::with_tag_prefix("GhostExodus") .priority(T::UnsignedPriority::get()) .longevity(T::UnsignedLongevity::get()) .and_provides((network_curve, authority_index)) .propagate(true) .build() }, Call::register_round6_public_share_package { dkg_package, signature } => { let network_curve = dkg_package.get_network_curve(); let authority_index = dkg_package.get_authority_index(); Self::validate_round6_package_size(dkg_package, signature)?; ValidTransaction::with_tag_prefix("GhostExodus") .priority(T::UnsignedPriority::get()) .longevity(T::UnsignedLongevity::get()) .and_provides((network_curve, authority_index)) .propagate(true) .build() }, Call::register_nonce_commitment { exodus_package, signature } => { let network_curve = exodus_package.get_network_curve(); let authority_index = exodus_package.get_authority_index(); let exodus_session = exodus_package.get_exodus_session(); let (_, roast_session) = Self::validate_exodus_nonce_commitment_size_and_get_metadata( exodus_package, signature, )?; let nonce_metadata = (network_curve, authority_index, exodus_session, roast_session); ValidTransaction::with_tag_prefix("GhostExodus") .priority(T::UnsignedPriority::get()) .longevity(T::UnsignedLongevity::get()) .and_provides(nonce_metadata) .propagate(true) .build() }, Call::register_group_commitment { exodus_package, signature } => { let network_curve = exodus_package.get_network_curve(); let authority_index = exodus_package.get_authority_index(); let exodus_session = exodus_package.get_exodus_session(); let roast_session = Self::validate_exodus_group_commitment_size_and_get_metadata( exodus_package, signature, )?; let group_metadata = (network_curve, authority_index, exodus_session, roast_session); ValidTransaction::with_tag_prefix("GhostExodus") .priority(T::UnsignedPriority::get()) .longevity(T::UnsignedLongevity::get()) .and_provides(group_metadata) .propagate(true) .build() } Call::register_signature_share { exodus_package, signature } => { let network_curve = exodus_package.get_network_curve(); let authority_index = exodus_package.get_authority_index(); let exodus_session = exodus_package.get_exodus_session(); let (roast_session, _) = Self::validate_exodus_signature_share_size_and_get_metadata( exodus_package, signature, )?; let signature_share_metadata = (network_curve, authority_index, exodus_session, roast_session); ValidTransaction::with_tag_prefix("GhostExodus") .priority(T::UnsignedPriority::get()) .longevity(T::UnsignedLongevity::get()) .and_provides(signature_share_metadata) .propagate(true) .build() } _ => InvalidTransaction::Call.into(), } } } } impl Pallet { fn do_register_evm_bridge_out_exodus( network_id: NetworkIdOf, amount: BalanceOf, bounty: Perbill, receiver: EvmAddress, ) -> DispatchResult { let network = T::NetworkDataHandler::get(&network_id) .ok_or(Error::::NetworkDoesNotExist)?; ensure!( QualificationDkgState::::get(&network.curve).is_dkg_finalized(), Error::::DkgAuthoritiesInProgress, ); ensure!(network.r#type.is_evm(), Error::::WrongNetworkType); let exodus_session = CurrentExodus::::get(); let request = ExodusRequest::evm_bridge_out( exodus_session, network_id, amount, bounty, receiver ); ExodusRequests::::insert(&network.curve, &exodus_session, request); CurrentExodus::::put(exodus_session.saturating_add(1)); Ok(()) } fn prepare_round_storage( network_curve: NetworkCurve, maybe_cursor: Option>, clear_fn: ClearFn, on_complete_fn: CompleteFn, ) -> (Weight, bool) where ClearFn: FnOnce(Option<&[u8]>, &mut Weight) -> Option>, CompleteFn: FnOnce(&mut Weight), { let mut weight = T::DbWeight::get().reads_writes(0, 0); let maybe_cursor_slice: Option<&[u8]> = maybe_cursor.as_ref().map(|v| &v[..]); let fully_cleared = match clear_fn(maybe_cursor_slice, &mut weight) { Some(cursor) => { match BoundedVec::::try_from(cursor) { Ok(cursor) => { DkgMaybeCursor::::insert(&network_curve, cursor)}, Err(_) => DkgMaybeCursor::::remove(&network_curve), // never should happen } weight.saturating_accrue(T::DbWeight::get().writes(1)); false }, None => { on_complete_fn(&mut weight); true } }; (weight, fully_cleared) } fn prepare_round0_storage( network_curve: NetworkCurve, dkg_qualification: &mut QualifyingState, maybe_cursor: Option>, ) -> (Weight, bool) { Self::prepare_round_storage( network_curve, maybe_cursor, |cursor_slice, weight| { let result = Round0Packages::::clear( T::RemovalLimit::get(), cursor_slice, ); weight.saturating_accrue( T::DbWeight::get().reads_writes( (result.backend as u64).saturating_add(1), result.backend as u64 ) ); result.maybe_cursor }, |_| { dkg_qualification.nullify_indexes(); }, ) } fn prepare_round1_storage( network_curve: NetworkCurve, dkg_qualification: &mut QualifyingState, maybe_cursor: Option>, ) -> (Weight, bool) { Self::prepare_round_storage( network_curve, maybe_cursor, |_, weight| { let participants_len = dkg_qualification.get_indexes().active_bits_count(); let empty_round1_packages = ParticipantsBitmap::::empty(participants_len); weight.saturating_accrue(T::DbWeight::get().writes(1)); Round1Packages::::insert(network_curve, empty_round1_packages); None }, |_| {} ) } fn prepare_round2_storage( network_curve: NetworkCurve, dkg_qualification: &mut QualifyingState, maybe_cursor: Option>, ) -> (Weight, bool) { let participants_len = dkg_qualification.get_indexes().active_bits_count(); Self::prepare_round_storage( network_curve, maybe_cursor, |_, weight| { weight.saturating_accrue(T::DbWeight::get().writes(1)); let empty_round2_packages = ParticipantsBitmap::::empty(participants_len); Round2Packages::::insert(network_curve, empty_round2_packages); None }, |weight| { weight.saturating_accrue(T::DbWeight::get().reads(1)); let round1_participants = Round1Packages::::get(&network_curve); dkg_qualification.intersect_indexes(&round1_participants); }, ) } pub fn prepare_round3_storage( network_curve: NetworkCurve, dkg_qualification: &mut QualifyingState, maybe_cursor: Option>, ) -> (Weight, bool) { Self::prepare_round_storage( network_curve, maybe_cursor, |_, weight| { weight.saturating_accrue(T::DbWeight::get().writes(1)); Complaints::::remove(network_curve); None }, |weight| { weight.saturating_accrue(T::DbWeight::get().reads(1)); let round2_packages = Round2Packages::::get(&network_curve); dkg_qualification.intersect_indexes(&round2_packages); } ) } pub fn prepare_round4_storage( network_curve: NetworkCurve, min_threshold: AuthIndex, dkg_qualification: &mut QualifyingState, maybe_cursor: Option>, ) -> (Weight, bool) { let dkg_index = dkg_qualification.get_dkg_index(); Self::prepare_round_storage( network_curve, maybe_cursor, |cursor_slice, weight| { let prev_dkg_index = dkg_index.saturating_sub(1); let result = Verifications::::clear_prefix( (network_curve, prev_dkg_index), T::RemovalLimit::get(), cursor_slice, ); weight.saturating_accrue( T::DbWeight::get().reads_writes( (result.backend as u64).saturating_add(1), result.backend as u64 ) ); let maybe_cursor = result.maybe_cursor; if maybe_cursor.is_none() { Justifications::::remove(network_curve); weight.saturating_accrue(T::DbWeight::get().writes(1)); } maybe_cursor }, |weight| { let qualification_indexes = dkg_qualification.get_indexes(); let safe_count = min_threshold.saturating_sub(1); let mut offence_counters = BTreeMap::::new(); let mut compromised_participants = ParticipantsBitmap::::empty_from( &qualification_indexes, ); let mut active_participants = ParticipantsBitmap::::empty_from( &qualification_indexes, ); weight.saturating_accrue(T::DbWeight::get().reads(1)); let complaints = Complaints::::get(&network_curve); complaints.iter().for_each(|(&auth_index, bitmap)| { active_participants.insert(auth_index); bitmap.iter::().for_each(|index| { offence_counters.entry(index) .and_modify(|count| *count = count.saturating_add(1)) .or_insert(1); }) }); offence_counters.iter() .filter(|(_, &count)| count >= safe_count) .for_each(|(&auth_index, _)| { compromised_participants.insert(auth_index); }); let result_mask = qualification_indexes & active_participants & !compromised_participants; dkg_qualification.intersect_indexes(&result_mask); } ) } pub fn prepare_round5_storage( network_curve: NetworkCurve, min_threshold: AuthIndex, dkg_qualification: &mut QualifyingState, maybe_cursor: Option>, ) -> (Weight, bool) { let dkg_index = dkg_qualification.get_dkg_index(); Self::prepare_round_storage( network_curve, maybe_cursor, |_, weight| { let prev_dkg_index = dkg_index.saturating_sub(1); weight.saturating_accrue(T::DbWeight::get().writes(1)); VerifyingKeyConsensus::::remove(network_curve, &prev_dkg_index); None }, |weight| { weight.saturating_accrue(T::DbWeight::get().reads(2)); let justifications_bitmap = Justifications::::get(network_curve); let complaints_bitmap = Complaints::::get(network_curve); let safe_count = min_threshold.saturating_sub(1); let mut number_of_leaked_keys = 0; let qualified_indexes = dkg_qualification.get_indexes(); let mut failed_authorities = ParticipantsBitmap::::empty_from( &qualified_indexes, ); let mut active_authorities = ParticipantsBitmap::::empty_from( &qualified_indexes, ); let mut complaints_matrix: BTreeMap> = BTreeMap::new(); complaints_bitmap.iter().for_each(|(&accuser_index, offenders_bitmap)| { offenders_bitmap.iter().for_each(|offended_index| { complaints_matrix .entry(offended_index) .or_insert_with(|| { ParticipantsBitmap::::empty_from(&qualified_indexes) }) .insert(accuser_index); }); }); justifications_bitmap.iter().for_each(|(&offended_index, recipients_bitmap)| { active_authorities.insert(offended_index); if recipients_bitmap.count_ones::() >= safe_count { number_of_leaked_keys.saturating_inc(); } if let Some(accusers_bitmap) = complaints_matrix.get(&offended_index) { let successful_justifications = accusers_bitmap & recipients_bitmap; let failed_to_justify = successful_justifications != *accusers_bitmap; if failed_to_justify { failed_authorities.insert(offended_index); } } }); let result_mask = if number_of_leaked_keys >= min_threshold { ParticipantsBitmap::::empty_from(&qualified_indexes) } else { active_authorities & !failed_authorities }; dkg_qualification.intersect_indexes(&result_mask); } ) } fn prepare_round6_storage( network_curve: NetworkCurve, dkg_qualification: &mut QualifyingState, maybe_cursor: Option>, ) -> (Weight, bool) { let dkg_index = dkg_qualification.get_dkg_index(); let default_participants = ParticipantsBitmap::::empty_from( &dkg_qualification.get_indexes(), ); Self::prepare_round_storage( network_curve, maybe_cursor, |_, weight| { let Some(prev_dkg_index) = dkg_index.checked_sub(1) else { return None; }; weight.saturating_accrue(T::DbWeight::get().writes(1)); VerifyingSharesParticipants::::insert( &network_curve, &prev_dkg_index, default_participants, ); None }, |weight| { weight.saturating_accrue(T::DbWeight::get().reads(1)); let state = VerifyingKeyConsensus::::get(&network_curve, &dkg_index); dkg_qualification.intersect_indexes(&state.participants); }) } fn prepare_round7_storage( network_curve: NetworkCurve, min_threshold: AuthIndex, dkg_qualification: &mut QualifyingState, maybe_cursor: Option>, ) -> (Weight, bool) { let dkg_index = dkg_qualification.get_dkg_index(); Self::prepare_round_storage( network_curve, maybe_cursor, |_, _| None, |weight| { weight.saturating_accrue(T::DbWeight::get().reads(1)); let participants = VerifyingSharesParticipants::::get(&network_curve, dkg_index); dkg_qualification.intersect_indexes(&participants); if dkg_qualification.count_ones::() < min_threshold { dkg_qualification.nullify_indexes(); return; } weight.saturating_accrue(T::DbWeight::get().reads(1)); let consensus = VerifyingKeyConsensus::::get(&network_curve, &dkg_index); let verifying_key_slice = consensus.element_bytes.as_slice(); weight.saturating_accrue(T::DbWeight::get().reads(2)); let mut exodus_session = CurrentExodus::::get(); for network_type in T::NetworkDataHandler::iter_types_by_curve(&network_curve) { let exodus_request = match network_type { NetworkType::Evm => { if verifying_key_slice.len() < 33 { dkg_qualification.nullify_indexes(); return; } let parity_byte = verifying_key_slice[0]; let parity = if parity_byte % 2 == 0 { 0u8 } else { 1u8 }; let public_key = EvmBytes32::from_slice(&verifying_key_slice[1..33]); ExodusRequest::evm_rotation(exodus_session, public_key, parity) } _ => { continue; } }; weight.saturating_accrue(T::DbWeight::get().writes(1)); ExodusRequests::::insert(&network_curve, &exodus_session, exodus_request); if let None = dkg_qualification .try_push_rotation_session(exodus_session, network_type) { break; } exodus_session = exodus_session.saturating_add(1); } CurrentExodus::::put(exodus_session); weight.saturating_accrue(T::DbWeight::get().writes(1)); dkg_qualification.set_verifying_key(consensus.element_bytes); }) } fn prepare_finalization_storage( network_curve: NetworkCurve, dkg_qualification: &mut QualifyingState, maybe_cursor: Option>, ) -> (Weight, bool) { Self::prepare_round_storage( network_curve, maybe_cursor, |_, _| None, |weight| { weight.saturating_accrue(T::DbWeight::get().reads(2)); let dkg_index = ActiveDkgAuthorities::::get(&network_curve) .get_dkg_index(); let is_initial_dkg_round = !ActiveVerifyingKey::::contains_key(network_curve); let all_rotations_have_signatures = dkg_qualification .iter_rotation_sessions() .all(|rotation_session| { let session = rotation_session.exodus_session; let r#type = rotation_session.network_type; weight.saturating_accrue(T::DbWeight::get().reads(1)); if ExodusRequests::::contains_key(&network_curve, &session) { return false }; weight.saturating_accrue(T::DbWeight::get().reads(1)); ExodusSignedRotations::::contains_key((session, r#type), dkg_index) }); if all_rotations_have_signatures || is_initial_dkg_round { let verifying_key = dkg_qualification.get_verifying_key(); let activated_state: ActivatedState = (&*dkg_qualification).into(); weight.saturating_accrue(T::DbWeight::get().reads(1)); let qualified_authorities = QualificationAuthorities::::get(&network_curve); weight.saturating_accrue(T::DbWeight::get().writes(3)); ActiveDkgAuthorities::::insert(&network_curve, activated_state); ActiveVerifyingKey::::insert(&network_curve, verifying_key); ActiveAuthorities::::set(&network_curve, qualified_authorities); } else { dkg_qualification.nullify_indexes(); } }) } fn register_latest_activity( package_ref: &impl PackageMetadata, ) -> DispatchResult { let authority_index = package_ref.get_authority_index(); let current_block = T::BlockNumberProvider::current_block_number(); ExodusActivity::::try_mutate(&authority_index, |stored_block| -> DispatchResult { ensure!( *stored_block < current_block, Error::::PackagesAlreadyRegistered, ); *stored_block = current_block; Ok(()) })?; Ok(()) } fn validate_round0_package_size( dkg_package: & DkgPackage, signature: &::Signature, ) -> Result<(), InvalidTransaction> { let network_curve = dkg_package.get_network_curve(); let authority_index = dkg_package.get_authority_index(); if !T::NetworkDataHandler::curve_exists(&network_curve) { return Err(InvalidTransaction::BadProof); } let state = QualificationDkgState::::get(&network_curve); if !state.is_zero_phase() { return Err(InvalidTransaction::BadProof); } if state.contains_index(authority_index) { return Err(InvalidTransaction::BadSigner); } let authority = QualificationAuthorities::::get(&network_curve) .get(authority_index as usize) .cloned() .ok_or(InvalidTransaction::BadSigner)?; let encoded_package = dkg_package.encode(); if !authority.verify(&encoded_package, signature) { return Err(InvalidTransaction::BadProof); } Ok(()) } fn validate_round1_package_size( dkg_package: &DkgPackage, signature: &::Signature, ) -> Result<(), InvalidTransaction> { let DkgPackage::Round1(round1) = dkg_package else { return Err(InvalidTransaction::BadProof); }; let network_curve = dkg_package.get_network_curve(); let authority_index = dkg_package.get_authority_index(); if !T::NetworkDataHandler::curve_exists(&network_curve) { return Err(InvalidTransaction::BadProof); } let state = QualificationDkgState::::get(&network_curve); if !state.is_first_phase() { return Err(InvalidTransaction::BadProof); } if !state.contains_index(authority_index) { return Err(InvalidTransaction::BadSigner); } let authorities = QualificationAuthorities::::get(&network_curve); let expected_count = get_byzantium_threshold(authorities.len()); let expected_package_len = round1_package_size( expected_count, network_curve.element_bytes_len(), network_curve.scalar_bytes_len(), network_curve.header_bytes_len(), ); if round1.package.len() != expected_package_len { return Err(InvalidTransaction::BadProof); } let authority = authorities .get(authority_index as usize) .ok_or(InvalidTransaction::BadSigner)?; let encoded_package = dkg_package.encode(); if !authority.verify(&encoded_package, signature) { return Err(InvalidTransaction::BadProof); } Ok(()) } fn validate_round2_package_size( dkg_package: &DkgPackage, signature: &::Signature, ) -> Result<(), InvalidTransaction> { let DkgPackage::Round2(round2) = dkg_package else { return Err(InvalidTransaction::BadProof); }; let network_curve = dkg_package.get_network_curve(); let authority_index = dkg_package.get_authority_index(); if !T::NetworkDataHandler::curve_exists(&network_curve) { return Err(InvalidTransaction::BadProof); } let state = QualificationDkgState::::get(&network_curve); if !state.is_second_phase() { return Err(InvalidTransaction::BadProof); } if !state.contains_index(authority_index) { return Err(InvalidTransaction::BadSigner); } let bundle_packages_count = round2.bundle.bitmask .iter() .map(|byte| byte.count_ones() as usize) .sum::() .saturating_add(1); if bundle_packages_count != state.count_ones::() { return Err(InvalidTransaction::BadProof); } let has_invalid_round2_recipients = round2.bundle.bitmask .iter() .enumerate() .any(|(byte_idx, &byte)| { if byte == 0 { return false; } (0..8).any(|bit_idx| { if (byte & (1u8 << bit_idx)) == 0 { return false; } let global_index = byte_idx * 8 + bit_idx; if global_index == authority_index as usize { return true; } !state.contains_index(global_index) }) }); if has_invalid_round2_recipients { return Err(InvalidTransaction::BadProof); } let padded_participants = state.highest_bit::() .map(|bit_pos| bit_pos.saturating_add(1)) .unwrap_or_default(); let expected_blob_len = round2_encrypted_package_size( padded_participants, network_curve.scalar_bytes_len(), network_curve.header_bytes_len(), ); if round2.bundle.blob.len() != expected_blob_len { return Err(InvalidTransaction::BadProof); } let authority = QualificationAuthorities::::get(&network_curve) .get(authority_index as usize) .cloned() .ok_or(InvalidTransaction::BadSigner)?; let encoded_package = dkg_package.encode(); if !authority.verify(&encoded_package, signature) { return Err(InvalidTransaction::BadProof); } Ok(()) } fn validate_round3_complaints_size( dkg_package: &DkgPackage, signature: &::Signature, ) -> Result<(), InvalidTransaction> { let DkgPackage::Round3(round3_complaints) = dkg_package else { return Err(InvalidTransaction::BadProof); }; let network_curve = dkg_package.get_network_curve(); let authority_index = dkg_package.get_authority_index(); if !T::NetworkDataHandler::curve_exists(&network_curve) { return Err(InvalidTransaction::BadProof); } let state = QualificationDkgState::::get(&network_curve); if !state.is_third_phase() { return Err(InvalidTransaction::BadProof); } if !state.contains_index(authority_index) { return Err(InvalidTransaction::BadSigner); } let estimated_indices_len = state .highest_bit::() .map(|bit_index| bit_index.saturating_add(1).div_ceil(8)) .unwrap_or_default(); if estimated_indices_len != round3_complaints.indices.len() { return Err(InvalidTransaction::BadProof); } let has_invalid_accused_indexes = round3_complaints.indices .iter() .enumerate() .any(|(byte_idx, &byte)| { if byte == 0 { return false; } (0..8).any(|bit_idx| { if (byte & (1u8 << bit_idx)) == 0 { return false; } let global_index = byte_idx * 8 + bit_idx; if global_index == authority_index as usize { return true; } !state.contains_index(global_index) }) }); if has_invalid_accused_indexes { return Err(InvalidTransaction::BadProof); } let authority = QualificationAuthorities::::get(&network_curve) .get(authority_index as usize) .cloned() .ok_or(InvalidTransaction::BadSigner)?; let encoded_package = dkg_package.encode(); if !authority.verify(&encoded_package, signature) { return Err(InvalidTransaction::BadProof); } Ok(()) } fn validate_round4_justifications_size( dkg_package: &DkgPackage, signature: &::Signature, ) -> Result<(), InvalidTransaction> { let DkgPackage::Round4(round4) = dkg_package else { return Err(InvalidTransaction::BadProof); }; let network_curve = dkg_package.get_network_curve(); let authority_index = dkg_package.get_authority_index(); if !T::NetworkDataHandler::curve_exists(&network_curve) { return Err(InvalidTransaction::BadProof); } let state = QualificationDkgState::::get(&network_curve); if !state.is_fourth_phase() { return Err(InvalidTransaction::BadProof); } if !state.contains_index(authority_index) { return Err(InvalidTransaction::BadSigner); } let bundle_packages_count = round4.bundle.bitmask .iter() .map(|byte| byte.count_ones() as usize) .sum::() .saturating_add(1); if bundle_packages_count > state.count_ones::() { return Err(InvalidTransaction::BadProof); } let has_invalid_justifications = round4.bundle.bitmask .iter() .enumerate() .any(|(byte_idx, &byte)| { if byte == 0 { return false; } (0..8).any(|bit_idx| { if (byte & (1u8 << bit_idx)) == 0 { return false; } let global_index = byte_idx * 8 + bit_idx; if global_index == authority_index as usize { return true; } !state.contains_index(global_index) }) }); if has_invalid_justifications { return Err(InvalidTransaction::BadProof); } let padded_participants = state.highest_bit::() .map(|bit_pos| bit_pos.saturating_add(1)) .unwrap_or_default(); let expected_blob_len = round2_package_size( padded_participants, network_curve.scalar_bytes_len(), network_curve.header_bytes_len(), ); if round4.bundle.blob.len() != expected_blob_len { return Err(InvalidTransaction::BadProof); } let authority = QualificationAuthorities::::get(&network_curve) .get(authority_index as usize) .cloned() .ok_or(InvalidTransaction::BadSigner)?; let encoded_package = dkg_package.encode(); if !authority.verify(&encoded_package, signature) { return Err(InvalidTransaction::BadProof); } Ok(()) } fn validate_round5_package_size( dkg_package: &DkgPackage, signature: &::Signature, ) -> Result<(), InvalidTransaction> { let network_curve = dkg_package.get_network_curve(); let authority_index = dkg_package.get_authority_index(); if !T::NetworkDataHandler::curve_exists(&network_curve) { return Err(InvalidTransaction::BadProof); } let state = QualificationDkgState::::get(&network_curve); if !state.is_fifth_phase() { return Err(InvalidTransaction::BadProof); } if !state.contains_index(authority_index) { return Err(InvalidTransaction::BadSigner); } let estimated_max_size = network_curve .element_bytes_len() .saturating_add(ExodusHash::len_bytes()); if dkg_package.bytes_len() as usize != estimated_max_size { return Err(InvalidTransaction::BadProof); } let authority = QualificationAuthorities::::get(&network_curve) .get(authority_index as usize) .cloned() .ok_or(InvalidTransaction::BadSigner)?; let encoded_package = dkg_package.encode(); if !authority.verify(&encoded_package, signature) { return Err(InvalidTransaction::BadProof); } Ok(()) } fn validate_round6_package_size( dkg_package: &DkgPackage, signature: &::Signature, ) -> Result<(), InvalidTransaction> { let network_curve = dkg_package.get_network_curve(); let authority_index = dkg_package.get_authority_index(); if !T::NetworkDataHandler::curve_exists(&network_curve) { return Err(InvalidTransaction::BadProof); } let state = QualificationDkgState::::get(&network_curve); if !state.is_sixth_phase() { return Err(InvalidTransaction::BadProof); } if !state.contains_index(authority_index) { return Err(InvalidTransaction::BadSigner); } let estimated_proof_count = state .count_ones::() .next_power_of_two() .trailing_zeros(); if dkg_package.bytes_len() != estimated_proof_count { return Err(InvalidTransaction::BadProof); } let authority = QualificationAuthorities::::get(&network_curve) .get(authority_index as usize) .cloned() .ok_or(InvalidTransaction::BadSigner)?; let encoded_package = dkg_package.encode(); if !authority.verify(&encoded_package, signature) { return Err(InvalidTransaction::BadProof); } Ok(()) } fn validate_exodus_nonce_commitment_size_and_get_metadata<'a>( exodus_package: &'a ExodusPackage, signature: &::Signature, ) -> Result<(ExodusRequest, BalanceOf>, RoastSession), InvalidTransaction> { let ExodusPackage::NonceCommitment(package) = exodus_package else { return Err(InvalidTransaction::BadProof); }; let network_curve = exodus_package.get_network_curve(); let authority_index = exodus_package.get_authority_index(); if package.hiding_commitment.len() != network_curve.element_bytes_len() { return Err(InvalidTransaction::BadProof); } if package.binding_commitment.len() != network_curve.element_bytes_len() { return Err(InvalidTransaction::BadProof); } if !T::NetworkDataHandler::curve_exists(&network_curve) { return Err(InvalidTransaction::BadProof); } if QualificationDkgState::::get(&network_curve).is_dkg_pending() { return Err(InvalidTransaction::BadProof); } let active_state = ActiveDkgAuthorities::::get(&network_curve); if !active_state.contains_index(authority_index) { return Err(InvalidTransaction::BadSigner); } let authority = ActiveAuthorities::::get(&network_curve) .get(authority_index as usize) .cloned() .ok_or(InvalidTransaction::BadSigner)?; let encoded_package = exodus_package.encode(); if !authority.verify(&encoded_package, signature) { return Err(InvalidTransaction::BadProof); } let exodus_request = ExodusRequests::::get(&network_curve, &package.session) .ok_or(InvalidTransaction::BadProof)?; let roast_session = match RoastSessions::::get(&package.session, &authority_index) { Some(_) => return Err(InvalidTransaction::BadProof), None => exodus_request.next_roast_session, }; Ok((exodus_request, roast_session)) } fn validate_exodus_group_commitment_size_and_get_metadata<'a>( exodus_package: &'a ExodusPackage, signature: &::Signature, ) -> Result { let ExodusPackage::GroupCommitment(package) = exodus_package else { return Err(InvalidTransaction::BadProof); }; let network_curve = exodus_package.get_network_curve(); let authority_index = exodus_package.get_authority_index(); if !T::NetworkDataHandler::curve_exists(&network_curve) { return Err(InvalidTransaction::BadProof); } if package.group_commitment.len() != network_curve.element_bytes_len() { return Err(InvalidTransaction::BadProof); } if QualificationDkgState::::get(&network_curve).is_dkg_pending() { return Err(InvalidTransaction::BadProof); } let active_state = ActiveDkgAuthorities::::get(&network_curve); if !active_state.contains_index(authority_index) { return Err(InvalidTransaction::BadSigner); } let authority = ActiveAuthorities::::get(&network_curve) .get(authority_index as usize) .cloned() .ok_or(InvalidTransaction::BadSigner)?; let encoded_package = exodus_package.encode(); if !authority.verify(&encoded_package, signature) { return Err(InvalidTransaction::BadProof); } RoastSessions::::get(&package.session, &authority_index) .ok_or(InvalidTransaction::BadProof) } fn validate_exodus_signature_share_size_and_get_metadata<'a>( exodus_package: &'a ExodusPackage, signature: &::Signature, ) -> Result<(RoastSession, DkgIndex), InvalidTransaction> { let ExodusPackage::SignatureShare(package) = exodus_package else { return Err(InvalidTransaction::BadProof); }; let network_curve = exodus_package.get_network_curve(); let authority_index = exodus_package.get_authority_index(); if package.signature_share.len() != network_curve.scalar_bytes_len() { return Err(InvalidTransaction::BadProof); } if package.self_binding_factor.len() != network_curve.scalar_bytes_len() { return Err(InvalidTransaction::BadProof); } if !T::NetworkDataHandler::curve_exists(&network_curve) { return Err(InvalidTransaction::BadProof); } let qualification_state = QualificationDkgState::::get(&network_curve); if qualification_state.is_dkg_pending() { return Err(InvalidTransaction::BadProof); } let active_state = ActiveDkgAuthorities::::get(&network_curve); if !active_state.contains_index(authority_index) { return Err(InvalidTransaction::BadSigner); } let authority = ActiveAuthorities::::get(&network_curve) .get(authority_index as usize) .cloned() .ok_or(InvalidTransaction::BadSigner)?; let encoded_package = exodus_package.encode(); if !authority.verify(&encoded_package, signature) { return Err(InvalidTransaction::BadProof); } let roast_session = RoastSessions::::get(&package.session, &authority_index) .ok_or(InvalidTransaction::BadProof)?; Ok((roast_session, active_state.get_dkg_index())) } fn start_exodus( current_block: BlockNumberFor, ) -> ExodusResult>>> { let converted_block: usize = current_block.unique_saturated_into(); let network_curve = T::NetworkDataHandler::network_for_block(converted_block) .map(|(_, network)| network.curve) .ok_or(ExodusError::NoStoredNetworks)?; let qualification = QualificationDkgState::::get(&network_curve); if qualification.is_dkg_pending() { Self::dkg_start(network_curve, current_block, qualification) } else { Self::exodus_start(network_curve, current_block) } } fn exodus_start( network_curve: NetworkCurve, current_block: BlockNumberFor, ) -> ExodusResult>>> { let network_encoded = network_curve.encode(); let exodus_lock_key = Self::create_storage_key(b"exodus-lock-", &network_encoded); let lock_until = rt_offchain::Duration::from_millis(MIN_LOCK_GUARD_PERIOD); let mut exodus_lock = StorageLock::