// 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<T> = BoundedBitmap<BitmapChunk, <T as Config>::MaxAuthoritiesChunks>;
type BitmapByAuthority<T> = BoundedBTreeMap<AuthIndex, ParticipantsBitmap<T>, <T as Config>::MaxAuthorities>;

type QualifyingState<T> = PendingDkgAuthorities<ParticipantsBitmap<T>, BlockNumberFor<T>>;
type ActivatedState<T> = ReadyDkgAuthorities<ParticipantsBitmap<T>>;

pub type BalanceOf<T> =
    <<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;

pub type NetworkIdOf<T> =
	 <<T as Config>::NetworkDataHandler as NetworkDataBasicHandler>::NetworkId;

pub type ValidatorId<T> = <<T as Config>::ValidatorSet as ValidatorSet<
    <T as frame_system::Config>::AccountId,
>>::ValidatorId;

pub type IdentificationTuple<T> = (
    ValidatorId<T>,
    <<T as Config>::ValidatorSet as ValidatorSetWithIdentification<
        <T as frame_system::Config>::AccountId,
    >>::Identification,
);

type ExodusResult<T> = Result<T, ExodusError>;

pub struct MaxAuthoritiesBitmaskSize<T>(sp_std::marker::PhantomData<T>);
impl<T: Config> Get<u32> for MaxAuthoritiesBitmaskSize<T> {
    fn get() -> u32 {
        let max_authorities = T::MaxAuthorities::get();
        max_authorities.div_ceil(8)
    }
}

pub struct Round1MaxBytes<T>(sp_std::marker::PhantomData<T>);
impl<T: Config> Get<u32> for Round1MaxBytes<T> {
    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<T>(sp_std::marker::PhantomData<T>);
impl<T: Config> Get<u32> for Round2BlobMaxBytes<T> {
    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<T>(sp_std::marker::PhantomData<T>);
impl<T: Config> Get<u32> for EncryptedRound2BlobMaxBytes<T> {
    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<T>(sp_std::marker::PhantomData<T>);
impl<T: Config> Get<u32> for AccusedIndicesMaxBytes<T> {
    fn get() -> u32 {
        let max_authorities = T::MaxAuthorities::get();
        max_authorities.saturating_add(7).div_ceil(8)
    }
}

pub struct MerkleProofMaxSize<T>(sp_std::marker::PhantomData<T>);
impl<T: Config> Get<u32> for MerkleProofMaxSize<T> {
    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<T>(sp_std::marker::PhantomData<T>);
impl<T: Config> Get<u32> for BindingFactorsProof<T> {
    fn get() -> u32 {
        let max_authorities = T::MaxAuthorities::get();
        get_byzantium_threshold(max_authorities)
            .next_power_of_two()
            .trailing_zeros()
    }
}

pub struct NonceCommitmentMaxBytes<T>(sp_std::marker::PhantomData<T>);
impl<T: Config> Get<u32> for NonceCommitmentMaxBytes<T> {
    fn get() -> u32 {
        nonce_commitment_package_size(
            ELEMENT_MAX_BYTES as usize,
            HEADER_MAX_BYTES as usize,
        ) as u32
    }
}

pub struct CiphertextMaxBytes<T>(sp_std::marker::PhantomData<T>);
impl<T: Config> Get<u32> for CiphertextMaxBytes<T> {
    fn get() -> u32 {
        encrypted_ciphertext_bytes_len(
            SCALAR_MAX_BYTES as usize,
            HEADER_MAX_BYTES as usize,
        ) as u32
    }
}

pub struct SignatureShareMaxBytes<T>(sp_std::marker::PhantomData<T>);
impl<T: Config> Get<u32> for SignatureShareMaxBytes<T> {
    fn get() -> u32 {
        signature_share_bytes_len(
            SCALAR_MAX_BYTES as usize,
            HEADER_MAX_BYTES as usize,
        ) as u32
    }
}

pub struct SignatureMaxBytes<T>(sp_std::marker::PhantomData<T>);
impl<T: Config> Get<u32> for SignatureMaxBytes<T> {
    fn get() -> u32 {
        signature_bytes_len(
            ELEMENT_MAX_BYTES as usize,
            SCALAR_MAX_BYTES as usize,
        ) as u32
    }
}

pub struct ZeroScalarDefault<T>(sp_std::marker::PhantomData<T>);
impl<T: Config> Get<BoundedVec<u8, ConstU32<{ SCALAR_MAX_BYTES }>>> for ZeroScalarDefault<T> {
    fn get() -> BoundedVec<u8, sp_core::ConstU32<{ SCALAR_MAX_BYTES }>> {
        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<T>(_);

    #[pallet::config]
    pub trait Config: SendTransactionTypes<Call<Self>> + frame_system::Config + core::fmt::Debug {
        type RuntimeEvent: From<Event<Self>>
			+ IsType<<Self as frame_system::Config>::RuntimeEvent>;

        type AuthorityId: Member
            + Parameter
            + RuntimeAppPublic
            + Ord
            + MaybeSerializeDeserialize
            + MaxEncodedLen;

        type ValidatorSet: ValidatorSetWithIdentification<Self::AccountId>;
        type Currency: Currency<Self::AccountId>;

        type NetworkDataHandler: NetworkDataInspectHandler<NetworkData>
            + NetworkDataMutateHandler<NetworkData, BalanceOf<Self>>
			+ NetworkDataBasicHandler<NetworkCurve = NetworkCurve, NetworkType = NetworkType>;

        type BlockNumberProvider: BlockNumberProvider<BlockNumber = BlockNumberFor<Self>>;

        type DisabledValidators: DisabledValidators;

        #[pallet::constant]
        type UnsignedPriority: Get<TransactionPriority>;

        #[pallet::constant]
        type UnsignedLongevity: Get<u64>;

        #[pallet::constant]
        type MaxAuthorities: Get<u32>;

        #[pallet::constant]
        type MaxAuthoritiesChunks: Get<u32>;

        #[pallet::constant]
        type DkgRoundPeriod: Get<u64>;

        #[pallet::constant]
        type RemovalLimit: Get<u32>;

        #[pallet::constant]
        type MaxCursorLen: Get<u32>;

        type WeightInfo: WeightInfo;
    }

    #[pallet::event]
    #[pallet::generate_deposit(pub(super) fn deposit_event)]
    pub enum Event<T: Config> {
        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<T>,
            amount: BalanceOf<T>,
            bounty: Perbill,
            receiver: EvmAddress,
        }
    }

    #[pallet::error]
    pub enum Error<T> {
        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<T: Config> =
		StorageValue<_, ExodusSession, ValueQuery>;

    #[pallet::storage]
    #[pallet::getter(fn exodus_requests)]
    pub(super) type ExodusRequests<T: Config> = StorageDoubleMap<
        _,
        Twox64Concat, NetworkCurve,
        Twox64Concat, ExodusSession,
        ExodusRequest<NetworkIdOf<T>, BalanceOf<T>>,
        OptionQuery,
    >;

    #[pallet::storage]
    #[pallet::getter(fn roast_states)]
    pub(super) type RoastSessionStates<T: Config> = StorageDoubleMap<
        _,
        Twox64Concat, ExodusSession,
        Twox64Concat, RoastSession,
        RoastSessionState<ParticipantsBitmap<T>>,
        ValueQuery,
    >;

    #[pallet::storage]
    #[pallet::getter(fn nonce_commitments)]
    pub(super) type NonceCommitments<T: Config> = StorageDoubleMap<
        _,
        Twox64Concat, (ExodusSession, RoastSession),
        Twox64Concat, AuthIndex,
        ExodusSeparatedNonce,
        OptionQuery,
    >;

    #[pallet::storage]
    #[pallet::getter(fn group_committers)]
    pub(super) type GroupCommitters<T: Config> = StorageNMap<
        _,
         (
        	NMapKey<Twox64Concat, ExodusSession>,
        	NMapKey<Twox64Concat, RoastSession>,
        	NMapKey<Blake2_256, ExodusHash>,
        	NMapKey<Blake2_256, BoundedVec<u8, ConstU32<{ ELEMENT_MAX_BYTES }>>>,
		),
        ParticipantsBitmap<T>,
        ValueQuery,
    >;

    #[pallet::storage]
    #[pallet::getter(fn group_commitments_consensus)]
    pub(super) type GroupCommitmentsConsensus<T: Config> = StorageDoubleMap<
        _,
        Twox64Concat, ExodusSession,
        Twox64Concat, RoastSession,
        ConsensusState<ParticipantsBitmap<T>>,
        ValueQuery,
    >;

    #[pallet::storage]
    #[pallet::getter(fn signature_scalars)]
    pub(super) type SignatureScalars<T: Config> = StorageDoubleMap<
        _,
        Twox64Concat, ExodusSession,
        Twox64Concat, RoastSession,
        BoundedVec<u8, ConstU32<{ SCALAR_MAX_BYTES }>>,
        ValueQuery,
        ZeroScalarDefault<T>,
    >;

    #[pallet::storage]
    #[pallet::getter(fn roast_sessions)]
    pub(super) type RoastSessions<T: Config> = StorageDoubleMap<
        _,
        Twox64Concat, ExodusSession,
        Twox64Concat, AuthIndex,
        RoastSession,
        OptionQuery,
    >;

    #[pallet::storage]
    #[pallet::getter(fn exodus_signatured_rotations)]
    pub(super) type ExodusSignedRotations<T: Config> = StorageDoubleMap<
        _,
        Twox64Concat, (ExodusSession, NetworkType),
        Twox64Concat, DkgIndex,
        ExodusSignedMessage<NetworkIdOf<T>, BalanceOf<T>>,
        OptionQuery,
    >;

    #[pallet::storage]
    #[pallet::getter(fn exodus_signed_bridges)]
    pub(super) type ExodusSignedBridges<T: Config> = StorageDoubleMap<
        _,
        Twox64Concat, (ExodusSession, NetworkIdOf<T>),
        Twox64Concat, DkgIndex,
        ExodusSignedMessage<NetworkIdOf<T>, BalanceOf<T>>,
        OptionQuery,
    >;

    #[pallet::storage]
    #[pallet::getter(fn exodus_signed_governance)]
    pub(super) type ExodusSignedGovernance<T: Config> = StorageDoubleMap<
        _,
        Twox64Concat, (ExodusSession, NetworkIdOf<T>),
        Twox64Concat, DkgIndex,
        ExodusSignedMessage<NetworkIdOf<T>, BalanceOf<T>>,
        OptionQuery,
    >;

    #[pallet::storage]
    #[pallet::getter(fn dkg_maybe_remove_cursor)]
    pub(super) type DkgMaybeCursor<T: Config> = StorageMap<
        _,
        Twox64Concat, NetworkCurve,
        BoundedVec<u8, T::MaxCursorLen>,
        OptionQuery,
    >;

    #[pallet::storage]
    #[pallet::getter(fn exodus_activity)]
    pub(super) type ExodusActivity<T: Config> = StorageMap<
		_,
        Twox64Concat, AuthIndex,
        BlockNumberFor<T>,
        ValueQuery,
	>;

    #[pallet::storage]
    #[pallet::getter(fn round0_packages)]
    pub(super) type Round0Packages<T: Config> = StorageDoubleMap<
        _,
        Twox64Concat, NetworkCurve,
        Twox64Concat, AuthIndex,
        ExodusHash,
        ValueQuery,
    >;

    #[pallet::storage]
    #[pallet::getter(fn round1_packages)]
    pub(super) type Round1Packages<T: Config> = StorageMap<
		_,
        Twox64Concat, NetworkCurve,
        ParticipantsBitmap<T>,
        ValueQuery,
	>;

    #[pallet::storage]
    #[pallet::getter(fn encrypted_round2_packages)]
    pub(super) type Round2Packages<T: Config> = StorageMap<
		_,
        Twox64Concat, NetworkCurve,
        ParticipantsBitmap<T>,
        ValueQuery,
	>;

    #[pallet::storage]
    #[pallet::getter(fn complaints)]
    pub(super) type Complaints<T: Config> = StorageMap<
		_,
        Twox64Concat, NetworkCurve,
        BitmapByAuthority<T>,
        ValueQuery,
	>;

    #[pallet::storage]
    #[pallet::getter(fn justifications)]
    pub(super) type Justifications<T: Config> = StorageMap<
		_,
        Twox64Concat, NetworkCurve,
        BitmapByAuthority<T>,
        ValueQuery,
    >;

    #[pallet::storage]
    #[pallet::getter(fn verifications)]
    pub(super) type Verifications<T: Config> = StorageNMap<
        _,
        (
        	NMapKey<Twox64Concat, NetworkCurve>,
        	NMapKey<Twox64Concat, DkgIndex>,
        	NMapKey<Twox64Concat, ExodusHash>,
		),
        ParticipantsBitmap<T>,
        ValueQuery,
    >;

    #[pallet::storage]
    #[pallet::getter(fn verifying_key_consensus)]
    pub(super) type VerifyingKeyConsensus<T: Config> = StorageDoubleMap<
        _,
        Twox64Concat, NetworkCurve,
        Twox64Concat, DkgIndex,
        ConsensusState<ParticipantsBitmap<T>>,
        ValueQuery,
    >;

    #[pallet::storage]
    #[pallet::getter(fn verifying_shares)]
    pub(super) type VerifyingShares<T: Config> = StorageNMap<
        _,
        (
        	NMapKey<Twox64Concat, NetworkCurve>,
        	NMapKey<Twox64Concat, DkgIndex>,
        	NMapKey<Twox64Concat, AuthIndex>,
		),
        BoundedVec<u8, ConstU32<ELEMENT_MAX_BYTES>>,
        OptionQuery,
    >;

    #[pallet::storage]
    #[pallet::getter(fn verifying_shares_participants)]
    pub(super) type VerifyingSharesParticipants<T: Config> = StorageDoubleMap<
        _,
        Twox64Concat, NetworkCurve,
        Twox64Concat, DkgIndex,
        ParticipantsBitmap<T>,
        ValueQuery,
    >;

    #[pallet::storage]
    #[pallet::getter(fn active_verifying_key)]
    pub(super) type ActiveVerifyingKey<T: Config> = StorageMap<
        _,
        Twox64Concat, NetworkCurve,
        BoundedVec<u8, ConstU32<ELEMENT_MAX_BYTES>>,
        ValueQuery,
    >;

    #[pallet::storage]
    #[pallet::getter(fn active_authorities)]
    pub(super) type ActiveAuthorities<T: Config> = StorageMap<
        _,
        Twox64Concat, NetworkCurve,
        WeakBoundedVec<<T as Config>::AuthorityId, <T as Config>::MaxAuthorities>,
        ValueQuery,
    >;

    #[pallet::storage]
    #[pallet::getter(fn active_dkg_authority)]
    pub(super) type ActiveDkgAuthorities<T: Config> = StorageMap<
        _,
        Twox64Concat, NetworkCurve,
        ActivatedState<T>,
        ValueQuery,
    >;

    #[pallet::storage]
    #[pallet::getter(fn qualification_authorities)]
    pub(super) type QualificationAuthorities<T: Config> = StorageMap<
        _,
        Twox64Concat, NetworkCurve,
        WeakBoundedVec<<T as Config>::AuthorityId, <T as Config>::MaxAuthorities>,
        ValueQuery,
    >;

    #[pallet::storage]
    #[pallet::getter(fn qualification_dkg_state)]
    pub(super) type QualificationDkgState<T: Config> = StorageMap<
        _,
        Twox64Concat, NetworkCurve,
        QualifyingState<T>,
        ValueQuery,
    >;

    #[pallet::genesis_config]
    #[derive(frame_support::DefaultNoBound)]
    pub struct GenesisConfig<T: Config> {
        pub authorities: Vec<T::AuthorityId>,
    }

    #[pallet::genesis_build]
    impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
        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> =
                T::UnsignedLongevity::get().unique_saturated_into();

            let converted_round_period: BlockNumberFor<T> =
                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::<ParticipantsBitmap<T>>(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::<NetworkIdOf<T>, BalanceOf<T>>::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<T: Config> Pallet<T> {
        #[pallet::call_index(0)]
        #[pallet::weight((
            T::WeightInfo::register_round0_package(),
            DispatchClass::Normal,
            Pays::No,
        ))]
        pub fn register_round0_package(
            origin: OriginFor<T>,
            dkg_package: DkgPackage<NetworkCurve, T>,
            signature: <T::AuthorityId as RuntimeAppPublic>::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::<T>::Round0PackageInvalidProof)?;

            Self::register_latest_activity(&dkg_package)?;

            let DkgPackage::Round0(round0) = dkg_package else {
                return Err(Error::<T>::Round0PackageInvalidProof.into());
            };

            QualificationDkgState::<T>::try_mutate(&network_curve,
                |qualification_state| -> DispatchResult {
                    ensure!(
                        qualification_state.is_zero_phase(),
                        Error::<T>::DkgWrongRound,
                    );

                    ensure!(
                        !qualification_state.contains_index(authority_index),
                        Error::<T>::Round0PackageAlreadyRegistered,
                    );

                    qualification_state.insert_index(authority_index);
                    Ok(())
                })?;

            Round0Packages::<T>::insert(
                &network_curve,
                authority_index,
                round0.package_hash,
            );

            Self::deposit_event(Event::<T>::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<T>,
            dkg_package: DkgPackage<NetworkCurve, T>,
            signature: <T::AuthorityId as RuntimeAppPublic>::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::<T>::Round1PackageInvalidProof)?;

            Self::register_latest_activity(&dkg_package)?;

            let DkgPackage::Round1(round1) = dkg_package else {
                return Err(Error::<T>::Round1PackageInvalidProof.into());
            };

            let hash1 = Round0Packages::<T>::get(&network_curve, authority_index);
            let hash2 = ExodusHash::from(blake2_256(&round1.package));
            ensure!(!hash1.is_zero() && hash1 == hash2, Error::<T>::Round1PackageBadHash);

            let estimated_commitment_count =
                network_curve.dkg_verify_proof_of_knowledge(
                    authority_index,
                    &round1.package,
                ).map_err(|_| Error::<T>::Round1PackageInvalidProof)?;

            let state = QualificationDkgState::<T>::get(&network_curve);
            ensure!(state.is_first_phase(), Error::<T>::DkgWrongRound);

            state.count_ones::<AuthIndex>().checked_sub(1)
                .map(|needed_commitment_count| {
                    estimated_commitment_count
                        .eq(&needed_commitment_count)
                        .then(|| ())
                })
                .ok_or(Error::<T>::Round1PackageInvalidLength)?;

            Round1Packages::<T>::try_mutate(
                &network_curve,
                |round1_packages| -> DispatchResult {
                    ensure!(
                        !round1_packages.contains(authority_index),
                        Error::<T>::Round1PackageAlreadyRegistered,
                    );
                    round1_packages.insert(authority_index);
                    Ok(())
                })?;

            Self::deposit_event(Event::<T>::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<T>,
            dkg_package: DkgPackage<NetworkCurve, T>,
            signature: <T::AuthorityId as RuntimeAppPublic>::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::<T>::Round2PackagesInvalidProof)?;

            Self::register_latest_activity(&dkg_package)?;

            let DkgPackage::Round2(round2_bundle) = dkg_package else {
                return Err(Error::<T>::Round2PackagesInvalidProof.into());
            };

            let state = QualificationDkgState::<T>::get(&network_curve);
            ensure!(state.is_second_phase(), Error::<T>::DkgWrongRound);

            let encrypted_indexes = ParticipantsBitmap::<T>::from_bitmask(
                &round2_bundle.bundle.bitmask,
            );

            let diff = state.get_indexes() ^ &encrypted_indexes;
            let only_one_lost = diff.count_ones::<AuthIndex>() == 1;
            let only_authority_index = diff.contains(authority_index);

            ensure!(
                only_one_lost && only_authority_index,
                Error::<T>::Round2PackagesWrongCoefficients,
            );

            Round2Packages::<T>::try_mutate(&network_curve,
                |round2_packages| -> DispatchResult {
                    ensure!(
                        !round2_packages.contains(authority_index),
                        Error::<T>::Round2PackagesAlreadyRegistered,
                    );

                    round2_packages.insert(authority_index);

                    Ok(())
                })?;

            Self::deposit_event(Event::<T>::Round2PackagesRegistered {
                encrypted_count: encrypted_indexes.count_ones::<AuthIndex>(),
                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<T>,
            dkg_package: DkgPackage<NetworkCurve, T>,
            signature: <T::AuthorityId as RuntimeAppPublic>::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::<T>::Round3PackageInvalidProof)?;

            Self::register_latest_activity(&dkg_package)?;

            let DkgPackage::Round3(round3_complaints) = dkg_package else {
                return Err(Error::<T>::Round3PackageInvalidProof.into());
            };

            let complaints_bitmap = ParticipantsBitmap::<T>::from_bitmask(
                &round3_complaints.indices
            );

            let complaints_count: AuthIndex = complaints_bitmap.count_ones();
            let state = QualificationDkgState::<T>::get(&network_curve);
            ensure!(state.is_third_phase(), Error::<T>::DkgWrongRound);

            Complaints::<T>::try_mutate(
                &network_curve,
                |complaints| -> DispatchResult {
                    ensure!(
                        !complaints.contains_key(&authority_index),
                        Error::<T>::Round3PackagesAlreadyRegistere,
                    );

                    complaints.try_insert(authority_index, complaints_bitmap)
                        .map_err(|_| Error::<T>::TooManyPackages)?;

                    Ok(())
                })?;

            Self::deposit_event(Event::<T>::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<T>,
            dkg_package: DkgPackage<NetworkCurve, T>,
            signature: <T::AuthorityId as RuntimeAppPublic>::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::<T>::Round4PackageInvalidProof)?;

            Self::register_latest_activity(&dkg_package)?;

            let DkgPackage::Round4(round4_bundle) = dkg_package else {
                return Err(Error::<T>::Round4PackageInvalidProof.into());
            };

            let justifications_bitmap = ParticipantsBitmap::<T>::from_bitmask(
                &round4_bundle.bundle.bitmask
            );

            let justifications_count: AuthIndex = justifications_bitmap.count_ones();
            let state = QualificationDkgState::<T>::get(&network_curve);
            ensure!(state.is_fourth_phase(), Error::<T>::DkgWrongRound);

            Justifications::<T>::try_mutate(
                &network_curve,
                |justifications| -> DispatchResult {
                    ensure!(
                        !justifications.contains_key(&authority_index),
                        Error::<T>::Round4PackagesAlreadyRegistered,
                    );

                    justifications.try_insert(authority_index, justifications_bitmap)
                        .map_err(|_| Error::<T>::TooManyPackages)?;

                    Ok(())
                })?;

            Self::deposit_event(Event::<T>::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<T>,
            dkg_package: DkgPackage<NetworkCurve, T>,
            signature:<T::AuthorityId as RuntimeAppPublic>::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::<T>::Round5PackageInvalidProof)?;

            Self::register_latest_activity(&dkg_package)?;

            let DkgPackage::Round5(round5) = dkg_package else {
                return Err(Error::<T>::Round5PackageInvalidProof.into());
            };

            let metadata_hashes = [
                SubstrateBlake2Hasher::hash(round5.merkle_root.as_ref()),
                SubstrateBlake2Hasher::hash(round5.verifying_key.as_ref()),
            ];
            let verifying_hash = sequential_hash::<SubstrateBlake2Hasher, _>(metadata_hashes);
            let state = QualificationDkgState::<T>::get(&network_curve);
            ensure!(state.is_fifth_phase(), Error::<T>::DkgWrongRound);

            let dkg_index = state.get_dkg_index();
            let verification_key = (network_curve, dkg_index, verifying_hash);

            let latest_participants = Verifications::<T>::try_mutate(
                &verification_key,
                |bitmap| -> Result<ParticipantsBitmap<T>, DispatchError> {
                    ensure!(
                        !bitmap.contains(authority_index),
                        Error::<T>::Round5PackagesAlreadyRegistered,
                    );

                    if bitmap.is_empty() {
                        *bitmap = ParticipantsBitmap::<T>::empty_from(
                            state.get_indexes(),
                        );
                    }

                    bitmap.insert(authority_index);
                    Ok(bitmap.clone())
                })?;

            VerifyingKeyConsensus::<T>::mutate(&network_curve, &dkg_index, |state| {
                let state_participants_count = state.participants.count_ones::<AuthIndex>();
                let latest_participants_count = latest_participants.count_ones::<AuthIndex>();

                if state_participants_count < latest_participants_count {
                    *state = ConsensusState::new(
                        round5.verifying_key,
                        latest_participants,
                        round5.merkle_root,
                    );
                }
            });

            Self::deposit_event(Event::<T>::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<T>,
            dkg_package: DkgPackage<NetworkCurve, T>,
            signature:<T::AuthorityId as RuntimeAppPublic>::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::<T>::Round6PackageInvalidProof)?;

            Self::register_latest_activity(&dkg_package)?;

            let DkgPackage::Round6(round6) = dkg_package else {
                return Err(Error::<T>::Round6PackageInvalidProof.into());
            };

            let expected_share_len = network_curve.element_bytes_len();
            ensure!(
                round6.verifying_share.len() == expected_share_len,
                Error::<T>::Round6PackageInvalidProof,
            );

            let state = QualificationDkgState::<T>::get(&network_curve);
            ensure!(state.is_sixth_phase(), Error::<T>::DkgWrongRound);

            let dkg_index = state.get_dkg_index();
            let consensus = VerifyingKeyConsensus::<T>::get(&network_curve, &dkg_index);

            network_curve.verify_merkle_proof(
                &round6.verifying_share,
                &round6.merkle_proof,
                consensus.merkle_root,
                authority_index,
            ).map_err(|_| Error::<T>::InvalidMerkleProof)?;

            let verifying_share_key = (network_curve, dkg_index, authority_index);

            ensure!(
                !VerifyingShares::<T>::contains_key(&verifying_share_key),
                Error::<T>::Round6PackagesAlreadyRegistered
            );

            VerifyingSharesParticipants::<T>::try_mutate(
                &network_curve,
                &dkg_index,
                |participants| -> DispatchResult {
                    ensure!(
                        !participants.contains(authority_index),
                        Error::<T>::Round6PackagesAlreadyRegistered
                    );

                    if participants.is_empty() {
                        *participants = ParticipantsBitmap::<T>::empty_from(
                            &state.get_indexes()
                        );
                    }

                    participants.insert(authority_index);
                    Ok(())
                })?;

            VerifyingShares::<T>::insert(&verifying_share_key, round6.verifying_share);

            Self::deposit_event(Event::<T>::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<T>,
            exodus_package: ExodusPackage<NetworkCurve, T>,
            signature:<T::AuthorityId as RuntimeAppPublic>::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::<T>::ExodusNoncePackageInvalidProof)?;

            Self::register_latest_activity(&exodus_package)?;

            let ExodusPackage::NonceCommitment(package) = exodus_package else {
                return Err(Error::<T>::ExodusNoncePackageInvalidProof.into());
            };

            let threshold = ActiveAuthorities::<T>::decode_len(&network_curve)
                .map(|max_participants| get_byzantium_threshold(max_participants))
                .ok_or(Error::<T>::DkgAuthoritiesNotInitialized)?;

            let exodus_session = package.session;

            ensure!(
                !RoastSessions::<T>::contains_key(&exodus_session, authority_index),
                Error::<T>::ExodusAlreadyInRoastSession,
            );

            let mut roast_state = RoastSessionStates::<T>::get(&exodus_session, roast_session);

            ensure!(
                roast_state.status == RoastStatus::NonceCommitments,
                Error::<T>::ExodusIncorrectRoastStatus,
            );

            ensure!(
                !roast_state.nonce_committers.contains(authority_index),
                Error::<T>::ExodusNonceAlreadyRegistered,
            );

            ensure!(
                !roast_state.group_committers.contains(authority_index),
                Error::<T>::ExodusGroupAlreadyRegistered,
            );

            ensure!(
                !roast_state.partial_signers.contains(authority_index),
                Error::<T>::ExodusSignedMessageAlreadyRegistered,
            );

            roast_state.nonce_committers.insert(authority_index);

            if roast_state.nonce_committers.count_ones::<usize>() == threshold {
                roast_state.status = RoastStatus::GroupCommitments;
                exodus_request.next_roast_session = roast_session
                    .saturating_add(1);

                ExodusRequests::<T>::insert(&network_curve, &exodus_session, exodus_request);
            }

            RoastSessions::<T>::insert(&exodus_session, &authority_index, roast_session);
            RoastSessionStates::<T>::insert(&exodus_session, &roast_session, roast_state);
            NonceCommitments::<T>::insert(
                (exodus_session, roast_session), authority_index,
                ExodusSeparatedNonce::new(
                    package.hiding_commitment,
                    package.binding_commitment,
                ),
            );

            Self::deposit_event(Event::<T>::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<T>,
            exodus_package: ExodusPackage<NetworkCurve, T>,
            signature:<T::AuthorityId as RuntimeAppPublic>::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::<T>::ExodusGroupPackageInvalidProof)?;

            Self::register_latest_activity(&exodus_package)?;

            let ExodusPackage::GroupCommitment(package) = exodus_package else {
                return Err(Error::<T>::ExodusGroupPackageInvalidProof.into());
            };

            let max_authorities = ActiveAuthorities::<T>::decode_len(&network_curve)
                .ok_or(Error::<T>::DkgAuthoritiesNotInitialized)?;

            let threshold = get_byzantium_threshold(max_authorities);
            let exodus_session = package.session;

            ensure!(
                ExodusRequests::<T>::contains_key(&network_curve, &exodus_session),
                Error::<T>::ExodusRequestNotFound
            );

            let group_commitment_key =(
				exodus_session,
				roast_session,
				package.binding_factors_root,
				&package.group_commitment
            );

            let mut group_participants = GroupCommitters::<T>::try_mutate(
                &group_commitment_key,
                |participants| -> Result<ParticipantsBitmap<T>, DispatchError> {
                    ensure!(
                        !participants.contains(authority_index),
                        Error::<T>::ExodusGroupAlreadyRegistered,
                    );

                    if participants.is_empty() {
                        *participants = ParticipantsBitmap::<T>::empty(max_authorities);
                    }

                    participants.insert(authority_index);

                    Ok(participants.clone())
                })?;

            let consensus_reached = group_participants
                .count_ones::<usize>()
                .ge(&threshold.div_ceil(2));

            let mut roast_state =
                RoastSessionStates::<T>::get(&exodus_session, roast_session);

            ensure!(
                consensus_reached || roast_state.status == RoastStatus::GroupCommitments,
                Error::<T>::ExodusIncorrectRoastStatus,
            );

            ensure!(
                roast_state.nonce_committers.contains(authority_index),
                Error::<T>::ExodusNonceNotRegistered,
            );

            ensure!(
                !roast_state.partial_signers.contains(authority_index),
                Error::<T>::ExodusSignedMessageAlreadyRegistered,
            );

            group_participants.insert(authority_index);
            roast_state.group_committers.insert(authority_index);

            let group_participants_count = group_participants.count_ones::<usize>();

            if group_participants_count >= threshold.div_ceil(2) {
                roast_state.group_committers |= &roast_state.nonce_committers;
                roast_state.status = RoastStatus::PartialSignatures;
            }

            RoastSessionStates::<T>::insert(&exodus_session, &roast_session, roast_state);
            GroupCommitters::<T>::insert(&group_commitment_key, group_participants.clone());
            GroupCommitmentsConsensus::<T>::mutate(&exodus_session, &roast_session, |consensus| {
                let consensus_count = consensus.participants
                    .count_ones::<usize>();

                if consensus_count < group_participants_count {
                    *consensus = ConsensusState::new(
                        package.group_commitment,
                        group_participants,
                        package.binding_factors_root,
                    );
                }
            });

            Self::deposit_event(Event::<T>::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<T>,
            exodus_package: ExodusPackage<NetworkCurve, T>,
            signature:<T::AuthorityId as RuntimeAppPublic>::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::<T>::ExodusSharePackageInvalidProof)?;

            Self::register_latest_activity(&exodus_package)?;

            let ExodusPackage::SignatureShare(package) = exodus_package else {
                return Err(Error::<T>::ExodusSharePackageInvalidProof.into());
            };

            let threshold = ActiveAuthorities::<T>::decode_len(&network_curve)
                .map(|max_participants| get_byzantium_threshold(max_participants))
                .ok_or(Error::<T>::DkgAuthoritiesNotInitialized)?;

            let exodus_session = package.session;

            let commitment_consensus =
                GroupCommitmentsConsensus::<T>::get(&exodus_session, &roast_session);

            let consensus_participants_count = commitment_consensus
                .participants
                .count_ones::<usize>();

            ensure!(
                consensus_participants_count >= threshold.div_ceil(2),
                Error::<T>::ExodusIncorrectRoastStatus,
            );

            let exodus_request =
                ExodusRequests::<T>::get(&network_curve, &exodus_session)
                    .ok_or(Error::<T>::ExodusRequestNotFound)?;

            ensure!(
                match exodus_request.r#type {
                    ExodusRequestType::EvmRotation { .. } => {
                        !ExodusSignedRotations::<T>::contains_key(
                            (exodus_session, NetworkType::Evm), active_dkg_index,
                        )
                    },
                    ExodusRequestType::UtxoRotation => {
                        !ExodusSignedRotations::<T>::contains_key(
                            (exodus_session, NetworkType::Utxo), active_dkg_index,
                        )
                    }
                    ExodusRequestType::EvmBridgeOut { network_id, .. } | ExodusRequestType::UtxoBridgeOut { network_id, .. } => {
                        !ExodusSignedBridges::<T>::contains_key(
                            (exodus_session, network_id), active_dkg_index,
                        )
                    }
                    ExodusRequestType::EvmGovernance { network_id, .. } => {
                        !ExodusSignedGovernance::<T>::contains_key(
                            (exodus_session, network_id), active_dkg_index,
                        )
                    }
                },
                Error::<T>::ExodusSignedMessageAlreadyExists,
            );

            let mut roast_state =
                RoastSessionStates::<T>::get(&exodus_session, roast_session);

            ensure!(
                roast_state.status == RoastStatus::PartialSignatures,
                Error::<T>::ExodusIncorrectRoastStatus,
            );

            ensure!(
                roast_state.nonce_committers.contains(authority_index),
                Error::<T>::ExodusNonceNotRegistered,
            );

            ensure!(
                roast_state.group_committers.contains(authority_index),
                Error::<T>::ExodusCommitmentNotRegistered,
            );

            ensure!(
                !roast_state.partial_signers.contains(authority_index),
                Error::<T>::ExodusSignedMessageAlreadyRegistered,
            );

            network_curve.verify_merkle_proof(
                &package.self_binding_factor,
                &package.binding_factors_proof,
                commitment_consensus.merkle_root,
                authority_index,
            ).map_err(|_| Error::<T>::InvalidMerkleProof)?;

            let nonce_commitment =
                NonceCommitments::<T>::get((exodus_session, roast_session), authority_index)
                    .ok_or(Error::<T>::ExodusNonceNotRegistered)?;

            let verifying_share =
                VerifyingShares::<T>::get((network_curve, active_dkg_index, authority_index))
                    .ok_or(Error::<T>::VerifyingShareNotFound)?;

            let verifying_key = ActiveVerifyingKey::<T>::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::<T>::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::<T>::ExodusInvalidSignatureShare.into());
            }

            let signature_scalar =
                SignatureScalars::<T>::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::<T>::ExodusAccumulationFailed)?;

            roast_state.partial_signers.insert(authority_index);

            let bounded_signature_scalar =
                BoundedVec::<u8, ConstU32<{ SCALAR_MAX_BYTES }>>::try_from(new_signature_scalar)
                    .map_err(|_| Error::<T>::TooManyEntries)?;

            let exodus_signed_message = ExodusSignedMessage::new(
                commitment_consensus.element_bytes.iter().copied::<u8>(),
                bounded_signature_scalar.iter().copied::<u8>(),
                exodus_request.r#type.clone(),
            ).ok_or(Error::<T>::TooManyEntries)?;

            Self::deposit_event(Event::<T>::PartialSignatureRegistered {
                authority_index,
                exodus_session,
                network_curve,
                roast_session
            });

            RoastSessions::<T>::remove(&exodus_session, authority_index);

            if roast_state.partial_signers.count_ones::<usize>() < threshold {
                RoastSessionStates::<T>::insert(&exodus_session, &roast_session, roast_state);
                SignatureScalars::<T>::insert(
                    exodus_session, roast_session,
                    bounded_signature_scalar,
                );
                return Ok(());
            }

            RoastSessionStates::<T>::remove(&exodus_session, &roast_session);
            SignatureScalars::<T>::remove(&exodus_session, &roast_session);

            GroupCommitmentsConsensus::<T>::remove(&exodus_session, &roast_session);
            GroupCommitters::<T>::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::<T>::remove(&network_curve, &exodus_session);

            match exodus_request.r#type {
                ExodusRequestType::EvmRotation { .. } => {
                    ExodusSignedRotations::<T>::insert(
                        (exodus_session, NetworkType::Evm), active_dkg_index,
                        exodus_signed_message,
                    );
                },
                ExodusRequestType::UtxoRotation => {
                    ExodusSignedRotations::<T>::insert(
                        (exodus_session, NetworkType::Utxo), active_dkg_index,
                        exodus_signed_message,
                    );
                }
                ExodusRequestType::EvmBridgeOut { network_id, .. } | ExodusRequestType::UtxoBridgeOut { network_id, .. } => {
                    ExodusSignedBridges::<T>::insert(
                        (exodus_session, network_id), active_dkg_index,
                        exodus_signed_message,
                    );
                }
                ExodusRequestType::EvmGovernance { network_id, .. } => {
                    ExodusSignedGovernance::<T>::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<T>,
            network_id: NetworkIdOf<T>,
            amount: BalanceOf<T>,
            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::<T>::EvmBridgeOutRegistered {
                who,
                network_id,
                amount,
                bounty,
                receiver,
            });

            Ok(())
        }
    }

    #[pallet::hooks]
    impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
        fn on_initialize(current_block: BlockNumberFor<T>) -> 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::<T>::get(&network_curve);

            if !dkg_qualification.is_dkg_finalized() {
                let qualification_len =
                    match QualificationAuthorities::<T>::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::<T>::get(&network_curve);

                let converted_round_period: BlockNumberFor<T> =
                    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::<AuthIndex>();

                if threshold_reached || dkg_qualification.is_dkg_vacant() {
                    dkg_qualification.next_phase();
                } else {
                    dkg_qualification.restart_dkg();
                }

                dkg_qualification.set_block(current_block);
                QualificationDkgState::<T>::insert(&network_curve, dkg_qualification);

                weight.saturating_accrue(T::DbWeight::get().writes(1))
            }

            weight
        }

        fn offchain_worker(current_block: BlockNumberFor<T>) {
            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<T: Config> ValidateUnsigned for Pallet<T> {
        type Call = Call<T>;
        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<T: Config> Pallet<T> {
    fn do_register_evm_bridge_out_exodus(
        network_id: NetworkIdOf<T>,
        amount: BalanceOf<T>,
        bounty: Perbill,
        receiver: EvmAddress,
    ) -> DispatchResult {
        let network = T::NetworkDataHandler::get(&network_id)
            .ok_or(Error::<T>::NetworkDoesNotExist)?;

        ensure!(
            QualificationDkgState::<T>::get(&network.curve).is_dkg_finalized(),
            Error::<T>::DkgAuthoritiesInProgress,
        );

        ensure!(network.r#type.is_evm(), Error::<T>::WrongNetworkType);

        let exodus_session = CurrentExodus::<T>::get();
        let request = ExodusRequest::evm_bridge_out(
            exodus_session,
            network_id,
            amount,
            bounty,
            receiver
        );

        ExodusRequests::<T>::insert(&network.curve, &exodus_session, request);
        CurrentExodus::<T>::put(exodus_session.saturating_add(1));

        Ok(())
    }

    fn prepare_round_storage<ClearFn, CompleteFn>(
        network_curve: NetworkCurve,
        maybe_cursor: Option<BoundedVec<u8, T::MaxCursorLen>>,
        clear_fn: ClearFn,
        on_complete_fn: CompleteFn,
    ) -> (Weight, bool)
    where
		ClearFn: FnOnce(Option<&[u8]>, &mut Weight) -> Option<Vec<u8>>,
		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::<u8, T::MaxCursorLen>::try_from(cursor) {
                    Ok(cursor) => {
                        DkgMaybeCursor::<T>::insert(&network_curve, cursor)},
                    Err(_) => DkgMaybeCursor::<T>::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<T>,
        maybe_cursor: Option<BoundedVec<u8, T::MaxCursorLen>>,
    ) -> (Weight, bool) {
        Self::prepare_round_storage(
            network_curve,
            maybe_cursor,
            |cursor_slice, weight| {
                let result = Round0Packages::<T>::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<T>,
        maybe_cursor: Option<BoundedVec<u8, T::MaxCursorLen>>,
    ) -> (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::<T>::empty(participants_len);

                weight.saturating_accrue(T::DbWeight::get().writes(1));
                Round1Packages::<T>::insert(network_curve, empty_round1_packages);
                None
            },
            |_| {}
        )
    }

    fn prepare_round2_storage(
        network_curve: NetworkCurve,
        dkg_qualification: &mut QualifyingState<T>,
        maybe_cursor: Option<BoundedVec<u8, T::MaxCursorLen>>,
    ) -> (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::<T>::empty(participants_len);
                Round2Packages::<T>::insert(network_curve, empty_round2_packages);
                None
            },
            |weight| {
                weight.saturating_accrue(T::DbWeight::get().reads(1));
                let round1_participants = Round1Packages::<T>::get(&network_curve);
                dkg_qualification.intersect_indexes(&round1_participants);
            },
        )
    }

    pub fn prepare_round3_storage(
        network_curve: NetworkCurve,
        dkg_qualification: &mut QualifyingState<T>,
        maybe_cursor: Option<BoundedVec<u8, T::MaxCursorLen>>,
    ) -> (Weight, bool) {
        Self::prepare_round_storage(
            network_curve,
            maybe_cursor,
            |_, weight| {
                weight.saturating_accrue(T::DbWeight::get().writes(1));
                Complaints::<T>::remove(network_curve);
                None
            },
            |weight| {
                weight.saturating_accrue(T::DbWeight::get().reads(1));
                let round2_packages = Round2Packages::<T>::get(&network_curve);
                dkg_qualification.intersect_indexes(&round2_packages);
            }
        )
    }

    pub fn prepare_round4_storage(
        network_curve: NetworkCurve,
        min_threshold: AuthIndex,
        dkg_qualification: &mut QualifyingState<T>,
        maybe_cursor: Option<BoundedVec<u8, T::MaxCursorLen>>,
    ) -> (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::<T>::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::<T>::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::<AuthIndex, AuthIndex>::new();

                let mut compromised_participants = ParticipantsBitmap::<T>::empty_from(
                    &qualification_indexes,
                );
                let mut active_participants = ParticipantsBitmap::<T>::empty_from(
                    &qualification_indexes,
                );

                weight.saturating_accrue(T::DbWeight::get().reads(1));
                let complaints = Complaints::<T>::get(&network_curve);

                complaints.iter().for_each(|(&auth_index, bitmap)| {
                    active_participants.insert(auth_index);
                    bitmap.iter::<AuthIndex>().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<T>,
        maybe_cursor: Option<BoundedVec<u8, T::MaxCursorLen>>,
    ) -> (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::<T>::remove(network_curve, &prev_dkg_index);
                None
            },
            |weight| {
                weight.saturating_accrue(T::DbWeight::get().reads(2));
                let justifications_bitmap = Justifications::<T>::get(network_curve);
                let complaints_bitmap = Complaints::<T>::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::<T>::empty_from(
                    &qualified_indexes,
                );

                let mut active_authorities = ParticipantsBitmap::<T>::empty_from(
                    &qualified_indexes,
                );

                let mut complaints_matrix: BTreeMap<AuthIndex, ParticipantsBitmap<T>> =
                    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::<T>::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::<AuthIndex>() >= 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::<T>::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<T>,
        maybe_cursor: Option<BoundedVec<u8, T::MaxCursorLen>>,
    ) -> (Weight, bool) {
        let dkg_index = dkg_qualification.get_dkg_index();
        let default_participants = ParticipantsBitmap::<T>::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::<T>::insert(
                    &network_curve,
                    &prev_dkg_index,
                    default_participants,
                );

                None
            },
            |weight| {
                weight.saturating_accrue(T::DbWeight::get().reads(1));
                let state = VerifyingKeyConsensus::<T>::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<T>,
        maybe_cursor: Option<BoundedVec<u8, T::MaxCursorLen>>,
    ) -> (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::<T>::get(&network_curve, dkg_index);

                dkg_qualification.intersect_indexes(&participants);

                if dkg_qualification.count_ones::<AuthIndex>() < min_threshold {
                    dkg_qualification.nullify_indexes();
                    return;
                }

                weight.saturating_accrue(T::DbWeight::get().reads(1));
                let consensus = VerifyingKeyConsensus::<T>::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::<T>::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::<T>::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::<T>::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<T>,
        maybe_cursor: Option<BoundedVec<u8, T::MaxCursorLen>>,
    ) -> (Weight, bool) {
        Self::prepare_round_storage(
            network_curve,
            maybe_cursor,
            |_, _| None,
            |weight| {
                weight.saturating_accrue(T::DbWeight::get().reads(2));
                let dkg_index = ActiveDkgAuthorities::<T>::get(&network_curve)
                    .get_dkg_index();

                let is_initial_dkg_round =
                    !ActiveVerifyingKey::<T>::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::<T>::contains_key(&network_curve, &session) {
                            return false
                        };

                        weight.saturating_accrue(T::DbWeight::get().reads(1));
                        ExodusSignedRotations::<T>::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<T> = (&*dkg_qualification).into();

                    weight.saturating_accrue(T::DbWeight::get().reads(1));
                    let qualified_authorities = QualificationAuthorities::<T>::get(&network_curve);

                    weight.saturating_accrue(T::DbWeight::get().writes(3));
                    ActiveDkgAuthorities::<T>::insert(&network_curve, activated_state);
                    ActiveVerifyingKey::<T>::insert(&network_curve, verifying_key);
                    ActiveAuthorities::<T>::set(&network_curve, qualified_authorities);
                } else {
                    dkg_qualification.nullify_indexes();
                }
            })
    }

    fn register_latest_activity(
        package_ref: &impl PackageMetadata<NetworkCurve>,
    ) -> DispatchResult {
        let authority_index = package_ref.get_authority_index();
        let current_block = T::BlockNumberProvider::current_block_number();

        ExodusActivity::<T>::try_mutate(&authority_index,
            |stored_block| -> DispatchResult {
                ensure!(
                    *stored_block < current_block,
                    Error::<T>::PackagesAlreadyRegistered,
                );
                *stored_block = current_block;
                Ok(())
            })?;

        Ok(())
    }

    fn validate_round0_package_size(
        dkg_package: & DkgPackage<NetworkCurve, T>,
        signature: &<T::AuthorityId as RuntimeAppPublic>::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::<T>::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::<T>::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<NetworkCurve, T>,
        signature: &<T::AuthorityId as RuntimeAppPublic>::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::<T>::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::<T>::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<NetworkCurve, T>,
        signature: &<T::AuthorityId as RuntimeAppPublic>::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::<T>::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::<usize>()
            .saturating_add(1);

        if bundle_packages_count != state.count_ones::<usize>() {
            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::<usize>()
            .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::<T>::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<NetworkCurve, T>,
        signature: &<T::AuthorityId as RuntimeAppPublic>::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::<T>::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::<usize>()
            .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::<T>::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<NetworkCurve, T>,
        signature: &<T::AuthorityId as RuntimeAppPublic>::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::<T>::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::<usize>()
            .saturating_add(1);

        if bundle_packages_count > state.count_ones::<usize>() {
            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::<usize>()
            .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::<T>::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<NetworkCurve, T>,
        signature: &<T::AuthorityId as RuntimeAppPublic>::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::<T>::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::<T>::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<NetworkCurve, T>,
        signature: &<T::AuthorityId as RuntimeAppPublic>::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::<T>::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::<usize>()
            .next_power_of_two()
            .trailing_zeros();

        if dkg_package.bytes_len() != estimated_proof_count {
            return Err(InvalidTransaction::BadProof);
        }

        let authority = QualificationAuthorities::<T>::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<NetworkCurve, T>,
        signature: &<T::AuthorityId as RuntimeAppPublic>::Signature,
    ) -> Result<(ExodusRequest<NetworkIdOf<T>, BalanceOf<T>>, 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::<T>::get(&network_curve).is_dkg_pending() {
            return Err(InvalidTransaction::BadProof);
        }

        let active_state = ActiveDkgAuthorities::<T>::get(&network_curve);

        if !active_state.contains_index(authority_index) {
            return Err(InvalidTransaction::BadSigner);
        }

        let authority = ActiveAuthorities::<T>::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::<T>::get(&network_curve, &package.session)
                .ok_or(InvalidTransaction::BadProof)?;

        let roast_session =
            match RoastSessions::<T>::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<NetworkCurve, T>,
        signature: &<T::AuthorityId as RuntimeAppPublic>::Signature,
    ) -> Result<RoastSession, InvalidTransaction> {
        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::<T>::get(&network_curve).is_dkg_pending() {
            return Err(InvalidTransaction::BadProof);
        }

        let active_state = ActiveDkgAuthorities::<T>::get(&network_curve);

        if !active_state.contains_index(authority_index) {
            return Err(InvalidTransaction::BadSigner);
        }

        let authority = ActiveAuthorities::<T>::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::<T>::get(&package.session, &authority_index)
            .ok_or(InvalidTransaction::BadProof)
    }

    fn validate_exodus_signature_share_size_and_get_metadata<'a>(
        exodus_package: &'a ExodusPackage<NetworkCurve, T>,
        signature: &<T::AuthorityId as RuntimeAppPublic>::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::<T>::get(&network_curve);
        if qualification_state.is_dkg_pending() {
            return Err(InvalidTransaction::BadProof);
        }

        let active_state = ActiveDkgAuthorities::<T>::get(&network_curve);

        if !active_state.contains_index(authority_index) {
            return Err(InvalidTransaction::BadSigner);
        }

        let authority = ActiveAuthorities::<T>::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::<T>::get(&package.session, &authority_index)
                .ok_or(InvalidTransaction::BadProof)?;

        Ok((roast_session, active_state.get_dkg_index()))
    }

    fn start_exodus(
        current_block: BlockNumberFor<T>,
    ) -> ExodusResult<Vec<ExodusResult<ExodusOk<NetworkCurve>>>> {
        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::<T>::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<T>,
    ) -> ExodusResult<Vec<ExodusResult<ExodusOk<NetworkCurve>>>> {
        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::<Time>::with_deadline(&exodus_lock_key, lock_until);
        let _guard = exodus_lock
            .try_lock()
            .map_err(|_| ExodusError::OffchainTimeoutPeriod)?;

        let authorities = ActiveAuthorities::<T>::get(&network_curve);
        let active_authorities = ActiveDkgAuthorities::<T>::get(&network_curve);

        let results = Self::get_local_authorities(authorities.into_iter())
            .map(move |(authority_index, authority_key)| {
                Self::exodus_run_process(
                    authority_index,
                    authority_key,
                    network_curve,
                    current_block,
                    &active_authorities,
                )
            })
            .collect();

        Ok(results)
    }

    fn exodus_run_process(
        authority_index: AuthIndex,
        authority_key: T::AuthorityId,
        network_curve: NetworkCurve,
        current_block: BlockNumberFor<T>,
        active_authorities: &ActivatedState<T>
    ) -> ExodusResult<ExodusOk<NetworkCurve>> {
        if !active_authorities.contains_index(authority_index) {
            return Err(ExodusError::InvalidParticipantId)
        }

        let dkg_index = active_authorities.get_dkg_index();
        let block_longevity: BlockNumberFor<T> =
            T::UnsignedLongevity::get().unique_saturated_into();

        let dkg_prefix = Self::create_dkg_based_prefix(dkg_index);
        let round_metadata = DkgStorage::default()
            .with_network_curve(network_curve)
            .with_current_block(current_block)
            .with_block_longevity(block_longevity)
            .read_local_storage(&dkg_prefix, authority_index);

        let Some(DkgStorageEnum::Round5 { secret_share, .. }) =
            round_metadata.into_inner() else {
                return Ok(ExodusOk::DkgRoundNothingStored(
                    ROUND_NUMBER_5,
                    authority_index,
                    network_curve,
                ));
            };

        let exodus_storage = ExodusStorage::default()
            .with_current_block(current_block)
            .with_block_longevity(block_longevity)
            .with_network_curve(network_curve)
            .with_authority_index(authority_index);

        let mut exodus_records = exodus_storage.read_local_storage();

        let max_authorities = ActiveAuthorities::<T>::decode_len(&network_curve)
            .ok_or(ExodusError::NoActiveAuthorities)?;

        let threshold = get_byzantium_threshold(max_authorities);

        let mut exodus_requests = ExodusRequests::<T>::iter_prefix(&network_curve)
            .collect::<Vec<(ExodusSession, ExodusRequest<_, _>)>>();

        exodus_requests.sort_by_key(|&(session, _)| session);

        let is_requests_empty = exodus_requests.len() == 0;

        let mut share_candidate = None;
        let mut group_candidate = None;
        let mut nonce_candidate = None;

        for (session, request) in exodus_requests.iter() {
            if exodus_records.is_exodus_session_banned(session) {
                continue;
            }

            if let Some(roast_session) = RoastSessions::<T>::get(session, &authority_index) {
                let state = RoastSessionStates::<T>::get(session, roast_session);
                let participants = GroupCommitmentsConsensus::<T>::get(&session, roast_session).participants;
                let commitment_consensus_reached = participants.count_ones::<usize>() >= threshold.div_ceil(2);

                if state.status == RoastStatus::PartialSignatures
                    && state.nonce_committers.contains(authority_index)
                    && !state.partial_signers.contains(authority_index)
                    && participants.contains(authority_index)
                    && commitment_consensus_reached
                    && !exodus_records.is_waiting_period_for_share(session, &current_block)
                {
                    share_candidate = Some((*session, request, state.status, roast_session));
                    break;
                }

                if group_candidate.is_none()
                    && (
						(state.status == RoastStatus::GroupCommitments
						    || state.status == RoastStatus::PartialSignatures)
                    )
                    && state.nonce_committers.contains(authority_index)
                    && !state.partial_signers.contains(authority_index)
                    && !participants.contains(authority_index)
                    && !exodus_records.is_waiting_period_for_group(session, &current_block)
                {
                    group_candidate = Some((*session, request, RoastStatus::GroupCommitments, roast_session));
                    break;
                }
            } else {
                let active_roast_session = request.next_roast_session;
                let state = RoastSessionStates::<T>::get(session, active_roast_session);

                if nonce_candidate.is_none()
                    && state.status == RoastStatus::NonceCommitments
                    && !state.nonce_committers.contains(authority_index)
                    && !state.partial_signers.contains(authority_index)
                    && !state.group_committers.contains(authority_index)
                    && !exodus_records.is_waiting_period_for_nonce(session, &current_block)
                {
                    nonce_candidate = Some((*session, request, state.status, active_roast_session));
                    break;
                }
            }
        }

        let target_session = share_candidate
            .or(group_candidate)
            .or(nonce_candidate);

        if let Some((session, request, roast_status, roast_session)) = target_session {
            match roast_status {
                RoastStatus::NonceCommitments => {
                    Self::exodus_run_nonce_commitment(
                        &exodus_storage,
                        &mut exodus_records,
                        secret_share,
                        &authority_key,
                        session,
                    )?;

                    Ok(ExodusOk::ExodusNonceCommitted(
                        session,
                        roast_session,
                        authority_index,
                        network_curve,
                    ))
                },
                RoastStatus::GroupCommitments => {
                    let mut message_buffer = [0u8; MAX_MESSAGE_SIZE as usize];
                    if let Some(message) = request.get_message(&mut message_buffer) {
                        let group_commitment_result =
                            Self::exodus_run_group_commitment(
                                &exodus_storage,
                                &mut exodus_records,
                                &message,
                                &authority_key,
                                session,
                                roast_session,
                            );

                        group_commitment_result
                            .and_then(|_| {
                                Ok(ExodusOk::ExodusGroupCommitted(
                                    session,
                                    roast_session,
                                    authority_index,
                                    network_curve,
                                ))
                            })
                            .or_else(|err| {
                                exodus_records.exclude_exodus_session(session);
                                exodus_storage.try_write_local_storage(&exodus_records);
                                Err(err)
                            })
                    } else {
                        Err(ExodusError::CouldNotConstructMessage(authority_index))
                    }
                },
                RoastStatus::PartialSignatures => {
                    let mut message_buffer = [0u8; MAX_MESSAGE_SIZE as usize];
                    if let Some(message) = request.get_message(&mut message_buffer) {
                        let signature_share_result =
                            Self::exodus_run_signature_share(
                                &exodus_storage,
                                &mut exodus_records,
                                &message,
                                secret_share,
                                &authority_key,
                                session,
                                roast_session,
                            );

                        signature_share_result
                            .and_then(|_| {
                                Ok(ExodusOk::ExodusSignedPartially(
                                    session,
                                    roast_session,
                                    authority_index,
                                    network_curve,
                                ))
                            })
                            .or_else(|err| {
                                exodus_records.exclude_exodus_session(session);
                                exodus_storage.try_write_local_storage(&exodus_records);
                                Err(err)
                            })
                    } else {
                        Err(ExodusError::CouldNotConstructMessage(authority_index))
                    }
                },
            }

        } else {
            if is_requests_empty {
                exodus_storage.force_clear_local_storage();
                Ok(ExodusOk::ExodusEmptyRequests(authority_index, network_curve))
            } else {
                Ok(ExodusOk::ExodusWaitingState(authority_index, network_curve))
            }
        }
    }

    fn dkg_start(
        network_curve: NetworkCurve,
        current_block: BlockNumberFor<T>,
        dkg_qualification: QualifyingState<T>,
    ) -> ExodusResult<Vec<ExodusResult<ExodusOk<NetworkCurve>>>> {
        let network_encoded = network_curve.encode();
        let network_lock_key = Self::create_storage_key(b"network-lock-", &network_encoded);
        let lock_until = rt_offchain::Duration::from_millis(MIN_LOCK_GUARD_PERIOD);

        let mut network_lock = StorageLock::<Time>::with_deadline(&network_lock_key, lock_until);
        let _guard = network_lock
            .try_lock()
            .map_err(|_| ExodusError::OffchainTimeoutPeriod)?;

        let authorities = QualificationAuthorities::<T>::get(&network_curve);

        let results = Self::get_local_authorities(authorities.into_iter())
            .map(move |(authority_index, authority_key)| {
                Self::dkg_run_process(
                    authority_index,
                    authority_key,
                    network_curve,
                    current_block,
                    &dkg_qualification
                )
            })
            .collect();

        Ok(results)
    }

    fn dkg_run_process(
        authority_index: AuthIndex,
        authority_key: T::AuthorityId,
        network_curve: NetworkCurve,
        current_block: BlockNumberFor<T>,
        qualification_authorities: &QualifyingState<T>
    ) -> ExodusResult<ExodusOk<NetworkCurve>> {
        let block_longevity: BlockNumberFor<T> =
            T::UnsignedLongevity::get().unique_saturated_into();

        let dkg_storage = DkgStorage::default()
            .with_network_curve(network_curve)
            .with_current_block(current_block)
            .with_authority_index(authority_index)
            .with_block_longevity(block_longevity);

        match qualification_authorities.get_phase() {
            DkgPhase::Round0 => Self::dkg_run_round0(
                dkg_storage,
                qualification_authorities,
                authority_key,
            ),
            DkgPhase::Round1 => Self::dkg_run_round1(
                dkg_storage,
                qualification_authorities,
                authority_key,
            ),
            DkgPhase::Round2 => Self::dkg_run_round2(
                dkg_storage,
                qualification_authorities,
                authority_key,
            ),
            DkgPhase::Round3 => Self::dkg_run_round3(
                dkg_storage,
                qualification_authorities,
                authority_key,
            ),
            DkgPhase::Round4 => Self::dkg_run_round4(
                dkg_storage,
                qualification_authorities,
                authority_key,
            ),
            DkgPhase::Round5 => Self::dkg_run_round5(
                dkg_storage,
                qualification_authorities,
                authority_key,
            ),
            DkgPhase::Round6 => Self::dkg_run_round6(
                dkg_storage,
                qualification_authorities,
                authority_key,
            ),
            _ => Self::dkg_run_reset(
                dkg_storage,
                qualification_authorities,
            )
        }
    }

    fn exodus_run_nonce_commitment(
        exodus_storage: &ExodusStorage<BlockNumberFor<T>, NetworkCurve>,
        exodus_records: &mut ExodusRecords<BlockNumberFor<T>>,
        secret_share: &[u8],
        authority_key: &T::AuthorityId,
        exodus_session: ExodusSession,
    ) -> ExodusResult<()> {
        let authority_index = exodus_storage.authority_index;
        let network_curve = exodus_storage.network_curve;

        #[cfg(not(any(test, feature = "runtime-benchmarks")))]
        let mut rng = Self::get_offchain_rng();

        #[cfg(any(test, feature = "runtime-benchmarks"))]
        let mut rng = Self::get_offchain_rng(authority_index);

        let (nonce, hiding_commitment, binding_commitment) = network_curve
            .generate_nonce_commitment(secret_share, &mut rng)?;

        exodus_storage.prepare_signed_call_with_context(
            authority_key,
            exodus_records,
            |context| {
                let package = context.with_exodus_nonce_commitment(
                    exodus_session,
                    hiding_commitment,
                    binding_commitment,
                )?;
                Ok(ExodusPackage::NonceCommitment(package))
            },
            |exodus_package, signature| {
                let call = Call::<T>::register_nonce_commitment { exodus_package, signature };
                SubmitTransaction::<T, Call<T>>::submit_unsigned_transaction(call.into())
                    .map_err(|_| ExodusError::TransactionSubmissionFailed(authority_index))
            },
            |exodus_records, next_release_block| {
                exodus_records.insert_exodus_nonce(
                    exodus_session,
                    next_release_block,
                    nonce,
                );
            }
        )
    }

    fn exodus_run_group_commitment(
        exodus_storage: &ExodusStorage<BlockNumberFor<T>, NetworkCurve>,
        exodus_records: &mut ExodusRecords<BlockNumberFor<T>>,
        message: &[u8],
        authority_key: &T::AuthorityId,
        exodus_session: ExodusSession,
        roast_session: RoastSession,
    ) -> ExodusResult<()> {
        let authority_index = exodus_storage.authority_index;
        let network_curve = exodus_storage.network_curve;

        let session_commitments =
            NonceCommitments::<T>::iter_prefix((exodus_session, roast_session))
                .collect::<BTreeMap<AuthIndex, _>>();

        let nonce_commitments = session_commitments
            .iter()
            .map(|(auth_index, nonces)| {
                (*auth_index, (nonces.hiding.as_ref(), nonces.binding.as_ref()))
            })
            .collect::<BTreeMap<AuthIndex, _>>();

        let verifying_key = ActiveVerifyingKey::<T>::get(&network_curve);

        let (
			group_commitment,
			self_binding_factor,
			binding_factors_proof,
			binding_factors_root,
        ) = network_curve.generate_group_commitment(
            authority_index,
            &nonce_commitments,
            &verifying_key,
            &message,
        )?;

        exodus_storage.prepare_signed_call_with_context(
            authority_key,
            exodus_records,
            |context| {
                let package = context.with_exodus_group_commitment(
                    exodus_session,
                    group_commitment,
                    binding_factors_root,
                )?;
                Ok(ExodusPackage::GroupCommitment(package))
            },
            |exodus_package, signature| {
                let call = Call::<T>::register_group_commitment { exodus_package, signature };
                SubmitTransaction::<T, Call<T>>::submit_unsigned_transaction(call.into())
                    .map_err(|_| ExodusError::TransactionSubmissionFailed(authority_index))
            },
            |exodus_records, next_release_block| {
                exodus_records.insert_exodus_group(
                    exodus_session,
                    next_release_block,
                    self_binding_factor,
                    binding_factors_proof,
                )
            }
        )
    }

    fn exodus_run_signature_share(
        exodus_storage: &ExodusStorage<BlockNumberFor<T>, NetworkCurve>,
        exodus_records: &mut ExodusRecords<BlockNumberFor<T>>,
        message: &[u8],
        secret_share: &[u8],
        authority_key: &T::AuthorityId,
        exodus_session: ExodusSession,
        roast_session: RoastSession,
    ) -> ExodusResult<()> {
        let authority_index = exodus_storage.authority_index;
        let network_curve = exodus_storage.network_curve;

        let session_commitments =
            NonceCommitments::<T>::iter_prefix((exodus_session, roast_session))
                .collect::<BTreeMap<AuthIndex, _>>();

        let nonce_commitments = session_commitments
            .iter()
            .map(|(auth_index, nonces)| {
                (*auth_index, (nonces.hiding.as_ref(), nonces.binding.as_ref()))
            })
            .collect::<BTreeMap<AuthIndex, _>>();

        let signing_package = network_curve.init_signing_package(&nonce_commitments, &message)?;
        let secret_nonce = exodus_records.get_secret_nonce(&exodus_session)
            .ok_or(ExodusError::TransactionSubmissionFailed(authority_index))?;

        let signature_share = network_curve.partial_sign_message(
            &signing_package,
            secret_nonce,
            &secret_share,
        )?;

        let exodus_group_record = match exodus_records
            .get_group_record(&exodus_session) {
                Some(exodus_group_record) => exodus_group_record,
                None => {
                    let verifying_key = ActiveVerifyingKey::<T>::get(&network_curve);

                    let (
						_group_commitment,
						self_binding_factor,
						binding_factors_proof,
						_binding_factors_root,
                    ) = network_curve.generate_group_commitment(
                        authority_index,
                        &nonce_commitments,
                        &verifying_key,
                        &message,
                    )?;

                    ExodusGroup { self_binding_factor, binding_factors_proof }
                }
            };

        exodus_storage.prepare_signed_call_with_context(
            authority_key,
            exodus_records,
            |context| {
                let package = context.with_exodus_signature_share(
                    exodus_session,
                    signature_share,
                    exodus_group_record.binding_factors_proof,
                    exodus_group_record.self_binding_factor,
                )?;
                Ok(ExodusPackage::SignatureShare(package))
            },
            |exodus_package, signature| {
                let call = Call::<T>::register_signature_share { exodus_package, signature };
                SubmitTransaction::<T, Call<T>>::submit_unsigned_transaction(call.into())
                    .map_err(|_| ExodusError::TransactionSubmissionFailed(authority_index))
            },
            |exodus_records, next_release_block| {
                exodus_records.insert_exodus_share(exodus_session, next_release_block);
            }
        )
    }

    fn dkg_run_round0(
        dkg_storage: DkgStorage<BlockNumberFor<T>, NetworkCurve>,
        qualification_state: &QualifyingState<T>,
        authority_key: T::AuthorityId,
    ) -> ExodusResult<ExodusOk<NetworkCurve>> {
        let network_curve = dkg_storage.network_curve;
        let authority_index = dkg_storage.authority_index;

        let max_signers = QualificationAuthorities::<T>::decode_len(&network_curve)
            .map(|max_signers| max_signers as AuthIndex)
            .ok_or(ExodusError::InvalidMaxSigners(0))?;

        let min_signers = get_byzantium_threshold(max_signers);

        if qualification_state.contains_index(authority_index) {
            return Ok(ExodusOk::DkgRoundAlreadyPassed(
                ROUND_NUMBER_0,
                authority_index,
                network_curve,
            ));
        }

        if Round0Packages::<T>::contains_key(&network_curve, authority_index) {
            return Ok(ExodusOk::DkgRoundAlreadyPassed(
                ROUND_NUMBER_0,
                authority_index,
                network_curve,
            ));
        }

        let mut round0_metadata = dkg_storage
            .read_local_storage(DKG_ROUND0_PREFIX, authority_index);

        if round0_metadata.is_waiting_period() {
            return Ok(ExodusOk::DkgRoundWaitingPeriod(
                ROUND_NUMBER_0,
                authority_index,
                network_curve,
            ));
        }

        #[cfg(not(any(test, feature = "runtime-benchmarks")))]
        let mut rng = Self::get_offchain_rng();

        #[cfg(any(test, feature = "runtime-benchmarks"))]
        let mut rng = Self::get_offchain_rng(authority_index);

        let (secret_package, public_package) = network_curve
            .dkg_part1(authority_index, max_signers, min_signers, &mut rng)?;

        let package_hash = ExodusHash::from(blake2_256(&public_package));

        let mut round1_metadata = dkg_storage
            .read_local_storage(DKG_ROUND1_PREFIX, authority_index);

        round1_metadata.update_with_block(
            Some(dkg_storage.get_release_block()),
            DkgStorageEnum::Round1 { secret_package, public_package },
        )?;

        dkg_storage.try_write_local_storage(&round1_metadata);

        round0_metadata.update_with_block(
            Some(dkg_storage.get_release_block()),
            DkgStorageEnum::Round0 { package_hash }
        )?;

        dkg_storage.prepare_signed_call_with_context(
            &authority_key,
            &mut round0_metadata,
            |context: PackageContext<NetworkCurve>| {
                Ok(DkgPackage::Round0(context.with_dkg_round0(package_hash)))
            },
            |dkg_package, signature| {
                let call = Call::<T>::register_round0_package { dkg_package, signature };
                SubmitTransaction::<T, Call<T>>::submit_unsigned_transaction(call.into())
                    .map_err(|_| ExodusError::TransactionSubmissionFailed(authority_index))
            }
        )?;

        dkg_storage.get_dkg_result(round0_metadata)
    }

    fn dkg_run_round1(
        dkg_storage: DkgStorage<BlockNumberFor<T>, NetworkCurve>,
        qualification_state: &QualifyingState<T>,
        authority_key: T::AuthorityId,
    ) -> ExodusResult<ExodusOk<NetworkCurve>> {
        let network_curve = dkg_storage.network_curve;
        let authority_index = dkg_storage.authority_index;

        if !qualification_state.contains_index(authority_index) {
            return Ok(ExodusOk::DkgNotPartOfRound(
                ROUND_NUMBER_1,
                authority_index,
                network_curve,
            ));
        }

        if Round1Packages::<T>::get(&network_curve).contains(authority_index) {
            return Ok(ExodusOk::DkgRoundAlreadyPassed(
                ROUND_NUMBER_1,
                authority_index,
                network_curve,
            ));
        }

        let round0_metadata = dkg_storage
            .read_local_storage(DKG_ROUND0_PREFIX, authority_index);

        let Some(DkgStorageEnum::Round0 { package_hash }) =
            round0_metadata.into_inner() else {
                return Ok(ExodusOk::DkgRoundNothingStored(
                    ROUND_NUMBER_1,
                    authority_index,
                    network_curve,
                ));
            };

        let mut round1_metadata = dkg_storage
            .read_local_storage(DKG_ROUND1_PREFIX, authority_index);

        if !round1_metadata.storage_exists() {
            return Ok(ExodusOk::DkgRoundNothingStored(
                ROUND_NUMBER_1,
                authority_index,
                network_curve,
            ));
        }

        if round1_metadata.is_waiting_period() {
            return Ok(ExodusOk::DkgRoundWaitingPeriod(
                ROUND_NUMBER_1,
                authority_index,
                network_curve,
            ));
        }

        let Some(DkgStorageEnum::Round1 { public_package, .. }) =
            round1_metadata.into_inner() else {
                return Ok(ExodusOk::DkgRoundNothingStored(
                    ROUND_NUMBER_1,
                    authority_index,
                    network_curve,
                ));
            };

        let public_package_clone = public_package.clone();

        let stored_package_hash = ExodusHash::from(blake2_256(&public_package_clone));
        let external_package_hash = Round0Packages::<T>::get(&network_curve, &authority_index);

        let stored_hash_matches = stored_package_hash.eq(package_hash);
        let external_hash_matches = external_package_hash.eq(package_hash);

        if !stored_hash_matches || !external_hash_matches {
            return Ok(ExodusOk::DkgRoundNothingStored(
                ROUND_NUMBER_1,
                authority_index,
                network_curve,
            ));
        }

        dkg_storage.prepare_signed_call_with_context(
            &authority_key,
            &mut round1_metadata,
            |context| {
                let package = context.with_dkg_round1(public_package_clone)?;
                Ok(DkgPackage::Round1(package))
            },
            |dkg_package, signature| {
                let call = Call::<T>::register_round1_package { dkg_package, signature };
                SubmitTransaction::<T, Call<T>>::submit_unsigned_transaction(call.into())
                    .map_err(|_| ExodusError::TransactionSubmissionFailed(authority_index))
            }
        )?;

        dkg_storage.get_dkg_result(round1_metadata)
    }

    fn dkg_run_round2(
        dkg_storage: DkgStorage<BlockNumberFor<T>, NetworkCurve>,
        qualification_state: &QualifyingState<T>,
        authority_key: T::AuthorityId,
    ) -> ExodusResult<ExodusOk<NetworkCurve>> {
        let network_curve = dkg_storage.network_curve;
        let authority_index = dkg_storage.authority_index;

        if !qualification_state.contains_index(authority_index) {
            return Ok(ExodusOk::DkgNotPartOfRound(
                ROUND_NUMBER_2,
                authority_index,
                network_curve,
            ));
        }

        if Round2Packages::<T>::get(&network_curve).contains(authority_index) {
            return Ok(ExodusOk::DkgRoundAlreadyPassed(
                ROUND_NUMBER_2,
                authority_index,
                network_curve,
            ));
        }

        let mut round1_metadata = dkg_storage
            .read_local_storage(DKG_ROUND1_PREFIX, authority_index);

        if !round1_metadata.storage_exists() {
            return Ok(ExodusOk::DkgRoundNothingStored(
                ROUND_NUMBER_2,
                authority_index,
                network_curve,
            ));
        };

        let mut round2_metadata = dkg_storage
            .read_local_storage(DKG_ROUND2_PREFIX, authority_index);

        if round2_metadata.is_waiting_period() {
            return Ok(ExodusOk::DkgRoundWaitingPeriod(
                ROUND_NUMBER_2,
                authority_index,
                network_curve,
            ));
        }

        let round1_packages =
            Self::gather_incoming_offchain_round1_packages(
                &qualification_state.get_indexes(),
                authority_index,
                network_curve,
            );

        let round1_secret_package = round1_metadata.try_update_max_signers(
            |round1_secret_package| -> ExodusResult<Vec<u8>> {
                network_curve.dkg_narrow_round_max_signers(
                    round1_secret_package,
                    round1_packages.len().saturating_add(1) as AuthIndex,
                    ROUND_NUMBER_1,
                )
            })?;

        let (secret_package, public_packages) = network_curve
            .dkg_part2(round1_secret_package, &round1_packages)?;

        let padded_participants = qualification_state
            .highest_bit::<usize>()
            .map(|bit_pos| bit_pos.saturating_add(1))
            .unwrap_or_default();

        let bitmask_len = padded_participants.div_ceil(8);
        let blob_len = round2_encrypted_package_size(
            padded_participants,
            network_curve.scalar_bytes_len(),
            network_curve.header_bytes_len(),
        );
        let max_package_size = blob_len.saturating_div(padded_participants);

        let mut bitmask = vec![0u8; bitmask_len];
        let mut blob = vec![0u8; blob_len];

        Self::ecdh_encrypt_round2_packages(
            &round1_packages,
            &public_packages,
            round1_secret_package,
            &mut bitmask,
            &mut blob,
            max_package_size,
            authority_index,
            qualification_state.get_dkg_index(),
            network_curve,
        );

        round2_metadata.update_with_block(
            Some(dkg_storage.get_release_block()),
            DkgStorageEnum::Round2 { secret_package, public_packages },
        )?;

        dkg_storage.prepare_signed_call_with_context(
            &authority_key,
            &mut round2_metadata,
            |context| {
                let encryption_bundle = BundledPackages { bitmask, blob };
                let package = context.with_dkg_round2(encryption_bundle)?;
                Ok(DkgPackage::Round2(package))
            },
            |dkg_package, signature| {
                let call = Call::<T>::register_encrypted_round2_packages { dkg_package, signature };
                SubmitTransaction::<T, Call<T>>::submit_unsigned_transaction(call.into())
                    .map_err(|_| ExodusError::TransactionSubmissionFailed(authority_index))
            }
        )?;

        dkg_storage.try_write_local_storage(&round1_metadata);
        dkg_storage.get_dkg_result(round2_metadata)
    }

    fn dkg_run_round3(
        dkg_storage: DkgStorage<BlockNumberFor<T>, NetworkCurve>,
        qualification_state: &QualifyingState<T>,
        authority_key: T::AuthorityId,
    ) -> ExodusResult<ExodusOk<NetworkCurve>> {
        let network_curve = dkg_storage.network_curve;
        let authority_index = dkg_storage.authority_index;

        if !qualification_state.contains_index(authority_index) {
            return Ok(ExodusOk::DkgNotPartOfRound(
                ROUND_NUMBER_3,
                authority_index,
                network_curve,
            ));
        }

        if Complaints::<T>::get(&network_curve).contains_key(&authority_index) {
            return Ok(ExodusOk::DkgRoundAlreadyPassed(
                ROUND_NUMBER_3,
                authority_index,
                network_curve,
            ));
        }

        let round1_metadata = dkg_storage
            .read_local_storage(DKG_ROUND1_PREFIX, authority_index);

        let Some(DkgStorageEnum::Round1 { secret_package, .. }) =
            round1_metadata.into_inner() else {
                return Ok(ExodusOk::DkgRoundNothingStored(
                    ROUND_NUMBER_3,
                    authority_index,
                    network_curve,
                ));
            };

        let mut round3_metadata = dkg_storage
            .read_local_storage(DKG_ROUND3_PREFIX, authority_index);

        if round3_metadata.is_waiting_period() {
            return Ok(ExodusOk::DkgRoundWaitingPeriod(
                ROUND_NUMBER_3,
                authority_index,
                network_curve,
            ));
        }

        let round1_packages =
            Self::gather_incoming_offchain_round1_packages(
                &qualification_state.get_indexes(),
                authority_index,
                network_curve,
            );

        let padded_participants = qualification_state
            .highest_bit::<usize>()
            .map(|bit_pos| bit_pos.saturating_add(1))
            .unwrap_or_default();

        let bitmask_len = padded_participants.div_ceil(8);
        let blob_len = round2_encrypted_package_size(
            padded_participants,
            network_curve.scalar_bytes_len(),
            network_curve.header_bytes_len(),
        );

        let round2_packages =
            Self::gather_incoming_offchain_packages(
                network_curve,
                authority_index,
                ROUND_NUMBER_2,
                &qualification_state.get_indexes(),
                blob_len.saturating_div(padded_participants),
                |mut raw_slice| EncryptionData::decode(&mut raw_slice).ok(),
            );

        let (indices, decrypted) =
            Self::ecdh_decrypt_round2_packages(
                &round1_packages,
                &secret_package,
                &round2_packages,
                authority_index,
                bitmask_len,
                qualification_state.get_dkg_index(),
                network_curve,
            );

        round3_metadata.update_with_block(
            Some(dkg_storage.get_release_block()),
            DkgStorageEnum::Round3 { decrypted },
        )?;

        dkg_storage.prepare_signed_call_with_context(
            &authority_key,
            &mut round3_metadata,
            |context| {
                let package = context.with_dkg_round3(indices)?;
                Ok(DkgPackage::Round3(package))
            },
            |dkg_package, signature| {
                let call = Call::<T>::register_round3_complaints { dkg_package, signature };
                SubmitTransaction::<T, Call<T>>::submit_unsigned_transaction(call.into())
                    .map_err(|_| ExodusError::TransactionSubmissionFailed(authority_index))
            }
        )?;

        dkg_storage.get_dkg_result(round3_metadata)
    }

    fn dkg_run_round4(
        dkg_storage: DkgStorage<BlockNumberFor<T>, NetworkCurve>,
        qualification_state: &QualifyingState<T>,
        authority_key: T::AuthorityId,
    ) -> ExodusResult<ExodusOk<NetworkCurve>> {
        let network_curve = dkg_storage.network_curve;
        let authority_index = dkg_storage.authority_index;

        if !qualification_state.contains_index(authority_index) {
            return Ok(ExodusOk::DkgNotPartOfRound(
                ROUND_NUMBER_4,
                authority_index,
                network_curve,
            ));
        }

        if Justifications::<T>::get(&network_curve).contains_key(&authority_index) {
            return Ok(ExodusOk::DkgRoundAlreadyPassed(
                ROUND_NUMBER_4,
                authority_index,
                network_curve,
            ));
        }

        let round2_metadata = dkg_storage
            .read_local_storage(DKG_ROUND2_PREFIX, authority_index);

        let Some(DkgStorageEnum::Round2 { public_packages, .. }) =
            round2_metadata.into_inner() else {
                return Ok(ExodusOk::DkgRoundNothingStored(
                    ROUND_NUMBER_4,
                    authority_index,
                    network_curve,
                ));
            };

        let mut round4_metadata = dkg_storage
            .read_local_storage(DKG_ROUND4_PREFIX, authority_index);

        if round4_metadata.is_waiting_period() {
            return Ok(ExodusOk::DkgRoundWaitingPeriod(
                ROUND_NUMBER_4,
                authority_index,
                network_curve,
            ));
        }

        let padded_participants = qualification_state
            .highest_bit::<usize>()
            .map(|bit_pos| bit_pos.saturating_add(1))
            .unwrap_or_default();

        let bitmask_len = padded_participants.div_ceil(8);
        let blob_len = round2_package_size(
            padded_participants,
            network_curve.scalar_bytes_len(),
            network_curve.header_bytes_len(),
        );

        let round2_package_size = blob_len.saturating_div(padded_participants);

        let mut bitmask = vec![0u8; bitmask_len];
        let mut blob = vec![0u8; blob_len];

        for (&accuser_index, bitmap) in Complaints::<T>::get(&network_curve).iter() {
            if !bitmap.contains(authority_index) { continue; }
            if !qualification_state.contains_index(accuser_index) { continue; }
            if authority_index == accuser_index { continue; }

            let round2_package = match public_packages.get(&accuser_index) {
                Some(round2_package) => round2_package,
                None => continue,
            };

            Self::prepare_bundled_data(
                &mut bitmask,
                &mut blob,
                &round2_package.encode(),
                round2_package_size,
                accuser_index,
            );
        }

        round4_metadata.update_with_block(
            Some(dkg_storage.get_release_block()),
            DkgStorageEnum::Round4,
        )?;

        dkg_storage.prepare_signed_call_with_context(
            &authority_key,
            &mut round4_metadata,
            |context| {
                let justification_bundle = BundledPackages { bitmask, blob };
                let package = context.with_dkg_round4(justification_bundle)?;
                Ok(DkgPackage::Round4(package))
            },
            |dkg_package, signature| {
                let call = Call::<T>::register_round4_justifications { dkg_package, signature };
                SubmitTransaction::<T, Call<T>>::submit_unsigned_transaction(call.into())
                    .map_err(|_| ExodusError::TransactionSubmissionFailed(authority_index))
            },
        )?;

        dkg_storage.get_dkg_result(round4_metadata)
    }

    fn dkg_run_round5(
        dkg_storage: DkgStorage<BlockNumberFor<T>, NetworkCurve>,
        qualification_state: &QualifyingState<T>,
        authority_key: T::AuthorityId,
    ) -> ExodusResult<ExodusOk<NetworkCurve>> {
        let network_curve = dkg_storage.network_curve;
        let authority_index = dkg_storage.authority_index;

        if !qualification_state.contains_index(authority_index) {
            return Ok(ExodusOk::DkgNotPartOfRound(
                ROUND_NUMBER_5,
                authority_index,
                network_curve,
            ));
        }

        let verification_found_for_self =
            VerifyingKeyConsensus::<T>::iter_values()
                .any(|state| state.participants.contains(authority_index));

        if verification_found_for_self {
            return Ok(ExodusOk::DkgRoundAlreadyPassed(
                ROUND_NUMBER_5,
                authority_index,
                network_curve,
            ));
        }

        let dkg_index = qualification_state.get_dkg_index();
        let dkg_based_prefix = Self::create_dkg_based_prefix(dkg_index);

        let mut round_metadata = dkg_storage.read_local_storage(
            &dkg_based_prefix,
            authority_index,
        );

        if round_metadata.is_waiting_period() {
            return Ok(ExodusOk::DkgRoundWaitingPeriod(
                ROUND_NUMBER_5,
                authority_index,
                network_curve,
            ));
        }

        let mut round1_packages =
            Self::gather_incoming_offchain_round1_packages(
                &qualification_state.get_indexes(),
                authority_index,
                network_curve,
            );

        let mut round3_metadata = dkg_storage
            .read_local_storage(DKG_ROUND3_PREFIX, authority_index);

        let Some(DkgStorageEnum::Round3 { decrypted, .. }) =
            round3_metadata.into_inner_mut() else {
                return Ok(ExodusOk::DkgRoundNothingStored(
                    ROUND_NUMBER_3,
                    authority_index,
                    network_curve,
                ));
            };

        Self::validate_compliants_and_justifications(
            &qualification_state,
            &mut round1_packages,
            decrypted,
            network_curve,
            authority_index,
        );

        decrypted.retain(|auth_idx, _| {
            qualification_state.contains_index(*auth_idx)
                && round1_packages.contains_key(auth_idx)
        });

        let mut round2_metadata = dkg_storage
            .read_local_storage(DKG_ROUND2_PREFIX, authority_index);

        let round2_secret_package = round2_metadata.try_update_max_signers(
            |secret_package| -> ExodusResult<Vec<u8>> {
                network_curve.dkg_narrow_round_max_signers(
                    secret_package,
                    round1_packages.len().saturating_add(1) as AuthIndex,
                    ROUND_NUMBER_2,
                )
            })?;

        let (
			secret_share,
			verifying_key,
			merkle_proof,
			merkle_root,
        ) = network_curve.dkg_part3(
            authority_index,
            &round2_secret_package,
            &round1_packages,
            &decrypted,
        )?;

        round_metadata.update_with_block(
            Some(dkg_storage.get_release_block()),
            DkgStorageEnum::Round5 { secret_share, merkle_proof }
        )?;

        dkg_storage.prepare_signed_call_with_context(
            &authority_key,
            &mut round_metadata,
            |context| {
                let package = context.with_dkg_round5(verifying_key, merkle_root)?;
                Ok(DkgPackage::Round5(package))
            },
            |dkg_package, signature| {
                let call = Call::<T>::register_round5_verifying_package { dkg_package, signature };
                SubmitTransaction::<T, Call<T>>::submit_unsigned_transaction(call.into())
                    .map_err(|_| ExodusError::TransactionSubmissionFailed(authority_index))
            },
        )?;

        dkg_storage.try_write_local_storage(&round2_metadata);
        dkg_storage.get_dkg_result(round_metadata)
    }

    fn dkg_run_round6(
        dkg_storage: DkgStorage<BlockNumberFor<T>, NetworkCurve>,
        qualification_state: &QualifyingState<T>,
        authority_key: T::AuthorityId,
    ) -> ExodusResult<ExodusOk<NetworkCurve>> {
        let network_curve = dkg_storage.network_curve;
        let authority_index = dkg_storage.authority_index;

        if !qualification_state.contains_index(authority_index) {
            return Ok(ExodusOk::DkgNotPartOfRound(
                ROUND_NUMBER_6,
                authority_index,
                network_curve,
            ));
        }

        let dkg_index = qualification_state.get_dkg_index();

        if VerifyingSharesParticipants::<T>::get(&network_curve, &dkg_index)
            .contains(authority_index) {
                return Ok(ExodusOk::DkgRoundAlreadyPassed(
                    ROUND_NUMBER_6,
                    authority_index,
                    network_curve,
                ));
            }

        let dkg_based_prefix = Self::create_dkg_based_prefix(
            qualification_state.get_dkg_index(),
        );

        let mut round_metadata = dkg_storage.read_local_storage(
            &dkg_based_prefix,
            authority_index,
        );

        if round_metadata.is_waiting_period() {
            return Ok(ExodusOk::DkgRoundWaitingPeriod(
                ROUND_NUMBER_6,
                authority_index,
                network_curve,
            ));
        }

        let Some(DkgStorageEnum::Round5 { secret_share, merkle_proof }) =
            round_metadata.into_inner_mut() else {
                return Ok(ExodusOk::DkgRoundNothingStored(
                    ROUND_NUMBER_6,
                    authority_index,
                    network_curve,
                ));
            };

        let verifying_share = network_curve
            .dkg_derive_verifying_share(secret_share)?;

        let owned_merkle_proof = core::mem::take(merkle_proof);

        dkg_storage.prepare_signed_call_with_context(
            &authority_key,
            &mut round_metadata,
            |context| {
                let package = context.with_dkg_round6(verifying_share, owned_merkle_proof)?;
                Ok(DkgPackage::Round6(package))
            },
            |dkg_package, signature| {
                let call = Call::<T>::register_round6_public_share_package { dkg_package, signature };
                SubmitTransaction::<T, Call<T>>::submit_unsigned_transaction(call.into())
                    .map_err(|_| ExodusError::TransactionSubmissionFailed(authority_index))
            },
        )?;

        dkg_storage.get_dkg_result(round_metadata)
    }

    fn dkg_run_reset(
        dkg_storage: DkgStorage<BlockNumberFor<T>, NetworkCurve>,
        qualification_state: &QualifyingState<T>,
    ) -> ExodusResult<ExodusOk<NetworkCurve>> {
        let network_curve = dkg_storage.network_curve;
        let authority_index = dkg_storage.authority_index;

        match qualification_state.get_dkg_index().checked_sub(1) {
            Some(dkg_index) => {
                let dkg_prefixed_index = Self::create_dkg_based_prefix(dkg_index);

                let storage_exists = {
                    dkg_storage
                        .read_local_storage(&dkg_prefixed_index, authority_index)
                        .storage_exists()
                };

                if storage_exists {
                    [DKG_ROUND0_PREFIX, DKG_ROUND1_PREFIX, DKG_ROUND2_PREFIX, DKG_ROUND3_PREFIX, DKG_ROUND4_PREFIX]
                        .iter()
                        .for_each(|prefix| dkg_storage.clear_local_storage(prefix));

                    Self::clear_incoming_offchain_packages(
                        network_curve,
                        &[ROUND_NUMBER_1, ROUND_NUMBER_2, ROUND_NUMBER_4],
                        T::MaxAuthorities::get() as AuthIndex,
                    );

                    dkg_storage.clear_local_storage(&dkg_prefixed_index);
                }

                Ok(ExodusOk::DkgPreviousPurged(authority_index, network_curve))
            },
            None => Err(ExodusError::UnexpectedRound),
        }
    }

    pub fn validate_compliants_and_justifications(
        qualification_state: &QualifyingState<T>,
        round1_packages: &mut BTreeMap<AuthIndex, Vec<u8>>,
        round2_packages: &mut BTreeMap<AuthIndex, Vec<u8>>,
        network_curve: NetworkCurve,
        authority_index: AuthIndex,
    ) {
        let padded_participants = qualification_state
            .highest_bit::<usize>()
            .map(|bit_pos| bit_pos.saturating_add(1))
            .unwrap_or_default();

        let blob_len = round2_package_size(
            padded_participants,
            network_curve.scalar_bytes_len(),
            network_curve.header_bytes_len(),
        );

        let round2_package_size = blob_len.saturating_div(padded_participants);

        for (&accuser_index, accused_bitmap) in Complaints::<T>::get(network_curve).iter() {
            if !qualification_state.contains_index(accuser_index) { continue; }
            if accuser_index == authority_index { continue; }

            let raw_round2_packages: BTreeMap<AuthIndex, Vec<u8>> =
                Self::gather_incoming_offchain_packages(
                    network_curve,
                    accuser_index,
                    ROUND_NUMBER_4,
                    &accused_bitmap,
                    round2_package_size,
                    |mut raw_slice| Vec::<u8>::decode(&mut raw_slice).ok(),
                );

            let mut kicked = sp_std::collections::btree_set::BTreeSet::new();

            for accused_index in accused_bitmap.iter::<AuthIndex>() {
                if !qualification_state.contains_index(accused_index) { continue; }
                if kicked.contains(&accused_index) { continue; }
                if accused_index == authority_index { continue; }

                let maybe_round1_package = round1_packages.get(&accused_index);
                let maybe_round2_package = raw_round2_packages.get(&accused_index);

                let (round1_package, round2_package) =
                    match (maybe_round1_package, maybe_round2_package) {
                        (Some(round1_package), Some(round2_package)) => {
                            (round1_package, round2_package)
                        },
                        _ => {
                            round1_packages.remove(&accused_index);
                            round2_packages.remove(&accused_index);
                            kicked.insert(accused_index);
                            continue;
                        }
                    };

                match network_curve.dkg_verify_private_package(
                    accuser_index,
                    round1_package,
                    round2_package,
                ) {
                    Ok(_) => {
                        if accuser_index != authority_index { continue; }
                        round2_packages.insert(
                            accused_index,
                            round2_package.clone(),
                        );
                    },
                    Err(_) => {
                        round1_packages.remove(&accused_index);
                        round2_packages.remove(&accused_index);
                        kicked.insert(accused_index);
                        continue;
                    }
                }
            }
        }
    }

    pub fn locate_package_bounds(
        bundle: &BundledPackages,
        single_bytes_len: usize,
        authority_index: AuthIndex,
    ) -> Option<(usize, usize)> {
        let global_index = authority_index as usize;
        let byte_index = global_index >> 3usize;
        let bit_index = global_index & 7usize;
        let bit_mask = 1u8 << bit_index;

        let bundle_bitmask_by_index = bundle.bitmask.get(byte_index)?;
        if (bundle_bitmask_by_index & bit_mask) == 0 {
            return None;
        }

        let start_offset = global_index * single_bytes_len;
        let end_offset = start_offset + single_bytes_len;

        if end_offset > bundle.blob.len() {
            return None;
        }

        Some((start_offset, end_offset))
    }

    pub fn gather_incoming_offchain_round1_packages(
        qualified_authorities: &ParticipantsBitmap<T>,
        authority_index: AuthIndex,
        network_curve: NetworkCurve,
    ) -> BTreeMap<AuthIndex, Vec<u8>> {
        qualified_authorities.iter::<AuthIndex>()
            .filter(|&index| index != authority_index)
            .filter_map(|index| {
                let key = Self::create_offchain_dkg_key(
                    network_curve,
                    index,
                    ROUND_NUMBER_1,
                );

                let round1_package = StorageValueRef::persistent(&key)
                    .get::<Vec<u8>>()
                    .ok()??;

                Some((index, round1_package))
            })
            .collect()
    }

    pub fn gather_incoming_offchain_packages<R, E, F>(
        network_curve: NetworkCurve,
        authority_index: AuthIndex,
        round: u8,
        participants: &ParticipantsBitmap<T>,
        single_bytes_len: usize,
        decode_fn: F
    ) -> E
	where
		E: FromIterator<(AuthIndex, R)>,
		F: Fn(&[u8]) -> Option<R>,
	{
        participants
            .iter::<AuthIndex>()
            .filter(|&index| index != authority_index)
            .filter_map(|index| {
                let key = Self::create_offchain_dkg_key(network_curve, index, round);
                let bundle = StorageValueRef::persistent(&key)
                    .get::<BundledPackages>()
                    .ok()??;

                let (start_offset, end_offset) =
                    Self::locate_package_bounds(
                        &bundle,
                        single_bytes_len,
                        authority_index,
                    )?;

                let raw_encrypted_slice = &bundle.blob[start_offset..end_offset];
                let decoded_data = decode_fn(raw_encrypted_slice)?;

                Some((index, decoded_data))
            })
            .collect::<E>()
    }

    fn clear_incoming_offchain_packages(
        network_curve: NetworkCurve,
        rounds: &[u8],
        max_signers: AuthIndex,
    ) {
        (0..max_signers).for_each(|index| rounds.iter().for_each(|&round| {
            let key = Self::create_offchain_dkg_key(network_curve, index, round);
            StorageValueRef::persistent(&key).clear();
        }));
    }

    fn ecdh_decrypt_round2_packages(
        round1_packages: &BTreeMap<AuthIndex, Vec<u8>>,
        round1_secret_package: &Vec<u8>,
        round2_packages: &BTreeMap<AuthIndex, EncryptionData<T>>,
        authority_index: AuthIndex,
        bitvec_len: usize,
        dkg_index: DkgIndex,
        network_curve: NetworkCurve,
    ) -> (Vec<u8>, BTreeMap<AuthIndex, Vec<u8>>) {
        round2_packages
            .iter()
            .filter(|(&sender_index, _)| sender_index != authority_index)
            .filter_map(|(sender_index, encrypted_round2_packages)| {
                let round1_sender_package = round1_packages.get(&sender_index)?;

                let info = network_curve.ecdh_prepare_additional_info(
                    *sender_index,
                    authority_index,
                    dkg_index,
                    sp_std::marker::PhantomData::<EncryptionData<T>>,
                );

                let decryption_result = network_curve
                    .ecdh_get_cipher_from_keys(
                        &round1_sender_package,
                        &round1_secret_package,
                        &info,
                        sp_std::marker::PhantomData::<EncryptionData<T>>,
                    )
                    .and_then(|cipher| {
                        network_curve.ecdh_decrypt_package(
                            &cipher,
                            &encrypted_round2_packages,
                            &info,
                        )
                    })
                    .and_then(|decrypted| {
                        network_curve.dkg_verify_private_package(
                            authority_index,
                            &round1_sender_package,
                            &decrypted,
                        ).map(|_| decrypted)
                    })
                    .map_err(|_| *sender_index)
                    .map(|decrypted_package| (*sender_index, decrypted_package));

                Some(decryption_result)
            })
            .fold(
                (vec![0u8; bitvec_len], BTreeMap::new()),
                |(mut accused_indexes, mut decrypted), decryption_result| {
                    match decryption_result {
                        Ok((sender_index, package)) => { decrypted.insert(sender_index, package); },
                        Err(sender_index) => {
                            let global_index = sender_index as usize;
                            let byte_index = global_index >> 3usize;

                            if let Some(byte) = accused_indexes.get_mut(byte_index) {
                                let bit_index = global_index & 7usize;
                                *byte |= 1u8 << bit_index;
                            }
                        },
                    }
                    (accused_indexes, decrypted)
                }
            )
    }

    fn ecdh_encrypt_round2_packages(
        round1_packages: &BTreeMap<AuthIndex, Vec<u8>>,
        round2_packages: &BTreeMap<AuthIndex, Vec<u8>>,
        round1_secret_package: &[u8],
        bitmask: &mut Vec<u8>,
        blob: &mut Vec<u8>,
        max_size: usize,
        authority_index: AuthIndex,
        dkg_index: DkgIndex,
        network_curve: NetworkCurve,
    ) {
        #[cfg(not(any(test, feature = "runtime-benchmarks")))]
        let mut rng = Self::get_offchain_rng();

        #[cfg(any(test, feature = "runtime-benchmarks"))]
        let mut rng = Self::get_offchain_rng(authority_index);

        for (&receiver_index, round2_package) in round2_packages.iter() {
            if receiver_index == authority_index { continue; }

            let round1_receiver_package = match round1_packages
                .get(&receiver_index) {
                    Some(package) => package,
                    None => continue,
                };

            let info = network_curve.ecdh_prepare_additional_info(
                authority_index,
                receiver_index,
                dkg_index,
                sp_std::marker::PhantomData::<EncryptionData<T>>,
            );

            let encrypted_data: EncryptionData<T> = match network_curve
                .ecdh_get_cipher_from_keys(
                    &round1_receiver_package,
                    &round1_secret_package,
                    &info,
                    sp_std::marker::PhantomData::<EncryptionData<T>>,
                )
                .and_then(|cipher| network_curve.ecdh_encrypt_package(
                    &cipher,
                    &info,
                    &round2_package,
                    &mut rng,
                )) {
                    Ok(encrypted_data) => encrypted_data,
                    Err(_) => break,
                };

            Self::prepare_bundled_data(
                bitmask,
                blob,
                &encrypted_data.encode(),
                max_size,
                receiver_index,
            );
        }
    }

    fn prepare_bundled_data(
        bitmask: &mut Vec<u8>,
        blob: &mut Vec<u8>,
        encoded_bytes: &[u8],
        max_size: usize,
        authority_index: AuthIndex,
    ) {
        let global_index = authority_index as usize;
        let byte_index = global_index >> 3usize;
        let bit_index = global_index & 7usize;

        if let Some(byte) = bitmask.get_mut(byte_index) {
            if encoded_bytes.len() > max_size { return; }

            let start_offset = global_index.saturating_mul(max_size);
            let end_offset = start_offset.saturating_add(max_size);

            if end_offset > blob.len() { return; }

            let target_slice = &mut blob[start_offset..end_offset];
            target_slice[..encoded_bytes.len()].copy_from_slice(&encoded_bytes);

            *byte |= 1 << bit_index;
        }
    }

    #[cfg(any(test, feature = "runtime-benchmarks"))]
    fn get_offchain_rng(authority_index: AuthIndex) -> ChaCha20Rng {
        let mut seed = [0u8; 32];

        let unique_id = (authority_index as u64).saturating_add(1);
        let index_bytes = unique_id.to_le_bytes();

        for (i, &byte) in index_bytes.iter().enumerate() {
            if i < seed.len() {
                seed[i] ^= byte;
            }
        }

        rand_chacha::ChaCha20Rng::from_seed(seed)
    }

    #[cfg(not(any(test, feature = "runtime-benchmarks")))]
    fn get_offchain_rng() -> ChaCha20Rng {
        let seed = sp_io::offchain::random_seed();
        ChaCha20Rng::from_seed(seed)
    }

    fn create_storage_key(first: &[u8], second: &[u8]) -> Vec<u8> {
        let mut key = DB_STORAGE_PREFIX.to_vec();
        key.extend(first);
        key.extend(second);
        key
    }

    fn create_dkg_based_prefix(dkg_index: DkgIndex) -> Vec<u8> {
        let dkg_bytes = dkg_index.to_le_bytes();
        let total_size = DKG_SECRET_PREFIX.len() + dkg_bytes.len();

        let mut prefix = Vec::with_capacity(total_size);

        prefix.extend_from_slice(DKG_SECRET_PREFIX);
        prefix.extend_from_slice(&dkg_bytes);

        prefix
    }

    fn create_offchain_dkg_key(
        network_curve: NetworkCurve,
        authority_index: AuthIndex,
        round_number: u8,
    ) -> Vec<u8> {
        let mut index_key = DB_STORAGE_PREFIX.to_vec();
        index_key.extend_from_slice(OFFCHAIN_INDEX_KEY);
        index_key.extend_from_slice(&network_curve.encode());
        index_key.extend_from_slice(&authority_index.encode());
        index_key.extend_from_slice(&round_number.encode());

        index_key
    }

    fn get_local_authorities(
        authorities: impl Iterator<Item = T::AuthorityId>,
    ) -> impl Iterator<Item = (AuthIndex, T::AuthorityId)> {
        let mut local_authorities = T::AuthorityId::all();
        local_authorities.sort();

        authorities
            .into_iter()
            .enumerate()
            .filter_map(move |(index, authority)| {
                local_authorities
                    .binary_search(&authority)
                    .ok()
                    .map(|location| (index as AuthIndex, local_authorities[location].clone()))
            })
    }

    fn start_dkg_qualification(
        authorities: Vec::<T::AuthorityId>,
        network_curves: impl Iterator<Item = NetworkCurve>,
    ) {
        let authorities_len = authorities.len();
        let next_authorities =
            WeakBoundedVec::<_, T::MaxAuthorities>::force_from(
                authorities,
                Some("exodus reached maximum number of authorities"),
            );

        let block_number = T::BlockNumberProvider::current_block_number();

        for network_curve in network_curves {
            let active_authorities = ActiveAuthorities::<T>::get(&network_curve);
            let keys_changed = active_authorities != next_authorities;

            if keys_changed {
                QualificationAuthorities::<T>::insert(&network_curve, next_authorities.clone());
                QualificationDkgState::<T>::mutate(&network_curve, |state| {
                    state.new_dkg(authorities_len);
                    state.set_block(block_number);
                });
            }
        }
    }
}

impl<T: Config> sp_runtime::BoundToRuntimeAppPublic for Pallet<T> {
    type Public = T::AuthorityId;
}

impl<T: Config> BlockNumberProvider for Pallet<T> {
    type BlockNumber = BlockNumberFor<T>;
    fn current_block_number() -> Self::BlockNumber {
        T::BlockNumberProvider::current_block_number()
    }
}

impl<T: Config> OneSessionHandler<T::AccountId> for Pallet<T> {
    type Key = T::AuthorityId;

    fn on_genesis_session<'a, I: 'a>(validators: I)
    where
    	I: Iterator<Item = (&'a T::AccountId, T::AuthorityId)>,
    {
        use strum::IntoEnumIterator;

        let mut authorities = validators.map(|x| x.1).collect::<Vec<_>>();
        authorities.sort();

        Self::start_dkg_qualification(authorities, NetworkCurve::iter());
    }

    fn on_new_session<'a, I: 'a>(_changed: bool, _validators: I, queued_validators: I)
    where
        I: Iterator<Item = (&'a T::AccountId, T::AuthorityId)>,
    {
        let mut authorities = queued_validators
            .map(|(_, authority_id)| authority_id)
            .collect::<Vec<_>>();

        authorities.sort();

        let network_curves = T::NetworkDataHandler::iter_curves();
        Self::start_dkg_qualification(authorities, network_curves);
    }

    fn on_before_session_ending() {}

    fn on_disabled(_validator_index: u32) {
        // NOTE: what should we do if validator is disabled?
        // It seems like nothing... Because disabling is not permanent
        // but temporary at max for one epoch. But it's not final
        // solution.
    }
}
