Skip to content

Commit

Permalink
Add quote guards to ServerInfo related extrinsics (#1123)
Browse files Browse the repository at this point in the history
* Add quote guard to `change_endpoint` extrinsic

* Add quote guard to `change_threshold_accounts` extrinsic

* Bump metadata

* RustFmt

* Get `entropy-client` test for `change_endpoint` working

* Get `change_threshold_account` test compiling

Doesn't work yet though, but this is at least a good checkpoint

* Add way to `request_attestation` from client

* Almost have `change_threshold_account` test working

Faling to verify the PCK though...

* TaploFmt

* Be a bit more descriptive with the TSS public key variable

* Make `update_threshold_account()` use updated PCK

It looks like if the TSS Account ID and X25519 public keys are changing then we're probably on
different hardware so the PCK will also change.

This also gets the client test for the extrisnic passing.

* Clean up `change_threshold_account()` test

* Clean up the `request_attestation` client method a bit

* Get `entropy-test-cli` compiling again

* Remove unnecessary `.clone()`

* Update `test-cli` for new extrinsic arguments

* Get staking tests working again

* Get Staking benchmarks compiling

* Get `change_endpoint` benchmark working

* Get `change_threshold_accounts` benchmark working

* RustFmt benches

* Use better mock endpoint

* Switch to requiring a PCK chain instead of a certificate directly

This matches what `validate()` does and prevents us from having an invalid PCK become part of
`ServerInfo`.

* Bump metadata

* Update `client` to use PCK certificate chains

* Update the `test-cli` to use PCK certificate chains

* Undo some formatting changes

* Variables mystery amount

* Add `CHANGELOG` entry

* Revert "Add `CHANGELOG` entry"

This reverts commit fe7aadd.

* Updated `CHANGELOG` without formatting
  • Loading branch information
HCastano authored Nov 8, 2024
1 parent 07225c7 commit 72e6d33
Show file tree
Hide file tree
Showing 12 changed files with 442 additions and 63 deletions.
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ At the moment this project **does not** adhere to
`AttestationQueue` config types were removed from the Attestation pallet.
- In [#1068](https://github.com/entropyxyz/entropy-core/pull/1068) an extra type `PckCertChainVerifier`
was added to the staking extension pallet's `Config` trait.
- In [#1134](https://github.com/entropyxyz/entropy-core/pull/1134/) the ```no-sync``` option was removed
- In [#1123](https://github.com/entropyxyz/entropy-core/pull/1123/) the `change_endpoint()` and
`change_threshold_accounts()` extrinsics got new TDX `quote` related parameters added.
- In [#1134](https://github.com/entropyxyz/entropy-core/pull/1134/) the `--no-sync` option was
removed.

### Added
- Protocol message versioning ([#1140](https://github.com/entropyxyz/entropy-core/pull/1140))
Expand All @@ -26,6 +29,7 @@ At the moment this project **does not** adhere to
- Use correct key rotation endpoint in OCW ([#1104](https://github.com/entropyxyz/entropy-core/pull/1104))
- Change attestation flow to be pull based ([#1109](https://github.com/entropyxyz/entropy-core/pull/1109/))
- Handle PCK certificates ([#1068](https://github.com/entropyxyz/entropy-core/pull/1068))
- 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/))

## [0.3.0-rc.1](https://github.com/entropyxyz/entropy-core/compare/release/v0.2.0...release/v0.3.0-rc.1) - 2024-10-04
Expand Down
1 change: 1 addition & 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/client/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ tokio ={ version="1.41", features=["time"] }
serial_test ="3.1.1"
sp-keyring ="34.0.0"
entropy-testing-utils={ path="../testing-utils" }
tdx-quote ={ version="0.0.1", features=["mock"] }

[features]
default=["native", "full-client-native"]
Expand Down
Binary file modified crates/client/entropy_metadata.scale
Binary file not shown.
45 changes: 41 additions & 4 deletions crates/client/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -341,8 +341,10 @@ pub async fn change_endpoint(
rpc: &LegacyRpcMethods<EntropyConfig>,
user_keypair: sr25519::Pair,
new_endpoint: String,
quote: Vec<u8>,
) -> anyhow::Result<EndpointChanged> {
let change_endpoint_tx = entropy::tx().staking_extension().change_endpoint(new_endpoint.into());
let change_endpoint_tx =
entropy::tx().staking_extension().change_endpoint(new_endpoint.into(), quote);
let in_block =
submit_transaction_with_pair(api, rpc, &user_keypair, &change_endpoint_tx, None).await?;
let result_event = in_block
Expand All @@ -358,13 +360,18 @@ pub async fn change_threshold_accounts(
user_keypair: sr25519::Pair,
new_tss_account: String,
new_x25519_public_key: String,
new_pck_certificate_chain: Vec<Vec<u8>>,
quote: Vec<u8>,
) -> anyhow::Result<ThresholdAccountChanged> {
let tss_account = SubxtAccountId32::from_str(&new_tss_account)?;
let x25519_public_key = hex::decode(new_x25519_public_key)?
.try_into()
.map_err(|_| anyhow!("X25519 pub key needs to be 32 bytes"))?;
let change_threshold_accounts = entropy::tx().staking_extension().change_threshold_accounts(
tss_account,
hex::decode(new_x25519_public_key)?
.try_into()
.map_err(|_| anyhow!("X25519 pub key needs to be 32 bytes"))?,
x25519_public_key,
new_pck_certificate_chain,
quote,
);
let in_block =
submit_transaction_with_pair(api, rpc, &user_keypair, &change_threshold_accounts, None)
Expand Down Expand Up @@ -414,3 +421,33 @@ async fn jumpstart_inner(

Ok(())
}

/// An extrinsic to indicate to the chain that it should expect an attestation from the `signer` at
/// some point in the near future.
///
/// The returned `nonce` must be used when generating a `quote` for the chain.
#[tracing::instrument(
skip_all,
fields(
attestee = ?attestee.public(),
)
)]
pub async fn request_attestation(
api: &OnlineClient<EntropyConfig>,
rpc: &LegacyRpcMethods<EntropyConfig>,
attestee: sr25519::Pair,
) -> Result<Vec<u8>, ClientError> {
tracing::debug!("{} is requesting an attestation.", attestee.public());

let request_attestation = entropy::tx().attestation().request_attestation();

let result =
submit_transaction_with_pair(api, rpc, &attestee, &request_attestation, None).await?;
let result_event = result
.find_first::<entropy::attestation::events::AttestationIssued>()?
.ok_or(crate::errors::SubstrateError::NoEvent)?;

let nonce = result_event.0;

Ok(nonce)
}
112 changes: 97 additions & 15 deletions crates/client/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,23 @@ use crate::{
},
get_api, get_rpc, EntropyConfig,
},
change_endpoint, change_threshold_accounts, register, remove_program, store_program,
change_endpoint, change_threshold_accounts, register, remove_program, request_attestation,
store_program,
substrate::query_chain,
update_programs,
};

use entropy_testing_utils::{
constants::{TEST_PROGRAM_WASM_BYTECODE, TSS_ACCOUNTS},
helpers::{derive_mock_pck_verifying_key, encode_verifying_key},
constants::{TEST_PROGRAM_WASM_BYTECODE, TSS_ACCOUNTS, X25519_PUBLIC_KEYS},
helpers::encode_verifying_key,
jump_start_network, spawn_testing_validators,
substrate_context::test_context_stationary,
test_node_process_testing_state, ChainSpecType,
};
use rand::{
rngs::{OsRng, StdRng},
SeedableRng,
};
use serial_test::serial;
use sp_core::{sr25519, Pair, H256};
use sp_keyring::AccountKeyring;
Expand All @@ -36,7 +42,33 @@ async fn test_change_endpoint() {
let api = get_api(&substrate_context.node_proc.ws_url).await.unwrap();
let rpc = get_rpc(&substrate_context.node_proc.ws_url).await.unwrap();

let result = change_endpoint(&api, &rpc, one.into(), "new_endpoint".to_string()).await.unwrap();
// By using this `Alice` account we can skip the `request_attestation` step since this is
// already set up at genesis.
let tss_account_id = &TSS_ACCOUNTS[0];
let x25519_public_key = X25519_PUBLIC_KEYS[0];

// This nonce is what was used in the genesis config for `Alice`.
let nonce = [0; 32];

let quote = {
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);

let mut pck_seeder = StdRng::from_seed(public_key.0);
let pck = tdx_quote::SigningKey::random(&mut pck_seeder);

tdx_quote::Quote::mock(signing_key.clone(), pck, input_data.0).as_bytes().to_vec()
};

let result =
change_endpoint(&api, &rpc, one.into(), "new_endpoint".to_string(), quote).await.unwrap();

assert_eq!(
format!("{:?}", result),
format!(
Expand All @@ -57,33 +89,83 @@ async fn test_change_threshold_accounts() {

let api = get_api(&substrate_context.node_proc.ws_url).await.unwrap();
let rpc = get_rpc(&substrate_context.node_proc.ws_url).await.unwrap();
let x25519_public_key = [0u8; 32];
let result = change_threshold_accounts(

// We need to use an account that's not a validator (so not our default development/test accounts)
// otherwise we're not able to update the TSS and X25519 keys for our existing validator.
let non_validator_seed =
"gospel prosper cactus remember snap enact refuse review bind rescue guard sock";
let (tss_signer_pair, x25519_secret) =
entropy_testing_utils::get_signer_and_x25519_secret_from_mnemonic(non_validator_seed)
.unwrap();

let tss_public_key = tss_signer_pair.signer().public();
let x25519_public_key = x25519_dalek::PublicKey::from(&x25519_secret);

// We need to give our new TSS account some funds before it can request an attestation.
let dest = tss_signer_pair.account_id().clone().into();
let amount = 10 * entropy_shared::MIN_BALANCE;
let balance_transfer_tx = entropy::tx().balances().transfer_allow_death(dest, amount);
let _transfer_result = crate::substrate::submit_transaction_with_pair(
&api,
&rpc,
one.into(),
AccountId32(one.pair().public().0.into()).to_string(),
hex::encode(x25519_public_key),
&one.pair(),
&balance_transfer_tx,
None,
)
.await
.unwrap();

let provisioning_certification_key = {
let key = derive_mock_pck_verifying_key(&TSS_ACCOUNTS[0]);
BoundedVec(encode_verifying_key(&key).unwrap().to_vec())
// When we request an attestation we get a nonce back that we must use when generating our quote.
let nonce = request_attestation(&api, &rpc, tss_signer_pair.signer().clone()).await.unwrap();
let nonce: [u8; 32] = nonce.try_into().unwrap();

let mut pck_seeder = StdRng::from_seed(tss_public_key.0.clone());
let pck = tdx_quote::SigningKey::random(&mut pck_seeder);
let encoded_pck = encode_verifying_key(&pck.verifying_key()).unwrap().to_vec();

// Our runtime is using the mock `PckCertChainVerifier`, which means that the expected
// "certificate" basically is just our TSS account ID. This account needs to match the one
// used to sign the following `quote`.
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,
);

let signing_key = tdx_quote::SigningKey::random(&mut OsRng);
tdx_quote::Quote::mock(signing_key.clone(), pck.clone(), input_data.0).as_bytes().to_vec()
};

let result = change_threshold_accounts(
&api,
&rpc,
one.into(),
tss_public_key.to_string(),
hex::encode(*x25519_public_key.as_bytes()),
pck_certificate_chain,
quote,
)
.await
.unwrap();

assert_eq!(
format!("{:?}", result),
format!(
"{:?}",
events::ThresholdAccountChanged(
AccountId32(one.pair().public().0),
ServerInfo {
tss_account: AccountId32(one.pair().public().0),
x25519_public_key,
tss_account: AccountId32(tss_public_key.0),
x25519_public_key: *x25519_public_key.as_bytes(),
endpoint: "127.0.0.1:3001".as_bytes().to_vec(),
provisioning_certification_key,
provisioning_certification_key: BoundedVec(encoded_pck),
}
)
)
Expand Down
23 changes: 21 additions & 2 deletions crates/test-cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,11 @@ enum CliCommand {
ChangeEndpoint {
/// New endpoint to change to (ex. "127.0.0.1:3001")
new_endpoint: String,
/// The Intel TDX quote used to prove that this TSS is running on TDX hardware.
///
/// The quote format is specified in:
/// https://download.01.org/intel-sgx/latest/dcap-latest/linux/docs/Intel_TDX_DCAP_Quoting_Library_API.pdf
quote: String,
/// The mnemonic for the validator stash account to use for the call, should be stash address
#[arg(short, long)]
mnemonic_option: Option<String>,
Expand All @@ -155,6 +160,13 @@ enum CliCommand {
new_tss_account: String,
/// New x25519 public key
new_x25519_public_key: String,
/// The new Provisioning Certification Key (PCK) certificate chain to be used for the TSS.
new_pck_certificate_chain: Vec<String>,
/// The Intel TDX quote used to prove that this TSS is running on TDX hardware.
///
/// The quote format is specified in:
/// https://download.01.org/intel-sgx/latest/dcap-latest/linux/docs/Intel_TDX_DCAP_Quoting_Library_API.pdf
quote: String,
/// The mnemonic for the validator stash account to use for the call, should be stash address
#[arg(short, long)]
mnemonic_option: Option<String>,
Expand Down Expand Up @@ -433,7 +445,7 @@ pub async fn run_command(

Ok("Got status".to_string())
},
CliCommand::ChangeEndpoint { new_endpoint, mnemonic_option } => {
CliCommand::ChangeEndpoint { new_endpoint, quote, mnemonic_option } => {
let mnemonic = if let Some(mnemonic_option) = mnemonic_option {
mnemonic_option
} else {
Expand All @@ -443,13 +455,16 @@ pub async fn run_command(
let user_keypair = <sr25519::Pair as Pair>::from_string(&mnemonic, None)?;
println!("User account for current call: {}", user_keypair.public());

let result_event = change_endpoint(&api, &rpc, user_keypair, new_endpoint).await?;
let result_event =
change_endpoint(&api, &rpc, user_keypair, new_endpoint, quote.into()).await?;
println!("Event result: {:?}", result_event);
Ok("Endpoint changed".to_string())
},
CliCommand::ChangeThresholdAccounts {
new_tss_account,
new_x25519_public_key,
new_pck_certificate_chain,
quote,
mnemonic_option,
} => {
let mnemonic = if let Some(mnemonic_option) = mnemonic_option {
Expand All @@ -460,12 +475,16 @@ pub async fn run_command(
let user_keypair = <sr25519::Pair as Pair>::from_string(&mnemonic, None)?;
println!("User account for current call: {}", user_keypair.public());

let new_pck_certificate_chain =
new_pck_certificate_chain.iter().cloned().map(|i| i.into()).collect::<_>();
let result_event = change_threshold_accounts(
&api,
&rpc,
user_keypair,
new_tss_account,
new_x25519_public_key,
new_pck_certificate_chain,
quote.into(),
)
.await?;
println!("Event result: {:?}", result_event);
Expand Down
2 changes: 2 additions & 0 deletions crates/testing-utils/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,5 @@ pub use entropy_tss::helpers::tests::{
};
pub use node_proc::TestNodeProcess;
pub use substrate_context::*;

pub use entropy_tss::helpers::validator::get_signer_and_x25519_secret_from_mnemonic;
2 changes: 1 addition & 1 deletion crates/threshold-signature-server/src/helpers/validator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ fn get_x25519_secret_from_hkdf(hkdf: &Hkdf<Sha256>) -> Result<StaticSecret, User
Ok(static_secret)
}

/// For testing where we sometimes don't have access to the kvdb, derive directly from the mnemnic
/// For testing where we sometimes don't have access to the kvdb, derive directly from the mnemonic
#[cfg(any(test, feature = "test_helpers"))]
pub fn get_signer_and_x25519_secret_from_mnemonic(
mnemonic: &str,
Expand Down
Loading

0 comments on commit 72e6d33

Please sign in to comment.