From 1d1f5b24cc7753d27f8ba0c6b5318587ad901b0e Mon Sep 17 00:00:00 2001 From: Akshay Date: Mon, 4 Nov 2024 17:23:39 +0000 Subject: [PATCH] new options kalypso-cli --- kalypso-cli/Cargo.toml | 4 + kalypso-cli/src/common_deps.rs | 86 +++++++++ kalypso-cli/src/config.json | 58 ++++++ kalypso-cli/src/operations/compute_pcrs.rs | 169 ++++++++++++++++++ .../src/operations/create_marketplace.rs | 83 +++++++++ kalypso-cli/src/operations/mod.rs | 5 + kalypso-cli/src/prompts.rs | 88 +++++++++ 7 files changed, 493 insertions(+) create mode 100644 kalypso-cli/src/operations/compute_pcrs.rs create mode 100644 kalypso-cli/src/operations/create_marketplace.rs diff --git a/kalypso-cli/Cargo.toml b/kalypso-cli/Cargo.toml index 8cd7f4b..85740f7 100644 --- a/kalypso-cli/Cargo.toml +++ b/kalypso-cli/Cargo.toml @@ -5,14 +5,18 @@ edition = "2021" [dependencies] async-trait = "0.1.64" +bytes = "1.0" bindings = { path = "../bindings", package = "foundry-contracts" } dotenv = "0.15.0" dialoguer = "0.10.3" env_logger = "0.11" +futures = "0.3" ethers = { version = "2", features = ["rustls"] } hex = "0.4.3" log = "0.4" +reqwest = { version = "0.11", features = ["stream"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" +sha3 = "0.10" tokio = { version = "1.15.0", features = ["full"] } url = "2.3.1" \ No newline at end of file diff --git a/kalypso-cli/src/common_deps.rs b/kalypso-cli/src/common_deps.rs index 9386428..04888a7 100644 --- a/kalypso-cli/src/common_deps.rs +++ b/kalypso-cli/src/common_deps.rs @@ -367,3 +367,89 @@ impl CommonDeps { }) } } + +pub struct MarketCreateInfo { + pub private_key_signer: LocalWallet, + pub proof_marketplace: bindings::proof_marketplace::ProofMarketplace< + SignerMiddleware, LocalWallet>, + >, + pub prover_pcrs: Vec, + pub ivs_pcrs: Vec, + pub payment_token: bindings::ierc20::IERC20, LocalWallet>>, + pub verifier_wrapper: Address, +} + +impl CommonDeps { + pub fn market_create_info( + config: &std::collections::HashMap, + ) -> Result { + get_config_ref!(config, "private_key", private_key); + get_config_ref!(config, "rpc_url", rpc_url); + get_config_ref!(config, "proof_marketplace", proof_marketplace_address); + get_config_ref!(config, "chain_id", chain_id); + get_config_ref!(config, "prover_image_id", prover_pcrs); + get_config_ref!(config, "verification_image_id", ivs_pcrs); + get_config_ref!(config, "verifier_wrapper", verifier_wrapper); + get_config_ref!(config, "payment_token", payment_token); + + let (proof_marketplace, private_key_signer) = get_proof_marketplace_instance( + private_key, + chain_id, + proof_marketplace_address, + rpc_url, + )?; + + let prover_pcrs = { + let trimmed_key = if prover_pcrs.starts_with("0x") || prover_pcrs.starts_with("0X") { + &prover_pcrs[2..] + } else { + prover_pcrs + }; + hex::decode(trimmed_key).map_err(|e| format!("Invalid Prover PCRs: {}", e))? + }; + + let ivs_pcrs = { + let trimmed_key = if ivs_pcrs.starts_with("0x") || ivs_pcrs.starts_with("0X") { + &ivs_pcrs[2..] + } else { + ivs_pcrs + }; + hex::decode(trimmed_key).map_err(|e| format!("Invalid IVS PCRs: {}", e))? + }; + + let (payment_token, _) = + get_token_instance(private_key, chain_id, &payment_token, rpc_url)?; + + let verifier_wrapper = verifier_wrapper + .parse::
() + .map_err(|e| format!("Invalid Verifier Wrapper Address: {}", e))?; + + Ok(MarketCreateInfo { + private_key_signer, + proof_marketplace, + prover_pcrs, + ivs_pcrs, + verifier_wrapper, + payment_token, + }) + } +} + +pub struct ComputePcrsInfo { + pub attestation_utility: String, + pub attestation_verifier: String, +} + +impl CommonDeps { + pub fn compute_pcrs_info( + config: &std::collections::HashMap, + ) -> Result { + get_config_ref!(config, "attestation_server_url", attestation_server_url); + get_config_ref!(config, "attestion_verifier_url", attestion_verifier_url); + + Ok(ComputePcrsInfo { + attestation_utility: attestation_server_url.to_string(), + attestation_verifier: attestion_verifier_url.to_string(), + }) + } +} diff --git a/kalypso-cli/src/config.json b/kalypso-cli/src/config.json index 67c93a3..8c17457 100644 --- a/kalypso-cli/src/config.json +++ b/kalypso-cli/src/config.json @@ -83,6 +83,42 @@ "prompt": "Enter the staking token address", "secret": false, "env_var": "STAKING_TOKEN" + }, + { + "field": "payment_token", + "prompt": "Enter the payment token address", + "secret": false, + "env_var": "PAYMENT_TOKEN" + }, + { + "field": "prover_image_id", + "prompt": "Enter the prover image pcrs", + "secret": false, + "env_var": "PROVER_IMAGE_PCRS" + }, + { + "field": "verification_image_id", + "prompt": "Enter the input_verification image pcrs", + "secret": false, + "env_var": "IVS_IMAGE_PCRS" + }, + { + "field": "verifier_wrapper", + "prompt": "Enter Proof Verifier Contract Address (refer to Kalypso Standard Wrapper Contract)", + "secret": false, + "env_var": "VERIFIER_WRAPPER_ADDRESS" + }, + { + "field": "attestation_server_url", + "prompt": "Enter Attestation Server URL", + "secret": false, + "env_var": "ATTESTATION_UTILITY_URL" + }, + { + "field": "attestion_verifier_url", + "prompt": "Enter Attestation VERIFIER URL", + "secret": false, + "env_var": "ATTESTATION_VERIFIER_URL" } ], "operations": [ @@ -147,6 +183,28 @@ "staking_address", "stake" ] + }, + { + "name": "Create Marketplace", + "description": "Create a new market place in kalypso", + "required_prompts": [ + "private_key", + "rpc_url", + "chain_id", + "proof_marketplace", + "prover_image_id", + "verification_image_id", + "verifier_wrapper", + "payment_token" + ] + }, + { + "name": "Compute PCRs", + "description": "Compute PCRs of a given enclave from it's attestation server", + "required_prompts": [ + "attestation_server_url", + "attestion_verifier_url" + ] } ] } diff --git a/kalypso-cli/src/operations/compute_pcrs.rs b/kalypso-cli/src/operations/compute_pcrs.rs new file mode 100644 index 0000000..eeb816b --- /dev/null +++ b/kalypso-cli/src/operations/compute_pcrs.rs @@ -0,0 +1,169 @@ +use crate::common_deps::CommonDeps; +use crate::operations::Operation; +use async_trait::async_trait; +use ethers::types::U256; +use serde::Deserialize; +use std::collections::HashMap; + +pub struct ComputePcrs; + +#[async_trait] +impl Operation for ComputePcrs { + async fn execute(&self, config: HashMap) -> Result<(), String> { + let compute_pcrs_info = CommonDeps::compute_pcrs_info(&config)?; + let attestation_stream = build_attestation(&compute_pcrs_info.attestation_utility, false) + .await + .map_err(|_| "Failed Building Attestations.".to_string())?; + + let attestation_data: Vec = attestation_stream + .fold(Vec::new(), |mut acc, item| async { + match item { + Ok(bytes) => { + acc.extend_from_slice(&bytes); + acc + } + Err(e) => { + println!("Error while receiving data: {}", e); + acc + } + } + }) + .await; + + println!("Attestation Data Length: {}", attestation_data.len()); + + let image_id = get_image_id( + &compute_pcrs_info.attestation_verifier, + attestation_data, + false, + ) + .await + .map_err(|_| "Failed Computing Image ID".to_string())?; + + print!("Image ID: \n{}", image_id); + + println!("\n\nSave Image For Further"); + + Ok(()) + } +} + +use bytes::Bytes; +use futures::{Stream, StreamExt}; +use hex::decode; +use reqwest::Client; +use std::error::Error; + +pub async fn build_attestation( + base_url: &str, + print_logs: bool, +) -> Result>, Box> { + let attestation_end_point = utility_url(base_url, "/attestation/raw"); + + if print_logs { + println!("build attestation {}", attestation_end_point); + } + + let client = Client::new(); + let response = client.get(&attestation_end_point).send().await?; + + // Check if the response status is successful (2xx) + if !response.status().is_success() { + println!("status code: {}", response.status()); + return Err("failed building the attestation".into()); + } + + // Get the response body as a stream of bytes + let stream = response.bytes_stream(); + + Ok(stream) +} + +fn utility_url(base_url: &str, path: &str) -> String { + format!("{}{}", base_url, path) +} + +#[derive(Debug, Deserialize)] +struct AttestationVerifierResponse { + #[allow(unused)] + secp256k1_public: String, + #[allow(unused)] + signature: String, + #[allow(unused)] + pcr0: String, + #[allow(unused)] + pcr1: String, + #[allow(unused)] + pcr2: String, + #[allow(unused)] + timestamp: u64, +} + +use ethers::abi::{encode, Token}; + +// Function to get attestation by sending attestation_data to the verifier +pub async fn get_image_id( + base_url: &str, + attestation_data: Vec, + print_logs: bool, +) -> Result> { + // Construct the verify endpoint URL + let verify_endpoint = utility_url(base_url, "/verify/raw"); + + if print_logs { + println!("Sending attestation data to {}", verify_endpoint); + } + + let client = Client::new(); + let response = client + .post(&verify_endpoint) + .header("Content-Type", "application/octet-stream") + .body(attestation_data) + .send() + .await?; + + if !response.status().is_success() { + if print_logs { + println!( + "Attestation verifier responded with status: {}", + response.status() + ); + } + return Err("Failed to verify attestation".into()); + } + + let verifier_response: AttestationVerifierResponse = response.json().await?; + + if print_logs { + println!("Fetched attestation successfully"); + println!("Verifier response: {:?}", verifier_response); + } + + // Extract and process secp256k1_public + let ecies_pubkey = format!("0x{}", verifier_response.secp256k1_public); + if ecies_pubkey.len() != 130 { + return Err("secp pub key length incorrect".into()); + } + + // Decode hex strings to bytes + let signature_bytes = decode(&verifier_response.signature.trim_start_matches("0x"))?; + let pcr0_bytes = decode(&verifier_response.pcr0.trim_start_matches("0x"))?; + let pcr1_bytes = decode(&verifier_response.pcr1.trim_start_matches("0x"))?; + let pcr2_bytes = decode(&verifier_response.pcr2.trim_start_matches("0x"))?; + + let _timestamp_u256 = U256::from(verifier_response.timestamp); + let _signature_vec = signature_bytes; + let _ecies_pubkey_vec = decode(&verifier_response.secp256k1_public.trim_start_matches("0x"))?; + + let pcr0_vec = pcr0_bytes; + let pcr1_vec = pcr1_bytes; + let pcr2_vec = pcr2_bytes; + + let encoded = encode(&[ + Token::Bytes(pcr0_vec), + Token::Bytes(pcr1_vec), + Token::Bytes(pcr2_vec), + ]); + + Ok(format!("0x{}", hex::encode(encoded))) +} diff --git a/kalypso-cli/src/operations/create_marketplace.rs b/kalypso-cli/src/operations/create_marketplace.rs new file mode 100644 index 0000000..0c2ca9b --- /dev/null +++ b/kalypso-cli/src/operations/create_marketplace.rs @@ -0,0 +1,83 @@ +use crate::{common_deps::CommonDeps, operations::Operation}; +use async_trait::async_trait; +use ethers::{signers::Signer, types::U256}; +use std::collections::HashMap; + +pub struct CreateMarketplace; + +#[async_trait] +impl Operation for CreateMarketplace { + async fn execute(&self, config: HashMap) -> Result<(), String> { + let market_create_info = CommonDeps::market_create_info(&config)?; + + let token_balance = market_create_info + .payment_token + .balance_of(market_create_info.private_key_signer.address()) + .call() + .await + .map_err(|_| "Failed making call to payment token contract.".to_string())?; + + let market_creation_cost = { + let result = market_create_info + .proof_marketplace + .market_creation_cost() + .call() + .await + .map_err(|_| "Failed making call to proof marketplace contract".to_string())?; + + result + 1 + }; + + if market_creation_cost > token_balance { + return Err("Insufficient payment token available".to_string()); + } + + let token_allowance = market_create_info + .payment_token + .allowance( + market_create_info.private_key_signer.address(), + market_create_info.proof_marketplace.address(), + ) + .call() + .await + .map_err(|_| "Failed making call to payment token contract.".to_string())?; + + if token_allowance < market_creation_cost { + let token_approval_transaction = CommonDeps::send_and_confirm( + market_create_info + .payment_token + .approve( + market_create_info.proof_marketplace.address(), + market_creation_cost, + ) + .send(), + ) + .await + .map_err(|e| format!("Token approval failed: {}", e))?; + + println!("Token Approval: {}", token_approval_transaction); + } + + let market_creation_transaction = CommonDeps::send_and_confirm( + market_create_info + .proof_marketplace + .create_marketplace( + vec![12, 23].into(), + market_create_info.verifier_wrapper, + U256::from(10).pow(U256::from(18)), + market_create_info.prover_pcrs.into(), + market_create_info.ivs_pcrs.into(), + ) + .send(), + ) + .await + .map_err(|e| format!("Market Creation Transaction failed: {}", e))?; + + println!( + "Market Creation Transaction: {}", + market_creation_transaction + ); + + Ok(()) + } +} diff --git a/kalypso-cli/src/operations/mod.rs b/kalypso-cli/src/operations/mod.rs index e9f48db..e8f74bc 100644 --- a/kalypso-cli/src/operations/mod.rs +++ b/kalypso-cli/src/operations/mod.rs @@ -1,9 +1,12 @@ // src/operations/mod.rs pub mod complete_registration; +pub mod compute_pcrs; +pub mod create_marketplace; pub mod join_marketplace; pub mod leave_or_request_leave_marketplace; pub mod stake; + // ... Add other operation modules here use async_trait::async_trait; @@ -46,6 +49,8 @@ pub fn get_operation(name: &str) -> Option> { "Leave Marketplace" => Some(Box::new( leave_or_request_leave_marketplace::LeaveMarketPlace, )), + "Create Marketplace" => Some(Box::new(create_marketplace::CreateMarketplace)), + "Compute PCRs" => Some(Box::new(compute_pcrs::ComputePcrs)), _ => unimplemented!(), } } diff --git a/kalypso-cli/src/prompts.rs b/kalypso-cli/src/prompts.rs index d734c5a..f6e6fde 100644 --- a/kalypso-cli/src/prompts.rs +++ b/kalypso-cli/src/prompts.rs @@ -43,6 +43,13 @@ impl<'a> Prompter<'a> { validators.insert("staking_address".into(), validate_eth_address); validators.insert("staking_token".to_string(), validate_eth_address); + validators.insert("payment_token".to_string(), validate_eth_address); + + validators.insert("prover_image_id".to_string(), validate_image_id); + validators.insert("verification_image_id".to_string(), validate_image_id); + validators.insert("verifier_wrapper".to_string(), validate_eth_address); + validators.insert("attestation_server_url".to_string(), validate_rpc_url); + validators.insert("attestion_verifier_url".to_string(), validate_rpc_url); validators.insert( "confirmation".to_string(), @@ -182,4 +189,85 @@ fn validate_eth_address(key: &str) -> Result<(), String> { )), } } + +fn validate_image_id(key: &str) -> Result<(), String> { + let trimmed_key = if key.starts_with("0x") || key.starts_with("0X") { + &key[2..] + } else { + key + }; + if trimmed_key.len() % 2 != 0 { + return Err("Hex string has an invalid length".to_string()); + } + + let pcrs = hex::decode(trimmed_key) + .map_err(|_| "Invalid Image ID: Hex decoding failed".to_string())?; + let _image_id = + get_image_id_from_pcrs(&pcrs).map_err(|e| format!("Error computing image ID: {}", e))?; + + Ok(()) +} + +// --- try to find some lib functions for these below rather than writting your own ------------ // +use ethers::abi::{decode, ParamType, Token}; + +fn get_image_id_from_pcrs(pcrs: &[u8]) -> Result<[u8; 32], Box> { + // Define the expected types: three dynamic bytes arrays + let types = vec![ParamType::Bytes, ParamType::Bytes, ParamType::Bytes]; + + // Decode the input `pcrs` according to the specified types + let tokens = decode(&types, pcrs)?; + + if tokens.len() != 3 { + return Err("Expected three PCRs after decoding".into()); + } + + // Extract each PCR as a byte vector + let pcr0 = match &tokens[0] { + Token::Bytes(b) => b, + _ => return Err("PCR0 is not of type bytes".into()), + }; + + let pcr1 = match &tokens[1] { + Token::Bytes(b) => b, + _ => return Err("PCR1 is not of type bytes".into()), + }; + + let pcr2 = match &tokens[2] { + Token::Bytes(b) => b, + _ => return Err("PCR2 is not of type bytes".into()), + }; + + // Compute the image ID using the inner function + let image_id = get_image_id_from_pcrs_inner(pcr0, pcr1, pcr2)?; + + Ok(image_id) +} + +use sha3::{Digest, Keccak256}; + +fn get_image_id_from_pcrs_inner( + pcr0: &[u8], + pcr1: &[u8], + pcr2: &[u8], +) -> Result<[u8; 32], Box> { + // Initialize the Keccak-256 hasher + let mut hasher = Keccak256::new(); + + // Update the hasher with each PCR + hasher.update(pcr0); + hasher.update(pcr1); + hasher.update(pcr2); + + // Finalize the hash computation + let result = hasher.finalize(); + + // Convert the hash result into a fixed-size array + let image_id: [u8; 32] = result + .as_slice() + .try_into() + .map_err(|_| "Hash output is not 32 bytes")?; + + Ok(image_id) +} // Add more validators as needed