Adapt cw-tee-mtcs to use quartz (#12)

This commit is contained in:
Shoaib Ahmed 2024-03-20 02:49:38 +05:30 committed by GitHub
parent b4e5f01cd1
commit 57b2a050ff
16 changed files with 507 additions and 264 deletions

View file

@ -54,5 +54,8 @@ schemars = "0.8.15"
serde = { version = "1.0.189", default-features = false, features = ["derive"] } serde = { version = "1.0.189", default-features = false, features = ["derive"] }
thiserror = { version = "1.0.49" } thiserror = { version = "1.0.49" }
quartz-cw = { path = "../../packages/quartz-cw" }
[dev-dependencies] [dev-dependencies]
cw-multi-test = "0.17.0" cw-multi-test = "0.17.0"
serde_json = "1.0.113"

View file

@ -1,12 +1,12 @@
use cosmwasm_std::{ use cosmwasm_std::{entry_point, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult};
entry_point, to_json_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult,
};
use cw2::set_contract_version; use cw2::set_contract_version;
use quartz_cw::handler::RawHandler;
use crate::error::ContractError; use crate::error::ContractError;
use crate::msg::execute::{BootstrapKeyManagerMsg, JoinComputeNodeMsg, RegisterEpochKeyMsg}; use crate::msg::execute::{SubmitObligationMsg, SubmitSetoffsMsg};
use crate::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; use crate::msg::QueryMsg;
use crate::state::{State, STATE}; use crate::msg::{ExecuteMsg, InstantiateMsg};
use crate::state::{current_epoch_key, ObligationsItem, State, OBLIGATIONS_KEY, STATE};
// version info for migration info // version info for migration info
const CONTRACT_NAME: &str = "crates.io:cw-tee-mtcs"; 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)] #[cfg_attr(not(feature = "library"), entry_point)]
pub fn instantiate( pub fn instantiate(
deps: DepsMut, mut deps: DepsMut,
_env: Env, env: Env,
info: MessageInfo, info: MessageInfo,
_msg: InstantiateMsg, msg: InstantiateMsg,
) -> Result<Response, ContractError> { ) -> Result<Response, ContractError> {
// must be the handled first!
msg.0.handle_raw(deps.branch(), &env, &info)?;
let state = State { let state = State {
owner: info.sender.to_string(), owner: info.sender.to_string(),
}; };
set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?; set_contract_version(deps.storage, CONTRACT_NAME, CONTRACT_VERSION)?;
STATE.save(deps.storage, &state)?; STATE.save(deps.storage, &state)?;
ObligationsItem::new(&current_epoch_key(OBLIGATIONS_KEY, deps.storage)?)
.save(deps.storage, &Default::default())?;
Ok(Response::new() Ok(Response::new()
.add_attribute("method", "instantiate") .add_attribute("method", "instantiate")
.add_attribute("owner", info.sender)) .add_attribute("owner", info.sender))
@ -33,151 +39,70 @@ pub fn instantiate(
#[cfg_attr(not(feature = "library"), entry_point)] #[cfg_attr(not(feature = "library"), entry_point)]
pub fn execute( pub fn execute(
deps: DepsMut, deps: DepsMut,
_env: Env, env: Env,
_info: MessageInfo, info: MessageInfo,
msg: ExecuteMsg, msg: ExecuteMsg,
) -> Result<Response, ContractError> { ) -> Result<Response, ContractError> {
match msg { match msg {
ExecuteMsg::BootstrapKeyManager(BootstrapKeyManagerMsg { ExecuteMsg::Quartz(msg) => msg.handle_raw(deps, &env, &info).map_err(Into::into),
compute_mrenclave, ExecuteMsg::SubmitObligation(SubmitObligationMsg { ciphertext, digest }) => {
key_manager_mrenclave, execute::submit_obligation(deps, ciphertext, digest)
tcb_info,
}) => {
execute::bootstrap_key_manger(deps, compute_mrenclave, key_manager_mrenclave, tcb_info)
} }
ExecuteMsg::RegisterEpochKey(RegisterEpochKeyMsg { epoch_key }) => { ExecuteMsg::SubmitSetoffs(SubmitSetoffsMsg { setoffs_enc }) => {
execute::register_epoch_key(deps, epoch_key) 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 { pub mod execute {
use cosmwasm_std::{DepsMut, Response}; use std::collections::BTreeMap;
use k256::ecdsa::VerifyingKey;
use cosmwasm_std::{DepsMut, HexBinary, Response};
use quartz_cw::state::Hash;
use crate::state::{ use crate::state::{
EpochState, Mrenclave, RawAddress, RawMrenclave, RawNonce, RawPublicKey, RawTcbInfo, current_epoch_key, ObligationsItem, RawCipherText, RawHash, SetoffsItem, OBLIGATIONS_KEY,
SgxState, EPOCH_STATE, SGX_STATE, SETOFFS_KEY,
}; };
use crate::state::{Request, REQUESTS};
use crate::ContractError; use crate::ContractError;
use crate::ContractError::BadLength;
pub fn bootstrap_key_manger( pub fn submit_obligation(
deps: DepsMut, deps: DepsMut,
compute_mrenclave: RawMrenclave, ciphertext: HexBinary,
key_manager_mrenclave: RawMrenclave, digest: HexBinary,
tcb_info: RawTcbInfo,
) -> Result<Response, ContractError> { ) -> Result<Response, ContractError> {
let _: Mrenclave = hex::decode(&compute_mrenclave)? let _: Hash = digest.to_array()?;
.try_into()
.map_err(|_| BadLength)?;
let _: Mrenclave = hex::decode(&key_manager_mrenclave)?
.try_into()
.map_err(|_| BadLength)?;
// TODO(hu55a1n1): validate TcbInfo
let sgx_state = SgxState { // store the `(digest, ciphertext)` tuple
compute_mrenclave: compute_mrenclave.clone(), ObligationsItem::new(&current_epoch_key(OBLIGATIONS_KEY, deps.storage)?).update(
key_manager_mrenclave: key_manager_mrenclave.clone(), deps.storage,
tcb_info: tcb_info.clone(), |mut obligations| {
}; if let Some(_duplicate) = obligations.insert(digest.clone(), ciphertext.clone()) {
return Err(ContractError::DuplicateEntry);
if SGX_STATE.exists(deps.storage) {
return Err(ContractError::Unauthorized);
} }
Ok(obligations)
SGX_STATE.save(deps.storage, &sgx_state)?; },
)?;
Ok(Response::new() Ok(Response::new()
.add_attribute("action", "bootstrap_key_manger") .add_attribute("action", "submit_obligation")
.add_attribute("compute_mrenclave", compute_mrenclave) .add_attribute("digest", digest.to_string())
.add_attribute("key_manager_mrenclave", key_manager_mrenclave) .add_attribute("ciphertext", ciphertext.to_string()))
.add_attribute("tcb_info", tcb_info))
} }
pub fn register_epoch_key( pub fn submit_setoffs(
deps: DepsMut, deps: DepsMut,
epoch_key: RawPublicKey, setoffs_enc: BTreeMap<RawHash, RawCipherText>,
) -> Result<Response, ContractError> { ) -> Result<Response, ContractError> {
let _ = VerifyingKey::from_sec1_bytes(&hex::decode(&epoch_key)?)?; // store the `BTreeMap<RawHash, RawCipherText>`
SetoffsItem::new(&current_epoch_key(SETOFFS_KEY, deps.storage)?)
.save(deps.storage, &setoffs_enc)?;
let epoch_state = EpochState { Ok(Response::new().add_attribute("action", "submit_setoffs"))
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<Response, ContractError> {
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))
} }
} }
#[cfg_attr(not(feature = "library"), entry_point)] #[cfg_attr(not(feature = "library"), entry_point)]
pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> { pub fn query(_deps: Deps, _env: Env, msg: QueryMsg) -> StdResult<Binary> {
match msg { 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 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<GetSgxStateResponse> {
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<GetEpochStateResponse> {
let EpochState { epoch_key } = EPOCH_STATE.load(deps.storage)?;
Ok(GetEpochStateResponse { epoch_key })
}
pub fn get_requests(deps: Deps) -> StdResult<GetRequestsResponse> {
Ok(GetRequestsResponse {
requests: REQUESTS.load(deps.storage)?,
})
}
}
#[cfg(test)]
mod tests {}

View file

@ -1,6 +1,7 @@
use cosmwasm_std::StdError; use cosmwasm_std::StdError;
use hex::FromHexError; use hex::FromHexError;
use k256::ecdsa::Error as K256Error; use k256::ecdsa::Error as K256Error;
use quartz_cw::error::Error as QuartzError;
use thiserror::Error; use thiserror::Error;
#[derive(Error, Debug)] #[derive(Error, Debug)]
@ -8,14 +9,20 @@ pub enum ContractError {
#[error("{0}")] #[error("{0}")]
Std(#[from] StdError), Std(#[from] StdError),
#[error("{0}")]
Quartz(#[from] QuartzError),
#[error("Unauthorized")] #[error("Unauthorized")]
Unauthorized, Unauthorized,
#[error("Duplicate entry found")]
DuplicateEntry,
#[error("Not Secp256K1")] #[error("Not Secp256K1")]
K256(K256Error), K256(K256Error),
#[error("Invalid hex")] #[error("Invalid hex")]
Hex(FromHexError), Hex(#[from] FromHexError),
#[error("Invalid length")] #[error("Invalid length")]
BadLength, BadLength,
@ -23,12 +30,6 @@ pub enum ContractError {
impl From<K256Error> for ContractError { impl From<K256Error> for ContractError {
fn from(e: K256Error) -> Self { fn from(e: K256Error) -> Self {
ContractError::K256(e) Self::K256(e)
}
}
impl From<FromHexError> for ContractError {
fn from(e: FromHexError) -> Self {
ContractError::Hex(e)
} }
} }

View file

@ -1,67 +1,134 @@
use std::collections::BTreeMap;
use cosmwasm_schema::{cw_serde, QueryResponses}; use cosmwasm_schema::{cw_serde, QueryResponses};
use quartz_cw::prelude::*;
use crate::state::{RawCipherText, RawHash};
#[cw_serde] #[cw_serde]
pub struct InstantiateMsg; #[serde(transparent)]
pub struct InstantiateMsg(pub QuartzInstantiateMsg);
#[cw_serde] #[cw_serde]
#[allow(clippy::large_enum_variant)]
pub enum ExecuteMsg { pub enum ExecuteMsg {
BootstrapKeyManager(execute::BootstrapKeyManagerMsg), Quartz(QuartzExecuteMsg),
RegisterEpochKey(execute::RegisterEpochKeyMsg), SubmitObligation(execute::SubmitObligationMsg),
JoinComputeNode(execute::JoinComputeNodeMsg), SubmitSetoffs(execute::SubmitSetoffsMsg),
} }
pub mod execute { pub mod execute {
use cosmwasm_std::HexBinary;
use super::*; use super::*;
#[cw_serde] #[cw_serde]
pub struct BootstrapKeyManagerMsg { pub struct SubmitObligationMsg {
pub compute_mrenclave: String, pub ciphertext: HexBinary,
pub key_manager_mrenclave: String, pub digest: HexBinary,
pub tcb_info: String, // pub signatures: [HexBinary; 2],
// pub proof: π
} }
#[cw_serde] #[cw_serde]
pub struct RegisterEpochKeyMsg { pub struct SubmitSetoffsMsg {
pub epoch_key: String, pub setoffs_enc: BTreeMap<RawHash, RawCipherText>,
} // pub proof: π,
#[cw_serde]
pub struct JoinComputeNodeMsg {
pub io_exchange_key: String,
pub address: String,
pub nonce: String,
} }
} }
#[cw_serde] #[cw_serde]
#[derive(QueryResponses)] #[derive(QueryResponses)]
pub enum QueryMsg { pub enum QueryMsg {}
#[returns(query::GetSgxStateResponse)]
GetSgxState {},
#[returns(query::GetEpochStateResponse)]
GetEpochState {},
#[returns(query::GetRequestsResponse)]
GetRequests {},
}
pub mod query { #[cfg(test)]
mod tests {
use super::*; use super::*;
use crate::state::{RawMrenclave, RawNonce, RawPublicKey, Request}; #[test]
fn test_serde_instantiate_msg() {
#[cw_serde] let _: InstantiateMsg = serde_json::from_str(
pub struct GetSgxStateResponse { r#"{
pub compute_mrenclave: RawMrenclave, "msg": {
pub key_manager_mrenclave: RawMrenclave, "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] #[test]
pub struct GetEpochStateResponse { fn test_serde_execute_msg() {
pub epoch_key: RawPublicKey, let _: ExecuteMsg = serde_json::from_str(
} r#"{
"quartz": {
#[cw_serde] "session_create": {
pub struct GetRequestsResponse { "msg": {
pub requests: Vec<(RawNonce, Request)>, "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");
} }
} }

View file

@ -1,37 +1,25 @@
use std::collections::BTreeMap;
use cosmwasm_schema::cw_serde; use cosmwasm_schema::cw_serde;
use cosmwasm_std::{HexBinary, StdError, Storage};
use cw_storage_plus::Item; use cw_storage_plus::Item;
use quartz_cw::state::EPOCH_COUNTER;
pub type RawNonce = String; pub type RawHash = HexBinary;
pub type RawPublicKey = String; pub type RawCipherText = HexBinary;
pub type RawAddress = String;
pub type RawMrenclave = String;
pub type RawTcbInfo = String;
pub type Mrenclave = [u8; 32]; pub type ObligationsItem<'a> = Item<'a, BTreeMap<RawHash, RawCipherText>>;
pub type SetoffsItem<'a> = Item<'a, BTreeMap<RawHash, RawCipherText>>;
#[cw_serde] #[cw_serde]
pub struct State { pub struct State {
pub owner: String, 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<State> = Item::new("state"); pub const STATE: Item<State> = Item::new("state");
pub const REQUESTS: Item<Vec<(RawNonce, Request)>> = Item::new("requests"); pub const OBLIGATIONS_KEY: &str = "obligations";
pub const SGX_STATE: Item<SgxState> = Item::new("sgx_state"); pub const SETOFFS_KEY: &str = "setoffs";
pub const EPOCH_STATE: Item<EpochState> = Item::new("epoch_state");
pub fn current_epoch_key(key: &str, storage: &dyn Storage) -> Result<String, StdError> {
Ok(format!("{}/{key}", EPOCH_COUNTER.load(storage)?))
}

View file

@ -10,6 +10,7 @@ cosmwasm-schema = "1.4.0"
cosmwasm-std = "1.4.0" cosmwasm-std = "1.4.0"
k256 = { version = "0.13.2", default-features = false, features = ["ecdsa", "alloc"] } k256 = { version = "0.13.2", default-features = false, features = ["ecdsa", "alloc"] }
serde = { version = "1.0.188", default-features = false, features = ["derive"] } serde = { version = "1.0.188", default-features = false, features = ["derive"] }
serde_json = "1.0.94"
sha2 = "0.10.8" sha2 = "0.10.8"
thiserror = "1.0.57" thiserror = "1.0.57"

View file

@ -6,9 +6,14 @@ use cosmwasm_std::{DepsMut, Env, MessageInfo, Response};
use crate::error::Error; use crate::error::Error;
use crate::handler::Handler; use crate::handler::Handler;
use crate::msg::execute::attested::Attestation;
use crate::msg::execute::attested::HasUserData;
use crate::msg::execute::Execute; use crate::msg::execute::Execute;
impl Handler for Execute { impl<A> Handler for Execute<A>
where
A: Handler + HasUserData + Attestation,
{
fn handle(self, deps: DepsMut<'_>, env: &Env, info: &MessageInfo) -> Result<Response, Error> { fn handle(self, deps: DepsMut<'_>, env: &Env, info: &MessageInfo) -> Result<Response, Error> {
match self { match self {
Execute::SessionCreate(msg) => msg.handle(deps, env, info), Execute::SessionCreate(msg) => msg.handle(deps, env, info),

View file

@ -3,7 +3,9 @@ use quartz_tee_ra::{verify_epid_attestation, Error as RaVerificationError};
use crate::error::Error; use crate::error::Error;
use crate::handler::Handler; 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; use crate::state::CONFIG;
impl Handler for EpidAttestation { impl Handler for EpidAttestation {
@ -13,13 +15,28 @@ impl Handler for EpidAttestation {
_env: &Env, _env: &Env,
_info: &MessageInfo, _info: &MessageInfo,
) -> Result<Response, Error> { ) -> Result<Response, Error> {
let (report, mr_enclave, user_data) = self.into_tuple(); // attestation handler MUST verify that the user_data and mr_enclave match the config/msg
verify_epid_attestation(report, mr_enclave, user_data) verify_epid_attestation(
self.clone().into_report(),
self.mr_enclave(),
self.user_data(),
)
.map(|_| Response::default()) .map(|_| Response::default())
.map_err(Error::RaVerification) .map_err(Error::RaVerification)
} }
} }
impl Handler for MockAttestation {
fn handle(
self,
_deps: DepsMut<'_>,
_env: &Env,
_info: &MessageInfo,
) -> Result<Response, Error> {
Ok(Response::default())
}
}
impl<M, A> Handler for Attested<M, A> impl<M, A> Handler for Attested<M, A>
where where
M: Handler + HasUserData, M: Handler + HasUserData,
@ -38,12 +55,17 @@ where
if let Some(config) = CONFIG.may_load(deps.storage)? { 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 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()); return Err(RaVerificationError::MrEnclaveMismatch.into());
} }
} }
Handler::handle(attestation, deps.branch(), env, info)?; // handle message first, this has 2 benefits -
Handler::handle(msg, deps, env, info) // 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)
} }
} }

View file

@ -3,25 +3,34 @@ use quartz_tee_ra::Error as RaVerificationError;
use crate::error::Error; use crate::error::Error;
use crate::handler::Handler; 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::msg::instantiate::{CoreInstantiate, Instantiate};
use crate::state::Config;
use crate::state::CONFIG; use crate::state::CONFIG;
use crate::state::{RawConfig, EPOCH_COUNTER};
impl Handler for Instantiate<EpidAttestation> { impl Handler for Instantiate<EpidAttestation> {
fn handle(self, deps: DepsMut<'_>, env: &Env, info: &MessageInfo) -> Result<Response, Error> { fn handle(self, deps: DepsMut<'_>, env: &Env, info: &MessageInfo) -> Result<Response, Error> {
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()); return Err(RaVerificationError::MrEnclaveMismatch.into());
} }
self.0.handle(deps, env, info) self.0.handle(deps, env, info)
} }
} }
impl Handler for Instantiate<MockAttestation> {
fn handle(self, deps: DepsMut<'_>, env: &Env, info: &MessageInfo) -> Result<Response, Error> {
self.0.handle(deps, env, info)
}
}
impl Handler for CoreInstantiate { impl Handler for CoreInstantiate {
fn handle(self, deps: DepsMut<'_>, _env: &Env, _info: &MessageInfo) -> Result<Response, Error> { fn handle(self, deps: DepsMut<'_>, _env: &Env, _info: &MessageInfo) -> Result<Response, Error> {
CONFIG CONFIG
.save(deps.storage, &Config::new(self.mr_enclave())) .save(deps.storage, &RawConfig::from(self.config().clone()))
.map_err(Error::Std)?; .map_err(Error::Std)?;
EPOCH_COUNTER.save(deps.storage, &1).map_err(Error::Std)?;
Ok(Response::new().add_attribute("action", "instantiate")) Ok(Response::new().add_attribute("action", "instantiate"))
} }
} }

View file

@ -55,9 +55,9 @@ where
} }
} }
impl<A> HasDomainType for RawExecute<A> impl<RA> HasDomainType for RawExecute<RA>
where where
A: HasDomainType, RA: HasDomainType,
{ {
type DomainType = Execute<A::DomainType>; type DomainType = Execute<RA::DomainType>;
} }

View file

@ -1,5 +1,5 @@
use cosmwasm_schema::cw_serde; use cosmwasm_schema::cw_serde;
use cosmwasm_std::{HexBinary, StdError}; use cosmwasm_std::StdError;
use quartz_tee_ra::IASReport; use quartz_tee_ra::IASReport;
use crate::msg::HasDomainType; use crate::msg::HasDomainType;
@ -12,6 +12,10 @@ pub struct Attested<M, A> {
} }
impl<M, A> Attested<M, A> { impl<M, A> Attested<M, A> {
pub fn new(msg: M, attestation: A) -> Self {
Self { msg, attestation }
}
pub fn into_tuple(self) -> (M, A) { pub fn into_tuple(self) -> (M, A) {
let Attested { msg, attestation } = self; let Attested { msg, attestation } = self;
(msg, attestation) (msg, attestation)
@ -75,42 +79,29 @@ pub trait HasUserData {
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct EpidAttestation { pub struct EpidAttestation {
report: IASReport, report: IASReport,
mr_enclave: MrEnclave,
user_data: UserData,
} }
impl EpidAttestation { impl EpidAttestation {
pub fn into_tuple(self) -> (IASReport, MrEnclave, UserData) { pub fn new(report: IASReport) -> Self {
let EpidAttestation { Self { report }
report,
mr_enclave,
user_data,
} = self;
(report, mr_enclave, user_data)
} }
pub fn report(&self) -> &IASReport { pub fn into_report(self) -> IASReport {
&self.report self.report
} }
} }
#[cw_serde] #[cw_serde]
pub struct RawEpidAttestation { pub struct RawEpidAttestation {
report: IASReport, report: IASReport,
mr_enclave: HexBinary,
user_data: HexBinary,
} }
impl TryFrom<RawEpidAttestation> for EpidAttestation { impl TryFrom<RawEpidAttestation> for EpidAttestation {
type Error = StdError; type Error = StdError;
fn try_from(value: RawEpidAttestation) -> Result<Self, Self::Error> { fn try_from(value: RawEpidAttestation) -> Result<Self, Self::Error> {
let mr_enclave = value.mr_enclave.to_array()?;
let user_data = value.user_data.to_array()?;
Ok(Self { Ok(Self {
report: value.report, report: value.report,
mr_enclave,
user_data,
}) })
} }
} }
@ -119,8 +110,6 @@ impl From<EpidAttestation> for RawEpidAttestation {
fn from(value: EpidAttestation) -> Self { fn from(value: EpidAttestation) -> Self {
Self { Self {
report: value.report, 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 { impl HasUserData for EpidAttestation {
fn user_data(&self) -> UserData { 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 { impl Attestation for EpidAttestation {
fn mr_enclave(&self) -> MrEnclave { 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<RawMockAttestation> for MockAttestation {
type Error = StdError;
fn try_from(_value: RawMockAttestation) -> Result<Self, Self::Error> {
Ok(Self)
}
}
impl From<MockAttestation> 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")
} }
} }

View file

@ -11,6 +11,10 @@ pub struct SessionCreate {
} }
impl SessionCreate { impl SessionCreate {
pub fn new(nonce: Nonce) -> Self {
Self { nonce }
}
pub fn into_nonce(self) -> Nonce { pub fn into_nonce(self) -> Nonce {
self.nonce self.nonce
} }

View file

@ -15,6 +15,10 @@ pub struct SessionSetPubKey {
} }
impl SessionSetPubKey { impl SessionSetPubKey {
pub fn new(nonce: Nonce, pub_key: VerifyingKey) -> Self {
Self { nonce, pub_key }
}
pub fn into_tuple(self) -> (Nonce, VerifyingKey) { pub fn into_tuple(self) -> (Nonce, VerifyingKey) {
(self.nonce, self.pub_key) (self.nonce, self.pub_key)
} }

View file

@ -1,15 +1,15 @@
use cosmwasm_schema::cw_serde; use cosmwasm_schema::cw_serde;
use cosmwasm_std::{HexBinary, StdError}; use cosmwasm_std::StdError;
use sha2::{Digest, Sha256}; use sha2::{Digest, Sha256};
use crate::msg::execute::attested::{ use crate::msg::execute::attested::{
Attested, EpidAttestation, HasUserData, RawAttested, RawEpidAttestation, Attested, EpidAttestation, HasUserData, RawAttested, RawEpidAttestation,
}; };
use crate::msg::HasDomainType; use crate::msg::HasDomainType;
use crate::state::{MrEnclave, UserData}; use crate::state::{Config, RawConfig, UserData};
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct Instantiate<A = EpidAttestation>(pub(crate) Attested<CoreInstantiate, A>); pub struct Instantiate<A = EpidAttestation>(pub Attested<CoreInstantiate, A>);
#[cw_serde] #[cw_serde]
pub struct RawInstantiate<RA = RawEpidAttestation>(RawAttested<RawCoreInstantiate, RA>); pub struct RawInstantiate<RA = RawEpidAttestation>(RawAttested<RawCoreInstantiate, RA>);
@ -34,40 +34,47 @@ where
} }
} }
impl HasDomainType for RawInstantiate { impl<RA> HasDomainType for RawInstantiate<RA>
type DomainType = Instantiate; where
RA: HasDomainType,
{
type DomainType = Instantiate<RA::DomainType>;
} }
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct CoreInstantiate { pub struct CoreInstantiate {
mr_enclave: MrEnclave, config: Config,
// TODO(hu55a1n1): config - e.g. Epoch duration, light client opts
} }
impl CoreInstantiate { impl CoreInstantiate {
pub fn mr_enclave(&self) -> MrEnclave { pub fn new(config: Config) -> Self {
self.mr_enclave Self { config }
}
pub fn config(&self) -> &Config {
&self.config
} }
} }
#[cw_serde] #[cw_serde]
pub struct RawCoreInstantiate { pub struct RawCoreInstantiate {
mr_enclave: HexBinary, config: RawConfig,
} }
impl TryFrom<RawCoreInstantiate> for CoreInstantiate { impl TryFrom<RawCoreInstantiate> for CoreInstantiate {
type Error = StdError; type Error = StdError;
fn try_from(value: RawCoreInstantiate) -> Result<Self, Self::Error> { fn try_from(value: RawCoreInstantiate) -> Result<Self, Self::Error> {
let mr_enclave = value.mr_enclave.to_array()?; Ok(Self {
Ok(Self { mr_enclave }) config: value.config.try_into()?,
})
} }
} }
impl From<CoreInstantiate> for RawCoreInstantiate { impl From<CoreInstantiate> for RawCoreInstantiate {
fn from(value: CoreInstantiate) -> Self { fn from(value: CoreInstantiate) -> Self {
Self { Self {
mr_enclave: value.mr_enclave.into(), config: value.config.into(),
} }
} }
} }
@ -79,7 +86,10 @@ impl HasDomainType for RawCoreInstantiate {
impl HasUserData for CoreInstantiate { impl HasUserData for CoreInstantiate {
fn user_data(&self) -> UserData { fn user_data(&self) -> UserData {
let mut hasher = Sha256::new(); 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 digest: [u8; 32] = hasher.finalize().into();
let mut user_data = [0u8; 64]; let mut user_data = [0u8; 64];

View file

@ -1,26 +1,199 @@
use core::time::Duration;
use cosmwasm_schema::cw_serde; use cosmwasm_schema::cw_serde;
use cosmwasm_std::HexBinary; use cosmwasm_std::{HexBinary, StdError};
use cw_storage_plus::Item; use cw_storage_plus::Item;
use k256::ecdsa::VerifyingKey; use k256::ecdsa::VerifyingKey;
pub type MrEnclave = [u8; 32]; pub type MrEnclave = [u8; 32];
pub type Nonce = [u8; 32]; pub type Nonce = [u8; 32];
pub type UserData = [u8; 64]; 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 { pub struct Config {
mr_enclave: HexBinary, mr_enclave: MrEnclave,
epoch_duration: Duration,
light_client_opts: LightClientOpts,
} }
impl Config { impl Config {
pub fn new(mr_enclave: MrEnclave) -> Self { pub fn new(
mr_enclave: MrEnclave,
epoch_duration: Duration,
light_client_opts: LightClientOpts,
) -> Self {
Self { Self {
mr_enclave: mr_enclave.into(), mr_enclave,
epoch_duration,
light_client_opts,
} }
} }
pub fn mr_enclave(&self) -> &HexBinary { pub fn light_client_opts(&self) -> &LightClientOpts {
&self.mr_enclave &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<RawConfig> for Config {
type Error = StdError;
fn try_from(value: RawConfig) -> Result<Self, Self::Error> {
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<Config> 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<Self, StdError> {
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<RawLightClientOpts> for LightClientOpts {
type Error = StdError;
fn try_from(value: RawLightClientOpts) -> Result<Self, Self::Error> {
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<LightClientOpts> for RawLightClientOpts {
fn from(value: LightClientOpts) -> Self {
Self {
chain_id: value.chain_id,
trusted_height: value.trusted_height,
trusted_hash: Vec::<u8>::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 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 SESSION: Item<'_, Session> = Item::new("quartz_session");
pub const EPOCH_COUNTER: Item<'_, usize> = Item::new("epoch_counter");

View file

@ -25,6 +25,7 @@ WASM_BIN="$1"
CHAIN_ID=${CHAIN_ID:-testing} CHAIN_ID=${CHAIN_ID:-testing}
LABEL=${LABEL:-bisenzone-mvp} LABEL=${LABEL:-bisenzone-mvp}
COUNT=${COUNT:-0} COUNT=${COUNT:-0}
INSTANTIATE_MSG=${INSTANTIATE_MSG:-"{}"}
TXFLAG="--chain-id ${CHAIN_ID} --gas-prices 0.0025ucosm --gas auto --gas-adjustment 1.3" TXFLAG="--chain-id ${CHAIN_ID} --gas-prices 0.0025ucosm --gas auto --gas-adjustment 1.3"
@ -45,7 +46,7 @@ echo "--------------------------------------------------------"
echo "Label: ${LABEL}" echo "Label: ${LABEL}"
echo "--------------------------------------------------------" 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 ""
echo "🕐 Waiting for contract to be queryable..." echo "🕐 Waiting for contract to be queryable..."