Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Attestation pallet #1003

Merged
merged 43 commits into from
Aug 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
55c9af2
WIP attestation pallet
ameba23 Aug 13, 2024
c5df03f
Merge branch 'master' into peg/attestation-pallet
ameba23 Aug 13, 2024
09e877e
Improve checks for attest extrinsic
ameba23 Aug 13, 2024
6fe7070
Give constant weight just to get it to compile (with warning)
ameba23 Aug 13, 2024
8cc3457
Attempt to write mock runtime
ameba23 Aug 14, 2024
0a7ee3c
Write trivial test to show mock setup works
ameba23 Aug 14, 2024
d9d4a80
Clean attestation pallet (#1006)
JesseAbram Aug 15, 2024
d106227
fmt
ameba23 Aug 15, 2024
430b372
Make an attestation in the test
ameba23 Aug 15, 2024
5a2206f
Lockfile
ameba23 Aug 15, 2024
305120b
Taplo
ameba23 Aug 15, 2024
7425039
Comments
ameba23 Aug 15, 2024
905359d
Make quote input constructor generic, rm authorship pallet
ameba23 Aug 15, 2024
254a89a
Taplo
ameba23 Aug 15, 2024
2c462b8
Add benchmarks
ameba23 Aug 15, 2024
37d08d0
Taplo
ameba23 Aug 15, 2024
f57303b
Add pallet to chain runtime
ameba23 Aug 15, 2024
fb5a4b7
Rm unused import
ameba23 Aug 15, 2024
69dd18e
Improve benchmark
ameba23 Aug 15, 2024
992a4d1
Merge branch 'master' into peg/attestation-pallet
ameba23 Aug 15, 2024
74a83ab
Generate weights
ameba23 Aug 15, 2024
e27262a
Include weights in runtime
ameba23 Aug 15, 2024
175166b
Update metadata
ameba23 Aug 15, 2024
c7db0ef
Submit the attest extrinsic to test the attestation pallet
ameba23 Aug 16, 2024
01213d1
Fixes for submitting extrinsic
ameba23 Aug 16, 2024
19c7a7a
Update attestation endpoint in preparation for using the propagation …
ameba23 Aug 16, 2024
8d1f3ca
Propagation pallet propagates attestation requests
ameba23 Aug 16, 2024
33797ef
Decode input from propagation pallet
ameba23 Aug 16, 2024
b6f69b8
Add initial attestation requests to genesis config
ameba23 Aug 19, 2024
314dc9a
Add initial attestation requests to chainspec
ameba23 Aug 19, 2024
7843545
Merge master
ameba23 Aug 19, 2024
76d95eb
Pull chain metadata
ameba23 Aug 19, 2024
3ef7fe8
Make mock attestation request at block 3
ameba23 Aug 20, 2024
fe93e35
Fix test
ameba23 Aug 20, 2024
0abb8be
Tidy
ameba23 Aug 20, 2024
cdd22b4
Update crates/threshold-signature-server/src/attestation/api.rs
ameba23 Aug 21, 2024
0e75228
Extra check in attestation pallet benchmarks
ameba23 Aug 21, 2024
d76bb0f
Improve error message for unexpected attestation
ameba23 Aug 21, 2024
8ad0c8d
Use actual weight
ameba23 Aug 21, 2024
dba2907
Add endpoint to node service
ameba23 Aug 21, 2024
9379469
Comments and minor changed following review
ameba23 Aug 21, 2024
2aca946
Changelog
ameba23 Aug 21, 2024
8d07bf9
Merge master
ameba23 Aug 21, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ At the moment this project **does not** adhere to
- Signing flow with derived accounts ([#990](https://github.com/entropyxyz/entropy-core/pull/990))
- TSS attestation endpoint ([#1001](https://github.com/entropyxyz/entropy-core/pull/1001))
- Add `network-jumpstart` command to `entropy-test-cli` ([#1004](https://github.com/entropyxyz/entropy-core/pull/1004))
- Attestation pallet ([#1003](https://github.com/entropyxyz/entropy-core/pull/1003))
- Update test CLI for new registration and signing flows ([#1008](https://github.com/entropyxyz/entropy-core/pull/1008))

### Changed
Expand Down
32 changes: 32 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Binary file modified crates/client/entropy_metadata.scale
Binary file not shown.
15 changes: 12 additions & 3 deletions crates/shared/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,15 @@ pub struct OcwMessageProactiveRefresh {
pub proactive_refresh_keys: Vec<Vec<u8>>,
}

/// Offchain worker message for requesting a TDX attestation
#[cfg(not(feature = "wasm"))]
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
#[derive(Clone, Encode, Decode, Debug, Eq, PartialEq, TypeInfo)]
pub struct OcwMessageAttestationRequest {
/// The account ids of all TSS servers who must submit an attestation this block
pub tss_account_ids: Vec<[u8; 32]>,
}

/// 256-bit hashing algorithms for deriving the point to be signed.
#[cfg_attr(any(feature = "wasm", feature = "std"), derive(Serialize, Deserialize))]
#[cfg_attr(feature = "std", derive(EnumIter))]
Expand All @@ -105,14 +114,14 @@ pub type EncodedVerifyingKey = [u8; VERIFICATION_KEY_LENGTH as usize];
pub struct QuoteInputData(pub [u8; 64]);

impl QuoteInputData {
pub fn new(
tss_account_id: [u8; 32],
pub fn new<T: Encode>(
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is just so it can be used with mock account ids in the attestation pallet test.

tss_account_id: T,
x25519_public_key: X25519PublicKey,
nonce: [u8; 32],
block_number: u32,
) -> Self {
let mut hasher = Blake2b512::new();
hasher.update(tss_account_id);
hasher.update(tss_account_id.encode());
hasher.update(x25519_public_key);
hasher.update(nonce);
hasher.update(block_number.to_be_bytes());
Expand Down
93 changes: 67 additions & 26 deletions crates/threshold-signature-server/src/attestation/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,57 +13,98 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

use crate::{attestation::errors::AttestationErr, AppState};
use crate::{
attestation::errors::AttestationErr,
chain_api::{entropy, get_api, get_rpc, EntropyConfig},
get_signer_and_x25519_secret,
helpers::substrate::{query_chain, submit_transaction},
AppState,
};
use axum::{body::Bytes, extract::State, http::StatusCode};
use entropy_shared::OcwMessageAttestationRequest;
use parity_scale_codec::Decode;
use sp_core::Pair;
use subxt::tx::PairSigner;
use x25519_dalek::StaticSecret;

/// HTTP POST endpoint to initiate a TDX attestation.
/// Not yet implemented.
#[cfg(not(any(test, feature = "unsafe")))]
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Before we had a production and mock version of the endpoint function - but that is now replaced with a common endpoint function which internally calls either a mock or production version of the create_quote function

pub async fn attest(
State(_app_state): State<AppState>,
_input: Bytes,
) -> Result<StatusCode, AttestationErr> {
// Non-mock attestation (the real thing) will go here
Err(AttestationErr::NotImplemented)
}

/// HTTP POST endpoint to initiate a mock TDX attestation for testing on non-TDX hardware.
/// The body of the request should be a 32 byte random nonce used to show 'freshness' of the
/// quote.
/// The response body contains a mock TDX v4 quote serialized as described in the
/// [Index TDX DCAP Quoting Library API](https://download.01.org/intel-sgx/latest/dcap-latest/linux/docs/Intel_TDX_DCAP_Quoting_Library_API.pdf).
#[cfg(any(test, feature = "unsafe"))]
pub async fn attest(
State(app_state): State<AppState>,
input: Bytes,
) -> Result<(StatusCode, Bytes), AttestationErr> {
use crate::{chain_api::get_rpc, get_signer_and_x25519_secret};
use rand_core::OsRng;
use sp_core::Pair;
) -> Result<StatusCode, AttestationErr> {
Copy link
Member

Choose a reason for hiding this comment

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

we will have to validate this message agains blockchain data (can be in a later PR but needs an issue

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll put it in the issue - i am adding a bunch of things into the once issue for this feature.

But since we check below with the chain that there is a pending attestation, im not sure what we gain by also checking the OcwMessageAttestationRequest matches on chain.

let (signer, x25519_secret) = get_signer_and_x25519_secret(&app_state.kv_store).await?;
let attestation_requests = OcwMessageAttestationRequest::decode(&mut input.as_ref())?;

// TODO (#982) confirm with the chain that an attestation should be happenning
let nonce = input.as_ref().try_into()?;
// Check whether there is an attestion request for us
if !attestation_requests.tss_account_ids.contains(&signer.signer().public().0) {
return Ok(StatusCode::OK);
}

let api = get_api(&app_state.configuration.endpoint).await?;
let rpc = get_rpc(&app_state.configuration.endpoint).await?;

// Get the input nonce for this attestation
let nonce = {
let pending_attestation_query =
entropy::storage().attestation().pending_attestations(signer.account_id());
query_chain(&api, &rpc, pending_attestation_query, None)
.await?
.ok_or_else(|| AttestationErr::Unexpected)?
Copy link
Member

Choose a reason for hiding this comment

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

wouldn't hate a better error message here, a generalize one for querying chains maybe that takes a string of a better description

};

// We also need the current block number as input
let block_number =
rpc.chain_get_header(None).await?.ok_or_else(|| AttestationErr::BlockNumber)?.number;

// We add 1 to the block number as this will be processed in the next block
let quote = create_quote(block_number + 1, nonce, &signer, &x25519_secret).await?;

// Submit the quote
let attest_tx = entropy::tx().attestation().attest(quote.clone());
submit_transaction(&api, &rpc, &signer, &attest_tx, None).await?;

Ok(StatusCode::OK)
}

/// Create a mock quote for testing on non-TDX hardware
#[cfg(any(test, feature = "unsafe"))]
pub async fn create_quote(
block_number: u32,
nonce: [u8; 32],
signer: &PairSigner<EntropyConfig, sp_core::sr25519::Pair>,
x25519_secret: &StaticSecret,
) -> Result<Vec<u8>, AttestationErr> {
use rand_core::OsRng;
use sp_core::Pair;

// In the real thing this is the hardware key used in the quoting enclave
let signing_key = tdx_quote::SigningKey::random(&mut OsRng);

let (signer, x25519_secret) = get_signer_and_x25519_secret(&app_state.kv_store).await?;
let public_key = x25519_dalek::PublicKey::from(&x25519_secret);
let public_key = x25519_dalek::PublicKey::from(x25519_secret);

let input_data = entropy_shared::QuoteInputData::new(
signer.signer().public().into(),
signer.signer().public(),
*public_key.as_bytes(),
nonce,
block_number,
);

let quote = tdx_quote::Quote::mock(signing_key.clone(), input_data.0);
// Here we would submit an attest extrinsic to the chain - but for now we just include it in the
// response
Ok((StatusCode::OK, Bytes::from(quote.as_bytes().to_vec())))
let quote = tdx_quote::Quote::mock(signing_key.clone(), input_data.0).as_bytes().to_vec();
Ok(quote)
}

/// Once implemented, this will create a TDX quote in production
#[cfg(not(any(test, feature = "unsafe")))]
pub async fn create_quote(
_block_number: u32,
_nonce: [u8; 32],
_signer: &PairSigner<EntropyConfig, sp_core::sr25519::Pair>,
_x25519_secret: &StaticSecret,
) -> Result<Vec<u8>, AttestationErr> {
// Non-mock attestation (the real thing) will go here
Err(AttestationErr::NotImplemented)
}
7 changes: 6 additions & 1 deletion crates/threshold-signature-server/src/attestation/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,14 @@ pub enum AttestationErr {
NotImplemented,
#[error("Input must be 32 bytes: {0}")]
TryFromSlice(#[from] TryFromSliceError),
#[cfg(any(test, feature = "unsafe"))]
#[error("Could not get block number")]
BlockNumber,
#[error("Substrate: {0}")]
SubstrateClient(#[from] entropy_client::substrate::SubstrateError),
#[error("Got an attestation request but there is no pending attestation request on chain")]
Unexpected,
#[error("Could not decode message: {0}")]
Codec(#[from] parity_scale_codec::Error),
}

impl IntoResponse for AttestationErr {
Expand Down
55 changes: 34 additions & 21 deletions crates/threshold-signature-server/src/attestation/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,16 @@
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
use crate::helpers::tests::{initialize_test_logger, spawn_testing_validators};
use crate::{
chain_api::{entropy, get_api, get_rpc},
helpers::{
substrate::query_chain,
tests::{initialize_test_logger, run_to_block, spawn_testing_validators},
},
};
use entropy_kvdb::clean_tests;
use entropy_shared::QuoteInputData;
use entropy_testing_utils::{
constants::{TSS_ACCOUNTS, X25519_PUBLIC_KEYS},
substrate_context::test_node_process_testing_state,
constants::TSS_ACCOUNTS, substrate_context::test_node_process_stationary,
};
use serial_test::serial;

Expand All @@ -27,25 +31,34 @@ async fn test_attest() {
initialize_test_logger().await;
clean_tests();

let _cxt = test_node_process_testing_state(false).await;
let cxt = test_node_process_stationary().await;
let (_validator_ips, _validator_ids) = spawn_testing_validators(false).await;
let api = get_api(&cxt.ws_url).await.unwrap();
let rpc = get_rpc(&cxt.ws_url).await.unwrap();

// Check that there is an attestation request at block 3 from the genesis config
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why block 3?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This was one of those problems which kind of got sorted but i don't completely understand why.

The propagation pallet's off chain worker hooks which run every block don't run on the genesis block. It seems to me that they also don't run on block one - although me and Jesse were getting different behavior. In the end i've ended up with block 3, and also am not subtracting 1 from the block number. Probably this could be either changed to 2, or the subtract 1 could be put back in.

let attestation_requests_query = entropy::storage().attestation().attestation_requests(3);
query_chain(&api, &rpc, attestation_requests_query, None).await.unwrap().unwrap();

let nonce = [0; 32];
let client = reqwest::Client::new();
let res = client
.post(format!("http://127.0.0.1:3001/attest"))
.body(nonce.to_vec())
.send()
.await
.unwrap();
assert_eq!(res.status(), 200);
let quote = res.bytes().await.unwrap();
// Get the nonce from the pending attestation from the genesis config
let nonce = {
let pending_attestation_query =
entropy::storage().attestation().pending_attestations(&TSS_ACCOUNTS[0]);
query_chain(&api, &rpc, pending_attestation_query, None).await.unwrap().unwrap()
};
assert_eq!(nonce, [0; 32]);

// This internally verifies the signature in the quote
let quote = tdx_quote::Quote::from_bytes(&quote).unwrap();
// Wait for the attestation to be handled
for _ in 0..10 {
let block_number = rpc.chain_get_header(None).await.unwrap().unwrap().number;
run_to_block(&rpc, block_number + 1).await;

// Check the input data of the quote
let expected_input_data =
QuoteInputData::new(TSS_ACCOUNTS[0].0, X25519_PUBLIC_KEYS[0], nonce, 0);
assert_eq!(quote.report_input_data(), expected_input_data.0);
// There should be no more pending attestation as the attestation has been handled
let pending_attestation_query =
entropy::storage().attestation().pending_attestations(&TSS_ACCOUNTS[0]);
if query_chain(&api, &rpc, pending_attestation_query, None).await.unwrap().is_none() {
return;
}
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This test isn't ideal - but currently the only outcome of a successful attestation is that there is no longer a pending attestation.

}
panic!("Waited 10 blocks and attestation is still pending");
}
14 changes: 9 additions & 5 deletions node/cli/src/chain_spec/dev.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ use crate::chain_spec::{get_account_id_from_seed, ChainSpec};
use crate::endowed_accounts::endowed_accounts_dev;

use entropy_runtime::{
constants::currency::*, wasm_binary_unwrap, AuthorityDiscoveryConfig, BabeConfig,
BalancesConfig, ElectionsConfig, GrandpaConfig, ImOnlineConfig, IndicesConfig, MaxNominations,
ParametersConfig, ProgramsConfig, RegistryConfig, SessionConfig, StakerStatus, StakingConfig,
StakingExtensionConfig, SudoConfig, TechnicalCommitteeConfig,
constants::currency::*, wasm_binary_unwrap, AttestationConfig, AuthorityDiscoveryConfig,
BabeConfig, BalancesConfig, ElectionsConfig, GrandpaConfig, ImOnlineConfig, IndicesConfig,
MaxNominations, ParametersConfig, ProgramsConfig, RegistryConfig, SessionConfig, StakerStatus,
StakingConfig, StakingExtensionConfig, SudoConfig, TechnicalCommitteeConfig,
};
use entropy_runtime::{AccountId, Balance};
use entropy_shared::{
Expand All @@ -34,7 +34,7 @@ use pallet_im_online::sr25519::AuthorityId as ImOnlineId;
use sc_service::ChainType;
use sp_authority_discovery::AuthorityId as AuthorityDiscoveryId;
use sp_consensus_babe::AuthorityId as BabeId;
use sp_core::sr25519;
use sp_core::{sr25519, ByteArray};
use sp_runtime::{BoundedVec, Perbill};

pub fn devnet_three_node_initial_tss_servers(
Expand Down Expand Up @@ -336,5 +336,9 @@ pub fn development_genesis_config(
10,
)],
},
"attestation": AttestationConfig {
initial_attestation_requests: vec![(3, vec![crate::chain_spec::tss_account_id::ALICE.to_raw_vec()])],
Copy link
Collaborator

Choose a reason for hiding this comment

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

Do you mind using an account instead of 3 here? Either a typedef or the result from one of the other helpful functions is fine

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is a block number, not an account id

initial_pending_attestations: vec![(crate::chain_spec::tss_account_id::ALICE.clone(), [0; 32])],
},
})
}
14 changes: 9 additions & 5 deletions node/cli/src/chain_spec/integration_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ use crate::chain_spec::{get_account_id_from_seed, ChainSpec};
use crate::endowed_accounts::endowed_accounts_dev;

use entropy_runtime::{
constants::currency::*, wasm_binary_unwrap, AuthorityDiscoveryConfig, BabeConfig,
BalancesConfig, ElectionsConfig, GrandpaConfig, ImOnlineConfig, IndicesConfig, MaxNominations,
ParametersConfig, ProgramsConfig, RegistryConfig, SessionConfig, StakerStatus, StakingConfig,
StakingExtensionConfig, SudoConfig, TechnicalCommitteeConfig,
constants::currency::*, wasm_binary_unwrap, AttestationConfig, AuthorityDiscoveryConfig,
BabeConfig, BalancesConfig, ElectionsConfig, GrandpaConfig, ImOnlineConfig, IndicesConfig,
MaxNominations, ParametersConfig, ProgramsConfig, RegistryConfig, SessionConfig, StakerStatus,
StakingConfig, StakingExtensionConfig, SudoConfig, TechnicalCommitteeConfig,
};
use entropy_runtime::{AccountId, Balance};
use entropy_shared::{
Expand All @@ -34,7 +34,7 @@ use pallet_im_online::sr25519::AuthorityId as ImOnlineId;
use sc_service::ChainType;
use sp_authority_discovery::AuthorityId as AuthorityDiscoveryId;
use sp_consensus_babe::AuthorityId as BabeId;
use sp_core::sr25519;
use sp_core::{sr25519, ByteArray};
use sp_runtime::{BoundedVec, Perbill};

/// The configuration used for the Threshold Signature Scheme server integration tests.
Expand Down Expand Up @@ -275,5 +275,9 @@ pub fn integration_tests_genesis_config(
10,
)],
},
"attestation": AttestationConfig {
initial_attestation_requests: vec![(3, vec![crate::chain_spec::tss_account_id::ALICE.to_raw_vec()])],
initial_pending_attestations: vec![(crate::chain_spec::tss_account_id::ALICE.clone(), [0; 32])],
},
})
}
5 changes: 5 additions & 0 deletions node/cli/src/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,11 @@ pub fn new_full_base(
b"reshare_validators",
&format!("{}/validator/reshare", endpoint).into_bytes(),
);
offchain_db.local_storage_set(
sp_core::offchain::StorageKind::PERSISTENT,
b"attest",
&format!("{}/attest", endpoint).into_bytes(),
);
log::info!("Threshold Signing Sever (TSS) location changed to {}", endpoint);
}
}
Expand Down
Loading
Loading