diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9994ddfa7..93c7fa91c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,12 +14,14 @@ At the moment this project **does not** adhere to
- In [#881](https://github.com/entropyxyz/entropy-core/pull/881) the `HashingAlgorithm` enum is
given an additional variant `Blake2_256` and marked as `non_exhaustive` meaning we must handle the
case that an unknown variant is added in the future.
+ - In [#900](https://github.com/entropyxyz/entropy-core/pull/900) the subgroup signer selection now adds a ```.sort()``` function before selecting the index to ensure consistentcy across libraries languages and clients
### Added
- Add a way to change program modification account ([#843](https://github.com/entropyxyz/entropy-core/pull/843))
- Add support for `--mnemonic-file` and `THRESHOLD_SERVER_MNEMONIC` ([#864](https://github.com/entropyxyz/entropy-core/pull/864))
- Add validator helpers to cli ([#870](https://github.com/entropyxyz/entropy-core/pull/870))
-- Add blake2 as built in hash function and make HashingAlgorithm non-exhaustive ([#881](https://github.com/entropyxyz/entropy-core/pull/881)
+- Add blake2 as built in hash function and make HashingAlgorithm non-exhaustive ([#881](https://github.com/entropyxyz/entropy-core/pull/881))
+- Add sort to subgroup signer selection ([#900](https://github.com/entropyxyz/entropy-core/pull/900))
### Changed
- Move TSS mnemonic out of keystore [#853](https://github.com/entropyxyz/entropy-core/pull/853)
diff --git a/crates/client/src/user.rs b/crates/client/src/user.rs
index b1269d64f..34ff132dc 100644
--- a/crates/client/src/user.rs
+++ b/crates/client/src/user.rs
@@ -61,11 +61,11 @@ pub async fn get_current_subgroup_signers(
async move {
let subgroup_info_query =
entropy::storage().staking_extension().signing_groups(i as u8);
- let subgroup_info =
- query_chain(api, rpc, subgroup_info_query, block_hash)
- .await?
- .ok_or_else(|| SubgroupGetError::ChainFetch("Subgroup Fetch Error"))?;
-
+ let mut subgroup_info = query_chain(api, rpc, subgroup_info_query, block_hash)
+ .await?
+ .ok_or_else(|| SubgroupGetError::ChainFetch("Subgroup Fetch Error"))?;
+ // sort subgroup for ease in client side calculations
+ subgroup_info.sort();
let index_of_signer_big = &*owned_number % subgroup_info.len();
let index_of_signer =
index_of_signer_big.to_usize().ok_or(SubgroupGetError::Usize("Usize error"))?;
diff --git a/crates/threshold-signature-server/src/lib.rs b/crates/threshold-signature-server/src/lib.rs
index d3bca4946..1690b6bc0 100644
--- a/crates/threshold-signature-server/src/lib.rs
+++ b/crates/threshold-signature-server/src/lib.rs
@@ -151,7 +151,7 @@ use validator::api::get_random_server_info;
use crate::{
health::api::healthz,
launch::Configuration,
- node_info::api::{hashes, version as get_version},
+ node_info::api::{get_subgroup_signers, hashes, version as get_version},
r#unsafe::api::{delete, put, remove_keys, unsafe_get},
signing_client::{api::*, ListenerState},
user::api::*,
@@ -185,6 +185,7 @@ pub fn app(app_state: AppState) -> Router {
.route("/user/receive_key", post(receive_key))
.route("/signer/proactive_refresh", post(proactive_refresh))
.route("/validator/sync_kvdb", post(sync_kvdb))
+ .route("/subgroup_signers", post(get_subgroup_signers))
.route("/healthz", get(healthz))
.route("/version", get(get_version))
.route("/hashes", get(hashes))
diff --git a/crates/threshold-signature-server/src/node_info/api.rs b/crates/threshold-signature-server/src/node_info/api.rs
index e5ca0541d..e310a33e6 100644
--- a/crates/threshold-signature-server/src/node_info/api.rs
+++ b/crates/threshold-signature-server/src/node_info/api.rs
@@ -12,9 +12,16 @@
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
-use axum::Json;
+use crate::{
+ chain_api::{get_api, get_rpc},
+ user::UserErr,
+ AppState,
+};
+use axum::{extract::State, Json};
+pub use entropy_client::user::get_current_subgroup_signers;
use entropy_shared::types::HashingAlgorithm;
use strum::IntoEnumIterator;
+
/// Returns the version and commit data
#[tracing::instrument]
pub async fn version() -> String {
@@ -26,3 +33,18 @@ pub async fn hashes() -> Json> {
let hashing_algos = HashingAlgorithm::iter().collect::>();
Json(hashing_algos)
}
+
+/// Returns the list of subgroup signers given a message hash.
+///
+/// Note: the message hash should not include a preceding `0x`.
+#[tracing::instrument(skip_all)]
+pub async fn get_subgroup_signers(
+ State(app_state): State,
+ message_hash: String,
+) -> Result {
+ tracing::debug!("Message hash {:?}", message_hash);
+ let api = get_api(&app_state.configuration.endpoint).await?;
+ let rpc = get_rpc(&app_state.configuration.endpoint).await?;
+ let subgroup_signers = get_current_subgroup_signers(&api, &rpc, &message_hash).await?;
+ Ok(serde_json::to_string(&subgroup_signers)?)
+}
diff --git a/crates/threshold-signature-server/src/node_info/tests.rs b/crates/threshold-signature-server/src/node_info/tests.rs
index 19c33c564..74ade5ec8 100644
--- a/crates/threshold-signature-server/src/node_info/tests.rs
+++ b/crates/threshold-signature-server/src/node_info/tests.rs
@@ -13,7 +13,14 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
-use crate::helpers::tests::{initialize_test_logger, setup_client};
+use crate::{
+ chain_api::{get_api, get_rpc},
+ helpers::tests::{initialize_test_logger, setup_client, spawn_testing_validators},
+};
+use entropy_client::user::get_current_subgroup_signers;
+use entropy_testing_utils::substrate_context::test_context_stationary;
+
+use entropy_kvdb::clean_tests;
use entropy_shared::types::HashingAlgorithm;
use serial_test::serial;
@@ -50,3 +57,31 @@ async fn hashes_test() {
]
);
}
+
+#[tokio::test]
+#[serial]
+async fn test_get_subgroup() {
+ initialize_test_logger().await;
+ clean_tests();
+
+ let _ = spawn_testing_validators(None, false, false).await;
+ let substrate_context = test_context_stationary().await;
+ let api = get_api(&substrate_context.node_proc.ws_url).await.unwrap();
+ let rpc = get_rpc(&substrate_context.node_proc.ws_url).await.unwrap();
+ let mock_client = reqwest::Client::new();
+ // example keccak hash
+ let message_hash = "06b3dfaec148fb1bb2b066f10ec285e7c9bf402ab32aa78a5d38e34566810cd2";
+ let response = mock_client
+ .post("http://127.0.0.1:3001/subgroup_signers")
+ .header("Content-Type", "application/json")
+ .body(message_hash)
+ .send()
+ .await;
+ let mock_result = get_current_subgroup_signers(&api, &rpc, &message_hash).await.unwrap();
+ assert_eq!(
+ serde_json::to_string(&mock_result).unwrap(),
+ response.unwrap().text().await.unwrap(),
+ "subgroup data should match"
+ );
+ clean_tests();
+}
diff --git a/crates/threshold-signature-server/src/user/tests.rs b/crates/threshold-signature-server/src/user/tests.rs
index 0e1058a07..7aa1982ba 100644
--- a/crates/threshold-signature-server/src/user/tests.rs
+++ b/crates/threshold-signature-server/src/user/tests.rs
@@ -1760,7 +1760,7 @@ async fn test_faucet() {
.await
.unwrap();
- let amount_to_send = 200000011;
+ let amount_to_send = 200000012;
let faucet_user_config = UserConfig { max_transfer_amount: amount_to_send };
update_programs(