From 11f4b39fa3296025eb5773f87109a2f6cac6a4ed Mon Sep 17 00:00:00 2001 From: peg Date: Mon, 12 Aug 2024 11:51:49 +0200 Subject: [PATCH] Add attest endpoint --- crates/threshold-signature-server/Cargo.toml | 3 +- crates/threshold-signature-server/src/lib.rs | 3 +- .../src/validator/api.rs | 59 +++++++++++++++++++ .../src/validator/errors.rs | 6 +- .../src/validator/tests.rs | 15 +++++ 5 files changed, 83 insertions(+), 3 deletions(-) diff --git a/crates/threshold-signature-server/Cargo.toml b/crates/threshold-signature-server/Cargo.toml index 73772b4a8..c04cce9f9 100644 --- a/crates/threshold-signature-server/Cargo.toml +++ b/crates/threshold-signature-server/Cargo.toml @@ -70,6 +70,7 @@ sha1 ="0.10.6" sha2 ="0.10.8" hkdf ="0.12.4" project-root ={ version="0.2.2", optional=true } +tdx-quote ={ git="https://github.com/entropyxyz/tdx-quote", optional=true, features=["mock"] } [dev-dependencies] serial_test ="3.1.1" @@ -102,7 +103,7 @@ vergen={ version="8.3.2", features=["build", "git", "gitcl"] } default =['std'] std =["sp-core/std"] test_helpers=["dep:project-root"] -unsafe =[] +unsafe =["dep:tdx-quote"] 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/lib.rs b/crates/threshold-signature-server/src/lib.rs index 67dfb086c..9ac84daf2 100644 --- a/crates/threshold-signature-server/src/lib.rs +++ b/crates/threshold-signature-server/src/lib.rs @@ -155,7 +155,7 @@ use crate::{ r#unsafe::api::{delete, put, remove_keys, unsafe_get}, signing_client::{api::*, ListenerState}, user::api::*, - validator::api::new_reshare, + validator::api::{attest, new_reshare}, }; #[derive(Clone)] @@ -178,6 +178,7 @@ pub fn app(app_state: AppState) -> Router { .route("/user/sign_tx", post(sign_tx)) .route("/signer/proactive_refresh", post(proactive_refresh)) .route("/validator/reshare", post(new_reshare)) + .route("/attest", post(attest)) .route("/healthz", get(healthz)) .route("/version", get(get_version)) .route("/hashes", get(hashes)) diff --git a/crates/threshold-signature-server/src/validator/api.rs b/crates/threshold-signature-server/src/validator/api.rs index 7632ed87e..a9377766f 100644 --- a/crates/threshold-signature-server/src/validator/api.rs +++ b/crates/threshold-signature-server/src/validator/api.rs @@ -28,6 +28,7 @@ use crate::{ AppState, }; use axum::{body::Bytes, extract::State, http::StatusCode}; +use blake2::{Blake2b512, Digest}; use entropy_kvdb::kv_manager::{helpers::serialize as key_serialize, KvManager}; use entropy_protocol::Subsession; pub use entropy_protocol::{ @@ -360,3 +361,61 @@ pub async fn prune_old_holders( validators_info.clone() }) } + +#[cfg(not(feature = "unsafe"))] +pub async fn attest( + State(_app_state): State, + _input: Bytes, +) -> Result { + // Non-mock attestation (the real thing) will go here + Err(ValidatorErr::NotImplemented) +} + +#[cfg(feature = "unsafe")] +pub async fn attest( + State(app_state): State, + input: Bytes, +) -> Result<(StatusCode, String), ValidatorErr> { + let nonce = input[..].try_into()?; + + let rpc = get_rpc(&app_state.configuration.endpoint).await?; + + let block_number = rpc + .chain_get_header(None) + .await? + .ok_or_else(|| ValidatorErr::OptionUnwrapError("Failed to get block number".to_string()))? + .number; + + // In the real thing this is the hardware key used in the quoting enclave + let signing_key = tdx_quote::SigningKey::random(&mut OsRng); + + let (signer, x25519_secret) = get_signer_and_x25519_secret(&app_state.kv_store).await?; + let public_key = x25519_dalek::PublicKey::from(&x25519_secret); + + let input_data = + QuoteInputData::new(signer.signer().public().into(), public_key, nonce, block_number); + + let quote = tdx_quote::Quote::mock(signing_key.clone(), input_data.0); + // Here we would submit an attest extrinsic to the chain - but for now we just include it in the + // response + Ok((StatusCode::OK, format!("{:?}", quote))) +} + +/// Input data to be included in a TDX attestation +pub struct QuoteInputData(pub [u8; 64]); + +impl QuoteInputData { + pub fn new( + account_id: AccountId32, + x25519_public_key: x25519_dalek::PublicKey, + nonce: [u8; 32], + block_number: u32, + ) -> Self { + let mut hasher = Blake2b512::new(); + hasher.update(account_id.0); + hasher.update(x25519_public_key.as_bytes()); + hasher.update(nonce); + hasher.update(block_number.to_be_bytes()); + Self(hasher.finalize().into()) + } +} diff --git a/crates/threshold-signature-server/src/validator/errors.rs b/crates/threshold-signature-server/src/validator/errors.rs index d7c8a8256..7d64816d4 100644 --- a/crates/threshold-signature-server/src/validator/errors.rs +++ b/crates/threshold-signature-server/src/validator/errors.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 . -use std::string::FromUtf8Error; +use std::{array::TryFromSliceError, string::FromUtf8Error}; use crate::signing_client::ProtocolErr; use axum::{ @@ -93,6 +93,10 @@ pub enum ValidatorErr { InvalidData, #[error("Data is repeated")] RepeatedData, + #[error("Not yet implemented")] + NotImplemented, + #[error("Input must be 32 bytes: {0}")] + TryFromSlice(#[from] TryFromSliceError), } impl IntoResponse for ValidatorErr { diff --git a/crates/threshold-signature-server/src/validator/tests.rs b/crates/threshold-signature-server/src/validator/tests.rs index 23b64fc25..e74556cff 100644 --- a/crates/threshold-signature-server/src/validator/tests.rs +++ b/crates/threshold-signature-server/src/validator/tests.rs @@ -255,3 +255,18 @@ async fn test_forbidden_keys() { let should_pass = check_forbidden_key("test"); assert_eq!(should_pass.unwrap(), ()); } + +#[tokio::test] +#[serial] +async fn test_attest() { + initialize_test_logger().await; + clean_tests(); + + // let cxt = test_node_process_testing_state(false).await; + let (_validator_ips, _validator_ids) = spawn_testing_validators(false).await; + + let client = reqwest::Client::new(); + let res = + client.post(format!("http://127.0.0.1:3001/attest")).body(Vec::new()).send().await.unwrap(); + println!("{:?}", res.text().await); +}