From 1de907a97d8b0dbf9b037f11ed3f05eb94f3d942 Mon Sep 17 00:00:00 2001
From: peg <ameba23@systemli.org>
Date: Tue, 22 Oct 2024 11:36:38 +0200
Subject: [PATCH 01/77] 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<bip39::Mnemonic>,
-
-    /// 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<PathBuf>,
 }
 
 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 <ameba23@systemli.org>
Date: Tue, 22 Oct 2024 11:49:51 +0200
Subject: [PATCH 02/77] 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 <ameba23@systemli.org>
Date: Tue, 22 Oct 2024 11:49:57 +0200
Subject: [PATCH 03/77] 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 <ameba23@systemli.org>
Date: Wed, 23 Oct 2024 10:59:17 +0200
Subject: [PATCH 04/77] 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 <https://www.gnu.org/licenses/>.
-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<Vec<HashingAlgorithm>> {
     let hashing_algos = HashingAlgorithm::iter().collect::<Vec<_>>();
     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<AppState>) -> Result<Json<TssPublicKeys>, 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 <https://www.gnu.org/licenses/>.
+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 <https://www.gnu.org/licenses/>.
 
-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 <ameba23@systemli.org>
Date: Wed, 23 Oct 2024 11:05:01 +0200
Subject: [PATCH 05/77] 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 <ameba23@systemli.org>
Date: Wed, 23 Oct 2024 11:07:01 +0200
Subject: [PATCH 06/77] 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 <ameba23@systemli.org>
Date: Wed, 23 Oct 2024 12:25:41 +0200
Subject: [PATCH 07/77] 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<AppState>) -> Result<Json<TssPublicKeys>, 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 }))
 }

From 1de6e81f815ba1ec9b9b825a2eeee6514e78c4e3 Mon Sep 17 00:00:00 2001
From: peg <ameba23@systemli.org>
Date: Thu, 5 Dec 2024 20:47:26 +0100
Subject: [PATCH 08/77] Fix lockfile

---
 Cargo.lock | 287 +++++++++++++++++++++++++++++++----------------------
 1 file changed, 170 insertions(+), 117 deletions(-)

diff --git a/Cargo.lock b/Cargo.lock
index f1a2f0ca8..088e0b920 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1,6 +1,6 @@
 # This file is automatically @generated by Cargo.
 # It is not intended for manual editing.
-version = 3
+version = 4
 
 [[package]]
 name = "Inflector"
@@ -86,7 +86,7 @@ dependencies = [
  "cipher 0.4.4",
  "ctr",
  "ghash",
- "subtle 2.5.0",
+ "subtle 2.6.1",
 ]
 
 [[package]]
@@ -215,9 +215,9 @@ dependencies = [
 
 [[package]]
 name = "anyhow"
-version = "1.0.93"
+version = "1.0.94"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775"
+checksum = "c1fd03a028ef38ba2276dce7e33fcd6369c158a1bca17946c4b1b701891c1ff7"
 
 [[package]]
 name = "approx"
@@ -836,7 +836,7 @@ dependencies = [
  "rand_core 0.6.4",
  "ripemd",
  "sha2 0.10.8",
- "subtle 2.5.0",
+ "subtle 2.6.1",
  "zeroize",
 ]
 
@@ -942,7 +942,7 @@ checksum = "23285ad32269793932e830392f2fe2f83e26488fd3ec778883a93c8323735780"
 dependencies = [
  "arrayref",
  "arrayvec 0.7.4",
- "constant_time_eq 0.3.0",
+ "constant_time_eq 0.3.1",
 ]
 
 [[package]]
@@ -953,20 +953,20 @@ checksum = "94230421e395b9920d23df13ea5d77a20e1725331f90fbbf6df6040b33f756ae"
 dependencies = [
  "arrayref",
  "arrayvec 0.7.4",
- "constant_time_eq 0.3.0",
+ "constant_time_eq 0.3.1",
 ]
 
 [[package]]
 name = "blake3"
-version = "1.5.4"
+version = "1.5.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d82033247fd8e890df8f740e407ad4d038debb9eb1f40533fffb32e7d17dc6f7"
+checksum = "b8ee0c1824c4dea5b5f81736aff91bae041d2c07ee1192bec91054e10e3e601e"
 dependencies = [
  "arrayref",
  "arrayvec 0.7.4",
  "cc",
  "cfg-if",
- "constant_time_eq 0.3.0",
+ "constant_time_eq 0.3.1",
 ]
 
 [[package]]
@@ -1072,9 +1072,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
 
 [[package]]
 name = "bytes"
-version = "1.8.0"
+version = "1.9.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da"
+checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b"
 dependencies = [
  "serde",
 ]
@@ -1301,9 +1301,9 @@ dependencies = [
 
 [[package]]
 name = "clap"
-version = "4.5.21"
+version = "4.5.22"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "fb3b4b9e5a7c7514dfa52869339ee98b3156b0bfb4e8a77c4ff4babb64b1604f"
+checksum = "69371e34337c4c984bbe322360c2547210bf632eb2814bbe78a6e87a2935bd2b"
 dependencies = [
  "clap_builder",
  "clap_derive",
@@ -1311,9 +1311,9 @@ dependencies = [
 
 [[package]]
 name = "clap_builder"
-version = "4.5.21"
+version = "4.5.22"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b17a95aa67cc7b5ebd32aa5370189aa0d79069ef1c64ce893bd30fb24bff20ec"
+checksum = "6e24c1b4099818523236a8ca881d2b45db98dadfb4625cf6608c12069fcbbde1"
 dependencies = [
  "anstream",
  "anstyle",
@@ -1458,9 +1458,9 @@ checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
 
 [[package]]
 name = "constant_time_eq"
-version = "0.3.0"
+version = "0.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2"
+checksum = "7c74b8349d32d297c9134b8c88677813a227df8f779daa29bfc29c183fe3dca6"
 
 [[package]]
 name = "constcat"
@@ -1780,8 +1780,20 @@ checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76"
 dependencies = [
  "generic-array 0.14.7",
  "rand_core 0.6.4",
- "serdect",
- "subtle 2.5.0",
+ "subtle 2.6.1",
+ "zeroize",
+]
+
+[[package]]
+name = "crypto-bigint"
+version = "0.6.0-rc.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d748d1f5b807ee6d0df5a548d0130417295c3aaed1dcbbb3d6a2e7106e11fcca"
+dependencies = [
+ "num-traits",
+ "rand_core 0.6.4",
+ "serdect 0.3.0-rc.0",
+ "subtle 2.6.1",
  "zeroize",
 ]
 
@@ -1813,7 +1825,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab"
 dependencies = [
  "generic-array 0.14.7",
- "subtle 2.5.0",
+ "subtle 2.6.1",
 ]
 
 [[package]]
@@ -1823,16 +1835,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "25fab6889090c8133f3deb8f73ba3c65a7f456f66436fc012a1b1e272b1e103e"
 dependencies = [
  "generic-array 0.14.7",
- "subtle 2.5.0",
+ "subtle 2.6.1",
 ]
 
 [[package]]
 name = "crypto-primes"
-version = "0.5.0"
+version = "0.6.0-pre.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "97a4eeb0a8686cfc94242c8860628627206a2b24148d6ab8f5d41c708438582f"
+checksum = "d9fad3f7645c77d3e0269f3e74a8dd25746de992b16bcecbb316059836e0b366"
 dependencies = [
- "crypto-bigint",
+ "crypto-bigint 0.6.0-rc.6",
  "rand_core 0.6.4",
 ]
 
@@ -1854,7 +1866,7 @@ dependencies = [
  "byteorder",
  "digest 0.9.0",
  "rand_core 0.5.1",
- "subtle 2.5.0",
+ "subtle 2.6.1",
  "zeroize",
 ]
 
@@ -1871,7 +1883,7 @@ dependencies = [
  "fiat-crypto",
  "platforms",
  "rustc_version",
- "subtle 2.5.0",
+ "subtle 2.6.1",
  "zeroize",
 ]
 
@@ -2193,7 +2205,7 @@ dependencies = [
  "block-buffer 0.10.4",
  "const-oid",
  "crypto-common",
- "subtle 2.5.0",
+ "subtle 2.6.1",
 ]
 
 [[package]]
@@ -2331,7 +2343,7 @@ dependencies = [
  "digest 0.10.7",
  "elliptic-curve",
  "rfc6979",
- "serdect",
+ "serdect 0.2.0",
  "signature",
  "spki",
 ]
@@ -2357,7 +2369,7 @@ dependencies = [
  "rand_core 0.6.4",
  "serde",
  "sha2 0.10.8",
- "subtle 2.5.0",
+ "subtle 2.6.1",
  "zeroize",
 ]
 
@@ -2403,7 +2415,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47"
 dependencies = [
  "base16ct",
- "crypto-bigint",
+ "crypto-bigint 0.5.5",
  "digest 0.10.7",
  "ff",
  "generic-array 0.14.7",
@@ -2412,8 +2424,8 @@ dependencies = [
  "pkcs8",
  "rand_core 0.6.4",
  "sec1",
- "serdect",
- "subtle 2.5.0",
+ "serdect 0.2.0",
+ "subtle 2.6.1",
  "zeroize",
 ]
 
@@ -2522,6 +2534,7 @@ dependencies = [
  "hex",
  "js-sys",
  "num",
+ "parity-scale-codec",
  "rand",
  "rand_core 0.6.4",
  "reqwest",
@@ -2534,7 +2547,7 @@ dependencies = [
  "subxt",
  "synedrion",
  "tdx-quote",
- "thiserror 2.0.3",
+ "thiserror 2.0.4",
  "tokio",
  "tracing",
  "x25519-dalek 2.0.1",
@@ -2569,7 +2582,7 @@ dependencies = [
  "sled",
  "sp-core 31.0.0",
  "synedrion",
- "thiserror 2.0.3",
+ "thiserror 2.0.4",
  "tokio",
  "tracing",
  "zeroize",
@@ -2577,8 +2590,9 @@ dependencies = [
 
 [[package]]
 name = "entropy-programs-core"
-version = "0.10.0"
-source = "git+https://github.com/entropyxyz/programs.git?branch=master#c689ad3d8114eff88bc646d9b200c71f6e14f436"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e9a92d75d04917995a04398bc9bb57971c1d9ce4fe7b0460fe12fe10213a2c9c"
 dependencies = [
  "getrandom 0.2.15",
  "serde",
@@ -2588,8 +2602,9 @@ dependencies = [
 
 [[package]]
 name = "entropy-programs-runtime"
-version = "0.10.0"
-source = "git+https://github.com/entropyxyz/programs.git?branch=master#c689ad3d8114eff88bc646d9b200c71f6e14f436"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1d5617ea52abc9c4eed998053a2dcf163f926224a1302fb75d4cc31ae99e700"
 dependencies = [
  "entropy-programs-core",
  "thiserror 1.0.68",
@@ -2627,7 +2642,7 @@ dependencies = [
  "sp-keyring 34.0.0",
  "subxt",
  "synedrion",
- "thiserror 2.0.3",
+ "thiserror 2.0.4",
  "tokio",
  "tokio-tungstenite",
  "tracing",
@@ -2753,6 +2768,7 @@ dependencies = [
  "entropy-client",
  "entropy-shared",
  "hex",
+ "reqwest",
  "serde",
  "serde_json",
  "sp-core 31.0.0",
@@ -2785,7 +2801,7 @@ dependencies = [
  "tdx-quote",
  "tokio",
  "tracing",
- "tracing-subscriber 0.3.18",
+ "tracing-subscriber 0.3.19",
 ]
 
 [[package]]
@@ -2842,14 +2858,14 @@ dependencies = [
  "subxt-signer",
  "synedrion",
  "tdx-quote",
- "thiserror 2.0.3",
+ "thiserror 2.0.4",
  "tokio",
  "tokio-tungstenite",
- "tower-http 0.6.1",
+ "tower-http 0.6.2",
  "tracing",
  "tracing-bunyan-formatter",
  "tracing-loki",
- "tracing-subscriber 0.3.18",
+ "tracing-subscriber 0.3.19",
  "uuid",
  "vergen",
  "x25519-dalek 2.0.1",
@@ -3109,7 +3125,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449"
 dependencies = [
  "rand_core 0.6.4",
- "subtle 2.5.0",
+ "subtle 2.6.1",
 ]
 
 [[package]]
@@ -4050,7 +4066,7 @@ checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63"
 dependencies = [
  "ff",
  "rand_core 0.6.4",
- "subtle 2.5.0",
+ "subtle 2.6.1",
 ]
 
 [[package]]
@@ -4856,10 +4872,11 @@ dependencies = [
 
 [[package]]
 name = "js-sys"
-version = "0.3.72"
+version = "0.3.74"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9"
+checksum = "a865e038f7f6ed956f788f0d7d60c541fff74c7bd74272c5d4cf15c63743e705"
 dependencies = [
+ "once_cell",
  "wasm-bindgen",
 ]
 
@@ -5109,7 +5126,7 @@ dependencies = [
  "ecdsa",
  "elliptic-curve",
  "once_cell",
- "serdect",
+ "serdect 0.2.0",
  "sha2 0.10.8",
 ]
 
@@ -5649,7 +5666,7 @@ checksum = "5be9b9bb642d8522a44d533eab56c16c738301965504753b03ad1de3425d5451"
 dependencies = [
  "crunchy",
  "digest 0.9.0",
- "subtle 2.5.0",
+ "subtle 2.6.1",
 ]
 
 [[package]]
@@ -6045,7 +6062,7 @@ dependencies = [
  "rand",
  "rand_chacha 0.3.1",
  "rand_distr",
- "subtle 2.5.0",
+ "subtle 2.6.1",
  "thiserror 1.0.68",
  "zeroize",
 ]
@@ -7240,6 +7257,7 @@ dependencies = [
  "frame-system",
  "log",
  "pallet-balances",
+ "pallet-oracle",
  "parity-scale-codec",
  "scale-info",
  "sp-core 29.0.0",
@@ -7264,6 +7282,7 @@ dependencies = [
  "pallet-babe",
  "pallet-bags-list",
  "pallet-balances",
+ "pallet-oracle",
  "pallet-parameters",
  "pallet-programs",
  "pallet-registry",
@@ -7330,6 +7349,7 @@ dependencies = [
  "pallet-authorship",
  "pallet-bags-list",
  "pallet-balances",
+ "pallet-oracle",
  "pallet-parameters",
  "pallet-programs",
  "pallet-session",
@@ -7573,6 +7593,7 @@ dependencies = [
  "frame-support 29.0.2",
  "frame-system",
  "pallet-balances",
+ "pallet-oracle",
  "pallet-programs",
  "parity-scale-codec",
  "scale-info",
@@ -7741,9 +7762,9 @@ dependencies = [
 
 [[package]]
 name = "parity-scale-codec"
-version = "3.6.12"
+version = "3.7.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "306800abfa29c7f16596b5970a588435e3d5b3149683d00c12b699cc19f895ee"
+checksum = "8be4817d39f3272f69c59fe05d0535ae6456c2dc2fa1ba02910296c7e0a5c590"
 dependencies = [
  "arrayvec 0.7.4",
  "bitvec",
@@ -7751,6 +7772,7 @@ dependencies = [
  "bytes",
  "impl-trait-for-tuples",
  "parity-scale-codec-derive",
+ "rustversion",
  "serde",
 ]
 
@@ -7846,7 +7868,7 @@ checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166"
 dependencies = [
  "base64ct",
  "rand_core 0.6.4",
- "subtle 2.5.0",
+ "subtle 2.6.1",
 ]
 
 [[package]]
@@ -8934,7 +8956,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2"
 dependencies = [
  "hmac 0.12.1",
- "subtle 2.5.0",
+ "subtle 2.6.1",
 ]
 
 [[package]]
@@ -9041,7 +9063,7 @@ dependencies = [
  "rand_core 0.6.4",
  "signature",
  "spki",
- "subtle 2.5.0",
+ "subtle 2.6.1",
  "zeroize",
 ]
 
@@ -9167,7 +9189,7 @@ dependencies = [
  "ring 0.17.8",
  "rustls-pki-types",
  "rustls-webpki 0.102.2",
- "subtle 2.5.0",
+ "subtle 2.6.1",
  "zeroize",
 ]
 
@@ -9180,7 +9202,7 @@ dependencies = [
  "once_cell",
  "rustls-pki-types",
  "rustls-webpki 0.102.2",
- "subtle 2.5.0",
+ "subtle 2.6.1",
  "zeroize",
 ]
 
@@ -11281,9 +11303,9 @@ dependencies = [
 
 [[package]]
 name = "scale-info"
-version = "2.11.5"
+version = "2.11.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1aa7ffc1c0ef49b0452c6e2986abf2b07743320641ffd5fc63d552458e3b779b"
+checksum = "346a3b32eba2640d17a9cb5927056b08f3de90f65b72fe09402c2ad07d684d0b"
 dependencies = [
  "bitvec",
  "cfg-if",
@@ -11295,9 +11317,9 @@ dependencies = [
 
 [[package]]
 name = "scale-info-derive"
-version = "2.11.5"
+version = "2.11.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "46385cc24172cf615450267463f937c10072516359b3ff1cb24228a4a08bf951"
+checksum = "c6630024bf739e2179b91fb424b28898baf819414262c5d376677dbff1fe7ebf"
 dependencies = [
  "proc-macro-crate 3.1.0",
  "proc-macro2",
@@ -11417,7 +11439,7 @@ dependencies = [
  "rand_core 0.6.4",
  "serde_bytes",
  "sha2 0.10.8",
- "subtle 2.5.0",
+ "subtle 2.6.1",
  "zeroize",
 ]
 
@@ -11471,8 +11493,8 @@ dependencies = [
  "der",
  "generic-array 0.14.7",
  "pkcs8",
- "serdect",
- "subtle 2.5.0",
+ "serdect 0.2.0",
+ "subtle 2.6.1",
  "zeroize",
 ]
 
@@ -11505,9 +11527,9 @@ dependencies = [
 
 [[package]]
 name = "secrecy"
-version = "0.9.0-pre.0"
+version = "0.10.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8e480ff13eb421e9c0201c7d8e17fe61ad336a38701989e91f8d0523001cd2fa"
+checksum = "e891af845473308773346dc847b2c23ee78fe442e0472ac50e22a18a93d3ae5a"
 dependencies = [
  "serde",
  "zeroize",
@@ -11581,6 +11603,17 @@ dependencies = [
  "serde_derive",
 ]
 
+[[package]]
+name = "serde-encoded-bytes"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec007ca0e3db940a5409d65780b6bd0202cbea68800861ae876b80655ee8e24b"
+dependencies = [
+ "base64 0.21.7",
+ "hex",
+ "serde",
+]
+
 [[package]]
 name = "serde_bytes"
 version = "0.11.14"
@@ -11665,6 +11698,16 @@ dependencies = [
  "serde",
 ]
 
+[[package]]
+name = "serdect"
+version = "0.3.0-rc.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a504c8ee181e3e594d84052f983d60afe023f4d94d050900be18062bbbf7b58"
+dependencies = [
+ "base16ct",
+ "serde",
+]
+
 [[package]]
 name = "serial_test"
 version = "3.2.0"
@@ -11988,7 +12031,7 @@ dependencies = [
  "ring 0.17.8",
  "rustc_version",
  "sha2 0.10.8",
- "subtle 2.5.0",
+ "subtle 2.6.1",
 ]
 
 [[package]]
@@ -13767,7 +13810,7 @@ dependencies = [
  "bitflags 1.3.2",
  "byteorder",
  "keccak",
- "subtle 2.5.0",
+ "subtle 2.6.1",
  "zeroize",
 ]
 
@@ -13946,9 +13989,9 @@ checksum = "2d67a5a62ba6e01cb2192ff309324cb4875d0c451d55fe2319433abe7a05a8ee"
 
 [[package]]
 name = "subtle"
-version = "2.5.0"
+version = "2.6.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
+checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
 
 [[package]]
 name = "subxt"
@@ -14143,23 +14186,22 @@ dependencies = [
 
 [[package]]
 name = "synedrion"
-version = "0.2.0-beta.0"
+version = "0.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d68ecdc79fc2be671be2abe5391d25b38fca840e18665ef2a177aa925f6740ac"
+checksum = "a619936bb0dd5fa8f8e79c703590c6a10be9e2160b61a2e85484f9f053a3c5b0"
 dependencies = [
- "base64 0.21.7",
  "bincode 2.0.0-rc.3",
  "bip32",
- "crypto-bigint",
+ "crypto-bigint 0.6.0-rc.6",
  "crypto-primes",
  "digest 0.10.7",
  "displaydoc",
  "hashing-serializer",
- "hex",
  "k256",
  "rand_core 0.6.4",
- "secrecy 0.9.0-pre.0",
+ "secrecy 0.10.3",
  "serde",
+ "serde-encoded-bytes",
  "sha2 0.10.8",
  "sha3",
  "signature",
@@ -14292,11 +14334,11 @@ dependencies = [
 
 [[package]]
 name = "thiserror"
-version = "2.0.3"
+version = "2.0.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c006c85c7651b3cf2ada4584faa36773bd07bac24acfb39f3c431b36d7e667aa"
+checksum = "2f49a1853cf82743e3b7950f77e0f4d622ca36cf4317cba00c767838bac8d490"
 dependencies = [
- "thiserror-impl 2.0.3",
+ "thiserror-impl 2.0.4",
 ]
 
 [[package]]
@@ -14312,9 +14354,9 @@ dependencies = [
 
 [[package]]
 name = "thiserror-impl"
-version = "2.0.3"
+version = "2.0.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568"
+checksum = "8381894bb3efe0c4acac3ded651301ceee58a15d47c2e34885ed1908ad667061"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -14415,9 +14457,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
 
 [[package]]
 name = "tokio"
-version = "1.41.1"
+version = "1.42.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33"
+checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551"
 dependencies = [
  "backtrace",
  "bytes",
@@ -14651,9 +14693,9 @@ dependencies = [
 
 [[package]]
 name = "tower-http"
-version = "0.6.1"
+version = "0.6.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8437150ab6bbc8c5f0f519e3d5ed4aa883a83dd4cdd3d1b21f9482936046cb97"
+checksum = "403fa3b783d4b626a8ad51d766ab03cb6d2dbfc46b1c5d4448395e6628dc9697"
 dependencies = [
  "bitflags 2.5.0",
  "bytes",
@@ -14679,9 +14721,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
 
 [[package]]
 name = "tracing"
-version = "0.1.40"
+version = "0.1.41"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
+checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
 dependencies = [
  "log",
  "pin-project-lite 0.2.14",
@@ -14691,9 +14733,9 @@ dependencies = [
 
 [[package]]
 name = "tracing-attributes"
-version = "0.1.27"
+version = "0.1.28"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
+checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -14702,9 +14744,9 @@ dependencies = [
 
 [[package]]
 name = "tracing-bunyan-formatter"
-version = "0.3.9"
+version = "0.3.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b5c266b9ac83dedf0e0385ad78514949e6d89491269e7065bee51d2bb8ec7373"
+checksum = "2d637245a0d8774bd48df6482e086c59a8b5348a910c3b0579354045a9d82411"
 dependencies = [
  "ahash 0.8.11",
  "gethostname",
@@ -14715,14 +14757,14 @@ dependencies = [
  "tracing",
  "tracing-core",
  "tracing-log 0.1.4",
- "tracing-subscriber 0.3.18",
+ "tracing-subscriber 0.3.19",
 ]
 
 [[package]]
 name = "tracing-core"
-version = "0.1.32"
+version = "0.1.33"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
+checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c"
 dependencies = [
  "once_cell",
  "valuable",
@@ -14776,8 +14818,8 @@ dependencies = [
  "tracing",
  "tracing-core",
  "tracing-log 0.1.4",
- "tracing-serde",
- "tracing-subscriber 0.3.18",
+ "tracing-serde 0.1.3",
+ "tracing-subscriber 0.3.19",
  "url",
 ]
 
@@ -14791,6 +14833,16 @@ dependencies = [
  "tracing-core",
 ]
 
+[[package]]
+name = "tracing-serde"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1"
+dependencies = [
+ "serde",
+ "tracing-core",
+]
+
 [[package]]
 name = "tracing-subscriber"
 version = "0.2.25"
@@ -14811,14 +14863,14 @@ dependencies = [
  "tracing",
  "tracing-core",
  "tracing-log 0.1.4",
- "tracing-serde",
+ "tracing-serde 0.1.3",
 ]
 
 [[package]]
 name = "tracing-subscriber"
-version = "0.3.18"
+version = "0.3.19"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b"
+checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008"
 dependencies = [
  "matchers 0.1.0",
  "nu-ansi-term",
@@ -14832,7 +14884,7 @@ dependencies = [
  "tracing",
  "tracing-core",
  "tracing-log 0.2.0",
- "tracing-serde",
+ "tracing-serde 0.2.0",
 ]
 
 [[package]]
@@ -15067,7 +15119,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8326b2c654932e3e4f9196e69d08fdf7cfd718e1dc6f66b347e6024a0c961402"
 dependencies = [
  "generic-array 0.14.7",
- "subtle 2.5.0",
+ "subtle 2.6.1",
 ]
 
 [[package]]
@@ -15077,7 +15129,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea"
 dependencies = [
  "crypto-common",
- "subtle 2.5.0",
+ "subtle 2.6.1",
 ]
 
 [[package]]
@@ -15229,9 +15281,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
 
 [[package]]
 name = "wasm-bindgen"
-version = "0.2.95"
+version = "0.2.97"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e"
+checksum = "d15e63b4482863c109d70a7b8706c1e364eb6ea449b201a76c5b89cedcec2d5c"
 dependencies = [
  "cfg-if",
  "once_cell",
@@ -15240,9 +15292,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-backend"
-version = "0.2.95"
+version = "0.2.97"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358"
+checksum = "8d36ef12e3aaca16ddd3f67922bc63e48e953f126de60bd33ccc0101ef9998cd"
 dependencies = [
  "bumpalo",
  "log",
@@ -15277,21 +15329,22 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-futures"
-version = "0.4.45"
+version = "0.4.47"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b"
+checksum = "9dfaf8f50e5f293737ee323940c7d8b08a66a95a419223d9f41610ca08b0833d"
 dependencies = [
  "cfg-if",
  "js-sys",
+ "once_cell",
  "wasm-bindgen",
  "web-sys",
 ]
 
 [[package]]
 name = "wasm-bindgen-macro"
-version = "0.2.95"
+version = "0.2.97"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56"
+checksum = "705440e08b42d3e4b36de7d66c944be628d579796b8090bfa3471478a2260051"
 dependencies = [
  "quote",
  "wasm-bindgen-macro-support",
@@ -15299,9 +15352,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-macro-support"
-version = "0.2.95"
+version = "0.2.97"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68"
+checksum = "98c9ae5a76e46f4deecd0f0255cc223cfa18dc9b261213b8aa0c7b36f61b3f1d"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -15312,9 +15365,9 @@ dependencies = [
 
 [[package]]
 name = "wasm-bindgen-shared"
-version = "0.2.95"
+version = "0.2.97"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d"
+checksum = "6ee99da9c5ba11bd675621338ef6fa52296b76b83305e9b6e5c77d4c286d6d49"
 
 [[package]]
 name = "wasm-encoder"
@@ -16038,9 +16091,9 @@ dependencies = [
 
 [[package]]
 name = "web-sys"
-version = "0.3.69"
+version = "0.3.74"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef"
+checksum = "a98bc3c33f0fe7e59ad7cd041b89034fa82a7c2d4365ca538dda6cdaf513863c"
 dependencies = [
  "js-sys",
  "wasm-bindgen",

From 8a8cb527d25375c34e0c3ca467ccd2ba471de6a1 Mon Sep 17 00:00:00 2001
From: peg <ameba23@systemli.org>
Date: Fri, 13 Dec 2024 10:33:20 +0100
Subject: [PATCH 09/77] Add keys to appstate

---
 crates/threshold-signature-server/src/lib.rs | 18 +++++++++++++++++-
 1 file changed, 17 insertions(+), 1 deletion(-)

diff --git a/crates/threshold-signature-server/src/lib.rs b/crates/threshold-signature-server/src/lib.rs
index 5cd01cb49..f7d888f1d 100644
--- a/crates/threshold-signature-server/src/lib.rs
+++ b/crates/threshold-signature-server/src/lib.rs
@@ -176,11 +176,14 @@ use axum::{
     Router,
 };
 use entropy_kvdb::kv_manager::KvManager;
+use rand_core::OsRng;
+use sp_core::{sr25519, Pair};
 use tower_http::{
     cors::{Any, CorsLayer},
     trace::{self, TraceLayer},
 };
 use tracing::Level;
+use x25519_dalek::StaticSecret;
 
 pub use crate::helpers::{
     launch,
@@ -200,13 +203,26 @@ use crate::{
 #[derive(Clone)]
 pub struct AppState {
     listener_state: ListenerState,
+    pair: sr25519::Pair,
+    x25519_secret: StaticSecret,
+    x25519_public_key: [u8; 32],
     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 }
+        let (pair, _seed) = sr25519::Pair::generate();
+        let x25519_secret = StaticSecret::random_from_rng(&mut OsRng);
+        let x25519_public_key = x25519_dalek::PublicKey::from(&x25519_secret).to_bytes();
+        Self {
+            pair,
+            x25519_secret,
+            x25519_public_key,
+            listener_state: ListenerState::default(),
+            configuration,
+            kv_store,
+        }
     }
 }
 

From 542849eb2967447c96e3f998c86504460f2d10b5 Mon Sep 17 00:00:00 2001
From: peg <ameba23@systemli.org>
Date: Fri, 13 Dec 2024 11:05:56 +0100
Subject: [PATCH 10/77] Rm persisted TSS keys

---
 .../src/attestation/api.rs                    |   6 +-
 .../src/helpers/launch.rs                     | 104 +++++++-----------
 .../src/helpers/signing.rs                    |   5 +-
 .../src/helpers/validator.rs                  |   6 +-
 crates/threshold-signature-server/src/lib.rs  |  24 +++-
 crates/threshold-signature-server/src/main.rs |  11 +-
 6 files changed, 66 insertions(+), 90 deletions(-)

diff --git a/crates/threshold-signature-server/src/attestation/api.rs b/crates/threshold-signature-server/src/attestation/api.rs
index d043e149a..4883e4da1 100644
--- a/crates/threshold-signature-server/src/attestation/api.rs
+++ b/crates/threshold-signature-server/src/attestation/api.rs
@@ -46,7 +46,8 @@ pub async fn attest(
     State(app_state): State<AppState>,
     input: Bytes,
 ) -> Result<StatusCode, AttestationErr> {
-    let (signer, x25519_secret) = get_signer_and_x25519_secret(&app_state.kv_store).await?;
+    let signer = app_state.signer;
+    let x25519_secret = app_state.x25519_secret;
     let attestation_requests = OcwMessageAttestationRequest::decode(&mut input.as_ref())?;
 
     let api = get_api(&app_state.configuration.endpoint).await?;
@@ -94,7 +95,8 @@ pub async fn get_attest(
     State(app_state): State<AppState>,
     Query(context_querystring): Query<QuoteContextQuery>,
 ) -> Result<(StatusCode, Vec<u8>), AttestationErr> {
-    let (signer, x25519_secret) = get_signer_and_x25519_secret(&app_state.kv_store).await?;
+    let signer = app_state.signer;
+    let x25519_secret = app_state.x25519_secret;
     let api = get_api(&app_state.configuration.endpoint).await?;
     let rpc = get_rpc(&app_state.configuration.endpoint).await?;
 
diff --git a/crates/threshold-signature-server/src/helpers/launch.rs b/crates/threshold-signature-server/src/helpers/launch.rs
index 260fbdb47..6ab065f73 100644
--- a/crates/threshold-signature-server/src/helpers/launch.rs
+++ b/crates/threshold-signature-server/src/helpers/launch.rs
@@ -236,73 +236,43 @@ pub fn development_mnemonic(validator_name: &Option<ValidatorName>) -> bip39::Mn
         .expect("Unable to parse given mnemonic.")
 }
 
-pub async fn setup_mnemonic(kv: &KvManager, mnemonic: bip39::Mnemonic) {
-    if has_mnemonic(kv).await {
-        tracing::warn!("Deleting account related keys from KVDB.");
-
-        kv.kv()
-            .delete(FORBIDDEN_KEY_MNEMONIC)
-            .await
-            .expect("Error deleting existing mnemonic from KVDB.");
-        kv.kv()
-            .delete(FORBIDDEN_KEY_SHARED_SECRET)
-            .await
-            .expect("Error deleting shared secret from KVDB.");
-        kv.kv()
-            .delete(FORBIDDEN_KEY_DIFFIE_HELLMAN_PUBLIC)
-            .await
-            .expect("Error deleting X25519 public key from KVDB.");
-    }
-
-    tracing::info!("Writing new mnemonic to KVDB.");
-
-    // Write our new mnemonic to the KVDB.
-    let reservation = kv
-        .kv()
-        .reserve_key(FORBIDDEN_KEY_MNEMONIC.to_string())
-        .await
-        .expect("Issue reserving mnemonic");
-    kv.kv()
-        .put(reservation, mnemonic.to_string().as_bytes().to_vec())
-        .await
-        .expect("failed to update mnemonic");
-
-    let (pair, static_secret) =
-        get_signer_and_x25519_secret(kv).await.expect("Cannot derive keypairs");
-    let x25519_public_key = x25519_dalek::PublicKey::from(&static_secret).to_bytes();
-
-    // Write the shared secret in the KVDB
-    let shared_secret_reservation = kv
-        .kv()
-        .reserve_key(FORBIDDEN_KEY_SHARED_SECRET.to_string())
-        .await
-        .expect("Issue reserving ss key");
-    kv.kv()
-        .put(shared_secret_reservation, static_secret.to_bytes().to_vec())
-        .await
-        .expect("failed to update secret share");
-
-    // Write the Diffie-Hellman key in the KVDB
-    let diffie_hellman_reservation = kv
-        .kv()
-        .reserve_key(FORBIDDEN_KEY_DIFFIE_HELLMAN_PUBLIC.to_string())
-        .await
-        .expect("Issue reserving DH key");
-
-    kv.kv()
-        .put(diffie_hellman_reservation, x25519_public_key.to_vec())
-        .await
-        .expect("failed to update dh");
-
-    // Now we write the TSS AccountID and X25519 public key to files for convenience reasons.
-    let formatted_dh_public = format!("{x25519_public_key:?}").replace('"', "");
-    fs::write(".entropy/public_key", formatted_dh_public).expect("Failed to write public key file");
-
-    let id = AccountId32::new(pair.signer().public().0);
-    fs::write(".entropy/account_id", format!("{id}")).expect("Failed to write account_id file");
-
-    tracing::debug!("Starting process with account ID: `{id}`");
-}
+// pub async fn setup_mnemonic(mnemonic: bip39::Mnemonic) -> (sr25519::Pair, StaticSecret) {
+//     let (pair, static_secret) =
+//         get_signer_and_x25519_secret(kv).await.expect("Cannot derive keypairs");
+//     let x25519_public_key = x25519_dalek::PublicKey::from(&static_secret).to_bytes();
+//
+//     // Write the shared secret in the KVDB
+//     let shared_secret_reservation = kv
+//         .kv()
+//         .reserve_key(FORBIDDEN_KEY_SHARED_SECRET.to_string())
+//         .await
+//         .expect("Issue reserving ss key");
+//     kv.kv()
+//         .put(shared_secret_reservation, static_secret.to_bytes().to_vec())
+//         .await
+//         .expect("failed to update secret share");
+//
+//     // Write the Diffie-Hellman key in the KVDB
+//     let diffie_hellman_reservation = kv
+//         .kv()
+//         .reserve_key(FORBIDDEN_KEY_DIFFIE_HELLMAN_PUBLIC.to_string())
+//         .await
+//         .expect("Issue reserving DH key");
+//
+//     kv.kv()
+//         .put(diffie_hellman_reservation, x25519_public_key.to_vec())
+//         .await
+//         .expect("failed to update dh");
+//
+//     // Now we write the TSS AccountID and X25519 public key to files for convenience reasons.
+//     let formatted_dh_public = format!("{x25519_public_key:?}").replace('"', "");
+//     fs::write(".entropy/public_key", formatted_dh_public).expect("Failed to write public key file");
+//
+//     let id = AccountId32::new(pair.signer().public().0);
+//     fs::write(".entropy/account_id", format!("{id}")).expect("Failed to write account_id file");
+//
+//     tracing::debug!("Starting process with account ID: `{id}`");
+// }
 
 pub async fn threshold_account_id(kv: &KvManager) -> String {
     let mnemonic = kv.kv().get(FORBIDDEN_KEY_MNEMONIC).await.expect("Issue getting mnemonic");
diff --git a/crates/threshold-signature-server/src/helpers/signing.rs b/crates/threshold-signature-server/src/helpers/signing.rs
index 609dde2f2..3b1c9d06e 100644
--- a/crates/threshold-signature-server/src/helpers/signing.rs
+++ b/crates/threshold-signature-server/src/helpers/signing.rs
@@ -54,9 +54,8 @@ pub async fn do_signing(
 
     let info = SignInit::new(relayer_signature_request.clone(), signing_session_info.clone());
     let signing_service = ThresholdSigningService::new(state, kv_manager);
-    let (pair_signer, x25519_secret_key) = get_signer_and_x25519_secret(kv_manager)
-        .await
-        .map_err(|e| ProtocolErr::UserError(e.to_string()))?;
+    let pair_signer = &app_state.signer;
+    let x25519_secret_key = &app_state.x25519_secret;
     let signer = pair_signer.signer();
 
     let account_id = AccountId32(signer.public().0);
diff --git a/crates/threshold-signature-server/src/helpers/validator.rs b/crates/threshold-signature-server/src/helpers/validator.rs
index 3811b0414..969662228 100644
--- a/crates/threshold-signature-server/src/helpers/validator.rs
+++ b/crates/threshold-signature-server/src/helpers/validator.rs
@@ -43,10 +43,10 @@ pub async fn get_signer(
 
 /// Get the PairSigner as above, and also the x25519 encryption keypair for
 /// this threshold server
-pub async fn get_signer_and_x25519_secret(
-    kv: &KvManager,
+pub fn get_signer_and_x25519_secret(
+    mnemonic: &str,
 ) -> Result<(PairSigner<EntropyConfig, sr25519::Pair>, StaticSecret), UserErr> {
-    let hkdf = get_hkdf(kv).await?;
+    let hkdf = get_hkdf_from_mnemonic(mnemonic)?;
     let pair_signer = get_signer_from_hkdf(&hkdf)?;
     let static_secret = get_x25519_secret_from_hkdf(&hkdf)?;
     Ok((pair_signer, static_secret))
diff --git a/crates/threshold-signature-server/src/lib.rs b/crates/threshold-signature-server/src/lib.rs
index f7d888f1d..af0ce8934 100644
--- a/crates/threshold-signature-server/src/lib.rs
+++ b/crates/threshold-signature-server/src/lib.rs
@@ -178,6 +178,7 @@ use axum::{
 use entropy_kvdb::kv_manager::KvManager;
 use rand_core::OsRng;
 use sp_core::{sr25519, Pair};
+use subxt::tx::PairSigner;
 use tower_http::{
     cors::{Any, CorsLayer},
     trace::{self, TraceLayer},
@@ -191,8 +192,9 @@ pub use crate::helpers::{
 };
 use crate::{
     attestation::api::{attest, get_attest},
+    chain_api::EntropyConfig,
     health::api::healthz,
-    launch::Configuration,
+    launch::{development_mnemonic, Configuration, ValidatorName},
     node_info::api::{hashes, info, version as get_version},
     r#unsafe::api::{delete, put, remove_keys, unsafe_get},
     signing_client::{api::*, ListenerState},
@@ -203,7 +205,7 @@ use crate::{
 #[derive(Clone)]
 pub struct AppState {
     listener_state: ListenerState,
-    pair: sr25519::Pair,
+    signer: PairSigner<EntropyConfig, sr25519::Pair>,
     x25519_secret: StaticSecret,
     x25519_public_key: [u8; 32],
     pub configuration: Configuration,
@@ -211,10 +213,22 @@ pub struct AppState {
 }
 
 impl AppState {
-    pub fn new(configuration: Configuration, kv_store: KvManager) -> Self {
-        let (pair, _seed) = sr25519::Pair::generate();
-        let x25519_secret = StaticSecret::random_from_rng(&mut OsRng);
+    pub fn new(
+        configuration: Configuration,
+        kv_store: KvManager,
+        validator_name: &ValidatorName,
+    ) -> Self {
+        let (pair, x25519_secret) = if cfg!(test) || validator_name.is_some() {
+            get_signer_and_x25519_secret(development_mnemonic(&validator_name))
+        } else {
+            let (pair, _seed) = sr25519::Pair::generate();
+            let x25519_secret = StaticSecret::random_from_rng(&mut OsRng);
+            (pair, x25519_secret)
+        };
+
+        let signer = PairSigner::<EntropyConfig, sr25519::Pair>::new(pair);
         let x25519_public_key = x25519_dalek::PublicKey::from(&x25519_secret).to_bytes();
+
         Self {
             pair,
             x25519_secret,
diff --git a/crates/threshold-signature-server/src/main.rs b/crates/threshold-signature-server/src/main.rs
index ec00329b0..0df52d3a8 100644
--- a/crates/threshold-signature-server/src/main.rs
+++ b/crates/threshold-signature-server/src/main.rs
@@ -64,16 +64,7 @@ async fn main() {
 
     let kv_store = load_kv_store(&validator_name, args.password_file).await;
 
-    let app_state = AppState::new(configuration.clone(), kv_store.clone());
-
-    if cfg!(test) || validator_name.is_some() {
-        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)
-            .expect("Failed to generate mnemonic");
-        setup_mnemonic(&kv_store, mnemonic).await
-    }
+    let app_state = AppState::new(configuration.clone(), kv_store.clone(), &validator_name);
 
     setup_latest_block_number(&kv_store).await.expect("Issue setting up Latest Block Number");
 

From 15d3bbe21a6d6fad2ebf2dabe593cccb87c4afe3 Mon Sep 17 00:00:00 2001
From: peg <ameba23@systemli.org>
Date: Fri, 13 Dec 2024 14:00:22 +0100
Subject: [PATCH 11/77] Tidy following app state change

---
 .../src/attestation/api.rs                    | 17 +++----
 .../src/helpers/launch.rs                     | 40 -----------------
 .../src/helpers/signing.rs                    |  4 +-
 .../src/helpers/validator.rs                  | 42 ++++++------------
 crates/threshold-signature-server/src/lib.rs  | 21 ++++-----
 crates/threshold-signature-server/src/main.rs |  4 +-
 .../src/node_info/api.rs                      |  9 ++--
 .../src/signing_client/api.rs                 | 11 ++---
 .../src/signing_client/protocol_transport.rs  |  7 +--
 .../src/user/api.rs                           | 37 +++++++---------
 .../src/validator/api.rs                      | 44 ++++++++-----------
 11 files changed, 76 insertions(+), 160 deletions(-)

diff --git a/crates/threshold-signature-server/src/attestation/api.rs b/crates/threshold-signature-server/src/attestation/api.rs
index 4883e4da1..614d4e01d 100644
--- a/crates/threshold-signature-server/src/attestation/api.rs
+++ b/crates/threshold-signature-server/src/attestation/api.rs
@@ -16,7 +16,6 @@
 use crate::{
     attestation::errors::AttestationErr,
     chain_api::{entropy, get_api, get_rpc, EntropyConfig},
-    get_signer_and_x25519_secret,
     helpers::{
         launch::LATEST_BLOCK_NUMBER_ATTEST,
         substrate::{query_chain, submit_transaction},
@@ -46,8 +45,6 @@ pub async fn attest(
     State(app_state): State<AppState>,
     input: Bytes,
 ) -> Result<StatusCode, AttestationErr> {
-    let signer = app_state.signer;
-    let x25519_secret = app_state.x25519_secret;
     let attestation_requests = OcwMessageAttestationRequest::decode(&mut input.as_ref())?;
 
     let api = get_api(&app_state.configuration.endpoint).await?;
@@ -60,7 +57,7 @@ pub async fn attest(
     validate_new_attestation(block_number, &attestation_requests, &app_state.kv_store).await?;
 
     // Check whether there is an attestion request for us
-    if !attestation_requests.tss_account_ids.contains(&signer.signer().public().0) {
+    if !attestation_requests.tss_account_ids.contains(&app_state.signer.public().0) {
         return Ok(StatusCode::OK);
     }
 
@@ -68,7 +65,7 @@ pub async fn attest(
     // Also acts as chain check to make sure data is on chain
     let nonce = {
         let pending_attestation_query =
-            entropy::storage().attestation().pending_attestations(signer.account_id());
+            entropy::storage().attestation().pending_attestations(app_state.signer().account_id());
         query_chain(&api, &rpc, pending_attestation_query, None)
             .await?
             .ok_or_else(|| AttestationErr::Unexpected)?
@@ -77,11 +74,11 @@ pub async fn attest(
     // TODO (#1181): since this endpoint is currently only used in tests we don't know what the context should be
     let context = QuoteContext::Validate;
 
-    let quote = create_quote(nonce, &signer, &x25519_secret, context).await?;
+    let quote = create_quote(nonce, &app_state.signer(), &app_state.x25519_secret, context).await?;
 
     // Submit the quote
     let attest_tx = entropy::tx().attestation().attest(quote.clone());
-    submit_transaction(&api, &rpc, &signer, &attest_tx, None).await?;
+    submit_transaction(&api, &rpc, &app_state.signer(), &attest_tx, None).await?;
 
     Ok(StatusCode::OK)
 }
@@ -95,17 +92,15 @@ pub async fn get_attest(
     State(app_state): State<AppState>,
     Query(context_querystring): Query<QuoteContextQuery>,
 ) -> Result<(StatusCode, Vec<u8>), AttestationErr> {
-    let signer = app_state.signer;
-    let x25519_secret = app_state.x25519_secret;
     let api = get_api(&app_state.configuration.endpoint).await?;
     let rpc = get_rpc(&app_state.configuration.endpoint).await?;
 
     // Request attestation to get nonce
-    let nonce = request_attestation(&api, &rpc, signer.signer()).await?;
+    let nonce = request_attestation(&api, &rpc, &app_state.signer).await?;
 
     let context = context_querystring.as_quote_context()?;
 
-    let quote = create_quote(nonce, &signer, &x25519_secret, context).await?;
+    let quote = create_quote(nonce, &app_state.signer(), &app_state.x25519_secret, context).await?;
 
     Ok((StatusCode::OK, quote))
 }
diff --git a/crates/threshold-signature-server/src/helpers/launch.rs b/crates/threshold-signature-server/src/helpers/launch.rs
index 6ab065f73..302fa4ae8 100644
--- a/crates/threshold-signature-server/src/helpers/launch.rs
+++ b/crates/threshold-signature-server/src/helpers/launch.rs
@@ -30,8 +30,6 @@ use subxt::ext::sp_core::{
     sr25519, Pair,
 };
 
-use crate::helpers::validator::get_signer_and_x25519_secret;
-
 pub const DEFAULT_MNEMONIC: &str =
     "alarm mutual concert decrease hurry invest culture survey diagram crash snap click";
 pub const DEFAULT_BOB_MNEMONIC: &str =
@@ -236,44 +234,6 @@ pub fn development_mnemonic(validator_name: &Option<ValidatorName>) -> bip39::Mn
         .expect("Unable to parse given mnemonic.")
 }
 
-// pub async fn setup_mnemonic(mnemonic: bip39::Mnemonic) -> (sr25519::Pair, StaticSecret) {
-//     let (pair, static_secret) =
-//         get_signer_and_x25519_secret(kv).await.expect("Cannot derive keypairs");
-//     let x25519_public_key = x25519_dalek::PublicKey::from(&static_secret).to_bytes();
-//
-//     // Write the shared secret in the KVDB
-//     let shared_secret_reservation = kv
-//         .kv()
-//         .reserve_key(FORBIDDEN_KEY_SHARED_SECRET.to_string())
-//         .await
-//         .expect("Issue reserving ss key");
-//     kv.kv()
-//         .put(shared_secret_reservation, static_secret.to_bytes().to_vec())
-//         .await
-//         .expect("failed to update secret share");
-//
-//     // Write the Diffie-Hellman key in the KVDB
-//     let diffie_hellman_reservation = kv
-//         .kv()
-//         .reserve_key(FORBIDDEN_KEY_DIFFIE_HELLMAN_PUBLIC.to_string())
-//         .await
-//         .expect("Issue reserving DH key");
-//
-//     kv.kv()
-//         .put(diffie_hellman_reservation, x25519_public_key.to_vec())
-//         .await
-//         .expect("failed to update dh");
-//
-//     // Now we write the TSS AccountID and X25519 public key to files for convenience reasons.
-//     let formatted_dh_public = format!("{x25519_public_key:?}").replace('"', "");
-//     fs::write(".entropy/public_key", formatted_dh_public).expect("Failed to write public key file");
-//
-//     let id = AccountId32::new(pair.signer().public().0);
-//     fs::write(".entropy/account_id", format!("{id}")).expect("Failed to write account_id file");
-//
-//     tracing::debug!("Starting process with account ID: `{id}`");
-// }
-
 pub async fn threshold_account_id(kv: &KvManager) -> String {
     let mnemonic = kv.kv().get(FORBIDDEN_KEY_MNEMONIC).await.expect("Issue getting mnemonic");
     let pair = <sr25519::Pair as Pair>::from_phrase(
diff --git a/crates/threshold-signature-server/src/helpers/signing.rs b/crates/threshold-signature-server/src/helpers/signing.rs
index 3b1c9d06e..bb92fd619 100644
--- a/crates/threshold-signature-server/src/helpers/signing.rs
+++ b/crates/threshold-signature-server/src/helpers/signing.rs
@@ -26,7 +26,6 @@ use tokio::time::timeout;
 
 use crate::{
     chain_api::EntropyConfig,
-    get_signer_and_x25519_secret,
     sign_init::SignInit,
     signing_client::{
         protocol_execution::{Channels, ThresholdSigningService},
@@ -54,9 +53,8 @@ pub async fn do_signing(
 
     let info = SignInit::new(relayer_signature_request.clone(), signing_session_info.clone());
     let signing_service = ThresholdSigningService::new(state, kv_manager);
-    let pair_signer = &app_state.signer;
     let x25519_secret_key = &app_state.x25519_secret;
-    let signer = pair_signer.signer();
+    let signer = &app_state.signer;
 
     let account_id = AccountId32(signer.public().0);
 
diff --git a/crates/threshold-signature-server/src/helpers/validator.rs b/crates/threshold-signature-server/src/helpers/validator.rs
index 969662228..c818548ff 100644
--- a/crates/threshold-signature-server/src/helpers/validator.rs
+++ b/crates/threshold-signature-server/src/helpers/validator.rs
@@ -15,51 +15,39 @@
 
 //! Utilites relating to [crate::validator]
 use bip39::{Language, Mnemonic};
-use entropy_kvdb::kv_manager::KvManager;
 use hkdf::Hkdf;
 use sha2::Sha256;
-use subxt::{
-    ext::sp_core::{sr25519, Pair},
-    tx::PairSigner,
-};
+use subxt::ext::sp_core::{sr25519, Pair};
 use x25519_dalek::StaticSecret;
 use zeroize::Zeroize;
 
-use crate::{chain_api::EntropyConfig, user::UserErr};
+use crate::user::UserErr;
 
 /// Constants used in the derivation path
 const KDF_SR25519: &[u8] = b"sr25519-threshold-account";
 const KDF_X25519: &[u8] = b"X25519-keypair";
 
-/// Returns a PairSigner for this node's threshold server.
-/// The PairSigner is stored as an encrypted mnemonic in the kvdb and
-/// is used to sign encrypted messages and to submit extrinsics on chain.
-pub async fn get_signer(
-    kv: &KvManager,
-) -> Result<PairSigner<EntropyConfig, sr25519::Pair>, UserErr> {
-    let hkdf = get_hkdf(kv).await?;
-    get_signer_from_hkdf(&hkdf)
-}
+// Returns a PairSigner for this node's threshold server.
+// The PairSigner is stored as an encrypted mnemonic in the kvdb and
+// is used to sign encrypted messages and to submit extrinsics on chain.
+// pub async fn get_signer(
+//     kv: &KvManager,
+// ) -> Result<PairSigner<EntropyConfig, sr25519::Pair>, UserErr> {
+//     let hkdf = get_hkdf(kv).await?;
+//     get_signer_from_hkdf(&hkdf)
+// }
 
 /// Get the PairSigner as above, and also the x25519 encryption keypair for
 /// this threshold server
 pub fn get_signer_and_x25519_secret(
     mnemonic: &str,
-) -> Result<(PairSigner<EntropyConfig, sr25519::Pair>, StaticSecret), UserErr> {
+) -> Result<(sr25519::Pair, StaticSecret), UserErr> {
     let hkdf = get_hkdf_from_mnemonic(mnemonic)?;
     let pair_signer = get_signer_from_hkdf(&hkdf)?;
     let static_secret = get_x25519_secret_from_hkdf(&hkdf)?;
     Ok((pair_signer, static_secret))
 }
 
-/// Get the key derivation struct to derive secret keys from a mnemonic stored in the KVDB
-async fn get_hkdf(kv: &KvManager) -> Result<Hkdf<Sha256>, UserErr> {
-    let _ = kv.kv().exists("MNEMONIC").await?;
-    let raw_m = kv.kv().get("MNEMONIC").await?;
-    let secret = core::str::from_utf8(&raw_m)?;
-    get_hkdf_from_mnemonic(secret)
-}
-
 /// Given a mnemonic, setup hkdf
 fn get_hkdf_from_mnemonic(mnemonic: &str) -> Result<Hkdf<Sha256>, UserErr> {
     let mnemonic = Mnemonic::parse_in_normalized(Language::English, mnemonic)
@@ -68,15 +56,13 @@ fn get_hkdf_from_mnemonic(mnemonic: &str) -> Result<Hkdf<Sha256>, UserErr> {
 }
 
 /// Derive signing keypair
-pub fn get_signer_from_hkdf(
-    hkdf: &Hkdf<Sha256>,
-) -> Result<PairSigner<EntropyConfig, sr25519::Pair>, UserErr> {
+pub fn get_signer_from_hkdf(hkdf: &Hkdf<Sha256>) -> Result<sr25519::Pair, UserErr> {
     let mut sr25519_seed = [0u8; 32];
     hkdf.expand(KDF_SR25519, &mut sr25519_seed)?;
     let pair = sr25519::Pair::from_seed(&sr25519_seed);
     sr25519_seed.zeroize();
 
-    Ok(PairSigner::<EntropyConfig, sr25519::Pair>::new(pair))
+    Ok(pair)
 }
 
 /// Derive x25519 secret
diff --git a/crates/threshold-signature-server/src/lib.rs b/crates/threshold-signature-server/src/lib.rs
index af0ce8934..93acf09d7 100644
--- a/crates/threshold-signature-server/src/lib.rs
+++ b/crates/threshold-signature-server/src/lib.rs
@@ -186,10 +186,7 @@ use tower_http::{
 use tracing::Level;
 use x25519_dalek::StaticSecret;
 
-pub use crate::helpers::{
-    launch,
-    validator::{get_signer, get_signer_and_x25519_secret},
-};
+pub use crate::helpers::{launch, validator::get_signer_and_x25519_secret};
 use crate::{
     attestation::api::{attest, get_attest},
     chain_api::EntropyConfig,
@@ -205,7 +202,7 @@ use crate::{
 #[derive(Clone)]
 pub struct AppState {
     listener_state: ListenerState,
-    signer: PairSigner<EntropyConfig, sr25519::Pair>,
+    signer: sr25519::Pair,
     x25519_secret: StaticSecret,
     x25519_public_key: [u8; 32],
     pub configuration: Configuration,
@@ -216,21 +213,21 @@ impl AppState {
     pub fn new(
         configuration: Configuration,
         kv_store: KvManager,
-        validator_name: &ValidatorName,
+        validator_name: &Option<ValidatorName>,
     ) -> Self {
-        let (pair, x25519_secret) = if cfg!(test) || validator_name.is_some() {
-            get_signer_and_x25519_secret(development_mnemonic(&validator_name))
+        let (signer, x25519_secret) = if cfg!(test) || validator_name.is_some() {
+            get_signer_and_x25519_secret(&development_mnemonic(&validator_name).to_string())
+                .unwrap()
         } else {
             let (pair, _seed) = sr25519::Pair::generate();
             let x25519_secret = StaticSecret::random_from_rng(&mut OsRng);
             (pair, x25519_secret)
         };
 
-        let signer = PairSigner::<EntropyConfig, sr25519::Pair>::new(pair);
         let x25519_public_key = x25519_dalek::PublicKey::from(&x25519_secret).to_bytes();
 
         Self {
-            pair,
+            signer,
             x25519_secret,
             x25519_public_key,
             listener_state: ListenerState::default(),
@@ -238,6 +235,10 @@ impl AppState {
             kv_store,
         }
     }
+
+    pub fn signer(&self) -> PairSigner<EntropyConfig, sr25519::Pair> {
+        PairSigner::<EntropyConfig, sr25519::Pair>::new(self.signer.clone())
+    }
 }
 
 pub fn app(app_state: AppState) -> Router {
diff --git a/crates/threshold-signature-server/src/main.rs b/crates/threshold-signature-server/src/main.rs
index 0df52d3a8..41784a320 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, has_mnemonic, load_kv_store, setup_latest_block_number,
-        setup_mnemonic, setup_only, Configuration, StartupArgs, ValidatorName,
+        load_kv_store, setup_latest_block_number, setup_only, Configuration, StartupArgs,
+        ValidatorName,
     },
     AppState,
 };
diff --git a/crates/threshold-signature-server/src/node_info/api.rs b/crates/threshold-signature-server/src/node_info/api.rs
index 52aa40f73..e462419a1 100644
--- a/crates/threshold-signature-server/src/node_info/api.rs
+++ b/crates/threshold-signature-server/src/node_info/api.rs
@@ -12,7 +12,7 @@
 //
 // You should have received a copy of the GNU Affero General Public License
 // along with this program.  If not, see <https://www.gnu.org/licenses/>.
-use crate::{get_signer_and_x25519_secret, node_info::errors::GetInfoError, AppState};
+use crate::{node_info::errors::GetInfoError, AppState};
 use axum::{extract::State, Json};
 use entropy_shared::{types::HashingAlgorithm, X25519PublicKey};
 use serde::{Deserialize, Serialize};
@@ -43,9 +43,6 @@ pub struct TssPublicKeys {
 /// Returns the TS server's public keys and HTTP endpoint
 #[tracing::instrument(skip_all)]
 pub async fn info(State(app_state): State<AppState>) -> Result<Json<TssPublicKeys>, 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();
-
-    Ok(Json(TssPublicKeys { x25519_public_key, tss_account }))
+    let tss_account = AccountId32(app_state.signer.public().0);
+    Ok(Json(TssPublicKeys { x25519_public_key: app_state.x25519_public_key, tss_account }))
 }
diff --git a/crates/threshold-signature-server/src/signing_client/api.rs b/crates/threshold-signature-server/src/signing_client/api.rs
index eb55d12a3..ba4f822a1 100644
--- a/crates/threshold-signature-server/src/signing_client/api.rs
+++ b/crates/threshold-signature-server/src/signing_client/api.rs
@@ -55,7 +55,7 @@ use crate::{
     },
     helpers::{
         launch::LATEST_BLOCK_NUMBER_PROACTIVE_REFRESH, substrate::query_chain,
-        user::check_in_registration_group, validator::get_signer_and_x25519_secret,
+        user::check_in_registration_group,
     },
     signing_client::{
         protocol_transport::{handle_socket, open_protocol_connections},
@@ -81,11 +81,8 @@ pub async fn proactive_refresh(
     let ocw_data = OcwMessageProactiveRefresh::decode(&mut encoded_data.as_ref())?;
     let api = get_api(&app_state.configuration.endpoint).await?;
     let rpc = get_rpc(&app_state.configuration.endpoint).await?;
-    let (signer, x25519_secret_key) = get_signer_and_x25519_secret(&app_state.kv_store)
-        .await
-        .map_err(|e| ProtocolErr::UserError(e.to_string()))?;
 
-    check_in_registration_group(&ocw_data.validators_info, signer.account_id())
+    check_in_registration_group(&ocw_data.validators_info, app_state.signer().account_id())
         .map_err(|e| ProtocolErr::UserError(e.to_string()))?;
     validate_proactive_refresh(&api, &rpc, &app_state.kv_store, &ocw_data).await?;
 
@@ -103,8 +100,8 @@ pub async fn proactive_refresh(
 
             let (new_key_share, aux_info) = do_proactive_refresh(
                 &ocw_data.validators_info,
-                &signer,
-                &x25519_secret_key,
+                &app_state.signer(),
+                &app_state.x25519_secret,
                 &app_state.listener_state,
                 encoded_key,
                 deserialized_old_key,
diff --git a/crates/threshold-signature-server/src/signing_client/protocol_transport.rs b/crates/threshold-signature-server/src/signing_client/protocol_transport.rs
index 4912e2dce..b8234d7b1 100644
--- a/crates/threshold-signature-server/src/signing_client/protocol_transport.rs
+++ b/crates/threshold-signature-server/src/signing_client/protocol_transport.rs
@@ -32,7 +32,6 @@ use tokio_tungstenite::connect_async;
 
 use super::ProtocolErr;
 use crate::{
-    get_signer_and_x25519_secret,
     signing_client::{SessionId, SubscribeErr},
     AppState, ListenerState, SUBSCRIBE_TIMEOUT_SECONDS,
 };
@@ -114,12 +113,8 @@ pub async fn open_protocol_connections(
 
 /// Handle an incoming websocket connection
 pub async fn handle_socket(socket: WebSocket, app_state: AppState) -> Result<(), WsError> {
-    let (_signer, x25519_secret_key) = get_signer_and_x25519_secret(&app_state.kv_store)
-        .await
-        .map_err(|_| WsError::SignerFromAppState)?;
-
     let (mut encrypted_connection, serialized_signed_message) =
-        noise_handshake_responder(socket, &x25519_secret_key)
+        noise_handshake_responder(socket, &app_state.x25519_secret)
             .await
             .map_err(|e| WsError::EncryptedConnection(e.to_string()))?;
 
diff --git a/crates/threshold-signature-server/src/user/api.rs b/crates/threshold-signature-server/src/user/api.rs
index c9397de91..25c48adac 100644
--- a/crates/threshold-signature-server/src/user/api.rs
+++ b/crates/threshold-signature-server/src/user/api.rs
@@ -37,7 +37,6 @@ use subxt::{
     utils::AccountId32 as SubxtAccountId32,
     OnlineClient,
 };
-use x25519_dalek::StaticSecret;
 
 use super::UserErr;
 use crate::chain_api::entropy::runtime_types::pallet_registry::pallet::RegisteredInfo;
@@ -51,7 +50,6 @@ use crate::{
             submit_transaction,
         },
         user::{check_in_registration_group, compute_hash, do_dkg},
-        validator::get_signer_and_x25519_secret,
     },
     validation::{check_stale, EncryptedSignedMessage},
     AppState,
@@ -91,7 +89,6 @@ pub async fn relay_tx(
     State(app_state): State<AppState>,
     Json(encrypted_msg): Json<EncryptedSignedMessage>,
 ) -> Result<(StatusCode, Body), UserErr> {
-    let (signer, x25519_secret) = get_signer_and_x25519_secret(&app_state.kv_store).await?;
     let api = get_api(&app_state.configuration.endpoint).await?;
     let rpc = get_rpc(&app_state.configuration.endpoint).await?;
 
@@ -105,7 +102,7 @@ pub async fn relay_tx(
 
     validators_info
         .iter()
-        .find(|validator| validator.tss_account == *signer.account_id())
+        .find(|validator| validator.tss_account == *app_state.signer().account_id())
         .ok_or_else(|| UserErr::NotValidator)?;
 
     let (selected_signers, all_signers) = get_signers_from_chain(&api, &rpc).await?;
@@ -114,10 +111,10 @@ pub async fn relay_tx(
 
     signers_info
         .iter()
-        .find(|signer_info| signer_info.tss_account == *signer.account_id())
+        .find(|signer_info| signer_info.tss_account == *app_state.signer().account_id())
         .map_or(Ok(()), |_| Err(UserErr::RelayMessageSigner))?;
 
-    let signed_message = encrypted_msg.decrypt(&x25519_secret, &[])?;
+    let signed_message = encrypted_msg.decrypt(&app_state.x25519_secret, &[])?;
 
     tracing::Span::current().record("request_author", signed_message.account_id().to_string());
 
@@ -155,7 +152,7 @@ pub async fn relay_tx(
                     .iter()
                     .map(|signer_info| async {
                         let signed_message = EncryptedSignedMessage::new(
-                            signer.signer(),
+                            &app_state.signer,
                             serde_json::to_vec(&relayer_sig_req.clone())?,
                             &signer_info.x25519_public_key,
                             &[],
@@ -220,12 +217,10 @@ pub async fn sign_tx(
     State(app_state): State<AppState>,
     Json(encrypted_msg): Json<EncryptedSignedMessage>,
 ) -> Result<(StatusCode, Body), UserErr> {
-    let (signer, x25519_secret) = get_signer_and_x25519_secret(&app_state.kv_store).await?;
-
     let api = get_api(&app_state.configuration.endpoint).await?;
     let rpc = get_rpc(&app_state.configuration.endpoint).await?;
 
-    let signed_message = encrypted_msg.decrypt(&x25519_secret, &[])?;
+    let signed_message = encrypted_msg.decrypt(&app_state.x25519_secret, &[])?;
 
     let request_author = SubxtAccountId32(*signed_message.account_id().as_ref());
     tracing::Span::current().record("request_author", signed_message.account_id().to_string());
@@ -343,6 +338,7 @@ pub async fn sign_tx(
 
     // Do the signing protocol in another task, so we can already respond
     tokio::spawn(async move {
+        let signer = app_state.clone().signer;
         let signing_protocol_output = do_signing(
             &rpc,
             relayer_sig_request,
@@ -355,7 +351,7 @@ pub async fn sign_tx(
         .map(|signature| {
             (
                 BASE64_STANDARD.encode(signature.to_rsv_bytes()),
-                signer.signer().sign(&signature.to_rsv_bytes()),
+                signer.sign(&signature.to_rsv_bytes()),
             )
         })
         .map_err(|error| error.to_string());
@@ -391,15 +387,14 @@ pub async fn generate_network_key(
 
     let api = get_api(&app_state.configuration.endpoint).await?;
     let rpc = get_rpc(&app_state.configuration.endpoint).await?;
-    let (signer, x25519_secret_key) = get_signer_and_x25519_secret(&app_state.kv_store).await?;
 
     let in_registration_group =
-        check_in_registration_group(&data.validators_info, signer.account_id());
+        check_in_registration_group(&data.validators_info, app_state.signer().account_id());
 
     if in_registration_group.is_err() {
         tracing::warn!(
             "The account {:?} is not in the registration group for block_number {:?}",
-            signer.account_id(),
+            app_state.signer().account_id(),
             data.block_number
         );
 
@@ -408,9 +403,10 @@ pub async fn generate_network_key(
 
     validate_jump_start(&data, &api, &rpc, &app_state.kv_store).await?;
 
+    let app_state = app_state.clone();
     // Do the DKG protocol in another task, so we can already respond
     tokio::spawn(async move {
-        if let Err(err) = setup_dkg(api, &rpc, signer, &x25519_secret_key, data, app_state).await {
+        if let Err(err) = setup_dkg(api, &rpc, data, app_state).await {
             // TODO here we would check the error and if it relates to a misbehaving node,
             // use the slashing mechanism
             tracing::error!("User registration failed {:?}", err);
@@ -431,16 +427,14 @@ pub async fn generate_network_key(
 async fn setup_dkg(
     api: OnlineClient<EntropyConfig>,
     rpc: &LegacyRpcMethods<EntropyConfig>,
-    signer: PairSigner<EntropyConfig, sr25519::Pair>,
-    x25519_secret_key: &StaticSecret,
     data: OcwMessageDkg,
     app_state: AppState,
 ) -> Result<(), UserErr> {
     tracing::debug!("Preparing to execute DKG");
     let (key_share, aux_info) = do_dkg(
         &data.validators_info,
-        &signer,
-        x25519_secret_key,
+        &app_state.signer(),
+        &app_state.x25519_secret,
         &app_state.listener_state,
         data.block_number,
     )
@@ -459,11 +453,12 @@ async fn setup_dkg(
         .await?
         .ok_or_else(|| UserErr::OptionUnwrapError("Error getting block hash".to_string()))?;
 
-    let nonce_call = entropy::apis().account_nonce_api().account_nonce(signer.account_id().clone());
+    let nonce_call =
+        entropy::apis().account_nonce_api().account_nonce(app_state.signer().account_id().clone());
     let nonce = api.runtime_api().at(block_hash).call(nonce_call).await?;
 
     // TODO: Error handling really complex needs to be thought about.
-    confirm_jump_start(&api, rpc, &signer, verifying_key, nonce).await?;
+    confirm_jump_start(&api, rpc, &app_state.signer(), verifying_key, nonce).await?;
     Ok(())
 }
 
diff --git a/crates/threshold-signature-server/src/validator/api.rs b/crates/threshold-signature-server/src/validator/api.rs
index 179342402..20d3607a3 100644
--- a/crates/threshold-signature-server/src/validator/api.rs
+++ b/crates/threshold-signature-server/src/validator/api.rs
@@ -18,7 +18,6 @@ use crate::{
         entropy::{self},
         get_api, get_rpc, EntropyConfig,
     },
-    get_signer_and_x25519_secret,
     helpers::{
         launch::{FORBIDDEN_KEYS, LATEST_BLOCK_NUMBER_RESHARE},
         substrate::{get_stash_address, get_validators_info, query_chain, submit_transaction},
@@ -44,7 +43,6 @@ use subxt::{
     OnlineClient,
 };
 use synedrion::{KeyResharingInputs, NewHolder, OldHolder};
-use x25519_dalek::StaticSecret;
 
 /// HTTP POST endpoint called by the off-chain worker (propagation pallet) during network reshare.
 ///
@@ -62,10 +60,6 @@ pub async fn new_reshare(
     let rpc = get_rpc(&app_state.configuration.endpoint).await?;
     validate_new_reshare(&api, &rpc, &data, &app_state.kv_store).await?;
 
-    let (signer, x25519_secret_key) = get_signer_and_x25519_secret(&app_state.kv_store)
-        .await
-        .map_err(|e| ValidatorErr::UserError(e.to_string()))?;
-
     let next_signers_query = entropy::storage().staking_extension().next_signers();
     let next_signers = query_chain(&api, &rpc, next_signers_query, None)
         .await?
@@ -77,18 +71,16 @@ pub async fn new_reshare(
 
     let is_proper_signer = validators_info
         .iter()
-        .any(|validator_info| validator_info.tss_account == *signer.account_id());
+        .any(|validator_info| validator_info.tss_account == *app_state.signer().account_id());
 
     if !is_proper_signer {
         return Ok(StatusCode::MISDIRECTED_REQUEST);
     }
 
+    let app_state = app_state.clone();
     // Do reshare in a separate task so we can already respond
     tokio::spawn(async move {
-        if let Err(err) =
-            do_reshare(&api, &rpc, signer, &x25519_secret_key, data, validators_info, app_state)
-                .await
-        {
+        if let Err(err) = do_reshare(&api, &rpc, data, validators_info, app_state).await {
             tracing::error!("Error during reshare: {err}");
         }
     });
@@ -98,8 +90,6 @@ pub async fn new_reshare(
 async fn do_reshare(
     api: &OnlineClient<EntropyConfig>,
     rpc: &LegacyRpcMethods<EntropyConfig>,
-    signer: PairSigner<EntropyConfig, sr25519::Pair>,
-    x25519_secret_key: &StaticSecret,
     data: OcwMessageReshare,
     validators_info: Vec<ValidatorInfo>,
     app_state: AppState,
@@ -121,7 +111,7 @@ async fn do_reshare(
             .map_err(|_| ValidatorErr::Conversion("Verifying key conversion"))?,
     )
     .map_err(|e| ValidatorErr::VerifyingKeyError(e.to_string()))?;
-    let my_stash_address = get_stash_address(api, rpc, signer.account_id())
+    let my_stash_address = get_stash_address(api, rpc, app_state.signer().account_id())
         .await
         .map_err(|e| ValidatorErr::UserError(e.to_string()))?;
 
@@ -165,7 +155,7 @@ async fn do_reshare(
     };
 
     let session_id = SessionId::Reshare { verifying_key, block_number: data.block_number };
-    let account_id = AccountId32(signer.signer().public().0);
+    let account_id = AccountId32(app_state.signer.public().0);
 
     let mut converted_validator_info = vec![];
     let mut tss_accounts = vec![];
@@ -184,13 +174,19 @@ async fn do_reshare(
         converted_validator_info,
         account_id,
         &session_id,
-        &signer,
-        x25519_secret_key,
+        &app_state.signer(),
+        &app_state.x25519_secret,
+    )
+    .await?;
+    let (new_key_share, aux_info) = execute_reshare(
+        session_id.clone(),
+        channels,
+        &app_state.signer,
+        inputs,
+        &new_holders,
+        None,
     )
     .await?;
-    let (new_key_share, aux_info) =
-        execute_reshare(session_id.clone(), channels, signer.signer(), inputs, &new_holders, None)
-            .await?;
 
     let serialized_key_share = key_serialize(&(new_key_share, aux_info))
         .map_err(|_| ProtocolErr::KvSerialize("Kv Serialize Error".to_string()))?;
@@ -204,7 +200,7 @@ async fn do_reshare(
     app_state.kv_store.kv().put(reservation, serialized_key_share.clone()).await?;
 
     // TODO: Error handling really complex needs to be thought about.
-    confirm_key_reshare(api, rpc, &signer).await?;
+    confirm_key_reshare(api, rpc, &app_state.signer()).await?;
     Ok(())
 }
 
@@ -221,10 +217,6 @@ pub async fn rotate_network_key(
 
     validate_rotate_network_key(&api, &rpc).await?;
 
-    let (signer, _) = get_signer_and_x25519_secret(&app_state.kv_store)
-        .await
-        .map_err(|e| ValidatorErr::UserError(e.to_string()))?;
-
     let signers_query = entropy::storage().staking_extension().signers();
     let signers = query_chain(&api, &rpc, signers_query, None)
         .await?
@@ -235,7 +227,7 @@ pub async fn rotate_network_key(
         .map_err(|e| ValidatorErr::UserError(e.to_string()))?;
 
     let is_proper_signer = is_signer_or_delete_parent_key(
-        signer.account_id(),
+        app_state.signer().account_id(),
         validators_info.clone(),
         &app_state.kv_store,
     )

From 1ca0a0cdd7393dcf04c868ccbdf76bde079d8451 Mon Sep 17 00:00:00 2001
From: peg <ameba23@systemli.org>
Date: Mon, 16 Dec 2024 09:13:45 +0100
Subject: [PATCH 12/77] Fixes for tests and test helpers

---
 crates/testing-utils/src/lib.rs               |  2 +-
 .../src/helpers/tests.rs                      | 54 ++++++-------------
 .../src/helpers/validator.rs                  |  5 +-
 crates/threshold-signature-server/src/lib.rs  |  6 ++-
 .../src/user/tests.rs                         | 32 +----------
 5 files changed, 28 insertions(+), 71 deletions(-)

diff --git a/crates/testing-utils/src/lib.rs b/crates/testing-utils/src/lib.rs
index 8fea4daa3..dd8b2bd89 100644
--- a/crates/testing-utils/src/lib.rs
+++ b/crates/testing-utils/src/lib.rs
@@ -26,4 +26,4 @@ pub use entropy_tss::helpers::tests::{spawn_testing_validators, ChainSpecType};
 pub use node_proc::TestNodeProcess;
 pub use substrate_context::*;
 
-pub use entropy_tss::helpers::validator::get_signer_and_x25519_secret_from_mnemonic;
+// pub use entropy_tss::helpers::validator::get_signer_and_x25519_secret_from_mnemonic;
diff --git a/crates/threshold-signature-server/src/helpers/tests.rs b/crates/threshold-signature-server/src/helpers/tests.rs
index 029ddcf4c..05eab6fc9 100644
--- a/crates/threshold-signature-server/src/helpers/tests.rs
+++ b/crates/threshold-signature-server/src/helpers/tests.rs
@@ -29,16 +29,11 @@ use crate::{
         },
         EntropyConfig,
     },
-    get_signer,
     helpers::{
-        launch::{
-            development_mnemonic, setup_latest_block_number, setup_mnemonic, Configuration,
-            ValidatorName, DEFAULT_ENDPOINT,
-        },
+        launch::{setup_latest_block_number, Configuration, ValidatorName, DEFAULT_ENDPOINT},
         logger::{Instrumentation, Logger},
         substrate::submit_transaction,
     },
-    signing_client::ListenerState,
     AppState,
 };
 use axum::{routing::IntoMakeService, Router};
@@ -74,13 +69,10 @@ pub async fn setup_client() -> KvManager {
         KvManager::new(get_db_path(true).into(), PasswordMethod::NoPassword.execute().unwrap())
             .unwrap();
 
-    let mnemonic = development_mnemonic(&Some(ValidatorName::Alice));
-    setup_mnemonic(&kv_store, mnemonic).await;
-
     let _ = setup_latest_block_number(&kv_store).await;
-    let listener_state = ListenerState::default();
     let configuration = Configuration::new(DEFAULT_ENDPOINT.to_string());
-    let app_state = AppState { listener_state, configuration, kv_store: kv_store.clone() };
+    let app_state = AppState::new(configuration, kv_store.clone(), &Some(ValidatorName::Alice));
+
     let app = app(app_state).into_make_service();
 
     let listener = tokio::net::TcpListener::bind("0.0.0.0:3001")
@@ -98,8 +90,7 @@ pub async fn create_clients(
     values: Vec<Vec<u8>>,
     keys: Vec<String>,
     validator_name: &Option<ValidatorName>,
-) -> (IntoMakeService<Router>, KvManager) {
-    let listener_state = ListenerState::default();
+) -> (IntoMakeService<Router>, KvManager, SubxtAccountId32) {
     let configuration = Configuration::new(DEFAULT_ENDPOINT.to_string());
 
     let path = format!(".entropy/testing/test_db_{key_number}");
@@ -108,9 +99,6 @@ pub async fn create_clients(
     let kv_store =
         KvManager::new(path.into(), PasswordMethod::NoPassword.execute().unwrap()).unwrap();
 
-    let mnemonic = development_mnemonic(validator_name);
-    crate::launch::setup_mnemonic(&kv_store, mnemonic).await;
-
     let _ = setup_latest_block_number(&kv_store).await;
 
     for (i, value) in values.into_iter().enumerate() {
@@ -118,11 +106,13 @@ pub async fn create_clients(
         let _ = kv_store.clone().kv().put(reservation, value).await;
     }
 
-    let app_state = AppState { listener_state, configuration, kv_store: kv_store.clone() };
+    let app_state = AppState::new(configuration, kv_store.clone(), validator_name);
+
+    let account_id = app_state.subxt_account_id();
 
     let app = app(app_state).into_make_service();
 
-    (app, kv_store)
+    (app, kv_store, account_id)
 }
 
 /// A way to specify which chainspec to use in testing
@@ -156,26 +146,18 @@ pub async fn spawn_testing_validators(
 ) -> (Vec<String>, Vec<PartyId>) {
     let ports = [3001i64, 3002, 3003, 3004];
 
-    let (alice_axum, alice_kv) =
+    let (alice_axum, alice_kv, alice_id) =
         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 alice_id = PartyId::new(alice_id);
 
-    let (bob_axum, bob_kv) =
+    let (bob_axum, bob_kv, bob_id) =
         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 bob_id = PartyId::new(bob_id);
 
-    let (charlie_axum, charlie_kv) =
+    let (charlie_axum, charlie_kv, charlie_id) =
         create_clients("validator3".to_string(), vec![], vec![], &Some(ValidatorName::Charlie))
             .await;
-    let charlie_id = PartyId::new(SubxtAccountId32(
-        *get_signer(&charlie_kv).await.unwrap().account_id().clone().as_ref(),
-    ));
-
-    let mut ids = vec![alice_id, bob_id, charlie_id];
+    let charlie_id = PartyId::new(charlie_id);
 
     let listener_alice = tokio::net::TcpListener::bind(format!("0.0.0.0:{}", ports[0]))
         .await
@@ -198,7 +180,7 @@ pub async fn spawn_testing_validators(
         axum::serve(listener_charlie, charlie_axum).await.unwrap();
     });
 
-    let (dave_axum, dave_kv) =
+    let (dave_axum, _dave_kv, dave_id) =
         create_clients("validator4".to_string(), vec![], vec![], &Some(ValidatorName::Dave)).await;
 
     let listener_dave = tokio::net::TcpListener::bind(format!("0.0.0.0:{}", ports[3]))
@@ -207,10 +189,8 @@ pub async fn spawn_testing_validators(
     tokio::spawn(async move {
         axum::serve(listener_dave, dave_axum).await.unwrap();
     });
-    let dave_id = PartyId::new(SubxtAccountId32(
-        *get_signer(&dave_kv).await.unwrap().account_id().clone().as_ref(),
-    ));
-    ids.push(dave_id);
+    let dave_id = PartyId::new(dave_id);
+    let ids = vec![alice_id, bob_id, charlie_id, dave_id];
 
     if chain_spec_type == ChainSpecType::IntegrationJumpStarted {
         put_keyshares_in_db(ValidatorName::Alice, alice_kv).await;
diff --git a/crates/threshold-signature-server/src/helpers/validator.rs b/crates/threshold-signature-server/src/helpers/validator.rs
index c818548ff..e7e33ad56 100644
--- a/crates/threshold-signature-server/src/helpers/validator.rs
+++ b/crates/threshold-signature-server/src/helpers/validator.rs
@@ -78,9 +78,10 @@ fn get_x25519_secret_from_hkdf(hkdf: &Hkdf<Sha256>) -> Result<StaticSecret, User
 #[cfg(any(test, feature = "test_helpers"))]
 pub fn get_signer_and_x25519_secret_from_mnemonic(
     mnemonic: &str,
-) -> Result<(PairSigner<EntropyConfig, sr25519::Pair>, StaticSecret), UserErr> {
+) -> Result<(subxt::tx::PairSigner<crate::EntropyConfig, sr25519::Pair>, StaticSecret), UserErr> {
     let hkdf = get_hkdf_from_mnemonic(mnemonic)?;
-    let pair_signer = get_signer_from_hkdf(&hkdf)?;
+    let pair = get_signer_from_hkdf(&hkdf)?;
+    let pair_signer = subxt::tx::PairSigner::new(pair);
     let static_secret = get_x25519_secret_from_hkdf(&hkdf)?;
     Ok((pair_signer, static_secret))
 }
diff --git a/crates/threshold-signature-server/src/lib.rs b/crates/threshold-signature-server/src/lib.rs
index 93acf09d7..572d85761 100644
--- a/crates/threshold-signature-server/src/lib.rs
+++ b/crates/threshold-signature-server/src/lib.rs
@@ -178,7 +178,7 @@ use axum::{
 use entropy_kvdb::kv_manager::KvManager;
 use rand_core::OsRng;
 use sp_core::{sr25519, Pair};
-use subxt::tx::PairSigner;
+use subxt::{tx::PairSigner, utils::AccountId32 as SubxtAccountId32};
 use tower_http::{
     cors::{Any, CorsLayer},
     trace::{self, TraceLayer},
@@ -239,6 +239,10 @@ impl AppState {
     pub fn signer(&self) -> PairSigner<EntropyConfig, sr25519::Pair> {
         PairSigner::<EntropyConfig, sr25519::Pair>::new(self.signer.clone())
     }
+
+    pub fn subxt_account_id(&self) -> SubxtAccountId32 {
+        SubxtAccountId32(self.signer.public().0)
+    }
 }
 
 pub fn app(app_state: AppState) -> Router {
diff --git a/crates/threshold-signature-server/src/user/tests.rs b/crates/threshold-signature-server/src/user/tests.rs
index d2620049c..2c2cb8712 100644
--- a/crates/threshold-signature-server/src/user/tests.rs
+++ b/crates/threshold-signature-server/src/user/tests.rs
@@ -52,16 +52,12 @@ use schemars::{schema_for, JsonSchema};
 use schnorrkel::{signing_context, Keypair as Sr25519Keypair, Signature as Sr25519Signature};
 use serde::{Deserialize, Serialize};
 use serial_test::serial;
-use sp_core::{crypto::Ss58Codec, Pair as OtherPair};
 use sp_keyring::{AccountKeyring, Sr25519Keyring};
 use std::{str, str::FromStr, time::Duration};
 use subxt::{
     backend::legacy::LegacyRpcMethods,
     config::PolkadotExtrinsicParamsBuilder as Params,
-    ext::{
-        sp_core::{hashing::blake2_256, sr25519, sr25519::Signature, Pair},
-        sp_runtime::AccountId32,
-    },
+    ext::sp_core::{hashing::blake2_256, sr25519, sr25519::Signature, Pair},
     tx::{PairSigner, TxStatus},
     utils::{AccountId32 as subxtAccountId32, MultiAddress, MultiSignature},
     OnlineClient,
@@ -78,12 +74,8 @@ use crate::{
         entropy::runtime_types::pallet_registry::pallet::ProgramInstance, get_api, get_rpc,
         EntropyConfig,
     },
-    get_signer,
     helpers::{
-        launch::{
-            development_mnemonic, load_kv_store, setup_mnemonic, threshold_account_id,
-            ValidatorName,
-        },
+        launch::{development_mnemonic, load_kv_store, ValidatorName},
         signing::Hasher,
         substrate::{get_oracle_data, get_signers_from_chain, query_chain, submit_transaction},
         tests::{
@@ -101,26 +93,6 @@ use crate::{
     validation::EncryptedSignedMessage,
 };
 
-#[tokio::test]
-#[serial]
-async fn test_get_signer_does_not_throw_err() {
-    initialize_test_logger().await;
-    clean_tests();
-
-    let pair = <sr25519::Pair as Pair>::from_phrase(crate::helpers::launch::DEFAULT_MNEMONIC, None)
-        .expect("Issue converting mnemonic to pair");
-    let expected_account_id = AccountId32::new(pair.0.public().into()).to_ss58check();
-
-    let kv_store = load_kv_store(&None, None).await;
-    setup_mnemonic(&kv_store, development_mnemonic(&None)).await;
-    development_mnemonic(&None).to_string();
-    let account = threshold_account_id(&kv_store).await;
-
-    assert_eq!(account, expected_account_id);
-    get_signer(&kv_store).await.unwrap();
-    clean_tests();
-}
-
 #[tokio::test]
 #[serial]
 async fn test_signature_requests_fail_on_different_conditions() {

From 1303be2a1ea4a68645d62f11615d22be05523609 Mon Sep 17 00:00:00 2001
From: peg <ameba23@systemli.org>
Date: Mon, 16 Dec 2024 09:33:40 +0100
Subject: [PATCH 13/77] Revert commented out import

---
 crates/testing-utils/src/lib.rs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/crates/testing-utils/src/lib.rs b/crates/testing-utils/src/lib.rs
index dd8b2bd89..8fea4daa3 100644
--- a/crates/testing-utils/src/lib.rs
+++ b/crates/testing-utils/src/lib.rs
@@ -26,4 +26,4 @@ pub use entropy_tss::helpers::tests::{spawn_testing_validators, ChainSpecType};
 pub use node_proc::TestNodeProcess;
 pub use substrate_context::*;
 
-// pub use entropy_tss::helpers::validator::get_signer_and_x25519_secret_from_mnemonic;
+pub use entropy_tss::helpers::validator::get_signer_and_x25519_secret_from_mnemonic;

From ebc339ede5c6f01ef3356428262ba321f8f70281 Mon Sep 17 00:00:00 2001
From: peg <ameba23@systemli.org>
Date: Mon, 16 Dec 2024 10:08:28 +0100
Subject: [PATCH 14/77] Clippy

---
 crates/threshold-signature-server/src/helpers/signing.rs | 2 +-
 crates/threshold-signature-server/src/lib.rs             | 5 ++---
 2 files changed, 3 insertions(+), 4 deletions(-)

diff --git a/crates/threshold-signature-server/src/helpers/signing.rs b/crates/threshold-signature-server/src/helpers/signing.rs
index bb92fd619..b894387d6 100644
--- a/crates/threshold-signature-server/src/helpers/signing.rs
+++ b/crates/threshold-signature-server/src/helpers/signing.rs
@@ -84,7 +84,7 @@ pub async fn do_signing(
         &session_id,
         signer,
         state,
-        &x25519_secret_key,
+        x25519_secret_key,
     )
     .await?;
 
diff --git a/crates/threshold-signature-server/src/lib.rs b/crates/threshold-signature-server/src/lib.rs
index 572d85761..4d544d5fa 100644
--- a/crates/threshold-signature-server/src/lib.rs
+++ b/crates/threshold-signature-server/src/lib.rs
@@ -216,11 +216,10 @@ impl AppState {
         validator_name: &Option<ValidatorName>,
     ) -> Self {
         let (signer, x25519_secret) = if cfg!(test) || validator_name.is_some() {
-            get_signer_and_x25519_secret(&development_mnemonic(&validator_name).to_string())
-                .unwrap()
+            get_signer_and_x25519_secret(&development_mnemonic(validator_name).to_string()).unwrap()
         } else {
             let (pair, _seed) = sr25519::Pair::generate();
-            let x25519_secret = StaticSecret::random_from_rng(&mut OsRng);
+            let x25519_secret = StaticSecret::random_from_rng(OsRng);
             (pair, x25519_secret)
         };
 

From 87c6afd9087fa9187ae176f8e272b988c835a96d Mon Sep 17 00:00:00 2001
From: peg <ameba23@systemli.org>
Date: Mon, 16 Dec 2024 11:06:16 +0100
Subject: [PATCH 15/77] Update unsafe get test

---
 crates/threshold-signature-server/src/unsafe/tests.rs | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/crates/threshold-signature-server/src/unsafe/tests.rs b/crates/threshold-signature-server/src/unsafe/tests.rs
index b40860951..e0bc0e6d5 100644
--- a/crates/threshold-signature-server/src/unsafe/tests.rs
+++ b/crates/threshold-signature-server/src/unsafe/tests.rs
@@ -18,7 +18,10 @@ use entropy_kvdb::clean_tests;
 use serial_test::serial;
 
 use super::api::UnsafeQuery;
-use crate::helpers::tests::{initialize_test_logger, setup_client};
+use crate::helpers::{
+    launch::LATEST_BLOCK_NUMBER_RESHARE,
+    tests::{initialize_test_logger, setup_client},
+};
 
 #[tokio::test]
 #[serial]
@@ -27,7 +30,7 @@ async fn test_unsafe_get_endpoint() {
     setup_client().await;
     let client = reqwest::Client::new();
 
-    let get_query = UnsafeQuery::new("MNEMONIC".to_string(), vec![10]).to_json();
+    let get_query = UnsafeQuery::new(LATEST_BLOCK_NUMBER_RESHARE.to_string(), vec![10]).to_json();
 
     // Test that the get endpoint works
     let response = client

From e71cc72b7e58317967ea0680d5c48954f542b7ce Mon Sep 17 00:00:00 2001
From: peg <ameba23@systemli.org>
Date: Mon, 16 Dec 2024 11:22:02 +0100
Subject: [PATCH 16/77] Rm setup only option, tidy

---
 .../src/helpers/launch.rs                     | 62 +------------------
 crates/threshold-signature-server/src/lib.rs  |  6 +-
 crates/threshold-signature-server/src/main.rs | 43 +++++--------
 3 files changed, 22 insertions(+), 89 deletions(-)

diff --git a/crates/threshold-signature-server/src/helpers/launch.rs b/crates/threshold-signature-server/src/helpers/launch.rs
index 302fa4ae8..5a66e0858 100644
--- a/crates/threshold-signature-server/src/helpers/launch.rs
+++ b/crates/threshold-signature-server/src/helpers/launch.rs
@@ -24,11 +24,6 @@ use entropy_kvdb::{
 };
 use entropy_shared::NETWORK_PARENT_KEY;
 use serde::Deserialize;
-use serde_json::json;
-use subxt::ext::sp_core::{
-    crypto::{AccountId32, Ss58Codec},
-    sr25519, Pair,
-};
 
 pub const DEFAULT_MNEMONIC: &str =
     "alarm mutual concert decrease hurry invest culture survey diagram crash snap click";
@@ -51,16 +46,7 @@ pub const LATEST_BLOCK_NUMBER_PROACTIVE_REFRESH: &str = "LATEST_BLOCK_NUMBER_PRO
 #[cfg(any(test, feature = "test_helpers"))]
 pub const DEFAULT_ENDPOINT: &str = "ws://localhost:9944";
 
-pub const FORBIDDEN_KEYS: [&str; 4] = [
-    FORBIDDEN_KEY_MNEMONIC,
-    FORBIDDEN_KEY_SHARED_SECRET,
-    FORBIDDEN_KEY_DIFFIE_HELLMAN_PUBLIC,
-    NETWORK_PARENT_KEY,
-];
-
-pub const FORBIDDEN_KEY_MNEMONIC: &str = "MNEMONIC";
-pub const FORBIDDEN_KEY_SHARED_SECRET: &str = "SHARED_SECRET";
-pub const FORBIDDEN_KEY_DIFFIE_HELLMAN_PUBLIC: &str = "DH_PUBLIC";
+pub const FORBIDDEN_KEYS: [&str; 1] = [NETWORK_PARENT_KEY];
 
 // Deafult name for TSS server
 // Will set mnemonic and db path
@@ -198,23 +184,6 @@ pub struct StartupArgs {
     /// The path to a password file
     #[arg(short = 'f', long = "password-file")]
     pub password_file: Option<PathBuf>,
-
-    /// Set up the key-value store (KVDB), or ensure one already exists, print setup information to
-    /// stdout, then exit. Supply the `--password-file` option for fully non-interactive operation.
-    ///
-    /// Returns the AccountID and Diffie-Hellman Public Keys associated with this server.
-    #[arg(long = "setup-only")]
-    pub setup_only: bool,
-}
-
-pub async fn has_mnemonic(kv: &KvManager) -> bool {
-    let exists = kv.kv().exists(FORBIDDEN_KEY_MNEMONIC).await.expect("issue querying DB");
-
-    if exists {
-        tracing::debug!("Existing mnemonic found in keystore.");
-    }
-
-    exists
 }
 
 pub fn development_mnemonic(validator_name: &Option<ValidatorName>) -> bip39::Mnemonic {
@@ -234,16 +203,6 @@ pub fn development_mnemonic(validator_name: &Option<ValidatorName>) -> bip39::Mn
         .expect("Unable to parse given mnemonic.")
 }
 
-pub async fn threshold_account_id(kv: &KvManager) -> String {
-    let mnemonic = kv.kv().get(FORBIDDEN_KEY_MNEMONIC).await.expect("Issue getting mnemonic");
-    let pair = <sr25519::Pair as Pair>::from_phrase(
-        &String::from_utf8(mnemonic).expect("Issue converting mnemonic to string"),
-        None,
-    )
-    .expect("Issue converting mnemonic to pair");
-    AccountId32::new(pair.0.public().into()).to_ss58check()
-}
-
 pub async fn setup_latest_block_number(kv: &KvManager) -> Result<(), KvError> {
     let exists_result_new_user =
         kv.kv().exists(LATEST_BLOCK_NUMBER_NEW_USER).await.expect("issue querying DB");
@@ -300,25 +259,6 @@ pub async fn setup_latest_block_number(kv: &KvManager) -> Result<(), KvError> {
     Ok(())
 }
 
-pub async fn setup_only(kv: &KvManager) {
-    let mnemonic = kv.kv().get(FORBIDDEN_KEYS[0]).await.expect("Issue getting mnemonic");
-    let pair = <sr25519::Pair as Pair>::from_phrase(
-        &String::from_utf8(mnemonic).expect("Issue converting mnemonic to string"),
-        None,
-    )
-    .expect("Issue converting mnemonic to pair");
-    let account_id = AccountId32::new(pair.0.public().into()).to_ss58check();
-
-    let dh_public_key = kv.kv().get(FORBIDDEN_KEYS[2]).await.expect("Issue getting dh public key");
-    let dh_public_key = format!("{dh_public_key:?}").replace('"', "");
-    let output = json!({
-        "account_id": account_id,
-        "dh_public_key": dh_public_key,
-    });
-
-    println!("{}", output);
-}
-
 pub async fn check_node_prerequisites(url: &str, account_id: &str) {
     use crate::chain_api::{get_api, get_rpc};
 
diff --git a/crates/threshold-signature-server/src/lib.rs b/crates/threshold-signature-server/src/lib.rs
index 4d544d5fa..86e7a1127 100644
--- a/crates/threshold-signature-server/src/lib.rs
+++ b/crates/threshold-signature-server/src/lib.rs
@@ -177,7 +177,7 @@ use axum::{
 };
 use entropy_kvdb::kv_manager::KvManager;
 use rand_core::OsRng;
-use sp_core::{sr25519, Pair};
+use sp_core::{crypto::AccountId32, sr25519, Pair};
 use subxt::{tx::PairSigner, utils::AccountId32 as SubxtAccountId32};
 use tower_http::{
     cors::{Any, CorsLayer},
@@ -239,6 +239,10 @@ impl AppState {
         PairSigner::<EntropyConfig, sr25519::Pair>::new(self.signer.clone())
     }
 
+    pub fn account_id(&self) -> AccountId32 {
+        AccountId32::new(self.signer.public().0)
+    }
+
     pub fn subxt_account_id(&self) -> SubxtAccountId32 {
         SubxtAccountId32(self.signer.public().0)
     }
diff --git a/crates/threshold-signature-server/src/main.rs b/crates/threshold-signature-server/src/main.rs
index 41784a320..bd9451e02 100644
--- a/crates/threshold-signature-server/src/main.rs
+++ b/crates/threshold-signature-server/src/main.rs
@@ -16,13 +16,11 @@
 use std::{net::SocketAddr, str::FromStr};
 
 use clap::Parser;
+use sp_core::crypto::Ss58Codec;
 
 use entropy_tss::{
     app,
-    launch::{
-        load_kv_store, setup_latest_block_number, setup_only, Configuration, StartupArgs,
-        ValidatorName,
-    },
+    launch::{load_kv_store, setup_latest_block_number, Configuration, StartupArgs, ValidatorName},
     AppState,
 };
 
@@ -31,19 +29,15 @@ async fn main() {
     let args = StartupArgs::parse();
     args.logger.setup().await;
 
-    if !args.setup_only {
-        tracing::info!("Starting Threshold Signature Sever");
-        tracing::info!("Starting server on: `{}`", &args.threshold_url);
-    }
+    tracing::info!("Starting Threshold Signature Sever");
+    tracing::info!("Starting server on: `{}`", &args.threshold_url);
 
     if args.logger.loki {
         tracing::info!("Sending logs to Loki server at `{}`", &args.logger.loki_endpoint);
     }
 
     let configuration = Configuration::new(args.chain_endpoint);
-    if !args.setup_only {
-        tracing::info!("Connecting to Substrate node at: `{}`", &configuration.endpoint);
-    }
+    tracing::info!("Connecting to Substrate node at: `{}`", &configuration.endpoint);
 
     let mut validator_name = None;
     if args.alice {
@@ -71,21 +65,16 @@ async fn main() {
     // Below deals with syncing the kvdb
     let addr = SocketAddr::from_str(&args.threshold_url).expect("failed to parse threshold url.");
 
-    if args.setup_only {
-        setup_only(&kv_store).await;
-    } else {
-        let account_id = entropy_tss::launch::threshold_account_id(&kv_store).await;
-        entropy_tss::launch::check_node_prerequisites(
-            &app_state.configuration.endpoint,
-            &account_id,
-        )
-        .await;
+    entropy_tss::launch::check_node_prerequisites(
+        &app_state.configuration.endpoint,
+        &app_state.account_id().to_ss58check(),
+    )
+    .await;
 
-        let listener = tokio::net::TcpListener::bind(&addr)
-            .await
-            .expect("Unable to bind to given server address.");
-        axum::serve(listener, app(app_state).into_make_service())
-            .await
-            .expect("failed to launch axum server.");
-    }
+    let listener = tokio::net::TcpListener::bind(&addr)
+        .await
+        .expect("Unable to bind to given server address.");
+    axum::serve(listener, app(app_state).into_make_service())
+        .await
+        .expect("failed to launch axum server.");
 }

From 6831f49ec939971fbf2650abf168d9e4c783ee50 Mon Sep 17 00:00:00 2001
From: peg <ameba23@systemli.org>
Date: Mon, 16 Dec 2024 11:52:03 +0100
Subject: [PATCH 17/77] Tidy AppState interface

---
 .../src/attestation/api.rs                    |  4 ++--
 .../src/helpers/signing.rs                    |  2 +-
 crates/threshold-signature-server/src/lib.rs  | 24 +++++++++++--------
 .../src/node_info/api.rs                      |  7 +++---
 .../src/signing_client/api.rs                 |  2 +-
 .../src/user/api.rs                           | 10 ++++----
 .../src/validator/api.rs                      | 21 ++++++----------
 7 files changed, 34 insertions(+), 36 deletions(-)

diff --git a/crates/threshold-signature-server/src/attestation/api.rs b/crates/threshold-signature-server/src/attestation/api.rs
index 614d4e01d..7387f82bc 100644
--- a/crates/threshold-signature-server/src/attestation/api.rs
+++ b/crates/threshold-signature-server/src/attestation/api.rs
@@ -57,7 +57,7 @@ pub async fn attest(
     validate_new_attestation(block_number, &attestation_requests, &app_state.kv_store).await?;
 
     // Check whether there is an attestion request for us
-    if !attestation_requests.tss_account_ids.contains(&app_state.signer.public().0) {
+    if !attestation_requests.tss_account_ids.contains(&app_state.pair.public().0) {
         return Ok(StatusCode::OK);
     }
 
@@ -96,7 +96,7 @@ pub async fn get_attest(
     let rpc = get_rpc(&app_state.configuration.endpoint).await?;
 
     // Request attestation to get nonce
-    let nonce = request_attestation(&api, &rpc, &app_state.signer).await?;
+    let nonce = request_attestation(&api, &rpc, &app_state.pair).await?;
 
     let context = context_querystring.as_quote_context()?;
 
diff --git a/crates/threshold-signature-server/src/helpers/signing.rs b/crates/threshold-signature-server/src/helpers/signing.rs
index b894387d6..a01cf43fc 100644
--- a/crates/threshold-signature-server/src/helpers/signing.rs
+++ b/crates/threshold-signature-server/src/helpers/signing.rs
@@ -54,7 +54,7 @@ pub async fn do_signing(
     let info = SignInit::new(relayer_signature_request.clone(), signing_session_info.clone());
     let signing_service = ThresholdSigningService::new(state, kv_manager);
     let x25519_secret_key = &app_state.x25519_secret;
-    let signer = &app_state.signer;
+    let signer = &app_state.pair;
 
     let account_id = AccountId32(signer.public().0);
 
diff --git a/crates/threshold-signature-server/src/lib.rs b/crates/threshold-signature-server/src/lib.rs
index 86e7a1127..2063512d4 100644
--- a/crates/threshold-signature-server/src/lib.rs
+++ b/crates/threshold-signature-server/src/lib.rs
@@ -202,9 +202,8 @@ use crate::{
 #[derive(Clone)]
 pub struct AppState {
     listener_state: ListenerState,
-    signer: sr25519::Pair,
+    pair: sr25519::Pair,
     x25519_secret: StaticSecret,
-    x25519_public_key: [u8; 32],
     pub configuration: Configuration,
     pub kv_store: KvManager,
 }
@@ -215,7 +214,7 @@ impl AppState {
         kv_store: KvManager,
         validator_name: &Option<ValidatorName>,
     ) -> Self {
-        let (signer, x25519_secret) = if cfg!(test) || validator_name.is_some() {
+        let (pair, x25519_secret) = if cfg!(test) || validator_name.is_some() {
             get_signer_and_x25519_secret(&development_mnemonic(validator_name).to_string()).unwrap()
         } else {
             let (pair, _seed) = sr25519::Pair::generate();
@@ -223,28 +222,33 @@ impl AppState {
             (pair, x25519_secret)
         };
 
-        let x25519_public_key = x25519_dalek::PublicKey::from(&x25519_secret).to_bytes();
-
         Self {
-            signer,
+            pair,
             x25519_secret,
-            x25519_public_key,
             listener_state: ListenerState::default(),
             configuration,
             kv_store,
         }
     }
 
+    /// Get a [PairSigner] for submitting extrinsics with subxt
     pub fn signer(&self) -> PairSigner<EntropyConfig, sr25519::Pair> {
-        PairSigner::<EntropyConfig, sr25519::Pair>::new(self.signer.clone())
+        PairSigner::<EntropyConfig, sr25519::Pair>::new(self.pair.clone())
     }
 
+    /// Get the [AccountId32]
     pub fn account_id(&self) -> AccountId32 {
-        AccountId32::new(self.signer.public().0)
+        AccountId32::new(self.pair.public().0)
     }
 
+    /// Get the subxt account ID
     pub fn subxt_account_id(&self) -> SubxtAccountId32 {
-        SubxtAccountId32(self.signer.public().0)
+        SubxtAccountId32(self.pair.public().0)
+    }
+
+    /// Get the x25519 public key
+    pub fn x25519_public_key(&self) -> [u8; 32] {
+        x25519_dalek::PublicKey::from(&self.x25519_secret).to_bytes()
     }
 }
 
diff --git a/crates/threshold-signature-server/src/node_info/api.rs b/crates/threshold-signature-server/src/node_info/api.rs
index e462419a1..48fab750f 100644
--- a/crates/threshold-signature-server/src/node_info/api.rs
+++ b/crates/threshold-signature-server/src/node_info/api.rs
@@ -16,7 +16,6 @@ use crate::{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;
 
@@ -43,6 +42,8 @@ pub struct TssPublicKeys {
 /// Returns the TS server's public keys and HTTP endpoint
 #[tracing::instrument(skip_all)]
 pub async fn info(State(app_state): State<AppState>) -> Result<Json<TssPublicKeys>, GetInfoError> {
-    let tss_account = AccountId32(app_state.signer.public().0);
-    Ok(Json(TssPublicKeys { x25519_public_key: app_state.x25519_public_key, tss_account }))
+    Ok(Json(TssPublicKeys {
+        x25519_public_key: app_state.x25519_public_key(),
+        tss_account: app_state.subxt_account_id(),
+    }))
 }
diff --git a/crates/threshold-signature-server/src/signing_client/api.rs b/crates/threshold-signature-server/src/signing_client/api.rs
index ba4f822a1..b6a507e2d 100644
--- a/crates/threshold-signature-server/src/signing_client/api.rs
+++ b/crates/threshold-signature-server/src/signing_client/api.rs
@@ -82,7 +82,7 @@ pub async fn proactive_refresh(
     let api = get_api(&app_state.configuration.endpoint).await?;
     let rpc = get_rpc(&app_state.configuration.endpoint).await?;
 
-    check_in_registration_group(&ocw_data.validators_info, app_state.signer().account_id())
+    check_in_registration_group(&ocw_data.validators_info, &app_state.subxt_account_id())
         .map_err(|e| ProtocolErr::UserError(e.to_string()))?;
     validate_proactive_refresh(&api, &rpc, &app_state.kv_store, &ocw_data).await?;
 
diff --git a/crates/threshold-signature-server/src/user/api.rs b/crates/threshold-signature-server/src/user/api.rs
index 25c48adac..63eb80fb8 100644
--- a/crates/threshold-signature-server/src/user/api.rs
+++ b/crates/threshold-signature-server/src/user/api.rs
@@ -111,7 +111,7 @@ pub async fn relay_tx(
 
     signers_info
         .iter()
-        .find(|signer_info| signer_info.tss_account == *app_state.signer().account_id())
+        .find(|signer_info| signer_info.tss_account == app_state.subxt_account_id())
         .map_or(Ok(()), |_| Err(UserErr::RelayMessageSigner))?;
 
     let signed_message = encrypted_msg.decrypt(&app_state.x25519_secret, &[])?;
@@ -152,7 +152,7 @@ pub async fn relay_tx(
                     .iter()
                     .map(|signer_info| async {
                         let signed_message = EncryptedSignedMessage::new(
-                            &app_state.signer,
+                            &app_state.pair,
                             serde_json::to_vec(&relayer_sig_req.clone())?,
                             &signer_info.x25519_public_key,
                             &[],
@@ -338,7 +338,7 @@ pub async fn sign_tx(
 
     // Do the signing protocol in another task, so we can already respond
     tokio::spawn(async move {
-        let signer = app_state.clone().signer;
+        let signer = app_state.pair.clone();
         let signing_protocol_output = do_signing(
             &rpc,
             relayer_sig_request,
@@ -394,7 +394,7 @@ pub async fn generate_network_key(
     if in_registration_group.is_err() {
         tracing::warn!(
             "The account {:?} is not in the registration group for block_number {:?}",
-            app_state.signer().account_id(),
+            app_state.subxt_account_id(),
             data.block_number
         );
 
@@ -454,7 +454,7 @@ async fn setup_dkg(
         .ok_or_else(|| UserErr::OptionUnwrapError("Error getting block hash".to_string()))?;
 
     let nonce_call =
-        entropy::apis().account_nonce_api().account_nonce(app_state.signer().account_id().clone());
+        entropy::apis().account_nonce_api().account_nonce(app_state.subxt_account_id());
     let nonce = api.runtime_api().at(block_hash).call(nonce_call).await?;
 
     // TODO: Error handling really complex needs to be thought about.
diff --git a/crates/threshold-signature-server/src/validator/api.rs b/crates/threshold-signature-server/src/validator/api.rs
index 20d3607a3..8a82ef7c5 100644
--- a/crates/threshold-signature-server/src/validator/api.rs
+++ b/crates/threshold-signature-server/src/validator/api.rs
@@ -36,7 +36,6 @@ pub use entropy_protocol::{
 };
 use entropy_shared::{OcwMessageReshare, NETWORK_PARENT_KEY, NEXT_NETWORK_PARENT_KEY};
 use parity_scale_codec::{Decode, Encode};
-use sp_core::Pair;
 use std::{collections::BTreeSet, str::FromStr};
 use subxt::{
     backend::legacy::LegacyRpcMethods, ext::sp_core::sr25519, tx::PairSigner, utils::AccountId32,
@@ -71,7 +70,7 @@ pub async fn new_reshare(
 
     let is_proper_signer = validators_info
         .iter()
-        .any(|validator_info| validator_info.tss_account == *app_state.signer().account_id());
+        .any(|validator_info| validator_info.tss_account == app_state.subxt_account_id());
 
     if !is_proper_signer {
         return Ok(StatusCode::MISDIRECTED_REQUEST);
@@ -111,7 +110,7 @@ async fn do_reshare(
             .map_err(|_| ValidatorErr::Conversion("Verifying key conversion"))?,
     )
     .map_err(|e| ValidatorErr::VerifyingKeyError(e.to_string()))?;
-    let my_stash_address = get_stash_address(api, rpc, app_state.signer().account_id())
+    let my_stash_address = get_stash_address(api, rpc, &app_state.subxt_account_id())
         .await
         .map_err(|e| ValidatorErr::UserError(e.to_string()))?;
 
@@ -155,7 +154,7 @@ async fn do_reshare(
     };
 
     let session_id = SessionId::Reshare { verifying_key, block_number: data.block_number };
-    let account_id = AccountId32(app_state.signer.public().0);
+    let account_id = app_state.subxt_account_id();
 
     let mut converted_validator_info = vec![];
     let mut tss_accounts = vec![];
@@ -178,15 +177,9 @@ async fn do_reshare(
         &app_state.x25519_secret,
     )
     .await?;
-    let (new_key_share, aux_info) = execute_reshare(
-        session_id.clone(),
-        channels,
-        &app_state.signer,
-        inputs,
-        &new_holders,
-        None,
-    )
-    .await?;
+    let (new_key_share, aux_info) =
+        execute_reshare(session_id.clone(), channels, &app_state.pair, inputs, &new_holders, None)
+            .await?;
 
     let serialized_key_share = key_serialize(&(new_key_share, aux_info))
         .map_err(|_| ProtocolErr::KvSerialize("Kv Serialize Error".to_string()))?;
@@ -227,7 +220,7 @@ pub async fn rotate_network_key(
         .map_err(|e| ValidatorErr::UserError(e.to_string()))?;
 
     let is_proper_signer = is_signer_or_delete_parent_key(
-        app_state.signer().account_id(),
+        &app_state.subxt_account_id(),
         validators_info.clone(),
         &app_state.kv_store,
     )

From 1c36ae0b94592c78bed14b5c7db2c0571b05cc78 Mon Sep 17 00:00:00 2001
From: peg <ameba23@systemli.org>
Date: Mon, 16 Dec 2024 14:11:20 +0100
Subject: [PATCH 18/77] Allow for entropy-tss to be put in a non-ready state

---
 .../protocol/src/protocol_transport/errors.rs |  2 ++
 crates/threshold-signature-server/src/lib.rs  | 15 +++++++++++++
 crates/threshold-signature-server/src/main.rs | 22 ++++++++++++-------
 .../src/node_info/api.rs                      |  2 ++
 .../src/signing_client/api.rs                 |  4 ++++
 .../src/signing_client/errors.rs              |  2 ++
 .../src/signing_client/protocol_transport.rs  |  4 ++++
 .../src/user/api.rs                           | 11 ++++++++++
 .../src/user/errors.rs                        |  2 ++
 .../src/validator/api.rs                      |  8 +++++++
 .../src/validator/errors.rs                   |  2 ++
 11 files changed, 66 insertions(+), 8 deletions(-)

diff --git a/crates/protocol/src/protocol_transport/errors.rs b/crates/protocol/src/protocol_transport/errors.rs
index b22f7d93b..cc4186fc8 100644
--- a/crates/protocol/src/protocol_transport/errors.rs
+++ b/crates/protocol/src/protocol_transport/errors.rs
@@ -40,6 +40,8 @@ pub enum WsError {
     Serialization(#[from] bincode::Error),
     #[error("Received bad subscribe message")]
     BadSubscribeMessage,
+    #[error("Node has started fresh and not yet successfully set up")]
+    NotReady,
 }
 
 /// An error relating to handling a `ProtocolMessage`
diff --git a/crates/threshold-signature-server/src/lib.rs b/crates/threshold-signature-server/src/lib.rs
index 2063512d4..d70c2419f 100644
--- a/crates/threshold-signature-server/src/lib.rs
+++ b/crates/threshold-signature-server/src/lib.rs
@@ -178,6 +178,7 @@ use axum::{
 use entropy_kvdb::kv_manager::KvManager;
 use rand_core::OsRng;
 use sp_core::{crypto::AccountId32, sr25519, Pair};
+use std::sync::{Arc, RwLock};
 use subxt::{tx::PairSigner, utils::AccountId32 as SubxtAccountId32};
 use tower_http::{
     cors::{Any, CorsLayer},
@@ -201,6 +202,7 @@ use crate::{
 
 #[derive(Clone)]
 pub struct AppState {
+    ready: Arc<RwLock<bool>>,
     listener_state: ListenerState,
     pair: sr25519::Pair,
     x25519_secret: StaticSecret,
@@ -223,6 +225,7 @@ impl AppState {
         };
 
         Self {
+            ready: Arc::new(RwLock::new(false)),
             pair,
             x25519_secret,
             listener_state: ListenerState::default(),
@@ -231,6 +234,18 @@ impl AppState {
         }
     }
 
+    pub fn is_ready(&self) -> bool {
+        match self.ready.read() {
+            Ok(r) => *r,
+            _ => false,
+        }
+    }
+
+    pub fn make_ready(&self) {
+        let mut is_ready = self.ready.write().unwrap();
+        *is_ready = true;
+    }
+
     /// Get a [PairSigner] for submitting extrinsics with subxt
     pub fn signer(&self) -> PairSigner<EntropyConfig, sr25519::Pair> {
         PairSigner::<EntropyConfig, sr25519::Pair>::new(self.pair.clone())
diff --git a/crates/threshold-signature-server/src/main.rs b/crates/threshold-signature-server/src/main.rs
index bd9451e02..bfcd6b4c8 100644
--- a/crates/threshold-signature-server/src/main.rs
+++ b/crates/threshold-signature-server/src/main.rs
@@ -62,15 +62,21 @@ async fn main() {
 
     setup_latest_block_number(&kv_store).await.expect("Issue setting up Latest Block Number");
 
-    // Below deals with syncing the kvdb
-    let addr = SocketAddr::from_str(&args.threshold_url).expect("failed to parse threshold url.");
-
-    entropy_tss::launch::check_node_prerequisites(
-        &app_state.configuration.endpoint,
-        &app_state.account_id().to_ss58check(),
-    )
-    .await;
+    {
+        let app_state = app_state.clone();
+        tokio::spawn(async move {
+            // Check for a connection to the chain node parallel to starting the tss_server so that
+            // we already can expose the `/info` http route
+            entropy_tss::launch::check_node_prerequisites(
+                &app_state.configuration.endpoint,
+                &app_state.account_id().to_ss58check(),
+            )
+            .await;
+            app_state.make_ready();
+        });
+    }
 
+    let addr = SocketAddr::from_str(&args.threshold_url).expect("failed to parse threshold url.");
     let listener = tokio::net::TcpListener::bind(&addr)
         .await
         .expect("Unable to bind to given server address.");
diff --git a/crates/threshold-signature-server/src/node_info/api.rs b/crates/threshold-signature-server/src/node_info/api.rs
index 48fab750f..066199975 100644
--- a/crates/threshold-signature-server/src/node_info/api.rs
+++ b/crates/threshold-signature-server/src/node_info/api.rs
@@ -35,6 +35,7 @@ pub async fn hashes() -> Json<Vec<HashingAlgorithm>> {
 /// Public signing and encryption keys associated with a TS server
 #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)]
 pub struct TssPublicKeys {
+    pub connected_to_chain: bool,
     pub tss_account: AccountId32,
     pub x25519_public_key: X25519PublicKey,
 }
@@ -43,6 +44,7 @@ pub struct TssPublicKeys {
 #[tracing::instrument(skip_all)]
 pub async fn info(State(app_state): State<AppState>) -> Result<Json<TssPublicKeys>, GetInfoError> {
     Ok(Json(TssPublicKeys {
+        connected_to_chain: app_state.is_ready(),
         x25519_public_key: app_state.x25519_public_key(),
         tss_account: app_state.subxt_account_id(),
     }))
diff --git a/crates/threshold-signature-server/src/signing_client/api.rs b/crates/threshold-signature-server/src/signing_client/api.rs
index b6a507e2d..a496f127f 100644
--- a/crates/threshold-signature-server/src/signing_client/api.rs
+++ b/crates/threshold-signature-server/src/signing_client/api.rs
@@ -78,6 +78,10 @@ pub async fn proactive_refresh(
     State(app_state): State<AppState>,
     encoded_data: Bytes,
 ) -> Result<StatusCode, ProtocolErr> {
+    if !app_state.is_ready() {
+        return Err(ProtocolErr::NotReady);
+    }
+
     let ocw_data = OcwMessageProactiveRefresh::decode(&mut encoded_data.as_ref())?;
     let api = get_api(&app_state.configuration.endpoint).await?;
     let rpc = get_rpc(&app_state.configuration.endpoint).await?;
diff --git a/crates/threshold-signature-server/src/signing_client/errors.rs b/crates/threshold-signature-server/src/signing_client/errors.rs
index 4079c0162..0cf1ec0cc 100644
--- a/crates/threshold-signature-server/src/signing_client/errors.rs
+++ b/crates/threshold-signature-server/src/signing_client/errors.rs
@@ -110,6 +110,8 @@ pub enum ProtocolErr {
     Listener(#[from] entropy_protocol::errors::ListenerErr),
     #[error("Failed to derive BIP-32 account: {0}")]
     Bip32DerivationError(#[from] bip32::Error),
+    #[error("Node has started fresh and not yet successfully set up")]
+    NotReady,
 }
 
 impl IntoResponse for ProtocolErr {
diff --git a/crates/threshold-signature-server/src/signing_client/protocol_transport.rs b/crates/threshold-signature-server/src/signing_client/protocol_transport.rs
index b8234d7b1..5c266753b 100644
--- a/crates/threshold-signature-server/src/signing_client/protocol_transport.rs
+++ b/crates/threshold-signature-server/src/signing_client/protocol_transport.rs
@@ -113,6 +113,10 @@ pub async fn open_protocol_connections(
 
 /// Handle an incoming websocket connection
 pub async fn handle_socket(socket: WebSocket, app_state: AppState) -> Result<(), WsError> {
+    if !app_state.is_ready() {
+        return Err(WsError::NotReady);
+    }
+
     let (mut encrypted_connection, serialized_signed_message) =
         noise_handshake_responder(socket, &app_state.x25519_secret)
             .await
diff --git a/crates/threshold-signature-server/src/user/api.rs b/crates/threshold-signature-server/src/user/api.rs
index 63eb80fb8..21c6b62bb 100644
--- a/crates/threshold-signature-server/src/user/api.rs
+++ b/crates/threshold-signature-server/src/user/api.rs
@@ -89,6 +89,9 @@ pub async fn relay_tx(
     State(app_state): State<AppState>,
     Json(encrypted_msg): Json<EncryptedSignedMessage>,
 ) -> Result<(StatusCode, Body), UserErr> {
+    if !app_state.is_ready() {
+        return Err(UserErr::NotReady);
+    }
     let api = get_api(&app_state.configuration.endpoint).await?;
     let rpc = get_rpc(&app_state.configuration.endpoint).await?;
 
@@ -217,6 +220,10 @@ pub async fn sign_tx(
     State(app_state): State<AppState>,
     Json(encrypted_msg): Json<EncryptedSignedMessage>,
 ) -> Result<(StatusCode, Body), UserErr> {
+    if !app_state.is_ready() {
+        return Err(UserErr::NotReady);
+    }
+
     let api = get_api(&app_state.configuration.endpoint).await?;
     let rpc = get_rpc(&app_state.configuration.endpoint).await?;
 
@@ -378,6 +385,10 @@ pub async fn generate_network_key(
     State(app_state): State<AppState>,
     encoded_data: Bytes,
 ) -> Result<StatusCode, UserErr> {
+    if !app_state.is_ready() {
+        return Err(UserErr::NotReady);
+    }
+
     let data = OcwMessageDkg::decode(&mut encoded_data.as_ref())?;
     tracing::Span::current().record("block_number", data.block_number);
 
diff --git a/crates/threshold-signature-server/src/user/errors.rs b/crates/threshold-signature-server/src/user/errors.rs
index f17942ca8..30cd0dfa9 100644
--- a/crates/threshold-signature-server/src/user/errors.rs
+++ b/crates/threshold-signature-server/src/user/errors.rs
@@ -179,6 +179,8 @@ pub enum UserErr {
     TooFewSigners,
     #[error("Non signer sent from relayer")]
     IncorrectSigner,
+    #[error("Node has started fresh and not yet successfully set up")]
+    NotReady,
 }
 
 impl From<hkdf::InvalidLength> for UserErr {
diff --git a/crates/threshold-signature-server/src/validator/api.rs b/crates/threshold-signature-server/src/validator/api.rs
index 8a82ef7c5..37cee0dc5 100644
--- a/crates/threshold-signature-server/src/validator/api.rs
+++ b/crates/threshold-signature-server/src/validator/api.rs
@@ -53,6 +53,10 @@ pub async fn new_reshare(
     State(app_state): State<AppState>,
     encoded_data: Bytes,
 ) -> Result<StatusCode, ValidatorErr> {
+    if !app_state.is_ready() {
+        return Err(ValidatorErr::NotReady);
+    }
+
     let data = OcwMessageReshare::decode(&mut encoded_data.as_ref())?;
 
     let api = get_api(&app_state.configuration.endpoint).await?;
@@ -204,6 +208,10 @@ async fn do_reshare(
 pub async fn rotate_network_key(
     State(app_state): State<AppState>,
 ) -> Result<StatusCode, ValidatorErr> {
+    if !app_state.is_ready() {
+        return Err(ValidatorErr::NotReady);
+    }
+
     // validate from chain
     let api = get_api(&app_state.configuration.endpoint).await?;
     let rpc = get_rpc(&app_state.configuration.endpoint).await?;
diff --git a/crates/threshold-signature-server/src/validator/errors.rs b/crates/threshold-signature-server/src/validator/errors.rs
index 7d64816d4..3556139e0 100644
--- a/crates/threshold-signature-server/src/validator/errors.rs
+++ b/crates/threshold-signature-server/src/validator/errors.rs
@@ -97,6 +97,8 @@ pub enum ValidatorErr {
     NotImplemented,
     #[error("Input must be 32 bytes: {0}")]
     TryFromSlice(#[from] TryFromSliceError),
+    #[error("Node has started fresh and not yet successfully set up")]
+    NotReady,
 }
 
 impl IntoResponse for ValidatorErr {

From e416800fca6c93d3ce74dacf46cd5abf514ef5e4 Mon Sep 17 00:00:00 2001
From: peg <ameba23@systemli.org>
Date: Mon, 16 Dec 2024 19:08:46 +0100
Subject: [PATCH 19/77] Update node info test

---
 crates/threshold-signature-server/src/node_info/tests.rs | 1 +
 1 file changed, 1 insertion(+)

diff --git a/crates/threshold-signature-server/src/node_info/tests.rs b/crates/threshold-signature-server/src/node_info/tests.rs
index 2676ecb87..5640c12f1 100644
--- a/crates/threshold-signature-server/src/node_info/tests.rs
+++ b/crates/threshold-signature-server/src/node_info/tests.rs
@@ -74,6 +74,7 @@ async fn info_test() {
         TssPublicKeys {
             tss_account: TSS_ACCOUNTS[0].clone(),
             x25519_public_key: X25519_PUBLIC_KEYS[0],
+            connected_to_chain: false,
         }
     );
     clean_tests();

From 7d8690bbaf82cefd384f8f8ea3c04f4f83f8bcc1 Mon Sep 17 00:00:00 2001
From: peg <ameba23@systemli.org>
Date: Tue, 17 Dec 2024 10:59:43 +0100
Subject: [PATCH 20/77] Make app state ready in tests

---
 crates/threshold-signature-server/src/helpers/tests.rs | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/crates/threshold-signature-server/src/helpers/tests.rs b/crates/threshold-signature-server/src/helpers/tests.rs
index 05eab6fc9..479a7109c 100644
--- a/crates/threshold-signature-server/src/helpers/tests.rs
+++ b/crates/threshold-signature-server/src/helpers/tests.rs
@@ -72,6 +72,7 @@ pub async fn setup_client() -> KvManager {
     let _ = setup_latest_block_number(&kv_store).await;
     let configuration = Configuration::new(DEFAULT_ENDPOINT.to_string());
     let app_state = AppState::new(configuration, kv_store.clone(), &Some(ValidatorName::Alice));
+    app_state.make_ready();
 
     let app = app(app_state).into_make_service();
 
@@ -107,6 +108,7 @@ pub async fn create_clients(
     }
 
     let app_state = AppState::new(configuration, kv_store.clone(), validator_name);
+    app_state.make_ready();
 
     let account_id = app_state.subxt_account_id();
 

From 96c6a1a898e265b0dab6b9daecfaaa5518ec65f7 Mon Sep 17 00:00:00 2001
From: peg <ameba23@systemli.org>
Date: Tue, 17 Dec 2024 11:13:02 +0100
Subject: [PATCH 21/77] Comments

---
 .../threshold-signature-server/src/helpers/launch.rs   | 10 ++++++++++
 crates/threshold-signature-server/src/helpers/tests.rs |  2 ++
 2 files changed, 12 insertions(+)

diff --git a/crates/threshold-signature-server/src/helpers/launch.rs b/crates/threshold-signature-server/src/helpers/launch.rs
index 5a66e0858..3c7085888 100644
--- a/crates/threshold-signature-server/src/helpers/launch.rs
+++ b/crates/threshold-signature-server/src/helpers/launch.rs
@@ -311,6 +311,16 @@ pub async fn check_node_prerequisites(url: &str, account_id: &str) {
                     tracing::warn!("Unable to query the account balance of `{}`", &account_id)
                 },
             }
+
+            // TODO now check if there exists a threshold server with our details - if there is not,
+            // we need to wait until there is
+            // let stash_address_query = entropy::storage()
+            //     .staking_extension()
+            //     .threshold_to_stash(validator_info.tss_account.clone());
+            //
+            // let stash_address = query_chain(&api, &rpc, stash_address_query, None)
+            //     .await?
+            //     .ok_or_else(|| UserErr::ChainFetch("Stash Fetch Error"))?;
         },
         Err(_err) => {
             tracing::error!("Unable to establish connection with Substrate node at `{}`", url);
diff --git a/crates/threshold-signature-server/src/helpers/tests.rs b/crates/threshold-signature-server/src/helpers/tests.rs
index 479a7109c..c618f682b 100644
--- a/crates/threshold-signature-server/src/helpers/tests.rs
+++ b/crates/threshold-signature-server/src/helpers/tests.rs
@@ -72,6 +72,7 @@ pub async fn setup_client() -> KvManager {
     let _ = setup_latest_block_number(&kv_store).await;
     let configuration = Configuration::new(DEFAULT_ENDPOINT.to_string());
     let app_state = AppState::new(configuration, kv_store.clone(), &Some(ValidatorName::Alice));
+    // Mock making the pre-requisite checks by setting the application state to ready
     app_state.make_ready();
 
     let app = app(app_state).into_make_service();
@@ -108,6 +109,7 @@ pub async fn create_clients(
     }
 
     let app_state = AppState::new(configuration, kv_store.clone(), validator_name);
+    // Mock making the pre-requisite checks by setting the application state to ready
     app_state.make_ready();
 
     let account_id = app_state.subxt_account_id();

From 91ac83422d1ab72f6d1900500b93851a4c6c141a Mon Sep 17 00:00:00 2001
From: peg <ameba23@systemli.org>
Date: Tue, 17 Dec 2024 12:23:07 +0100
Subject: [PATCH 22/77] Fix node info test

---
 crates/threshold-signature-server/src/node_info/tests.rs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/crates/threshold-signature-server/src/node_info/tests.rs b/crates/threshold-signature-server/src/node_info/tests.rs
index 5640c12f1..502dd7b2b 100644
--- a/crates/threshold-signature-server/src/node_info/tests.rs
+++ b/crates/threshold-signature-server/src/node_info/tests.rs
@@ -74,7 +74,7 @@ async fn info_test() {
         TssPublicKeys {
             tss_account: TSS_ACCOUNTS[0].clone(),
             x25519_public_key: X25519_PUBLIC_KEYS[0],
-            connected_to_chain: false,
+            connected_to_chain: true,
         }
     );
     clean_tests();

From b59d78d598ba6cdc832566d1f66962fc4db938a2 Mon Sep 17 00:00:00 2001
From: peg <ameba23@systemli.org>
Date: Tue, 17 Dec 2024 13:06:46 +0100
Subject: [PATCH 23/77] Update pre-requisite checks

---
 .../src/helpers/launch.rs                     | 49 +++++++++++++------
 crates/threshold-signature-server/src/main.rs |  8 +--
 2 files changed, 35 insertions(+), 22 deletions(-)

diff --git a/crates/threshold-signature-server/src/helpers/launch.rs b/crates/threshold-signature-server/src/helpers/launch.rs
index 3c7085888..092fe7ec6 100644
--- a/crates/threshold-signature-server/src/helpers/launch.rs
+++ b/crates/threshold-signature-server/src/helpers/launch.rs
@@ -17,13 +17,16 @@
 
 use std::{fs, path::PathBuf};
 
+use crate::{chain_api::entropy, helpers::substrate::query_chain, AppState};
 use clap::Parser;
+use entropy_client::substrate::SubstrateError;
 use entropy_kvdb::{
     encrypted_sled::PasswordMethod,
     kv_manager::{error::KvError, KvManager},
 };
 use entropy_shared::NETWORK_PARENT_KEY;
 use serde::Deserialize;
+use sp_core::crypto::Ss58Codec;
 
 pub const DEFAULT_MNEMONIC: &str =
     "alarm mutual concert decrease hurry invest culture survey diagram crash snap click";
@@ -259,8 +262,10 @@ pub async fn setup_latest_block_number(kv: &KvManager) -> Result<(), KvError> {
     Ok(())
 }
 
-pub async fn check_node_prerequisites(url: &str, account_id: &str) {
+pub async fn check_node_prerequisites(app_state: AppState) {
     use crate::chain_api::{get_api, get_rpc};
+    let url = &app_state.configuration.endpoint;
+    let account_id = app_state.account_id();
 
     let connect_to_substrate_node = || async {
         tracing::info!("Attempting to establish connection to Substrate node at `{}`", url);
@@ -277,8 +282,10 @@ pub async fn check_node_prerequisites(url: &str, account_id: &str) {
     };
 
     // Note: By default this will wait 15 minutes before it stops retry attempts.
-    let backoff = backoff::ExponentialBackoff::default();
-    match backoff::future::retry(backoff, connect_to_substrate_node).await {
+    let mut backoff = backoff::ExponentialBackoff::default();
+    // Never give up trying to connect
+    backoff.max_elapsed_time = None;
+    match backoff::future::retry(backoff.clone(), connect_to_substrate_node).await {
         Ok((api, rpc)) => {
             tracing::info!("Sucessfully connected to Substrate node!");
 
@@ -286,7 +293,7 @@ pub async fn check_node_prerequisites(url: &str, account_id: &str) {
             let balance_query = crate::validator::api::check_balance_for_fees(
                 &api,
                 &rpc,
-                account_id.to_string(),
+                account_id.to_ss58check().to_string(),
                 entropy_shared::MIN_BALANCE,
             )
             .await
@@ -312,19 +319,31 @@ pub async fn check_node_prerequisites(url: &str, account_id: &str) {
                 },
             }
 
-            // TODO now check if there exists a threshold server with our details - if there is not,
+            // Now check if there exists a threshold server with our details - if there is not,
             // we need to wait until there is
-            // let stash_address_query = entropy::storage()
-            //     .staking_extension()
-            //     .threshold_to_stash(validator_info.tss_account.clone());
-            //
-            // let stash_address = query_chain(&api, &rpc, stash_address_query, None)
-            //     .await?
-            //     .ok_or_else(|| UserErr::ChainFetch("Stash Fetch Error"))?;
+            let check_for_tss_account_id = || async {
+                let stash_address_query = entropy::storage()
+                    .staking_extension()
+                    .threshold_to_stash(subxt::utils::AccountId32(*account_id.as_ref()));
+
+                let _stash_address = query_chain(&api, &rpc, stash_address_query, None)
+                    .await?
+                    .ok_or_else(|| SubstrateError::NoEvent)?;
+                Ok(())
+            };
+
+            tracing::info!(
+                "Checking if our account ID has been registered on chain `{}`",
+                &account_id
+            );
+            if let Err(error) = backoff::future::retry(backoff, check_for_tss_account_id).await {
+                tracing::error!("This should never happen because backoff has no permanent errors or maximum timeout: {error}");
+            }
+            tracing::info!("TSS node passed all prerequisite checks and is ready");
+            app_state.make_ready();
         },
-        Err(_err) => {
-            tracing::error!("Unable to establish connection with Substrate node at `{}`", url);
-            panic!("Unable to establish connection with Substrate node.");
+        Err(error) => {
+            tracing::error!("This should never happen because backoff has no permanent errors or maximum timeout: {error:?}");
         },
     }
 }
diff --git a/crates/threshold-signature-server/src/main.rs b/crates/threshold-signature-server/src/main.rs
index bfcd6b4c8..9dfffd748 100644
--- a/crates/threshold-signature-server/src/main.rs
+++ b/crates/threshold-signature-server/src/main.rs
@@ -16,7 +16,6 @@
 use std::{net::SocketAddr, str::FromStr};
 
 use clap::Parser;
-use sp_core::crypto::Ss58Codec;
 
 use entropy_tss::{
     app,
@@ -67,12 +66,7 @@ async fn main() {
         tokio::spawn(async move {
             // Check for a connection to the chain node parallel to starting the tss_server so that
             // we already can expose the `/info` http route
-            entropy_tss::launch::check_node_prerequisites(
-                &app_state.configuration.endpoint,
-                &app_state.account_id().to_ss58check(),
-            )
-            .await;
-            app_state.make_ready();
+            entropy_tss::launch::check_node_prerequisites(app_state).await;
         });
     }
 

From c7d5ca2444c38cb2c51a5f773c89ec4f8fdba597 Mon Sep 17 00:00:00 2001
From: peg <ameba23@systemli.org>
Date: Tue, 17 Dec 2024 13:23:46 +0100
Subject: [PATCH 24/77] Clippy

---
 crates/threshold-signature-server/src/helpers/launch.rs | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/crates/threshold-signature-server/src/helpers/launch.rs b/crates/threshold-signature-server/src/helpers/launch.rs
index 092fe7ec6..2cf98b994 100644
--- a/crates/threshold-signature-server/src/helpers/launch.rs
+++ b/crates/threshold-signature-server/src/helpers/launch.rs
@@ -281,10 +281,8 @@ pub async fn check_node_prerequisites(app_state: AppState) {
         Ok((api, rpc))
     };
 
-    // Note: By default this will wait 15 minutes before it stops retry attempts.
-    let mut backoff = backoff::ExponentialBackoff::default();
     // Never give up trying to connect
-    backoff.max_elapsed_time = None;
+    let backoff = backoff::ExponentialBackoff { max_elapsed_time: None, ..Default::default() };
     match backoff::future::retry(backoff.clone(), connect_to_substrate_node).await {
         Ok((api, rpc)) => {
             tracing::info!("Sucessfully connected to Substrate node!");

From a05a2a333853ea12c3ea7518f6ff01cbd06a7935 Mon Sep 17 00:00:00 2001
From: peg <ameba23@systemli.org>
Date: Tue, 17 Dec 2024 14:19:14 +0100
Subject: [PATCH 25/77] Force getting minimum balance before start

---
 .../src/helpers/launch.rs                     | 55 ++++++++++---------
 1 file changed, 28 insertions(+), 27 deletions(-)

diff --git a/crates/threshold-signature-server/src/helpers/launch.rs b/crates/threshold-signature-server/src/helpers/launch.rs
index 2cf98b994..d8e43ab2f 100644
--- a/crates/threshold-signature-server/src/helpers/launch.rs
+++ b/crates/threshold-signature-server/src/helpers/launch.rs
@@ -283,40 +283,41 @@ pub async fn check_node_prerequisites(app_state: AppState) {
 
     // Never give up trying to connect
     let backoff = backoff::ExponentialBackoff { max_elapsed_time: None, ..Default::default() };
+
     match backoff::future::retry(backoff.clone(), connect_to_substrate_node).await {
         Ok((api, rpc)) => {
             tracing::info!("Sucessfully connected to Substrate node!");
 
             tracing::info!("Checking balance of threshold server AccountId `{}`", &account_id);
-            let balance_query = crate::validator::api::check_balance_for_fees(
-                &api,
-                &rpc,
-                account_id.to_ss58check().to_string(),
-                entropy_shared::MIN_BALANCE,
-            )
-            .await
-            .map_err(|_| Err::<bool, String>("Failed to get balance of account.".to_string()));
-
-            match balance_query {
-                Ok(has_minimum_balance) => {
-                    if has_minimum_balance {
-                        tracing::info!(
-                            "The account `{}` has enough funds for submitting extrinsics.",
-                            &account_id
-                        )
-                    } else {
-                        tracing::warn!(
-                            "The account `{}` does not meet the minimum balance of `{}`",
-                            &account_id,
-                            entropy_shared::MIN_BALANCE,
-                        )
-                    }
-                },
-                Err(_) => {
-                    tracing::warn!("Unable to query the account balance of `{}`", &account_id)
-                },
+
+            let balance_query = || async {
+                let has_minimum_balance = crate::validator::api::check_balance_for_fees(
+                    &api,
+                    &rpc,
+                    account_id.to_ss58check().to_string(),
+                    entropy_shared::MIN_BALANCE,
+                )
+                .await
+                .map_err(|_| {
+                    tracing::error!("Unable to query the account balance of `{}`", &account_id);
+                    "Unable to query account balance".to_string()
+                })?;
+                Ok(if has_minimum_balance {
+                    ()
+                } else {
+                    Err("Minimum balance not met".to_string())?
+                })
+            };
+
+            if let Err(error) = backoff::future::retry(backoff.clone(), balance_query).await {
+                tracing::error!("This should never happen because backoff has no permanent errors or maximum timeout: {error}");
             }
 
+            tracing::info!(
+                "The account `{}` has enough funds for submitting extrinsics.",
+                &account_id
+            );
+
             // Now check if there exists a threshold server with our details - if there is not,
             // we need to wait until there is
             let check_for_tss_account_id = || async {

From 079c394d4c5e09852d87f6c2c391784c399d5085 Mon Sep 17 00:00:00 2001
From: peg <ameba23@systemli.org>
Date: Tue, 17 Dec 2024 16:03:07 +0100
Subject: [PATCH 26/77] Clippy

---
 crates/threshold-signature-server/src/helpers/launch.rs | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)

diff --git a/crates/threshold-signature-server/src/helpers/launch.rs b/crates/threshold-signature-server/src/helpers/launch.rs
index d8e43ab2f..3a541eddd 100644
--- a/crates/threshold-signature-server/src/helpers/launch.rs
+++ b/crates/threshold-signature-server/src/helpers/launch.rs
@@ -302,11 +302,10 @@ pub async fn check_node_prerequisites(app_state: AppState) {
                     tracing::error!("Unable to query the account balance of `{}`", &account_id);
                     "Unable to query account balance".to_string()
                 })?;
-                Ok(if has_minimum_balance {
-                    ()
-                } else {
+                if !has_minimum_balance {
                     Err("Minimum balance not met".to_string())?
-                })
+                }
+                Ok(())
             };
 
             if let Err(error) = backoff::future::retry(backoff.clone(), balance_query).await {

From 12e940d9fc2d330abb0fee5b42932fcb81e102fe Mon Sep 17 00:00:00 2001
From: peg <ameba23@systemli.org>
Date: Tue, 17 Dec 2024 16:21:52 +0100
Subject: [PATCH 27/77] Comments

---
 crates/protocol/src/protocol_transport/errors.rs     |  2 +-
 .../src/helpers/validator.rs                         | 10 ----------
 crates/threshold-signature-server/src/lib.rs         | 12 ++++++++++++
 3 files changed, 13 insertions(+), 11 deletions(-)

diff --git a/crates/protocol/src/protocol_transport/errors.rs b/crates/protocol/src/protocol_transport/errors.rs
index cc4186fc8..9d33cb7e3 100644
--- a/crates/protocol/src/protocol_transport/errors.rs
+++ b/crates/protocol/src/protocol_transport/errors.rs
@@ -40,7 +40,7 @@ pub enum WsError {
     Serialization(#[from] bincode::Error),
     #[error("Received bad subscribe message")]
     BadSubscribeMessage,
-    #[error("Node has started fresh and not yet successfully set up")]
+    #[error("Node has started fresh and is not yet successfully set up")]
     NotReady,
 }
 
diff --git a/crates/threshold-signature-server/src/helpers/validator.rs b/crates/threshold-signature-server/src/helpers/validator.rs
index e7e33ad56..06d3bf809 100644
--- a/crates/threshold-signature-server/src/helpers/validator.rs
+++ b/crates/threshold-signature-server/src/helpers/validator.rs
@@ -27,16 +27,6 @@ use crate::user::UserErr;
 const KDF_SR25519: &[u8] = b"sr25519-threshold-account";
 const KDF_X25519: &[u8] = b"X25519-keypair";
 
-// Returns a PairSigner for this node's threshold server.
-// The PairSigner is stored as an encrypted mnemonic in the kvdb and
-// is used to sign encrypted messages and to submit extrinsics on chain.
-// pub async fn get_signer(
-//     kv: &KvManager,
-// ) -> Result<PairSigner<EntropyConfig, sr25519::Pair>, UserErr> {
-//     let hkdf = get_hkdf(kv).await?;
-//     get_signer_from_hkdf(&hkdf)
-// }
-
 /// Get the PairSigner as above, and also the x25519 encryption keypair for
 /// this threshold server
 pub fn get_signer_and_x25519_secret(
diff --git a/crates/threshold-signature-server/src/lib.rs b/crates/threshold-signature-server/src/lib.rs
index d70c2419f..841aeb1b6 100644
--- a/crates/threshold-signature-server/src/lib.rs
+++ b/crates/threshold-signature-server/src/lib.rs
@@ -202,15 +202,24 @@ use crate::{
 
 #[derive(Clone)]
 pub struct AppState {
+    /// Tracks whether prerequisite checks have passed.
+    /// This means:
+    /// - Communication has been established with the chain node
+    /// - The TSS account is funded
+    /// - The TSS account is registered with the staking extension pallet
     ready: Arc<RwLock<bool>>,
+    /// Tracks incoming protocol connections with other TSS nodes
     listener_state: ListenerState,
+    /// Keypair for TSS account
     pair: sr25519::Pair,
+    /// Secret encryption key
     x25519_secret: StaticSecret,
     pub configuration: Configuration,
     pub kv_store: KvManager,
 }
 
 impl AppState {
+    /// Setup AppState, generating new keypairs unless a test validator name is passed
     pub fn new(
         configuration: Configuration,
         kv_store: KvManager,
@@ -234,6 +243,8 @@ impl AppState {
         }
     }
 
+    /// Returns true if all prerequisite checks have passed.
+    /// Is is not possible to participate in the protocols before this is true.
     pub fn is_ready(&self) -> bool {
         match self.ready.read() {
             Ok(r) => *r,
@@ -241,6 +252,7 @@ impl AppState {
         }
     }
 
+    /// Mark the node as ready. This is called once when the prerequisite checks have passed.
     pub fn make_ready(&self) {
         let mut is_ready = self.ready.write().unwrap();
         *is_ready = true;

From a71521b645bf9b174e698ce7b0ff4984b3b5aceb Mon Sep 17 00:00:00 2001
From: peg <ameba23@systemli.org>
Date: Tue, 17 Dec 2024 16:36:31 +0100
Subject: [PATCH 28/77] Fixes, add helper

---
 crates/threshold-signature-server/src/lib.rs    | 17 +++++++++++++++--
 .../src/node_info/api.rs                        |  5 +++--
 .../src/node_info/tests.rs                      |  2 +-
 3 files changed, 19 insertions(+), 5 deletions(-)

diff --git a/crates/threshold-signature-server/src/lib.rs b/crates/threshold-signature-server/src/lib.rs
index 841aeb1b6..d6055cca3 100644
--- a/crates/threshold-signature-server/src/lib.rs
+++ b/crates/threshold-signature-server/src/lib.rs
@@ -179,7 +179,10 @@ use entropy_kvdb::kv_manager::KvManager;
 use rand_core::OsRng;
 use sp_core::{crypto::AccountId32, sr25519, Pair};
 use std::sync::{Arc, RwLock};
-use subxt::{tx::PairSigner, utils::AccountId32 as SubxtAccountId32};
+use subxt::{
+    backend::legacy::LegacyRpcMethods, tx::PairSigner, utils::AccountId32 as SubxtAccountId32,
+    OnlineClient,
+};
 use tower_http::{
     cors::{Any, CorsLayer},
     trace::{self, TraceLayer},
@@ -190,7 +193,7 @@ use x25519_dalek::StaticSecret;
 pub use crate::helpers::{launch, validator::get_signer_and_x25519_secret};
 use crate::{
     attestation::api::{attest, get_attest},
-    chain_api::EntropyConfig,
+    chain_api::{get_api, get_rpc, EntropyConfig},
     health::api::healthz,
     launch::{development_mnemonic, Configuration, ValidatorName},
     node_info::api::{hashes, info, version as get_version},
@@ -277,6 +280,16 @@ impl AppState {
     pub fn x25519_public_key(&self) -> [u8; 32] {
         x25519_dalek::PublicKey::from(&self.x25519_secret).to_bytes()
     }
+
+    /// Convenience function to get chain api and rpc
+    pub async fn get_api_rpc(
+        &self,
+    ) -> Result<(OnlineClient<EntropyConfig>, LegacyRpcMethods<EntropyConfig>), subxt::Error> {
+        Ok((
+            get_api(&self.configuration.endpoint).await?,
+            get_rpc(&self.configuration.endpoint).await?,
+        ))
+    }
 }
 
 pub fn app(app_state: AppState) -> Router {
diff --git a/crates/threshold-signature-server/src/node_info/api.rs b/crates/threshold-signature-server/src/node_info/api.rs
index 066199975..ba977c6f2 100644
--- a/crates/threshold-signature-server/src/node_info/api.rs
+++ b/crates/threshold-signature-server/src/node_info/api.rs
@@ -35,7 +35,8 @@ pub async fn hashes() -> Json<Vec<HashingAlgorithm>> {
 /// Public signing and encryption keys associated with a TS server
 #[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)]
 pub struct TssPublicKeys {
-    pub connected_to_chain: bool,
+    /// Indicates that all prerequisite checks have passed
+    pub ready: bool,
     pub tss_account: AccountId32,
     pub x25519_public_key: X25519PublicKey,
 }
@@ -44,7 +45,7 @@ pub struct TssPublicKeys {
 #[tracing::instrument(skip_all)]
 pub async fn info(State(app_state): State<AppState>) -> Result<Json<TssPublicKeys>, GetInfoError> {
     Ok(Json(TssPublicKeys {
-        connected_to_chain: app_state.is_ready(),
+        ready: app_state.is_ready(),
         x25519_public_key: app_state.x25519_public_key(),
         tss_account: app_state.subxt_account_id(),
     }))
diff --git a/crates/threshold-signature-server/src/node_info/tests.rs b/crates/threshold-signature-server/src/node_info/tests.rs
index 502dd7b2b..df74acb8c 100644
--- a/crates/threshold-signature-server/src/node_info/tests.rs
+++ b/crates/threshold-signature-server/src/node_info/tests.rs
@@ -74,7 +74,7 @@ async fn info_test() {
         TssPublicKeys {
             tss_account: TSS_ACCOUNTS[0].clone(),
             x25519_public_key: X25519_PUBLIC_KEYS[0],
-            connected_to_chain: true,
+            ready: true,
         }
     );
     clean_tests();

From 7cb7ac68bdcdf1640e86927801ccfadd34c29a17 Mon Sep 17 00:00:00 2001
From: peg <ameba23@systemli.org>
Date: Wed, 18 Dec 2024 10:01:59 +0100
Subject: [PATCH 29/77] Changelog

---
 CHANGELOG.md | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4f17c8377..31b5d5281 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -32,6 +32,9 @@ runtime
 - In [#1147](https://github.com/entropyxyz/entropy-core/pull/1147) a field is added to the
   chainspec: `jump_started_signers` which allows the chain to be started in a pre-jumpstarted state
   for testing. If this is not desired it should be set to `None`.
+- In [#1216](https://github.com/entropyxyz/entropy-core/pull/1216) the `--setup-only` option for `entropy-tss`
+  was removed. `entropy-tss` should be started only once, and the public keys retrieved using the `/info`
+  http route.
 
 ### Added
 - In [#1128](https://github.com/entropyxyz/entropy-core/pull/1128) an `/info` route was added to `entropy-tss`
@@ -52,6 +55,7 @@ runtime
 - Update programs to accept multiple oracle data ([#1153](https://github.com/entropyxyz/entropy-core/pull/1153/))
 - Use context, not block number in TDX quote input data ([#1179](https://github.com/entropyxyz/entropy-core/pull/1179))
 - Allow offchain worker requests to all TSS nodes in entropy-tss test environment ([#1147](https://github.com/entropyxyz/entropy-core/pull/1147))
+- Non persistent TSS signer and x25519 keypair ([#1216](https://github.com/entropyxyz/entropy-core/pull/1216))
 
 ### Fixed
 

From 3c08c75d2633420e8011adac13f389777b2f3bc6 Mon Sep 17 00:00:00 2001
From: peg <ameba23@systemli.org>
Date: Wed, 18 Dec 2024 11:18:13 +0100
Subject: [PATCH 30/77] Improve display of failed balance check errors

---
 crates/threshold-signature-server/src/helpers/launch.rs | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/crates/threshold-signature-server/src/helpers/launch.rs b/crates/threshold-signature-server/src/helpers/launch.rs
index 3a541eddd..0d8d15b33 100644
--- a/crates/threshold-signature-server/src/helpers/launch.rs
+++ b/crates/threshold-signature-server/src/helpers/launch.rs
@@ -298,9 +298,9 @@ pub async fn check_node_prerequisites(app_state: AppState) {
                     entropy_shared::MIN_BALANCE,
                 )
                 .await
-                .map_err(|_| {
-                    tracing::error!("Unable to query the account balance of `{}`", &account_id);
-                    "Unable to query account balance".to_string()
+                .map_err(|e| {
+                    tracing::error!("Account: {} {}", &account_id, e);
+                    e.to_string()
                 })?;
                 if !has_minimum_balance {
                     Err("Minimum balance not met".to_string())?

From c18124eed53105492273ea8d835b9fc946924aa6 Mon Sep 17 00:00:00 2001
From: peg <ameba23@systemli.org>
Date: Wed, 18 Dec 2024 11:26:01 +0100
Subject: [PATCH 31/77] Improve display of failed registration checks

---
 crates/threshold-signature-server/src/helpers/launch.rs | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/crates/threshold-signature-server/src/helpers/launch.rs b/crates/threshold-signature-server/src/helpers/launch.rs
index 0d8d15b33..d007c4b68 100644
--- a/crates/threshold-signature-server/src/helpers/launch.rs
+++ b/crates/threshold-signature-server/src/helpers/launch.rs
@@ -299,7 +299,7 @@ pub async fn check_node_prerequisites(app_state: AppState) {
                 )
                 .await
                 .map_err(|e| {
-                    tracing::error!("Account: {} {}", &account_id, e);
+                    tracing::warn!("Account: {} {}", &account_id, e);
                     e.to_string()
                 })?;
                 if !has_minimum_balance {
@@ -326,7 +326,9 @@ pub async fn check_node_prerequisites(app_state: AppState) {
 
                 let _stash_address = query_chain(&api, &rpc, stash_address_query, None)
                     .await?
-                    .ok_or_else(|| SubstrateError::NoEvent)?;
+                    .ok_or_else(|| {
+                        tracing::warn!("TSS account ID {account_id} not yet registered on-chain - you need to call `validate` or `change_threshold_accounts`");
+                        SubstrateError::NoEvent})?;
                 Ok(())
             };
 

From 35f6c9ad44bd88d1ef4855f1149d7d32bbe16df3 Mon Sep 17 00:00:00 2001
From: peg <ameba23@systemli.org>
Date: Wed, 8 Jan 2025 12:37:54 +0100
Subject: [PATCH 32/77] Add a 15 minutes maximum time limit for connecting to
 chain, funding account, and registering on chain

---
 .../src/helpers/launch.rs                     | 125 +++++++++---------
 crates/threshold-signature-server/src/main.rs |   7 +-
 2 files changed, 64 insertions(+), 68 deletions(-)

diff --git a/crates/threshold-signature-server/src/helpers/launch.rs b/crates/threshold-signature-server/src/helpers/launch.rs
index d007c4b68..0fa8b4276 100644
--- a/crates/threshold-signature-server/src/helpers/launch.rs
+++ b/crates/threshold-signature-server/src/helpers/launch.rs
@@ -262,7 +262,7 @@ pub async fn setup_latest_block_number(kv: &KvManager) -> Result<(), KvError> {
     Ok(())
 }
 
-pub async fn check_node_prerequisites(app_state: AppState) {
+pub async fn check_node_prerequisites(app_state: AppState) -> Result<(), &'static str> {
     use crate::chain_api::{get_api, get_rpc};
     let url = &app_state.configuration.endpoint;
     let account_id = app_state.account_id();
@@ -281,69 +281,62 @@ pub async fn check_node_prerequisites(app_state: AppState) {
         Ok((api, rpc))
     };
 
-    // Never give up trying to connect
-    let backoff = backoff::ExponentialBackoff { max_elapsed_time: None, ..Default::default() };
-
-    match backoff::future::retry(backoff.clone(), connect_to_substrate_node).await {
-        Ok((api, rpc)) => {
-            tracing::info!("Sucessfully connected to Substrate node!");
-
-            tracing::info!("Checking balance of threshold server AccountId `{}`", &account_id);
-
-            let balance_query = || async {
-                let has_minimum_balance = crate::validator::api::check_balance_for_fees(
-                    &api,
-                    &rpc,
-                    account_id.to_ss58check().to_string(),
-                    entropy_shared::MIN_BALANCE,
-                )
-                .await
-                .map_err(|e| {
-                    tracing::warn!("Account: {} {}", &account_id, e);
-                    e.to_string()
-                })?;
-                if !has_minimum_balance {
-                    Err("Minimum balance not met".to_string())?
-                }
-                Ok(())
-            };
-
-            if let Err(error) = backoff::future::retry(backoff.clone(), balance_query).await {
-                tracing::error!("This should never happen because backoff has no permanent errors or maximum timeout: {error}");
-            }
-
-            tracing::info!(
-                "The account `{}` has enough funds for submitting extrinsics.",
-                &account_id
-            );
-
-            // Now check if there exists a threshold server with our details - if there is not,
-            // we need to wait until there is
-            let check_for_tss_account_id = || async {
-                let stash_address_query = entropy::storage()
-                    .staking_extension()
-                    .threshold_to_stash(subxt::utils::AccountId32(*account_id.as_ref()));
-
-                let _stash_address = query_chain(&api, &rpc, stash_address_query, None)
-                    .await?
-                    .ok_or_else(|| {
-                        tracing::warn!("TSS account ID {account_id} not yet registered on-chain - you need to call `validate` or `change_threshold_accounts`");
-                        SubstrateError::NoEvent})?;
-                Ok(())
-            };
-
-            tracing::info!(
-                "Checking if our account ID has been registered on chain `{}`",
-                &account_id
-            );
-            if let Err(error) = backoff::future::retry(backoff, check_for_tss_account_id).await {
-                tracing::error!("This should never happen because backoff has no permanent errors or maximum timeout: {error}");
-            }
-            tracing::info!("TSS node passed all prerequisite checks and is ready");
-            app_state.make_ready();
-        },
-        Err(error) => {
-            tracing::error!("This should never happen because backoff has no permanent errors or maximum timeout: {error:?}");
-        },
-    }
+    // Use the default maximum elapsed time of 15 minutes.
+    // This means if we do not get a connection within 15 minutes the process will terminate and the
+    // keypair will be lost.
+    let backoff = backoff::ExponentialBackoff::default();
+
+    let (api, rpc) = backoff::future::retry(backoff.clone(), connect_to_substrate_node)
+        .await
+        .map_err(|_| "Timed out waiting for connection to chain")?;
+    tracing::info!("Sucessfully connected to Substrate node!");
+
+    tracing::info!("Checking balance of threshold server AccountId `{}`", &account_id);
+
+    let balance_query = || async {
+        let has_minimum_balance = crate::validator::api::check_balance_for_fees(
+            &api,
+            &rpc,
+            account_id.to_ss58check().to_string(),
+            entropy_shared::MIN_BALANCE,
+        )
+        .await
+        .map_err(|e| {
+            tracing::warn!("Account: {} {}", &account_id, e);
+            e.to_string()
+        })?;
+        if !has_minimum_balance {
+            Err("Minimum balance not met".to_string())?
+        }
+        Ok(())
+    };
+
+    backoff::future::retry(backoff.clone(), balance_query)
+        .await
+        .map_err(|_| "Timed out waiting for account to be funded")?;
+
+    tracing::info!("The account `{}` has enough funds for submitting extrinsics.", &account_id);
+
+    // Now check if there exists a threshold server with our details - if there is not,
+    // we need to wait until there is
+    let check_for_tss_account_id = || async {
+        let stash_address_query = entropy::storage()
+            .staking_extension()
+            .threshold_to_stash(subxt::utils::AccountId32(*account_id.as_ref()));
+
+        let _stash_address = query_chain(&api, &rpc, stash_address_query, None)
+            .await?
+            .ok_or_else(|| {
+                tracing::warn!("TSS account ID {account_id} not yet registered on-chain - you need to call `validate` or `change_threshold_accounts`");
+                SubstrateError::NoEvent})?;
+        Ok(())
+    };
+
+    tracing::info!("Checking if our account ID has been registered on chain `{}`", &account_id);
+    backoff::future::retry(backoff, check_for_tss_account_id)
+        .await
+        .map_err(|_| "Timed out waiting for TSS account to be registered on chain")?;
+    tracing::info!("TSS node passed all prerequisite checks and is ready");
+    app_state.make_ready();
+    Ok(())
 }
diff --git a/crates/threshold-signature-server/src/main.rs b/crates/threshold-signature-server/src/main.rs
index 9dfffd748..88d5d561c 100644
--- a/crates/threshold-signature-server/src/main.rs
+++ b/crates/threshold-signature-server/src/main.rs
@@ -13,7 +13,7 @@
 // You should have received a copy of the GNU Affero General Public License
 // along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
-use std::{net::SocketAddr, str::FromStr};
+use std::{net::SocketAddr, process, str::FromStr};
 
 use clap::Parser;
 
@@ -66,7 +66,10 @@ async fn main() {
         tokio::spawn(async move {
             // Check for a connection to the chain node parallel to starting the tss_server so that
             // we already can expose the `/info` http route
-            entropy_tss::launch::check_node_prerequisites(app_state).await;
+            if let Err(error) = entropy_tss::launch::check_node_prerequisites(app_state).await {
+                tracing::error!("Prerequistite checks failed: {} - terminating.", error);
+                process::exit(1);
+            }
         });
     }
 

From f67fa3d04d86a6f5747baa708994c98999d31d44 Mon Sep 17 00:00:00 2001
From: peg <ameba23@systemli.org>
Date: Wed, 8 Jan 2025 12:49:48 +0100
Subject: [PATCH 33/77] Minor edits from PR review

---
 .../src/helpers/launch.rs                           | 13 ++++++++-----
 1 file changed, 8 insertions(+), 5 deletions(-)

diff --git a/crates/threshold-signature-server/src/helpers/launch.rs b/crates/threshold-signature-server/src/helpers/launch.rs
index 0fa8b4276..0996bd032 100644
--- a/crates/threshold-signature-server/src/helpers/launch.rs
+++ b/crates/threshold-signature-server/src/helpers/launch.rs
@@ -324,11 +324,14 @@ pub async fn check_node_prerequisites(app_state: AppState) -> Result<(), &'stati
             .staking_extension()
             .threshold_to_stash(subxt::utils::AccountId32(*account_id.as_ref()));
 
-        let _stash_address = query_chain(&api, &rpc, stash_address_query, None)
-            .await?
-            .ok_or_else(|| {
-                tracing::warn!("TSS account ID {account_id} not yet registered on-chain - you need to call `validate` or `change_threshold_accounts`");
-                SubstrateError::NoEvent})?;
+        let _stash_address =
+            query_chain(&api, &rpc, stash_address_query, None).await?.ok_or_else(|| {
+                tracing::warn!(
+                    "TSS account ID {account_id} not yet registered on-chain - you need to \
+                    call `validate` or `change_threshold_accounts`"
+                );
+                SubstrateError::NoEvent
+            })?;
         Ok(())
     };
 

From 3dbf710b84e9598ae441b4663775883b6f07a59d Mon Sep 17 00:00:00 2001
From: peg <ameba23@systemli.org>
Date: Wed, 8 Jan 2025 12:55:23 +0100
Subject: [PATCH 34/77] Minor edits from PR review

---
 crates/threshold-signature-server/src/attestation/api.rs | 3 +--
 crates/threshold-signature-server/src/lib.rs             | 2 ++
 crates/threshold-signature-server/src/node_info/api.rs   | 2 ++
 3 files changed, 5 insertions(+), 2 deletions(-)

diff --git a/crates/threshold-signature-server/src/attestation/api.rs b/crates/threshold-signature-server/src/attestation/api.rs
index 7387f82bc..f713eae29 100644
--- a/crates/threshold-signature-server/src/attestation/api.rs
+++ b/crates/threshold-signature-server/src/attestation/api.rs
@@ -32,7 +32,6 @@ use entropy_kvdb::kv_manager::KvManager;
 use entropy_shared::{OcwMessageAttestationRequest, QuoteContext};
 use parity_scale_codec::Decode;
 use serde::Deserialize;
-use sp_core::Pair;
 use subxt::tx::PairSigner;
 use x25519_dalek::StaticSecret;
 
@@ -57,7 +56,7 @@ pub async fn attest(
     validate_new_attestation(block_number, &attestation_requests, &app_state.kv_store).await?;
 
     // Check whether there is an attestion request for us
-    if !attestation_requests.tss_account_ids.contains(&app_state.pair.public().0) {
+    if !attestation_requests.tss_account_ids.contains(&app_state.subxt_account_id().0) {
         return Ok(StatusCode::OK);
     }
 
diff --git a/crates/threshold-signature-server/src/lib.rs b/crates/threshold-signature-server/src/lib.rs
index d6055cca3..7a180af02 100644
--- a/crates/threshold-signature-server/src/lib.rs
+++ b/crates/threshold-signature-server/src/lib.rs
@@ -217,7 +217,9 @@ pub struct AppState {
     pair: sr25519::Pair,
     /// Secret encryption key
     x25519_secret: StaticSecret,
+    /// Configuation containing the chain endpoint
     pub configuration: Configuration,
+    /// Key-value store
     pub kv_store: KvManager,
 }
 
diff --git a/crates/threshold-signature-server/src/node_info/api.rs b/crates/threshold-signature-server/src/node_info/api.rs
index ba977c6f2..176c88723 100644
--- a/crates/threshold-signature-server/src/node_info/api.rs
+++ b/crates/threshold-signature-server/src/node_info/api.rs
@@ -37,7 +37,9 @@ pub async fn hashes() -> Json<Vec<HashingAlgorithm>> {
 pub struct TssPublicKeys {
     /// Indicates that all prerequisite checks have passed
     pub ready: bool,
+    /// The TSS account ID
     pub tss_account: AccountId32,
+    /// The public encryption key
     pub x25519_public_key: X25519PublicKey,
 }
 

From af51f2d04ee7a392b5f564ef4962c614a729edc4 Mon Sep 17 00:00:00 2001
From: peg <ameba23@systemli.org>
Date: Tue, 14 Jan 2025 12:47:16 +0100
Subject: [PATCH 35/77] Add key provider module

---
 .../src/helpers/launch.rs                     | 58 ++++++------
 .../src/helpers/tests.rs                      |  9 +-
 .../src/key_provider/api.rs                   | 92 +++++++++++++++++++
 .../src/key_provider/errors.rs                | 38 ++++++++
 .../src/key_provider/mod.rs                   | 21 +++++
 .../src/key_provider/tests.rs                 | 41 +++++++++
 crates/threshold-signature-server/src/lib.rs  |  3 +
 crates/threshold-signature-server/src/main.rs |  2 +-
 8 files changed, 229 insertions(+), 35 deletions(-)
 create mode 100644 crates/threshold-signature-server/src/key_provider/api.rs
 create mode 100644 crates/threshold-signature-server/src/key_provider/errors.rs
 create mode 100644 crates/threshold-signature-server/src/key_provider/mod.rs
 create mode 100644 crates/threshold-signature-server/src/key_provider/tests.rs

diff --git a/crates/threshold-signature-server/src/helpers/launch.rs b/crates/threshold-signature-server/src/helpers/launch.rs
index 965d31328..aa1818d1d 100644
--- a/crates/threshold-signature-server/src/helpers/launch.rs
+++ b/crates/threshold-signature-server/src/helpers/launch.rs
@@ -15,15 +15,12 @@
 
 //! Utilities for starting and running the server.
 
-use std::{fs, path::PathBuf};
+use std::path::PathBuf;
 
 use crate::{chain_api::entropy, helpers::substrate::query_chain, AppState};
 use clap::Parser;
 use entropy_client::substrate::SubstrateError;
-use entropy_kvdb::{
-    encrypted_sled::PasswordMethod,
-    kv_manager::{error::KvError, KvManager},
-};
+use entropy_kvdb::kv_manager::{error::KvError, KvManager};
 use serde::Deserialize;
 use sp_core::crypto::Ss58Codec;
 
@@ -85,55 +82,60 @@ impl Configuration {
     }
 }
 
+pub async fn setup_kv_store() {
+    let path: PathBuf = PathBuf::from(entropy_kvdb::get_db_path(false));
+    let exists = std::fs::metadata(path).is_ok();
+    if exists {
+        // Read key provider details
+        // Make provider request
+        // open store with retrieved key
+        // load_kv_store(validator_name, key);
+    } else {
+        // Generate TSS account (or use ValidatorName)
+        // Select a provider by making chain query and choosing a tss node
+        // Make provider request
+        // Store provider details
+        // open store with retrived key
+        // load_kv_store(validator_name, key);
+    }
+    // return TSS account private keys to be used in appstate
+}
+
 pub async fn load_kv_store(
     validator_name: &Option<ValidatorName>,
-    password_path: Option<PathBuf>,
+    key: Option<[u8; 32]>,
 ) -> KvManager {
+    let key = key.unwrap_or_default();
     let mut root: PathBuf = PathBuf::from(entropy_kvdb::get_db_path(false));
     if cfg!(test) {
-        return KvManager::new(
-            entropy_kvdb::get_db_path(true).into(),
-            PasswordMethod::NoPassword.execute().unwrap(),
-        )
-        .unwrap();
+        return KvManager::new(entropy_kvdb::get_db_path(true).into(), key).unwrap();
     }
 
     if validator_name == &Some(ValidatorName::Alice) {
-        return KvManager::new(root, PasswordMethod::NoPassword.execute().unwrap()).unwrap();
+        return KvManager::new(root, key).unwrap();
     };
 
     if validator_name == &Some(ValidatorName::Bob) {
         root.push("bob");
-        return KvManager::new(root, PasswordMethod::NoPassword.execute().unwrap()).unwrap();
+        return KvManager::new(root, key).unwrap();
     };
 
     if validator_name == &Some(ValidatorName::Charlie) {
         root.push("charlie");
-        return KvManager::new(root, PasswordMethod::NoPassword.execute().unwrap()).unwrap();
+        return KvManager::new(root, key).unwrap();
     };
 
     if validator_name == &Some(ValidatorName::Dave) {
         root.push("dave");
-        return KvManager::new(root, PasswordMethod::NoPassword.execute().unwrap()).unwrap();
+        return KvManager::new(root, key).unwrap();
     };
 
     if validator_name == &Some(ValidatorName::Eve) {
         root.push("eve");
-        return KvManager::new(root, PasswordMethod::NoPassword.execute().unwrap()).unwrap();
-    };
-
-    let password = if let Some(password_path) = password_path {
-        std::str::from_utf8(&fs::read(password_path).expect("error reading password file"))
-            .expect("failed to convert password to string")
-            .trim()
-            .to_string()
-            .into()
-    } else {
-        PasswordMethod::Prompt.execute().unwrap()
+        return KvManager::new(root, key).unwrap();
     };
 
-    // this step takes a long time due to password-based decryption
-    KvManager::new(root, password).unwrap()
+    KvManager::new(root, key).unwrap()
 }
 
 #[derive(Parser, Debug, Clone)]
diff --git a/crates/threshold-signature-server/src/helpers/tests.rs b/crates/threshold-signature-server/src/helpers/tests.rs
index beab864db..127bdb58e 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_client::substrate::query_chain;
-use entropy_kvdb::{encrypted_sled::PasswordMethod, get_db_path, kv_manager::KvManager};
+use entropy_kvdb::{get_db_path, kv_manager::KvManager};
 use entropy_protocol::PartyId;
 #[cfg(test)]
 use entropy_shared::EncodedVerifyingKey;
@@ -67,9 +67,7 @@ pub async fn initialize_test_logger() {
 }
 
 pub async fn setup_client() -> KvManager {
-    let kv_store =
-        KvManager::new(get_db_path(true).into(), PasswordMethod::NoPassword.execute().unwrap())
-            .unwrap();
+    let kv_store = KvManager::new(get_db_path(true).into(), [0; 32]).unwrap();
 
     let _ = setup_latest_block_number(&kv_store).await;
     let configuration = Configuration::new(DEFAULT_ENDPOINT.to_string());
@@ -100,8 +98,7 @@ pub async fn create_clients(
     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 kv_store = KvManager::new(path.into(), [0; 32]).unwrap();
 
     let _ = setup_latest_block_number(&kv_store).await;
 
diff --git a/crates/threshold-signature-server/src/key_provider/api.rs b/crates/threshold-signature-server/src/key_provider/api.rs
new file mode 100644
index 000000000..af3aa28bf
--- /dev/null
+++ b/crates/threshold-signature-server/src/key_provider/api.rs
@@ -0,0 +1,92 @@
+// 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 <https://www.gnu.org/licenses/>.
+
+use crate::{
+    key_provider::errors::KeyProviderError, validation::EncryptedSignedMessage, AppState,
+    SubxtAccountId32,
+};
+use axum::{extract::State, Json};
+use entropy_shared::{ValidatorInfo, X25519PublicKey};
+use rand_core::OsRng;
+use serde::{Deserialize, Serialize};
+use x25519_dalek::{PublicKey, StaticSecret};
+
+pub async fn make_provider_request(
+    validator_info: ValidatorInfo,
+    tss_account: SubxtAccountId32,
+) -> Result<[u8; 32], KeyProviderError> {
+    let quote = Vec::new(); // TODO
+
+    let response_secret_key = StaticSecret::random_from_rng(OsRng);
+    let response_key = PublicKey::from(&response_secret_key).to_bytes();
+
+    let key_request = EncryptionKeyRequest { tss_account, response_key, quote };
+
+    let client = reqwest::Client::new();
+    let response = client
+        .post(format!(
+            "http://{}/request_encryption_key",
+            String::from_utf8(validator_info.ip_address).unwrap()
+        ))
+        .header("Content-Type", "application/json")
+        .body(serde_json::to_string(&key_request).unwrap())
+        .send()
+        .await?;
+
+    let status = response.status();
+    if status != reqwest::StatusCode::OK {
+        let text = response.text().await.unwrap();
+        panic!("Bad status code {}: {}", status, text);
+    }
+    let response_bytes = response.bytes().await?;
+
+    let encrypted_response: EncryptedSignedMessage =
+        serde_json::from_slice(&response_bytes).unwrap();
+    let signed_message = encrypted_response.decrypt(&response_secret_key, &[]).unwrap();
+
+    Ok(signed_message.message.0.try_into().unwrap())
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct EncryptionKeyRequest {
+    tss_account: SubxtAccountId32,
+    response_key: X25519PublicKey,
+    quote: Vec<u8>,
+}
+
+pub async fn request_encryption_key(
+    State(app_state): State<AppState>,
+    Json(key_request): Json<EncryptionKeyRequest>,
+) -> Result<Json<EncryptedSignedMessage>, KeyProviderError> {
+    // Build quote input
+    // Verify quote
+    // Check kvdb for existing key - or generate and store one
+    let lookup_key = format!("BACKUP_KEY:{}", key_request.tss_account.to_string());
+    let key: [u8; 32] = match app_state.kv_store.kv().get(&lookup_key).await {
+        Ok(existing_key) => existing_key.try_into().map_err(|_| KeyProviderError::BadKeyLength)?,
+        Err(_) => {
+            // TODO Generate random 32 byte key
+            let encryption_key = [0; 32];
+            let reservation = app_state.kv_store.kv().reserve_key(lookup_key).await?;
+            app_state.kv_store.kv().put(reservation, encryption_key.to_vec()).await?;
+            encryption_key
+        },
+    };
+    // Encrypt response
+    let signed_message =
+        EncryptedSignedMessage::new(&app_state.pair, key.to_vec(), &key_request.response_key, &[])
+            .unwrap();
+    Ok(Json(signed_message))
+}
diff --git a/crates/threshold-signature-server/src/key_provider/errors.rs b/crates/threshold-signature-server/src/key_provider/errors.rs
new file mode 100644
index 000000000..cbd7e553f
--- /dev/null
+++ b/crates/threshold-signature-server/src/key_provider/errors.rs
@@ -0,0 +1,38 @@
+// 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 <https://www.gnu.org/licenses/>.
+use axum::{
+    http::StatusCode,
+    response::{IntoResponse, Response},
+};
+use entropy_kvdb::kv_manager::error::KvError;
+use thiserror::Error;
+
+#[derive(Debug, Error)]
+pub enum KeyProviderError {
+    #[error("HTTP request: {0}")]
+    HttpRequest(#[from] reqwest::Error),
+    #[error("Key-value store: {0}")]
+    Kv(#[from] KvError),
+    #[error("Encryption key must be 32 bytes")]
+    BadKeyLength,
+}
+
+impl IntoResponse for KeyProviderError {
+    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/key_provider/mod.rs b/crates/threshold-signature-server/src/key_provider/mod.rs
new file mode 100644
index 000000000..3b6dc0597
--- /dev/null
+++ b/crates/threshold-signature-server/src/key_provider/mod.rs
@@ -0,0 +1,21 @@
+// 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 <https://www.gnu.org/licenses/>.
+
+//! Key provider service
+pub mod api;
+pub mod errors;
+
+#[cfg(test)]
+mod tests;
diff --git a/crates/threshold-signature-server/src/key_provider/tests.rs b/crates/threshold-signature-server/src/key_provider/tests.rs
new file mode 100644
index 000000000..78fcd5045
--- /dev/null
+++ b/crates/threshold-signature-server/src/key_provider/tests.rs
@@ -0,0 +1,41 @@
+// 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 <https://www.gnu.org/licenses/>.
+
+use crate::{
+    helpers::tests::{initialize_test_logger, setup_client},
+    key_provider::api::make_provider_request,
+    SubxtAccountId32,
+};
+use entropy_kvdb::clean_tests;
+use entropy_shared::ValidatorInfo;
+use entropy_testing_utils::constants::{TSS_ACCOUNTS, X25519_PUBLIC_KEYS};
+use serial_test::serial;
+use sp_keyring::AccountKeyring;
+
+#[tokio::test]
+#[serial]
+async fn key_provider_test() {
+    clean_tests();
+    initialize_test_logger().await;
+    setup_client().await;
+    let validator_info = ValidatorInfo {
+        tss_account: TSS_ACCOUNTS[0].0.to_vec(),
+        x25519_public_key: X25519_PUBLIC_KEYS[0],
+        ip_address: b"127.0.0.1:3001".to_vec(),
+    };
+    let tss_account = SubxtAccountId32(AccountKeyring::Bob.to_raw_public());
+    let _key = make_provider_request(validator_info, tss_account).await.unwrap();
+    // TODO now do it a second time and check key is identical
+}
diff --git a/crates/threshold-signature-server/src/lib.rs b/crates/threshold-signature-server/src/lib.rs
index 7a180af02..452ea3cde 100644
--- a/crates/threshold-signature-server/src/lib.rs
+++ b/crates/threshold-signature-server/src/lib.rs
@@ -162,6 +162,7 @@ pub use entropy_client::chain_api;
 pub(crate) mod attestation;
 pub(crate) mod health;
 pub mod helpers;
+pub(crate) mod key_provider;
 pub(crate) mod node_info;
 pub(crate) mod sign_init;
 pub(crate) mod signing_client;
@@ -195,6 +196,7 @@ use crate::{
     attestation::api::{attest, get_attest},
     chain_api::{get_api, get_rpc, EntropyConfig},
     health::api::healthz,
+    key_provider::api::request_encryption_key,
     launch::{development_mnemonic, Configuration, ValidatorName},
     node_info::api::{hashes, info, version as get_version},
     r#unsafe::api::{delete, put, remove_keys, unsafe_get},
@@ -304,6 +306,7 @@ pub fn app(app_state: AppState) -> Router {
         .route("/rotate_network_key", post(rotate_network_key))
         .route("/attest", post(attest))
         .route("/attest", get(get_attest))
+        .route("/request_encryption_key", post(request_encryption_key))
         .route("/healthz", get(healthz))
         .route("/version", get(get_version))
         .route("/hashes", get(hashes))
diff --git a/crates/threshold-signature-server/src/main.rs b/crates/threshold-signature-server/src/main.rs
index 88d5d561c..cdf310ca8 100644
--- a/crates/threshold-signature-server/src/main.rs
+++ b/crates/threshold-signature-server/src/main.rs
@@ -55,7 +55,7 @@ async fn main() {
         validator_name = Some(ValidatorName::Eve);
     }
 
-    let kv_store = load_kv_store(&validator_name, args.password_file).await;
+    let kv_store = load_kv_store(&validator_name, None).await;
 
     let app_state = AppState::new(configuration.clone(), kv_store.clone(), &validator_name);
 

From a6a983832eee165a047930ec3dafb0ec6c94c149 Mon Sep 17 00:00:00 2001
From: peg <ameba23@systemli.org>
Date: Tue, 14 Jan 2025 12:47:43 +0100
Subject: [PATCH 36/77] KVDB - replace password with 32 byte key

---
 crates/kvdb/src/encrypted_sled/constants.rs |  2 -
 crates/kvdb/src/encrypted_sled/kv.rs        | 52 +++++++--------------
 crates/kvdb/src/encrypted_sled/mod.rs       |  5 --
 crates/kvdb/src/encrypted_sled/tests.rs     | 37 +++++----------
 crates/kvdb/src/kv_manager/kv.rs            | 17 +++----
 crates/kvdb/src/kv_manager/tests.rs         | 23 ++++-----
 crates/kvdb/src/kv_manager/value.rs         |  5 +-
 7 files changed, 51 insertions(+), 90 deletions(-)

diff --git a/crates/kvdb/src/encrypted_sled/constants.rs b/crates/kvdb/src/encrypted_sled/constants.rs
index 335f0131e..bf13e969a 100644
--- a/crates/kvdb/src/encrypted_sled/constants.rs
+++ b/crates/kvdb/src/encrypted_sled/constants.rs
@@ -16,5 +16,3 @@
 //! Constants for [encrypted_sled](crate::encrypted_sled)
 pub(super) const PASSWORD_VERIFICATION_KEY: &str = "verification_key";
 pub(super) const PASSWORD_VERIFICATION_VALUE: &str = "verification_value";
-pub(super) const PASSWORD_SALT_KEY: &[u8] = b"password_salt_key";
-pub(super) const UNSAFE_PASSWORD: &str = "entropy_unsafe_password";
diff --git a/crates/kvdb/src/encrypted_sled/kv.rs b/crates/kvdb/src/encrypted_sled/kv.rs
index 4672ad430..1d1170416 100644
--- a/crates/kvdb/src/encrypted_sled/kv.rs
+++ b/crates/kvdb/src/encrypted_sled/kv.rs
@@ -19,8 +19,6 @@
 //! to be inserted, forming a [EncryptedRecord]:<encrypted value, nonce>. The nonce is later
 //! used to decrypt and retrieve the originally inserted value.
 
-use std::convert::TryInto;
-
 use chacha20poly1305::{
     self,
     aead::{AeadInPlace, NewAead},
@@ -32,7 +30,6 @@ use zeroize::Zeroize;
 
 use super::{
     constants::*,
-    password::{Password, PasswordSalt},
     record::EncryptedRecord,
     result::{EncryptedDbError::*, EncryptedDbResult},
 };
@@ -48,26 +45,13 @@ impl EncryptedDb {
     /// Creates an XChaCha20 stream cipher from a password-based-key-derivation-function and
     /// verifies that the password is valid.
     /// See [super::Password] for more info on pdkdf.
-    pub fn open<P>(db_name: P, password: Password) -> EncryptedDbResult<Self>
+    pub fn open<P>(db_name: P, mut key: [u8; 32]) -> EncryptedDbResult<Self>
     where
         P: AsRef<std::path::Path>,
     {
         let kv = sled::open(db_name).map_err(CorruptedKv)?;
 
-        let password_salt: PasswordSalt = if kv.was_recovered() {
-            // existing kv: get the existing password salt
-            kv.get(PASSWORD_SALT_KEY)?.ok_or(MissingPasswordSalt)?.try_into()?
-        } else {
-            // new kv: choose a new password salt and store it
-            let mut password_salt = [0u8; 32];
-            rand::thread_rng().fill_bytes(&mut password_salt);
-            kv.insert(PASSWORD_SALT_KEY, &password_salt)?;
-            password_salt.into()
-        };
-
-        // zeroize key since we are no longer using it after creating cipher
-        let mut key = Self::chacha20poly1305_kdf(password, password_salt)?;
-        let cipher = XChaCha20Poly1305::new(&key);
+        let cipher = XChaCha20Poly1305::new(&key.into());
         key.zeroize();
 
         let encrypted_db = EncryptedDb { kv, cipher };
@@ -84,22 +68,22 @@ impl EncryptedDb {
         Ok(encrypted_db)
     }
 
-    fn chacha20poly1305_kdf(
-        password: Password,
-        salt: PasswordSalt,
-    ) -> EncryptedDbResult<chacha20poly1305::Key> {
-        let mut output = chacha20poly1305::Key::default();
-
-        // default params: log_n = 15, r = 8, p = 1
-        scrypt::scrypt(
-            password.as_ref(),
-            salt.as_ref(),
-            &scrypt::Params::default(),
-            output.as_mut_slice(),
-        )?;
-
-        Ok(output)
-    }
+    // fn chacha20poly1305_kdf(
+    //     password: Password,
+    //     salt: PasswordSalt,
+    // ) -> EncryptedDbResult<chacha20poly1305::Key> {
+    //     let mut output = chacha20poly1305::Key::default();
+    //
+    //     // default params: log_n = 15, r = 8, p = 1
+    //     scrypt::scrypt(
+    //         password.as_ref(),
+    //         salt.as_ref(),
+    //         &scrypt::Params::default(),
+    //         output.as_mut_slice(),
+    //     )?;
+    //
+    //     Ok(output)
+    // }
 
     /// get a new random nonce to use for value encryption using [rand::thread_rng]
     fn generate_nonce() -> chacha20poly1305::XNonce {
diff --git a/crates/kvdb/src/encrypted_sled/mod.rs b/crates/kvdb/src/encrypted_sled/mod.rs
index 303d47be3..10a8a2315 100644
--- a/crates/kvdb/src/encrypted_sled/mod.rs
+++ b/crates/kvdb/src/encrypted_sled/mod.rs
@@ -20,17 +20,12 @@
 
 mod constants;
 mod kv;
-mod password;
 mod record;
 mod result;
 
 // match the API of sled
 pub use kv::EncryptedDb as Db;
-pub use password::{Password, PasswordMethod, PasswordSalt};
 pub use result::{EncryptedDbError as Error, EncryptedDbResult as Result};
 
 #[cfg(test)]
 mod tests;
-
-#[cfg(test)]
-pub use tests::get_test_password;
diff --git a/crates/kvdb/src/encrypted_sled/tests.rs b/crates/kvdb/src/encrypted_sled/tests.rs
index d546f7d0c..c6f2c2d2d 100644
--- a/crates/kvdb/src/encrypted_sled/tests.rs
+++ b/crates/kvdb/src/encrypted_sled/tests.rs
@@ -15,23 +15,17 @@
 
 use serial_test::serial;
 
-use super::{kv::EncryptedDb, Password};
+use super::kv::EncryptedDb;
 use crate::{clean_tests, encrypted_sled::Db, get_db_path};
 
-fn setup_db(require_password: bool) -> Db {
-    let db = if !require_password {
-        EncryptedDb::open(get_db_path(true), get_test_password())
-    } else {
-        EncryptedDb::open(get_db_path(true), Password::from("super-secret password."))
-    };
-    assert!(db.is_ok());
-    db.unwrap()
+fn setup_db(key: [u8; 32]) -> Db {
+    EncryptedDb::open(get_db_path(true), key).unwrap()
 }
 
 #[test]
 #[serial]
 fn test_encrypted_sled() {
-    let db = setup_db(false);
+    let db = setup_db([1; 32]);
 
     // insert <key: value> -> returns None
     let res = db.insert("key", "value").unwrap();
@@ -73,28 +67,25 @@ fn test_encrypted_sled() {
 
 #[test]
 #[serial]
-fn test_use_existing_salt() {
-    let db = setup_db(false);
+fn test_use_existing_key() {
+    let db = setup_db([1; 32]);
     let db_path = get_db_path(true);
     drop(db);
     // open existing db
-    assert!(EncryptedDb::open(db_path, get_test_password()).is_ok());
+    assert!(EncryptedDb::open(db_path, [1; 32]).is_ok());
     clean_tests();
 }
 
 #[test]
 #[serial]
-fn test_password() {
-    let db = setup_db(true);
+fn test_key() {
+    let db = setup_db([1; 32]);
     let db_path = get_db_path(true);
 
     drop(db);
 
-    // try to open the kv store using a different password
-    let db = EncryptedDb::open(
-        db_path,
-        Password::from("super-secret password!"), // replace '.' with '!'
-    );
+    // try to open the kv store using a different key
+    let db = EncryptedDb::open(db_path, [2; 32]);
     assert!(matches!(db, Err(super::result::EncryptedDbError::WrongPassword)));
     clean_tests();
 }
@@ -102,7 +93,7 @@ fn test_password() {
 #[test]
 #[serial]
 fn test_large_input() {
-    let db = setup_db(false);
+    let db = setup_db([1; 32]);
 
     let large_value = vec![0; 100000];
     let res = db.insert("key", large_value.clone()).unwrap();
@@ -112,7 +103,3 @@ fn test_large_input() {
     assert_eq!(res, Some(sled::IVec::from(large_value)));
     clean_tests();
 }
-
-pub fn get_test_password() -> Password {
-    crate::encrypted_sled::PasswordMethod::NoPassword.execute().unwrap()
-}
diff --git a/crates/kvdb/src/kv_manager/kv.rs b/crates/kvdb/src/kv_manager/kv.rs
index bc3a0aa4a..d1f7ddc92 100644
--- a/crates/kvdb/src/kv_manager/kv.rs
+++ b/crates/kvdb/src/kv_manager/kv.rs
@@ -29,7 +29,7 @@ use super::{
         KeyReservation, DEFAULT_KV_NAME, DEFAULT_KV_PATH,
     },
 };
-use crate::encrypted_sled::{self, Password};
+use crate::encrypted_sled;
 
 #[derive(Clone)]
 pub struct Kv<V> {
@@ -44,22 +44,22 @@ where
 {
     /// Creates a new kv service. Returns [InitErr] on failure.
     /// the path of the kvstore is `root_path` + "/kvstore/" + `kv_name`
-    pub fn new(root_path: PathBuf, password: Password) -> KvResult<Self> {
+    pub fn new(root_path: PathBuf, key: [u8; 32]) -> KvResult<Self> {
         let kv_path = root_path.join(DEFAULT_KV_PATH).join(DEFAULT_KV_NAME);
         // use to_string_lossy() instead of to_str() to avoid handling Option<&str>
         let kv_path = kv_path.to_string_lossy().to_string();
-        Self::with_db_name(kv_path, password)
+        Self::with_db_name(kv_path, key)
     }
 
     /// Creates a kvstore at `full_db_name` and spawns a new kv_manager. Returns [InitErr] on
     /// failure. `full_db_name` is the name of the path of the kvstrore + its name
     /// Example: ~/entropy/kvstore/database_1
-    pub fn with_db_name(full_db_name: String, password: Password) -> KvResult<Self> {
+    pub fn with_db_name(full_db_name: String, key: [u8; 32]) -> KvResult<Self> {
         let (sender, rx) = mpsc::unbounded_channel();
 
         // get kv store from db name before entering the kv_cmd_handler because
         // it's more convenient to return an error from outside of a tokio::span
-        let kv = get_kv_store(&full_db_name, password)?;
+        let kv = get_kv_store(&full_db_name, key)?;
 
         tokio::spawn(kv_cmd_handler(rx, kv));
         Ok(Self { sender })
@@ -129,13 +129,10 @@ where
 ///  let my_db = get_kv_store(&"my_current_dir_db")?;
 ///  let my_db = get_kv_store(&"/tmp/my_tmp_bd")?;
 #[tracing::instrument(skip_all, fields(db_name))]
-pub fn get_kv_store(
-    db_name: &str,
-    password: Password,
-) -> encrypted_sled::Result<encrypted_sled::Db> {
+pub fn get_kv_store(db_name: &str, key: [u8; 32]) -> encrypted_sled::Result<encrypted_sled::Db> {
     // create/open DB
     tracing::debug!("Decrypting KV store");
-    let kv = encrypted_sled::Db::open(db_name, password)?;
+    let kv = encrypted_sled::Db::open(db_name, key)?;
 
     // log whether the DB was newly created or not
     if kv.was_recovered() {
diff --git a/crates/kvdb/src/kv_manager/tests.rs b/crates/kvdb/src/kv_manager/tests.rs
index 7251ab711..2d264b9ee 100644
--- a/crates/kvdb/src/kv_manager/tests.rs
+++ b/crates/kvdb/src/kv_manager/tests.rs
@@ -30,18 +30,19 @@ use super::{
 };
 use crate::{
     clean_tests,
-    encrypted_sled::{get_test_password, Db, Result},
+    encrypted_sled::{Db, Result},
     get_db_path,
 };
 
-pub fn open_with_test_password() -> Result<Db> {
-    Db::open(get_db_path(true), get_test_password())
+pub fn open_with_test_key() -> Result<Db> {
+    Db::open(get_db_path(true), [1; 32])
 }
 
 #[test]
 #[serial]
 fn reserve_success() {
-    let kv = open_with_test_password().unwrap();
+    let kv = open_with_test_key().unwrap();
+
     let key: String = "key".to_string();
     assert_eq!(handle_reserve(&kv, key.clone()).unwrap(), KeyReservation { key: key.clone() });
 
@@ -57,7 +58,7 @@ fn reserve_success() {
 #[test]
 #[serial]
 fn reserve_failure() {
-    let kv = open_with_test_password().unwrap();
+    let kv = open_with_test_key().unwrap();
 
     let key: String = "key".to_string();
     handle_reserve(&kv, key.clone()).unwrap();
@@ -70,7 +71,7 @@ fn reserve_failure() {
 #[test]
 #[serial]
 fn put_success() {
-    let kv = open_with_test_password().unwrap();
+    let kv = open_with_test_key().unwrap();
 
     let key: String = "key".to_string();
     handle_reserve(&kv, key.clone()).unwrap();
@@ -84,7 +85,7 @@ fn put_success() {
 #[test]
 #[serial]
 fn put_failure_no_reservation() {
-    let kv = open_with_test_password().unwrap();
+    let kv = open_with_test_key().unwrap();
 
     let key: String = "key".to_string();
 
@@ -101,7 +102,7 @@ fn put_failure_no_reservation() {
 #[test]
 #[serial]
 fn put_failure_put_twice() {
-    let kv = open_with_test_password().unwrap();
+    let kv = open_with_test_key().unwrap();
 
     let key: String = "key".to_string();
     let value = "value".to_string();
@@ -127,7 +128,7 @@ fn put_failure_put_twice() {
 #[test]
 #[serial]
 fn get_success() {
-    let kv = open_with_test_password().unwrap();
+    let kv = open_with_test_key().unwrap();
 
     let key: String = "key".to_string();
     let value = "value";
@@ -144,7 +145,7 @@ fn get_success() {
 #[test]
 #[serial]
 fn get_failure() {
-    let kv = open_with_test_password().unwrap();
+    let kv = open_with_test_key().unwrap();
 
     let key: String = "key".to_string();
     let err = handle_get::<String>(&kv, key).err().unwrap();
@@ -156,7 +157,7 @@ fn get_failure() {
 #[test]
 #[serial]
 fn test_exists() {
-    let kv = open_with_test_password().unwrap();
+    let kv = open_with_test_key().unwrap();
 
     let key: String = "key".to_string();
     let value: String = "value".to_string();
diff --git a/crates/kvdb/src/kv_manager/value.rs b/crates/kvdb/src/kv_manager/value.rs
index 7c84edc1f..ed8733d2a 100644
--- a/crates/kvdb/src/kv_manager/value.rs
+++ b/crates/kvdb/src/kv_manager/value.rs
@@ -27,7 +27,6 @@ use super::{
     helpers::{deserialize, serialize},
     kv::Kv,
 };
-use crate::encrypted_sled::Password;
 
 /// Mnemonic type needs to be known globaly to create/access the mnemonic kv store
 #[derive(Zeroize, Debug, Clone, Serialize, Deserialize)]
@@ -59,8 +58,8 @@ pub struct KvManager {
 }
 
 impl KvManager {
-    pub fn new(root: PathBuf, password: Password) -> KvResult<Self> {
-        Ok(KvManager { kv: Kv::<KvValue>::new(root, password)? })
+    pub fn new(root: PathBuf, key: [u8; 32]) -> KvResult<Self> {
+        Ok(KvManager { kv: Kv::<KvValue>::new(root, key)? })
     }
 
     pub fn kv(&self) -> &Kv<KvValue> {

From 55e4847317b4ca8ad81f9b0192cae9f3cc1824dd Mon Sep 17 00:00:00 2001
From: peg <ameba23@systemli.org>
Date: Tue, 14 Jan 2025 12:48:46 +0100
Subject: [PATCH 37/77] KVDB - rm password.rs

---
 crates/kvdb/src/encrypted_sled/password.rs | 91 ----------------------
 1 file changed, 91 deletions(-)
 delete mode 100644 crates/kvdb/src/encrypted_sled/password.rs

diff --git a/crates/kvdb/src/encrypted_sled/password.rs b/crates/kvdb/src/encrypted_sled/password.rs
deleted file mode 100644
index 3b5a907f0..000000000
--- a/crates/kvdb/src/encrypted_sled/password.rs
+++ /dev/null
@@ -1,91 +0,0 @@
-// 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 <https://www.gnu.org/licenses/>.
-
-//! Handles the generation of a key for the stream cipher from the user's password using [scrypt]
-//! pbkdf.
-use std::convert::{TryFrom, TryInto};
-
-use sled::IVec;
-use zeroize::Zeroize;
-
-use super::{constants::UNSAFE_PASSWORD, result::EncryptedDbResult};
-
-/// Safely store strings
-// TODO use https://docs.rs/secrecy ?
-#[derive(Zeroize, Clone)]
-#[zeroize(drop)]
-pub struct Password(String);
-
-impl AsRef<[u8]> for Password {
-    fn as_ref(&self) -> &[u8] {
-        self.0.as_bytes()
-    }
-}
-
-impl From<String> for Password {
-    fn from(string: String) -> Self {
-        Self(string)
-    }
-}
-
-pub struct PasswordSalt([u8; 32]);
-
-impl AsRef<[u8]> for PasswordSalt {
-    fn as_ref(&self) -> &[u8] {
-        &self.0
-    }
-}
-
-impl From<[u8; 32]> for PasswordSalt {
-    fn from(bytes: [u8; 32]) -> Self {
-        Self(bytes)
-    }
-}
-
-impl TryFrom<IVec> for PasswordSalt {
-    type Error = std::array::TryFromSliceError;
-
-    fn try_from(value: IVec) -> Result<Self, Self::Error> {
-        Ok(Self(value.as_ref().try_into()?))
-    }
-}
-
-use rpassword::read_password;
-
-/// Specifies how [Password] will be retrieved
-#[derive(Clone, Debug)]
-pub enum PasswordMethod {
-    NoPassword,
-    Prompt,
-}
-impl PasswordMethod {
-    /// Execute the password method to retrieve a password
-    pub fn execute(&self) -> EncryptedDbResult<Password> {
-        Ok(match self {
-            Self::NoPassword => Password(UNSAFE_PASSWORD.to_string()),
-            Self::Prompt => {
-                println!("Please type your password:");
-                Password(read_password()?)
-            },
-        })
-    }
-}
-
-#[cfg(test)]
-impl From<&str> for Password {
-    fn from(value: &str) -> Self {
-        Self(value.to_string())
-    }
-}

From d9c0a3caf81a5985392423e363dee25ed16acdf5 Mon Sep 17 00:00:00 2001
From: peg <ameba23@systemli.org>
Date: Tue, 14 Jan 2025 12:52:50 +0100
Subject: [PATCH 38/77] Rm commented code

---
 crates/kvdb/src/encrypted_sled/kv.rs | 17 -----------------
 1 file changed, 17 deletions(-)

diff --git a/crates/kvdb/src/encrypted_sled/kv.rs b/crates/kvdb/src/encrypted_sled/kv.rs
index 1d1170416..204223a99 100644
--- a/crates/kvdb/src/encrypted_sled/kv.rs
+++ b/crates/kvdb/src/encrypted_sled/kv.rs
@@ -68,23 +68,6 @@ impl EncryptedDb {
         Ok(encrypted_db)
     }
 
-    // fn chacha20poly1305_kdf(
-    //     password: Password,
-    //     salt: PasswordSalt,
-    // ) -> EncryptedDbResult<chacha20poly1305::Key> {
-    //     let mut output = chacha20poly1305::Key::default();
-    //
-    //     // default params: log_n = 15, r = 8, p = 1
-    //     scrypt::scrypt(
-    //         password.as_ref(),
-    //         salt.as_ref(),
-    //         &scrypt::Params::default(),
-    //         output.as_mut_slice(),
-    //     )?;
-    //
-    //     Ok(output)
-    // }
-
     /// get a new random nonce to use for value encryption using [rand::thread_rng]
     fn generate_nonce() -> chacha20poly1305::XNonce {
         let mut bytes = chacha20poly1305::XNonce::default();

From 10ab4bda29ba8d6b70711245613f59e931ba36f6 Mon Sep 17 00:00:00 2001
From: peg <ameba23@systemli.org>
Date: Wed, 15 Jan 2025 10:32:04 +0100
Subject: [PATCH 39/77] Add key provider logic

---
 .../src/helpers/launch.rs                     | 78 +++++++++++++++---
 .../src/helpers/validator.rs                  | 16 ++--
 .../src/key_provider/api.rs                   | 80 ++++++++++++++++---
 crates/threshold-signature-server/src/lib.rs  |  6 +-
 4 files changed, 146 insertions(+), 34 deletions(-)

diff --git a/crates/threshold-signature-server/src/helpers/launch.rs b/crates/threshold-signature-server/src/helpers/launch.rs
index aa1818d1d..ad5bc4c58 100644
--- a/crates/threshold-signature-server/src/helpers/launch.rs
+++ b/crates/threshold-signature-server/src/helpers/launch.rs
@@ -17,12 +17,20 @@
 
 use std::path::PathBuf;
 
-use crate::{chain_api::entropy, helpers::substrate::query_chain, AppState};
+use crate::{
+    chain_api::entropy,
+    helpers::{substrate::query_chain, validator::get_signer_and_x25519_secret},
+    key_provider::api::{get_key_provider_details, make_provider_request},
+    AppState,
+};
 use clap::Parser;
 use entropy_client::substrate::SubstrateError;
 use entropy_kvdb::kv_manager::{error::KvError, KvManager};
+use rand_core::OsRng;
 use serde::Deserialize;
 use sp_core::crypto::Ss58Codec;
+use sp_core::{sr25519, Pair};
+use x25519_dalek::StaticSecret;
 
 pub const DEFAULT_MNEMONIC: &str =
     "alarm mutual concert decrease hurry invest culture survey diagram crash snap click";
@@ -42,6 +50,9 @@ pub const LATEST_BLOCK_NUMBER_ATTEST: &str = "LATEST_BLOCK_NUMBER_ATTEST";
 
 pub const LATEST_BLOCK_NUMBER_PROACTIVE_REFRESH: &str = "LATEST_BLOCK_NUMBER_PROACTIVE_REFRESH";
 
+const X25519_SECRET: &str = "X25519_SECRET";
+const SR25519_SEED: &str = "SR25519_SEED";
+
 #[cfg(any(test, feature = "test_helpers"))]
 pub const DEFAULT_ENDPOINT: &str = "ws://localhost:9944";
 
@@ -82,23 +93,66 @@ impl Configuration {
     }
 }
 
-pub async fn setup_kv_store() {
+pub enum StoreSetupOutput {
+    Exists,
+    New([u8; 32]),
+}
+
+pub async fn setup_kv_store(
+    validator_name: &Option<ValidatorName>,
+) -> (StoreSetupOutput, KvManager, sr25519::Pair, StaticSecret) {
+    // Check for existing database
     let path: PathBuf = PathBuf::from(entropy_kvdb::get_db_path(false));
-    let exists = std::fs::metadata(path).is_ok();
+    let exists = std::fs::metadata(path.clone()).is_ok();
     if exists {
         // Read key provider details
-        // Make provider request
-        // open store with retrieved key
-        // load_kv_store(validator_name, key);
+        let key_provider_details = get_key_provider_details(path).unwrap();
+        // Retrieve encryption key from another TSS node
+        let key = make_provider_request(key_provider_details).await.unwrap();
+        let kv_manager = load_kv_store(validator_name, Some(key)).await;
+        let x25519_secret: [u8; 32] =
+            kv_manager.kv().get(X25519_SECRET).await.unwrap().try_into().unwrap();
+        let sr25519_seed: [u8; 32] =
+            kv_manager.kv().get(SR25519_SEED).await.unwrap().try_into().unwrap();
+        let pair = sr25519::Pair::from_seed(&sr25519_seed);
+        (StoreSetupOutput::Exists, kv_manager, pair, x25519_secret.into())
     } else {
         // Generate TSS account (or use ValidatorName)
-        // Select a provider by making chain query and choosing a tss node
-        // Make provider request
-        // Store provider details
-        // open store with retrived key
-        // load_kv_store(validator_name, key);
+        let (pair, seed, x25519_secret) = if cfg!(test) || validator_name.is_some() {
+            get_signer_and_x25519_secret(&development_mnemonic(validator_name).to_string()).unwrap()
+        } else {
+            let (pair, seed) = sr25519::Pair::generate();
+            let x25519_secret = StaticSecret::random_from_rng(OsRng);
+            (pair, seed, x25519_secret)
+        };
+        // TODO randomly generate key
+        let encryption_key = [0; 32];
+        // open store with generated key
+        let kv_manager = load_kv_store(validator_name, Some(encryption_key)).await;
+        // store TSS keys in kv store
+        let reservation = kv_manager
+            .kv()
+            .reserve_key(X25519_SECRET.to_string())
+            .await
+            .expect("Issue reserving x25519 secret key");
+        kv_manager
+            .kv()
+            .put(reservation, x25519_secret.to_bytes().to_vec())
+            .await
+            .expect("failed to store x25519 secret");
+
+        let reservation = kv_manager
+            .kv()
+            .reserve_key(SR25519_SEED.to_string())
+            .await
+            .expect("Issue reserving sr25519 seed");
+        kv_manager
+            .kv()
+            .put(reservation, seed.to_vec())
+            .await
+            .expect("failed to store sr25519 seed");
+        (StoreSetupOutput::New(encryption_key), kv_manager, pair, x25519_secret)
     }
-    // return TSS account private keys to be used in appstate
 }
 
 pub async fn load_kv_store(
diff --git a/crates/threshold-signature-server/src/helpers/validator.rs b/crates/threshold-signature-server/src/helpers/validator.rs
index 06d3bf809..a484df6d5 100644
--- a/crates/threshold-signature-server/src/helpers/validator.rs
+++ b/crates/threshold-signature-server/src/helpers/validator.rs
@@ -27,15 +27,15 @@ use crate::user::UserErr;
 const KDF_SR25519: &[u8] = b"sr25519-threshold-account";
 const KDF_X25519: &[u8] = b"X25519-keypair";
 
-/// Get the PairSigner as above, and also the x25519 encryption keypair for
+/// Get the PairSigner, seed, and also the x25519 encryption keypair for
 /// this threshold server
 pub fn get_signer_and_x25519_secret(
     mnemonic: &str,
-) -> Result<(sr25519::Pair, StaticSecret), UserErr> {
+) -> Result<(sr25519::Pair, [u8; 32], StaticSecret), UserErr> {
     let hkdf = get_hkdf_from_mnemonic(mnemonic)?;
-    let pair_signer = get_signer_from_hkdf(&hkdf)?;
+    let (pair_signer, seed) = get_signer_from_hkdf(&hkdf)?;
     let static_secret = get_x25519_secret_from_hkdf(&hkdf)?;
-    Ok((pair_signer, static_secret))
+    Ok((pair_signer, seed, static_secret))
 }
 
 /// Given a mnemonic, setup hkdf
@@ -46,13 +46,11 @@ fn get_hkdf_from_mnemonic(mnemonic: &str) -> Result<Hkdf<Sha256>, UserErr> {
 }
 
 /// Derive signing keypair
-pub fn get_signer_from_hkdf(hkdf: &Hkdf<Sha256>) -> Result<sr25519::Pair, UserErr> {
+pub fn get_signer_from_hkdf(hkdf: &Hkdf<Sha256>) -> Result<(sr25519::Pair, [u8; 32]), UserErr> {
     let mut sr25519_seed = [0u8; 32];
     hkdf.expand(KDF_SR25519, &mut sr25519_seed)?;
     let pair = sr25519::Pair::from_seed(&sr25519_seed);
-    sr25519_seed.zeroize();
-
-    Ok(pair)
+    Ok((pair, sr25519_seed))
 }
 
 /// Derive x25519 secret
@@ -70,7 +68,7 @@ pub fn get_signer_and_x25519_secret_from_mnemonic(
     mnemonic: &str,
 ) -> Result<(subxt::tx::PairSigner<crate::EntropyConfig, sr25519::Pair>, StaticSecret), UserErr> {
     let hkdf = get_hkdf_from_mnemonic(mnemonic)?;
-    let pair = get_signer_from_hkdf(&hkdf)?;
+    let (pair, _) = get_signer_from_hkdf(&hkdf)?;
     let pair_signer = subxt::tx::PairSigner::new(pair);
     let static_secret = get_x25519_secret_from_hkdf(&hkdf)?;
     Ok((pair_signer, static_secret))
diff --git a/crates/threshold-signature-server/src/key_provider/api.rs b/crates/threshold-signature-server/src/key_provider/api.rs
index af3aa28bf..bba5a7391 100644
--- a/crates/threshold-signature-server/src/key_provider/api.rs
+++ b/crates/threshold-signature-server/src/key_provider/api.rs
@@ -14,32 +14,33 @@
 // along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
 use crate::{
-    key_provider::errors::KeyProviderError, validation::EncryptedSignedMessage, AppState,
-    SubxtAccountId32,
+    chain_api::entropy, key_provider::errors::KeyProviderError, validation::EncryptedSignedMessage,
+    AppState, EntropyConfig, SubxtAccountId32,
 };
 use axum::{extract::State, Json};
-use entropy_shared::{ValidatorInfo, X25519PublicKey};
+use entropy_client::substrate::query_chain;
+use entropy_shared::{user::ValidatorInfo, X25519PublicKey};
+use rand::{rngs::StdRng, Rng, SeedableRng};
 use rand_core::OsRng;
 use serde::{Deserialize, Serialize};
+use subxt::{backend::legacy::LegacyRpcMethods, OnlineClient};
 use x25519_dalek::{PublicKey, StaticSecret};
 
 pub async fn make_provider_request(
-    validator_info: ValidatorInfo,
-    tss_account: SubxtAccountId32,
+    key_provider_details: KeyProviderDetails,
 ) -> Result<[u8; 32], KeyProviderError> {
     let quote = Vec::new(); // TODO
 
+    // Generate encryption keypair used for receiving the key
     let response_secret_key = StaticSecret::random_from_rng(OsRng);
     let response_key = PublicKey::from(&response_secret_key).to_bytes();
 
-    let key_request = EncryptionKeyRequest { tss_account, response_key, quote };
+    let key_request =
+        EncryptionKeyRequest { tss_account: key_provider_details.tss_account, response_key, quote };
 
     let client = reqwest::Client::new();
     let response = client
-        .post(format!(
-            "http://{}/request_encryption_key",
-            String::from_utf8(validator_info.ip_address).unwrap()
-        ))
+        .post(format!("http://{}/request_encryption_key", key_provider_details.provider.ip_address))
         .header("Content-Type", "application/json")
         .body(serde_json::to_string(&key_request).unwrap())
         .send()
@@ -59,6 +60,12 @@ pub async fn make_provider_request(
     Ok(signed_message.message.0.try_into().unwrap())
 }
 
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct KeyProviderDetails {
+    pub provider: ValidatorInfo,
+    pub tss_account: SubxtAccountId32,
+}
+
 #[derive(Debug, Clone, Serialize, Deserialize)]
 pub struct EncryptionKeyRequest {
     tss_account: SubxtAccountId32,
@@ -90,3 +97,56 @@ pub async fn request_encryption_key(
             .unwrap();
     Ok(Json(signed_message))
 }
+
+pub fn store_key_provider_details(
+    mut path: std::path::PathBuf,
+    key_provider_details: KeyProviderDetails,
+) -> std::io::Result<()> {
+    path.push("key-provider-details.json");
+    std::fs::write(path, &serde_json::to_vec(&key_provider_details).unwrap())
+}
+
+pub fn get_key_provider_details(
+    mut path: std::path::PathBuf,
+) -> std::io::Result<KeyProviderDetails> {
+    path.push("key-provider-details.json");
+    let bytes = std::fs::read(path)?;
+    Ok(serde_json::from_slice(&bytes).unwrap())
+}
+
+pub async fn select_key_provider(
+    api: &OnlineClient<EntropyConfig>,
+    rpc: &LegacyRpcMethods<EntropyConfig>,
+    tss_account: SubxtAccountId32,
+) -> ValidatorInfo {
+    let validators_query = entropy::storage().session().validators();
+    let validators = query_chain(api, rpc, validators_query, None).await.unwrap().unwrap();
+    // .ok_or_else(|| SubgroupGetError::ChainFetch("Error getting validators"))?;
+
+    let mut deterministic_rng = StdRng::from_seed(tss_account.0);
+    let random_index = deterministic_rng.gen_range(0..validators.len());
+    let validator = &validators[random_index];
+
+    let threshold_address_query =
+        entropy::storage().staking_extension().threshold_servers(validator);
+    let server_info =
+        query_chain(&api, &rpc, threshold_address_query, None).await.unwrap().unwrap();
+    // .ok_or_else(|| SubgroupGetError::ChainFetch("threshold_servers query error"))?;
+    ValidatorInfo {
+        x25519_public_key: server_info.x25519_public_key,
+        ip_address: std::str::from_utf8(&server_info.endpoint).unwrap().to_string(),
+        tss_account: server_info.tss_account,
+    }
+}
+
+// pub async fn make_key_backup() {
+//         // Select a provider by making chain query and choosing a tss node
+//
+//         let key_provider_details =
+//             KeyProviderDetails { tss_account: SubxtAccountId32(pair.public().0), provider };
+//         // Make provider request
+//         // p
+//         let key = make_provider_request(key_provider_details).await.unwrap();
+//         // Store provider details
+//         store_key_provider_details(path, key_provider_details).unwrap();
+// }
diff --git a/crates/threshold-signature-server/src/lib.rs b/crates/threshold-signature-server/src/lib.rs
index 452ea3cde..1b65d2554 100644
--- a/crates/threshold-signature-server/src/lib.rs
+++ b/crates/threshold-signature-server/src/lib.rs
@@ -232,12 +232,12 @@ impl AppState {
         kv_store: KvManager,
         validator_name: &Option<ValidatorName>,
     ) -> Self {
-        let (pair, x25519_secret) = if cfg!(test) || validator_name.is_some() {
+        let (pair, _seed, x25519_secret) = if cfg!(test) || validator_name.is_some() {
             get_signer_and_x25519_secret(&development_mnemonic(validator_name).to_string()).unwrap()
         } else {
-            let (pair, _seed) = sr25519::Pair::generate();
+            let (pair, seed) = sr25519::Pair::generate();
             let x25519_secret = StaticSecret::random_from_rng(OsRng);
-            (pair, x25519_secret)
+            (pair, seed, x25519_secret)
         };
 
         Self {

From 06fe850d83d87ab8d8ece3e97c83341e865b8fa5 Mon Sep 17 00:00:00 2001
From: peg <ameba23@systemli.org>
Date: Thu, 16 Jan 2025 12:09:25 +0100
Subject: [PATCH 40/77] Improve key provider and add test

---
 .../src/helpers/launch.rs                     |  49 +++---
 .../src/helpers/tests.rs                      |  24 ++-
 .../src/key_provider/api.rs                   | 144 ++++++++++++------
 .../src/key_provider/tests.rs                 |  47 ++++--
 crates/threshold-signature-server/src/lib.rs  |  21 +--
 crates/threshold-signature-server/src/main.rs |  10 +-
 .../src/user/tests.rs                         |   6 +-
 7 files changed, 187 insertions(+), 114 deletions(-)

diff --git a/crates/threshold-signature-server/src/helpers/launch.rs b/crates/threshold-signature-server/src/helpers/launch.rs
index ad5bc4c58..a916d38f1 100644
--- a/crates/threshold-signature-server/src/helpers/launch.rs
+++ b/crates/threshold-signature-server/src/helpers/launch.rs
@@ -20,7 +20,7 @@ use std::path::PathBuf;
 use crate::{
     chain_api::entropy,
     helpers::{substrate::query_chain, validator::get_signer_and_x25519_secret},
-    key_provider::api::{get_key_provider_details, make_provider_request},
+    key_provider::api::{get_key_provider_details, request_recover_encryption_key},
     AppState,
 };
 use clap::Parser;
@@ -93,29 +93,26 @@ impl Configuration {
     }
 }
 
-pub enum StoreSetupOutput {
-    Exists,
-    New([u8; 32]),
-}
-
 pub async fn setup_kv_store(
     validator_name: &Option<ValidatorName>,
-) -> (StoreSetupOutput, KvManager, sr25519::Pair, StaticSecret) {
+    storage_path: Option<PathBuf>,
+) -> (KvManager, sr25519::Pair, StaticSecret, bool) {
+    let storage_path = storage_path.unwrap_or_else(|| build_db_path(validator_name));
+
     // Check for existing database
-    let path: PathBuf = PathBuf::from(entropy_kvdb::get_db_path(false));
-    let exists = std::fs::metadata(path.clone()).is_ok();
+    let exists = std::fs::metadata(storage_path.clone()).is_ok();
     if exists {
         // Read key provider details
-        let key_provider_details = get_key_provider_details(path).unwrap();
+        let key_provider_details = get_key_provider_details(storage_path.clone()).unwrap();
         // Retrieve encryption key from another TSS node
-        let key = make_provider_request(key_provider_details).await.unwrap();
-        let kv_manager = load_kv_store(validator_name, Some(key)).await;
+        let key = request_recover_encryption_key(key_provider_details).await.unwrap();
+        let kv_manager = KvManager::new(storage_path, key).unwrap();
         let x25519_secret: [u8; 32] =
             kv_manager.kv().get(X25519_SECRET).await.unwrap().try_into().unwrap();
         let sr25519_seed: [u8; 32] =
             kv_manager.kv().get(SR25519_SEED).await.unwrap().try_into().unwrap();
         let pair = sr25519::Pair::from_seed(&sr25519_seed);
-        (StoreSetupOutput::Exists, kv_manager, pair, x25519_secret.into())
+        (kv_manager, pair, x25519_secret.into(), false)
     } else {
         // Generate TSS account (or use ValidatorName)
         let (pair, seed, x25519_secret) = if cfg!(test) || validator_name.is_some() {
@@ -128,7 +125,7 @@ pub async fn setup_kv_store(
         // TODO randomly generate key
         let encryption_key = [0; 32];
         // open store with generated key
-        let kv_manager = load_kv_store(validator_name, Some(encryption_key)).await;
+        let kv_manager = KvManager::new(storage_path, encryption_key).unwrap();
         // store TSS keys in kv store
         let reservation = kv_manager
             .kv()
@@ -151,45 +148,33 @@ pub async fn setup_kv_store(
             .put(reservation, seed.to_vec())
             .await
             .expect("failed to store sr25519 seed");
-        (StoreSetupOutput::New(encryption_key), kv_manager, pair, x25519_secret)
+        (kv_manager, pair, x25519_secret, true)
     }
 }
 
-pub async fn load_kv_store(
-    validator_name: &Option<ValidatorName>,
-    key: Option<[u8; 32]>,
-) -> KvManager {
-    let key = key.unwrap_or_default();
-    let mut root: PathBuf = PathBuf::from(entropy_kvdb::get_db_path(false));
+pub fn build_db_path(validator_name: &Option<ValidatorName>) -> PathBuf {
     if cfg!(test) {
-        return KvManager::new(entropy_kvdb::get_db_path(true).into(), key).unwrap();
+        return PathBuf::from(entropy_kvdb::get_db_path(true));
     }
 
-    if validator_name == &Some(ValidatorName::Alice) {
-        return KvManager::new(root, key).unwrap();
-    };
-
+    let mut root: PathBuf = PathBuf::from(entropy_kvdb::get_db_path(false));
+    // Alice has no extra subdirectory
     if validator_name == &Some(ValidatorName::Bob) {
         root.push("bob");
-        return KvManager::new(root, key).unwrap();
     };
 
     if validator_name == &Some(ValidatorName::Charlie) {
         root.push("charlie");
-        return KvManager::new(root, key).unwrap();
     };
 
     if validator_name == &Some(ValidatorName::Dave) {
         root.push("dave");
-        return KvManager::new(root, key).unwrap();
     };
 
     if validator_name == &Some(ValidatorName::Eve) {
         root.push("eve");
-        return KvManager::new(root, key).unwrap();
     };
-
-    KvManager::new(root, key).unwrap()
+    root
 }
 
 #[derive(Parser, Debug, Clone)]
diff --git a/crates/threshold-signature-server/src/helpers/tests.rs b/crates/threshold-signature-server/src/helpers/tests.rs
index 127bdb58e..532ea5174 100644
--- a/crates/threshold-signature-server/src/helpers/tests.rs
+++ b/crates/threshold-signature-server/src/helpers/tests.rs
@@ -31,7 +31,10 @@ use crate::{
         EntropyConfig,
     },
     helpers::{
-        launch::{setup_latest_block_number, Configuration, ValidatorName, DEFAULT_ENDPOINT},
+        launch::{
+            setup_kv_store, setup_latest_block_number, Configuration, ValidatorName,
+            DEFAULT_ENDPOINT,
+        },
         logger::{Instrumentation, Logger},
         substrate::submit_transaction,
     },
@@ -45,7 +48,7 @@ use entropy_protocol::PartyId;
 use entropy_shared::EncodedVerifyingKey;
 use entropy_shared::NETWORK_PARENT_KEY;
 use sp_keyring::AccountKeyring;
-use std::{fmt, net::SocketAddr, str, time::Duration};
+use std::{fmt, net::SocketAddr, path::PathBuf, str, time::Duration};
 use subxt::{
     backend::legacy::LegacyRpcMethods, ext::sp_core::sr25519, tx::PairSigner,
     utils::AccountId32 as SubxtAccountId32, Config, OnlineClient,
@@ -67,11 +70,15 @@ pub async fn initialize_test_logger() {
 }
 
 pub async fn setup_client() -> KvManager {
-    let kv_store = KvManager::new(get_db_path(true).into(), [0; 32]).unwrap();
+    let configuration = Configuration::new(DEFAULT_ENDPOINT.to_string());
+
+    let storage_path: PathBuf = get_db_path(true).into();
+    let (kv_store, sr25519_pair, x25519_secret, _should_backup) =
+        setup_kv_store(&Some(ValidatorName::Alice), Some(storage_path.clone())).await;
 
     let _ = setup_latest_block_number(&kv_store).await;
-    let configuration = Configuration::new(DEFAULT_ENDPOINT.to_string());
-    let app_state = AppState::new(configuration, kv_store.clone(), &Some(ValidatorName::Alice));
+    let app_state = AppState::new(configuration, kv_store.clone(), sr25519_pair, x25519_secret);
+
     // Mock making the pre-requisite checks by setting the application state to ready
     app_state.make_ready();
 
@@ -98,7 +105,11 @@ pub async fn create_clients(
     let path = format!(".entropy/testing/test_db_{key_number}");
     let _ = std::fs::remove_dir_all(path.clone());
 
-    let kv_store = KvManager::new(path.into(), [0; 32]).unwrap();
+    let (kv_store, sr25519_pair, x25519_secret, _should_backup) =
+        setup_kv_store(validator_name, Some(path.into())).await;
+
+    let _ = setup_latest_block_number(&kv_store).await;
+    let app_state = AppState::new(configuration, kv_store.clone(), sr25519_pair, x25519_secret);
 
     let _ = setup_latest_block_number(&kv_store).await;
 
@@ -107,7 +118,6 @@ pub async fn create_clients(
         let _ = kv_store.clone().kv().put(reservation, value).await;
     }
 
-    let app_state = AppState::new(configuration, kv_store.clone(), validator_name);
     // Mock making the pre-requisite checks by setting the application state to ready
     app_state.make_ready();
 
diff --git a/crates/threshold-signature-server/src/key_provider/api.rs b/crates/threshold-signature-server/src/key_provider/api.rs
index bba5a7391..1fac53239 100644
--- a/crates/threshold-signature-server/src/key_provider/api.rs
+++ b/crates/threshold-signature-server/src/key_provider/api.rs
@@ -23,10 +23,40 @@ use entropy_shared::{user::ValidatorInfo, X25519PublicKey};
 use rand::{rngs::StdRng, Rng, SeedableRng};
 use rand_core::OsRng;
 use serde::{Deserialize, Serialize};
+use std::path::PathBuf;
 use subxt::{backend::legacy::LegacyRpcMethods, OnlineClient};
 use x25519_dalek::{PublicKey, StaticSecret};
 
-pub async fn make_provider_request(
+const KEY_PROVIDER_FILENAME: &str = "key-provider-details.json";
+
+/// Make a request to a given TSS node to backup a given encryption key
+pub async fn request_backup_encryption_key(
+    key: [u8; 32],
+    key_provider_details: KeyProviderDetails,
+) -> Result<(), KeyProviderError> {
+    let quote = Vec::new(); // TODO
+
+    let key_request =
+        BackupEncryptionKeyRequest { tss_account: key_provider_details.tss_account, quote, key };
+
+    let client = reqwest::Client::new();
+    let response = client
+        .post(format!("http://{}/backup_encryption_key", key_provider_details.provider.ip_address))
+        .header("Content-Type", "application/json")
+        .body(serde_json::to_string(&key_request).unwrap())
+        .send()
+        .await?;
+
+    let status = response.status();
+    if status != reqwest::StatusCode::OK {
+        let text = response.text().await.unwrap();
+        panic!("Bad status code {}: {}", status, text);
+    }
+    Ok(())
+}
+
+/// Make a request to a given TSS node to recover an encryption key
+pub async fn request_recover_encryption_key(
     key_provider_details: KeyProviderDetails,
 ) -> Result<[u8; 32], KeyProviderError> {
     let quote = Vec::new(); // TODO
@@ -35,12 +65,15 @@ pub async fn make_provider_request(
     let response_secret_key = StaticSecret::random_from_rng(OsRng);
     let response_key = PublicKey::from(&response_secret_key).to_bytes();
 
-    let key_request =
-        EncryptionKeyRequest { tss_account: key_provider_details.tss_account, response_key, quote };
+    let key_request = RecoverEncryptionKeyRequest {
+        tss_account: key_provider_details.tss_account,
+        response_key,
+        quote,
+    };
 
     let client = reqwest::Client::new();
     let response = client
-        .post(format!("http://{}/request_encryption_key", key_provider_details.provider.ip_address))
+        .post(format!("http://{}/recover_encryption_key", key_provider_details.provider.ip_address))
         .header("Content-Type", "application/json")
         .body(serde_json::to_string(&key_request).unwrap())
         .send()
@@ -67,30 +100,46 @@ pub struct KeyProviderDetails {
 }
 
 #[derive(Debug, Clone, Serialize, Deserialize)]
-pub struct EncryptionKeyRequest {
+pub struct BackupEncryptionKeyRequest {
+    key: [u8; 32],
+    tss_account: SubxtAccountId32,
+    quote: Vec<u8>,
+}
+
+#[derive(Debug, Clone, Serialize, Deserialize)]
+pub struct RecoverEncryptionKeyRequest {
     tss_account: SubxtAccountId32,
     response_key: X25519PublicKey,
     quote: Vec<u8>,
 }
 
-pub async fn request_encryption_key(
+/// HTTP to backup an encryption key on initial launch
+pub async fn backup_encryption_key(
     State(app_state): State<AppState>,
-    Json(key_request): Json<EncryptionKeyRequest>,
-) -> Result<Json<EncryptedSignedMessage>, KeyProviderError> {
+    Json(key_request): Json<BackupEncryptionKeyRequest>,
+) -> Result<(), KeyProviderError> {
     // Build quote input
     // Verify quote
     // Check kvdb for existing key - or generate and store one
-    let lookup_key = format!("BACKUP_KEY:{}", key_request.tss_account.to_string());
-    let key: [u8; 32] = match app_state.kv_store.kv().get(&lookup_key).await {
-        Ok(existing_key) => existing_key.try_into().map_err(|_| KeyProviderError::BadKeyLength)?,
-        Err(_) => {
-            // TODO Generate random 32 byte key
-            let encryption_key = [0; 32];
-            let reservation = app_state.kv_store.kv().reserve_key(lookup_key).await?;
-            app_state.kv_store.kv().put(reservation, encryption_key.to_vec()).await?;
-            encryption_key
-        },
-    };
+    let lookup_key = format!("BACKUP_KEY:{}", key_request.tss_account);
+
+    let reservation = app_state.kv_store.kv().reserve_key(lookup_key).await?;
+    app_state.kv_store.kv().put(reservation, key_request.key.to_vec()).await?;
+
+    Ok(())
+}
+
+/// HTTP endpoint to recover an encryption key following a process restart
+pub async fn recover_encryption_key(
+    State(app_state): State<AppState>,
+    Json(key_request): Json<RecoverEncryptionKeyRequest>,
+) -> Result<Json<EncryptedSignedMessage>, KeyProviderError> {
+    // TODO Build quote input
+    // TODO Verify quote
+    let lookup_key = format!("BACKUP_KEY:{}", key_request.tss_account);
+    let existing_key = app_state.kv_store.kv().get(&lookup_key).await?;
+    let key: [u8; 32] = existing_key.try_into().map_err(|_| KeyProviderError::BadKeyLength)?;
+
     // Encrypt response
     let signed_message =
         EncryptedSignedMessage::new(&app_state.pair, key.to_vec(), &key_request.response_key, &[])
@@ -98,27 +147,25 @@ pub async fn request_encryption_key(
     Ok(Json(signed_message))
 }
 
-pub fn store_key_provider_details(
-    mut path: std::path::PathBuf,
+fn store_key_provider_details(
+    mut path: PathBuf,
     key_provider_details: KeyProviderDetails,
 ) -> std::io::Result<()> {
-    path.push("key-provider-details.json");
-    std::fs::write(path, &serde_json::to_vec(&key_provider_details).unwrap())
+    path.push(KEY_PROVIDER_FILENAME);
+    std::fs::write(path, serde_json::to_vec(&key_provider_details).unwrap())
 }
 
-pub fn get_key_provider_details(
-    mut path: std::path::PathBuf,
-) -> std::io::Result<KeyProviderDetails> {
-    path.push("key-provider-details.json");
+pub fn get_key_provider_details(mut path: PathBuf) -> std::io::Result<KeyProviderDetails> {
+    path.push(KEY_PROVIDER_FILENAME);
     let bytes = std::fs::read(path)?;
     Ok(serde_json::from_slice(&bytes).unwrap())
 }
 
-pub async fn select_key_provider(
+async fn select_key_provider(
     api: &OnlineClient<EntropyConfig>,
     rpc: &LegacyRpcMethods<EntropyConfig>,
     tss_account: SubxtAccountId32,
-) -> ValidatorInfo {
+) -> KeyProviderDetails {
     let validators_query = entropy::storage().session().validators();
     let validators = query_chain(api, rpc, validators_query, None).await.unwrap().unwrap();
     // .ok_or_else(|| SubgroupGetError::ChainFetch("Error getting validators"))?;
@@ -129,24 +176,29 @@ pub async fn select_key_provider(
 
     let threshold_address_query =
         entropy::storage().staking_extension().threshold_servers(validator);
-    let server_info =
-        query_chain(&api, &rpc, threshold_address_query, None).await.unwrap().unwrap();
+    let server_info = query_chain(api, rpc, threshold_address_query, None).await.unwrap().unwrap();
     // .ok_or_else(|| SubgroupGetError::ChainFetch("threshold_servers query error"))?;
-    ValidatorInfo {
-        x25519_public_key: server_info.x25519_public_key,
-        ip_address: std::str::from_utf8(&server_info.endpoint).unwrap().to_string(),
-        tss_account: server_info.tss_account,
+    KeyProviderDetails {
+        provider: ValidatorInfo {
+            x25519_public_key: server_info.x25519_public_key,
+            ip_address: std::str::from_utf8(&server_info.endpoint).unwrap().to_string(),
+            tss_account: server_info.tss_account,
+        },
+        tss_account,
     }
 }
 
-// pub async fn make_key_backup() {
-//         // Select a provider by making chain query and choosing a tss node
-//
-//         let key_provider_details =
-//             KeyProviderDetails { tss_account: SubxtAccountId32(pair.public().0), provider };
-//         // Make provider request
-//         // p
-//         let key = make_provider_request(key_provider_details).await.unwrap();
-//         // Store provider details
-//         store_key_provider_details(path, key_provider_details).unwrap();
-// }
+pub async fn make_key_backup(
+    api: &OnlineClient<EntropyConfig>,
+    rpc: &LegacyRpcMethods<EntropyConfig>,
+    key: [u8; 32],
+    tss_account: SubxtAccountId32,
+    storage_path: PathBuf,
+) {
+    // Select a provider by making chain query and choosing a tss node
+    let key_provider_details = select_key_provider(api, rpc, tss_account).await;
+    // Get them to backup the key
+    request_backup_encryption_key(key, key_provider_details.clone()).await.unwrap();
+    // Store provider details so we know who to ask when recovering
+    store_key_provider_details(storage_path, key_provider_details).unwrap();
+}
diff --git a/crates/threshold-signature-server/src/key_provider/tests.rs b/crates/threshold-signature-server/src/key_provider/tests.rs
index 78fcd5045..6913dbc6a 100644
--- a/crates/threshold-signature-server/src/key_provider/tests.rs
+++ b/crates/threshold-signature-server/src/key_provider/tests.rs
@@ -15,12 +15,19 @@
 
 use crate::{
     helpers::tests::{initialize_test_logger, setup_client},
-    key_provider::api::make_provider_request,
+    key_provider::api::{
+        make_key_backup, request_backup_encryption_key, request_recover_encryption_key,
+        KeyProviderDetails,
+    },
     SubxtAccountId32,
 };
 use entropy_kvdb::clean_tests;
-use entropy_shared::ValidatorInfo;
-use entropy_testing_utils::constants::{TSS_ACCOUNTS, X25519_PUBLIC_KEYS};
+use entropy_shared::user::ValidatorInfo;
+use entropy_testing_utils::{
+    constants::{TSS_ACCOUNTS, X25519_PUBLIC_KEYS},
+    helpers::spawn_tss_nodes_and_start_chain,
+    ChainSpecType,
+};
 use serial_test::serial;
 use sp_keyring::AccountKeyring;
 
@@ -29,13 +36,33 @@ use sp_keyring::AccountKeyring;
 async fn key_provider_test() {
     clean_tests();
     initialize_test_logger().await;
+
+    let (_ctx, api, rpc, _validator_ips, _validator_ids) =
+        spawn_tss_nodes_and_start_chain(ChainSpecType::IntegrationJumpStarted).await;
+
+    let storage_path = ".entropy/testing/test_db_validator1".into();
+    let key = [0; 32]; // TODO this should be the actual key used. Since we dont have access to
+                       // kvmanager, alice bob etc. should use known keys
+    make_key_backup(&api, &rpc, key, TSS_ACCOUNTS[0].clone(), storage_path).await;
+}
+
+#[tokio::test]
+#[serial]
+async fn key_provider_unit_test() {
+    clean_tests();
+    initialize_test_logger().await;
     setup_client().await;
-    let validator_info = ValidatorInfo {
-        tss_account: TSS_ACCOUNTS[0].0.to_vec(),
-        x25519_public_key: X25519_PUBLIC_KEYS[0],
-        ip_address: b"127.0.0.1:3001".to_vec(),
+    let key_provider_details = KeyProviderDetails {
+        provider: ValidatorInfo {
+            tss_account: TSS_ACCOUNTS[0].clone(),
+            x25519_public_key: X25519_PUBLIC_KEYS[0],
+            ip_address: "127.0.0.1:3001".to_string(),
+        },
+        tss_account: SubxtAccountId32(AccountKeyring::Bob.to_raw_public()),
     };
-    let tss_account = SubxtAccountId32(AccountKeyring::Bob.to_raw_public());
-    let _key = make_provider_request(validator_info, tss_account).await.unwrap();
-    // TODO now do it a second time and check key is identical
+    let key = [1; 32];
+
+    request_backup_encryption_key(key, key_provider_details.clone()).await.unwrap();
+    let recovered_key = request_recover_encryption_key(key_provider_details).await.unwrap();
+    assert_eq!(key, recovered_key);
 }
diff --git a/crates/threshold-signature-server/src/lib.rs b/crates/threshold-signature-server/src/lib.rs
index 1b65d2554..2e657e3ab 100644
--- a/crates/threshold-signature-server/src/lib.rs
+++ b/crates/threshold-signature-server/src/lib.rs
@@ -177,7 +177,6 @@ use axum::{
     Router,
 };
 use entropy_kvdb::kv_manager::KvManager;
-use rand_core::OsRng;
 use sp_core::{crypto::AccountId32, sr25519, Pair};
 use std::sync::{Arc, RwLock};
 use subxt::{
@@ -196,8 +195,8 @@ use crate::{
     attestation::api::{attest, get_attest},
     chain_api::{get_api, get_rpc, EntropyConfig},
     health::api::healthz,
-    key_provider::api::request_encryption_key,
-    launch::{development_mnemonic, Configuration, ValidatorName},
+    key_provider::api::{backup_encryption_key, recover_encryption_key},
+    launch::Configuration,
     node_info::api::{hashes, info, version as get_version},
     r#unsafe::api::{delete, put, remove_keys, unsafe_get},
     signing_client::{api::*, ListenerState},
@@ -226,20 +225,13 @@ pub struct AppState {
 }
 
 impl AppState {
-    /// Setup AppState, generating new keypairs unless a test validator name is passed
+    /// Setup AppState with given secret keys
     pub fn new(
         configuration: Configuration,
         kv_store: KvManager,
-        validator_name: &Option<ValidatorName>,
+        pair: sr25519::Pair,
+        x25519_secret: StaticSecret,
     ) -> Self {
-        let (pair, _seed, x25519_secret) = if cfg!(test) || validator_name.is_some() {
-            get_signer_and_x25519_secret(&development_mnemonic(validator_name).to_string()).unwrap()
-        } else {
-            let (pair, seed) = sr25519::Pair::generate();
-            let x25519_secret = StaticSecret::random_from_rng(OsRng);
-            (pair, seed, x25519_secret)
-        };
-
         Self {
             ready: Arc::new(RwLock::new(false)),
             pair,
@@ -306,7 +298,8 @@ pub fn app(app_state: AppState) -> Router {
         .route("/rotate_network_key", post(rotate_network_key))
         .route("/attest", post(attest))
         .route("/attest", get(get_attest))
-        .route("/request_encryption_key", post(request_encryption_key))
+        .route("/backup_encryption_key", post(backup_encryption_key))
+        .route("/recover_encryption_key", post(recover_encryption_key))
         .route("/healthz", get(healthz))
         .route("/version", get(get_version))
         .route("/hashes", get(hashes))
diff --git a/crates/threshold-signature-server/src/main.rs b/crates/threshold-signature-server/src/main.rs
index cdf310ca8..0279678a3 100644
--- a/crates/threshold-signature-server/src/main.rs
+++ b/crates/threshold-signature-server/src/main.rs
@@ -19,7 +19,9 @@ use clap::Parser;
 
 use entropy_tss::{
     app,
-    launch::{load_kv_store, setup_latest_block_number, Configuration, StartupArgs, ValidatorName},
+    launch::{
+        setup_kv_store, setup_latest_block_number, Configuration, StartupArgs, ValidatorName,
+    },
     AppState,
 };
 
@@ -55,9 +57,11 @@ async fn main() {
         validator_name = Some(ValidatorName::Eve);
     }
 
-    let kv_store = load_kv_store(&validator_name, None).await;
+    let (kv_store, sr25519_pair, x25519_secret, should_backup) =
+        setup_kv_store(&validator_name, None).await;
 
-    let app_state = AppState::new(configuration.clone(), kv_store.clone(), &validator_name);
+    let app_state =
+        AppState::new(configuration.clone(), kv_store.clone(), sr25519_pair, x25519_secret);
 
     setup_latest_block_number(&kv_store).await.expect("Issue setting up Latest Block Number");
 
diff --git a/crates/threshold-signature-server/src/user/tests.rs b/crates/threshold-signature-server/src/user/tests.rs
index 2c2cb8712..9e4c08118 100644
--- a/crates/threshold-signature-server/src/user/tests.rs
+++ b/crates/threshold-signature-server/src/user/tests.rs
@@ -21,6 +21,7 @@ use entropy_client::{
     user::{get_all_signers_from_chain, UserSignatureRequest},
 };
 use entropy_kvdb::clean_tests;
+use entropy_kvdb::kv_manager::KvManager;
 use entropy_programs_runtime::Runtime;
 use entropy_protocol::{
     decode_verifying_key,
@@ -75,7 +76,7 @@ use crate::{
         EntropyConfig,
     },
     helpers::{
-        launch::{development_mnemonic, load_kv_store, ValidatorName},
+        launch::{build_db_path, development_mnemonic, ValidatorName},
         signing::Hasher,
         substrate::{get_oracle_data, get_signers_from_chain, query_chain, submit_transaction},
         tests::{
@@ -1516,7 +1517,8 @@ async fn test_increment_or_wipe_request_limit() {
     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 kv_store = load_kv_store(&None, None).await;
+
+    let kv_store = KvManager::new(build_db_path(&None), [0; 32]).unwrap();
 
     let request_limit_query = entropy::storage().parameters().request_limit();
     let request_limit = query_chain(&api, &rpc, request_limit_query, None).await.unwrap().unwrap();

From d5922da4136b862f8b077d4f922d2832f0198ad9 Mon Sep 17 00:00:00 2001
From: peg <ameba23@systemli.org>
Date: Thu, 16 Jan 2025 12:09:44 +0100
Subject: [PATCH 41/77] Kvdb stores key for backup

---
 crates/kvdb/src/kv_manager/kv.rs    | 4 ++--
 crates/kvdb/src/kv_manager/value.rs | 9 +++++++--
 2 files changed, 9 insertions(+), 4 deletions(-)

diff --git a/crates/kvdb/src/kv_manager/kv.rs b/crates/kvdb/src/kv_manager/kv.rs
index d1f7ddc92..8e04c75c7 100644
--- a/crates/kvdb/src/kv_manager/kv.rs
+++ b/crates/kvdb/src/kv_manager/kv.rs
@@ -54,12 +54,12 @@ where
     /// Creates a kvstore at `full_db_name` and spawns a new kv_manager. Returns [InitErr] on
     /// failure. `full_db_name` is the name of the path of the kvstrore + its name
     /// Example: ~/entropy/kvstore/database_1
-    pub fn with_db_name(full_db_name: String, key: [u8; 32]) -> KvResult<Self> {
+    pub fn with_db_name(full_db_name: String, encryption_key: [u8; 32]) -> KvResult<Self> {
         let (sender, rx) = mpsc::unbounded_channel();
 
         // get kv store from db name before entering the kv_cmd_handler because
         // it's more convenient to return an error from outside of a tokio::span
-        let kv = get_kv_store(&full_db_name, key)?;
+        let kv = get_kv_store(&full_db_name, encryption_key)?;
 
         tokio::spawn(kv_cmd_handler(rx, kv));
         Ok(Self { sender })
diff --git a/crates/kvdb/src/kv_manager/value.rs b/crates/kvdb/src/kv_manager/value.rs
index ed8733d2a..3216dca58 100644
--- a/crates/kvdb/src/kv_manager/value.rs
+++ b/crates/kvdb/src/kv_manager/value.rs
@@ -55,16 +55,21 @@ impl fmt::Debug for PartyInfo {
 #[derive(Clone)]
 pub struct KvManager {
     kv: Kv<KvValue>,
+    encryption_key: [u8; 32],
 }
 
 impl KvManager {
-    pub fn new(root: PathBuf, key: [u8; 32]) -> KvResult<Self> {
-        Ok(KvManager { kv: Kv::<KvValue>::new(root, key)? })
+    pub fn new(root: PathBuf, encryption_key: [u8; 32]) -> KvResult<Self> {
+        Ok(KvManager { kv: Kv::<KvValue>::new(root, encryption_key)?, encryption_key })
     }
 
     pub fn kv(&self) -> &Kv<KvValue> {
         &self.kv
     }
+
+    pub fn encryption_key(&self) -> [u8; 32] {
+        self.encryption_key
+    }
 }
 
 /// Value type stored in the kv-store

From 374c98fb516c691dcd4b472abdedd3f76caf4f97 Mon Sep 17 00:00:00 2001
From: peg <ameba23@systemli.org>
Date: Thu, 16 Jan 2025 14:14:03 +0100
Subject: [PATCH 42/77] Actually back up keyshares in production

---
 crates/kvdb/src/kv_manager/value.rs           | 13 ++++---
 .../src/helpers/launch.rs                     | 34 +++++++++++++++----
 crates/threshold-signature-server/src/main.rs |  6 ++--
 3 files changed, 40 insertions(+), 13 deletions(-)

diff --git a/crates/kvdb/src/kv_manager/value.rs b/crates/kvdb/src/kv_manager/value.rs
index 3216dca58..860a9c86d 100644
--- a/crates/kvdb/src/kv_manager/value.rs
+++ b/crates/kvdb/src/kv_manager/value.rs
@@ -55,20 +55,23 @@ impl fmt::Debug for PartyInfo {
 #[derive(Clone)]
 pub struct KvManager {
     kv: Kv<KvValue>,
-    encryption_key: [u8; 32],
+    storage_path: PathBuf,
 }
 
 impl KvManager {
-    pub fn new(root: PathBuf, encryption_key: [u8; 32]) -> KvResult<Self> {
-        Ok(KvManager { kv: Kv::<KvValue>::new(root, encryption_key)?, encryption_key })
+    pub fn new(storage_path: PathBuf, encryption_key: [u8; 32]) -> KvResult<Self> {
+        Ok(KvManager {
+            kv: Kv::<KvValue>::new(storage_path.clone(), encryption_key)?,
+            storage_path,
+        })
     }
 
     pub fn kv(&self) -> &Kv<KvValue> {
         &self.kv
     }
 
-    pub fn encryption_key(&self) -> [u8; 32] {
-        self.encryption_key
+    pub fn storage_path(&self) -> &PathBuf {
+        &self.storage_path
     }
 }
 
diff --git a/crates/threshold-signature-server/src/helpers/launch.rs b/crates/threshold-signature-server/src/helpers/launch.rs
index a916d38f1..953abe0d3 100644
--- a/crates/threshold-signature-server/src/helpers/launch.rs
+++ b/crates/threshold-signature-server/src/helpers/launch.rs
@@ -20,12 +20,15 @@ use std::path::PathBuf;
 use crate::{
     chain_api::entropy,
     helpers::{substrate::query_chain, validator::get_signer_and_x25519_secret},
-    key_provider::api::{get_key_provider_details, request_recover_encryption_key},
+    key_provider::api::{
+        get_key_provider_details, make_key_backup, request_recover_encryption_key,
+    },
     AppState,
 };
 use clap::Parser;
 use entropy_client::substrate::SubstrateError;
 use entropy_kvdb::kv_manager::{error::KvError, KvManager};
+use rand::RngCore;
 use rand_core::OsRng;
 use serde::Deserialize;
 use sp_core::crypto::Ss58Codec;
@@ -96,7 +99,7 @@ impl Configuration {
 pub async fn setup_kv_store(
     validator_name: &Option<ValidatorName>,
     storage_path: Option<PathBuf>,
-) -> (KvManager, sr25519::Pair, StaticSecret, bool) {
+) -> (KvManager, sr25519::Pair, StaticSecret, Option<[u8; 32]>) {
     let storage_path = storage_path.unwrap_or_else(|| build_db_path(validator_name));
 
     // Check for existing database
@@ -112,7 +115,7 @@ pub async fn setup_kv_store(
         let sr25519_seed: [u8; 32] =
             kv_manager.kv().get(SR25519_SEED).await.unwrap().try_into().unwrap();
         let pair = sr25519::Pair::from_seed(&sr25519_seed);
-        (kv_manager, pair, x25519_secret.into(), false)
+        (kv_manager, pair, x25519_secret.into(), None)
     } else {
         // Generate TSS account (or use ValidatorName)
         let (pair, seed, x25519_secret) = if cfg!(test) || validator_name.is_some() {
@@ -123,7 +126,9 @@ pub async fn setup_kv_store(
             (pair, seed, x25519_secret)
         };
         // TODO randomly generate key
-        let encryption_key = [0; 32];
+        let mut encryption_key = [0; 32];
+        OsRng.fill_bytes(&mut encryption_key);
+
         // open store with generated key
         let kv_manager = KvManager::new(storage_path, encryption_key).unwrap();
         // store TSS keys in kv store
@@ -148,7 +153,7 @@ pub async fn setup_kv_store(
             .put(reservation, seed.to_vec())
             .await
             .expect("failed to store sr25519 seed");
-        (kv_manager, pair, x25519_secret, true)
+        (kv_manager, pair, x25519_secret, Some(encryption_key))
     }
 }
 
@@ -302,7 +307,10 @@ pub async fn setup_latest_block_number(kv: &KvManager) -> Result<(), KvError> {
     Ok(())
 }
 
-pub async fn check_node_prerequisites(app_state: AppState) -> Result<(), &'static str> {
+pub async fn check_node_prerequisites(
+    app_state: AppState,
+    key_to_backup: Option<[u8; 32]>,
+) -> Result<(), &'static str> {
     use crate::chain_api::{get_api, get_rpc};
     let url = &app_state.configuration.endpoint;
     let account_id = app_state.account_id();
@@ -379,6 +387,20 @@ pub async fn check_node_prerequisites(app_state: AppState) -> Result<(), &'stati
     backoff::future::retry(backoff, check_for_tss_account_id)
         .await
         .map_err(|_| "Timed out waiting for TSS account to be registered on chain")?;
+
+    if let Some(key_to_backup) = key_to_backup {
+        tracing::info!("Backing up keyshare...");
+        make_key_backup(
+            &api,
+            &rpc,
+            key_to_backup,
+            app_state.subxt_account_id(),
+            app_state.kv_store.storage_path().to_path_buf(),
+        )
+        .await;
+        tracing::info!("Successfully backed up keyshare");
+    }
+
     tracing::info!("TSS node passed all prerequisite checks and is ready");
     app_state.make_ready();
     Ok(())
diff --git a/crates/threshold-signature-server/src/main.rs b/crates/threshold-signature-server/src/main.rs
index 0279678a3..896c1b18f 100644
--- a/crates/threshold-signature-server/src/main.rs
+++ b/crates/threshold-signature-server/src/main.rs
@@ -57,7 +57,7 @@ async fn main() {
         validator_name = Some(ValidatorName::Eve);
     }
 
-    let (kv_store, sr25519_pair, x25519_secret, should_backup) =
+    let (kv_store, sr25519_pair, x25519_secret, key_option) =
         setup_kv_store(&validator_name, None).await;
 
     let app_state =
@@ -70,7 +70,9 @@ async fn main() {
         tokio::spawn(async move {
             // Check for a connection to the chain node parallel to starting the tss_server so that
             // we already can expose the `/info` http route
-            if let Err(error) = entropy_tss::launch::check_node_prerequisites(app_state).await {
+            if let Err(error) =
+                entropy_tss::launch::check_node_prerequisites(app_state, key_option).await
+            {
                 tracing::error!("Prerequistite checks failed: {} - terminating.", error);
                 process::exit(1);
             }

From 974cdc896e81ac38c1808e4856e09663d630ae1a Mon Sep 17 00:00:00 2001
From: peg <ameba23@systemli.org>
Date: Fri, 17 Jan 2025 09:52:40 +0100
Subject: [PATCH 43/77] Doccomments

---
 .../src/key_provider/api.rs                   | 54 +++++++++++++------
 1 file changed, 39 insertions(+), 15 deletions(-)

diff --git a/crates/threshold-signature-server/src/key_provider/api.rs b/crates/threshold-signature-server/src/key_provider/api.rs
index 1fac53239..0c48d901d 100644
--- a/crates/threshold-signature-server/src/key_provider/api.rs
+++ b/crates/threshold-signature-server/src/key_provider/api.rs
@@ -39,6 +39,15 @@ pub async fn request_backup_encryption_key(
     let key_request =
         BackupEncryptionKeyRequest { tss_account: key_provider_details.tss_account, quote, key };
 
+    // TODO this should be encrypted
+    // let signed_message = EncryptedSignedMessage::new(
+    //     &pair,
+    //     serde_json::to_vec(&key_request).unwrap(),
+    //     &key_provider_details.provider.x25519_public_key,
+    //     &[],
+    // )
+    // .unwrap();
+
     let client = reqwest::Client::new();
     let response = client
         .post(format!("http://{}/backup_encryption_key", key_provider_details.provider.ip_address))
@@ -93,29 +102,40 @@ pub async fn request_recover_encryption_key(
     Ok(signed_message.message.0.try_into().unwrap())
 }
 
+/// [ValidatorInfo] of a TSS node chosen to make a key backup, together with the account ID of the TSS
+/// node who the backup is for
 #[derive(Debug, Clone, Serialize, Deserialize)]
 pub struct KeyProviderDetails {
     pub provider: ValidatorInfo,
     pub tss_account: SubxtAccountId32,
 }
 
+/// Payload of the encrypted POST request body for the `/backup_encryption_key` HTTP route
 #[derive(Debug, Clone, Serialize, Deserialize)]
 pub struct BackupEncryptionKeyRequest {
     key: [u8; 32],
     tss_account: SubxtAccountId32,
+    // TODO im not sure we need a quote here, as we should have registered with the staking pallet
+    // by the time we make this request, so they can just check that which proves we have done an
+    // attestation on chain
     quote: Vec<u8>,
 }
 
+/// POST request body for thse `/recover_encryption_key` HTTP route
 #[derive(Debug, Clone, Serialize, Deserialize)]
 pub struct RecoverEncryptionKeyRequest {
+    /// The account ID of the TSS node requesting to recover their encryption key
     tss_account: SubxtAccountId32,
+    /// An ephemeral encryption public key used to receive and encrypted response
     response_key: X25519PublicKey,
+    /// A TDX quote
     quote: Vec<u8>,
 }
 
 /// HTTP to backup an encryption key on initial launch
 pub async fn backup_encryption_key(
     State(app_state): State<AppState>,
+    // TODO this should be encrypted
     Json(key_request): Json<BackupEncryptionKeyRequest>,
 ) -> Result<(), KeyProviderError> {
     // Build quote input
@@ -147,6 +167,23 @@ pub async fn recover_encryption_key(
     Ok(Json(signed_message))
 }
 
+/// Create a backup of our key-value store encryption key by sending it to another TSS node to store
+pub async fn make_key_backup(
+    api: &OnlineClient<EntropyConfig>,
+    rpc: &LegacyRpcMethods<EntropyConfig>,
+    key: [u8; 32],
+    tss_account: SubxtAccountId32,
+    storage_path: PathBuf,
+) {
+    // Select a provider by making chain query and choosing a tss node
+    let key_provider_details = select_key_provider(api, rpc, tss_account).await;
+    // Get them to backup the key
+    request_backup_encryption_key(key, key_provider_details.clone()).await.unwrap();
+    // Store provider details so we know who to ask when recovering
+    store_key_provider_details(storage_path, key_provider_details).unwrap();
+}
+
+/// Store the details of a TSS node who has a backup of our encryption key in a file
 fn store_key_provider_details(
     mut path: PathBuf,
     key_provider_details: KeyProviderDetails,
@@ -155,12 +192,14 @@ fn store_key_provider_details(
     std::fs::write(path, serde_json::to_vec(&key_provider_details).unwrap())
 }
 
+/// Retrieve the details of a TSS node who has a backup of our encryption key from a file
 pub fn get_key_provider_details(mut path: PathBuf) -> std::io::Result<KeyProviderDetails> {
     path.push(KEY_PROVIDER_FILENAME);
     let bytes = std::fs::read(path)?;
     Ok(serde_json::from_slice(&bytes).unwrap())
 }
 
+/// Choose a TSS node to request to make a backup from
 async fn select_key_provider(
     api: &OnlineClient<EntropyConfig>,
     rpc: &LegacyRpcMethods<EntropyConfig>,
@@ -187,18 +226,3 @@ async fn select_key_provider(
         tss_account,
     }
 }
-
-pub async fn make_key_backup(
-    api: &OnlineClient<EntropyConfig>,
-    rpc: &LegacyRpcMethods<EntropyConfig>,
-    key: [u8; 32],
-    tss_account: SubxtAccountId32,
-    storage_path: PathBuf,
-) {
-    // Select a provider by making chain query and choosing a tss node
-    let key_provider_details = select_key_provider(api, rpc, tss_account).await;
-    // Get them to backup the key
-    request_backup_encryption_key(key, key_provider_details.clone()).await.unwrap();
-    // Store provider details so we know who to ask when recovering
-    store_key_provider_details(storage_path, key_provider_details).unwrap();
-}

From 9043c95bbc67ca754308fe4fd1bbc82a74b9dfe5 Mon Sep 17 00:00:00 2001
From: peg <ameba23@systemli.org>
Date: Fri, 17 Jan 2025 10:42:45 +0100
Subject: [PATCH 44/77] Store backed-up keys in memory not kvdb

---
 .../threshold-signature-server/src/helpers/launch.rs |  5 +----
 .../src/key_provider/api.rs                          | 12 +++++-------
 .../src/key_provider/errors.rs                       |  4 ++--
 crates/threshold-signature-server/src/lib.rs         |  8 +++++++-
 4 files changed, 15 insertions(+), 14 deletions(-)

diff --git a/crates/threshold-signature-server/src/helpers/launch.rs b/crates/threshold-signature-server/src/helpers/launch.rs
index 953abe0d3..2903051b4 100644
--- a/crates/threshold-signature-server/src/helpers/launch.rs
+++ b/crates/threshold-signature-server/src/helpers/launch.rs
@@ -103,10 +103,7 @@ pub async fn setup_kv_store(
     let storage_path = storage_path.unwrap_or_else(|| build_db_path(validator_name));
 
     // Check for existing database
-    let exists = std::fs::metadata(storage_path.clone()).is_ok();
-    if exists {
-        // Read key provider details
-        let key_provider_details = get_key_provider_details(storage_path.clone()).unwrap();
+    if let Ok(key_provider_details) = get_key_provider_details(storage_path.clone()) {
         // Retrieve encryption key from another TSS node
         let key = request_recover_encryption_key(key_provider_details).await.unwrap();
         let kv_manager = KvManager::new(storage_path, key).unwrap();
diff --git a/crates/threshold-signature-server/src/key_provider/api.rs b/crates/threshold-signature-server/src/key_provider/api.rs
index 0c48d901d..40ada03e8 100644
--- a/crates/threshold-signature-server/src/key_provider/api.rs
+++ b/crates/threshold-signature-server/src/key_provider/api.rs
@@ -140,11 +140,9 @@ pub async fn backup_encryption_key(
 ) -> Result<(), KeyProviderError> {
     // Build quote input
     // Verify quote
-    // Check kvdb for existing key - or generate and store one
-    let lookup_key = format!("BACKUP_KEY:{}", key_request.tss_account);
 
-    let reservation = app_state.kv_store.kv().reserve_key(lookup_key).await?;
-    app_state.kv_store.kv().put(reservation, key_request.key.to_vec()).await?;
+    let mut backups = app_state.encryption_key_backups.write().unwrap();
+    backups.insert(key_request.tss_account.0, key_request.key);
 
     Ok(())
 }
@@ -156,9 +154,9 @@ pub async fn recover_encryption_key(
 ) -> Result<Json<EncryptedSignedMessage>, KeyProviderError> {
     // TODO Build quote input
     // TODO Verify quote
-    let lookup_key = format!("BACKUP_KEY:{}", key_request.tss_account);
-    let existing_key = app_state.kv_store.kv().get(&lookup_key).await?;
-    let key: [u8; 32] = existing_key.try_into().map_err(|_| KeyProviderError::BadKeyLength)?;
+    //
+    let backups = app_state.encryption_key_backups.read().unwrap();
+    let key = backups.get(&key_request.tss_account.0).ok_or(KeyProviderError::NoKeyInStore)?;
 
     // Encrypt response
     let signed_message =
diff --git a/crates/threshold-signature-server/src/key_provider/errors.rs b/crates/threshold-signature-server/src/key_provider/errors.rs
index cbd7e553f..81f1150f5 100644
--- a/crates/threshold-signature-server/src/key_provider/errors.rs
+++ b/crates/threshold-signature-server/src/key_provider/errors.rs
@@ -25,8 +25,8 @@ pub enum KeyProviderError {
     HttpRequest(#[from] reqwest::Error),
     #[error("Key-value store: {0}")]
     Kv(#[from] KvError),
-    #[error("Encryption key must be 32 bytes")]
-    BadKeyLength,
+    #[error("Encryption key is not present in backup store")]
+    NoKeyInStore,
 }
 
 impl IntoResponse for KeyProviderError {
diff --git a/crates/threshold-signature-server/src/lib.rs b/crates/threshold-signature-server/src/lib.rs
index 2e657e3ab..5794993b7 100644
--- a/crates/threshold-signature-server/src/lib.rs
+++ b/crates/threshold-signature-server/src/lib.rs
@@ -178,7 +178,10 @@ use axum::{
 };
 use entropy_kvdb::kv_manager::KvManager;
 use sp_core::{crypto::AccountId32, sr25519, Pair};
-use std::sync::{Arc, RwLock};
+use std::{
+    collections::HashMap,
+    sync::{Arc, RwLock},
+};
 use subxt::{
     backend::legacy::LegacyRpcMethods, tx::PairSigner, utils::AccountId32 as SubxtAccountId32,
     OnlineClient,
@@ -222,6 +225,8 @@ pub struct AppState {
     pub configuration: Configuration,
     /// Key-value store
     pub kv_store: KvManager,
+    /// Storage for encryption key backups for other TSS nodes
+    pub encryption_key_backups: Arc<RwLock<HashMap<[u8; 32], [u8; 32]>>>,
 }
 
 impl AppState {
@@ -239,6 +244,7 @@ impl AppState {
             listener_state: ListenerState::default(),
             configuration,
             kv_store,
+            encryption_key_backups: Default::default(),
         }
     }
 

From b06a721154af4049467881770d91d8a2a37a0e03 Mon Sep 17 00:00:00 2001
From: peg <ameba23@systemli.org>
Date: Fri, 17 Jan 2025 11:17:01 +0100
Subject: [PATCH 45/77] Encrypt keys when sending to be backed up

---
 .../src/helpers/launch.rs                     | 12 ++++---
 .../src/key_provider/api.rs                   | 34 +++++++++++--------
 .../src/key_provider/tests.rs                 | 21 ++++++++++--
 3 files changed, 45 insertions(+), 22 deletions(-)

diff --git a/crates/threshold-signature-server/src/helpers/launch.rs b/crates/threshold-signature-server/src/helpers/launch.rs
index 2903051b4..eae683206 100644
--- a/crates/threshold-signature-server/src/helpers/launch.rs
+++ b/crates/threshold-signature-server/src/helpers/launch.rs
@@ -102,7 +102,7 @@ pub async fn setup_kv_store(
 ) -> (KvManager, sr25519::Pair, StaticSecret, Option<[u8; 32]>) {
     let storage_path = storage_path.unwrap_or_else(|| build_db_path(validator_name));
 
-    // Check for existing database
+    // Check for existing database with backup details
     if let Ok(key_provider_details) = get_key_provider_details(storage_path.clone()) {
         // Retrieve encryption key from another TSS node
         let key = request_recover_encryption_key(key_provider_details).await.unwrap();
@@ -122,13 +122,13 @@ pub async fn setup_kv_store(
             let x25519_secret = StaticSecret::random_from_rng(OsRng);
             (pair, seed, x25519_secret)
         };
-        // TODO randomly generate key
+        // Generate new encryption key
         let mut encryption_key = [0; 32];
         OsRng.fill_bytes(&mut encryption_key);
 
-        // open store with generated key
+        // Open store with generated key
         let kv_manager = KvManager::new(storage_path, encryption_key).unwrap();
-        // store TSS keys in kv store
+        // Store TSS secret keys in kv store
         let reservation = kv_manager
             .kv()
             .reserve_key(X25519_SECRET.to_string())
@@ -150,6 +150,8 @@ pub async fn setup_kv_store(
             .put(reservation, seed.to_vec())
             .await
             .expect("failed to store sr25519 seed");
+
+        // Return the encryption key so that it can be backed up as part of the pre-requisite checks
         (kv_manager, pair, x25519_secret, Some(encryption_key))
     }
 }
@@ -391,7 +393,7 @@ pub async fn check_node_prerequisites(
             &api,
             &rpc,
             key_to_backup,
-            app_state.subxt_account_id(),
+            &app_state.pair,
             app_state.kv_store.storage_path().to_path_buf(),
         )
         .await;
diff --git a/crates/threshold-signature-server/src/key_provider/api.rs b/crates/threshold-signature-server/src/key_provider/api.rs
index 40ada03e8..a7d25b6f6 100644
--- a/crates/threshold-signature-server/src/key_provider/api.rs
+++ b/crates/threshold-signature-server/src/key_provider/api.rs
@@ -23,6 +23,7 @@ use entropy_shared::{user::ValidatorInfo, X25519PublicKey};
 use rand::{rngs::StdRng, Rng, SeedableRng};
 use rand_core::OsRng;
 use serde::{Deserialize, Serialize};
+use sp_core::{sr25519, Pair};
 use std::path::PathBuf;
 use subxt::{backend::legacy::LegacyRpcMethods, OnlineClient};
 use x25519_dalek::{PublicKey, StaticSecret};
@@ -30,29 +31,30 @@ use x25519_dalek::{PublicKey, StaticSecret};
 const KEY_PROVIDER_FILENAME: &str = "key-provider-details.json";
 
 /// Make a request to a given TSS node to backup a given encryption key
+/// This makes a client request to [backup_encryption_key]
 pub async fn request_backup_encryption_key(
     key: [u8; 32],
     key_provider_details: KeyProviderDetails,
+    sr25519_pair: &sr25519::Pair,
 ) -> Result<(), KeyProviderError> {
     let quote = Vec::new(); // TODO
 
     let key_request =
         BackupEncryptionKeyRequest { tss_account: key_provider_details.tss_account, quote, key };
 
-    // TODO this should be encrypted
-    // let signed_message = EncryptedSignedMessage::new(
-    //     &pair,
-    //     serde_json::to_vec(&key_request).unwrap(),
-    //     &key_provider_details.provider.x25519_public_key,
-    //     &[],
-    // )
-    // .unwrap();
+    let signed_message = EncryptedSignedMessage::new(
+        sr25519_pair,
+        serde_json::to_vec(&key_request).unwrap(),
+        &key_provider_details.provider.x25519_public_key,
+        &[],
+    )
+    .unwrap();
 
     let client = reqwest::Client::new();
     let response = client
         .post(format!("http://{}/backup_encryption_key", key_provider_details.provider.ip_address))
         .header("Content-Type", "application/json")
-        .body(serde_json::to_string(&key_request).unwrap())
+        .body(serde_json::to_string(&signed_message).unwrap())
         .send()
         .await?;
 
@@ -135,14 +137,17 @@ pub struct RecoverEncryptionKeyRequest {
 /// HTTP to backup an encryption key on initial launch
 pub async fn backup_encryption_key(
     State(app_state): State<AppState>,
-    // TODO this should be encrypted
-    Json(key_request): Json<BackupEncryptionKeyRequest>,
+    Json(encrypted_backup_request): Json<EncryptedSignedMessage>,
 ) -> Result<(), KeyProviderError> {
     // Build quote input
     // Verify quote
 
+    let signed_message = encrypted_backup_request.decrypt(&app_state.x25519_secret, &[]).unwrap();
+    let backup_request: BackupEncryptionKeyRequest =
+        serde_json::from_slice(&signed_message.message.0).unwrap();
+
     let mut backups = app_state.encryption_key_backups.write().unwrap();
-    backups.insert(key_request.tss_account.0, key_request.key);
+    backups.insert(backup_request.tss_account.0, backup_request.key);
 
     Ok(())
 }
@@ -170,13 +175,14 @@ pub async fn make_key_backup(
     api: &OnlineClient<EntropyConfig>,
     rpc: &LegacyRpcMethods<EntropyConfig>,
     key: [u8; 32],
-    tss_account: SubxtAccountId32,
+    sr25519_pair: &sr25519::Pair,
     storage_path: PathBuf,
 ) {
+    let tss_account = SubxtAccountId32(sr25519_pair.public().0);
     // Select a provider by making chain query and choosing a tss node
     let key_provider_details = select_key_provider(api, rpc, tss_account).await;
     // Get them to backup the key
-    request_backup_encryption_key(key, key_provider_details.clone()).await.unwrap();
+    request_backup_encryption_key(key, key_provider_details.clone(), sr25519_pair).await.unwrap();
     // Store provider details so we know who to ask when recovering
     store_key_provider_details(storage_path, key_provider_details).unwrap();
 }
diff --git a/crates/threshold-signature-server/src/key_provider/tests.rs b/crates/threshold-signature-server/src/key_provider/tests.rs
index 6913dbc6a..6f2bc25a5 100644
--- a/crates/threshold-signature-server/src/key_provider/tests.rs
+++ b/crates/threshold-signature-server/src/key_provider/tests.rs
@@ -14,11 +14,15 @@
 // along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
 use crate::{
-    helpers::tests::{initialize_test_logger, setup_client},
+    helpers::{
+        tests::{initialize_test_logger, setup_client},
+        validator::get_signer_and_x25519_secret_from_mnemonic,
+    },
     key_provider::api::{
         make_key_backup, request_backup_encryption_key, request_recover_encryption_key,
         KeyProviderDetails,
     },
+    launch::{development_mnemonic, ValidatorName},
     SubxtAccountId32,
 };
 use entropy_kvdb::clean_tests;
@@ -43,7 +47,12 @@ async fn key_provider_test() {
     let storage_path = ".entropy/testing/test_db_validator1".into();
     let key = [0; 32]; // TODO this should be the actual key used. Since we dont have access to
                        // kvmanager, alice bob etc. should use known keys
-    make_key_backup(&api, &rpc, key, TSS_ACCOUNTS[0].clone(), storage_path).await;
+
+    let mnemonic = development_mnemonic(&Some(ValidatorName::Alice));
+    let (tss_signer, _static_secret) =
+        get_signer_and_x25519_secret_from_mnemonic(&mnemonic.to_string()).unwrap();
+
+    make_key_backup(&api, &rpc, key, tss_signer.signer(), storage_path).await;
 }
 
 #[tokio::test]
@@ -62,7 +71,13 @@ async fn key_provider_unit_test() {
     };
     let key = [1; 32];
 
-    request_backup_encryption_key(key, key_provider_details.clone()).await.unwrap();
+    let mnemonic = development_mnemonic(&Some(ValidatorName::Bob));
+    let (tss_signer, _static_secret) =
+        get_signer_and_x25519_secret_from_mnemonic(&mnemonic.to_string()).unwrap();
+
+    request_backup_encryption_key(key, key_provider_details.clone(), tss_signer.signer())
+        .await
+        .unwrap();
     let recovered_key = request_recover_encryption_key(key_provider_details).await.unwrap();
     assert_eq!(key, recovered_key);
 }

From 39f3f68506dc8280c33e95504732bbc5afcf498a Mon Sep 17 00:00:00 2001
From: peg <ameba23@systemli.org>
Date: Fri, 17 Jan 2025 13:03:47 +0100
Subject: [PATCH 46/77] Error handling, quote handling

---
 crates/shared/src/types.rs                    |   5 +
 .../src/attestation/api.rs                    |  21 +--
 .../src/helpers/launch.rs                     |   6 +-
 .../src/key_provider/api.rs                   | 135 +++++++++++-------
 .../src/key_provider/errors.rs                |  22 +++
 .../src/key_provider/tests.rs                 |   2 +-
 6 files changed, 128 insertions(+), 63 deletions(-)

diff --git a/crates/shared/src/types.rs b/crates/shared/src/types.rs
index 7bada801c..3ff4ddf78 100644
--- a/crates/shared/src/types.rs
+++ b/crates/shared/src/types.rs
@@ -144,6 +144,8 @@ pub enum QuoteContext {
     ChangeEndpoint,
     /// To be used in the `change_threshold_accounts` extrinsic
     ChangeThresholdAccounts,
+    /// To be use when requesting to recover an encryption key
+    EncryptionKeyRecoveryRequest,
 }
 
 #[cfg(feature = "std")]
@@ -154,6 +156,9 @@ impl std::fmt::Display for QuoteContext {
             QuoteContext::Validate => write!(f, "validate"),
             QuoteContext::ChangeEndpoint => write!(f, "change_endpoint"),
             QuoteContext::ChangeThresholdAccounts => write!(f, "change_threshold_accounts"),
+            QuoteContext::EncryptionKeyRecoveryRequest => {
+                write!(f, "encryption_key_recovery_request")
+            },
         }
     }
 }
diff --git a/crates/threshold-signature-server/src/attestation/api.rs b/crates/threshold-signature-server/src/attestation/api.rs
index 543da1d3a..7ffbe2735 100644
--- a/crates/threshold-signature-server/src/attestation/api.rs
+++ b/crates/threshold-signature-server/src/attestation/api.rs
@@ -15,12 +15,12 @@
 
 use crate::{
     attestation::errors::AttestationErr,
-    chain_api::{entropy, get_api, get_rpc, EntropyConfig},
+    chain_api::{entropy, get_api, get_rpc},
     helpers::{
         launch::LATEST_BLOCK_NUMBER_ATTEST,
         substrate::{query_chain, submit_transaction},
     },
-    AppState,
+    AppState, SubxtAccountId32,
 };
 use axum::{
     body::Bytes,
@@ -32,7 +32,6 @@ use entropy_kvdb::kv_manager::KvManager;
 use entropy_shared::{OcwMessageAttestationRequest, QuoteContext};
 use parity_scale_codec::Decode;
 use serde::Deserialize;
-use subxt::tx::PairSigner;
 use x25519_dalek::StaticSecret;
 
 /// HTTP POST endpoint to initiate a TDX attestation.
@@ -73,7 +72,9 @@ pub async fn attest(
     // TODO (#1181): since this endpoint is currently only used in tests we don't know what the context should be
     let context = QuoteContext::Validate;
 
-    let quote = create_quote(nonce, &app_state.signer(), &app_state.x25519_secret, context).await?;
+    let quote =
+        create_quote(nonce, app_state.subxt_account_id(), &app_state.x25519_secret, context)
+            .await?;
 
     // Submit the quote
     let attest_tx = entropy::tx().attestation().attest(quote.clone());
@@ -99,7 +100,9 @@ pub async fn get_attest(
 
     let context = context_querystring.as_quote_context()?;
 
-    let quote = create_quote(nonce, &app_state.signer(), &app_state.x25519_secret, context).await?;
+    let quote =
+        create_quote(nonce, app_state.subxt_account_id(), &app_state.x25519_secret, context)
+            .await?;
 
     Ok((StatusCode::OK, quote))
 }
@@ -108,13 +111,12 @@ pub async fn get_attest(
 #[cfg(not(feature = "production"))]
 pub async fn create_quote(
     nonce: [u8; 32],
-    signer: &PairSigner<EntropyConfig, sp_core::sr25519::Pair>,
+    tss_account: SubxtAccountId32,
     x25519_secret: &StaticSecret,
     context: QuoteContext,
 ) -> Result<Vec<u8>, AttestationErr> {
     use rand::{rngs::StdRng, SeedableRng};
     use rand_core::OsRng;
-    use sp_core::Pair;
 
     // In the real thing this is the key used in the quoting enclave
     let signing_key = tdx_quote::SigningKey::random(&mut OsRng);
@@ -122,14 +124,14 @@ pub async fn create_quote(
     let public_key = x25519_dalek::PublicKey::from(x25519_secret);
 
     let input_data = entropy_shared::QuoteInputData::new(
-        signer.signer().public(),
+        tss_account.clone(),
         *public_key.as_bytes(),
         nonce,
         context,
     );
 
     // This is generated deterministically from TSS account id
-    let mut pck_seeder = StdRng::from_seed(signer.signer().public().0);
+    let mut pck_seeder = StdRng::from_seed(tss_account.0);
     let pck = tdx_quote::SigningKey::random(&mut pck_seeder);
 
     let pck_encoded = tdx_quote::encode_verifying_key(pck.verifying_key())?.to_vec();
@@ -172,6 +174,7 @@ pub async fn validate_new_attestation(
 #[cfg(feature = "production")]
 pub async fn create_quote(
     nonce: [u8; 32],
+    // TODO change this to SubxtAccountId32
     signer: &PairSigner<EntropyConfig, sp_core::sr25519::Pair>,
     x25519_secret: &StaticSecret,
     context: QuoteContext,
diff --git a/crates/threshold-signature-server/src/helpers/launch.rs b/crates/threshold-signature-server/src/helpers/launch.rs
index eae683206..64bab2529 100644
--- a/crates/threshold-signature-server/src/helpers/launch.rs
+++ b/crates/threshold-signature-server/src/helpers/launch.rs
@@ -396,7 +396,11 @@ pub async fn check_node_prerequisites(
             &app_state.pair,
             app_state.kv_store.storage_path().to_path_buf(),
         )
-        .await;
+        .await
+        .map_err(|e| {
+            tracing::error!("Could not make key backup: {}", e);
+            "Could not make key backup"
+        })?;
         tracing::info!("Successfully backed up keyshare");
     }
 
diff --git a/crates/threshold-signature-server/src/key_provider/api.rs b/crates/threshold-signature-server/src/key_provider/api.rs
index a7d25b6f6..4e94e4870 100644
--- a/crates/threshold-signature-server/src/key_provider/api.rs
+++ b/crates/threshold-signature-server/src/key_provider/api.rs
@@ -14,12 +14,12 @@
 // along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
 use crate::{
-    chain_api::entropy, key_provider::errors::KeyProviderError, validation::EncryptedSignedMessage,
-    AppState, EntropyConfig, SubxtAccountId32,
+    attestation::api::create_quote, chain_api::entropy, key_provider::errors::KeyProviderError,
+    validation::EncryptedSignedMessage, AppState, EntropyConfig, SubxtAccountId32,
 };
 use axum::{extract::State, Json};
 use entropy_client::substrate::query_chain;
-use entropy_shared::{user::ValidatorInfo, X25519PublicKey};
+use entropy_shared::{user::ValidatorInfo, QuoteContext, QuoteInputData, X25519PublicKey};
 use rand::{rngs::StdRng, Rng, SeedableRng};
 use rand_core::OsRng;
 use serde::{Deserialize, Serialize};
@@ -37,31 +37,28 @@ pub async fn request_backup_encryption_key(
     key_provider_details: KeyProviderDetails,
     sr25519_pair: &sr25519::Pair,
 ) -> Result<(), KeyProviderError> {
-    let quote = Vec::new(); // TODO
-
     let key_request =
-        BackupEncryptionKeyRequest { tss_account: key_provider_details.tss_account, quote, key };
+        BackupEncryptionKeyRequest { tss_account: key_provider_details.tss_account, key };
 
     let signed_message = EncryptedSignedMessage::new(
         sr25519_pair,
-        serde_json::to_vec(&key_request).unwrap(),
+        serde_json::to_vec(&key_request)?,
         &key_provider_details.provider.x25519_public_key,
         &[],
-    )
-    .unwrap();
+    )?;
 
     let client = reqwest::Client::new();
     let response = client
         .post(format!("http://{}/backup_encryption_key", key_provider_details.provider.ip_address))
         .header("Content-Type", "application/json")
-        .body(serde_json::to_string(&signed_message).unwrap())
+        .body(serde_json::to_string(&signed_message)?)
         .send()
         .await?;
 
     let status = response.status();
     if status != reqwest::StatusCode::OK {
-        let text = response.text().await.unwrap();
-        panic!("Bad status code {}: {}", status, text);
+        let text = response.text().await?;
+        return Err(KeyProviderError::BadProviderResponse(status, text));
     }
     Ok(())
 }
@@ -70,12 +67,23 @@ pub async fn request_backup_encryption_key(
 pub async fn request_recover_encryption_key(
     key_provider_details: KeyProviderDetails,
 ) -> Result<[u8; 32], KeyProviderError> {
-    let quote = Vec::new(); // TODO
-
     // Generate encryption keypair used for receiving the key
     let response_secret_key = StaticSecret::random_from_rng(OsRng);
     let response_key = PublicKey::from(&response_secret_key).to_bytes();
 
+    // TODO This is tricky as having to request a nonce means we need 2 request-responses to recover the
+    // key
+    let quote_nonce = [0; 32];
+
+    // Quote input should contain: key_provider_details.tss_account, and response_key
+    let quote = create_quote(
+        quote_nonce,
+        key_provider_details.tss_account.clone(),
+        &response_secret_key,
+        QuoteContext::EncryptionKeyRecoveryRequest,
+    )
+    .await?;
+
     let key_request = RecoverEncryptionKeyRequest {
         tss_account: key_provider_details.tss_account,
         response_key,
@@ -86,22 +94,22 @@ pub async fn request_recover_encryption_key(
     let response = client
         .post(format!("http://{}/recover_encryption_key", key_provider_details.provider.ip_address))
         .header("Content-Type", "application/json")
-        .body(serde_json::to_string(&key_request).unwrap())
+        .body(serde_json::to_string(&key_request)?)
         .send()
         .await?;
 
     let status = response.status();
     if status != reqwest::StatusCode::OK {
-        let text = response.text().await.unwrap();
-        panic!("Bad status code {}: {}", status, text);
+        let text = response.text().await?;
+        return Err(KeyProviderError::BadProviderResponse(status, text));
     }
+
     let response_bytes = response.bytes().await?;
 
-    let encrypted_response: EncryptedSignedMessage =
-        serde_json::from_slice(&response_bytes).unwrap();
-    let signed_message = encrypted_response.decrypt(&response_secret_key, &[]).unwrap();
+    let encrypted_response: EncryptedSignedMessage = serde_json::from_slice(&response_bytes)?;
+    let signed_message = encrypted_response.decrypt(&response_secret_key, &[])?;
 
-    Ok(signed_message.message.0.try_into().unwrap())
+    signed_message.message.0.try_into().map_err(|_| KeyProviderError::BadKeyLength)
 }
 
 /// [ValidatorInfo] of a TSS node chosen to make a key backup, together with the account ID of the TSS
@@ -115,12 +123,10 @@ pub struct KeyProviderDetails {
 /// Payload of the encrypted POST request body for the `/backup_encryption_key` HTTP route
 #[derive(Debug, Clone, Serialize, Deserialize)]
 pub struct BackupEncryptionKeyRequest {
+    /// The encryption key to be backed-up
     key: [u8; 32],
+    /// The account ID of the TSS node for whom the backup should be made
     tss_account: SubxtAccountId32,
-    // TODO im not sure we need a quote here, as we should have registered with the staking pallet
-    // by the time we make this request, so they can just check that which proves we have done an
-    // attestation on chain
-    quote: Vec<u8>,
 }
 
 /// POST request body for thse `/recover_encryption_key` HTTP route
@@ -139,14 +145,20 @@ pub async fn backup_encryption_key(
     State(app_state): State<AppState>,
     Json(encrypted_backup_request): Json<EncryptedSignedMessage>,
 ) -> Result<(), KeyProviderError> {
-    // Build quote input
-    // Verify quote
-
-    let signed_message = encrypted_backup_request.decrypt(&app_state.x25519_secret, &[]).unwrap();
+    let signed_message = encrypted_backup_request.decrypt(&app_state.x25519_secret, &[])?;
     let backup_request: BackupEncryptionKeyRequest =
-        serde_json::from_slice(&signed_message.message.0).unwrap();
+        serde_json::from_slice(&signed_message.message.0)?;
 
-    let mut backups = app_state.encryption_key_backups.write().unwrap();
+    // Check for tss account on the staking pallet - which proves they have made an on-chain attestation
+    let threshold_address_query =
+        entropy::storage().staking_extension().threshold_to_stash(&backup_request.tss_account);
+    let (api, rpc) = app_state.get_api_rpc().await?;
+    query_chain(&api, &rpc, threshold_address_query, None)
+        .await?
+        .ok_or(KeyProviderError::NotRegisteredWithStakingPallet)?;
+
+    let mut backups =
+        app_state.encryption_key_backups.write().map_err(|_| KeyProviderError::RwLockPoison)?;
     backups.insert(backup_request.tss_account.0, backup_request.key);
 
     Ok(())
@@ -157,16 +169,35 @@ pub async fn recover_encryption_key(
     State(app_state): State<AppState>,
     Json(key_request): Json<RecoverEncryptionKeyRequest>,
 ) -> Result<Json<EncryptedSignedMessage>, KeyProviderError> {
-    // TODO Build quote input
-    // TODO Verify quote
+    // TODO verify quote - and move verifying quote logic to the attestation module
+    // let quote = Quote::from_bytes(&quote).map_err(|_| VerifyQuoteError::BadQuote)?;
+
+    let nonce = [0; 32]; // TODO
+    let _expected_input_data = QuoteInputData::new(
+        key_request.tss_account.clone(),
+        key_request.response_key,
+        nonce,
+        QuoteContext::EncryptionKeyRecoveryRequest,
+    );
+    // if quote.report_input_data() != expected_input_data.0 {
+    //     return Err(KeyProviderError::BadQuoteInputData);
+    // }
+
+    // Check build-time measurement matches a current-supported release of entropy-tss
+    // let mrtd_value =
+    //     BoundedVec::try_from(quote.mrtd().to_vec()).map_err(|_| VerifyQuoteError::BadMrtdValue)?;
+    // let accepted_mrtd_values = pallet_parameters::Pallet::<T>::accepted_mrtd_values();
+    // ensure!(accepted_mrtd_values.contains(&mrtd_value), VerifyQuoteError::BadMrtdValue);
     //
-    let backups = app_state.encryption_key_backups.read().unwrap();
+    // let pck = verify_pck_certificate_chain(&quote)?;
+
+    let backups =
+        app_state.encryption_key_backups.read().map_err(|_| KeyProviderError::RwLockPoison)?;
     let key = backups.get(&key_request.tss_account.0).ok_or(KeyProviderError::NoKeyInStore)?;
 
     // Encrypt response
     let signed_message =
-        EncryptedSignedMessage::new(&app_state.pair, key.to_vec(), &key_request.response_key, &[])
-            .unwrap();
+        EncryptedSignedMessage::new(&app_state.pair, key.to_vec(), &key_request.response_key, &[])?;
     Ok(Json(signed_message))
 }
 
@@ -177,30 +208,31 @@ pub async fn make_key_backup(
     key: [u8; 32],
     sr25519_pair: &sr25519::Pair,
     storage_path: PathBuf,
-) {
+) -> Result<(), KeyProviderError> {
     let tss_account = SubxtAccountId32(sr25519_pair.public().0);
     // Select a provider by making chain query and choosing a tss node
-    let key_provider_details = select_key_provider(api, rpc, tss_account).await;
+    let key_provider_details = select_key_provider(api, rpc, tss_account).await?;
     // Get them to backup the key
-    request_backup_encryption_key(key, key_provider_details.clone(), sr25519_pair).await.unwrap();
+    request_backup_encryption_key(key, key_provider_details.clone(), sr25519_pair).await?;
     // Store provider details so we know who to ask when recovering
-    store_key_provider_details(storage_path, key_provider_details).unwrap();
+    store_key_provider_details(storage_path, key_provider_details)?;
+    Ok(())
 }
 
 /// Store the details of a TSS node who has a backup of our encryption key in a file
 fn store_key_provider_details(
     mut path: PathBuf,
     key_provider_details: KeyProviderDetails,
-) -> std::io::Result<()> {
+) -> Result<(), KeyProviderError> {
     path.push(KEY_PROVIDER_FILENAME);
-    std::fs::write(path, serde_json::to_vec(&key_provider_details).unwrap())
+    Ok(std::fs::write(path, serde_json::to_vec(&key_provider_details)?)?)
 }
 
 /// Retrieve the details of a TSS node who has a backup of our encryption key from a file
-pub fn get_key_provider_details(mut path: PathBuf) -> std::io::Result<KeyProviderDetails> {
+pub fn get_key_provider_details(mut path: PathBuf) -> Result<KeyProviderDetails, KeyProviderError> {
     path.push(KEY_PROVIDER_FILENAME);
     let bytes = std::fs::read(path)?;
-    Ok(serde_json::from_slice(&bytes).unwrap())
+    Ok(serde_json::from_slice(&bytes)?)
 }
 
 /// Choose a TSS node to request to make a backup from
@@ -208,10 +240,9 @@ async fn select_key_provider(
     api: &OnlineClient<EntropyConfig>,
     rpc: &LegacyRpcMethods<EntropyConfig>,
     tss_account: SubxtAccountId32,
-) -> KeyProviderDetails {
+) -> Result<KeyProviderDetails, KeyProviderError> {
     let validators_query = entropy::storage().session().validators();
-    let validators = query_chain(api, rpc, validators_query, None).await.unwrap().unwrap();
-    // .ok_or_else(|| SubgroupGetError::ChainFetch("Error getting validators"))?;
+    let validators = query_chain(api, rpc, validators_query, None).await?.unwrap();
 
     let mut deterministic_rng = StdRng::from_seed(tss_account.0);
     let random_index = deterministic_rng.gen_range(0..validators.len());
@@ -219,14 +250,14 @@ async fn select_key_provider(
 
     let threshold_address_query =
         entropy::storage().staking_extension().threshold_servers(validator);
-    let server_info = query_chain(api, rpc, threshold_address_query, None).await.unwrap().unwrap();
-    // .ok_or_else(|| SubgroupGetError::ChainFetch("threshold_servers query error"))?;
-    KeyProviderDetails {
+    let server_info = query_chain(api, rpc, threshold_address_query, None).await?.unwrap();
+
+    Ok(KeyProviderDetails {
         provider: ValidatorInfo {
             x25519_public_key: server_info.x25519_public_key,
-            ip_address: std::str::from_utf8(&server_info.endpoint).unwrap().to_string(),
+            ip_address: std::str::from_utf8(&server_info.endpoint)?.to_string(),
             tss_account: server_info.tss_account,
         },
         tss_account,
-    }
+    })
 }
diff --git a/crates/threshold-signature-server/src/key_provider/errors.rs b/crates/threshold-signature-server/src/key_provider/errors.rs
index 81f1150f5..71eb3c836 100644
--- a/crates/threshold-signature-server/src/key_provider/errors.rs
+++ b/crates/threshold-signature-server/src/key_provider/errors.rs
@@ -27,6 +27,28 @@ pub enum KeyProviderError {
     Kv(#[from] KvError),
     #[error("Encryption key is not present in backup store")]
     NoKeyInStore,
+    #[error("Panic while holding lock on backup store")]
+    RwLockPoison,
+    #[error("JSON: {0}")]
+    SerdeJson(#[from] serde_json::Error),
+    #[error("Encryption: {0}")]
+    Encryption(#[from] crate::validation::EncryptedSignedMessageErr),
+    #[error("Attestation: {0}")]
+    Attestation(#[from] crate::attestation::errors::AttestationErr),
+    #[error("Generic Substrate error: {0}")]
+    GenericSubstrate(#[from] subxt::error::Error),
+    #[error("Bad response from backup provider: {0} {1}")]
+    BadProviderResponse(reqwest::StatusCode, String),
+    #[error("Provider responded with a key which is not 32 bytes")]
+    BadKeyLength,
+    #[error("Substrate: {0}")]
+    SubstrateClient(#[from] entropy_client::substrate::SubstrateError),
+    #[error("The account requesting to recover a key is not registered with the staking pallet")]
+    NotRegisteredWithStakingPallet,
+    #[error("Filesystem IO: {0}")]
+    Io(#[from] std::io::Error),
+    #[error("Utf8Error: {0:?}")]
+    Utf8(#[from] std::str::Utf8Error),
 }
 
 impl IntoResponse for KeyProviderError {
diff --git a/crates/threshold-signature-server/src/key_provider/tests.rs b/crates/threshold-signature-server/src/key_provider/tests.rs
index 6f2bc25a5..82e9a32ee 100644
--- a/crates/threshold-signature-server/src/key_provider/tests.rs
+++ b/crates/threshold-signature-server/src/key_provider/tests.rs
@@ -52,7 +52,7 @@ async fn key_provider_test() {
     let (tss_signer, _static_secret) =
         get_signer_and_x25519_secret_from_mnemonic(&mnemonic.to_string()).unwrap();
 
-    make_key_backup(&api, &rpc, key, tss_signer.signer(), storage_path).await;
+    make_key_backup(&api, &rpc, key, tss_signer.signer(), storage_path).await.unwrap();
 }
 
 #[tokio::test]

From 7a6b4b7c516f1b1a92546f06c70cfbfa7bfceb79 Mon Sep 17 00:00:00 2001
From: peg <ameba23@systemli.org>
Date: Fri, 17 Jan 2025 14:53:28 +0100
Subject: [PATCH 47/77] Fix test

---
 .../threshold-signature-server/src/key_provider/tests.rs   | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/crates/threshold-signature-server/src/key_provider/tests.rs b/crates/threshold-signature-server/src/key_provider/tests.rs
index 82e9a32ee..5948c250b 100644
--- a/crates/threshold-signature-server/src/key_provider/tests.rs
+++ b/crates/threshold-signature-server/src/key_provider/tests.rs
@@ -60,14 +60,17 @@ async fn key_provider_test() {
 async fn key_provider_unit_test() {
     clean_tests();
     initialize_test_logger().await;
-    setup_client().await;
+
+    let (_ctx, _api, _rpc, _validator_ips, _validator_ids) =
+        spawn_tss_nodes_and_start_chain(ChainSpecType::IntegrationJumpStarted).await;
+
     let key_provider_details = KeyProviderDetails {
         provider: ValidatorInfo {
             tss_account: TSS_ACCOUNTS[0].clone(),
             x25519_public_key: X25519_PUBLIC_KEYS[0],
             ip_address: "127.0.0.1:3001".to_string(),
         },
-        tss_account: SubxtAccountId32(AccountKeyring::Bob.to_raw_public()),
+        tss_account: TSS_ACCOUNTS[1].clone(),
     };
     let key = [1; 32];
 

From 88ac607129b9ba8441861105ce5557eca79d9ee1 Mon Sep 17 00:00:00 2001
From: peg <ameba23@systemli.org>
Date: Mon, 20 Jan 2025 10:13:01 +0100
Subject: [PATCH 48/77] Use tss_account id from signed message, not one
 provided by user

---
 .../src/key_provider/api.rs                   | 25 ++++++-------------
 .../src/key_provider/tests.rs                 |  5 +---
 2 files changed, 8 insertions(+), 22 deletions(-)

diff --git a/crates/threshold-signature-server/src/key_provider/api.rs b/crates/threshold-signature-server/src/key_provider/api.rs
index 4e94e4870..6ec42aa24 100644
--- a/crates/threshold-signature-server/src/key_provider/api.rs
+++ b/crates/threshold-signature-server/src/key_provider/api.rs
@@ -37,12 +37,9 @@ pub async fn request_backup_encryption_key(
     key_provider_details: KeyProviderDetails,
     sr25519_pair: &sr25519::Pair,
 ) -> Result<(), KeyProviderError> {
-    let key_request =
-        BackupEncryptionKeyRequest { tss_account: key_provider_details.tss_account, key };
-
     let signed_message = EncryptedSignedMessage::new(
         sr25519_pair,
-        serde_json::to_vec(&key_request)?,
+        key.to_vec(),
         &key_provider_details.provider.x25519_public_key,
         &[],
     )?;
@@ -120,15 +117,6 @@ pub struct KeyProviderDetails {
     pub tss_account: SubxtAccountId32,
 }
 
-/// Payload of the encrypted POST request body for the `/backup_encryption_key` HTTP route
-#[derive(Debug, Clone, Serialize, Deserialize)]
-pub struct BackupEncryptionKeyRequest {
-    /// The encryption key to be backed-up
-    key: [u8; 32],
-    /// The account ID of the TSS node for whom the backup should be made
-    tss_account: SubxtAccountId32,
-}
-
 /// POST request body for thse `/recover_encryption_key` HTTP route
 #[derive(Debug, Clone, Serialize, Deserialize)]
 pub struct RecoverEncryptionKeyRequest {
@@ -140,18 +128,19 @@ pub struct RecoverEncryptionKeyRequest {
     quote: Vec<u8>,
 }
 
-/// HTTP to backup an encryption key on initial launch
+/// HTTP endpoint to backup an encryption key on initial launch
 pub async fn backup_encryption_key(
     State(app_state): State<AppState>,
     Json(encrypted_backup_request): Json<EncryptedSignedMessage>,
 ) -> Result<(), KeyProviderError> {
     let signed_message = encrypted_backup_request.decrypt(&app_state.x25519_secret, &[])?;
-    let backup_request: BackupEncryptionKeyRequest =
-        serde_json::from_slice(&signed_message.message.0)?;
+    let key: [u8; 32] =
+        signed_message.message.0.try_into().map_err(|_| KeyProviderError::BadKeyLength)?;
 
+    let tss_account = SubxtAccountId32(signed_message.sender.0);
     // Check for tss account on the staking pallet - which proves they have made an on-chain attestation
     let threshold_address_query =
-        entropy::storage().staking_extension().threshold_to_stash(&backup_request.tss_account);
+        entropy::storage().staking_extension().threshold_to_stash(&tss_account);
     let (api, rpc) = app_state.get_api_rpc().await?;
     query_chain(&api, &rpc, threshold_address_query, None)
         .await?
@@ -159,7 +148,7 @@ pub async fn backup_encryption_key(
 
     let mut backups =
         app_state.encryption_key_backups.write().map_err(|_| KeyProviderError::RwLockPoison)?;
-    backups.insert(backup_request.tss_account.0, backup_request.key);
+    backups.insert(tss_account.0, key);
 
     Ok(())
 }
diff --git a/crates/threshold-signature-server/src/key_provider/tests.rs b/crates/threshold-signature-server/src/key_provider/tests.rs
index 5948c250b..59008a285 100644
--- a/crates/threshold-signature-server/src/key_provider/tests.rs
+++ b/crates/threshold-signature-server/src/key_provider/tests.rs
@@ -15,15 +15,13 @@
 
 use crate::{
     helpers::{
-        tests::{initialize_test_logger, setup_client},
-        validator::get_signer_and_x25519_secret_from_mnemonic,
+        tests::initialize_test_logger, validator::get_signer_and_x25519_secret_from_mnemonic,
     },
     key_provider::api::{
         make_key_backup, request_backup_encryption_key, request_recover_encryption_key,
         KeyProviderDetails,
     },
     launch::{development_mnemonic, ValidatorName},
-    SubxtAccountId32,
 };
 use entropy_kvdb::clean_tests;
 use entropy_shared::user::ValidatorInfo;
@@ -33,7 +31,6 @@ use entropy_testing_utils::{
     ChainSpecType,
 };
 use serial_test::serial;
-use sp_keyring::AccountKeyring;
 
 #[tokio::test]
 #[serial]

From e2558a850f2a74ca7c806244ed78efa9725af0ba Mon Sep 17 00:00:00 2001
From: peg <ameba23@systemli.org>
Date: Mon, 20 Jan 2025 11:01:22 +0100
Subject: [PATCH 49/77] Copy quote validation logic into entropy-tss and verify
 quotes when requesting recover backup

---
 crates/shared/src/types.rs                    | 37 +++++++++++++++++++
 .../src/attestation/api.rs                    | 31 +++++++++++++++-
 .../src/key_provider/api.rs                   | 22 ++++++-----
 .../src/key_provider/errors.rs                |  6 +++
 4 files changed, 86 insertions(+), 10 deletions(-)

diff --git a/crates/shared/src/types.rs b/crates/shared/src/types.rs
index 3ff4ddf78..c04ebf389 100644
--- a/crates/shared/src/types.rs
+++ b/crates/shared/src/types.rs
@@ -223,3 +223,40 @@ pub enum VerifyQuoteError {
     /// Pck certificate could not be extracted from quote
     PckCertificateNoCertificate,
 }
+
+#[cfg(feature = "std")]
+impl std::fmt::Display for VerifyQuoteError {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self {
+            VerifyQuoteError::BadQuote => write!(f, "Quote could not be parsed of verified"),
+            VerifyQuoteError::UnexpectedAttestation => {
+                write!(f, "Attestation extrinsic submitted when not requested")
+            },
+            VerifyQuoteError::IncorrectInputData => {
+                write!(f, "Hashed input data does not match what was expected")
+            },
+            VerifyQuoteError::BadMrtdValue => write!(f, "Unacceptable VM image running"),
+            VerifyQuoteError::CannotEncodeVerifyingKey => {
+                write!(f, "Cannot encode verifying key (PCK)")
+            },
+            VerifyQuoteError::CannotDecodeVerifyingKey => {
+                write!(f, "Cannot decode verifying key (PCK)")
+            },
+            VerifyQuoteError::PckCertificateParse => {
+                write!(f, "PCK certificate chain cannot be parsed")
+            },
+            VerifyQuoteError::PckCertificateVerify => {
+                write!(f, "PCK certificate chain cannot be verified")
+            },
+            VerifyQuoteError::PckCertificateBadPublicKey => {
+                write!(f, "PCK certificate chain public key is not well formed")
+            },
+            VerifyQuoteError::PckCertificateNoCertificate => {
+                write!(f, "PCK certificate could not be extracted from quote")
+            },
+        }
+    }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for VerifyQuoteError {}
diff --git a/crates/threshold-signature-server/src/attestation/api.rs b/crates/threshold-signature-server/src/attestation/api.rs
index 7ffbe2735..f2c96ac55 100644
--- a/crates/threshold-signature-server/src/attestation/api.rs
+++ b/crates/threshold-signature-server/src/attestation/api.rs
@@ -29,9 +29,10 @@ use axum::{
 };
 use entropy_client::user::request_attestation;
 use entropy_kvdb::kv_manager::KvManager;
-use entropy_shared::{OcwMessageAttestationRequest, QuoteContext};
+use entropy_shared::{OcwMessageAttestationRequest, QuoteContext, VerifyQuoteError};
 use parity_scale_codec::Decode;
 use serde::Deserialize;
+use tdx_quote::{Quote, VerifyingKey};
 use x25519_dalek::StaticSecret;
 
 /// HTTP POST endpoint to initiate a TDX attestation.
@@ -212,3 +213,31 @@ impl QuoteContextQuery {
         }
     }
 }
+
+// TODO these functions are duplicated in the attestation pallet, maybe move somewhere common eg:
+// entropy-shared
+/// Verify a PCK certificate chain from a quote in production
+#[cfg(feature = "production")]
+pub fn verify_pck_certificate_chain(quote: &Quote) -> Result<VerifyingKey, VerifyQuoteError> {
+    quote.verify().map_err(|_| VerifyQuoteError::PckCertificateVerify)
+}
+
+/// A mock version of verifying the PCK certificate chain.
+/// When generating mock quotes, we just put the encoded PCK in place of the certificate chain
+/// so this function just decodes it, checks it was used to sign the quote, and returns it
+#[cfg(not(feature = "production"))]
+pub fn verify_pck_certificate_chain(quote: &Quote) -> Result<VerifyingKey, VerifyQuoteError> {
+    let provisioning_certification_key =
+        quote.pck_cert_chain().map_err(|_| VerifyQuoteError::PckCertificateNoCertificate)?;
+    let provisioning_certification_key = tdx_quote::decode_verifying_key(
+        &provisioning_certification_key
+            .try_into()
+            .map_err(|_| VerifyQuoteError::CannotDecodeVerifyingKey)?,
+    )
+    .map_err(|_| VerifyQuoteError::CannotDecodeVerifyingKey)?;
+
+    quote
+        .verify_with_pck(&provisioning_certification_key)
+        .map_err(|_| VerifyQuoteError::PckCertificateVerify)?;
+    Ok(provisioning_certification_key)
+}
diff --git a/crates/threshold-signature-server/src/key_provider/api.rs b/crates/threshold-signature-server/src/key_provider/api.rs
index 6ec42aa24..55287fb09 100644
--- a/crates/threshold-signature-server/src/key_provider/api.rs
+++ b/crates/threshold-signature-server/src/key_provider/api.rs
@@ -14,8 +14,11 @@
 // along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
 use crate::{
-    attestation::api::create_quote, chain_api::entropy, key_provider::errors::KeyProviderError,
-    validation::EncryptedSignedMessage, AppState, EntropyConfig, SubxtAccountId32,
+    attestation::api::{create_quote, verify_pck_certificate_chain},
+    chain_api::entropy,
+    key_provider::errors::KeyProviderError,
+    validation::EncryptedSignedMessage,
+    AppState, EntropyConfig, SubxtAccountId32,
 };
 use axum::{extract::State, Json};
 use entropy_client::substrate::query_chain;
@@ -26,6 +29,7 @@ use serde::{Deserialize, Serialize};
 use sp_core::{sr25519, Pair};
 use std::path::PathBuf;
 use subxt::{backend::legacy::LegacyRpcMethods, OnlineClient};
+use tdx_quote::Quote;
 use x25519_dalek::{PublicKey, StaticSecret};
 
 const KEY_PROVIDER_FILENAME: &str = "key-provider-details.json";
@@ -72,7 +76,7 @@ pub async fn request_recover_encryption_key(
     // key
     let quote_nonce = [0; 32];
 
-    // Quote input should contain: key_provider_details.tss_account, and response_key
+    // Quote input contains: key_provider_details.tss_account, and response_key
     let quote = create_quote(
         quote_nonce,
         key_provider_details.tss_account.clone(),
@@ -159,18 +163,18 @@ pub async fn recover_encryption_key(
     Json(key_request): Json<RecoverEncryptionKeyRequest>,
 ) -> Result<Json<EncryptedSignedMessage>, KeyProviderError> {
     // TODO verify quote - and move verifying quote logic to the attestation module
-    // let quote = Quote::from_bytes(&quote).map_err(|_| VerifyQuoteError::BadQuote)?;
+    let quote = Quote::from_bytes(&key_request.quote).unwrap();
 
     let nonce = [0; 32]; // TODO
-    let _expected_input_data = QuoteInputData::new(
+    let expected_input_data = QuoteInputData::new(
         key_request.tss_account.clone(),
         key_request.response_key,
         nonce,
         QuoteContext::EncryptionKeyRecoveryRequest,
     );
-    // if quote.report_input_data() != expected_input_data.0 {
-    //     return Err(KeyProviderError::BadQuoteInputData);
-    // }
+    if quote.report_input_data() != expected_input_data.0 {
+        return Err(KeyProviderError::BadQuoteInputData);
+    }
 
     // Check build-time measurement matches a current-supported release of entropy-tss
     // let mrtd_value =
@@ -178,7 +182,7 @@ pub async fn recover_encryption_key(
     // let accepted_mrtd_values = pallet_parameters::Pallet::<T>::accepted_mrtd_values();
     // ensure!(accepted_mrtd_values.contains(&mrtd_value), VerifyQuoteError::BadMrtdValue);
     //
-    // let pck = verify_pck_certificate_chain(&quote)?;
+    let _pck = verify_pck_certificate_chain(&quote)?;
 
     let backups =
         app_state.encryption_key_backups.read().map_err(|_| KeyProviderError::RwLockPoison)?;
diff --git a/crates/threshold-signature-server/src/key_provider/errors.rs b/crates/threshold-signature-server/src/key_provider/errors.rs
index 71eb3c836..449ff970c 100644
--- a/crates/threshold-signature-server/src/key_provider/errors.rs
+++ b/crates/threshold-signature-server/src/key_provider/errors.rs
@@ -49,6 +49,12 @@ pub enum KeyProviderError {
     Io(#[from] std::io::Error),
     #[error("Utf8Error: {0:?}")]
     Utf8(#[from] std::str::Utf8Error),
+    // #[error("Quote parse: {0}")]
+    // QuoteParse(#[from] tdx_quote::QuoteParseError),
+    #[error("Bad quote input data: TSS account, response public key, or nonce are incorrect")]
+    BadQuoteInputData,
+    #[error("Quote verify: {0}")]
+    VerifyQuote(#[from] entropy_shared::VerifyQuoteError),
 }
 
 impl IntoResponse for KeyProviderError {

From cb7dd4f9f8a4874041c4cedbcf66ec53f0d35992 Mon Sep 17 00:00:00 2001
From: peg <ameba23@systemli.org>
Date: Mon, 20 Jan 2025 11:29:46 +0100
Subject: [PATCH 50/77] Error handling, tidy

---
 .../src/attestation/api.rs                    | 11 ++-----
 .../src/helpers/launch.rs                     | 32 ++++++++++++-------
 .../src/helpers/tests.rs                      |  4 +--
 .../src/key_provider/api.rs                   | 12 +++++--
 .../src/key_provider/errors.rs                |  4 +++
 crates/threshold-signature-server/src/main.rs |  2 +-
 6 files changed, 40 insertions(+), 25 deletions(-)

diff --git a/crates/threshold-signature-server/src/attestation/api.rs b/crates/threshold-signature-server/src/attestation/api.rs
index f2c96ac55..554c2f9f0 100644
--- a/crates/threshold-signature-server/src/attestation/api.rs
+++ b/crates/threshold-signature-server/src/attestation/api.rs
@@ -175,19 +175,14 @@ pub async fn validate_new_attestation(
 #[cfg(feature = "production")]
 pub async fn create_quote(
     nonce: [u8; 32],
-    // TODO change this to SubxtAccountId32
-    signer: &PairSigner<EntropyConfig, sp_core::sr25519::Pair>,
+    tss_account: SubxtAccountId32,
     x25519_secret: &StaticSecret,
     context: QuoteContext,
 ) -> Result<Vec<u8>, AttestationErr> {
     let public_key = x25519_dalek::PublicKey::from(x25519_secret);
 
-    let input_data = entropy_shared::QuoteInputData::new(
-        signer.signer().public(),
-        *public_key.as_bytes(),
-        nonce,
-        context,
-    );
+    let input_data =
+        entropy_shared::QuoteInputData::new(tss_account, *public_key.as_bytes(), nonce, context);
 
     Ok(configfs_tsm::create_quote(input_data.0)
         .map_err(|e| AttestationErr::QuoteGeneration(format!("{:?}", e)))?)
diff --git a/crates/threshold-signature-server/src/helpers/launch.rs b/crates/threshold-signature-server/src/helpers/launch.rs
index 64bab2529..87beb0ad4 100644
--- a/crates/threshold-signature-server/src/helpers/launch.rs
+++ b/crates/threshold-signature-server/src/helpers/launch.rs
@@ -99,24 +99,34 @@ impl Configuration {
 pub async fn setup_kv_store(
     validator_name: &Option<ValidatorName>,
     storage_path: Option<PathBuf>,
-) -> (KvManager, sr25519::Pair, StaticSecret, Option<[u8; 32]>) {
+) -> anyhow::Result<(KvManager, sr25519::Pair, StaticSecret, Option<[u8; 32]>)> {
     let storage_path = storage_path.unwrap_or_else(|| build_db_path(validator_name));
 
     // Check for existing database with backup details
     if let Ok(key_provider_details) = get_key_provider_details(storage_path.clone()) {
         // Retrieve encryption key from another TSS node
-        let key = request_recover_encryption_key(key_provider_details).await.unwrap();
-        let kv_manager = KvManager::new(storage_path, key).unwrap();
-        let x25519_secret: [u8; 32] =
-            kv_manager.kv().get(X25519_SECRET).await.unwrap().try_into().unwrap();
-        let sr25519_seed: [u8; 32] =
-            kv_manager.kv().get(SR25519_SEED).await.unwrap().try_into().unwrap();
+        let key = request_recover_encryption_key(key_provider_details).await?;
+
+        let kv_manager = KvManager::new(storage_path, key)?;
+
+        let x25519_secret: [u8; 32] = kv_manager
+            .kv()
+            .get(X25519_SECRET)
+            .await?
+            .try_into()
+            .map_err(|_| anyhow::anyhow!("X25519 secret from db is not 32 bytes"))?;
+        let sr25519_seed: [u8; 32] = kv_manager
+            .kv()
+            .get(SR25519_SEED)
+            .await?
+            .try_into()
+            .map_err(|_| anyhow::anyhow!("sr25519 seed from db is not 32 bytes"))?;
         let pair = sr25519::Pair::from_seed(&sr25519_seed);
-        (kv_manager, pair, x25519_secret.into(), None)
+        Ok((kv_manager, pair, x25519_secret.into(), None))
     } else {
         // Generate TSS account (or use ValidatorName)
         let (pair, seed, x25519_secret) = if cfg!(test) || validator_name.is_some() {
-            get_signer_and_x25519_secret(&development_mnemonic(validator_name).to_string()).unwrap()
+            get_signer_and_x25519_secret(&development_mnemonic(validator_name).to_string())?
         } else {
             let (pair, seed) = sr25519::Pair::generate();
             let x25519_secret = StaticSecret::random_from_rng(OsRng);
@@ -127,7 +137,7 @@ pub async fn setup_kv_store(
         OsRng.fill_bytes(&mut encryption_key);
 
         // Open store with generated key
-        let kv_manager = KvManager::new(storage_path, encryption_key).unwrap();
+        let kv_manager = KvManager::new(storage_path, encryption_key)?;
         // Store TSS secret keys in kv store
         let reservation = kv_manager
             .kv()
@@ -152,7 +162,7 @@ pub async fn setup_kv_store(
             .expect("failed to store sr25519 seed");
 
         // Return the encryption key so that it can be backed up as part of the pre-requisite checks
-        (kv_manager, pair, x25519_secret, Some(encryption_key))
+        Ok((kv_manager, pair, x25519_secret, Some(encryption_key)))
     }
 }
 
diff --git a/crates/threshold-signature-server/src/helpers/tests.rs b/crates/threshold-signature-server/src/helpers/tests.rs
index 532ea5174..f18a3b3e1 100644
--- a/crates/threshold-signature-server/src/helpers/tests.rs
+++ b/crates/threshold-signature-server/src/helpers/tests.rs
@@ -74,7 +74,7 @@ pub async fn setup_client() -> KvManager {
 
     let storage_path: PathBuf = get_db_path(true).into();
     let (kv_store, sr25519_pair, x25519_secret, _should_backup) =
-        setup_kv_store(&Some(ValidatorName::Alice), Some(storage_path.clone())).await;
+        setup_kv_store(&Some(ValidatorName::Alice), Some(storage_path.clone())).await.unwrap();
 
     let _ = setup_latest_block_number(&kv_store).await;
     let app_state = AppState::new(configuration, kv_store.clone(), sr25519_pair, x25519_secret);
@@ -106,7 +106,7 @@ pub async fn create_clients(
     let _ = std::fs::remove_dir_all(path.clone());
 
     let (kv_store, sr25519_pair, x25519_secret, _should_backup) =
-        setup_kv_store(validator_name, Some(path.into())).await;
+        setup_kv_store(validator_name, Some(path.into())).await.unwrap();
 
     let _ = setup_latest_block_number(&kv_store).await;
     let app_state = AppState::new(configuration, kv_store.clone(), sr25519_pair, x25519_secret);
diff --git a/crates/threshold-signature-server/src/key_provider/api.rs b/crates/threshold-signature-server/src/key_provider/api.rs
index 55287fb09..3c2f95d36 100644
--- a/crates/threshold-signature-server/src/key_provider/api.rs
+++ b/crates/threshold-signature-server/src/key_provider/api.rs
@@ -162,7 +162,6 @@ pub async fn recover_encryption_key(
     State(app_state): State<AppState>,
     Json(key_request): Json<RecoverEncryptionKeyRequest>,
 ) -> Result<Json<EncryptedSignedMessage>, KeyProviderError> {
-    // TODO verify quote - and move verifying quote logic to the attestation module
     let quote = Quote::from_bytes(&key_request.quote).unwrap();
 
     let nonce = [0; 32]; // TODO
@@ -235,7 +234,12 @@ async fn select_key_provider(
     tss_account: SubxtAccountId32,
 ) -> Result<KeyProviderDetails, KeyProviderError> {
     let validators_query = entropy::storage().session().validators();
-    let validators = query_chain(api, rpc, validators_query, None).await?.unwrap();
+    let validators = query_chain(api, rpc, validators_query, None)
+        .await?
+        .ok_or(KeyProviderError::NoValidators)?;
+    if validators.is_empty() {
+        return Err(KeyProviderError::NoValidators);
+    }
 
     let mut deterministic_rng = StdRng::from_seed(tss_account.0);
     let random_index = deterministic_rng.gen_range(0..validators.len());
@@ -243,7 +247,9 @@ async fn select_key_provider(
 
     let threshold_address_query =
         entropy::storage().staking_extension().threshold_servers(validator);
-    let server_info = query_chain(api, rpc, threshold_address_query, None).await?.unwrap();
+    let server_info = query_chain(api, rpc, threshold_address_query, None)
+        .await?
+        .ok_or(KeyProviderError::NoServerInfo)?;
 
     Ok(KeyProviderDetails {
         provider: ValidatorInfo {
diff --git a/crates/threshold-signature-server/src/key_provider/errors.rs b/crates/threshold-signature-server/src/key_provider/errors.rs
index 449ff970c..3b716071e 100644
--- a/crates/threshold-signature-server/src/key_provider/errors.rs
+++ b/crates/threshold-signature-server/src/key_provider/errors.rs
@@ -55,6 +55,10 @@ pub enum KeyProviderError {
     BadQuoteInputData,
     #[error("Quote verify: {0}")]
     VerifyQuote(#[from] entropy_shared::VerifyQuoteError),
+    #[error("Could not find another TSS node to request backup")]
+    NoValidators,
+    #[error("Could not get server info for TSS node chosen for backup")]
+    NoServerInfo,
 }
 
 impl IntoResponse for KeyProviderError {
diff --git a/crates/threshold-signature-server/src/main.rs b/crates/threshold-signature-server/src/main.rs
index 896c1b18f..58ba0831b 100644
--- a/crates/threshold-signature-server/src/main.rs
+++ b/crates/threshold-signature-server/src/main.rs
@@ -58,7 +58,7 @@ async fn main() {
     }
 
     let (kv_store, sr25519_pair, x25519_secret, key_option) =
-        setup_kv_store(&validator_name, None).await;
+        setup_kv_store(&validator_name, None).await.unwrap();
 
     let app_state =
         AppState::new(configuration.clone(), kv_store.clone(), sr25519_pair, x25519_secret);

From aacb5fc3300b78fe105374c0a8fb922083b77ae4 Mon Sep 17 00:00:00 2001
From: peg <ameba23@systemli.org>
Date: Mon, 20 Jan 2025 12:49:15 +0100
Subject: [PATCH 51/77] Check measurement values when verifying quote

---
 .../src/key_provider/api.rs                   | 28 +++++++++++++++----
 .../src/key_provider/errors.rs                |  2 ++
 2 files changed, 24 insertions(+), 6 deletions(-)

diff --git a/crates/threshold-signature-server/src/key_provider/api.rs b/crates/threshold-signature-server/src/key_provider/api.rs
index 3c2f95d36..e0c7e85b8 100644
--- a/crates/threshold-signature-server/src/key_provider/api.rs
+++ b/crates/threshold-signature-server/src/key_provider/api.rs
@@ -133,6 +133,7 @@ pub struct RecoverEncryptionKeyRequest {
 }
 
 /// HTTP endpoint to backup an encryption key on initial launch
+/// The request body should be an encryption key to backup as a [u8; 32] wrapped in an [EncryptedSignedMessage]
 pub async fn backup_encryption_key(
     State(app_state): State<AppState>,
     Json(encrypted_backup_request): Json<EncryptedSignedMessage>,
@@ -157,11 +158,16 @@ pub async fn backup_encryption_key(
     Ok(())
 }
 
-/// HTTP endpoint to recover an encryption key following a process restart
+/// HTTP endpoint to recover an encryption key following a process restart.
+/// The request body should contain a JSON encoded [RecoverEncryptionKeyRequest].
+/// If successfull, the response body will contain the encryption key as a [u8; 32] wrapped in an
+/// [EncryptedSignedMessage].
 pub async fn recover_encryption_key(
     State(app_state): State<AppState>,
     Json(key_request): Json<RecoverEncryptionKeyRequest>,
 ) -> Result<Json<EncryptedSignedMessage>, KeyProviderError> {
+    // TODO we can correctly handle this error once we have merged the latest commits which give us
+    // the latest version of tdx_quote
     let quote = Quote::from_bytes(&key_request.quote).unwrap();
 
     let nonce = [0; 32]; // TODO
@@ -176,11 +182,21 @@ pub async fn recover_encryption_key(
     }
 
     // Check build-time measurement matches a current-supported release of entropy-tss
-    // let mrtd_value =
-    //     BoundedVec::try_from(quote.mrtd().to_vec()).map_err(|_| VerifyQuoteError::BadMrtdValue)?;
-    // let accepted_mrtd_values = pallet_parameters::Pallet::<T>::accepted_mrtd_values();
-    // ensure!(accepted_mrtd_values.contains(&mrtd_value), VerifyQuoteError::BadMrtdValue);
-    //
+    // This bit differs slightly in the attestation pallet implementation vs entropy-tss
+    // because here we don't have direct access to the parameters pallet - we need to make a query
+    let mrtd_value = quote.mrtd().to_vec();
+    let query = entropy::storage().parameters().accepted_mrtd_values();
+    let (api, rpc) = app_state.get_api_rpc().await?;
+    let accepted_mrtd_values: Vec<_> = query_chain(&api, &rpc, query, None)
+        .await?
+        .ok_or(KeyProviderError::NoMeasurementValues)?
+        .into_iter()
+        .map(|v| v.0)
+        .collect();
+    if !accepted_mrtd_values.contains(&mrtd_value) {
+        return Err(entropy_shared::VerifyQuoteError::BadMrtdValue.into());
+    };
+
     let _pck = verify_pck_certificate_chain(&quote)?;
 
     let backups =
diff --git a/crates/threshold-signature-server/src/key_provider/errors.rs b/crates/threshold-signature-server/src/key_provider/errors.rs
index 3b716071e..64fc89c37 100644
--- a/crates/threshold-signature-server/src/key_provider/errors.rs
+++ b/crates/threshold-signature-server/src/key_provider/errors.rs
@@ -59,6 +59,8 @@ pub enum KeyProviderError {
     NoValidators,
     #[error("Could not get server info for TSS node chosen for backup")]
     NoServerInfo,
+    #[error("Could not get accepted measurement values from on-chain parameters")]
+    NoMeasurementValues,
 }
 
 impl IntoResponse for KeyProviderError {

From bc60ec7e3a6a6ee6d4e978f237eb838bdddb28f2 Mon Sep 17 00:00:00 2001
From: peg <ameba23@systemli.org>
Date: Mon, 20 Jan 2025 13:01:45 +0100
Subject: [PATCH 52/77] Rm unused import

---
 crates/threshold-signature-server/src/user/api.rs | 1 -
 1 file changed, 1 deletion(-)

diff --git a/crates/threshold-signature-server/src/user/api.rs b/crates/threshold-signature-server/src/user/api.rs
index ca4364769..f7ca9c202 100644
--- a/crates/threshold-signature-server/src/user/api.rs
+++ b/crates/threshold-signature-server/src/user/api.rs
@@ -50,7 +50,6 @@ use crate::{
             submit_transaction,
         },
         user::{check_in_registration_group, compute_hash, do_dkg, evaluate_program},
-        validator::get_signer_and_x25519_secret,
     },
     validation::{check_stale, EncryptedSignedMessage},
     AppState,

From 76c9443d2ef53288b199f8d9d9cb009b8675bce2 Mon Sep 17 00:00:00 2001
From: peg <ameba23@systemli.org>
Date: Mon, 20 Jan 2025 14:08:16 +0100
Subject: [PATCH 53/77] Handle tdx-quote errors

---
 crates/threshold-signature-server/src/key_provider/api.rs    | 4 +---
 crates/threshold-signature-server/src/key_provider/errors.rs | 4 ++--
 2 files changed, 3 insertions(+), 5 deletions(-)

diff --git a/crates/threshold-signature-server/src/key_provider/api.rs b/crates/threshold-signature-server/src/key_provider/api.rs
index e0c7e85b8..2225fdeb3 100644
--- a/crates/threshold-signature-server/src/key_provider/api.rs
+++ b/crates/threshold-signature-server/src/key_provider/api.rs
@@ -166,9 +166,7 @@ pub async fn recover_encryption_key(
     State(app_state): State<AppState>,
     Json(key_request): Json<RecoverEncryptionKeyRequest>,
 ) -> Result<Json<EncryptedSignedMessage>, KeyProviderError> {
-    // TODO we can correctly handle this error once we have merged the latest commits which give us
-    // the latest version of tdx_quote
-    let quote = Quote::from_bytes(&key_request.quote).unwrap();
+    let quote = Quote::from_bytes(&key_request.quote)?;
 
     let nonce = [0; 32]; // TODO
     let expected_input_data = QuoteInputData::new(
diff --git a/crates/threshold-signature-server/src/key_provider/errors.rs b/crates/threshold-signature-server/src/key_provider/errors.rs
index 64fc89c37..2852f0f04 100644
--- a/crates/threshold-signature-server/src/key_provider/errors.rs
+++ b/crates/threshold-signature-server/src/key_provider/errors.rs
@@ -49,8 +49,8 @@ pub enum KeyProviderError {
     Io(#[from] std::io::Error),
     #[error("Utf8Error: {0:?}")]
     Utf8(#[from] std::str::Utf8Error),
-    // #[error("Quote parse: {0}")]
-    // QuoteParse(#[from] tdx_quote::QuoteParseError),
+    #[error("Quote parse: {0}")]
+    QuoteParse(#[from] tdx_quote::QuoteParseError),
     #[error("Bad quote input data: TSS account, response public key, or nonce are incorrect")]
     BadQuoteInputData,
     #[error("Quote verify: {0}")]

From 184c2dd656b8f13631b3cf07b6a476438a5de627 Mon Sep 17 00:00:00 2001
From: peg <ameba23@systemli.org>
Date: Tue, 21 Jan 2025 09:54:37 +0100
Subject: [PATCH 54/77] Use known encryption keys for test validators

---
 .../src/helpers/launch.rs                     | 19 ++++++++-----
 .../src/key_provider/tests.rs                 | 27 ++++++++++++-------
 2 files changed, 30 insertions(+), 16 deletions(-)

diff --git a/crates/threshold-signature-server/src/helpers/launch.rs b/crates/threshold-signature-server/src/helpers/launch.rs
index 87beb0ad4..a2f3d4e4f 100644
--- a/crates/threshold-signature-server/src/helpers/launch.rs
+++ b/crates/threshold-signature-server/src/helpers/launch.rs
@@ -124,17 +124,22 @@ pub async fn setup_kv_store(
         let pair = sr25519::Pair::from_seed(&sr25519_seed);
         Ok((kv_manager, pair, x25519_secret.into(), None))
     } else {
-        // Generate TSS account (or use ValidatorName)
-        let (pair, seed, x25519_secret) = if cfg!(test) || validator_name.is_some() {
-            get_signer_and_x25519_secret(&development_mnemonic(validator_name).to_string())?
+        // Generate TSS account (or use ValidatorName to get a test account)
+        let (pair, seed, x25519_secret, encryption_key) = if cfg!(test) || validator_name.is_some()
+        {
+            let (pair, seed, x25519_secret) =
+                get_signer_and_x25519_secret(&development_mnemonic(validator_name).to_string())?;
+            // For testing the db encryption key is just the TSS account id
+            let encryption_key = pair.public().0;
+            (pair, seed, x25519_secret, encryption_key)
         } else {
+            // Generate new keys
             let (pair, seed) = sr25519::Pair::generate();
             let x25519_secret = StaticSecret::random_from_rng(OsRng);
-            (pair, seed, x25519_secret)
+            let mut encryption_key = [0; 32];
+            OsRng.fill_bytes(&mut encryption_key);
+            (pair, seed, x25519_secret, encryption_key)
         };
-        // Generate new encryption key
-        let mut encryption_key = [0; 32];
-        OsRng.fill_bytes(&mut encryption_key);
 
         // Open store with generated key
         let kv_manager = KvManager::new(storage_path, encryption_key)?;
diff --git a/crates/threshold-signature-server/src/key_provider/tests.rs b/crates/threshold-signature-server/src/key_provider/tests.rs
index 59008a285..242bf4071 100644
--- a/crates/threshold-signature-server/src/key_provider/tests.rs
+++ b/crates/threshold-signature-server/src/key_provider/tests.rs
@@ -13,13 +13,15 @@
 // You should have received a copy of the GNU Affero General Public License
 // along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
+use std::path::PathBuf;
+
 use crate::{
     helpers::{
         tests::initialize_test_logger, validator::get_signer_and_x25519_secret_from_mnemonic,
     },
     key_provider::api::{
-        make_key_backup, request_backup_encryption_key, request_recover_encryption_key,
-        KeyProviderDetails,
+        get_key_provider_details, make_key_backup, request_backup_encryption_key,
+        request_recover_encryption_key, KeyProviderDetails,
     },
     launch::{development_mnemonic, ValidatorName},
 };
@@ -32,29 +34,35 @@ use entropy_testing_utils::{
 };
 use serial_test::serial;
 
+/// This tests the whole process of selecting and using a backup provider
 #[tokio::test]
 #[serial]
-async fn key_provider_test() {
+async fn key_backup_provider_test() {
     clean_tests();
     initialize_test_logger().await;
 
     let (_ctx, api, rpc, _validator_ips, _validator_ids) =
         spawn_tss_nodes_and_start_chain(ChainSpecType::IntegrationJumpStarted).await;
 
-    let storage_path = ".entropy/testing/test_db_validator1".into();
-    let key = [0; 32]; // TODO this should be the actual key used. Since we dont have access to
-                       // kvmanager, alice bob etc. should use known keys
+    let storage_path: PathBuf = ".entropy/testing/test_db_validator1".into();
+    // For testing we use TSS account ID as the db encryption key
+    let key = TSS_ACCOUNTS[0].0;
 
     let mnemonic = development_mnemonic(&Some(ValidatorName::Alice));
     let (tss_signer, _static_secret) =
         get_signer_and_x25519_secret_from_mnemonic(&mnemonic.to_string()).unwrap();
 
-    make_key_backup(&api, &rpc, key, tss_signer.signer(), storage_path).await.unwrap();
+    make_key_backup(&api, &rpc, key, tss_signer.signer(), storage_path.clone()).await.unwrap();
+
+    let key_provider_details = get_key_provider_details(storage_path).unwrap();
+    let recovered_key = request_recover_encryption_key(key_provider_details).await.unwrap();
+    assert_eq!(key, recovered_key);
 }
 
+/// More low-level version of key_backup_provider_test
 #[tokio::test]
 #[serial]
-async fn key_provider_unit_test() {
+async fn key_backup_provider_unit_test() {
     clean_tests();
     initialize_test_logger().await;
 
@@ -69,7 +77,8 @@ async fn key_provider_unit_test() {
         },
         tss_account: TSS_ACCOUNTS[1].clone(),
     };
-    let key = [1; 32];
+    // For testing we use TSS account ID as the db encryption key
+    let key = TSS_ACCOUNTS[1].0;
 
     let mnemonic = development_mnemonic(&Some(ValidatorName::Bob));
     let (tss_signer, _static_secret) =

From 6aa01fd8a4a5cc29cbc3c05f4faab75419c824f9 Mon Sep 17 00:00:00 2001
From: peg <ameba23@systemli.org>
Date: Tue, 21 Jan 2025 11:37:47 +0100
Subject: [PATCH 55/77] Rename to backup provider, quote nonce getting api

---
 .../{key_provider => backup_provider}/api.rs  | 145 ++++++++++++------
 .../errors.rs                                 |   6 +-
 .../{key_provider => backup_provider}/mod.rs  |   2 +-
 .../tests.rs                                  |  14 +-
 .../src/helpers/launch.rs                     |   6 +-
 crates/threshold-signature-server/src/lib.rs  |  14 +-
 6 files changed, 126 insertions(+), 61 deletions(-)
 rename crates/threshold-signature-server/src/{key_provider => backup_provider}/api.rs (65%)
 rename crates/threshold-signature-server/src/{key_provider => backup_provider}/errors.rs (94%)
 rename crates/threshold-signature-server/src/{key_provider => backup_provider}/mod.rs (94%)
 rename crates/threshold-signature-server/src/{key_provider => backup_provider}/tests.rs (93%)

diff --git a/crates/threshold-signature-server/src/key_provider/api.rs b/crates/threshold-signature-server/src/backup_provider/api.rs
similarity index 65%
rename from crates/threshold-signature-server/src/key_provider/api.rs
rename to crates/threshold-signature-server/src/backup_provider/api.rs
index 2225fdeb3..a7b9c5f73 100644
--- a/crates/threshold-signature-server/src/key_provider/api.rs
+++ b/crates/threshold-signature-server/src/backup_provider/api.rs
@@ -15,15 +15,15 @@
 
 use crate::{
     attestation::api::{create_quote, verify_pck_certificate_chain},
+    backup_provider::errors::BackupProviderError,
     chain_api::entropy,
-    key_provider::errors::KeyProviderError,
     validation::EncryptedSignedMessage,
     AppState, EntropyConfig, SubxtAccountId32,
 };
 use axum::{extract::State, Json};
 use entropy_client::substrate::query_chain;
 use entropy_shared::{user::ValidatorInfo, QuoteContext, QuoteInputData, X25519PublicKey};
-use rand::{rngs::StdRng, Rng, SeedableRng};
+use rand::{rngs::StdRng, Rng, RngCore, SeedableRng};
 use rand_core::OsRng;
 use serde::{Deserialize, Serialize};
 use sp_core::{sr25519, Pair};
@@ -32,25 +32,28 @@ use subxt::{backend::legacy::LegacyRpcMethods, OnlineClient};
 use tdx_quote::Quote;
 use x25519_dalek::{PublicKey, StaticSecret};
 
-const KEY_PROVIDER_FILENAME: &str = "key-provider-details.json";
+const BACKUP_PROVIDER_FILENAME: &str = "backup-provider-details.json";
 
 /// Make a request to a given TSS node to backup a given encryption key
 /// This makes a client request to [backup_encryption_key]
 pub async fn request_backup_encryption_key(
     key: [u8; 32],
-    key_provider_details: KeyProviderDetails,
+    backup_provider_details: BackupProviderDetails,
     sr25519_pair: &sr25519::Pair,
-) -> Result<(), KeyProviderError> {
+) -> Result<(), BackupProviderError> {
     let signed_message = EncryptedSignedMessage::new(
         sr25519_pair,
         key.to_vec(),
-        &key_provider_details.provider.x25519_public_key,
+        &backup_provider_details.provider.x25519_public_key,
         &[],
     )?;
 
     let client = reqwest::Client::new();
     let response = client
-        .post(format!("http://{}/backup_encryption_key", key_provider_details.provider.ip_address))
+        .post(format!(
+            "http://{}/backup_provider/backup_encryption_key",
+            backup_provider_details.provider.ip_address
+        ))
         .header("Content-Type", "application/json")
         .body(serde_json::to_string(&signed_message)?)
         .send()
@@ -59,41 +62,42 @@ pub async fn request_backup_encryption_key(
     let status = response.status();
     if status != reqwest::StatusCode::OK {
         let text = response.text().await?;
-        return Err(KeyProviderError::BadProviderResponse(status, text));
+        return Err(BackupProviderError::BadProviderResponse(status, text));
     }
     Ok(())
 }
 
 /// Make a request to a given TSS node to recover an encryption key
 pub async fn request_recover_encryption_key(
-    key_provider_details: KeyProviderDetails,
-) -> Result<[u8; 32], KeyProviderError> {
+    backup_provider_details: BackupProviderDetails,
+) -> Result<[u8; 32], BackupProviderError> {
     // Generate encryption keypair used for receiving the key
     let response_secret_key = StaticSecret::random_from_rng(OsRng);
     let response_key = PublicKey::from(&response_secret_key).to_bytes();
 
-    // TODO This is tricky as having to request a nonce means we need 2 request-responses to recover the
-    // key
-    let quote_nonce = [0; 32];
+    let quote_nonce = request_quote_nonce(&response_secret_key, &backup_provider_details).await?;
 
     // Quote input contains: key_provider_details.tss_account, and response_key
     let quote = create_quote(
         quote_nonce,
-        key_provider_details.tss_account.clone(),
+        backup_provider_details.tss_account.clone(),
         &response_secret_key,
         QuoteContext::EncryptionKeyRecoveryRequest,
     )
     .await?;
 
     let key_request = RecoverEncryptionKeyRequest {
-        tss_account: key_provider_details.tss_account,
+        tss_account: backup_provider_details.tss_account,
         response_key,
         quote,
     };
 
     let client = reqwest::Client::new();
     let response = client
-        .post(format!("http://{}/recover_encryption_key", key_provider_details.provider.ip_address))
+        .post(format!(
+            "http://{}/backup_provider/recover_encryption_key",
+            backup_provider_details.provider.ip_address
+        ))
         .header("Content-Type", "application/json")
         .body(serde_json::to_string(&key_request)?)
         .send()
@@ -102,7 +106,7 @@ pub async fn request_recover_encryption_key(
     let status = response.status();
     if status != reqwest::StatusCode::OK {
         let text = response.text().await?;
-        return Err(KeyProviderError::BadProviderResponse(status, text));
+        return Err(BackupProviderError::BadProviderResponse(status, text));
     }
 
     let response_bytes = response.bytes().await?;
@@ -110,13 +114,13 @@ pub async fn request_recover_encryption_key(
     let encrypted_response: EncryptedSignedMessage = serde_json::from_slice(&response_bytes)?;
     let signed_message = encrypted_response.decrypt(&response_secret_key, &[])?;
 
-    signed_message.message.0.try_into().map_err(|_| KeyProviderError::BadKeyLength)
+    signed_message.message.0.try_into().map_err(|_| BackupProviderError::BadKeyLength)
 }
 
 /// [ValidatorInfo] of a TSS node chosen to make a key backup, together with the account ID of the TSS
 /// node who the backup is for
 #[derive(Debug, Clone, Serialize, Deserialize)]
-pub struct KeyProviderDetails {
+pub struct BackupProviderDetails {
     pub provider: ValidatorInfo,
     pub tss_account: SubxtAccountId32,
 }
@@ -137,10 +141,10 @@ pub struct RecoverEncryptionKeyRequest {
 pub async fn backup_encryption_key(
     State(app_state): State<AppState>,
     Json(encrypted_backup_request): Json<EncryptedSignedMessage>,
-) -> Result<(), KeyProviderError> {
+) -> Result<(), BackupProviderError> {
     let signed_message = encrypted_backup_request.decrypt(&app_state.x25519_secret, &[])?;
     let key: [u8; 32] =
-        signed_message.message.0.try_into().map_err(|_| KeyProviderError::BadKeyLength)?;
+        signed_message.message.0.try_into().map_err(|_| BackupProviderError::BadKeyLength)?;
 
     let tss_account = SubxtAccountId32(signed_message.sender.0);
     // Check for tss account on the staking pallet - which proves they have made an on-chain attestation
@@ -149,10 +153,10 @@ pub async fn backup_encryption_key(
     let (api, rpc) = app_state.get_api_rpc().await?;
     query_chain(&api, &rpc, threshold_address_query, None)
         .await?
-        .ok_or(KeyProviderError::NotRegisteredWithStakingPallet)?;
+        .ok_or(BackupProviderError::NotRegisteredWithStakingPallet)?;
 
     let mut backups =
-        app_state.encryption_key_backups.write().map_err(|_| KeyProviderError::RwLockPoison)?;
+        app_state.encryption_key_backups.write().map_err(|_| BackupProviderError::RwLockPoison)?;
     backups.insert(tss_account.0, key);
 
     Ok(())
@@ -165,10 +169,15 @@ pub async fn backup_encryption_key(
 pub async fn recover_encryption_key(
     State(app_state): State<AppState>,
     Json(key_request): Json<RecoverEncryptionKeyRequest>,
-) -> Result<Json<EncryptedSignedMessage>, KeyProviderError> {
+) -> Result<Json<EncryptedSignedMessage>, BackupProviderError> {
     let quote = Quote::from_bytes(&key_request.quote)?;
 
-    let nonce = [0; 32]; // TODO
+    let nonce = {
+        let nonces =
+            app_state.attestation_nonces.read().map_err(|_| BackupProviderError::RwLockPoison)?;
+        nonces.get(&key_request.response_key).ok_or(BackupProviderError::NoNonceInStore)?.clone()
+    };
+
     let expected_input_data = QuoteInputData::new(
         key_request.tss_account.clone(),
         key_request.response_key,
@@ -176,7 +185,7 @@ pub async fn recover_encryption_key(
         QuoteContext::EncryptionKeyRecoveryRequest,
     );
     if quote.report_input_data() != expected_input_data.0 {
-        return Err(KeyProviderError::BadQuoteInputData);
+        return Err(BackupProviderError::BadQuoteInputData);
     }
 
     // Check build-time measurement matches a current-supported release of entropy-tss
@@ -187,7 +196,7 @@ pub async fn recover_encryption_key(
     let (api, rpc) = app_state.get_api_rpc().await?;
     let accepted_mrtd_values: Vec<_> = query_chain(&api, &rpc, query, None)
         .await?
-        .ok_or(KeyProviderError::NoMeasurementValues)?
+        .ok_or(BackupProviderError::NoMeasurementValues)?
         .into_iter()
         .map(|v| v.0)
         .collect();
@@ -198,8 +207,8 @@ pub async fn recover_encryption_key(
     let _pck = verify_pck_certificate_chain(&quote)?;
 
     let backups =
-        app_state.encryption_key_backups.read().map_err(|_| KeyProviderError::RwLockPoison)?;
-    let key = backups.get(&key_request.tss_account.0).ok_or(KeyProviderError::NoKeyInStore)?;
+        app_state.encryption_key_backups.read().map_err(|_| BackupProviderError::RwLockPoison)?;
+    let key = backups.get(&key_request.tss_account.0).ok_or(BackupProviderError::NoKeyInStore)?;
 
     // Encrypt response
     let signed_message =
@@ -214,10 +223,10 @@ pub async fn make_key_backup(
     key: [u8; 32],
     sr25519_pair: &sr25519::Pair,
     storage_path: PathBuf,
-) -> Result<(), KeyProviderError> {
+) -> Result<(), BackupProviderError> {
     let tss_account = SubxtAccountId32(sr25519_pair.public().0);
     // Select a provider by making chain query and choosing a tss node
-    let key_provider_details = select_key_provider(api, rpc, tss_account).await?;
+    let key_provider_details = select_backup_provider(api, rpc, tss_account).await?;
     // Get them to backup the key
     request_backup_encryption_key(key, key_provider_details.clone(), sr25519_pair).await?;
     // Store provider details so we know who to ask when recovering
@@ -228,31 +237,33 @@ pub async fn make_key_backup(
 /// Store the details of a TSS node who has a backup of our encryption key in a file
 fn store_key_provider_details(
     mut path: PathBuf,
-    key_provider_details: KeyProviderDetails,
-) -> Result<(), KeyProviderError> {
-    path.push(KEY_PROVIDER_FILENAME);
-    Ok(std::fs::write(path, serde_json::to_vec(&key_provider_details)?)?)
+    backup_provider_details: BackupProviderDetails,
+) -> Result<(), BackupProviderError> {
+    path.push(BACKUP_PROVIDER_FILENAME);
+    Ok(std::fs::write(path, serde_json::to_vec(&backup_provider_details)?)?)
 }
 
 /// Retrieve the details of a TSS node who has a backup of our encryption key from a file
-pub fn get_key_provider_details(mut path: PathBuf) -> Result<KeyProviderDetails, KeyProviderError> {
-    path.push(KEY_PROVIDER_FILENAME);
+pub fn get_key_provider_details(
+    mut path: PathBuf,
+) -> Result<BackupProviderDetails, BackupProviderError> {
+    path.push(BACKUP_PROVIDER_FILENAME);
     let bytes = std::fs::read(path)?;
     Ok(serde_json::from_slice(&bytes)?)
 }
 
-/// Choose a TSS node to request to make a backup from
-async fn select_key_provider(
+/// Choose a TSS node to request to make a backup
+async fn select_backup_provider(
     api: &OnlineClient<EntropyConfig>,
     rpc: &LegacyRpcMethods<EntropyConfig>,
     tss_account: SubxtAccountId32,
-) -> Result<KeyProviderDetails, KeyProviderError> {
+) -> Result<BackupProviderDetails, BackupProviderError> {
     let validators_query = entropy::storage().session().validators();
     let validators = query_chain(api, rpc, validators_query, None)
         .await?
-        .ok_or(KeyProviderError::NoValidators)?;
+        .ok_or(BackupProviderError::NoValidators)?;
     if validators.is_empty() {
-        return Err(KeyProviderError::NoValidators);
+        return Err(BackupProviderError::NoValidators);
     }
 
     let mut deterministic_rng = StdRng::from_seed(tss_account.0);
@@ -263,9 +274,9 @@ async fn select_key_provider(
         entropy::storage().staking_extension().threshold_servers(validator);
     let server_info = query_chain(api, rpc, threshold_address_query, None)
         .await?
-        .ok_or(KeyProviderError::NoServerInfo)?;
+        .ok_or(BackupProviderError::NoServerInfo)?;
 
-    Ok(KeyProviderDetails {
+    Ok(BackupProviderDetails {
         provider: ValidatorInfo {
             x25519_public_key: server_info.x25519_public_key,
             ip_address: std::str::from_utf8(&server_info.endpoint)?.to_string(),
@@ -274,3 +285,49 @@ async fn select_key_provider(
         tss_account,
     })
 }
+
+pub async fn quote_nonce(
+    State(app_state): State<AppState>,
+    Json(response_key): Json<X25519PublicKey>,
+) -> Result<Json<EncryptedSignedMessage>, BackupProviderError> {
+    let mut nonce = [0; 32];
+    OsRng.fill_bytes(&mut nonce);
+    let mut nonces =
+        app_state.attestation_nonces.write().map_err(|_| BackupProviderError::RwLockPoison)?;
+    nonces.insert(response_key, nonce);
+    // Encrypt response
+    let signed_message =
+        EncryptedSignedMessage::new(&app_state.pair, nonce.to_vec(), &response_key, &[])?;
+    Ok(Json(signed_message))
+}
+
+async fn request_quote_nonce(
+    response_secret_key: &StaticSecret,
+    backup_provider_details: &BackupProviderDetails,
+) -> Result<[u8; 32], BackupProviderError> {
+    let response_key = PublicKey::from(response_secret_key).to_bytes();
+
+    let client = reqwest::Client::new();
+    let response = client
+        .post(format!(
+            "http://{}/backup_provider/quote_nonce",
+            backup_provider_details.provider.ip_address
+        ))
+        .header("Content-Type", "application/json")
+        .body(serde_json::to_string(&response_key)?)
+        .send()
+        .await?;
+
+    let status = response.status();
+    if status != reqwest::StatusCode::OK {
+        let text = response.text().await?;
+        return Err(BackupProviderError::BadProviderResponse(status, text));
+    }
+
+    let response_bytes = response.bytes().await?;
+
+    let encrypted_response: EncryptedSignedMessage = serde_json::from_slice(&response_bytes)?;
+    let signed_message = encrypted_response.decrypt(response_secret_key, &[])?;
+
+    signed_message.message.0.try_into().map_err(|_| BackupProviderError::BadKeyLength)
+}
diff --git a/crates/threshold-signature-server/src/key_provider/errors.rs b/crates/threshold-signature-server/src/backup_provider/errors.rs
similarity index 94%
rename from crates/threshold-signature-server/src/key_provider/errors.rs
rename to crates/threshold-signature-server/src/backup_provider/errors.rs
index 2852f0f04..3482edc41 100644
--- a/crates/threshold-signature-server/src/key_provider/errors.rs
+++ b/crates/threshold-signature-server/src/backup_provider/errors.rs
@@ -20,13 +20,15 @@ use entropy_kvdb::kv_manager::error::KvError;
 use thiserror::Error;
 
 #[derive(Debug, Error)]
-pub enum KeyProviderError {
+pub enum BackupProviderError {
     #[error("HTTP request: {0}")]
     HttpRequest(#[from] reqwest::Error),
     #[error("Key-value store: {0}")]
     Kv(#[from] KvError),
     #[error("Encryption key is not present in backup store")]
     NoKeyInStore,
+    #[error("Cannot retrieve associated nonce for this backup")]
+    NoNonceInStore,
     #[error("Panic while holding lock on backup store")]
     RwLockPoison,
     #[error("JSON: {0}")]
@@ -63,7 +65,7 @@ pub enum KeyProviderError {
     NoMeasurementValues,
 }
 
-impl IntoResponse for KeyProviderError {
+impl IntoResponse for BackupProviderError {
     fn into_response(self) -> Response {
         tracing::error!("{:?}", format!("{self}"));
         let body = format!("{self}").into_bytes();
diff --git a/crates/threshold-signature-server/src/key_provider/mod.rs b/crates/threshold-signature-server/src/backup_provider/mod.rs
similarity index 94%
rename from crates/threshold-signature-server/src/key_provider/mod.rs
rename to crates/threshold-signature-server/src/backup_provider/mod.rs
index 3b6dc0597..eaa71cb82 100644
--- a/crates/threshold-signature-server/src/key_provider/mod.rs
+++ b/crates/threshold-signature-server/src/backup_provider/mod.rs
@@ -13,7 +13,7 @@
 // You should have received a copy of the GNU Affero General Public License
 // along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
-//! Key provider service
+//! Backup encryption key provider service
 pub mod api;
 pub mod errors;
 
diff --git a/crates/threshold-signature-server/src/key_provider/tests.rs b/crates/threshold-signature-server/src/backup_provider/tests.rs
similarity index 93%
rename from crates/threshold-signature-server/src/key_provider/tests.rs
rename to crates/threshold-signature-server/src/backup_provider/tests.rs
index 242bf4071..3599601b8 100644
--- a/crates/threshold-signature-server/src/key_provider/tests.rs
+++ b/crates/threshold-signature-server/src/backup_provider/tests.rs
@@ -16,13 +16,13 @@
 use std::path::PathBuf;
 
 use crate::{
+    backup_provider::api::{
+        get_key_provider_details, make_key_backup, request_backup_encryption_key,
+        request_recover_encryption_key, BackupProviderDetails,
+    },
     helpers::{
         tests::initialize_test_logger, validator::get_signer_and_x25519_secret_from_mnemonic,
     },
-    key_provider::api::{
-        get_key_provider_details, make_key_backup, request_backup_encryption_key,
-        request_recover_encryption_key, KeyProviderDetails,
-    },
     launch::{development_mnemonic, ValidatorName},
 };
 use entropy_kvdb::clean_tests;
@@ -37,7 +37,7 @@ use serial_test::serial;
 /// This tests the whole process of selecting and using a backup provider
 #[tokio::test]
 #[serial]
-async fn key_backup_provider_test() {
+async fn backup_provider_test() {
     clean_tests();
     initialize_test_logger().await;
 
@@ -62,14 +62,14 @@ async fn key_backup_provider_test() {
 /// More low-level version of key_backup_provider_test
 #[tokio::test]
 #[serial]
-async fn key_backup_provider_unit_test() {
+async fn backup_provider_unit_test() {
     clean_tests();
     initialize_test_logger().await;
 
     let (_ctx, _api, _rpc, _validator_ips, _validator_ids) =
         spawn_tss_nodes_and_start_chain(ChainSpecType::IntegrationJumpStarted).await;
 
-    let key_provider_details = KeyProviderDetails {
+    let key_provider_details = BackupProviderDetails {
         provider: ValidatorInfo {
             tss_account: TSS_ACCOUNTS[0].clone(),
             x25519_public_key: X25519_PUBLIC_KEYS[0],
diff --git a/crates/threshold-signature-server/src/helpers/launch.rs b/crates/threshold-signature-server/src/helpers/launch.rs
index a2f3d4e4f..bf272c615 100644
--- a/crates/threshold-signature-server/src/helpers/launch.rs
+++ b/crates/threshold-signature-server/src/helpers/launch.rs
@@ -18,11 +18,11 @@
 use std::path::PathBuf;
 
 use crate::{
-    chain_api::entropy,
-    helpers::{substrate::query_chain, validator::get_signer_and_x25519_secret},
-    key_provider::api::{
+    backup_provider::api::{
         get_key_provider_details, make_key_backup, request_recover_encryption_key,
     },
+    chain_api::entropy,
+    helpers::{substrate::query_chain, validator::get_signer_and_x25519_secret},
     AppState,
 };
 use clap::Parser;
diff --git a/crates/threshold-signature-server/src/lib.rs b/crates/threshold-signature-server/src/lib.rs
index 5794993b7..0aa1d1769 100644
--- a/crates/threshold-signature-server/src/lib.rs
+++ b/crates/threshold-signature-server/src/lib.rs
@@ -160,9 +160,9 @@
 #![doc(html_logo_url = "https://entropy.xyz/assets/logo_02.png")]
 pub use entropy_client::chain_api;
 pub(crate) mod attestation;
+pub(crate) mod backup_provider;
 pub(crate) mod health;
 pub mod helpers;
-pub(crate) mod key_provider;
 pub(crate) mod node_info;
 pub(crate) mod sign_init;
 pub(crate) mod signing_client;
@@ -196,9 +196,9 @@ use x25519_dalek::StaticSecret;
 pub use crate::helpers::{launch, validator::get_signer_and_x25519_secret};
 use crate::{
     attestation::api::{attest, get_attest},
+    backup_provider::api::{backup_encryption_key, quote_nonce, recover_encryption_key},
     chain_api::{get_api, get_rpc, EntropyConfig},
     health::api::healthz,
-    key_provider::api::{backup_encryption_key, recover_encryption_key},
     launch::Configuration,
     node_info::api::{hashes, info, version as get_version},
     r#unsafe::api::{delete, put, remove_keys, unsafe_get},
@@ -226,7 +226,11 @@ pub struct AppState {
     /// Key-value store
     pub kv_store: KvManager,
     /// Storage for encryption key backups for other TSS nodes
+    /// Maps TSS account id to encryption key
     pub encryption_key_backups: Arc<RwLock<HashMap<[u8; 32], [u8; 32]>>>,
+    /// Storage for quote nonces for other TSS nodes wanting to make encryption key backups
+    /// Maps TSS account ID to quote nonce
+    pub attestation_nonces: Arc<RwLock<HashMap<[u8; 32], [u8; 32]>>>,
 }
 
 impl AppState {
@@ -245,6 +249,7 @@ impl AppState {
             configuration,
             kv_store,
             encryption_key_backups: Default::default(),
+            attestation_nonces: Default::default(),
         }
     }
 
@@ -304,8 +309,9 @@ pub fn app(app_state: AppState) -> Router {
         .route("/rotate_network_key", post(rotate_network_key))
         .route("/attest", post(attest))
         .route("/attest", get(get_attest))
-        .route("/backup_encryption_key", post(backup_encryption_key))
-        .route("/recover_encryption_key", post(recover_encryption_key))
+        .route("/backup_provider/backup_encryption_key", post(backup_encryption_key))
+        .route("/backup_provider/recover_encryption_key", post(recover_encryption_key))
+        .route("/backup_provider/quote_nonce", post(quote_nonce))
         .route("/healthz", get(healthz))
         .route("/version", get(get_version))
         .route("/hashes", get(hashes))

From 74b3927d61d81430f41480a182a9950a41b121b4 Mon Sep 17 00:00:00 2001
From: peg <ameba23@systemli.org>
Date: Tue, 21 Jan 2025 11:57:46 +0100
Subject: [PATCH 56/77] Clippy

---
 crates/threshold-signature-server/src/backup_provider/api.rs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/crates/threshold-signature-server/src/backup_provider/api.rs b/crates/threshold-signature-server/src/backup_provider/api.rs
index a7b9c5f73..92bbf7d13 100644
--- a/crates/threshold-signature-server/src/backup_provider/api.rs
+++ b/crates/threshold-signature-server/src/backup_provider/api.rs
@@ -175,7 +175,7 @@ pub async fn recover_encryption_key(
     let nonce = {
         let nonces =
             app_state.attestation_nonces.read().map_err(|_| BackupProviderError::RwLockPoison)?;
-        nonces.get(&key_request.response_key).ok_or(BackupProviderError::NoNonceInStore)?.clone()
+        *nonces.get(&key_request.response_key).ok_or(BackupProviderError::NoNonceInStore)?
     };
 
     let expected_input_data = QuoteInputData::new(

From 1d10dabd82cf24e01997aebd7781eb5042235140 Mon Sep 17 00:00:00 2001
From: peg <ameba23@systemli.org>
Date: Tue, 21 Jan 2025 12:40:30 +0100
Subject: [PATCH 57/77] Require node is ready in http route handlers

---
 .../src/attestation/api.rs                    |  2 -
 .../src/backup_provider/api.rs                | 54 +++++++++++++++----
 .../src/backup_provider/errors.rs             |  2 +
 3 files changed, 45 insertions(+), 13 deletions(-)

diff --git a/crates/threshold-signature-server/src/attestation/api.rs b/crates/threshold-signature-server/src/attestation/api.rs
index 554c2f9f0..aa75b1703 100644
--- a/crates/threshold-signature-server/src/attestation/api.rs
+++ b/crates/threshold-signature-server/src/attestation/api.rs
@@ -209,8 +209,6 @@ impl QuoteContextQuery {
     }
 }
 
-// TODO these functions are duplicated in the attestation pallet, maybe move somewhere common eg:
-// entropy-shared
 /// Verify a PCK certificate chain from a quote in production
 #[cfg(feature = "production")]
 pub fn verify_pck_certificate_chain(quote: &Quote) -> Result<VerifyingKey, VerifyQuoteError> {
diff --git a/crates/threshold-signature-server/src/backup_provider/api.rs b/crates/threshold-signature-server/src/backup_provider/api.rs
index 92bbf7d13..f92bfc620 100644
--- a/crates/threshold-signature-server/src/backup_provider/api.rs
+++ b/crates/threshold-signature-server/src/backup_provider/api.rs
@@ -34,13 +34,14 @@ use x25519_dalek::{PublicKey, StaticSecret};
 
 const BACKUP_PROVIDER_FILENAME: &str = "backup-provider-details.json";
 
-/// Make a request to a given TSS node to backup a given encryption key
+/// Client function to make a request to a given TSS node to backup a given encryption key
 /// This makes a client request to [backup_encryption_key]
 pub async fn request_backup_encryption_key(
     key: [u8; 32],
     backup_provider_details: BackupProviderDetails,
     sr25519_pair: &sr25519::Pair,
 ) -> Result<(), BackupProviderError> {
+    // Encrypt the key to the backup provider's public x25519 key
     let signed_message = EncryptedSignedMessage::new(
         sr25519_pair,
         key.to_vec(),
@@ -48,6 +49,7 @@ pub async fn request_backup_encryption_key(
         &[],
     )?;
 
+    // Make the request
     let client = reqwest::Client::new();
     let response = client
         .post(format!(
@@ -111,6 +113,7 @@ pub async fn request_recover_encryption_key(
 
     let response_bytes = response.bytes().await?;
 
+    // Decrypt the response
     let encrypted_response: EncryptedSignedMessage = serde_json::from_slice(&response_bytes)?;
     let signed_message = encrypted_response.decrypt(&response_secret_key, &[])?;
 
@@ -142,12 +145,17 @@ pub async fn backup_encryption_key(
     State(app_state): State<AppState>,
     Json(encrypted_backup_request): Json<EncryptedSignedMessage>,
 ) -> Result<(), BackupProviderError> {
+    if !app_state.is_ready() {
+        return Err(BackupProviderError::NotReady);
+    }
+
+    // Decrypt the request body to get the key to be backed-up
     let signed_message = encrypted_backup_request.decrypt(&app_state.x25519_secret, &[])?;
     let key: [u8; 32] =
         signed_message.message.0.try_into().map_err(|_| BackupProviderError::BadKeyLength)?;
 
     let tss_account = SubxtAccountId32(signed_message.sender.0);
-    // Check for tss account on the staking pallet - which proves they have made an on-chain attestation
+    // Check for TSS account on the staking pallet - which proves they have made an on-chain attestation
     let threshold_address_query =
         entropy::storage().staking_extension().threshold_to_stash(&tss_account);
     let (api, rpc) = app_state.get_api_rpc().await?;
@@ -170,12 +178,16 @@ pub async fn recover_encryption_key(
     State(app_state): State<AppState>,
     Json(key_request): Json<RecoverEncryptionKeyRequest>,
 ) -> Result<Json<EncryptedSignedMessage>, BackupProviderError> {
+    if !app_state.is_ready() {
+        return Err(BackupProviderError::NotReady);
+    }
+
     let quote = Quote::from_bytes(&key_request.quote)?;
 
     let nonce = {
-        let nonces =
-            app_state.attestation_nonces.read().map_err(|_| BackupProviderError::RwLockPoison)?;
-        *nonces.get(&key_request.response_key).ok_or(BackupProviderError::NoNonceInStore)?
+        let mut nonces =
+            app_state.attestation_nonces.write().map_err(|_| BackupProviderError::RwLockPoison)?;
+        nonces.remove(&key_request.response_key).ok_or(BackupProviderError::NoNonceInStore)?
     };
 
     let expected_input_data = QuoteInputData::new(
@@ -206,9 +218,13 @@ pub async fn recover_encryption_key(
 
     let _pck = verify_pck_certificate_chain(&quote)?;
 
-    let backups =
-        app_state.encryption_key_backups.read().map_err(|_| BackupProviderError::RwLockPoison)?;
-    let key = backups.get(&key_request.tss_account.0).ok_or(BackupProviderError::NoKeyInStore)?;
+    let key = {
+        let backups = app_state
+            .encryption_key_backups
+            .read()
+            .map_err(|_| BackupProviderError::RwLockPoison)?;
+        *backups.get(&key_request.tss_account.0).ok_or(BackupProviderError::NoKeyInStore)?
+    };
 
     // Encrypt response
     let signed_message =
@@ -258,6 +274,7 @@ async fn select_backup_provider(
     rpc: &LegacyRpcMethods<EntropyConfig>,
     tss_account: SubxtAccountId32,
 ) -> Result<BackupProviderDetails, BackupProviderError> {
+    // Get all active validators
     let validators_query = entropy::storage().session().validators();
     let validators = query_chain(api, rpc, validators_query, None)
         .await?
@@ -266,10 +283,12 @@ async fn select_backup_provider(
         return Err(BackupProviderError::NoValidators);
     }
 
+    // Choose one deterministically based on account ID
     let mut deterministic_rng = StdRng::from_seed(tss_account.0);
     let random_index = deterministic_rng.gen_range(0..validators.len());
     let validator = &validators[random_index];
 
+    // Get associated details
     let threshold_address_query =
         entropy::storage().staking_extension().threshold_servers(validator);
     let server_info = query_chain(api, rpc, threshold_address_query, None)
@@ -286,21 +305,34 @@ async fn select_backup_provider(
     })
 }
 
+/// HTTP POST route which provides a quote nonce to be used in the quote when requesting to recover
+/// an encryption key.
+/// The nonce is returned encrypted with the given ephemeral public key. This key is also used as a
+/// lookup key for the nonce.
 pub async fn quote_nonce(
     State(app_state): State<AppState>,
     Json(response_key): Json<X25519PublicKey>,
 ) -> Result<Json<EncryptedSignedMessage>, BackupProviderError> {
+    if !app_state.is_ready() {
+        return Err(BackupProviderError::NotReady);
+    }
+
     let mut nonce = [0; 32];
     OsRng.fill_bytes(&mut nonce);
-    let mut nonces =
-        app_state.attestation_nonces.write().map_err(|_| BackupProviderError::RwLockPoison)?;
-    nonces.insert(response_key, nonce);
+
+    {
+        let mut nonces =
+            app_state.attestation_nonces.write().map_err(|_| BackupProviderError::RwLockPoison)?;
+        nonces.insert(response_key, nonce);
+    }
+
     // Encrypt response
     let signed_message =
         EncryptedSignedMessage::new(&app_state.pair, nonce.to_vec(), &response_key, &[])?;
     Ok(Json(signed_message))
 }
 
+/// Client function used to make a POST request to `backup_provider/quote_nonce`
 async fn request_quote_nonce(
     response_secret_key: &StaticSecret,
     backup_provider_details: &BackupProviderDetails,
diff --git a/crates/threshold-signature-server/src/backup_provider/errors.rs b/crates/threshold-signature-server/src/backup_provider/errors.rs
index 3482edc41..9827a16f6 100644
--- a/crates/threshold-signature-server/src/backup_provider/errors.rs
+++ b/crates/threshold-signature-server/src/backup_provider/errors.rs
@@ -63,6 +63,8 @@ pub enum BackupProviderError {
     NoServerInfo,
     #[error("Could not get accepted measurement values from on-chain parameters")]
     NoMeasurementValues,
+    #[error("Node has started fresh and not yet successfully set up")]
+    NotReady,
 }
 
 impl IntoResponse for BackupProviderError {

From 5911e08c6157656b76dfd24302be5902eea437f1 Mon Sep 17 00:00:00 2001
From: peg <ameba23@systemli.org>
Date: Tue, 21 Jan 2025 12:57:46 +0100
Subject: [PATCH 58/77] Mv quote measurement checking fn to attestation module

---
 .../src/attestation/api.rs                    | 27 +++++++++++++++++--
 .../src/attestation/errors.rs                 | 11 ++++++++
 .../src/backup_provider/api.rs                | 17 ++----------
 .../src/backup_provider/errors.rs             |  5 ++--
 .../src/backup_provider/mod.rs                |  2 +-
 5 files changed, 42 insertions(+), 20 deletions(-)

diff --git a/crates/threshold-signature-server/src/attestation/api.rs b/crates/threshold-signature-server/src/attestation/api.rs
index aa75b1703..81eab945c 100644
--- a/crates/threshold-signature-server/src/attestation/api.rs
+++ b/crates/threshold-signature-server/src/attestation/api.rs
@@ -14,7 +14,7 @@
 // along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
 use crate::{
-    attestation::errors::AttestationErr,
+    attestation::errors::{AttestationErr, QuoteMeasurementErr},
     chain_api::{entropy, get_api, get_rpc},
     helpers::{
         launch::LATEST_BLOCK_NUMBER_ATTEST,
@@ -27,11 +27,12 @@ use axum::{
     extract::{Query, State},
     http::StatusCode,
 };
-use entropy_client::user::request_attestation;
+use entropy_client::{chain_api::EntropyConfig, user::request_attestation};
 use entropy_kvdb::kv_manager::KvManager;
 use entropy_shared::{OcwMessageAttestationRequest, QuoteContext, VerifyQuoteError};
 use parity_scale_codec::Decode;
 use serde::Deserialize;
+use subxt::{backend::legacy::LegacyRpcMethods, OnlineClient};
 use tdx_quote::{Quote, VerifyingKey};
 use x25519_dalek::StaticSecret;
 
@@ -234,3 +235,25 @@ pub fn verify_pck_certificate_chain(quote: &Quote) -> Result<VerifyingKey, Verif
         .map_err(|_| VerifyQuoteError::PckCertificateVerify)?;
     Ok(provisioning_certification_key)
 }
+
+/// Check build-time measurement matches a current-supported release of entropy-tss
+/// This differs slightly from the attestation pallet implementation because here we don't have direct
+/// access to the parameters pallet - we need to make a query
+pub async fn check_quote_measurement(
+    api: &OnlineClient<EntropyConfig>,
+    rpc: &LegacyRpcMethods<EntropyConfig>,
+    quote: &Quote,
+) -> Result<(), QuoteMeasurementErr> {
+    let mrtd_value = quote.mrtd().to_vec();
+    let query = entropy::storage().parameters().accepted_mrtd_values();
+    let accepted_mrtd_values: Vec<_> = query_chain(api, rpc, query, None)
+        .await?
+        .ok_or(QuoteMeasurementErr::NoMeasurementValues)?
+        .into_iter()
+        .map(|v| v.0)
+        .collect();
+    if !accepted_mrtd_values.contains(&mrtd_value) {
+        return Err(entropy_shared::VerifyQuoteError::BadMrtdValue.into());
+    };
+    Ok(())
+}
diff --git a/crates/threshold-signature-server/src/attestation/errors.rs b/crates/threshold-signature-server/src/attestation/errors.rs
index 0905ceb27..72b496f0c 100644
--- a/crates/threshold-signature-server/src/attestation/errors.rs
+++ b/crates/threshold-signature-server/src/attestation/errors.rs
@@ -64,3 +64,14 @@ impl IntoResponse for AttestationErr {
         (StatusCode::INTERNAL_SERVER_ERROR, body).into_response()
     }
 }
+
+/// Error when checking quote measurement value
+#[derive(Debug, Error)]
+pub enum QuoteMeasurementErr {
+    #[error("Substrate: {0}")]
+    SubstrateClient(#[from] entropy_client::substrate::SubstrateError),
+    #[error("Could not get accepted measurement values from on-chain parameters")]
+    NoMeasurementValues,
+    #[error("Quote verification: {0}")]
+    Kv(#[from] entropy_shared::VerifyQuoteError),
+}
diff --git a/crates/threshold-signature-server/src/backup_provider/api.rs b/crates/threshold-signature-server/src/backup_provider/api.rs
index f92bfc620..d3c565aab 100644
--- a/crates/threshold-signature-server/src/backup_provider/api.rs
+++ b/crates/threshold-signature-server/src/backup_provider/api.rs
@@ -14,7 +14,7 @@
 // along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
 use crate::{
-    attestation::api::{create_quote, verify_pck_certificate_chain},
+    attestation::api::{check_quote_measurement, create_quote, verify_pck_certificate_chain},
     backup_provider::errors::BackupProviderError,
     chain_api::entropy,
     validation::EncryptedSignedMessage,
@@ -200,21 +200,8 @@ pub async fn recover_encryption_key(
         return Err(BackupProviderError::BadQuoteInputData);
     }
 
-    // Check build-time measurement matches a current-supported release of entropy-tss
-    // This bit differs slightly in the attestation pallet implementation vs entropy-tss
-    // because here we don't have direct access to the parameters pallet - we need to make a query
-    let mrtd_value = quote.mrtd().to_vec();
-    let query = entropy::storage().parameters().accepted_mrtd_values();
     let (api, rpc) = app_state.get_api_rpc().await?;
-    let accepted_mrtd_values: Vec<_> = query_chain(&api, &rpc, query, None)
-        .await?
-        .ok_or(BackupProviderError::NoMeasurementValues)?
-        .into_iter()
-        .map(|v| v.0)
-        .collect();
-    if !accepted_mrtd_values.contains(&mrtd_value) {
-        return Err(entropy_shared::VerifyQuoteError::BadMrtdValue.into());
-    };
+    check_quote_measurement(&api, &rpc, &quote).await?;
 
     let _pck = verify_pck_certificate_chain(&quote)?;
 
diff --git a/crates/threshold-signature-server/src/backup_provider/errors.rs b/crates/threshold-signature-server/src/backup_provider/errors.rs
index 9827a16f6..e96e2ca9a 100644
--- a/crates/threshold-signature-server/src/backup_provider/errors.rs
+++ b/crates/threshold-signature-server/src/backup_provider/errors.rs
@@ -19,6 +19,7 @@ use axum::{
 use entropy_kvdb::kv_manager::error::KvError;
 use thiserror::Error;
 
+/// An error relating to backing-up or recovering a key-value database encryption key
 #[derive(Debug, Error)]
 pub enum BackupProviderError {
     #[error("HTTP request: {0}")]
@@ -61,10 +62,10 @@ pub enum BackupProviderError {
     NoValidators,
     #[error("Could not get server info for TSS node chosen for backup")]
     NoServerInfo,
-    #[error("Could not get accepted measurement values from on-chain parameters")]
-    NoMeasurementValues,
     #[error("Node has started fresh and not yet successfully set up")]
     NotReady,
+    #[error("Quote measurement: {0}")]
+    QuoteMeasurement(#[from] crate::attestation::errors::QuoteMeasurementErr),
 }
 
 impl IntoResponse for BackupProviderError {
diff --git a/crates/threshold-signature-server/src/backup_provider/mod.rs b/crates/threshold-signature-server/src/backup_provider/mod.rs
index eaa71cb82..fa00b855f 100644
--- a/crates/threshold-signature-server/src/backup_provider/mod.rs
+++ b/crates/threshold-signature-server/src/backup_provider/mod.rs
@@ -13,7 +13,7 @@
 // You should have received a copy of the GNU Affero General Public License
 // along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
-//! Backup encryption key provider service
+//! Backup database encryption key provider service
 pub mod api;
 pub mod errors;
 

From e73ad941ea19cbc6bc5fae01b93fa018ec6c3362 Mon Sep 17 00:00:00 2001
From: peg <ameba23@systemli.org>
Date: Tue, 21 Jan 2025 13:12:10 +0100
Subject: [PATCH 59/77] Error handling

---
 .../src/helpers/launch.rs                     | 34 +++++++------------
 crates/threshold-signature-server/src/main.rs | 20 ++++++-----
 2 files changed, 24 insertions(+), 30 deletions(-)

diff --git a/crates/threshold-signature-server/src/helpers/launch.rs b/crates/threshold-signature-server/src/helpers/launch.rs
index bf272c615..1cb5036ed 100644
--- a/crates/threshold-signature-server/src/helpers/launch.rs
+++ b/crates/threshold-signature-server/src/helpers/launch.rs
@@ -96,6 +96,8 @@ impl Configuration {
     }
 }
 
+/// Setup the encrypted key-value store, recovering the encryption key if needed
+/// Returns the kv store, the TSS keypairs, and the encryption key if it needs to be backed-up
 pub async fn setup_kv_store(
     validator_name: &Option<ValidatorName>,
     storage_path: Option<PathBuf>,
@@ -107,8 +109,10 @@ pub async fn setup_kv_store(
         // Retrieve encryption key from another TSS node
         let key = request_recover_encryption_key(key_provider_details).await?;
 
+        // Open existing db with recovered key
         let kv_manager = KvManager::new(storage_path, key)?;
 
+        // Get keypairs from existing db
         let x25519_secret: [u8; 32] = kv_manager
             .kv()
             .get(X25519_SECRET)
@@ -129,7 +133,7 @@ pub async fn setup_kv_store(
         {
             let (pair, seed, x25519_secret) =
                 get_signer_and_x25519_secret(&development_mnemonic(validator_name).to_string())?;
-            // For testing the db encryption key is just the TSS account id
+            // For testing, the db encryption key is just the TSS account id
             let encryption_key = pair.public().0;
             (pair, seed, x25519_secret, encryption_key)
         } else {
@@ -143,34 +147,20 @@ pub async fn setup_kv_store(
 
         // Open store with generated key
         let kv_manager = KvManager::new(storage_path, encryption_key)?;
-        // Store TSS secret keys in kv store
-        let reservation = kv_manager
-            .kv()
-            .reserve_key(X25519_SECRET.to_string())
-            .await
-            .expect("Issue reserving x25519 secret key");
-        kv_manager
-            .kv()
-            .put(reservation, x25519_secret.to_bytes().to_vec())
-            .await
-            .expect("failed to store x25519 secret");
 
-        let reservation = kv_manager
-            .kv()
-            .reserve_key(SR25519_SEED.to_string())
-            .await
-            .expect("Issue reserving sr25519 seed");
-        kv_manager
-            .kv()
-            .put(reservation, seed.to_vec())
-            .await
-            .expect("failed to store sr25519 seed");
+        // Store TSS secret keys in kv store
+        let reservation = kv_manager.kv().reserve_key(X25519_SECRET.to_string()).await?;
+        kv_manager.kv().put(reservation, x25519_secret.to_bytes().to_vec()).await?;
+        let reservation = kv_manager.kv().reserve_key(SR25519_SEED.to_string()).await?;
+        kv_manager.kv().put(reservation, seed.to_vec()).await?;
 
         // Return the encryption key so that it can be backed up as part of the pre-requisite checks
         Ok((kv_manager, pair, x25519_secret, Some(encryption_key)))
     }
 }
 
+/// Build the storage path for the key-value store, providing separate subdirectories for the
+/// different test accounts when testing
 pub fn build_db_path(validator_name: &Option<ValidatorName>) -> PathBuf {
     if cfg!(test) {
         return PathBuf::from(entropy_kvdb::get_db_path(true));
diff --git a/crates/threshold-signature-server/src/main.rs b/crates/threshold-signature-server/src/main.rs
index 58ba0831b..a1a97310d 100644
--- a/crates/threshold-signature-server/src/main.rs
+++ b/crates/threshold-signature-server/src/main.rs
@@ -15,6 +15,7 @@
 
 use std::{net::SocketAddr, process, str::FromStr};
 
+use anyhow::{anyhow, ensure};
 use clap::Parser;
 
 use entropy_tss::{
@@ -26,7 +27,7 @@ use entropy_tss::{
 };
 
 #[tokio::main]
-async fn main() {
+async fn main() -> anyhow::Result<()> {
     let args = StartupArgs::parse();
     args.logger.setup().await;
 
@@ -58,12 +59,15 @@ async fn main() {
     }
 
     let (kv_store, sr25519_pair, x25519_secret, key_option) =
-        setup_kv_store(&validator_name, None).await.unwrap();
+        setup_kv_store(&validator_name, None).await?;
 
     let app_state =
         AppState::new(configuration.clone(), kv_store.clone(), sr25519_pair, x25519_secret);
 
-    setup_latest_block_number(&kv_store).await.expect("Issue setting up Latest Block Number");
+    ensure!(
+        setup_latest_block_number(&kv_store).await.is_ok(),
+        "Issue setting up Latest Block Number"
+    );
 
     {
         let app_state = app_state.clone();
@@ -79,11 +83,11 @@ async fn main() {
         });
     }
 
-    let addr = SocketAddr::from_str(&args.threshold_url).expect("failed to parse threshold url.");
+    let addr = SocketAddr::from_str(&args.threshold_url)
+        .map_err(|_| anyhow!("Failed to parse threshold url"))?;
     let listener = tokio::net::TcpListener::bind(&addr)
         .await
-        .expect("Unable to bind to given server address.");
-    axum::serve(listener, app(app_state).into_make_service())
-        .await
-        .expect("failed to launch axum server.");
+        .map_err(|_| anyhow!("Unable to bind to given server address"))?;
+    axum::serve(listener, app(app_state).into_make_service()).await?;
+    Ok(())
 }

From 7005f44b4841205f417347f397dd391300318058 Mon Sep 17 00:00:00 2001
From: peg <ameba23@systemli.org>
Date: Tue, 21 Jan 2025 13:29:23 +0100
Subject: [PATCH 60/77] Changelog

---
 CHANGELOG.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 15494e041..0ce513c85 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -50,6 +50,7 @@ runtime
 - Test CLI command to retrieve quote and change endpoint / TSS account in one command ([#1198](https://github.com/entropyxyz/entropy-core/pull/1198))
 - On-chain unresponsiveness reporting [(#1215)](https://github.com/entropyxyz/entropy-core/pull/1215)
 - Add cli options for adding validator [(#1242)](https://github.com/entropyxyz/entropy-core/pull/1242)
+- Key backup / recovery feature for entropy-tss [(#1249)](https://github.com/entropyxyz/entropy-core/pull/1249)
 
 ### Changed
 - Use correct key rotation endpoint in OCW ([#1104](https://github.com/entropyxyz/entropy-core/pull/1104))

From dcdaa67e1f4a6a5d50ef2cdab7ad99e72e03ea33 Mon Sep 17 00:00:00 2001
From: peg <ameba23@systemli.org>
Date: Tue, 21 Jan 2025 13:30:17 +0100
Subject: [PATCH 61/77] Changelog

---
 CHANGELOG.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0ce513c85..cdaec0ad5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -50,7 +50,7 @@ runtime
 - Test CLI command to retrieve quote and change endpoint / TSS account in one command ([#1198](https://github.com/entropyxyz/entropy-core/pull/1198))
 - On-chain unresponsiveness reporting [(#1215)](https://github.com/entropyxyz/entropy-core/pull/1215)
 - Add cli options for adding validator [(#1242)](https://github.com/entropyxyz/entropy-core/pull/1242)
-- Key backup / recovery feature for entropy-tss [(#1249)](https://github.com/entropyxyz/entropy-core/pull/1249)
+- Database encryption key backup / recovery feature for entropy-tss [(#1249)](https://github.com/entropyxyz/entropy-core/pull/1249)
 
 ### Changed
 - Use correct key rotation endpoint in OCW ([#1104](https://github.com/entropyxyz/entropy-core/pull/1104))

From 054bcbc46d39f530e61b3881d44287196fe327bc Mon Sep 17 00:00:00 2001
From: peg <ameba23@systemli.org>
Date: Wed, 22 Jan 2025 09:10:05 +0100
Subject: [PATCH 62/77] Doccomments following review

---
 crates/shared/src/types.rs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/crates/shared/src/types.rs b/crates/shared/src/types.rs
index c04ebf389..354cf8b27 100644
--- a/crates/shared/src/types.rs
+++ b/crates/shared/src/types.rs
@@ -144,7 +144,7 @@ pub enum QuoteContext {
     ChangeEndpoint,
     /// To be used in the `change_threshold_accounts` extrinsic
     ChangeThresholdAccounts,
-    /// To be use when requesting to recover an encryption key
+    /// To be used when requesting to recover an encryption key
     EncryptionKeyRecoveryRequest,
 }
 

From b33d2614c5d0715f81e2b807ce87c49b90cb8764 Mon Sep 17 00:00:00 2001
From: peg <ameba23@systemli.org>
Date: Wed, 22 Jan 2025 09:10:30 +0100
Subject: [PATCH 63/77] Choose backup provider randomly, not from TSS id

---
 .../threshold-signature-server/src/backup_provider/api.rs | 8 +++-----
 1 file changed, 3 insertions(+), 5 deletions(-)

diff --git a/crates/threshold-signature-server/src/backup_provider/api.rs b/crates/threshold-signature-server/src/backup_provider/api.rs
index d3c565aab..f179a1223 100644
--- a/crates/threshold-signature-server/src/backup_provider/api.rs
+++ b/crates/threshold-signature-server/src/backup_provider/api.rs
@@ -23,7 +23,7 @@ use crate::{
 use axum::{extract::State, Json};
 use entropy_client::substrate::query_chain;
 use entropy_shared::{user::ValidatorInfo, QuoteContext, QuoteInputData, X25519PublicKey};
-use rand::{rngs::StdRng, Rng, RngCore, SeedableRng};
+use rand::{seq::SliceRandom, RngCore};
 use rand_core::OsRng;
 use serde::{Deserialize, Serialize};
 use sp_core::{sr25519, Pair};
@@ -270,10 +270,8 @@ async fn select_backup_provider(
         return Err(BackupProviderError::NoValidators);
     }
 
-    // Choose one deterministically based on account ID
-    let mut deterministic_rng = StdRng::from_seed(tss_account.0);
-    let random_index = deterministic_rng.gen_range(0..validators.len());
-    let validator = &validators[random_index];
+    // Choose one randomly
+    let validator = validators.choose(&mut OsRng).unwrap();
 
     // Get associated details
     let threshold_address_query =

From 8c513cc0cfd029545253891e71e232be8580e8e0 Mon Sep 17 00:00:00 2001
From: peg <ameba23@systemli.org>
Date: Wed, 22 Jan 2025 09:52:04 +0100
Subject: [PATCH 64/77] Refactor duplicated quote verifying fn

---
 Cargo.lock                                    |   1 +
 crates/client/src/client.rs                   |   2 +-
 crates/shared/Cargo.toml                      |   3 +
 crates/shared/src/attestation.rs              | 198 ++++++++++++++++++
 crates/shared/src/lib.rs                      |   1 +
 crates/shared/src/types.rs                    | 148 -------------
 crates/threshold-signature-server/Cargo.toml  |   2 +-
 .../src/attestation/api.rs                    |  46 +---
 .../src/attestation/errors.rs                 |   2 +-
 .../src/backup_provider/api.rs                |   8 +-
 .../src/backup_provider/errors.rs             |   2 +-
 pallets/attestation/Cargo.toml                |   2 +-
 pallets/attestation/src/lib.rs                |  35 +---
 pallets/staking/src/lib.rs                    |  15 +-
 14 files changed, 238 insertions(+), 227 deletions(-)
 create mode 100644 crates/shared/src/attestation.rs

diff --git a/Cargo.lock b/Cargo.lock
index d52c742e6..b75201920 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -2753,6 +2753,7 @@ dependencies = [
  "strum 0.26.3",
  "strum_macros 0.26.4",
  "subxt",
+ "tdx-quote",
 ]
 
 [[package]]
diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs
index f93b3cb84..d795db175 100644
--- a/crates/client/src/client.rs
+++ b/crates/client/src/client.rs
@@ -50,7 +50,7 @@ pub use crate::{
     errors::{ClientError, SubstrateError},
 };
 pub use entropy_protocol::{sign_and_encrypt::EncryptedSignedMessage, KeyParams};
-pub use entropy_shared::{HashingAlgorithm, QuoteContext};
+pub use entropy_shared::{attestation::QuoteContext, HashingAlgorithm};
 use parity_scale_codec::Decode;
 use rand::Rng;
 use std::str::FromStr;
diff --git a/crates/shared/Cargo.toml b/crates/shared/Cargo.toml
index 10cd726f5..a3dce6c90 100644
--- a/crates/shared/Cargo.toml
+++ b/crates/shared/Cargo.toml
@@ -23,6 +23,7 @@ lazy_static={ version="1.5.0", features=["spin_no_std"] }
 hex-literal="0.4.1"
 sp-core    ={ version="29.0.0", default-features=false }
 subxt      ={ version="0.35.3", default-features=false, optional=true }
+tdx-quote  ="0.0.3"
 
 [features]
 default    =["std"]
@@ -31,3 +32,5 @@ wasm       =["codec/std", "scale-info/std", "serde/std", "sp-std/std"]
 wasm-no-std=["sp-runtime"]
 user-native=["dep:subxt", "subxt/native"]
 user-wasm  =["dep:subxt", "subxt/web"]
+# Enables non-mock TDX quote verification
+production =[]
diff --git a/crates/shared/src/attestation.rs b/crates/shared/src/attestation.rs
new file mode 100644
index 000000000..1c43ecd45
--- /dev/null
+++ b/crates/shared/src/attestation.rs
@@ -0,0 +1,198 @@
+// 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 <https://www.gnu.org/licenses/>.
+//! TDX attestion related shared types and functions
+
+use crate::{BoundedVecEncodedVerifyingKey, X25519PublicKey};
+use blake2::{Blake2b512, Digest};
+use codec::{Decode, Encode};
+
+/// Input data to be included in a TDX attestation
+pub struct QuoteInputData(pub [u8; 64]);
+
+impl QuoteInputData {
+    pub fn new<T: Encode>(
+        tss_account_id: T,
+        x25519_public_key: X25519PublicKey,
+        nonce: [u8; 32],
+        context: QuoteContext,
+    ) -> Self {
+        let mut hasher = Blake2b512::new();
+        hasher.update(tss_account_id.encode());
+        hasher.update(x25519_public_key);
+        hasher.update(nonce);
+        hasher.update(context.encode());
+        Self(hasher.finalize().into())
+    }
+}
+
+/// An indicator as to the context in which a quote is intended to be used
+#[derive(Clone, Encode, Decode, Debug, Eq, PartialEq)]
+#[non_exhaustive]
+pub enum QuoteContext {
+    /// To be used in the `validate` extrinsic
+    Validate,
+    /// To be used in the `change_endpoint` extrinsic
+    ChangeEndpoint,
+    /// To be used in the `change_threshold_accounts` extrinsic
+    ChangeThresholdAccounts,
+    /// To be used when requesting to recover an encryption key
+    EncryptionKeyRecoveryRequest,
+}
+
+#[cfg(feature = "std")]
+impl std::fmt::Display for QuoteContext {
+    /// Custom display implementation so that it can be used to build a query string
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self {
+            QuoteContext::Validate => write!(f, "validate"),
+            QuoteContext::ChangeEndpoint => write!(f, "change_endpoint"),
+            QuoteContext::ChangeThresholdAccounts => write!(f, "change_threshold_accounts"),
+            QuoteContext::EncryptionKeyRecoveryRequest => {
+                write!(f, "encryption_key_recovery_request")
+            },
+        }
+    }
+}
+
+#[cfg(feature = "wasm-no-std")]
+use sp_std::vec::Vec;
+
+/// A trait for types which can handle attestation requests.
+#[cfg(not(feature = "wasm"))]
+pub trait AttestationHandler<AccountId> {
+    /// Verify that the given quote is valid and matches the given information about the attestee.
+    /// The Provisioning Certification Key (PCK) certifcate chain is extracted from the quote and
+    /// verified. If successful, the PCK public key used to sign the quote is returned.
+    fn verify_quote(
+        attestee: &AccountId,
+        x25519_public_key: X25519PublicKey,
+        quote: Vec<u8>,
+        context: QuoteContext,
+    ) -> Result<BoundedVecEncodedVerifyingKey, VerifyQuoteError>;
+
+    /// Indicate to the attestation handler that a quote is desired.
+    ///
+    /// The `nonce` should be a piece of data (e.g a random number) which indicates that the quote
+    /// is reasonably fresh and has not been reused.
+    fn request_quote(attestee: &AccountId, nonce: [u8; 32]);
+}
+
+/// A convenience implementation for testing and benchmarking.
+#[cfg(not(feature = "wasm"))]
+impl<AccountId> AttestationHandler<AccountId> for () {
+    fn verify_quote(
+        _attestee: &AccountId,
+        _x25519_public_key: X25519PublicKey,
+        _quote: Vec<u8>,
+        _context: QuoteContext,
+    ) -> Result<BoundedVecEncodedVerifyingKey, VerifyQuoteError> {
+        Ok(BoundedVecEncodedVerifyingKey::try_from([0; 33].to_vec()).unwrap())
+    }
+
+    fn request_quote(_attestee: &AccountId, _nonce: [u8; 32]) {}
+}
+
+/// An error when verifying a quote
+#[cfg(not(feature = "wasm"))]
+#[derive(Debug, Eq, PartialEq)]
+pub enum VerifyQuoteError {
+    /// Quote could not be parsed or verified
+    BadQuote,
+    /// Attestation extrinsic submitted when not requested
+    UnexpectedAttestation,
+    /// Hashed input data does not match what was expected
+    IncorrectInputData,
+    /// Unacceptable VM image running
+    BadMrtdValue,
+    /// Cannot encode verifying key (PCK)
+    CannotEncodeVerifyingKey,
+    /// Cannot decode verifying key (PCK)
+    CannotDecodeVerifyingKey,
+    /// PCK certificate chain cannot be parsed
+    PckCertificateParse,
+    /// PCK certificate chain cannot be verified
+    PckCertificateVerify,
+    /// PCK certificate chain public key is not well formed
+    PckCertificateBadPublicKey,
+    /// Pck certificate could not be extracted from quote
+    PckCertificateNoCertificate,
+}
+
+#[cfg(feature = "std")]
+impl std::fmt::Display for VerifyQuoteError {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        match self {
+            VerifyQuoteError::BadQuote => write!(f, "Quote could not be parsed of verified"),
+            VerifyQuoteError::UnexpectedAttestation => {
+                write!(f, "Attestation extrinsic submitted when not requested")
+            },
+            VerifyQuoteError::IncorrectInputData => {
+                write!(f, "Hashed input data does not match what was expected")
+            },
+            VerifyQuoteError::BadMrtdValue => write!(f, "Unacceptable VM image running"),
+            VerifyQuoteError::CannotEncodeVerifyingKey => {
+                write!(f, "Cannot encode verifying key (PCK)")
+            },
+            VerifyQuoteError::CannotDecodeVerifyingKey => {
+                write!(f, "Cannot decode verifying key (PCK)")
+            },
+            VerifyQuoteError::PckCertificateParse => {
+                write!(f, "PCK certificate chain cannot be parsed")
+            },
+            VerifyQuoteError::PckCertificateVerify => {
+                write!(f, "PCK certificate chain cannot be verified")
+            },
+            VerifyQuoteError::PckCertificateBadPublicKey => {
+                write!(f, "PCK certificate chain public key is not well formed")
+            },
+            VerifyQuoteError::PckCertificateNoCertificate => {
+                write!(f, "PCK certificate could not be extracted from quote")
+            },
+        }
+    }
+}
+
+#[cfg(feature = "std")]
+impl std::error::Error for VerifyQuoteError {}
+
+/// Verify a PCK certificate chain from a quote in production
+#[cfg(feature = "production")]
+pub fn verify_pck_certificate_chain(
+    quote: &tdx_quote::Quote,
+) -> Result<tdx_quote::VerifyingKey, VerifyQuoteError> {
+    quote.verify().map_err(|_| VerifyQuoteError::PckCertificateVerify)
+}
+
+/// A mock version of verifying the PCK certificate chain.
+/// When generating mock quotes, we just put the encoded PCK in place of the certificate chain
+/// so this function just decodes it, checks it was used to sign the quote, and returns it
+#[cfg(not(feature = "production"))]
+pub fn verify_pck_certificate_chain(
+    quote: &tdx_quote::Quote,
+) -> Result<tdx_quote::VerifyingKey, VerifyQuoteError> {
+    let provisioning_certification_key =
+        quote.pck_cert_chain().map_err(|_| VerifyQuoteError::PckCertificateNoCertificate)?;
+    let provisioning_certification_key = tdx_quote::decode_verifying_key(
+        &provisioning_certification_key
+            .try_into()
+            .map_err(|_| VerifyQuoteError::CannotDecodeVerifyingKey)?,
+    )
+    .map_err(|_| VerifyQuoteError::CannotDecodeVerifyingKey)?;
+
+    quote
+        .verify_with_pck(&provisioning_certification_key)
+        .map_err(|_| VerifyQuoteError::PckCertificateVerify)?;
+    Ok(provisioning_certification_key)
+}
diff --git a/crates/shared/src/lib.rs b/crates/shared/src/lib.rs
index 30fb9f855..613dc8d8e 100644
--- a/crates/shared/src/lib.rs
+++ b/crates/shared/src/lib.rs
@@ -20,6 +20,7 @@
 //! This helps ensures those structs are synced among clients and nodes.
 pub use constants::*;
 pub use types::*;
+pub mod attestation;
 pub mod constants;
 pub mod types;
 
diff --git a/crates/shared/src/types.rs b/crates/shared/src/types.rs
index 354cf8b27..8bb4fbd74 100644
--- a/crates/shared/src/types.rs
+++ b/crates/shared/src/types.rs
@@ -12,9 +12,7 @@
 // You should have received a copy of the GNU Affero General Public License
 // along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
-#![allow(dead_code)]
 use super::constants::VERIFICATION_KEY_LENGTH;
-use blake2::{Blake2b512, Digest};
 #[cfg(not(feature = "wasm"))]
 use codec::alloc::vec::Vec;
 use codec::{Decode, Encode};
@@ -114,149 +112,3 @@ pub type EncodedVerifyingKey = [u8; VERIFICATION_KEY_LENGTH as usize];
 #[cfg(not(feature = "wasm"))]
 pub type BoundedVecEncodedVerifyingKey =
     sp_runtime::BoundedVec<u8, sp_runtime::traits::ConstU32<VERIFICATION_KEY_LENGTH>>;
-
-/// Input data to be included in a TDX attestation
-pub struct QuoteInputData(pub [u8; 64]);
-
-impl QuoteInputData {
-    pub fn new<T: Encode>(
-        tss_account_id: T,
-        x25519_public_key: X25519PublicKey,
-        nonce: [u8; 32],
-        context: QuoteContext,
-    ) -> Self {
-        let mut hasher = Blake2b512::new();
-        hasher.update(tss_account_id.encode());
-        hasher.update(x25519_public_key);
-        hasher.update(nonce);
-        hasher.update(context.encode());
-        Self(hasher.finalize().into())
-    }
-}
-
-/// An indicator as to the context in which a quote is intended to be used
-#[derive(Clone, Encode, Decode, Debug, Eq, PartialEq)]
-#[non_exhaustive]
-pub enum QuoteContext {
-    /// To be used in the `validate` extrinsic
-    Validate,
-    /// To be used in the `change_endpoint` extrinsic
-    ChangeEndpoint,
-    /// To be used in the `change_threshold_accounts` extrinsic
-    ChangeThresholdAccounts,
-    /// To be used when requesting to recover an encryption key
-    EncryptionKeyRecoveryRequest,
-}
-
-#[cfg(feature = "std")]
-impl std::fmt::Display for QuoteContext {
-    /// Custom display implementation so that it can be used to build a query string
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        match self {
-            QuoteContext::Validate => write!(f, "validate"),
-            QuoteContext::ChangeEndpoint => write!(f, "change_endpoint"),
-            QuoteContext::ChangeThresholdAccounts => write!(f, "change_threshold_accounts"),
-            QuoteContext::EncryptionKeyRecoveryRequest => {
-                write!(f, "encryption_key_recovery_request")
-            },
-        }
-    }
-}
-
-/// A trait for types which can handle attestation requests.
-#[cfg(not(feature = "wasm"))]
-pub trait AttestationHandler<AccountId> {
-    /// Verify that the given quote is valid and matches the given information about the attestee.
-    /// The Provisioning Certification Key (PCK) certifcate chain is extracted from the quote and
-    /// verified. If successful, the PCK public key used to sign the quote is returned.
-    fn verify_quote(
-        attestee: &AccountId,
-        x25519_public_key: X25519PublicKey,
-        quote: Vec<u8>,
-        context: QuoteContext,
-    ) -> Result<BoundedVecEncodedVerifyingKey, VerifyQuoteError>;
-
-    /// Indicate to the attestation handler that a quote is desired.
-    ///
-    /// The `nonce` should be a piece of data (e.g a random number) which indicates that the quote
-    /// is reasonably fresh and has not been reused.
-    fn request_quote(attestee: &AccountId, nonce: [u8; 32]);
-}
-
-/// A convenience implementation for testing and benchmarking.
-#[cfg(not(feature = "wasm"))]
-impl<AccountId> AttestationHandler<AccountId> for () {
-    fn verify_quote(
-        _attestee: &AccountId,
-        _x25519_public_key: X25519PublicKey,
-        _quote: Vec<u8>,
-        _context: QuoteContext,
-    ) -> Result<BoundedVecEncodedVerifyingKey, VerifyQuoteError> {
-        Ok(BoundedVecEncodedVerifyingKey::try_from([0; 33].to_vec()).unwrap())
-    }
-
-    fn request_quote(_attestee: &AccountId, _nonce: [u8; 32]) {}
-}
-
-/// An error when verifying a quote
-#[cfg(not(feature = "wasm"))]
-#[derive(Debug, Eq, PartialEq)]
-pub enum VerifyQuoteError {
-    /// Quote could not be parsed or verified
-    BadQuote,
-    /// Attestation extrinsic submitted when not requested
-    UnexpectedAttestation,
-    /// Hashed input data does not match what was expected
-    IncorrectInputData,
-    /// Unacceptable VM image running
-    BadMrtdValue,
-    /// Cannot encode verifying key (PCK)
-    CannotEncodeVerifyingKey,
-    /// Cannot decode verifying key (PCK)
-    CannotDecodeVerifyingKey,
-    /// PCK certificate chain cannot be parsed
-    PckCertificateParse,
-    /// PCK certificate chain cannot be verified
-    PckCertificateVerify,
-    /// PCK certificate chain public key is not well formed
-    PckCertificateBadPublicKey,
-    /// Pck certificate could not be extracted from quote
-    PckCertificateNoCertificate,
-}
-
-#[cfg(feature = "std")]
-impl std::fmt::Display for VerifyQuoteError {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        match self {
-            VerifyQuoteError::BadQuote => write!(f, "Quote could not be parsed of verified"),
-            VerifyQuoteError::UnexpectedAttestation => {
-                write!(f, "Attestation extrinsic submitted when not requested")
-            },
-            VerifyQuoteError::IncorrectInputData => {
-                write!(f, "Hashed input data does not match what was expected")
-            },
-            VerifyQuoteError::BadMrtdValue => write!(f, "Unacceptable VM image running"),
-            VerifyQuoteError::CannotEncodeVerifyingKey => {
-                write!(f, "Cannot encode verifying key (PCK)")
-            },
-            VerifyQuoteError::CannotDecodeVerifyingKey => {
-                write!(f, "Cannot decode verifying key (PCK)")
-            },
-            VerifyQuoteError::PckCertificateParse => {
-                write!(f, "PCK certificate chain cannot be parsed")
-            },
-            VerifyQuoteError::PckCertificateVerify => {
-                write!(f, "PCK certificate chain cannot be verified")
-            },
-            VerifyQuoteError::PckCertificateBadPublicKey => {
-                write!(f, "PCK certificate chain public key is not well formed")
-            },
-            VerifyQuoteError::PckCertificateNoCertificate => {
-                write!(f, "PCK certificate could not be extracted from quote")
-            },
-        }
-    }
-}
-
-#[cfg(feature = "std")]
-impl std::error::Error for VerifyQuoteError {}
diff --git a/crates/threshold-signature-server/Cargo.toml b/crates/threshold-signature-server/Cargo.toml
index f0471d3fd..276f2ad21 100644
--- a/crates/threshold-signature-server/Cargo.toml
+++ b/crates/threshold-signature-server/Cargo.toml
@@ -108,7 +108,7 @@ default     =["std", "dep:tdx-quote"]
 std         =["sp-core/std"]
 test_helpers=["dep:project-root"]
 unsafe      =[]
-production  =["std", "dep:configfs-tsm"]
+production  =["std", "dep:configfs-tsm", "entropy-shared/production"]
 alice       =[]
 bob         =[]
 # Enable this feature to run the integration tests for the wasm API of entropy-protocol
diff --git a/crates/threshold-signature-server/src/attestation/api.rs b/crates/threshold-signature-server/src/attestation/api.rs
index 81eab945c..d19cdf1eb 100644
--- a/crates/threshold-signature-server/src/attestation/api.rs
+++ b/crates/threshold-signature-server/src/attestation/api.rs
@@ -29,11 +29,14 @@ use axum::{
 };
 use entropy_client::{chain_api::EntropyConfig, user::request_attestation};
 use entropy_kvdb::kv_manager::KvManager;
-use entropy_shared::{OcwMessageAttestationRequest, QuoteContext, VerifyQuoteError};
+use entropy_shared::{
+    attestation::{QuoteContext, QuoteInputData, VerifyQuoteError},
+    OcwMessageAttestationRequest,
+};
 use parity_scale_codec::Decode;
 use serde::Deserialize;
 use subxt::{backend::legacy::LegacyRpcMethods, OnlineClient};
-use tdx_quote::{Quote, VerifyingKey};
+use tdx_quote::Quote;
 use x25519_dalek::StaticSecret;
 
 /// HTTP POST endpoint to initiate a TDX attestation.
@@ -125,12 +128,8 @@ pub async fn create_quote(
 
     let public_key = x25519_dalek::PublicKey::from(x25519_secret);
 
-    let input_data = entropy_shared::QuoteInputData::new(
-        tss_account.clone(),
-        *public_key.as_bytes(),
-        nonce,
-        context,
-    );
+    let input_data =
+        QuoteInputData::new(tss_account.clone(), *public_key.as_bytes(), nonce, context);
 
     // This is generated deterministically from TSS account id
     let mut pck_seeder = StdRng::from_seed(tss_account.0);
@@ -182,8 +181,7 @@ pub async fn create_quote(
 ) -> Result<Vec<u8>, AttestationErr> {
     let public_key = x25519_dalek::PublicKey::from(x25519_secret);
 
-    let input_data =
-        entropy_shared::QuoteInputData::new(tss_account, *public_key.as_bytes(), nonce, context);
+    let input_data = QuoteInputData::new(tss_account, *public_key.as_bytes(), nonce, context);
 
     Ok(configfs_tsm::create_quote(input_data.0)
         .map_err(|e| AttestationErr::QuoteGeneration(format!("{:?}", e)))?)
@@ -210,32 +208,6 @@ impl QuoteContextQuery {
     }
 }
 
-/// Verify a PCK certificate chain from a quote in production
-#[cfg(feature = "production")]
-pub fn verify_pck_certificate_chain(quote: &Quote) -> Result<VerifyingKey, VerifyQuoteError> {
-    quote.verify().map_err(|_| VerifyQuoteError::PckCertificateVerify)
-}
-
-/// A mock version of verifying the PCK certificate chain.
-/// When generating mock quotes, we just put the encoded PCK in place of the certificate chain
-/// so this function just decodes it, checks it was used to sign the quote, and returns it
-#[cfg(not(feature = "production"))]
-pub fn verify_pck_certificate_chain(quote: &Quote) -> Result<VerifyingKey, VerifyQuoteError> {
-    let provisioning_certification_key =
-        quote.pck_cert_chain().map_err(|_| VerifyQuoteError::PckCertificateNoCertificate)?;
-    let provisioning_certification_key = tdx_quote::decode_verifying_key(
-        &provisioning_certification_key
-            .try_into()
-            .map_err(|_| VerifyQuoteError::CannotDecodeVerifyingKey)?,
-    )
-    .map_err(|_| VerifyQuoteError::CannotDecodeVerifyingKey)?;
-
-    quote
-        .verify_with_pck(&provisioning_certification_key)
-        .map_err(|_| VerifyQuoteError::PckCertificateVerify)?;
-    Ok(provisioning_certification_key)
-}
-
 /// Check build-time measurement matches a current-supported release of entropy-tss
 /// This differs slightly from the attestation pallet implementation because here we don't have direct
 /// access to the parameters pallet - we need to make a query
@@ -253,7 +225,7 @@ pub async fn check_quote_measurement(
         .map(|v| v.0)
         .collect();
     if !accepted_mrtd_values.contains(&mrtd_value) {
-        return Err(entropy_shared::VerifyQuoteError::BadMrtdValue.into());
+        return Err(VerifyQuoteError::BadMrtdValue.into());
     };
     Ok(())
 }
diff --git a/crates/threshold-signature-server/src/attestation/errors.rs b/crates/threshold-signature-server/src/attestation/errors.rs
index 72b496f0c..ee9a6ff76 100644
--- a/crates/threshold-signature-server/src/attestation/errors.rs
+++ b/crates/threshold-signature-server/src/attestation/errors.rs
@@ -73,5 +73,5 @@ pub enum QuoteMeasurementErr {
     #[error("Could not get accepted measurement values from on-chain parameters")]
     NoMeasurementValues,
     #[error("Quote verification: {0}")]
-    Kv(#[from] entropy_shared::VerifyQuoteError),
+    Kv(#[from] entropy_shared::attestation::VerifyQuoteError),
 }
diff --git a/crates/threshold-signature-server/src/backup_provider/api.rs b/crates/threshold-signature-server/src/backup_provider/api.rs
index f179a1223..585f7aad9 100644
--- a/crates/threshold-signature-server/src/backup_provider/api.rs
+++ b/crates/threshold-signature-server/src/backup_provider/api.rs
@@ -14,7 +14,7 @@
 // along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
 use crate::{
-    attestation::api::{check_quote_measurement, create_quote, verify_pck_certificate_chain},
+    attestation::api::{check_quote_measurement, create_quote},
     backup_provider::errors::BackupProviderError,
     chain_api::entropy,
     validation::EncryptedSignedMessage,
@@ -22,7 +22,11 @@ use crate::{
 };
 use axum::{extract::State, Json};
 use entropy_client::substrate::query_chain;
-use entropy_shared::{user::ValidatorInfo, QuoteContext, QuoteInputData, X25519PublicKey};
+use entropy_shared::{
+    attestation::{verify_pck_certificate_chain, QuoteContext, QuoteInputData},
+    user::ValidatorInfo,
+    X25519PublicKey,
+};
 use rand::{seq::SliceRandom, RngCore};
 use rand_core::OsRng;
 use serde::{Deserialize, Serialize};
diff --git a/crates/threshold-signature-server/src/backup_provider/errors.rs b/crates/threshold-signature-server/src/backup_provider/errors.rs
index e96e2ca9a..dda19e8f4 100644
--- a/crates/threshold-signature-server/src/backup_provider/errors.rs
+++ b/crates/threshold-signature-server/src/backup_provider/errors.rs
@@ -57,7 +57,7 @@ pub enum BackupProviderError {
     #[error("Bad quote input data: TSS account, response public key, or nonce are incorrect")]
     BadQuoteInputData,
     #[error("Quote verify: {0}")]
-    VerifyQuote(#[from] entropy_shared::VerifyQuoteError),
+    VerifyQuote(#[from] entropy_shared::attestation::VerifyQuoteError),
     #[error("Could not find another TSS node to request backup")]
     NoValidators,
     #[error("Could not get server info for TSS node chosen for backup")]
diff --git a/pallets/attestation/Cargo.toml b/pallets/attestation/Cargo.toml
index 2fa933c84..dfbf31b7f 100644
--- a/pallets/attestation/Cargo.toml
+++ b/pallets/attestation/Cargo.toml
@@ -61,4 +61,4 @@ std=[
 ]
 try-runtime=['frame-support/try-runtime']
 # When enabled, use real PCK certificate chain verification
-production=[]
+production=['entropy-shared/production']
diff --git a/pallets/attestation/src/lib.rs b/pallets/attestation/src/lib.rs
index 1d4f08c6f..7731ef1e9 100644
--- a/pallets/attestation/src/lib.rs
+++ b/pallets/attestation/src/lib.rs
@@ -47,7 +47,10 @@ mod tests;
 
 #[frame_support::pallet]
 pub mod pallet {
-    use entropy_shared::{AttestationHandler, QuoteContext, QuoteInputData, VerifyQuoteError};
+    use entropy_shared::attestation::{
+        verify_pck_certificate_chain, AttestationHandler, QuoteContext, QuoteInputData,
+        VerifyQuoteError,
+    };
     use frame_support::pallet_prelude::*;
     use frame_support::traits::Randomness;
     use frame_system::pallet_prelude::*;
@@ -58,7 +61,7 @@ pub mod pallet {
         rand_core::{RngCore, SeedableRng},
         ChaCha20Rng, ChaChaRng,
     };
-    use tdx_quote::{encode_verifying_key, Quote, VerifyingKey};
+    use tdx_quote::{encode_verifying_key, Quote};
 
     pub use crate::weights::WeightInfo;
 
@@ -203,7 +206,7 @@ pub mod pallet {
         }
     }
 
-    impl<T: Config> entropy_shared::AttestationHandler<T::AccountId> for Pallet<T> {
+    impl<T: Config> AttestationHandler<T::AccountId> for Pallet<T> {
         fn verify_quote(
             attestee: &T::AccountId,
             x25519_public_key: entropy_shared::X25519PublicKey,
@@ -250,30 +253,4 @@ pub mod pallet {
             PendingAttestations::<T>::insert(who, nonce)
         }
     }
-
-    #[cfg(feature = "production")]
-    fn verify_pck_certificate_chain(quote: &Quote) -> Result<VerifyingKey, VerifyQuoteError> {
-        quote.verify().map_err(|_| VerifyQuoteError::PckCertificateVerify)
-    }
-
-    /// A mock version of verifying the PCK certificate chain.
-    /// When generating mock quotes, we just put the encoded PCK in place of the certificate chain
-    /// so this function just decodes it, checks it was used to sign the quote, and returns it
-    #[cfg(not(feature = "production"))]
-    fn verify_pck_certificate_chain(quote: &Quote) -> Result<VerifyingKey, VerifyQuoteError> {
-        let provisioning_certification_key =
-            quote.pck_cert_chain().map_err(|_| VerifyQuoteError::PckCertificateNoCertificate)?;
-        let provisioning_certification_key = tdx_quote::decode_verifying_key(
-            &provisioning_certification_key
-                .try_into()
-                .map_err(|_| VerifyQuoteError::CannotDecodeVerifyingKey)?,
-        )
-        .map_err(|_| VerifyQuoteError::CannotDecodeVerifyingKey)?;
-
-        ensure!(
-            quote.verify_with_pck(&provisioning_certification_key).is_ok(),
-            VerifyQuoteError::PckCertificateVerify
-        );
-        Ok(provisioning_certification_key)
-    }
 }
diff --git a/pallets/staking/src/lib.rs b/pallets/staking/src/lib.rs
index 516e6a3e6..a227fcaa0 100644
--- a/pallets/staking/src/lib.rs
+++ b/pallets/staking/src/lib.rs
@@ -58,8 +58,9 @@ use sp_staking::SessionIndex;
 #[frame_support::pallet]
 pub mod pallet {
     use entropy_shared::{
-        QuoteContext, ValidatorInfo, VerifyQuoteError, X25519PublicKey, MAX_SIGNERS,
-        PREGENERATED_NETWORK_VERIFYING_KEY, VERIFICATION_KEY_LENGTH,
+        attestation::{AttestationHandler, QuoteContext, VerifyQuoteError},
+        ValidatorInfo, X25519PublicKey, MAX_SIGNERS, PREGENERATED_NETWORK_VERIFYING_KEY,
+        VERIFICATION_KEY_LENGTH,
     };
     use frame_support::{
         dispatch::{DispatchResult, DispatchResultWithPostInfo},
@@ -105,7 +106,7 @@ pub mod pallet {
         type MaxEndpointLength: Get<u32>;
 
         /// The handler to use when issuing and verifying attestations.
-        type AttestationHandler: entropy_shared::AttestationHandler<Self::AccountId>;
+        type AttestationHandler: AttestationHandler<Self::AccountId>;
     }
 
     /// Endpoint where a threshold server can be reached at
@@ -433,7 +434,9 @@ pub mod pallet {
                 if let Some(server_info) = maybe_server_info {
                     // Before we modify the `server_info`, we want to check that the validator is
                     // still running TDX hardware.
-                    <T::AttestationHandler as entropy_shared::AttestationHandler<_>>::verify_quote(
+                    <T::AttestationHandler as entropy_shared::attestation::AttestationHandler<
+                        _,
+                    >>::verify_quote(
                         &server_info.tss_account.clone(),
                         server_info.x25519_public_key,
                         quote,
@@ -497,7 +500,7 @@ pub mod pallet {
                         // Before we modify the `server_info`, we want to check that the validator is
                         // still running TDX hardware.
                         let provisioning_certification_key =
-                            <T::AttestationHandler as entropy_shared::AttestationHandler<_>>::verify_quote(
+                            <T::AttestationHandler as entropy_shared::attestation::AttestationHandler<_>>::verify_quote(
                                 &tss_account.clone(),
                                 x25519_public_key,
                                 quote,
@@ -632,7 +635,7 @@ pub mod pallet {
             );
 
             let provisioning_certification_key =
-                <T::AttestationHandler as entropy_shared::AttestationHandler<_>>::verify_quote(
+                <T::AttestationHandler as entropy_shared::attestation::AttestationHandler<_>>::verify_quote(
                     &joining_server_info.tss_account.clone(),
                     joining_server_info.x25519_public_key,
                     quote,

From d1ecf5ba8e132960a3c1369cd9399bafbe909bf8 Mon Sep 17 00:00:00 2001
From: peg <ameba23@systemli.org>
Date: Wed, 22 Jan 2025 09:57:17 +0100
Subject: [PATCH 65/77] Taplo

---
 crates/shared/Cargo.toml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/crates/shared/Cargo.toml b/crates/shared/Cargo.toml
index a3dce6c90..40e39bee8 100644
--- a/crates/shared/Cargo.toml
+++ b/crates/shared/Cargo.toml
@@ -33,4 +33,4 @@ wasm-no-std=["sp-runtime"]
 user-native=["dep:subxt", "subxt/native"]
 user-wasm  =["dep:subxt", "subxt/web"]
 # Enables non-mock TDX quote verification
-production =[]
+production=[]

From 05f5f3c252d15f6b19a6efaa089b7bfae2d81601 Mon Sep 17 00:00:00 2001
From: peg <ameba23@systemli.org>
Date: Wed, 22 Jan 2025 10:16:42 +0100
Subject: [PATCH 66/77] Update test-cli

---
 crates/test-cli/src/lib.rs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/crates/test-cli/src/lib.rs b/crates/test-cli/src/lib.rs
index d30afc313..528798578 100644
--- a/crates/test-cli/src/lib.rs
+++ b/crates/test-cli/src/lib.rs
@@ -34,7 +34,7 @@ use entropy_client::{
         VERIFYING_KEY_LENGTH,
     },
 };
-pub use entropy_shared::{QuoteContext, PROGRAM_VERSION_NUMBER};
+pub use entropy_shared::{attestation::QuoteContext, PROGRAM_VERSION_NUMBER};
 use parity_scale_codec::Decode;
 use sp_core::{sr25519, Hasher, Pair};
 use sp_runtime::{traits::BlakeTwo256, Serialize};

From 1de72b62c8aa98c6225491030693955ee35bc9ab Mon Sep 17 00:00:00 2001
From: peg <ameba23@systemli.org>
Date: Wed, 22 Jan 2025 10:46:18 +0100
Subject: [PATCH 67/77] Fix for building entropy-shared for wasm

---
 crates/shared/src/attestation.rs | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/crates/shared/src/attestation.rs b/crates/shared/src/attestation.rs
index 1c43ecd45..43d8870cc 100644
--- a/crates/shared/src/attestation.rs
+++ b/crates/shared/src/attestation.rs
@@ -14,7 +14,7 @@
 // along with this program.  If not, see <https://www.gnu.org/licenses/>.
 //! TDX attestion related shared types and functions
 
-use crate::{BoundedVecEncodedVerifyingKey, X25519PublicKey};
+use crate::X25519PublicKey;
 use blake2::{Blake2b512, Digest};
 use codec::{Decode, Encode};
 
@@ -80,7 +80,7 @@ pub trait AttestationHandler<AccountId> {
         x25519_public_key: X25519PublicKey,
         quote: Vec<u8>,
         context: QuoteContext,
-    ) -> Result<BoundedVecEncodedVerifyingKey, VerifyQuoteError>;
+    ) -> Result<crate::BoundedVecEncodedVerifyingKey, VerifyQuoteError>;
 
     /// Indicate to the attestation handler that a quote is desired.
     ///
@@ -97,8 +97,8 @@ impl<AccountId> AttestationHandler<AccountId> for () {
         _x25519_public_key: X25519PublicKey,
         _quote: Vec<u8>,
         _context: QuoteContext,
-    ) -> Result<BoundedVecEncodedVerifyingKey, VerifyQuoteError> {
-        Ok(BoundedVecEncodedVerifyingKey::try_from([0; 33].to_vec()).unwrap())
+    ) -> Result<crate::BoundedVecEncodedVerifyingKey, VerifyQuoteError> {
+        Ok(crate::BoundedVecEncodedVerifyingKey::try_from([0; 33].to_vec()).unwrap())
     }
 
     fn request_quote(_attestee: &AccountId, _nonce: [u8; 32]) {}
@@ -178,7 +178,7 @@ pub fn verify_pck_certificate_chain(
 /// A mock version of verifying the PCK certificate chain.
 /// When generating mock quotes, we just put the encoded PCK in place of the certificate chain
 /// so this function just decodes it, checks it was used to sign the quote, and returns it
-#[cfg(not(feature = "production"))]
+#[cfg(not(any(feature = "production", feature = "wasm")))]
 pub fn verify_pck_certificate_chain(
     quote: &tdx_quote::Quote,
 ) -> Result<tdx_quote::VerifyingKey, VerifyQuoteError> {

From 9991f6d21d95a1cf3ae1b33f1a7212ab46c1fd0a Mon Sep 17 00:00:00 2001
From: peg <ameba23@systemli.org>
Date: Wed, 22 Jan 2025 12:31:00 +0100
Subject: [PATCH 68/77] Fix staking pallet mock

---
 pallets/staking/src/mock.rs | 14 +++++++++-----
 1 file changed, 9 insertions(+), 5 deletions(-)

diff --git a/pallets/staking/src/mock.rs b/pallets/staking/src/mock.rs
index 85e217f74..f7f13aab5 100644
--- a/pallets/staking/src/mock.rs
+++ b/pallets/staking/src/mock.rs
@@ -16,7 +16,7 @@
 use core::convert::{TryFrom, TryInto};
 use std::cell::RefCell;
 
-use entropy_shared::QuoteContext;
+use entropy_shared::attestation::QuoteContext;
 use frame_election_provider_support::{
     bounds::{ElectionBounds, ElectionBoundsBuilder},
     onchain, SequentialPhragmen, VoteWeight,
@@ -395,18 +395,22 @@ pub(crate) const INVALID_QUOTE: [u8; 32] = [1; 32];
 
 pub struct MockAttestationHandler;
 
-impl entropy_shared::AttestationHandler<AccountId> for MockAttestationHandler {
+impl entropy_shared::attestation::AttestationHandler<AccountId> for MockAttestationHandler {
     fn verify_quote(
         _attestee: &AccountId,
         _x25519_public_key: entropy_shared::X25519PublicKey,
         quote: Vec<u8>,
         _context: QuoteContext,
-    ) -> Result<entropy_shared::BoundedVecEncodedVerifyingKey, entropy_shared::VerifyQuoteError>
-    {
+    ) -> Result<
+        entropy_shared::BoundedVecEncodedVerifyingKey,
+        entropy_shared::attestation::VerifyQuoteError,
+    > {
         let quote: Result<[u8; 32], _> = quote.try_into();
         match quote {
             Ok(q) if q == VALID_QUOTE => Ok([0; 33].to_vec().try_into().unwrap()),
-            Ok(q) if q == INVALID_QUOTE => Err(entropy_shared::VerifyQuoteError::BadQuote),
+            Ok(q) if q == INVALID_QUOTE => {
+                Err(entropy_shared::attestation::VerifyQuoteError::BadQuote)
+            },
             _ => {
                 // We don't really want to verify quotes for tests in this pallet, so if we get
                 // something else we'll just accept it.

From c4c7e426e1bc6ae4c6c51cb33740e6441e606524 Mon Sep 17 00:00:00 2001
From: peg <ameba23@systemli.org>
Date: Wed, 22 Jan 2025 12:48:25 +0100
Subject: [PATCH 69/77] Fix staking pallet benchmarks

---
 pallets/staking/src/benchmarking.rs | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/pallets/staking/src/benchmarking.rs b/pallets/staking/src/benchmarking.rs
index 8b59b3e0a..fbb4e29c0 100644
--- a/pallets/staking/src/benchmarking.rs
+++ b/pallets/staking/src/benchmarking.rs
@@ -18,7 +18,10 @@
 use super::*;
 #[allow(unused_imports)]
 use crate::Pallet as Staking;
-use entropy_shared::{AttestationHandler, QuoteContext, MAX_SIGNERS};
+use entropy_shared::{
+    attestation::{AttestationHandler, QuoteContext},
+    MAX_SIGNERS,
+};
 use frame_benchmarking::{account, benchmarks, impl_benchmark_test_suite, whitelisted_caller};
 use frame_support::{
     assert_ok, ensure,
@@ -107,7 +110,7 @@ fn prepare_attestation_for_validate<T: Config>(
 
         let attestation_key = tdx_quote::SigningKey::from_bytes(&ATTESTATION_KEY.into()).unwrap();
 
-        let input_data = entropy_shared::QuoteInputData::new(
+        let input_data = entropy_shared::attestation::QuoteInputData::new(
             &threshold,
             x25519_public_key,
             nonce,

From 57924d9350ee5a7874a9a09388960eb6d19bc19b Mon Sep 17 00:00:00 2001
From: peg <ameba23@systemli.org>
Date: Wed, 22 Jan 2025 13:35:45 +0100
Subject: [PATCH 70/77] Fix attestation pallet tests

---
 pallets/attestation/src/tests.rs | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/pallets/attestation/src/tests.rs b/pallets/attestation/src/tests.rs
index 7b57f452f..8e5d985bd 100644
--- a/pallets/attestation/src/tests.rs
+++ b/pallets/attestation/src/tests.rs
@@ -14,7 +14,9 @@
 // along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
 use crate::mock::*;
-use entropy_shared::{AttestationHandler, QuoteContext, QuoteInputData, VerifyQuoteError};
+use entropy_shared::attestation::{
+    AttestationHandler, QuoteContext, QuoteInputData, VerifyQuoteError,
+};
 use frame_support::{assert_noop, assert_ok};
 use rand_core::OsRng;
 

From 0822704340a381719edbde3a0a94d5937dc530b0 Mon Sep 17 00:00:00 2001
From: peg <ameba23@systemli.org>
Date: Wed, 22 Jan 2025 13:58:50 +0100
Subject: [PATCH 71/77] Fix client tests

---
 crates/client/src/tests.rs | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/crates/client/src/tests.rs b/crates/client/src/tests.rs
index a4633cb39..6560201fd 100644
--- a/crates/client/src/tests.rs
+++ b/crates/client/src/tests.rs
@@ -19,7 +19,7 @@ use crate::{
     update_programs,
 };
 
-use entropy_shared::{QuoteContext, QuoteInputData};
+use entropy_shared::attestation::{QuoteContext, QuoteInputData};
 use entropy_testing_utils::{
     constants::{TEST_PROGRAM_WASM_BYTECODE, TSS_ACCOUNTS, X25519_PUBLIC_KEYS},
     helpers::{encode_verifying_key, spawn_tss_nodes_and_start_chain},
@@ -129,7 +129,7 @@ async fn test_change_threshold_accounts() {
     let encoded_pck = encode_verifying_key(&pck.verifying_key()).unwrap().to_vec();
 
     let quote = {
-        let input_data = entropy_shared::QuoteInputData::new(
+        let input_data = QuoteInputData::new(
             tss_public_key,
             *x25519_public_key.as_bytes(),
             nonce,
@@ -368,7 +368,7 @@ async fn test_set_session_key_and_declare_validate() {
     let encoded_pck = encode_verifying_key(&pck.verifying_key()).unwrap().to_vec();
 
     let quote = {
-        let input_data = entropy_shared::QuoteInputData::new(
+        let input_data = QuoteInputData::new(
             tss_public_key,
             *x25519_public_key.as_bytes(),
             nonce,

From 9c68e94452855bcfc0119524b273b0844f62d69d Mon Sep 17 00:00:00 2001
From: peg <ameba23@systemli.org>
Date: Thu, 23 Jan 2025 13:43:47 +0100
Subject: [PATCH 72/77] Use sp_core::crypto::AccountId32 as key for hashmap to
 be more explicit

---
 .../src/backup_provider/api.rs                        | 11 ++++++-----
 crates/threshold-signature-server/src/lib.rs          |  7 ++++---
 2 files changed, 10 insertions(+), 8 deletions(-)

diff --git a/crates/threshold-signature-server/src/backup_provider/api.rs b/crates/threshold-signature-server/src/backup_provider/api.rs
index 585f7aad9..0905d6c4f 100644
--- a/crates/threshold-signature-server/src/backup_provider/api.rs
+++ b/crates/threshold-signature-server/src/backup_provider/api.rs
@@ -155,13 +155,14 @@ pub async fn backup_encryption_key(
 
     // Decrypt the request body to get the key to be backed-up
     let signed_message = encrypted_backup_request.decrypt(&app_state.x25519_secret, &[])?;
+    let tss_account = signed_message.account_id();
     let key: [u8; 32] =
         signed_message.message.0.try_into().map_err(|_| BackupProviderError::BadKeyLength)?;
 
-    let tss_account = SubxtAccountId32(signed_message.sender.0);
     // Check for TSS account on the staking pallet - which proves they have made an on-chain attestation
-    let threshold_address_query =
-        entropy::storage().staking_extension().threshold_to_stash(&tss_account);
+    let threshold_address_query = entropy::storage()
+        .staking_extension()
+        .threshold_to_stash(&SubxtAccountId32(*tss_account.as_ref()));
     let (api, rpc) = app_state.get_api_rpc().await?;
     query_chain(&api, &rpc, threshold_address_query, None)
         .await?
@@ -169,7 +170,7 @@ pub async fn backup_encryption_key(
 
     let mut backups =
         app_state.encryption_key_backups.write().map_err(|_| BackupProviderError::RwLockPoison)?;
-    backups.insert(tss_account.0, key);
+    backups.insert(tss_account, key);
 
     Ok(())
 }
@@ -214,7 +215,7 @@ pub async fn recover_encryption_key(
             .encryption_key_backups
             .read()
             .map_err(|_| BackupProviderError::RwLockPoison)?;
-        *backups.get(&key_request.tss_account.0).ok_or(BackupProviderError::NoKeyInStore)?
+        *backups.get(&key_request.tss_account.0.into()).ok_or(BackupProviderError::NoKeyInStore)?
     };
 
     // Encrypt response
diff --git a/crates/threshold-signature-server/src/lib.rs b/crates/threshold-signature-server/src/lib.rs
index 0aa1d1769..3196308f9 100644
--- a/crates/threshold-signature-server/src/lib.rs
+++ b/crates/threshold-signature-server/src/lib.rs
@@ -177,6 +177,7 @@ use axum::{
     Router,
 };
 use entropy_kvdb::kv_manager::KvManager;
+use entropy_shared::X25519PublicKey;
 use sp_core::{crypto::AccountId32, sr25519, Pair};
 use std::{
     collections::HashMap,
@@ -227,10 +228,10 @@ pub struct AppState {
     pub kv_store: KvManager,
     /// Storage for encryption key backups for other TSS nodes
     /// Maps TSS account id to encryption key
-    pub encryption_key_backups: Arc<RwLock<HashMap<[u8; 32], [u8; 32]>>>,
+    pub encryption_key_backups: Arc<RwLock<HashMap<AccountId32, [u8; 32]>>>,
     /// Storage for quote nonces for other TSS nodes wanting to make encryption key backups
-    /// Maps TSS account ID to quote nonce
-    pub attestation_nonces: Arc<RwLock<HashMap<[u8; 32], [u8; 32]>>>,
+    /// Maps response x25519 public key to quote nonce
+    pub attestation_nonces: Arc<RwLock<HashMap<X25519PublicKey, [u8; 32]>>>,
 }
 
 impl AppState {

From 57d22e2ab79e9a6e12741bd8d5995f3a6c50e488 Mon Sep 17 00:00:00 2001
From: peg <ameba23@systemli.org>
Date: Thu, 23 Jan 2025 13:47:23 +0100
Subject: [PATCH 73/77] Rename struct field following review

---
 .../threshold-signature-server/src/backup_provider/api.rs | 8 +++++---
 crates/threshold-signature-server/src/lib.rs              | 4 ++--
 2 files changed, 7 insertions(+), 5 deletions(-)

diff --git a/crates/threshold-signature-server/src/backup_provider/api.rs b/crates/threshold-signature-server/src/backup_provider/api.rs
index 0905d6c4f..3e4816000 100644
--- a/crates/threshold-signature-server/src/backup_provider/api.rs
+++ b/crates/threshold-signature-server/src/backup_provider/api.rs
@@ -168,8 +168,10 @@ pub async fn backup_encryption_key(
         .await?
         .ok_or(BackupProviderError::NotRegisteredWithStakingPallet)?;
 
-    let mut backups =
-        app_state.encryption_key_backups.write().map_err(|_| BackupProviderError::RwLockPoison)?;
+    let mut backups = app_state
+        .encryption_key_backup_provider
+        .write()
+        .map_err(|_| BackupProviderError::RwLockPoison)?;
     backups.insert(tss_account, key);
 
     Ok(())
@@ -212,7 +214,7 @@ pub async fn recover_encryption_key(
 
     let key = {
         let backups = app_state
-            .encryption_key_backups
+            .encryption_key_backup_provider
             .read()
             .map_err(|_| BackupProviderError::RwLockPoison)?;
         *backups.get(&key_request.tss_account.0.into()).ok_or(BackupProviderError::NoKeyInStore)?
diff --git a/crates/threshold-signature-server/src/lib.rs b/crates/threshold-signature-server/src/lib.rs
index 3196308f9..38a4b1f52 100644
--- a/crates/threshold-signature-server/src/lib.rs
+++ b/crates/threshold-signature-server/src/lib.rs
@@ -228,7 +228,7 @@ pub struct AppState {
     pub kv_store: KvManager,
     /// Storage for encryption key backups for other TSS nodes
     /// Maps TSS account id to encryption key
-    pub encryption_key_backups: Arc<RwLock<HashMap<AccountId32, [u8; 32]>>>,
+    pub encryption_key_backup_provider: Arc<RwLock<HashMap<AccountId32, [u8; 32]>>>,
     /// Storage for quote nonces for other TSS nodes wanting to make encryption key backups
     /// Maps response x25519 public key to quote nonce
     pub attestation_nonces: Arc<RwLock<HashMap<X25519PublicKey, [u8; 32]>>>,
@@ -249,7 +249,7 @@ impl AppState {
             listener_state: ListenerState::default(),
             configuration,
             kv_store,
-            encryption_key_backups: Default::default(),
+            encryption_key_backup_provider: Default::default(),
             attestation_nonces: Default::default(),
         }
     }

From 18105ee25cdff844c535527c512b65f879d1d8a2 Mon Sep 17 00:00:00 2001
From: peg <ameba23@systemli.org>
Date: Thu, 23 Jan 2025 14:00:11 +0100
Subject: [PATCH 74/77] Minor edits from PR review

---
 .../src/backup_provider/api.rs                    | 15 ++++++++++-----
 .../src/helpers/validator.rs                      |  2 +-
 2 files changed, 11 insertions(+), 6 deletions(-)

diff --git a/crates/threshold-signature-server/src/backup_provider/api.rs b/crates/threshold-signature-server/src/backup_provider/api.rs
index 3e4816000..72e30a584 100644
--- a/crates/threshold-signature-server/src/backup_provider/api.rs
+++ b/crates/threshold-signature-server/src/backup_provider/api.rs
@@ -124,20 +124,20 @@ pub async fn request_recover_encryption_key(
     signed_message.message.0.try_into().map_err(|_| BackupProviderError::BadKeyLength)
 }
 
-/// [ValidatorInfo] of a TSS node chosen to make a key backup, together with the account ID of the TSS
-/// node who the backup is for
+/// [ValidatorInfo] of a TSS node chosen to make a key backup, together with the account ID of the
+/// TSS node who the backup is for
 #[derive(Debug, Clone, Serialize, Deserialize)]
 pub struct BackupProviderDetails {
     pub provider: ValidatorInfo,
     pub tss_account: SubxtAccountId32,
 }
 
-/// POST request body for thse `/recover_encryption_key` HTTP route
+/// POST request body for the `/recover_encryption_key` HTTP route
 #[derive(Debug, Clone, Serialize, Deserialize)]
 pub struct RecoverEncryptionKeyRequest {
     /// The account ID of the TSS node requesting to recover their encryption key
     tss_account: SubxtAccountId32,
-    /// An ephemeral encryption public key used to receive and encrypted response
+    /// An ephemeral encryption public key used to receive an encrypted response
     response_key: X25519PublicKey,
     /// A TDX quote
     quote: Vec<u8>,
@@ -179,7 +179,7 @@ pub async fn backup_encryption_key(
 
 /// HTTP endpoint to recover an encryption key following a process restart.
 /// The request body should contain a JSON encoded [RecoverEncryptionKeyRequest].
-/// If successfull, the response body will contain the encryption key as a [u8; 32] wrapped in an
+/// If successful, the response body will contain the encryption key as a [u8; 32] wrapped in an
 /// [EncryptedSignedMessage].
 pub async fn recover_encryption_key(
     State(app_state): State<AppState>,
@@ -287,6 +287,11 @@ async fn select_backup_provider(
         .await?
         .ok_or(BackupProviderError::NoServerInfo)?;
 
+    tracing::info!(
+        "Selected TSS account {} to act as a db encrpytion key backup provider",
+        server_info.tss_account
+    );
+
     Ok(BackupProviderDetails {
         provider: ValidatorInfo {
             x25519_public_key: server_info.x25519_public_key,
diff --git a/crates/threshold-signature-server/src/helpers/validator.rs b/crates/threshold-signature-server/src/helpers/validator.rs
index a484df6d5..f77314d76 100644
--- a/crates/threshold-signature-server/src/helpers/validator.rs
+++ b/crates/threshold-signature-server/src/helpers/validator.rs
@@ -45,7 +45,7 @@ fn get_hkdf_from_mnemonic(mnemonic: &str) -> Result<Hkdf<Sha256>, UserErr> {
     Ok(Hkdf::<Sha256>::new(None, &mnemonic.to_seed("")))
 }
 
-/// Derive signing keypair
+/// Derive signing keypair and return it together with the seed
 pub fn get_signer_from_hkdf(hkdf: &Hkdf<Sha256>) -> Result<(sr25519::Pair, [u8; 32]), UserErr> {
     let mut sr25519_seed = [0u8; 32];
     hkdf.expand(KDF_SR25519, &mut sr25519_seed)?;

From ef46c512895ea4d546df90e435eda22c655236e5 Mon Sep 17 00:00:00 2001
From: peg <ameba23@systemli.org>
Date: Thu, 23 Jan 2025 15:59:25 +0100
Subject: [PATCH 75/77] Clippy

---
 crates/threshold-signature-server/src/backup_provider/api.rs | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/crates/threshold-signature-server/src/backup_provider/api.rs b/crates/threshold-signature-server/src/backup_provider/api.rs
index 72e30a584..2b960ccac 100644
--- a/crates/threshold-signature-server/src/backup_provider/api.rs
+++ b/crates/threshold-signature-server/src/backup_provider/api.rs
@@ -162,7 +162,7 @@ pub async fn backup_encryption_key(
     // Check for TSS account on the staking pallet - which proves they have made an on-chain attestation
     let threshold_address_query = entropy::storage()
         .staking_extension()
-        .threshold_to_stash(&SubxtAccountId32(*tss_account.as_ref()));
+        .threshold_to_stash(SubxtAccountId32(*tss_account.as_ref()));
     let (api, rpc) = app_state.get_api_rpc().await?;
     query_chain(&api, &rpc, threshold_address_query, None)
         .await?

From a31ff0eb3b7357f2fdf629c46ee95cf6f97b54c2 Mon Sep 17 00:00:00 2001
From: peg <ameba23@systemli.org>
Date: Fri, 24 Jan 2025 10:42:56 +0100
Subject: [PATCH 76/77] Flatten HTTP API structure following review

---
 .../threshold-signature-server/src/backup_provider/api.rs | 8 ++++----
 crates/threshold-signature-server/src/lib.rs              | 6 +++---
 2 files changed, 7 insertions(+), 7 deletions(-)

diff --git a/crates/threshold-signature-server/src/backup_provider/api.rs b/crates/threshold-signature-server/src/backup_provider/api.rs
index 2b960ccac..4effc6700 100644
--- a/crates/threshold-signature-server/src/backup_provider/api.rs
+++ b/crates/threshold-signature-server/src/backup_provider/api.rs
@@ -57,7 +57,7 @@ pub async fn request_backup_encryption_key(
     let client = reqwest::Client::new();
     let response = client
         .post(format!(
-            "http://{}/backup_provider/backup_encryption_key",
+            "http://{}/backup_encryption_key",
             backup_provider_details.provider.ip_address
         ))
         .header("Content-Type", "application/json")
@@ -101,7 +101,7 @@ pub async fn request_recover_encryption_key(
     let client = reqwest::Client::new();
     let response = client
         .post(format!(
-            "http://{}/backup_provider/recover_encryption_key",
+            "http://{}/recover_encryption_key",
             backup_provider_details.provider.ip_address
         ))
         .header("Content-Type", "application/json")
@@ -329,7 +329,7 @@ pub async fn quote_nonce(
     Ok(Json(signed_message))
 }
 
-/// Client function used to make a POST request to `backup_provider/quote_nonce`
+/// Client function used to make a POST request to `backup_provider_quote_nonce`
 async fn request_quote_nonce(
     response_secret_key: &StaticSecret,
     backup_provider_details: &BackupProviderDetails,
@@ -339,7 +339,7 @@ async fn request_quote_nonce(
     let client = reqwest::Client::new();
     let response = client
         .post(format!(
-            "http://{}/backup_provider/quote_nonce",
+            "http://{}/backup_provider_quote_nonce",
             backup_provider_details.provider.ip_address
         ))
         .header("Content-Type", "application/json")
diff --git a/crates/threshold-signature-server/src/lib.rs b/crates/threshold-signature-server/src/lib.rs
index 38a4b1f52..43c59faa7 100644
--- a/crates/threshold-signature-server/src/lib.rs
+++ b/crates/threshold-signature-server/src/lib.rs
@@ -310,9 +310,9 @@ pub fn app(app_state: AppState) -> Router {
         .route("/rotate_network_key", post(rotate_network_key))
         .route("/attest", post(attest))
         .route("/attest", get(get_attest))
-        .route("/backup_provider/backup_encryption_key", post(backup_encryption_key))
-        .route("/backup_provider/recover_encryption_key", post(recover_encryption_key))
-        .route("/backup_provider/quote_nonce", post(quote_nonce))
+        .route("/backup_encryption_key", post(backup_encryption_key))
+        .route("/recover_encryption_key", post(recover_encryption_key))
+        .route("/backup_provider_quote_nonce", post(quote_nonce))
         .route("/healthz", get(healthz))
         .route("/version", get(get_version))
         .route("/hashes", get(hashes))

From f6146f41eed2530e7387f144afd8d9eb79e3f5c8 Mon Sep 17 00:00:00 2001
From: peg <peg@magmacollective.org>
Date: Tue, 28 Jan 2025 09:57:13 +0100
Subject: [PATCH 77/77] Add an extra TSS state for connected to chain but not
 funded / fully ready (#1263)

* Add an extra TSS state for connected to chain but not funded / fully ready

* Clippy
---
 .../src/backup_provider/api.rs                | 10 ++-
 .../src/backup_provider/errors.rs             |  2 +
 .../src/helpers/launch.rs                     |  4 +-
 .../src/helpers/tests.rs                      |  4 +-
 crates/threshold-signature-server/src/lib.rs  | 71 +++++++++++++++----
 5 files changed, 73 insertions(+), 18 deletions(-)

diff --git a/crates/threshold-signature-server/src/backup_provider/api.rs b/crates/threshold-signature-server/src/backup_provider/api.rs
index 4effc6700..633fe044d 100644
--- a/crates/threshold-signature-server/src/backup_provider/api.rs
+++ b/crates/threshold-signature-server/src/backup_provider/api.rs
@@ -149,8 +149,14 @@ pub async fn backup_encryption_key(
     State(app_state): State<AppState>,
     Json(encrypted_backup_request): Json<EncryptedSignedMessage>,
 ) -> Result<(), BackupProviderError> {
-    if !app_state.is_ready() {
-        return Err(BackupProviderError::NotReady);
+    // Wait for read access to the chain
+    let mut n = 0;
+    while !app_state.can_read_from_chain() {
+        if n > 9 {
+            return Err(BackupProviderError::NotConnectedToChain);
+        }
+        tokio::time::sleep(std::time::Duration::from_secs(1)).await;
+        n += 1;
     }
 
     // Decrypt the request body to get the key to be backed-up
diff --git a/crates/threshold-signature-server/src/backup_provider/errors.rs b/crates/threshold-signature-server/src/backup_provider/errors.rs
index dda19e8f4..7dc458598 100644
--- a/crates/threshold-signature-server/src/backup_provider/errors.rs
+++ b/crates/threshold-signature-server/src/backup_provider/errors.rs
@@ -66,6 +66,8 @@ pub enum BackupProviderError {
     NotReady,
     #[error("Quote measurement: {0}")]
     QuoteMeasurement(#[from] crate::attestation::errors::QuoteMeasurementErr),
+    #[error("Timed out waiting to be connected to chain")]
+    NotConnectedToChain,
 }
 
 impl IntoResponse for BackupProviderError {
diff --git a/crates/threshold-signature-server/src/helpers/launch.rs b/crates/threshold-signature-server/src/helpers/launch.rs
index 1cb5036ed..dc5bc3d66 100644
--- a/crates/threshold-signature-server/src/helpers/launch.rs
+++ b/crates/threshold-signature-server/src/helpers/launch.rs
@@ -341,7 +341,9 @@ pub async fn check_node_prerequisites(
     let (api, rpc) = backoff::future::retry(backoff.clone(), connect_to_substrate_node)
         .await
         .map_err(|_| "Timed out waiting for connection to chain")?;
+
     tracing::info!("Sucessfully connected to Substrate node!");
+    app_state.connected_to_chain_node().map_err(|_| "Poisoned mutex")?;
 
     tracing::info!("Checking balance of threshold server AccountId `{}`", &account_id);
 
@@ -410,6 +412,6 @@ pub async fn check_node_prerequisites(
     }
 
     tracing::info!("TSS node passed all prerequisite checks and is ready");
-    app_state.make_ready();
+    app_state.make_ready().map_err(|_| "Poisoned mutex")?;
     Ok(())
 }
diff --git a/crates/threshold-signature-server/src/helpers/tests.rs b/crates/threshold-signature-server/src/helpers/tests.rs
index f18a3b3e1..24328e3b6 100644
--- a/crates/threshold-signature-server/src/helpers/tests.rs
+++ b/crates/threshold-signature-server/src/helpers/tests.rs
@@ -80,7 +80,7 @@ pub async fn setup_client() -> KvManager {
     let app_state = AppState::new(configuration, kv_store.clone(), sr25519_pair, x25519_secret);
 
     // Mock making the pre-requisite checks by setting the application state to ready
-    app_state.make_ready();
+    app_state.make_ready().unwrap();
 
     let app = app(app_state).into_make_service();
 
@@ -119,7 +119,7 @@ pub async fn create_clients(
     }
 
     // Mock making the pre-requisite checks by setting the application state to ready
-    app_state.make_ready();
+    app_state.make_ready().unwrap();
 
     let account_id = app_state.subxt_account_id();
 
diff --git a/crates/threshold-signature-server/src/lib.rs b/crates/threshold-signature-server/src/lib.rs
index 43c59faa7..25cad4d79 100644
--- a/crates/threshold-signature-server/src/lib.rs
+++ b/crates/threshold-signature-server/src/lib.rs
@@ -181,7 +181,7 @@ use entropy_shared::X25519PublicKey;
 use sp_core::{crypto::AccountId32, sr25519, Pair};
 use std::{
     collections::HashMap,
-    sync::{Arc, RwLock},
+    sync::{Arc, PoisonError, RwLock},
 };
 use subxt::{
     backend::legacy::LegacyRpcMethods, tx::PairSigner, utils::AccountId32 as SubxtAccountId32,
@@ -208,14 +208,35 @@ use crate::{
     validator::api::{new_reshare, rotate_network_key},
 };
 
+/// Represents the state relating to the prerequisite checks
+#[derive(Clone, PartialEq, Eq)]
+pub enum TssState {
+    /// Initial state where no connection to chain node has been made
+    NoChainConnection,
+    /// Connection is made to the chain node but the account may not be yet funded
+    ReadOnlyChainConnection,
+    /// Fully ready and able to participate in the protocols
+    Ready,
+}
+
+impl TssState {
+    fn new() -> Self {
+        TssState::NoChainConnection
+    }
+
+    fn is_ready(&self) -> bool {
+        self == &TssState::Ready
+    }
+
+    fn can_read_from_chain(&self) -> bool {
+        self != &TssState::NoChainConnection
+    }
+}
+
 #[derive(Clone)]
 pub struct AppState {
-    /// Tracks whether prerequisite checks have passed.
-    /// This means:
-    /// - Communication has been established with the chain node
-    /// - The TSS account is funded
-    /// - The TSS account is registered with the staking extension pallet
-    ready: Arc<RwLock<bool>>,
+    /// Tracks the state of prerequisite checks
+    tss_state: Arc<RwLock<TssState>>,
     /// Tracks incoming protocol connections with other TSS nodes
     listener_state: ListenerState,
     /// Keypair for TSS account
@@ -243,7 +264,7 @@ impl AppState {
         x25519_secret: StaticSecret,
     ) -> Self {
         Self {
-            ready: Arc::new(RwLock::new(false)),
+            tss_state: Arc::new(RwLock::new(TssState::new())),
             pair,
             x25519_secret,
             listener_state: ListenerState::default(),
@@ -256,17 +277,41 @@ impl AppState {
 
     /// Returns true if all prerequisite checks have passed.
     /// Is is not possible to participate in the protocols before this is true.
+    /// 'Ready' means:
+    ///  - Communication has been established with the chain node
+    ///  - The TSS account is funded
+    ///  - The TSS account is registered with the staking extension pallet
     pub fn is_ready(&self) -> bool {
-        match self.ready.read() {
-            Ok(r) => *r,
+        match self.tss_state.read() {
+            Ok(state) => state.is_ready(),
             _ => false,
         }
     }
 
+    /// Returns true if we are able to make chain queries
+    pub fn can_read_from_chain(&self) -> bool {
+        match self.tss_state.read() {
+            Ok(state) => state.can_read_from_chain(),
+            _ => false,
+        }
+    }
+
+    /// Mark the node as able to make chain queries. This is called once during prerequisite checks
+    pub fn connected_to_chain_node(
+        &self,
+    ) -> Result<(), PoisonError<std::sync::RwLockWriteGuard<'_, TssState>>> {
+        let mut tss_state = self.tss_state.write()?;
+        if *tss_state == TssState::NoChainConnection {
+            *tss_state = TssState::ReadOnlyChainConnection;
+        }
+        Ok(())
+    }
+
     /// Mark the node as ready. This is called once when the prerequisite checks have passed.
-    pub fn make_ready(&self) {
-        let mut is_ready = self.ready.write().unwrap();
-        *is_ready = true;
+    pub fn make_ready(&self) -> Result<(), PoisonError<std::sync::RwLockWriteGuard<'_, TssState>>> {
+        let mut tss_state = self.tss_state.write()?;
+        *tss_state = TssState::Ready;
+        Ok(())
     }
 
     /// Get a [PairSigner] for submitting extrinsics with subxt