Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add /relay_tx endpoint #1050

Merged
merged 48 commits into from
Sep 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
26e1519
add relay tx endpoint
JesseAbram Sep 12, 2024
0c33812
Merge branch 'master' of github.com:entropyxyz/entropy-core into add-…
JesseAbram Sep 13, 2024
2b2594e
working
JesseAbram Sep 13, 2024
8150b14
redesign test
JesseAbram Sep 13, 2024
350e9c0
fix sig recovery check
JesseAbram Sep 13, 2024
51b3353
add pre sign checks
JesseAbram Sep 13, 2024
abe8958
add signer check
JesseAbram Sep 13, 2024
75a1721
add validator checks
JesseAbram Sep 16, 2024
6256782
remove validator info from user sig req message
JesseAbram Sep 16, 2024
f238480
fixing tests
JesseAbram Sep 17, 2024
e8c4858
another test
JesseAbram Sep 17, 2024
0c17e6e
fix user tests
JesseAbram Sep 17, 2024
feef814
more tests
JesseAbram Sep 17, 2024
411b0cb
clean
JesseAbram Sep 18, 2024
8ab7646
test
JesseAbram Sep 18, 2024
c89d04b
clean
JesseAbram Sep 18, 2024
f25e7a4
fmt
JesseAbram Sep 18, 2024
2d40885
fmt
JesseAbram Sep 18, 2024
5d96f9d
docs
JesseAbram Sep 19, 2024
352f90b
tests
JesseAbram Sep 19, 2024
4739610
Merge branch 'master' of github.com:entropyxyz/entropy-core into add-…
JesseAbram Sep 19, 2024
0216957
fmt
JesseAbram Sep 19, 2024
5f9745d
fix tests
JesseAbram Sep 20, 2024
0074333
fix test
JesseAbram Sep 20, 2024
0259ec5
Apply suggestions from code review
JesseAbram Sep 23, 2024
961ae6a
Update crates/threshold-signature-server/src/user/api.rs
JesseAbram Sep 23, 2024
f6dc78a
move get_signers_from_chain
JesseAbram Sep 23, 2024
333427c
return option validator name
JesseAbram Sep 23, 2024
74796ee
random selection
JesseAbram Sep 23, 2024
13a3511
add validator checks
JesseAbram Sep 23, 2024
dbed146
clean tests
JesseAbram Sep 23, 2024
f8a7a72
tests
JesseAbram Sep 23, 2024
d545ecf
remove poping selected signers
JesseAbram Sep 25, 2024
f862b6e
Merge branch 'master' of github.com:entropyxyz/entropy-core into add-…
JesseAbram Sep 25, 2024
d59d6a0
fix
JesseAbram Sep 25, 2024
31afe48
Apply suggestions from code review
JesseAbram Sep 25, 2024
f57a5b2
collapse user_sig_request into relayer request
JesseAbram Sep 25, 2024
3d7cec4
Apply suggestions from code review
JesseAbram Sep 25, 2024
59906f7
clean
JesseAbram Sep 25, 2024
d423fee
clean
JesseAbram Sep 25, 2024
57485d8
clean
JesseAbram Sep 25, 2024
826af21
clean
JesseAbram Sep 25, 2024
6682807
Update crates/threshold-signature-server/src/user/tests.rs
JesseAbram Sep 25, 2024
13eced6
clean
JesseAbram Sep 25, 2024
9abc957
replace charlie with dave in key creation
JesseAbram Sep 26, 2024
0f68423
fmt
JesseAbram Sep 26, 2024
2374b32
lint
JesseAbram Sep 26, 2024
e07904e
test fix
JesseAbram Sep 26, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ At the moment this project **does not** adhere to
pallet. The genesis build config was also removed. Additionally, the `new/user/` HTTP endpoint in
the TSS was removed since it was no longer necessary.
- In [#1045](https://github.com/entropyxyz/entropy-core/pull/1045), `ProgramsInfo` now takes `version_number` to maintain backwards compatibility if programs runtime is updated
- In [#1050](https://github.com/entropyxyz/entropy-core/pull/1050), the flow for signing has changed.
A user now sends their request to any validator that is not a signer. This will act as a relayer.
As such, `UserSignatureRequest` no longer requires the `validators_info` field since the the
relayer adds that in after. The response received from the validator is now a `Vec<Responses>`
from the signers.

### Added
- Jumpstart network ([#918](https://github.com/entropyxyz/entropy-core/pull/918))
Expand All @@ -40,6 +45,7 @@ At the moment this project **does not** adhere to

### Changed
- Fix TSS `AccountId` keys in chainspec ([#993](https://github.com/entropyxyz/entropy-core/pull/993))
- Add relay tx endpoint ([#1050](https://github.com/entropyxyz/entropy-core/pull/1050))

### Removed
- Remove `prune_registration` extrinsic ([#1022](https://github.com/entropyxyz/entropy-core/pull/1022))
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ thiserror ="1.0.64"
futures ="0.3"
sp-core ={ version="31.0.0", default-features=false, features=["full_crypto", "serde"] }
tracing ="0.1.37"
rand ={ version="0.8", default-features=false }

# Present when "full-client" feature is active
blake2 ={ version="0.10.4", optional=true }
Expand Down
123 changes: 59 additions & 64 deletions crates/client/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ pub use crate::{
};
use anyhow::anyhow;
pub use entropy_protocol::{sign_and_encrypt::EncryptedSignedMessage, KeyParams};
use rand::Rng;
use std::str::FromStr;
pub use synedrion::KeyShare;

Expand All @@ -38,17 +39,18 @@ use crate::{
},
client::entropy::staking_extension::events::{EndpointChanged, ThresholdAccountChanged},
substrate::{get_registered_details, submit_transaction_with_pair},
user::{get_signers_from_chain, UserSignatureRequest},
user::{get_all_signers_from_chain, get_validators_not_signer_for_relay, UserSignatureRequest},
Hasher,
};

use base64::prelude::{Engine, BASE64_STANDARD};
use entropy_protocol::RecoverableSignature;
use entropy_shared::HashingAlgorithm;
use futures::{future, stream::StreamExt};
use futures::stream::StreamExt;
use sp_core::{sr25519, Pair};
use subxt::{
backend::legacy::LegacyRpcMethods,
ext::sp_core::sr25519::Signature,
utils::{AccountId32 as SubxtAccountId32, H256},
Config, OnlineClient,
};
Expand Down Expand Up @@ -113,14 +115,13 @@ pub async fn sign(
) -> Result<RecoverableSignature, ClientError> {
let message_hash = Hasher::keccak(&message);

let validators_info = get_signers_from_chain(api, rpc).await?;
let validators_info = get_validators_not_signer_for_relay(api, rpc).await?;

tracing::debug!("Validators info {:?}", validators_info);
let block_number = rpc.chain_get_header(None).await?.ok_or(ClientError::BlockNumber)?.number;
let signature_request = UserSignatureRequest {
message: hex::encode(message),
auxilary_data: Some(vec![auxilary_data.map(hex::encode)]),
validators_info: validators_info.clone(),
block_number,
hash: HashingAlgorithm::Keccak,
signature_verifying_key: signature_verifying_key.to_vec(),
Expand All @@ -129,70 +130,64 @@ pub async fn sign(
let signature_request_vec = serde_json::to_vec(&signature_request)?;
let client = reqwest::Client::new();

// Make http requests to TSS servers
let submit_transaction_requests = validators_info
.iter()
.map(|validator_info| async {
let encrypted_message = EncryptedSignedMessage::new(
&user_keypair,
signature_request_vec.clone(),
&validator_info.x25519_public_key,
&[],
)?;
let message_json = serde_json::to_string(&encrypted_message)?;

let url = format!("http://{}/user/sign_tx", validator_info.ip_address);

let res = client
.post(url)
.header("Content-Type", "application/json")
.body(message_json)
.send()
.await;
Ok::<_, ClientError>(res)
})
.collect::<Vec<_>>();

// If we have a keyshare, connect to TSS servers
let results = future::try_join_all(submit_transaction_requests).await?;

// Get the first result
if let Some(res) = results.into_iter().next() {
let output = res?;
if output.status() != 200 {
return Err(ClientError::SigningFailed(output.text().await?));
}

let mut bytes_stream = output.bytes_stream();
let chunk = bytes_stream.next().await.ok_or(ClientError::NoResponse)??;
let signing_result: Result<(String, sr25519::Signature), String> =
serde_json::from_slice(&chunk)?;
let (signature_base64, signature_of_signature) =
signing_result.map_err(ClientError::SigningFailed)?;
tracing::debug!("Signature: {}", signature_base64);
let mut decoded_sig = BASE64_STANDARD.decode(signature_base64)?;

// Verify the response signature from the TSS client
if !sr25519::Pair::verify(
let mut rng = rand::thread_rng();
let random_index = rng.gen_range(0..validators_info.len());
let validator_info = &validators_info[random_index];

// Make http request to TSS server
let encrypted_message = EncryptedSignedMessage::new(
&user_keypair,
signature_request_vec.clone(),
&validator_info.x25519_public_key,
&[],
)?;
let message_json = serde_json::to_string(&encrypted_message)?;

let url = format!("http://{}/user/relay_tx", validator_info.ip_address);

let result = client
.post(url)
.header("Content-Type", "application/json")
.body(message_json)
.send()
.await?;

let mut bytes_stream = result.bytes_stream();
let chunk = bytes_stream.next().await.ok_or(ClientError::NoResponse)??;
let signing_results: Vec<Result<(String, Signature), String>> = serde_json::from_slice(&chunk)?;
// take only one of the responses randomly
let mut rng = rand::thread_rng();
let random_index = rng.gen_range(0..signing_results.len());
let (signature_base64, signature_of_signature) =
signing_results[random_index].clone().map_err(ClientError::SigningFailed)?;
tracing::debug!("Signature: {}", signature_base64);
let mut decoded_sig = BASE64_STANDARD.decode(signature_base64)?;

// Verify the response signature from the TSS client
let signers = get_all_signers_from_chain(api, rpc).await?;
let mut sig_recovery_results = vec![];
for signer_info in signers {
let sig_recovery = <sr25519::Pair as Pair>::verify(
&signature_of_signature,
&decoded_sig,
&sr25519::Public(validators_info[0].tss_account.0),
) {
return Err(ClientError::BadSignature);
}
decoded_sig.clone(),
&sr25519::Public(signer_info.tss_account.0),
);
sig_recovery_results.push(sig_recovery)
}

let recovery_digit = decoded_sig.pop().ok_or(ClientError::NoRecoveryId)?;
let signature = k256Signature::from_slice(&decoded_sig)?;
let recovery_id =
RecoveryId::from_byte(recovery_digit).ok_or(ClientError::BadRecoveryId)?;
if !sig_recovery_results.contains(&true) {
return Err(ClientError::BadSignature);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this ever happens, how can you tell who the bad guy was? Why not bail earlier, when you're collecting the verification results?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ya I mean this relates to peg's earlier comment, I will open an issue in a bit and link it, pretty much how we return the results im open to discuss

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}

let verifying_key_of_signature =
VerifyingKey::recover_from_prehash(&message_hash, &signature, recovery_id)?;
tracing::debug!("Verifying Key {:?}", verifying_key_of_signature);
let recovery_digit = decoded_sig.pop().ok_or(ClientError::NoRecoveryId)?;
let signature = k256Signature::from_slice(&decoded_sig)?;
let recovery_id = RecoveryId::from_byte(recovery_digit).ok_or(ClientError::BadRecoveryId)?;

return Ok(RecoverableSignature { signature, recovery_id });
}
Err(ClientError::NoResponse)
let verifying_key_of_signature =
VerifyingKey::recover_from_prehash(&message_hash, &signature, recovery_id)?;
tracing::debug!("Verifying Key {:?}", verifying_key_of_signature);

return Ok(RecoverableSignature { signature, recovery_id });
}

/// Store a program on chain and return it's hash
Expand Down
73 changes: 62 additions & 11 deletions crates/client/src/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,6 @@ pub struct UserSignatureRequest {
pub message: String,
/// Hex-encoded auxilary data for program evaluation, will not be signed (eg. zero-knowledge proof, serialized struct, etc)
pub auxilary_data: Option<Vec<Option<String>>>,
/// Information from the validators in signing party
pub validators_info: Vec<ValidatorInfo>,
/// When the message was created and signed
pub block_number: BlockNumber,
/// Hashing algorithm to be used for signing
Expand All @@ -40,24 +38,32 @@ pub struct UserSignatureRequest {
pub signature_verifying_key: Vec<u8>,
}

pub async fn get_signers_from_chain(
/// Represents an unparsed transaction request coming from a relayer to a signer.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RelayerSignatureRequest {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can probably just have this in the body of UserSignatureRequest, something like:

pub struct UserSignatureRequest {
    pub request: RelayerSignatureRequest,
    pub validators_info: Vec<ValidatorInfo>,
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes but opposite

// Request relayed from user to signer
pub user_signature_request: UserSignatureRequest,
/// Information for the validators in the signing party
pub validators_info: Vec<ValidatorInfo>,
}

/// Gets a validator from chain to relay a message to the signers
/// Filters out all signers
pub async fn get_validators_not_signer_for_relay(
api: &OnlineClient<EntropyConfig>,
rpc: &LegacyRpcMethods<EntropyConfig>,
) -> Result<Vec<ValidatorInfo>, SubgroupGetError> {
let signer_query = entropy::storage().staking_extension().signers();
let mut validators = query_chain(api, rpc, signer_query, None)
let signers = query_chain(api, rpc, signer_query, None)
.await?
.ok_or_else(|| SubgroupGetError::ChainFetch("Get all validators error"))?;

let key_info_query = entropy::storage().parameters().signers_info();
let threshold = query_chain(api, rpc, key_info_query, None)
let validators_query = entropy::storage().session().validators();
let mut validators = query_chain(api, rpc, validators_query, None)
.await?
.ok_or_else(|| SubgroupGetError::ChainFetch("Failed to get signers info"))?
.threshold;

// TODO #899 For now we just take the first t validators as the ones to perform signing
validators.truncate(threshold as usize);
.ok_or_else(|| SubgroupGetError::ChainFetch("Error getting validators"))?;

validators.retain(|validator| !signers.contains(validator));
let block_hash = rpc.chain_get_block_hash(None).await?;
let mut handles = Vec::new();

Expand Down Expand Up @@ -85,6 +91,51 @@ pub async fn get_signers_from_chain(
handles.push(handle);
}

let mut all_validators: Vec<ValidatorInfo> = vec![];
for handle in handles {
all_validators.push(handle.await??);
}

Ok(all_validators)
}

/// Gets all signers from chain
pub async fn get_all_signers_from_chain(
api: &OnlineClient<EntropyConfig>,
rpc: &LegacyRpcMethods<EntropyConfig>,
) -> Result<Vec<ValidatorInfo>, SubgroupGetError> {
let signer_query = entropy::storage().staking_extension().signers();
let signers = query_chain(api, rpc, signer_query, None)
.await?
.ok_or_else(|| SubgroupGetError::ChainFetch("Get all validators error"))?;

let block_hash = rpc.chain_get_block_hash(None).await?;
let mut handles = Vec::new();

for signer in signers {
let handle: tokio::task::JoinHandle<Result<ValidatorInfo, SubgroupGetError>> =
tokio::task::spawn({
let api = api.clone();
let rpc = rpc.clone();
async move {
let threshold_address_query =
entropy::storage().staking_extension().threshold_servers(signer);
let server_info = query_chain(&api, &rpc, threshold_address_query, block_hash)
.await?
.ok_or_else(|| {
SubgroupGetError::ChainFetch("threshold_servers query error")
})?;
Ok(ValidatorInfo {
x25519_public_key: server_info.x25519_public_key,
ip_address: std::str::from_utf8(&server_info.endpoint)?.to_string(),
tss_account: server_info.tss_account,
})
}
});

handles.push(handle);
}

let mut all_signers: Vec<ValidatorInfo> = vec![];
for handle in handles {
all_signers.push(handle.await??);
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
7 changes: 4 additions & 3 deletions crates/testing-utils/src/create_test_keyshares.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,18 +38,19 @@ where
Params: SchemeParams,
{
let signing_key = SigningKey::from_bytes(&(distributed_secret_key_bytes).into()).unwrap();
let signers = vec![alice.clone(), bob, charlie.clone()];
let signers = vec![alice.clone(), bob.clone(), charlie.clone()];
let session_id = SessionId::from_seed(b"12345".as_slice());
let all_parties =
signers.iter().map(|pair| PartyId::from(pair.public())).collect::<BTreeSet<_>>();

let old_holders = all_parties.clone().into_iter().take(2).collect::<BTreeSet<_>>();
let mut old_holders = all_parties.clone();
old_holders.remove(&PartyId::from(charlie.clone().public()));

let keyshares =
KeyShare::<Params, PartyId>::new_centralized(&mut OsRng, &old_holders, Some(&signing_key));
let aux_infos = AuxInfo::<Params, PartyId>::new_centralized(&mut OsRng, &all_parties);

let alice_id = PartyId::from(alice.public());
let alice_id = PartyId::from(bob.public());
let new_holder = NewHolder {
verifying_key: keyshares[&alice_id].verifying_key(),
old_threshold: 2,
Expand Down
10 changes: 5 additions & 5 deletions crates/threshold-signature-server/src/helpers/signing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
pub use entropy_client::Hasher;
use std::time::Duration;

use entropy_client::user::UserSignatureRequest;
use entropy_client::user::RelayerSignatureRequest;
use entropy_protocol::{Listener, RecoverableSignature, SessionId, SigningSessionInfo};
use entropy_shared::SETUP_TIMEOUT_SECONDS;
use sp_core::Pair;
Expand All @@ -41,7 +41,7 @@ use crate::{
#[tracing::instrument(skip(app_state), level = tracing::Level::DEBUG)]
pub async fn do_signing(
rpc: &LegacyRpcMethods<EntropyConfig>,
user_signature_request: UserSignatureRequest,
relayer_signature_request: RelayerSignatureRequest,
app_state: &AppState,
signing_session_info: SigningSessionInfo,
request_limit: u32,
Expand All @@ -52,7 +52,7 @@ pub async fn do_signing(
let state = &app_state.listener_state;
let kv_manager = &app_state.kv_store;

let info = SignInit::new(user_signature_request.clone(), signing_session_info.clone());
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
Expand All @@ -64,15 +64,15 @@ pub async fn do_signing(
// Set up context for signing protocol execution
let sign_context = signing_service.get_sign_context(info.clone(), derivation_path).await?;

let tss_accounts: Vec<AccountId32> = user_signature_request
let tss_accounts: Vec<AccountId32> = relayer_signature_request
.validators_info
.iter()
.map(|validator_info| validator_info.tss_account.clone())
.collect();

// subscribe to all other participating parties. Listener waits for other subscribers.
let (rx_ready, rx_from_others, listener) =
Listener::new(user_signature_request.validators_info, &account_id);
Listener::new(relayer_signature_request.validators_info, &account_id);

let session_id = SessionId::Sign(sign_context.sign_init.signing_session_info.clone());

Expand Down
Loading
Loading