From 5c98ed3e6dfbdce2a8e34df8887b6e5223f4829b Mon Sep 17 00:00:00 2001 From: JesseAbram <33698952+JesseAbram@users.noreply.github.com> Date: Fri, 26 Jul 2024 13:42:04 -0400 Subject: [PATCH] Key reshare addition (#950) * Add keyshare trigger * tests * add parent key veryifying key onchain * add next signer to chain * get party ids * get session info * execute reshare protocol * prepare refresh tests * proactive refresh test * add more info from chain * error handling * clean * clean * metadata * fix * fix test * clean * Apply suggestions from code review Co-authored-by: peg * clean * update time to 0.3.36 * lint fix * lint * lint * fix * update metadata --------- Co-authored-by: peg --- Cargo.lock | 8 +- crates/client/entropy_metadata.scale | Bin 205368 -> 205774 bytes crates/protocol/src/execute_protocol.rs | 2 +- crates/protocol/src/lib.rs | 4 +- crates/protocol/tests/helpers/mod.rs | 6 +- crates/protocol/tests/protocol.rs | 4 +- crates/shared/src/types.rs | 10 + .../src/helpers/substrate.rs | 39 ++++ .../src/helpers/tests.rs | 21 +- .../src/helpers/validator.rs | 2 +- crates/threshold-signature-server/src/lib.rs | 8 +- .../src/signing_client/api.rs | 2 +- .../src/signing_client/tests.rs | 2 +- .../src/user/tests.rs | 16 +- .../src/validator/api.rs | 188 +++++++++++++++++- .../src/validator/errors.rs | 37 +++- .../src/validator/tests.rs | 91 ++++++++- .../threshold-signature-server/tests/sign.rs | 2 +- .../tests/sign_eth_tx.rs | 2 +- node/cli/src/chain_spec/testnet.rs | 2 +- node/cli/src/service.rs | 5 + pallets/propagation/src/lib.rs | 57 +++++- pallets/propagation/src/tests.rs | 19 +- pallets/staking/src/lib.rs | 18 +- pallets/staking/src/tests.rs | 15 +- runtime/src/lib.rs | 4 +- 26 files changed, 509 insertions(+), 55 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 45be58c34..5134118e4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14096,9 +14096,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.34" +version = "0.3.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8248b6521bb14bc45b4067159b9b6ad792e2d6d754d6c41fb50e29fefe38749" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ "deranged", "itoa", @@ -14119,9 +14119,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.17" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ba3a3ef41e6672a2f0f001392bb5dcd3ff0a9992d618ca761a11c3121547774" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" dependencies = [ "num-conv", "time-core", diff --git a/crates/client/entropy_metadata.scale b/crates/client/entropy_metadata.scale index a9c157862c53c7f4d15d5c263c7eaa81de9f1861..9748b94ce140124336e156818ddb48b26d1c9399 100644 GIT binary patch delta 13483 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 for PairWrapper { } } -async fn execute_protocol_generic( +pub async fn execute_protocol_generic( mut chans: Channels, session: Session, session_id_hash: [u8; 32], diff --git a/crates/protocol/src/lib.rs b/crates/protocol/src/lib.rs index 79443ea59..2b1b77ab7 100644 --- a/crates/protocol/src/lib.rs +++ b/crates/protocol/src/lib.rs @@ -160,7 +160,7 @@ pub enum SessionId { /// A distributed key generation protocol session for registering Dkg { user: AccountId32, block_number: u32 }, /// A proactive refresh session - ProactiveRefresh { verifying_key: Vec, block_number: u32 }, + Reshare { verifying_key: Vec, block_number: u32 }, /// A signing session Sign(SigningSessionInfo), } @@ -185,7 +185,7 @@ impl Hash for SessionId { user.0.hash(state); block_number.hash(state); }, - SessionId::ProactiveRefresh { verifying_key, block_number } => { + SessionId::Reshare { verifying_key, block_number } => { verifying_key.hash(state); block_number.hash(state); }, diff --git a/crates/protocol/tests/helpers/mod.rs b/crates/protocol/tests/helpers/mod.rs index 972bac0f2..627d1c912 100644 --- a/crates/protocol/tests/helpers/mod.rs +++ b/crates/protocol/tests/helpers/mod.rs @@ -55,7 +55,7 @@ struct ServerState { #[derive(Clone)] pub enum ProtocolOutput { Sign(RecoverableSignature), - ProactiveRefresh(ThresholdKeyShare), + Reshare(ThresholdKeyShare), Dkg(KeyShareWithAuxInfo), } @@ -130,7 +130,7 @@ pub async fn server( let (signature, recovery_id) = rsig.to_backend(); Ok(ProtocolOutput::Sign(RecoverableSignature { signature, recovery_id })) }, - SessionId::ProactiveRefresh { .. } => { + SessionId::Reshare { .. } => { let new_keyshare = execute_proactive_refresh( session_id, channels, @@ -139,7 +139,7 @@ pub async fn server( threshold_keyshare.unwrap(), ) .await?; - Ok(ProtocolOutput::ProactiveRefresh(new_keyshare)) + Ok(ProtocolOutput::Reshare(new_keyshare)) }, SessionId::Dkg { .. } => { let keyshare_and_aux_info = diff --git a/crates/protocol/tests/protocol.rs b/crates/protocol/tests/protocol.rs index 79aef51ee..a2e287203 100644 --- a/crates/protocol/tests/protocol.rs +++ b/crates/protocol/tests/protocol.rs @@ -113,7 +113,7 @@ async fn test_refresh_with_parties(num_parties: usize) { let keyshares = KeyShare::::new_centralized(&mut OsRng, &ids, None); let verifying_key = keyshares[&PartyId::from(pairs[0].public())].verifying_key(); - let session_id = SessionId::ProactiveRefresh { + let session_id = SessionId::Reshare { verifying_key: verifying_key.to_encoded_point(true).as_bytes().to_vec(), block_number: 0, }; @@ -131,7 +131,7 @@ async fn test_refresh_with_parties(num_parties: usize) { .collect(); let threshold = parties.len(); let mut outputs = test_protocol_with_parties(parties, session_id, threshold).await; - if let ProtocolOutput::ProactiveRefresh(keyshare) = outputs.pop().unwrap() { + if let ProtocolOutput::Reshare(keyshare) = outputs.pop().unwrap() { assert!(keyshare.verifying_key() == verifying_key); } else { panic!("Unexpected protocol output"); diff --git a/crates/shared/src/types.rs b/crates/shared/src/types.rs index ecec165e1..9f860d031 100644 --- a/crates/shared/src/types.rs +++ b/crates/shared/src/types.rs @@ -50,6 +50,16 @@ pub struct OcwMessageDkg { pub validators_info: Vec, } +/// Offchain worker message for initiating a refresh +#[cfg(not(feature = "wasm"))] +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +#[derive(Clone, Encode, Decode, Debug, Eq, PartialEq, TypeInfo)] +pub struct OcwMessageReshare { + // Stash address of new signer + pub new_signer: Vec, + pub block_number: BlockNumber, +} + /// Offchain worker message for initiating a proactive refresh #[cfg(not(feature = "wasm"))] #[derive( diff --git a/crates/threshold-signature-server/src/helpers/substrate.rs b/crates/threshold-signature-server/src/helpers/substrate.rs index 9cb99b308..5b036275c 100644 --- a/crates/threshold-signature-server/src/helpers/substrate.rs +++ b/crates/threshold-signature-server/src/helpers/substrate.rs @@ -28,6 +28,7 @@ use crate::{ user::UserErr, }; pub use entropy_client::substrate::{query_chain, submit_transaction}; +use entropy_shared::user::ValidatorInfo; use subxt::{backend::legacy::LegacyRpcMethods, utils::AccountId32, Config, OnlineClient}; /// Given a threshold server's account ID, return its corresponding stash (validator) address. @@ -72,3 +73,41 @@ pub async fn get_registered_details( .ok_or_else(|| UserErr::ChainFetch("Not Registering error: Register Onchain first"))?; Ok(result) } + +/// Takes Stash keys and returns validator info from chain +pub async fn get_validators_info( + api: &OnlineClient, + rpc: &LegacyRpcMethods, + validators: Vec, +) -> Result, UserErr> { + let mut handles = Vec::new(); + let block_hash = rpc.chain_get_block_hash(None).await?; + for validator in validators { + let handle: tokio::task::JoinHandle> = tokio::task::spawn({ + let api = api.clone(); + let rpc = rpc.clone(); + + async move { + let threshold_address_query = + entropy::storage().staking_extension().threshold_servers(validator); + let server_info = query_chain(&api, &rpc, threshold_address_query, block_hash) + .await? + .ok_or_else(|| { + UserErr::OptionUnwrapError("Failed to unwrap validator info".to_string()) + })?; + + Ok(ValidatorInfo { + x25519_public_key: server_info.x25519_public_key, + ip_address: std::str::from_utf8(&server_info.endpoint)?.to_string(), + tss_account: server_info.tss_account, + }) + } + }); + handles.push(handle); + } + let mut all_signers: Vec = vec![]; + for handle in handles { + all_signers.push(handle.await.unwrap().unwrap()); + } + Ok(all_signers) +} diff --git a/crates/threshold-signature-server/src/helpers/tests.rs b/crates/threshold-signature-server/src/helpers/tests.rs index 8f7a9e3be..79226ee35 100644 --- a/crates/threshold-signature-server/src/helpers/tests.rs +++ b/crates/threshold-signature-server/src/helpers/tests.rs @@ -39,7 +39,7 @@ use crate::{ use axum::{routing::IntoMakeService, Router}; use entropy_kvdb::{encrypted_sled::PasswordMethod, get_db_path, kv_manager::KvManager}; use entropy_protocol::PartyId; -use entropy_shared::{DAVE_VERIFYING_KEY, EVE_VERIFYING_KEY}; +use entropy_shared::{DAVE_VERIFYING_KEY, EVE_VERIFYING_KEY, NETWORK_PARENT_KEY}; use std::time::Duration; use subxt::{ backend::legacy::LegacyRpcMethods, ext::sp_core::sr25519, tx::PairSigner, @@ -118,7 +118,7 @@ pub async fn create_clients( } /// Spawn 3 TSS nodes with pre-stored keyshares -pub async fn spawn_testing_validators() -> (Vec, Vec) { +pub async fn spawn_testing_validators(add_parent_key: bool) -> (Vec, Vec) { // spawn threshold servers let ports = [3001i64, 3002, 3003]; @@ -143,9 +143,9 @@ pub async fn spawn_testing_validators() -> (Vec, Vec) { let ids = vec![alice_id, bob_id, charlie_id]; - put_keyshares_in_db("alice", alice_kv).await; - put_keyshares_in_db("bob", bob_kv).await; - put_keyshares_in_db("charlie", charlie_kv).await; + put_keyshares_in_db("alice", alice_kv, add_parent_key).await; + put_keyshares_in_db("bob", bob_kv, add_parent_key).await; + put_keyshares_in_db("charlie", charlie_kv, add_parent_key).await; let listener_alice = tokio::net::TcpListener::bind(format!("0.0.0.0:{}", ports[0])) .await @@ -175,9 +175,12 @@ pub async fn spawn_testing_validators() -> (Vec, Vec) { } /// Add the pre-generated test keyshares to a kvdb -async fn put_keyshares_in_db(holder_name: &str, kvdb: KvManager) { - let user_names_and_verifying_keys = [("eve", EVE_VERIFYING_KEY), ("dave", DAVE_VERIFYING_KEY)]; - +async fn put_keyshares_in_db(holder_name: &str, kvdb: KvManager, add_parent_key: bool) { + let mut user_names_and_verifying_keys = + vec![("eve", hex::encode(EVE_VERIFYING_KEY)), ("dave", hex::encode(DAVE_VERIFYING_KEY))]; + if add_parent_key { + user_names_and_verifying_keys.push(("eve", hex::encode(NETWORK_PARENT_KEY))) + } for (user_name, user_verifying_key) in user_names_and_verifying_keys { let keyshare_bytes = { let project_root = @@ -188,7 +191,7 @@ async fn put_keyshares_in_db(holder_name: &str, kvdb: KvManager) { )); std::fs::read(file_path).unwrap() }; - let reservation = kvdb.kv().reserve_key(hex::encode(user_verifying_key)).await.unwrap(); + let reservation = kvdb.kv().reserve_key(user_verifying_key).await.unwrap(); kvdb.kv().put(reservation, keyshare_bytes).await.unwrap(); } } diff --git a/crates/threshold-signature-server/src/helpers/validator.rs b/crates/threshold-signature-server/src/helpers/validator.rs index 6b80ad2b0..b571e0181 100644 --- a/crates/threshold-signature-server/src/helpers/validator.rs +++ b/crates/threshold-signature-server/src/helpers/validator.rs @@ -68,7 +68,7 @@ fn get_hkdf_from_mnemonic(mnemonic: &str) -> Result, UserErr> { } /// Derive signing keypair -fn get_signer_from_hkdf( +pub fn get_signer_from_hkdf( hkdf: &Hkdf, ) -> Result, UserErr> { let mut sr25519_seed = [0u8; 32]; diff --git a/crates/threshold-signature-server/src/lib.rs b/crates/threshold-signature-server/src/lib.rs index 0c06f9693..67dfb086c 100644 --- a/crates/threshold-signature-server/src/lib.rs +++ b/crates/threshold-signature-server/src/lib.rs @@ -86,10 +86,10 @@ //! in a [crate::validation::SignedMessage]. //! //! - [`/ws`](crate::signing_client::api::ws_handler()) - Websocket server for signing and DKG protocol -//! messages. This is opened by other threshold servers when the signing procotol is initiated. +//! messages. This is opened by other threshold servers when the signing procotol is initiated. //! //! - [`/validator/sync_kvdb`](crate::validator::api::sync_kvdb()) - POST - Called by another -//! threshold server when joining to get the key-shares from a member of their sub-group. +//! threshold server when joining to get the key-shares from a member of their sub-group. //! //! Takes a list of users account IDs for which shares are requested, wrapped in a //! [crate::validation::SignedMessage]. @@ -119,7 +119,7 @@ //! //! - Axum server - Includes global state and mutex locked IPs //! - [kvdb](entropy_kvdb) - Encrypted key-value database for storing key-shares and other data, build using -//! [sled](https://docs.rs/sled) +//! [sled](https://docs.rs/sled) #![doc(html_logo_url = "https://entropy.xyz/assets/logo_02.png")] pub use entropy_client::chain_api; pub(crate) mod health; @@ -155,6 +155,7 @@ use crate::{ r#unsafe::api::{delete, put, remove_keys, unsafe_get}, signing_client::{api::*, ListenerState}, user::api::*, + validator::api::new_reshare, }; #[derive(Clone)] @@ -176,6 +177,7 @@ pub fn app(app_state: AppState) -> Router { .route("/user/new", post(new_user)) .route("/user/sign_tx", post(sign_tx)) .route("/signer/proactive_refresh", post(proactive_refresh)) + .route("/validator/reshare", post(new_reshare)) .route("/healthz", get(healthz)) .route("/version", get(get_version)) .route("/hashes", get(hashes)) diff --git a/crates/threshold-signature-server/src/signing_client/api.rs b/crates/threshold-signature-server/src/signing_client/api.rs index c30ba7f64..8c3858ef6 100644 --- a/crates/threshold-signature-server/src/signing_client/api.rs +++ b/crates/threshold-signature-server/src/signing_client/api.rs @@ -157,7 +157,7 @@ pub async fn do_proactive_refresh( tracing::debug!("Preparing to perform proactive refresh"); tracing::debug!("Signing with {:?}", &signer.signer().public()); - let session_id = SessionId::ProactiveRefresh { verifying_key, block_number }; + let session_id = SessionId::Reshare { verifying_key, block_number }; let account_id = SubxtAccountId32(signer.signer().public().0); let mut converted_validator_info = vec![]; let mut tss_accounts = vec![]; diff --git a/crates/threshold-signature-server/src/signing_client/tests.rs b/crates/threshold-signature-server/src/signing_client/tests.rs index 2bdba0861..b4a9ee4ff 100644 --- a/crates/threshold-signature-server/src/signing_client/tests.rs +++ b/crates/threshold-signature-server/src/signing_client/tests.rs @@ -45,7 +45,7 @@ async fn test_proactive_refresh() { clean_tests(); let _cxt = test_node_process_testing_state(false).await; - let (validator_ips, _ids) = spawn_testing_validators().await; + let (validator_ips, _ids) = spawn_testing_validators(false).await; let client = reqwest::Client::new(); diff --git a/crates/threshold-signature-server/src/user/tests.rs b/crates/threshold-signature-server/src/user/tests.rs index 908237e4a..ab0506e42 100644 --- a/crates/threshold-signature-server/src/user/tests.rs +++ b/crates/threshold-signature-server/src/user/tests.rs @@ -167,7 +167,7 @@ async fn test_sign_tx_no_chain() { let one = AccountKeyring::Dave; let two = AccountKeyring::Two; - let (_validator_ips, _validator_ids) = spawn_testing_validators().await; + let (_validator_ips, _validator_ids) = spawn_testing_validators(false).await; let substrate_context = test_context_stationary().await; let entropy_api = get_api(&substrate_context.node_proc.ws_url).await.unwrap(); let rpc = get_rpc(&substrate_context.node_proc.ws_url).await.unwrap(); @@ -369,7 +369,7 @@ async fn test_sign_tx_no_chain_fail() { let one = AccountKeyring::Dave; - let (_validator_ips, _validator_ids) = spawn_testing_validators().await; + let (_validator_ips, _validator_ids) = spawn_testing_validators(false).await; let substrate_context = test_context_stationary().await; let entropy_api = get_api(&substrate_context.node_proc.ws_url).await.unwrap(); let rpc = get_rpc(&substrate_context.node_proc.ws_url).await.unwrap(); @@ -493,7 +493,7 @@ async fn test_program_with_config() { let one = AccountKeyring::Dave; let two = AccountKeyring::Two; - let (_validator_ips, _validator_ids) = spawn_testing_validators().await; + let (_validator_ips, _validator_ids) = spawn_testing_validators(false).await; let substrate_context = test_context_stationary().await; let entropy_api = get_api(&substrate_context.node_proc.ws_url).await.unwrap(); let rpc = get_rpc(&substrate_context.node_proc.ws_url).await.unwrap(); @@ -559,7 +559,7 @@ async fn test_store_share() { let program_manager = AccountKeyring::Dave; let cxt = test_context_stationary().await; - let (_validator_ips, _validator_ids) = spawn_testing_validators().await; + let (_validator_ips, _validator_ids) = spawn_testing_validators(false).await; let api = get_api(&cxt.node_proc.ws_url).await.unwrap(); let rpc = get_rpc(&cxt.node_proc.ws_url).await.unwrap(); @@ -730,7 +730,7 @@ async fn test_jumpstart_network() { let alice = AccountKeyring::Alice; let cxt = test_context_stationary().await; - let (_validator_ips, _validator_ids) = spawn_testing_validators().await; + let (_validator_ips, _validator_ids) = spawn_testing_validators(false).await; let api = get_api(&cxt.node_proc.ws_url).await.unwrap(); let rpc = get_rpc(&cxt.node_proc.ws_url).await.unwrap(); @@ -927,7 +927,7 @@ async fn test_fail_infinite_program() { let one = AccountKeyring::Dave; let two = AccountKeyring::Two; - let (validator_ips, _validator_ids) = spawn_testing_validators().await; + let (validator_ips, _validator_ids) = spawn_testing_validators(false).await; let substrate_context = test_context_stationary().await; let entropy_api = get_api(&substrate_context.node_proc.ws_url).await.unwrap(); let rpc = get_rpc(&substrate_context.node_proc.ws_url).await.unwrap(); @@ -1026,7 +1026,7 @@ async fn test_device_key_proxy() { let one = AccountKeyring::Dave; - let (_validator_ips, _validator_ids) = spawn_testing_validators().await; + let (_validator_ips, _validator_ids) = spawn_testing_validators(false).await; let substrate_context = test_context_stationary().await; let entropy_api = get_api(&substrate_context.node_proc.ws_url).await.unwrap(); let rpc = get_rpc(&substrate_context.node_proc.ws_url).await.unwrap(); @@ -1134,7 +1134,7 @@ async fn test_faucet() { let two = AccountKeyring::Eve; let alice = AccountKeyring::Alice; - let (validator_ips, _validator_ids) = spawn_testing_validators().await; + let (validator_ips, _validator_ids) = spawn_testing_validators(false).await; let substrate_context = test_node_process_testing_state(true).await; let entropy_api = get_api(&substrate_context.ws_url).await.unwrap(); let rpc = get_rpc(&substrate_context.ws_url).await.unwrap(); diff --git a/crates/threshold-signature-server/src/validator/api.rs b/crates/threshold-signature-server/src/validator/api.rs index b8234e8a0..e7e6738b5 100644 --- a/crates/threshold-signature-server/src/validator/api.rs +++ b/crates/threshold-signature-server/src/validator/api.rs @@ -16,13 +16,195 @@ use crate::{ chain_api::{ entropy::{self}, - EntropyConfig, + get_api, get_rpc, EntropyConfig, }, - helpers::{launch::FORBIDDEN_KEYS, substrate::query_chain}, + get_signer_and_x25519_secret, + helpers::{ + launch::FORBIDDEN_KEYS, + substrate::{get_stash_address, get_validators_info, query_chain}, + }, + signing_client::{protocol_transport::open_protocol_connections, ProtocolErr}, validator::errors::ValidatorErr, + AppState, +}; +use axum::{body::Bytes, extract::State, http::StatusCode}; +use entropy_kvdb::kv_manager::helpers::serialize as key_serialize; +pub use entropy_protocol::{ + decode_verifying_key, + errors::ProtocolExecutionErr, + execute_protocol::{execute_protocol_generic, Channels, PairWrapper}, + KeyParams, KeyShareWithAuxInfo, Listener, PartyId, SessionId, ValidatorInfo, }; -use std::str::FromStr; +use entropy_shared::{OcwMessageReshare, NETWORK_PARENT_KEY, SETUP_TIMEOUT_SECONDS}; +use parity_scale_codec::{Decode, Encode}; +use rand_core::OsRng; +use sp_core::Pair; +use std::{collections::BTreeSet, str::FromStr, time::Duration}; use subxt::{backend::legacy::LegacyRpcMethods, utils::AccountId32, OnlineClient}; +use synedrion::{ + make_key_resharing_session, sessions::SessionId as SynedrionSessionId, KeyResharingInputs, + NewHolder, OldHolder, +}; +use tokio::time::timeout; + +/// HTTP POST endpoint called by the off-chain worker (propagation pallet) during network reshare. +/// +/// The HTTP request takes a Parity SCALE encoded [OcwMessageReshare] which indicates which validator is joining +/// +/// This will trigger the key reshare process. +#[tracing::instrument(skip_all)] +pub async fn new_reshare( + State(app_state): State, + encoded_data: Bytes, +) -> Result { + let data = OcwMessageReshare::decode(&mut encoded_data.as_ref())?; + // TODO: validate message came from chain (check reshare block # against current block number) see #941 + + let api = get_api(&app_state.configuration.endpoint).await?; + let rpc = get_rpc(&app_state.configuration.endpoint).await?; + + let signers_query = entropy::storage().staking_extension().signers(); + let signers = query_chain(&api, &rpc, signers_query, None) + .await? + .ok_or_else(|| ValidatorErr::ChainFetch("Error getting signers"))?; + + let next_signers_query = entropy::storage().staking_extension().signers(); + let next_signers = query_chain(&api, &rpc, next_signers_query, None) + .await? + .ok_or_else(|| ValidatorErr::ChainFetch("Error getting next signers"))?; + + let validators_info = get_validators_info(&api, &rpc, next_signers) + .await + .map_err(|e| ValidatorErr::UserError(e.to_string()))?; + let (signer, x25519_secret_key) = get_signer_and_x25519_secret(&app_state.kv_store) + .await + .map_err(|e| ValidatorErr::UserError(e.to_string()))?; + + let verifying_key_query = entropy::storage().registry().jump_start_progress(); + let verifying_key = query_chain(&api, &rpc, verifying_key_query, None) + .await? + .ok_or_else(|| ValidatorErr::ChainFetch("Parent verifying key error"))? + .verifying_key + .ok_or_else(|| ValidatorErr::OptionUnwrapError("Failed to get verifying key".to_string()))? + .0; + + let decoded_verifying_key = decode_verifying_key( + &verifying_key + .clone() + .try_into() + .map_err(|_| ValidatorErr::Conversion("Verifying key conversion"))?, + ) + .map_err(|e| ValidatorErr::VerifyingKeyError(e.to_string()))?; + + let is_proper_signer = &validators_info + .iter() + .any(|validator_info| validator_info.tss_account == *signer.account_id()); + + if !is_proper_signer { + return Ok(StatusCode::MISDIRECTED_REQUEST); + } + // get old key if have it + let my_stash_address = get_stash_address(&api, &rpc, signer.account_id()) + .await + .map_err(|e| ValidatorErr::UserError(e.to_string()))?; + let old_holder: Option> = + if data.new_signer == my_stash_address.encode() { + None + } else { + let kvdb_result = app_state.kv_store.kv().get(&hex::encode(NETWORK_PARENT_KEY)).await?; + let key_share: KeyShareWithAuxInfo = + entropy_kvdb::kv_manager::helpers::deserialize(&kvdb_result) + .ok_or_else(|| ValidatorErr::KvDeserialize("Failed to load KeyShare".into()))?; + Some(OldHolder { key_share: key_share.0 }) + }; + let party_ids: BTreeSet = + validators_info.iter().cloned().map(|x| PartyId::new(x.tss_account)).collect(); + + let old_holders_info = get_validators_info(&api, &rpc, signers) + .await + .map_err(|e| ValidatorErr::UserError(e.to_string()))?; + let old_holders: BTreeSet = + old_holders_info.iter().cloned().map(|x| PartyId::new(x.tss_account)).collect(); + + let new_holder = NewHolder { + verifying_key: decoded_verifying_key, + // TODO: get from chain see #941 + old_threshold: party_ids.len(), + old_holders, + }; + let key_info_query = entropy::storage().parameters().signers_info(); + let threshold = query_chain(&api, &rpc, key_info_query, None) + .await? + .ok_or_else(|| ValidatorErr::ChainFetch("Failed to get signers info"))? + .threshold; + + let inputs = KeyResharingInputs { + old_holder, + new_holder: Some(new_holder), + new_holders: party_ids.clone(), + new_threshold: threshold as usize, + }; + + let session_id = SessionId::Reshare { verifying_key, block_number: data.block_number }; + let account_id = AccountId32(signer.signer().public().0); + let session_id_hash = session_id.blake2(None)?; + let pair = PairWrapper(signer.signer().clone()); + + let mut converted_validator_info = vec![]; + let mut tss_accounts = vec![]; + for validator_info in validators_info { + let validator_info = ValidatorInfo { + x25519_public_key: validator_info.x25519_public_key, + ip_address: validator_info.ip_address, + tss_account: validator_info.tss_account.clone(), + }; + converted_validator_info.push(validator_info.clone()); + tss_accounts.push(validator_info.tss_account.clone()); + } + + let (rx_ready, rx_from_others, listener) = + Listener::new(converted_validator_info.clone(), &account_id); + app_state + .listener_state + .listeners + .lock() + .map_err(|_| ValidatorErr::SessionError("Error getting lock".to_string()))? + .insert(session_id.clone(), listener); + + open_protocol_connections( + &converted_validator_info, + &session_id, + signer.signer(), + &app_state.listener_state, + &x25519_secret_key, + ) + .await?; + + let channels = { + let ready = timeout(Duration::from_secs(SETUP_TIMEOUT_SECONDS), rx_ready).await?; + let broadcast_out = ready??; + Channels(broadcast_out, rx_from_others) + }; + + let session = make_key_resharing_session( + &mut OsRng, + SynedrionSessionId::from_seed(session_id_hash.as_slice()), + pair, + &party_ids, + inputs, + ) + .map_err(ProtocolExecutionErr::SessionCreation)?; + + let new_key_share = execute_protocol_generic(channels, session, session_id_hash) + .await + .map_err(|_| ValidatorErr::ProtocolError("Error executing protocol".to_string()))? + .0 + .ok_or(ValidatorErr::NoOutputFromReshareProtocol)?; + let _serialized_key_share = key_serialize(&new_key_share) + .map_err(|_| ProtocolErr::KvSerialize("Kv Serialize Error".to_string()))?; + // TODO: do reshare call confirm_reshare (delete key when done) see #941 + Ok(StatusCode::OK) +} /// Validation for if an account can cover tx fees for a tx pub async fn check_balance_for_fees( diff --git a/crates/threshold-signature-server/src/validator/errors.rs b/crates/threshold-signature-server/src/validator/errors.rs index bb1b075a8..6b8c7dae6 100644 --- a/crates/threshold-signature-server/src/validator/errors.rs +++ b/crates/threshold-signature-server/src/validator/errors.rs @@ -15,12 +15,15 @@ use std::string::FromUtf8Error; +use crate::signing_client::ProtocolErr; use axum::{ http::StatusCode, response::{IntoResponse, Response}, }; -use entropy_protocol::sign_and_encrypt::EncryptedSignedMessageErr; +use entropy_protocol::{errors::ProtocolExecutionErr, sign_and_encrypt::EncryptedSignedMessageErr}; +use synedrion::sessions; use thiserror::Error; +use tokio::sync::oneshot::error::RecvError; #[derive(Debug, Error)] pub enum ValidatorErr { @@ -52,6 +55,38 @@ pub enum ValidatorErr { Authentication, #[error("Substrate: {0}")] SubstrateClient(#[from] entropy_client::substrate::SubstrateError), + #[error("Codec decoding error: {0}")] + CodecError(#[from] parity_scale_codec::Error), + #[error("User Error: {0}")] + UserError(String), + #[error("Option Unwrap error: {0}")] + OptionUnwrapError(String), + #[error("Vec Conversion Error: {0}")] + Conversion(&'static str), + #[error("Verifying key Error: {0}")] + VerifyingKeyError(String), + #[error("Session Error: {0}")] + SessionError(String), + #[error("Protocol Execution Error {0}")] + ProtocolExecution(#[from] ProtocolExecutionErr), + #[error("Listener: {0}")] + Listener(#[from] entropy_protocol::errors::ListenerErr), + #[error("Reshare protocol error: {0}")] + SigningClientError(#[from] ProtocolErr), + #[error("Timed out waiting for remote party")] + Timeout(#[from] tokio::time::error::Elapsed), + #[error("Oneshot timeout error: {0}")] + OneshotTimeout(#[from] RecvError), + #[error("Synedrion session creation error: {0}")] + SessionCreation(sessions::LocalError), + #[error("No output from reshare protocol")] + NoOutputFromReshareProtocol, + #[error("Protocol Error: {0}")] + ProtocolError(String), + #[error("Kv Fatal error")] + KvSerialize(String), + #[error("Kv Deserialization Error: {0}")] + KvDeserialize(String), } impl IntoResponse for ValidatorErr { diff --git a/crates/threshold-signature-server/src/validator/tests.rs b/crates/threshold-signature-server/src/validator/tests.rs index 6f2090642..cc4bb3efd 100644 --- a/crates/threshold-signature-server/src/validator/tests.rs +++ b/crates/threshold-signature-server/src/validator/tests.rs @@ -12,19 +12,96 @@ // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -use entropy_shared::MIN_BALANCE; +use super::api::{check_balance_for_fees, check_forbidden_key}; +use crate::{ + chain_api::{ + entropy::{self, runtime_types::bounded_collections::bounded_vec}, + get_api, get_rpc, EntropyConfig, + }, + helpers::{ + launch::{development_mnemonic, ValidatorName, FORBIDDEN_KEYS}, + substrate::submit_transaction, + tests::initialize_test_logger, + validator::get_signer_and_x25519_secret_from_mnemonic, + }, + validator::errors::ValidatorErr, +}; +use entropy_kvdb::clean_tests; +use entropy_shared::{OcwMessageReshare, EVE_VERIFYING_KEY, MIN_BALANCE}; use entropy_testing_utils::{ constants::{ALICE_STASH_ADDRESS, RANDOM_ACCOUNT}, + spawn_testing_validators, substrate_context::testing_context, + test_context_stationary, }; - -use super::api::{check_balance_for_fees, check_forbidden_key}; -use crate::{ - chain_api::{get_api, get_rpc}, - helpers::{launch::FORBIDDEN_KEYS, tests::initialize_test_logger}, - validator::errors::ValidatorErr, +use futures::future::join_all; +use parity_scale_codec::Encode; +use serial_test::serial; +use sp_keyring::AccountKeyring; +use subxt::{ + backend::legacy::LegacyRpcMethods, ext::sp_core::sr25519, tx::PairSigner, OnlineClient, }; +#[tokio::test] +#[serial] +async fn test_reshare() { + initialize_test_logger().await; + clean_tests(); + + let alice = AccountKeyring::Alice; + + let cxt = test_context_stationary().await; + let (_validator_ips, _validator_ids) = spawn_testing_validators(true).await; + let api = get_api(&cxt.node_proc.ws_url).await.unwrap(); + let rpc = get_rpc(&cxt.node_proc.ws_url).await.unwrap(); + + let client = reqwest::Client::new(); + let block_number = rpc.chain_get_header(None).await.unwrap().unwrap().number + 1; + + let onchain_reshare_request = + OcwMessageReshare { new_signer: alice.public().encode(), block_number }; + setup_for_reshare(&api, &rpc).await; + + let response_results = join_all( + vec![3001, 3002, 3003] + .iter() + .map(|port| { + client + .post(format!("http://127.0.0.1:{}/validator/reshare", port)) + .body(onchain_reshare_request.clone().encode()) + .send() + }) + .collect::>(), + ) + .await; + for response_result in response_results { + assert_eq!(response_result.unwrap().text().await.unwrap(), ""); + } + clean_tests(); +} + +async fn setup_for_reshare( + api: &OnlineClient, + rpc: &LegacyRpcMethods, +) { + let alice = AccountKeyring::Alice; + let signer = PairSigner::::new(alice.clone().into()); + + let jump_start_request = entropy::tx().registry().jump_start_network(); + let _result = submit_transaction(api, rpc, &signer, &jump_start_request, None).await.unwrap(); + + let validators_names = vec![ValidatorName::Alice, ValidatorName::Bob]; + for validator_name in validators_names { + let mnemonic = development_mnemonic(&Some(validator_name)); + let (tss_signer, _static_secret) = + get_signer_and_x25519_secret_from_mnemonic(&mnemonic.to_string()).unwrap(); + let jump_start_confirm_request = entropy::tx() + .registry() + .confirm_jump_start(bounded_vec::BoundedVec(EVE_VERIFYING_KEY.to_vec())); + + submit_transaction(api, rpc, &tss_signer, &jump_start_confirm_request, None).await.unwrap(); + } +} #[tokio::test] #[should_panic = "Account does not exist, add balance"] async fn test_check_balance_for_fees() { diff --git a/crates/threshold-signature-server/tests/sign.rs b/crates/threshold-signature-server/tests/sign.rs index 8acc5295d..9ad3cd48b 100644 --- a/crates/threshold-signature-server/tests/sign.rs +++ b/crates/threshold-signature-server/tests/sign.rs @@ -40,7 +40,7 @@ async fn integration_test_sign_public() { let eve = AccountKeyring::Eve; let request_author = AccountKeyring::One; - let (_validator_ips, _validator_ids) = spawn_testing_validators().await; + let (_validator_ips, _validator_ids) = spawn_testing_validators(false).await; let substrate_context = test_context_stationary().await; let api = get_api(&substrate_context.node_proc.ws_url).await.unwrap(); diff --git a/crates/threshold-signature-server/tests/sign_eth_tx.rs b/crates/threshold-signature-server/tests/sign_eth_tx.rs index 8991bb953..c957f5148 100644 --- a/crates/threshold-signature-server/tests/sign_eth_tx.rs +++ b/crates/threshold-signature-server/tests/sign_eth_tx.rs @@ -48,7 +48,7 @@ async fn integration_test_sign_eth_tx() { clean_tests(); let pre_registered_user = AccountKeyring::Eve; - let (_validator_ips, _validator_ids) = spawn_testing_validators().await; + let (_validator_ips, _validator_ids) = spawn_testing_validators(false).await; let substrate_context = test_context_stationary().await; let api = get_api(&substrate_context.node_proc.ws_url).await.unwrap(); let rpc = get_rpc(&substrate_context.node_proc.ws_url).await.unwrap(); diff --git a/node/cli/src/chain_spec/testnet.rs b/node/cli/src/chain_spec/testnet.rs index 9824db2d4..c304a555d 100644 --- a/node/cli/src/chain_spec/testnet.rs +++ b/node/cli/src/chain_spec/testnet.rs @@ -202,7 +202,7 @@ pub fn testnet_local_initial_tss_servers() -> Vec<(TssAccountId, TssX25519Public /// However, this can be done by: /// - First, spinning up the machines you expect to be running at genesis /// - Then, running each TSS server with the `--setup-only` flag to get the `TssAccountId` and -/// `TssX25519PublicKey` +/// `TssX25519PublicKey` /// - Finally, writing all that information back here, and generating the chainspec from that. /// /// Note that if the KVDB of the TSS is deleted at any point during this process you will end up diff --git a/node/cli/src/service.rs b/node/cli/src/service.rs index a26bb59c0..bf5a28bbd 100644 --- a/node/cli/src/service.rs +++ b/node/cli/src/service.rs @@ -367,6 +367,11 @@ pub fn new_full_base( b"refresh", &format!("{}/signer/proactive_refresh", endpoint).into_bytes(), ); + offchain_db.local_storage_set( + sp_core::offchain::StorageKind::PERSISTENT, + b"reshare_validators", + &format!("{}/validator/reshare", endpoint).into_bytes(), + ); log::info!("Threshold Signing Sever (TSS) location changed to {}", endpoint); } } diff --git a/pallets/propagation/src/lib.rs b/pallets/propagation/src/lib.rs index 31260c546..99062ca39 100644 --- a/pallets/propagation/src/lib.rs +++ b/pallets/propagation/src/lib.rs @@ -30,7 +30,9 @@ mod tests; #[frame_support::pallet] pub mod pallet { use codec::Encode; - use entropy_shared::{OcwMessageDkg, OcwMessageProactiveRefresh, ValidatorInfo}; + use entropy_shared::{ + OcwMessageDkg, OcwMessageProactiveRefresh, OcwMessageReshare, ValidatorInfo, + }; use frame_support::{pallet_prelude::*, sp_runtime::traits::Saturating}; use frame_system::pallet_prelude::*; use sp_runtime::{ @@ -56,6 +58,7 @@ pub mod pallet { impl Hooks> for Pallet { fn offchain_worker(block_number: BlockNumberFor) { let _ = Self::post_dkg(block_number); + let _ = Self::post_reshare(block_number); let _ = Self::post_user_registration(block_number); let _ = Self::post_proactive_refresh(block_number); } @@ -77,6 +80,10 @@ pub mod pallet { /// Proactive Refresh Message passed to validators /// parameters. [OcwMessageProactiveRefresh] ProactiveRefreshMessagePassed(OcwMessageProactiveRefresh), + + /// Proactive Refresh Message passed to validators + /// parameters. [OcwMessageReshare] + KeyReshareMessagePassed(OcwMessageReshare), } #[pallet::call] @@ -201,6 +208,54 @@ pub mod pallet { Ok(()) } + /// Submits a request to do a key refresh on the signers parent key. + pub fn post_reshare(block_number: BlockNumberFor) -> Result<(), http::Error> { + let reshare_data = pallet_staking_extension::Pallet::::reshare_data(); + if reshare_data.block_number != block_number { + return Ok(()); + } + + let deadline = sp_io::offchain::timestamp().add(Duration::from_millis(2_000)); + let kind = sp_core::offchain::StorageKind::PERSISTENT; + let from_local = sp_io::offchain::local_storage_get(kind, b"reshare_validators") + .unwrap_or_else(|| b"http://localhost:3001/validator/reshare".to_vec()); + let url = + str::from_utf8(&from_local).unwrap_or("http://localhost:3001/validator/reshare"); + let converted_block_number: u32 = + BlockNumberFor::::try_into(block_number).unwrap_or_default(); + + let req_body = OcwMessageReshare { + new_signer: reshare_data.new_signer, + // subtract 1 from blocknumber since the request is from the last block + block_number: converted_block_number.saturating_sub(1), + }; + + log::warn!("propagation::post::req_body reshare: {:?}", &[req_body.encode()]); + + // We construct the request + // important: the header->Content-Type must be added and match that of the receiving + // party!! + let pending = http::Request::post(url, vec![req_body.encode()]) + .deadline(deadline) + .send() + .map_err(|_| http::Error::IoError)?; + + // We await response, same as in fn get() + let response = + pending.try_wait(deadline).map_err(|_| http::Error::DeadlineReached)??; + + // check response code + if response.code != 200 { + log::warn!("Unexpected status code: {}", response.code); + return Err(http::Error::Unknown); + } + let _res_body = response.body().collect::>(); + + Self::deposit_event(Event::KeyReshareMessagePassed(req_body)); + + Ok(()) + } + /// Submits a request to perform a proactive refresh to the threshold servers. pub fn post_proactive_refresh(block_number: BlockNumberFor) -> Result<(), http::Error> { let refresh_info = pallet_staking_extension::Pallet::::proactive_refresh(); diff --git a/pallets/propagation/src/tests.rs b/pallets/propagation/src/tests.rs index 0f0bffa5f..b3e411e11 100644 --- a/pallets/propagation/src/tests.rs +++ b/pallets/propagation/src/tests.rs @@ -20,7 +20,7 @@ use entropy_shared::ValidatorInfo; use frame_support::{assert_ok, traits::OnInitialize, BoundedVec}; use pallet_programs::ProgramInfo; use pallet_registry::ProgramInstance; -use pallet_staking_extension::RefreshInfo; +use pallet_staking_extension::{RefreshInfo, ReshareInfo}; use sp_core::offchain::{testing, OffchainDbExt, OffchainWorkerExt, TransactionPoolExt}; use sp_io::TestExternalities; use sp_keystore::{testing::MemoryKeystore, KeystoreExt}; @@ -73,6 +73,14 @@ fn knows_how_to_mock_several_http_calls() { .to_vec(), ..Default::default() }); + state.expect_request(testing::PendingRequest { + method: "POST".into(), + uri: "http://localhost:3001/validator/reshare".into(), + sent: true, + response: Some([].to_vec()), + body: [32, 1, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0].to_vec(), + ..Default::default() + }); }); t.execute_with(|| { @@ -120,6 +128,15 @@ fn knows_how_to_mock_several_http_calls() { Propagation::post_proactive_refresh(6).unwrap(); Propagation::on_initialize(6); assert_eq!(Staking::proactive_refresh(), RefreshInfo::default()); + + // doesn't trigger no reshare block + Propagation::post_reshare(7).unwrap(); + pallet_staking_extension::ReshareData::::put(ReshareInfo { + block_number: 7, + new_signer: 1u64.encode(), + }); + // now triggers + Propagation::post_reshare(7).unwrap(); }) } diff --git a/pallets/staking/src/lib.rs b/pallets/staking/src/lib.rs index 85f3073a9..8496a3b31 100644 --- a/pallets/staking/src/lib.rs +++ b/pallets/staking/src/lib.rs @@ -114,6 +114,11 @@ pub mod pallet { pub proactive_refresh_keys: Vec>, } + #[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, TypeInfo, Default)] + pub struct ReshareInfo { + pub new_signer: Vec, + pub block_number: BlockNumber, + } #[pallet::pallet] #[pallet::without_storage_info] pub struct Pallet(_); @@ -170,6 +175,11 @@ pub mod pallet { #[pallet::getter(fn next_signers)] pub type NextSigners = StorageValue<_, Vec, ValueQuery>; + /// The next time a reshare should happen + #[pallet::storage] + #[pallet::getter(fn reshare_data)] + pub type ReshareData = StorageValue<_, ReshareInfo>, ValueQuery>; + /// A type used to simplify the genesis configuration definition. pub type ThresholdServersConfig = ( ::ValidatorId, @@ -437,9 +447,15 @@ pub mod pallet { current_signers.remove(0); current_signers.push(next_signer_up.clone()); NextSigners::::put(current_signers); + // trigger reshare at next block + let current_block_number = >::block_number(); + let reshare_info = ReshareInfo { + block_number: current_block_number + sp_runtime::traits::One::one(), + new_signer: next_signer_up.encode(), + }; + ReshareData::::put(reshare_info); // for next PR - // tell signers to do new key rotation with new signer group (dkg) // confirm action has taken place Ok(()) diff --git a/pallets/staking/src/tests.rs b/pallets/staking/src/tests.rs index b0142bdda..eb517982c 100644 --- a/pallets/staking/src/tests.rs +++ b/pallets/staking/src/tests.rs @@ -14,10 +14,10 @@ // along with this program. If not, see . use crate::{mock::*, tests::RuntimeEvent, Error, IsValidatorSynced, ServerInfo, ThresholdToStash}; +use codec::Encode; use frame_support::{assert_noop, assert_ok}; use frame_system::{EventRecord, Phase}; use pallet_session::SessionManager; - const NULL_ARR: [u8; 32] = [0; 32]; #[test] @@ -331,11 +331,24 @@ fn it_tests_new_session_handler() { // no next signers at start assert_eq!(Staking::next_signers().len(), 0); + assert_eq!(Staking::reshare_data().block_number, 0, "Check reshare block start at zero"); + System::set_block_number(100); assert_ok!(Staking::new_session_handler(&[1, 2, 3])); // takes signers original (5,6) pops off first 5, adds (fake randomness in mock so adds 1) assert_eq!(Staking::next_signers(), vec![6, 1]); + assert_eq!( + Staking::reshare_data().block_number, + 101, + "Check reshare block start at 100 + 1" + ); + assert_eq!( + Staking::reshare_data().new_signer, + 1u64.encode(), + "Check reshare next signer up is 1" + ); + assert_ok!(Staking::new_session_handler(&[6, 5, 3])); // takes 3 and leaves 5 and 6 since already in signer group assert_eq!(Staking::next_signers(), vec![6, 3]); diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 53104c22a..ad26f4020 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -1384,8 +1384,8 @@ impl pallet_transaction_storage::Config for Runtime { parameter_types! { pub const BagThresholds: &'static [u64] = &voter_bags::THRESHOLDS; } -type VoterBagsListInstance = pallet_bags_list::Instance1; -impl pallet_bags_list::Config for Runtime { + +impl pallet_bags_list::Config for Runtime { type BagThresholds = BagThresholds; type RuntimeEvent = RuntimeEvent; type Score = VoteWeight;