Skip to content

Commit

Permalink
Merge branch 'master' into peg/attestation-pallet
Browse files Browse the repository at this point in the history
* master:
  TSS attestation endpoint (#1001)
  • Loading branch information
ameba23 committed Aug 13, 2024
2 parents 55c9af2 + fccd8a3 commit c5df03f
Show file tree
Hide file tree
Showing 12 changed files with 224 additions and 2 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ At the moment this project **does not** adhere to
- Reshare confirmation ([#965](https://github.com/entropyxyz/entropy-core/pull/965))
- Set inital signers ([#971](https://github.com/entropyxyz/entropy-core/pull/971))
- Add parent key threshold dynamically ([#974](https://github.com/entropyxyz/entropy-core/pull/974))
- TSS attestation endpoint ([#1001](https://github.com/entropyxyz/entropy-core/pull/1001)

### Changed
- Fix TSS `AccountId` keys in chainspec ([#993](https://github.com/entropyxyz/entropy-core/pull/993))
Expand Down
2 changes: 2 additions & 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/shared/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ serde ={ version="1.0", default-features=false, features=["derive"] }
serde_derive="1.0.147"
strum ="0.26.3"
strum_macros="0.26.4"
blake2 ={ version="0.10.4", default-features=false }

sp-runtime ={ version="32.0.0", default-features=false, optional=true, features=["serde"] }
sp-std ={ version="12.0.0", default-features=false }
Expand Down
20 changes: 20 additions & 0 deletions crates/shared/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

#![allow(dead_code)]
use super::constants::VERIFICATION_KEY_LENGTH;
use blake2::{Blake2b512, Digest};
#[cfg(not(feature = "wasm"))]
use codec::alloc::vec::Vec;
use codec::{Decode, Encode};
Expand Down Expand Up @@ -99,3 +100,22 @@ pub enum HashingAlgorithm {

/// A compressed, serialized [synedrion::ecdsa::VerifyingKey<k256::Secp256k1>]
pub type EncodedVerifyingKey = [u8; VERIFICATION_KEY_LENGTH as usize];

/// Input data to be included in a TDX attestation
pub struct QuoteInputData(pub [u8; 64]);

impl QuoteInputData {
pub fn new(
tss_account_id: [u8; 32],
x25519_public_key: X25519PublicKey,
nonce: [u8; 32],
block_number: u32,
) -> Self {
let mut hasher = Blake2b512::new();
hasher.update(tss_account_id);
hasher.update(x25519_public_key);
hasher.update(nonce);
hasher.update(block_number.to_be_bytes());
Self(hasher.finalize().into())
}
}
4 changes: 3 additions & 1 deletion crates/threshold-signature-server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -83,6 +84,7 @@ ethers-core ="2.0.14"
schnorrkel ={ version="0.11.4", default-features=false, features=["std"] }
schemars ={ version="0.8.21" }
subxt-signer="0.35.3"
tdx-quote ={ git="https://github.com/entropyxyz/tdx-quote", features=["mock"] }

# Note: We don't specify versions here because otherwise we run into a cyclical dependency between
# `entropy-tss` and `entropy-testing-utils` when we try and publish the `entropy-tss` crate.
Expand All @@ -102,7 +104,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
Expand Down
69 changes: 69 additions & 0 deletions crates/threshold-signature-server/src/attestation/api.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Copyright (C) 2023 Entropy Cryptography Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

use crate::{attestation::errors::AttestationErr, AppState};
use axum::{body::Bytes, extract::State, http::StatusCode};

/// HTTP POST endpoint to initiate a TDX attestation.
/// Not yet implemented.
#[cfg(not(any(test, feature = "unsafe")))]
pub async fn attest(
State(_app_state): State<AppState>,
_input: Bytes,
) -> Result<StatusCode, AttestationErr> {
// Non-mock attestation (the real thing) will go here
Err(AttestationErr::NotImplemented)
}

/// HTTP POST endpoint to initiate a mock TDX attestation for testing on non-TDX hardware.
/// The body of the request should be a 32 byte random nonce used to show 'freshness' of the
/// quote.
/// The response body contains a mock TDX v4 quote serialized as described in the
/// [Index TDX DCAP Quoting Library API](https://download.01.org/intel-sgx/latest/dcap-latest/linux/docs/Intel_TDX_DCAP_Quoting_Library_API.pdf).
#[cfg(any(test, feature = "unsafe"))]
pub async fn attest(
State(app_state): State<AppState>,
input: Bytes,
) -> Result<(StatusCode, Bytes), AttestationErr> {
use crate::{chain_api::get_rpc, get_signer_and_x25519_secret};
use rand_core::OsRng;
use sp_core::Pair;

// TODO (#982) confirm with the chain that an attestation should be happenning
let nonce = input.as_ref().try_into()?;

let rpc = get_rpc(&app_state.configuration.endpoint).await?;

let block_number =
rpc.chain_get_header(None).await?.ok_or_else(|| AttestationErr::BlockNumber)?.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 = entropy_shared::QuoteInputData::new(
signer.signer().public().into(),
*public_key.as_bytes(),
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, Bytes::from(quote.as_bytes().to_vec())))
}
46 changes: 46 additions & 0 deletions crates/threshold-signature-server/src/attestation/errors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Copyright (C) 2023 Entropy Cryptography Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

use std::array::TryFromSliceError;

use axum::{
http::StatusCode,
response::{IntoResponse, Response},
};
use thiserror::Error;

#[derive(Debug, Error)]
pub enum AttestationErr {
#[error("Generic Substrate error: {0}")]
GenericSubstrate(#[from] subxt::error::Error),
#[error("User Error: {0}")]
UserErr(#[from] crate::user::UserErr),
#[cfg(not(any(test, feature = "unsafe")))]
#[error("Not yet implemented")]
NotImplemented,
#[error("Input must be 32 bytes: {0}")]
TryFromSlice(#[from] TryFromSliceError),
#[cfg(any(test, feature = "unsafe"))]
#[error("Could not get block number")]
BlockNumber,
}

impl IntoResponse for AttestationErr {
fn into_response(self) -> Response {
tracing::error!("{:?}", format!("{self}"));
let body = format!("{self}").into_bytes();
(StatusCode::INTERNAL_SERVER_ERROR, body).into_response()
}
}
22 changes: 22 additions & 0 deletions crates/threshold-signature-server/src/attestation/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright (C) 2023 Entropy Cryptography Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

//! Makes attestations that this program is running on TDX hardware
pub mod api;
pub mod errors;

#[cfg(test)]
mod tests;
51 changes: 51 additions & 0 deletions crates/threshold-signature-server/src/attestation/tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright (C) 2023 Entropy Cryptography Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use crate::helpers::tests::{initialize_test_logger, spawn_testing_validators};
use entropy_kvdb::clean_tests;
use entropy_shared::QuoteInputData;
use entropy_testing_utils::{
constants::{TSS_ACCOUNTS, X25519_PUBLIC_KEYS},
substrate_context::test_node_process_testing_state,
};
use serial_test::serial;

#[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 nonce = [0; 32];
let client = reqwest::Client::new();
let res = client
.post(format!("http://127.0.0.1:3001/attest"))
.body(nonce.to_vec())
.send()
.await
.unwrap();
assert_eq!(res.status(), 200);
let quote = res.bytes().await.unwrap();

// This internally verifies the signature in the quote
let quote = tdx_quote::Quote::from_bytes(&quote).unwrap();

// Check the input data of the quote
let expected_input_data =
QuoteInputData::new(TSS_ACCOUNTS[0].0, X25519_PUBLIC_KEYS[0], nonce, 0);
assert_eq!(quote.report_input_data(), expected_input_data.0);
}
3 changes: 3 additions & 0 deletions crates/threshold-signature-server/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@
//! [sled](https://docs.rs/sled)
#![doc(html_logo_url = "https://entropy.xyz/assets/logo_02.png")]
pub use entropy_client::chain_api;
pub(crate) mod attestation;
pub(crate) mod health;
pub mod helpers;
pub(crate) mod node_info;
Expand Down Expand Up @@ -149,6 +150,7 @@ pub use crate::helpers::{
validator::{get_signer, get_signer_and_x25519_secret},
};
use crate::{
attestation::api::attest,
health::api::healthz,
launch::Configuration,
node_info::api::{hashes, version as get_version},
Expand Down Expand Up @@ -178,6 +180,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))
Expand Down
6 changes: 5 additions & 1 deletion crates/threshold-signature-server/src/validator/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

use std::string::FromUtf8Error;
use std::{array::TryFromSliceError, string::FromUtf8Error};

use crate::signing_client::ProtocolErr;
use axum::{
Expand Down Expand Up @@ -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 {
Expand Down
1 change: 1 addition & 0 deletions deny.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ exceptions=[
{ allow=["AGPL-3.0"], name="entropy-programs-core" },
{ allow=["AGPL-3.0"], name="entropy-programs-runtime" },
{ allow=["AGPL-3.0"], name="synedrion" },
{ allow=["AGPL-3.0"], name="tdx-quote" },

# These are the only crates using these licenses, put them here instead of globally allowing
# them
Expand Down

0 comments on commit c5df03f

Please sign in to comment.