diff --git a/CHANGELOG.md b/CHANGELOG.md index b9fca2f70..152aff91b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,8 @@ 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 - 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 088e0b920..298582681 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1301,9 +1301,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.22" +version = "4.5.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69371e34337c4c984bbe322360c2547210bf632eb2814bbe78a6e87a2935bd2b" +checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84" dependencies = [ "clap_builder", "clap_derive", @@ -1311,9 +1311,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.22" +version = "4.5.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e24c1b4099818523236a8ca881d2b45db98dadfb4625cf6608c12069fcbbde1" +checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838" dependencies = [ "anstream", "anstyle", @@ -1336,9 +1336,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.0" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "codespan-reporting" @@ -2547,7 +2547,7 @@ dependencies = [ "subxt", "synedrion", "tdx-quote", - "thiserror 2.0.4", + "thiserror 2.0.6", "tokio", "tracing", "x25519-dalek 2.0.1", @@ -2582,7 +2582,7 @@ dependencies = [ "sled", "sp-core 31.0.0", "synedrion", - "thiserror 2.0.4", + "thiserror 2.0.6", "tokio", "tracing", "zeroize", @@ -2642,7 +2642,7 @@ dependencies = [ "sp-keyring 34.0.0", "subxt", "synedrion", - "thiserror 2.0.4", + "thiserror 2.0.6", "tokio", "tokio-tungstenite", "tracing", @@ -2858,7 +2858,7 @@ dependencies = [ "subxt-signer", "synedrion", "tdx-quote", - "thiserror 2.0.4", + "thiserror 2.0.6", "tokio", "tokio-tungstenite", "tower-http 0.6.2", @@ -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", @@ -14334,11 +14334,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.4" +version = "2.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f49a1853cf82743e3b7950f77e0f4d622ca36cf4317cba00c767838bac8d490" +checksum = "8fec2a1820ebd077e2b90c4df007bebf344cd394098a13c563957d0afc83ea47" dependencies = [ - "thiserror-impl 2.0.4", + "thiserror-impl 2.0.6", ] [[package]] @@ -14354,9 +14354,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.4" +version = "2.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8381894bb3efe0c4acac3ded651301ceee58a15d47c2e34885ed1908ad667061" +checksum = "d65750cab40f4ff1929fb1ba509e9914eb756131cef4210da8d5d700d26f6312" dependencies = [ "proc-macro2", "quote", diff --git a/crates/client/Cargo.toml b/crates/client/Cargo.toml index 4d8ea2e9f..66a2244a8 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.4" +thiserror ="2.0.6" futures ="0.3" sp-core ={ version="31.0.0", default-features=false, features=["full_crypto", "serde"] } tracing ="0.1.41" 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/kvdb/Cargo.toml b/crates/kvdb/Cargo.toml index 6c9f3702d..2d8a7d22d 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.4" +thiserror="2.0.6" hex ="0.4.3" # Substrate diff --git a/crates/protocol/Cargo.toml b/crates/protocol/Cargo.toml index 09ea23155..c6b542101 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.4" +thiserror ="2.0.6" snow ="0.9.6" getrandom ={ version="0.2", features=["js"] } rand_core ={ version="0.6.4", features=["getrandom"] } 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/Cargo.toml b/crates/test-cli/Cargo.toml index d4b45ae44..23c8c67b8 100644 --- a/crates/test-cli/Cargo.toml +++ b/crates/test-cli/Cargo.toml @@ -10,7 +10,7 @@ edition ='2021' [dependencies] entropy-client={ version="0.3.0", path="../client" } -clap ={ version="4.5.22", features=["derive"] } +clap ={ version="4.5.23", features=["derive"] } colored ="2.0.4" subxt ="0.35.3" sp-core ="31.0.0" @@ -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/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, + } + } +} diff --git a/crates/testing-utils/Cargo.toml b/crates/testing-utils/Cargo.toml index 510d81127..65c6fa263 100644 --- a/crates/testing-utils/Cargo.toml +++ b/crates/testing-utils/Cargo.toml @@ -13,7 +13,7 @@ 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.7.0" +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"] } diff --git a/crates/threshold-signature-server/Cargo.toml b/crates/threshold-signature-server/Cargo.toml index 7db76ca66..e1a299245 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.4" +thiserror ="2.0.6" anyhow ="1.0.94" blake2 ="0.10.4" x25519-dalek ={ version="2.0.1", features=["static_secrets"] } @@ -36,7 +36,7 @@ axum ={ version="0.7.9", features=["ws"] } # Substrate subxt ="0.35.3" -parity-scale-codec="3.7.0" +parity-scale-codec="3.6.12" sp-core ={ version="31.0.0", default-features=false } # Entropy @@ -63,7 +63,7 @@ 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.22", features=["derive"] } +clap ={ version="4.5.23", features=["derive"] } num ="0.4.3" snow ="0.9.6" sha3 ="0.10.8" diff --git a/node/cli/Cargo.toml b/node/cli/Cargo.toml index b00f1ecbc..b1cf2d0cb 100644 --- a/node/cli/Cargo.toml +++ b/node/cli/Cargo.toml @@ -19,7 +19,7 @@ name='entropy' [dependencies] # Third-party dependencies -clap ={ version="4.5.22", features=["derive"], optional=true } +clap ={ version="4.5.23", features=["derive"], optional=true } codec ={ package="parity-scale-codec", version="3.0.0" } futures ="0.3.31" hex-literal ="0.4.1" @@ -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 @@ -99,7 +99,7 @@ pallet-staking-extension={ version="0.3.0", path="../../pallets/staking" } project-root="0.2.2" [build-dependencies] -clap={ version="4.5.22", optional=true } +clap={ version="4.5.23", optional=true } pallet-balances ={ version="29.0.0" } substrate-build-script-utils={ version="11.0.0" } 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))?) }, 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 }