diff --git a/Cargo.lock b/Cargo.lock index bdcb8c61b..46dfc0fcc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12319,14 +12319,22 @@ checksum = "3369f5ac52d5eb6ab48c6b4ffdc8efbcad6b89c765749064ba298f2c68a16a76" name = "testing-utils" version = "0.0.8" dependencies = [ + "axum", + "entropy-protocol", "entropy-shared", "hex-literal", + "kvdb 0.1.0", "lazy_static", "parity-scale-codec", "project-root", + "rand_core 0.6.4", + "server", "sp-core 21.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "sp-keyring 24.0.0 (registry+https://github.com/rust-lang/crates.io-index)", "subxt", + "subxt-signer", + "synedrion", + "tokio", "tracing", "tracing-subscriber 0.3.17", "which", diff --git a/crypto/server/src/common.rs b/crypto/server/src/common.rs new file mode 100644 index 000000000..e3dedf125 --- /dev/null +++ b/crypto/server/src/common.rs @@ -0,0 +1,6 @@ +//! Re-exports of things needed for a client for integration tests +pub use crate::{ + helpers::signing::{create_unique_tx_id, Hasher}, + user::api::UserSignatureRequest, + validation, +}; diff --git a/crypto/server/src/helpers/launch.rs b/crypto/server/src/helpers/launch.rs index e0f33c447..4a231abe5 100644 --- a/crypto/server/src/helpers/launch.rs +++ b/crypto/server/src/helpers/launch.rs @@ -54,7 +54,7 @@ pub struct Configuration { } impl Configuration { - pub(crate) fn new(endpoint: String) -> Configuration { + pub fn new(endpoint: String) -> Configuration { Configuration { endpoint } } } diff --git a/crypto/server/src/lib.rs b/crypto/server/src/lib.rs new file mode 100644 index 000000000..8046dd89a --- /dev/null +++ b/crypto/server/src/lib.rs @@ -0,0 +1,184 @@ +//! # Server +//! +//! The Threshold Server which stores key shares and participates in the signing protocol. +//! +//! ## Overview +//! +//! This exposes a HTTP API. +//! +//! ## The HTTP endpoints +//! +//! Some endpoints are designed to be called by the user, some by the entropy chain node, +//! and some by other instances of `server`: +//! +//! ### For the user +//! +//! Most user-facing endpoints take a [SignedMessage](crate::validation::SignedMessage) which +//! is an encrypted, signed message. +//! +//! +//! #### `/user/sign_tx` - POST +//! +//! [crate::user::api::sign_tx()] +//! +//! Called by a user to submit a transaction to sign (the new way of doing signing). Takes a +//! [`UserTransactionRequest`](crate::user::api::UserTransactionRequest) encryted in a +//! `SignedMessage`. +//! +//! The response is chunked response stream. If the `UserTransactionRequest` could be processed, a +//! success response header is sent. Then the signing protocol runs. When the it finishes, a single +//! message will be sent on the response stream with the result. +//! +//! If everything went well, the message will be a JSON object with a signle property "Ok" +//! containing a base64 encoded signature, for example: +//! +//! `{"Ok":"BnJRjRUw9+trW36bK7S2KglY+TG5rGn1e3FKQlJYvx+jai7wG5Z0BWPFGYPxAwB5yROUOnucuzXoG7TrI7QNIAE="}` +//! +//! Otherwise, the message will be a JSON object with a signle property "Err" containing an error +//! message, for example: +//! +//! `{"Err":"reqwest event error: Invalid status code: 500 Internal Server Error"}` +//! +//! Curl example for `user/sign_tx`: +//! ```text +//! curl -X POST -H "Content-Type: application/json" \ +//! -d '{"msg" "0x174...hex encoded signedmessage...","sig":"821754409744cbb878b44bd1e3dc575a4ea721e12d781b074fcdb808fc79fd33dd1928b1a281c0b6261a30536a7c0106a102f27dad1bc3ef475b626f0e57c983","pk":[172,133,159,138,33,110,235,27,50,11,76,118,209,24,218,61,116,7,250,82,52,132,208,169,128,18,109,59,77,13,34,10],"recip":[10,192,41,240,184,83,178,59,237,101,45,109,13,230,155,124,195,141,148,249,55,50,238,252,133,181,134,30,144,247,58,34],"a":[169,94,23,7,19,184,134,70,233,117,2,84,242,135,246,95,159,14,218,125,209,191,175,89,41,196,182,96,117,5,159,98],"nonce":[114,93,158,35,209,188,96,248,85,131,95,237]}' \ +//! -H "Accept: application/json" \ +//! http://127.0.0.1:3001/user/sign_tx +//! ``` +//! +//! ### For the blockchain node +//! +//! #### `/user/new` - POST +//! +//! [crate::user::api::new_user()] +//! +//! Called by the off-chain worker (propagation pallet) during user registration. +//! This takes a parity scale encoded [entropy_shared::types::OcwMessageDkg] which tells us which +//! validators are in the registration group and will perform a DKG. +//! +//! ### For other instances of the threshold server +//! +//! - [`/user/receive_key`](receive_key) - recieve a keyshare from another threshold server in the +//! same signing subgroup. Takes a [UserRegistrationInfo] wrapped in a +//! [crate::validation::SignedMessage]. +//! - [`/ws`](crate::signing_client::api::ws_handler()) - Websocket server for signing protocol +//! 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. +//! +//! ### For testing / development +//! +//! [Unsafe](crate::unsafe::api) has additional routes which are for testing and development +//! purposes only and will not be used in production. These routes are only available if this crate +//! is compiled with the `unsafe` feature enabled. +//! +//! - [`unsafe/get`](crate::unsafe::api::unsafe_get()) - POST - get a value from the key-value +//! store, given its key. +//! - [`unsafe/put`](crate::unsafe::api::put()) - POST - update an existing value in the key-value +//! store. +//! - [`unsafe/delete`](crate::unsafe::api::delete()) - POST - remove a value from the key-value +//! store, given its key. +//! - [`unsafe/remove_keys`](crate::unsafe::api::remove_keys()) - GET - remove everything from the +//! key-value store. +//! +//! ## Pieces Launched +//! +//! - Axum server - Includes global state and mutex locked IPs +//! - [kvdb](kvdb) - Encrypted key-value database for storing key-shares and other data, build using +//! [sled](https://docs.rs/sled) +#![doc(html_logo_url = "https://entropy.xyz/assets/logo_02.png")] +pub mod chain_api; +pub mod common; +pub(crate) mod health; +pub(crate) mod helpers; +pub(crate) mod sign_init; +pub(crate) mod signing_client; +pub(crate) mod r#unsafe; +pub(crate) mod user; +pub mod validation; +pub(crate) mod validator; +pub(crate) mod version; + +use axum::{ + http::Method, + routing::{get, post}, + Router, +}; +use kvdb::kv_manager::KvManager; +use tower_http::{ + cors::{Any, CorsLayer}, + trace::{self, TraceLayer}, +}; +use tracing::Level; +use validator::api::get_random_server_info; + +use crate::{ + health::api::healthz, + launch::Configuration, + r#unsafe::api::{delete, put, remove_keys, unsafe_get}, + signing_client::{api::*, ListenerState}, + user::api::*, + validator::api::sync_kvdb, + version::api::version as get_version, +}; +pub use crate::{ + helpers::{launch, validator::get_signer}, + validator::api::sync_validator, +}; + +#[derive(Clone)] +pub struct AppState { + listener_state: ListenerState, + pub configuration: Configuration, + pub kv_store: KvManager, +} + +impl AppState { + pub fn new(configuration: Configuration, kv_store: KvManager) -> Self { + Self { listener_state: ListenerState::default(), configuration, kv_store } + } +} + +pub fn app(app_state: AppState) -> Router { + let mut routes = Router::new() + .route("/user/sign_tx", post(sign_tx)) + .route("/user/new", post(new_user)) + .route("/user/receive_key", post(receive_key)) + .route("/signer/proactive_refresh", post(proactive_refresh)) + .route("/validator/sync_kvdb", post(sync_kvdb)) + .route("/healthz", get(healthz)) + .route("/version", get(get_version)) + .route("/ws", get(ws_handler)); + + // Unsafe routes are for testing purposes only + // they are unsafe as they can expose vulnerabilites + // should they be used in production. Unsafe routes + // are disabled by default. + // To enable unsafe routes compile with --feature unsafe. + if cfg!(feature = "unsafe") || cfg!(test) { + tracing::warn!("Server started in unsafe mode do not use in production!!!!!!!"); + routes = routes + .route("/unsafe/put", post(put)) + .route("/unsafe/get", post(unsafe_get)) + .route("/unsafe/delete", post(delete)) + .route("/unsafe/remove_keys", get(remove_keys)); + } + + routes + .with_state(app_state) + .layer( + TraceLayer::new_for_http() + .make_span_with(|request: &axum::http::Request| { + tracing::info_span!( + "http-request", + uuid = %uuid::Uuid::new_v4(), + uri = %request.uri(), + method = %request.method(), + ) + }) + .on_request(trace::DefaultOnRequest::new().level(Level::INFO)) + .on_response(trace::DefaultOnResponse::new().level(Level::INFO)), + ) + .layer(CorsLayer::new().allow_origin(Any).allow_methods([Method::GET, Method::POST])) +} diff --git a/crypto/server/src/main.rs b/crypto/server/src/main.rs index 38fa9a2bf..c96c57960 100644 --- a/crypto/server/src/main.rs +++ b/crypto/server/src/main.rs @@ -1,144 +1,16 @@ -//! # Server -//! -//! The Threshold Server which stores key shares and participates in the signing protocol. -//! -//! ## Overview -//! -//! This exposes a HTTP API. -//! -//! ## The HTTP endpoints -//! -//! Some endpoints are designed to be called by the user, some by the entropy chain node, -//! and some by other instances of `server`: -//! -//! ### For the user -//! -//! Most user-facing endpoints take a [SignedMessage](crate::validation::SignedMessage) which -//! is an encrypted, signed message. -//! -//! -//! #### `/user/sign_tx` - POST -//! -//! [crate::user::api::sign_tx()] -//! -//! Called by a user to submit a transaction to sign (the new way of doing signing). Takes a -//! [`UserTransactionRequest`](crate::user::api::UserTransactionRequest) encryted in a -//! `SignedMessage`. -//! -//! The response is chunked response stream. If the `UserTransactionRequest` could be processed, a -//! success response header is sent. Then the signing protocol runs. When the it finishes, a single -//! message will be sent on the response stream with the result. -//! -//! If everything went well, the message will be a JSON object with a signle property "Ok" -//! containing a base64 encoded signature, for example: -//! -//! `{"Ok":"BnJRjRUw9+trW36bK7S2KglY+TG5rGn1e3FKQlJYvx+jai7wG5Z0BWPFGYPxAwB5yROUOnucuzXoG7TrI7QNIAE="}` -//! -//! Otherwise, the message will be a JSON object with a signle property "Err" containing an error -//! message, for example: -//! -//! `{"Err":"reqwest event error: Invalid status code: 500 Internal Server Error"}` -//! -//! Curl example for `user/sign_tx`: -//! ```text -//! curl -X POST -H "Content-Type: application/json" \ -//! -d '{"msg" "0x174...hex encoded signedmessage...","sig":"821754409744cbb878b44bd1e3dc575a4ea721e12d781b074fcdb808fc79fd33dd1928b1a281c0b6261a30536a7c0106a102f27dad1bc3ef475b626f0e57c983","pk":[172,133,159,138,33,110,235,27,50,11,76,118,209,24,218,61,116,7,250,82,52,132,208,169,128,18,109,59,77,13,34,10],"recip":[10,192,41,240,184,83,178,59,237,101,45,109,13,230,155,124,195,141,148,249,55,50,238,252,133,181,134,30,144,247,58,34],"a":[169,94,23,7,19,184,134,70,233,117,2,84,242,135,246,95,159,14,218,125,209,191,175,89,41,196,182,96,117,5,159,98],"nonce":[114,93,158,35,209,188,96,248,85,131,95,237]}' \ -//! -H "Accept: application/json" \ -//! http://127.0.0.1:3001/user/sign_tx -//! ``` -//! -//! ### For the blockchain node -//! -//! #### `/user/new` - POST -//! -//! [crate::user::api::new_user()] -//! -//! Called by the off-chain worker (propagation pallet) during user registration. -//! This takes a parity scale encoded [entropy_shared::types::OcwMessageDkg] which tells us which -//! validators are in the registration group and will perform a DKG. -//! -//! ### For other instances of the threshold server -//! -//! - [`/user/receive_key`](receive_key) - recieve a keyshare from another threshold server in the -//! same signing subgroup. Takes a [UserRegistrationInfo] wrapped in a -//! [crate::validation::SignedMessage]. -//! - [`/ws`](crate::signing_client::api::ws_handler()) - Websocket server for signing protocol -//! 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. -//! -//! ### For testing / development -//! -//! [Unsafe](crate::unsafe::api) has additional routes which are for testing and development -//! purposes only and will not be used in production. These routes are only available if this crate -//! is compiled with the `unsafe` feature enabled. -//! -//! - [`unsafe/get`](crate::unsafe::api::unsafe_get()) - POST - get a value from the key-value -//! store, given its key. -//! - [`unsafe/put`](crate::unsafe::api::put()) - POST - update an existing value in the key-value -//! store. -//! - [`unsafe/delete`](crate::unsafe::api::delete()) - POST - remove a value from the key-value -//! store, given its key. -//! - [`unsafe/remove_keys`](crate::unsafe::api::remove_keys()) - GET - remove everything from the -//! key-value store. -//! -//! ## Pieces Launched -//! -//! - Axum server - Includes global state and mutex locked IPs -//! - [kvdb](kvdb) - Encrypted key-value database for storing key-shares and other data, build using -//! [sled](https://docs.rs/sled) -#![doc(html_logo_url = "https://entropy.xyz/assets/logo_02.png")] -pub(crate) mod chain_api; -pub(crate) mod health; -mod helpers; -pub(crate) mod sign_init; -mod signing_client; -mod r#unsafe; -mod user; -pub(crate) mod validation; -mod validator; -pub(crate) mod version; use std::{net::SocketAddr, str::FromStr}; -use axum::{ - http::Method, - routing::{get, post}, - Router, -}; use clap::Parser; -use kvdb::kv_manager::KvManager; -use tower_http::{ - cors::{Any, CorsLayer}, - trace::{self, TraceLayer}, -}; -use tracing::Level; -use validator::api::get_random_server_info; -use self::{ - signing_client::{api::*, ListenerState}, - user::api::*, -}; -use crate::{ - health::api::healthz, - helpers::{ - launch::{ - init_tracing, load_kv_store, setup_latest_block_number, setup_mnemonic, Configuration, - StartupArgs, ValidatorName, - }, - validator::get_signer, +use server::{ + app, + launch::{ + init_tracing, load_kv_store, setup_latest_block_number, setup_mnemonic, Configuration, + StartupArgs, ValidatorName, }, - r#unsafe::api::{delete, put, remove_keys, unsafe_get}, - validator::api::{sync_kvdb, sync_validator}, - version::api::version as get_version, + sync_validator, AppState, }; -#[derive(Clone)] -pub struct AppState { - pub listener_state: ListenerState, - pub configuration: Configuration, - pub kv_store: KvManager, -} - #[tokio::main] async fn main() { init_tracing(); @@ -147,7 +19,6 @@ async fn main() { let args = StartupArgs::parse(); tracing::info!("Starting server on: `{}`", &args.threshold_url); - let listener_state = ListenerState::default(); let configuration = Configuration::new(args.chain_endpoint); tracing::info!("Connecting to Substrate node at: `{}`", &configuration.endpoint); @@ -160,11 +31,7 @@ async fn main() { } let kv_store = load_kv_store(&validator_name, args.no_password).await; - let app_state = AppState { - listener_state, - configuration: configuration.clone(), - kv_store: kv_store.clone(), - }; + let app_state = AppState::new(configuration.clone(), kv_store.clone()); setup_mnemonic(&kv_store, &validator_name).await.expect("Issue creating Mnemonic"); setup_latest_block_number(&kv_store).await.expect("Issue setting up Latest Block Number"); @@ -177,46 +44,3 @@ async fn main() { .await .expect("failed to launch axum server."); } - -pub fn app(app_state: AppState) -> Router { - let mut routes = Router::new() - .route("/user/sign_tx", post(sign_tx)) - .route("/user/new", post(new_user)) - .route("/user/receive_key", post(receive_key)) - .route("/signer/proactive_refresh", post(proactive_refresh)) - .route("/validator/sync_kvdb", post(sync_kvdb)) - .route("/healthz", get(healthz)) - .route("/version", get(get_version)) - .route("/ws", get(ws_handler)); - - // Unsafe routes are for testing purposes only - // they are unsafe as they can expose vulnerabilites - // should they be used in production. Unsafe routes - // are disabled by default. - // To enable unsafe routes compile with --feature unsafe. - if cfg!(feature = "unsafe") || cfg!(test) { - tracing::warn!("Server started in unsafe mode - do not use in production!"); - routes = routes - .route("/unsafe/put", post(put)) - .route("/unsafe/get", post(unsafe_get)) - .route("/unsafe/delete", post(delete)) - .route("/unsafe/remove_keys", get(remove_keys)); - } - - routes - .with_state(app_state) - .layer( - TraceLayer::new_for_http() - .make_span_with(|request: &axum::http::Request| { - tracing::info_span!( - "http-request", - uuid = %uuid::Uuid::new_v4(), - uri = %request.uri(), - method = %request.method(), - ) - }) - .on_request(trace::DefaultOnRequest::new().level(Level::INFO)) - .on_response(trace::DefaultOnResponse::new().level(Level::INFO)), - ) - .layer(CorsLayer::new().allow_origin(Any).allow_methods([Method::GET, Method::POST])) -} diff --git a/crypto/server/src/user/tests.rs b/crypto/server/src/user/tests.rs index 5c40de4cd..ac0a48f17 100644 --- a/crypto/server/src/user/tests.rs +++ b/crypto/server/src/user/tests.rs @@ -67,7 +67,7 @@ use crate::{ get_signer, helpers::{ launch::{ - setup_mnemonic, Configuration, ValidatorName, DEFAULT_BOB_MNEMONIC, + load_kv_store, setup_mnemonic, Configuration, ValidatorName, DEFAULT_BOB_MNEMONIC, DEFAULT_CHARLIE_MNEMONIC, DEFAULT_ENDPOINT, DEFAULT_MNEMONIC, }, signing::{create_unique_tx_id, Hasher}, @@ -79,7 +79,7 @@ use crate::{ }, user::send_key, }, - load_kv_store, new_user, + new_user, r#unsafe::api::UnsafeQuery, signing_client::ListenerState, user::api::{recover_key, UserRegistrationInfo, UserSignatureRequest}, @@ -1038,123 +1038,6 @@ async fn test_sign_tx_user_participates() { clean_tests(); } -#[tokio::test] -#[serial] -async fn test_wasm_sign_tx_user_participates() { - clean_tests(); - let one = AccountKeyring::Eve; - - let signing_address = one.clone().to_account_id().to_ss58check(); - let (validator_ips, _validator_ids, users_keyshare_option) = - spawn_testing_validators(Some(signing_address.clone()), true).await; - let substrate_context = test_context_stationary().await; - let entropy_api = get_api(&substrate_context.node_proc.ws_url).await.unwrap(); - - update_programs(&entropy_api, &one.pair(), &one.pair(), TEST_PROGRAM_WASM_BYTECODE.to_owned()) - .await; - - let validators_info = vec![ - ValidatorInfo { - ip_address: "127.0.0.1:3001".to_string(), - x25519_public_key: X25519_PUBLIC_KEYS[0], - tss_account: TSS_ACCOUNTS[0].clone(), - }, - ValidatorInfo { - ip_address: "127.0.0.1:3002".to_string(), - x25519_public_key: X25519_PUBLIC_KEYS[1], - tss_account: TSS_ACCOUNTS[1].clone(), - }, - ]; - - let encoded_transaction_request: String = hex::encode(PREIMAGE_SHOULD_SUCCEED); - let message_should_succeed_hash = Hasher::keccak(PREIMAGE_SHOULD_SUCCEED); - - let signing_address = one.to_account_id().to_ss58check(); - let hash_as_hexstring = hex::encode(message_should_succeed_hash); - let sig_uid = create_unique_tx_id(&signing_address, &hash_as_hexstring); - - let mut generic_msg = UserSignatureRequest { - message: encoded_transaction_request.clone(), - auxilary_data: Some(hex::encode(AUXILARY_DATA_SHOULD_SUCCEED)), - validators_info: validators_info.clone(), - timestamp: SystemTime::now(), - }; - - let submit_transaction_requests = - |validator_urls_and_keys: Vec<(String, [u8; 32])>, - generic_msg: UserSignatureRequest, - keyring: Sr25519Keyring| async move { - let mock_client = reqwest::Client::new(); - join_all( - validator_urls_and_keys - .iter() - .map(|validator_tuple| async { - let server_public_key = PublicKey::from(validator_tuple.1); - let signed_message = SignedMessage::new( - &keyring.pair(), - &Bytes(serde_json::to_vec(&generic_msg.clone()).unwrap()), - &server_public_key, - ) - .unwrap(); - let url = format!("http://{}/user/sign_tx", validator_tuple.0.clone()); - mock_client - .post(url) - .header("Content-Type", "application/json") - .body(serde_json::to_string(&signed_message).unwrap()) - .send() - .await - }) - .collect::>(), - ) - .await - }; - let validator_ips_and_keys = vec![ - (validator_ips[0].clone(), X25519_PUBLIC_KEYS[0]), - (validator_ips[1].clone(), X25519_PUBLIC_KEYS[1]), - ]; - generic_msg.timestamp = SystemTime::now(); - - let (_one_keypair, one_x25519_sk) = keyring_to_subxt_signer_and_x25519(&one); - - let eve_seed: [u8; 32] = - hex::decode("786ad0e2df456fe43dd1f91ebca22e235bc162e0bb8d53c633e8c85b2af68b7a") - .unwrap() - .try_into() - .unwrap(); - - // Submit transaction requests, and connect and participate in signing - let (mut test_user_res, user_sig) = future::join( - submit_transaction_requests(validator_ips_and_keys.clone(), generic_msg.clone(), one), - spawn_user_participates_in_signing_protocol( - &users_keyshare_option.clone().unwrap(), - &sig_uid, - validators_info.clone(), - eve_seed, - &one_x25519_sk, - ), - ) - .await; - - // Check that the signature the user gets matches the first of the server's signatures - let user_sig = if let Some(user_sig_stripped) = user_sig.strip_suffix("\n") { - user_sig_stripped.to_string() - } else { - user_sig - }; - let mut server_res = test_user_res.pop().unwrap().unwrap(); - assert_eq!(server_res.status(), 200); - let chunk = server_res.chunk().await.unwrap().unwrap(); - let signing_result: Result<(String, Signature), String> = - serde_json::from_slice(&chunk).unwrap(); - assert_eq!(user_sig, signing_result.unwrap().0); - - // Verify the remaining server results, which should be the same - verify_signature(test_user_res, message_should_succeed_hash, users_keyshare_option.clone()) - .await; - - clean_tests(); -} - /// Test demonstrating registering with private key visibility, where the user participates in DKG /// and holds a keyshare. #[tokio::test] @@ -1230,104 +1113,6 @@ async fn test_register_with_private_key_visibility() { clean_tests(); } -/// Test demonstrating registering with private key visibility on wasm -#[tokio::test] -#[serial] -async fn test_wasm_register_with_private_key_visibility() { - clean_tests(); - - let one = AccountKeyring::One; - let program_modification_account = AccountKeyring::Charlie; - - let (validator_ips, _validator_ids, _users_keyshare_option) = - spawn_testing_validators(None, 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(); - let block_number = rpc.chain_get_header(None).await.unwrap().unwrap().number + 1; - - let (_one_keypair, one_x25519_sk) = keyring_to_subxt_signer_and_x25519(&one); - let x25519_public_key = PublicKey::from(&one_x25519_sk).to_bytes(); - - put_register_request_on_chain( - &api, - &one, - program_modification_account.to_account_id().into(), - KeyVisibility::Private(x25519_public_key), - ) - .await; - run_to_block(&rpc, block_number + 1).await; - - // Simulate the propagation pallet making a `user/new` request to the second validator - // as we only have one chain node running - let onchain_user_request = { - // Since we only have two validators we use both of them, but if we had more we would - // need to select them using same method as the chain does (based on block number) - let validators_info: Vec = validator_ips - .iter() - .enumerate() - .map(|(i, ip)| entropy_shared::ValidatorInfo { - ip_address: ip.as_bytes().to_vec(), - x25519_public_key: X25519_PUBLIC_KEYS[i], - tss_account: TSS_ACCOUNTS[i].clone().encode(), - }) - .collect(); - OcwMessageDkg { sig_request_accounts: vec![one.encode()], block_number, validators_info } - }; - - let client = reqwest::Client::new(); - let validators_info: Vec = validator_ips - .iter() - .enumerate() - .map(|(i, ip)| ValidatorInfo { - ip_address: ip.clone(), - x25519_public_key: X25519_PUBLIC_KEYS[i], - tss_account: TSS_ACCOUNTS[i].clone(), - }) - .collect(); - - let one_seed: [u8; 32] = - hex::decode("3b3993c957ed9342cbb011eb9029c53fb253345114eff7da5951e98a41ba5ad5") - .unwrap() - .try_into() - .unwrap(); - - // Call the `user/new` endpoint, and connect and participate in the protocol - let (new_user_response_result, user_keyshare_json) = future::join( - client - .post("http://127.0.0.1:3002/user/new") - .body(onchain_user_request.clone().encode()) - .send(), - spawn_user_participates_in_dkg_protocol(validators_info.clone(), one_seed, &one_x25519_sk), - ) - .await; - - let response = new_user_response_result.unwrap(); - assert_eq!(response.status(), StatusCode::OK); - assert_eq!(response.text().await.unwrap(), ""); - - let signing_address = one.to_account_id().to_ss58check(); - let get_query = UnsafeQuery::new(signing_address, vec![]).to_json(); - let server_keyshare_response = client - .post("http://127.0.0.1:3001/unsafe/get") - .header("Content-Type", "application/json") - .body(get_query.clone()) - .send() - .await - .unwrap(); - let server_keyshare_serialized = server_keyshare_response.bytes().await.unwrap(); - let server_keyshare: KeyShare = - keyshare_deserialize(&server_keyshare_serialized).unwrap(); - - let user_keyshare: KeyShare = serde_json::from_str(&user_keyshare_json).unwrap(); - - let user_verifying_key = user_keyshare.verifying_key(); - let server_verifying_key = server_keyshare.verifying_key(); - assert_eq!(user_verifying_key, server_verifying_key); - - clean_tests(); -} - pub async fn verify_signature( test_user_res: Vec>, message_should_succeed_hash: [u8; 32], @@ -1364,101 +1149,3 @@ pub async fn verify_signature( i += 1; } } - -#[derive(Debug, Clone, Serialize, Deserialize)] -struct UserParticipatesInSigningProtocolArgs { - user_sig_req_seed: Vec, - x25519_private_key: Vec, - sig_uid: String, - key_share: String, - validators_info: Vec, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -struct UserParticipatesInDkgProtocolArgs { - user_sig_req_seed: Vec, - x25519_private_key: Vec, - validators_info: Vec, -} - -#[derive(Debug, Clone, Serialize, Deserialize)] -struct ValidatorInfoParsed { - x25519_public_key: [u8; 32], - ip_address: String, - tss_account: [u8; 32], -} - -/// For testing running the protocol on wasm, spawn a process running nodejs and pass -/// the protocol runnning parameters as JSON as a command line argument -pub async fn spawn_user_participates_in_signing_protocol( - key_share: &KeyShare, - sig_uid: &str, - validators_info: Vec, - user_sig_req_seed: [u8; 32], - x25519_private_key: &x25519_dalek::StaticSecret, -) -> String { - let args = UserParticipatesInSigningProtocolArgs { - sig_uid: sig_uid.to_string(), - user_sig_req_seed: user_sig_req_seed.to_vec(), - validators_info: validators_info - .into_iter() - .map(|validator_info| ValidatorInfoParsed { - x25519_public_key: validator_info.x25519_public_key, - ip_address: validator_info.ip_address.to_string(), - tss_account: *validator_info.tss_account.as_ref(), - }) - .collect(), - key_share: serde_json::to_string(key_share).unwrap(), - x25519_private_key: x25519_private_key.to_bytes().to_vec(), - }; - let json_params = serde_json::to_string(&args).unwrap(); - - let test_script_path = format!( - "{}/crypto/protocol/nodejs-test/index.js", - project_root::get_project_root().unwrap().to_string_lossy() - ); - - let output = tokio::process::Command::new("node") - .arg(test_script_path) - .arg("sign") - .arg(json_params) - .output() - .await - .unwrap(); - String::from_utf8(output.stdout).unwrap() -} - -/// For testing running the DKG protocol on wasm, spawn a process running nodejs and pass -/// the protocol runnning parameters as JSON as a command line argument -pub async fn spawn_user_participates_in_dkg_protocol( - validators_info: Vec, - user_sig_req_seed: [u8; 32], - x25519_private_key: &x25519_dalek::StaticSecret, -) -> String { - let args = UserParticipatesInDkgProtocolArgs { - user_sig_req_seed: user_sig_req_seed.to_vec(), - validators_info: validators_info - .into_iter() - .map(|validator_info| ValidatorInfoParsed { - x25519_public_key: validator_info.x25519_public_key, - ip_address: validator_info.ip_address.to_string(), - tss_account: *validator_info.tss_account.as_ref(), - }) - .collect(), - x25519_private_key: x25519_private_key.to_bytes().to_vec(), - }; - let json_params = serde_json::to_string(&args).unwrap(); - - let test_script_path = format!( - "{}/crypto/protocol/nodejs-test/index.js", - project_root::get_project_root().unwrap().to_string_lossy() - ); - let output = tokio::process::Command::new("node") - .arg(test_script_path) - .arg("register") - .arg(json_params) - .output() - .await - .unwrap(); - String::from_utf8(output.stdout).unwrap() -} diff --git a/crypto/server/tests/helpers/mod.rs b/crypto/server/tests/helpers/mod.rs new file mode 100644 index 000000000..c4e99ce28 --- /dev/null +++ b/crypto/server/tests/helpers/mod.rs @@ -0,0 +1,47 @@ +//! Helper functions for integration tests +use entropy_protocol::KeyParams; +use server::launch::{DEFAULT_BOB_MNEMONIC, DEFAULT_MNEMONIC}; +use synedrion::{ + k256::ecdsa::{RecoveryId, Signature as k256Signature, VerifyingKey}, + KeyShare, +}; + +use subxt::ext::sp_core::{sr25519, sr25519::Signature, Pair}; + +/// Verfiy a signature in a response from `/user/sign_tx` +pub async fn verify_signature( + test_user_res: Vec>, + message_should_succeed_hash: [u8; 32], + keyshare_option: Option>, +) { + let mut i = 0; + for res in test_user_res { + let mut res = res.unwrap(); + + assert_eq!(res.status(), 200); + let chunk = res.chunk().await.unwrap().unwrap(); + let signing_result: Result<(String, Signature), String> = + serde_json::from_slice(&chunk).unwrap(); + assert_eq!(signing_result.clone().unwrap().0.len(), 88); + let mut decoded_sig = base64::decode(signing_result.clone().unwrap().0).unwrap(); + let recovery_digit = decoded_sig.pop().unwrap(); + let signature = k256Signature::from_slice(&decoded_sig).unwrap(); + let recover_id = RecoveryId::from_byte(recovery_digit).unwrap(); + let recovery_key_from_sig = VerifyingKey::recover_from_prehash( + &message_should_succeed_hash, + &signature, + recover_id, + ) + .unwrap(); + assert_eq!(keyshare_option.clone().unwrap().verifying_key(), recovery_key_from_sig); + let mnemonic = if i == 0 { DEFAULT_MNEMONIC } else { DEFAULT_BOB_MNEMONIC }; + let sk = ::from_string(mnemonic, None).unwrap(); + let sig_recovery = ::verify( + &signing_result.clone().unwrap().1, + base64::decode(signing_result.unwrap().0).unwrap(), + &sr25519::Public(sk.public().0), + ); + assert!(sig_recovery); + i += 1; + } +} diff --git a/crypto/server/tests/protocol_wasm.rs b/crypto/server/tests/protocol_wasm.rs new file mode 100644 index 000000000..9b0853587 --- /dev/null +++ b/crypto/server/tests/protocol_wasm.rs @@ -0,0 +1,375 @@ +//! Integration tests which use a nodejs process to test wasm bindings to the entropy-protocol +//! client functions +mod helpers; +use axum::http::StatusCode; +use entropy_protocol::{KeyParams, ValidatorInfo}; +use entropy_shared::{KeyVisibility, OcwMessageDkg}; +use futures::future::join_all; +use futures::future::{self}; +use kvdb::clean_tests; +use parity_scale_codec::Encode; +use serde::{Deserialize, Serialize}; +use serial_test::serial; +use sp_core::crypto::{AccountId32, Ss58Codec}; +use sp_keyring::{AccountKeyring, Sr25519Keyring}; +use std::{ + thread, + time::{Duration, SystemTime}, +}; +use subxt::{ + backend::legacy::LegacyRpcMethods, + ext::sp_core::{sr25519::Signature, Bytes}, + Config, OnlineClient, +}; +use synedrion::KeyShare; +use testing_utils::{ + constants::{ + AUXILARY_DATA_SHOULD_SUCCEED, PREIMAGE_SHOULD_SUCCEED, TEST_PROGRAM_WASM_BYTECODE, + TSS_ACCOUNTS, X25519_PUBLIC_KEYS, + }, + substrate_context::test_context_stationary, + test_client::{put_register_request_on_chain, update_program}, + tss_server_process::spawn_testing_validators, +}; +use x25519_dalek::PublicKey; + +use server::{ + chain_api::{ + entropy::{self, runtime_types::pallet_relayer::pallet::RegisteredInfo}, + get_api, get_rpc, EntropyConfig, + }, + common::{ + create_unique_tx_id, + validation::{derive_static_secret, SignedMessage}, + Hasher, UserSignatureRequest, + }, +}; + +/// Test demonstrating signing a message with private key visibility on wasm +#[tokio::test] +#[serial] +async fn test_wasm_sign_tx_user_participates() { + clean_tests(); + let one = AccountKeyring::Eve; + + let signing_address = one.clone().to_account_id().to_ss58check(); + let (validator_ips, _validator_ids, users_keyshare_option) = + spawn_testing_validators(Some(signing_address.clone()), true).await; + let substrate_context = test_context_stationary().await; + let entropy_api = get_api(&substrate_context.node_proc.ws_url).await.unwrap(); + + update_program(&entropy_api, &one.pair(), &one.pair(), TEST_PROGRAM_WASM_BYTECODE.to_owned()) + .await; + + let validators_info = vec![ + ValidatorInfo { + ip_address: "127.0.0.1:3001".to_string(), + x25519_public_key: X25519_PUBLIC_KEYS[0], + tss_account: TSS_ACCOUNTS[0].clone(), + }, + ValidatorInfo { + ip_address: "127.0.0.1:3002".to_string(), + x25519_public_key: X25519_PUBLIC_KEYS[1], + tss_account: TSS_ACCOUNTS[1].clone(), + }, + ]; + + let encoded_transaction_request: String = hex::encode(PREIMAGE_SHOULD_SUCCEED); + let message_should_succeed_hash = Hasher::keccak(PREIMAGE_SHOULD_SUCCEED); + + let signing_address = one.to_account_id().to_ss58check(); + let hash_as_hexstring = hex::encode(message_should_succeed_hash); + let sig_uid = create_unique_tx_id(&signing_address, &hash_as_hexstring); + + let mut generic_msg = UserSignatureRequest { + message: encoded_transaction_request.clone(), + auxilary_data: Some(hex::encode(AUXILARY_DATA_SHOULD_SUCCEED)), + validators_info: validators_info.clone(), + timestamp: SystemTime::now(), + }; + + let submit_transaction_requests = + |validator_urls_and_keys: Vec<(String, [u8; 32])>, + generic_msg: UserSignatureRequest, + keyring: Sr25519Keyring| async move { + let mock_client = reqwest::Client::new(); + join_all( + validator_urls_and_keys + .iter() + .map(|validator_tuple| async { + let server_public_key = PublicKey::from(validator_tuple.1); + let signed_message = SignedMessage::new( + &keyring.pair(), + &Bytes(serde_json::to_vec(&generic_msg.clone()).unwrap()), + &server_public_key, + ) + .unwrap(); + let url = format!("http://{}/user/sign_tx", validator_tuple.0.clone()); + mock_client + .post(url) + .header("Content-Type", "application/json") + .body(serde_json::to_string(&signed_message).unwrap()) + .send() + .await + }) + .collect::>(), + ) + .await + }; + let validator_ips_and_keys = vec![ + (validator_ips[0].clone(), X25519_PUBLIC_KEYS[0]), + (validator_ips[1].clone(), X25519_PUBLIC_KEYS[1]), + ]; + generic_msg.timestamp = SystemTime::now(); + + let one_x25519_sk = derive_static_secret(&one.pair()); + + let eve_seed: [u8; 32] = + hex::decode("786ad0e2df456fe43dd1f91ebca22e235bc162e0bb8d53c633e8c85b2af68b7a") + .unwrap() + .try_into() + .unwrap(); + + // Submit transaction requests, and connect and participate in signing + let (mut test_user_res, user_sig) = future::join( + submit_transaction_requests(validator_ips_and_keys.clone(), generic_msg.clone(), one), + spawn_user_participates_in_signing_protocol( + &users_keyshare_option.clone().unwrap(), + &sig_uid, + validators_info.clone(), + eve_seed, + &one_x25519_sk, + ), + ) + .await; + + // Check that the signature the user gets matches the first of the server's signatures + let user_sig = if let Some(user_sig_stripped) = user_sig.strip_suffix("\n") { + user_sig_stripped.to_string() + } else { + user_sig + }; + let mut server_res = test_user_res.pop().unwrap().unwrap(); + assert_eq!(server_res.status(), 200); + let chunk = server_res.chunk().await.unwrap().unwrap(); + let signing_result: Result<(String, Signature), String> = + serde_json::from_slice(&chunk).unwrap(); + assert_eq!(user_sig, signing_result.unwrap().0); + + // Verify the remaining server results, which should be the same + helpers::verify_signature( + test_user_res, + message_should_succeed_hash, + users_keyshare_option.clone(), + ) + .await; + + clean_tests(); +} + +/// Test demonstrating registering with private key visibility on wasm +#[tokio::test] +#[serial] +async fn test_wasm_register_with_private_key_visibility() { + clean_tests(); + + let one = AccountKeyring::One; + let program_modification_account = AccountKeyring::Charlie; + + let (validator_ips, _validator_ids, _users_keyshare_option) = + spawn_testing_validators(None, 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(); + let block_number = rpc.chain_get_header(None).await.unwrap().unwrap().number + 1; + + let one_x25519_sk = derive_static_secret(&one.pair()); + let x25519_public_key = PublicKey::from(&one_x25519_sk).to_bytes(); + + put_register_request_on_chain( + &api, + one.pair(), + program_modification_account.to_account_id().into(), + KeyVisibility::Private(x25519_public_key), + ) + .await; + run_to_block(&rpc, block_number + 1).await; + + // Simulate the propagation pallet making a `user/new` request to the second validator + // as we only have one chain node running + let onchain_user_request = { + // Since we only have two validators we use both of them, but if we had more we would + // need to select them using same method as the chain does (based on block number) + let validators_info: Vec = validator_ips + .iter() + .enumerate() + .map(|(i, ip)| entropy_shared::ValidatorInfo { + ip_address: ip.as_bytes().to_vec(), + x25519_public_key: X25519_PUBLIC_KEYS[i], + tss_account: TSS_ACCOUNTS[i].clone().encode(), + }) + .collect(); + OcwMessageDkg { sig_request_accounts: vec![one.encode()], block_number, validators_info } + }; + + let client = reqwest::Client::new(); + let validators_info: Vec = validator_ips + .iter() + .enumerate() + .map(|(i, ip)| ValidatorInfo { + ip_address: ip.clone(), + x25519_public_key: X25519_PUBLIC_KEYS[i], + tss_account: TSS_ACCOUNTS[i].clone(), + }) + .collect(); + + let one_seed: [u8; 32] = + hex::decode("3b3993c957ed9342cbb011eb9029c53fb253345114eff7da5951e98a41ba5ad5") + .unwrap() + .try_into() + .unwrap(); + + // Call the `user/new` endpoint, and connect and participate in the protocol + let (new_user_response_result, user_keyshare_json) = future::join( + client + .post("http://127.0.0.1:3002/user/new") + .body(onchain_user_request.clone().encode()) + .send(), + spawn_user_participates_in_dkg_protocol(validators_info.clone(), one_seed, &one_x25519_sk), + ) + .await; + + let response = new_user_response_result.unwrap(); + assert_eq!(response.status(), StatusCode::OK); + assert_eq!(response.text().await.unwrap(), ""); + + let registered_info = wait_for_register_confirmation(one.to_account_id(), api, rpc).await; + + let user_keyshare: KeyShare = serde_json::from_str(&user_keyshare_json).unwrap(); + let user_verifying_key = + user_keyshare.verifying_key().to_encoded_point(true).as_bytes().to_vec(); + + assert_eq!(user_verifying_key, registered_info.verifying_key.0); + + clean_tests(); +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +struct UserParticipatesInSigningProtocolArgs { + user_sig_req_seed: Vec, + x25519_private_key: Vec, + sig_uid: String, + key_share: String, + validators_info: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +struct UserParticipatesInDkgProtocolArgs { + user_sig_req_seed: Vec, + x25519_private_key: Vec, + validators_info: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +struct ValidatorInfoParsed { + x25519_public_key: [u8; 32], + ip_address: String, + tss_account: [u8; 32], +} + +/// For testing running the protocol on wasm, spawn a process running nodejs and pass +/// the protocol runnning parameters as JSON as a command line argument +async fn spawn_user_participates_in_signing_protocol( + key_share: &KeyShare, + sig_uid: &str, + validators_info: Vec, + user_sig_req_seed: [u8; 32], + x25519_private_key: &x25519_dalek::StaticSecret, +) -> String { + let args = UserParticipatesInSigningProtocolArgs { + sig_uid: sig_uid.to_string(), + user_sig_req_seed: user_sig_req_seed.to_vec(), + validators_info: validators_info + .into_iter() + .map(|validator_info| ValidatorInfoParsed { + x25519_public_key: validator_info.x25519_public_key, + ip_address: validator_info.ip_address.to_string(), + tss_account: *validator_info.tss_account.as_ref(), + }) + .collect(), + key_share: serde_json::to_string(key_share).unwrap(), + x25519_private_key: x25519_private_key.to_bytes().to_vec(), + }; + let json_params = serde_json::to_string(&args).unwrap(); + + spawn_node_process(json_params, "sign".to_string()).await +} + +/// For testing running the DKG protocol on wasm, spawn a process running nodejs and pass +/// the protocol runnning parameters as JSON as a command line argument +async fn spawn_user_participates_in_dkg_protocol( + validators_info: Vec, + user_sig_req_seed: [u8; 32], + x25519_private_key: &x25519_dalek::StaticSecret, +) -> String { + let args = UserParticipatesInDkgProtocolArgs { + user_sig_req_seed: user_sig_req_seed.to_vec(), + validators_info: validators_info + .into_iter() + .map(|validator_info| ValidatorInfoParsed { + x25519_public_key: validator_info.x25519_public_key, + ip_address: validator_info.ip_address.to_string(), + tss_account: *validator_info.tss_account.as_ref(), + }) + .collect(), + x25519_private_key: x25519_private_key.to_bytes().to_vec(), + }; + let json_params = serde_json::to_string(&args).unwrap(); + + spawn_node_process(json_params, "register".to_string()).await +} + +async fn spawn_node_process(json_params: String, command_for_script: String) -> String { + let test_script_path = format!( + "{}/crypto/protocol/nodejs-test/index.js", + project_root::get_project_root().unwrap().to_string_lossy() + ); + let output = tokio::process::Command::new("node") + .arg(test_script_path) + .arg(command_for_script) + .arg(json_params) + .output() + .await + .unwrap(); + + let std_err = String::from_utf8(output.stderr).unwrap(); + if !std_err.is_empty() { + tracing::warn!("Standard error from node process {}", std_err); + } + String::from_utf8(output.stdout).unwrap() +} + +async fn run_to_block(rpc: &LegacyRpcMethods, block_run: u32) { + let mut current_block = 0; + while current_block < block_run { + current_block = rpc.chain_get_header(None).await.unwrap().unwrap().number; + } +} + +async fn wait_for_register_confirmation( + account_id: AccountId32, + api: OnlineClient, + rpc: LegacyRpcMethods, +) -> RegisteredInfo { + let account_id: ::AccountId = account_id.into(); + let registered_query = entropy::storage().relayer().registered(account_id); + for _ in 0..30 { + let block_hash = rpc.chain_get_block_hash(None).await.unwrap().unwrap(); + let query_registered_status = api.storage().at(block_hash).fetch(®istered_query).await; + if let Some(user_info) = query_registered_status.unwrap() { + return user_info; + } + thread::sleep(Duration::from_millis(2000)); + } + panic!("Timed out waiting for register confirmation"); +} diff --git a/crypto/testing-utils/Cargo.toml b/crypto/testing-utils/Cargo.toml index fda02c881..525f4f0e5 100644 --- a/crypto/testing-utils/Cargo.toml +++ b/crypto/testing-utils/Cargo.toml @@ -20,6 +20,14 @@ parity-scale-codec="3.0.0" lazy_static ="1.4.0" hex-literal ="0.3.4" entropy-shared ={ path="../shared" } +entropy-protocol ={ path="../protocol" } +axum ={ version="0.6.18" } +rand_core ="0.6.4" +kvdb ={ path="../kvdb", default-features=false } +synedrion ={ git="ssh://git@github.com/entropyxyz/synedrion.git", branch="fix-32bit" } +server ={ path="../server" } +tokio ={ version="1.16", features=["macros", "fs", "rt-multi-thread", "io-util", "process"] } +subxt-signer ="0.31.0" # Logging tracing ="0.1.37" diff --git a/crypto/testing-utils/src/chain_api.rs b/crypto/testing-utils/src/chain_api.rs deleted file mode 100644 index 57d4e00b6..000000000 --- a/crypto/testing-utils/src/chain_api.rs +++ /dev/null @@ -1,5 +0,0 @@ -#![allow(clippy::all)] -pub use subxt::config::PolkadotConfig as EntropyConfig; - -#[subxt::subxt(runtime_metadata_path = "../server/entropy_metadata.scale")] -pub mod entropy {} diff --git a/crypto/testing-utils/src/lib.rs b/crypto/testing-utils/src/lib.rs index 157505d49..765e387c1 100644 --- a/crypto/testing-utils/src/lib.rs +++ b/crypto/testing-utils/src/lib.rs @@ -1,9 +1,11 @@ #[macro_use] extern crate lazy_static; -mod chain_api; +pub use server::chain_api; pub mod constants; mod node_proc; pub mod substrate_context; +pub mod test_client; +pub mod tss_server_process; pub use node_proc::TestNodeProcess; pub use substrate_context::*; diff --git a/crypto/testing-utils/src/test_client/mod.rs b/crypto/testing-utils/src/test_client/mod.rs new file mode 100644 index 000000000..d6d7f7ac0 --- /dev/null +++ b/crypto/testing-utils/src/test_client/mod.rs @@ -0,0 +1,66 @@ +//! Client functionality used in itegration tests +use entropy_shared::KeyVisibility; +use subxt::{ + ext::sp_core::{sr25519, Pair}, + tx::PairSigner, + utils::{AccountId32 as SubxtAccountId32, Static}, + OnlineClient, +}; + +use server::chain_api::{entropy, EntropyConfig}; + +/// Submit a register transaction +pub async fn put_register_request_on_chain( + api: &OnlineClient, + sig_req_account: sr25519::Pair, + program_modification_account: SubxtAccountId32, + key_visibility: KeyVisibility, +) { + let sig_req_account = PairSigner::::new(sig_req_account); + + let empty_program = vec![]; + let registering_tx = entropy::tx().relayer().register( + program_modification_account, + Static(key_visibility), + empty_program, + ); + + api.tx() + .sign_and_submit_then_watch_default(®istering_tx, &sig_req_account) + .await + .unwrap() + .wait_for_in_block() + .await + .unwrap() + .wait_for_success() + .await + .unwrap(); +} + +/// Set or update the program associated with a given entropy account +pub async fn update_program( + entropy_api: &OnlineClient, + sig_req_account: &sr25519::Pair, + program_modification_account: &sr25519::Pair, + initial_program: Vec, +) { + // update/set their programs + let update_program_tx = entropy::tx() + .programs() + .update_program(SubxtAccountId32::from(sig_req_account.public()), initial_program); + + let program_modification_account = + PairSigner::::new(program_modification_account.clone()); + + entropy_api + .tx() + .sign_and_submit_then_watch_default(&update_program_tx, &program_modification_account) + .await + .unwrap() + .wait_for_in_block() + .await + .unwrap() + .wait_for_success() + .await + .unwrap(); +} diff --git a/crypto/testing-utils/src/tss_server_process.rs b/crypto/testing-utils/src/tss_server_process.rs new file mode 100644 index 000000000..e031f05ce --- /dev/null +++ b/crypto/testing-utils/src/tss_server_process.rs @@ -0,0 +1,107 @@ +use std::{net::TcpListener, time::Duration}; + +use axum::{routing::IntoMakeService, Router}; +use entropy_protocol::{KeyParams, PartyId}; +use kvdb::{encrypted_sled::PasswordMethod, kv_manager::KvManager}; +use rand_core::OsRng; +use subxt::utils::AccountId32 as SubxtAccountId32; +use synedrion::KeyShare; + +use server::{ + app, + get_signer, + launch::{setup_latest_block_number, setup_mnemonic, Configuration, ValidatorName}, + // signing_client::ListenerState, + AppState, +}; + +pub const DEFAULT_ENDPOINT: &str = "ws://localhost:9944"; + +async fn create_clients( + key_number: String, + values: Vec>, + keys: Vec, + validator_name: &Option, +) -> (IntoMakeService, KvManager) { + let configuration = Configuration::new(DEFAULT_ENDPOINT.to_string()); + + let path = format!(".entropy/testing/test_db_{key_number}"); + let _ = std::fs::remove_dir_all(path.clone()); + + let kv_store = + KvManager::new(path.into(), PasswordMethod::NoPassword.execute().unwrap()).unwrap(); + let _ = setup_mnemonic(&kv_store, validator_name).await; + let _ = setup_latest_block_number(&kv_store).await; + + for (i, value) in values.into_iter().enumerate() { + let reservation = kv_store.clone().kv().reserve_key(keys[i].to_string()).await.unwrap(); + let _ = kv_store.clone().kv().put(reservation, value).await; + } + + let app_state = AppState::new(configuration, kv_store.clone()); + let app = app(app_state).into_make_service(); + + (app, kv_store) +} + +pub async fn spawn_testing_validators( + sig_req_keyring: Option, + // If this is true a keyshare for the user will be generated and returned + extra_private_keys: bool, +) -> (Vec, Vec, Option>) { + // spawn threshold servers + let ports = [3001i64, 3002]; + + let (alice_axum, alice_kv) = + create_clients("validator1".to_string(), vec![], vec![], &Some(ValidatorName::Alice)).await; + let alice_id = PartyId::new(SubxtAccountId32( + *get_signer(&alice_kv).await.unwrap().account_id().clone().as_ref(), + )); + + let (bob_axum, bob_kv) = + create_clients("validator2".to_string(), vec![], vec![], &Some(ValidatorName::Bob)).await; + let bob_id = PartyId::new(SubxtAccountId32( + *get_signer(&bob_kv).await.unwrap().account_id().clone().as_ref(), + )); + + let user_keyshare_option = if sig_req_keyring.is_some() { + let number_of_shares = if extra_private_keys { 3 } else { 2 }; + let shares = KeyShare::::new_centralized(&mut OsRng, number_of_shares, None); + let validator_1_threshold_keyshare: Vec = + kvdb::kv_manager::helpers::serialize(&shares[0]).unwrap(); + let validator_2_threshold_keyshare: Vec = + kvdb::kv_manager::helpers::serialize(&shares[1]).unwrap(); + // add key share to kvdbs + let alice_reservation = + alice_kv.kv().reserve_key(sig_req_keyring.clone().unwrap()).await.unwrap(); + alice_kv.kv().put(alice_reservation, validator_1_threshold_keyshare).await.unwrap(); + + let bob_reservation = + bob_kv.kv().reserve_key(sig_req_keyring.clone().unwrap()).await.unwrap(); + bob_kv.kv().put(bob_reservation, validator_2_threshold_keyshare).await.unwrap(); + + if extra_private_keys { + Some(shares[2].clone()) + } else { + Some(shares[1].clone()) + } + } else { + None + }; + + let listener_alice = TcpListener::bind(format!("0.0.0.0:{}", ports[0])).unwrap(); + let listener_bob = TcpListener::bind(format!("0.0.0.0:{}", ports[1])).unwrap(); + tokio::spawn(async move { + axum::Server::from_tcp(listener_alice).unwrap().serve(alice_axum).await.unwrap(); + }); + + tokio::spawn(async move { + axum::Server::from_tcp(listener_bob).unwrap().serve(bob_axum).await.unwrap(); + }); + + tokio::time::sleep(Duration::from_secs(1)).await; + + let ips = ports.iter().map(|port| format!("127.0.0.1:{port}")).collect(); + let ids = vec![alice_id, bob_id]; + (ips, ids, user_keyshare_option) +}