enclaves: light client check (#69)
Co-authored-by: dave <davidkajpust@informal.systems>
This commit is contained in:
parent
e1b13a0e06
commit
2a4e7f5ec5
9 changed files with 186 additions and 97 deletions
31
Cargo.lock
generated
31
Cargo.lock
generated
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<dyn std::error::Error>> {
|
|||
|
||||
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?;
|
||||
|
||||
|
|
|
@ -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<A> {
|
||||
config: Config,
|
||||
sk: Arc<Mutex<Option<SigningKey>>>,
|
||||
attestor: A,
|
||||
}
|
||||
|
@ -42,8 +43,12 @@ impl<A> MtcsService<A>
|
|||
where
|
||||
A: Attestor,
|
||||
{
|
||||
pub fn new(sk: Arc<Mutex<Option<SigningKey>>>, attestor: A) -> Self {
|
||||
Self { sk, attestor }
|
||||
pub fn new(config: Config, sk: Arc<Mutex<Option<SigningKey>>>, attestor: A) -> Self {
|
||||
Self {
|
||||
config,
|
||||
sk,
|
||||
attestor,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -56,28 +61,21 @@ where
|
|||
&self,
|
||||
request: Request<RunClearingRequest>,
|
||||
) -> TonicResult<Response<RunClearingResponse>> {
|
||||
// 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<RunClearingMessage> = {
|
||||
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();
|
||||
|
||||
|
|
|
@ -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<u8> =
|
||||
|
|
|
@ -65,11 +65,13 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
|
||||
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?;
|
||||
|
||||
|
|
|
@ -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<A> {
|
||||
config: Config,
|
||||
sk: Arc<Mutex<Option<SigningKey>>>,
|
||||
attestor: A,
|
||||
}
|
||||
|
@ -91,8 +92,12 @@ impl<A> TransfersService<A>
|
|||
where
|
||||
A: Attestor,
|
||||
{
|
||||
pub fn new(sk: Arc<Mutex<Option<SigningKey>>>, attestor: A) -> Self {
|
||||
Self { sk, attestor }
|
||||
pub fn new(config: Config, sk: Arc<Mutex<Option<SigningKey>>>, attestor: A) -> Self {
|
||||
Self {
|
||||
config,
|
||||
sk,
|
||||
attestor,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -102,14 +107,22 @@ where
|
|||
A: Attestor + Send + Sync + 'static,
|
||||
{
|
||||
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
|
||||
let message: UpdateRequestMessage = {
|
||||
let message: ProofOfPublication<UpdateRequestMessage> = {
|
||||
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 {
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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<RawSessionSetPubKeyRequest>,
|
||||
) -> TonicResult<Response<RawSessionSetPubKeyResponse>> {
|
||||
// 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<Option<()>> =
|
||||
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<M> {
|
||||
light_client_proof: Vec<LightBlock>,
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue