From 1de907a97d8b0dbf9b037f11ed3f05eb94f3d942 Mon Sep 17 00:00:00 2001 From: peg Date: Tue, 22 Oct 2024 11:36:38 +0200 Subject: [PATCH 1/7] Dont allow mnemonic to be passed in via CLI, or environment variable - generate it internally --- .../src/helpers/launch.rs | 31 ------------- crates/threshold-signature-server/src/main.rs | 43 ++++--------------- 2 files changed, 9 insertions(+), 65 deletions(-) diff --git a/crates/threshold-signature-server/src/helpers/launch.rs b/crates/threshold-signature-server/src/helpers/launch.rs index 8f5a98042..a61b47611 100644 --- a/crates/threshold-signature-server/src/helpers/launch.rs +++ b/crates/threshold-signature-server/src/helpers/launch.rs @@ -217,37 +217,6 @@ pub struct StartupArgs { /// Returns the AccountID and Diffie-Hellman Public Keys associated with this server. #[arg(long = "setup-only")] pub setup_only: bool, - - /// The BIP-39 mnemonic (i.e seed phrase) to use for deriving the Threshold Signature Server - /// SR25519 account ID and the X25519 public key. - /// - /// The SR25519 account is responsible for signing and submitting extrinsics to the Entropy - /// network. - /// - /// The X25519 public key is used for encrypting/decrypting messages to other threshold - /// servers. - /// - /// Note that this may keep a mnemonic in your shell history. If you would like to avoid this - /// use one of the alternative options, or tools like the 1Password CLI. - /// - /// **Alternatives**: There are two other ways to supply the mnemonic to the TSS: the - /// `--mnemonic-file` flag and the `THRESHOLD_SERVER_MNEMONIC` environment variable. - /// - /// **Warning**: Passing this flag will overwrite any existing mnemonic! If you would like to - /// use an existing mnemonic omit this flag when running the process. - #[arg(long = "mnemonic")] - pub mnemonic: Option, - - /// The path to a file containing the BIP-39 mnemonic (i.e seed phrase) to use for deriving the - /// Threshold Signature Server SR25519 account ID and the X25519 public key. - /// - /// **Alternatives**: There are two other ways to supply the mnemonic to the TSS: the - /// `--mnemonic` flag and the `THRESHOLD_SERVER_MNEMONIC` environment variable. - /// - /// **Warning**: Passing this flag will overwrite any existing mnemonic! If you would like to - /// use an existing mnemonic omit this flag when running the process. - #[arg(long = "mnemonic-file", conflicts_with = "mnemonic")] - pub mnemonic_file: Option, } pub async fn has_mnemonic(kv: &KvManager) -> bool { diff --git a/crates/threshold-signature-server/src/main.rs b/crates/threshold-signature-server/src/main.rs index c9e520e7d..66d591709 100644 --- a/crates/threshold-signature-server/src/main.rs +++ b/crates/threshold-signature-server/src/main.rs @@ -20,8 +20,8 @@ use clap::Parser; use entropy_tss::{ app, launch::{ - development_mnemonic, load_kv_store, setup_latest_block_number, setup_mnemonic, setup_only, - Configuration, StartupArgs, ValidatorName, + development_mnemonic, has_mnemonic, load_kv_store, setup_latest_block_number, + setup_mnemonic, setup_only, Configuration, StartupArgs, ValidatorName, }, AppState, }; @@ -66,39 +66,14 @@ async fn main() { let app_state = AppState::new(configuration.clone(), kv_store.clone()); - // We consider the inputs in order of most to least explicit: CLI flag, supplied file, - // environment variable. - let user_mnemonic = args - .mnemonic - .or_else(|| { - args.mnemonic_file.map(|path| { - let file = std::fs::read(path).expect("Unable to read mnemonic file."); - let mnemonic = std::str::from_utf8(&file) - .expect("Unable to convert provided mnemonic to UTF-8 string.") - .trim(); - - bip39::Mnemonic::parse_normalized(mnemonic) - .expect("Unable to parse given mnemonic.") - }) - }) - .or_else(|| { - std::env::var("THRESHOLD_SERVER_MNEMONIC").ok().map(|mnemonic| { - bip39::Mnemonic::parse_normalized(&mnemonic) - .expect("Unable to parse given mnemonic.") - }) - }); - - if let Some(mnemonic) = user_mnemonic { - setup_mnemonic(&kv_store, mnemonic).await - } else if cfg!(test) || validator_name.is_some() { + if cfg!(test) || validator_name.is_some() { setup_mnemonic(&kv_store, development_mnemonic(&validator_name)).await - } else { - let has_mnemonic = entropy_tss::launch::has_mnemonic(&kv_store).await; - assert!( - has_mnemonic, - "No mnemonic provided. Please provide one or use a development account." - ); - }; + } else if !has_mnemonic(&kv_store).await { + let mut rng = rand::thread_rng(); + let mnemonic = + bip39::Mnemonic::generate_in_with(&mut rng, bip39::Language::English, 24).unwrap(); + setup_mnemonic(&kv_store, mnemonic).await + } setup_latest_block_number(&kv_store).await.expect("Issue setting up Latest Block Number"); From 6b97f8f37898ef8fa7f28a5624465d5f3537c704 Mon Sep 17 00:00:00 2001 From: peg Date: Tue, 22 Oct 2024 11:49:51 +0200 Subject: [PATCH 2/7] Changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ff3d7bb4..f39e4325f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,9 @@ At the moment this project **does not** adhere to structure, and the `NodeInfoChanged` event were removed from the Staking Extension pallet. The `AttestationHandler` config type was added to the Staking Extension pallet. The `KeyProvider` and `AttestationQueue` config types were removed from the Attestation pallet. +- In [#1128](https://github.com/entropyxyz/entropy-core/pull/1128) mnemonics can no longer be passed + in via a command line argument, file, or environment variable. Instead they are randomly generated + internally. ### Changed - Use correct key rotation endpoint in OCW ([#1104](https://github.com/entropyxyz/entropy-core/pull/1104)) From 7bd43586629236e5877ab8b6f4c9c5a066343629 Mon Sep 17 00:00:00 2001 From: peg Date: Tue, 22 Oct 2024 11:49:57 +0200 Subject: [PATCH 3/7] Error handling --- crates/threshold-signature-server/src/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/threshold-signature-server/src/main.rs b/crates/threshold-signature-server/src/main.rs index 66d591709..ec00329b0 100644 --- a/crates/threshold-signature-server/src/main.rs +++ b/crates/threshold-signature-server/src/main.rs @@ -70,8 +70,8 @@ async fn main() { setup_mnemonic(&kv_store, development_mnemonic(&validator_name)).await } else if !has_mnemonic(&kv_store).await { let mut rng = rand::thread_rng(); - let mnemonic = - bip39::Mnemonic::generate_in_with(&mut rng, bip39::Language::English, 24).unwrap(); + let mnemonic = bip39::Mnemonic::generate_in_with(&mut rng, bip39::Language::English, 24) + .expect("Failed to generate mnemonic"); setup_mnemonic(&kv_store, mnemonic).await } From 4fcfc303c694d8f1806a5d99ceabe567dcb0fad7 Mon Sep 17 00:00:00 2001 From: peg Date: Wed, 23 Oct 2024 10:59:17 +0200 Subject: [PATCH 4/7] Add endpoint giving public keys --- crates/threshold-signature-server/src/lib.rs | 3 +- .../src/node_info/api.rs | 26 ++++++++++++-- .../src/node_info/errors.rs | 34 +++++++++++++++++++ .../src/node_info/mod.rs | 1 + .../src/node_info/tests.rs | 25 +++++++++++++- 5 files changed, 85 insertions(+), 4 deletions(-) create mode 100644 crates/threshold-signature-server/src/node_info/errors.rs diff --git a/crates/threshold-signature-server/src/lib.rs b/crates/threshold-signature-server/src/lib.rs index 7ea5b0734..ccfaf6e05 100644 --- a/crates/threshold-signature-server/src/lib.rs +++ b/crates/threshold-signature-server/src/lib.rs @@ -185,7 +185,7 @@ use crate::{ attestation::api::attest, health::api::healthz, launch::Configuration, - node_info::api::{hashes, version as get_version}, + node_info::api::{hashes, info, version as get_version}, r#unsafe::api::{delete, put, remove_keys, unsafe_get}, signing_client::{api::*, ListenerState}, user::api::*, @@ -217,6 +217,7 @@ pub fn app(app_state: AppState) -> Router { .route("/healthz", get(healthz)) .route("/version", get(get_version)) .route("/hashes", get(hashes)) + .route("/info", get(info)) .route("/ws", get(ws_handler)); // Unsafe routes are for testing purposes only diff --git a/crates/threshold-signature-server/src/node_info/api.rs b/crates/threshold-signature-server/src/node_info/api.rs index 23a0e1526..30b98b5b6 100644 --- a/crates/threshold-signature-server/src/node_info/api.rs +++ b/crates/threshold-signature-server/src/node_info/api.rs @@ -12,9 +12,13 @@ // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -use axum::Json; -use entropy_shared::types::HashingAlgorithm; +use crate::{get_signer_and_x25519_secret, node_info::errors::GetInfoError, AppState}; +use axum::{extract::State, Json}; +use entropy_shared::{types::HashingAlgorithm, X25519PublicKey}; +use serde::{Deserialize, Serialize}; +use sp_core::Pair; use strum::IntoEnumIterator; +use subxt::utils::AccountId32; /// Returns the version and commit data #[tracing::instrument] @@ -22,8 +26,26 @@ pub async fn version() -> String { format!("{}-{}", env!("CARGO_PKG_VERSION"), env!("VERGEN_GIT_DESCRIBE")) } +/// Lists the supported hashing algorithms #[tracing::instrument] pub async fn hashes() -> Json> { let hashing_algos = HashingAlgorithm::iter().collect::>(); Json(hashing_algos) } + +/// Public signing and encryption keys associated with a TS server +#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] +pub struct TssPublicKeys { + pub tss_account: AccountId32, + pub x25519_public_key: X25519PublicKey, +} + +/// Returns the TS server's public keys and HTTP endpoint +#[tracing::instrument(skip_all)] +pub async fn info(State(app_state): State) -> Result, GetInfoError> { + let (signer, x25519_secret) = get_signer_and_x25519_secret(&app_state.kv_store).await?; + let tss_account = AccountId32(signer.signer().public().0); + let x25519_public_key = x25519_dalek::PublicKey::from(&x25519_secret).as_bytes().clone(); + + Ok(Json(TssPublicKeys { x25519_public_key, tss_account })) +} diff --git a/crates/threshold-signature-server/src/node_info/errors.rs b/crates/threshold-signature-server/src/node_info/errors.rs new file mode 100644 index 000000000..9936634f9 --- /dev/null +++ b/crates/threshold-signature-server/src/node_info/errors.rs @@ -0,0 +1,34 @@ +// Copyright (C) 2023 Entropy Cryptography Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +use axum::{ + http::StatusCode, + response::{IntoResponse, Response}, +}; +use thiserror::Error; + +/// Errors for protocol execution +#[derive(Debug, Error)] +pub enum GetInfoError { + #[error("Could not get public keys: {0}")] + User(#[from] crate::user::errors::UserErr), +} + +impl IntoResponse for GetInfoError { + fn into_response(self) -> Response { + tracing::error!("{:?}", format!("{self}")); + let body = format!("{self}").into_bytes(); + (StatusCode::INTERNAL_SERVER_ERROR, body).into_response() + } +} diff --git a/crates/threshold-signature-server/src/node_info/mod.rs b/crates/threshold-signature-server/src/node_info/mod.rs index 5837e4d77..3dcdf1f61 100644 --- a/crates/threshold-signature-server/src/node_info/mod.rs +++ b/crates/threshold-signature-server/src/node_info/mod.rs @@ -15,6 +15,7 @@ //! Provides information about this instance of `entropy-tss` pub mod api; +mod errors; #[cfg(test)] mod tests; diff --git a/crates/threshold-signature-server/src/node_info/tests.rs b/crates/threshold-signature-server/src/node_info/tests.rs index ba851a702..2676ecb87 100644 --- a/crates/threshold-signature-server/src/node_info/tests.rs +++ b/crates/threshold-signature-server/src/node_info/tests.rs @@ -13,9 +13,13 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -use crate::helpers::tests::{initialize_test_logger, setup_client}; +use crate::{ + helpers::tests::{initialize_test_logger, setup_client}, + node_info::api::TssPublicKeys, +}; use entropy_kvdb::clean_tests; use entropy_shared::types::HashingAlgorithm; +use entropy_testing_utils::constants::{TSS_ACCOUNTS, X25519_PUBLIC_KEYS}; use serial_test::serial; #[tokio::test] @@ -55,3 +59,22 @@ async fn hashes_test() { ); clean_tests(); } + +#[tokio::test] +#[serial] +async fn info_test() { + clean_tests(); + initialize_test_logger().await; + setup_client().await; + let client = reqwest::Client::new(); + let response = client.get("http://127.0.0.1:3001/info").send().await.unwrap(); + let public_keys: TssPublicKeys = response.json().await.unwrap(); + assert_eq!( + public_keys, + TssPublicKeys { + tss_account: TSS_ACCOUNTS[0].clone(), + x25519_public_key: X25519_PUBLIC_KEYS[0], + } + ); + clean_tests(); +} From e1649a48098e2394d263922900d914e9e123e866 Mon Sep 17 00:00:00 2001 From: peg Date: Wed, 23 Oct 2024 11:05:01 +0200 Subject: [PATCH 5/7] Document new endpoint --- crates/threshold-signature-server/src/lib.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/crates/threshold-signature-server/src/lib.rs b/crates/threshold-signature-server/src/lib.rs index ccfaf6e05..c89b34795 100644 --- a/crates/threshold-signature-server/src/lib.rs +++ b/crates/threshold-signature-server/src/lib.rs @@ -110,6 +110,11 @@ //! http://127.0.0.1:3001/user/sign_tx //! ``` //! +//! ### For the node operator +//! +//! [`/info`](crate::node_info::api::info()) - Get - get a Json object of type +//! [crate::node_info::api::TssPublicKeys] which contains the TSS account ID and x25519 public key. +//! //! ### For the blockchain node //! //! ### For other instances of the threshold server From b12149b9d3a3e5f9a6e90636b07e99de5b6dd920 Mon Sep 17 00:00:00 2001 From: peg Date: Wed, 23 Oct 2024 11:07:01 +0200 Subject: [PATCH 6/7] Changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f39e4325f..96050c602 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,10 @@ At the moment this project **does not** adhere to in via a command line argument, file, or environment variable. Instead they are randomly generated internally. +### Added +- [#1128](https://github.com/entropyxyz/entropy-core/pull/1128) adds an `/info` route to `entropy-tss` + which can be used to get the TSS account ID and x25519 public key. + ### Changed - Use correct key rotation endpoint in OCW ([#1104](https://github.com/entropyxyz/entropy-core/pull/1104)) - Change attestation flow to be pull based ([#1109](https://github.com/entropyxyz/entropy-core/pull/1109/)) From 53d5175148b509b6668f7c21c9aa206b5d36d57e Mon Sep 17 00:00:00 2001 From: peg Date: Wed, 23 Oct 2024 12:25:41 +0200 Subject: [PATCH 7/7] Clippy --- crates/threshold-signature-server/src/node_info/api.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/threshold-signature-server/src/node_info/api.rs b/crates/threshold-signature-server/src/node_info/api.rs index 30b98b5b6..52aa40f73 100644 --- a/crates/threshold-signature-server/src/node_info/api.rs +++ b/crates/threshold-signature-server/src/node_info/api.rs @@ -45,7 +45,7 @@ pub struct TssPublicKeys { pub async fn info(State(app_state): State) -> Result, GetInfoError> { let (signer, x25519_secret) = get_signer_and_x25519_secret(&app_state.kv_store).await?; let tss_account = AccountId32(signer.signer().public().0); - let x25519_public_key = x25519_dalek::PublicKey::from(&x25519_secret).as_bytes().clone(); + let x25519_public_key = *x25519_dalek::PublicKey::from(&x25519_secret).as_bytes(); Ok(Json(TssPublicKeys { x25519_public_key, tss_account })) }