Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
30 changes: 12 additions & 18 deletions crates/contract/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,10 @@ use crate::{
errors::{Error, RequestError},
primitives::ckd::{CKDRequest, CKDRequestArgs},
storage_keys::StorageKey,
tee::{quote::TeeQuoteStatus, tee_state::TeeState},
tee::{
quote::TeeQuoteStatus,
tee_state::{AttestationRecord, TeeState},
},
update::{ProposeUpdateArgs, ProposedUpdates, Update, UpdateId},
};

Expand Down Expand Up @@ -55,10 +58,7 @@ use primitives::{
thresholds::{Threshold, ThresholdParameters},
};
use state::{running::RunningContractState, ProtocolContractState};
use tee::{
proposal::MpcDockerImageHash,
tee_state::{NodeId, TeeValidationResult},
};
use tee::{proposal::MpcDockerImageHash, tee_state::TeeValidationResult};

/// Gas required for a sign request
const GAS_FOR_SIGN_CALL: Gas = Gas::from_tgas(15);
Expand Down Expand Up @@ -584,11 +584,11 @@ impl MpcContract {

// Add the participant information to the contract state
let is_new_attestation = self.tee_state.add_participant(
NodeId {
tls_public_key,
AttestationRecord {
account_id: account_id.clone(),
tls_public_key,
attestation: proposed_participant_attestation,
},
proposed_participant_attestation,
);

// Both participants and non-participants can propose. Non-participants must pay for the
Expand Down Expand Up @@ -991,7 +991,7 @@ impl MpcContract {
/// Returns all accounts that have TEE attestations stored in the contract.
/// Note: This includes both current protocol participants and accounts that may have
/// submitted TEE information but are not currently part of the active participant set.
pub fn get_tee_accounts(&self) -> Vec<NodeId> {
pub fn get_tee_accounts(&self) -> Vec<PublicKey> {
log!("get_tee_accounts: signer={}", env::signer_account_id());
self.tee_state.get_tee_accounts()
}
Expand Down Expand Up @@ -1430,18 +1430,12 @@ impl MpcContract {
)),
);
}
// ensure that this node has a valid TEE quote
let node_id = NodeId {
account_id: account_id.clone(),
tls_public_key: expected_destination_node
.destination_node_info
.sign_pk
.clone(),
};
// ensure that the node has a valid TEE quote
let public_key = &expected_destination_node.signer_account_pk;

if !(matches!(
self.tee_state.verify_tee_participant(
&node_id,
public_key,
Duration::from_secs(self.config.tee_upgrade_deadline_duration_seconds)
),
TeeQuoteStatus::Valid
Expand Down
10 changes: 2 additions & 8 deletions crates/contract/src/primitives/participants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,6 @@ use crate::errors::{Error, InvalidCandidateSet, InvalidParameters};
use near_sdk::{near, AccountId, PublicKey};
use std::{collections::BTreeSet, fmt::Display};

#[cfg(any(test, feature = "test-utils"))]
use crate::tee::tee_state::NodeId;

pub mod hpke {
pub type PublicKey = [u8; 32];
}
Expand Down Expand Up @@ -204,13 +201,10 @@ impl Participants {
}
}

pub fn get_node_ids(&self) -> BTreeSet<NodeId> {
pub fn get_public_keys(&self) -> BTreeSet<PublicKey> {
self.participants()
.iter()
.map(|(account_id, _, p_info)| NodeId {
account_id: account_id.clone(),
tls_public_key: p_info.sign_pk.clone(),
})
.map(|(_, _, p_info)| p_info.sign_pk.clone())
.collect()
}
}
Expand Down
64 changes: 24 additions & 40 deletions crates/contract/src/tee/tee_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,13 @@ use attestation::{
};
use borsh::{BorshDeserialize, BorshSerialize};
use mpc_primitives::hash::LauncherDockerComposeHash;
use near_sdk::{env, near, store::IterableMap, AccountId, PublicKey};
use near_sdk::{env, store::IterableMap, AccountId, PublicKey};
use std::{collections::HashSet, time::Duration};

#[near(serializers=[borsh, json])]
#[derive(Debug, Eq, Ord, PartialEq, PartialOrd, Clone, Hash)]
pub struct NodeId {
/// Operator account
#[derive(Debug, Clone, BorshSerialize, BorshDeserialize)]
pub struct AttestationRecord {
pub account_id: AccountId,
/// TLS public key
pub tls_public_key: PublicKey,
pub attestation: Attestation,
}

pub enum TeeValidationResult {
Expand All @@ -40,7 +37,7 @@ pub struct TeeState {
pub(crate) allowed_docker_image_hashes: AllowedDockerImageHashes,
pub(crate) allowed_launcher_compose_hashes: Vec<LauncherDockerComposeHash>,
pub(crate) votes: CodeHashesVotes,
pub(crate) participants_attestations: IterableMap<NodeId, Attestation>,
pub(crate) participants_attestations: IterableMap<PublicKey, AttestationRecord>,
}

impl Default for TeeState {
Expand Down Expand Up @@ -84,23 +81,22 @@ impl TeeState {
/// Verifies the TEE quote and Docker image
pub(crate) fn verify_tee_participant(
&mut self,
node_id: &NodeId,
tls_public_key: &PublicKey,
tee_upgrade_deadline_duration: Duration,
) -> TeeQuoteStatus {
let allowed_mpc_docker_image_hashes =
self.get_allowed_mpc_docker_image_hashes(tee_upgrade_deadline_duration);
let allowed_launcher_compose_hashes = &self.allowed_launcher_compose_hashes;

let participant_attestation = self.participants_attestations.get(node_id);
let Some(participant_attestation) = participant_attestation else {
let participant_attestation = self.participants_attestations.get(tls_public_key);
let Some(attestation_record) = participant_attestation else {
return TeeQuoteStatus::None;
};

let expected_report_data =
ReportData::V1(ReportDataV1::new(node_id.tls_public_key.clone()));
let expected_report_data = ReportData::V1(ReportDataV1::new(tls_public_key.clone()));
let time_stamp_seconds = Self::current_time_seconds();

let quote_result = participant_attestation.verify(
let quote_result = attestation_record.attestation.verify(
expected_report_data,
time_stamp_seconds,
&allowed_mpc_docker_image_hashes,
Expand Down Expand Up @@ -130,17 +126,11 @@ impl TeeState {
let participants_with_valid_attestation: Vec<_> = participants
.participants()
.iter()
.filter(|(account_id, _, participant_info)| {
let tls_public_key = participant_info.sign_pk.clone();
.filter(|(_, _, participant_info)| {
let tls_public_key = &participant_info.sign_pk;

matches!(
self.verify_tee_participant(
&NodeId {
account_id: account_id.clone(),
tls_public_key
},
tee_upgrade_deadline_duration
),
self.verify_tee_participant(tls_public_key, tee_upgrade_deadline_duration),
TeeQuoteStatus::Valid | TeeQuoteStatus::None
)
})
Expand All @@ -166,11 +156,11 @@ impl TeeState {
/// - `false` if the node already had an attestation (the existing one was replaced).
pub fn add_participant(
&mut self,
node_id: NodeId,
proposed_tee_participant: Attestation,
node_public_key: PublicKey,
proposed_tee_participant: AttestationRecord,
) -> bool {
self.participants_attestations
.insert(node_id, proposed_tee_participant)
.insert(node_public_key, proposed_tee_participant)
.is_none()
}

Expand Down Expand Up @@ -216,32 +206,26 @@ impl TeeState {
/// Removes TEE information for accounts that are not in the provided participants list.
/// This is used to clean up storage after a resharing concludes.
pub fn clean_non_participants(&mut self, participants: &Participants) {
let participant_accounts: HashSet<NodeId> = participants
let participant_public_keys: HashSet<&PublicKey> = participants
.participants()
.iter()
.map(|(account_id, _, p_info)| NodeId {
account_id: account_id.clone(),
tls_public_key: p_info.sign_pk.clone(),
})
.map(|(_, _, p_info)| &p_info.sign_pk)
.collect();

// Collect accounts to remove (can't remove while iterating)
let nodes_to_remove: Vec<NodeId> = self
let keys_to_remove: Vec<_> = self
.participants_attestations
.keys()
.filter(|node_id| !participant_accounts.contains(node_id))
.filter(|public_key| participant_public_keys.contains(public_key))
.cloned()
.collect();

// Remove non-participant TEE information
for node_id in &nodes_to_remove {
self.participants_attestations.remove(node_id);
for public_key in keys_to_remove {
self.participants_attestations.remove(&public_key);
}
}

/// Returns the list of accounts that currently have TEE attestations stored.
/// Note: This may include accounts that are no longer active protocol participants.
pub fn get_tee_accounts(&self) -> Vec<NodeId> {
/// Returns the list of node public keys that have attestations associated with them.
pub fn get_tee_accounts(&self) -> Vec<PublicKey> {
self.participants_attestations.keys().cloned().collect()
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ async fn test_tee_cleanup_after_full_resharing_flow() -> Result<()> {

// extract initial participants:
let initial_participants = assert_running_return_participants(&contract).await?;
let expected_node_ids = initial_participants.get_node_ids();
let expected_node_ids = initial_participants.get_public_keys();

// submit attestations
submit_tee_attestations(&contract, &mut env_accounts, &expected_node_ids).await?;
Expand All @@ -51,7 +51,7 @@ async fn test_tee_cleanup_after_full_resharing_flow() -> Result<()> {
// Add two prospective Participants
// Note: this test fails if `vote_reshared` needs to clean up more than 3 attestations
let (mut env_non_participant_accounts, non_participants) = gen_accounts(&worker, 1).await;
let non_participant_uids = non_participants.get_node_ids();
let non_participant_uids = non_participants.get_public_keys();
submit_tee_attestations(
&contract,
&mut env_non_participant_accounts,
Expand Down Expand Up @@ -95,7 +95,7 @@ async fn test_tee_cleanup_after_full_resharing_flow() -> Result<()> {
.expect("Failed to insert participant");
}

let expected_tee_post_resharing = new_participants.get_node_ids();
let expected_tee_post_resharing = new_participants.get_public_keys();
let new_threshold_parameters =
ThresholdParameters::new(new_participants, Threshold::new(2)).unwrap();

Expand All @@ -116,7 +116,7 @@ async fn test_tee_cleanup_after_full_resharing_flow() -> Result<()> {
.expect("Expected contract to be in Running state after resharing.");

// Get current participants to compare
let final_participants_node_ids = final_participants.get_node_ids();
let final_participants_node_ids = final_participants.get_public_keys();
// Verify only the new participants remain
assert_eq!(final_participants_node_ids, expected_tee_post_resharing);
// Verify TEE participants are properly cleaned up
Expand Down
4 changes: 2 additions & 2 deletions crates/contract/tests/sandbox/tee.rs
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,7 @@ async fn test_clean_tee_status_succeeds_when_contract_calls_itself() -> Result<(

let participant_uids = assert_running_return_participants(&contract)
.await?
.get_node_ids();
.get_public_keys();
submit_tee_attestations(&contract, &mut accounts, &participant_uids).await?;

// Verify current participants have TEE data
Expand All @@ -346,7 +346,7 @@ async fn test_clean_tee_status_succeeds_when_contract_calls_itself() -> Result<(
const NUM_ADDITIONAL_ACCOUNTS: usize = 2;
let (mut additional_accounts, additional_participants) =
gen_accounts(&worker, NUM_ADDITIONAL_ACCOUNTS).await;
let additional_uids = additional_participants.get_node_ids();
let additional_uids = additional_participants.get_public_keys();
submit_tee_attestations(&contract, &mut additional_accounts, &additional_uids).await?;

// Verify we have TEE data for all accounts before cleanup
Expand Down
12 changes: 6 additions & 6 deletions crates/node/src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ impl StartCmd {
home_dir.clone(),
config.indexer.clone(),
config.my_near_account_id.clone(),
persistent_secrets.near_signer_key.clone(),
persistent_secrets.node_signing_key.clone(),
respond_config,
web_contract_sender,
indexer_exit_sender,
Expand Down Expand Up @@ -325,13 +325,13 @@ impl StartCmd {

let (debug_request_sender, _) = tokio::sync::broadcast::channel(10);

let tls_public_key = secrets
let node_public_key = secrets
.persistent_secrets
.p2p_private_key
.node_signing_key
.verifying_key()
.to_near_sdk_public_key()?;

let report_data = ReportData::new(tls_public_key);
let report_data = ReportData::new(node_public_key);
let tee_authority = TeeAuthority::try_from(self.tee_authority)?;
let attestation = tee_authority.generate_attestation(report_data).await?;
let web_server = start_web_server(
Expand Down Expand Up @@ -366,7 +366,7 @@ impl StartCmd {

// submit remote attestation
{
let account_public_key = secrets.persistent_secrets.near_signer_key.verifying_key();
let account_public_key = secrets.persistent_secrets.node_signing_key.verifying_key();

submit_remote_attestation(
indexer_api.txn_sender.clone(),
Expand Down Expand Up @@ -472,7 +472,7 @@ impl Cli {
&subdir,
desired_responder_keys_per_participant,
)
.map(|secret| secret.p2p_private_key)
.map(|secret| secret.node_signing_key)
})
.collect::<Result<Vec<_>, _>>()?;
let configs = generate_test_p2p_configs(
Expand Down
11 changes: 4 additions & 7 deletions crates/node/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -240,8 +240,7 @@ impl SecretsConfig {
/// from outside.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct PersistentSecrets {
pub p2p_private_key: SigningKey,
pub near_signer_key: SigningKey,
pub node_signing_key: SigningKey,
pub near_responder_keys: Vec<SigningKey>,
}

Expand Down Expand Up @@ -270,15 +269,13 @@ impl PersistentSecrets {

let mut os_rng = rand::rngs::OsRng;
let p2p_secret = SigningKey::generate(&mut os_rng);
let near_signer_key = SigningKey::generate(&mut os_rng);

let near_responder_keys = (0..number_of_responder_keys)
.map(|_| SigningKey::generate(&mut os_rng))
.collect::<Vec<_>>();

let secrets = PersistentSecrets {
p2p_private_key: p2p_secret,
near_signer_key,
node_signing_key: p2p_secret,
near_responder_keys,
};

Expand Down Expand Up @@ -374,8 +371,8 @@ mod tests {
let actual_secrets = PersistentSecrets::generate_or_get_existing(temp_dir_path, 424)?;

assert_eq!(
actual_secrets.p2p_private_key,
expected_secrets.p2p_private_key
actual_secrets.node_signing_key,
expected_secrets.node_signing_key
);
assert_eq!(
actual_secrets.near_signer_key,
Expand Down
4 changes: 2 additions & 2 deletions crates/node/src/coordinator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ where
chain_txn_sender: TransactionSender,
key_event_receiver: watch::Receiver<ContractKeyEventInstance>,
) -> anyhow::Result<MpcJobResult> {
let p2p_key = &secrets.persistent_secrets.p2p_private_key;
let p2p_key = &secrets.persistent_secrets.node_signing_key;
let Some(mpc_config) = MpcConfig::from_participants_with_near_account_id(
participants,
&config_file.my_near_account_id,
Expand Down Expand Up @@ -359,7 +359,7 @@ where
.participants
.retain(|p| participants_config.participants.contains(p));

let p2p_key = &secrets.persistent_secrets.p2p_private_key;
let p2p_key = &secrets.persistent_secrets.node_signing_key;
let Some(mpc_config) = MpcConfig::from_participants_with_near_account_id(
participants_config,
&config_file.my_near_account_id,
Expand Down
Loading
Loading