From 57b2a050ff394186080bb355c52e170fc80bebf3 Mon Sep 17 00:00:00 2001 From: Shoaib Ahmed Date: Wed, 20 Mar 2024 02:49:38 +0530 Subject: [PATCH] Adapt cw-tee-mtcs to use quartz (#12) --- .../contracts/cw-tee-mtcs/Cargo.toml | 3 + .../contracts/cw-tee-mtcs/src/contract.rs | 181 +++++----------- .../contracts/cw-tee-mtcs/src/error.rs | 17 +- .../contracts/cw-tee-mtcs/src/msg.rs | 149 ++++++++++---- .../contracts/cw-tee-mtcs/src/state.rs | 40 ++-- .../packages/quartz-cw/Cargo.toml | 1 + .../packages/quartz-cw/src/handler/execute.rs | 7 +- .../quartz-cw/src/handler/execute/attested.rs | 38 +++- .../quartz-cw/src/handler/instantiate.rs | 17 +- .../packages/quartz-cw/src/msg/execute.rs | 6 +- .../quartz-cw/src/msg/execute/attested.rs | 69 +++++-- .../src/msg/execute/session_create.rs | 4 + .../src/msg/execute/session_set_pub_key.rs | 4 + .../packages/quartz-cw/src/msg/instantiate.rs | 38 ++-- .../packages/quartz-cw/src/state.rs | 194 +++++++++++++++++- bisenzone-cw-mvp/scripts/deploy-contract.sh | 3 +- 16 files changed, 507 insertions(+), 264 deletions(-) diff --git a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/Cargo.toml b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/Cargo.toml index 73c5a16..0e33e50 100644 --- a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/Cargo.toml +++ b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/Cargo.toml @@ -54,5 +54,8 @@ schemars = "0.8.15" serde = { version = "1.0.189", default-features = false, features = ["derive"] } thiserror = { version = "1.0.49" } +quartz-cw = { path = "../../packages/quartz-cw" } + [dev-dependencies] cw-multi-test = "0.17.0" +serde_json = "1.0.113" diff --git a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/contract.rs b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/contract.rs index 85d7db4..46b9ba8 100644 --- a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/contract.rs +++ b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/contract.rs @@ -1,12 +1,12 @@ -use cosmwasm_std::{ - entry_point, to_json_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult, -}; +use cosmwasm_std::{entry_point, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult}; use cw2::set_contract_version; +use quartz_cw::handler::RawHandler; use crate::error::ContractError; -use crate::msg::execute::{BootstrapKeyManagerMsg, JoinComputeNodeMsg, RegisterEpochKeyMsg}; -use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; -use crate::state::{State, STATE}; +use crate::msg::execute::{SubmitObligationMsg, SubmitSetoffsMsg}; +use crate::msg::QueryMsg; +use crate::msg::{ExecuteMsg, InstantiateMsg}; +use crate::state::{current_epoch_key, ObligationsItem, State, OBLIGATIONS_KEY, STATE}; // version info for migration info const CONTRACT_NAME: &str = "crates.io:cw-tee-mtcs"; @@ -14,17 +14,23 @@ const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); #[cfg_attr(not(feature = "library"), entry_point)] pub fn instantiate( - deps: DepsMut, - _env: Env, + mut deps: DepsMut, + env: Env, info: MessageInfo, - _msg: InstantiateMsg, + msg: InstantiateMsg, ) -> Result { + // must be the handled first! + msg.0.handle_raw(deps.branch(), &env, &info)?; + let state = State { owner: info.sender.to_string(), }; set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; STATE.save(deps.storage, &state)?; + ObligationsItem::new(¤t_epoch_key(OBLIGATIONS_KEY, deps.storage)?) + .save(deps.storage, &Default::default())?; + Ok(Response::new() .add_attribute("method", "instantiate") .add_attribute("owner", info.sender)) @@ -33,151 +39,70 @@ pub fn instantiate( #[cfg_attr(not(feature = "library"), entry_point)] pub fn execute( deps: DepsMut, - _env: Env, - _info: MessageInfo, + env: Env, + info: MessageInfo, msg: ExecuteMsg, ) -> Result { match msg { - ExecuteMsg::BootstrapKeyManager(BootstrapKeyManagerMsg { - compute_mrenclave, - key_manager_mrenclave, - tcb_info, - }) => { - execute::bootstrap_key_manger(deps, compute_mrenclave, key_manager_mrenclave, tcb_info) + ExecuteMsg::Quartz(msg) => msg.handle_raw(deps, &env, &info).map_err(Into::into), + ExecuteMsg::SubmitObligation(SubmitObligationMsg { ciphertext, digest }) => { + execute::submit_obligation(deps, ciphertext, digest) } - ExecuteMsg::RegisterEpochKey(RegisterEpochKeyMsg { epoch_key }) => { - execute::register_epoch_key(deps, epoch_key) + ExecuteMsg::SubmitSetoffs(SubmitSetoffsMsg { setoffs_enc }) => { + execute::submit_setoffs(deps, setoffs_enc) } - ExecuteMsg::JoinComputeNode(JoinComputeNodeMsg { - io_exchange_key, - address, - nonce, - }) => execute::enqueue_join_request(deps, io_exchange_key, address, nonce), } } pub mod execute { - use cosmwasm_std::{DepsMut, Response}; - use k256::ecdsa::VerifyingKey; + use std::collections::BTreeMap; + + use cosmwasm_std::{DepsMut, HexBinary, Response}; + use quartz_cw::state::Hash; use crate::state::{ - EpochState, Mrenclave, RawAddress, RawMrenclave, RawNonce, RawPublicKey, RawTcbInfo, - SgxState, EPOCH_STATE, SGX_STATE, + current_epoch_key, ObligationsItem, RawCipherText, RawHash, SetoffsItem, OBLIGATIONS_KEY, + SETOFFS_KEY, }; - use crate::state::{Request, REQUESTS}; use crate::ContractError; - use crate::ContractError::BadLength; - pub fn bootstrap_key_manger( + pub fn submit_obligation( deps: DepsMut, - compute_mrenclave: RawMrenclave, - key_manager_mrenclave: RawMrenclave, - tcb_info: RawTcbInfo, + ciphertext: HexBinary, + digest: HexBinary, ) -> Result { - let _: Mrenclave = hex::decode(&compute_mrenclave)? - .try_into() - .map_err(|_| BadLength)?; - let _: Mrenclave = hex::decode(&key_manager_mrenclave)? - .try_into() - .map_err(|_| BadLength)?; - // TODO(hu55a1n1): validate TcbInfo + let _: Hash = digest.to_array()?; - let sgx_state = SgxState { - compute_mrenclave: compute_mrenclave.clone(), - key_manager_mrenclave: key_manager_mrenclave.clone(), - tcb_info: tcb_info.clone(), - }; - - if SGX_STATE.exists(deps.storage) { - return Err(ContractError::Unauthorized); - } - - SGX_STATE.save(deps.storage, &sgx_state)?; + // store the `(digest, ciphertext)` tuple + ObligationsItem::new(¤t_epoch_key(OBLIGATIONS_KEY, deps.storage)?).update( + deps.storage, + |mut obligations| { + if let Some(_duplicate) = obligations.insert(digest.clone(), ciphertext.clone()) { + return Err(ContractError::DuplicateEntry); + } + Ok(obligations) + }, + )?; Ok(Response::new() - .add_attribute("action", "bootstrap_key_manger") - .add_attribute("compute_mrenclave", compute_mrenclave) - .add_attribute("key_manager_mrenclave", key_manager_mrenclave) - .add_attribute("tcb_info", tcb_info)) + .add_attribute("action", "submit_obligation") + .add_attribute("digest", digest.to_string()) + .add_attribute("ciphertext", ciphertext.to_string())) } - pub fn register_epoch_key( + pub fn submit_setoffs( deps: DepsMut, - epoch_key: RawPublicKey, + setoffs_enc: BTreeMap, ) -> Result { - let _ = VerifyingKey::from_sec1_bytes(&hex::decode(&epoch_key)?)?; + // store the `BTreeMap` + SetoffsItem::new(¤t_epoch_key(SETOFFS_KEY, deps.storage)?) + .save(deps.storage, &setoffs_enc)?; - let epoch_state = EpochState { - epoch_key: epoch_key.clone(), - }; - EPOCH_STATE.save(deps.storage, &epoch_state)?; - - Ok(Response::new() - .add_attribute("action", "register_epoch_key") - .add_attribute("epoch_key", epoch_key)) - } - - pub fn enqueue_join_request( - deps: DepsMut, - io_exchange_key: RawPublicKey, - address: RawAddress, - nonce: RawNonce, - ) -> Result { - let _ = VerifyingKey::from_sec1_bytes(&hex::decode(&io_exchange_key)?)?; - let _ = deps.api.addr_validate(&address)?; - let _ = hex::decode(&nonce); - - let mut requests = REQUESTS.may_load(deps.storage)?.unwrap_or_default(); - requests.push(( - nonce, - Request::JoinComputeNode((io_exchange_key.clone(), address)), - )); - REQUESTS.save(deps.storage, &requests)?; - - Ok(Response::new() - .add_attribute("action", "enqueue_request") - .add_attribute("io_exchange_key", io_exchange_key)) + Ok(Response::new().add_attribute("action", "submit_setoffs")) } } #[cfg_attr(not(feature = "library"), entry_point)] -pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { - match msg { - QueryMsg::GetSgxState {} => to_json_binary(&query::get_sgx_state(deps)?), - QueryMsg::GetEpochState {} => to_json_binary(&query::get_epoch_state(deps)?), - QueryMsg::GetRequests {} => to_json_binary(&query::get_requests(deps)?), - } +pub fn query(_deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { + match msg {} } - -pub mod query { - use cosmwasm_std::{Deps, StdResult}; - - use crate::msg::query::{GetEpochStateResponse, GetRequestsResponse, GetSgxStateResponse}; - use crate::state::{EpochState, SgxState, EPOCH_STATE, REQUESTS, SGX_STATE}; - - pub fn get_sgx_state(deps: Deps) -> StdResult { - let SgxState { - compute_mrenclave, - key_manager_mrenclave, - .. - } = SGX_STATE.load(deps.storage)?; - Ok(GetSgxStateResponse { - compute_mrenclave, - key_manager_mrenclave, - }) - } - - pub fn get_epoch_state(deps: Deps) -> StdResult { - let EpochState { epoch_key } = EPOCH_STATE.load(deps.storage)?; - Ok(GetEpochStateResponse { epoch_key }) - } - - pub fn get_requests(deps: Deps) -> StdResult { - Ok(GetRequestsResponse { - requests: REQUESTS.load(deps.storage)?, - }) - } -} - -#[cfg(test)] -mod tests {} diff --git a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/error.rs b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/error.rs index f482c7b..9683c23 100644 --- a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/error.rs +++ b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/error.rs @@ -1,6 +1,7 @@ use cosmwasm_std::StdError; use hex::FromHexError; use k256::ecdsa::Error as K256Error; +use quartz_cw::error::Error as QuartzError; use thiserror::Error; #[derive(Error, Debug)] @@ -8,14 +9,20 @@ pub enum ContractError { #[error("{0}")] Std(#[from] StdError), + #[error("{0}")] + Quartz(#[from] QuartzError), + #[error("Unauthorized")] Unauthorized, + #[error("Duplicate entry found")] + DuplicateEntry, + #[error("Not Secp256K1")] K256(K256Error), #[error("Invalid hex")] - Hex(FromHexError), + Hex(#[from] FromHexError), #[error("Invalid length")] BadLength, @@ -23,12 +30,6 @@ pub enum ContractError { impl From for ContractError { fn from(e: K256Error) -> Self { - ContractError::K256(e) - } -} - -impl From for ContractError { - fn from(e: FromHexError) -> Self { - ContractError::Hex(e) + Self::K256(e) } } diff --git a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/msg.rs b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/msg.rs index 701b249..fff496e 100644 --- a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/msg.rs +++ b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/msg.rs @@ -1,67 +1,134 @@ +use std::collections::BTreeMap; + use cosmwasm_schema::{cw_serde, QueryResponses}; +use quartz_cw::prelude::*; + +use crate::state::{RawCipherText, RawHash}; #[cw_serde] -pub struct InstantiateMsg; +#[serde(transparent)] +pub struct InstantiateMsg(pub QuartzInstantiateMsg); #[cw_serde] +#[allow(clippy::large_enum_variant)] pub enum ExecuteMsg { - BootstrapKeyManager(execute::BootstrapKeyManagerMsg), - RegisterEpochKey(execute::RegisterEpochKeyMsg), - JoinComputeNode(execute::JoinComputeNodeMsg), + Quartz(QuartzExecuteMsg), + SubmitObligation(execute::SubmitObligationMsg), + SubmitSetoffs(execute::SubmitSetoffsMsg), } pub mod execute { + use cosmwasm_std::HexBinary; + use super::*; #[cw_serde] - pub struct BootstrapKeyManagerMsg { - pub compute_mrenclave: String, - pub key_manager_mrenclave: String, - pub tcb_info: String, + pub struct SubmitObligationMsg { + pub ciphertext: HexBinary, + pub digest: HexBinary, + // pub signatures: [HexBinary; 2], + // pub proof: π } #[cw_serde] - pub struct RegisterEpochKeyMsg { - pub epoch_key: String, - } - - #[cw_serde] - pub struct JoinComputeNodeMsg { - pub io_exchange_key: String, - pub address: String, - pub nonce: String, + pub struct SubmitSetoffsMsg { + pub setoffs_enc: BTreeMap, + // pub proof: π, } } - #[cw_serde] #[derive(QueryResponses)] -pub enum QueryMsg { - #[returns(query::GetSgxStateResponse)] - GetSgxState {}, - #[returns(query::GetEpochStateResponse)] - GetEpochState {}, - #[returns(query::GetRequestsResponse)] - GetRequests {}, -} +pub enum QueryMsg {} -pub mod query { +#[cfg(test)] +mod tests { use super::*; - use crate::state::{RawMrenclave, RawNonce, RawPublicKey, Request}; - - #[cw_serde] - pub struct GetSgxStateResponse { - pub compute_mrenclave: RawMrenclave, - pub key_manager_mrenclave: RawMrenclave, + #[test] + fn test_serde_instantiate_msg() { + let _: InstantiateMsg = serde_json::from_str( + r#"{ + "msg": { + "config": { + "mr_enclave": "1bfb949d235f61e5dc40f874ba3e9c36adef1e7a521b4b5f70e10fb1dc803251", + "epoch_duration": { + "secs": 43200, + "nanos": 0 + }, + "light_client_opts": { + "chain_id": "testing", + "trusted_height": 1, + "trusted_hash": "a1d115ba3a5e9fcc12ed68a9d8669159e9085f6f96ec26619f5c7ceb4ee02869", + "trust_threshold": [ + 2, + 3 + ], + "trusting_period": 1209600, + "max_clock_drift": 5, + "max_block_lag": 5 + } + } + }, + "attestation": { + "report": { + "report": { + "id": "5246688123689513540899231107533660789", + "timestamp": "2024-02-07T17:06:23.913745", + "version": 4, + "epidPseudonym": "+CUyIi74LPqS6M0NF7YrSxLqPdX3MKs6D6LIPqRG/ZEB4WmxZVvxAJwdwg/0m9cYnUUQguLnJotthX645lAogfJgO8Xg5/91lSegwyUKvHmKgtjOHX/YTbVe/wmgWiBdaL+KmarY0Je459Px/FqGLWLsAF7egPAJRd1Xn88Znrs=", + "advisoryURL": "https://security-center.intel.com", + "advisoryIDs": [ + "INTEL-SA-00161", + "INTEL-SA-00219", + "INTEL-SA-00289", + "INTEL-SA-00334", + "INTEL-SA-00615" + ], + "isvEnclaveQuoteStatus": "CONFIGURATION_AND_SW_HARDENING_NEEDED", + "platformInfoBlob": "150200650000080000141402040180070000000000000000000D00000C000000020000000000000CB0F08115F3DE71AE97980FE5E10B042054930ACE356C79EC44603D3F890756EC6ED73927A7C58CDE9AF1E754AEC77E335E8D80294407936BEB6404F27669FF7BB1", + "isvEnclaveQuoteBody": "AgABALAMAAAPAA8AAAAAAFHK9aSLRQ1iSu/jKG0xSJQAAAAAAAAAAAAAAAAAAAAAFBQCBwGAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAHAAAAAAAAAOPC8qW4QNieBprK/8rbZRDvhmpz06nuVxAO1fhkbuS7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc8uUpEUEPvz8ZkFapjVh5WlWaLoAJM/f80T0EhGInHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACRE7C+d+1dDWhoDsdyBrjVh+1AZ5txMhzN1UBeTVSmggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + }, + "reportsig": "YcY4SPvkfR4P2E8A5huutCeS+vY/ir+xq6disalNfNtAcUyOIOqTPVXhAZgY1M5B47Hjj1oYWf2qC2w+dnj7VcZjzO9oR0pJYdA+A7jaVrNzH2eXA79yICkuU8WE/x58I0j5vjXLoHXahaKlpZkMeTphqBY8u+FTVSdP3cWPho4viPapTfQRuEWmYq4KIq2zSr6wLg3Pz+yQ+G3e9BASVkLYxdYGTDFH1pMmfas9SEI7V4I+j8DaXmL8bucSRakmcQdmDMPGiA7mvIhSAlprzCrdxM7CHeUC6MPLN1fmFFcc9kyO/ved69j/651MWC83GgxSJ15L80U+DQzmrSW8xg==" + } + } + }"#, + ).expect("failed to deserialize hardcoded quartz instantiate msg"); } - #[cw_serde] - pub struct GetEpochStateResponse { - pub epoch_key: RawPublicKey, - } - - #[cw_serde] - pub struct GetRequestsResponse { - pub requests: Vec<(RawNonce, Request)>, + #[test] + fn test_serde_execute_msg() { + let _: ExecuteMsg = serde_json::from_str( + r#"{ + "quartz": { + "session_create": { + "msg": { + "nonce": "425d87f8620e1dedeee70590cc55b164b8f01480ee59e0b1da35436a2f7c2777" + }, + "attestation": { + "report": { + "report": { + "id": "5246688123689513540899231107533660789", + "timestamp": "2024-02-07T17:06:23.913745", + "version": 4, + "epidPseudonym": "+CUyIi74LPqS6M0NF7YrSxLqPdX3MKs6D6LIPqRG/ZEB4WmxZVvxAJwdwg/0m9cYnUUQguLnJotthX645lAogfJgO8Xg5/91lSegwyUKvHmKgtjOHX/YTbVe/wmgWiBdaL+KmarY0Je459Px/FqGLWLsAF7egPAJRd1Xn88Znrs=", + "advisoryURL": "https://security-center.intel.com", + "advisoryIDs": [ + "INTEL-SA-00161", + "INTEL-SA-00219", + "INTEL-SA-00289", + "INTEL-SA-00334", + "INTEL-SA-00615" + ], + "isvEnclaveQuoteStatus": "CONFIGURATION_AND_SW_HARDENING_NEEDED", + "platformInfoBlob": "150200650000080000141402040180070000000000000000000D00000C000000020000000000000CB0F08115F3DE71AE97980FE5E10B042054930ACE356C79EC44603D3F890756EC6ED73927A7C58CDE9AF1E754AEC77E335E8D80294407936BEB6404F27669FF7BB1", + "isvEnclaveQuoteBody": "AgABALAMAAAPAA8AAAAAAFHK9aSLRQ1iSu/jKG0xSJQAAAAAAAAAAAAAAAAAAAAAFBQCBwGAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABQAAAAAAAAAHAAAAAAAAAOPC8qW4QNieBprK/8rbZRDvhmpz06nuVxAO1fhkbuS7AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAc8uUpEUEPvz8ZkFapjVh5WlWaLoAJM/f80T0EhGInHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACRE7C+d+1dDWhoDsdyBrjVh+1AZ5txMhzN1UBeTVSmggAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + }, + "reportsig": "YcY4SPvkfR4P2E8A5huutCeS+vY/ir+xq6disalNfNtAcUyOIOqTPVXhAZgY1M5B47Hjj1oYWf2qC2w+dnj7VcZjzO9oR0pJYdA+A7jaVrNzH2eXA79yICkuU8WE/x58I0j5vjXLoHXahaKlpZkMeTphqBY8u+FTVSdP3cWPho4viPapTfQRuEWmYq4KIq2zSr6wLg3Pz+yQ+G3e9BASVkLYxdYGTDFH1pMmfas9SEI7V4I+j8DaXmL8bucSRakmcQdmDMPGiA7mvIhSAlprzCrdxM7CHeUC6MPLN1fmFFcc9kyO/ved69j/651MWC83GgxSJ15L80U+DQzmrSW8xg==" + } + } + } + } + }"#, + ).expect("failed to deserialize hardcoded quartz msg"); } } diff --git a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/state.rs b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/state.rs index c618384..f49ac64 100644 --- a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/state.rs +++ b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/state.rs @@ -1,37 +1,25 @@ +use std::collections::BTreeMap; + use cosmwasm_schema::cw_serde; +use cosmwasm_std::{HexBinary, StdError, Storage}; use cw_storage_plus::Item; +use quartz_cw::state::EPOCH_COUNTER; -pub type RawNonce = String; -pub type RawPublicKey = String; -pub type RawAddress = String; -pub type RawMrenclave = String; -pub type RawTcbInfo = String; +pub type RawHash = HexBinary; +pub type RawCipherText = HexBinary; -pub type Mrenclave = [u8; 32]; +pub type ObligationsItem<'a> = Item<'a, BTreeMap>; +pub type SetoffsItem<'a> = Item<'a, BTreeMap>; #[cw_serde] pub struct State { pub owner: String, } -#[cw_serde] -pub enum Request { - JoinComputeNode((RawPublicKey, RawAddress)), -} - -#[cw_serde] -pub struct SgxState { - pub compute_mrenclave: RawMrenclave, - pub key_manager_mrenclave: RawMrenclave, - pub tcb_info: RawTcbInfo, -} - -#[cw_serde] -pub struct EpochState { - pub epoch_key: RawPublicKey, -} - pub const STATE: Item = Item::new("state"); -pub const REQUESTS: Item> = Item::new("requests"); -pub const SGX_STATE: Item = Item::new("sgx_state"); -pub const EPOCH_STATE: Item = Item::new("epoch_state"); +pub const OBLIGATIONS_KEY: &str = "obligations"; +pub const SETOFFS_KEY: &str = "setoffs"; + +pub fn current_epoch_key(key: &str, storage: &dyn Storage) -> Result { + Ok(format!("{}/{key}", EPOCH_COUNTER.load(storage)?)) +} diff --git a/bisenzone-cw-mvp/packages/quartz-cw/Cargo.toml b/bisenzone-cw-mvp/packages/quartz-cw/Cargo.toml index f09e59a..b1b0459 100644 --- a/bisenzone-cw-mvp/packages/quartz-cw/Cargo.toml +++ b/bisenzone-cw-mvp/packages/quartz-cw/Cargo.toml @@ -10,6 +10,7 @@ cosmwasm-schema = "1.4.0" cosmwasm-std = "1.4.0" k256 = { version = "0.13.2", default-features = false, features = ["ecdsa", "alloc"] } serde = { version = "1.0.188", default-features = false, features = ["derive"] } +serde_json = "1.0.94" sha2 = "0.10.8" thiserror = "1.0.57" diff --git a/bisenzone-cw-mvp/packages/quartz-cw/src/handler/execute.rs b/bisenzone-cw-mvp/packages/quartz-cw/src/handler/execute.rs index 7d6ad47..b0d4d99 100644 --- a/bisenzone-cw-mvp/packages/quartz-cw/src/handler/execute.rs +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/handler/execute.rs @@ -6,9 +6,14 @@ use cosmwasm_std::{DepsMut, Env, MessageInfo, Response}; use crate::error::Error; use crate::handler::Handler; +use crate::msg::execute::attested::Attestation; +use crate::msg::execute::attested::HasUserData; use crate::msg::execute::Execute; -impl Handler for Execute { +impl Handler for Execute +where + A: Handler + HasUserData + Attestation, +{ fn handle(self, deps: DepsMut<'_>, env: &Env, info: &MessageInfo) -> Result { match self { Execute::SessionCreate(msg) => msg.handle(deps, env, info), diff --git a/bisenzone-cw-mvp/packages/quartz-cw/src/handler/execute/attested.rs b/bisenzone-cw-mvp/packages/quartz-cw/src/handler/execute/attested.rs index f2dc5e0..3da23da 100644 --- a/bisenzone-cw-mvp/packages/quartz-cw/src/handler/execute/attested.rs +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/handler/execute/attested.rs @@ -3,7 +3,9 @@ use quartz_tee_ra::{verify_epid_attestation, Error as RaVerificationError}; use crate::error::Error; use crate::handler::Handler; -use crate::msg::execute::attested::{Attestation, Attested, EpidAttestation, HasUserData}; +use crate::msg::execute::attested::{ + Attestation, Attested, EpidAttestation, HasUserData, MockAttestation, +}; use crate::state::CONFIG; impl Handler for EpidAttestation { @@ -13,10 +15,25 @@ impl Handler for EpidAttestation { _env: &Env, _info: &MessageInfo, ) -> Result { - let (report, mr_enclave, user_data) = self.into_tuple(); - verify_epid_attestation(report, mr_enclave, user_data) - .map(|_| Response::default()) - .map_err(Error::RaVerification) + // attestation handler MUST verify that the user_data and mr_enclave match the config/msg + verify_epid_attestation( + self.clone().into_report(), + self.mr_enclave(), + self.user_data(), + ) + .map(|_| Response::default()) + .map_err(Error::RaVerification) + } +} + +impl Handler for MockAttestation { + fn handle( + self, + _deps: DepsMut<'_>, + _env: &Env, + _info: &MessageInfo, + ) -> Result { + Ok(Response::default()) } } @@ -38,12 +55,17 @@ where if let Some(config) = CONFIG.may_load(deps.storage)? { // if we weren't able to load then the context was from InstantiateMsg so we don't fail - if *config.mr_enclave() != attestation.mr_enclave() { + // in such cases, the InstantiateMsg handler will verify that the mr_enclave matches + if config.mr_enclave() != attestation.mr_enclave() { return Err(RaVerificationError::MrEnclaveMismatch.into()); } } - Handler::handle(attestation, deps.branch(), env, info)?; - Handler::handle(msg, deps, env, info) + // handle message first, this has 2 benefits - + // 1. we avoid (the more expensive) attestation verification if the message handler fails + // 2. we allow the message handler to make changes to the config so that the attestation + // handler can use those changes, e.g. InstantiateMsg + Handler::handle(msg, deps.branch(), env, info)?; + Handler::handle(attestation, deps, env, info) } } diff --git a/bisenzone-cw-mvp/packages/quartz-cw/src/handler/instantiate.rs b/bisenzone-cw-mvp/packages/quartz-cw/src/handler/instantiate.rs index ab25d04..e59419b 100644 --- a/bisenzone-cw-mvp/packages/quartz-cw/src/handler/instantiate.rs +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/handler/instantiate.rs @@ -3,25 +3,34 @@ use quartz_tee_ra::Error as RaVerificationError; use crate::error::Error; use crate::handler::Handler; -use crate::msg::execute::attested::{Attestation, EpidAttestation}; +use crate::msg::execute::attested::{Attestation, EpidAttestation, MockAttestation}; use crate::msg::instantiate::{CoreInstantiate, Instantiate}; -use crate::state::Config; use crate::state::CONFIG; +use crate::state::{RawConfig, EPOCH_COUNTER}; impl Handler for Instantiate { fn handle(self, deps: DepsMut<'_>, env: &Env, info: &MessageInfo) -> Result { - if self.0.msg().mr_enclave() != self.0.attestation().mr_enclave() { + if self.0.msg().config().mr_enclave() != self.0.attestation().mr_enclave() { return Err(RaVerificationError::MrEnclaveMismatch.into()); } self.0.handle(deps, env, info) } } +impl Handler for Instantiate { + fn handle(self, deps: DepsMut<'_>, env: &Env, info: &MessageInfo) -> Result { + self.0.handle(deps, env, info) + } +} + impl Handler for CoreInstantiate { fn handle(self, deps: DepsMut<'_>, _env: &Env, _info: &MessageInfo) -> Result { CONFIG - .save(deps.storage, &Config::new(self.mr_enclave())) + .save(deps.storage, &RawConfig::from(self.config().clone())) .map_err(Error::Std)?; + + EPOCH_COUNTER.save(deps.storage, &1).map_err(Error::Std)?; + Ok(Response::new().add_attribute("action", "instantiate")) } } diff --git a/bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute.rs b/bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute.rs index f21dafc..f6f1ed1 100644 --- a/bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute.rs +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute.rs @@ -55,9 +55,9 @@ where } } -impl HasDomainType for RawExecute +impl HasDomainType for RawExecute where - A: HasDomainType, + RA: HasDomainType, { - type DomainType = Execute; + type DomainType = Execute; } diff --git a/bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute/attested.rs b/bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute/attested.rs index 1e252fe..31de243 100644 --- a/bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute/attested.rs +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute/attested.rs @@ -1,5 +1,5 @@ use cosmwasm_schema::cw_serde; -use cosmwasm_std::{HexBinary, StdError}; +use cosmwasm_std::StdError; use quartz_tee_ra::IASReport; use crate::msg::HasDomainType; @@ -12,6 +12,10 @@ pub struct Attested { } impl Attested { + pub fn new(msg: M, attestation: A) -> Self { + Self { msg, attestation } + } + pub fn into_tuple(self) -> (M, A) { let Attested { msg, attestation } = self; (msg, attestation) @@ -75,42 +79,29 @@ pub trait HasUserData { #[derive(Clone, Debug, PartialEq)] pub struct EpidAttestation { report: IASReport, - mr_enclave: MrEnclave, - user_data: UserData, } impl EpidAttestation { - pub fn into_tuple(self) -> (IASReport, MrEnclave, UserData) { - let EpidAttestation { - report, - mr_enclave, - user_data, - } = self; - (report, mr_enclave, user_data) + pub fn new(report: IASReport) -> Self { + Self { report } } - pub fn report(&self) -> &IASReport { - &self.report + pub fn into_report(self) -> IASReport { + self.report } } #[cw_serde] pub struct RawEpidAttestation { report: IASReport, - mr_enclave: HexBinary, - user_data: HexBinary, } impl TryFrom for EpidAttestation { type Error = StdError; fn try_from(value: RawEpidAttestation) -> Result { - let mr_enclave = value.mr_enclave.to_array()?; - let user_data = value.user_data.to_array()?; Ok(Self { report: value.report, - mr_enclave, - user_data, }) } } @@ -119,8 +110,6 @@ impl From for RawEpidAttestation { fn from(value: EpidAttestation) -> Self { Self { report: value.report, - mr_enclave: value.mr_enclave.into(), - user_data: value.user_data.into(), } } } @@ -131,7 +120,7 @@ impl HasDomainType for RawEpidAttestation { impl HasUserData for EpidAttestation { fn user_data(&self) -> UserData { - self.user_data + self.report.report.isv_enclave_quote_body.user_data() } } @@ -141,6 +130,42 @@ pub trait Attestation { impl Attestation for EpidAttestation { fn mr_enclave(&self) -> MrEnclave { - self.report().report.isv_enclave_quote_body.mrenclave() + self.report.report.isv_enclave_quote_body.mrenclave() + } +} + +#[derive(Clone, Debug, PartialEq)] +pub struct MockAttestation; + +#[cw_serde] +pub struct RawMockAttestation; + +impl TryFrom for MockAttestation { + type Error = StdError; + + fn try_from(_value: RawMockAttestation) -> Result { + Ok(Self) + } +} + +impl From for RawMockAttestation { + fn from(_value: MockAttestation) -> Self { + Self + } +} + +impl HasDomainType for RawMockAttestation { + type DomainType = MockAttestation; +} + +impl HasUserData for MockAttestation { + fn user_data(&self) -> UserData { + unimplemented!("MockAttestation handler is a noop") + } +} + +impl Attestation for MockAttestation { + fn mr_enclave(&self) -> MrEnclave { + unimplemented!("MockAttestation handler is a noop") } } diff --git a/bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute/session_create.rs b/bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute/session_create.rs index d515421..7603555 100644 --- a/bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute/session_create.rs +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute/session_create.rs @@ -11,6 +11,10 @@ pub struct SessionCreate { } impl SessionCreate { + pub fn new(nonce: Nonce) -> Self { + Self { nonce } + } + pub fn into_nonce(self) -> Nonce { self.nonce } diff --git a/bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute/session_set_pub_key.rs b/bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute/session_set_pub_key.rs index 9a0db70..47b3c34 100644 --- a/bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute/session_set_pub_key.rs +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute/session_set_pub_key.rs @@ -15,6 +15,10 @@ pub struct SessionSetPubKey { } impl SessionSetPubKey { + pub fn new(nonce: Nonce, pub_key: VerifyingKey) -> Self { + Self { nonce, pub_key } + } + pub fn into_tuple(self) -> (Nonce, VerifyingKey) { (self.nonce, self.pub_key) } diff --git a/bisenzone-cw-mvp/packages/quartz-cw/src/msg/instantiate.rs b/bisenzone-cw-mvp/packages/quartz-cw/src/msg/instantiate.rs index 03659c7..6f2c2ef 100644 --- a/bisenzone-cw-mvp/packages/quartz-cw/src/msg/instantiate.rs +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/msg/instantiate.rs @@ -1,15 +1,15 @@ use cosmwasm_schema::cw_serde; -use cosmwasm_std::{HexBinary, StdError}; +use cosmwasm_std::StdError; use sha2::{Digest, Sha256}; use crate::msg::execute::attested::{ Attested, EpidAttestation, HasUserData, RawAttested, RawEpidAttestation, }; use crate::msg::HasDomainType; -use crate::state::{MrEnclave, UserData}; +use crate::state::{Config, RawConfig, UserData}; #[derive(Clone, Debug, PartialEq)] -pub struct Instantiate(pub(crate) Attested); +pub struct Instantiate(pub Attested); #[cw_serde] pub struct RawInstantiate(RawAttested); @@ -34,40 +34,47 @@ where } } -impl HasDomainType for RawInstantiate { - type DomainType = Instantiate; +impl HasDomainType for RawInstantiate +where + RA: HasDomainType, +{ + type DomainType = Instantiate; } #[derive(Clone, Debug, PartialEq)] pub struct CoreInstantiate { - mr_enclave: MrEnclave, - // TODO(hu55a1n1): config - e.g. Epoch duration, light client opts + config: Config, } impl CoreInstantiate { - pub fn mr_enclave(&self) -> MrEnclave { - self.mr_enclave + pub fn new(config: Config) -> Self { + Self { config } + } + + pub fn config(&self) -> &Config { + &self.config } } #[cw_serde] pub struct RawCoreInstantiate { - mr_enclave: HexBinary, + config: RawConfig, } impl TryFrom for CoreInstantiate { type Error = StdError; fn try_from(value: RawCoreInstantiate) -> Result { - let mr_enclave = value.mr_enclave.to_array()?; - Ok(Self { mr_enclave }) + Ok(Self { + config: value.config.try_into()?, + }) } } impl From for RawCoreInstantiate { fn from(value: CoreInstantiate) -> Self { Self { - mr_enclave: value.mr_enclave.into(), + config: value.config.into(), } } } @@ -79,7 +86,10 @@ impl HasDomainType for RawCoreInstantiate { impl HasUserData for CoreInstantiate { fn user_data(&self) -> UserData { let mut hasher = Sha256::new(); - hasher.update(self.mr_enclave); + hasher.update( + serde_json::to_string(&RawConfig::from(self.config.clone())) + .expect("infallible serializer"), + ); let digest: [u8; 32] = hasher.finalize().into(); let mut user_data = [0u8; 64]; diff --git a/bisenzone-cw-mvp/packages/quartz-cw/src/state.rs b/bisenzone-cw-mvp/packages/quartz-cw/src/state.rs index 5e3cb06..b7edf88 100644 --- a/bisenzone-cw-mvp/packages/quartz-cw/src/state.rs +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/state.rs @@ -1,26 +1,199 @@ +use core::time::Duration; + use cosmwasm_schema::cw_serde; -use cosmwasm_std::HexBinary; +use cosmwasm_std::{HexBinary, StdError}; use cw_storage_plus::Item; use k256::ecdsa::VerifyingKey; pub type MrEnclave = [u8; 32]; pub type Nonce = [u8; 32]; pub type UserData = [u8; 64]; +pub type Hash = [u8; 32]; +pub type Height = u64; +pub type TrustThreshold = (u64, u64); -#[cw_serde] +#[derive(Clone, Debug, PartialEq)] pub struct Config { - mr_enclave: HexBinary, + mr_enclave: MrEnclave, + epoch_duration: Duration, + light_client_opts: LightClientOpts, } impl Config { - pub fn new(mr_enclave: MrEnclave) -> Self { + pub fn new( + mr_enclave: MrEnclave, + epoch_duration: Duration, + light_client_opts: LightClientOpts, + ) -> Self { Self { - mr_enclave: mr_enclave.into(), + mr_enclave, + epoch_duration, + light_client_opts, } } - pub fn mr_enclave(&self) -> &HexBinary { - &self.mr_enclave + pub fn light_client_opts(&self) -> &LightClientOpts { + &self.light_client_opts + } + + pub fn mr_enclave(&self) -> MrEnclave { + self.mr_enclave + } +} + +#[cw_serde] +pub struct RawConfig { + mr_enclave: HexBinary, + epoch_duration: Duration, + light_client_opts: RawLightClientOpts, +} + +impl RawConfig { + pub fn mr_enclave(&self) -> &[u8] { + self.mr_enclave.as_slice() + } +} + +impl TryFrom for Config { + type Error = StdError; + + fn try_from(value: RawConfig) -> Result { + Ok(Self { + mr_enclave: value.mr_enclave.to_array()?, + epoch_duration: value.epoch_duration, + light_client_opts: value + .light_client_opts + .try_into() + .map_err(|e| StdError::parse_err("light_client_opts", e))?, + }) + } +} + +impl From for RawConfig { + fn from(value: Config) -> Self { + Self { + mr_enclave: value.mr_enclave.into(), + epoch_duration: value.epoch_duration, + light_client_opts: value.light_client_opts.into(), + } + } +} + +#[derive(Clone, Debug, PartialEq)] +pub struct LightClientOpts { + chain_id: String, + trusted_height: Height, + trusted_hash: Hash, + trust_threshold: TrustThreshold, + trusting_period: u64, + max_clock_drift: u64, + max_block_lag: u64, +} + +impl LightClientOpts { + #[allow(clippy::too_many_arguments)] + pub fn new( + chain_id: String, + trusted_height: Height, + trusted_hash: Hash, + trust_threshold: TrustThreshold, + trusting_period: u64, + max_clock_drift: u64, + max_block_lag: u64, + ) -> Result { + let (numerator, denominator) = (trust_threshold.0, trust_threshold.1); + if numerator > denominator { + return Err(StdError::generic_err("trust_threshold_too_large")); + } + if denominator == 0 { + return Err(StdError::generic_err("undefined_trust_threshold")); + } + if 3 * numerator < denominator { + return Err(StdError::generic_err("trust_threshold_too_small")); + } + + let _trusted_height: i64 = trusted_height + .try_into() + .map_err(|_| StdError::generic_err("trusted_height too large"))?; + + Ok(Self { + chain_id, + trusted_height, + trusted_hash, + trust_threshold, + trusting_period, + max_clock_drift, + max_block_lag, + }) + } + + pub fn chain_id(&self) -> &String { + &self.chain_id + } + + pub fn trusted_height(&self) -> Height { + self.trusted_height + } + + pub fn trusted_hash(&self) -> &Hash { + &self.trusted_hash + } + + pub fn trust_threshold(&self) -> &TrustThreshold { + &self.trust_threshold + } + + pub fn trusting_period(&self) -> u64 { + self.trusting_period + } + + pub fn max_clock_drift(&self) -> u64 { + self.max_clock_drift + } + + pub fn max_block_lag(&self) -> u64 { + self.max_block_lag + } +} + +#[cw_serde] +pub struct RawLightClientOpts { + chain_id: String, + trusted_height: u64, + trusted_hash: HexBinary, + trust_threshold: (u64, u64), + trusting_period: u64, + max_clock_drift: u64, + max_block_lag: u64, +} + +impl TryFrom for LightClientOpts { + type Error = StdError; + + fn try_from(value: RawLightClientOpts) -> Result { + Self::new( + value.chain_id, + value.trusted_height, + value.trusted_hash.to_array()?, + (value.trust_threshold.0, value.trust_threshold.1), + value.trusting_period, + value.max_clock_drift, + value.max_block_lag, + ) + } +} + +impl From for RawLightClientOpts { + fn from(value: LightClientOpts) -> Self { + Self { + chain_id: value.chain_id, + trusted_height: value.trusted_height, + trusted_hash: Vec::::from(value.trusted_hash).into(), + trust_threshold: (value.trust_threshold.0, value.trust_threshold.1), + trusting_period: value.trusting_period, + max_clock_drift: value.max_clock_drift, + max_block_lag: value.max_block_lag, + } } } @@ -46,7 +219,12 @@ impl Session { None } } + + pub fn nonce(&self) -> Nonce { + self.nonce.to_array().expect("correct by construction") + } } -pub const CONFIG: Item<'_, Config> = Item::new("quartz_config"); +pub const CONFIG: Item<'_, RawConfig> = Item::new("quartz_config"); pub const SESSION: Item<'_, Session> = Item::new("quartz_session"); +pub const EPOCH_COUNTER: Item<'_, usize> = Item::new("epoch_counter"); diff --git a/bisenzone-cw-mvp/scripts/deploy-contract.sh b/bisenzone-cw-mvp/scripts/deploy-contract.sh index 9deca3e..30d6aec 100755 --- a/bisenzone-cw-mvp/scripts/deploy-contract.sh +++ b/bisenzone-cw-mvp/scripts/deploy-contract.sh @@ -25,6 +25,7 @@ WASM_BIN="$1" CHAIN_ID=${CHAIN_ID:-testing} LABEL=${LABEL:-bisenzone-mvp} COUNT=${COUNT:-0} +INSTANTIATE_MSG=${INSTANTIATE_MSG:-"{}"} TXFLAG="--chain-id ${CHAIN_ID} --gas-prices 0.0025ucosm --gas auto --gas-adjustment 1.3" @@ -45,7 +46,7 @@ echo "--------------------------------------------------------" echo "Label: ${LABEL}" echo "--------------------------------------------------------" -wasmd tx wasm instantiate "$CODE_ID" "null" --from "$USER_ADDR" --label $LABEL $TXFLAG -y --no-admin 2>&1 > /dev/null +wasmd tx wasm instantiate "$CODE_ID" "$INSTANTIATE_MSG" --from "$USER_ADDR" --label $LABEL $TXFLAG -y --no-admin 2>&1 > /dev/null echo "" echo "🕐 Waiting for contract to be queryable..."