Skip to content

Commit

Permalink
Use context, not block number in TDX quote input data (#1179)
Browse files Browse the repository at this point in the history
* Use context, not block number in quote input

* Get context from querystring

* Rm blocknumber from quoteinput in staking pallet benchmarking code

* Fix pallet staking benchmarking

* Update client tests

* Error handle

* Changelog

* Neater handling of querystring

* Make context enum non exhaustive
  • Loading branch information
ameba23 authored Nov 25, 2024
1 parent b74ea7d commit d62db3b
Show file tree
Hide file tree
Showing 11 changed files with 98 additions and 61 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ At the moment this project **does not** adhere to
- In [#1153](https://github.com/entropyxyz/entropy-core/pull/1153/) the program runtime was updated to accept
multiple oracle inputs, this means any programs that were compiled and used need to be recompiled to the new
runtime
- In [#1179](https://github.com/entropyxyz/entropy-core/pull/1179) the format of TDX Quote input data has
been changed.

### Added
- Protocol message versioning ([#1140](https://github.com/entropyxyz/entropy-core/pull/1140))
Expand All @@ -37,6 +39,7 @@ runtime
- Add quote guards to `ServerInfo` related extrinsics ([#1123](https://github.com/entropyxyz/entropy-core/pull/1123/))
- Remove declare synced ([#1134](https://github.com/entropyxyz/entropy-core/pull/1134/))
- Update programs to accept multiple oracle data ([#1153](https://github.com/entropyxyz/entropy-core/pull/1153/))
- Use context, not block number in TDX quote input data ([#1179](https://github.com/entropyxyz/entropy-core/pull/1179))

## [0.3.0](https://github.com/entropyxyz/entropy-core/compare/release/v0.2.0...release/v0.3.0) - 2024-10-22

Expand Down
11 changes: 3 additions & 8 deletions crates/client/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use crate::{
update_programs,
};

use entropy_shared::{QuoteContext, QuoteInputData};
use entropy_testing_utils::{
constants::{TEST_PROGRAM_WASM_BYTECODE, TSS_ACCOUNTS, X25519_PUBLIC_KEYS},
helpers::encode_verifying_key,
Expand Down Expand Up @@ -54,11 +55,8 @@ async fn test_change_endpoint() {
let signing_key = tdx_quote::SigningKey::random(&mut OsRng);
let public_key = sr25519::Public(tss_account_id.0);

// We need to add `1` here since the quote is being checked in the next block
let block_number = rpc.chain_get_header(None).await.unwrap().unwrap().number + 1;

let input_data =
entropy_shared::QuoteInputData::new(public_key, x25519_public_key, nonce, block_number);
QuoteInputData::new(public_key, x25519_public_key, nonce, QuoteContext::ChangeEndpoint);

let mut pck_seeder = StdRng::from_seed(public_key.0);
let pck = tdx_quote::SigningKey::random(&mut pck_seeder);
Expand Down Expand Up @@ -129,14 +127,11 @@ async fn test_change_threshold_accounts() {
let pck_certificate_chain = vec![tss_public_key.0.to_vec()];

let quote = {
// We need to add `1` here since the quote is being checked in the next block
let block_number = rpc.chain_get_header(None).await.unwrap().unwrap().number + 1;

let input_data = entropy_shared::QuoteInputData::new(
tss_public_key,
*x25519_public_key.as_bytes(),
nonce,
block_number,
QuoteContext::ChangeThresholdAccounts,
);

let signing_key = tdx_quote::SigningKey::random(&mut OsRng);
Expand Down
18 changes: 16 additions & 2 deletions crates/shared/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,17 +123,29 @@ impl QuoteInputData {
tss_account_id: T,
x25519_public_key: X25519PublicKey,
nonce: [u8; 32],
block_number: u32,
context: QuoteContext,
) -> Self {
let mut hasher = Blake2b512::new();
hasher.update(tss_account_id.encode());
hasher.update(x25519_public_key);
hasher.update(nonce);
hasher.update(block_number.to_be_bytes());
hasher.update(context.encode());
Self(hasher.finalize().into())
}
}

/// An indicator as to the context in which a quote is intended to be used
#[derive(Clone, Encode, Decode, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum QuoteContext {
/// To be used in the `validate` extrinsic
Validate,
/// To be used in the `change_endpoint` extrinsic
ChangeEndpoint,
/// To be used in the `change_threshold_accounts` extrinsic
ChangeThresholdAccounts,
}

/// A trait for types which can handle attestation requests.
#[cfg(not(feature = "wasm"))]
pub trait AttestationHandler<AccountId> {
Expand All @@ -143,6 +155,7 @@ pub trait AttestationHandler<AccountId> {
x25519_public_key: X25519PublicKey,
provisioning_certification_key: BoundedVecEncodedVerifyingKey,
quote: Vec<u8>,
context: QuoteContext,
) -> Result<(), sp_runtime::DispatchError>;

/// Indicate to the attestation handler that a quote is desired.
Expand All @@ -160,6 +173,7 @@ impl<AccountId> AttestationHandler<AccountId> for () {
_x25519_public_key: X25519PublicKey,
_provisioning_certification_key: BoundedVecEncodedVerifyingKey,
_quote: Vec<u8>,
_context: QuoteContext,
) -> Result<(), sp_runtime::DispatchError> {
Ok(())
}
Expand Down
52 changes: 39 additions & 13 deletions crates/threshold-signature-server/src/attestation/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,16 @@ use crate::{
},
AppState,
};
use axum::{body::Bytes, extract::State, http::StatusCode};
use axum::{
body::Bytes,
extract::{Query, State},
http::StatusCode,
};
use entropy_client::user::request_attestation;
use entropy_kvdb::kv_manager::KvManager;
use entropy_shared::OcwMessageAttestationRequest;
use entropy_shared::{OcwMessageAttestationRequest, QuoteContext};
use parity_scale_codec::Decode;
use serde::Deserialize;
use sp_core::Pair;
use subxt::tx::PairSigner;
use x25519_dalek::StaticSecret;
Expand Down Expand Up @@ -68,8 +73,10 @@ pub async fn attest(
.ok_or_else(|| AttestationErr::Unexpected)?
};

// We add 1 to the block number as this will be processed in the next block
let quote = create_quote(block_number + 1, nonce, &signer, &x25519_secret).await?;
// TODO (#1181): since this endpoint is currently only used in tests we don't know what the context should be
let context = QuoteContext::Validate;

let quote = create_quote(nonce, &signer, &x25519_secret, context).await?;

// Submit the quote
let attest_tx = entropy::tx().attestation().attest(quote.clone());
Expand All @@ -85,6 +92,7 @@ pub async fn attest(
/// and `change_tss_accounts` extrinsics.
pub async fn get_attest(
State(app_state): State<AppState>,
Query(context_querystring): Query<QuoteContextQuery>,
) -> Result<(StatusCode, Vec<u8>), AttestationErr> {
let (signer, x25519_secret) = get_signer_and_x25519_secret(&app_state.kv_store).await?;
let api = get_api(&app_state.configuration.endpoint).await?;
Expand All @@ -93,23 +101,20 @@ pub async fn get_attest(
// Request attestation to get nonce
let nonce = request_attestation(&api, &rpc, signer.signer()).await?;

// We also need the current block number as input
let block_number =
rpc.chain_get_header(None).await?.ok_or_else(|| AttestationErr::BlockNumber)?.number;
let context = context_querystring.as_quote_context()?;

// We add 1 to the block number as this will be processed in the next block
let quote = create_quote(block_number + 1, nonce, &signer, &x25519_secret).await?;
let quote = create_quote(nonce, &signer, &x25519_secret, context).await?;

Ok((StatusCode::OK, quote))
}

/// Create a mock quote for testing on non-TDX hardware
#[cfg(not(feature = "production"))]
pub async fn create_quote(
block_number: u32,
nonce: [u8; 32],
signer: &PairSigner<EntropyConfig, sp_core::sr25519::Pair>,
x25519_secret: &StaticSecret,
context: QuoteContext,
) -> Result<Vec<u8>, AttestationErr> {
use rand::{rngs::StdRng, SeedableRng};
use rand_core::OsRng;
Expand All @@ -124,7 +129,7 @@ pub async fn create_quote(
signer.signer().public(),
*public_key.as_bytes(),
nonce,
block_number,
context,
);

// This is generated deterministically from TSS account id
Expand Down Expand Up @@ -167,19 +172,40 @@ pub async fn validate_new_attestation(
/// Create a TDX quote in production
#[cfg(feature = "production")]
pub async fn create_quote(
block_number: u32,
nonce: [u8; 32],
signer: &PairSigner<EntropyConfig, sp_core::sr25519::Pair>,
x25519_secret: &StaticSecret,
context: QuoteContext,
) -> Result<Vec<u8>, AttestationErr> {
let public_key = x25519_dalek::PublicKey::from(x25519_secret);

let input_data = entropy_shared::QuoteInputData::new(
signer.signer().public(),
*public_key.as_bytes(),
nonce,
block_number,
context,
);

Ok(configfs_tsm::create_quote(input_data.0)?)
}

/// Querystring for the GET `/attest` endpoint
#[derive(Deserialize)]
pub struct QuoteContextQuery {
/// The context in which the requested quote will be used.
///
/// Must be one of `validate`, `change_endpoint`, `change_threshold_accounts`.
/// Eg: `http://127.0.0.1:3001/attest?context=validate`
context: String,
}

impl QuoteContextQuery {
fn as_quote_context(&self) -> Result<QuoteContext, AttestationErr> {
match self.context.as_str() {
"validate" => Ok(QuoteContext::Validate),
"change_endpoint" => Ok(QuoteContext::ChangeEndpoint),
"change_threshold_accounts" => Ok(QuoteContext::ChangeThresholdAccounts),
_ => Err(AttestationErr::UnknownContext),
}
}
}
2 changes: 2 additions & 0 deletions crates/threshold-signature-server/src/attestation/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ pub enum AttestationErr {
StaleData,
#[error("Attestation request: {0}")]
AttestationRequest(#[from] entropy_client::errors::AttestationRequestError),
#[error("Invalid or unknown context value given in query string")]
UnknownContext,
}

impl IntoResponse for AttestationErr {
Expand Down
8 changes: 6 additions & 2 deletions crates/threshold-signature-server/src/attestation/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,12 @@ async fn test_get_attest() {
let api = get_api(&cxt.ws_url).await.unwrap();
let rpc = get_rpc(&cxt.ws_url).await.unwrap();

let quote_bytes =
reqwest::get("http://127.0.0.1:3002/attest").await.unwrap().bytes().await.unwrap();
let quote_bytes = reqwest::get("http://127.0.0.1:3002/attest?context=validate")
.await
.unwrap()
.bytes()
.await
.unwrap();
let quote = Quote::from_bytes(&quote_bytes).unwrap();

let query =
Expand Down
11 changes: 3 additions & 8 deletions pallets/attestation/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ mod tests;

#[frame_support::pallet]
pub mod pallet {
use entropy_shared::{AttestationHandler, QuoteInputData};
use entropy_shared::{AttestationHandler, QuoteContext, QuoteInputData};
use frame_support::pallet_prelude::*;
use frame_support::traits::Randomness;
use frame_system::pallet_prelude::*;
Expand Down Expand Up @@ -205,6 +205,7 @@ pub mod pallet {
x25519_public_key: entropy_shared::X25519PublicKey,
provisioning_certification_key: entropy_shared::BoundedVecEncodedVerifyingKey,
quote: Vec<u8>,
context: QuoteContext,
) -> Result<(), DispatchError> {
// Check that we were expecting a quote from this validator by getting the associated
// nonce from PendingAttestations.
Expand All @@ -214,15 +215,9 @@ pub mod pallet {
// Parse the quote (which internally verifies the attestation key signature)
let quote = Quote::from_bytes(&quote).map_err(|_| Error::<T>::BadQuote)?;

// Get current block number
let block_number: u32 = {
let block_number = <frame_system::Pallet<T>>::block_number();
BlockNumberFor::<T>::try_into(block_number).unwrap_or_default()
};

// Check report input data matches the nonce, TSS details and block number
let expected_input_data =
QuoteInputData::new(attestee, x25519_public_key, nonce, block_number);
QuoteInputData::new(attestee, x25519_public_key, nonce, context);
ensure!(
quote.report_input_data() == expected_input_data.0,
Error::<T>::IncorrectInputData
Expand Down
12 changes: 7 additions & 5 deletions pallets/attestation/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
// along with this program. If not, see <https://www.gnu.org/licenses/>.

use crate::mock::*;
use entropy_shared::{AttestationHandler, QuoteInputData};
use entropy_shared::{AttestationHandler, QuoteContext, QuoteInputData};
use frame_support::{assert_noop, assert_ok};
use rand_core::OsRng;

Expand All @@ -32,13 +32,12 @@ fn verify_quote_works() {
let pck_encoded = tdx_quote::encode_verifying_key(pck.verifying_key()).unwrap();

let x25519_public_key = [0; 32];
let block_number = 0;

let input_data = QuoteInputData::new(
ATTESTEE, // TSS Account ID
x25519_public_key,
nonce,
block_number,
QuoteContext::Validate,
);

let quote = tdx_quote::Quote::mock(attestation_key.clone(), pck, input_data.0);
Expand All @@ -47,6 +46,7 @@ fn verify_quote_works() {
x25519_public_key,
sp_runtime::BoundedVec::try_from(pck_encoded.to_vec()).unwrap(),
quote.as_bytes().to_vec(),
QuoteContext::Validate,
));
})
}
Expand All @@ -63,13 +63,12 @@ fn verify_quote_fails_with_mismatched_input_data() {
let pck_encoded = tdx_quote::encode_verifying_key(pck.verifying_key()).unwrap();

let x25519_public_key = [0; 32];
let block_number = 0;

let input_data = QuoteInputData::new(
ATTESTEE, // TSS Account ID
x25519_public_key,
nonce,
block_number,
QuoteContext::Validate,
);

let quote = tdx_quote::Quote::mock(attestation_key.clone(), pck, input_data.0);
Expand All @@ -83,6 +82,7 @@ fn verify_quote_fails_with_mismatched_input_data() {
x25519_public_key,
sp_runtime::BoundedVec::try_from(pck_encoded.to_vec()).unwrap(),
quote.as_bytes().to_vec(),
QuoteContext::Validate,
),
crate::Error::<Test>::UnexpectedAttestation,
);
Expand All @@ -96,6 +96,7 @@ fn verify_quote_fails_with_mismatched_input_data() {
mismatched_x25519_public_key,
sp_runtime::BoundedVec::try_from(pck_encoded.to_vec()).unwrap(),
quote.as_bytes().to_vec(),
QuoteContext::Validate,
),
crate::Error::<Test>::IncorrectInputData,
);
Expand All @@ -116,6 +117,7 @@ fn verify_quote_fails_with_mismatched_input_data() {
x25519_public_key,
sp_runtime::BoundedVec::try_from(mismatched_pck_encoded.to_vec()).unwrap(),
quote.as_bytes().to_vec(),
QuoteContext::Validate,
),
crate::Error::<Test>::PckVerification
);
Expand Down
Loading

0 comments on commit d62db3b

Please sign in to comment.