diff --git a/bisenzone-cw-mvp/.cargo/config b/bisenzone-cw-mvp/.cargo/config new file mode 100644 index 0000000..af5698e --- /dev/null +++ b/bisenzone-cw-mvp/.cargo/config @@ -0,0 +1,4 @@ +[alias] +wasm = "build --release --lib --target wasm32-unknown-unknown" +unit-test = "test --lib" +schema = "run --bin schema" diff --git a/bisenzone-cw-mvp/.github/workflows/Basic.yml b/bisenzone-cw-mvp/.github/workflows/Basic.yml new file mode 100644 index 0000000..7b69ff3 --- /dev/null +++ b/bisenzone-cw-mvp/.github/workflows/Basic.yml @@ -0,0 +1,89 @@ +# Based on https://github.com/actions-rs/example/blob/master/.github/workflows/quickstart.yml + +on: [ push, pull_request ] + +name: Basic + +jobs: + + test: + name: Test Suite + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v2 + + - name: Install stable toolchain + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: 1.73.0 + target: wasm32-unknown-unknown + override: true + + - name: Run unit tests + uses: actions-rs/cargo@v1 + with: + command: unit-test + args: --locked + env: + RUST_BACKTRACE: 1 + + - name: Compile WASM contract + uses: actions-rs/cargo@v1 + with: + command: wasm + args: --locked + env: + RUSTFLAGS: "-C link-arg=-s" + + lints: + name: Lints + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v2 + + - name: Install stable toolchain + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + components: rustfmt, clippy + + - name: Run cargo clippy + uses: actions-rs/cargo@v1 + with: + command: clippy + args: -- -D warnings + + - name: Generate Schema (for cofi-karma-game) + uses: actions-rs/cargo@v1 + with: + command: schema + args: --locked -p cofi-karma-game + + - name: Generate Schema (for cw-tee-mtcs) + uses: actions-rs/cargo@v1 + with: + command: schema + args: --locked -p cw-tee-mtcs + + - name: Schema Changes + # fails if any changes not committed + run: git diff --exit-code schema + + - name: Install nighly toolchain + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: nightly + override: true + components: rustfmt + + - name: Run cargo nightly fmt + uses: actions-rs/cargo@v1 + with: + command: fmt + args: --all -- --check \ No newline at end of file diff --git a/bisenzone-cw-mvp/.github/workflows/Release.yml b/bisenzone-cw-mvp/.github/workflows/Release.yml new file mode 100644 index 0000000..a9d5979 --- /dev/null +++ b/bisenzone-cw-mvp/.github/workflows/Release.yml @@ -0,0 +1,35 @@ +name: release wasm + +on: + release: + types: [created] + +jobs: + release: + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v2 + - name: Install cargo-run-script + uses: actions-rs/cargo@v1 + with: + command: install + args: cargo-run-script + - name: Run cargo optimize + uses: actions-rs/cargo@v1 + with: + command: run-script + args: optimize + - name: Get release ID + id: get_release + uses: bruceadams/get-release@v1.2.3 + env: + GITHUB_TOKEN: ${{ github.token }} + - name: Upload optimized wasm + uses: svenstaro/upload-release-action@v2 + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file: ./artifacts/*.wasm + tag: ${{ github.ref }} + overwrite: true + file_glob: true diff --git a/bisenzone-cw-mvp/.gitignore b/bisenzone-cw-mvp/.gitignore new file mode 100644 index 0000000..52990d7 --- /dev/null +++ b/bisenzone-cw-mvp/.gitignore @@ -0,0 +1,17 @@ +# Build results +/artifacts +target/ +schema/ + +# Cargo+Git helper file (https://github.com/rust-lang/cargo/blob/0.44.1/src/cargo/sources/git/utils.rs#L320-L327) +.cargo-ok + +# Text file backups +**/*.rs.bk + +# macOS +.DS_Store + +# IDEs +*.iml +.idea diff --git a/bisenzone-cw-mvp/README.md b/bisenzone-cw-mvp/README.md new file mode 100644 index 0000000..590fd28 --- /dev/null +++ b/bisenzone-cw-mvp/README.md @@ -0,0 +1,28 @@ +# Bisenzone CosmWasm MVP + +CosmWasm smart contracts used in the Bisenzone MVP. + +## Testing instructions + +* Deploy the smart contract on a local wasmd instance + (see [Obligation Clearing Mvp: Local setup](https://github.com/informalsystems/obligation-clearing-mvp#local-setup)) + * Should normally be as simple as running the following scripts -> + ```bash + # terminal-1 + ./scripts/keygen.sh + ./scripts/init-node.sh + ./scripts/run-node.sh + ``` + ```bash + # terminal-2 + ./scripts/build-contract.sh + ./scripts/deploy-contract.sh artifacts/cofi_karma_game.wasm + ``` +* Set contract env var (using the output of the `deploy.sh` script) - + +``` +export CONTRACT="wasm13we0myxwzlpx8l5ark8elw5gj5d59dl6cjkzmt80c5q5cv5rt54qhmta7s" +``` + +For further instructions, refer to the contract specific READMEs +(e.g. [cofi-karma-game README.md](contracts/cofi-karma-game/README.md)) \ No newline at end of file diff --git a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/Cargo.toml b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/Cargo.toml new file mode 100644 index 0000000..13529be --- /dev/null +++ b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/Cargo.toml @@ -0,0 +1,63 @@ +[package] +name = "cw-tee-mtcs" +version = "0.1.0" +authors = ["hu55a1n1 "] +edition = "2021" + +exclude = [ + # Those files are rust-optimizer artifacts. You might want to commit them for convenience but they should not be part of the source code publication. + "contract.wasm", + "hash.txt", +] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +crate-type = ["cdylib", "rlib"] + +[profile.release] +opt-level = 3 +debug = false +rpath = false +lto = true +debug-assertions = false +codegen-units = 1 +panic = 'abort' +incremental = false +overflow-checks = true + +[features] +# for more explicit tests, cargo test --features=backtraces +backtraces = ["cosmwasm-std/backtraces"] +# use library feature to disable all instantiate/execute/query exports +library = [] + +[package.metadata.scripts] +optimize = """docker run --rm -v "$(pwd)":/code \ + --mount type=volume,source="$(basename "$(pwd)")_cache",target=/target \ + --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ + cosmwasm/rust-optimizer:0.14.0 +""" + +[dependencies] +cosmwasm-schema = "1.5.0" +cosmwasm-std = { version = "1.5.0", features = [ + "cosmwasm_1_3", + # Enable this if you only deploy to chains that have CosmWasm 1.4 or higher + # "cosmwasm_1_4", +] } +cw-storage-plus = "1.1.0" +cw20-base = { version = "1.1.1", features = ["library"] } +cw20 = "1.1.1" +cw2 = "1.1.1" +hex = { version = "0.4.3", default-features = false } +k256 = { version = "0.13.2", default-features = false, features = ["ecdsa"] } +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/README.md b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/README.md new file mode 100644 index 0000000..b278a07 --- /dev/null +++ b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/README.md @@ -0,0 +1,50 @@ +# CosmWasm smart contract to support MTCS on TEE + +An implementation of the on-chain component of +the [Key managers proposal v1](https://github.com/informalsystems/tee-mtcs/issues/26). + +## Testing instructions + +* Submit a bootstrap key manager request - + +``` +export EXECUTE='{ + "bootstrap_key_manager": { + "compute_mrenclave": "dc43f8c42d8e5f52c8bbd68f426242153f0be10630ff8cca255129a3ca03d273", + "key_manager_mrenclave": "1cf2e52911410fbf3f199056a98d58795a559a2e800933f7fcd13d048462271c", + "tcb_info": "" + } +}' +wasmd tx wasm execute "$CONTRACT" "$EXECUTE" --from alice --chain-id testing -y +``` + +* Query the bootstrap state - + +``` +wasmd query wasm contract-state raw "$CONTRACT" 7367787374617465 # BIN_HEX('sgx_state') +# OR ---- +wasmd query wasm contract-state smart "$CONTRACT" '{ + "get_sgx_state": { } +}' +``` + +* Submit a join compute node request - + +``` +export EXECUTE='{ + "join_compute_node": { + "io_exchange_key": "03E67EF09213633074FB4FBF338643F4F0C574ED60EF11D03422EEB06FA38C8F3F", + "address": "wasm10n4dsljyyfp2k2hy6e8vuc9ry32px2egwt5e0m", + "nonce": "425d87f8620e1dedeee70590cc55b164b8f01480ee59e0b1da35436a2f7c2777" + } +}' +wasmd tx wasm execute "$CONTRACT" "$EXECUTE" --from alice --chain-id testing -y +``` + +* Query requests - + +``` +wasmd query wasm contract-state smart "$CONTRACT" '{ + "get_requests": { } +}' +``` diff --git a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/bin/schema.rs b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/bin/schema.rs new file mode 100644 index 0000000..c80c69f --- /dev/null +++ b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/bin/schema.rs @@ -0,0 +1,10 @@ +use cosmwasm_schema::write_api; +use cw_tee_mtcs::msg::{ExecuteMsg, InstantiateMsg, QueryMsg}; + +fn main() { + write_api! { + instantiate: InstantiateMsg, + execute: ExecuteMsg, + query: QueryMsg, + } +} diff --git a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/contract.rs b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/contract.rs new file mode 100644 index 0000000..e218a95 --- /dev/null +++ b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/contract.rs @@ -0,0 +1,278 @@ +use cosmwasm_std::{ + entry_point, to_json_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult, + Uint128, +}; +use cw2::set_contract_version; +use cw20_base::{ + contract::{execute_mint, query_balance as cw20_query_balance}, + state::{MinterData, TokenInfo, TOKEN_INFO}, +}; +use quartz_cw::{handler::RawHandler, state::EPOCH_COUNTER}; + +use crate::{ + error::ContractError, + msg::{ + execute::{SubmitObligationMsg, SubmitObligationsMsg, SubmitSetoffsMsg}, + ExecuteMsg, InstantiateMsg, QueryMsg, + }, + state::{ + current_epoch_key, LiquiditySourcesItem, ObligationsItem, State, LIQUIDITY_SOURCES_KEY, + OBLIGATIONS_KEY, STATE, + }, +}; + +// version info for migration info +const CONTRACT_NAME: &str = "crates.io:cw-tee-mtcs"; +const CONTRACT_VERSION: &str = env!("CARGO_PKG_VERSION"); + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn instantiate( + mut deps: DepsMut, + env: Env, + info: MessageInfo, + 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)?; + + EPOCH_COUNTER.save(deps.storage, &1)?; + + ObligationsItem::new(¤t_epoch_key(OBLIGATIONS_KEY, deps.storage)?) + .save(deps.storage, &Default::default())?; + + LiquiditySourcesItem::new(¤t_epoch_key(LIQUIDITY_SOURCES_KEY, deps.storage)?) + .save(deps.storage, &Default::default())?; + + // store token info using cw20-base format + let data = TokenInfo { + name: "USD".to_string(), + symbol: "!$".to_string(), + decimals: 0, + total_supply: Uint128::zero(), + // set self as minter, so we can properly execute mint and burn + mint: Some(MinterData { + minter: env.contract.address.clone(), + cap: None, + }), + }; + TOKEN_INFO.save(deps.storage, &data)?; + + let info = MessageInfo { + sender: env.contract.address.clone(), + funds: vec![], + }; + + execute_mint( + deps.branch(), + env.clone(), + info.clone(), + "wasm1qv9nel6lwtrq5jmwruxfndqw7ejskn5ysz53hp".to_owned(), + Uint128::new(1000), + )?; + + execute_mint( + deps.branch(), + env.clone(), + info.clone(), + "wasm1tfxrdcj5kk6rewzmmkku4d9htpjqr0kk6lcftv".to_owned(), + Uint128::new(1000), + )?; + + execute_mint( + deps.branch(), + env.clone(), + info.clone(), + "wasm1gjg72awjl7jvtmq4kjqp3al9p6crstpar8wgn5".to_owned(), + Uint128::new(1000), + )?; + + Ok(Response::new() + .add_attribute("method", "instantiate") + .add_attribute("owner", info.sender)) +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn execute( + mut deps: DepsMut, + env: Env, + info: MessageInfo, + msg: ExecuteMsg, +) -> Result { + match msg { + 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::SubmitObligations(SubmitObligationsMsg { + obligations, + liquidity_sources, + }) => { + for o in obligations { + execute::submit_obligation(deps.branch(), o.ciphertext, o.digest)?; + } + execute::append_liquidity_sources(deps, liquidity_sources)?; + Ok(Response::new()) + } + ExecuteMsg::SubmitSetoffs(SubmitSetoffsMsg { setoffs_enc }) => { + execute::submit_setoffs(deps, env, info, setoffs_enc) + } + ExecuteMsg::InitClearing => execute::init_clearing(deps), + } +} + +pub mod execute { + use std::collections::BTreeMap; + + use cosmwasm_std::{DepsMut, Env, HexBinary, MessageInfo, Response, StdResult}; + use cw20_base::contract::{execute_burn, execute_mint}; + use quartz_cw::state::{Hash, EPOCH_COUNTER}; + + use crate::{ + state::{ + current_epoch_key, previous_epoch_key, LiquiditySourcesItem, ObligationsItem, RawHash, + SetoffsItem, SettleOff, LIQUIDITY_SOURCES_KEY, OBLIGATIONS_KEY, SETOFFS_KEY, + }, + ContractError, + }; + + pub fn submit_obligation( + deps: DepsMut, + ciphertext: HexBinary, + digest: HexBinary, + ) -> Result { + let _: Hash = digest.to_array()?; + + // 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", "submit_obligation") + .add_attribute("digest", digest.to_string()) + .add_attribute("ciphertext", ciphertext.to_string())) + } + + pub fn append_liquidity_sources( + deps: DepsMut, + liquidity_sources: Vec, + ) -> Result<(), ContractError> { + liquidity_sources + .iter() + .try_for_each(|ls| deps.api.addr_validate(ls).map(|_| ()))?; + + // store the liquidity sources + LiquiditySourcesItem::new(¤t_epoch_key(LIQUIDITY_SOURCES_KEY, deps.storage)?) + .update(deps.storage, |mut ls| { + ls.extend(liquidity_sources); + Ok::<_, ContractError>(ls) + })?; + + Ok(()) + } + + pub fn submit_setoffs( + mut deps: DepsMut, + env: Env, + _info: MessageInfo, + setoffs_enc: BTreeMap, + ) -> Result { + // store the `BTreeMap` + SetoffsItem::new(&previous_epoch_key(SETOFFS_KEY, deps.storage)?) + .save(deps.storage, &setoffs_enc)?; + + for (_, so) in setoffs_enc { + if let SettleOff::Transfer(t) = so { + let info = MessageInfo { + sender: env.contract.address.clone(), + funds: vec![], + }; + + execute_mint( + deps.branch(), + env.clone(), + info.clone(), + t.payee.to_string(), + t.amount.into(), + )?; + + let payer = deps.api.addr_validate(&t.payer.to_string())?; + let info = MessageInfo { + sender: payer, + funds: vec![], + }; + + execute_burn(deps.branch(), env.clone(), info, t.amount.into())?; + } + } + + Ok(Response::new().add_attribute("action", "submit_setoffs")) + } + + pub fn init_clearing(deps: DepsMut) -> Result { + EPOCH_COUNTER.update(deps.storage, |mut counter| -> StdResult<_> { + counter += 1; + Ok(counter) + })?; + Ok(Response::new().add_attribute("action", "init_clearing")) + } +} + +#[cfg_attr(not(feature = "library"), entry_point)] +pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { + match msg { + QueryMsg::GetAllSetoffs => to_json_binary(&query::get_all_setoffs(deps)?), + QueryMsg::GetLiquiditySources { epoch } => { + to_json_binary(&query::get_liquidity_sources(deps, epoch)?) + } + QueryMsg::Balance { address } => to_json_binary(&cw20_query_balance(deps, address)?), + } +} + +pub mod query { + use cosmwasm_std::{Deps, StdResult}; + + use crate::{ + msg::{GetAllSetoffsResponse, GetLiquiditySourcesResponse}, + state::{ + current_epoch_key, epoch_key, previous_epoch_key, LiquiditySourcesItem, SetoffsItem, + LIQUIDITY_SOURCES_KEY, SETOFFS_KEY, + }, + }; + + pub fn get_all_setoffs(deps: Deps) -> StdResult { + let setoffs = SetoffsItem::new(&previous_epoch_key(SETOFFS_KEY, deps.storage)?) + .load(deps.storage)? + .into_iter() + .collect(); + Ok(GetAllSetoffsResponse { setoffs }) + } + + pub fn get_liquidity_sources( + deps: Deps, + epoch: Option, + ) -> StdResult { + let epoch_key = match epoch { + None => current_epoch_key(LIQUIDITY_SOURCES_KEY, deps.storage)?, + Some(e) => epoch_key(LIQUIDITY_SOURCES_KEY, e)?, + }; + + let liquidity_sources = LiquiditySourcesItem::new(&epoch_key) + .load(deps.storage)? + .into_iter() + .collect(); + Ok(GetLiquiditySourcesResponse { liquidity_sources }) + } +} diff --git a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/error.rs b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/error.rs new file mode 100644 index 0000000..ff82711 --- /dev/null +++ b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/error.rs @@ -0,0 +1,45 @@ +use cosmwasm_std::StdError; +use cw20_base::ContractError as Cw20ContractError; +use hex::FromHexError; +use k256::ecdsa::Error as K256Error; +use quartz_cw::error::Error as QuartzError; +use thiserror::Error; + +#[derive(Error, Debug)] +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(#[from] FromHexError), + + #[error("Invalid length")] + BadLength, + + #[error("Cw20 error: {0}")] + Cw20(Cw20ContractError), +} + +impl From for ContractError { + fn from(e: K256Error) -> Self { + Self::K256(e) + } +} + +impl From for ContractError { + fn from(e: Cw20ContractError) -> Self { + Self::Cw20(e) + } +} diff --git a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/lib.rs b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/lib.rs new file mode 100644 index 0000000..f71330c --- /dev/null +++ b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/lib.rs @@ -0,0 +1,15 @@ +#![deny( + warnings, + trivial_casts, + trivial_numeric_casts, + unused_import_braces, + unused_qualifications +)] +#![forbid(unsafe_code)] + +pub mod contract; +mod error; +pub mod msg; +pub mod state; + +pub use crate::error::ContractError; diff --git a/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/msg.rs b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/msg.rs new file mode 100644 index 0000000..3e0bfe6 --- /dev/null +++ b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/msg.rs @@ -0,0 +1,166 @@ +use std::collections::BTreeMap; + +use cosmwasm_schema::{cw_serde, QueryResponses}; +use cosmwasm_std::HexBinary; +use quartz_cw::prelude::*; + +use crate::state::{RawHash, SettleOff}; + +#[cw_serde] +pub struct InstantiateMsg(pub QuartzInstantiateMsg); + +#[cw_serde] +#[allow(clippy::large_enum_variant)] +pub enum ExecuteMsg { + Quartz(QuartzExecuteMsg), + SubmitObligation(execute::SubmitObligationMsg), + SubmitObligations(execute::SubmitObligationsMsg), + SubmitSetoffs(execute::SubmitSetoffsMsg), + InitClearing, +} + +pub mod execute { + use super::*; + + #[cw_serde] + pub struct SubmitObligationMsg { + pub ciphertext: HexBinary, + pub digest: HexBinary, + // pub signatures: [HexBinary; 2], + // pub proof: π + } + + #[cw_serde] + pub struct SubmitObligationsMsg { + pub obligations: Vec, + pub liquidity_sources: Vec, + } + + #[cw_serde] + pub struct SubmitTenderMsg { + pub ciphertext: HexBinary, + pub digest: HexBinary, + // pub proof: π + } + + #[cw_serde] + pub struct SubmitSetoffsMsg { + pub setoffs_enc: BTreeMap, + // pub proof: π, + } +} + +#[cw_serde] +#[derive(QueryResponses)] +pub enum QueryMsg { + #[returns(GetAllSetoffsResponse)] + GetAllSetoffs, + #[returns(GetLiquiditySourcesResponse)] + GetLiquiditySources { epoch: Option }, // `None` means latest + #[returns(cw20::BalanceResponse)] + Balance { address: String }, +} + +// We define a custom struct for each query response +#[cw_serde] +pub struct GetAllSetoffsResponse { + pub setoffs: Vec<(HexBinary, SettleOff)>, +} + +#[cw_serde] +pub struct GetLiquiditySourcesResponse { + pub liquidity_sources: Vec, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[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"); + } + + #[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 new file mode 100644 index 0000000..dcdb30b --- /dev/null +++ b/bisenzone-cw-mvp/contracts/cw-tee-mtcs/src/state.rs @@ -0,0 +1,49 @@ +use std::collections::{BTreeMap, BTreeSet}; + +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{HexBinary, StdError, Storage}; +use cw_storage_plus::Item; +use quartz_cw::state::EPOCH_COUNTER; + +pub type RawHash = HexBinary; +pub type RawCipherText = HexBinary; + +pub type ObligationsItem<'a> = Item<'a, BTreeMap>; +pub type SetoffsItem<'a> = Item<'a, BTreeMap>; +pub type LiquiditySourcesItem<'a> = Item<'a, BTreeSet>; + +#[cw_serde] +pub struct State { + pub owner: String, +} + +#[cw_serde] +pub struct Transfer { + pub payer: String, + pub payee: String, + pub amount: u64, +} + +#[cw_serde] +#[serde(untagged)] +pub enum SettleOff { + SetOff(Vec), + Transfer(Transfer), +} + +pub const STATE: Item = Item::new("state"); +pub const OBLIGATIONS_KEY: &str = "obligations"; +pub const SETOFFS_KEY: &str = "setoffs"; +pub const LIQUIDITY_SOURCES_KEY: &str = "liquidity_sources"; + +pub fn current_epoch_key(key: &str, storage: &dyn Storage) -> Result { + epoch_key(key, EPOCH_COUNTER.load(storage)?) +} + +pub fn previous_epoch_key(key: &str, storage: &dyn Storage) -> Result { + epoch_key(key, EPOCH_COUNTER.load(storage)? - 1) +} + +pub fn epoch_key(key: &str, epoch: usize) -> Result { + Ok(format!("{}/{key}", epoch)) +} diff --git a/bisenzone-cw-mvp/packages/quartz-cw/.cargo/config b/bisenzone-cw-mvp/packages/quartz-cw/.cargo/config new file mode 100644 index 0000000..76fb435 --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-cw/.cargo/config @@ -0,0 +1,5 @@ +[alias] +wasm = "build --release --lib --target wasm32-unknown-unknown" +wasm-debug = "build --lib --target wasm32-unknown-unknown" +schema = "run --bin schema" + diff --git a/bisenzone-cw-mvp/packages/quartz-cw/Cargo.toml b/bisenzone-cw-mvp/packages/quartz-cw/Cargo.toml new file mode 100644 index 0000000..b1b0459 --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-cw/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "quartz-cw" +version = "0.1.0" +authors = ["hu55a1n1 "] +edition = "2021" + +[dependencies] +cw-storage-plus = "1.1.0" +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" + +quartz-tee-ra = { path = "../../packages/quartz-tee-ra" } + +[dev-dependencies] +serde_json = "1.0.113" diff --git a/bisenzone-cw-mvp/packages/quartz-cw/README.md b/bisenzone-cw-mvp/packages/quartz-cw/README.md new file mode 100644 index 0000000..f4a6bbc --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-cw/README.md @@ -0,0 +1 @@ +# Quartz CosmWasm spec diff --git a/bisenzone-cw-mvp/packages/quartz-cw/src/error.rs b/bisenzone-cw-mvp/packages/quartz-cw/src/error.rs new file mode 100644 index 0000000..228d20d --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/error.rs @@ -0,0 +1,22 @@ +use cosmwasm_std::StdError; +use k256::ecdsa::Error as K256Error; +use quartz_tee_ra::Error as RaVerificationError; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum Error { + #[error("{0}")] + Std(#[from] StdError), + #[error("{0}")] + RaVerification(#[from] RaVerificationError), + #[error("Not Secp256K1")] + K256(K256Error), + #[error("invalid session nonce or attempt to reset pub_key")] + BadSessionTransition, +} + +impl From for Error { + fn from(e: K256Error) -> Self { + Self::K256(e) + } +} diff --git a/bisenzone-cw-mvp/packages/quartz-cw/src/handler.rs b/bisenzone-cw-mvp/packages/quartz-cw/src/handler.rs new file mode 100644 index 0000000..386ba8b --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/handler.rs @@ -0,0 +1,211 @@ +pub mod execute; +pub mod instantiate; + +use cosmwasm_std::{DepsMut, Env, MessageInfo, Response}; + +use crate::{error::Error, msg::HasDomainType}; + +pub trait Handler { + fn handle(self, deps: DepsMut<'_>, env: &Env, info: &MessageInfo) -> Result; +} + +pub trait RawHandler: HasDomainType { + fn handle_raw( + self, + deps: DepsMut<'_>, + env: &Env, + info: &MessageInfo, + ) -> Result; +} + +impl RawHandler for RM +where + RM: HasDomainType, + RM::DomainType: Handler, +{ + fn handle_raw( + self, + deps: DepsMut<'_>, + env: &Env, + info: &MessageInfo, + ) -> Result { + let execute: RM::DomainType = self.try_into()?; + execute.handle(deps, env, info) + } +} + +#[cfg(test)] +mod tests { + use cosmwasm_std::{ + testing::{mock_dependencies, mock_env, mock_info}, + DepsMut, + }; + use serde::Deserialize; + + use crate::{ + handler::Handler, + msg::{HasDomainType, RawExecuteMsg, RawInstantiateMsg}, + state::SESSION, + }; + + fn parse_msg<'a, R>(msg_str: &'a str) -> R::DomainType + where + R: HasDomainType + Deserialize<'a>, + { + let raw_msg: R = + serde_json::from_str(msg_str).expect("deserialization failure for hard-coded RawMsg"); + raw_msg.try_into().expect("invalid hard-coded RawMsg") + } + + fn handle_msg<'a, R>(mut deps: DepsMut<'_>, msg_str: &'a str) + where + R: HasDomainType + Deserialize<'a>, + R::DomainType: Handler, + { + let msg = parse_msg::(msg_str); + let info = mock_info("creator", &[]); + let env = mock_env(); + let res = msg + .handle(deps.branch(), &env, &info) + .expect("msg handler failure"); + + assert_eq!(0, res.messages.len()); + } + + fn instantiate(deps: DepsMut<'_>) { + handle_msg::( + deps, + r#"{ + "msg": { + "mr_enclave": "e3c2f2a5b840d89e069acaffcadb6510ef866a73d3a9ee57100ed5f8646ee4bb" + }, + "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==" + }, + "mr_enclave": "e3c2f2a5b840d89e069acaffcadb6510ef866a73d3a9ee57100ed5f8646ee4bb", + "user_data": "9113b0be77ed5d0d68680ec77206b8d587ed40679b71321ccdd5405e4d54a6820000000000000000000000000000000000000000000000000000000000000000" + } + }"#, + ); + } + + fn session_create(deps: DepsMut<'_>) { + handle_msg::( + deps, + r#"{ + "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==" + }, + "mr_enclave": "e3c2f2a5b840d89e069acaffcadb6510ef866a73d3a9ee57100ed5f8646ee4bb", + "user_data": "425d87f8620e1dedeee70590cc55b164b8f01480ee59e0b1da35436a2f7c27770000000000000000000000000000000000000000000000000000000000000000" + } + } + }"#, + ); + } + + fn session_set_pub_key(deps: DepsMut<'_>) { + handle_msg::( + deps, + r#"{ + "session_set_pub_key": { + "msg": { + "nonce": "425d87f8620e1dedeee70590cc55b164b8f01480ee59e0b1da35436a2f7c2777" + "pub_key": "03E67EF09213633074FB4FBF338643F4F0C574ED60EF11D03422EEB06FA38C8F3F" + }, + "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==" + }, + "mr_enclave": "e3c2f2a5b840d89e069acaffcadb6510ef866a73d3a9ee57100ed5f8646ee4bb", + "user_data": "425d87f8620e1dedeee70590cc55b164b8f01480ee59e0b1da35436a2f7c27770000000000000000000000000000000000000000000000000000000000000000" + } + } + }"#, + ); + } + + #[test] + #[ignore] + fn test_instantiate_handler() { + let mut deps = mock_dependencies(); + instantiate(deps.as_mut()); + } + + #[test] + #[ignore] + fn test_session_create_handler() { + let mut deps = mock_dependencies(); + instantiate(deps.as_mut()); + session_create(deps.as_mut()); + SESSION.load(&deps.storage).expect("Session not created"); + } + + #[test] + #[ignore] + fn test_session_set_pub_key_handler() { + let mut deps = mock_dependencies(); + instantiate(deps.as_mut()); + session_create(deps.as_mut()); + session_set_pub_key(deps.as_mut()); + SESSION.load(&deps.storage).expect("Session not created"); + // TODO(hu55a1n1): check that nonce & pub_key match, etc. + } +} diff --git a/bisenzone-cw-mvp/packages/quartz-cw/src/handler/execute.rs b/bisenzone-cw-mvp/packages/quartz-cw/src/handler/execute.rs new file mode 100644 index 0000000..c332ac9 --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/handler/execute.rs @@ -0,0 +1,26 @@ +pub mod attested; +pub mod session_create; +pub mod session_set_pub_key; + +use cosmwasm_std::{DepsMut, Env, MessageInfo, Response}; + +use crate::{ + error::Error, + handler::Handler, + msg::execute::{ + attested::{Attestation, HasUserData}, + 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), + Execute::SessionSetPubKey(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 new file mode 100644 index 0000000..f49d1aa --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/handler/execute/attested.rs @@ -0,0 +1,73 @@ +use cosmwasm_std::{DepsMut, Env, MessageInfo, Response}; +use quartz_tee_ra::{verify_epid_attestation, Error as RaVerificationError}; + +use crate::{ + error::Error, + handler::Handler, + msg::execute::attested::{ + Attestation, Attested, EpidAttestation, HasUserData, MockAttestation, + }, + state::CONFIG, +}; + +impl Handler for EpidAttestation { + fn handle( + self, + _deps: DepsMut<'_>, + _env: &Env, + _info: &MessageInfo, + ) -> Result { + // 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()) + } +} + +impl Handler for Attested +where + M: Handler + HasUserData, + A: Handler + HasUserData + Attestation, +{ + fn handle( + self, + mut deps: DepsMut<'_>, + env: &Env, + info: &MessageInfo, + ) -> Result { + let (msg, attestation) = self.into_tuple(); + if msg.user_data() != attestation.user_data() { + return Err(RaVerificationError::UserDataMismatch.into()); + } + + 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 + // 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()); + } + } + + // 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/execute/session_create.rs b/bisenzone-cw-mvp/packages/quartz-cw/src/handler/execute/session_create.rs new file mode 100644 index 0000000..cd182a7 --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/handler/execute/session_create.rs @@ -0,0 +1,19 @@ +use cosmwasm_std::{DepsMut, Env, MessageInfo, Response}; + +use crate::{ + error::Error, + handler::Handler, + msg::execute::session_create::SessionCreate, + state::{Session, SESSION}, +}; + +impl Handler for SessionCreate { + fn handle(self, deps: DepsMut<'_>, _env: &Env, _info: &MessageInfo) -> Result { + // TODO(hu55a1n1): overwrite previous session? + SESSION + .save(deps.storage, &Session::create(self.into_nonce())) + .map_err(Error::Std)?; + + Ok(Response::new().add_attribute("action", "session_create")) + } +} diff --git a/bisenzone-cw-mvp/packages/quartz-cw/src/handler/execute/session_set_pub_key.rs b/bisenzone-cw-mvp/packages/quartz-cw/src/handler/execute/session_set_pub_key.rs new file mode 100644 index 0000000..ddebe9e --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/handler/execute/session_set_pub_key.rs @@ -0,0 +1,25 @@ +use cosmwasm_std::{DepsMut, Env, HexBinary, MessageInfo, Response}; + +use crate::{ + error::Error, handler::Handler, msg::execute::session_set_pub_key::SessionSetPubKey, + state::SESSION, +}; + +impl Handler for SessionSetPubKey { + fn handle(self, deps: DepsMut<'_>, _env: &Env, _info: &MessageInfo) -> Result { + let session = SESSION.load(deps.storage).map_err(Error::Std)?; + let (nonce, pub_key) = self.into_tuple(); + let session = session + .with_pub_key(nonce, pub_key) + .ok_or(Error::BadSessionTransition)?; + + SESSION.save(deps.storage, &session).map_err(Error::Std)?; + + Ok(Response::new() + .add_attribute("action", "session_set_pub_key") + .add_attribute( + "pub_key", + HexBinary::from(pub_key.to_sec1_bytes().into_vec()).to_hex(), + )) + } +} diff --git a/bisenzone-cw-mvp/packages/quartz-cw/src/handler/instantiate.rs b/bisenzone-cw-mvp/packages/quartz-cw/src/handler/instantiate.rs new file mode 100644 index 0000000..3804bc0 --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/handler/instantiate.rs @@ -0,0 +1,39 @@ +use cosmwasm_std::{DepsMut, Env, MessageInfo, Response}; +use quartz_tee_ra::Error as RaVerificationError; + +use crate::{ + error::Error, + handler::Handler, + msg::{ + execute::attested::{Attestation, EpidAttestation, MockAttestation}, + instantiate::{CoreInstantiate, Instantiate}, + }, + state::{RawConfig, CONFIG, EPOCH_COUNTER}, +}; + +impl Handler for Instantiate { + fn handle(self, deps: DepsMut<'_>, env: &Env, info: &MessageInfo) -> Result { + 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, &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/lib.rs b/bisenzone-cw-mvp/packages/quartz-cw/src/lib.rs new file mode 100644 index 0000000..4438a11 --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/lib.rs @@ -0,0 +1,22 @@ +#![warn( + clippy::checked_conversions, + clippy::panic, + clippy::panic_in_result_fn, + clippy::unwrap_used, + rust_2018_idioms, + unused_lifetimes +)] +#![deny( + trivial_casts, + trivial_numeric_casts, + unused_import_braces, + unused_qualifications, + warnings +)] +#![forbid(unsafe_code)] + +pub mod error; +pub mod handler; +pub mod msg; +pub mod prelude; +pub mod state; diff --git a/bisenzone-cw-mvp/packages/quartz-cw/src/msg.rs b/bisenzone-cw-mvp/packages/quartz-cw/src/msg.rs new file mode 100644 index 0000000..4b440f8 --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/msg.rs @@ -0,0 +1,11 @@ +pub mod execute; +pub mod instantiate; +pub mod query; + +use cosmwasm_std::StdError; +pub use execute::{Execute as ExecuteMsg, RawExecute as RawExecuteMsg}; +pub use instantiate::{Instantiate as InstantiateMsg, RawInstantiate as RawInstantiateMsg}; + +pub trait HasDomainType: From { + type DomainType: TryFrom; +} diff --git a/bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute.rs b/bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute.rs new file mode 100644 index 0000000..b95fc4e --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute.rs @@ -0,0 +1,66 @@ +pub mod attested; +pub mod session_create; +pub mod session_set_pub_key; + +use cosmwasm_schema::cw_serde; +use cosmwasm_std::StdError; + +use crate::msg::{ + execute::{ + attested::{Attested, EpidAttestation, RawAttested, RawEpidAttestation}, + session_create::{RawSessionCreate, SessionCreate}, + session_set_pub_key::{RawSessionSetPubKey, SessionSetPubKey}, + }, + HasDomainType, +}; + +#[derive(Clone, Debug, PartialEq)] +pub enum Execute { + SessionCreate(Attested), + SessionSetPubKey(Attested), +} + +#[cw_serde] +pub enum RawExecute { + #[serde(rename = "session_create")] + RawSessionCreate(RawAttested), + #[serde(rename = "session_set_pub_key")] + RawSessionSetPubKey(RawAttested), +} + +impl TryFrom> for Execute +where + RA: HasDomainType, +{ + type Error = StdError; + + fn try_from(value: RawExecute) -> Result { + match value { + RawExecute::RawSessionCreate(msg) => { + Ok(Execute::SessionCreate(TryFrom::try_from(msg)?)) + } + RawExecute::RawSessionSetPubKey(msg) => { + Ok(Execute::SessionSetPubKey(TryFrom::try_from(msg)?)) + } + } + } +} + +impl From> for RawExecute +where + RA: HasDomainType, +{ + fn from(value: Execute) -> Self { + match value { + Execute::SessionCreate(msg) => RawExecute::RawSessionCreate(From::from(msg)), + Execute::SessionSetPubKey(msg) => RawExecute::RawSessionSetPubKey(From::from(msg)), + } + } +} + +impl HasDomainType for RawExecute +where + RA: HasDomainType, +{ + 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 new file mode 100644 index 0000000..125abf6 --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute/attested.rs @@ -0,0 +1,173 @@ +use cosmwasm_schema::cw_serde; +use cosmwasm_std::StdError; +use quartz_tee_ra::IASReport; + +use crate::{ + msg::HasDomainType, + state::{MrEnclave, UserData}, +}; + +#[derive(Clone, Debug, PartialEq)] +pub struct Attested { + msg: M, + attestation: A, +} + +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) + } + + pub fn msg(&self) -> &M { + &self.msg + } + + pub fn attestation(&self) -> &A { + &self.attestation + } +} + +#[cw_serde] +pub struct RawAttested { + pub msg: RM, + pub attestation: RA, +} + +impl TryFrom> for Attested +where + RM: HasDomainType, + RA: HasDomainType, +{ + type Error = StdError; + + fn try_from(value: RawAttested) -> Result { + Ok(Self { + msg: value.msg.try_into()?, + attestation: value.attestation.try_into()?, + }) + } +} + +impl From> for RawAttested +where + RM: HasDomainType, + RA: HasDomainType, +{ + fn from(value: Attested) -> Self { + Self { + msg: value.msg.into(), + attestation: value.attestation.into(), + } + } +} + +impl HasDomainType for RawAttested +where + RM: HasDomainType, + RA: HasDomainType, +{ + type DomainType = Attested; +} + +pub trait HasUserData { + fn user_data(&self) -> UserData; +} + +#[derive(Clone, Debug, PartialEq)] +pub struct EpidAttestation { + report: IASReport, +} + +impl EpidAttestation { + pub fn new(report: IASReport) -> Self { + Self { report } + } + + pub fn into_report(self) -> IASReport { + self.report + } +} + +#[cw_serde] +pub struct RawEpidAttestation { + report: IASReport, +} + +impl TryFrom for EpidAttestation { + type Error = StdError; + + fn try_from(value: RawEpidAttestation) -> Result { + Ok(Self { + report: value.report, + }) + } +} + +impl From for RawEpidAttestation { + fn from(value: EpidAttestation) -> Self { + Self { + report: value.report, + } + } +} + +impl HasDomainType for RawEpidAttestation { + type DomainType = EpidAttestation; +} + +impl HasUserData for EpidAttestation { + fn user_data(&self) -> UserData { + self.report.report.isv_enclave_quote_body.user_data() + } +} + +pub trait Attestation { + fn mr_enclave(&self) -> MrEnclave; +} + +impl Attestation for EpidAttestation { + fn mr_enclave(&self) -> 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 new file mode 100644 index 0000000..99aa545 --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute/session_create.rs @@ -0,0 +1,56 @@ +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{HexBinary, StdError}; + +use crate::{ + msg::{execute::attested::HasUserData, HasDomainType}, + state::{Nonce, UserData}, +}; + +#[derive(Clone, Debug, PartialEq)] +pub struct SessionCreate { + nonce: Nonce, +} + +impl SessionCreate { + pub fn new(nonce: Nonce) -> Self { + Self { nonce } + } + + pub fn into_nonce(self) -> Nonce { + self.nonce + } +} + +#[cw_serde] +pub struct RawSessionCreate { + nonce: HexBinary, +} + +impl TryFrom for SessionCreate { + type Error = StdError; + + fn try_from(value: RawSessionCreate) -> Result { + let nonce = value.nonce.to_array()?; + Ok(Self { nonce }) + } +} + +impl From for RawSessionCreate { + fn from(value: SessionCreate) -> Self { + Self { + nonce: value.nonce.into(), + } + } +} + +impl HasDomainType for RawSessionCreate { + type DomainType = SessionCreate; +} + +impl HasUserData for SessionCreate { + fn user_data(&self) -> UserData { + let mut user_data = [0u8; 64]; + user_data[0..32].copy_from_slice(&self.nonce); + user_data + } +} 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 new file mode 100644 index 0000000..9f857f2 --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/msg/execute/session_set_pub_key.rs @@ -0,0 +1,70 @@ +use cosmwasm_schema::cw_serde; +use cosmwasm_std::{HexBinary, StdError}; +use k256::ecdsa::VerifyingKey; +use sha2::{Digest, Sha256}; + +use crate::{ + error::Error, + msg::{execute::attested::HasUserData, HasDomainType}, + state::{Nonce, UserData}, +}; + +#[derive(Clone, Debug, PartialEq)] +pub struct SessionSetPubKey { + nonce: Nonce, + pub_key: VerifyingKey, +} + +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) + } +} + +#[cw_serde] +pub struct RawSessionSetPubKey { + nonce: HexBinary, + pub_key: HexBinary, +} + +impl TryFrom for SessionSetPubKey { + type Error = StdError; + + fn try_from(value: RawSessionSetPubKey) -> Result { + let nonce = value.nonce.to_array()?; + let pub_key = VerifyingKey::from_sec1_bytes(&value.pub_key) + .map_err(Error::from) + .map_err(|e| StdError::generic_err(e.to_string()))?; + Ok(Self { nonce, pub_key }) + } +} + +impl From for RawSessionSetPubKey { + fn from(value: SessionSetPubKey) -> Self { + Self { + nonce: value.nonce.into(), + pub_key: value.pub_key.to_sec1_bytes().into_vec().into(), + } + } +} + +impl HasDomainType for RawSessionSetPubKey { + type DomainType = SessionSetPubKey; +} + +impl HasUserData for SessionSetPubKey { + fn user_data(&self) -> UserData { + let mut hasher = Sha256::new(); + hasher.update(self.nonce); + hasher.update(self.pub_key.to_sec1_bytes()); + let digest: [u8; 32] = hasher.finalize().into(); + + let mut user_data = [0u8; 64]; + user_data[0..32].copy_from_slice(&digest); + user_data + } +} diff --git a/bisenzone-cw-mvp/packages/quartz-cw/src/msg/instantiate.rs b/bisenzone-cw-mvp/packages/quartz-cw/src/msg/instantiate.rs new file mode 100644 index 0000000..6b47169 --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/msg/instantiate.rs @@ -0,0 +1,103 @@ +use cosmwasm_schema::cw_serde; +use cosmwasm_std::StdError; +use sha2::{Digest, Sha256}; + +use crate::{ + msg::{ + execute::attested::{ + Attested, EpidAttestation, HasUserData, RawAttested, RawEpidAttestation, + }, + HasDomainType, + }, + state::{Config, RawConfig, UserData}, +}; + +#[derive(Clone, Debug, PartialEq)] +pub struct Instantiate(pub Attested); + +#[cw_serde] +pub struct RawInstantiate(RawAttested); + +impl TryFrom> for Instantiate +where + RA: HasDomainType, +{ + type Error = StdError; + + fn try_from(value: RawInstantiate) -> Result { + Ok(Self(TryFrom::try_from(value.0)?)) + } +} + +impl From> for RawInstantiate +where + RA: HasDomainType, +{ + fn from(value: Instantiate) -> Self { + Self(From::from(value.0)) + } +} + +impl HasDomainType for RawInstantiate +where + RA: HasDomainType, +{ + type DomainType = Instantiate; +} + +#[derive(Clone, Debug, PartialEq)] +pub struct CoreInstantiate { + config: Config, +} + +impl CoreInstantiate { + pub fn new(config: Config) -> Self { + Self { config } + } + + pub fn config(&self) -> &Config { + &self.config + } +} + +#[cw_serde] +pub struct RawCoreInstantiate { + config: RawConfig, +} + +impl TryFrom for CoreInstantiate { + type Error = StdError; + + fn try_from(value: RawCoreInstantiate) -> Result { + Ok(Self { + config: value.config.try_into()?, + }) + } +} + +impl From for RawCoreInstantiate { + fn from(value: CoreInstantiate) -> Self { + Self { + config: value.config.into(), + } + } +} + +impl HasDomainType for RawCoreInstantiate { + type DomainType = CoreInstantiate; +} + +impl HasUserData for CoreInstantiate { + fn user_data(&self) -> UserData { + let mut hasher = Sha256::new(); + 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]; + user_data[0..32].copy_from_slice(&digest); + user_data + } +} diff --git a/bisenzone-cw-mvp/packages/quartz-cw/src/msg/query.rs b/bisenzone-cw-mvp/packages/quartz-cw/src/msg/query.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/msg/query.rs @@ -0,0 +1 @@ + diff --git a/bisenzone-cw-mvp/packages/quartz-cw/src/prelude.rs b/bisenzone-cw-mvp/packages/quartz-cw/src/prelude.rs new file mode 100644 index 0000000..d938635 --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/prelude.rs @@ -0,0 +1,7 @@ +pub use crate::{ + handler::RawHandler, + msg::{ + execute::RawExecute as QuartzExecuteMsg, + instantiate::RawInstantiate as QuartzInstantiateMsg, + }, +}; diff --git a/bisenzone-cw-mvp/packages/quartz-cw/src/state.rs b/bisenzone-cw-mvp/packages/quartz-cw/src/state.rs new file mode 100644 index 0000000..b7edf88 --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-cw/src/state.rs @@ -0,0 +1,230 @@ +use core::time::Duration; + +use cosmwasm_schema::cw_serde; +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); + +#[derive(Clone, Debug, PartialEq)] +pub struct Config { + mr_enclave: MrEnclave, + epoch_duration: Duration, + light_client_opts: LightClientOpts, +} + +impl Config { + pub fn new( + mr_enclave: MrEnclave, + epoch_duration: Duration, + light_client_opts: LightClientOpts, + ) -> Self { + Self { + mr_enclave, + epoch_duration, + light_client_opts, + } + } + + 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, + } + } +} + +#[cw_serde] +pub struct Session { + nonce: HexBinary, + pub_key: Option, +} + +impl Session { + pub fn create(nonce: Nonce) -> Self { + Self { + nonce: nonce.into(), + pub_key: None, + } + } + + pub fn with_pub_key(mut self, nonce: Nonce, pub_key: VerifyingKey) -> Option { + if self.nonce == nonce && self.pub_key.is_none() { + self.pub_key = Some(pub_key.to_sec1_bytes().into_vec().into()); + Some(self) + } else { + None + } + } + + pub fn nonce(&self) -> Nonce { + self.nonce.to_array().expect("correct by construction") + } +} + +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/packages/quartz-tee-ra/Cargo.toml b/bisenzone-cw-mvp/packages/quartz-tee-ra/Cargo.toml new file mode 100644 index 0000000..2bc9d3f --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-tee-ra/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "quartz-tee-ra" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +cosmwasm-schema = "1.4.0" +cosmwasm-std = "1.4.0" +hex-literal = "0.4.1" +num-bigint = "0.4.4" +serde_json = "1.0.108" +sha2 = "0.10.8" +thiserror = { version = "1.0.49" } + +der = { version = "0.7.9", default-features = false } +displaydoc = { version = "0.2.4", default-features = false } +mc-sgx-core-types = { git = "https://github.com/hu55a1n1/sgx" } +mc-sgx-dcap-types = { git = "https://github.com/hu55a1n1/sgx" } +mc-sgx-dcap-sys-types = { git = "https://github.com/hu55a1n1/sgx" } +mc-attestation-verifier = { git = "https://github.com/hu55a1n1/attestation" } +serde = { version = "1.0.198", default-features = false } +x509-cert = { version = "0.2.5", default-features = false } +zeroize = { version = "1.7.0", default-features = false } diff --git a/bisenzone-cw-mvp/packages/quartz-tee-ra/data/DcapRootCACert.pem b/bisenzone-cw-mvp/packages/quartz-tee-ra/data/DcapRootCACert.pem new file mode 100644 index 0000000..408bd93 --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-tee-ra/data/DcapRootCACert.pem @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE----- +MIICjzCCAjSgAwIBAgIUImUM1lqdNInzg7SVUr9QGzknBqwwCgYIKoZIzj0EAwIw +aDEaMBgGA1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENv +cnBvcmF0aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJ +BgNVBAYTAlVTMB4XDTE4MDUyMTEwNDUxMFoXDTQ5MTIzMTIzNTk1OVowaDEaMBgG +A1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENvcnBvcmF0 +aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJBgNVBAYT +AlVTMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEC6nEwMDIYZOj/iPWsCzaEKi7 +1OiOSLRFhWGjbnBVJfVnkY4u3IjkDYYL0MxO4mqsyYjlBalTVYxFP2sJBK5zlKOB +uzCBuDAfBgNVHSMEGDAWgBQiZQzWWp00ifODtJVSv1AbOScGrDBSBgNVHR8ESzBJ +MEegRaBDhkFodHRwczovL2NlcnRpZmljYXRlcy50cnVzdGVkc2VydmljZXMuaW50 +ZWwuY29tL0ludGVsU0dYUm9vdENBLmRlcjAdBgNVHQ4EFgQUImUM1lqdNInzg7SV +Ur9QGzknBqwwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwCgYI +KoZIzj0EAwIDSQAwRgIhAOW/5QkR+S9CiSDcNoowLuPRLsWGf/Yi7GSX94BgwTwg +AiEA4J0lrHoMs+Xo5o/sX6O9QWxHRAvZUGOdRQ7cvqRXaqI= +-----END CERTIFICATE----- \ No newline at end of file diff --git a/bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx.rs b/bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx.rs new file mode 100644 index 0000000..3e7bd02 --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx.rs @@ -0,0 +1,14 @@ +use thiserror::Error; + +pub mod dcap; +pub mod epid; + +#[derive(Error, Debug)] +pub enum Error { + #[error("Specified user data does not match the report")] + UserDataMismatch, + #[error("Specified MRENCLAVE does not match the report")] + MrEnclaveMismatch, + #[error("EPID specific error: {0}")] + Epid(#[from] epid::Error), +} diff --git a/bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx/dcap.rs b/bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx/dcap.rs new file mode 100644 index 0000000..0668ca9 --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx/dcap.rs @@ -0,0 +1,130 @@ +pub mod certificate_chain; +pub mod mc_attest_verifier; +pub mod mc_attest_verifier_types; + +/// Root anchor PEM file for use with DCAP +pub const DCAP_ROOT_ANCHOR: &str = include_str!("../../data/DcapRootCACert.pem"); + +pub use mc_attest_verifier::dcap::DcapVerifier; +pub use mc_attest_verifier_types::verification::EnclaveReportDataContents; +pub use mc_attestation_verifier::*; +pub use mc_sgx_dcap_sys_types::sgx_ql_qve_collateral_t; +pub use mc_sgx_dcap_types::{CertificationData, Collateral}; +pub use x509_cert::Certificate; + +#[cfg(test)] +mod tests { + use hex_literal::hex; + use mc_sgx_dcap_types::Quote3; + + #[test] + fn test_quote_parse() { + let quote_bytes = hex!( + "03000200000000000a000f00939a7233f79c4ca9940a0db3957f0607ce4\ + 8836fd48a951172fe155220a719bd00000000141402070180010000000000000000000000000000000000000000\ + 00000000000000000000000000000000000000000005000000000000000700000000000000dc43f8c42d8e5f52c\ + 8bbd68f426242153f0be10630ff8cca255129a3ca03d27300000000000000000000000000000000000000000000\ + 000000000000000000001cf2e52911410fbf3f199056a98d58795a559a2e800933f7fcd13d048462271c0000000\ + 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 00000000000000000000000000000000000000009113b0be77ed5d0d68680ec77206b8d587ed40679b71321ccdd\ + 5405e4d54a682000000000000000000000000000000000000000000000000000000000000000044100000552c9b\ + 321744cf259b8c239213413ca4226ea8a705ad6eabb505bb4f3a8850f1ed9f2ff3d9e17ba8be8cc69a0d911575a\ + 813392202bddaa7b971d406704989e0be6177e039634cfbca4739ac246fda7df8c312a98f30f57b63f3c8921fce\ + 51d90a93031f97f769637be9b028e7b007a4e458d4fa717befbd81b069050825801314140207018001000000000\ + 0000000000000000000000000000000000000000000000000000000000000000000000000150000000000000007\ + 0000000000000096b347a64e5a045e27369c26e6dcda51fd7c850e9b3a3a79e718f43261dee1e40000000000000\ + 0000000000000000000000000000000000000000000000000008c4f5775d796503e96137f77c68a829a0056ac8d\ + ed70140b081b094490c57bff0000000000000000000000000000000000000000000000000000000000000000000\ + 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\ + 000000000000000000000000000000000001000a000000000000000000000000000000000000000000000000000\ + 00000000000000000000000000000000000000000000000000000000000000000000000b0f056c5355f6c770413\ + 938c2a41ed1b5c34ecb35f85fa539f16cca7a30d6da900000000000000000000000000000000000000000000000\ + 00000000000000000010a4d552582e428f8fd138cbb5f8af51050776f2c487996147d4ebfb5c817ba733905f30a\ + f7b4f340eaf0fdf2bb64dcb045d56f1609a1b042e6f5aeed09175c2000000102030405060708090a0b0c0d0e0f1\ + 01112131415161718191a1b1c1d1e1f0500dc0d00002d2d2d2d2d424547494e2043455254494649434154452d2d\ + 2d2d2d0a4d4949456a544343424453674177494241674956414a5172493559365a78484836785034424941715a4\ + e6b326e4c7a594d416f4743437147534d343942414d430a4d484578497a416842674e5642414d4d476b6c756447\ + 567349464e48574342515130736755484a765932567a6332397949454e424d526f77474159445651514b0a44424\ + 64a626e526c6243424462334a7762334a6864476c76626a45554d424947413155454277774c5532467564474567\ + 51327868636d4578437a414a42674e560a4241674d416b4e424d517377435159445651514745774a56557a41654\ + 67730794d7a45784d5441784e7a45334d4452614677307a4d4445784d5441784e7a45330a4d4452614d48417849\ + 6a416742674e5642414d4d47556c756447567349464e4857434251513073675132567964476c6d61574e6864475\ + 578476a415942674e560a42416f4d45556c756447567349454e76636e4276636d4630615739754d525177456759\ + 4456515148444174545957353059534244624746795954454c4d416b470a413155454341774351304578437a414\ + a42674e5642415954416c56544d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a30444151634451\ + 6741450a77625468574d583134443163657835317875614958456771517a69636e744b7a48454a32536f31336e3\ + 84a427050314a67383673764263462f7070715a554e710a68524b4642667469584c6d4f536c614955784e6e5136\ + 4f434171677767674b6b4d42384741315564497751594d426141464e446f71747031312f6b75535265590a50487\ + 3555a644456386c6c4e4d477747413155644877526c4d474d77596142666f463247573268306448427a4f693876\ + 595842704c6e527964584e305a57527a0a5a584a3261574e6c63793570626e526c6243356a62323076633264344\ + c324e6c636e52705a6d6c6a5958527062323476646a517663474e7259334a7350324e680a5058427962324e6c63\ + 334e7663695a6c626d4e765a476c755a7a316b5a584977485159445652304f42425945464a57566e41395a63736\ + f3753356b6a2f647a4a0a594f3034534175644d41344741315564447745422f775145417749477744414d42674e\ + 5648524d4241663845416a41414d4949423141594a4b6f5a496876684e0a4151304242494942785443434163457\ + 74867594b4b6f5a496876684e41513042415151514d6d5867725757774c59554164456d6c766c36615344434341\ + 5751470a43697147534962345451454e41514977676746554d42414743797147534962345451454e41514942416\ + 745554d42414743797147534962345451454e415149430a416745554d42414743797147534962345451454e4151\ + 4944416745434d42414743797147534962345451454e41514945416745454d42414743797147534962340a54514\ + 54e41514946416745424d42454743797147534962345451454e41514947416749416744415142677371686b6947\ + 2b4530424451454342774942414441510a42677371686b69472b453042445145434341494241444151426773716\ + 86b69472b45304244514543435149424144415142677371686b69472b453042445145430a436749424144415142\ + 677371686b69472b45304244514543437749424144415142677371686b69472b453042445145434441494241444\ + 15142677371686b69470a2b45304244514543445149424144415142677371686b69472b45304244514543446749\ + 424144415142677371686b69472b4530424451454344774942414441510a42677371686b69472b4530424451454\ + 3454149424144415142677371686b69472b45304244514543455149424454416642677371686b69472b45304244\ + 5145430a4567515146425143424147414141414141414141414141414144415142676f71686b69472b453042445\ + 14544424149414144415542676f71686b69472b4530420a44514545424159416b473668414141774477594b4b6f\ + 5a496876684e4151304242516f424144414b42676771686b6a4f5051514441674e4841444245416942560a6e584\ + 667364277466a6945474230417162424e702b4b56734a477245744f4f49666e6f365450387031414967536c4430\ + 574e39595261575968346534656835330a314637434537664964724f55414c5177757632735948513d0a2d2d2d2\ + d2d454e442043455254494649434154452d2d2d2d2d0a2d2d2d2d2d424547494e2043455254494649434154452d\ + 2d2d2d2d0a4d4949436d444343416a36674177494241674956414e446f71747031312f6b7553526559504873555\ + a644456386c6c4e4d416f4743437147534d343942414d430a4d476778476a415942674e5642414d4d45556c7564\ + 47567349464e48574342536232393049454e424d526f77474159445651514b4442464a626e526c624342440a623\ + 34a7762334a6864476c76626a45554d424947413155454277774c553246756447456751327868636d4578437a41\ + 4a42674e564241674d416b4e424d5173770a435159445651514745774a56557a4165467730784f4441314d6a457\ + 84d4455774d5442614677307a4d7a41314d6a45784d4455774d5442614d484578497a41680a42674e5642414d4d\ + 476b6c756447567349464e48574342515130736755484a765932567a6332397949454e424d526f7747415944565\ + 1514b4442464a626e526c0a6243424462334a7762334a6864476c76626a45554d424947413155454277774c5532\ + 46756447456751327868636d4578437a414a42674e564241674d416b4e420a4d517377435159445651514745774\ + a56557a425a4d424d4742797147534d34394167454743437147534d34394177454841304941424c39712b4e4d70\ + 32494f670a74646c31626b2f75575a352b5447516d38614369387a373866732b664b435133642b75447a586e565\ + 44154325a68444369667949754a77764e33774e427039690a484253534d4a4d4a72424f6a676273776762677748\ + 7759445652306a42426777466f4155496d554d316c71644e496e7a6737535655723951477a6b6e427177770a556\ + 759445652306642457377535442486f45576751345a426148523063484d364c79396a5a584a3061575a70593246\ + 305a584d7564484a316333526c5a484e6c0a636e5a705932567a4c6d6c75644756734c6d4e766253394a626e526\ + c62464e4857464a76623352445153356b5a584977485159445652304f42425945464e446f0a71747031312f6b75\ + 53526559504873555a644456386c6c4e4d41344741315564447745422f77514541774942426a415342674e56485\ + 24d4241663845434441470a4151482f416745414d416f4743437147534d343942414d43413067414d4555434951\ + 434a6754627456714f795a316d336a716941584d365159613672357357530a34792f4737793875494a477864774\ + 9675271507642534b7a7a516167424c517135733541373070646f6961524a387a2f3075447a344e675639316b3d\ + 0a2d2d2d2d2d454e442043455254494649434154452d2d2d2d2d0a2d2d2d2d2d424547494e20434552544946494\ + 34154452d2d2d2d2d0a4d4949436a7a4343416a53674177494241674955496d554d316c71644e496e7a67375356\ + 55723951477a6b6e42717777436759494b6f5a497a6a3045417749770a614445614d42674741315545417777525\ + 35735305a5777675530645949464a766233516751304578476a415942674e5642416f4d45556c75644756734945\ + 4e760a636e4276636d4630615739754d52517745675944565151484441745459573530595342446247467959544\ + 54c4d416b47413155454341774351304578437a414a0a42674e5642415954416c56544d423458445445344d4455\ + 794d5445774e4455784d466f58445451354d54497a4d54497a4e546b314f566f77614445614d4267470a4131554\ + 541777752535735305a5777675530645949464a766233516751304578476a415942674e5642416f4d45556c7564\ + 47567349454e76636e4276636d46300a615739754d5251774567594456515148444174545957353059534244624\ + 746795954454c4d416b47413155454341774351304578437a414a42674e56424159540a416c56544d466b774577\ + 59484b6f5a497a6a3043415159494b6f5a497a6a3044415163445167414543366e45774d4449595a4f6a2f69505\ + 773437a61454b69370a314f694f534c52466857476a626e42564a66566e6b59347533496a6b4459594c304d784f\ + 346d717379596a6c42616c54565978465032734a424b357a6c4b4f420a757a43427544416642674e5648534d454\ + 7444157674251695a517a575770303069664f44744a5653763141624f5363477244425342674e5648523845537a\ + 424a0a4d45656752614244686b466f64485277637a6f764c324e6c636e52705a6d6c6a5958526c63793530636e5\ + 67a6447566b63325679646d6c6a5a584d75615735300a5a577775593239744c306c756447567355306459556d39\ + 7664454e424c6d526c636a416442674e564851344546675155496d554d316c71644e496e7a673753560a5572395\ + 1477a6b6e4271777744675944565230504151482f42415144416745474d42494741315564457745422f7751494d\ + 4159424166384341514577436759490a4b6f5a497a6a3045417749445351417752674968414f572f35516b522b5\ + 33943695344634e6f6f774c7550524c735747662f59693747535839344267775477670a41694541344a306c7248\ + 6f4d732b586f356f2f7358364f39515778485241765a55474f6452513763767152586171493d0a2d2d2d2d2d454\ + e442043455254494649434154452d2d2d2d2d0a00" + ); + let _quote: Quote3> = Quote3::try_from(quote_bytes.to_vec()) + .expect("Failed to parse quote") + .into(); + } +} diff --git a/bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx/dcap/certificate_chain.rs b/bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx/dcap/certificate_chain.rs new file mode 100644 index 0000000..7955730 --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx/dcap/certificate_chain.rs @@ -0,0 +1,17 @@ +use der::DateTime; +use mc_attestation_verifier::{CertificateChainVerifier, CertificateChainVerifierError}; +use x509_cert::{crl::CertificateList, Certificate}; + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Eq, PartialEq)] +pub struct TlsCertificateChainVerifier; + +impl CertificateChainVerifier for TlsCertificateChainVerifier { + fn verify_certificate_chain<'a, 'b>( + &self, + _certificate_chain: impl IntoIterator, + _crls: impl IntoIterator, + _time: impl Into>, + ) -> Result<(), CertificateChainVerifierError> { + todo!() + } +} diff --git a/bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx/dcap/mc_attest_verifier.rs b/bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx/dcap/mc_attest_verifier.rs new file mode 100644 index 0000000..10d02c2 --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx/dcap/mc_attest_verifier.rs @@ -0,0 +1,4 @@ +// Trimmed down version of the `mc_attest_verifier` crate (for DCAP verification only) -> +// https://github.com/hu55a1n1/mobilecoin/blob/a1eba594f5b1ebe3d2b0736cf76d38120f84a4a0/attest/verifier + +pub mod dcap; diff --git a/bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx/dcap/mc_attest_verifier/dcap.rs b/bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx/dcap/mc_attest_verifier/dcap.rs new file mode 100644 index 0000000..11698b6 --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx/dcap/mc_attest_verifier/dcap.rs @@ -0,0 +1,82 @@ +// Copyright (c) 2023 The MobileCoin Foundation + +//! Verify the contents of a Quote3. + +use der::DateTime; +use mc_attestation_verifier::{ + Accessor, And, AndOutput, Evidence, EvidenceValue, EvidenceVerifier, ReportDataVerifier, + TrustedIdentity, VerificationOutput, Verifier, +}; +use mc_sgx_core_types::ReportData; + +// use super::DCAP_ROOT_ANCHOR; +use super::super::certificate_chain::TlsCertificateChainVerifier; +use super::super::mc_attest_verifier_types::verification::EnclaveReportDataContents; + +#[derive(Debug)] +pub struct DcapVerifier { + verifier: And, ReportDataHashVerifier>, +} + +type DcapVerifierOutput = AndOutput; + +impl DcapVerifier { + /// Create a new instance of the DcapVerifier. + /// + /// # Arguments + /// * `trusted_identities` - The allowed identities that can be used in an + /// enclave. Verification will succeed if any of these match. + /// * `time` - The time to use to verify the validity of the certificates + /// and collateral. If time is provided, verification will fail if this + /// time is before or after any of the validity periods. Otherwise, time + /// validation of certificates will be skipped. + pub fn new( + trusted_identities: I, + time: impl Into>, + report_data: EnclaveReportDataContents, + ) -> Self + where + I: IntoIterator, + ID: Into, + { + let certificate_verifier = TlsCertificateChainVerifier; + let verifier = And::new( + EvidenceVerifier::new(certificate_verifier, trusted_identities, time), + ReportDataHashVerifier::new(report_data), + ); + Self { verifier } + } + + /// Verify the `evidence` + pub fn verify(&self, evidence: &Evidence>) -> VerificationOutput { + self.verifier.verify(evidence) + } +} + +#[derive(Debug, Clone)] +pub struct ReportDataHashVerifier { + report_data_verifier: ReportDataVerifier, +} + +impl ReportDataHashVerifier { + pub fn new(report_data: EnclaveReportDataContents) -> Self { + let mut expected_report_data_bytes = [0u8; 64]; + expected_report_data_bytes[..32].copy_from_slice(report_data.sha256().as_ref()); + let mut mask = [0u8; 64]; + mask[..32].copy_from_slice([0xffu8; 32].as_ref()); + let report_data_verifier = + ReportDataVerifier::new(expected_report_data_bytes.into(), mask.into()); + + Self { + report_data_verifier, + } + } +} + +impl> Verifier for ReportDataHashVerifier { + type Value = ReportData; + + fn verify(&self, evidence: &E) -> VerificationOutput { + self.report_data_verifier.verify(evidence) + } +} diff --git a/bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx/dcap/mc_attest_verifier_types.rs b/bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx/dcap/mc_attest_verifier_types.rs new file mode 100644 index 0000000..a40c715 --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx/dcap/mc_attest_verifier_types.rs @@ -0,0 +1,4 @@ +// Heavily trimmed down version of the `mc-attest-verifier-types` crate +// https://github.com/hu55a1n1/mobilecoin/blob/89d90c427bf9cf637a124c0afad266d52b573dc8/attest/verifier/types/src/verification.rs + +pub mod verification; diff --git a/bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx/dcap/mc_attest_verifier_types/verification.rs b/bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx/dcap/mc_attest_verifier_types/verification.rs new file mode 100644 index 0000000..266001c --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx/dcap/mc_attest_verifier_types/verification.rs @@ -0,0 +1,60 @@ +// Copyright (c) 2018-2022 The MobileCoin Foundation + +//! Attestation Verification Report type. + +use core::fmt::Debug; + +use mc_sgx_core_types::QuoteNonce; +use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha256}; + +/// Structure for holding the contents of the Enclave's Report Data. +/// The Enclave Quote's ReportData member contains a SHA256 hash of this +/// structure's contents. +#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)] +pub struct EnclaveReportDataContents { + nonce: QuoteNonce, + custom_identity: Option<[u8; 32]>, +} + +impl EnclaveReportDataContents { + /// Create a new EnclaveReportDataContents. + /// + /// # Arguments + /// * `nonce` - The nonce provided from the enclave when generating the + /// Report. + /// * `key` - The public key of the enclave. Previously this was bytes 0..32 + /// of the enclave's [`ReportData`](mc-sgx-core-types::ReportData). + /// * `custom_identity` - The custom identity of the enclave. Previously + /// this was bytes 32..64 of the enclave's + /// [`ReportData`](mc-sgx-core-types::ReportData). + pub fn new(nonce: QuoteNonce, custom_identity: impl Into>) -> Self { + Self { + nonce, + custom_identity: custom_identity.into(), + } + } + + /// Get the nonce + pub fn nonce(&self) -> &QuoteNonce { + &self.nonce + } + + /// Get the custom identity + pub fn custom_identity(&self) -> Option<&[u8; 32]> { + self.custom_identity.as_ref() + } + + /// Returns a SHA256 hash of the contents of this structure. + /// + /// This is the value that is stored in bytes 0..32 of the enclave's + /// [`ReportData`](mc-sgx-core-types::ReportData). + pub fn sha256(&self) -> [u8; 32] { + let mut hasher = Sha256::new(); + hasher.update(&self.nonce); + if let Some(custom_identity) = &self.custom_identity { + hasher.update(custom_identity); + } + hasher.finalize().into() + } +} diff --git a/bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx/epid.rs b/bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx/epid.rs new file mode 100644 index 0000000..f1d11f4 --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx/epid.rs @@ -0,0 +1,17 @@ +use hex_literal::hex; +use thiserror::Error; + +pub const INTEL_ROOT_MODULUS: &[u8] = &hex!("a97a2de0e66ea6147c9ee745ac0162686c7192099afc4b3f040fad6de093511d74e802f510d716038157dcaf84f4104bd3fed7e6b8f99c8817fd1ff5b9b864296c3d81fa8f1b729e02d21d72ffee4ced725efe74bea68fbc4d4244286fcdd4bf64406a439a15bcb4cf67754489c423972b4a80df5c2e7c5bc2dbaf2d42bb7b244f7c95bf92c75d3b33fc5410678a89589d1083da3acc459f2704cd99598c275e7c1878e00757e5bdb4e840226c11c0a17ff79c80b15c1ddb5af21cc2417061fbd2a2da819ed3b72b7efaa3bfebe2805c9b8ac19aa346512d484cfc81941e15f55881cc127e8f7aa12300cd5afb5742fa1d20cb467a5beb1c666cf76a368978b5"); + +pub const INTEL_ROOT_EXPONENT: &[u8] = + &hex!("0000000000000000000000000000000000000000000000000000000000010001"); + +pub mod types; + +pub mod verifier; + +#[derive(Error, Debug)] +pub enum Error { + #[error("Recovered digest from signature does not match the specified report")] + RecoveredDigestMismatch, +} diff --git a/bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx/epid/types.rs b/bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx/epid/types.rs new file mode 100644 index 0000000..b80a100 --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx/epid/types.rs @@ -0,0 +1,49 @@ +use cosmwasm_schema::cw_serde; +use cosmwasm_std::Binary; + +#[cw_serde] +pub struct IASReport { + pub report: ReportBody, + #[serde(rename = "reportsig")] + pub report_sig: Binary, +} + +#[cw_serde] +pub struct ReportBody { + pub id: String, + pub timestamp: String, + pub version: u64, + #[serde(rename = "epidPseudonym")] + pub epid_pseudonym: Binary, + #[serde(rename = "advisoryURL")] + pub advisory_url: String, + #[serde(rename = "advisoryIDs")] + pub advisory_ids: Vec, + #[serde(rename = "isvEnclaveQuoteStatus")] + pub isv_enclave_quote_status: String, + #[serde(rename = "platformInfoBlob")] + pub platform_info_blob: String, + #[serde(rename = "isvEnclaveQuoteBody")] + pub isv_enclave_quote_body: IsvEnclaveQuoteBody, +} + +#[cw_serde] +#[serde(transparent)] +pub struct IsvEnclaveQuoteBody(Binary); + +impl IsvEnclaveQuoteBody { + pub fn mrenclave(&self) -> [u8; 32] { + Self::array_chunk(self.0.as_slice(), 112) + } + + pub fn user_data(&self) -> [u8; 64] { + Self::array_chunk(self.0.as_slice(), 368) + } + + fn array_chunk(quote_body: &[u8], offset: usize) -> [u8; N] { + assert!(offset + N <= quote_body.len()); + quote_body[offset..offset + N] + .try_into() + .expect("array length mismatch") + } +} diff --git a/bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx/epid/verifier.rs b/bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx/epid/verifier.rs new file mode 100644 index 0000000..7234836 --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-tee-ra/src/intel_sgx/epid/verifier.rs @@ -0,0 +1,172 @@ +use cosmwasm_std::ensure_eq; +use num_bigint::BigUint; +use sha2::{Digest, Sha256}; + +use crate::intel_sgx::{ + epid::{types::IASReport, Error as EpidError, INTEL_ROOT_EXPONENT, INTEL_ROOT_MODULUS}, + Error, +}; + +/// Given an RSA signature and the signer's exponent + modulus we recover the digest that was signed by the signature. +pub fn recover_signature_digest(signature: &[u8], exponent: &[u8], modulus: &[u8]) -> Vec { + let sig_as_bignum_be = BigUint::from_bytes_be(signature); + let intel_modulus_be = BigUint::from_bytes_be(modulus); + let intel_exponent_be = BigUint::from_bytes_be(exponent); + + let digest_be = sig_as_bignum_be.modpow(&intel_exponent_be, &intel_modulus_be); + + // last 32 bytes contain the digest + let digest_bytes = digest_be.to_bytes_be(); + let n = digest_bytes.len(); + digest_bytes[n - 32..n].to_vec() +} + +pub fn verify( + ias_report: IASReport, + mrenclave: impl AsRef<[u8]>, + user_data: impl AsRef<[u8]>, +) -> Result<(), Error> { + // Extract the payload from the quote body + let user_data_in_quote = ias_report.report.isv_enclave_quote_body.user_data(); + + // check user_report_data + ensure_eq!( + user_data_in_quote, + user_data.as_ref(), + Error::UserDataMismatch + ); + + // Extract the mrenclave from the quote body + let mrenclave_in_quote = ias_report.report.isv_enclave_quote_body.mrenclave(); + + // check mrenclave + ensure_eq!( + mrenclave_in_quote, + mrenclave.as_ref(), + Error::MrEnclaveMismatch + ); + + // Recover the RSA signature's digest + let recovered_digest = recover_signature_digest( + ias_report.report_sig.as_slice(), + INTEL_ROOT_EXPONENT, + INTEL_ROOT_MODULUS, + ); + // Convert the recovered digest into a byte slice + let recovered_digest = recovered_digest.as_slice(); + + // Convert the ias report as a json string, removing all the backslashes to escape stuff + let ias_report_asjson = serde_json::to_string(&ias_report.report) + .expect("infallible serializer for IASReport") + .replace('\\', ""); + // Convert the ias report without the back slashes to bytes + let ias_report_asbytes = ias_report_asjson.as_bytes(); + // We are going to calculate our own digest of the ias report + let mut hasher = Sha256::default(); + // Update the hasher contents with that of the ias report json + hasher.update(ias_report_asbytes); + // finalize the sha256 hasher so that it produces a byte slice of the digest, should be 32 bytes + let ias_report_digest = &hasher.finalize()[..]; + + // ensure that the recovered digest from the signature matches the digest of the report + ensure_eq!( + ias_report_digest, + recovered_digest, + EpidError::RecoveredDigestMismatch + ); + + Ok(()) +} + +#[cfg(test)] +mod tests { + use cosmwasm_schema::cw_serde; + use cosmwasm_std::HexBinary; + + use super::*; + + #[cw_serde] + pub enum QueryMsg { + /// Verify an attestation + VerifyEpidAttestation { + // The report that is generated by an enclave + report: IASReport, + // The MRENCLAVE of this enclave (as hex string) + mrenclave: HexBinary, + // User data - whose commitment is in the `user_report_data` (as hex string) + user_data: HexBinary, + }, + } + + fn parse_attestation(query_verifier: &str) -> (IASReport, HexBinary, HexBinary) { + let query_verifier: QueryMsg = + serde_json::from_str(query_verifier).expect("deserialize query"); + match query_verifier { + QueryMsg::VerifyEpidAttestation { + report, + mrenclave, + user_data, + } => (report, mrenclave, user_data), + } + } + + fn verify_attestation(query_verifier: &str) { + let (report, mrenclave, user_data) = parse_attestation(query_verifier); + verify(report, mrenclave, user_data).expect("RA verification failure"); + } + + #[test] + fn test_verifier_ok() { + verify_attestation( + r#"{ + "verify_epid_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=="}, + "mrenclave": "e3c2f2a5b840d89e069acaffcadb6510ef866a73d3a9ee57100ed5f8646ee4bb", + "user_data": "9113b0be77ed5d0d68680ec77206b8d587ed40679b71321ccdd5405e4d54a6820000000000000000000000000000000000000000000000000000000000000000" + } + }"#, + ) + } + + #[test] + #[should_panic] + fn test_verifier_bad_mrenclave() { + verify_attestation( + r#"{ + "verify_epid_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=="}, + "mrenclave": "f3c2f2a5b840d89e069acaffcadb6510ef866a73d3a9ee57100ed5f8646ee4bb", + "user_data": "9113b0be77ed5d0d68680ec77206b8d587ed40679b71321ccdd5405e4d54a6820000000000000000000000000000000000000000000000000000000000000000" + } + }"#, + ) + } + + #[test] + #[should_panic] + fn test_verifier_bad_user_data() { + verify_attestation( + r#"{ + "verify_epid_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=="}, + "mrenclave": "e3c2f2a5b840d89e069acaffcadb6510ef866a73d3a9ee57100ed5f8646ee4bb", + "user_data": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + } + }"#, + ) + } + + #[test] + #[should_panic] + fn test_verifier_bad_platform_info_blob() { + verify_attestation( + r#"{ + "verify_epid_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":"YaY4SPvkfR4P2E8A5huutCeS+vY/ir+xq6disalNfNtAcUyOIOqTPVXhAZgY1M5B47Hjj1oYWf2qC2w+dnj7VcZjzO9oR0pJYdA+A7jaVrNzH2eXA79yICkuU8WE/x58I0j5vjXLoHXahaKlpZkMeTphqBY8u+FTVSdP3cWPho4viPapTfQRuEWmYq4KIq2zSr6wLg3Pz+yQ+G3e9BASVkLYxdYGTDFH1pMmfas9SEI7V4I+j8DaXmL8bucSRakmcQdmDMPGiA7mvIhSAlprzCrdxM7CHeUC6MPLN1fmFFcc9kyO/ved69j/651MWC83GgxSJ15L80U+DQzmrSW8xg=="}, + "mrenclave": "e3c2f2a5b840d89e069acaffcadb6510ef866a73d3a9ee57100ed5f8646ee4bb", + "user_data": "9113b0be77ed5d0d68680ec77206b8d587ed40679b71321ccdd5405e4d54a6820000000000000000000000000000000000000000000000000000000000000000" + } + }"#, + ) + } +} diff --git a/bisenzone-cw-mvp/packages/quartz-tee-ra/src/lib.rs b/bisenzone-cw-mvp/packages/quartz-tee-ra/src/lib.rs new file mode 100644 index 0000000..75fac91 --- /dev/null +++ b/bisenzone-cw-mvp/packages/quartz-tee-ra/src/lib.rs @@ -0,0 +1,23 @@ +#![warn( + clippy::checked_conversions, + clippy::panic, + clippy::panic_in_result_fn, + clippy::unwrap_used, + rust_2018_idioms, + unused_lifetimes +)] +#![deny( + trivial_casts, + trivial_numeric_casts, + unused_import_braces, + unused_qualifications, + warnings +)] +#![forbid(unsafe_code)] + +pub mod intel_sgx; + +pub use intel_sgx::{ + epid::{types::IASReport, verifier::verify as verify_epid_attestation}, + Error, +}; diff --git a/bisenzone-cw-mvp/scripts/build-contract.sh b/bisenzone-cw-mvp/scripts/build-contract.sh new file mode 100755 index 0000000..eec2412 --- /dev/null +++ b/bisenzone-cw-mvp/scripts/build-contract.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +# Build and optimize the contract to output a WASM binary that can be deployed to a CosmWasm chain. + +set -euo pipefail + +if ! [ -f "Cargo.toml" ]; then + echo "❌ Error: Cannot find 'Cargo.toml' in current directory. Make sure this command is run from the contract's source directory" + exit 1 +fi + +echo "👷 Building and optimizing the contract..." +echo "===========================================" + +RUSTFLAGS='-C link-arg=-s' cargo wasm + +docker run --rm -v "$(pwd)":/code \ + --mount type=volume,source="$(basename "$(pwd)")_cache",target=/code/target \ + --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ + cosmwasm/rust-optimizer:0.15.0 + +echo "" +echo "🎉 Contract build and optimization completed successfully!" diff --git a/bisenzone-cw-mvp/scripts/deploy-contract.sh b/bisenzone-cw-mvp/scripts/deploy-contract.sh new file mode 100755 index 0000000..c720f20 --- /dev/null +++ b/bisenzone-cw-mvp/scripts/deploy-contract.sh @@ -0,0 +1,73 @@ +#!/bin/bash + +# Deploy the specified contract's `WASM_BIN` to the chain specified by `CHAIN_ID` using the `USER_ADDR` account. + +set -eo pipefail + +usage() { + echo "Usage: $0 WASM_BIN [COUNT]" + echo "Example: $0 artifacts/cofi_karma_game.wasm" + exit 1 +} + +if [ -z "$1" ]; then + echo "❌ Error: Missing WASM_BIN parameter. Please check if all parameters were specified." + usage +fi + +if [ "$#" -gt 9 ]; then + echo "❌ Error: Incorrect number of parameters." + usage +fi + +USER_ADDR=${USER_ADDR:-$(wasmd keys show -a admin)} +WASM_BIN="$1" +CHAIN_ID=${CHAIN_ID:-testing} +NODE_URL=${NODE_URL:-127.0.0.1:26657} +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" + +CMD="wasmd --node http://$NODE_URL" + +echo "🚀 Deploying WASM contract '${WASM_BIN}' on chain '${CHAIN_ID}' using account '${USER_ADDR}'..." +echo "====================================================================" + +RES=$($CMD tx wasm store "$WASM_BIN" --from "$USER_ADDR" $TXFLAG -y --output json) +TX_HASH=$(echo $RES | jq -r '.["txhash"]') + +while ! $CMD query tx $TX_HASH &> /dev/null; do + echo "... 🕐 waiting for contract to deploy" + sleep 1 +done + +RES=$($CMD query tx "$TX_HASH" --output json) +CODE_ID=$(echo $RES | jq -r '.logs[0].events[1].attributes[1].value') + +echo "" +echo "🚀 Instantiating contract with the following parameters:" +echo "--------------------------------------------------------" +echo "Label: ${LABEL}" +echo "--------------------------------------------------------" + +RES=$($CMD tx wasm instantiate "$CODE_ID" "$INSTANTIATE_MSG" --from "$USER_ADDR" --label $LABEL $TXFLAG -y --no-admin --output json) +TX_HASH=$(echo $RES | jq -r '.["txhash"]') + + +echo "" +while ! $CMD query tx $TX_HASH &> /dev/null; do + echo "... 🕐 waiting for contract to be queryable" + sleep 1 +done + +RES=$($CMD query wasm list-contract-by-code "$CODE_ID" --output json) +CONTRACT=$(echo $RES | jq -r '.contracts[0]') + +echo "🚀 Successfully deployed and instantiated contract!" +echo "🔗 Chain ID: ${CHAIN_ID}" +echo "🆔 Code ID: ${CODE_ID}" +echo "📌 Contract Address: ${CONTRACT}" +echo "🔑 Contract Key: ${KEY}" +echo "🔖 Contract Label: ${LABEL}" diff --git a/bisenzone-cw-mvp/scripts/init-node.sh b/bisenzone-cw-mvp/scripts/init-node.sh new file mode 100755 index 0000000..445b2fd --- /dev/null +++ b/bisenzone-cw-mvp/scripts/init-node.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +# Initialize a wasmd node that can host the MVP CosmWasm smart contract. +# Also creates a validator account and adds default genesis accounts with sufficient tokens for testing (stake and fees) + +set -euo pipefail + +ADMIN=${ADMIN:-$(wasmd keys show -a admin)} +ALICE=${ALICE:-$(wasmd keys show -a alice)} +BOB=${BOB:-$(wasmd keys show -a bob)} +CHARLIE=${CHARLIE:-$(wasmd keys show -a charlie)} + +echo "Remove old docker volume (if it exists)..." +docker volume rm -f wasmd_data + + +echo "" +echo "Setup wasmd (with validator and default genesis accounts)..." +docker run --rm -it \ + --mount type=volume,source=wasmd_data,target=/root \ + --name wasmd \ + cosmwasm/wasmd:v0.44.0 \ + /bin/sh -c "sed -i 's/1000000000/12000000000000/g' /opt/setup_wasmd.sh; + /opt/setup_wasmd.sh "$ADMIN" "$ALICE" "$BOB" "$CHARLIE";" \ + + diff --git a/bisenzone-cw-mvp/scripts/keygen.sh b/bisenzone-cw-mvp/scripts/keygen.sh new file mode 100755 index 0000000..00a7789 --- /dev/null +++ b/bisenzone-cw-mvp/scripts/keygen.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +# Generate keys for testing. + +set -euo pipefail + +wasmd keys add admin +wasmd keys add alice +wasmd keys add bob +wasmd keys add charlie diff --git a/bisenzone-cw-mvp/scripts/run-node.sh b/bisenzone-cw-mvp/scripts/run-node.sh new file mode 100755 index 0000000..61c62bf --- /dev/null +++ b/bisenzone-cw-mvp/scripts/run-node.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +# Run a previously initialized wasmd node. + +set -uo pipefail + +docker volume inspect wasmd_data >/dev/null 2>&1 +RESULT=$? +if [ $RESULT -eq 1 ]; then + echo "wasmd isn't initialized - run 'init-node.sh' first" + exit 1 +fi + +echo "Starting wasmd node" + +docker run --rm -it -p 26657:26657 -p 26656:26656 -p 1317:1317 -p 9090:9090 \ + --mount type=volume,source=wasmd_data,target=/root \ + --name wasmd \ + cosmwasm/wasmd:v0.44.0 \ + /bin/sh -c "sed -i 's/enabled-unsafe-cors = false/enabled-unsafe-cors = true/g' /root/.wasmd/config/app.toml; + sed -i 's/enable = false/enable = true/g' /root/.wasmd/config/app.toml; + sed -i 's/rpc-max-body-bytes = 1000000$/rpc-max-body-bytes = 1000000000/g' /root/.wasmd/config/app.toml; + sed -i 's/cors_allowed_origins = \[\]/cors_allowed_origins = \[\"*\"\]/g' /root/.wasmd/config/config.toml; + sed -i 's/max_body_bytes = 1000000$/max_body_bytes = 1000000000/g' /root/.wasmd/config/config.toml; + sed -i 's/max_tx_bytes = 1048576$/max_tx_bytes = 104857600/g' /root/.wasmd/config/config.toml; + /opt/run_wasmd.sh"