From cfcb3672c8ce931ba13420aa474b03b07e399f99 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 11 Dec 2024 07:37:07 +0000 Subject: [PATCH 1/9] Bump the patch-dependencies group with 2 updates (#1212) Bumps the patch-dependencies group with 2 updates: [serde](https://github.com/serde-rs/serde) and [serde_derive](https://github.com/serde-rs/serde). Updates `serde` from 1.0.215 to 1.0.216 - [Release notes](https://github.com/serde-rs/serde/releases) - [Commits](https://github.com/serde-rs/serde/compare/v1.0.215...v1.0.216) Updates `serde_derive` from 1.0.215 to 1.0.216 - [Release notes](https://github.com/serde-rs/serde/releases) - [Commits](https://github.com/serde-rs/serde/compare/v1.0.215...v1.0.216) --- updated-dependencies: - dependency-name: serde dependency-type: direct:production update-type: version-update:semver-patch dependency-group: patch-dependencies - dependency-name: serde_derive dependency-type: direct:production update-type: version-update:semver-patch dependency-group: patch-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 8 ++++---- crates/test-cli/Cargo.toml | 2 +- node/cli/Cargo.toml | 2 +- pallets/staking/Cargo.toml | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cdf55f3b5..298582681 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11596,9 +11596,9 @@ checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" [[package]] name = "serde" -version = "1.0.215" +version = "1.0.216" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" +checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e" dependencies = [ "serde_derive", ] @@ -11625,9 +11625,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.215" +version = "1.0.216" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" +checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e" dependencies = [ "proc-macro2", "quote", diff --git a/crates/test-cli/Cargo.toml b/crates/test-cli/Cargo.toml index d9d9fc141..23c8c67b8 100644 --- a/crates/test-cli/Cargo.toml +++ b/crates/test-cli/Cargo.toml @@ -22,5 +22,5 @@ x25519-dalek ="2.0.1" sp-runtime ={ version="32.0.0", default-features=false } entropy-shared={ version="0.3.0", path="../shared" } serde_json ="1.0.133" -serde ={ version="1.0.215", features=["derive"] } +serde ={ version="1.0.216", features=["derive"] } reqwest ="0.12.9" diff --git a/node/cli/Cargo.toml b/node/cli/Cargo.toml index a33fbdefd..b1cf2d0cb 100644 --- a/node/cli/Cargo.toml +++ b/node/cli/Cargo.toml @@ -29,7 +29,7 @@ lazy_static ={ version="1.5.0", features=["spin_no_std"] } log ="0.4.22" pallet-im-online={ version="28.0.0" } rand ="0.8.5" -serde ={ version="1.0.215", features=["derive"] } +serde ={ version="1.0.216", features=["derive"] } serde_json ='1.0.133' # Substrate Client diff --git a/pallets/staking/Cargo.toml b/pallets/staking/Cargo.toml index 70da4e1c2..3cb777887 100644 --- a/pallets/staking/Cargo.toml +++ b/pallets/staking/Cargo.toml @@ -15,7 +15,7 @@ targets=['x86_64-unknown-linux-gnu'] codec ={ package="parity-scale-codec", version="3.6.3", default-features=false, features=["derive"] } scale-info ={ version="2.11", default-features=false, features=["derive"] } log ={ version="0.4.22", default-features=false } -serde ={ version="1.0.215", default-features=false } +serde ={ version="1.0.216", default-features=false } rand_chacha={ version="0.3", default-features=false } frame-benchmarking={ version="29.0.0", default-features=false, optional=true } From 6d3351eb347d5ff5812e49cfdd2ae1a6e6b06c73 Mon Sep 17 00:00:00 2001 From: peg Date: Wed, 11 Dec 2024 08:43:48 +0100 Subject: [PATCH 2/9] Test CLI command to retrieve quote and change endpoint / TSS account in one command (#1198) * Add get_quote_and_change_endpoint client fn, error handling * Cli command to change endpoint no longer takes quote * Doccomments * Clippy * Add context when calling attest endpoint * Error handle http response in GetTdxQuote command * Improve error handling when getting quotes * Use display not debug for building quote context querystring * Refactor handling of mnemonics in test-cli * Update change TSS endpoints client function and CLI command * get-tdx-quote command now takes quote context as an arguement * Changelog * Use From not Into --- CHANGELOG.md | 1 + crates/client/src/client.rs | 110 +++++++++++++++++++++++------ crates/client/src/errors.rs | 4 ++ crates/client/src/tests.rs | 4 +- crates/shared/src/types.rs | 12 ++++ crates/test-cli/src/lib.rs | 136 ++++++++++++++++-------------------- 6 files changed, 166 insertions(+), 101 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f64774980..3c1670223 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,7 @@ runtime - Protocol message versioning ([#1140](https://github.com/entropyxyz/entropy-core/pull/1140)) - CLI command to get oracle headings ([#1170](https://github.com/entropyxyz/entropy-core/pull/1170)) - Add TSS endpoint to get TDX quote ([#1173](https://github.com/entropyxyz/entropy-core/pull/1173)) +- Test CLI command to retrieve quote and change endpoint / TSS account in one command ([#1198](https://github.com/entropyxyz/entropy-core/pull/1198)) ### Changed - Use correct key rotation endpoint in OCW ([#1104](https://github.com/entropyxyz/entropy-core/pull/1104)) diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index 062de590a..71a5c240f 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -17,13 +17,12 @@ //! Used in integration tests and for the test-cli pub use crate::{ chain_api::{get_api, get_rpc}, - errors::ClientError, + errors::{ClientError, SubstrateError}, }; -use anyhow::anyhow; pub use entropy_protocol::{sign_and_encrypt::EncryptedSignedMessage, KeyParams}; +pub use entropy_shared::{HashingAlgorithm, QuoteContext}; use parity_scale_codec::Decode; use rand::Rng; -use std::str::FromStr; pub use synedrion::KeyShare; use crate::{ @@ -39,7 +38,7 @@ use crate::{ EntropyConfig, }, client::entropy::staking_extension::events::{EndpointChanged, ThresholdAccountChanged}, - substrate::{get_registered_details, submit_transaction_with_pair}, + substrate::{get_registered_details, query_chain, submit_transaction_with_pair}, user::{ self, get_all_signers_from_chain, get_validators_not_signer_for_relay, UserSignatureRequest, }, @@ -48,7 +47,6 @@ use crate::{ use base64::prelude::{Engine, BASE64_STANDARD}; use entropy_protocol::RecoverableSignature; -use entropy_shared::HashingAlgorithm; use futures::stream::StreamExt; use sp_core::{ sr25519::{self, Signature}, @@ -338,50 +336,90 @@ pub async fn put_register_request_on_chain( registered_event } -/// Changes the endpoint of a validator +/// Changes the endpoint of a validator, retrieving a TDX quote from the new endpoint internally +pub async fn get_quote_and_change_endpoint( + api: &OnlineClient, + rpc: &LegacyRpcMethods, + validator_keypair: sr25519::Pair, + new_endpoint: String, +) -> Result { + let quote = get_tdx_quote(&new_endpoint, QuoteContext::ChangeEndpoint).await?; + change_endpoint(api, rpc, validator_keypair, new_endpoint, quote).await +} + +/// Changes the endpoint of a validator, with a TDX quote given as an argument pub async fn change_endpoint( api: &OnlineClient, rpc: &LegacyRpcMethods, user_keypair: sr25519::Pair, new_endpoint: String, quote: Vec, -) -> anyhow::Result { +) -> Result { 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 .find_first::()? - .ok_or(anyhow!("Error with transaction"))?; + .ok_or(SubstrateError::NoEvent)?; Ok(result_event) } +/// Changes the threshold account info of a validator, retrieving a TDX quote from the new endpoint internally +pub async fn get_quote_and_change_threshold_accounts( + api: &OnlineClient, + rpc: &LegacyRpcMethods, + validator_keypair: sr25519::Pair, + new_tss_account: SubxtAccountId32, + new_x25519_public_key: [u8; 32], + new_pck_certificate_chain: Vec>, +) -> Result { + let quote = get_tdx_quote_with_validator_id( + api, + rpc, + &SubxtAccountId32(validator_keypair.public().0), + QuoteContext::ChangeThresholdAccounts, + ) + .await?; + change_threshold_accounts( + api, + rpc, + validator_keypair, + new_tss_account, + new_x25519_public_key, + new_pck_certificate_chain, + quote, + ) + .await +} + /// Changes the threshold account info of a validator pub async fn change_threshold_accounts( api: &OnlineClient, rpc: &LegacyRpcMethods, - user_keypair: sr25519::Pair, - new_tss_account: String, - new_x25519_public_key: String, + validator_keypair: sr25519::Pair, + new_tss_account: SubxtAccountId32, + new_x25519_public_key: [u8; 32], new_pck_certificate_chain: Vec>, quote: Vec, -) -> anyhow::Result { - 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"))?; +) -> Result { let change_threshold_accounts = entropy::tx().staking_extension().change_threshold_accounts( - tss_account, - x25519_public_key, + new_tss_account, + new_x25519_public_key, new_pck_certificate_chain, quote, ); - let in_block = - submit_transaction_with_pair(api, rpc, &user_keypair, &change_threshold_accounts, None) - .await?; + let in_block = submit_transaction_with_pair( + api, + rpc, + &validator_keypair, + &change_threshold_accounts, + None, + ) + .await?; let result_event = in_block .find_first::()? - .ok_or(anyhow!("Error with transaction"))?; + .ok_or(SubstrateError::NoEvent)?; Ok(result_event) } @@ -463,3 +501,31 @@ pub async fn get_oracle_headings( } Ok(headings) } + +/// Retrieve a TDX quote using the currently configured endpoint associated with the given validator +/// ID +pub async fn get_tdx_quote_with_validator_id( + api: &OnlineClient, + rpc: &LegacyRpcMethods, + validator_stash: &SubxtAccountId32, + quote_context: QuoteContext, +) -> Result, ClientError> { + let query = entropy::storage().staking_extension().threshold_servers(validator_stash); + let server_info = query_chain(api, rpc, query, None).await?.ok_or(ClientError::NoServerInfo)?; + + let tss_endpoint = std::str::from_utf8(&server_info.endpoint)?; + get_tdx_quote(tss_endpoint, quote_context).await +} + +/// Retrieve a TDX quote with a given socket address +pub async fn get_tdx_quote( + tss_endpoint: &str, + quote_context: QuoteContext, +) -> Result, ClientError> { + let response = + reqwest::get(format!("http://{}/attest?context={}", tss_endpoint, quote_context)).await?; + if response.status() != reqwest::StatusCode::OK { + return Err(ClientError::QuoteGet(response.text().await?)); + } + Ok(response.bytes().await?.to_vec()) +} diff --git a/crates/client/src/errors.rs b/crates/client/src/errors.rs index c2f4ab241..61cd74d8f 100644 --- a/crates/client/src/errors.rs +++ b/crates/client/src/errors.rs @@ -123,4 +123,8 @@ pub enum ClientError { Codec(#[from] parity_scale_codec::Error), #[error("Attestation request: {0}")] AttestationRequest(#[from] AttestationRequestError), + #[error("Unable to get TDX quote: {0}")] + QuoteGet(String), + #[error("Unable to get info for TSS server from chain")] + NoServerInfo, } diff --git a/crates/client/src/tests.rs b/crates/client/src/tests.rs index 52abd3329..a42b7758e 100644 --- a/crates/client/src/tests.rs +++ b/crates/client/src/tests.rs @@ -141,8 +141,8 @@ async fn test_change_threshold_accounts() { &api, &rpc, one.into(), - tss_public_key.to_string(), - hex::encode(*x25519_public_key.as_bytes()), + tss_public_key.into(), + *x25519_public_key.as_bytes(), pck_certificate_chain, quote, ) diff --git a/crates/shared/src/types.rs b/crates/shared/src/types.rs index d6156a0be..2de1b647c 100644 --- a/crates/shared/src/types.rs +++ b/crates/shared/src/types.rs @@ -146,6 +146,18 @@ pub enum QuoteContext { ChangeThresholdAccounts, } +#[cfg(feature = "std")] +impl std::fmt::Display for QuoteContext { + /// Custom display implementation so that it can be used to build a query string + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + QuoteContext::Validate => write!(f, "validate"), + QuoteContext::ChangeEndpoint => write!(f, "change_endpoint"), + QuoteContext::ChangeThresholdAccounts => write!(f, "change_threshold_accounts"), + } + } +} + /// A trait for types which can handle attestation requests. #[cfg(not(feature = "wasm"))] pub trait AttestationHandler { diff --git a/crates/test-cli/src/lib.rs b/crates/test-cli/src/lib.rs index 8b76fe5f8..42a030545 100644 --- a/crates/test-cli/src/lib.rs +++ b/crates/test-cli/src/lib.rs @@ -15,7 +15,7 @@ //! Simple CLI to test registering, updating programs and signing use anyhow::{anyhow, ensure}; -use clap::{Parser, Subcommand}; +use clap::{Parser, Subcommand, ValueEnum}; use colored::Colorize; use entropy_client::{ chain_api::{ @@ -27,15 +27,15 @@ use entropy_client::{ EntropyConfig, }, client::{ - change_endpoint, change_threshold_accounts, get_accounts, get_api, get_oracle_headings, - get_programs, get_rpc, jumpstart_network, register, remove_program, sign, store_program, - update_programs, VERIFYING_KEY_LENGTH, + get_accounts, get_api, get_oracle_headings, get_programs, get_quote_and_change_endpoint, + get_quote_and_change_threshold_accounts, get_rpc, get_tdx_quote, jumpstart_network, + register, remove_program, sign, store_program, update_programs, VERIFYING_KEY_LENGTH, }, }; -pub use entropy_shared::PROGRAM_VERSION_NUMBER; +pub use entropy_shared::{QuoteContext, PROGRAM_VERSION_NUMBER}; use sp_core::{sr25519, Hasher, Pair}; use sp_runtime::{traits::BlakeTwo256, Serialize}; -use std::{fs, path::PathBuf}; +use std::{fs, path::PathBuf, str::FromStr}; use subxt::{ backend::legacy::LegacyRpcMethods, utils::{AccountId32 as SubxtAccountId32, H256}, @@ -150,11 +150,6 @@ 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, @@ -167,11 +162,6 @@ enum CliCommand { new_x25519_public_key: String, /// The new Provisioning Certification Key (PCK) certificate chain to be used for the TSS. new_pck_certificate_chain: Vec, - /// 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, @@ -197,6 +187,9 @@ enum CliCommand { GetTdxQuote { /// The socket address of the TS server, eg: `127.0.0.1:3002` tss_endpoint: String, + /// The context in which this quote will be used. Must be one of + #[arg(value_enum)] + quote_context: QuoteContextArg, /// The filename to write the quote to. Defaults to `quote.dat` #[arg(long)] output_filename: Option, @@ -222,20 +215,12 @@ pub async fn run_command( std::env::var("ENTROPY_DEVNET").unwrap_or("ws://localhost:9944".to_string()) }); - let passed_mnemonic = std::env::var("DEPLOYER_MNEMONIC"); - let api = get_api(&endpoint_addr).await?; let rpc = get_rpc(&endpoint_addr).await?; match cli.command.clone() { CliCommand::Register { mnemonic_option, programs, program_version_numbers } => { - let mnemonic = if let Some(mnemonic_option) = mnemonic_option { - mnemonic_option - } else { - passed_mnemonic.expect("No mnemonic set") - }; - - let program_keypair = ::from_string(&mnemonic, None)?; + let program_keypair = handle_mnemonic(mnemonic_option)?; let program_account = SubxtAccountId32(program_keypair.public().0); cli.log(format!("Program account: {}", program_keypair.public())); @@ -274,13 +259,9 @@ pub async fn run_command( } }, CliCommand::Sign { signature_verifying_key, message, auxilary_data, mnemonic_option } => { - let mnemonic = if let Some(mnemonic_option) = mnemonic_option { - mnemonic_option - } else { - passed_mnemonic.unwrap_or("//Alice".to_string()) - }; // If an account name is not provided, use the Alice key - let user_keypair = ::from_string(&mnemonic, None)?; + let user_keypair = handle_mnemonic(mnemonic_option) + .unwrap_or(::from_string("//Alice", None)?); cli.log(format!("User account for current call: {}", user_keypair.public())); @@ -315,12 +296,7 @@ pub async fn run_command( aux_data_interface_file, program_version_number, } => { - let mnemonic = if let Some(mnemonic_option) = mnemonic_option { - mnemonic_option - } else { - passed_mnemonic.expect("No Mnemonic set") - }; - let keypair = ::from_string(&mnemonic, None)?; + let keypair = handle_mnemonic(mnemonic_option)?; cli.log(format!("Storing program using account: {}", keypair.public())); let program = match program_file { @@ -370,12 +346,7 @@ pub async fn run_command( } }, CliCommand::RemoveProgram { mnemonic_option, hash } => { - let mnemonic = if let Some(mnemonic_option) = mnemonic_option { - mnemonic_option - } else { - passed_mnemonic.expect("No Mnemonic set") - }; - let keypair = ::from_string(&mnemonic, None)?; + let keypair = handle_mnemonic(mnemonic_option)?; cli.log(format!("Removing program using account: {}", keypair.public())); let hash: [u8; 32] = hex::decode(hash)? @@ -396,12 +367,7 @@ pub async fn run_command( programs, program_version_numbers, } => { - let mnemonic = if let Some(mnemonic_option) = mnemonic_option { - mnemonic_option - } else { - passed_mnemonic.expect("No Mnemonic set") - }; - let program_keypair = ::from_string(&mnemonic, None)?; + let program_keypair = handle_mnemonic(mnemonic_option)?; cli.log(format!("Program account: {}", program_keypair.public())); let mut programs_info = Vec::new(); @@ -499,18 +465,12 @@ pub async fn run_command( Ok("Got status".to_string()) } }, - CliCommand::ChangeEndpoint { new_endpoint, quote, mnemonic_option } => { - let mnemonic = if let Some(mnemonic_option) = mnemonic_option { - mnemonic_option - } else { - passed_mnemonic.expect("No Mnemonic set") - }; - - let user_keypair = ::from_string(&mnemonic, None)?; + CliCommand::ChangeEndpoint { new_endpoint, mnemonic_option } => { + let user_keypair = handle_mnemonic(mnemonic_option)?; cli.log(format!("User account for current call: {}", user_keypair.public())); let result_event = - change_endpoint(&api, &rpc, user_keypair, new_endpoint, quote.into()).await?; + get_quote_and_change_endpoint(&api, &rpc, user_keypair, new_endpoint).await?; cli.log(format!("Event result: {:?}", result_event)); if cli.json { @@ -523,27 +483,24 @@ pub async fn run_command( new_tss_account, new_x25519_public_key, new_pck_certificate_chain, - quote, mnemonic_option, } => { - let mnemonic = if let Some(mnemonic_option) = mnemonic_option { - mnemonic_option - } else { - passed_mnemonic.expect("No Mnemonic set") - }; - let user_keypair = ::from_string(&mnemonic, None)?; + let user_keypair = handle_mnemonic(mnemonic_option)?; cli.log(format!("User account for current call: {}", user_keypair.public())); + let new_tss_account = SubxtAccountId32::from_str(&new_tss_account)?; + let new_x25519_public_key = hex::decode(new_x25519_public_key)? + .try_into() + .map_err(|_| anyhow!("X25519 pub key needs to be 32 bytes"))?; let new_pck_certificate_chain = new_pck_certificate_chain.iter().cloned().map(|i| i.into()).collect::<_>(); - let result_event = change_threshold_accounts( + let result_event = get_quote_and_change_threshold_accounts( &api, &rpc, user_keypair, new_tss_account, new_x25519_public_key, new_pck_certificate_chain, - quote.into(), ) .await?; cli.log(format!("Event result: {:?}", result_event)); @@ -555,13 +512,7 @@ pub async fn run_command( } }, CliCommand::JumpstartNetwork { mnemonic_option } => { - let mnemonic = if let Some(mnemonic_option) = mnemonic_option { - mnemonic_option - } else { - passed_mnemonic.unwrap_or("//Alice".to_string()) - }; - - let signer = ::from_string(&mnemonic, None)?; + let signer = handle_mnemonic(mnemonic_option)?; cli.log(format!("Account being used for jumpstart: {}", signer.public())); jumpstart_network(&api, &rpc, signer).await?; @@ -576,9 +527,8 @@ pub async fn run_command( let headings = get_oracle_headings(&api, &rpc).await?; Ok(serde_json::to_string_pretty(&headings)?) }, - CliCommand::GetTdxQuote { tss_endpoint, output_filename } => { - let quote_bytes = - reqwest::get(format!("http://{}/attest", tss_endpoint)).await?.bytes().await?; + CliCommand::GetTdxQuote { tss_endpoint, output_filename, quote_context } => { + let quote_bytes = get_tdx_quote(&tss_endpoint, quote_context.into()).await?; let output_filename = output_filename.unwrap_or("quote.dat".into()); std::fs::write(&output_filename, quote_bytes)?; @@ -713,3 +663,35 @@ impl StatusOutput { Self { accounts, programs } } } + +/// Get an sr25519 from a mnemonic given as either option or environment variable +fn handle_mnemonic(mnemonic_option: Option) -> anyhow::Result { + let mnemonic = if let Some(mnemonic) = mnemonic_option { + mnemonic + } else { + std::env::var("DEPLOYER_MNEMONIC") + .map_err(|_| anyhow!("A mnemonic must be given either by the command line option or DEPLOYER_MNEMONIC environment variable"))? + }; + Ok(::from_string(&mnemonic, None)?) +} + +/// This is the same as [QuoteContext] but implements [ValueEnum] +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, ValueEnum, Debug)] +enum QuoteContextArg { + /// 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, +} + +impl From for QuoteContext { + fn from(quote_context: QuoteContextArg) -> Self { + match quote_context { + QuoteContextArg::Validate => QuoteContext::Validate, + QuoteContextArg::ChangeEndpoint => QuoteContext::ChangeEndpoint, + QuoteContextArg::ChangeThresholdAccounts => QuoteContext::ChangeThresholdAccounts, + } + } +} From 44f80c289985bd112adb3d28c567283b40365c22 Mon Sep 17 00:00:00 2001 From: peg Date: Wed, 11 Dec 2024 11:11:15 +0100 Subject: [PATCH 3/9] Add TDX test network chainspec (#1204) * Add tdx-testnet chainspec * Add accepted MRTD values to TDX testnet chainspec * Changelog * Comments * Improve naming following review --- CHANGELOG.md | 1 + node/cli/src/chain_spec/dev.rs | 11 ++- node/cli/src/chain_spec/mod.rs | 9 ++- node/cli/src/chain_spec/tdx_testnet.rs | 98 ++++++++++++++++++++++++++ node/cli/src/command.rs | 2 + 5 files changed, 117 insertions(+), 4 deletions(-) create mode 100644 node/cli/src/chain_spec/tdx_testnet.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c1670223..95388bab7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -34,6 +34,7 @@ runtime - Protocol message versioning ([#1140](https://github.com/entropyxyz/entropy-core/pull/1140)) - CLI command to get oracle headings ([#1170](https://github.com/entropyxyz/entropy-core/pull/1170)) - Add TSS endpoint to get TDX quote ([#1173](https://github.com/entropyxyz/entropy-core/pull/1173)) +- Add TDX test network chainspec ([#1204](https://github.com/entropyxyz/entropy-core/pull/1204)) - Test CLI command to retrieve quote and change endpoint / TSS account in one command ([#1198](https://github.com/entropyxyz/entropy-core/pull/1198)) ### Changed diff --git a/node/cli/src/chain_spec/dev.rs b/node/cli/src/chain_spec/dev.rs index bdbdcf4a4..355785b55 100644 --- a/node/cli/src/chain_spec/dev.rs +++ b/node/cli/src/chain_spec/dev.rs @@ -13,7 +13,9 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -use crate::chain_spec::{get_account_id_from_seed, provisioning_certification_key, ChainSpec}; +use crate::chain_spec::{ + get_account_id_from_seed, provisioning_certification_key, ChainSpec, MrtdValues, +}; use crate::endowed_accounts::endowed_accounts_dev; use entropy_runtime::{ @@ -123,6 +125,7 @@ pub fn development_config() -> ChainSpec { vec![], get_account_id_from_seed::("Alice"), devnet_four_node_initial_tss_servers(), + None, )) .build() } @@ -148,6 +151,7 @@ pub fn devnet_local_four_node_config() -> crate::chain_spec::ChainSpec { vec![], get_account_id_from_seed::("Alice"), devnet_local_docker_four_node_initial_tss_servers(), + None, )) .build() } @@ -169,6 +173,7 @@ pub fn development_genesis_config( String, BoundedVecEncodedVerifyingKey, )>, + accepted_mrtd_values: Option, ) -> serde_json::Value { // Note that any endowed_accounts added here will be included in the `elections` and // `technical_committee` genesis configs. If you don't want that, don't push those accounts to @@ -283,10 +288,10 @@ pub fn development_genesis_config( max_instructions_per_programs: INITIAL_MAX_INSTRUCTIONS_PER_PROGRAM, total_signers: TOTAL_SIGNERS, threshold: SIGNER_THRESHOLD, - accepted_mrtd_values: vec![ + accepted_mrtd_values: accepted_mrtd_values.unwrap_or(vec![ BoundedVec::try_from([0; 48].to_vec()).unwrap(), BoundedVec::try_from([1; 48].to_vec()).unwrap(), - ], + ]), ..Default::default() }, "programs": ProgramsConfig { diff --git a/node/cli/src/chain_spec/mod.rs b/node/cli/src/chain_spec/mod.rs index a4fbc6bcc..4e13ce939 100644 --- a/node/cli/src/chain_spec/mod.rs +++ b/node/cli/src/chain_spec/mod.rs @@ -33,6 +33,7 @@ pub mod dev; pub mod integration_tests; +pub mod tdx_testnet; pub mod testnet; pub use entropy_runtime::{AccountId, RuntimeGenesisConfig, Signature}; @@ -47,7 +48,10 @@ use serde_json::json; use sp_authority_discovery::AuthorityId as AuthorityDiscoveryId; use sp_consensus_babe::AuthorityId as BabeId; use sp_core::{sr25519, Pair, Public}; -use sp_runtime::traits::{IdentifyAccount, Verify}; +use sp_runtime::{ + traits::{ConstU32, IdentifyAccount, Verify}, + BoundedVec, +}; type AccountPublic = ::Signer; @@ -215,3 +219,6 @@ pub fn authority_keys_from_seed( get_from_seed::(seed), ) } + +/// Accepted build time measurement values for TDX attestation +pub type MrtdValues = Vec>>; diff --git a/node/cli/src/chain_spec/tdx_testnet.rs b/node/cli/src/chain_spec/tdx_testnet.rs new file mode 100644 index 000000000..058d215a9 --- /dev/null +++ b/node/cli/src/chain_spec/tdx_testnet.rs @@ -0,0 +1,98 @@ +// 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 . + +use crate::chain_spec::{dev::development_genesis_config, get_account_id_from_seed, ChainSpec}; + +use entropy_runtime::wasm_binary_unwrap; +use entropy_shared::{BoundedVecEncodedVerifyingKey, X25519PublicKey as TssX25519PublicKey}; +use sc_service::ChainType; +use sp_core::sr25519; +use sp_runtime::BoundedVec; + +/// The build time measurement value from the current entropy-tss VM images +const ACCEPTED_MRTD: [u8; 48] = [ + 145, 235, 43, 68, 209, 65, 212, 236, 224, 159, 12, 117, 194, 197, 61, 36, 122, 60, 104, 237, + 215, 250, 254, 138, 53, 32, 201, 66, 166, 4, 164, 7, 222, 3, 174, 109, 197, 248, 127, 39, 66, + 139, 37, 56, 135, 49, 24, 183, +]; + +lazy_static::lazy_static! { + /// This is the PCK from the certificates of the current TDX machine we are using for testing + pub static ref PCK: BoundedVecEncodedVerifyingKey = vec![ + 2, 166, 103, 136, 58, 157, 155, 124, 186, 75, 81, 133, 87, 255, 233, 182, 192, 125, 235, 230, + 121, 173, 147, 108, 47, 190, 240, 181, 75, 181, 31, 148, 128, + ].try_into().unwrap(); +} + +fn tdx_devnet_four_node_initial_tss_servers( +) -> Vec<(sp_runtime::AccountId32, TssX25519PublicKey, String, BoundedVecEncodedVerifyingKey)> { + let tss_ip = std::env::var("ENTROPY_TESTNET_TSS_IP") + .expect("ENTROPY_TESTNET_TSS_IP environment variable to be set"); + + let alice = ( + crate::chain_spec::tss_account_id::ALICE.clone(), + crate::chain_spec::tss_x25519_public_key::ALICE, + format!("{tss_ip}:3001"), + PCK.clone(), + ); + + let bob = ( + crate::chain_spec::tss_account_id::BOB.clone(), + crate::chain_spec::tss_x25519_public_key::BOB, + format!("{tss_ip}:3002"), + PCK.clone(), + ); + + let charlie = ( + crate::chain_spec::tss_account_id::CHARLIE.clone(), + crate::chain_spec::tss_x25519_public_key::CHARLIE, + format!("{tss_ip}:3003"), + PCK.clone(), + ); + + let dave = ( + crate::chain_spec::tss_account_id::DAVE.clone(), + crate::chain_spec::tss_x25519_public_key::DAVE, + format!("{tss_ip}:3004"), + PCK.clone(), + ); + + vec![alice, bob, charlie, dave] +} + +/// The configuration used for the TDX testnet. +/// +/// Since Entropy requires at two-of-three threshold setup, and requires an additional relayer node, +/// we spin up four validators: Alice, Bob, Charlie and Dave. +pub fn tdx_testnet_config() -> ChainSpec { + ChainSpec::builder(wasm_binary_unwrap(), Default::default()) + .with_name("TDX-testnet") + .with_id("tdx") + .with_chain_type(ChainType::Development) + .with_properties(crate::chain_spec::entropy_properties()) + .with_genesis_config_patch(development_genesis_config( + vec![ + crate::chain_spec::authority_keys_from_seed("Alice"), + crate::chain_spec::authority_keys_from_seed("Bob"), + crate::chain_spec::authority_keys_from_seed("Charlie"), + crate::chain_spec::authority_keys_from_seed("Dave"), + ], + vec![], + get_account_id_from_seed::("Alice"), + tdx_devnet_four_node_initial_tss_servers(), + Some(vec![BoundedVec::try_from(ACCEPTED_MRTD.to_vec()).unwrap()]), + )) + .build() +} diff --git a/node/cli/src/command.rs b/node/cli/src/command.rs index b206b5ee5..92adba44e 100644 --- a/node/cli/src/command.rs +++ b/node/cli/src/command.rs @@ -74,6 +74,7 @@ impl SubstrateCli for Cli { // | integration-tests | Two nodes, Four threshold servers, Alice and Bob, Development Configuration | // | testnet-local | Two Nodes, Two threshold servers, Alice and Bob, Testnet Configuration, Docker Compatible | // | testnet | Four nodes, Two threshold servers, Own Seed, Testnet Configuration | + // | tdx-testnet | Four nodes, Four threshold servers, Alice Bob Chalie and Dave, Development Configuration adapted for TDX testnet | fn load_spec(&self, id: &str) -> Result, String> { Ok(match id { "" | "dev" => Box::new(chain_spec::dev::development_config()), @@ -88,6 +89,7 @@ impl SubstrateCli for Cli { }, "testnet-local" => Box::new(chain_spec::testnet::testnet_local_config()), "testnet" => Box::new(chain_spec::testnet::testnet_config()), + "tdx-testnet" => Box::new(chain_spec::tdx_testnet::tdx_testnet_config()), path => { Box::new(chain_spec::ChainSpec::from_json_file(std::path::PathBuf::from(path))?) }, From 2b267d93dc286c03282499e4be5553ae11035f4f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Dec 2024 08:22:14 +0000 Subject: [PATCH 4/9] Bump thiserror from 2.0.6 to 2.0.7 in the patch-dependencies group (#1217) Bumps the patch-dependencies group with 1 update: [thiserror](https://github.com/dtolnay/thiserror). Updates `thiserror` from 2.0.6 to 2.0.7 - [Release notes](https://github.com/dtolnay/thiserror/releases) - [Commits](https://github.com/dtolnay/thiserror/compare/2.0.6...2.0.7) --- updated-dependencies: - dependency-name: thiserror dependency-type: direct:production update-type: version-update:semver-patch dependency-group: patch-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 18 +++++++++--------- crates/client/Cargo.toml | 2 +- crates/kvdb/Cargo.toml | 2 +- crates/protocol/Cargo.toml | 2 +- crates/threshold-signature-server/Cargo.toml | 2 +- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 298582681..9ee242ecb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2547,7 +2547,7 @@ dependencies = [ "subxt", "synedrion", "tdx-quote", - "thiserror 2.0.6", + "thiserror 2.0.7", "tokio", "tracing", "x25519-dalek 2.0.1", @@ -2582,7 +2582,7 @@ dependencies = [ "sled", "sp-core 31.0.0", "synedrion", - "thiserror 2.0.6", + "thiserror 2.0.7", "tokio", "tracing", "zeroize", @@ -2642,7 +2642,7 @@ dependencies = [ "sp-keyring 34.0.0", "subxt", "synedrion", - "thiserror 2.0.6", + "thiserror 2.0.7", "tokio", "tokio-tungstenite", "tracing", @@ -2858,7 +2858,7 @@ dependencies = [ "subxt-signer", "synedrion", "tdx-quote", - "thiserror 2.0.6", + "thiserror 2.0.7", "tokio", "tokio-tungstenite", "tower-http 0.6.2", @@ -14334,11 +14334,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.6" +version = "2.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fec2a1820ebd077e2b90c4df007bebf344cd394098a13c563957d0afc83ea47" +checksum = "93605438cbd668185516ab499d589afb7ee1859ea3d5fc8f6b0755e1c7443767" dependencies = [ - "thiserror-impl 2.0.6", + "thiserror-impl 2.0.7", ] [[package]] @@ -14354,9 +14354,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.6" +version = "2.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d65750cab40f4ff1929fb1ba509e9914eb756131cef4210da8d5d700d26f6312" +checksum = "e1d8749b4531af2117677a5fcd12b1348a3fe2b81e36e61ffeac5c4aa3273e36" dependencies = [ "proc-macro2", "quote", diff --git a/crates/client/Cargo.toml b/crates/client/Cargo.toml index 66a2244a8..6bf8292f3 100644 --- a/crates/client/Cargo.toml +++ b/crates/client/Cargo.toml @@ -14,7 +14,7 @@ serde ={ version="1.0", default-features=false, features=["derive"] } entropy-shared={ version="0.3.0", path="../shared", default-features=false } subxt ={ version="0.35.3", default-features=false, features=["jsonrpsee"] } num ="0.4.3" -thiserror ="2.0.6" +thiserror ="2.0.7" futures ="0.3" sp-core ={ version="31.0.0", default-features=false, features=["full_crypto", "serde"] } tracing ="0.1.41" diff --git a/crates/kvdb/Cargo.toml b/crates/kvdb/Cargo.toml index 2d8a7d22d..5e2b386bd 100644 --- a/crates/kvdb/Cargo.toml +++ b/crates/kvdb/Cargo.toml @@ -12,7 +12,7 @@ edition ='2021' # Common rand ={ version="0.8", default-features=false } serde ={ version="1.0", features=["derive"] } -thiserror="2.0.6" +thiserror="2.0.7" hex ="0.4.3" # Substrate diff --git a/crates/protocol/Cargo.toml b/crates/protocol/Cargo.toml index c6b542101..f82dd3b84 100644 --- a/crates/protocol/Cargo.toml +++ b/crates/protocol/Cargo.toml @@ -20,7 +20,7 @@ x25519-dalek ={ version="2.0.1", features=["static_secrets"] } futures ="0.3" hex ="0.4.3" blake2 ="0.10.4" -thiserror ="2.0.6" +thiserror ="2.0.7" snow ="0.9.6" getrandom ={ version="0.2", features=["js"] } rand_core ={ version="0.6.4", features=["getrandom"] } diff --git a/crates/threshold-signature-server/Cargo.toml b/crates/threshold-signature-server/Cargo.toml index e1a299245..2356aae20 100644 --- a/crates/threshold-signature-server/Cargo.toml +++ b/crates/threshold-signature-server/Cargo.toml @@ -12,7 +12,7 @@ edition ='2021' # Common serde ={ version="1.0", default-features=false, features=["derive"] } serde_json ="1.0" -thiserror ="2.0.6" +thiserror ="2.0.7" anyhow ="1.0.94" blake2 ="0.10.4" x25519-dalek ={ version="2.0.1", features=["static_secrets"] } From a1e36983135cd6333197101b4285bb715e4d7e5e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Dec 2024 11:39:53 +0100 Subject: [PATCH 5/9] Bump colored from 2.1.0 to 2.2.0 (#1218) Bumps [colored](https://github.com/mackwic/colored) from 2.1.0 to 2.2.0. - [Release notes](https://github.com/mackwic/colored/releases) - [Changelog](https://github.com/colored-rs/colored/blob/master/CHANGELOG.md) - [Commits](https://github.com/mackwic/colored/compare/v2.1.0...v2.2.0) --- updated-dependencies: - dependency-name: colored dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 6 +++--- crates/test-cli/Cargo.toml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9ee242ecb..384f1fa8c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1358,12 +1358,12 @@ checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" [[package]] name = "colored" -version = "2.1.0" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8" +checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" dependencies = [ "lazy_static", - "windows-sys 0.48.0", + "windows-sys 0.59.0", ] [[package]] diff --git a/crates/test-cli/Cargo.toml b/crates/test-cli/Cargo.toml index 23c8c67b8..b21357028 100644 --- a/crates/test-cli/Cargo.toml +++ b/crates/test-cli/Cargo.toml @@ -11,7 +11,7 @@ edition ='2021' [dependencies] entropy-client={ version="0.3.0", path="../client" } clap ={ version="4.5.23", features=["derive"] } -colored ="2.0.4" +colored ="2.2.0" subxt ="0.35.3" sp-core ="31.0.0" anyhow ="1.0.94" From 2892c05157b456d7bfc4e3141df6ef18efc0c688 Mon Sep 17 00:00:00 2001 From: Hernando Castano Date: Tue, 17 Dec 2024 16:17:44 -0500 Subject: [PATCH 6/9] On-chain unresponsiveness reporting (#1215) * Add extrinsic to report unstable peer * Handle errors properly * Add a couple of reporting tests * RustFmt * Bump metadata * Add benchmark for `report_unstable_peer` * Run benches for `report_unstable_peer` * Use benchmark result in pallet * Add doccomment to extrinsic * Add Slashing pallet to Propagation mock * Actually should probably be a `dev-dependency` * Add Slashing pallet to Attestation mock * Add Slashing pallet to Registry mock * TaploFmt * Fix typo * Add `CHANGELOG` entry --- CHANGELOG.md | 1 + Cargo.lock | 4 ++ crates/client/entropy_metadata.scale | Bin 209990 -> 210103 bytes pallets/attestation/Cargo.toml | 2 + pallets/attestation/src/mock.rs | 13 +++++ pallets/propagation/Cargo.toml | 5 +- pallets/propagation/src/mock.rs | 13 +++++ pallets/registry/Cargo.toml | 1 + pallets/registry/src/benchmarking.rs | 2 +- pallets/registry/src/mock.rs | 13 +++++ pallets/staking/Cargo.toml | 2 + pallets/staking/src/benchmarking.rs | 42 +++++++++++++ pallets/staking/src/lib.rs | 53 +++++++++++++++++ pallets/staking/src/mock.rs | 38 ++++++++++++ pallets/staking/src/tests.rs | 55 ++++++++++++++++++ pallets/staking/src/weights.rs | 37 ++++++++++++ .../src/weights/pallet_staking_extension.rs | 18 ++++++ 17 files changed, 296 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 95388bab7..efa13cc42 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ runtime - Add TSS endpoint to get TDX quote ([#1173](https://github.com/entropyxyz/entropy-core/pull/1173)) - Add TDX test network chainspec ([#1204](https://github.com/entropyxyz/entropy-core/pull/1204)) - Test CLI command to retrieve quote and change endpoint / TSS account in one command ([#1198](https://github.com/entropyxyz/entropy-core/pull/1198)) +- On-chain unresponsiveness reporting [(#1215)](https://github.com/entropyxyz/entropy-core/pull/1215) ### Changed - Use correct key rotation endpoint in OCW ([#1104](https://github.com/entropyxyz/entropy-core/pull/1104)) diff --git a/Cargo.lock b/Cargo.lock index 384f1fa8c..4e486e848 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6768,6 +6768,7 @@ dependencies = [ "pallet-balances", "pallet-parameters", "pallet-session", + "pallet-slashing", "pallet-staking", "pallet-staking-extension", "pallet-staking-reward-curve", @@ -7287,6 +7288,7 @@ dependencies = [ "pallet-programs", "pallet-registry", "pallet-session", + "pallet-slashing", "pallet-staking", "pallet-staking-extension", "pallet-staking-reward-curve", @@ -7353,6 +7355,7 @@ dependencies = [ "pallet-parameters", "pallet-programs", "pallet-session", + "pallet-slashing", "pallet-staking", "pallet-staking-extension", "pallet-staking-reward-curve", @@ -7494,6 +7497,7 @@ dependencies = [ "pallet-balances", "pallet-parameters", "pallet-session", + "pallet-slashing", "pallet-staking", "pallet-staking-reward-curve", "pallet-timestamp", diff --git a/crates/client/entropy_metadata.scale b/crates/client/entropy_metadata.scale index 7ffe949d773fa4b2ff3ef96d9f6c173a033a0360..9d94e92bcafb91bedcad567f69379041060a5b66 100644 GIT binary patch delta 340 zcmYk0%}PQ+7>4Il!AkAlBC;Q%Xw|L@NQ59H8OqAt;pF*_XV7M3W`bOV)H&2Tj0W1Y z6lzf*i~MJa%oGQAj8HrmeI0d?)z)zJZbn&f%iDTo8JfRX9o#C+lG- z;K@H2Aan=&_D+;k~^9BuXFkZ=Ojp0Mh z1Xm7@@RW&7suH zWo29AK1${`$hcVxU=AisK7jemBq?p$tcFgXIOk!xO47u@6)au01L(Gc$lc=) sp_io::TestExternalities { let mut t = system::GenesisConfig::::default().build_storage().unwrap(); diff --git a/pallets/propagation/Cargo.toml b/pallets/propagation/Cargo.toml index e634f2b51..9385b0fbb 100644 --- a/pallets/propagation/Cargo.toml +++ b/pallets/propagation/Cargo.toml @@ -29,10 +29,10 @@ sp-staking ={ version="27.0.0", default-features=false } entropy-shared={ version="0.3.0", path="../../crates/shared", default-features=false, features=[ "wasm-no-std", ] } -pallet-registry={ version="0.3.0", path="../registry", default-features=false } +pallet-attestation={ version="0.3.0", path="../attestation", default-features=false } pallet-programs={ version="0.3.0", path="../programs", default-features=false } +pallet-registry={ version="0.3.0", path="../registry", default-features=false } pallet-staking-extension={ version="0.3.0", path="../staking", default-features=false } -pallet-attestation={ version="0.3.0", path="../attestation", default-features=false } [dev-dependencies] parking_lot="0.12.3" @@ -49,6 +49,7 @@ sp-keystore ={ version="0.35.0" } sp-npos-elections ={ version="27.0.0", default-features=false } pallet-parameters ={ version="0.3.0", path="../parameters", default-features=false } pallet-oracle ={ version='0.3.0', path='../oracle', default-features=false } +pallet-slashing ={ version="0.3.0", path="../slashing", default-features=false } [features] default=['std'] diff --git a/pallets/propagation/src/mock.rs b/pallets/propagation/src/mock.rs index 1fce23e1c..e3d3bf77e 100644 --- a/pallets/propagation/src/mock.rs +++ b/pallets/propagation/src/mock.rs @@ -61,6 +61,7 @@ frame_support::construct_runtime!( Parameters: pallet_parameters, Attestation: pallet_attestation, Oracle: pallet_oracle, + Slashing: pallet_slashing, } ); @@ -394,6 +395,18 @@ impl pallet_attestation::Config for Test { type Randomness = TestPastRandomness; } +parameter_types! { + pub const ReportThreshold: u32 = 5; +} + +impl pallet_slashing::Config for Test { + type RuntimeEvent = RuntimeEvent; + type AuthorityId = UintAuthorityId; + type ReportThreshold = ReportThreshold; + type ValidatorSet = Historical; + type ReportUnresponsiveness = (); +} + // Build genesis storage according to the mock runtime. pub fn new_test_ext() -> sp_io::TestExternalities { let mut t = system::GenesisConfig::::default().build_storage().unwrap(); diff --git a/pallets/registry/Cargo.toml b/pallets/registry/Cargo.toml index 8c092beca..64cbe8362 100644 --- a/pallets/registry/Cargo.toml +++ b/pallets/registry/Cargo.toml @@ -46,6 +46,7 @@ sp-io ={ version="31.0.0", default-features=false } sp-npos-elections ={ version="27.0.0", default-features=false } sp-staking ={ version="27.0.0", default-features=false } pallet-oracle ={ version='0.3.0', path='../oracle', default-features=false } +pallet-slashing ={ version="0.3.0", path="../slashing", default-features=false } [features] diff --git a/pallets/registry/src/benchmarking.rs b/pallets/registry/src/benchmarking.rs index 65849c535..13caabbd4 100644 --- a/pallets/registry/src/benchmarking.rs +++ b/pallets/registry/src/benchmarking.rs @@ -216,7 +216,7 @@ benchmarks! { let network_verifying_key = synedrion::ecdsa::VerifyingKey::try_from(network_verifying_key.as_slice()).unwrap(); - // We substract one from the count since this gets incremented after a succesful registration, + // We subtract one from the count since this gets incremented after a succesful registration, // and we're interested in the account we just registered. let count = >::count() - 1; let derivation_path = diff --git a/pallets/registry/src/mock.rs b/pallets/registry/src/mock.rs index 76054af22..573ea9e75 100644 --- a/pallets/registry/src/mock.rs +++ b/pallets/registry/src/mock.rs @@ -60,6 +60,7 @@ frame_support::construct_runtime!( Programs: pallet_programs, Parameters: pallet_parameters, Oracle: pallet_oracle, + Slashing: pallet_slashing, } ); @@ -380,6 +381,18 @@ impl pallet_parameters::Config for Test { type WeightInfo = (); } +parameter_types! { + pub const ReportThreshold: u32 = 5; +} + +impl pallet_slashing::Config for Test { + type RuntimeEvent = RuntimeEvent; + type AuthorityId = UintAuthorityId; + type ReportThreshold = ReportThreshold; + type ValidatorSet = Historical; + type ReportUnresponsiveness = (); +} + // Build genesis storage according to the mock runtime. pub fn new_test_ext() -> sp_io::TestExternalities { let mut t = system::GenesisConfig::::default().build_storage().unwrap(); diff --git a/pallets/staking/Cargo.toml b/pallets/staking/Cargo.toml index 3cb777887..ff6ecfbbe 100644 --- a/pallets/staking/Cargo.toml +++ b/pallets/staking/Cargo.toml @@ -34,6 +34,7 @@ p256 ={ version="0.13.2", default-features=false, features=["ecdsa" rand ={ version="0.8.5", default-features=false, features=["alloc"] } pallet-parameters={ version="0.3.0", path="../parameters", default-features=false } +pallet-slashing={ version="0.3.0", path="../slashing", default-features=false } entropy-shared={ version="0.3.0", path="../../crates/shared", features=[ "wasm-no-std", ], default-features=false } @@ -69,6 +70,7 @@ std=[ 'pallet-balances/std', 'pallet-parameters/std', 'pallet-session/std', + 'pallet-slashing/std', 'pallet-staking/std', 'scale-info/std', 'sp-consensus-babe/std', diff --git a/pallets/staking/src/benchmarking.rs b/pallets/staking/src/benchmarking.rs index 112a9c978..a6dad4dae 100644 --- a/pallets/staking/src/benchmarking.rs +++ b/pallets/staking/src/benchmarking.rs @@ -29,6 +29,7 @@ use frame_support::{ }; use frame_system::{EventRecord, RawOrigin}; use pallet_parameters::{SignersInfo, SignersSize}; +use pallet_slashing::Event as SlashingEvent; use pallet_staking::{ Event as FrameStakingEvent, MaxNominationsOf, MaxValidatorsCount, Nominations, Pallet as FrameStaking, RewardDestination, ValidatorPrefs, @@ -56,6 +57,16 @@ fn assert_last_event_frame_staking( assert_eq!(event, &system_event); } +fn assert_last_event_slashing( + generic_event: ::RuntimeEvent, +) { + let events = frame_system::Pallet::::events(); + let system_event: ::RuntimeEvent = generic_event.into(); + // compare to the last event record + let EventRecord { event, .. } = &events[events.len() - 1]; + assert_eq!(event, &system_event); +} + pub fn create_validators( count: u32, seed: u32, @@ -518,6 +529,37 @@ benchmarks! { verify { assert!(NextSigners::::get().is_some()); } + + report_unstable_peer { + // We subtract `2` here to give room to our test signers + let s in 0 .. (MAX_SIGNERS - 2) as u32; + + let threshold_reporter: T::AccountId = whitelisted_caller(); + let threshold_offender: T::AccountId = account("threshold_offender", 0, SEED); + + let reporter_validator_id = ::ValidatorId::try_from(threshold_reporter.clone()) + .or(Err(Error::::InvalidValidatorId)) + .unwrap(); + + let offender_validator_id = ::ValidatorId::try_from(threshold_offender.clone()) + .or(Err(Error::::InvalidValidatorId)) + .unwrap(); + + ThresholdToStash::::insert(&threshold_reporter, &reporter_validator_id); + ThresholdToStash::::insert(&threshold_offender, &offender_validator_id); + + let mut signers = vec![reporter_validator_id.clone(); s as usize]; + signers.push(reporter_validator_id); + signers.push(offender_validator_id); + + Signers::::put(signers.clone()); + + }: _(RawOrigin::Signed(threshold_reporter.clone()), threshold_offender.clone()) + verify { + assert_last_event_slashing::( + pallet_slashing::Event::NoteReport(threshold_reporter, threshold_offender).into(), + ); + } } impl_benchmark_test_suite!(Staking, crate::mock::new_test_ext(), crate::mock::Test); diff --git a/pallets/staking/src/lib.rs b/pallets/staking/src/lib.rs index 88bf6cbec..b4961cabe 100644 --- a/pallets/staking/src/lib.rs +++ b/pallets/staking/src/lib.rs @@ -90,6 +90,7 @@ pub mod pallet { + frame_system::Config + pallet_staking::Config + pallet_parameters::Config + + pallet_slashing::Config { /// Because this pallet emits events, it depends on the runtime's definition of an event. type RuntimeEvent: From> + IsType<::RuntimeEvent>; @@ -336,6 +337,7 @@ pub mod pallet { InvalidValidatorId, SigningGroupError, TssAccountAlreadyExists, + NotSigner, NotNextSigner, ReshareNotInProgress, AlreadyConfirmed, @@ -735,6 +737,57 @@ pub mod pallet { // signers see https://github.com/entropyxyz/entropy-core/issues/985 Ok(Pays::No.into()) } + + /// An on-chain hook for TSS servers in the signing committee to report other TSS servers in + /// the committee for misbehaviour. + /// + /// Any "conequences" are handled by the configured Slashing pallet and not this pallet + /// itself. + #[pallet::call_index(7)] + #[pallet::weight(::WeightInfo::report_unstable_peer(MAX_SIGNERS as u32))] + pub fn report_unstable_peer( + origin: OriginFor, + offender_tss_account: T::AccountId, + ) -> DispatchResultWithPostInfo { + let reporter_tss_account = ensure_signed(origin)?; + + // For reporting purposes we need to know the validator account tied to the TSS account. + let reporter_validator_id = Self::threshold_to_stash(&reporter_tss_account) + .ok_or(Error::::NoThresholdKey)?; + let offender_validator_id = Self::threshold_to_stash(&offender_tss_account) + .ok_or(Error::::NoThresholdKey)?; + + // Note: This operation is O(n), but with a small enough Signer group this should be + // fine to do on-chain. + let signers = Self::signers(); + ensure!(signers.contains(&reporter_validator_id), Error::::NotSigner); + ensure!(signers.contains(&offender_validator_id), Error::::NotSigner); + + // We do a bit of a weird conversion here since we want the validator's underlying + // `AccountId` for the reporting mechanism, not their `ValidatorId`. + // + // The Session pallet should have this configured to be the same thing, but we can't + // prove that to the compiler. + let encoded_validator_id = T::ValidatorId::encode(&reporter_validator_id); + let reporter_validator_account = T::AccountId::decode(&mut &encoded_validator_id[..]) + .expect("A `ValidatorId` should be equivalent to an `AccountId`."); + + let encoded_validator_id = T::ValidatorId::encode(&offender_validator_id); + let offending_peer_validator_account = + T::AccountId::decode(&mut &encoded_validator_id[..]) + .expect("A `ValidatorId` should be equivalent to an `AccountId`."); + + // We don't actually take any action here, we offload the reporting to the Slashing + // pallet. + pallet_slashing::Pallet::::note_report( + reporter_validator_account, + offending_peer_validator_account, + )?; + + let actual_weight = + ::WeightInfo::report_unstable_peer(signers.len() as u32); + Ok(Some(actual_weight).into()) + } } impl Pallet { diff --git a/pallets/staking/src/mock.rs b/pallets/staking/src/mock.rs index 21a35e06e..4c71b12b8 100644 --- a/pallets/staking/src/mock.rs +++ b/pallets/staking/src/mock.rs @@ -63,6 +63,7 @@ frame_support::construct_runtime!( Historical: pallet_session_historical, BagsList: pallet_bags_list, Parameters: pallet_parameters, + Slashing: pallet_slashing, } ); @@ -418,6 +419,43 @@ impl entropy_shared::AttestationHandler for MockAttestationHandler { fn request_quote(_attestee: &AccountId, _nonce: [u8; 32]) {} } +type IdentificationTuple = (u64, pallet_staking::Exposure); +type Offence = pallet_slashing::UnresponsivenessOffence; + +parameter_types! { + pub static Offences: Vec = vec![]; +} + +/// A mock offence report handler. +pub struct OffenceHandler; +impl sp_staking::offence::ReportOffence + for OffenceHandler +{ + fn report_offence( + _reporters: Vec, + offence: Offence, + ) -> Result<(), sp_staking::offence::OffenceError> { + Offences::mutate(|l| l.push(offence)); + Ok(()) + } + + fn is_known_offence(_offenders: &[IdentificationTuple], _time_slot: &SessionIndex) -> bool { + false + } +} + +parameter_types! { + pub const ReportThreshold: u32 = 5; +} + +impl pallet_slashing::Config for Test { + type RuntimeEvent = RuntimeEvent; + type AuthorityId = UintAuthorityId; + type ReportThreshold = ReportThreshold; + type ValidatorSet = Historical; + type ReportUnresponsiveness = OffenceHandler; +} + impl pallet_staking_extension::Config for Test { type AttestationHandler = MockAttestationHandler; type Currency = Balances; diff --git a/pallets/staking/src/tests.rs b/pallets/staking/src/tests.rs index 92125b2a9..f2a957b85 100644 --- a/pallets/staking/src/tests.rs +++ b/pallets/staking/src/tests.rs @@ -844,3 +844,58 @@ fn it_stops_chill_when_signer_or_next_signer() { ); }); } + +#[test] +fn cannot_report_outside_of_signer_set() { + new_test_ext().execute_with(|| { + // These mappings come from the mock GenesisConfig + let (alice_validator, alice_tss) = (5, 7); + let (_bob_validator, bob_tss) = (6, 8); + + let (_not_validator, not_tss) = (33, 33); + + // We only want Alice to be part of the signing committee for the test. + Signers::::put(vec![alice_validator]); + + // A TSS which doesn't have a `ValidatorId` cannot report another peer + assert_noop!( + Staking::report_unstable_peer(RuntimeOrigin::signed(not_tss), bob_tss), + Error::::NoThresholdKey + ); + + // A validator which isn't part of the signing committee cannot report another peer + assert_noop!( + Staking::report_unstable_peer(RuntimeOrigin::signed(bob_tss), alice_tss), + Error::::NotSigner + ); + + // An offender that does not have a `ValidatorId` cannot be reported + assert_noop!( + Staking::report_unstable_peer(RuntimeOrigin::signed(alice_tss), not_tss), + Error::::NoThresholdKey + ); + + // An offender which isn't part of the signing committee cannot be reported + assert_noop!( + Staking::report_unstable_peer(RuntimeOrigin::signed(alice_tss), bob_tss), + Error::::NotSigner + ); + }) +} + +#[test] +fn can_report_unstable_peer() { + new_test_ext().execute_with(|| { + // These mappings come from the mock GenesisConfig + let (alice_validator, alice_tss) = (5, 7); + let (bob_validator, bob_tss) = (6, 8); + + Signers::::put(vec![alice_validator, bob_validator]); + + // The TSS accounts are used for reports. We expect the accompanying validator to be + // reported though. + assert_ok!(Staking::report_unstable_peer(RuntimeOrigin::signed(alice_tss), bob_tss)); + + assert_eq!(>::failed_registrations(bob_validator), 1); + }) +} diff --git a/pallets/staking/src/weights.rs b/pallets/staking/src/weights.rs index 012f873ce..e2aa31bfa 100644 --- a/pallets/staking/src/weights.rs +++ b/pallets/staking/src/weights.rs @@ -62,6 +62,7 @@ pub trait WeightInfo { fn confirm_key_reshare_completed() -> Weight; fn new_session_base_weight(s: u32) -> Weight; fn new_session(c: u32, l: u32, v: u32, r: u32) -> Weight; + fn report_unstable_peer(s: u32, ) -> Weight; } /// Weights for pallet_staking_extension using the Substrate node and recommended hardware. @@ -332,6 +333,24 @@ impl WeightInfo for SubstrateWeight { .saturating_add(Weight::from_parts(0, 11364552184692736).saturating_mul(r.into())) .saturating_add(Weight::from_parts(0, 18).saturating_mul(v.into())) } + /// Storage: `StakingExtension::ThresholdToStash` (r:2 w:0) + /// Proof: `StakingExtension::ThresholdToStash` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `StakingExtension::Signers` (r:1 w:0) + /// Proof: `StakingExtension::Signers` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `Slashing::FailedRegistrations` (r:1 w:1) + /// Proof: `Slashing::FailedRegistrations` (`max_values`: None, `max_size`: Some(36), added: 2511, mode: `MaxEncodedLen`) + /// The range of component `s` is `[0, 13]`. + fn report_unstable_peer(s: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `519 + s * (32 ±0)` + // Estimated: `6459 + s * (32 ±0)` + // Minimum execution time: 16_000_000 picoseconds. + Weight::from_parts(19_055_447, 0) + .saturating_add(Weight::from_parts(0, 6459)) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(1)) + .saturating_add(Weight::from_parts(0, 32).saturating_mul(s.into())) + } } // For backwards compatibility and tests @@ -601,4 +620,22 @@ impl WeightInfo for () { .saturating_add(Weight::from_parts(0, 11364552184692736).saturating_mul(r.into())) .saturating_add(Weight::from_parts(0, 18).saturating_mul(v.into())) } + /// Storage: `StakingExtension::ThresholdToStash` (r:2 w:0) + /// Proof: `StakingExtension::ThresholdToStash` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `StakingExtension::Signers` (r:1 w:0) + /// Proof: `StakingExtension::Signers` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `Slashing::FailedRegistrations` (r:1 w:1) + /// Proof: `Slashing::FailedRegistrations` (`max_values`: None, `max_size`: Some(36), added: 2511, mode: `MaxEncodedLen`) + /// The range of component `s` is `[0, 13]`. + fn report_unstable_peer(s: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `519 + s * (32 ±0)` + // Estimated: `6459 + s * (32 ±0)` + // Minimum execution time: 16_000_000 picoseconds. + Weight::from_parts(19_055_447, 0) + .saturating_add(Weight::from_parts(0, 6459)) + .saturating_add(RocksDbWeight::get().reads(4)) + .saturating_add(RocksDbWeight::get().writes(1)) + .saturating_add(Weight::from_parts(0, 32).saturating_mul(s.into())) + } } diff --git a/runtime/src/weights/pallet_staking_extension.rs b/runtime/src/weights/pallet_staking_extension.rs index a6bc32bad..0132f9f95 100644 --- a/runtime/src/weights/pallet_staking_extension.rs +++ b/runtime/src/weights/pallet_staking_extension.rs @@ -321,4 +321,22 @@ impl pallet_staking_extension::WeightInfo for WeightInf .saturating_add(Weight::from_parts(0, 11364552184692736).saturating_mul(r.into())) .saturating_add(Weight::from_parts(0, 18).saturating_mul(v.into())) } + /// Storage: `StakingExtension::ThresholdToStash` (r:2 w:0) + /// Proof: `StakingExtension::ThresholdToStash` (`max_values`: None, `max_size`: None, mode: `Measured`) + /// Storage: `StakingExtension::Signers` (r:1 w:0) + /// Proof: `StakingExtension::Signers` (`max_values`: Some(1), `max_size`: None, mode: `Measured`) + /// Storage: `Slashing::FailedRegistrations` (r:1 w:1) + /// Proof: `Slashing::FailedRegistrations` (`max_values`: None, `max_size`: Some(36), added: 2511, mode: `MaxEncodedLen`) + /// The range of component `s` is `[0, 13]`. + fn report_unstable_peer(s: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `519 + s * (32 ±0)` + // Estimated: `6459 + s * (32 ±0)` + // Minimum execution time: 16_000_000 picoseconds. + Weight::from_parts(19_055_447, 0) + .saturating_add(Weight::from_parts(0, 6459)) + .saturating_add(T::DbWeight::get().reads(4)) + .saturating_add(T::DbWeight::get().writes(1)) + .saturating_add(Weight::from_parts(0, 32).saturating_mul(s.into())) + } } From 6fa0befd4f12747552e01761fece7c1a9a65d6f7 Mon Sep 17 00:00:00 2001 From: peg Date: Wed, 18 Dec 2024 08:20:30 +0100 Subject: [PATCH 7/9] Generate mnemonic internally in `entropy-tss` (#1128) * Dont allow mnemonic to be passed in via CLI, or environment variable - generate it internally * Changelog * Error handling * Add endpoint giving public keys * Document new endpoint * Changelog * Clippy * Fix lockfile * Fix changelog following merge master * Changelog changes following review --- CHANGELOG.md | 5 +++ .../src/helpers/launch.rs | 31 ------------- crates/threshold-signature-server/src/lib.rs | 8 +++- crates/threshold-signature-server/src/main.rs | 43 ++++--------------- .../src/node_info/api.rs | 26 ++++++++++- .../src/node_info/errors.rs | 34 +++++++++++++++ .../src/node_info/mod.rs | 1 + .../src/node_info/tests.rs | 25 ++++++++++- 8 files changed, 104 insertions(+), 69 deletions(-) create mode 100644 crates/threshold-signature-server/src/node_info/errors.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index efa13cc42..4f17c8377 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,9 @@ 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 [#1128](https://github.com/entropyxyz/entropy-core/pull/1128) mnemonics can no longer be passed + in to `entropy-tss` via the `--mnemonic` command line argument, a file, or an environment variable. + Instead they are randomly generated internally and can be retrieved with the `/info` HTTP route. - In [#1179](https://github.com/entropyxyz/entropy-core/pull/1179) the format of TDX Quote input data has been changed. - In [#1147](https://github.com/entropyxyz/entropy-core/pull/1147) a field is added to the @@ -31,6 +34,8 @@ runtime for testing. If this is not desired it should be set to `None`. ### Added +- In [#1128](https://github.com/entropyxyz/entropy-core/pull/1128) an `/info` route was added to `entropy-tss` + which can be used to get the TSS account ID and x25519 public key. - Protocol message versioning ([#1140](https://github.com/entropyxyz/entropy-core/pull/1140)) - CLI command to get oracle headings ([#1170](https://github.com/entropyxyz/entropy-core/pull/1170)) - Add TSS endpoint to get TDX quote ([#1173](https://github.com/entropyxyz/entropy-core/pull/1173)) diff --git a/crates/threshold-signature-server/src/helpers/launch.rs b/crates/threshold-signature-server/src/helpers/launch.rs index 444d98f62..260fbdb47 100644 --- a/crates/threshold-signature-server/src/helpers/launch.rs +++ b/crates/threshold-signature-server/src/helpers/launch.rs @@ -207,37 +207,6 @@ pub struct StartupArgs { /// Returns the AccountID and Diffie-Hellman Public Keys associated with this server. #[arg(long = "setup-only")] pub setup_only: bool, - - /// The BIP-39 mnemonic (i.e seed phrase) to use for deriving the Threshold Signature Server - /// SR25519 account ID and the X25519 public key. - /// - /// The SR25519 account is responsible for signing and submitting extrinsics to the Entropy - /// network. - /// - /// The X25519 public key is used for encrypting/decrypting messages to other threshold - /// servers. - /// - /// Note that this may keep a mnemonic in your shell history. If you would like to avoid this - /// use one of the alternative options, or tools like the 1Password CLI. - /// - /// **Alternatives**: There are two other ways to supply the mnemonic to the TSS: the - /// `--mnemonic-file` flag and the `THRESHOLD_SERVER_MNEMONIC` environment variable. - /// - /// **Warning**: Passing this flag will overwrite any existing mnemonic! If you would like to - /// use an existing mnemonic omit this flag when running the process. - #[arg(long = "mnemonic")] - pub mnemonic: Option, - - /// The path to a file containing the BIP-39 mnemonic (i.e seed phrase) to use for deriving the - /// Threshold Signature Server SR25519 account ID and the X25519 public key. - /// - /// **Alternatives**: There are two other ways to supply the mnemonic to the TSS: the - /// `--mnemonic` flag and the `THRESHOLD_SERVER_MNEMONIC` environment variable. - /// - /// **Warning**: Passing this flag will overwrite any existing mnemonic! If you would like to - /// use an existing mnemonic omit this flag when running the process. - #[arg(long = "mnemonic-file", conflicts_with = "mnemonic")] - pub mnemonic_file: Option, } pub async fn has_mnemonic(kv: &KvManager) -> bool { diff --git a/crates/threshold-signature-server/src/lib.rs b/crates/threshold-signature-server/src/lib.rs index 2468208b2..5cd01cb49 100644 --- a/crates/threshold-signature-server/src/lib.rs +++ b/crates/threshold-signature-server/src/lib.rs @@ -110,6 +110,11 @@ //! http://127.0.0.1:3001/user/sign_tx //! ``` //! +//! ### For the node operator +//! +//! [`/info`](crate::node_info::api::info()) - Get - get a Json object of type +//! [crate::node_info::api::TssPublicKeys] which contains the TSS account ID and x25519 public key. +//! //! ### For the blockchain node //! //! ### For other instances of the threshold server @@ -185,7 +190,7 @@ use crate::{ attestation::api::{attest, get_attest}, health::api::healthz, launch::Configuration, - node_info::api::{hashes, version as get_version}, + node_info::api::{hashes, info, version as get_version}, r#unsafe::api::{delete, put, remove_keys, unsafe_get}, signing_client::{api::*, ListenerState}, user::api::*, @@ -218,6 +223,7 @@ pub fn app(app_state: AppState) -> Router { .route("/healthz", get(healthz)) .route("/version", get(get_version)) .route("/hashes", get(hashes)) + .route("/info", get(info)) .route("/ws", get(ws_handler)); // Unsafe routes are for testing purposes only diff --git a/crates/threshold-signature-server/src/main.rs b/crates/threshold-signature-server/src/main.rs index c9e520e7d..ec00329b0 100644 --- a/crates/threshold-signature-server/src/main.rs +++ b/crates/threshold-signature-server/src/main.rs @@ -20,8 +20,8 @@ use clap::Parser; use entropy_tss::{ app, launch::{ - development_mnemonic, load_kv_store, setup_latest_block_number, setup_mnemonic, setup_only, - Configuration, StartupArgs, ValidatorName, + development_mnemonic, has_mnemonic, load_kv_store, setup_latest_block_number, + setup_mnemonic, setup_only, Configuration, StartupArgs, ValidatorName, }, AppState, }; @@ -66,39 +66,14 @@ async fn main() { let app_state = AppState::new(configuration.clone(), kv_store.clone()); - // We consider the inputs in order of most to least explicit: CLI flag, supplied file, - // environment variable. - let user_mnemonic = args - .mnemonic - .or_else(|| { - args.mnemonic_file.map(|path| { - let file = std::fs::read(path).expect("Unable to read mnemonic file."); - let mnemonic = std::str::from_utf8(&file) - .expect("Unable to convert provided mnemonic to UTF-8 string.") - .trim(); - - bip39::Mnemonic::parse_normalized(mnemonic) - .expect("Unable to parse given mnemonic.") - }) - }) - .or_else(|| { - std::env::var("THRESHOLD_SERVER_MNEMONIC").ok().map(|mnemonic| { - bip39::Mnemonic::parse_normalized(&mnemonic) - .expect("Unable to parse given mnemonic.") - }) - }); - - if let Some(mnemonic) = user_mnemonic { - setup_mnemonic(&kv_store, mnemonic).await - } else if cfg!(test) || validator_name.is_some() { + if cfg!(test) || validator_name.is_some() { setup_mnemonic(&kv_store, development_mnemonic(&validator_name)).await - } else { - let has_mnemonic = entropy_tss::launch::has_mnemonic(&kv_store).await; - assert!( - has_mnemonic, - "No mnemonic provided. Please provide one or use a development account." - ); - }; + } else if !has_mnemonic(&kv_store).await { + let mut rng = rand::thread_rng(); + let mnemonic = bip39::Mnemonic::generate_in_with(&mut rng, bip39::Language::English, 24) + .expect("Failed to generate mnemonic"); + setup_mnemonic(&kv_store, mnemonic).await + } setup_latest_block_number(&kv_store).await.expect("Issue setting up Latest Block Number"); diff --git a/crates/threshold-signature-server/src/node_info/api.rs b/crates/threshold-signature-server/src/node_info/api.rs index 23a0e1526..52aa40f73 100644 --- a/crates/threshold-signature-server/src/node_info/api.rs +++ b/crates/threshold-signature-server/src/node_info/api.rs @@ -12,9 +12,13 @@ // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -use axum::Json; -use entropy_shared::types::HashingAlgorithm; +use crate::{get_signer_and_x25519_secret, node_info::errors::GetInfoError, AppState}; +use axum::{extract::State, Json}; +use entropy_shared::{types::HashingAlgorithm, X25519PublicKey}; +use serde::{Deserialize, Serialize}; +use sp_core::Pair; use strum::IntoEnumIterator; +use subxt::utils::AccountId32; /// Returns the version and commit data #[tracing::instrument] @@ -22,8 +26,26 @@ pub async fn version() -> String { format!("{}-{}", env!("CARGO_PKG_VERSION"), env!("VERGEN_GIT_DESCRIBE")) } +/// Lists the supported hashing algorithms #[tracing::instrument] pub async fn hashes() -> Json> { let hashing_algos = HashingAlgorithm::iter().collect::>(); Json(hashing_algos) } + +/// Public signing and encryption keys associated with a TS server +#[derive(Serialize, Deserialize, Clone, Debug, Eq, PartialEq)] +pub struct TssPublicKeys { + pub tss_account: AccountId32, + pub x25519_public_key: X25519PublicKey, +} + +/// Returns the TS server's public keys and HTTP endpoint +#[tracing::instrument(skip_all)] +pub async fn info(State(app_state): State) -> Result, GetInfoError> { + let (signer, x25519_secret) = get_signer_and_x25519_secret(&app_state.kv_store).await?; + let tss_account = AccountId32(signer.signer().public().0); + let x25519_public_key = *x25519_dalek::PublicKey::from(&x25519_secret).as_bytes(); + + Ok(Json(TssPublicKeys { x25519_public_key, tss_account })) +} diff --git a/crates/threshold-signature-server/src/node_info/errors.rs b/crates/threshold-signature-server/src/node_info/errors.rs new file mode 100644 index 000000000..9936634f9 --- /dev/null +++ b/crates/threshold-signature-server/src/node_info/errors.rs @@ -0,0 +1,34 @@ +// 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 . +use axum::{ + http::StatusCode, + response::{IntoResponse, Response}, +}; +use thiserror::Error; + +/// Errors for protocol execution +#[derive(Debug, Error)] +pub enum GetInfoError { + #[error("Could not get public keys: {0}")] + User(#[from] crate::user::errors::UserErr), +} + +impl IntoResponse for GetInfoError { + fn into_response(self) -> Response { + tracing::error!("{:?}", format!("{self}")); + let body = format!("{self}").into_bytes(); + (StatusCode::INTERNAL_SERVER_ERROR, body).into_response() + } +} diff --git a/crates/threshold-signature-server/src/node_info/mod.rs b/crates/threshold-signature-server/src/node_info/mod.rs index 5837e4d77..3dcdf1f61 100644 --- a/crates/threshold-signature-server/src/node_info/mod.rs +++ b/crates/threshold-signature-server/src/node_info/mod.rs @@ -15,6 +15,7 @@ //! Provides information about this instance of `entropy-tss` pub mod api; +mod errors; #[cfg(test)] mod tests; diff --git a/crates/threshold-signature-server/src/node_info/tests.rs b/crates/threshold-signature-server/src/node_info/tests.rs index ba851a702..2676ecb87 100644 --- a/crates/threshold-signature-server/src/node_info/tests.rs +++ b/crates/threshold-signature-server/src/node_info/tests.rs @@ -13,9 +13,13 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -use crate::helpers::tests::{initialize_test_logger, setup_client}; +use crate::{ + helpers::tests::{initialize_test_logger, setup_client}, + node_info::api::TssPublicKeys, +}; use entropy_kvdb::clean_tests; use entropy_shared::types::HashingAlgorithm; +use entropy_testing_utils::constants::{TSS_ACCOUNTS, X25519_PUBLIC_KEYS}; use serial_test::serial; #[tokio::test] @@ -55,3 +59,22 @@ async fn hashes_test() { ); clean_tests(); } + +#[tokio::test] +#[serial] +async fn info_test() { + clean_tests(); + initialize_test_logger().await; + setup_client().await; + let client = reqwest::Client::new(); + let response = client.get("http://127.0.0.1:3001/info").send().await.unwrap(); + let public_keys: TssPublicKeys = response.json().await.unwrap(); + assert_eq!( + public_keys, + TssPublicKeys { + tss_account: TSS_ACCOUNTS[0].clone(), + x25519_public_key: X25519_PUBLIC_KEYS[0], + } + ); + clean_tests(); +} From 147fdf462eacf1d5c8e49ab388d3b35e14b6c0f4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 18 Dec 2024 07:36:58 +0000 Subject: [PATCH 8/9] Bump thiserror from 2.0.7 to 2.0.8 in the patch-dependencies group (#1224) Bumps the patch-dependencies group with 1 update: [thiserror](https://github.com/dtolnay/thiserror). Updates `thiserror` from 2.0.7 to 2.0.8 - [Release notes](https://github.com/dtolnay/thiserror/releases) - [Commits](https://github.com/dtolnay/thiserror/compare/2.0.7...2.0.8) --- updated-dependencies: - dependency-name: thiserror dependency-type: direct:production update-type: version-update:semver-patch dependency-group: patch-dependencies ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Cargo.lock | 18 +++++++++--------- crates/client/Cargo.toml | 2 +- crates/kvdb/Cargo.toml | 2 +- crates/protocol/Cargo.toml | 2 +- crates/threshold-signature-server/Cargo.toml | 2 +- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4e486e848..d7d0e9ea8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2547,7 +2547,7 @@ dependencies = [ "subxt", "synedrion", "tdx-quote", - "thiserror 2.0.7", + "thiserror 2.0.8", "tokio", "tracing", "x25519-dalek 2.0.1", @@ -2582,7 +2582,7 @@ dependencies = [ "sled", "sp-core 31.0.0", "synedrion", - "thiserror 2.0.7", + "thiserror 2.0.8", "tokio", "tracing", "zeroize", @@ -2642,7 +2642,7 @@ dependencies = [ "sp-keyring 34.0.0", "subxt", "synedrion", - "thiserror 2.0.7", + "thiserror 2.0.8", "tokio", "tokio-tungstenite", "tracing", @@ -2858,7 +2858,7 @@ dependencies = [ "subxt-signer", "synedrion", "tdx-quote", - "thiserror 2.0.7", + "thiserror 2.0.8", "tokio", "tokio-tungstenite", "tower-http 0.6.2", @@ -14338,11 +14338,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.7" +version = "2.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93605438cbd668185516ab499d589afb7ee1859ea3d5fc8f6b0755e1c7443767" +checksum = "08f5383f3e0071702bf93ab5ee99b52d26936be9dedd9413067cbdcddcb6141a" dependencies = [ - "thiserror-impl 2.0.7", + "thiserror-impl 2.0.8", ] [[package]] @@ -14358,9 +14358,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.7" +version = "2.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1d8749b4531af2117677a5fcd12b1348a3fe2b81e36e61ffeac5c4aa3273e36" +checksum = "f2f357fcec90b3caef6623a099691be676d033b40a058ac95d2a6ade6fa0c943" dependencies = [ "proc-macro2", "quote", diff --git a/crates/client/Cargo.toml b/crates/client/Cargo.toml index 6bf8292f3..b07b110d1 100644 --- a/crates/client/Cargo.toml +++ b/crates/client/Cargo.toml @@ -14,7 +14,7 @@ serde ={ version="1.0", default-features=false, features=["derive"] } entropy-shared={ version="0.3.0", path="../shared", default-features=false } subxt ={ version="0.35.3", default-features=false, features=["jsonrpsee"] } num ="0.4.3" -thiserror ="2.0.7" +thiserror ="2.0.8" futures ="0.3" sp-core ={ version="31.0.0", default-features=false, features=["full_crypto", "serde"] } tracing ="0.1.41" diff --git a/crates/kvdb/Cargo.toml b/crates/kvdb/Cargo.toml index 5e2b386bd..c21efd4dd 100644 --- a/crates/kvdb/Cargo.toml +++ b/crates/kvdb/Cargo.toml @@ -12,7 +12,7 @@ edition ='2021' # Common rand ={ version="0.8", default-features=false } serde ={ version="1.0", features=["derive"] } -thiserror="2.0.7" +thiserror="2.0.8" hex ="0.4.3" # Substrate diff --git a/crates/protocol/Cargo.toml b/crates/protocol/Cargo.toml index f82dd3b84..36b5ea286 100644 --- a/crates/protocol/Cargo.toml +++ b/crates/protocol/Cargo.toml @@ -20,7 +20,7 @@ x25519-dalek ={ version="2.0.1", features=["static_secrets"] } futures ="0.3" hex ="0.4.3" blake2 ="0.10.4" -thiserror ="2.0.7" +thiserror ="2.0.8" snow ="0.9.6" getrandom ={ version="0.2", features=["js"] } rand_core ={ version="0.6.4", features=["getrandom"] } diff --git a/crates/threshold-signature-server/Cargo.toml b/crates/threshold-signature-server/Cargo.toml index 2356aae20..090041cfd 100644 --- a/crates/threshold-signature-server/Cargo.toml +++ b/crates/threshold-signature-server/Cargo.toml @@ -12,7 +12,7 @@ edition ='2021' # Common serde ={ version="1.0", default-features=false, features=["derive"] } serde_json ="1.0" -thiserror ="2.0.7" +thiserror ="2.0.8" anyhow ="1.0.94" blake2 ="0.10.4" x25519-dalek ={ version="2.0.1", features=["static_secrets"] } From 664e406c66486a8ffb0892789fd8a2deab607559 Mon Sep 17 00:00:00 2001 From: peg Date: Wed, 18 Dec 2024 17:50:15 +0100 Subject: [PATCH 9/9] Extract PCK certificate chain from quotes (#1209) * WIP move PCK cert chain verification to attestation pallet * Switch things around for PCK cert chain verification happenning as part of quote verification * Fix error handling in entropy-tss * Use git version of tdx-quote * Use git version of tdx-quote * Update node/cli/Cargo.toml Co-authored-by: Hernando Castano * Fix staking pallet cargo.toml * Taplo * Update tdx-quote, rm unwraps * Improve error handling in attestation pallet * Bump tdx-quote again following improvement of error type * Update tdx-quote following pck_verify takes reference * Revert cargo.lock, fix ensure! macro call * Fix staking pallet config in other pallets mock files * Update metadata * Fix client test * Update test-cli * Fixes for tests * Fix staking pallet benchmarking * Fix staking pallet benchmarking * Fix client following merge master * Typo * Error handling * Update staking pallet tests * Comments, changelog * VerifyQuoteError implements Debug * Rm PCK cert chain from JoiningServerInfo * Pull chain metadata * Clippy * Clippy * Add to changelog * Minor changes following review comments * Use specific commit of tdx-quote rather than branch --------- Co-authored-by: Hernando Castano --- CHANGELOG.md | 8 ++ Cargo.lock | 20 ++- crates/client/Cargo.toml | 8 +- crates/client/entropy_metadata.scale | Bin 210103 -> 210681 bytes crates/client/src/client.rs | 4 - crates/client/src/tests.rs | 19 +-- crates/shared/src/types.rs | 37 +++++- crates/test-cli/src/lib.rs | 6 - crates/testing-utils/Cargo.toml | 36 ++--- crates/threshold-signature-server/Cargo.toml | 54 ++++---- .../src/attestation/api.rs | 5 +- .../src/attestation/errors.rs | 3 + .../src/attestation/tests.rs | 2 +- node/cli/Cargo.toml | 3 + pallets/attestation/Cargo.toml | 24 ++-- pallets/attestation/src/lib.rs | 69 ++++++---- pallets/attestation/src/mock.rs | 1 - pallets/attestation/src/tests.rs | 44 +++---- .../test_pck_certs/pck_cert.der | Bin .../test_pck_certs/platform_pcs_cert.der | Bin pallets/propagation/src/mock.rs | 1 - pallets/registry/src/mock.rs | 1 - pallets/staking/Cargo.toml | 13 +- pallets/staking/src/benchmarking.rs | 46 ++++--- pallets/staking/src/lib.rs | 123 +++++++++--------- pallets/staking/src/mock.rs | 12 +- ..._SGX_Provisioning_Certification_RootCA.cer | Bin 659 -> 0 bytes pallets/staking/src/pck/mock.rs | 56 -------- pallets/staking/src/pck/mod.rs | 58 --------- pallets/staking/src/pck/production.rs | 110 ---------------- pallets/staking/src/tests.rs | 100 ++++---------- runtime/Cargo.toml | 3 + runtime/src/lib.rs | 1 - 33 files changed, 326 insertions(+), 541 deletions(-) rename pallets/{staking => attestation}/test_pck_certs/pck_cert.der (100%) rename pallets/{staking => attestation}/test_pck_certs/platform_pcs_cert.der (100%) delete mode 100644 pallets/staking/src/pck/Intel_SGX_Provisioning_Certification_RootCA.cer delete mode 100644 pallets/staking/src/pck/mock.rs delete mode 100644 pallets/staking/src/pck/mod.rs delete mode 100644 pallets/staking/src/pck/production.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f17c8377..f3ce565ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,10 @@ runtime - In [#1147](https://github.com/entropyxyz/entropy-core/pull/1147) a field is added to the chainspec: `jump_started_signers` which allows the chain to be started in a pre-jumpstarted state for testing. If this is not desired it should be set to `None`. +- In [#1209](https://github.com/entropyxyz/entropy-core/pull/1209) the `validate` and `change_threshold_accounts` + extrinsics no longer take a PCK certificate chain. Rather, the certificate chain is extracted from the + provided quote. The test CLI `change-threshold-accounts` command also no longer takes a PCK + certificate chain. ### Added - In [#1128](https://github.com/entropyxyz/entropy-core/pull/1128) an `/info` route was added to `entropy-tss` @@ -41,6 +45,9 @@ runtime - Add TSS endpoint to get TDX quote ([#1173](https://github.com/entropyxyz/entropy-core/pull/1173)) - Add TDX test network chainspec ([#1204](https://github.com/entropyxyz/entropy-core/pull/1204)) - Test CLI command to retrieve quote and change endpoint / TSS account in one command ([#1198](https://github.com/entropyxyz/entropy-core/pull/1198)) +- In ([#1209]()) a `production` feature flag was added to `entropy` which if enabled will use + non-mock verification of PCK certificate chains in TDX quotes, meaning TSS servers must be running + on TDX hardware - On-chain unresponsiveness reporting [(#1215)](https://github.com/entropyxyz/entropy-core/pull/1215) ### Changed @@ -52,6 +59,7 @@ runtime - 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)) - Allow offchain worker requests to all TSS nodes in entropy-tss test environment ([#1147](https://github.com/entropyxyz/entropy-core/pull/1147)) +- Extract PCK certificate chain from quotes ([#1209](https://github.com/entropyxyz/entropy-core/pull/1209)) ### Fixed diff --git a/Cargo.lock b/Cargo.lock index d7d0e9ea8..87c185317 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7492,7 +7492,6 @@ dependencies = [ "frame-support 29.0.2", "frame-system", "log", - "p256", "pallet-bags-list", "pallet-balances", "pallet-parameters", @@ -7514,9 +7513,7 @@ dependencies = [ "sp-runtime 32.0.0", "sp-staking 27.0.0", "sp-std 14.0.0", - "spki", "tdx-quote", - "x509-verify", ] [[package]] @@ -7916,6 +7913,15 @@ dependencies = [ "base64 0.13.1", ] +[[package]] +name = "pem" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e459365e590736a54c3fa561947c84837534b8e9af6fc5bf781307e82658fae" +dependencies = [ + "base64 0.22.1", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -8758,7 +8764,7 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffbe84efe2f38dea12e9bfc1f65377fdf03e53a18cb3b995faedf7934c7e785b" dependencies = [ - "pem", + "pem 1.1.1", "ring 0.16.20", "time", "yasna", @@ -14281,12 +14287,14 @@ checksum = "e1fc403891a21bcfb7c37834ba66a547a8f402146eba7265b5a6d88059c9ff2f" [[package]] name = "tdx-quote" version = "0.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10fe3140395153ccb4050f6eb51fe621a3b6d76da489601aec9fabe2fb1c3e0" +source = "git+https://github.com/entropyxyz/tdx-quote.git?rev=67a9d011809d0c9109d1ac42aeb809a84b663be6#67a9d011809d0c9109d1ac42aeb809a84b663be6" dependencies = [ "nom", "p256", + "pem 3.0.4", "sha2 0.10.8", + "spki", + "x509-verify", ] [[package]] diff --git a/crates/client/Cargo.toml b/crates/client/Cargo.toml index b07b110d1..0161ad229 100644 --- a/crates/client/Cargo.toml +++ b/crates/client/Cargo.toml @@ -38,10 +38,12 @@ js-sys={ version="0.3.74", optional=true } tokio ={ version="1.42", features=["time"] } [dev-dependencies] -serial_test ="3.2.0" -sp-keyring ="34.0.0" +serial_test="3.2.0" +sp-keyring="34.0.0" entropy-testing-utils={ path="../testing-utils" } -tdx-quote ={ version="0.0.1", features=["mock"] } +tdx-quote={ git="https://github.com/entropyxyz/tdx-quote.git", rev="67a9d011809d0c9109d1ac42aeb809a84b663be6", features=[ + "mock", +] } [features] default=["native", "full-client-native"] diff --git a/crates/client/entropy_metadata.scale b/crates/client/entropy_metadata.scale index 9d94e92bcafb91bedcad567f69379041060a5b66..592a37ddec70dae2e6a6ff3f88f9de6a70b28494 100644 GIT binary patch delta 572 zcmdn~lIQ1Jo(&2w8F?lvzI55#`*I!=kg4+~YV*1`meUw>w!dg%>|o?pa7s)GEX^-T zWe}V`=O?2bqtNu*KN&q3g{Q0hVl?45080B7m866v=9H!~h)kFN&8TMXnwOlPk{Xs; zl$lnUnV0UJTFD^F(&C($mzQ6nkP4PmD1*r=WT#dtXaqQWYfiWO%_zkvHa+q;qw@5E z7Dmx5ah4fC35Dd;qLR$C%;dz9RE6Y>#LPT}WQbWwsR{*&Ma8Kp0RhR`&InZys~IF% z77)@7c1C7u%Jlgyj521DEN8H5C@4+J$xH@2HM3X&q1FMxE^we==@i z#1?pLL%qn*2jUp_Q=oI!!dz{PR_yDt%;ip{a@JbxIs K^Za32Z2kPAo}{PtHip%)7|QXb_f~Yy-lj z7It=lzxGS>, ) -> Result { let quote = get_tdx_quote_with_validator_id( api, @@ -387,7 +386,6 @@ pub async fn get_quote_and_change_threshold_accounts( validator_keypair, new_tss_account, new_x25519_public_key, - new_pck_certificate_chain, quote, ) .await @@ -400,13 +398,11 @@ pub async fn change_threshold_accounts( validator_keypair: sr25519::Pair, new_tss_account: SubxtAccountId32, new_x25519_public_key: [u8; 32], - new_pck_certificate_chain: Vec>, quote: Vec, ) -> Result { let change_threshold_accounts = entropy::tx().staking_extension().change_threshold_accounts( new_tss_account, new_x25519_public_key, - new_pck_certificate_chain, quote, ); let in_block = submit_transaction_with_pair( diff --git a/crates/client/src/tests.rs b/crates/client/src/tests.rs index a42b7758e..bf3870102 100644 --- a/crates/client/src/tests.rs +++ b/crates/client/src/tests.rs @@ -59,8 +59,11 @@ async fn test_change_endpoint() { let mut pck_seeder = StdRng::from_seed(public_key.0); let pck = tdx_quote::SigningKey::random(&mut pck_seeder); + let pck_encoded = tdx_quote::encode_verifying_key(pck.verifying_key()).unwrap().to_vec(); - tdx_quote::Quote::mock(signing_key.clone(), pck, input_data.0).as_bytes().to_vec() + tdx_quote::Quote::mock(signing_key.clone(), pck, input_data.0, pck_encoded) + .as_bytes() + .to_vec() }; let result = @@ -116,14 +119,12 @@ async fn test_change_threshold_accounts() { let nonce = request_attestation(&api, &rpc, tss_signer_pair.signer()).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 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(); let quote = { let input_data = entropy_shared::QuoteInputData::new( @@ -134,7 +135,10 @@ async fn test_change_threshold_accounts() { ); 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() + + tdx_quote::Quote::mock(signing_key.clone(), pck.clone(), input_data.0, encoded_pck.clone()) + .as_bytes() + .to_vec() }; let result = change_threshold_accounts( @@ -143,7 +147,6 @@ async fn test_change_threshold_accounts() { one.into(), tss_public_key.into(), *x25519_public_key.as_bytes(), - pck_certificate_chain, quote, ) .await diff --git a/crates/shared/src/types.rs b/crates/shared/src/types.rs index 2de1b647c..a9655c760 100644 --- a/crates/shared/src/types.rs +++ b/crates/shared/src/types.rs @@ -162,13 +162,14 @@ impl std::fmt::Display for QuoteContext { #[cfg(not(feature = "wasm"))] pub trait AttestationHandler { /// Verify that the given quote is valid and matches the given information about the attestee. + /// The Provisioning Certification Key (PCK) certifcate chain is extracted from the quote and + /// verified. If successful, the PCK public key used to sign the quote is returned. fn verify_quote( attestee: &AccountId, x25519_public_key: X25519PublicKey, - provisioning_certification_key: BoundedVecEncodedVerifyingKey, quote: Vec, context: QuoteContext, - ) -> Result<(), sp_runtime::DispatchError>; + ) -> Result; /// Indicate to the attestation handler that a quote is desired. /// @@ -183,12 +184,38 @@ impl AttestationHandler for () { fn verify_quote( _attestee: &AccountId, _x25519_public_key: X25519PublicKey, - _provisioning_certification_key: BoundedVecEncodedVerifyingKey, _quote: Vec, _context: QuoteContext, - ) -> Result<(), sp_runtime::DispatchError> { - Ok(()) + ) -> Result { + // Ok(sp_runtime::BoundedVec::new()) + Ok(BoundedVecEncodedVerifyingKey::try_from([0; 33].to_vec()).unwrap()) } fn request_quote(_attestee: &AccountId, _nonce: [u8; 32]) {} } + +/// An error when verifying a quote +#[cfg(not(feature = "wasm"))] +#[derive(Debug, Eq, PartialEq)] +pub enum VerifyQuoteError { + /// Quote could not be parsed or verified + BadQuote, + /// Attestation extrinsic submitted when not requested + UnexpectedAttestation, + /// Hashed input data does not match what was expected + IncorrectInputData, + /// Unacceptable VM image running + BadMrtdValue, + /// Cannot encode verifying key (PCK) + CannotEncodeVerifyingKey, + /// Cannot decode verifying key (PCK) + CannotDecodeVerifyingKey, + /// PCK certificate chain cannot be parsed + PckCertificateParse, + /// PCK certificate chain cannot be verified + PckCertificateVerify, + /// PCK certificate chain public key is not well formed + PckCertificateBadPublicKey, + /// Pck certificate could not be extracted from quote + PckCertificateNoCertificate, +} diff --git a/crates/test-cli/src/lib.rs b/crates/test-cli/src/lib.rs index 42a030545..731ab0812 100644 --- a/crates/test-cli/src/lib.rs +++ b/crates/test-cli/src/lib.rs @@ -160,8 +160,6 @@ 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, /// The mnemonic for the validator stash account to use for the call, should be stash address #[arg(short, long)] mnemonic_option: Option, @@ -482,7 +480,6 @@ pub async fn run_command( CliCommand::ChangeThresholdAccounts { new_tss_account, new_x25519_public_key, - new_pck_certificate_chain, mnemonic_option, } => { let user_keypair = handle_mnemonic(mnemonic_option)?; @@ -492,15 +489,12 @@ pub async fn run_command( let new_x25519_public_key = hex::decode(new_x25519_public_key)? .try_into() .map_err(|_| anyhow!("X25519 pub key needs to be 32 bytes"))?; - let new_pck_certificate_chain = - new_pck_certificate_chain.iter().cloned().map(|i| i.into()).collect::<_>(); let result_event = get_quote_and_change_threshold_accounts( &api, &rpc, user_keypair, new_tss_account, new_x25519_public_key, - new_pck_certificate_chain, ) .await?; cli.log(format!("Event result: {:?}", result_event)); diff --git a/crates/testing-utils/Cargo.toml b/crates/testing-utils/Cargo.toml index 65c6fa263..75d0d0755 100644 --- a/crates/testing-utils/Cargo.toml +++ b/crates/testing-utils/Cargo.toml @@ -9,24 +9,26 @@ repository ='https://github.com/entropyxyz/entropy-core' edition ='2021' [dependencies] -subxt ="0.35.3" -sp-keyring ="34.0.0" -project-root ="0.2.2" -sp-core ={ version="31.0.0", default-features=false } +subxt="0.35.3" +sp-keyring="34.0.0" +project-root="0.2.2" +sp-core={ version="31.0.0", default-features=false } parity-scale-codec="3.6.12" -lazy_static ="1.5.0" -hex-literal ="0.4.1" -tokio ={ version="1.42", features=["macros", "fs", "rt-multi-thread", "io-util", "process"] } -axum ={ version="0.7.9" } -entropy-shared ={ version="0.3.0", path="../shared" } -entropy-kvdb ={ version="0.3.0", path="../kvdb", default-features=false } -entropy-tss ={ version="0.3.0", path="../threshold-signature-server", features=["test_helpers"] } -entropy-protocol ={ version="0.3.0", path="../protocol" } -synedrion ="0.2.0" -hex ="0.4.3" -rand_core ="0.6.4" -rand ="0.8.5" -tdx-quote ={ version="0.0.1", features=["mock"] } +lazy_static="1.5.0" +hex-literal="0.4.1" +tokio={ version="1.42", features=["macros", "fs", "rt-multi-thread", "io-util", "process"] } +axum={ version="0.7.9" } +entropy-shared={ version="0.3.0", path="../shared" } +entropy-kvdb={ version="0.3.0", path="../kvdb", default-features=false } +entropy-tss={ version="0.3.0", path="../threshold-signature-server", features=["test_helpers"] } +entropy-protocol={ version="0.3.0", path="../protocol" } +synedrion="0.2.0" +hex="0.4.3" +rand_core="0.6.4" +rand="0.8.5" +tdx-quote={ git="https://github.com/entropyxyz/tdx-quote.git", rev="67a9d011809d0c9109d1ac42aeb809a84b663be6", features=[ + "mock", +] } # Logging tracing ="0.1.41" diff --git a/crates/threshold-signature-server/Cargo.toml b/crates/threshold-signature-server/Cargo.toml index 090041cfd..be5cdf564 100644 --- a/crates/threshold-signature-server/Cargo.toml +++ b/crates/threshold-signature-server/Cargo.toml @@ -58,36 +58,40 @@ uuid ={ version="1.11.0", features=["v4"] } # Misc tokio-tungstenite="0.24.0" -bincode ="1.3.3" -bip32 ={ version="0.5.2" } -bip39 ={ version="2.1.0", features=["zeroize"] } -bytes ={ version="1.9", default-features=false, features=["serde"] } -base64 ="0.22.1" -clap ={ version="4.5.23", features=["derive"] } -num ="0.4.3" -snow ="0.9.6" -sha3 ="0.10.8" -hostname ="0.4" -sha1 ="0.10.6" -sha2 ="0.10.8" -hkdf ="0.12.4" -project-root ={ version="0.2.2", optional=true } -tdx-quote ={ version="0.0.1", optional=true, features=["mock"] } -configfs-tsm ={ version="0.0.1", optional=true } +bincode="1.3.3" +bip32={ version="0.5.2" } +bip39={ version="2.1.0", features=["zeroize"] } +bytes={ version="1.9", default-features=false, features=["serde"] } +base64="0.22.1" +clap={ version="4.5.23", features=["derive"] } +num="0.4.3" +snow="0.9.6" +sha3="0.10.8" +hostname="0.4" +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.git", rev="67a9d011809d0c9109d1ac42aeb809a84b663be6", optional=true, features=[ + "mock", +] } +configfs-tsm={ version="0.0.1", optional=true } [dev-dependencies] -serial_test ="3.2.0" -hex-literal ="0.4.1" +serial_test="3.2.0" +hex-literal="0.4.1" project-root="0.2.2" -sp-keyring ="34.0.0" +sp-keyring="34.0.0" more-asserts="0.3.1" -lazy_static ="1.5.0" -blake3 ="1.5.5" -ethers-core ="2.0.14" -schnorrkel ={ version="0.11.4", default-features=false, features=["std"] } -schemars ={ version="0.8.21" } +lazy_static="1.5.0" +blake3="1.5.5" +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 ={ version="0.0.1", features=["mock"] } +tdx-quote={ git="https://github.com/entropyxyz/tdx-quote.git", rev="67a9d011809d0c9109d1ac42aeb809a84b663be6", 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. diff --git a/crates/threshold-signature-server/src/attestation/api.rs b/crates/threshold-signature-server/src/attestation/api.rs index d043e149a..90bc302a8 100644 --- a/crates/threshold-signature-server/src/attestation/api.rs +++ b/crates/threshold-signature-server/src/attestation/api.rs @@ -136,7 +136,10 @@ pub async fn create_quote( let mut pck_seeder = StdRng::from_seed(signer.signer().public().0); let pck = tdx_quote::SigningKey::random(&mut pck_seeder); - let quote = tdx_quote::Quote::mock(signing_key.clone(), pck, input_data.0).as_bytes().to_vec(); + let pck_encoded = tdx_quote::encode_verifying_key(pck.verifying_key())?.to_vec(); + let quote = tdx_quote::Quote::mock(signing_key.clone(), pck, input_data.0, pck_encoded) + .as_bytes() + .to_vec(); Ok(quote) } diff --git a/crates/threshold-signature-server/src/attestation/errors.rs b/crates/threshold-signature-server/src/attestation/errors.rs index 7daad52fb..0905ceb27 100644 --- a/crates/threshold-signature-server/src/attestation/errors.rs +++ b/crates/threshold-signature-server/src/attestation/errors.rs @@ -40,6 +40,9 @@ pub enum AttestationErr { #[cfg(feature = "production")] #[error("Quote generation: {0}")] QuoteGeneration(String), + #[cfg(not(feature = "production"))] + #[error("Cannot encode verifying key: {0}")] + EncodeVerifyingKey(#[from] tdx_quote::VerifyingKeyError), #[error("Vec Conversion Error: {0}")] Conversion(&'static str), #[error("Data is repeated")] diff --git a/crates/threshold-signature-server/src/attestation/tests.rs b/crates/threshold-signature-server/src/attestation/tests.rs index 8ba451305..ae68641f8 100644 --- a/crates/threshold-signature-server/src/attestation/tests.rs +++ b/crates/threshold-signature-server/src/attestation/tests.rs @@ -63,7 +63,7 @@ async fn test_get_attest() { decode_verifying_key(&server_info.provisioning_certification_key.0.try_into().unwrap()) .unwrap(); - assert!(quote.verify_with_pck(provisioning_certification_key).is_ok()) + assert!(quote.verify_with_pck(&provisioning_certification_key).is_ok()) } #[ignore] diff --git a/node/cli/Cargo.toml b/node/cli/Cargo.toml index b1cf2d0cb..e3fb11609 100644 --- a/node/cli/Cargo.toml +++ b/node/cli/Cargo.toml @@ -119,3 +119,6 @@ runtime-benchmarks=[ # Enable features that allow the runtime to be tried and debugged. Name might be subject to change # in the near future. try-runtime=["entropy-runtime/try-runtime", "try-runtime-cli/try-runtime"] +# Enables real PCK certificate chain verification - which means TSS nodes must be runnning on TDX +# hardware +production=["entropy-runtime/production"] diff --git a/pallets/attestation/Cargo.toml b/pallets/attestation/Cargo.toml index 917fdf320..30f7b239b 100644 --- a/pallets/attestation/Cargo.toml +++ b/pallets/attestation/Cargo.toml @@ -28,19 +28,21 @@ entropy-shared={ version="0.3.0", path="../../crates/shared", features=[ "wasm-no-std", ], default-features=false } pallet-staking-extension={ version="0.3.0", path="../staking", default-features=false } -tdx-quote="0.0.1" +tdx-quote={ git="https://github.com/entropyxyz/tdx-quote.git", rev="67a9d011809d0c9109d1ac42aeb809a84b663be6" } [dev-dependencies] -pallet-session ={ version="29.0.0", default-features=false } -pallet-staking ={ version="29.0.0", default-features=false } -pallet-balances ={ version="29.0.0", default-features=false } -pallet-bags-list ={ version="28.0.0", default-features=false } -pallet-timestamp ={ version="28.0.0", default-features=false } -sp-npos-elections ={ version="27.0.0", default-features=false } +pallet-session={ version="29.0.0", default-features=false } +pallet-staking={ version="29.0.0", default-features=false } +pallet-balances={ version="29.0.0", default-features=false } +pallet-bags-list={ version="28.0.0", default-features=false } +pallet-timestamp={ version="28.0.0", default-features=false } +sp-npos-elections={ version="27.0.0", default-features=false } frame-election-provider-support={ version="29.0.0", default-features=false } -pallet-staking-reward-curve ={ version="11.0.0" } -tdx-quote ={ version="0.0.1", features=["mock"] } -rand_core ="0.6.4" +pallet-staking-reward-curve={ version="11.0.0" } +tdx-quote={ git="https://github.com/entropyxyz/tdx-quote.git", rev="67a9d011809d0c9109d1ac42aeb809a84b663be6", features=[ + "mock", +] } +rand_core="0.6.4" pallet-slashing={ version="0.3.0", path="../slashing", default-features=false } @@ -60,3 +62,5 @@ std=[ "rand_chacha/std", ] try-runtime=['frame-support/try-runtime'] +# When enabled, use real PCK certificate chain verification +production=[] diff --git a/pallets/attestation/src/lib.rs b/pallets/attestation/src/lib.rs index ec046b851..1d4f08c6f 100644 --- a/pallets/attestation/src/lib.rs +++ b/pallets/attestation/src/lib.rs @@ -47,7 +47,7 @@ mod tests; #[frame_support::pallet] pub mod pallet { - use entropy_shared::{AttestationHandler, QuoteContext, QuoteInputData}; + use entropy_shared::{AttestationHandler, QuoteContext, QuoteInputData, VerifyQuoteError}; use frame_support::pallet_prelude::*; use frame_support::traits::Randomness; use frame_system::pallet_prelude::*; @@ -58,7 +58,7 @@ pub mod pallet { rand_core::{RngCore, SeedableRng}, ChaCha20Rng, ChaChaRng, }; - use tdx_quote::{decode_verifying_key, Quote}; + use tdx_quote::{encode_verifying_key, Quote, VerifyingKey}; pub use crate::weights::WeightInfo; @@ -133,12 +133,16 @@ pub mod pallet { NoPCKForAccount, /// Unacceptable VM image running BadMrtdValue, + /// Cannot encode verifying key (PCK) + CannotEncodeVerifyingKey, /// Cannot decode verifying key (PCK) CannotDecodeVerifyingKey, /// Could not verify PCK signature PckVerification, /// There's an existing attestation request for this account ID. OutstandingAttestationRequest, + /// PCK certificate chain cannot be extracted from quote + NoPckCertChain, } #[pallet::call] @@ -203,54 +207,73 @@ pub mod pallet { fn verify_quote( attestee: &T::AccountId, x25519_public_key: entropy_shared::X25519PublicKey, - provisioning_certification_key: entropy_shared::BoundedVecEncodedVerifyingKey, quote: Vec, context: QuoteContext, - ) -> Result<(), DispatchError> { + ) -> Result { // Check that we were expecting a quote from this validator by getting the associated // nonce from PendingAttestations. - let nonce = - PendingAttestations::::get(attestee).ok_or(Error::::UnexpectedAttestation)?; + let nonce = PendingAttestations::::get(attestee) + .ok_or(VerifyQuoteError::UnexpectedAttestation)?; // Parse the quote (which internally verifies the attestation key signature) - let quote = Quote::from_bytes("e).map_err(|_| Error::::BadQuote)?; + let quote = Quote::from_bytes("e).map_err(|_| VerifyQuoteError::BadQuote)?; // Check report input data matches the nonce, TSS details and block number let expected_input_data = QuoteInputData::new(attestee, x25519_public_key, nonce, context); ensure!( quote.report_input_data() == expected_input_data.0, - Error::::IncorrectInputData + VerifyQuoteError::IncorrectInputData ); // Check build-time measurement matches a current-supported release of entropy-tss let mrtd_value = BoundedVec::try_from(quote.mrtd().to_vec()) - .map_err(|_| Error::::BadMrtdValue)?; + .map_err(|_| VerifyQuoteError::BadMrtdValue)?; let accepted_mrtd_values = pallet_parameters::Pallet::::accepted_mrtd_values(); - ensure!(accepted_mrtd_values.contains(&mrtd_value), Error::::BadMrtdValue); - - // Check that the attestation public key is signed with the PCK - let provisioning_certification_key = decode_verifying_key( - &provisioning_certification_key - .to_vec() - .try_into() - .map_err(|_| Error::::CannotDecodeVerifyingKey)?, - ) - .map_err(|_| Error::::CannotDecodeVerifyingKey)?; + ensure!(accepted_mrtd_values.contains(&mrtd_value), VerifyQuoteError::BadMrtdValue); - quote - .verify_with_pck(provisioning_certification_key) - .map_err(|_| Error::::PckVerification)?; + let pck = verify_pck_certificate_chain("e)?; PendingAttestations::::remove(attestee); // TODO #982 If anything fails, don't just return an error - do something mean - Ok(()) + BoundedVec::try_from( + encode_verifying_key(&pck) + .map_err(|_| VerifyQuoteError::CannotEncodeVerifyingKey)? + .to_vec(), + ) + .map_err(|_| VerifyQuoteError::CannotEncodeVerifyingKey) } fn request_quote(who: &T::AccountId, nonce: [u8; 32]) { PendingAttestations::::insert(who, nonce) } } + + #[cfg(feature = "production")] + fn verify_pck_certificate_chain(quote: &Quote) -> Result { + quote.verify().map_err(|_| VerifyQuoteError::PckCertificateVerify) + } + + /// A mock version of verifying the PCK certificate chain. + /// When generating mock quotes, we just put the encoded PCK in place of the certificate chain + /// so this function just decodes it, checks it was used to sign the quote, and returns it + #[cfg(not(feature = "production"))] + fn verify_pck_certificate_chain(quote: &Quote) -> Result { + let provisioning_certification_key = + quote.pck_cert_chain().map_err(|_| VerifyQuoteError::PckCertificateNoCertificate)?; + let provisioning_certification_key = tdx_quote::decode_verifying_key( + &provisioning_certification_key + .try_into() + .map_err(|_| VerifyQuoteError::CannotDecodeVerifyingKey)?, + ) + .map_err(|_| VerifyQuoteError::CannotDecodeVerifyingKey)?; + + ensure!( + quote.verify_with_pck(&provisioning_certification_key).is_ok(), + VerifyQuoteError::PckCertificateVerify + ); + Ok(provisioning_certification_key) + } } diff --git a/pallets/attestation/src/mock.rs b/pallets/attestation/src/mock.rs index 7ed5c402f..575c2cead 100644 --- a/pallets/attestation/src/mock.rs +++ b/pallets/attestation/src/mock.rs @@ -316,7 +316,6 @@ impl pallet_staking_extension::Config for Test { type AttestationHandler = (); type Currency = Balances; type MaxEndpointLength = MaxEndpointLength; - type PckCertChainVerifier = pallet_staking_extension::pck::MockPckCertChainVerifier; type Randomness = TestPastRandomness; type RuntimeEvent = RuntimeEvent; type WeightInfo = (); diff --git a/pallets/attestation/src/tests.rs b/pallets/attestation/src/tests.rs index 113d5cd15..7b57f452f 100644 --- a/pallets/attestation/src/tests.rs +++ b/pallets/attestation/src/tests.rs @@ -14,7 +14,7 @@ // along with this program. If not, see . use crate::mock::*; -use entropy_shared::{AttestationHandler, QuoteContext, QuoteInputData}; +use entropy_shared::{AttestationHandler, QuoteContext, QuoteInputData, VerifyQuoteError}; use frame_support::{assert_noop, assert_ok}; use rand_core::OsRng; @@ -40,11 +40,15 @@ fn verify_quote_works() { QuoteContext::Validate, ); - let quote = tdx_quote::Quote::mock(attestation_key.clone(), pck, input_data.0); + let quote = tdx_quote::Quote::mock( + attestation_key.clone(), + pck, + input_data.0, + pck_encoded.to_vec(), + ); assert_ok!(Attestation::verify_quote( &ATTESTEE, x25519_public_key, - sp_runtime::BoundedVec::try_from(pck_encoded.to_vec()).unwrap(), quote.as_bytes().to_vec(), QuoteContext::Validate, )); @@ -71,7 +75,12 @@ fn verify_quote_fails_with_mismatched_input_data() { QuoteContext::Validate, ); - let quote = tdx_quote::Quote::mock(attestation_key.clone(), pck, input_data.0); + let quote = tdx_quote::Quote::mock( + attestation_key.clone(), + pck.clone(), + input_data.0, + pck_encoded.to_vec(), + ); // We want to test that our quote verification fails if we commit to data that doesn't match // the `quote`. @@ -80,11 +89,10 @@ fn verify_quote_fails_with_mismatched_input_data() { Attestation::verify_quote( &mismatched_attestee, x25519_public_key, - sp_runtime::BoundedVec::try_from(pck_encoded.to_vec()).unwrap(), quote.as_bytes().to_vec(), QuoteContext::Validate, ), - crate::Error::::UnexpectedAttestation, + VerifyQuoteError::UnexpectedAttestation, ); // The X25519 public key we're comitting to here doesn't match what we used to generate the @@ -94,32 +102,10 @@ fn verify_quote_fails_with_mismatched_input_data() { Attestation::verify_quote( &ATTESTEE, mismatched_x25519_public_key, - sp_runtime::BoundedVec::try_from(pck_encoded.to_vec()).unwrap(), - quote.as_bytes().to_vec(), - QuoteContext::Validate, - ), - crate::Error::::IncorrectInputData, - ); - - // Here we have a random p256 ECDSA key which doesn't match the one used to generate the - // quote. - let mismatched_pck = [ - 34, 106, 242, 46, 148, 212, 105, 237, 205, 76, 112, 33, 10, 46, 94, 228, 46, 62, 226, - 127, 19, 73, 28, 106, 68, 253, 119, 138, 136, 246, 37, 251, - ]; - let mismatched_pck = tdx_quote::SigningKey::from_bytes(&mismatched_pck.into()).unwrap(); - let mismatched_pck_encoded = - tdx_quote::encode_verifying_key(mismatched_pck.verifying_key()).unwrap(); - - assert_noop!( - Attestation::verify_quote( - &ATTESTEE, - x25519_public_key, - sp_runtime::BoundedVec::try_from(mismatched_pck_encoded.to_vec()).unwrap(), quote.as_bytes().to_vec(), QuoteContext::Validate, ), - crate::Error::::PckVerification + VerifyQuoteError::IncorrectInputData, ); }) } diff --git a/pallets/staking/test_pck_certs/pck_cert.der b/pallets/attestation/test_pck_certs/pck_cert.der similarity index 100% rename from pallets/staking/test_pck_certs/pck_cert.der rename to pallets/attestation/test_pck_certs/pck_cert.der diff --git a/pallets/staking/test_pck_certs/platform_pcs_cert.der b/pallets/attestation/test_pck_certs/platform_pcs_cert.der similarity index 100% rename from pallets/staking/test_pck_certs/platform_pcs_cert.der rename to pallets/attestation/test_pck_certs/platform_pcs_cert.der diff --git a/pallets/propagation/src/mock.rs b/pallets/propagation/src/mock.rs index e3d3bf77e..fac31d4ec 100644 --- a/pallets/propagation/src/mock.rs +++ b/pallets/propagation/src/mock.rs @@ -311,7 +311,6 @@ impl pallet_staking_extension::Config for Test { type AttestationHandler = (); type Currency = Balances; type MaxEndpointLength = MaxEndpointLength; - type PckCertChainVerifier = pallet_staking_extension::pck::MockPckCertChainVerifier; type Randomness = TestPastRandomness; type RuntimeEvent = RuntimeEvent; type WeightInfo = (); diff --git a/pallets/registry/src/mock.rs b/pallets/registry/src/mock.rs index 573ea9e75..c38c485da 100644 --- a/pallets/registry/src/mock.rs +++ b/pallets/registry/src/mock.rs @@ -308,7 +308,6 @@ impl pallet_staking_extension::Config for Test { type AttestationHandler = (); type Currency = Balances; type MaxEndpointLength = MaxEndpointLength; - type PckCertChainVerifier = pallet_staking_extension::pck::MockPckCertChainVerifier; type Randomness = TestPastRandomness; type RuntimeEvent = RuntimeEvent; type WeightInfo = (); diff --git a/pallets/staking/Cargo.toml b/pallets/staking/Cargo.toml index ff6ecfbbe..04cb6a592 100644 --- a/pallets/staking/Cargo.toml +++ b/pallets/staking/Cargo.toml @@ -28,9 +28,6 @@ sp-runtime ={ version="32.0.0", default-features=false } sp-staking ={ version="27.0.0", default-features=false } sp-std ={ version="14.0.0", default-features=false } sp-consensus-babe ={ version="0.33.0", default-features=false } -x509-verify ={ version="0.4.6", features=["x509"] } -spki ="0.7.3" -p256 ={ version="0.13.2", default-features=false, features=["ecdsa"] } rand ={ version="0.8.5", default-features=false, features=["alloc"] } pallet-parameters={ version="0.3.0", path="../parameters", default-features=false } @@ -38,10 +35,9 @@ pallet-slashing={ version="0.3.0", path="../slashing", default-features=false } entropy-shared={ version="0.3.0", path="../../crates/shared", features=[ "wasm-no-std", ], default-features=false } - -# We only use this in benchmarks and tests. Since the benches don't run with the `test` feature we need to -# import it here _and_ as a `dev-dependency`. -tdx-quote={ version="0.0.1", features=["mock"], optional=true } +tdx-quote={ git="https://github.com/entropyxyz/tdx-quote.git", rev="67a9d011809d0c9109d1ac42aeb809a84b663be6", features=[ + "mock", +], optional=true } [dev-dependencies] frame-election-provider-support={ version="29.0.0", default-features=false } @@ -53,11 +49,10 @@ sp-io ={ version="31.0.0", default-features=false } sp-npos-elections ={ version="27.0.0", default-features=false } rand_core="0.6.4" -tdx-quote={ version="0.0.1", features=["mock"] } [features] default=['std'] -runtime-benchmarks=['frame-benchmarking', 'tdx-quote'] +runtime-benchmarks=['frame-benchmarking', "tdx-quote"] std=[ "sp-npos-elections/std", "sp-staking/std", diff --git a/pallets/staking/src/benchmarking.rs b/pallets/staking/src/benchmarking.rs index a6dad4dae..8b59b3e0a 100644 --- a/pallets/staking/src/benchmarking.rs +++ b/pallets/staking/src/benchmarking.rs @@ -16,7 +16,6 @@ //! Benchmarking setup for pallet-propgation #![allow(unused_imports)] use super::*; -use crate::pck::{signing_key_from_seed, MOCK_PCK_DERIVED_FROM_NULL_ARRAY}; #[allow(unused_imports)] use crate::Pallet as Staking; use entropy_shared::{AttestationHandler, QuoteContext, MAX_SIGNERS}; @@ -34,8 +33,14 @@ use pallet_staking::{ Event as FrameStakingEvent, MaxNominationsOf, MaxValidatorsCount, Nominations, Pallet as FrameStaking, RewardDestination, ValidatorPrefs, }; +use rand::{rngs::StdRng, SeedableRng}; use sp_std::{vec, vec::Vec}; +use tdx_quote::SigningKey; +const MOCK_PCK_DERIVED_FROM_NULL_ARRAY: [u8; 33] = [ + 3, 237, 193, 27, 177, 204, 234, 67, 54, 141, 157, 13, 62, 87, 113, 224, 4, 121, 206, 251, 190, + 151, 134, 87, 68, 46, 37, 163, 127, 97, 252, 174, 108, +]; const NULL_ARR: [u8; 32] = [0; 32]; const SEED: u32 = 0; @@ -89,6 +94,7 @@ fn prepare_attestation_for_validate( threshold: T::AccountId, x25519_public_key: [u8; 32], endpoint: Vec, + quote_context: QuoteContext, ) -> (Vec, JoiningServerInfo) { let nonce = NULL_ARR; let quote = { @@ -105,20 +111,16 @@ fn prepare_attestation_for_validate( &threshold, x25519_public_key, nonce, - QuoteContext::Validate, + quote_context, ); - - tdx_quote::Quote::mock(attestation_key.clone(), pck, input_data.0).as_bytes().to_vec() + let pck_encoded = tdx_quote::encode_verifying_key(pck.verifying_key()).unwrap(); + tdx_quote::Quote::mock(attestation_key.clone(), pck, input_data.0, pck_encoded.to_vec()) + .as_bytes() + .to_vec() }; - let joining_server_info = JoiningServerInfo { - tss_account: threshold.clone(), - x25519_public_key, - endpoint, - // Since we are using the mock PckCertChainVerifier, this needs to be the same seed for - // generating the PCK as we used to sign the quote above - pck_certificate_chain: vec![NULL_ARR.to_vec()], - }; + let joining_server_info = + JoiningServerInfo { tss_account: threshold.clone(), x25519_public_key, endpoint }; // We need to tell the attestation handler that we want a quote. This will let the system to // know to expect one back when we call `validate()`. @@ -126,6 +128,11 @@ fn prepare_attestation_for_validate( (quote, joining_server_info) } +fn signing_key_from_seed(input: [u8; 32]) -> SigningKey { + let mut pck_seeder = StdRng::from_seed(input); + SigningKey::random(&mut pck_seeder) +} + fn prep_bond_and_validate( validate_also: bool, caller: T::AccountId, @@ -147,8 +154,12 @@ fn prep_bond_and_validate( if validate_also { let endpoint = b"http://localhost:3001".to_vec(); - let (quote, joining_server_info) = - prepare_attestation_for_validate::(threshold, x25519_public_key, endpoint); + let (quote, joining_server_info) = prepare_attestation_for_validate::( + threshold, + x25519_public_key, + endpoint, + QuoteContext::Validate, + ); assert_ok!(>::validate( RawOrigin::Signed(bonder.clone()).into(), @@ -198,6 +209,7 @@ benchmarks! { threshold, x25519_public_key, endpoint.clone().to_vec(), + QuoteContext::ChangeEndpoint, ) .0; }: _(RawOrigin::Signed(bonder.clone()), endpoint.to_vec(), quote) @@ -239,17 +251,15 @@ benchmarks! { new_threshold.clone(), x25519_public_key, endpoint.clone().to_vec(), + QuoteContext::ChangeThresholdAccounts, ); - let pck_certificate_chain = joining_server_info.pck_certificate_chain; - let signers = vec![validator_id_signers.clone(); s as usize]; Signers::::put(signers.clone()); }: _( RawOrigin::Signed(_bonder.clone()), new_threshold.clone(), x25519_public_key.clone(), - pck_certificate_chain, quote ) verify { @@ -388,7 +398,7 @@ benchmarks! { ); let (quote, joining_server_info) = - prepare_attestation_for_validate::(threshold_account.clone(), x25519_public_key, endpoint.clone()); + prepare_attestation_for_validate::(threshold_account.clone(), x25519_public_key, endpoint.clone(), QuoteContext::Validate); }: _(RawOrigin::Signed(bonder.clone()), ValidatorPrefs::default(), joining_server_info, quote) verify { assert_last_event::( diff --git a/pallets/staking/src/lib.rs b/pallets/staking/src/lib.rs index b4961cabe..5181e3307 100644 --- a/pallets/staking/src/lib.rs +++ b/pallets/staking/src/lib.rs @@ -41,8 +41,6 @@ use serde::{Deserialize, Serialize}; pub use crate::weights::WeightInfo; -pub mod pck; - #[cfg(test)] mod mock; @@ -60,7 +58,7 @@ use sp_staking::SessionIndex; #[frame_support::pallet] pub mod pallet { use entropy_shared::{ - QuoteContext, ValidatorInfo, X25519PublicKey, MAX_SIGNERS, + QuoteContext, ValidatorInfo, VerifyQuoteError, X25519PublicKey, MAX_SIGNERS, PREGENERATED_NETWORK_VERIFYING_KEY, TEST_RESHARE_BLOCK_NUMBER, VERIFICATION_KEY_LENGTH, }; use frame_support::{ @@ -70,7 +68,6 @@ pub mod pallet { DefaultNoBound, }; use frame_system::pallet_prelude::*; - use pck::PckCertChainVerifier; use rand_chacha::{ rand_core::{RngCore, SeedableRng}, ChaCha20Rng, ChaChaRng, @@ -98,9 +95,6 @@ pub mod pallet { /// The weight information of this pallet. type WeightInfo: WeightInfo; - /// A type that verifies a provisioning certification key (PCK) certificate chain. - type PckCertChainVerifier: PckCertChainVerifier; - /// Something that provides randomness in the runtime. type Randomness: Randomness>; @@ -133,14 +127,13 @@ pub mod pallet { } /// Information about a threshold server in the process of joining - /// This becomes a [ServerInfo] when the Pck certificate chain has been validated + /// This becomes a [ServerInfo] when an attestation has been verified #[derive(Encode, Decode, Clone, Eq, PartialEq, RuntimeDebug, TypeInfo)] #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] pub struct JoiningServerInfo { pub tss_account: AccountId, pub x25519_public_key: X25519PublicKey, pub endpoint: TssServerURL, - pub pck_certificate_chain: Vec>, } /// Info that is requiered to do a proactive refresh @@ -347,20 +340,48 @@ pub mod pallet { NoUnnominatingWhenSigner, NoUnnominatingWhenNextSigner, NoChangingThresholdAccountWhenSigner, + /// Quote could not be parsed or verified + BadQuote, + /// Attestation extrinsic submitted when not requested + UnexpectedAttestation, + /// Hashed input data does not match what was expected + IncorrectInputData, + /// Unacceptable VM image running + BadMrtdValue, + /// Cannot encode verifying key (PCK) + CannotEncodeVerifyingKey, + /// Cannot decode verifying key (PCK) + CannotDecodeVerifyingKey, + /// PCK certificate chain cannot be parsed PckCertificateParse, + /// PCK certificate chain cannot be verified PckCertificateVerify, + /// PCK certificate chain public key is not well formed PckCertificateBadPublicKey, + /// Pck certificate could not be extracted from quote PckCertificateNoCertificate, - FailedAttestationCheck, } - impl From for Error { - fn from(error: pck::PckParseVerifyError) -> Self { + impl From for Error { + /// As there are many reasons why quote verification can fail we want these error types to + /// be reflected in the dispatch errors from extrinsics in this pallet which do quote + /// verification + fn from(error: VerifyQuoteError) -> Self { match error { - pck::PckParseVerifyError::Parse => Error::::PckCertificateParse, - pck::PckParseVerifyError::Verify => Error::::PckCertificateVerify, - pck::PckParseVerifyError::BadPublicKey => Error::::PckCertificateBadPublicKey, - pck::PckParseVerifyError::NoCertificate => Error::::PckCertificateNoCertificate, + VerifyQuoteError::BadQuote => Error::::BadQuote, + VerifyQuoteError::UnexpectedAttestation => Error::::UnexpectedAttestation, + VerifyQuoteError::IncorrectInputData => Error::::IncorrectInputData, + VerifyQuoteError::BadMrtdValue => Error::::BadMrtdValue, + VerifyQuoteError::CannotEncodeVerifyingKey => Error::::CannotEncodeVerifyingKey, + VerifyQuoteError::PckCertificateParse => Error::::PckCertificateParse, + VerifyQuoteError::PckCertificateVerify => Error::::PckCertificateVerify, + VerifyQuoteError::PckCertificateBadPublicKey => { + Error::::PckCertificateBadPublicKey + }, + VerifyQuoteError::PckCertificateNoCertificate => { + Error::::PckCertificateNoCertificate + }, + VerifyQuoteError::CannotDecodeVerifyingKey => Error::::CannotDecodeVerifyingKey, } } } @@ -430,17 +451,12 @@ pub mod pallet { if let Some(server_info) = maybe_server_info { // Before we modify the `server_info`, we want to check that the validator is // still running TDX hardware. - ensure!( - >::verify_quote( - &server_info.tss_account.clone(), - server_info.x25519_public_key, - server_info.provisioning_certification_key.clone(), - quote, - QuoteContext::ChangeEndpoint, - ) - .is_ok(), - Error::::FailedAttestationCheck - ); + >::verify_quote( + &server_info.tss_account.clone(), + server_info.x25519_public_key, + quote, + QuoteContext::ChangeEndpoint, + )?; server_info.endpoint.clone_from(&endpoint); @@ -473,7 +489,6 @@ pub mod pallet { origin: OriginFor, tss_account: T::AccountId, x25519_public_key: X25519PublicKey, - pck_certificate_chain: Vec>, quote: Vec, ) -> DispatchResultWithPostInfo { let who = ensure_signed(origin)?; @@ -493,30 +508,19 @@ pub mod pallet { Error::::NoChangingThresholdAccountWhenSigner ); - let provisioning_certification_key = - T::PckCertChainVerifier::verify_pck_certificate_chain(pck_certificate_chain) - .map_err(|error| { - let e: Error = error.into(); - e - })?; - let new_server_info: ServerInfo = ThresholdServers::::try_mutate( &validator_id, |maybe_server_info| { if let Some(server_info) = maybe_server_info { // Before we modify the `server_info`, we want to check that the validator is // still running TDX hardware. - ensure!( + let provisioning_certification_key = >::verify_quote( &tss_account.clone(), x25519_public_key, - provisioning_certification_key.clone(), quote, QuoteContext::ChangeThresholdAccounts, - ) - .is_ok(), - Error::::FailedAttestationCheck - ); + )?; server_info.tss_account = tss_account; server_info.x25519_public_key = x25519_public_key; @@ -635,42 +639,31 @@ pub mod pallet { ) -> DispatchResult { let who = ensure_signed(origin.clone())?; - let provisioning_certification_key = - T::PckCertChainVerifier::verify_pck_certificate_chain( - joining_server_info.pck_certificate_chain, - ) - .map_err(|error| { - let e: Error = error.into(); - e - })?; - - let server_info = ServerInfo:: { - tss_account: joining_server_info.tss_account, - x25519_public_key: joining_server_info.x25519_public_key, - endpoint: joining_server_info.endpoint, - provisioning_certification_key, - }; ensure!( - server_info.endpoint.len() as u32 <= T::MaxEndpointLength::get(), + joining_server_info.endpoint.len() as u32 <= T::MaxEndpointLength::get(), Error::::EndpointTooLong ); ensure!( - !ThresholdToStash::::contains_key(&server_info.tss_account), + !ThresholdToStash::::contains_key(&joining_server_info.tss_account), Error::::TssAccountAlreadyExists ); - ensure!( + let provisioning_certification_key = >::verify_quote( - &server_info.tss_account.clone(), - server_info.x25519_public_key, - server_info.provisioning_certification_key.clone(), + &joining_server_info.tss_account.clone(), + joining_server_info.x25519_public_key, quote, QuoteContext::Validate, ) - .is_ok(), - Error::::FailedAttestationCheck - ); + .map_err(>>::into)?; + + let server_info = ServerInfo:: { + tss_account: joining_server_info.tss_account, + x25519_public_key: joining_server_info.x25519_public_key, + endpoint: joining_server_info.endpoint, + provisioning_certification_key, + }; pallet_staking::Pallet::::validate(origin, prefs)?; diff --git a/pallets/staking/src/mock.rs b/pallets/staking/src/mock.rs index 4c71b12b8..bd6faffef 100644 --- a/pallets/staking/src/mock.rs +++ b/pallets/staking/src/mock.rs @@ -38,7 +38,6 @@ use sp_staking::{EraIndex, SessionIndex}; use sp_std::vec; use crate as pallet_staking_extension; -use pallet_staking_extension::pck::MockPckCertChainVerifier; type Block = frame_system::mocking::MockBlock; type BlockNumber = u64; @@ -400,18 +399,18 @@ impl entropy_shared::AttestationHandler for MockAttestationHandler { fn verify_quote( _attestee: &AccountId, _x25519_public_key: entropy_shared::X25519PublicKey, - _provisioning_certification_key: entropy_shared::BoundedVecEncodedVerifyingKey, quote: Vec, _context: QuoteContext, - ) -> Result<(), sp_runtime::DispatchError> { + ) -> Result + { let quote: Result<[u8; 32], _> = quote.try_into(); match quote { - Ok(q) if q == VALID_QUOTE => Ok(()), - Ok(q) if q == INVALID_QUOTE => Err(sp_runtime::DispatchError::Other("Invalid quote")), + Ok(q) if q == VALID_QUOTE => Ok([0; 33].to_vec().try_into().unwrap()), + Ok(q) if q == INVALID_QUOTE => Err(entropy_shared::VerifyQuoteError::BadQuote), _ => { // We don't really want to verify quotes for tests in this pallet, so if we get // something else we'll just accept it. - Ok(()) + Ok(BoundedVec::new()) }, } } @@ -460,7 +459,6 @@ impl pallet_staking_extension::Config for Test { type AttestationHandler = MockAttestationHandler; type Currency = Balances; type MaxEndpointLength = MaxEndpointLength; - type PckCertChainVerifier = MockPckCertChainVerifier; type Randomness = TestPastRandomness; type RuntimeEvent = RuntimeEvent; type WeightInfo = (); diff --git a/pallets/staking/src/pck/Intel_SGX_Provisioning_Certification_RootCA.cer b/pallets/staking/src/pck/Intel_SGX_Provisioning_Certification_RootCA.cer deleted file mode 100644 index 768806c673709fb71537020abbabad2b1dc23773..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 659 zcmXqLV(K?&Vlr94%*4pVB%+kcb1iDFN$2P0EmMQ`2S{70v#l}UV&l+i^EhYA!pvll zVJKxF!NwfQ!ptM+nOBmUqY&&Kp%9dxU!vgb2$SQ2$vNj2733EsmSpDV8HyMPf%LKS za0e&ml_V-S=Oh*-8gd(OfM-Ch~kvMtrKjmv1bZ!Z(*nQf5%loS+O>FXz_7L{bCWhN(hi5#{Wtmy%j!APX{+k420HyOn$(X?{k= z|17|0XJi9Mo~$s4&tbp@q?i~PkrM{9Cxd|-lOn^@{ZBasf9gARD%>&aGSGW`QSWG5 z{WqmIDbv3SH3&@mV{?50sw!g B#yS81 diff --git a/pallets/staking/src/pck/mock.rs b/pallets/staking/src/pck/mock.rs deleted file mode 100644 index 5623cdd0d..000000000 --- a/pallets/staking/src/pck/mock.rs +++ /dev/null @@ -1,56 +0,0 @@ -// 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 . -use super::{CompressedVerifyingKey, PckCertChainVerifier, PckParseVerifyError}; -use p256::ecdsa::{SigningKey, VerifyingKey}; -use rand::{rngs::StdRng, SeedableRng}; -use sp_std::vec::Vec; - -/// This is used in the benchmarking tests to check that ServerInfo is as expected -pub const MOCK_PCK_DERIVED_FROM_NULL_ARRAY: [u8; 33] = [ - 3, 237, 193, 27, 177, 204, 234, 67, 54, 141, 157, 13, 62, 87, 113, 224, 4, 121, 206, 251, 190, - 151, 134, 87, 68, 46, 37, 163, 127, 97, 252, 174, 108, -]; - -/// A PCK certificate chain verifier for testing. -/// Rather than actually use test certificates, we give here the TSS account ID instead of the first -/// certificate, and derive a keypair from it. The same keypair will be derived when creating a mock -/// quote in entropy-tss -pub struct MockPckCertChainVerifier {} - -impl PckCertChainVerifier for MockPckCertChainVerifier { - fn verify_pck_certificate_chain( - pck_certificate_chain: Vec>, - ) -> Result { - let first_certificate = - pck_certificate_chain.first().ok_or(PckParseVerifyError::NoCertificate)?; - - // Read the certificate bytes as a TSS account id - let tss_account_id: [u8; 32] = - first_certificate.clone().try_into().map_err(|_| PckParseVerifyError::Parse)?; - - // Derive a keypair - let pck_secret = signing_key_from_seed(tss_account_id); - - // Convert/compress the public key - let pck_public = VerifyingKey::from(&pck_secret); - let pck_public = pck_public.to_encoded_point(true).as_bytes().to_vec(); - pck_public.try_into().map_err(|_| PckParseVerifyError::Parse) - } -} - -pub fn signing_key_from_seed(input: [u8; 32]) -> SigningKey { - let mut pck_seeder = StdRng::from_seed(input); - SigningKey::random(&mut pck_seeder) -} diff --git a/pallets/staking/src/pck/mod.rs b/pallets/staking/src/pck/mod.rs deleted file mode 100644 index 3694d988a..000000000 --- a/pallets/staking/src/pck/mod.rs +++ /dev/null @@ -1,58 +0,0 @@ -// 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 . -mod mock; -pub use mock::{signing_key_from_seed, MockPckCertChainVerifier, MOCK_PCK_DERIVED_FROM_NULL_ARRAY}; -mod production; -use super::VerifyingKey as CompressedVerifyingKey; -use core::array::TryFromSliceError; -use sp_std::vec::Vec; - -/// Provides a way of verifying a chain of certificates to give a chain of trust between the -/// provisioning certification key used to sign a TDX quote to the Intel route certificate authority -pub trait PckCertChainVerifier { - /// Verify an arbitrary chain of DER-encoded x509 certificates against Intel's root CA. - /// Typically this is two certificates, the PCK certificate and an intermediary provider - /// certificate - fn verify_pck_certificate_chain( - pck_certificate_chain: Vec>, - ) -> Result; -} - -/// An error when parsing or verifying a PCK or provider certificate -#[derive(Debug)] -pub enum PckParseVerifyError { - Parse, - Verify, - BadPublicKey, - NoCertificate, -} - -impl From for PckParseVerifyError { - fn from(_: spki::der::Error) -> PckParseVerifyError { - PckParseVerifyError::Parse - } -} - -impl From for PckParseVerifyError { - fn from(_: x509_verify::Error) -> PckParseVerifyError { - PckParseVerifyError::Verify - } -} - -impl From for PckParseVerifyError { - fn from(_: TryFromSliceError) -> PckParseVerifyError { - PckParseVerifyError::BadPublicKey - } -} diff --git a/pallets/staking/src/pck/production.rs b/pallets/staking/src/pck/production.rs deleted file mode 100644 index 7a0404123..000000000 --- a/pallets/staking/src/pck/production.rs +++ /dev/null @@ -1,110 +0,0 @@ -// 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 . -use sp_std::vec::Vec; -use x509_verify::{ - der::{Decode, Encode}, - x509_cert::Certificate, - Signature, VerifyInfo, VerifyingKey, -}; - -use super::{CompressedVerifyingKey, PckCertChainVerifier, PckParseVerifyError}; - -/// Intels root CA certificate in DER format available from here: -/// https://certificates.trustedservices.intel.com/Intel_SGX_Provisioning_Certification_RootCA.cer -/// Valid until December 31 2049 -const INTEL_ROOT_CA_DER: &[u8; 659] = - include_bytes!("Intel_SGX_Provisioning_Certification_RootCA.cer"); - -/// A PCK certificate chain verifier for use in production where entropy-tss is running on TDX -/// hardware and we have a PCK certificate chain -pub struct ProductionPckCertChainVerifier {} - -impl PckCertChainVerifier for ProductionPckCertChainVerifier { - fn verify_pck_certificate_chain( - pck_certificate_chain: Vec>, - ) -> Result { - let pck_uncompressed = verify_pck_cert_chain(pck_certificate_chain)?; - - // Compress / convert public key - let point = p256::EncodedPoint::from_bytes(pck_uncompressed) - .map_err(|_| PckParseVerifyError::BadPublicKey)?; - let pck_verifying_key = p256::ecdsa::VerifyingKey::from_encoded_point(&point) - .map_err(|_| PckParseVerifyError::BadPublicKey)?; - let pck_compressed = pck_verifying_key.to_encoded_point(true).as_bytes().to_vec(); - pck_compressed.try_into().map_err(|_| PckParseVerifyError::BadPublicKey) - } -} - -/// Validate PCK and provider certificates and if valid return the PCK -/// These certificates will be provided by a joining validator -fn verify_pck_cert_chain(certificates_der: Vec>) -> Result<[u8; 65], PckParseVerifyError> { - if certificates_der.is_empty() { - return Err(PckParseVerifyError::NoCertificate); - } - - // Parse the certificates - let mut certificates = Vec::new(); - for certificate in certificates_der { - certificates.push(Certificate::from_der(&certificate)?); - } - // Add the root certificate to the end of the chain. Since the root cert is self-signed, this - // will work regardless of whether the user has included this certicate in the chain or not - certificates.push(Certificate::from_der(INTEL_ROOT_CA_DER)?); - - // Verify the certificate chain - for i in 0..certificates.len() { - let verifying_key: &VerifyingKey = if i + 1 == certificates.len() { - &certificates[i].tbs_certificate.subject_public_key_info.clone().try_into()? - } else { - &certificates[i + 1].tbs_certificate.subject_public_key_info.clone().try_into()? - }; - verify_cert(&certificates[i], verifying_key)?; - } - - // Get the first certificate - let pck_key = &certificates - .first() - .ok_or(PckParseVerifyError::NoCertificate)? - .tbs_certificate - .subject_public_key_info - .subject_public_key; - - Ok(pck_key.as_bytes().ok_or(PckParseVerifyError::BadPublicKey)?.try_into()?) -} - -/// Given a cerificate and a public key, verify the certificate -fn verify_cert(subject: &Certificate, issuer_pk: &VerifyingKey) -> Result<(), PckParseVerifyError> { - let verify_info = VerifyInfo::new( - subject.tbs_certificate.to_der()?.into(), - Signature::new( - &subject.signature_algorithm, - subject.signature.as_bytes().ok_or(PckParseVerifyError::Parse)?, - ), - ); - Ok(issuer_pk.verify(&verify_info)?) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_verify_pck_cert_chain() { - let pck = include_bytes!("../../test_pck_certs/pck_cert.der").to_vec(); - let platform = include_bytes!("../../test_pck_certs/platform_pcs_cert.der").to_vec(); - assert!(ProductionPckCertChainVerifier::verify_pck_certificate_chain(vec![pck, platform]) - .is_ok()); - } -} diff --git a/pallets/staking/src/tests.rs b/pallets/staking/src/tests.rs index f2a957b85..54074d087 100644 --- a/pallets/staking/src/tests.rs +++ b/pallets/staking/src/tests.rs @@ -14,8 +14,8 @@ // along with this program. If not, see . use crate::{ - mock::*, pck::MOCK_PCK_DERIVED_FROM_NULL_ARRAY, tests::RuntimeEvent, Error, JoiningServerInfo, - NextSignerInfo, NextSigners, ServerInfo, Signers, + mock::*, tests::RuntimeEvent, Error, JoiningServerInfo, NextSignerInfo, NextSigners, + ServerInfo, Signers, }; use codec::Encode; use frame_support::{assert_noop, assert_ok}; @@ -61,12 +61,8 @@ fn it_takes_in_an_endpoint() { pallet_staking::RewardDestination::Account(1), )); - let joining_server_info = JoiningServerInfo { - tss_account: 3, - x25519_public_key: NULL_ARR, - endpoint: vec![20], - pck_certificate_chain: vec![[0u8; 32].to_vec()], - }; + let joining_server_info = + JoiningServerInfo { tss_account: 3, x25519_public_key: NULL_ARR, endpoint: vec![20] }; assert_ok!(Staking::validate( RuntimeOrigin::signed(1), pallet_staking::ValidatorPrefs::default(), @@ -83,7 +79,6 @@ fn it_takes_in_an_endpoint() { tss_account: 3, x25519_public_key: NULL_ARR, endpoint: [20; (crate::tests::MaxEndpointLength::get() + 1) as usize].to_vec(), - pck_certificate_chain: vec![[0u8; 32].to_vec()], }; assert_noop!( Staking::validate( @@ -99,7 +94,6 @@ fn it_takes_in_an_endpoint() { tss_account: 5, x25519_public_key: NULL_ARR, endpoint: vec![20, 20], - pck_certificate_chain: vec![[0u8; 32].to_vec()], }; assert_noop!( Staking::validate( @@ -122,12 +116,8 @@ fn it_will_not_allow_validator_to_use_existing_tss_account() { pallet_staking::RewardDestination::Account(1), )); - let joining_server_info = JoiningServerInfo { - tss_account: 3, - x25519_public_key: NULL_ARR, - endpoint: vec![20], - pck_certificate_chain: vec![[0u8; 32].to_vec()], - }; + let joining_server_info = + JoiningServerInfo { tss_account: 3, x25519_public_key: NULL_ARR, endpoint: vec![20] }; assert_ok!(Staking::validate( RuntimeOrigin::signed(1), pallet_staking::ValidatorPrefs::default(), @@ -168,7 +158,6 @@ fn it_changes_endpoint() { tss_account: 3, x25519_public_key: NULL_ARR, endpoint: endpoint.clone(), - pck_certificate_chain: vec![[0u8; 32].to_vec()], }; assert_ok!(Staking::validate( RuntimeOrigin::signed(1), @@ -206,7 +195,6 @@ fn it_doesnt_change_endpoint_with_invalid_quote() { tss_account: 3, x25519_public_key: NULL_ARR, endpoint: endpoint.clone(), - pck_certificate_chain: vec![[0u8; 32].to_vec()], }; assert_ok!(Staking::validate( @@ -218,7 +206,7 @@ fn it_doesnt_change_endpoint_with_invalid_quote() { assert_noop!( Staking::change_endpoint(RuntimeOrigin::signed(1), endpoint, INVALID_QUOTE.to_vec()), - Error::::FailedAttestationCheck + Error::::BadQuote ); }) } @@ -232,13 +220,8 @@ fn it_changes_threshold_account() { pallet_staking::RewardDestination::Account(1), )); - let pck_certificate_chain = vec![vec![0u8; 32]]; - let joining_server_info = JoiningServerInfo { - tss_account: 3, - x25519_public_key: NULL_ARR, - endpoint: vec![20], - pck_certificate_chain: pck_certificate_chain.clone(), - }; + let joining_server_info = + JoiningServerInfo { tss_account: 3, x25519_public_key: NULL_ARR, endpoint: vec![20] }; assert_ok!(Staking::validate( RuntimeOrigin::signed(1), pallet_staking::ValidatorPrefs::default(), @@ -250,7 +233,6 @@ fn it_changes_threshold_account() { RuntimeOrigin::signed(1), 4, NULL_ARR, - pck_certificate_chain.clone(), VALID_QUOTE.to_vec() )); assert_eq!(Staking::threshold_server(1).unwrap().tss_account, 4); @@ -261,7 +243,6 @@ fn it_changes_threshold_account() { RuntimeOrigin::signed(4), 5, NULL_ARR, - pck_certificate_chain.clone(), VALID_QUOTE.to_vec() ), Error::::NotController @@ -274,12 +255,8 @@ fn it_changes_threshold_account() { pallet_staking::RewardDestination::Account(2), )); - let joining_server_info = JoiningServerInfo { - tss_account: 5, - x25519_public_key: NULL_ARR, - endpoint: vec![20], - pck_certificate_chain: pck_certificate_chain.clone(), - }; + let joining_server_info = + JoiningServerInfo { tss_account: 5, x25519_public_key: NULL_ARR, endpoint: vec![20] }; assert_ok!(Staking::validate( RuntimeOrigin::signed(2), pallet_staking::ValidatorPrefs::default(), @@ -292,7 +269,6 @@ fn it_changes_threshold_account() { RuntimeOrigin::signed(1), 5, NULL_ARR, - pck_certificate_chain.clone(), VALID_QUOTE.to_vec() ), Error::::TssAccountAlreadyExists @@ -304,7 +280,6 @@ fn it_changes_threshold_account() { RuntimeOrigin::signed(1), 9, NULL_ARR, - pck_certificate_chain.clone(), VALID_QUOTE.to_vec() ), Error::::NoChangingThresholdAccountWhenSigner @@ -321,13 +296,8 @@ fn it_doesnt_allow_changing_threshold_account_with_invalid_quote() { pallet_staking::RewardDestination::Account(1), )); - let pck_certificate_chain = vec![[0u8; 32].to_vec()]; - let joining_server_info = JoiningServerInfo { - tss_account: 3, - x25519_public_key: NULL_ARR, - endpoint: vec![20], - pck_certificate_chain: pck_certificate_chain.clone(), - }; + let joining_server_info = + JoiningServerInfo { tss_account: 3, x25519_public_key: NULL_ARR, endpoint: vec![20] }; assert_ok!(Staking::validate( RuntimeOrigin::signed(1), pallet_staking::ValidatorPrefs::default(), @@ -340,10 +310,9 @@ fn it_doesnt_allow_changing_threshold_account_with_invalid_quote() { RuntimeOrigin::signed(1), 4, NULL_ARR, - pck_certificate_chain.clone(), INVALID_QUOTE.to_vec() ), - Error::::FailedAttestationCheck + Error::::BadQuote ); }) } @@ -357,13 +326,8 @@ fn it_will_not_allow_existing_tss_account_when_changing_threshold_account() { pallet_staking::RewardDestination::Account(1), )); - let pck_certificate_chain = vec![[0u8; 32].to_vec()]; - let joining_server_info = JoiningServerInfo { - tss_account: 3, - x25519_public_key: NULL_ARR, - endpoint: vec![20], - pck_certificate_chain: pck_certificate_chain.clone(), - }; + let joining_server_info = + JoiningServerInfo { tss_account: 3, x25519_public_key: NULL_ARR, endpoint: vec![20] }; assert_ok!(Staking::validate( RuntimeOrigin::signed(1), pallet_staking::ValidatorPrefs::default(), @@ -378,12 +342,8 @@ fn it_will_not_allow_existing_tss_account_when_changing_threshold_account() { pallet_staking::RewardDestination::Account(2), )); - let joining_server_info = JoiningServerInfo { - tss_account: 5, - x25519_public_key: NULL_ARR, - endpoint: vec![20], - pck_certificate_chain: pck_certificate_chain.clone(), - }; + let joining_server_info = + JoiningServerInfo { tss_account: 5, x25519_public_key: NULL_ARR, endpoint: vec![20] }; assert_ok!(Staking::validate( RuntimeOrigin::signed(2), pallet_staking::ValidatorPrefs::default(), @@ -396,7 +356,6 @@ fn it_will_not_allow_existing_tss_account_when_changing_threshold_account() { RuntimeOrigin::signed(1), 5, NULL_ARR, - pck_certificate_chain.clone(), VALID_QUOTE.to_vec() ), Error::::TssAccountAlreadyExists @@ -416,12 +375,8 @@ fn it_deletes_when_no_bond_left() { pallet_staking::RewardDestination::Account(1), )); - let joining_server_info = JoiningServerInfo { - tss_account: 3, - x25519_public_key: NULL_ARR, - endpoint: vec![20], - pck_certificate_chain: vec![[0u8; 32].to_vec()], - }; + let joining_server_info = + JoiningServerInfo { tss_account: 3, x25519_public_key: NULL_ARR, endpoint: vec![20] }; assert_ok!(Staking::validate( RuntimeOrigin::signed(2), pallet_staking::ValidatorPrefs::default(), @@ -702,12 +657,8 @@ fn it_requires_attestation_before_validate_is_succesful() { pallet_staking::RewardDestination::Account(alice), )); - let joining_server_info = JoiningServerInfo { - tss_account: bob, - x25519_public_key: NULL_ARR, - endpoint: vec![20], - pck_certificate_chain: vec![[0u8; 32].to_vec()], - }; + let joining_server_info = + JoiningServerInfo { tss_account: bob, x25519_public_key: NULL_ARR, endpoint: vec![20] }; // First we test that an invalid attestation doesn't allow us to submit our candidacy. assert_noop!( @@ -717,7 +668,7 @@ fn it_requires_attestation_before_validate_is_succesful() { joining_server_info.clone(), INVALID_QUOTE.to_vec(), ), - Error::::FailedAttestationCheck + Error::::BadQuote ); assert_eq!(Staking::threshold_server(bob), None); @@ -735,10 +686,7 @@ fn it_requires_attestation_before_validate_is_succesful() { tss_account: joining_server_info.tss_account, x25519_public_key: joining_server_info.x25519_public_key, endpoint: joining_server_info.endpoint, - provisioning_certification_key: MOCK_PCK_DERIVED_FROM_NULL_ARRAY - .to_vec() - .try_into() - .unwrap(), + provisioning_certification_key: [0; 33].to_vec().try_into().unwrap(), }; assert_eq!(Staking::threshold_to_stash(bob), Some(alice)); assert_eq!(Staking::threshold_server(alice), Some(server_info)); diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index 3cdcdb26b..ec9553a2e 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -273,3 +273,6 @@ try-runtime=[ "pallet-utility/try-runtime", "pallet-vesting/try-runtime", ] +# Enables real PCK certificate chain verification - which means TSS nodes must be runnning on TDX +# hardware +production=["pallet-attestation/production"] diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 461a4d49d..874ee062b 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -718,7 +718,6 @@ impl pallet_staking_extension::Config for Runtime { type AttestationHandler = Attestation; type Currency = Balances; type MaxEndpointLength = MaxEndpointLength; - type PckCertChainVerifier = pallet_staking_extension::pck::MockPckCertChainVerifier; type Randomness = pallet_babe::RandomnessFromOneEpochAgo; type RuntimeEvent = RuntimeEvent; type WeightInfo = weights::pallet_staking_extension::WeightInfo;