Skip to content
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 quote guards to ServerInfo related extrinsics #1123

Open
wants to merge 32 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
fe47faa
Add quote guard to `change_endpoint` extrinsic
HCastano Oct 21, 2024
6c18434
Add quote guard to `change_threshold_accounts` extrinsic
HCastano Oct 21, 2024
641134b
Bump metadata
HCastano Oct 21, 2024
ae73f0d
Merge branch 'master' into hc/add-quote-guards
HCastano Oct 28, 2024
b2e5d01
RustFmt
HCastano Oct 28, 2024
8613c80
Get `entropy-client` test for `change_endpoint` working
HCastano Oct 28, 2024
fdff83a
Get `change_threshold_account` test compiling
HCastano Oct 28, 2024
abec5a9
Add way to `request_attestation` from client
HCastano Oct 28, 2024
9296095
Almost have `change_threshold_account` test working
HCastano Oct 28, 2024
d5f1545
TaploFmt
HCastano Oct 28, 2024
8daaa24
Be a bit more descriptive with the TSS public key variable
HCastano Oct 28, 2024
2e39ce9
Make `update_threshold_account()` use updated PCK
HCastano Oct 30, 2024
edc2f7c
Clean up `change_threshold_account()` test
HCastano Oct 30, 2024
ed5db5e
Clean up the `request_attestation` client method a bit
HCastano Oct 30, 2024
cc71358
Get `entropy-test-cli` compiling again
HCastano Oct 30, 2024
68c8f4c
Remove unnecessary `.clone()`
HCastano Oct 30, 2024
f7571c5
Update `test-cli` for new extrinsic arguments
HCastano Nov 1, 2024
5a0283c
Get staking tests working again
HCastano Nov 1, 2024
3518cd0
Get Staking benchmarks compiling
HCastano Nov 1, 2024
70da5e3
Get `change_endpoint` benchmark working
HCastano Nov 1, 2024
a4f41c7
Get `change_threshold_accounts` benchmark working
HCastano Nov 1, 2024
f70db4b
RustFmt benches
HCastano Nov 4, 2024
c68a4ed
Use better mock endpoint
HCastano Nov 4, 2024
f237a73
Switch to requiring a PCK chain instead of a certificate directly
HCastano Nov 4, 2024
212628d
Bump metadata
HCastano Nov 4, 2024
21bdfa7
Update `client` to use PCK certificate chains
HCastano Nov 4, 2024
492b9d6
Update the `test-cli` to use PCK certificate chains
HCastano Nov 4, 2024
2434d4d
Undo some formatting changes
HCastano Nov 4, 2024
1628f8a
Variables mystery amount
HCastano Nov 4, 2024
fe7aadd
Add `CHANGELOG` entry
HCastano Nov 4, 2024
5a7ab4d
Revert "Add `CHANGELOG` entry"
HCastano Nov 4, 2024
dd695c9
Updated `CHANGELOG` without formatting
HCastano Nov 4, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,16 @@ 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.

### Changed
- 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(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For followup: If this is going to also be called by entropy-tss, which i think we probably will want to when we add a generate quote endpoint - it will need to go in a different module as stuff in this one is behind the full-client feature flag.

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>()?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thats nice that you can get the nonce like this - i didnt understand that from the last PR.

.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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah ok, i was thinking we were going to ditch having the pending attestation set at genesis because it wasn't used anymore

// 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,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For a followup:

Maybe we wanna have these be given as hex. I think otherwise they need to be given as [234, 1, 43...].

It will be easiest if we have them be given in whatever format the TSS server gives them to the operator in the quote generation endpoint, which doesn't yet exist. So probably we can fix this once it does exist.

/// 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>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For followup: If given as strings, it probably makes sense for them to be PEM encoded as this is the standard for encoding x509 certificates as utf-8. This would mean adding a dependency to the test-cli to decode them. Otherwise we could have them be read in from files i guess.

We can probably leave this until we develop tooling for operators to be able to retrieve these certificates and see what makes most sense then.

/// 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
Loading