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

Open
wants to merge 24 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@ 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 a validator that is not a signer. This will act as a realyer.
As such ```UserSignatureRequest``` no longer has ```validators_info``` as the realyer 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 @@ -39,6 +43,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
3 changes: 2 additions & 1 deletion 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.63"
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
119 changes: 56 additions & 63 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,62 @@ 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 rng = rand::thread_rng();
let random_index = rng.gen_range(0..validators_info.len());
let validator_info = &validators_info[random_index];

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(
// Make http requests to TSS servers
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
let (signature_base64, signature_of_signature) =
signing_results[0].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)
}

if !sig_recovery_results.contains(&true) {
return Err(ClientError::BadSignature);
}

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)?;
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)?;

let verifying_key_of_signature =
VerifyingKey::recover_from_prehash(&message_hash, &signature, recovery_id)?;
tracing::debug!("Verifying Key {:?}", verifying_key_of_signature);
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 });
}
Err(ClientError::NoResponse)
return Ok(RecoverableSignature { signature, recovery_id });
}

/// Store a program on chain and return it's hash
Expand Down
131 changes: 126 additions & 5 deletions crates/client/src/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ use crate::{
substrate::query_chain,
};
use entropy_shared::{user::ValidatorInfo, BlockNumber, HashingAlgorithm};
use rand::prelude::SliceRandom;
use serde::{Deserialize, Serialize};
use subxt::{backend::legacy::LegacyRpcMethods, OnlineClient};

Expand All @@ -30,8 +31,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,12 +39,30 @@ pub struct UserSignatureRequest {
pub signature_verifying_key: Vec<u8>,
}

/// Represents an unparsed, transaction request coming from the relayer to a signer.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct RelayerSignatureRequest {
/// Hex-encoded raw data to be signed (eg. hex-encoded RLP-serialized Ethereum transaction)
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>>>,
/// When the message was created and signed
pub block_number: BlockNumber,
/// Hashing algorithm to be used for signing
pub hash: HashingAlgorithm,
/// The verifying key for the signature requested
pub signature_verifying_key: Vec<u8>,
/// Information for the validators in the signing party
pub validators_info: Vec<ValidatorInfo>,
}

/// Returns a threshold of signer's ValidatorInfo from the chain
pub async fn get_signers_from_chain(
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"))?;

Expand All @@ -55,9 +72,68 @@ pub async fn get_signers_from_chain(
.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);
let selected_signers: Vec<_> = {
let mut cloned_signers = signers.clone();
// TODO: temp remove dave for now until test dave is spun up correctly
cloned_signers.pop();
cloned_signers
.choose_multiple(&mut rand::thread_rng(), threshold as usize)
.cloned()
.collect()
};

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

for signer in selected_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??);
}

Ok(all_signers)
}

/// 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 signers = query_chain(api, rpc, signer_query, None)
.await?
.ok_or_else(|| SubgroupGetError::ChainFetch("Get all validators error"))?;

let validators_query = entropy::storage().session().validators();
let mut validators = query_chain(api, rpc, validators_query, None)
.await?
.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 +161,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
2 changes: 1 addition & 1 deletion crates/threshold-signature-server/src/helpers/launch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ pub const FORBIDDEN_KEY_DIFFIE_HELLMAN_PUBLIC: &str = "DH_PUBLIC";

// Deafult name for TSS server
// Will set mnemonic and db path
#[derive(Debug, PartialEq)]
#[derive(Debug, PartialEq, Clone)]
pub enum ValidatorName {
Alice,
Bob,
Expand Down
Loading
Loading