Skip to content

Commit ebc7f0d

Browse files
pbezakevindeforth
andauthored
feat(contract): async TEE attestation verification, drop dcap-qvl (#3714)
Co-authored-by: kevindeforth <32777623+kevindeforth@users.noreply.github.com>
1 parent 9e92d0f commit ebc7f0d

40 files changed

Lines changed: 2157 additions & 637 deletions

Cargo.lock

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/attestation/src/attestation.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ pub(crate) const KEY_PROVIDER_EVENT: &str = "key-provider";
3535
const RTMR3_INDEX: u32 = 3;
3636

3737
#[derive(Clone, Constructor, Serialize, Deserialize, BorshDeserialize, BorshSerialize)]
38+
#[cfg_attr(feature = "borsh-schema", derive(borsh::BorshSchema))]
3839
pub struct DstackAttestation {
3940
pub quote: QuoteBytes,
4041
pub collateral: Collateral,

crates/attestation/src/tcb_info.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
use alloc::string::String;
2+
#[cfg(feature = "borsh-schema")]
3+
use alloc::string::ToString;
24
use alloc::vec::Vec;
35
use borsh::{BorshDeserialize, BorshSerialize};
46
#[cfg(any(test, feature = "dstack-conversions"))]
@@ -16,6 +18,7 @@ pub enum ParsingError {
1618

1719
#[serde_as]
1820
#[derive(Debug, Clone, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
21+
#[cfg_attr(feature = "borsh-schema", derive(borsh::BorshSchema))]
1922
pub struct TcbInfo {
2023
pub mrtd: HexBytes<48>,
2124
pub rtmr0: HexBytes<48>,
@@ -32,6 +35,7 @@ pub struct TcbInfo {
3235

3336
#[serde_as]
3437
#[derive(Debug, Clone, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
38+
#[cfg_attr(feature = "borsh-schema", derive(borsh::BorshSchema))]
3539
pub struct EventLog {
3640
pub imr: u32,
3741
pub event_type: u32,
@@ -60,6 +64,33 @@ pub struct EventLog {
6064
#[serde(transparent)]
6165
pub struct HexBytes<const N: usize>(#[serde_as(as = "Hex")] [u8; N]);
6266

67+
/// Manual impl because the derive drops the const parameter from the
68+
/// declaration, so `HexBytes<48>` and `HexBytes<32>` in one schema collide
69+
/// ("Redefining type schema for HexBytes").
70+
#[cfg(feature = "borsh-schema")]
71+
impl<const N: usize> borsh::BorshSchema for HexBytes<N> {
72+
fn declaration() -> borsh::schema::Declaration {
73+
alloc::format!("HexBytes<{N}>")
74+
}
75+
76+
fn add_definitions_recursively(
77+
definitions: &mut alloc::collections::BTreeMap<
78+
borsh::schema::Declaration,
79+
borsh::schema::Definition,
80+
>,
81+
) {
82+
let fields = borsh::schema::Fields::UnnamedFields(alloc::vec![
83+
<[u8; N] as borsh::BorshSchema>::declaration()
84+
]);
85+
borsh::schema::add_definition(
86+
Self::declaration(),
87+
borsh::schema::Definition::Struct { fields },
88+
definitions,
89+
);
90+
<[u8; N] as borsh::BorshSchema>::add_definitions_recursively(definitions);
91+
}
92+
}
93+
6394
#[serde_as]
6495
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
6596
pub enum HexBytesOrEmpty<const N: usize> {
@@ -198,6 +229,17 @@ mod tests {
198229
use rstest::rstest;
199230
use serde_json;
200231

232+
/// `TcbInfo` holds both `HexBytes<48>` and `HexBytes<32>`; schema
233+
/// generation panics if their declarations collide.
234+
#[cfg(feature = "borsh-schema")]
235+
#[test]
236+
fn TcbInfo__should_generate_borsh_schema() {
237+
let container = borsh::schema_container_of::<TcbInfo>();
238+
239+
assert!(container.get_definition("HexBytes<48>").is_some());
240+
assert!(container.get_definition("HexBytes<32>").is_some());
241+
}
242+
201243
#[test]
202244
fn TcbInfo__should_deserialize_from_real_test_data() {
203245
// Given

crates/chain-gateway/src/transaction_sender/signer.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ mod tests {
145145
method_name: "do_something".to_string(),
146146
args: b"test args".to_vec(),
147147
gas: TEST_GAS,
148-
deposit: NearToken::from_yoctonear(0),
148+
deposit: NearToken::from_near(0),
149149
}
150150
}
151151

crates/chain-gateway/src/transaction_sender/traits.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ mod tests {
8686
let mut args = vec![0u8; 16];
8787
rng.fill(&mut args[..]);
8888
let gas = NearGas::from_gas(300);
89-
let deposit = NearToken::from_yoctonear(0);
89+
let deposit = NearToken::from_near(0);
9090
(
9191
receiver_id,
9292
FunctionCallArgs {

crates/contract/Cargo.toml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ abi = [
6565
"near-mpc-contract-interface/abi",
6666
"mpc-attestation/abi",
6767
"mpc-primitives/abi",
68+
"tee-verifier-interface/borsh-schema",
6869
"schemars",
6970
]
7071
# This is used when running `cargo clippy --all-features`, because otherwise `abi` feat will break compilation.
@@ -74,6 +75,7 @@ __abi-generate = ["abi", "near-sdk/__abi-generate"]
7475

7576
[dependencies]
7677
assert_matches = { workspace = true }
78+
attestation = { workspace = true }
7779
blstrs = { workspace = true }
7880
borsh = { workspace = true }
7981
curve25519-dalek = { workspace = true }
@@ -87,7 +89,7 @@ k256 = { workspace = true, features = [
8789
"arithmetic",
8890
"expose-field",
8991
] }
90-
mpc-attestation = { workspace = true, features = ["local-verify"] }
92+
mpc-attestation = { workspace = true }
9193
mpc-primitives = { workspace = true }
9294
near-account-id = { workspace = true, features = ["serde"] }
9395
near-mpc-bounded-collections = { workspace = true }
@@ -102,6 +104,7 @@ rand = { workspace = true, optional = true }
102104
serde = { workspace = true }
103105
serde_json = { workspace = true }
104106
serde_with = { workspace = true }
107+
tee-verifier-interface = { workspace = true }
105108
thiserror = { workspace = true }
106109
threshold-signatures = { workspace = true, optional = true }
107110

crates/contract/src/config.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ const DEFAULT_RETURN_SIGNATURE_AND_CLEAN_STATE_ON_SUCCESS_CALL_TERA_GAS: u64 = 7
2121
const DEFAULT_RETURN_CK_AND_CLEAN_STATE_ON_SUCCESS_CALL_TERA_GAS: u64 = 7;
2222
/// Prepaid gas for a `fail_on_timeout` call
2323
const DEFAULT_FAIL_ON_TIMEOUT_TERA_GAS: u64 = 2;
24+
/// Prepaid gas for a `fail_attestation_submission` call
25+
const DEFAULT_FAIL_ATTESTATION_SUBMISSION_TERA_GAS: u64 = 2;
2426
/// Prepaid gas for a `clean_tee_status` call
2527
const DEFAULT_CLEAN_TEE_STATUS_TERA_GAS: u64 = 10;
2628
/// Prepaid gas for the reshare-time `clean_invalid_attestations` promise.
@@ -34,6 +36,11 @@ const DEFAULT_REMOVE_NON_PARTICIPANT_UPDATE_VOTES_TERA_GAS: u64 = 5;
3436
const DEFAULT_CLEAN_FOREIGN_CHAIN_DATA_TERA_GAS: u64 = 5;
3537
/// Prepaid gas for a `remove_non_participant_tee_verifier_votes` call
3638
const DEFAULT_REMOVE_NON_PARTICIPANT_TEE_VERIFIER_VOTES_TERA_GAS: u64 = 5;
39+
/// Gas attached to the cross-contract `verify_quote` call on the TEE verifier.
40+
const DEFAULT_VERIFIER_TERA_GAS: u64 = 200;
41+
/// Prepaid gas for the `resolve_verification` callback. Carries the bulk of the
42+
/// post-DCAP work (allowlist match, RTMR3 replay, app-compose validation, store).
43+
const DEFAULT_RESOLVE_VERIFICATION_TERA_GAS: u64 = 60;
3744

3845
/// Config for V2 of the contract.
3946
#[near(serializers=[borsh, json])]
@@ -56,6 +63,8 @@ pub(crate) struct Config {
5663
pub(crate) return_ck_and_clean_state_on_success_call_tera_gas: u64,
5764
/// Prepaid gas for a `fail_on_timeout` call.
5865
pub(crate) fail_on_timeout_tera_gas: u64,
66+
/// Prepaid gas for a `fail_attestation_submission` call.
67+
pub(crate) fail_attestation_submission_tera_gas: u64,
5968
/// Prepaid gas for a `clean_tee_status` call.
6069
pub(crate) clean_tee_status_tera_gas: u64,
6170
/// Prepaid gas for the reshare-time `clean_invalid_attestations` promise.
@@ -68,6 +77,10 @@ pub(crate) struct Config {
6877
pub(crate) clean_foreign_chain_data_tera_gas: u64,
6978
/// Prepaid gas for a `remove_non_participant_tee_verifier_votes` call.
7079
pub(crate) remove_non_participant_tee_verifier_votes_tera_gas: u64,
80+
/// Gas attached to the cross-contract `verify_quote` call on the verifier.
81+
pub(crate) verifier_tera_gas: u64,
82+
/// Prepaid gas for the `resolve_verification` callback.
83+
pub(crate) resolve_verification_tera_gas: u64,
7184
}
7285

7386
impl Default for Config {
@@ -85,6 +98,7 @@ impl Default for Config {
8598
return_ck_and_clean_state_on_success_call_tera_gas:
8699
DEFAULT_RETURN_CK_AND_CLEAN_STATE_ON_SUCCESS_CALL_TERA_GAS,
87100
fail_on_timeout_tera_gas: DEFAULT_FAIL_ON_TIMEOUT_TERA_GAS,
101+
fail_attestation_submission_tera_gas: DEFAULT_FAIL_ATTESTATION_SUBMISSION_TERA_GAS,
88102
clean_tee_status_tera_gas: DEFAULT_CLEAN_TEE_STATUS_TERA_GAS,
89103
clean_invalid_attestations_tera_gas: DEFAULT_CLEAN_INVALID_ATTESTATIONS_TERA_GAS,
90104
cleanup_orphaned_node_migrations_tera_gas:
@@ -94,6 +108,8 @@ impl Default for Config {
94108
clean_foreign_chain_data_tera_gas: DEFAULT_CLEAN_FOREIGN_CHAIN_DATA_TERA_GAS,
95109
remove_non_participant_tee_verifier_votes_tera_gas:
96110
DEFAULT_REMOVE_NON_PARTICIPANT_TEE_VERIFIER_VOTES_TERA_GAS,
111+
verifier_tera_gas: DEFAULT_VERIFIER_TERA_GAS,
112+
resolve_verification_tera_gas: DEFAULT_RESOLVE_VERIFICATION_TERA_GAS,
97113
}
98114
}
99115
}

crates/contract/src/dto_mapping.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,9 @@ impl From<near_mpc_contract_interface::types::InitConfig> for Config {
472472
if let Some(v) = config_ext.fail_on_timeout_tera_gas {
473473
config.fail_on_timeout_tera_gas = v;
474474
}
475+
if let Some(v) = config_ext.fail_attestation_submission_tera_gas {
476+
config.fail_attestation_submission_tera_gas = v;
477+
}
475478
if let Some(v) = config_ext.clean_tee_status_tera_gas {
476479
config.clean_tee_status_tera_gas = v;
477480
}
@@ -490,6 +493,12 @@ impl From<near_mpc_contract_interface::types::InitConfig> for Config {
490493
if let Some(v) = config_ext.remove_non_participant_tee_verifier_votes_tera_gas {
491494
config.remove_non_participant_tee_verifier_votes_tera_gas = v;
492495
}
496+
if let Some(v) = config_ext.verifier_tera_gas {
497+
config.verifier_tera_gas = v;
498+
}
499+
if let Some(v) = config_ext.resolve_verification_tera_gas {
500+
config.resolve_verification_tera_gas = v;
501+
}
493502

494503
config
495504
}
@@ -510,6 +519,7 @@ impl From<&Config> for near_mpc_contract_interface::types::Config {
510519
return_ck_and_clean_state_on_success_call_tera_gas: value
511520
.return_ck_and_clean_state_on_success_call_tera_gas,
512521
fail_on_timeout_tera_gas: value.fail_on_timeout_tera_gas,
522+
fail_attestation_submission_tera_gas: value.fail_attestation_submission_tera_gas,
513523
clean_tee_status_tera_gas: value.clean_tee_status_tera_gas,
514524
clean_invalid_attestations_tera_gas: value.clean_invalid_attestations_tera_gas,
515525
cleanup_orphaned_node_migrations_tera_gas: value
@@ -519,6 +529,8 @@ impl From<&Config> for near_mpc_contract_interface::types::Config {
519529
clean_foreign_chain_data_tera_gas: value.clean_foreign_chain_data_tera_gas,
520530
remove_non_participant_tee_verifier_votes_tera_gas: value
521531
.remove_non_participant_tee_verifier_votes_tera_gas,
532+
verifier_tera_gas: value.verifier_tera_gas,
533+
resolve_verification_tera_gas: value.resolve_verification_tera_gas,
522534
}
523535
}
524536
}
@@ -538,6 +550,7 @@ impl From<near_mpc_contract_interface::types::Config> for Config {
538550
return_ck_and_clean_state_on_success_call_tera_gas: value
539551
.return_ck_and_clean_state_on_success_call_tera_gas,
540552
fail_on_timeout_tera_gas: value.fail_on_timeout_tera_gas,
553+
fail_attestation_submission_tera_gas: value.fail_attestation_submission_tera_gas,
541554
clean_tee_status_tera_gas: value.clean_tee_status_tera_gas,
542555
clean_invalid_attestations_tera_gas: value.clean_invalid_attestations_tera_gas,
543556
cleanup_orphaned_node_migrations_tera_gas: value
@@ -547,6 +560,8 @@ impl From<near_mpc_contract_interface::types::Config> for Config {
547560
clean_foreign_chain_data_tera_gas: value.clean_foreign_chain_data_tera_gas,
548561
remove_non_participant_tee_verifier_votes_tera_gas: value
549562
.remove_non_participant_tee_verifier_votes_tera_gas,
563+
verifier_tera_gas: value.verifier_tera_gas,
564+
resolve_verification_tera_gas: value.resolve_verification_tera_gas,
550565
}
551566
}
552567
}

crates/contract/src/errors.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use crate::crypto_shared::kdf::TweakNotOnCurve;
22
use crate::primitives::domain::MIN_RECONSTRUCTION_THRESHOLD;
33
use crate::primitives::key_state::{EpochId, Keyset};
4+
use crate::tee::tee_state::AttestationSubmissionError;
45
use near_account_id::AccountId;
56
use near_mpc_contract_interface::types as dtos;
67
use near_mpc_contract_interface::types::{DomainId, DomainPurpose, ForeignChain, Protocol};
@@ -28,6 +29,14 @@ pub enum TeeError {
2829
"Due to previously failed TEE validation, the network is not accepting new requests at this point in time. Try again later."
2930
)]
3031
TeeValidationFailed,
32+
#[error(
33+
"No TEE verifier is configured yet. Participants must vote one in via vote_tee_verifier_change before Dstack attestations can be submitted."
34+
)]
35+
VerifierNotConfigured,
36+
#[error("The TEE verifier rejected the quote: {reason}")]
37+
QuoteRejected { reason: String },
38+
#[error("The TEE verifier did not answer the verify_quote call.")]
39+
VerifierUnavailable,
3140
}
3241

3342
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
@@ -318,6 +327,9 @@ pub enum Error {
318327
// Tee errors
319328
#[error(transparent)]
320329
NodeMigrationError(#[from] NodeMigrationError),
330+
// Tee attestation submission errors
331+
#[error(transparent)]
332+
AttestationSubmission(#[from] AttestationSubmissionError),
321333
}
322334

323335
impl near_sdk::FunctionError for Error {

0 commit comments

Comments
 (0)