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("e).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("e)?; + + 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("e).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("e)?; + let _pck = verify_pck_certificate_chain("e)?; 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("e)?; 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("e)?; 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("e)?; - 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, "e).await?; let _pck = verify_pck_certificate_chain("e)?; 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