-
Notifications
You must be signed in to change notification settings - Fork 1
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
Add /relay_tx
endpoint
#1050
Changes from 32 commits
26e1519
0c33812
2b2594e
8150b14
350e9c0
51b3353
abe8958
75a1721
6256782
f238480
e8c4858
0c17e6e
feef814
411b0cb
8ab7646
c89d04b
f25e7a4
2d40885
5d96f9d
352f90b
4739610
0216957
5f9745d
0074333
0259ec5
961ae6a
f6dc78a
333427c
74796ee
13a3511
dbed146
f8a7a72
d545ecf
f862b6e
d59d6a0
31afe48
f57a5b2
3d7cec4
59906f7
d423fee
57485d8
826af21
6682807
13eced6
9abc957
0f68423
2374b32
e07904e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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; | ||
|
||
|
@@ -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, | ||
}; | ||
|
@@ -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(), | ||
|
@@ -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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
|
@@ -40,24 +38,40 @@ 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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can probably just have this in the body of pub struct UserSignatureRequest {
pub request: RelayerSignatureRequest,
pub validators_info: Vec<ValidatorInfo>,
} There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes but opposite |
||
/// 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>, | ||
} | ||
|
||
/// 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(); | ||
|
||
|
@@ -85,6 +99,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??); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could also say that the endpoint for submitting user signature requests has changed to
user/relay_tx