diff --git a/CHANGELOG.md b/CHANGELOG.md index 95388bab7..c448f25a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,18 +24,28 @@ 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 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` + 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)) - 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)) @@ -46,6 +56,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 c3402db70..0a7bf8e21 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -675,7 +675,7 @@ dependencies = [ "sha1", "sync_wrapper 1.0.1", "tokio", - "tokio-tungstenite", + "tokio-tungstenite 0.24.0", "tower 0.5.1", "tower-layer", "tower-service", @@ -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,9 +2642,9 @@ dependencies = [ "sp-keyring 34.0.0", "subxt", "synedrion", - "thiserror 2.0.7", + "thiserror 2.0.8", "tokio", - "tokio-tungstenite", + "tokio-tungstenite 0.26.0", "tracing", "wasm-bindgen", "wasm-bindgen-derive", @@ -2859,9 +2859,9 @@ dependencies = [ "subxt-signer", "synedrion", "tdx-quote", - "thiserror 2.0.7", + "thiserror 2.0.8", "tokio", - "tokio-tungstenite", + "tokio-tungstenite 0.26.0", "tower-http 0.6.2", "tracing", "tracing-bunyan-formatter", @@ -6769,6 +6769,7 @@ dependencies = [ "pallet-balances", "pallet-parameters", "pallet-session", + "pallet-slashing", "pallet-staking", "pallet-staking-extension", "pallet-staking-reward-curve", @@ -7288,6 +7289,7 @@ dependencies = [ "pallet-programs", "pallet-registry", "pallet-session", + "pallet-slashing", "pallet-staking", "pallet-staking-extension", "pallet-staking-reward-curve", @@ -7354,6 +7356,7 @@ dependencies = [ "pallet-parameters", "pallet-programs", "pallet-session", + "pallet-slashing", "pallet-staking", "pallet-staking-extension", "pallet-staking-reward-curve", @@ -7490,11 +7493,11 @@ dependencies = [ "frame-support 29.0.2", "frame-system", "log", - "p256", "pallet-bags-list", "pallet-balances", "pallet-parameters", "pallet-session", + "pallet-slashing", "pallet-staking", "pallet-staking-reward-curve", "pallet-timestamp", @@ -7511,9 +7514,7 @@ dependencies = [ "sp-runtime 32.0.0", "sp-staking 27.0.0", "sp-std 14.0.0", - "spki", "tdx-quote", - "x509-verify", ] [[package]] @@ -7913,6 +7914,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" @@ -8755,7 +8765,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", @@ -14278,12 +14288,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]] @@ -14335,11 +14347,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]] @@ -14355,9 +14367,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", @@ -14559,7 +14571,19 @@ dependencies = [ "futures-util", "log", "tokio", - "tungstenite", + "tungstenite 0.24.0", +] + +[[package]] +name = "tokio-tungstenite" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c14b3e8ebea4eb2577de77903e6c008d9ac80b5aae1f9ae781c5229ae935a44" +dependencies = [ + "futures-util", + "log", + "tokio", + "tungstenite 0.26.0", ] [[package]] @@ -15023,6 +15047,24 @@ dependencies = [ "utf-8", ] +[[package]] +name = "tungstenite" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4ab519cc9c1e57e6cab1087f262f9fc978a4e9d5f943b0e029567521d3525cb" +dependencies = [ + "byteorder", + "bytes", + "data-encoding", + "http 1.1.0", + "httparse", + "log", + "rand", + "sha1", + "thiserror 2.0.8", + "utf-8", +] + [[package]] name = "twox-hash" version = "1.6.3" diff --git a/crates/client/Cargo.toml b/crates/client/Cargo.toml index 6bf8292f3..0161ad229 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" @@ -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 7ffe949d7..592a37dde 100644 Binary files a/crates/client/entropy_metadata.scale and b/crates/client/entropy_metadata.scale differ diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index 71a5c240f..be7ec88e1 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -372,7 +372,6 @@ pub async fn get_quote_and_change_threshold_accounts( 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, @@ -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/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..8f246a69b 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"] } @@ -35,7 +35,7 @@ num ="0.4.3" # Used only with the `server` feature to implement the WsConnection trait axum ={ version="0.7.9", features=["ws"], optional=true } -tokio-tungstenite={ version="0.24.0", optional=true } +tokio-tungstenite={ version="0.26.0", optional=true } # Used only with the `wasm` feature gloo-net ={ version="0.6.0", default-features=false, features=["websocket"], optional=true } diff --git a/crates/protocol/src/protocol_transport/mod.rs b/crates/protocol/src/protocol_transport/mod.rs index 4fff145dd..8b86d5663 100644 --- a/crates/protocol/src/protocol_transport/mod.rs +++ b/crates/protocol/src/protocol_transport/mod.rs @@ -106,14 +106,14 @@ impl WsConnection for tokio_tungstenite::WebSocketStream) -> Result<(), WsError> { - SinkExt::send(&mut self, tungstenite::Message::Binary(msg)) + SinkExt::send(&mut self, tungstenite::Message::Binary(msg.into())) .await .map_err(|_| WsError::ConnectionClosed) } @@ -130,14 +130,14 @@ impl WsConnection for tokio_tungstenite::WebSocketStream .ok_or(WsError::ConnectionClosed)? .map_err(|e| WsError::ConnectionError(e.to_string()))? { - Ok(msg) + Ok(msg.to_vec()) } else { Err(WsError::UnexpectedMessageType) } } async fn send(&mut self, msg: Vec) -> Result<(), WsError> { - SinkExt::send(&mut self, tungstenite::Message::Binary(msg)) + SinkExt::send(&mut self, tungstenite::Message::Binary(msg.into())) .await .map_err(|_| WsError::ConnectionClosed) } diff --git a/crates/shared/src/constants.rs b/crates/shared/src/constants.rs index ea5badf27..7b8ec635a 100644 --- a/crates/shared/src/constants.rs +++ b/crates/shared/src/constants.rs @@ -74,8 +74,5 @@ pub const MAX_SIGNERS: u8 = 15; /// Threshold for those signers pub const SIGNER_THRESHOLD: u8 = 2; -/// For testing to line up chain mock data and reshare_test -pub const TEST_RESHARE_BLOCK_NUMBER: u32 = 10; - /// Program version number, must be incremented if version number changes pub const PROGRAM_VERSION_NUMBER: u8 = 0; diff --git a/crates/shared/src/types.rs b/crates/shared/src/types.rs index 2de1b647c..7bada801c 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,37 @@ 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(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 9220046ba..853cb6d09 100644 --- a/crates/test-cli/src/lib.rs +++ b/crates/test-cli/src/lib.rs @@ -163,8 +163,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, @@ -497,7 +495,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)?; @@ -507,15 +504,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 2356aae20..f641a0043 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"] } @@ -38,6 +38,7 @@ axum ={ version="0.7.9", features=["ws"] } subxt ="0.35.3" parity-scale-codec="3.6.12" sp-core ={ version="31.0.0", default-features=false } +sp-keyring ="34.0.0" # Entropy entropy-shared ={ version="0.3.0", path="../shared" } @@ -57,37 +58,40 @@ tracing-bunyan-formatter="0.3.10" 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 } +tokio-tungstenite="0.26.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={ 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" 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/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/helpers/tests.rs b/crates/threshold-signature-server/src/helpers/tests.rs index 029ddcf4c..97679c9aa 100644 --- a/crates/threshold-signature-server/src/helpers/tests.rs +++ b/crates/threshold-signature-server/src/helpers/tests.rs @@ -25,6 +25,7 @@ use crate::{ chain_api::{ entropy::{ self, + runtime_types::entropy_runtime::RuntimeCall, runtime_types::pallet_staking_extension::pallet::{JumpStartStatus, ServerInfo}, }, EntropyConfig, @@ -48,6 +49,7 @@ use entropy_protocol::PartyId; #[cfg(test)] use entropy_shared::EncodedVerifyingKey; use entropy_shared::NETWORK_PARENT_KEY; +use sp_keyring::AccountKeyring; use std::{fmt, net::SocketAddr, str, time::Duration}; use subxt::{ backend::legacy::LegacyRpcMethods, ext::sp_core::sr25519, tx::PairSigner, @@ -370,3 +372,18 @@ pub fn get_port(server_info: &ServerInfo) -> u32 { str::from_utf8(&server_info.endpoint).unwrap().parse().unwrap(); socket_address.port().into() } + +/// Calls set storage from sudo for testing, allows use to manipulate chain storage. +pub async fn call_set_storage( + api: &OnlineClient, + rpc: &LegacyRpcMethods, + call: RuntimeCall, +) { + let set_storage = entropy::tx().sudo().sudo(call); + let alice = AccountKeyring::Alice; + + let signature_request_pair_signer = + PairSigner::::new(alice.into()); + + submit_transaction(api, rpc, &signature_request_pair_signer, &set_storage, None).await.unwrap(); +} 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(); +} diff --git a/crates/threshold-signature-server/src/validator/api.rs b/crates/threshold-signature-server/src/validator/api.rs index 179342402..074629da9 100644 --- a/crates/threshold-signature-server/src/validator/api.rs +++ b/crates/threshold-signature-server/src/validator/api.rs @@ -295,7 +295,7 @@ pub async fn validate_new_reshare( .await? .ok_or_else(|| ValidatorErr::ChainFetch("Not Currently in a reshare"))?; - if chain_data.block_number != reshare_data.block_number.saturating_sub(1) + if chain_data.block_number != reshare_data.block_number || chain_data.new_signers != reshare_data.new_signers { return Err(ValidatorErr::InvalidData); diff --git a/crates/threshold-signature-server/src/validator/tests.rs b/crates/threshold-signature-server/src/validator/tests.rs index faf73f645..8d9489b37 100644 --- a/crates/threshold-signature-server/src/validator/tests.rs +++ b/crates/threshold-signature-server/src/validator/tests.rs @@ -17,8 +17,8 @@ use crate::{ helpers::{ launch::{FORBIDDEN_KEYS, LATEST_BLOCK_NUMBER_RESHARE}, tests::{ - get_port, initialize_test_logger, run_to_block, setup_client, spawn_testing_validators, - unsafe_get, + call_set_storage, get_port, initialize_test_logger, run_to_block, setup_client, + spawn_testing_validators, unsafe_get, }, }, validator::{ @@ -29,8 +29,13 @@ use crate::{ use entropy_client::{self as test_client}; use entropy_client::{ chain_api::{ - entropy, entropy::runtime_types::bounded_collections::bounded_vec::BoundedVec, - entropy::runtime_types::pallet_registry::pallet::ProgramInstance, get_api, get_rpc, + entropy, + entropy::runtime_types::bounded_collections::bounded_vec::BoundedVec, + entropy::runtime_types::entropy_runtime::RuntimeCall, + entropy::runtime_types::frame_system::pallet::Call as SystemsCall, + entropy::runtime_types::pallet_registry::pallet::ProgramInstance, + entropy::runtime_types::pallet_staking_extension::pallet::{NextSignerInfo, ReshareInfo}, + get_api, get_rpc, }, substrate::query_chain, Hasher, @@ -69,7 +74,8 @@ async fn test_reshare_basic() { .await; let api = get_api(&context[0].ws_url).await.unwrap(); let rpc = get_rpc(&context[0].ws_url).await.unwrap(); - + let alice_stash = AccountKeyring::AliceStash; + let dave_stash = AccountKeyring::DaveStash; let client = reqwest::Client::new(); // Get current signers @@ -78,7 +84,9 @@ async fn test_reshare_basic() { let old_signer_ids: HashSet<[u8; 32]> = HashSet::from_iter(signer_stash_accounts.clone().into_iter().map(|id| id.0)); let mut signers = Vec::new(); + let mut next_signers = vec![]; for signer in signer_stash_accounts.iter() { + next_signers.push(signer); let query = entropy::storage().staking_extension().threshold_servers(signer); let server_info = query_chain(&api, &rpc, query, None).await.unwrap().unwrap(); signers.push(server_info); @@ -89,6 +97,29 @@ async fn test_reshare_basic() { let key_share = unsafe_get(&client, hex::encode(NETWORK_PARENT_KEY), port).await; assert!(!key_share.is_empty()); } + next_signers.remove(0); + let binding = dave_stash.to_account_id().into(); + next_signers.push(&binding); + let block_number = rpc.chain_get_header(None).await.unwrap().unwrap().number + 1; + let storage_address_next_signers = entropy::storage().staking_extension().next_signers(); + let value_next_signers = + NextSignerInfo { confirmations: vec![], next_signers: next_signers.clone() }; + // Add reshare + let call = RuntimeCall::System(SystemsCall::set_storage { + items: vec![(storage_address_next_signers.to_root_bytes(), value_next_signers.encode())], + }); + call_set_storage(&api, &rpc, call).await; + + let storage_address_reshare_data = entropy::storage().staking_extension().reshare_data(); + let value_reshare_info = + ReshareInfo { block_number, new_signers: vec![dave_stash.public().encode()] }; + // Add reshare + let call = RuntimeCall::System(SystemsCall::set_storage { + items: vec![(storage_address_reshare_data.to_root_bytes(), value_reshare_info.encode())], + }); + call_set_storage(&api, &rpc, call).await; + + let key_share_before = unsafe_get(&client, hex::encode(NETWORK_PARENT_KEY), 3002).await; let mut i = 0; // Wait up to 2min for reshare to complete: check once every second if we have a new set of signers. @@ -101,7 +132,7 @@ async fn test_reshare_basic() { if new_signer_ids != old_signer_ids { break Ok(new_signer_ids); } - if i > 120 { + if i > 240 { break Err("Timed out waiting for reshare"); } i += 1; @@ -109,6 +140,11 @@ async fn test_reshare_basic() { } .unwrap(); + // wait for roatate keyshare + tokio::time::sleep(std::time::Duration::from_secs(10)).await; + + let key_share_after = unsafe_get(&client, hex::encode(NETWORK_PARENT_KEY), 3002).await; + assert_ne!(key_share_before, key_share_after); // At this point the signing set has changed on-chain, but the keyshares haven't been rotated // but by the time we have stored a program and registered, the rotation should have happened @@ -173,6 +209,36 @@ async fn test_reshare_basic() { let key_share = unsafe_get(&client, hex::encode(NETWORK_PARENT_KEY), port).await; assert!(!key_share.is_empty()); } + let block_number = rpc.chain_get_header(None).await.unwrap().unwrap().number + 1; + let key_share_before_2 = unsafe_get(&client, hex::encode(NETWORK_PARENT_KEY), 3003).await; + + next_signers.remove(0); + let binding = alice_stash.to_account_id().into(); + next_signers.push(&binding); + + let storage_address_next_signers = entropy::storage().staking_extension().next_signers(); + let value_next_signers = NextSignerInfo { confirmations: vec![], next_signers }; + // Add another reshare by adding next signer info + let call = RuntimeCall::System(SystemsCall::set_storage { + items: vec![(storage_address_next_signers.to_root_bytes(), value_next_signers.encode())], + }); + call_set_storage(&api, &rpc, call).await; + + let storage_address_reshare_data = entropy::storage().staking_extension().reshare_data(); + let value_reshare_info = + ReshareInfo { block_number, new_signers: vec![alice_stash.public().encode()] }; + // Same reshare needs reshare data too + let call = RuntimeCall::System(SystemsCall::set_storage { + items: vec![(storage_address_reshare_data.to_root_bytes(), value_reshare_info.encode())], + }); + call_set_storage(&api, &rpc, call).await; + + // wait for roatate keyshare + tokio::time::sleep(std::time::Duration::from_secs(60)).await; + + let key_share_after_2 = unsafe_get(&client, hex::encode(NETWORK_PARENT_KEY), 3003).await; + assert_ne!(key_share_before_2, key_share_after_2); + clean_tests(); } @@ -225,9 +291,17 @@ async fn test_reshare_validation_fail() { validate_new_reshare(&api, &rpc, &ocw_message, &kv).await.map_err(|e| e.to_string()); assert_eq!(err_stale_data, Err("Data is stale".to_string())); - let block_number = rpc.chain_get_header(None).await.unwrap().unwrap().number + 1; + let block_number = rpc.chain_get_header(None).await.unwrap().unwrap().number; + let storage_address_reshare_data = entropy::storage().staking_extension().reshare_data(); + let value_reshare_info = + ReshareInfo { block_number: block_number + 1, new_signers: vec![dave.public().encode()] }; + // Add reshare + let call = RuntimeCall::System(SystemsCall::set_storage { + items: vec![(storage_address_reshare_data.to_root_bytes(), value_reshare_info.encode())], + }); + ocw_message.block_number = block_number; - run_to_block(&rpc, block_number + 1).await; + call_set_storage(&api, &rpc, call).await; let err_incorrect_data = validate_new_reshare(&api, &rpc, &ocw_message, &kv).await.map_err(|e| e.to_string()); 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/node/cli/src/chain_spec/dev.rs b/node/cli/src/chain_spec/dev.rs index 355785b55..833b1d839 100644 --- a/node/cli/src/chain_spec/dev.rs +++ b/node/cli/src/chain_spec/dev.rs @@ -255,7 +255,6 @@ pub fn development_genesis_config( }) .collect::>(), proactive_refresh_data: (vec![], vec![]), - mock_signer_rotate: (false, vec![], vec![]), jump_started_signers: None, }, "elections": ElectionsConfig { diff --git a/node/cli/src/chain_spec/integration_tests.rs b/node/cli/src/chain_spec/integration_tests.rs index 18a9523ea..192f7f646 100644 --- a/node/cli/src/chain_spec/integration_tests.rs +++ b/node/cli/src/chain_spec/integration_tests.rs @@ -66,10 +66,6 @@ pub fn integration_tests_config(jumpstarted: bool) -> ChainSpec { ], vec![], get_account_id_from_seed::("Alice"), - vec![ - get_account_id_from_seed::("Bob//stash"), - get_account_id_from_seed::("Charlie//stash"), - ], jump_started_signers, )) .build() @@ -87,7 +83,6 @@ pub fn integration_tests_genesis_config( )>, initial_nominators: Vec, root_key: AccountId, - mock_signer_rotate_data: Vec, jump_started_signers: Option>, ) -> serde_json::Value { // Note that any endowed_accounts added here will be included in the `elections` and @@ -229,7 +224,6 @@ pub fn integration_tests_genesis_config( ], vec![PREGENERATED_NETWORK_VERIFYING_KEY.to_vec(), DAVE_VERIFYING_KEY.to_vec()], ), - mock_signer_rotate: (true, mock_signer_rotate_data, vec![get_account_id_from_seed::("Dave//stash")]), jump_started_signers, }, "elections": ElectionsConfig { diff --git a/node/cli/src/chain_spec/testnet.rs b/node/cli/src/chain_spec/testnet.rs index 590e14be3..64874f068 100644 --- a/node/cli/src/chain_spec/testnet.rs +++ b/node/cli/src/chain_spec/testnet.rs @@ -427,7 +427,6 @@ pub fn testnet_genesis_config( }) .collect::>(), proactive_refresh_data: (vec![], vec![]), - mock_signer_rotate: (false, vec![], vec![]), jump_started_signers: None, }, "elections": ElectionsConfig { diff --git a/pallets/attestation/Cargo.toml b/pallets/attestation/Cargo.toml index e9b7356bd..30f7b239b 100644 --- a/pallets/attestation/Cargo.toml +++ b/pallets/attestation/Cargo.toml @@ -28,19 +28,23 @@ 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 } [features] default=['std'] @@ -58,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 32cd7369d..8c26ea362 100644 --- a/pallets/attestation/src/mock.rs +++ b/pallets/attestation/src/mock.rs @@ -64,6 +64,7 @@ frame_support::construct_runtime!( Historical: pallet_session_historical, BagsList: pallet_bags_list, Parameters: pallet_parameters, + Slashing: pallet_slashing, } ); @@ -315,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 = (); @@ -342,6 +342,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(); @@ -360,7 +372,6 @@ pub fn new_test_ext() -> sp_io::TestExternalities { (5, (0, NULL_ARR, vec![20], pck_encoded.to_vec().try_into().unwrap())), ], proactive_refresh_data: (vec![], vec![]), - mock_signer_rotate: (false, vec![], vec![]), jump_started_signers: None, }; pallet_staking_extension.assimilate_storage(&mut t).unwrap(); 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/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/lib.rs b/pallets/propagation/src/lib.rs index fcea66d47..372cae8bd 100644 --- a/pallets/propagation/src/lib.rs +++ b/pallets/propagation/src/lib.rs @@ -160,11 +160,11 @@ pub mod pallet { /// Submits a request to do a key refresh on the signers parent key. pub fn post_reshare(block_number: BlockNumberFor) -> Result<(), http::Error> { let reshare_data = pallet_staking_extension::Pallet::::reshare_data(); - if reshare_data.block_number != block_number { + if reshare_data.block_number + sp_runtime::traits::One::one() != block_number { return Ok(()); } - let deadline = sp_io::offchain::timestamp().add(Duration::from_millis(2_000)); + let deadline = sp_io::offchain::timestamp().add(Duration::from_millis(10_000)); let kind = sp_core::offchain::StorageKind::PERSISTENT; let from_local = sp_io::offchain::local_storage_get(kind, b"reshare_validators") .unwrap_or_else(|| b"http://localhost:3001/validator/reshare".to_vec()); diff --git a/pallets/propagation/src/mock.rs b/pallets/propagation/src/mock.rs index 1fce23e1c..31de2ed29 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, } ); @@ -310,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 = (); @@ -394,6 +394,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(); @@ -406,7 +418,6 @@ pub fn new_test_ext() -> sp_io::TestExternalities { (2, (4, NULL_ARR, vec![11], BoundedVec::with_max_capacity())), ], proactive_refresh_data: (vec![], vec![]), - mock_signer_rotate: (false, vec![], vec![]), jump_started_signers: None, }; diff --git a/pallets/propagation/src/tests.rs b/pallets/propagation/src/tests.rs index 05b597633..0be5f1315 100644 --- a/pallets/propagation/src/tests.rs +++ b/pallets/propagation/src/tests.rs @@ -93,7 +93,7 @@ fn knows_how_to_mock_several_http_calls() { // doesn't trigger no reshare block Propagation::post_reshare(7).unwrap(); pallet_staking_extension::ReshareData::::put(ReshareInfo { - block_number: 7, + block_number: 6, new_signers: vec![1u64.encode()], }); // now triggers 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..c91d3f730 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, } ); @@ -307,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 = (); @@ -380,6 +380,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(); @@ -393,7 +405,6 @@ pub fn new_test_ext() -> sp_io::TestExternalities { (7, (4, NULL_ARR, vec![50], BoundedVec::with_max_capacity())), ], proactive_refresh_data: (vec![], vec![]), - mock_signer_rotate: (false, vec![], vec![]), jump_started_signers: None, }; diff --git a/pallets/staking/Cargo.toml b/pallets/staking/Cargo.toml index 3cb777887..04cb6a592 100644 --- a/pallets/staking/Cargo.toml +++ b/pallets/staking/Cargo.toml @@ -28,19 +28,16 @@ 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 } +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 } @@ -52,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", @@ -69,6 +65,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..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}; @@ -29,12 +28,19 @@ 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, }; +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; @@ -56,6 +62,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, @@ -78,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 = { @@ -94,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()`. @@ -115,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, @@ -136,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(), @@ -187,6 +209,7 @@ benchmarks! { threshold, x25519_public_key, endpoint.clone().to_vec(), + QuoteContext::ChangeEndpoint, ) .0; }: _(RawOrigin::Signed(bonder.clone()), endpoint.to_vec(), quote) @@ -228,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 { @@ -377,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::( @@ -518,6 +539,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..516e6a3e6 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,8 +58,8 @@ use sp_staking::SessionIndex; #[frame_support::pallet] pub mod pallet { use entropy_shared::{ - QuoteContext, ValidatorInfo, X25519PublicKey, MAX_SIGNERS, - PREGENERATED_NETWORK_VERIFYING_KEY, TEST_RESHARE_BLOCK_NUMBER, VERIFICATION_KEY_LENGTH, + QuoteContext, ValidatorInfo, VerifyQuoteError, X25519PublicKey, MAX_SIGNERS, + PREGENERATED_NETWORK_VERIFYING_KEY, VERIFICATION_KEY_LENGTH, }; use frame_support::{ dispatch::{DispatchResult, DispatchResultWithPostInfo}, @@ -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, @@ -90,6 +87,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>; @@ -97,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>; @@ -132,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 @@ -262,8 +256,6 @@ pub mod pallet { pub threshold_servers: Vec>, /// validator info and accounts to take part in proactive refresh pub proactive_refresh_data: (Vec, Vec>), - /// validator info and account new signer to take part in a reshare - pub mock_signer_rotate: (bool, Vec, Vec), /// Whether to begin in an already jumpstarted state in order to be able to test signing /// using pre-generated keyshares pub jump_started_signers: Option>, @@ -295,22 +287,6 @@ pub mod pallet { proactive_refresh_keys: self.proactive_refresh_data.1.clone(), }; ProactiveRefresh::::put(refresh_info); - // mocks a signer rotation for tss new_reshare tests - if self.mock_signer_rotate.0 { - let next_signers = &mut self.mock_signer_rotate.1.clone(); - let mut new_signers = vec![]; - for new_signer in self.mock_signer_rotate.2.clone() { - next_signers.push(new_signer.clone()); - new_signers.push(new_signer.encode()) - } - let next_signers = next_signers.to_vec(); - NextSigners::::put(NextSignerInfo { next_signers, confirmations: vec![] }); - ReshareData::::put(ReshareInfo { - // To give enough time for test_reshare setup - block_number: TEST_RESHARE_BLOCK_NUMBER.into(), - new_signers, - }) - } if let Some(jump_started_signers) = &self.jump_started_signers { Signers::::put(jump_started_signers.clone()); @@ -336,6 +312,7 @@ pub mod pallet { InvalidValidatorId, SigningGroupError, TssAccountAlreadyExists, + NotSigner, NotNextSigner, ReshareNotInProgress, AlreadyConfirmed, @@ -345,20 +322,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, } } } @@ -428,17 +433,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); @@ -471,7 +471,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)?; @@ -491,30 +490,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; @@ -633,42 +621,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)?; @@ -735,6 +712,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 { @@ -876,7 +904,7 @@ pub mod pallet { // trigger reshare at next block let current_block_number = >::block_number(); let reshare_info = ReshareInfo { - block_number: current_block_number + sp_runtime::traits::One::one(), + block_number: current_block_number - sp_runtime::traits::One::one(), new_signers, }; diff --git a/pallets/staking/src/mock.rs b/pallets/staking/src/mock.rs index 21a35e06e..85e217f74 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; @@ -63,6 +62,7 @@ frame_support::construct_runtime!( Historical: pallet_session_historical, BagsList: pallet_bags_list, Parameters: pallet_parameters, + Slashing: pallet_slashing, } ); @@ -399,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()) }, } } @@ -418,11 +418,47 @@ 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; type MaxEndpointLength = MaxEndpointLength; - type PckCertChainVerifier = MockPckCertChainVerifier; type Randomness = TestPastRandomness; type RuntimeEvent = RuntimeEvent; type WeightInfo = (); @@ -441,7 +477,6 @@ pub fn new_test_ext() -> sp_io::TestExternalities { (6, (8, NULL_ARR, vec![40], BoundedVec::with_max_capacity())), ], proactive_refresh_data: (vec![], vec![]), - mock_signer_rotate: (false, vec![], vec![]), jump_started_signers: None, }; pallet_balances.assimilate_storage(&mut t).unwrap(); 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 768806c67..000000000 Binary files a/pallets/staking/src/pck/Intel_SGX_Provisioning_Certification_RootCA.cer and /dev/null differ 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 92125b2a9..4d971598f 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(), @@ -555,11 +510,7 @@ fn it_tests_new_session_handler() { assert_ok!(Staking::new_session_handler(&[1, 5, 6])); // takes signers original (5,6) pops off one and adds in new validator assert_eq!(Staking::next_signers().unwrap().next_signers, vec![6, 1]); - assert_eq!( - Staking::reshare_data().block_number, - 101, - "Check reshare block start at 100 + 1" - ); + assert_eq!(Staking::reshare_data().block_number, 99, "Check reshare block start at 99 + 1"); assert_eq!( Staking::reshare_data().new_signers, vec![1u64.encode()], @@ -571,12 +522,6 @@ fn it_tests_new_session_handler() { "parent key threhsold updated" ); - assert_eq!( - Staking::reshare_data().block_number, - 101, - "Check reshare block start at 100 + 1" - ); - assert_ok!(Staking::new_session_handler(&[6, 5, 3])); // takes 3 and leaves 5 and 6 since already in signer group assert_eq!(Staking::next_signers().unwrap().next_signers, vec![6, 3]); @@ -702,12 +647,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 +658,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 +676,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)); @@ -844,3 +782,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/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; 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())) + } }