enclaves: light client check (#69)

Co-authored-by: dave <davidkajpust@informal.systems>
This commit is contained in:
Shoaib Ahmed 2024-07-24 21:30:36 +02:00 committed by GitHub
parent e1b13a0e06
commit 2a4e7f5ec5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 186 additions and 97 deletions

31
Cargo.lock generated
View file

@ -864,6 +864,26 @@ dependencies = [
"zeroize", "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]] [[package]]
name = "cw-proof" name = "cw-proof"
version = "0.1.0" version = "0.1.0"
@ -1839,6 +1859,15 @@ dependencies = [
"either", "either",
] ]
[[package]]
name = "itertools"
version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
dependencies = [
"either",
]
[[package]] [[package]]
name = "itoa" name = "itoa"
version = "1.0.11" version = "1.0.11"
@ -2563,6 +2592,8 @@ dependencies = [
"color-eyre", "color-eyre",
"cosmrs", "cosmrs",
"cosmwasm-std", "cosmwasm-std",
"cw-multi-test",
"cw-proof",
"cw-tee-mtcs", "cw-tee-mtcs",
"cycles-sync", "cycles-sync",
"ecies", "ecies",

View file

@ -28,12 +28,17 @@ tendermint.workspace = true
tendermint-light-client.workspace = true tendermint-light-client.workspace = true
# quartz # quartz
cw-proof.workspace = true
cw-tee-mtcs.workspace = true cw-tee-mtcs.workspace = true
cycles-sync.workspace = true cycles-sync.workspace = true
mtcs.workspace = true mtcs.workspace = true
quartz-cw.workspace = true quartz-cw.workspace = true
quartz-proto.workspace = true
quartz-enclave.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] [build-dependencies]
tonic-build.workspace = true tonic-build.workspace = true

View file

@ -4,7 +4,6 @@
clippy::checked_conversions, clippy::checked_conversions,
clippy::panic, clippy::panic,
clippy::panic_in_result_fn, clippy::panic_in_result_fn,
missing_docs,
trivial_casts, trivial_casts,
trivial_numeric_casts, trivial_numeric_casts,
rust_2018_idioms, rust_2018_idioms,
@ -65,11 +64,11 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
Server::builder() Server::builder()
.add_service(CoreServer::new(CoreService::new( .add_service(CoreServer::new(CoreService::new(
config, config.clone(),
sk.clone(), sk.clone(),
attestor.clone(), attestor.clone(),
))) )))
.add_service(MtcsServer::new(MtcsService::new(sk, attestor))) .add_service(MtcsServer::new(MtcsService::new(config, sk, attestor)))
.serve(args.rpc_addr) .serve(args.rpc_addr)
.await?; .await?;

View file

@ -17,8 +17,8 @@ use mtcs::{
algo::mcmf::primal_dual::PrimalDual, impls::complex_id::ComplexIdMtcs, algo::mcmf::primal_dual::PrimalDual, impls::complex_id::ComplexIdMtcs,
obligation::SimpleObligation, prelude::DefaultMtcs, setoff::SimpleSetoff, Mtcs, obligation::SimpleObligation, prelude::DefaultMtcs, setoff::SimpleSetoff, Mtcs,
}; };
use quartz_cw::msg::execute::attested::RawAttested; use quartz_cw::{msg::execute::attested::RawAttested, state::Config};
use quartz_enclave::attestor::Attestor; use quartz_enclave::{attestor::Attestor, server::ProofOfPublication};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tonic::{Request, Response, Result as TonicResult, Status}; use tonic::{Request, Response, Result as TonicResult, Status};
@ -28,6 +28,7 @@ pub type RawCipherText = HexBinary;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct MtcsService<A> { pub struct MtcsService<A> {
config: Config,
sk: Arc<Mutex<Option<SigningKey>>>, sk: Arc<Mutex<Option<SigningKey>>>,
attestor: A, attestor: A,
} }
@ -42,8 +43,12 @@ impl<A> MtcsService<A>
where where
A: Attestor, A: Attestor,
{ {
pub fn new(sk: Arc<Mutex<Option<SigningKey>>>, attestor: A) -> Self { pub fn new(config: Config, sk: Arc<Mutex<Option<SigningKey>>>, attestor: A) -> Self {
Self { sk, attestor } Self {
config,
sk,
attestor,
}
} }
} }
@ -56,28 +61,21 @@ where
&self, &self,
request: Request<RunClearingRequest>, request: Request<RunClearingRequest>,
) -> TonicResult<Response<RunClearingResponse>> { ) -> TonicResult<Response<RunClearingResponse>> {
// Pass in JSON of Requests vector and the STATE let message: ProofOfPublication<RunClearingMessage> = {
// 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 = request.into_inner().message; let message = request.into_inner().message;
serde_json::from_str(&message).map_err(|e| Status::invalid_argument(e.to_string()))? 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 = message.intents;
let (digests, ciphertexts): (Vec<_>, Vec<_>) = digests_ciphertexts.into_iter().unzip(); let (digests, ciphertexts): (Vec<_>, Vec<_>) = digests_ciphertexts.into_iter().unzip();

View file

@ -1,6 +1,5 @@
use std::collections::BTreeMap; use std::collections::BTreeMap;
use anyhow;
use cosmwasm_std::{Addr, HexBinary, Uint128}; use cosmwasm_std::{Addr, HexBinary, Uint128};
use ecies::encrypt; use ecies::encrypt;
use k256::ecdsa::VerifyingKey; use k256::ecdsa::VerifyingKey;
@ -42,7 +41,7 @@ fn main() {
let msg = ClearTextTransferRequestMsg { let msg = ClearTextTransferRequestMsg {
sender: Addr::unchecked("alice"), sender: Addr::unchecked("alice"),
receiver: Addr::unchecked("bob"), receiver: Addr::unchecked("bob"),
amount: Uint128::from(100 as u32), amount: Uint128::from(100_u32),
}; };
let decoded: Vec<u8> = let decoded: Vec<u8> =

View file

@ -65,11 +65,13 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
Server::builder() Server::builder()
.add_service(CoreServer::new(CoreService::new( .add_service(CoreServer::new(CoreService::new(
config, config.clone(),
sk.clone(), sk.clone(),
attestor.clone(), attestor.clone(),
))) )))
.add_service(TransfersServer::new(TransfersService::new(sk, attestor))) .add_service(TransfersServer::new(TransfersService::new(
config, sk, attestor,
)))
.serve(args.rpc_addr) .serve(args.rpc_addr)
.await?; .await?;

View file

@ -8,9 +8,9 @@ use ecies::{decrypt, encrypt};
use k256::ecdsa::{SigningKey, VerifyingKey}; use k256::ecdsa::{SigningKey, VerifyingKey};
use quartz_cw::{ use quartz_cw::{
msg::execute::attested::{HasUserData, RawAttested}, 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 serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256}; use sha2::{Digest, Sha256};
use tonic::{Request, Response, Result as TonicResult, Status}; use tonic::{Request, Response, Result as TonicResult, Status};
@ -27,6 +27,7 @@ pub type RawCipherText = HexBinary;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct TransfersService<A> { pub struct TransfersService<A> {
config: Config,
sk: Arc<Mutex<Option<SigningKey>>>, sk: Arc<Mutex<Option<SigningKey>>>,
attestor: A, attestor: A,
} }
@ -91,8 +92,12 @@ impl<A> TransfersService<A>
where where
A: Attestor, A: Attestor,
{ {
pub fn new(sk: Arc<Mutex<Option<SigningKey>>>, attestor: A) -> Self { pub fn new(config: Config, sk: Arc<Mutex<Option<SigningKey>>>, attestor: A) -> Self {
Self { sk, attestor } Self {
config,
sk,
attestor,
}
} }
} }
@ -102,14 +107,22 @@ where
A: Attestor + Send + Sync + 'static, A: Attestor + Send + Sync + 'static,
{ {
async fn run(&self, request: Request<UpdateRequest>) -> TonicResult<Response<UpdateResponse>> { async fn run(&self, request: Request<UpdateRequest>) -> TonicResult<Response<UpdateResponse>> {
// Request contains a serialized json string
// Serialize request into struct containing State and the Requests vec // Serialize request into struct containing State and the Requests vec
let message: UpdateRequestMessage = { let message: ProofOfPublication<UpdateRequestMessage> = {
let message = request.into_inner().message; let message = request.into_inner().message;
serde_json::from_str(&message).map_err(|e| Status::invalid_argument(e.to_string()))? 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 // Decrypt and deserialize the state
let mut state = { let mut state = {
if message.state.len() == 1 && message.state[0] == 0 { if message.state.len() == 1 && message.state[0] == 0 {

View file

@ -39,19 +39,57 @@ REPORT_SIG_FILE="/tmp/${USER}_datareportsig"
if echo "$CLEAN_MSG" | grep -q 'wasm-transfer'; then if echo "$CLEAN_MSG" | grep -q 'wasm-transfer'; then
echo "---------------------------------------------------------" echo "---------------------------------------------------------"
echo "... received wasm-transfer event!" 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" echo "... fetching requests"
REQUESTS=$($CMD query wasm contract-state raw $CONTRACT $(printf '%s' "requests" | \ REQUESTS=$($CMD query wasm contract-state raw $CONTRACT $(printf '%s' "requests" | \
hexdump -ve '/1 "%02X"') -o json | jq -r .data | base64 -d) hexdump -ve '/1 "%02X"') -o json | jq -r .data | base64 -d)
STATE=$($CMD query wasm contract-state raw $CONTRACT $(printf '%s' "state" | \ STATE=$($CMD query wasm contract-state raw $CONTRACT $(printf '%s' "state" | \
hexdump -ve '/1 "%02X"') -o json | jq -r .data | base64 -d) 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 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 cd $ROOT/cycles-quartz/apps/transfers/enclave
echo "... executing transfer" echo "... executing transfer"
export ATTESTED_MSG=$(grpcurl -plaintext -import-path ./proto/ -proto transfers.proto \ 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) jq .message | jq -R 'fromjson | fromjson' | jq -c)
QUOTE=$(echo "$ATTESTED_MSG" | jq -c '.attestation') QUOTE=$(echo "$ATTESTED_MSG" | jq -c '.attestation')
MSG=$(echo "$ATTESTED_MSG" | jq -c '.msg') MSG=$(echo "$ATTESTED_MSG" | jq -c '.msg')

View file

@ -3,12 +3,9 @@ use std::{
time::Duration, time::Duration,
}; };
use cw_proof::{ use cw_proof::proof::{
error::ProofError,
proof::{
cw::{CwProof, RawCwProof}, cw::{CwProof, RawCwProof},
Proof, Proof,
},
}; };
use k256::ecdsa::SigningKey; use k256::ecdsa::SigningKey;
use quartz_cw::{ use quartz_cw::{
@ -16,7 +13,7 @@ use quartz_cw::{
execute::{session_create::SessionCreate, session_set_pub_key::SessionSetPubKey}, execute::{session_create::SessionCreate, session_set_pub_key::SessionSetPubKey},
instantiate::CoreInstantiate, instantiate::CoreInstantiate,
}, },
state::{Config, Nonce, Session}, state::{Config, LightClientOpts, Nonce, Session},
}; };
use quartz_proto::quartz::{ use quartz_proto::quartz::{
core_server::Core, InstantiateRequest as RawInstantiateRequest, core_server::Core, InstantiateRequest as RawInstantiateRequest,
@ -102,58 +99,15 @@ where
request: Request<RawSessionSetPubKeyRequest>, request: Request<RawSessionSetPubKeyRequest>,
) -> TonicResult<Response<RawSessionSetPubKeyResponse>> { ) -> TonicResult<Response<RawSessionSetPubKeyResponse>> {
// FIXME(hu55a1n1) - disallow calling more than once // FIXME(hu55a1n1) - disallow calling more than once
let proof: ProofOfPublication = serde_json::from_str(&request.into_inner().message) let proof: ProofOfPublication<Option<()>> =
serde_json::from_str(&request.into_inner().message)
.map_err(|e| Status::invalid_argument(e.to_string()))?; .map_err(|e| Status::invalid_argument(e.to_string()))?;
let config_trust_threshold = self.config.light_client_opts().trust_threshold(); let (value, _msg) = proof
let trust_threshold = .verify(self.config.light_client_opts())
TrustThreshold::new(config_trust_threshold.0, config_trust_threshold.1).unwrap(); .map_err(Status::failed_precondition)?;
let config_trusting_period = self.config.light_client_opts().trusting_period(); let session: Session = serde_json::from_slice(&value).unwrap();
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 nonce = self.nonce.lock().unwrap(); let nonce = self.nonce.lock().unwrap();
if session.nonce() != *nonce { if session.nonce() != *nonce {
@ -177,7 +131,57 @@ where
} }
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub struct ProofOfPublication { pub struct ProofOfPublication<M> {
light_client_proof: Vec<LightBlock>, light_client_proof: Vec<LightBlock>,
merkle_proof: RawCwProof, merkle_proof: RawCwProof,
msg: M,
}
impl<M> ProofOfPublication<M> {
pub fn verify(self, light_client_opts: &LightClientOpts) -> Result<(Vec<u8>, 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))
}
} }