diff --git a/Cargo.lock b/Cargo.lock index 2f877a7..4e6deda 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -864,6 +864,26 @@ dependencies = [ "zeroize", ] +[[package]] +name = "cw-multi-test" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0034bfb4c06dfc8b50f0b1a06c3fc0f2312a1bae568a97db65930de071288ba" +dependencies = [ + "anyhow", + "bech32", + "cosmwasm-std", + "cw-storage-plus", + "cw-utils", + "derivative", + "itertools 0.13.0", + "prost", + "schemars", + "serde", + "sha2 0.10.8", + "thiserror", +] + [[package]] name = "cw-proof" version = "0.1.0" @@ -1839,6 +1859,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.11" @@ -2563,6 +2592,8 @@ dependencies = [ "color-eyre", "cosmrs", "cosmwasm-std", + "cw-multi-test", + "cw-proof", "cw-tee-mtcs", "cycles-sync", "ecies", diff --git a/apps/mtcs/enclave/Cargo.toml b/apps/mtcs/enclave/Cargo.toml index da6a274..87b3d6c 100644 --- a/apps/mtcs/enclave/Cargo.toml +++ b/apps/mtcs/enclave/Cargo.toml @@ -28,12 +28,17 @@ tendermint.workspace = true tendermint-light-client.workspace = true # quartz +cw-proof.workspace = true cw-tee-mtcs.workspace = true cycles-sync.workspace = true mtcs.workspace = true quartz-cw.workspace = true -quartz-proto.workspace = true quartz-enclave.workspace = true +quartz-proto.workspace = true + +[dev-dependencies] +cw-multi-test = "2.0.0" +serde_json = "1.0.113" [build-dependencies] tonic-build.workspace = true diff --git a/apps/mtcs/enclave/src/main.rs b/apps/mtcs/enclave/src/main.rs index db18cf3..049e508 100644 --- a/apps/mtcs/enclave/src/main.rs +++ b/apps/mtcs/enclave/src/main.rs @@ -4,7 +4,6 @@ clippy::checked_conversions, clippy::panic, clippy::panic_in_result_fn, - missing_docs, trivial_casts, trivial_numeric_casts, rust_2018_idioms, @@ -65,11 +64,11 @@ async fn main() -> Result<(), Box> { Server::builder() .add_service(CoreServer::new(CoreService::new( - config, + config.clone(), sk.clone(), attestor.clone(), ))) - .add_service(MtcsServer::new(MtcsService::new(sk, attestor))) + .add_service(MtcsServer::new(MtcsService::new(config, sk, attestor))) .serve(args.rpc_addr) .await?; diff --git a/apps/mtcs/enclave/src/mtcs_server.rs b/apps/mtcs/enclave/src/mtcs_server.rs index 0674210..c0db86d 100644 --- a/apps/mtcs/enclave/src/mtcs_server.rs +++ b/apps/mtcs/enclave/src/mtcs_server.rs @@ -17,8 +17,8 @@ use mtcs::{ algo::mcmf::primal_dual::PrimalDual, impls::complex_id::ComplexIdMtcs, obligation::SimpleObligation, prelude::DefaultMtcs, setoff::SimpleSetoff, Mtcs, }; -use quartz_cw::msg::execute::attested::RawAttested; -use quartz_enclave::attestor::Attestor; +use quartz_cw::{msg::execute::attested::RawAttested, state::Config}; +use quartz_enclave::{attestor::Attestor, server::ProofOfPublication}; use serde::{Deserialize, Serialize}; use tonic::{Request, Response, Result as TonicResult, Status}; @@ -28,6 +28,7 @@ pub type RawCipherText = HexBinary; #[derive(Clone, Debug)] pub struct MtcsService { + config: Config, sk: Arc>>, attestor: A, } @@ -42,8 +43,12 @@ impl MtcsService where A: Attestor, { - pub fn new(sk: Arc>>, attestor: A) -> Self { - Self { sk, attestor } + pub fn new(config: Config, sk: Arc>>, attestor: A) -> Self { + Self { + config, + sk, + attestor, + } } } @@ -56,28 +61,21 @@ where &self, request: Request, ) -> TonicResult> { - // Pass in JSON of Requests vector and the STATE - - // Serialize into Requests enum - // Loop through, decrypt the ciphertexts - - // Read the state blob from chain - - // Decrypt and deserialize - - // Loop through requests and apply onto state - - // Encrypt state - - // Create withdraw requests - - // Send to chain - - let message: RunClearingMessage = { + let message: ProofOfPublication = { let message = request.into_inner().message; serde_json::from_str(&message).map_err(|e| Status::invalid_argument(e.to_string()))? }; + let (proof_value, message) = message + .verify(self.config.light_client_opts()) + .map_err(Status::failed_precondition)?; + + let proof_value_matches_msg = + serde_json::to_string(&message.intents).is_ok_and(|s| s.as_bytes() == proof_value); + if !proof_value_matches_msg { + return Err(Status::failed_precondition("proof verification")); + } + let digests_ciphertexts = message.intents; let (digests, ciphertexts): (Vec<_>, Vec<_>) = digests_ciphertexts.into_iter().unzip(); diff --git a/apps/transfers/enclave/bin/encrypt.rs b/apps/transfers/enclave/bin/encrypt.rs index ff10e36..2ed4484 100644 --- a/apps/transfers/enclave/bin/encrypt.rs +++ b/apps/transfers/enclave/bin/encrypt.rs @@ -1,6 +1,5 @@ use std::collections::BTreeMap; -use anyhow; use cosmwasm_std::{Addr, HexBinary, Uint128}; use ecies::encrypt; use k256::ecdsa::VerifyingKey; @@ -42,7 +41,7 @@ fn main() { let msg = ClearTextTransferRequestMsg { sender: Addr::unchecked("alice"), receiver: Addr::unchecked("bob"), - amount: Uint128::from(100 as u32), + amount: Uint128::from(100_u32), }; let decoded: Vec = diff --git a/apps/transfers/enclave/src/main.rs b/apps/transfers/enclave/src/main.rs index 335101e..a8faf8f 100644 --- a/apps/transfers/enclave/src/main.rs +++ b/apps/transfers/enclave/src/main.rs @@ -65,11 +65,13 @@ async fn main() -> Result<(), Box> { Server::builder() .add_service(CoreServer::new(CoreService::new( - config, + config.clone(), sk.clone(), attestor.clone(), ))) - .add_service(TransfersServer::new(TransfersService::new(sk, attestor))) + .add_service(TransfersServer::new(TransfersService::new( + config, sk, attestor, + ))) .serve(args.rpc_addr) .await?; diff --git a/apps/transfers/enclave/src/transfers_server.rs b/apps/transfers/enclave/src/transfers_server.rs index fcf6f37..1ba13e2 100644 --- a/apps/transfers/enclave/src/transfers_server.rs +++ b/apps/transfers/enclave/src/transfers_server.rs @@ -8,9 +8,9 @@ use ecies::{decrypt, encrypt}; use k256::ecdsa::{SigningKey, VerifyingKey}; use quartz_cw::{ msg::execute::attested::{HasUserData, RawAttested}, - state::UserData, + state::{Config, UserData}, }; -use quartz_enclave::attestor::Attestor; +use quartz_enclave::{attestor::Attestor, server::ProofOfPublication}; use serde::{Deserialize, Serialize}; use sha2::{Digest, Sha256}; use tonic::{Request, Response, Result as TonicResult, Status}; @@ -27,6 +27,7 @@ pub type RawCipherText = HexBinary; #[derive(Clone, Debug)] pub struct TransfersService { + config: Config, sk: Arc>>, attestor: A, } @@ -91,8 +92,12 @@ impl TransfersService where A: Attestor, { - pub fn new(sk: Arc>>, attestor: A) -> Self { - Self { sk, attestor } + pub fn new(config: Config, sk: Arc>>, attestor: A) -> Self { + Self { + config, + sk, + attestor, + } } } @@ -102,14 +107,22 @@ where A: Attestor + Send + Sync + 'static, { async fn run(&self, request: Request) -> TonicResult> { - // Request contains a serialized json string - // Serialize request into struct containing State and the Requests vec - let message: UpdateRequestMessage = { + let message: ProofOfPublication = { let message = request.into_inner().message; serde_json::from_str(&message).map_err(|e| Status::invalid_argument(e.to_string()))? }; + let (proof_value, message) = message + .verify(self.config.light_client_opts()) + .map_err(Status::failed_precondition)?; + + let proof_value_matches_msg = + serde_json::to_string(&message.requests).is_ok_and(|s| s.as_bytes() == proof_value); + if !proof_value_matches_msg { + return Err(Status::failed_precondition("proof verification")); + } + // Decrypt and deserialize the state let mut state = { if message.state.len() == 1 && message.state[0] == 0 { diff --git a/apps/transfers/scripts/listen.sh b/apps/transfers/scripts/listen.sh index aa934ff..279c275 100755 --- a/apps/transfers/scripts/listen.sh +++ b/apps/transfers/scripts/listen.sh @@ -39,19 +39,57 @@ REPORT_SIG_FILE="/tmp/${USER}_datareportsig" if echo "$CLEAN_MSG" | grep -q 'wasm-transfer'; then echo "---------------------------------------------------------" echo "... received wasm-transfer event!" + + current_height=$($CMD status | jq -r .SyncInfo.latest_block_height) + next_height=$((current_height + 1)) + + while [ "$($CMD status 2>&1 | jq -r .SyncInfo.latest_block_height)" -lt "$next_height" ]; do + echo "waiting for next block" + sleep 1 + done + echo "... fetching requests" REQUESTS=$($CMD query wasm contract-state raw $CONTRACT $(printf '%s' "requests" | \ hexdump -ve '/1 "%02X"') -o json | jq -r .data | base64 -d) STATE=$($CMD query wasm contract-state raw $CONTRACT $(printf '%s' "state" | \ hexdump -ve '/1 "%02X"') -o json | jq -r .data | base64 -d) + + cd "$ROOT/cycles-quartz/apps/transfers" + export TRUSTED_HASH=$(cat trusted.hash) + export TRUSTED_HEIGHT=$(cat trusted.height) + + cd $ROOT/cycles-quartz/utils/tm-prover + export PROOF_FILE="light-client-proof.json" + if [ -f "$PROOF_FILE" ]; then + rm "$PROOF_FILE" + echo "removed old $PROOF_FILE" + fi + + echo "trusted hash $TRUSTED_HASH" + echo "trusted hash $TRUSTED_HEIGHT" + echo "contract $CONTRACT" + + # run prover to get light client proof + cargo run -- --chain-id testing \ + --primary "http://$NODE_URL" \ + --witnesses "http://$NODE_URL" \ + --trusted-height $TRUSTED_HEIGHT \ + --trusted-hash $TRUSTED_HASH \ + --contract-address $CONTRACT \ + --storage-key "requests" \ + --trace-file $PROOF_FILE + + export POP=$(cat $PROOF_FILE) + export ENCLAVE_REQUEST=$(jq -nc --argjson requests "$REQUESTS" --argjson state $STATE '$ARGS.named') - export REQUEST_MSG=$(jq -nc --arg message "$ENCLAVE_REQUEST" '$ARGS.named') + export REQUEST_MSG=$(jq --argjson msg "$ENCLAVE_REQUEST" '. + {msg: $msg}' <<< "$POP") + export PROTO_MSG=$(jq -nc --arg message "$REQUEST_MSG" '$ARGS.named') cd $ROOT/cycles-quartz/apps/transfers/enclave echo "... executing transfer" export ATTESTED_MSG=$(grpcurl -plaintext -import-path ./proto/ -proto transfers.proto \ - -d "$REQUEST_MSG" "127.0.0.1:$QUARTZ_PORT" transfers.Settlement/Run | \ + -d "$PROTO_MSG" "127.0.0.1:$QUARTZ_PORT" transfers.Settlement/Run | \ jq .message | jq -R 'fromjson | fromjson' | jq -c) QUOTE=$(echo "$ATTESTED_MSG" | jq -c '.attestation') MSG=$(echo "$ATTESTED_MSG" | jq -c '.msg') diff --git a/core/quartz/src/server.rs b/core/quartz/src/server.rs index 45e2bf5..f51d48b 100644 --- a/core/quartz/src/server.rs +++ b/core/quartz/src/server.rs @@ -3,12 +3,9 @@ use std::{ time::Duration, }; -use cw_proof::{ - error::ProofError, - proof::{ - cw::{CwProof, RawCwProof}, - Proof, - }, +use cw_proof::proof::{ + cw::{CwProof, RawCwProof}, + Proof, }; use k256::ecdsa::SigningKey; use quartz_cw::{ @@ -16,7 +13,7 @@ use quartz_cw::{ execute::{session_create::SessionCreate, session_set_pub_key::SessionSetPubKey}, instantiate::CoreInstantiate, }, - state::{Config, Nonce, Session}, + state::{Config, LightClientOpts, Nonce, Session}, }; use quartz_proto::quartz::{ core_server::Core, InstantiateRequest as RawInstantiateRequest, @@ -102,58 +99,15 @@ where request: Request, ) -> TonicResult> { // FIXME(hu55a1n1) - disallow calling more than once - let proof: ProofOfPublication = serde_json::from_str(&request.into_inner().message) - .map_err(|e| Status::invalid_argument(e.to_string()))?; + let proof: ProofOfPublication> = + serde_json::from_str(&request.into_inner().message) + .map_err(|e| Status::invalid_argument(e.to_string()))?; - let config_trust_threshold = self.config.light_client_opts().trust_threshold(); - let trust_threshold = - TrustThreshold::new(config_trust_threshold.0, config_trust_threshold.1).unwrap(); + let (value, _msg) = proof + .verify(self.config.light_client_opts()) + .map_err(Status::failed_precondition)?; - let config_trusting_period = self.config.light_client_opts().trusting_period(); - let trusting_period = Duration::from_secs(config_trusting_period); - - let config_clock_drift = self.config.light_client_opts().max_clock_drift(); - let clock_drift = Duration::from_secs(config_clock_drift); - let options = Options { - trust_threshold, - trusting_period, - clock_drift, - }; - - let target_height = proof.light_client_proof.last().unwrap().height(); - - let primary_block = make_provider( - self.config.light_client_opts().chain_id(), - self.config - .light_client_opts() - .trusted_height() - .try_into() - .unwrap(), - self.config - .light_client_opts() - .trusted_hash() - .to_vec() - .try_into() - .unwrap(), - proof.light_client_proof, - options, - ) - .and_then(|mut primary| primary.verify_to_height(target_height)) - .map_err(|e| Status::internal(e.to_string()))?; - - let proof = CwProof::from(proof.merkle_proof); - proof - .verify( - primary_block - .signed_header - .header - .app_hash - .as_bytes() - .to_vec(), - ) - .map_err(|e: ProofError| Status::internal(e.to_string()))?; - - let session: Session = serde_json::from_slice(&proof.value).unwrap(); + let session: Session = serde_json::from_slice(&value).unwrap(); let nonce = self.nonce.lock().unwrap(); if session.nonce() != *nonce { @@ -177,7 +131,57 @@ where } #[derive(Clone, Debug, Serialize, Deserialize)] -pub struct ProofOfPublication { +pub struct ProofOfPublication { light_client_proof: Vec, merkle_proof: RawCwProof, + msg: M, +} + +impl ProofOfPublication { + pub fn verify(self, light_client_opts: &LightClientOpts) -> Result<(Vec, M), String> { + let config_trust_threshold = light_client_opts.trust_threshold(); + let trust_threshold = + TrustThreshold::new(config_trust_threshold.0, config_trust_threshold.1).unwrap(); + + let config_trusting_period = light_client_opts.trusting_period(); + let trusting_period = Duration::from_secs(config_trusting_period); + + let config_clock_drift = light_client_opts.max_clock_drift(); + let clock_drift = Duration::from_secs(config_clock_drift); + let options = Options { + trust_threshold, + trusting_period, + clock_drift, + }; + + let target_height = self.light_client_proof.last().unwrap().height(); + + let primary_block = make_provider( + light_client_opts.chain_id(), + light_client_opts.trusted_height().try_into().unwrap(), + light_client_opts + .trusted_hash() + .to_vec() + .try_into() + .unwrap(), + self.light_client_proof, + options, + ) + .and_then(|mut primary| primary.verify_to_height(target_height)) + .map_err(|e| e.to_string())?; + + let proof = CwProof::from(self.merkle_proof); + proof + .verify( + primary_block + .signed_header + .header + .app_hash + .as_bytes() + .to_vec(), + ) + .map_err(|e| e.to_string())?; + + Ok((proof.value, self.msg)) + } }