diff --git a/bitacross-worker/Cargo.lock b/bitacross-worker/Cargo.lock index 7820d81aff..4e9007f1ed 100644 --- a/bitacross-worker/Cargo.lock +++ b/bitacross-worker/Cargo.lock @@ -384,7 +384,7 @@ dependencies = [ ] [[package]] -name = "bc-musig2-runner" +name = "bc-musig2-event" version = "0.1.0" dependencies = [ "bc-enclave-registry", @@ -408,6 +408,8 @@ dependencies = [ "sgx_rand", "sgx_tstd", "sp-core", + "threadpool 1.8.0", + "threadpool 1.8.1", ] [[package]] @@ -445,11 +447,12 @@ dependencies = [ ] [[package]] -name = "bc-task-receiver" +name = "bc-task-processor" version = "0.1.0" dependencies = [ "bc-enclave-registry", "bc-musig2-ceremony", + "bc-musig2-event", "bc-relayer-registry", "bc-signer-registry", "bc-task-sender", @@ -457,6 +460,8 @@ dependencies = [ "futures 0.3.8", "hex", "ita-stf", + "itc-direct-rpc-client", + "itc-direct-rpc-server", "itp-ocall-api", "itp-sgx-crypto", "itp-sgx-externalities", @@ -467,7 +472,9 @@ dependencies = [ "litentry-primitives", "log 0.4.20", "parity-scale-codec", + "sgx_crypto_helper", "sgx_tstd", + "sp-core", "thiserror 1.0.44", "thiserror 1.0.9", "threadpool 1.8.0", @@ -2970,7 +2977,6 @@ version = "0.1.0" dependencies = [ "itp-rpc", "itp-types", - "itp-utils", "log 0.4.20", "parity-scale-codec", "rustls 0.19.0 (git+https://github.com/mesalock-linux/rustls?tag=sgx_1.1.3)", diff --git a/bitacross-worker/Cargo.toml b/bitacross-worker/Cargo.toml index 39a6962fd9..5b64c7fda0 100644 --- a/bitacross-worker/Cargo.toml +++ b/bitacross-worker/Cargo.toml @@ -57,13 +57,13 @@ members = [ "service", "litentry/primitives", "litentry/core/direct-call", - "bitacross/core/bc-task-receiver", + "bitacross/core/bc-task-processor", "bitacross/core/bc-task-sender", "bitacross/core/bc-enclave-registry", "bitacross/core/bc-relayer-registry", "bitacross/core/bc-signer-registry", "bitacross/core/bc-musig2-ceremony", - "bitacross/core/bc-musig2-runner", + "bitacross/core/bc-musig2-event", ] [patch."https://github.com/apache/teaclave-sgx-sdk.git"] diff --git a/bitacross-worker/bitacross/core/bc-musig2-ceremony/src/lib.rs b/bitacross-worker/bitacross/core/bc-musig2-ceremony/src/lib.rs index a4609b60f9..d8e0a79835 100644 --- a/bitacross-worker/bitacross/core/bc-musig2-ceremony/src/lib.rs +++ b/bitacross-worker/bitacross/core/bc-musig2-ceremony/src/lib.rs @@ -28,76 +28,63 @@ use std::{format, string::String, sync::Arc}; #[cfg(all(feature = "std", feature = "sgx"))] compile_error!("feature \"std\" and feature \"sgx\" cannot be enabled at the same time"); -use k256::SecretKey; -use musig2::{ - secp::Point, verify_single, BinaryEncoding, CompactSignature, KeyAggContext, LiftedSignature, - SecNonceSpices, -}; -use std::{vec, vec::Vec}; +#[cfg(feature = "std")] +use std::sync::RwLock; + +#[cfg(feature = "sgx")] +use std::sync::SgxRwLock as RwLock; -use crate::CeremonyEvent::CeremonyEnded; use codec::{Decode, Encode}; use itp_sgx_crypto::{key_repository::AccessKey, schnorr::Pair as SchnorrPair}; +use k256::SecretKey; pub use k256::{elliptic_curve::sec1::FromEncodedPoint, PublicKey}; use litentry_primitives::RequestAesKey; -use log::{debug, error, info, trace}; -use musig2::secp::Scalar; +use log::*; +use musig2::{ + secp::{Point, Scalar}, + verify_single, BinaryEncoding, CompactSignature, KeyAggContext, LiftedSignature, + SecNonceSpices, +}; pub use musig2::{PartialSignature, PubNonce}; -use std::collections::HashMap; +use std::{ + collections::HashMap, + time::{SystemTime, UNIX_EPOCH}, + vec, + vec::Vec, +}; pub type CeremonyId = SignBitcoinPayload; pub type SignaturePayload = Vec; pub type Signers = Vec; -pub type CeremonyRegistry = HashMap>; -pub type CeremonyCommandsRegistry = HashMap>; +pub type CeremonyRegistry = HashMap>>, u64)>; +pub type CeremonyCommandTmp = HashMap>>, u64)>; // enclave public key is used as signer identifier pub type SignerId = [u8; 32]; pub type SignersWithKeys = Vec<(SignerId, PublicKey)>; -pub struct PendingCeremonyCommand { - pub ticks_left: u32, - pub command: CeremonyCommand, -} - -impl PendingCeremonyCommand { - pub fn tick(&mut self) { - self.ticks_left -= 1; - } -} - -#[derive(Encode, Debug)] -pub enum SignBitcoinError { - InvalidSigner, - CeremonyError, -} - -#[derive(Debug, Eq, PartialEq)] +#[derive(Debug, Eq, PartialEq, Encode)] pub enum CeremonyError { - NonceReceivingError(NonceReceivingErrorReason), - PartialSignatureReceivingError(PartialSignatureReceivingErrorReason), + CeremonyInitError(CeremonyErrorReason), + NonceReceivingError(CeremonyErrorReason), + PartialSignatureReceivingError(CeremonyErrorReason), } -#[derive(Debug, Eq, PartialEq)] -pub enum NonceReceivingErrorReason { +#[derive(Debug, Eq, PartialEq, Encode)] +pub enum CeremonyErrorReason { + AlreadyExist, + CreateCeremonyError, SignerNotFound, ContributionError, IncorrectRound, - FirstRoundFinalizationError, + RoundFinalizationError, } -#[derive(Debug, Eq, PartialEq)] -pub enum PartialSignatureReceivingErrorReason { - SignerNotFound, - ContributionError, - IncorrectRound, - SecondRoundFinalizationError, -} - -// events come from outside and are consumed by ceremony in tick fn -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq, Clone)] pub enum CeremonyCommand { + InitCeremony([u8; 32], SignersWithKeys, SignBitcoinPayload, bool), SaveNonce(SignerId, PubNonce), SavePartialSignature(SignerId, PartialSignature), + KillCeremony, } // commands are created by ceremony and executed by runner @@ -105,9 +92,8 @@ pub enum CeremonyCommand { pub enum CeremonyEvent { FirstRoundStarted(Signers, CeremonyId, PubNonce), SecondRoundStarted(Signers, CeremonyId, PartialSignature), - CeremonyError(Signers, CeremonyError, RequestAesKey), CeremonyEnded([u8; 64], RequestAesKey, bool, bool), - CeremonyTimedOut(Signers, RequestAesKey), + CeremonyError(Signers, CeremonyError, RequestAesKey), } #[derive(Encode, Decode, Clone, Debug, PartialEq, Eq, Hash)] @@ -123,37 +109,39 @@ pub fn generate_aggregated_public_key(mut public_keys: Vec) -> Public KeyAggContext::new(public_keys).unwrap().aggregated_pubkey() } -pub struct MuSig2Ceremony> { +pub struct MuSig2CeremonyData> { payload: SignBitcoinPayload, // P-713: move to layer above, ceremony should be communication agnostic aes_key: RequestAesKey, me: SignerId, signers: SignersWithKeys, - commands: Vec, - events: Vec, signing_key_access: Arc, - first_round: Option, - second_round: Option>, - ticks_left: u32, - agg_key: Option, + agg_key: PublicKey, // indicates whether it's check run - signature verification result is returned instead of signature check_run: bool, } +pub struct MuSig2CeremonyState { + first_round: Option, + second_round: Option>, +} + +pub struct MuSig2Ceremony> { + ceremony_data: MuSig2CeremonyData, + ceremony_state: MuSig2CeremonyState, +} + impl> MuSig2Ceremony { - // Creates new ceremony and starts first round - #[allow(clippy::too_many_arguments)] + // Creates new ceremony pub fn new( me: SignerId, aes_key: RequestAesKey, mut signers: SignersWithKeys, payload: SignBitcoinPayload, - commands: Vec, signing_key_access: Arc, - ttl: u32, check_run: bool, - ) -> Result { - info!("Creating new ceremony {:?} and events {:?}", payload, commands); + ) -> Result<(Self, CeremonyEvent), String> { + info!("Creating new ceremony {:?}", payload); if signers.len() < 3 { return Err(format!("Not enough signers, minimum: {:?}, actual {:?}", 3, signers.len())) } @@ -195,139 +183,103 @@ impl> MuSig2Ceremony { "Ceremony aggregated public key: {:?}", key_context.aggregated_pubkey::().to_sec1_bytes().to_vec() ); - let agg_key_copy = key_context.aggregated_pubkey::(); + let agg_key = key_context.aggregated_pubkey::(); let nonce_seed = random_seed(); let first_round = musig2::FirstRound::new(key_context, nonce_seed, my_index, SecNonceSpices::new()) .map_err(|e| format!("First round creation error: {:?}", e))?; - let public_nonce = first_round.our_public_nonce(); - let events = vec![CeremonyEvent::FirstRoundStarted( - signers.iter().filter(|e| e.0 != me).map(|s| s.0).collect(), - payload.clone(), - public_nonce, - )]; - - Ok(Self { - payload, - aes_key, - me, - signers, - commands, - events, - signing_key_access, - first_round: Some(first_round), - second_round: None, - ticks_left: ttl, - agg_key: Some(agg_key_copy), - check_run, - }) - } - - pub fn tick(&mut self) -> Vec { - trace!("Ceremony {:?} tick", self.get_id_ref()); - self.process_commands(); - self.ticks_left -= 1; - - if self.ticks_left == 0 { - self.events.push(CeremonyEvent::CeremonyTimedOut( - self.signers.iter().filter(|e| e.0 != self.me).map(|s| s.0).collect(), - self.aes_key, - )); - } - - self.events.drain(0..).collect() - } - - pub fn save_event(&mut self, event: CeremonyCommand) { - self.commands.push(event); + let ceremony = Self { + ceremony_data: MuSig2CeremonyData { + payload, + aes_key, + me, + signers, + signing_key_access, + agg_key, + check_run, + }, + ceremony_state: MuSig2CeremonyState { + first_round: Some(first_round), + second_round: None, + }, + }; + let event = ceremony.start_first_round(); + Ok((ceremony, event)) } - fn process_commands(&mut self) { - let mut i = 0; - let mut commands_to_execute = vec![]; - while i < self.commands.len() { - if match self.commands.get(i).unwrap() { - CeremonyCommand::SaveNonce(_, _) => self.first_round.is_some(), - CeremonyCommand::SavePartialSignature(_, _) => self.second_round.is_some(), - } { - commands_to_execute.push(self.commands.remove(i)); - } else { - debug!( - "Skipping ceremony command {:?} processing due to incorrect round", - self.commands.get(i).unwrap() - ); - i += 1; - } - } - for command in commands_to_execute.into_iter() { - debug!( - "Processing ceremony command: {:?} for ceremony: {:?}", - command, - self.get_id_ref() - ); - if let Err(e) = match command { - CeremonyCommand::SaveNonce(signer, nonce) => self.receive_nonce(signer, nonce), - CeremonyCommand::SavePartialSignature(signer, partial_signature) => - self.receive_partial_sign(signer, partial_signature), - } { - self.events.push(CeremonyEvent::CeremonyError( - self.signers.iter().filter(|e| e.0 != self.me).map(|s| s.0).collect(), - e, - self.aes_key, - )); - } - } + fn start_first_round(&self) -> CeremonyEvent { + self.ceremony_state + .first_round + .as_ref() + .map(|f| { + CeremonyEvent::FirstRoundStarted( + self.get_signers_except_self(), + self.ceremony_data.payload.clone(), + f.our_public_nonce(), + ) + }) + .unwrap() } // Saves signer's nonce - fn receive_nonce(&mut self, signer: SignerId, nonce: PubNonce) -> Result<(), CeremonyError> { + pub fn receive_nonce( + &mut self, + signer: SignerId, + nonce: PubNonce, + ) -> Result, CeremonyError> { info!("Saving nonce from signer: {:?}", signer); - let peer_index = - self.signers.iter().position(|p| p.0 == signer).ok_or( - CeremonyError::NonceReceivingError(NonceReceivingErrorReason::SignerNotFound), - )?; - - if let Some(ref mut r) = self.first_round { + let peer_index = self + .ceremony_data + .signers + .iter() + .position(|p| p.0 == signer) + .ok_or(CeremonyError::NonceReceivingError(CeremonyErrorReason::SignerNotFound))?; + + if let Some(ref mut r) = self.ceremony_state.first_round { r.receive_nonce(peer_index, nonce).map_err(|e| { error!("Nonce receiving error: {:?}", e); - CeremonyError::NonceReceivingError(NonceReceivingErrorReason::ContributionError) + CeremonyError::NonceReceivingError(CeremonyErrorReason::ContributionError) })?; if r.is_complete() { let secret_key = SecretKey::from_slice( &self + .ceremony_data .signing_key_access .retrieve_key() .map_err(|e| { error!("Nonce receiving error: {:?}", e); CeremonyError::NonceReceivingError( - NonceReceivingErrorReason::FirstRoundFinalizationError, + CeremonyErrorReason::RoundFinalizationError, ) })? .private_bytes(), ) .map_err(|e| { error!("Nonce receiving error: {:?}", e); - CeremonyError::NonceReceivingError( - NonceReceivingErrorReason::FirstRoundFinalizationError, - ) + CeremonyError::NonceReceivingError(CeremonyErrorReason::RoundFinalizationError) })?; - self.start_second_round(secret_key)?; + self.start_second_round(secret_key).map(Some) + } else { + Ok(None) } - Ok(()) } else { - Err(CeremonyError::NonceReceivingError(NonceReceivingErrorReason::IncorrectRound)) + Err(CeremonyError::NonceReceivingError(CeremonyErrorReason::IncorrectRound)) } } // Starts the second round - fn start_second_round(&mut self, private_key: SecretKey) -> Result<(), CeremonyError> { + fn start_second_round( + &mut self, + private_key: SecretKey, + ) -> Result { let first_round = self + .ceremony_state .first_round .take() - .ok_or(CeremonyError::NonceReceivingError(NonceReceivingErrorReason::IncorrectRound))?; + .ok_or(CeremonyError::NonceReceivingError(CeremonyErrorReason::IncorrectRound))?; - let message = match &self.payload { + let message = match &self.ceremony_data.payload { SignBitcoinPayload::TaprootSpendable(message, _) => message.clone(), SignBitcoinPayload::Derived(message) => message.clone(), SignBitcoinPayload::TaprootUnspendable(message) => message.clone(), @@ -335,89 +287,108 @@ impl> MuSig2Ceremony { }; let second_round = first_round.finalize(private_key, message).map_err(|e| { error!("Could not start second round: {:?}", e); - CeremonyError::NonceReceivingError( - NonceReceivingErrorReason::FirstRoundFinalizationError, - ) + CeremonyError::NonceReceivingError(CeremonyErrorReason::RoundFinalizationError) })?; let partial_signature: PartialSignature = second_round.our_signature(); - self.events.push(CeremonyEvent::SecondRoundStarted( - self.signers.iter().filter(|e| e.0 != self.me).map(|s| s.0).collect(), + self.ceremony_state.second_round = Some(second_round); + + Ok(CeremonyEvent::SecondRoundStarted( + self.get_signers_except_self(), self.get_id_ref().clone(), partial_signature, - )); - self.second_round = Some(second_round); - Ok(()) + )) } // Saves signer's partial signature - fn receive_partial_sign( + pub fn receive_partial_sign( &mut self, signer: SignerId, partial_signature: impl Into, - ) -> Result<(), CeremonyError> { + ) -> Result, CeremonyError> { info!("Saving partial signature from signer: {:?}", signer); - let peer_index = self.signers.iter().position(|p| p.0 == signer).ok_or( - CeremonyError::PartialSignatureReceivingError( - PartialSignatureReceivingErrorReason::SignerNotFound, - ), + let peer_index = self.ceremony_data.signers.iter().position(|p| p.0 == signer).ok_or( + CeremonyError::PartialSignatureReceivingError(CeremonyErrorReason::SignerNotFound), )?; - if let Some(ref mut r) = self.second_round { + if let Some(ref mut r) = self.ceremony_state.second_round { r.receive_signature(peer_index, partial_signature).map_err(|e| { error!("Signature receiving error: {:?}", e); CeremonyError::PartialSignatureReceivingError( - PartialSignatureReceivingErrorReason::ContributionError, + CeremonyErrorReason::ContributionError, ) })?; if r.is_complete() { - if let Some(r) = self.second_round.take() { + if let Some(r) = self.ceremony_state.second_round.take() { let signature: CompactSignature = r .finalize::() .map_err(|e| { error!("Could not finish second round: {:?}", e); CeremonyError::PartialSignatureReceivingError( - PartialSignatureReceivingErrorReason::SecondRoundFinalizationError, + CeremonyErrorReason::RoundFinalizationError, ) })? .compact(); - info!("Ceremony {:?} has ended", self.get_id_ref()); - info!("Aggregated public key {:?}", self.agg_key.unwrap().to_sec1_bytes()); + info!("Ceremony {:?} `has ended`", self.get_id_ref()); + info!("Aggregated public key {:?}", self.ceremony_data.agg_key.to_sec1_bytes()); info!("Signature {:?}", signature.to_bytes()); - let message = match &self.payload { + let message = match &self.ceremony_data.payload { SignBitcoinPayload::Derived(p) => p, SignBitcoinPayload::TaprootUnspendable(p) => p, SignBitcoinPayload::TaprootSpendable(p, _) => p, SignBitcoinPayload::WithTweaks(p, _) => p, }; - let result = verify_single(self.agg_key.unwrap(), signature, message).is_ok(); - self.events.push(CeremonyEnded( + + let result = + verify_single(self.ceremony_data.agg_key, signature, message).is_ok(); + Ok(Some(CeremonyEvent::CeremonyEnded( signature.to_bytes(), - self.aes_key, - self.check_run, + self.ceremony_data.aes_key, + self.ceremony_data.check_run, result, - )); + ))) + } else { + Err(CeremonyError::PartialSignatureReceivingError( + CeremonyErrorReason::IncorrectRound, + )) } + } else { + Ok(None) } - Ok(()) } else { - Err(CeremonyError::PartialSignatureReceivingError( - PartialSignatureReceivingErrorReason::IncorrectRound, - )) + Err(CeremonyError::PartialSignatureReceivingError(CeremonyErrorReason::IncorrectRound)) } } + pub fn get_signers_except_self(&self) -> Signers { + self.ceremony_data + .signers + .iter() + .filter(|e| e.0 != self.ceremony_data.me) + .map(|s| s.0) + .collect() + } + pub fn get_id_ref(&self) -> &CeremonyId { - &self.payload + &self.ceremony_data.payload } + pub fn get_aes_key(&self) -> &RequestAesKey { - &self.aes_key + &self.ceremony_data.aes_key + } + + pub fn is_first_round(&self) -> bool { + self.ceremony_state.first_round.is_some() } } +pub fn get_current_timestamp() -> u64 { + SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() +} + #[cfg(feature = "std")] fn random_seed() -> [u8; 32] { use rand::{thread_rng, RngCore}; @@ -440,18 +411,14 @@ fn random_seed() -> [u8; 32] { #[cfg(test)] pub mod test { use crate::{ - CeremonyCommand, CeremonyError, CeremonyEvent, MuSig2Ceremony, NonceReceivingErrorReason, - SignBitcoinPayload, SignerId, SignersWithKeys, + CeremonyError, CeremonyErrorReason, CeremonyEvent, MuSig2Ceremony, SignBitcoinPayload, + SignerId, SignersWithKeys, }; use alloc::sync::Arc; use itp_sgx_crypto::{key_repository::AccessKey, schnorr::Pair as SchnorrPair}; - use k256::{ - elliptic_curve::PublicKey, - schnorr::{signature::Keypair, SigningKey}, - sha2::digest::Mac, - }; + use k256::{elliptic_curve::PublicKey, schnorr::SigningKey}; use litentry_primitives::RequestAesKey; - use musig2::{secp::MaybeScalar, SecNonce}; + use musig2::SecNonce; pub const MY_SIGNER_ID: SignerId = [0u8; 32]; @@ -504,31 +471,6 @@ pub mod test { ] } - fn save_signer1_nonce_cmd() -> CeremonyCommand { - CeremonyCommand::SaveNonce( - SIGNER_1_ID, - SecNonce::from_bytes(&SIGNER_1_SEC_NONCE).unwrap().public_nonce(), - ) - } - - fn save_signer2_nonce_cmd() -> CeremonyCommand { - CeremonyCommand::SaveNonce( - SIGNER_2_ID, - SecNonce::from_bytes(&SIGNER_2_SEC_NONCE).unwrap().public_nonce(), - ) - } - - fn save_signer1_partial_sign_cmd() -> CeremonyCommand { - CeremonyCommand::SavePartialSignature(SIGNER_1_ID, MaybeScalar::Zero) - } - - fn unknown_signer_nonce_cmd() -> CeremonyCommand { - CeremonyCommand::SaveNonce( - [10u8; 32], - SecNonce::from_bytes(&SIGNER_2_SEC_NONCE).unwrap().public_nonce(), - ) - } - pub const SAMPLE_REQUEST_AES_KEY: RequestAesKey = [0u8; 32]; pub const SAMPLE_SIGNATURE_PAYLOAD: [u8; 32] = [0u8; 32]; @@ -545,7 +487,7 @@ pub mod test { } #[test] - fn it_should_create_ceremony_without_pending_commands() { + fn it_should_create_ceremony_in_firstround() { // given let signing_key_access = MockedSigningKeyAccess { signing_key: my_priv_key() }; @@ -555,14 +497,13 @@ pub mod test { SAMPLE_REQUEST_AES_KEY.clone(), signers_with_keys(), SignBitcoinPayload::Derived(SAMPLE_SIGNATURE_PAYLOAD.to_vec()), - vec![], Arc::new(signing_key_access), - 10, false, ); // then - assert!(result.is_ok()) + assert!(result.is_ok()); + assert!(result.unwrap().0.is_first_round()) } #[test] @@ -576,9 +517,7 @@ pub mod test { SAMPLE_REQUEST_AES_KEY.clone(), signers_with_keys()[0..1].to_vec(), SignBitcoinPayload::Derived(SAMPLE_SIGNATURE_PAYLOAD.to_vec()), - vec![], Arc::new(signing_key_access), - 10, false, ); @@ -587,155 +526,80 @@ pub mod test { } #[test] - fn it_should_timeout_after_certain_ticks() { + fn it_should_produce_error_due_to_nonce_from_unknown_signer() { // given let signing_key_access = MockedSigningKeyAccess { signing_key: my_priv_key() }; - - let ceremony_id = SignBitcoinPayload::Derived(SAMPLE_SIGNATURE_PAYLOAD.to_vec()); let mut ceremony = MuSig2Ceremony::new( MY_SIGNER_ID, SAMPLE_REQUEST_AES_KEY.clone(), signers_with_keys(), - ceremony_id.clone(), - vec![], - Arc::new(signing_key_access), - 10, - false, - ) - .unwrap(); - - // when - (0..9).for_each(|_| { - ceremony.tick(); - }); - let events = ceremony.tick(); - - // then - assert!(matches!(events.get(0), Some(CeremonyEvent::CeremonyTimedOut(_, _)))); - assert_eq!(events.len(), 1) - } - - #[test] - fn newly_created_ceremony_without_commands_should_produce_first_round_started_event_after_tick() - { - // given - let signing_key_access = MockedSigningKeyAccess { signing_key: my_priv_key() }; - let ceremony_id = SignBitcoinPayload::Derived(SAMPLE_SIGNATURE_PAYLOAD.to_vec()); - let mut ceremony = MuSig2Ceremony::new( - MY_SIGNER_ID, - SAMPLE_REQUEST_AES_KEY.clone(), - signers_with_keys(), - ceremony_id.clone(), - vec![], + SignBitcoinPayload::Derived(SAMPLE_SIGNATURE_PAYLOAD.to_vec()), Arc::new(signing_key_access), - 10, false, ) - .unwrap(); + .unwrap() + .0; - // when - let events = ceremony.tick(); + assert!(ceremony.ceremony_state.first_round.is_some()); + assert!(ceremony.ceremony_state.second_round.is_none()); - assert!( - matches!(events.get(0), Some(CeremonyEvent::FirstRoundStarted(ref signers, ref ev_ceremony_id, ref _pub_nonce)) if *signers == vec![SIGNER_1_ID, SIGNER_2_ID] && *ev_ceremony_id == ceremony_id) + let event = ceremony.receive_nonce( + [10u8; 32], + SecNonce::from_bytes(&SIGNER_2_SEC_NONCE).unwrap().public_nonce(), ); - assert_eq!(events.len(), 1) + assert!(ceremony.ceremony_state.first_round.is_some()); + assert!(ceremony.ceremony_state.second_round.is_none()); + assert!(event.is_err()); + assert!(matches!( + event.unwrap_err(), + CeremonyError::NonceReceivingError(CeremonyErrorReason::SignerNotFound) + )); } #[test] - fn newly_created_ceremony_with_not_all_nonce_commands_should_produce_only_first_round_started_event_after_tick( - ) { + fn it_should_complete_successfully() { // given let signing_key_access = MockedSigningKeyAccess { signing_key: my_priv_key() }; - let ceremony_id = SignBitcoinPayload::Derived(SAMPLE_SIGNATURE_PAYLOAD.to_vec()); let mut ceremony = MuSig2Ceremony::new( MY_SIGNER_ID, SAMPLE_REQUEST_AES_KEY.clone(), signers_with_keys(), - ceremony_id.clone(), - vec![save_signer1_nonce_cmd()], + SignBitcoinPayload::Derived(SAMPLE_SIGNATURE_PAYLOAD.to_vec()), Arc::new(signing_key_access), - 10, false, ) - .unwrap(); + .unwrap() + .0; - // when - let events = ceremony.tick(); + assert!(ceremony.ceremony_state.first_round.is_some()); + assert!(ceremony.ceremony_state.second_round.is_none()); - assert!( - matches!(events.get(0), Some(CeremonyEvent::FirstRoundStarted(ref signers, ref ev_ceremony_id, ref _pub_nonce)) if *signers == vec![SIGNER_1_ID, SIGNER_2_ID] && *ev_ceremony_id == ceremony_id) + let event = ceremony.receive_nonce( + SIGNER_1_ID, + SecNonce::from_bytes(&SIGNER_1_SEC_NONCE).unwrap().public_nonce(), ); - assert_eq!(events.len(), 1) - } - - #[test] - fn newly_created_ceremony_with_all_nonce_commands_should_produce_first_and_second_round_started_events_after_tick( - ) { - // given - let signing_key_access = MockedSigningKeyAccess { signing_key: my_priv_key() }; - let ceremony_id = SignBitcoinPayload::Derived(SAMPLE_SIGNATURE_PAYLOAD.to_vec()); - let mut ceremony = MuSig2Ceremony::new( - MY_SIGNER_ID, - SAMPLE_REQUEST_AES_KEY.clone(), - signers_with_keys(), - ceremony_id.clone(), - vec![ - save_signer1_partial_sign_cmd(), - save_signer1_nonce_cmd(), - save_signer2_nonce_cmd(), - ], - Arc::new(signing_key_access), - 10, - false, - ) - .unwrap(); + assert!(ceremony.ceremony_state.first_round.is_some()); + assert!(ceremony.ceremony_state.second_round.is_none()); + assert!(event.is_ok()); + assert!(event.unwrap().is_none()); - // when - let events = ceremony.tick(); - - assert!( - matches!(events.get(0), Some(CeremonyEvent::FirstRoundStarted(ref signers, ref ev_ceremony_id, ref _pub_nonce)) if *signers == vec![SIGNER_1_ID, SIGNER_2_ID] && *ev_ceremony_id == ceremony_id) - ); - assert!( - matches!(events.get(1), Some(CeremonyEvent::SecondRoundStarted(ref signers, ref ev_ceremony_id, ref _partial_signature)) if *signers == vec![SIGNER_1_ID, SIGNER_2_ID] && *ev_ceremony_id == ceremony_id) + let event = ceremony.receive_nonce( + SIGNER_2_ID, + SecNonce::from_bytes(&SIGNER_2_SEC_NONCE).unwrap().public_nonce(), ); - assert_eq!(events.len(), 2) - } - - #[test] - fn newly_created_ceremony_with_unknown_signer_command_should_produce_first_round_started_event_and_error_event_after_tick( - ) { - // given - let signing_key_access = MockedSigningKeyAccess { signing_key: my_priv_key() }; - let ceremony_id = SignBitcoinPayload::Derived(SAMPLE_SIGNATURE_PAYLOAD.to_vec()); - let mut ceremony = MuSig2Ceremony::new( - MY_SIGNER_ID, - SAMPLE_REQUEST_AES_KEY.clone(), - signers_with_keys(), - ceremony_id.clone(), - vec![unknown_signer_nonce_cmd()], - Arc::new(signing_key_access), - 10, - false, - ) - .unwrap(); - - // when - let events = ceremony.tick(); - - assert!( - matches!(events.get(0), Some(CeremonyEvent::FirstRoundStarted(ref signers, ref ev_ceremony_id, ref _pub_nonce)) if *signers == vec![SIGNER_1_ID, SIGNER_2_ID] && *ev_ceremony_id == ceremony_id) + assert!(ceremony.ceremony_state.first_round.is_none()); + assert!(ceremony.ceremony_state.second_round.is_some()); + assert!(event.is_ok()); + let event = event.unwrap(); + assert!(event.is_some()); + assert_eq!( + event.unwrap(), + CeremonyEvent::SecondRoundStarted( + vec![SIGNER_1_ID, SIGNER_2_ID], + SignBitcoinPayload::Derived(SAMPLE_SIGNATURE_PAYLOAD.to_vec()), + ceremony.ceremony_state.second_round.as_ref().unwrap().our_signature(), + ) ); - assert!(matches!( - events.get(1), - Some(CeremonyEvent::CeremonyError( - _, - CeremonyError::NonceReceivingError(NonceReceivingErrorReason::SignerNotFound), - _ - )) - )); - assert_eq!(events.len(), 2) } } @@ -777,12 +641,11 @@ pub mod sgx_tests { SAMPLE_REQUEST_AES_KEY.clone(), signers_with_keys(), ceremony_id.clone(), - vec![], Arc::new(my_signer_key_access), - 10, false, ) - .unwrap(); + .unwrap() + .0; // signer 1 let signer1_key_access = MockedSigningKeyAccess { signing_key: signer1_priv_key() }; let mut signer1_ceremony = MuSig2Ceremony::new( @@ -790,12 +653,11 @@ pub mod sgx_tests { SAMPLE_REQUEST_AES_KEY.clone(), signers_with_keys(), ceremony_id.clone(), - vec![], Arc::new(signer1_key_access), - 10, false, ) - .unwrap(); + .unwrap() + .0; // signer 2 let signer2_key_access = MockedSigningKeyAccess { signing_key: signer2_priv_key() }; let mut signer2_ceremony = MuSig2Ceremony::new( @@ -803,117 +665,109 @@ pub mod sgx_tests { SAMPLE_REQUEST_AES_KEY.clone(), signers_with_keys(), ceremony_id.clone(), - vec![], Arc::new(signer2_key_access), - 10, false, ) - .unwrap(); - - // when - - // first tick (performs first round in this case) - let mut my_ceremony_events = my_ceremony.tick(); - let mut signer1_ceremony_events = signer1_ceremony.tick(); - let mut signer2_ceremony_events = signer2_ceremony.tick(); - - let my_ceremony_first_round_started_ev = my_ceremony_events.get(0).unwrap(); - let signer1_ceremony_first_round_started_ev = signer1_ceremony_events.get(0).unwrap(); - let signer2_ceremony_first_round_started_ev = signer2_ceremony_events.get(0).unwrap(); - - match my_ceremony_first_round_started_ev { - CeremonyEvent::FirstRoundStarted(_, _, nonce) => { - signer1_ceremony.receive_nonce(MY_SIGNER_ID, nonce.clone()).unwrap(); - signer2_ceremony.receive_nonce(MY_SIGNER_ID, nonce.clone()).unwrap(); - }, - _ => {}, - } - - match signer1_ceremony_first_round_started_ev { - CeremonyEvent::FirstRoundStarted(_, _, nonce) => { - my_ceremony.receive_nonce(SIGNER_1_ID, nonce.clone()).unwrap(); - signer2_ceremony.receive_nonce(SIGNER_1_ID, nonce.clone()).unwrap(); - }, - _ => {}, - } - - match signer2_ceremony_first_round_started_ev { - CeremonyEvent::FirstRoundStarted(_, _, nonce) => { - my_ceremony.receive_nonce(SIGNER_2_ID, nonce.clone()).unwrap(); - signer1_ceremony.receive_nonce(SIGNER_2_ID, nonce.clone()).unwrap(); - }, - _ => {}, + .unwrap() + .0; + + let my_ceremony_nonce = + my_ceremony.ceremony_state.first_round.as_ref().unwrap().our_public_nonce(); + let signer1_ceremony_nonce = + signer1_ceremony.ceremony_state.first_round.as_ref().unwrap().our_public_nonce(); + let signer2_ceremony_nonce = + signer2_ceremony.ceremony_state.first_round.as_ref().unwrap().our_public_nonce(); + + // my signer receive nonce + let my_ceremony_receive_first_nonce_ev = + my_ceremony.receive_nonce(SIGNER_1_ID, signer1_ceremony_nonce.clone()).unwrap(); + match my_ceremony_receive_first_nonce_ev { + None => {}, + ev => panic!("except None but get: {:?}", ev), } + let my_ceremony_second_round_started_ev = + my_ceremony.receive_nonce(SIGNER_2_ID, signer2_ceremony_nonce.clone()).unwrap(); + let my_ceremony_partial_sign = match my_ceremony_second_round_started_ev { + Some(CeremonyEvent::SecondRoundStarted(_, _, partial_sign)) => partial_sign, + ev => panic!("except Some(CeremonyEvent::SecondRoundStarted) but get: {:?}", ev), + }; - // second tick (performs second round in this case) - my_ceremony_events = my_ceremony.tick(); - signer1_ceremony_events = signer1_ceremony.tick(); - signer2_ceremony_events = signer2_ceremony.tick(); - - let my_ceremony_second_round_started_ev = my_ceremony_events.get(0).unwrap(); - let signer1_ceremony_second_round_started_ev = signer1_ceremony_events.get(0).unwrap(); - let signer2_ceremony_second_round_started_ev = signer2_ceremony_events.get(0).unwrap(); - - match my_ceremony_second_round_started_ev { - CeremonyEvent::SecondRoundStarted(_, _, partial_sign) => { - signer1_ceremony - .receive_partial_sign(MY_SIGNER_ID, partial_sign.clone()) - .unwrap(); - signer2_ceremony - .receive_partial_sign(MY_SIGNER_ID, partial_sign.clone()) - .unwrap(); - }, - _ => {}, + // signer 1 receive nonce + let signer1_ceremony_receive_first_nonce_ev = + signer1_ceremony.receive_nonce(MY_SIGNER_ID, my_ceremony_nonce.clone()).unwrap(); + match signer1_ceremony_receive_first_nonce_ev { + None => {}, + ev => panic!("except None but get: {:?}", ev), } + let signer1_ceremony_second_round_started_ev = signer1_ceremony + .receive_nonce(SIGNER_2_ID, signer2_ceremony_nonce.clone()) + .unwrap(); + let signer1_ceremony_partial_sign = match signer1_ceremony_second_round_started_ev { + Some(CeremonyEvent::SecondRoundStarted(_, _, partial_sign)) => partial_sign, + ev => panic!("except Some(CeremonyEvent::SecondRoundStarted) but get: {:?}", ev), + }; - match signer1_ceremony_second_round_started_ev { - CeremonyEvent::SecondRoundStarted(_, _, partial_sign) => { - my_ceremony.receive_partial_sign(SIGNER_1_ID, partial_sign.clone()).unwrap(); - signer2_ceremony - .receive_partial_sign(SIGNER_1_ID, partial_sign.clone()) - .unwrap(); - }, - _ => {}, + // signer 2 receive nonce + let signer2_ceremony_receive_first_nonce_ev = + signer2_ceremony.receive_nonce(MY_SIGNER_ID, my_ceremony_nonce.clone()).unwrap(); + match signer2_ceremony_receive_first_nonce_ev { + None => {}, + ev => panic!("except None but get: {:?}", ev), } + let signer2_ceremony_second_round_started_ev = signer2_ceremony + .receive_nonce(SIGNER_1_ID, signer1_ceremony_nonce.clone()) + .unwrap(); + let signer2_ceremony_partial_sign = match signer2_ceremony_second_round_started_ev { + Some(CeremonyEvent::SecondRoundStarted(_, _, partial_sign)) => partial_sign, + ev => panic!("except Some(CeremonyEvent::SecondRoundStarted) but get: {:?}", ev), + }; - match signer2_ceremony_second_round_started_ev { - CeremonyEvent::SecondRoundStarted(_, _, partial_sign) => { - my_ceremony.receive_partial_sign(SIGNER_2_ID, partial_sign.clone()).unwrap(); - signer1_ceremony - .receive_partial_sign(SIGNER_2_ID, partial_sign.clone()) - .unwrap(); - }, - _ => {}, + // my signer receive partial_sign + let my_ceremony_receive_first_partial_sign_ev = my_ceremony + .receive_partial_sign(SIGNER_1_ID, signer1_ceremony_partial_sign) + .unwrap(); + match my_ceremony_receive_first_partial_sign_ev { + None => {}, + ev => panic!("except None but get: {:?}", ev), } - - // third tick (finalizes ceremony and produces final signature in this case) - my_ceremony_events = my_ceremony.tick(); - signer1_ceremony_events = signer1_ceremony.tick(); - signer2_ceremony_events = signer2_ceremony.tick(); - - let my_ceremony_ceremony_ended_ev = my_ceremony_events.get(0).unwrap(); - let signer1_ceremony_ceremony_ended_ev = signer1_ceremony_events.get(0).unwrap(); - let signer2_ceremony_ceremony_ended_ev = signer2_ceremony_events.get(0).unwrap(); - - let my_ceremony_final_signature = match my_ceremony_ceremony_ended_ev { - CeremonyEvent::CeremonyEnded(signature, _, _, _) => signature.clone(), - _ => { - panic!("Ceremony should be ended") - }, + let my_ceremony_ended_ev = my_ceremony + .receive_partial_sign(SIGNER_2_ID, signer2_ceremony_partial_sign) + .unwrap(); + let my_ceremony_final_signature = match my_ceremony_ended_ev { + Some(CeremonyEvent::CeremonyEnded(signature, _, _, _)) => signature, + ev => panic!("except Some(CeremonyEvent::CeremonyEnded) but get: {:?}", ev), }; - let signer1_ceremony_final_signature = match signer1_ceremony_ceremony_ended_ev { - CeremonyEvent::CeremonyEnded(signature, _, _, _) => signature.clone(), - _ => { - panic!("Ceremony should be ended") - }, + // signer 1 receive partial_sign + let signer1_receive_first_partial_sign_ev = signer1_ceremony + .receive_partial_sign(MY_SIGNER_ID, my_ceremony_partial_sign) + .unwrap(); + match signer1_receive_first_partial_sign_ev { + None => {}, + ev => panic!("except None but get: {:?}", ev), + } + let signer1_ceremony_ended_ev = signer1_ceremony + .receive_partial_sign(SIGNER_2_ID, signer2_ceremony_partial_sign) + .unwrap(); + let signer1_ceremony_final_signature = match signer1_ceremony_ended_ev { + Some(CeremonyEvent::CeremonyEnded(signature, _, _, _)) => signature, + ev => panic!("except Some(CeremonyEvent::CeremonyEnded) but get: {:?}", ev), }; - let signer2_ceremony_final_signature = match signer2_ceremony_ceremony_ended_ev { - CeremonyEvent::CeremonyEnded(signature, _, _, _) => signature.clone(), - _ => { - panic!("Ceremony should be ended") - }, + // signer 2 receive partial_sign + let signer2_receive_first_partial_sign_ev = signer2_ceremony + .receive_partial_sign(MY_SIGNER_ID, my_ceremony_partial_sign) + .unwrap(); + match signer2_receive_first_partial_sign_ev { + None => {}, + ev => panic!("except None but get: {:?}", ev), + } + let signer2_ceremony_ended_ev = signer2_ceremony + .receive_partial_sign(SIGNER_1_ID, signer1_ceremony_partial_sign) + .unwrap(); + let signer2_ceremony_final_signature = match signer2_ceremony_ended_ev { + Some(CeremonyEvent::CeremonyEnded(signature, _, _, _)) => signature, + ev => panic!("except Some(CeremonyEvent::CeremonyEnded) but get: {:?}", ev), }; assert_eq!(my_ceremony_final_signature, signer1_ceremony_final_signature); diff --git a/bitacross-worker/bitacross/core/bc-musig2-runner/Cargo.toml b/bitacross-worker/bitacross/core/bc-musig2-event/Cargo.toml similarity index 92% rename from bitacross-worker/bitacross/core/bc-musig2-runner/Cargo.toml rename to bitacross-worker/bitacross/core/bc-musig2-event/Cargo.toml index f25fd5f74b..5b2295d3a1 100644 --- a/bitacross-worker/bitacross/core/bc-musig2-runner/Cargo.toml +++ b/bitacross-worker/bitacross/core/bc-musig2-event/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "bc-musig2-runner" +name = "bc-musig2-event" authors = ["Trust Computing GmbH "] version = "0.1.0" edition = "2021" @@ -8,9 +8,11 @@ edition = "2021" [dependencies] # std dependencies +threadpool = { version = "1.8.0", optional = true } # sgx dependencies sgx_tstd = { git = "https://github.com/apache/teaclave-sgx-sdk.git", branch = "master", optional = true, features = ["net", "thread"] } +threadpool_sgx = { git = "https://github.com/mesalock-linux/rust-threadpool-sgx", package = "threadpool", tag = "sgx_1.1.3", optional = true } bc-enclave-registry = { path = "../bc-enclave-registry", default-features = false } @@ -56,6 +58,7 @@ std = [ "lc-direct-call/std", "itp-sgx-crypto/std", "rand", + "threadpool", ] sgx = [ "sgx_tstd", @@ -70,4 +73,5 @@ sgx = [ "itp-sgx-crypto/sgx", "sgx_crypto_helper/mesalock_sgx", "sgx_rand", + "threadpool_sgx", ] diff --git a/bitacross-worker/bitacross/core/bc-musig2-event/src/lib.rs b/bitacross-worker/bitacross/core/bc-musig2-event/src/lib.rs new file mode 100644 index 0000000000..ba3c8a8ae7 --- /dev/null +++ b/bitacross-worker/bitacross/core/bc-musig2-event/src/lib.rs @@ -0,0 +1,277 @@ +// Copyright 2020-2024 Trust Computing GmbH. +// This file is part of Litentry. +// +// Litentry is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Litentry is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Litentry. If not, see . + +#![cfg_attr(not(feature = "std"), no_std)] + +extern crate core; +#[cfg(all(not(feature = "std"), feature = "sgx"))] +extern crate sgx_tstd as std; + +#[cfg(all(feature = "std", feature = "sgx"))] +compile_error!("feature \"std\" and feature \"sgx\" cannot be enabled at the same time"); + +#[cfg(feature = "std")] +use threadpool::ThreadPool; + +#[cfg(feature = "sgx")] +use threadpool_sgx::ThreadPool; + +#[cfg(feature = "std")] +use std::sync::Mutex; + +#[cfg(feature = "sgx")] +use std::sync::SgxMutex as Mutex; + +use bc_musig2_ceremony::{CeremonyEvent, CeremonyId}; +use codec::Encode; +use itc_direct_rpc_client::{DirectRpcClient, RpcClient}; +use itc_direct_rpc_server::SendRpcResponse; +use itp_ocall_api::EnclaveAttestationOCallApi; +use itp_rpc::{Id, RpcRequest}; +use itp_sgx_crypto::{ + key_repository::{AccessKey, AccessPubkey}, + ShieldingCryptoEncrypt, +}; +pub use itp_types::{DirectRequestStatus, Hash}; +use itp_utils::hex::ToHexPrefixed; +use lc_direct_call::CeremonyRoundCall; +use litentry_primitives::{aes_encrypt_default, Address32, AesRequest, Identity, ShardIdentifier}; +use log::*; +use sgx_crypto_helper::rsa3072::Rsa3072PubKey; +use sp_core::{blake2_256, ed25519, Pair as SpCorePair, H256}; +use std::{collections::HashMap, string::ToString, sync::Arc, vec}; + +#[allow(clippy::too_many_arguments)] +pub fn process_event( + signing_key_access: Arc, + shielding_key_access: Arc, + ocall_api: Arc, + responder: Arc, + event: CeremonyEvent, + ceremony_id: CeremonyId, + event_threads_pool: ThreadPool, + peers_map: Arc>>, +) where + OCallApi: EnclaveAttestationOCallApi + 'static, + SIGNINGAK: AccessKey + Send + Sync + 'static, + SHIELDAK: AccessPubkey + Send + Sync + 'static, + Responder: SendRpcResponse + 'static, +{ + let my_identity: Address32 = signing_key_access.retrieve_key().unwrap().public().0.into(); + let identity = Identity::Substrate(my_identity); + let mr_enclave = ocall_api.get_mrenclave_of_self().unwrap().m; + + match event { + CeremonyEvent::FirstRoundStarted(signers, message, nonce) => { + let aes_key = random_aes_key(); + let direct_call = + CeremonyRoundCall::NonceShare(identity, aes_key, message, nonce.serialize()); + let request = prepare_request( + aes_key, + shielding_key_access.as_ref(), + signing_key_access.as_ref(), + mr_enclave, + direct_call, + ); + + signers.iter().for_each(|signer_id| { + debug!( + "Sharing nonce with signer: {:?} for ceremony: {:?}", + signer_id, ceremony_id + ); + + let signer_id = *signer_id; + let client = peers_map.lock().unwrap().get(&signer_id).cloned(); + if let Some(mut client) = client { + let request = request.clone(); + event_threads_pool.execute(move || { + if let Err(e) = client.send(&request) { + error!( + "Could not send request to signer: {:?}, reason: {:?}", + signer_id, e + ) + } + }); + } else { + error!("Fail to share nonce, unknown signer: {:?}", signer_id); + } + }); + }, + CeremonyEvent::SecondRoundStarted(signers, message, signature) => { + let aes_key = random_aes_key(); + let direct_call = CeremonyRoundCall::PartialSignatureShare( + identity, + aes_key, + message, + signature.serialize(), + ); + let request = prepare_request( + aes_key, + shielding_key_access.as_ref(), + signing_key_access.as_ref(), + mr_enclave, + direct_call, + ); + + signers.iter().for_each(|signer_id| { + debug!( + "Sharing partial signature with signer: {:?} for ceremony: {:?}", + signer_id, ceremony_id + ); + + let signer_id = *signer_id; + let client = peers_map.lock().unwrap().get(&signer_id).cloned(); + if let Some(mut client) = client { + let request = request.clone(); + event_threads_pool.execute(move || { + if let Err(e) = client.send(&request) { + error!( + "Could not send request to signer: {:?}, reason: {:?}", + signer_id, e + ) + } + }); + } else { + error!("Fail to share partial signature, unknown signer: {:?}", signer_id); + } + }); + }, + CeremonyEvent::CeremonyEnded( + signature, + request_aes_key, + is_check_run, + verification_result, + ) => { + debug!("Ceremony {:?} ended, signature {:?}", ceremony_id, signature); + let hash = blake2_256(&ceremony_id.encode()); + let result = if is_check_run { + verification_result.encode() + } else { + let result = signature; + aes_encrypt_default(&request_aes_key, &result.encode()).encode() + }; + event_threads_pool.execute(move || { + if let Err(e) = responder.send_state_with_status( + Hash::from_slice(&hash), + result, + DirectRequestStatus::Ok, + ) { + error!("Could not send response to {:?}, reason: {:?}", &hash, e); + } + }); + }, + CeremonyEvent::CeremonyError(signers, error, request_aes_key) => { + debug!("Ceremony {:?} error {:?}", ceremony_id, error); + let hash = blake2_256(&ceremony_id.encode()); + let encrypted_result = aes_encrypt_default(&request_aes_key, &error.encode()).encode(); + event_threads_pool.execute(move || { + if let Err(e) = responder.send_state_with_status( + Hash::from_slice(&hash), + encrypted_result, + DirectRequestStatus::Error, + ) { + error!("Could not send response to {:?}, reason: {:?}", &hash, e); + } + }); + + let aes_key = random_aes_key(); + let direct_call = + CeremonyRoundCall::KillCeremony(identity, aes_key, ceremony_id.clone()); + let request = prepare_request( + aes_key, + shielding_key_access.as_ref(), + signing_key_access.as_ref(), + mr_enclave, + direct_call, + ); + + //kill ceremonies on other workers + signers.iter().for_each(|signer_id| { + debug!( + "Requesting ceremony kill on signer: {:?} for ceremony: {:?}", + signer_id, ceremony_id + ); + + let signer_id = *signer_id; + let client = peers_map.lock().unwrap().get(&signer_id).cloned(); + if let Some(mut client) = client { + let request = request.clone(); + event_threads_pool.execute(move || { + if let Err(e) = client.send(&request) { + error!( + "Could not send request to signer: {:?}, reason: {:?}", + signer_id, e + ) + } + }); + } else { + error!("Fail to share killing info, unknown signer: {:?}", signer_id); + } + }); + }, + } +} + +fn prepare_request( + aes_key: [u8; 32], + shielding_key_access: &SHIELDAK, + signing_key_access: &SIGNINGAK, + mr_enclave: [u8; 32], + ceremony_round_call: CeremonyRoundCall, +) -> RpcRequest +where + SIGNINGAK: AccessKey + Send + Sync + 'static, + SHIELDAK: AccessPubkey + Send + Sync + 'static, +{ + // this should never panic, if pub key is poisoned the state is corrupted + let aes_key_encrypted = + shielding_key_access.retrieve_pubkey().unwrap().encrypt(&aes_key).unwrap(); + + let shard = ShardIdentifier::from_slice(&mr_enclave); + // same as above + let dc_signed = ceremony_round_call.sign( + &signing_key_access.retrieve_key().unwrap().into(), + &mr_enclave, + &shard, + ); + let encrypted_dc = aes_encrypt_default(&aes_key, &dc_signed.encode()); + let request = AesRequest { shard, key: aes_key_encrypted, payload: encrypted_dc }; + RpcRequest { + jsonrpc: "2.0".to_string(), + method: "bitacross_btcDataShare".to_string(), + params: vec![request.to_hex()], + id: Id::Number(1), + } +} + +#[cfg(feature = "std")] +fn random_aes_key() -> [u8; 32] { + use rand::{thread_rng, RngCore}; + + let mut seed = [0u8; 32]; + let mut rand = thread_rng(); + rand.fill_bytes(&mut seed); + seed +} + +#[cfg(feature = "sgx")] +fn random_aes_key() -> [u8; 32] { + use sgx_rand::{Rng, StdRng}; + let mut seed = [0u8; 32]; + let mut rand = StdRng::new().unwrap(); + rand.fill_bytes(&mut seed); + seed +} diff --git a/bitacross-worker/bitacross/core/bc-musig2-runner/src/lib.rs b/bitacross-worker/bitacross/core/bc-musig2-runner/src/lib.rs deleted file mode 100644 index f0cf242618..0000000000 --- a/bitacross-worker/bitacross/core/bc-musig2-runner/src/lib.rs +++ /dev/null @@ -1,429 +0,0 @@ -// Copyright 2020-2024 Trust Computing GmbH. -// This file is part of Litentry. -// -// Litentry is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Litentry is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Litentry. If not, see . - -#![cfg_attr(not(feature = "std"), no_std)] - -extern crate core; -#[cfg(all(not(feature = "std"), feature = "sgx"))] -extern crate sgx_tstd as std; - -#[cfg(all(feature = "std", feature = "sgx"))] -compile_error!("feature \"std\" and feature \"sgx\" cannot be enabled at the same time"); - -use bc_enclave_registry::EnclaveRegistryLookup; -use codec::Encode; -use core::time::Duration; -use itc_direct_rpc_client::{RpcClient, RpcClientFactory}; -use itc_direct_rpc_server::SendRpcResponse; -use itp_ocall_api::EnclaveAttestationOCallApi; -use itp_sgx_crypto::{ - key_repository::{AccessKey, AccessPubkey}, - ShieldingCryptoEncrypt, -}; -use itp_utils::hex::ToHexPrefixed; -use log::{debug, error, info, trace, warn}; -use sgx_crypto_helper::rsa3072::Rsa3072PubKey; -use sp_core::{blake2_256, ed25519, Pair as SpCorePair, H256}; -use std::vec::Vec; - -#[cfg(feature = "std")] -use std::sync::Mutex; - -use bc_musig2_ceremony::{ - CeremonyCommandsRegistry, CeremonyEvent, CeremonyId, CeremonyRegistry, SignBitcoinError, - SignerId, -}; - -use itp_rpc::{Id, RpcRequest}; -use itp_sgx_crypto::schnorr::Pair as SchnorrPair; -use itp_types::{DirectRequestStatus, Hash}; -use lc_direct_call::DirectCall; -use litentry_primitives::{aes_encrypt_default, Address32, AesRequest, Identity, ShardIdentifier}; -#[cfg(feature = "sgx")] -use std::sync::SgxMutex as Mutex; -use std::{ - collections::HashMap, - string::ToString, - sync::{mpsc::channel, Arc}, - vec, -}; - -#[allow(clippy::too_many_arguments)] -pub fn init_ceremonies_thread( - signing_key_access: Arc, - shielding_key_access: Arc, - client_factory: Arc, - enclave_registry: Arc, - ceremony_registry: Arc>>, - ceremony_commands: Arc>, - ocall_api: Arc, - responder: Arc, -) where - ClientFactory: RpcClientFactory + Send + Sync + 'static, - AK: AccessKey + Send + Sync + 'static, - ER: EnclaveRegistryLookup + Send + Sync + 'static, - OCallApi: EnclaveAttestationOCallApi + 'static, - SIGNINGAK: AccessKey + Send + Sync + 'static, - SHIELDAK: AccessPubkey + Send + Sync + 'static, - Responder: SendRpcResponse + 'static, -{ - let (responses_sender, responses_receiver) = channel(); - std::thread::spawn(move || { - let my_identity: Address32 = signing_key_access.retrieve_key().unwrap().public().0.into(); - let identity = Identity::Substrate(my_identity); - let mut peers_map = HashMap::new(); - let mut ceremonies_to_remove = vec![]; - loop { - enclave_registry.get_all().iter().for_each(|(identity, address)| { - if my_identity != *identity && !peers_map.contains_key(identity.as_ref()) { - info!("creating new connection to peer: {:?}", address); - match client_factory.create(address, responses_sender.clone()) { - Ok(client) => { - peers_map.insert(*identity.as_ref(), client); - }, - Err(e) => error!("Could not connect to peer {}, reason: {:?}", address, e), - } - } - }); - { - // do not hold lock for too long - let ceremonies_to_process: Vec = - if let Ok(ceremonies) = ceremony_registry.try_lock() { - ceremonies.keys().cloned().collect() - } else { - warn!("Could not determine ceremonies to process"); - vec![] - }; - - for ceremony_id_to_process in ceremonies_to_process { - // do not hold lock for too long as logic below includes I/O - let events = if let Ok(mut ceremonies) = ceremony_registry.try_lock() { - if let Some(ceremony) = ceremonies.get_mut(&ceremony_id_to_process) { - ceremony.tick() - } else { - warn!("Could not find ceremony with id: {:?}", ceremony_id_to_process); - vec![] - } - } else { - vec![] - }; - - trace!("Got ceremony {:?} events {:?}", ceremony_id_to_process, events); - // should be retrieved once, but cannot be at startup because it's not yet initialized so it panics ... - let mr_enclave = ocall_api.get_mrenclave_of_self().unwrap().m; - for event in events { - debug!( - "Processing ceremony event: {:?} for ceremony: {:?}", - event, ceremony_id_to_process - ); - match event { - CeremonyEvent::FirstRoundStarted(signers, message, nonce) => { - signers.iter().for_each(|signer_id| { - let aes_key = random_aes_key(); - let direct_call = DirectCall::NonceShare( - identity.clone(), - aes_key, - message.clone(), - nonce.serialize(), - ); - - debug!( - "Sharing nonce with signer: {:?} for ceremony: {:?}", - signer_id, ceremony_id_to_process - ); - - let request = prepare_request( - aes_key, - shielding_key_access.as_ref(), - signing_key_access.as_ref(), - mr_enclave, - direct_call, - ); - send_to_signer::( - &request, - signer_id, - &mut peers_map, - ); - }); - }, - CeremonyEvent::SecondRoundStarted(signers, message, signature) => { - signers.iter().for_each(|signer_id| { - let aes_key = random_aes_key(); - let direct_call = DirectCall::PartialSignatureShare( - identity.clone(), - aes_key, - message.clone(), - signature.serialize(), - ); - - debug!( - "Sharing partial signature with signer: {:?} for ceremony: {:?}", - signer_id, - ceremony_id_to_process - ); - - let request = prepare_request( - aes_key, - shielding_key_access.as_ref(), - signing_key_access.as_ref(), - mr_enclave, - direct_call, - ); - send_to_signer::( - &request, - signer_id, - &mut peers_map, - ); - }); - }, - CeremonyEvent::CeremonyEnded( - signature, - request_aes_key, - is_check_run, - verification_result, - ) => { - debug!( - "Ceremony {:?} ended, signature {:?}", - ceremony_id_to_process, signature - ); - let hash = blake2_256(&ceremony_id_to_process.encode()); - let result = if is_check_run { - verification_result.encode() - } else { - let result = signature; - - aes_encrypt_default(&request_aes_key, &result.encode()).encode() - }; - if let Err(e) = responder.send_state_with_status( - Hash::from_slice(&hash), - result, - DirectRequestStatus::Ok, - ) { - error!( - "Could not send response to {:?}, reason: {:?}", - &hash, e - ); - } - - ceremonies_to_remove.push(ceremony_id_to_process.clone()); - }, - CeremonyEvent::CeremonyError(signers, error, request_aes_key) => { - debug!("Ceremony {:?} error {:?}", ceremony_id_to_process, error); - let hash = blake2_256(&ceremony_id_to_process.encode()); - let result = SignBitcoinError::CeremonyError; - let encrypted_result = - aes_encrypt_default(&request_aes_key, &result.encode()) - .encode(); - if let Err(e) = responder.send_state_with_status( - Hash::from_slice(&hash), - encrypted_result, - DirectRequestStatus::Error, - ) { - error!( - "Could not send response to {:?}, reason: {:?}", - &hash, e - ); - } - ceremonies_to_remove.push(ceremony_id_to_process.clone()); - - //kill ceremonies on other workers - signers.iter().for_each(|signer_id| { - let aes_key = random_aes_key(); - let direct_call = DirectCall::KillCeremony( - identity.clone(), - aes_key, - ceremony_id_to_process.clone(), - ); - - debug!( - "Requesting ceremony kill on signer: {:?} for ceremony: {:?}", - signer_id, - ceremony_id_to_process - ); - - let request = prepare_request( - aes_key, - shielding_key_access.as_ref(), - signing_key_access.as_ref(), - mr_enclave, - direct_call, - ); - send_to_all_signers::(&request, &mut peers_map); - }); - }, - CeremonyEvent::CeremonyTimedOut(signers, request_aes_key) => { - debug!("Ceremony {:?} timed out", ceremony_id_to_process); - let hash = blake2_256(&ceremony_id_to_process.encode()); - let result = SignBitcoinError::CeremonyError; - let encrypted_result = - aes_encrypt_default(&request_aes_key, &result.encode()) - .encode(); - if let Err(e) = responder.send_state_with_status( - Hash::from_slice(&hash), - encrypted_result, - DirectRequestStatus::Error, - ) { - error!( - "Could not send response to {:?}, reason: {:?}", - &hash, e - ); - } - ceremonies_to_remove.push(ceremony_id_to_process.clone()); - - //kill ceremonies on other workers - signers.iter().for_each(|signer_id| { - let aes_key = random_aes_key(); - let direct_call = DirectCall::KillCeremony( - identity.clone(), - aes_key, - ceremony_id_to_process.clone(), - ); - - debug!( - "Requesting ceremony kill on signer: {:?} for ceremony: {:?}", - signer_id, - ceremony_id_to_process - ); - - let request = prepare_request( - aes_key, - shielding_key_access.as_ref(), - signing_key_access.as_ref(), - mr_enclave, - direct_call, - ); - send_to_all_signers::(&request, &mut peers_map); - }); - }, - } - } - } - - let ceremony_commands = ceremony_commands.try_lock(); - let ceremony_registry = ceremony_registry.try_lock(); - - if let Ok(mut ceremony_commands) = ceremony_commands { - ceremony_commands.retain(|_, ceremony_pending_commands| { - ceremony_pending_commands.retain_mut(|c| { - c.tick(); - c.ticks_left > 0 - }); - !ceremony_pending_commands.is_empty() - }); - - if let Ok(mut ceremonies) = ceremony_registry { - ceremonies_to_remove.iter().for_each(|ceremony_id| { - debug!("Removing ceremony {:?}", ceremony_id); - let _ = ceremonies.remove_entry(ceremony_id); - let _ = ceremony_commands.remove_entry(ceremony_id); - }); - ceremonies_to_remove = vec![]; - } else { - warn!("Could not get ceremonies lock"); - } - } else { - warn!("Could not get ceremony commands lock"); - } - } - - std::thread::sleep(Duration::from_millis(1)) - } - }); - // here we will process all responses - std::thread::spawn(move || { - while let Ok((_id, rpc_return_value)) = responses_receiver.recv() { - info!("Got RPC return value: {:?}", rpc_return_value); - if rpc_return_value.status == DirectRequestStatus::Error { - error!("Got unexpected direct request status: {:?}", rpc_return_value.status); - } - } - }); -} - -fn send_to_signer( - request: &RpcRequest, - signer_id: &SignerId, - peers: &mut HashMap, -) where - ClientFactory: RpcClientFactory, -{ - if let Some(client) = peers.get_mut(signer_id) { - if let Err(e) = client.send(request) { - error!("Could not send request to signer: {:?}, reason: {:?}", signer_id, e) - } - } -} - -fn send_to_all_signers( - request: &RpcRequest, - peers: &mut HashMap, -) where - ClientFactory: RpcClientFactory, -{ - for (signer_id, client) in peers.iter_mut() { - if let Err(e) = client.send(request) { - error!("Could not send request to signer: {:?}, reason: {:?}", signer_id, e) - } - } -} - -fn prepare_request( - aes_key: [u8; 32], - shielding_key_access: &SHIELDAK, - signing_key_access: &SIGNINGAK, - mr_enclave: [u8; 32], - direct_call: DirectCall, -) -> RpcRequest -where - SIGNINGAK: AccessKey + Send + Sync + 'static, - SHIELDAK: AccessPubkey + Send + Sync + 'static, -{ - // this should never panic, if pub key is poisoned the state is corrupted - let aes_key_encrypted = - shielding_key_access.retrieve_pubkey().unwrap().encrypt(&aes_key).unwrap(); - - let shard = ShardIdentifier::from_slice(&mr_enclave); - // same as above - let dc_signed = - direct_call.sign(&signing_key_access.retrieve_key().unwrap().into(), &mr_enclave, &shard); - let encrypted_dc = aes_encrypt_default(&aes_key, &dc_signed.encode()); - let request = AesRequest { shard, key: aes_key_encrypted, payload: encrypted_dc }; - RpcRequest { - jsonrpc: "2.0".to_string(), - method: "bitacross_submitRequest".to_string(), - params: vec![request.to_hex()], - id: Id::Number(1), - } -} - -#[cfg(feature = "std")] -fn random_aes_key() -> [u8; 32] { - use rand::{thread_rng, RngCore}; - - let mut seed = [0u8; 32]; - let mut rand = thread_rng(); - rand.fill_bytes(&mut seed); - seed -} - -#[cfg(feature = "sgx")] -fn random_aes_key() -> [u8; 32] { - use sgx_rand::{Rng, StdRng}; - let mut seed = [0u8; 32]; - let mut rand = StdRng::new().unwrap(); - rand.fill_bytes(&mut seed); - seed -} diff --git a/bitacross-worker/bitacross/core/bc-task-receiver/Cargo.toml b/bitacross-worker/bitacross/core/bc-task-processor/Cargo.toml similarity index 81% rename from bitacross-worker/bitacross/core/bc-task-receiver/Cargo.toml rename to bitacross-worker/bitacross/core/bc-task-processor/Cargo.toml index 167389ecfc..c67aa5abae 100644 --- a/bitacross-worker/bitacross/core/bc-task-receiver/Cargo.toml +++ b/bitacross-worker/bitacross/core/bc-task-processor/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "bc-task-receiver" +name = "bc-task-processor" authors = ["Trust Computing GmbH "] version = "0.1.0" edition = "2021" @@ -38,10 +38,15 @@ itp-utils = { path = "../../../core-primitives/utils", default-features = false # litentry primities bc-enclave-registry = { path = "../bc-enclave-registry", default-features = false } bc-musig2-ceremony = { path = "../bc-musig2-ceremony", default-features = false } +bc-musig2-event = { path = "../bc-musig2-event", default-features = false } bc-relayer-registry = { path = "../bc-relayer-registry", default-features = false } bc-signer-registry = { path = "../bc-signer-registry", default-features = false } +itc-direct-rpc-client = { path = "../../../core/direct-rpc-client", default-features = false } +itc-direct-rpc-server = { path = "../../../core/direct-rpc-server", default-features = false } lc-direct-call = { path = "../../../litentry/core/direct-call", default-features = false } litentry-primitives = { path = "../../../litentry/primitives", default-features = false } +sgx_crypto_helper = { branch = "master", git = "https://github.com/apache/teaclave-sgx-sdk.git", default-features = false } +sp-core = { default-features = false, features = ["full_crypto"], git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.42" } bc-task-sender = { path = "../bc-task-sender", default-features = false } @@ -51,6 +56,7 @@ sgx = [ "threadpool_sgx", "sgx_tstd", "bc-musig2-ceremony/sgx", + "bc-musig2-event/sgx", "bc-task-sender/sgx", "bc-enclave-registry/sgx", "bc-relayer-registry/sgx", @@ -64,11 +70,15 @@ sgx = [ "itp-stf-state-handler/sgx", "thiserror_sgx", "futures_sgx", + "itc-direct-rpc-server/sgx", + "itc-direct-rpc-client/sgx", + "sgx_crypto_helper/mesalock_sgx", ] std = [ "threadpool", "log/std", "bc-musig2-ceremony/std", + "bc-musig2-event/std", "bc-task-sender/std", "bc-enclave-registry/std", "bc-relayer-registry/std", @@ -82,5 +92,7 @@ std = [ "itp-stf-executor/std", "itp-stf-state-handler/std", "thiserror", + "itc-direct-rpc-server/std", + "itc-direct-rpc-client/std", ] development = [] diff --git a/bitacross-worker/bitacross/core/bc-task-processor/src/lib.rs b/bitacross-worker/bitacross/core/bc-task-processor/src/lib.rs new file mode 100644 index 0000000000..bc3dc3025b --- /dev/null +++ b/bitacross-worker/bitacross/core/bc-task-processor/src/lib.rs @@ -0,0 +1,730 @@ +// Copyright 2020-2024 Trust Computing GmbH. +// This file is part of Litentry. +// +// Litentry is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Litentry is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Litentry. If not, see . + +#![cfg_attr(not(feature = "std"), no_std)] + +extern crate core; +#[cfg(all(not(feature = "std"), feature = "sgx"))] +extern crate sgx_tstd as std; + +// re-export module to properly feature gate sgx and regular std environment +#[cfg(all(not(feature = "std"), feature = "sgx"))] +pub mod sgx_reexport_prelude { + pub use futures_sgx as futures; + pub use thiserror_sgx as thiserror; + pub use threadpool_sgx as threadpool; +} + +#[cfg(all(feature = "std", feature = "sgx"))] +compile_error!("feature \"std\" and feature \"sgx\" cannot be enabled at the same time"); + +#[cfg(all(not(feature = "std"), feature = "sgx"))] +pub use crate::sgx_reexport_prelude::*; + +#[cfg(feature = "std")] +use std::sync::RwLock; + +#[cfg(feature = "sgx")] +use std::sync::SgxRwLock as RwLock; + +#[cfg(feature = "std")] +use std::sync::Mutex; + +#[cfg(feature = "sgx")] +use std::sync::SgxMutex as Mutex; + +use bc_enclave_registry::EnclaveRegistryLookup; +use bc_musig2_ceremony::{ + get_current_timestamp, CeremonyCommand, CeremonyCommandTmp, CeremonyError, CeremonyErrorReason, + CeremonyEvent, CeremonyId, CeremonyRegistry, MuSig2Ceremony, SignBitcoinPayload, +}; +use bc_musig2_event::{process_event, DirectRequestStatus, Hash}; +use bc_relayer_registry::RelayerRegistryLookup; +use bc_signer_registry::SignerRegistryLookup; +use bc_task_sender::{ + init_bit_across_task_sender_storage, BitAcrossProcessingResult, BitAcrossRequest, +}; +use codec::{Decode, Encode}; +use core::{ops::Deref, time::Duration}; +use frame_support::{ensure, sp_runtime::app_crypto::sp_core::blake2_256}; +use ita_stf::TrustedCallSigned; +use itc_direct_rpc_client::{DirectRpcClient, DirectRpcClientFactory, RpcClientFactory}; +use itc_direct_rpc_server::SendRpcResponse; +use itp_ocall_api::{EnclaveAttestationOCallApi, EnclaveMetricsOCallApi, EnclaveOnChainOCallApi}; +use itp_sgx_crypto::{ + ecdsa::Pair as EcdsaPair, + key_repository::{AccessKey, AccessPubkey}, + schnorr::Pair as SchnorrPair, + ShieldingCryptoDecrypt, ShieldingCryptoEncrypt, +}; +use itp_sgx_externalities::SgxExternalitiesTrait; +use itp_stf_executor::traits::StfEnclaveSigning; +use itp_stf_state_handler::handle_state::HandleState; +use lc_direct_call::{ + handler::{ + kill_ceremony, nonce_share, partial_signature_share, + sign_bitcoin::{self, SignBitcoinError}, + sign_ethereum, + }, + CeremonyRoundCall, CeremonyRoundCallSigned, DirectCall, DirectCallSigned, +}; +use litentry_primitives::{aes_encrypt_default, Address32, AesRequest, DecryptableRequest}; +use log::*; +use sgx_crypto_helper::rsa3072::Rsa3072PubKey; +use sp_core::{ed25519, Pair, H256}; +use std::{ + boxed::Box, + collections::HashMap, + format, + string::{String, ToString}, + sync::Arc, + vec, + vec::Vec, +}; +use threadpool::ThreadPool; + +#[derive(Debug, thiserror::Error, Clone)] +pub enum Error { + #[error("Request error: {0}")] + RequestError(String), + + #[error("Other error: {0}")] + OtherError(String), +} + +pub struct BitAcrossTaskContext< + SKR, + SIGNINGAK, + EKR, + BKR, + S: StfEnclaveSigning, + H: HandleState, + O: EnclaveOnChainOCallApi, + RRL: RelayerRegistryLookup, + ERL: EnclaveRegistryLookup, + SRL: SignerRegistryLookup, + Responder, +> where + SKR: AccessKey + AccessPubkey, + SIGNINGAK: AccessKey, + EKR: AccessKey, + BKR: AccessKey, + ::KeyType: ShieldingCryptoEncrypt + 'static, + Responder: SendRpcResponse, +{ + pub shielding_key: Arc, + pub signing_key_access: Arc, + pub ethereum_key_repository: Arc, + pub bitcoin_key_repository: Arc, + pub enclave_signer: Arc, + pub state_handler: Arc, + pub ocall_api: Arc, + pub relayer_registry_lookup: Arc, + pub enclave_registry_lookup: Arc, + pub signer_registry_lookup: Arc, + pub signing_key_pub: [u8; 32], + pub responder: Arc, + pub ceremony_registry: Arc>>, + pub ceremony_command_tmp: Arc>, +} + +impl< + SKR, + SIGNINGAK, + EKR, + BKR, + S: StfEnclaveSigning, + H: HandleState, + O: EnclaveOnChainOCallApi, + RRL: RelayerRegistryLookup, + ERL: EnclaveRegistryLookup, + SRL: SignerRegistryLookup, + Responder, + > BitAcrossTaskContext +where + SKR: AccessKey + AccessPubkey, + SIGNINGAK: AccessKey, + EKR: AccessKey, + BKR: AccessKey, + ::KeyType: ShieldingCryptoEncrypt + 'static, + H::StateT: SgxExternalitiesTrait, + Responder: SendRpcResponse, +{ + #[allow(clippy::too_many_arguments)] + pub fn new( + shielding_key: Arc, + signing_key_access: Arc, + ethereum_key_repository: Arc, + bitcoin_key_repository: Arc, + enclave_signer: Arc, + state_handler: Arc, + ocall_api: Arc, + relayer_registry_lookup: Arc, + enclave_registry_lookup: Arc, + signer_registry_lookup: Arc, + signing_key_pub: [u8; 32], + ceremony_registry: Arc>>, + ceremony_command_tmp: Arc>, + responder: Arc, + ) -> Self { + Self { + shielding_key, + signing_key_access, + ethereum_key_repository, + bitcoin_key_repository, + enclave_signer, + state_handler, + ocall_api, + relayer_registry_lookup, + enclave_registry_lookup, + signer_registry_lookup, + signing_key_pub, + ceremony_registry, + ceremony_command_tmp, + responder, + } + } +} + +#[allow(clippy::type_complexity)] +pub fn run_bit_across_handler_runner( + context: Arc>, + ceremony_commands_thread_count: u8, + ceremony_events_thread_count: u8, +) where + SKR: AccessKey + AccessPubkey + Send + Sync + 'static, + SIGNINGAK: AccessKey + Send + Sync + 'static, + EKR: AccessKey + Send + Sync + 'static, + BKR: AccessKey + Send + Sync + 'static, + ::KeyType: ShieldingCryptoEncrypt + ShieldingCryptoDecrypt + 'static, + S: StfEnclaveSigning + Send + Sync + 'static, + H: HandleState + Send + Sync + 'static, + H::StateT: SgxExternalitiesTrait, + O: EnclaveOnChainOCallApi + EnclaveMetricsOCallApi + EnclaveAttestationOCallApi + 'static, + RRL: RelayerRegistryLookup + Send + Sync + 'static, + ERL: EnclaveRegistryLookup + Send + Sync + 'static, + SRL: SignerRegistryLookup + Send + Sync + 'static, + Responder: SendRpcResponse + Send + Sync + 'static, +{ + // timeout tick + let ceremony_registry = context.ceremony_registry.clone(); + let ceremony_command_tmp = context.ceremony_command_tmp.clone(); + let responder = context.responder.clone(); + let time_to_live = 30u64; + std::thread::spawn(move || loop { + std::thread::sleep(Duration::from_secs(3)); + let now = get_current_timestamp(); + { + let mut ceremony_registry_write = ceremony_registry.write().unwrap(); + ceremony_registry_write.retain(|_, (ceremony, create_time)| { + let if_retain = now - *create_time < time_to_live; + if !if_retain { + let ceremony_rwlock = ceremony.clone(); + let ceremony = ceremony_rwlock.read().unwrap(); + let hash = blake2_256(&ceremony.get_id_ref().encode()); + let encrypted_result = aes_encrypt_default( + ceremony.get_aes_key(), + &SignBitcoinError::CeremonyError.encode(), + ) + .encode(); + if let Err(e) = responder.send_state_with_status( + Hash::from_slice(&hash), + encrypted_result, + DirectRequestStatus::Error, + ) { + error!("Could not send response to {:?}, reason: {:?}", &hash, e); + } + } + if_retain + }); + } + { + let mut command_tmp_write = ceremony_command_tmp.write().unwrap(); + command_tmp_write.retain(|_, &mut (_, create_time)| now - create_time < time_to_live); + } + }); + + let bit_across_task_receiver = init_bit_across_task_sender_storage(); + let peers_map = Arc::new(Mutex::new(HashMap::<[u8; 32], DirectRpcClient>::new())); + let command_threads_pool = ThreadPool::new(ceremony_commands_thread_count.into()); + let event_threads_pool = ThreadPool::new(ceremony_events_thread_count.into()); + + while let Ok(req) = bit_across_task_receiver.recv() { + let context = context.clone(); + let event_threads_pool = event_threads_pool.clone(); + let peers_map = peers_map.clone(); + command_threads_pool.execute(move || { + if let Some((ceremony_id, command)) = handle_request(req, context.clone()) { + handle_ceremony_command( + context, + ceremony_id, + command, + event_threads_pool, + peers_map, + ); + } + }); + } + + command_threads_pool.join(); + event_threads_pool.join(); + warn!("bit_across_task_receiver loop terminated"); +} + +#[allow(clippy::type_complexity)] +fn handle_ceremony_command( + context: Arc>, + ceremony_id: CeremonyId, + command: CeremonyCommand, + event_threads_pool: ThreadPool, + peers_map: Arc>>, +) where + SKR: AccessKey + AccessPubkey + Send + Sync + 'static, + SIGNINGAK: AccessKey + Send + Sync + 'static, + EKR: AccessKey + Send + Sync + 'static, + BKR: AccessKey + Send + Sync + 'static, + ::KeyType: ShieldingCryptoEncrypt + ShieldingCryptoDecrypt + 'static, + S: StfEnclaveSigning + Send + Sync + 'static, + H: HandleState + Send + Sync + 'static, + H::StateT: SgxExternalitiesTrait, + O: EnclaveOnChainOCallApi + EnclaveMetricsOCallApi + EnclaveAttestationOCallApi + 'static, + RRL: RelayerRegistryLookup + Send + Sync + 'static, + ERL: EnclaveRegistryLookup + Send + Sync + 'static, + SRL: SignerRegistryLookup + Send + Sync + 'static, + Responder: SendRpcResponse + Send + Sync + 'static, +{ + // check whether to store command to tmp + let is_first_round = { + context + .ceremony_registry + .read() + .unwrap() + .get(&ceremony_id) + .map(|(c, _)| c.read().unwrap().is_first_round()) + }; + match (is_first_round, &command) { + (None, CeremonyCommand::InitCeremony(_, _, _, _)) + | (Some(true), CeremonyCommand::SaveNonce(_, _)) + | (Some(false), CeremonyCommand::SavePartialSignature(_, _)) + | (_, CeremonyCommand::KillCeremony) => {}, + (None, CeremonyCommand::SaveNonce(_, _)) + | (Some(true), CeremonyCommand::SavePartialSignature(_, _)) => { + context + .ceremony_command_tmp + .write() + .unwrap() + .entry(ceremony_id) + .and_modify(|(command_tmp, _)| command_tmp.write().unwrap().push(command.clone())) + .or_insert((Arc::new(RwLock::new(vec![command])), get_current_timestamp())); + return + }, + (is_first_round, command) => { + error!( + "receive wrong command: is_first_round: {:?}, command: {:?}, drop it", + is_first_round, command + ); + return + }, + } + + // try to udpate peers_map + let my_identity: Address32 = + context.signing_key_access.retrieve_key().unwrap().public().0.into(); + context + .enclave_registry_lookup + .get_all() + .iter() + .for_each(|(identity, address)| { + if my_identity != *identity + && !peers_map.lock().unwrap().contains_key(identity.as_ref()) + { + info!("creating new connection to peer: {:?}", address); + match (DirectRpcClientFactory {}).create(address) { + Ok(client) => { + peers_map.lock().unwrap().insert(*identity.as_ref(), client); + }, + Err(e) => error!("Could not connect to peer {}, reason: {:?}", address, e), + } + } + }); + + // process commands and events + let mut commands_to_process = vec![command]; + while !commands_to_process.is_empty() { + let command = commands_to_process.pop().unwrap(); + + let event = process_command(context.clone(), ceremony_id.clone(), command); + + if let Some(event) = event { + match event { + CeremonyEvent::FirstRoundStarted(_, _, _) + | CeremonyEvent::SecondRoundStarted(_, _, _) => { + // get all ceremony_command_tmp + let mut ceremony_command_tmp_write = + context.ceremony_command_tmp.write().unwrap(); + if let Some((ceremony_command_tmp, _)) = + ceremony_command_tmp_write.remove(&ceremony_id) + { + commands_to_process = ceremony_command_tmp.read().unwrap().clone(); + } + }, + CeremonyEvent::CeremonyEnded(_, _, _, _) + | CeremonyEvent::CeremonyError(_, _, _) => { + // remove ceremony + { + let mut registry_write = context.ceremony_registry.write().unwrap(); + registry_write.remove(&ceremony_id); + } + { + context.ceremony_command_tmp.write().unwrap().remove(&ceremony_id); + } + }, + } + + process_event( + context.signing_key_access.clone(), + context.shielding_key.clone(), + context.ocall_api.clone(), + context.responder.clone(), + event, + ceremony_id.clone(), + event_threads_pool.clone(), + peers_map.clone(), + ); + } + } +} + +#[allow(clippy::type_complexity)] +fn process_command( + context: Arc>, + ceremony_id: CeremonyId, + command: CeremonyCommand, +) -> Option +where + SKR: AccessKey + AccessPubkey + Send + Sync + 'static, + SIGNINGAK: AccessKey + Send + Sync + 'static, + EKR: AccessKey + Send + Sync + 'static, + BKR: AccessKey + Send + Sync + 'static, + ::KeyType: ShieldingCryptoEncrypt + ShieldingCryptoDecrypt + 'static, + S: StfEnclaveSigning + Send + Sync + 'static, + H: HandleState + Send + Sync + 'static, + H::StateT: SgxExternalitiesTrait, + O: EnclaveOnChainOCallApi + EnclaveMetricsOCallApi + EnclaveAttestationOCallApi + 'static, + RRL: RelayerRegistryLookup + Send + Sync + 'static, + ERL: EnclaveRegistryLookup + Send + Sync + 'static, + SRL: SignerRegistryLookup + Send + Sync + 'static, + Responder: SendRpcResponse + Send + Sync + 'static, +{ + match command { + CeremonyCommand::InitCeremony(aes_key, signers, payload, check_run) => { + // InitCeremony should create ceremony first + let result = MuSig2Ceremony::new( + context.signing_key_pub, + aes_key, + signers, + payload, + context.bitcoin_key_repository.clone(), + check_run, + ); + + match result { + Ok((ceremony, event)) => { + { + let mut registry_write = context.ceremony_registry.write().unwrap(); + if registry_write.contains_key(&ceremony_id) { + let error = + CeremonyError::CeremonyInitError(CeremonyErrorReason::AlreadyExist); + return Some(CeremonyEvent::CeremonyError(vec![], error, aes_key)) + } + registry_write.insert( + ceremony_id, + (Arc::new(RwLock::new(ceremony)), get_current_timestamp()), + ); + } + Some(event) + }, + Err(e) => { + error!("Could not start ceremony, error: {:?}", e); + let error = + CeremonyError::CeremonyInitError(CeremonyErrorReason::CreateCeremonyError); + Some(CeremonyEvent::CeremonyError(vec![], error, aes_key)) + }, + } + }, + CeremonyCommand::SaveNonce(signer, nonce) => { + let ceremony_rwlock = + context.ceremony_registry.read().unwrap().get(&ceremony_id).cloned(); + if let Some(ceremony_rwlock) = ceremony_rwlock { + let mut ceremony_write_lock = ceremony_rwlock.0.write().unwrap(); + let event_ret = ceremony_write_lock.receive_nonce(signer, nonce); + match event_ret { + Ok(event) => event, + Err(e) => Some(CeremonyEvent::CeremonyError( + ceremony_write_lock.get_signers_except_self(), + e, + *ceremony_write_lock.get_aes_key(), + )), + } + } else { + None + } + }, + CeremonyCommand::SavePartialSignature(signer, partial_signature) => { + let ceremony_rwlock = + context.ceremony_registry.read().unwrap().get(&ceremony_id).cloned(); + if let Some(ceremony_rwlock) = ceremony_rwlock { + let mut ceremony_write_lock = ceremony_rwlock.0.write().unwrap(); + let event_ret = ceremony_write_lock.receive_partial_sign(signer, partial_signature); + match event_ret { + Ok(event) => event, + Err(e) => Some(CeremonyEvent::CeremonyError( + ceremony_write_lock.get_signers_except_self(), + e, + *ceremony_write_lock.get_aes_key(), + )), + } + } else { + None + } + }, + CeremonyCommand::KillCeremony => { + { + context.ceremony_registry.write().unwrap().remove(&ceremony_id); + } + { + context.ceremony_command_tmp.write().unwrap().remove(&ceremony_id); + } + None + }, + } +} + +#[allow(clippy::type_complexity)] +fn handle_request( + request: BitAcrossRequest, + context: Arc>, +) -> Option<(CeremonyId, CeremonyCommand)> +where + SKR: AccessKey + AccessPubkey, + SIGNINGAK: AccessKey, + EKR: AccessKey, + BKR: AccessKey, + ::KeyType: ShieldingCryptoEncrypt + ShieldingCryptoDecrypt + 'static, + S: StfEnclaveSigning + Send + Sync + 'static, + H: HandleState + Send + Sync + 'static, + O: EnclaveOnChainOCallApi + EnclaveMetricsOCallApi + EnclaveAttestationOCallApi + 'static, + RRL: RelayerRegistryLookup + 'static, + ERL: EnclaveRegistryLookup + 'static, + SRL: SignerRegistryLookup + 'static, + Responder: SendRpcResponse + Send + Sync + 'static, +{ + match request { + BitAcrossRequest::Request(mut aes_request, sender) => { + match handle_direct_call(&mut aes_request, context) { + Ok((processing_ret, to_process)) => { + if let Some(processing_ret) = processing_ret { + if let Err(e) = sender.send(Ok(processing_ret)) { + warn!("Unable to submit response back to the handler: {:?}", e); + } + } + to_process + }, + Err(e) => { + if let Err(e) = sender.send(Err(e)) { + warn!("Unable to submit response back to the handler: {:?}", e); + } + None + }, + } + }, + BitAcrossRequest::ShareCeremonyData(mut aes_request) => + handle_ceremony_round_call(&mut aes_request, context).unwrap_or_default(), + } +} + +#[allow(clippy::type_complexity)] +fn handle_direct_call( + request: &mut AesRequest, + context: Arc>, +) -> Result<(Option, Option<(CeremonyId, CeremonyCommand)>), Vec> +where + SKR: AccessKey + AccessPubkey, + SIGNINGAK: AccessKey, + EKR: AccessKey, + BKR: AccessKey, + ::KeyType: ShieldingCryptoEncrypt + ShieldingCryptoDecrypt + 'static, + S: StfEnclaveSigning + Send + Sync + 'static, + H: HandleState + Send + Sync + 'static, + O: EnclaveOnChainOCallApi + EnclaveMetricsOCallApi + EnclaveAttestationOCallApi + 'static, + RRL: RelayerRegistryLookup + 'static, + ERL: EnclaveRegistryLookup + 'static, + SRL: SignerRegistryLookup + 'static, + Responder: SendRpcResponse + Send + Sync + 'static, +{ + let enclave_shielding_key = context.shielding_key.retrieve_key().map_err(|e| { + let err = format!("Failed to retrieve shielding key: {:?}", e); + error!("{}", err); + err + })?; + let dc = request + .decrypt(Box::new(enclave_shielding_key)) + .ok() + .and_then(|v| DirectCallSigned::decode(&mut v.as_slice()).ok()) + .ok_or_else(|| { + let err = "Failed to decode payload".to_string(); + error!("{}", err); + err + })?; + + let mrenclave = match context.ocall_api.get_mrenclave_of_self() { + Ok(m) => m.m, + Err(_) => { + let err = "Failed to get mrenclave"; + error!("{}", err); + return Err(err.encode()) + }, + }; + debug!("Direct call is: {:?}", dc); + ensure!(dc.verify_signature(&mrenclave, &request.shard), "Failed to verify sig".to_string()); + match dc.call { + DirectCall::SignBitcoin(signer, aes_key, payload) => { + let hash = blake2_256(&payload.encode()); + let command = sign_bitcoin::handle( + signer, + payload.clone(), + aes_key, + context.relayer_registry_lookup.deref(), + context.signer_registry_lookup.clone(), + context.enclave_registry_lookup.as_ref(), + false, + ) + .map_err(|e| { + error!("SignBitcoin error: {:?}", e); + aes_encrypt_default(&aes_key, &e.encode()).encode() + })?; + let ret = BitAcrossProcessingResult::Submitted(hash); + Ok((Some(ret), Some((payload, command)))) + }, + DirectCall::CheckSignBitcoin(signer) => { + let payload = SignBitcoinPayload::Derived([0u8; 32].to_vec()); + let aes_key = [0u8; 32]; + let hash = blake2_256(&payload.encode()); + let command = sign_bitcoin::handle( + signer, + payload.clone(), + aes_key, + context.relayer_registry_lookup.deref(), + context.signer_registry_lookup.clone(), + context.enclave_registry_lookup.as_ref(), + true, + ) + .map_err(|e| { + error!("SignBitcoinCheck error: {:?}", e); + aes_encrypt_default(&aes_key, &e.encode()).encode() + })?; + let ret = BitAcrossProcessingResult::Submitted(hash); + Ok((Some(ret), Some((payload, command)))) + }, + DirectCall::SignEthereum(signer, aes_key, msg) => sign_ethereum::handle( + signer, + msg, + context.relayer_registry_lookup.deref(), + context.ethereum_key_repository.deref(), + ) + .map_err(|e| { + error!("SignEthereum error: {:?}", e); + aes_encrypt_default(&aes_key, &e.encode()).encode() + }) + .map(|r| { + (Some(BitAcrossProcessingResult::Ok(aes_encrypt_default(&aes_key, &r).encode())), None) + }), + } +} + +#[allow(clippy::type_complexity)] +fn handle_ceremony_round_call( + request: &mut AesRequest, + context: Arc>, +) -> Result, Vec> +where + SKR: AccessKey + AccessPubkey, + SIGNINGAK: AccessKey, + EKR: AccessKey, + BKR: AccessKey, + ::KeyType: ShieldingCryptoEncrypt + ShieldingCryptoDecrypt + 'static, + S: StfEnclaveSigning + Send + Sync + 'static, + H: HandleState + Send + Sync + 'static, + O: EnclaveOnChainOCallApi + EnclaveMetricsOCallApi + EnclaveAttestationOCallApi + 'static, + RRL: RelayerRegistryLookup + 'static, + ERL: EnclaveRegistryLookup + 'static, + SRL: SignerRegistryLookup + 'static, + Responder: SendRpcResponse + Send + Sync + 'static, +{ + let enclave_shielding_key = context.shielding_key.retrieve_key().map_err(|e| { + let err = format!("Failed to retrieve shielding key: {:?}", e); + error!("{}", err); + err + })?; + let crc = request + .decrypt(Box::new(enclave_shielding_key)) + .ok() + .and_then(|v| CeremonyRoundCallSigned::decode(&mut v.as_slice()).ok()) + .ok_or_else(|| { + let err = "Failed to decode payload".to_string(); + error!("{}", err); + err + })?; + + let mrenclave = match context.ocall_api.get_mrenclave_of_self() { + Ok(m) => m.m, + Err(_) => { + let err = "Failed to get mrenclave"; + error!("{}", err); + return Err(err.encode()) + }, + }; + debug!("Ceremony round call is: {:?}", crc); + ensure!(crc.verify_signature(&mrenclave, &request.shard), "Failed to verify sig".to_string()); + match crc.call { + CeremonyRoundCall::NonceShare(signer, aes_key, message, nonce) => + nonce_share::handle(signer, &message, nonce, context.enclave_registry_lookup.clone()) + .map_err(|e| { + error!("NonceShare error: {:?}", e); + aes_encrypt_default(&aes_key, &e.encode()).encode() + }) + .map(|command| Some((message, command))), + CeremonyRoundCall::PartialSignatureShare(signer, aes_key, message, signature) => + partial_signature_share::handle( + signer, + &message, + signature, + context.enclave_registry_lookup.clone(), + ) + .map_err(|e| { + error!("PartialSignatureShare error: {:?}", e); + aes_encrypt_default(&aes_key, &e.encode()).encode() + }) + .map(|command| Some((message, command))), + CeremonyRoundCall::KillCeremony(signer, aes_key, message) => + kill_ceremony::handle(signer, context.enclave_registry_lookup.as_ref()) + .map_err(|e| { + error!("KillCeremony error: {:?}", e); + aes_encrypt_default(&aes_key, &e.encode()).encode() + }) + .map(|command| Some((message, command))), + } +} diff --git a/bitacross-worker/bitacross/core/bc-task-receiver/src/lib.rs b/bitacross-worker/bitacross/core/bc-task-receiver/src/lib.rs deleted file mode 100644 index 314cda29ff..0000000000 --- a/bitacross-worker/bitacross/core/bc-task-receiver/src/lib.rs +++ /dev/null @@ -1,342 +0,0 @@ -// Copyright 2020-2024 Trust Computing GmbH. -// This file is part of Litentry. -// -// Litentry is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// Litentry is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with Litentry. If not, see . - -#![cfg_attr(not(feature = "std"), no_std)] - -extern crate core; -#[cfg(all(not(feature = "std"), feature = "sgx"))] -extern crate sgx_tstd as std; - -// re-export module to properly feature gate sgx and regular std environment -#[cfg(all(not(feature = "std"), feature = "sgx"))] -pub mod sgx_reexport_prelude { - pub use futures_sgx as futures; - pub use thiserror_sgx as thiserror; - pub use threadpool_sgx as threadpool; -} - -#[cfg(all(feature = "std", feature = "sgx"))] -compile_error!("feature \"std\" and feature \"sgx\" cannot be enabled at the same time"); - -#[cfg(all(not(feature = "std"), feature = "sgx"))] -pub use crate::sgx_reexport_prelude::*; - -#[cfg(feature = "std")] -use std::sync::Mutex; - -#[cfg(feature = "sgx")] -use std::sync::SgxMutex as Mutex; - -use core::ops::Deref; - -use bc_musig2_ceremony::{CeremonyCommandsRegistry, CeremonyRegistry, SignBitcoinPayload}; -use bc_task_sender::{init_bit_across_task_sender_storage, BitAcrossProcessingResult}; -use codec::{Decode, Encode}; -use frame_support::{ensure, sp_runtime::app_crypto::sp_core::blake2_256}; -use lc_direct_call::{DirectCall, DirectCallSigned}; -use litentry_primitives::{aes_encrypt_default, AesRequest}; -use log::*; -use std::{ - boxed::Box, - format, - string::{String, ToString}, - sync::Arc, - vec::Vec, -}; -use threadpool::ThreadPool; - -use itp_ocall_api::{EnclaveAttestationOCallApi, EnclaveMetricsOCallApi, EnclaveOnChainOCallApi}; -use itp_sgx_crypto::{key_repository::AccessKey, ShieldingCryptoDecrypt, ShieldingCryptoEncrypt}; -use itp_sgx_externalities::SgxExternalitiesTrait; -use itp_stf_executor::traits::StfEnclaveSigning; -use itp_stf_state_handler::handle_state::HandleState; - -use bc_enclave_registry::EnclaveRegistryLookup; -use bc_relayer_registry::RelayerRegistryLookup; -use bc_signer_registry::SignerRegistryLookup; -use ita_stf::TrustedCallSigned; -use itp_sgx_crypto::{ecdsa::Pair as EcdsaPair, schnorr::Pair as SchnorrPair}; -use lc_direct_call::handler::{ - kill_ceremony, nonce_share, partial_signature_share, sign_bitcoin, sign_ethereum, -}; -use litentry_primitives::DecryptableRequest; - -#[derive(Debug, thiserror::Error, Clone)] -pub enum Error { - #[error("Request error: {0}")] - RequestError(String), - - #[error("Other error: {0}")] - OtherError(String), -} - -pub struct BitAcrossTaskContext< - SKR, - EKR, - BKR, - S: StfEnclaveSigning, - H: HandleState, - O: EnclaveOnChainOCallApi, - RRL: RelayerRegistryLookup, - ERL: EnclaveRegistryLookup, - SRL: SignerRegistryLookup, -> where - SKR: AccessKey, - EKR: AccessKey, - BKR: AccessKey, - ::KeyType: ShieldingCryptoEncrypt + 'static, -{ - pub shielding_key: Arc, - pub ethereum_key_repository: Arc, - pub bitcoin_key_repository: Arc, - pub enclave_signer: Arc, - pub state_handler: Arc, - pub ocall_api: Arc, - pub relayer_registry_lookup: Arc, - pub musig2_ceremony_registry: Arc>>, - pub enclave_registry_lookup: Arc, - pub signer_registry_lookup: Arc, - pub musig2_ceremony_pending_commands: Arc>, - pub signing_key_pub: [u8; 32], -} - -impl< - SKR, - EKR, - BKR, - S: StfEnclaveSigning, - H: HandleState, - O: EnclaveOnChainOCallApi, - RRL: RelayerRegistryLookup, - ERL: EnclaveRegistryLookup, - SRL: SignerRegistryLookup, - > BitAcrossTaskContext -where - SKR: AccessKey, - EKR: AccessKey, - BKR: AccessKey, - ::KeyType: ShieldingCryptoEncrypt + 'static, - H::StateT: SgxExternalitiesTrait, -{ - #[allow(clippy::too_many_arguments)] - pub fn new( - shielding_key: Arc, - ethereum_key_repository: Arc, - bitcoin_key_repository: Arc, - enclave_signer: Arc, - state_handler: Arc, - ocall_api: Arc, - relayer_registry_lookup: Arc, - musig2_ceremony_registry: Arc>>, - enclave_registry_lookup: Arc, - signer_registry_lookup: Arc, - musig2_ceremony_pending_commands: Arc>, - signing_key_pub: [u8; 32], - ) -> Self { - Self { - shielding_key, - ethereum_key_repository, - bitcoin_key_repository, - enclave_signer, - state_handler, - ocall_api, - relayer_registry_lookup, - musig2_ceremony_registry, - enclave_registry_lookup, - signer_registry_lookup, - musig2_ceremony_pending_commands, - signing_key_pub, - } - } -} - -#[allow(clippy::type_complexity)] -pub fn run_bit_across_handler_runner( - context: Arc>, -) where - SKR: AccessKey + Send + Sync + 'static, - EKR: AccessKey + Send + Sync + 'static, - BKR: AccessKey + Send + Sync + 'static, - ::KeyType: ShieldingCryptoEncrypt + ShieldingCryptoDecrypt + 'static, - S: StfEnclaveSigning + Send + Sync + 'static, - H: HandleState + Send + Sync + 'static, - H::StateT: SgxExternalitiesTrait, - O: EnclaveOnChainOCallApi + EnclaveMetricsOCallApi + EnclaveAttestationOCallApi + 'static, - RRL: RelayerRegistryLookup + Send + Sync + 'static, - ERL: EnclaveRegistryLookup + Send + Sync + 'static, - SRL: SignerRegistryLookup + Send + Sync + 'static, -{ - let bit_across_task_receiver = init_bit_across_task_sender_storage(); - let n_workers = 2; - let pool = ThreadPool::new(n_workers); - - while let Ok(mut req) = bit_across_task_receiver.recv() { - let context_pool = context.clone(); - pool.execute(move || { - if let Err(e) = req.sender.send(handle_request(&mut req.request, context_pool)) { - warn!("Unable to submit response back to the handler: {:?}", e); - } - }); - } - - pool.join(); - warn!("bit_across_task_receiver loop terminated"); -} - -#[allow(clippy::type_complexity)] -pub fn handle_request( - request: &mut AesRequest, - context: Arc>, -) -> Result> -where - SKR: AccessKey, - EKR: AccessKey, - BKR: AccessKey, - ::KeyType: ShieldingCryptoEncrypt + ShieldingCryptoDecrypt + 'static, - S: StfEnclaveSigning + Send + Sync + 'static, - H: HandleState + Send + Sync + 'static, - O: EnclaveOnChainOCallApi + EnclaveMetricsOCallApi + EnclaveAttestationOCallApi + 'static, - RRL: RelayerRegistryLookup + 'static, - ERL: EnclaveRegistryLookup + 'static, - SRL: SignerRegistryLookup + 'static, -{ - let enclave_shielding_key = context.shielding_key.retrieve_key().map_err(|e| { - let err = format!("Failed to retrieve shielding key: {:?}", e); - error!("{}", err); - err - })?; - let dc = request - .decrypt(Box::new(enclave_shielding_key)) - .ok() - .and_then(|v| DirectCallSigned::decode(&mut v.as_slice()).ok()) - .ok_or_else(|| { - let err = "Failed to decode payload".to_string(); - error!("{}", err); - err - })?; - - let mrenclave = match context.ocall_api.get_mrenclave_of_self() { - Ok(m) => m.m, - Err(_) => { - let err = "Failed to get mrenclave"; - error!("{}", err); - return Err(err.encode()) - }, - }; - debug!("Direct call is: {:?}", dc); - ensure!(dc.verify_signature(&mrenclave, &request.shard), "Failed to verify sig".to_string()); - match dc.call { - DirectCall::SignBitcoin(signer, aes_key, payload) => { - let hash = blake2_256(&payload.encode()); - sign_bitcoin::handle( - signer, - payload, - aes_key, - false, - context.relayer_registry_lookup.deref(), - context.musig2_ceremony_registry.clone(), - context.musig2_ceremony_pending_commands.clone(), - context.signer_registry_lookup.clone(), - context.enclave_registry_lookup.as_ref(), - &context.signing_key_pub, - context.bitcoin_key_repository.clone(), - ) - .map_err(|e| { - error!("SignBitcoin error: {:?}", e); - aes_encrypt_default(&aes_key, &e.encode()).encode() - })?; - Ok(BitAcrossProcessingResult::Submitted(hash)) - }, - DirectCall::CheckSignBitcoin(signer) => { - let payload = SignBitcoinPayload::Derived([0u8; 32].to_vec()); - let aes_key = [0u8; 32]; - let hash = blake2_256(&payload.encode()); - sign_bitcoin::handle( - signer, - payload, - aes_key, - true, - context.relayer_registry_lookup.deref(), - context.musig2_ceremony_registry.clone(), - context.musig2_ceremony_pending_commands.clone(), - context.signer_registry_lookup.clone(), - context.enclave_registry_lookup.as_ref(), - &context.signing_key_pub, - context.bitcoin_key_repository.clone(), - ) - .map_err(|e| { - error!("SignBitcoinCheck error: {:?}", e); - aes_encrypt_default(&aes_key, &e.encode()).encode() - })?; - Ok(BitAcrossProcessingResult::Submitted(hash)) - }, - DirectCall::SignEthereum(signer, aes_key, msg) => sign_ethereum::handle( - signer, - msg, - context.relayer_registry_lookup.deref(), - context.ethereum_key_repository.deref(), - ) - .map_err(|e| { - error!("SignEthereum error: {:?}", e); - aes_encrypt_default(&aes_key, &e.encode()).encode() - }) - .map(|r| BitAcrossProcessingResult::Ok(aes_encrypt_default(&aes_key, &r).encode())), - DirectCall::NonceShare(signer, aes_key, message, nonce) => nonce_share::handle( - signer, - message, - nonce, - context.musig2_ceremony_registry.clone(), - context.musig2_ceremony_pending_commands.clone(), - context.enclave_registry_lookup.clone(), - ) - .map_err(|e| { - error!("NonceShare error: {:?}", e); - aes_encrypt_default(&aes_key, &e.encode()).encode() - }) - .map(|r| { - BitAcrossProcessingResult::Ok(aes_encrypt_default(&aes_key, &r.encode()).encode()) - }), - DirectCall::PartialSignatureShare(signer, aes_key, message, signature) => - partial_signature_share::handle( - signer, - message, - signature, - context.musig2_ceremony_registry.clone(), - context.enclave_registry_lookup.clone(), - ) - .map_err(|e| { - error!("PartialSignatureShare error: {:?}", e); - aes_encrypt_default(&aes_key, &e.encode()).encode() - }) - .map(|r| { - BitAcrossProcessingResult::Ok(aes_encrypt_default(&aes_key, &r.encode()).encode()) - }), - DirectCall::KillCeremony(signer, aes_key, message) => kill_ceremony::handle( - signer, - message, - context.musig2_ceremony_registry.clone(), - context.musig2_ceremony_pending_commands.clone(), - context.enclave_registry_lookup.clone(), - ) - .map_err(|e| { - error!("KillCeremony error: {:?}", e); - aes_encrypt_default(&aes_key, &e.encode()).encode() - }) - .map(|r| { - BitAcrossProcessingResult::Ok(aes_encrypt_default(&aes_key, &r.encode()).encode()) - }), - } -} diff --git a/bitacross-worker/bitacross/core/bc-task-sender/src/lib.rs b/bitacross-worker/bitacross/core/bc-task-sender/src/lib.rs index f21440a314..82fc05ca9a 100644 --- a/bitacross-worker/bitacross/core/bc-task-sender/src/lib.rs +++ b/bitacross-worker/bitacross/core/bc-task-sender/src/lib.rs @@ -51,9 +51,9 @@ use std::{ }; #[derive(Debug)] -pub struct BitAcrossRequest { - pub sender: oneshot::Sender>>, - pub request: AesRequest, +pub enum BitAcrossRequest { + Request(AesRequest, oneshot::Sender>>), + ShareCeremonyData(AesRequest), } #[derive(Encode, Decode, Clone, Debug)] diff --git a/bitacross-worker/core-primitives/enclave-api/ffi/src/lib.rs b/bitacross-worker/core-primitives/enclave-api/ffi/src/lib.rs index 187f8a53e2..ba18cbaa7d 100644 --- a/bitacross-worker/core-primitives/enclave-api/ffi/src/lib.rs +++ b/bitacross-worker/core-primitives/enclave-api/ffi/src/lib.rs @@ -28,6 +28,8 @@ extern "C" { untrusted_worker_addr_size: u32, encoded_base_dir_str: *const u8, encoded_base_dir_size: u32, + ceremony_commands_thread_count: u8, + ceremony_events_thread_count: u8, ) -> sgx_status_t; pub fn init_direct_invocation_server( diff --git a/bitacross-worker/core-primitives/enclave-api/src/enclave_base.rs b/bitacross-worker/core-primitives/enclave-api/src/enclave_base.rs index 4079052a60..b56b5ed0ba 100644 --- a/bitacross-worker/core-primitives/enclave-api/src/enclave_base.rs +++ b/bitacross-worker/core-primitives/enclave-api/src/enclave_base.rs @@ -35,6 +35,8 @@ pub trait EnclaveBase: Send + Sync + 'static { mu_ra_addr: &str, untrusted_worker_addr: &str, base_dir: &str, + ceremony_commands_thread_count: u8, + ceremony_events_thread_count: u8, ) -> EnclaveResult<()>; /// Initialize the direct invocation RPC server. fn init_direct_invocation_server(&self, rpc_server_addr: String) -> EnclaveResult<()>; @@ -118,6 +120,8 @@ mod impl_ffi { mu_ra_addr: &str, untrusted_worker_addr: &str, base_dir: &str, + ceremony_commands_thread_count: u8, + ceremony_events_thread_count: u8, ) -> EnclaveResult<()> { let mut retval = sgx_status_t::SGX_SUCCESS; @@ -135,6 +139,8 @@ mod impl_ffi { encoded_untrusted_worker_addr.len() as u32, encoded_base_dir.as_ptr(), encoded_base_dir.len() as u32, + ceremony_commands_thread_count, + ceremony_events_thread_count, ) }; diff --git a/bitacross-worker/core/direct-rpc-client/Cargo.toml b/bitacross-worker/core/direct-rpc-client/Cargo.toml index 354bc3c8b2..dcb21c803b 100644 --- a/bitacross-worker/core/direct-rpc-client/Cargo.toml +++ b/bitacross-worker/core/direct-rpc-client/Cargo.toml @@ -25,7 +25,6 @@ webpki = { version = "0.21", optional = true } # local dependencies itp-rpc = { path = "../../core-primitives/rpc", default-features = false } itp-types = { path = "../../core-primitives/types", default-features = false } -itp-utils = { path = "../../core-primitives/utils", default-features = false } [features] default = ["std"] @@ -43,6 +42,5 @@ std = [ "url/std", "itp-rpc/std", "itp-types/std", - "itp-utils/std", "log/std", ] diff --git a/bitacross-worker/core/direct-rpc-client/src/lib.rs b/bitacross-worker/core/direct-rpc-client/src/lib.rs index 69f5d95bfc..b11d95fb0c 100644 --- a/bitacross-worker/core/direct-rpc-client/src/lib.rs +++ b/bitacross-worker/core/direct-rpc-client/src/lib.rs @@ -41,11 +41,7 @@ use core::str::FromStr; use log::{debug, error}; -use serde_json::from_str; - -use itp_rpc::{Id, RpcRequest, RpcResponse, RpcReturnValue}; - -use itp_utils::FromHexPrefixed; +use itp_rpc::{Id, RpcRequest, RpcReturnValue}; use std::{ boxed::Box, @@ -98,12 +94,8 @@ impl rustls::ClientCertVerifier for IgnoreCertVerifier { } pub trait RpcClientFactory { - type Client: RpcClient; - fn create( - &self, - url: &str, - response_sink: Sender, - ) -> Result>; + type Client: RpcClient + Send + Clone; + fn create(&self, url: &str) -> Result>; } pub struct DirectRpcClientFactory {} @@ -111,12 +103,8 @@ pub struct DirectRpcClientFactory {} impl RpcClientFactory for DirectRpcClientFactory { type Client = DirectRpcClient; - fn create( - &self, - url: &str, - response_sink: Sender, - ) -> Result> { - DirectRpcClient::new(url, response_sink) + fn create(&self, url: &str) -> Result> { + DirectRpcClient::new(url) } } @@ -124,12 +112,13 @@ pub trait RpcClient { fn send(&mut self, request: &RpcRequest) -> Result<(), Box>; } +#[derive(Clone)] pub struct DirectRpcClient { request_sink: Sender, } impl DirectRpcClient { - pub fn new(url: &str, response_sink: Sender) -> Result> { + pub fn new(url: &str) -> Result> { let server_url = Url::from_str(url).map_err(|e| format!("Could not connect, reason: {:?}", e))?; let mut config = rustls::ClientConfig::new(); @@ -157,14 +146,6 @@ impl DirectRpcClient { error!("Could not write message to socket, reason: {:?}", e) } } - - if let Ok(message) = socket.read_message() { - if let Ok(Some(response)) = Self::handle_ws_message(message) { - if let Err(e) = response_sink.send(response) { - log::error!("Could not forward response, reason: {:?}", e) - }; - } - } std::thread::sleep(Duration::from_millis(1)) } }); @@ -190,23 +171,6 @@ impl DirectRpcClient { _ => {}, } } - - fn handle_ws_message(message: Message) -> Result, Box> { - match message { - Message::Text(text) => { - let rpc_response: RpcResponse = from_str(&text) - .map_err(|e| format!("Could not deserialize RpcResponse, reason: {:?}", e))?; - let return_value: RpcReturnValue = - RpcReturnValue::from_hex(&rpc_response.result) - .map_err(|e| format!("Could not deserialize value , reason: {:?}", e))?; - Ok(Some((rpc_response.id, return_value))) - }, - _ => { - log::warn!("Only text messages are supported"); - Ok(None) - }, - } - } } #[derive(Clone)] @@ -224,39 +188,3 @@ impl RpcClient for DirectRpcClient { .map_err(|e| format!("Could not write message, reason: {:?}", e).into()) } } - -#[cfg(test)] -mod tests { - use crate::DirectRpcClient; - use itp_rpc::{Id, RpcResponse, RpcReturnValue}; - use itp_types::{DirectRequestStatus, TrustedOperationStatus, H256}; - use itp_utils::ToHexPrefixed; - use tungstenite::Message; - - #[test] - fn test_response_handling() { - let id = Id::Text( - "0x0000000000000000000000000000000000000000000000000000000000000000".to_owned(), - ); - let return_value: RpcReturnValue = RpcReturnValue::new( - vec![], - false, - DirectRequestStatus::TrustedOperationStatus( - TrustedOperationStatus::TopExecuted(vec![], true), - H256::random(), - ), - ); - let rpc_response: RpcResponse = RpcResponse { - jsonrpc: "2.0".to_owned(), - result: return_value.to_hex(), - id: id.clone(), - }; - let serialized_rpc_response = serde_json::to_string(&rpc_response).unwrap(); - let message = Message::text(serialized_rpc_response); - - let (result_id, result) = DirectRpcClient::handle_ws_message(message).unwrap().unwrap(); - - assert_eq!(id, result_id); - assert_eq!(return_value, result); - } -} diff --git a/bitacross-worker/enclave-runtime/Cargo.lock b/bitacross-worker/enclave-runtime/Cargo.lock index 6d3baa0106..cb35bec9c2 100644 --- a/bitacross-worker/enclave-runtime/Cargo.lock +++ b/bitacross-worker/enclave-runtime/Cargo.lock @@ -294,7 +294,7 @@ dependencies = [ ] [[package]] -name = "bc-musig2-runner" +name = "bc-musig2-event" version = "0.1.0" dependencies = [ "bc-enclave-registry", @@ -316,6 +316,7 @@ dependencies = [ "sgx_rand", "sgx_tstd", "sp-core", + "threadpool", ] [[package]] @@ -349,11 +350,12 @@ dependencies = [ ] [[package]] -name = "bc-task-receiver" +name = "bc-task-processor" version = "0.1.0" dependencies = [ "bc-enclave-registry", "bc-musig2-ceremony", + "bc-musig2-event", "bc-relayer-registry", "bc-signer-registry", "bc-task-sender", @@ -361,6 +363,8 @@ dependencies = [ "futures 0.3.8", "hex", "ita-stf", + "itc-direct-rpc-client", + "itc-direct-rpc-server", "itp-ocall-api", "itp-sgx-crypto", "itp-sgx-externalities", @@ -371,7 +375,9 @@ dependencies = [ "litentry-primitives", "log", "parity-scale-codec", + "sgx_crypto_helper", "sgx_tstd", + "sp-core", "thiserror", "threadpool", ] @@ -1021,10 +1027,9 @@ dependencies = [ "array-bytes 6.1.0", "bc-enclave-registry", "bc-musig2-ceremony", - "bc-musig2-runner", "bc-relayer-registry", "bc-signer-registry", - "bc-task-receiver", + "bc-task-processor", "bc-task-sender", "cid", "derive_more", @@ -2030,7 +2035,6 @@ version = "0.1.0" dependencies = [ "itp-rpc", "itp-types", - "itp-utils", "log", "parity-scale-codec", "rustls 0.19.0 (git+https://github.com/mesalock-linux/rustls?tag=sgx_1.1.3)", diff --git a/bitacross-worker/enclave-runtime/Cargo.toml b/bitacross-worker/enclave-runtime/Cargo.toml index 28307ca02e..d847ed2052 100644 --- a/bitacross-worker/enclave-runtime/Cargo.toml +++ b/bitacross-worker/enclave-runtime/Cargo.toml @@ -20,7 +20,7 @@ development = [ "itp-attestation-handler/development", "litentry-primitives/development", "litentry-macros/development", - "bc-task-receiver/development", + "bc-task-processor/development", ] offchain-worker = [ "itp-settings/offchain-worker", @@ -131,7 +131,6 @@ itp-utils = { path = "../core-primitives/utils", default-features = false } # litentry bc-enclave-registry = { path = "../bitacross/core/bc-enclave-registry", default-features = false, features = ["sgx"] } bc-musig2-ceremony = { path = "../bitacross/core/bc-musig2-ceremony", default-features = false, features = ["sgx"] } -bc-musig2-runner = { path = "../bitacross/core/bc-musig2-runner", default-features = false, features = ["sgx"] } bc-relayer-registry = { path = "../bitacross/core/bc-relayer-registry", default-features = false, features = ["sgx"] } bc-signer-registry = { path = "../bitacross/core/bc-signer-registry", default-features = false, features = ["sgx"] } bc-task-sender = { path = "../bitacross/core/bc-task-sender", default-features = false, features = ["sgx"] } @@ -142,7 +141,7 @@ litentry-primitives = { path = "../litentry/primitives", default-features = fals litentry-proc-macros = { path = "../../primitives/core/proc-macros", default-features = false } # bitacross -bc-task-receiver = { path = "../bitacross/core/bc-task-receiver", default-features = false, features = ["sgx"] } +bc-task-processor = { path = "../bitacross/core/bc-task-processor", default-features = false, features = ["sgx"] } # substrate deps frame-support = { default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.42" } diff --git a/bitacross-worker/enclave-runtime/Enclave.config.production.xml b/bitacross-worker/enclave-runtime/Enclave.config.production.xml index 00336e8aa8..669cbe2087 100644 --- a/bitacross-worker/enclave-runtime/Enclave.config.production.xml +++ b/bitacross-worker/enclave-runtime/Enclave.config.production.xml @@ -4,7 +4,7 @@ 0 0x40000 0x20000000 - 32 + 64 0 1 0 diff --git a/bitacross-worker/enclave-runtime/Enclave.config.xml b/bitacross-worker/enclave-runtime/Enclave.config.xml index 62e08c1a5f..747875739c 100644 --- a/bitacross-worker/enclave-runtime/Enclave.config.xml +++ b/bitacross-worker/enclave-runtime/Enclave.config.xml @@ -4,7 +4,7 @@ 0 0x40000 0x20000000 - 32 + 64 0 0 0 diff --git a/bitacross-worker/enclave-runtime/Enclave.edl b/bitacross-worker/enclave-runtime/Enclave.edl index b0b0b64e7f..7b54768ba5 100644 --- a/bitacross-worker/enclave-runtime/Enclave.edl +++ b/bitacross-worker/enclave-runtime/Enclave.edl @@ -40,7 +40,8 @@ enclave { public sgx_status_t init( [in, size=mu_ra_addr_size] uint8_t* mu_ra_addr, uint32_t mu_ra_addr_size, [in, size=untrusted_worker_addr_size] uint8_t* untrusted_worker_addr, uint32_t untrusted_worker_addr_size, - [in, size=encoded_base_dir_size] uint8_t* encoded_base_dir_str, uint32_t encoded_base_dir_size + [in, size=encoded_base_dir_size] uint8_t* encoded_base_dir_str, uint32_t encoded_base_dir_size, + uint8_t ceremony_commands_thread_count, uint8_t ceremony_events_thread_count ); public sgx_status_t publish_wallets(); diff --git a/bitacross-worker/enclave-runtime/src/initialization/mod.rs b/bitacross-worker/enclave-runtime/src/initialization/mod.rs index 6c401a78f2..d9b45b67c0 100644 --- a/bitacross-worker/enclave-runtime/src/initialization/mod.rs +++ b/bitacross-worker/enclave-runtime/src/initialization/mod.rs @@ -45,20 +45,17 @@ use crate::{ }; use base58::ToBase58; use bc_enclave_registry::EnclaveRegistryUpdater; -use bc_musig2_ceremony::{CeremonyRegistry, MuSig2Ceremony}; -use bc_musig2_runner::init_ceremonies_thread; +use bc_musig2_ceremony::{CeremonyCommandTmp, CeremonyId, CeremonyRegistry, MuSig2Ceremony}; use bc_relayer_registry::{RelayerRegistry, RelayerRegistryUpdater}; use bc_signer_registry::SignerRegistryUpdater; -use bc_task_receiver::{run_bit_across_handler_runner, BitAcrossTaskContext}; +use bc_task_processor::{run_bit_across_handler_runner, BitAcrossTaskContext}; use codec::Encode; use ita_stf::{Getter, TrustedCallSigned}; -use itc_direct_rpc_client::DirectRpcClientFactory; use itc_direct_rpc_server::{ create_determine_watch, rpc_connection_registry::ConnectionRegistry, - rpc_ws_handler::RpcWsHandler, + rpc_responder::RpcResponder, rpc_ws_handler::RpcWsHandler, }; -use bc_musig2_ceremony::{CeremonyCommandsRegistry, CeremonyId}; use itc_parentchain_light_client::{concurrent_access::ValidatorAccess, ExtrinsicSender}; use itc_tls_websocket_server::{ certificate_generation::ed25519_self_signed_certificate, @@ -97,16 +94,21 @@ use itp_top_pool_author::author::AuthorTopFilter; use itp_types::{parentchain::ParentchainId, OpaqueCall, ShardIdentifier}; use litentry_macros::if_development_or; use log::*; -use sp_core::crypto::Pair; +use sp_core::{crypto::Pair, H256}; use std::{collections::HashMap, path::PathBuf, string::String, sync::Arc}; -use std::sync::SgxMutex as Mutex; +use std::sync::SgxRwLock as RwLock; pub(crate) fn init_enclave( mu_ra_url: String, untrusted_worker_url: String, base_dir: PathBuf, + ceremony_commands_thread_count: u8, + ceremony_events_thread_count: u8, ) -> EnclaveResult<()> { + info!("Ceremony commands thread count: {}", ceremony_commands_thread_count); + info!("Ceremony events thread count: {}", ceremony_events_thread_count); + let signing_key_repository = Arc::new(get_ed25519_repository(base_dir.clone())?); GLOBAL_SIGNING_KEY_REPOSITORY_COMPONENT.initialize(signing_key_repository.clone()); @@ -216,12 +218,12 @@ pub(crate) fn init_enclave( let getter_executor = Arc::new(EnclaveGetterExecutor::new(state_observer)); - let ceremony_registry = Arc::new(Mutex::new(HashMap::< + let ceremony_registry = Arc::new(RwLock::new(HashMap::< CeremonyId, - MuSig2Ceremony>, + (Arc>>>, u64), >::new())); - let pending_ceremony_commands = Arc::new(Mutex::new(CeremonyCommandsRegistry::new())); + let ceremony_command_tmp = Arc::new(RwLock::new(CeremonyCommandTmp::new())); let attestation_handler = Arc::new(IntelAttestationHandler::new(ocall_api.clone(), signing_key_repository.clone())); @@ -237,13 +239,13 @@ pub(crate) fn init_enclave( let enclave_registry = Arc::new(EnclaveRegistry::new(base_dir)); enclave_registry.init().map_err(|e| Error::Other(e.into()))?; - GLOBAL_ENCLAVE_REGISTRY.initialize(enclave_registry.clone()); + GLOBAL_ENCLAVE_REGISTRY.initialize(enclave_registry); let io_handler = public_api_rpc_handler( top_pool_author, getter_executor, shielding_key_repository, - ocall_api.clone(), + ocall_api, signing_key_repository, bitcoin_key_repository, ethereum_key_repository, @@ -252,25 +254,18 @@ pub(crate) fn init_enclave( let rpc_handler = Arc::new(RpcWsHandler::new(io_handler, watch_extractor, connection_registry)); GLOBAL_RPC_WS_HANDLER_COMPONENT.initialize(rpc_handler); - let ceremony_registry_cloned = ceremony_registry.clone(); - let pending_ceremony_commands_cloned = pending_ceremony_commands.clone(); - std::thread::spawn(move || { - run_bit_across_handler(ceremony_registry, pending_ceremony_commands, signer.public().0) - .unwrap() + run_bit_across_handler( + ceremony_registry, + ceremony_command_tmp, + signer.public().0, + rpc_responder, + ceremony_commands_thread_count, + ceremony_events_thread_count, + ) + .unwrap() }); - let client_factory = DirectRpcClientFactory {}; - init_ceremonies_thread( - GLOBAL_SIGNING_KEY_REPOSITORY_COMPONENT.get()?, - GLOBAL_SHIELDING_KEY_REPOSITORY_COMPONENT.get()?, - Arc::new(client_factory), - enclave_registry, - ceremony_registry_cloned, - pending_ceremony_commands_cloned, - ocall_api, - rpc_responder, - ); Ok(()) } @@ -367,11 +362,17 @@ fn initialize_state_observer( } fn run_bit_across_handler( - musig2_ceremony_registry: Arc>>>, - musig2_ceremony_pending_commands: Arc>, + ceremony_registry: Arc>>>, + musig2_ceremony_pending_commands: Arc>, signing_key_pub: [u8; 32], + responder: Arc< + RpcResponder, H256, RpcResponseChannel>, + >, + ceremony_commands_thread_count: u8, + ceremony_events_thread_count: u8, ) -> Result<(), Error> { let author_api = GLOBAL_TOP_POOL_AUTHOR_COMPONENT.get()?; + let signing_key = GLOBAL_SIGNING_KEY_REPOSITORY_COMPONENT.get()?; let state_handler = GLOBAL_STATE_HANDLER_COMPONENT.get()?; let state_observer = GLOBAL_STATE_OBSERVER_COMPONENT.get()?; let relayer_registry_lookup = GLOBAL_RELAYER_REGISTRY.get()?; @@ -393,19 +394,25 @@ fn run_bit_across_handler( let stf_task_context = BitAcrossTaskContext::new( shielding_key_repository, + signing_key, ethereum_key_repository, bitcoin_key_repository, stf_enclave_signer, state_handler, ocall_api, relayer_registry_lookup, - musig2_ceremony_registry, enclave_registry_lookup, signer_registry_lookup, - musig2_ceremony_pending_commands, signing_key_pub, + ceremony_registry, + musig2_ceremony_pending_commands, + responder, + ); + run_bit_across_handler_runner( + Arc::new(stf_task_context), + ceremony_commands_thread_count, + ceremony_events_thread_count, ); - run_bit_across_handler_runner(Arc::new(stf_task_context)); Ok(()) } diff --git a/bitacross-worker/enclave-runtime/src/lib.rs b/bitacross-worker/enclave-runtime/src/lib.rs index c0b49ad5c2..6de9732e39 100644 --- a/bitacross-worker/enclave-runtime/src/lib.rs +++ b/bitacross-worker/enclave-runtime/src/lib.rs @@ -129,6 +129,8 @@ pub unsafe extern "C" fn init( untrusted_worker_addr_size: u32, encoded_base_dir_str: *const u8, encoded_base_dir_size: u32, + ceremony_commands_thread_count: u8, + ceremony_events_thread_count: u8, ) -> sgx_status_t { // Initialize the logging environment in the enclave. if_development_or!( @@ -184,7 +186,13 @@ pub unsafe extern "C" fn init( // Litentry: the default value here is only for clippy checking BASE_PATH.set(path.clone()).unwrap_or(()); - match initialization::init_enclave(mu_ra_url, untrusted_worker_url, path) { + match initialization::init_enclave( + mu_ra_url, + untrusted_worker_url, + path, + ceremony_commands_thread_count, + ceremony_events_thread_count, + ) { Err(e) => e.into(), Ok(()) => sgx_status_t::SGX_SUCCESS, } diff --git a/bitacross-worker/enclave-runtime/src/rpc/worker_api_direct.rs b/bitacross-worker/enclave-runtime/src/rpc/worker_api_direct.rs index 3128077783..7fc6a8d60c 100644 --- a/bitacross-worker/enclave-runtime/src/rpc/worker_api_direct.rs +++ b/bitacross-worker/enclave-runtime/src/rpc/worker_api_direct.rs @@ -149,7 +149,7 @@ where io.add_method("bitacross_submitRequest", move |params: Params| { debug!("worker_api_direct rpc was called: bitacross_submitRequest"); async move { - let json_value = match request_bit_across_inner(params).await { + let json_value = match bitacross_task_create_inner(params).await { Ok(value) => value.to_hex(), Err(error) => RpcReturnValue { value: error, @@ -162,6 +162,18 @@ where } }); + // btc sign task data share + io.add_sync_method("bitacross_btcDataShare", move |params: Params| { + debug!("worker_api_direct rpc was called: bitacross_btcDataShare"); + let json_value = match bitacross_data_share_inner(params) { + Ok(value) => value.to_hex(), + Err(error) => + RpcReturnValue { value: error, do_watch: false, status: DirectRequestStatus::Error } + .to_hex(), + }; + Ok(json!(json_value)) + }); + io.add_method("bitacross_checkSignBitcoin", move |_params: Params| { debug!("worker_api_direct rpc was called: bitacross_checkSignBitcoin"); let request = prepare_check_sign_bitcoin_request( @@ -172,7 +184,7 @@ where async move { if let Ok(request) = request { let params = Params::Array(vec![jsonrpc_core::Value::String(request.to_hex())]); - let json_value = match request_bit_across_inner(params).await { + let json_value = match bitacross_task_create_inner(params).await { Ok(value) => value.to_hex(), Err(error) => RpcReturnValue { value: error, @@ -512,15 +524,13 @@ pub enum BitacrossRequestError { Other(Vec), } -async fn request_bit_across_inner(params: Params) -> Result> { - let payload = get_request_payload(params)?; - let request = AesRequest::from_hex(&payload) - .map_err(|e| format!("AesRequest construction error: {:?}", e))?; +async fn bitacross_task_create_inner(params: Params) -> Result> { + let request = get_request_from_params(params)?; let bit_across_request_sender = BitAcrossRequestSender::new(); let (sender, receiver) = oneshot::channel::>>(); - bit_across_request_sender.send(BitAcrossRequest { sender, request })?; + bit_across_request_sender.send(BitAcrossRequest::Request(request, sender))?; // we only expect one response, hence no loop match receiver.await { @@ -556,14 +566,24 @@ async fn request_bit_across_inner(params: Params) -> Result Result> { + let request = get_request_from_params(params)?; + let bit_across_request_sender = BitAcrossRequestSender::new(); + bit_across_request_sender.send(BitAcrossRequest::ShareCeremonyData(request))?; + Ok(RpcReturnValue { do_watch: false, value: vec![], status: DirectRequestStatus::Ok }) +} + // we expect our `params` to be "by-position array" // see https://www.jsonrpc.org/specification#parameter_structures -fn get_request_payload(params: Params) -> Result { +fn get_request_from_params(params: Params) -> Result { let s_vec = params.parse::>().map_err(|e| format!("{}", e))?; let s = s_vec.get(0).ok_or_else(|| "Empty params".to_string())?; debug!("Request payload: {}", s); - Ok(s.to_owned()) + + let request = + AesRequest::from_hex(s).map_err(|e| format!("AesRequest construction error: {:?}", e))?; + Ok(request) } #[cfg(feature = "test")] diff --git a/bitacross-worker/litentry/core/direct-call/src/handler/kill_ceremony.rs b/bitacross-worker/litentry/core/direct-call/src/handler/kill_ceremony.rs index 21f94fbd00..842733c60a 100644 --- a/bitacross-worker/litentry/core/direct-call/src/handler/kill_ceremony.rs +++ b/bitacross-worker/litentry/core/direct-call/src/handler/kill_ceremony.rs @@ -15,30 +15,19 @@ // along with Litentry. If not, see . use bc_enclave_registry::EnclaveRegistryLookup; -use parentchain_primitives::Identity; -use std::sync::Arc; - -#[cfg(feature = "std")] -use std::sync::Mutex; - -use bc_musig2_ceremony::{CeremonyCommandsRegistry, CeremonyId, CeremonyRegistry}; +use bc_musig2_ceremony::CeremonyCommand; use codec::Encode; -use itp_sgx_crypto::{key_repository::AccessKey, schnorr::Pair as SchnorrPair}; -#[cfg(feature = "sgx")] -use std::sync::SgxMutex as Mutex; +use parentchain_primitives::Identity; #[derive(Encode, Debug)] pub enum KillCeremonyError { InvalidSigner, } -pub fn handle>( +pub fn handle( signer: Identity, - ceremony_id: CeremonyId, - ceremony_registry: Arc>>, - ceremony_commands: Arc>, - enclave_registry: Arc, -) -> Result<(), KillCeremonyError> { + enclave_registry: &ER, +) -> Result { let is_valid_signer = match signer { Identity::Substrate(address) => enclave_registry.contains_key(&address), _ => false, @@ -48,11 +37,7 @@ pub fn handle>( } match signer { - Identity::Substrate(_) => { - ceremony_registry.lock().unwrap().remove(&ceremony_id); - ceremony_commands.lock().unwrap().remove(&ceremony_id); - }, - _ => return Err(KillCeremonyError::InvalidSigner), + Identity::Substrate(_) => Ok(CeremonyCommand::KillCeremony), + _ => Err(KillCeremonyError::InvalidSigner), } - Ok(()) } diff --git a/bitacross-worker/litentry/core/direct-call/src/handler/nonce_share.rs b/bitacross-worker/litentry/core/direct-call/src/handler/nonce_share.rs index 2ebbbc71ed..d446718fe3 100644 --- a/bitacross-worker/litentry/core/direct-call/src/handler/nonce_share.rs +++ b/bitacross-worker/litentry/core/direct-call/src/handler/nonce_share.rs @@ -14,23 +14,13 @@ // You should have received a copy of the GNU General Public License // along with Litentry. If not, see . -use bc_enclave_registry::EnclaveRegistryLookup; -use parentchain_primitives::Identity; -use std::{sync::Arc, vec}; - -#[cfg(feature = "std")] -use std::sync::Mutex; - use crate::handler::nonce_share::NonceShareError::InvalidSigner; -use bc_musig2_ceremony::{ - CeremonyCommand, CeremonyCommandsRegistry, CeremonyId, CeremonyRegistry, - PendingCeremonyCommand, PubNonce, -}; +use bc_enclave_registry::EnclaveRegistryLookup; +use bc_musig2_ceremony::{CeremonyCommand, CeremonyId, PubNonce}; use codec::Encode; -use itp_sgx_crypto::{key_repository::AccessKey, schnorr::Pair as SchnorrPair}; use log::debug; -#[cfg(feature = "sgx")] -use std::sync::SgxMutex as Mutex; +use parentchain_primitives::Identity; +use std::sync::Arc; #[derive(Encode, Debug)] pub enum NonceShareError { @@ -38,14 +28,12 @@ pub enum NonceShareError { InvalidNonce, } -pub fn handle>( +pub fn handle( signer: Identity, - ceremony_id: CeremonyId, + ceremony_id: &CeremonyId, payload: [u8; 66], - ceremony_registry: Arc>>, - ceremony_commands: Arc>, enclave_registry: Arc, -) -> Result<(), NonceShareError> { +) -> Result { debug!("Received nonce share from: {:?} for ceremony {:?}", signer, ceremony_id); let is_valid_signer = match signer { Identity::Substrate(address) => enclave_registry.contains_key(&address), @@ -59,41 +47,18 @@ pub fn handle>( PubNonce::from_bytes(payload.as_slice()).map_err(|_| NonceShareError::InvalidNonce)?; match signer { - Identity::Substrate(address) => { - let mut registry = ceremony_registry.lock().unwrap(); - if let Some(ceremony) = registry.get_mut(&ceremony_id) { - ceremony.save_event(CeremonyCommand::SaveNonce(*address.as_ref(), nonce)); - } else { - debug!("Ceremony {:?} not found, saving events...", ceremony_id); - let mut commands = ceremony_commands.lock().unwrap(); - // ~1 minute (1 tick ~ 1 s) - let ceremony_tick_to_live = 60_000; - let command = PendingCeremonyCommand { - ticks_left: ceremony_tick_to_live, - command: CeremonyCommand::SaveNonce(*address.as_ref(), nonce), - }; - if let Some(events) = commands.get_mut(&ceremony_id) { - debug!("Commands found, appending..."); - events.push(command); - } else { - debug!("Commands not found, creating..."); - commands.insert(ceremony_id, vec![command]); - } - } - }, - _ => return Err(InvalidSigner), + Identity::Substrate(address) => Ok(CeremonyCommand::SaveNonce(*address.as_ref(), nonce)), + _ => Err(InvalidSigner), } - Ok(()) } #[cfg(test)] pub mod test { - use crate::handler::nonce_share::{handle, NonceShareError, SchnorrPair}; + use crate::handler::nonce_share::{handle, NonceShareError}; use alloc::sync::Arc; use bc_enclave_registry::{EnclaveRegistry, EnclaveRegistryUpdater}; - use bc_musig2_ceremony::{CeremonyCommandsRegistry, CeremonyRegistry, SignBitcoinPayload}; - use codec::alloc::sync::Mutex; - use itp_sgx_crypto::{key_repository::AccessKey, Error}; + use bc_musig2_ceremony::SignBitcoinPayload; + use itp_sgx_crypto::{key_repository::AccessKey, schnorr::Pair as SchnorrPair, Error}; use parentchain_primitives::Identity; use sp_core::{sr25519, Pair}; @@ -113,8 +78,6 @@ pub mod test { let alice_key_pair = sr25519::Pair::from_string("//Alice", None).unwrap(); let signer_account = Identity::Substrate(alice_key_pair.public().into()); let ceremony_id = SignBitcoinPayload::Derived(vec![]); - let ceremony_registry = Arc::new(Mutex::new(CeremonyRegistry::::new())); - let ceremony_commands_registry = Arc::new(Mutex::new(CeremonyCommandsRegistry::new())); let enclave_registry = Arc::new(EnclaveRegistry::default()); let _ = enclave_registry.update(alice_key_pair.public().into(), "localhost:2000".to_string()); @@ -122,15 +85,13 @@ pub mod test { // when let result = handle( signer_account, - ceremony_id, + &ceremony_id, [ 2, 121, 190, 102, 126, 249, 220, 187, 172, 85, 160, 98, 149, 206, 135, 11, 7, 2, 155, 252, 219, 45, 206, 40, 217, 89, 242, 129, 91, 22, 248, 23, 152, 3, 45, 226, 102, 38, 40, 201, 11, 3, 245, 231, 32, 40, 78, 181, 47, 247, 215, 31, 66, 132, 246, 39, 182, 138, 133, 61, 120, 199, 142, 31, 254, 147, ], - ceremony_registry, - ceremony_commands_registry, enclave_registry, ); @@ -144,22 +105,18 @@ pub mod test { let alice_key_pair = sr25519::Pair::from_string("//Alice", None).unwrap(); let signer_account = Identity::Substrate(alice_key_pair.public().into()); let ceremony_id = SignBitcoinPayload::Derived(vec![]); - let ceremony_registry = Arc::new(Mutex::new(CeremonyRegistry::::new())); - let ceremony_commands_registry = Arc::new(Mutex::new(CeremonyCommandsRegistry::new())); let enclave_registry = Arc::new(EnclaveRegistry::default()); // when let result = handle( signer_account, - ceremony_id, + &ceremony_id, [ 2, 121, 190, 102, 126, 249, 220, 187, 172, 85, 160, 98, 149, 206, 135, 11, 7, 2, 155, 252, 219, 45, 206, 40, 217, 89, 242, 129, 91, 22, 248, 23, 152, 3, 45, 226, 102, 38, 40, 201, 11, 3, 245, 231, 32, 40, 78, 181, 47, 247, 215, 31, 66, 132, 246, 39, 182, 138, 133, 61, 120, 199, 142, 31, 254, 147, ], - ceremony_registry, - ceremony_commands_registry, enclave_registry, ); diff --git a/bitacross-worker/litentry/core/direct-call/src/handler/partial_signature_share.rs b/bitacross-worker/litentry/core/direct-call/src/handler/partial_signature_share.rs index 3880aadeac..027a52f63b 100644 --- a/bitacross-worker/litentry/core/direct-call/src/handler/partial_signature_share.rs +++ b/bitacross-worker/litentry/core/direct-call/src/handler/partial_signature_share.rs @@ -14,22 +14,13 @@ // You should have received a copy of the GNU General Public License // along with Litentry. If not, see . +use crate::handler::partial_signature_share::PartialSignatureShareError::InvalidSignature; use bc_enclave_registry::EnclaveRegistryLookup; +use bc_musig2_ceremony::{CeremonyCommand, CeremonyId, PartialSignature}; use codec::Encode; use log::debug; use parentchain_primitives::Identity; -use std::{collections::HashMap, sync::Arc}; - -#[cfg(feature = "std")] -use std::sync::Mutex; - -use crate::handler::partial_signature_share::PartialSignatureShareError::{ - InvalidSignature, SignatureSaveError, -}; -use bc_musig2_ceremony::{CeremonyCommand, CeremonyId, MuSig2Ceremony, PartialSignature}; -use itp_sgx_crypto::{key_repository::AccessKey, schnorr::Pair as SchnorrPair}; -#[cfg(feature = "sgx")] -use std::sync::SgxMutex as Mutex; +use std::sync::Arc; #[derive(Encode, Debug)] pub enum PartialSignatureShareError { @@ -38,13 +29,12 @@ pub enum PartialSignatureShareError { InvalidSignature, } -pub fn handle>( +pub fn handle( signer: Identity, - ceremony_id: CeremonyId, + ceremony_id: &CeremonyId, signature: [u8; 32], - ceremony_registry: Arc>>>, enclave_registry: Arc, -) -> Result<(), PartialSignatureShareError> { +) -> Result { debug!("Received partial signature share from: {:?} for ceremony {:?}", signer, ceremony_id); let is_valid_signer = match signer { Identity::Substrate(address) => enclave_registry.contains_key(&address), @@ -53,32 +43,23 @@ pub fn handle>( if !is_valid_signer { return Err(PartialSignatureShareError::InvalidSigner) } - let mut registry = ceremony_registry.lock().map_err(|_| SignatureSaveError)?; + match signer { - Identity::Substrate(address) => - if let Some(ceremony) = registry.get_mut(&ceremony_id) { - ceremony.save_event(CeremonyCommand::SavePartialSignature( - *address.as_ref(), - PartialSignature::from_slice(&signature).map_err(|_| InvalidSignature)?, - )); - }, - _ => return Err(PartialSignatureShareError::InvalidSigner), + Identity::Substrate(address) => Ok(CeremonyCommand::SavePartialSignature( + *address.as_ref(), + PartialSignature::from_slice(&signature).map_err(|_| InvalidSignature)?, + )), + _ => Err(PartialSignatureShareError::InvalidSigner), } - - Ok(()) } #[cfg(test)] pub mod test { - - use crate::handler::partial_signature_share::{ - handle, PartialSignatureShareError, SchnorrPair, - }; + use crate::handler::partial_signature_share::{handle, PartialSignatureShareError}; use alloc::sync::Arc; use bc_enclave_registry::{EnclaveRegistry, EnclaveRegistryUpdater}; - use bc_musig2_ceremony::{CeremonyRegistry, SignBitcoinPayload}; - use codec::alloc::sync::Mutex; - use itp_sgx_crypto::{key_repository::AccessKey, Error}; + use bc_musig2_ceremony::SignBitcoinPayload; + use itp_sgx_crypto::{key_repository::AccessKey, schnorr::Pair as SchnorrPair, Error}; use parentchain_primitives::Identity; use sp_core::{sr25519, Pair}; @@ -98,7 +79,6 @@ pub mod test { let alice_key_pair = sr25519::Pair::from_string("//Alice", None).unwrap(); let signer_account = Identity::Substrate(alice_key_pair.public().into()); let ceremony_id = SignBitcoinPayload::Derived(vec![]); - let ceremony_registry = Arc::new(Mutex::new(CeremonyRegistry::::new())); let enclave_registry = Arc::new(EnclaveRegistry::default()); let _ = enclave_registry.update(alice_key_pair.public().into(), "localhost:2000".to_string()); @@ -106,12 +86,11 @@ pub mod test { // when let result = handle( signer_account, - ceremony_id, + &ceremony_id, [ 137, 19, 147, 124, 98, 243, 46, 98, 24, 93, 239, 14, 218, 117, 49, 69, 110, 245, 176, 150, 209, 28, 241, 70, 195, 172, 198, 5, 12, 146, 251, 228, ], - ceremony_registry, enclave_registry, ); @@ -125,18 +104,16 @@ pub mod test { let alice_key_pair = sr25519::Pair::from_string("//Alice", None).unwrap(); let signer_account = Identity::Substrate(alice_key_pair.public().into()); let ceremony_id = SignBitcoinPayload::Derived(vec![]); - let ceremony_registry = Arc::new(Mutex::new(CeremonyRegistry::::new())); let enclave_registry = Arc::new(EnclaveRegistry::default()); // when let result = handle( signer_account, - ceremony_id, + &ceremony_id, [ 137, 19, 147, 124, 98, 243, 46, 98, 24, 93, 239, 14, 218, 117, 49, 69, 110, 245, 176, 150, 209, 28, 241, 70, 195, 172, 198, 5, 12, 146, 251, 228, ], - ceremony_registry, enclave_registry, ); diff --git a/bitacross-worker/litentry/core/direct-call/src/handler/sign_bitcoin.rs b/bitacross-worker/litentry/core/direct-call/src/handler/sign_bitcoin.rs index c0b673b3bb..2370599387 100644 --- a/bitacross-worker/litentry/core/direct-call/src/handler/sign_bitcoin.rs +++ b/bitacross-worker/litentry/core/direct-call/src/handler/sign_bitcoin.rs @@ -14,56 +14,35 @@ // You should have received a copy of the GNU General Public License // along with Litentry. If not, see . +use bc_enclave_registry::EnclaveRegistryLookup; +use bc_musig2_ceremony::{CeremonyCommand, PublicKey, SignBitcoinPayload, SignersWithKeys}; use bc_relayer_registry::RelayerRegistryLookup; -use itp_sgx_crypto::key_repository::AccessKey; -use log::error; +use bc_signer_registry::SignerRegistryLookup; +use codec::Encode; use parentchain_primitives::Identity; use std::sync::Arc; -#[cfg(feature = "std")] -use std::sync::Mutex; - -use bc_musig2_ceremony::{ - CeremonyCommandsRegistry, CeremonyRegistry, MuSig2Ceremony, PublicKey, SignBitcoinError, - SignersWithKeys, -}; -use bc_signer_registry::SignerRegistryLookup; -use itp_sgx_crypto::schnorr::Pair as SchnorrPair; - -use bc_enclave_registry::EnclaveRegistryLookup; -use bc_musig2_ceremony::SignBitcoinPayload; -#[cfg(feature = "sgx")] -use std::sync::SgxMutex as Mutex; +#[derive(Encode, Debug)] +pub enum SignBitcoinError { + InvalidSigner, + CeremonyError, +} #[allow(clippy::too_many_arguments)] -pub fn handle< - RRL: RelayerRegistryLookup, - SR: SignerRegistryLookup, - AK: AccessKey, - ER: EnclaveRegistryLookup, ->( +pub fn handle( signer: Identity, payload: SignBitcoinPayload, aes_key: [u8; 32], - check_run: bool, relayer_registry: &RRL, - ceremony_registry: Arc>>, - ceremony_commands: Arc>, signer_registry: Arc, enclave_registry: &ER, - enclave_key_pub: &[u8; 32], - signer_access_key: Arc, -) -> Result<(), SignBitcoinError> { - //sign bitcoin can come from two trusted sources - authorized relayers or any registered enclave + check_run: bool, +) -> Result { if relayer_registry.contains_key(&signer) || match &signer { Identity::Substrate(address) => enclave_registry.contains_key(address), _ => false, } { - let mut registry = ceremony_registry.lock().map_err(|_| SignBitcoinError::CeremonyError)?; - // ~1 minute (1 tick ~ 1 ms) - let ceremony_tick_to_live = 60_000; - let signers: Result = signer_registry .get_all() .iter() @@ -74,32 +53,7 @@ pub fn handle< }) .collect(); - if registry.contains_key(&payload) { - return Err(SignBitcoinError::CeremonyError) - } - - let pending_commands = ceremony_commands - .lock() - .map_err(|_| SignBitcoinError::CeremonyError)? - .remove(&payload) - .unwrap_or_default(); - let ceremony = MuSig2Ceremony::new( - *enclave_key_pub, - aes_key, - signers?, - payload.clone(), - pending_commands.into_iter().map(|c| c.command).collect(), - signer_access_key, - ceremony_tick_to_live, - check_run, - ) - .map_err(|e| { - error!("Could not start ceremony, error: {:?}", e); - SignBitcoinError::CeremonyError - })?; - registry.insert(payload, ceremony); - - Ok(()) + Ok(CeremonyCommand::InitCeremony(aes_key, signers?, payload, check_run)) } else { Err(SignBitcoinError::InvalidSigner) } @@ -110,10 +64,9 @@ pub mod test { use crate::handler::sign_bitcoin::{handle, SignBitcoinError}; use alloc::sync::Arc; use bc_enclave_registry::{EnclaveRegistry, EnclaveRegistryUpdater}; - use bc_musig2_ceremony::{CeremonyCommandsRegistry, CeremonyRegistry, SignBitcoinPayload}; + use bc_musig2_ceremony::SignBitcoinPayload; use bc_relayer_registry::{RelayerRegistry, RelayerRegistryUpdater}; use bc_signer_registry::{PubKey, SignerRegistryLookup}; - use codec::alloc::sync::Mutex; use itp_sgx_crypto::{key_repository::AccessKey, schnorr::Pair as SchnorrPair, Error}; use parentchain_primitives::{Address32, Identity}; use sp_core::{sr25519, Pair}; @@ -170,24 +123,17 @@ pub mod test { let alice_key_pair = sr25519::Pair::from_string("//Alice", None).unwrap(); let relayer_account = Identity::Substrate(alice_key_pair.public().into()); relayer_registry.update(relayer_account.clone()).unwrap(); - let ceremony_registry = Arc::new(Mutex::new(CeremonyRegistry::new())); - let ceremony_commands_registry = Arc::new(Mutex::new(CeremonyCommandsRegistry::new())); let signers_registry = Arc::new(SignersRegistryMock {}); - let signer_access_key = Arc::new(SignerAccess {}); // when let result = handle( relayer_account, SignBitcoinPayload::Derived(vec![]), [0u8; 32], - false, &relayer_registry, - ceremony_registry, - ceremony_commands_registry, signers_registry, &enclave_registry, - &[0u8; 32], - signer_access_key, + false, ); // then @@ -198,28 +144,21 @@ pub mod test { pub fn it_should_return_ok_for_enclave_signer() { // given let relayer_registry = RelayerRegistry::default(); - let mut enclave_registry = EnclaveRegistry::default(); + let enclave_registry = EnclaveRegistry::default(); let alice_key_pair = sr25519::Pair::from_string("//Alice", None).unwrap(); let enclave_account = Identity::Substrate(alice_key_pair.public().into()); enclave_registry.update(alice_key_pair.public().into(), "".to_string()).unwrap(); - let ceremony_registry = Arc::new(Mutex::new(CeremonyRegistry::new())); - let ceremony_commands_registry = Arc::new(Mutex::new(CeremonyCommandsRegistry::new())); let signers_registry = Arc::new(SignersRegistryMock {}); - let signer_access_key = Arc::new(SignerAccess {}); // when let result = handle( enclave_account, SignBitcoinPayload::Derived(vec![]), [0u8; 32], - false, &relayer_registry, - ceremony_registry, - ceremony_commands_registry, signers_registry, &enclave_registry, - &[0u8; 32], - signer_access_key, + false, ); // then @@ -234,74 +173,20 @@ pub mod test { let alice_key_pair = sr25519::Pair::from_string("//Alice", None).unwrap(); let non_relayer_account = Identity::Substrate(alice_key_pair.public().into()); - let ceremony_registry = Arc::new(Mutex::new(CeremonyRegistry::new())); - let ceremony_commands_registry = Arc::new(Mutex::new(CeremonyCommandsRegistry::new())); let signers_registry = Arc::new(SignersRegistryMock {}); - let signer_access_key = Arc::new(SignerAccess {}); //when let result = handle( non_relayer_account, SignBitcoinPayload::Derived(vec![]), [0u8; 32], - false, &relayer_registry, - ceremony_registry, - ceremony_commands_registry, signers_registry, &enclave_registry, - &alice_key_pair.public().0, - signer_access_key, + false, ); //then assert!(matches!(result, Err(SignBitcoinError::InvalidSigner))) } - - #[test] - pub fn it_should_return_err_for_existing_ceremony() { - // given - let relayer_registry = RelayerRegistry::default(); - let enclave_registry = EnclaveRegistry::default(); - let alice_key_pair = sr25519::Pair::from_string("//Alice", None).unwrap(); - let relayer_account = Identity::Substrate(alice_key_pair.public().into()); - relayer_registry.update(relayer_account.clone()).unwrap(); - let ceremony_registry = Arc::new(Mutex::new(CeremonyRegistry::new())); - let ceremony_commands_registry = Arc::new(Mutex::new(CeremonyCommandsRegistry::new())); - let signers_registry = Arc::new(SignersRegistryMock {}); - let signer_access_key = Arc::new(SignerAccess {}); - - // when - handle( - relayer_account.clone(), - SignBitcoinPayload::Derived(vec![]), - [0u8; 32], - false, - &relayer_registry, - ceremony_registry.clone(), - ceremony_commands_registry.clone(), - signers_registry.clone(), - &enclave_registry, - &[0u8; 32], - signer_access_key.clone(), - ) - .unwrap(); - - let result = handle( - relayer_account, - SignBitcoinPayload::Derived(vec![]), - [0u8; 32], - false, - &relayer_registry, - ceremony_registry, - ceremony_commands_registry, - signers_registry, - &enclave_registry, - &[0u8; 32], - signer_access_key, - ); - - // then - assert!(matches!(result, Err(SignBitcoinError::CeremonyError))) - } } diff --git a/bitacross-worker/litentry/core/direct-call/src/lib.rs b/bitacross-worker/litentry/core/direct-call/src/lib.rs index 938b4d6c0f..522367c39d 100644 --- a/bitacross-worker/litentry/core/direct-call/src/lib.rs +++ b/bitacross-worker/litentry/core/direct-call/src/lib.rs @@ -51,9 +51,6 @@ impl DirectCallSigned { pub enum DirectCall { SignBitcoin(Identity, RequestAesKey, SignBitcoinPayload), SignEthereum(Identity, RequestAesKey, PrehashedEthereumMessage), - NonceShare(Identity, RequestAesKey, SignBitcoinPayload, [u8; 66]), - PartialSignatureShare(Identity, RequestAesKey, SignBitcoinPayload, [u8; 32]), - KillCeremony(Identity, RequestAesKey, SignBitcoinPayload), CheckSignBitcoin(Identity), } @@ -62,9 +59,6 @@ impl DirectCall { match self { Self::SignBitcoin(signer, ..) => signer, Self::SignEthereum(signer, ..) => signer, - Self::NonceShare(signer, ..) => signer, - Self::PartialSignatureShare(signer, ..) => signer, - Self::KillCeremony(signer, ..) => signer, Self::CheckSignBitcoin(signer) => signer, } } @@ -85,3 +79,52 @@ impl DirectCall { } } } + +#[derive(Encode, Decode, Clone, Debug, PartialEq, Eq)] +pub struct CeremonyRoundCallSigned { + pub call: CeremonyRoundCall, + pub signature: LitentryMultiSignature, +} + +impl CeremonyRoundCallSigned { + pub fn verify_signature(&self, mrenclave: &[u8; 32], shard: &ShardIdentifier) -> bool { + let mut payload = self.call.encode(); + payload.append(&mut mrenclave.encode()); + payload.append(&mut shard.encode()); + + self.signature.verify(blake2_256(&payload).as_slice(), self.call.signer()) + } +} + +#[derive(Encode, Decode, Clone, Debug, PartialEq, Eq)] +pub enum CeremonyRoundCall { + NonceShare(Identity, RequestAesKey, SignBitcoinPayload, [u8; 66]), + PartialSignatureShare(Identity, RequestAesKey, SignBitcoinPayload, [u8; 32]), + KillCeremony(Identity, RequestAesKey, SignBitcoinPayload), +} + +impl CeremonyRoundCall { + pub fn signer(&self) -> &Identity { + match self { + Self::NonceShare(signer, ..) => signer, + Self::PartialSignatureShare(signer, ..) => signer, + Self::KillCeremony(signer, ..) => signer, + } + } + + pub fn sign( + &self, + pair: &KeyPair, + mrenclave: &[u8; 32], + shard: &ShardIdentifier, + ) -> CeremonyRoundCallSigned { + let mut payload = self.encode(); + payload.append(&mut mrenclave.encode()); + payload.append(&mut shard.encode()); + + CeremonyRoundCallSigned { + call: self.clone(), + signature: pair.sign(blake2_256(&payload).as_slice()), + } + } +} diff --git a/bitacross-worker/service/src/cli.yml b/bitacross-worker/service/src/cli.yml index 53b1d5184d..c311123924 100644 --- a/bitacross-worker/service/src/cli.yml +++ b/bitacross-worker/service/src/cli.yml @@ -114,6 +114,18 @@ args: takes_value: true required: false default_value: "0" + - ceremony-commands-thread-count: + long: ceremony-commands-thread-count + help: Number of threads to spawn for ceremony commands handling + takes_value: true + default_value: "4" + required: false + - ceremony-events-thread-count: + long: ceremony-events-thread-count + help: Number of threads to spawn for ceremony events handling + takes_value: true + default_value: "20" + required: false subcommands: - run: diff --git a/bitacross-worker/service/src/config.rs b/bitacross-worker/service/src/config.rs index 97401027f3..92b37cc262 100644 --- a/bitacross-worker/service/src/config.rs +++ b/bitacross-worker/service/src/config.rs @@ -69,6 +69,12 @@ pub struct Config { /// the parentchain block number to start syncing with pub parentchain_start_block: String, + + /// Number of threads to spawn for ceremony commands handling + pub ceremony_commands_thread_count: u8, + + /// Number of threads to spawn for ceremony events handling + pub ceremony_events_thread_count: u8, } #[allow(clippy::too_many_arguments)] @@ -93,6 +99,8 @@ impl Config { data_dir: PathBuf, run_config: Option, parentchain_start_block: String, + ceremony_commands_thread_count: u8, + ceremony_events_thread_count: u8, ) -> Self { Self { litentry_rpc_url, @@ -114,6 +122,8 @@ impl Config { data_dir, run_config, parentchain_start_block, + ceremony_commands_thread_count, + ceremony_events_thread_count, } } @@ -249,6 +259,12 @@ impl From<&ArgMatches<'_>> for Config { let parentchain_start_block = m.value_of("parentchain-start-block").unwrap_or(DEFAULT_PARENTCHAIN_START_BLOCK); + + let ceremony_commands_thread_count = + m.value_of("ceremony-commands-thread-count").unwrap_or("4").parse().unwrap(); + let ceremony_events_thread_count = + m.value_of("ceremony-events-thread-count").unwrap_or("20").parse().unwrap(); + Self::new( m.value_of("node-url").unwrap_or(DEFAULT_NODE_URL).into(), m.value_of("node-port").unwrap_or(DEFAULT_NODE_PORT).into(), @@ -272,6 +288,8 @@ impl From<&ArgMatches<'_>> for Config { data_dir, run_config, parentchain_start_block.to_string(), + ceremony_commands_thread_count, + ceremony_events_thread_count, ) } } diff --git a/bitacross-worker/service/src/enclave/api.rs b/bitacross-worker/service/src/enclave/api.rs index 7fa49a1a9c..86030f8d8d 100644 --- a/bitacross-worker/service/src/enclave/api.rs +++ b/bitacross-worker/service/src/enclave/api.rs @@ -108,6 +108,8 @@ pub fn enclave_init(config: &Config) -> EnclaveResult { &config.mu_ra_url_external(), &config.untrusted_worker_url_external(), &config.data_dir().display().to_string(), + config.ceremony_commands_thread_count, + config.ceremony_events_thread_count, )?; Ok(enclave_api) diff --git a/bitacross-worker/service/src/tests/commons.rs b/bitacross-worker/service/src/tests/commons.rs index 068576e8d7..9f9ec63271 100644 --- a/bitacross-worker/service/src/tests/commons.rs +++ b/bitacross-worker/service/src/tests/commons.rs @@ -57,5 +57,7 @@ pub fn local_worker_config( crate::config::pwd(), None, "0".to_string(), + 5, + 10, ) } diff --git a/bitacross-worker/service/src/tests/mocks/enclave_api_mock.rs b/bitacross-worker/service/src/tests/mocks/enclave_api_mock.rs index f2b4e17b67..5b37ce280d 100644 --- a/bitacross-worker/service/src/tests/mocks/enclave_api_mock.rs +++ b/bitacross-worker/service/src/tests/mocks/enclave_api_mock.rs @@ -37,7 +37,14 @@ use sp_core::ed25519; pub struct EnclaveMock; impl EnclaveBase for EnclaveMock { - fn init(&self, _mu_ra_url: &str, _untrusted_url: &str, _base_dir: &str) -> EnclaveResult<()> { + fn init( + &self, + _mu_ra_url: &str, + _untrusted_url: &str, + _base_dir: &str, + _ceremony_commands_thread_count: u8, + _ceremony_events_thread_count: u8, + ) -> EnclaveResult<()> { Ok(()) }